Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • graphpolaris/frontend-v2
  • rijkheere/frontend-v-2-reordering-paoh
2 results
Show changes
Commits on Source (9)
Showing
with 213 additions and 22 deletions
......@@ -25,7 +25,7 @@ COPY apps /app/apps
COPY libs /app/libs
COPY --from=install /app /app
# Fixes: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
ENV NODE_OPTIONS="--max-old-space-size=4096"
ENV NODE_OPTIONS="--max-old-space-size=8192"
RUN pnpm run build
......
......@@ -7,4 +7,4 @@ BACKEND_USER=:3000
GRAPHPOLARIS_VERSION=dev
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
SENTRY_URL= GP_AUTH_URL=
\ No newline at end of file
......
......@@ -7,4 +7,6 @@ VITE_BACKEND_QUERY=:3003
VITE_BACKEND_SCHEMA=:3002
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
SENTRY_URL=
GP_AUTH_URL=
\ No newline at end of file
......@@ -6,4 +6,6 @@ SKIP_LOGIN=
BACKEND_USER=
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
SENTRY_URL=
GP_AUTH_URL=
\ No newline at end of file
......@@ -10,6 +10,8 @@ VITE_BACKEND_SCHEMA=/schema
SENTRY_ENABLED=false
SENTRY_URL=
GP_AUTH_URL=https://auth.staging.graphpolaris.com/
WIP_TABLEVIS=false
WIP_NODELINKVIS=false
WIP_RAWJSONVIS=false
......
......@@ -10,7 +10,7 @@ export default {
title: 'App',
decorators: [
// using the real store here
(story) => (
(story: any) => (
<Provider store={store}>
<Router>
<Routes>
......
......@@ -106,7 +106,9 @@ export const Navbar = () => {
/>
)}
<DropdownItem value="Settings" onClick={() => {}} />
<DropdownItem value="Log out" onClick={() => {}} />
<DropdownItem value="Log out" onClick={() => {
location.replace(`${import.meta.env['GP_AUTH_URL']}/flows/-/default/invalidation/`)
}} />
</>
) : (
<>
......
......@@ -78,3 +78,19 @@ body {
cursor: se-resize;
}
*/
.variable-node {
display: inline-block;
user-select: none;
padding: 3px 7px;
margin: 0px 7px;
font-size: 11px;
border-radius: 5px;
background: #e0e7f5;
color: #6e788e;
border: 1px solid #6e788e80;
}
p > span:first-child > .variable-node {
margin-left: 0px;
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ export default {
title: 'Visual charts/Charts/Axis',
component: AxisComponent,
decorators: [
(Story) => (
(Story: any) => (
<div className="w-full h-full flex flex-row justify-center flex-grow">
<svg className="border border-secondary-300" width="300" height="200">
<Story />
......
......@@ -114,7 +114,7 @@ export function DropdownItem({ value, disabled, className, onClick, submenu, sel
<li
ref={itemRef}
style={{ border: 0 }}
className={`cursor-pointer divide-y origin-top-right rounded-sm truncate block text-sm px-2 py-1 hover:bg-secondary-200 ${className && className} ${selected ? 'bg-secondary-400 text-white hover:text-black' : ''}`}
className={`cursor-pointer divide-y origin-top-right rounded-sm truncate flex items-center justify-between block text-sm px-2 pe-1 py-1 hover:bg-secondary-200 ${className && className} ${selected ? 'bg-secondary-400 text-white hover:text-black' : ''}`}
onClick={() => {
!disabled && onClick && onClick(value);
}}
......@@ -122,7 +122,9 @@ export function DropdownItem({ value, disabled, className, onClick, submenu, sel
onMouseLeave={() => setIsSubmenuOpen(false)}
>
{value}
{submenu != null ? <Icon component='icon-[ic--baseline-arrow-right] ms-2' size={14} /> : ''}
{submenu && isSubmenuOpen && <DropdownSubmenuContainer ref={submenuRef}>{submenu}</DropdownSubmenuContainer>}
{children}
</li>
);
}
......@@ -133,7 +135,7 @@ type DropdownSubmenuContainerProps = {
export const DropdownSubmenuContainer = React.forwardRef<HTMLDivElement, DropdownSubmenuContainerProps>(({ children }, ref) => {
return (
<div ref={ref} className="z-10 absolute bg-light rounded shadow w-44 left-[-80%] -translate-y-7">
<div ref={ref} className="absolute bg-light p-1 rounded max-h-60 overflow-auto focus:outline-none shadow-sm border left-[97%]" style={{transform: 'translate(0px, calc(50% - 19px))'}}>
<ul className="text-sm text-secondary-700">{children}</ul>
</div>
);
......
......@@ -34,6 +34,7 @@ type TextProps = {
className?: string;
validate?: (value: any) => boolean;
onChange?: (value: string) => void;
onClick?: (e: Event) => void;
};
type NumberProps = {
......@@ -210,6 +211,7 @@ export const TextInput = ({
validate,
disabled = false,
onChange,
onClick = () => {},
inline = false,
tooltip,
info,
......@@ -249,6 +251,7 @@ export const TextInput = ({
onChange(e.target.value);
}
}}
onClick={onClick}
required={required}
disabled={disabled}
/>
......
......@@ -141,7 +141,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, React.HTMLProps<HT
return (
<FloatingPortal>
<FloatingOverlay className="grid place-items-center z-50" lockScroll style={{ backgroundColor: 'rgba(0, 0, 0, 0.4)' }}>
<FloatingOverlay className="grid place-items-center" lockScroll style={{ backgroundColor: 'rgba(0, 0, 0, 0.4)' }}>
<FloatingFocusManager context={context.floatingContext}>
<div
ref={ref}
......
......@@ -99,7 +99,7 @@ type ContextType =
})
| null;
const PopoverContext = React.createContext<ContextType>(null);
export const PopoverContext = React.createContext<ContextType>(null);
export const usePopoverContext = () => {
const context = React.useContext(PopoverContext);
......
import React, { useContext, useState } from 'react';
import { pillWidth, pillHeight, pillXPadding, pillInnerMargin, topLineHeight, pillBorderWidth, pillDropdownPadding } from './pill.const';
import { pillWidth, pillHeight, pillXPadding, pillInnerMargin, topLineHeight, pillBorderWidth, pillAttributesPadding } from './pill.const';
import { Position } from 'reactflow';
import { PillHandle } from './PillHandle';
import { PillContext } from './PillContext';
......@@ -124,8 +124,8 @@ export const Pill = React.memo((props: PillI) => {
style={{
top: pillHeight - pillInnerMargin + (corner === 'diamond' ? pillHeight / 2 - pillBorderWidth : 0),
width: pillWidth - (corner === 'diamond' ? pillWidth * 0.05 : 0),
paddingLeft: pillDropdownPadding + (corner === 'diamond' ? pillWidth * 0.05 : 0),
paddingRight: pillDropdownPadding,
paddingLeft: pillAttributesPadding + (corner === 'diamond' ? pillWidth * 0.05 : 0),
paddingRight: pillAttributesPadding,
}}
>
{props.children}
......
export const pillHeight = 26;
export const pillInnerMargin = 2;
export const pillXPadding = 10;
export const pillDropdownPadding = 2;
export const pillAttributesPadding = 2;
export const pillWidth = 154;
export const topLineHeight = 3;
export const pillBorderWidth = 1;
......@@ -2,13 +2,13 @@ import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { EntityPillSelector } from './entityPillSelector';
const metaPillDropdown: Meta<typeof EntityPillSelector> = {
const metaPillAttributes: Meta<typeof EntityPillSelector> = {
component: EntityPillSelector,
title: 'Components/Selectors/Entity',
decorators: [(story) => <div className="flex items-center justify-center m-11 p-11">{story()}</div>],
};
export default metaPillDropdown;
export default metaPillAttributes;
type Story = StoryObj<typeof EntityPillSelector>;
......
import React from 'react';
export function Placeholder({ placeholder }: { placeholder?: string }) {
return placeholder && <div className="absolute inset-0 pointer-events-none flex items-center px-2 text-secondary-400">{placeholder}</div>;
return placeholder && <div className="absolute inset-0 pointer-events-none flex p-3 text-secondary-400">{placeholder}</div>;
}
import React from 'react';
import { useRef } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
import { EditorState } from 'lexical';
import { LexicalEditor, EditorState } from 'lexical';
import { $generateHtmlFromNodes } from "@lexical/html";
import { MyOnChangePlugin } from './plugins/StateChangePlugin';
import { ToolbarPlugin } from './plugins/ToolbarPlugin';
import { InsertVariablesPlugin } from './plugins/InsertVariablesPlugin';
import { ErrorHandler } from './ErrorHandler';
import { Placeholder } from './Placeholder';
import { VariableNode } from './VariableNode';
import { fontFamily } from 'html2canvas/dist/types/css/property-descriptors/font-family';
type TextEditorProps = {
editorState: EditorState | undefined;
......@@ -17,14 +21,21 @@ type TextEditorProps = {
};
export function TextEditor({ editorState, setEditorState, showToolbar, placeholder }: TextEditorProps) {
function onChange(editorState: EditorState) {
function onChange(editorState: EditorState, editor: LexicalEditor) {
setEditorState(editorState);
editor.read(() => {
const html = $generateHtmlFromNodes(editor)
text.current = html;
});
}
const text = useRef<string>("hai");
const initialConfig = {
namespace: 'MyEditor',
editorState: editorState,
onError: ErrorHandler,
nodes: [ VariableNode ],
};
return (
......@@ -32,12 +43,17 @@ export function TextEditor({ editorState, setEditorState, showToolbar, placehold
{showToolbar && <ToolbarPlugin />}
<div className="relative">
<RichTextPlugin
contentEditable={<ContentEditable className="border min-h-24" />}
contentEditable={<ContentEditable className="border min-h-24 p-3" />}
placeholder={<Placeholder placeholder={placeholder} />}
ErrorBoundary={LexicalErrorBoundary}
/>
</div>
<MyOnChangePlugin onChange={onChange} />
<InsertVariablesPlugin />
<br /><b>Debug:</b>
<div style={{fontFamily: 'monospace'}}>
{ text.current }
</div>
</LexicalComposer>
);
}
import type { NodeKey, LexicalEditor, DOMExportOutput } from 'lexical';
import { DecoratorNode, EditorConfig } from 'lexical';
export enum VariableType {
statistic,
visualization
};
export class VariableNode extends DecoratorNode<JSX.Element> {
__name: string;
__variableType: VariableType;
static getType(): string {
return 'variable-node';
}
static clone(node: VariableNode): VariableNode {
return new VariableNode(node.__name, node.__variableType, node.__key);
}
constructor(name: string, type: VariableType, key?: NodeKey) {
super(key);
this.__name = name;
this.__variableType = type;
}
setName(name: string) {
const self = this.getWritable();
this.__name = name;
}
getName(): string {
const self = this.getLatest();
return self.__name;
}
getTextContent(): string {
const self = this.getLatest();
return `{{ ${self.__variableType}:${self.__name} }}`;
}
// View
createDOM(config: EditorConfig): HTMLElement {
const span = document.createElement("span");
const theme = config.theme;
const className = theme.image;
if (className !== undefined) {
span.className = `${className}`;
}
return span;
}
updateDOM(): false {
return false;
}
exportDOM(editor: LexicalEditor): DOMExportOutput {
const self = this.getLatest();
return {
element: new Text(self.getTextContent())
};
}
decorate(): JSX.Element {
return (
<div className="variable-node">
{this.getName()}
</div>
);
}
}
\ No newline at end of file
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import {
$copyNode,
$getRoot,
$isParagraphNode,
$isElementNode,
$getNodeByKey,
$getSelection,
type BaseSelection,
$parseSerializedNode
} from "lexical";
import { Input } from '@graphpolaris/shared/lib/components/inputs';
import { VariableNode, VariableType } from '../VariableNode';
import { useState } from 'react';
import { useGraphQueryResult, useVisualization } from '@graphpolaris/shared/lib/data-access';
export const InsertVariablesPlugin = () => {
const [editor] = useLexicalComposerContext();
const { openVisualizationArray } = useVisualization();
const visualizationOptions = openVisualizationArray
.map(x => x.name);
const onChange = (value: string | number, type: VariableType) => {
editor.update(() => {
const selection = $getSelection() as BaseSelection;
const node = new VariableNode(String(value), type);
selection.insertNodes([node]);
// TODO: enable drag and dropping nodes
});
};
const result = useGraphQueryResult();
const nodeTypes = Object.keys(result.metaData?.nodes.types || {});
function optionsForType(type: string) {
const typeObj = result.metaData?.nodes.types[type] ?? {attributes: null};
if (!('attributes' in typeObj) || typeObj.attributes == null) {
return [];
}
return Object.entries(typeObj.attributes).map(([k,v]) => Object.keys(v).map(x => `${k}_${x}`)).flat();
}
return <>
{ nodeTypes.map((nodeType) =>
<Input
type="dropdown"
label={`${nodeType} variable`}
value=""
options={optionsForType(nodeType)}
onChange={(v) => onChange(v, VariableType.statistic)}
/>
)
}
{ (visualizationOptions.length > 0) ? <Input
type="dropdown"
label={`Visualization`}
value=""
options={visualizationOptions}
onChange={(v) => onChange(v, VariableType.visualization)}
/> : ''
}
</>;
};
\ No newline at end of file