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 217 additions and 50 deletions
apps/web/public/assets/sprite_selected.png

646 B | W: 0px | H: 0px

apps/web/public/assets/sprite_selected.png

697 B | W: 0px | H: 0px

apps/web/public/assets/sprite_selected.png
apps/web/public/assets/sprite_selected.png
apps/web/public/assets/sprite_selected.png
apps/web/public/assets/sprite_selected.png
  • 2-up
  • Swipe
  • Onion skin
apps/web/public/assets/sprite_selected_square.png

100 B | W: 0px | H: 0px

apps/web/public/assets/sprite_selected_square.png

96 B | W: 0px | H: 0px

apps/web/public/assets/sprite_selected_square.png
apps/web/public/assets/sprite_selected_square.png
apps/web/public/assets/sprite_selected_square.png
apps/web/public/assets/sprite_selected_square.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -172,7 +172,7 @@ export const SettingsForm = (props: { onClose(): void; open: 'add' | 'update'; s ...@@ -172,7 +172,7 @@ export const SettingsForm = (props: { onClose(): void; open: 'add' | 'update'; s
<Button <Button
variantType="primary" variantType="primary"
className="flex-grow" className="flex-grow"
label={connection.updating ? formTitle.slice(0, -1) + 'ing...' : formTitle} label={connection.updating ? (formTitle === 'Add' ? formTitle + 'ing...' : formTitle.slice(0, -1) + 'ing...') : formTitle}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
handleSubmit(); handleSubmit();
......
...@@ -645,7 +645,7 @@ GraphPolaris uses [Material UI](https://mui.com/material-ui/material-icons/) thr ...@@ -645,7 +645,7 @@ GraphPolaris uses [Material UI](https://mui.com/material-ui/material-icons/) thr
</div> </div>
```jsx ```jsx
import Icon from '@graphpolaris/shared/lib/components/icon'; import { Icon } from '@graphpolaris/shared/lib/components/icon';
<Icon name="ArrowBack" size={32} />; <Icon name="ArrowBack" size={32} />;
``` ```
......
import React, { ReactElement, ReactPropTypes, useMemo } from 'react'; import React, { ReactElement, ReactPropTypes, useMemo } from 'react';
import styles from './buttons.module.scss'; import styles from './buttons.module.scss';
import Icon, { Sizes } from '../icon'; import { Icon, Sizes } from '../icon';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
type ButtonProps = { type ButtonProps = {
......
import React, { useState, useEffect, useRef, ReactNode } from 'react'; import React, { useState, useEffect, useRef, ReactNode } from 'react';
import styles from './dropdowns.module.scss'; import styles from './dropdowns.module.scss';
import Icon from '../icon'; import { Icon } from '../icon';
import { ArrowDropDown } from '@mui/icons-material'; import { ArrowDropDown } from '@mui/icons-material';
import { PopoverContent, PopoverTrigger, Popover, PopoverOptions } from '../layout/Popover'; import { PopoverContent, PopoverTrigger, Popover, PopoverOptions } from '../layout/Popover';
...@@ -45,7 +45,7 @@ export function DropdownTrigger({ ...@@ -45,7 +45,7 @@ export function DropdownTrigger({
const inner = children || ( const inner = children || (
<div <div
className={`inline-flex w-full truncate justify-between items-center gap-x-1.5 ${variantClass} ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm hover:bg-secondary-50 disabled:bg-secondary-100 disabled:cursor-not-allowed disabled:text-secondary-400 pl-1 truncate${className ? ` ${className}` : ''}`} className={`inline-flex w-full truncate justify-between items-center gap-x-1.5 ${variantClass} ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm hover:bg-secondary-50 disabled:bg-secondary-100 disabled:cursor-not-allowed disabled:text-secondary-400 pl-1 truncate cursor-pointer${className ? ` ${className}` : ''}`}
> >
<span className={`text-${size}`}>{title}</span> <span className={`text-${size}`}>{title}</span>
<Icon component={<ArrowDropDown />} size={16} /> <Icon component={<ArrowDropDown />} size={16} />
...@@ -70,6 +70,7 @@ type DropdownItemContainerProps = { ...@@ -70,6 +70,7 @@ type DropdownItemContainerProps = {
}; };
export const DropdownItemContainer = React.forwardRef<HTMLDivElement, DropdownItemContainerProps>(({ children, className }, ref) => { export const DropdownItemContainer = React.forwardRef<HTMLDivElement, DropdownItemContainerProps>(({ children, className }, ref) => {
if (!children || !React.Children.count(children)) return null;
return ( return (
<PopoverContent <PopoverContent
ref={ref} ref={ref}
...@@ -91,9 +92,10 @@ type DropdownItemProps = { ...@@ -91,9 +92,10 @@ type DropdownItemProps = {
onClick?: (value: string) => void; onClick?: (value: string) => void;
submenu?: React.ReactNode; submenu?: React.ReactNode;
selected?: boolean; selected?: boolean;
children?: ReactNode;
}; };
export function DropdownItem({ value, disabled, className, onClick, submenu, selected }: DropdownItemProps) { export function DropdownItem({ value, disabled, className, onClick, submenu, selected, children }: DropdownItemProps) {
const itemRef = useRef(null); const itemRef = useRef(null);
const submenuRef = useRef(null); const submenuRef = useRef(null);
const [isSubmenuOpen, setIsSubmenuOpen] = useState(false); const [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
...@@ -109,7 +111,7 @@ export function DropdownItem({ value, disabled, className, onClick, submenu, sel ...@@ -109,7 +111,7 @@ export function DropdownItem({ value, disabled, className, onClick, submenu, sel
onMouseEnter={() => setIsSubmenuOpen(true)} onMouseEnter={() => setIsSubmenuOpen(true)}
onMouseLeave={() => setIsSubmenuOpen(false)} onMouseLeave={() => setIsSubmenuOpen(false)}
> >
{ value } {value}
{submenu && isSubmenuOpen && <DropdownSubmenuContainer ref={submenuRef}>{submenu}</DropdownSubmenuContainer>} {submenu && isSubmenuOpen && <DropdownSubmenuContainer ref={submenuRef}>{submenu}</DropdownSubmenuContainer>}
</li> </li>
); );
......
import { StoryObj, Meta } from '@storybook/react'; import { StoryObj, Meta } from '@storybook/react';
import Icon from '../icon'; import { Icon } from '../icon';
import { ArrowBack, DeleteOutline, KeyboardArrowLeft, Settings } from '@mui/icons-material'; import { ArrowBack, DeleteOutline, KeyboardArrowLeft, Settings } from '@mui/icons-material';
const Component: Meta<typeof Icon> = { const Component: Meta<typeof Icon> = {
......
...@@ -5,9 +5,10 @@ export type Sizes = 12 | 14 | 16 | 20 | 24 | 28 | 32 | 40; ...@@ -5,9 +5,10 @@ export type Sizes = 12 | 14 | 16 | 20 | 24 | 28 | 32 | 40;
export type IconProps = SVGProps<SVGSVGElement> & { export type IconProps = SVGProps<SVGSVGElement> & {
component: ReactElement<any>; component: ReactElement<any>;
size?: Sizes; size?: Sizes;
color?: string;
}; };
export const Icon: React.FC<IconProps> = ({ component, size = 24, ...props }) => { export const Icon: React.FC<IconProps> = ({ component, size = 24, color, ...props }) => {
if (!component) { if (!component) {
console.error(`No icon found`); console.error(`No icon found`);
return <div></div>; return <div></div>;
...@@ -15,5 +16,3 @@ export const Icon: React.FC<IconProps> = ({ component, size = 24, ...props }) => ...@@ -15,5 +16,3 @@ export const Icon: React.FC<IconProps> = ({ component, size = 24, ...props }) =>
return React.cloneElement(component, { style: { fontSize: size }, width: size, height: size, ...props }); return React.cloneElement(component, { style: { fontSize: size }, width: size, height: size, ...props });
}; };
export default Icon;
import { Canvas, Meta, Story } from '@storybook/blocks'; import { Canvas, Meta, Story } from '@storybook/blocks';
import * as IconStories from './icon.stories'; import * as IconStories from './icon.stories';
import Icon from '.'; import { Icon } from '.';
<Meta title="Components/Icon" component={Icon} /> <Meta title="Components/Icon" component={Icon} />
......
import React from 'react'; import React from 'react';
import Icon from '../icon'; import { Icon } from '../icon';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
import { InfoOutlined } from '@mui/icons-material'; import { InfoOutlined } from '@mui/icons-material';
......
...@@ -101,7 +101,7 @@ export const Pill = React.memo((props: PillI) => { ...@@ -101,7 +101,7 @@ export const Pill = React.memo((props: PillI) => {
></div> ></div>
)} )}
<div <div
className={'font-semibold bg-neutral-100 ' + (corner !== 'square' ? 'rounded-b-[3px]' : '')} className={'font-semibold ' + (corner !== 'square' ? 'rounded-b-[3px]' : '')}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
> >
......
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import EntityPillSelector, { EntityPillSelectorProps } from './entityPillSelector';
const metaPillDropdown: 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;
type Story = StoryObj<typeof EntityPillSelector>;
export const entity: Story = {
args: {
dropdownNodes: ['kamerleden', 'commissies'],
},
};
import React, { useRef, useState } from 'react';
import { Button } from '../buttons';
import { ArrowDropDown } from '@mui/icons-material';
import { EntityPill } from '@graphpolaris/shared/lib/components/pills/Pill';
import { DropdownContainer, DropdownItemContainer, DropdownTrigger, DropdownItem } from '../dropdowns';
export type EntityPillSelectorProps = {
selectedNode?: string;
dropdownNodes: string[];
onSelectOption: (option: string) => void;
};
export function EntityPillSelector({ dropdownNodes, onSelectOption, selectedNode }: EntityPillSelectorProps) {
const [isCollapsed, setIsCollapsed] = useState(true);
// const [initialNamePill, setInitialNamePill] = useState('Choose a node:');
const handleButtonClick = () => {
setIsCollapsed(!isCollapsed);
};
const handleOptionClick = (option: string) => {
setIsCollapsed(true);
onSelectOption(option);
};
return (
<DropdownContainer placement="bottom">
<DropdownTrigger title={selectedNode || 'Choose a node:'} size="sm">
<EntityPill
className="cursor-pointer"
title={
<div className="flex flex-row items-center justify-between pointer-events-none">
<span>{selectedNode || 'Choose a node:'}</span>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent={<ArrowDropDown />} onClick={handleButtonClick} />
</div>
}
/>
</DropdownTrigger>
<DropdownItemContainer>
{dropdownNodes
.map((node, index) => (
<DropdownItem
className="my-0 cursor-pointer"
selected={selectedNode === node}
onClick={() => handleOptionClick(node)}
key={'entity_' + index + '-' + node}
value={node}
>
<EntityPill title={node} />
</DropdownItem>
))
.filter((node) => node.props.value !== selectedNode)}
</DropdownItemContainer>
</DropdownContainer>
);
}
...@@ -3,6 +3,7 @@ import Shape from './shape'; ...@@ -3,6 +3,7 @@ import Shape from './shape';
import Size from './size'; import Size from './size';
import Axis from './axis'; import Axis from './axis';
import Opacity from './opacity'; import Opacity from './opacity';
import EntityPill from './entityPillSelector';
export const EncodingSelector = { export const EncodingSelector = {
Color: Color, Color: Color,
...@@ -10,6 +11,7 @@ export const EncodingSelector = { ...@@ -10,6 +11,7 @@ export const EncodingSelector = {
Size: Size, Size: Size,
Axis: Axis, Axis: Axis,
Opacity: Opacity, Opacity: Opacity,
EntityPill: EntityPill,
}; };
// Inspiration: https://uwdata.github.io/visualization-curriculum/altair_marks_encoding.html // Inspiration: https://uwdata.github.io/visualization-curriculum/altair_marks_encoding.html
...@@ -5,6 +5,8 @@ import { ...@@ -5,6 +5,8 @@ import {
offset, offset,
flip, flip,
shift, shift,
hide,
arrow,
useHover, useHover,
useFocus, useFocus,
useDismiss, useDismiss,
...@@ -12,6 +14,7 @@ import { ...@@ -12,6 +14,7 @@ import {
useInteractions, useInteractions,
useMergeRefs, useMergeRefs,
FloatingPortal, FloatingPortal,
FloatingArrow
} from '@floating-ui/react'; } from '@floating-ui/react';
import type { Placement } from '@floating-ui/react'; import type { Placement } from '@floating-ui/react';
import { FloatingDelayGroup } from '@floating-ui/react'; import { FloatingDelayGroup } from '@floating-ui/react';
...@@ -21,6 +24,8 @@ interface TooltipOptions { ...@@ -21,6 +24,8 @@ interface TooltipOptions {
placement?: Placement; placement?: Placement;
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
boundaryElement?: React.RefObject<HTMLElement> | null;
showArrow?: boolean;
} }
export function useTooltip({ export function useTooltip({
...@@ -28,6 +33,8 @@ export function useTooltip({ ...@@ -28,6 +33,8 @@ export function useTooltip({
placement = 'top', placement = 'top',
open: controlledOpen, open: controlledOpen,
onOpenChange: setControlledOpen, onOpenChange: setControlledOpen,
boundaryElement = null,
showArrow = false
}: TooltipOptions = {}): { }: TooltipOptions = {}): {
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
...@@ -38,8 +45,9 @@ export function useTooltip({ ...@@ -38,8 +45,9 @@ export function useTooltip({
const open = controlledOpen ?? uncontrolledOpen; const open = controlledOpen ?? uncontrolledOpen;
const setOpen = setControlledOpen ?? setUncontrolledOpen; const setOpen = setControlledOpen ?? setUncontrolledOpen;
const arrowRef = React.useRef<SVGSVGElement | null>(null);
const data = useFloating({ let config = {
placement, placement,
open, open,
onOpenChange: setOpen, onOpenChange: setOpen,
...@@ -49,11 +57,25 @@ export function useTooltip({ ...@@ -49,11 +57,25 @@ export function useTooltip({
flip({ flip({
crossAxis: placement.includes('-'), crossAxis: placement.includes('-'),
fallbackAxisSideDirection: 'start', fallbackAxisSideDirection: 'start',
padding: 5, padding: 5
}), }),
shift({ padding: 5 }), shift({ padding: 5 }),
], ],
}); }
if (boundaryElement != null) {
const boundary = boundaryElement?.current ?? undefined;
config.middleware.find(x => x.name == 'flip')!.options[0].boundary = boundary;
config.middleware.find(x => x.name == 'shift')!.options[0].boundary = boundary;
config.middleware.push(hide({ boundary }));
}
if (showArrow) {
config.middleware.push(arrow({ element: arrowRef }));
}
const data = useFloating(config);
(data.refs as any).arrow = arrowRef;
const context = data.context; const context = data.context;
...@@ -98,17 +120,48 @@ export function Tooltip({ children, ...options }: { children: React.ReactNode } ...@@ -98,17 +120,48 @@ export function Tooltip({ children, ...options }: { children: React.ReactNode }
// This can accept any props as options, e.g. `placement`, // This can accept any props as options, e.g. `placement`,
// or other positioning options. // or other positioning options.
const tooltip = useTooltip(options); const tooltip = useTooltip(options);
return <TooltipContext.Provider value={tooltip}>{children}</TooltipContext.Provider>;
return <TooltipContext.Provider value={tooltip}>
{children}
</TooltipContext.Provider>;
} }
export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean }>(function TooltipTrigger( export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean, x?: number, y?: number }>(function TooltipTrigger(
{ children, asChild = false, ...props }, { children, asChild = false, x = null, y = null, ...props },
propRef, propRef,
) { ) {
const context = useTooltipContext(); const context = useTooltipContext();
const childrenRef = (children as any).ref; const childrenRef = React.useMemo(() => {
if (children == null) {
return null;
} else {
return (children as any).ref;
}
}, [children]);
const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]); const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]);
React.useEffect(() => {
if (x && y && context.data.refs.reference.current != null) {
const element = context.data.refs.reference.current as HTMLElement;
element.style.position = 'absolute';
const {x: offsetX, y: offsetY} = element.getBoundingClientRect();
element.getBoundingClientRect = () => {
return {
width: 0,
height: 0,
x: offsetX,
y: offsetY,
top: y + offsetY,
left: x + offsetX,
right: x + offsetX,
bottom: y + offsetY,
} as DOMRect
}
context.data.update();
}
}, [x, y]);
// `asChild` allows the user to pass any element as the anchor // `asChild` allows the user to pass any element as the anchor
if (asChild && React.isValidElement(children)) { if (asChild && React.isValidElement(children)) {
return React.cloneElement( return React.cloneElement(
...@@ -149,13 +202,21 @@ export const TooltipContent = React.forwardRef< ...@@ -149,13 +202,21 @@ export const TooltipContent = React.forwardRef<
<FloatingPortal> <FloatingPortal>
<div <div
ref={ref} ref={ref}
className={`z-50 max-w-64 overflow-hidden rounded bg-light px-2 py-1 shadow text-xs border border-secondary-200 text-dark animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2${className ? ` ${className}` : ''}`} className={`z-50 max-w-64 rounded bg-light px-2 py-1 shadow text-xs border border-secondary-200 text-dark animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2${className ? ` ${className}` : ''}`}
style={{ style={{
...context.data.floatingStyles, ...context.data.floatingStyles,
...style, ...style,
display: context.data.middlewareData.hide?.referenceHidden ? 'none' : 'block',
}} }}
{...context.interactions.getFloatingProps(props)} {...context.interactions.getFloatingProps(props)}
/> >
{ props.children }
{ context.data.middlewareData.arrow ? <FloatingArrow
ref={(context.data.refs as any).arrow}
context={context.data.context}
style={{fill: 'white'}}
/> : null }
</div>
</FloatingPortal> </FloatingPortal>
); );
}); });
......
...@@ -49,7 +49,7 @@ export type Edge = { ...@@ -49,7 +49,7 @@ export type Edge = {
// Define a type for the slice state // Define a type for the slice state
export type GraphQueryResult = { export type GraphQueryResult = {
metaData: GraphMetadata; metaData?: GraphMetadata;
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
queryingBackend: boolean; queryingBackend: boolean;
...@@ -57,7 +57,7 @@ export type GraphQueryResult = { ...@@ -57,7 +57,7 @@ export type GraphQueryResult = {
// Define the initial state using that type // Define the initial state using that type
export const initialState: GraphQueryResult = { export const initialState: GraphQueryResult = {
metaData: { nodes: { labels: [], types: {} }, edges: { labels: [], types: {} } }, metaData: undefined,
nodes: [], nodes: [],
edges: [], edges: [],
queryingBackend: false, queryingBackend: false,
...@@ -139,14 +139,14 @@ export const graphQueryResultSlice = createSlice({ ...@@ -139,14 +139,14 @@ export const graphQueryResultSlice = createSlice({
const { metaData, nodes, edges } = graphQueryBackend2graphQuery(payload); const { metaData, nodes, edges } = graphQueryBackend2graphQuery(payload);
// Assign new state // Assign new state
state.metaData = extractStatistics(metaData); state.metaData = metaData;
state.nodes = nodes; state.nodes = nodes;
state.edges = edges; state.edges = edges;
state.queryingBackend = false; state.queryingBackend = false;
}, },
resetGraphQueryResults: (state) => { resetGraphQueryResults: (state) => {
// Assign new state // Assign new state
state.metaData = { nodes: { labels: [], types: {} }, edges: { labels: [], types: {} } }; state.metaData = undefined;
state.nodes = []; state.nodes = [];
state.edges = []; state.edges = [];
state.queryingBackend = false; state.queryingBackend = false;
......
...@@ -39,7 +39,7 @@ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; ...@@ -39,7 +39,7 @@ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
/** Gives the graphQueryResult from the store */ /** Gives the graphQueryResult from the store */
export const useGraphQueryResult: () => GraphQueryResult = () => useAppSelector(selectGraphQueryResult); export const useGraphQueryResult: () => GraphQueryResult = () => useAppSelector(selectGraphQueryResult);
export const useGraphQueryResultMeta: () => GraphMetadata = () => useAppSelector(selectGraphQueryResultMetaData); export const useGraphQueryResultMeta: () => GraphMetadata | undefined = () => useAppSelector(selectGraphQueryResultMetaData);
// Gives the schema // Gives the schema
export const useSchemaGraph: () => SchemaGraph = () => useAppSelector(schemaGraph); export const useSchemaGraph: () => SchemaGraph = () => useAppSelector(schemaGraph);
......
import { SchemaAttribute } from '../../..';
import { Handles, QueryElementTypes } from '../reactflow';
import { QueryGraphEdgeAttribute, QueryGraphEdgeHandle, QueryGraphNodes } from './model';
const metaAttribute: Record<string, QueryGraphEdgeAttribute> = {
'(# Connection)': {
attributeName: '(# Connection)',
attributeType: 'float',
attributeDimension: 'numerical',
},
};
export function checkForMetaAttributes(graphologyAttributes: QueryGraphNodes): QueryGraphEdgeHandle[] {
const ret: QueryGraphEdgeHandle[] = [];
const defaultHandleData = {
nodeId: graphologyAttributes.id,
nodeName: graphologyAttributes.name || '',
nodeType: graphologyAttributes.type,
handleType: graphologyAttributes.type === QueryElementTypes.Entity ? Handles.EntityAttribute : Handles.RelationAttribute,
};
// Only include if not already there
const metaAttributesToInclude = Object.keys(metaAttribute).filter((attributeName) => !(attributeName in graphologyAttributes.attributes));
return metaAttributesToInclude.map((attributeName) => ({
...defaultHandleData,
...metaAttribute[attributeName],
})) as QueryGraphEdgeHandle[];
}
...@@ -23,7 +23,7 @@ export type NodeDefaults = { ...@@ -23,7 +23,7 @@ export type NodeDefaults = {
type: QueryElementTypes; type: QueryElementTypes;
width?: number; width?: number;
height?: number; height?: number;
attributes?: NodeAttribute[]; attributes: NodeAttribute[];
selected?: boolean; selected?: boolean;
}; };
...@@ -33,6 +33,7 @@ export interface EntityData { ...@@ -33,6 +33,7 @@ export interface EntityData {
leftRelationHandleId?: QueryGraphEdgeHandle; leftRelationHandleId?: QueryGraphEdgeHandle;
rightRelationHandleId?: QueryGraphEdgeHandle; rightRelationHandleId?: QueryGraphEdgeHandle;
selected?: boolean; selected?: boolean;
type: QueryElementTypes.Entity;
} }
/** Interface for the data in an relation node. */ /** Interface for the data in an relation node. */
...@@ -44,6 +45,7 @@ export interface RelationData { ...@@ -44,6 +45,7 @@ export interface RelationData {
rightEntityHandleId?: QueryGraphEdgeHandle; rightEntityHandleId?: QueryGraphEdgeHandle;
direction?: 'left' | 'right' | 'both'; direction?: 'left' | 'right' | 'both';
selected?: boolean; selected?: boolean;
type: QueryElementTypes.Relation;
} }
export interface LogicData { export interface LogicData {
...@@ -53,18 +55,19 @@ export interface LogicData { ...@@ -53,18 +55,19 @@ export interface LogicData {
// key: string; // key: string;
logic: GeneralDescription<AllLogicTypes>; logic: GeneralDescription<AllLogicTypes>;
inputs: Record<string, InputNodeTypeTypes>; // name from InputNode -> InputNodeTypeTypes inputs: Record<string, InputNodeTypeTypes>; // name from InputNode -> InputNodeTypeTypes
type: QueryElementTypes.Logic;
} }
export type EntityNodeAttributes = XYPosition & EntityData & NodeDefaults; export type EntityNodeAttributes = XYPosition & NodeDefaults & EntityData;
export type RelationNodeAttributes = XYPosition & RelationData & NodeDefaults; export type RelationNodeAttributes = XYPosition & NodeDefaults & RelationData;
export type LogicNodeAttributes = XYPosition & LogicData & NodeDefaults; export type LogicNodeAttributes = XYPosition & NodeDefaults & LogicData;
export type QueryGraphNodes = EntityNodeAttributes | RelationNodeAttributes | LogicNodeAttributes; export type QueryGraphNodes = EntityNodeAttributes | RelationNodeAttributes | LogicNodeAttributes;
export type QueryGraphEdgeAttribute = { export type QueryGraphEdgeAttribute = {
attributeName?: string; attributeName: string;
attributeType?: InputNodeType; attributeType: InputNodeType;
attributeDimension?: InputNodeDimension; attributeDimension: InputNodeDimension;
}; };
export type QueryGraphEdgeHandle = { export type QueryGraphEdgeHandle = {
...@@ -72,7 +75,7 @@ export type QueryGraphEdgeHandle = { ...@@ -72,7 +75,7 @@ export type QueryGraphEdgeHandle = {
nodeName: string; nodeName: string;
nodeType: QueryElementTypes; nodeType: QueryElementTypes;
handleType: Handles; handleType: Handles;
} & QueryGraphEdgeAttribute; } & Partial<QueryGraphEdgeAttribute>;
export type QueryGraphEdges = { export type QueryGraphEdges = {
type: string; type: string;
...@@ -80,10 +83,6 @@ export type QueryGraphEdges = { ...@@ -80,10 +83,6 @@ export type QueryGraphEdges = {
targetHandleData: QueryGraphEdgeHandle; targetHandleData: QueryGraphEdgeHandle;
}; };
export type QueryGraphEdgesOpt = { export type QueryGraphEdgesOpt = Partial<QueryGraphEdges>;
type?: string;
sourceHandleData?: QueryGraphEdgeHandle;
targetHandleData?: QueryGraphEdgeHandle;
};
// export class QueryGraph extends Graph<QueryGraphNodes, GAttributes, GAttributes>; // is in utils.ts // export class QueryGraph extends Graph<QueryGraphNodes, GAttributes, GAttributes>; // is in utils.ts
...@@ -4,7 +4,6 @@ import { Attributes as GAttributes, Attributes, SerializedGraph } from 'grapholo ...@@ -4,7 +4,6 @@ import { Attributes as GAttributes, Attributes, SerializedGraph } from 'grapholo
import { import {
EntityNodeAttributes, EntityNodeAttributes,
LogicNodeAttributes, LogicNodeAttributes,
QueryGraphEdgeAttribute,
QueryGraphEdgeHandle, QueryGraphEdgeHandle,
QueryGraphEdges, QueryGraphEdges,
QueryGraphEdgesOpt, QueryGraphEdgesOpt,
...@@ -15,6 +14,7 @@ import { XYPosition } from 'reactflow'; ...@@ -15,6 +14,7 @@ import { XYPosition } from 'reactflow';
import { Handles, QueryElementTypes } from '../reactflow'; import { Handles, QueryElementTypes } from '../reactflow';
import { SchemaAttribute, SchemaAttributeTypes } from '@graphpolaris/shared/lib/schema'; import { SchemaAttribute, SchemaAttributeTypes } from '@graphpolaris/shared/lib/schema';
import { InputNodeType, InputNodeTypeTypes } from '../logic/general'; import { InputNodeType, InputNodeTypeTypes } from '../logic/general';
import { checkForMetaAttributes } from './metaAttributes';
/** monospace fontsize table */ /** monospace fontsize table */
const widthPerFontsize = { const widthPerFontsize = {
...@@ -54,6 +54,9 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges ...@@ -54,6 +54,9 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges
if (!attributes.id) attributes.id = 'id_' + (Date.now() + Math.floor(Math.random() * 1000)).toString(); if (!attributes.id) attributes.id = 'id_' + (Date.now() + Math.floor(Math.random() * 1000)).toString();
// Add to the beginning the meta attributes, such as (# Connection)
attributes.attributes = [...checkForMetaAttributes(attributes).map((a) => ({ handleData: a })), ...attributes.attributes];
return attributes; return attributes;
} }
...@@ -113,19 +116,17 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges ...@@ -113,19 +116,17 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges
return attributes; return attributes;
} }
public addLogicPill2Graphology(attributes: QueryGraphNodes, inputValues: Record<string, InputNodeTypeTypes> = {}): QueryGraphNodes { public addLogicPill2Graphology(attributes: LogicNodeAttributes, inputValues: Record<string, InputNodeTypeTypes> = {}): QueryGraphNodes {
attributes = this.configureDefaults(attributes); attributes = this.configureDefaults(attributes) as LogicNodeAttributes;
if (!attributes.type) attributes.type = QueryElementTypes.Logic;
if (!attributes.name || !attributes.id) throw Error('type or name is not defined'); if (!attributes.name || !attributes.id) throw Error('type or name is not defined');
// add default inputs, but only if not there yet // add default inputs, but only if not there yet
if (attributes.type === QueryElementTypes.Logic) { if (attributes.type === QueryElementTypes.Logic) {
if ((attributes as LogicNodeAttributes).inputs === undefined) { if (attributes.inputs === undefined || Object.keys(attributes.inputs).length === 0) {
attributes = attributes as LogicNodeAttributes; attributes.logic.inputs.forEach((input, i) => {
(attributes as LogicNodeAttributes).logic.inputs.forEach((input, i) => {
// Setup default non-linked inputs as regular values matching the input expected type // Setup default non-linked inputs as regular values matching the input expected type
if (!(attributes as LogicNodeAttributes).inputs) (attributes as LogicNodeAttributes).inputs = {}; if (!attributes.inputs) attributes.inputs = {};
(attributes as LogicNodeAttributes).inputs[input.name] = inputValues?.[input.name] || input.default; attributes.inputs[input.name] = inputValues?.[input.name] || input.default;
}); });
// (attributes as LogicNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, ''); // (attributes as LogicNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, '');
// (attributes as LogicNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, ''); // (attributes as LogicNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, '');
......