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 (6)
Showing
with 486 additions and 123 deletions
...@@ -172,7 +172,7 @@ export default function DatabaseSelector({}) { ...@@ -172,7 +172,7 @@ export default function DatabaseSelector({}) {
onMouseEnter={() => setHovered(save.id)} onMouseEnter={() => setHovered(save.id)}
onMouseLeave={() => setHovered(null)} onMouseLeave={() => setHovered(null)}
> >
<Tooltip> <Tooltip placement={'left'}>
<TooltipTrigger> <TooltipTrigger>
<div <div
className={`h-[8px] w-[8px] rounded-full shrink-0 ${ className={`h-[8px] w-[8px] rounded-full shrink-0 ${
...@@ -180,7 +180,7 @@ export default function DatabaseSelector({}) { ...@@ -180,7 +180,7 @@ export default function DatabaseSelector({}) {
}`} }`}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'left'}> <TooltipContent>
<p> <p>
{session.testedSaveState[save.id] === DatabaseStatus.tested {session.testedSaveState[save.id] === DatabaseStatus.tested
? 'Database connection tested' ? 'Database connection tested'
...@@ -209,7 +209,7 @@ export default function DatabaseSelector({}) { ...@@ -209,7 +209,7 @@ export default function DatabaseSelector({}) {
<TooltipTrigger> <TooltipTrigger>
<Settings /> <Settings />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Change the connection details</p> <p>Change the connection details</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -232,7 +232,7 @@ export default function DatabaseSelector({}) { ...@@ -232,7 +232,7 @@ export default function DatabaseSelector({}) {
<TooltipTrigger> <TooltipTrigger>
<Delete /> <Delete />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Delete the database</p> <p>Delete the database</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
......
...@@ -47,7 +47,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta ...@@ -47,7 +47,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta
}, [formData]); }, [formData]);
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-4 my-2">
<Input <Input
type="text" type="text"
label="Name of database connection" label="Name of database connection"
......
...@@ -120,7 +120,7 @@ export const SettingsForm = (props: { onClose(): void; open: 'add' | 'update'; s ...@@ -120,7 +120,7 @@ export const SettingsForm = (props: { onClose(): void; open: 'add' | 'update'; s
}} }}
> >
<DialogContent className="lg:min-w-[50rem]"> <DialogContent className="lg:min-w-[50rem]">
<div className="flex justify-between align-center"> <div className="flex justify-between align-center m-2">
<h2 className="text-xl font-bold">{formTitle} Database Connection</h2> <h2 className="text-xl font-bold">{formTitle} Database Connection</h2>
<div> <div>
{sampleDataPanel === true ? ( {sampleDataPanel === true ? (
......
import React from 'react';
import type { SVGProps } from 'react';
export function CarbonStringInteger(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path
fill="currentColor"
d="M26 12h-4v2h4v2h-3v2h3v2h-4v2h4a2.003 2.003 0 0 0 2-2v-6a2 2 0 0 0-2-2m-7 10h-6v-4a2 2 0 0 1 2-2h2v-2h-4v-2h4a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-2v2h4zM8 20v-8H6v1H4v2h2v5H4v2h6v-2z"
></path>
</svg>
);
}
export function CarbonStringText(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path
fill="currentColor"
d="M29 22h-5a2.003 2.003 0 0 1-2-2v-6a2 2 0 0 1 2-2h5v2h-5v6h5zM18 12h-4V8h-2v14h6a2.003 2.003 0 0 0 2-2v-6a2 2 0 0 0-2-2m-4 8v-6h4v6zm-6-8H3v2h5v2H4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h6v-8a2 2 0 0 0-2-2m0 8H4v-2h4z"
></path>
</svg>
);
}
export function CarbonCalendar(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path
fill="currentColor"
d="M26 4h-4V2h-2v2h-8V2h-2v2H6c-1.1 0-2 .9-2 2v20c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 22H6V12h20zm0-16H6V6h4v2h2V6h8v2h2V6h4z"
></path>
</svg>
);
}
export function CarbonBoolean(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path fill="currentColor" d="M23 23a7 7 0 1 1 7-7a7.01 7.01 0 0 1-7 7m0-12a5 5 0 1 0 5 5a5.006 5.006 0 0 0-5-5"></path>
<circle cx={9} cy={16} r={7} fill="currentColor"></circle>
</svg>
);
}
export function CarbonUndefined(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path fill="currentColor" d="M11 14h10v4H11z"></path>
<path
fill="currentColor"
d="M29.391 14.527L17.473 2.609A2.08 2.08 0 0 0 16 2c-.533 0-1.067.203-1.473.609L2.609 14.527C2.203 14.933 2 15.466 2 16s.203 1.067.609 1.473L14.526 29.39c.407.407.941.61 1.474.61s1.067-.203 1.473-.609L29.39 17.474c.407-.407.61-.94.61-1.474s-.203-1.067-.609-1.473M16 28.036L3.965 16L16 3.964L28.036 16z"
></path>
</svg>
);
}
import React from 'react';
import { Icon } from '@graphpolaris/shared/lib/components/icon';
import { Numbers, Close } from '@mui/icons-material';
import styles from './cardtooltipvis.module.scss';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@graphpolaris/shared/lib/components/tooltip';
import {
CarbonStringInteger,
CarbonStringText,
CarbonCalendar,
CarbonBoolean,
CarbonUndefined,
} from '@graphpolaris/shared/lib/assets/carbonIcons/carbonIcons';
export type CardToolTipVisProps = {
type: string;
name: string;
data: Record<string, any>;
typeOfSchema?: string;
colorHeader: string;
connectedTo?: string;
connectedFrom?: string;
maxVisibleItems?: number;
numberOfElements?: number;
};
const formatNumber = (number: number) => {
return number.toLocaleString('de-DE'); // Format number with dots as thousand separators
};
export const VisualizationTooltip: React.FC<CardToolTipVisProps> = ({
type,
name,
data,
colorHeader,
maxVisibleItems = 5,
connectedFrom,
connectedTo,
typeOfSchema,
numberOfElements,
}) => {
const itemsToShow = Object.entries(data).slice(0, maxVisibleItems);
return (
<div className="border-1 border-sec-200 bg-white w-[12rem] -mx-2 -my-1">
<div className="flex m-0 justify-start items-stretch border-b border-sec-200 relative">
<div className="left-0 top-0 h-auto w-1.5" style={{ backgroundColor: colorHeader }}></div>
<div className="px-2.5 py-1 truncate flex">
<Tooltip>
<TooltipTrigger className={'flex max-w-full'}>
<span className="text-base font-semibold truncate">{name}</span>
</TooltipTrigger>
<TooltipContent side={'top'}>
<span>{name}</span>
</TooltipContent>
</Tooltip>
</div>
{/*
<div className="flex-shrink-0 ml-2">
<Button variantType="secondary" variant="ghost" size="xs" rounded={true} iconComponent={<Close />} onClick={() => {}} />
</div>
*/}
</div>
{type === 'schema' && numberOfElements && (
<div className="px-4 py-1 border-b border-sec-200">
<div className="flex flex-row gap-1 items-center justify-between">
<Icon component={<Numbers />} size={24} /> <span className="ml-auto text-right">{formatNumber(numberOfElements)}</span>
</div>
</div>
)}
{type === 'schema' && typeOfSchema === 'relationship' && (
<div className="px-4 py-1 border-b border-sec-200">
<div className="flex flex-row gap-3 items-center justify-between">
<span className="font-semibold">From</span>
<span className="ml-auto text-right">{connectedFrom}</span>
</div>
<div className="flex flex-row gap-1 items-center justify-between">
<span className="font-semibold">To</span>
<span className="ml-auto text-right">{connectedTo}</span>
</div>
</div>
)}
<TooltipProvider delayDuration={300}>
<div className={`px-3 py-1.5 ${data.length > maxVisibleItems ? 'max-h-20 overflow-y-auto' : ''}`}>
{data && Object.keys(data).length === 0 ? (
<div className="flex justify-center items-center h-full">
<span>No attributes</span>
</div>
) : (
Object.entries(data).map(([k, v]) => (
<Tooltip key={k}>
<div className="flex flex-row gap-1 items-center min-h-6">
<span className={`font-semibold truncate ${type === 'schema' ? 'w-[90%]' : 'min-w-[40%]'}`}>{k}</span>
<TooltipTrigger asChild>
<span className="ml-auto text-right truncate grow-1 flex items-center">
{type === 'schema' ? (
<Icon
className="ml-auto text-right flex-shrink-0"
component={
v === 'int' || v === 'float' ? (
<CarbonStringInteger />
) : v === 'string' ? (
<CarbonStringText />
) : v === 'boolean' ? (
<CarbonBoolean />
) : v === 'date' ? (
<CarbonCalendar />
) : v === 'undefined' ? (
<CarbonUndefined />
) : (
<CarbonUndefined />
)
}
color="hsl(var(--clr-sec--400))"
size={24}
/>
) : v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? (
<span className="ml-auto text-right truncate">{typeof v === 'number' ? formatNumber(v) : v.toString()}</span>
) : (
<div className={`ml-auto mt-auto h-4 w-12 ${styles['diagonal-lines']}`}></div>
)}
</span>
</TooltipTrigger>
<TooltipContent side="right">
<div className="max-w-[18rem] break-all line-clamp-6">
{v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? v : 'noData'}
</div>
</TooltipContent>
</div>
</Tooltip>
))
)}
</div>
</TooltipProvider>
</div>
);
};
.diagonal-lines {
border: 1px solid lightgray;
background:
repeating-linear-gradient(-45deg, transparent, transparent 6px, #eaeaea 6px, #eaeaea 8px),
/* Gray diagonal lines */ linear-gradient(to bottom, transparent, transparent); /* Vertical gradient */
}
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { VisualizationTooltip, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/CardToolTipVis';
const metaCardToolTipVis: Meta<typeof VisualizationTooltip> = {
component: VisualizationTooltip,
title: 'Components/CardToolTipVis',
};
export default metaCardToolTipVis;
type Story = StoryObj<typeof VisualizationTooltip>;
export const SchemaNode: Story = {
render: (args) => {
return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} />
</div>
);
},
args: {
type: 'schema',
typeOfSchema: 'node',
name: 'Person',
data: {
born: 'int',
name: 'string',
description: 'string',
},
colorHeader: '#fb7b04',
numberOfElements: 1000,
},
};
export const SchemaRelationship: Story = {
render: (args) => {
return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} />
</div>
);
},
args: {
type: 'schema',
typeOfSchema: 'relationship',
name: 'Directed',
data: {
born: 'int',
name: 'string',
description: 'string',
imdb: 'string',
imdbVotes: 'int',
},
colorHeader: '#0676C1',
connectedTo: 'Person',
numberOfElements: 231230,
connectedFrom: 'Movie',
},
};
export const PopUpVis: Story = {
render: (args) => {
return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} />
</div>
);
},
args: {
name: 'Person',
type: 'popupvis',
data: {
bio: 'From wikipedia was born in usa from a firefighter father',
name: 'Charlotte Henry',
born: {},
imdbRank: 21213,
imdbVotes: 1213,
poster: 'https://image.tmdb.org/t/p/w440_and_h660_face/kTKiREs37qd8GUlNI4Koiupwy6W.jpg',
tmdbId: '94105',
country: undefined,
labels: ['Actor', 'Person', 'Human'],
},
colorHeader: '#B69AEf',
},
};
export * from './VisualizationTooltip';
...@@ -43,11 +43,11 @@ const ColorMode = () => { ...@@ -43,11 +43,11 @@ const ColorMode = () => {
return ( return (
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip placement={'right'}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button variant="ghost" size="sm" iconComponent={iconComponent} onClick={toggleTheme} /> <Button variant="ghost" size="sm" iconComponent={iconComponent} onClick={toggleTheme} />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'right'}> <TooltipContent>
<p>{`Switch to ${theme === 'dark-mode' ? 'light' : 'dark'}-mode`}</p> <p>{`Switch to ${theme === 'dark-mode' ? 'light' : 'dark'}-mode`}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
......
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 } from '@mui/icons-material';
import { CarbonStringInteger } from '@graphpolaris/shared/lib/assets/carbonIcons/carbonIcons';
const Component: Meta<typeof Icon> = { const Component: Meta<typeof Icon> = {
title: 'Components/Icon', title: 'Components/Icon',
component: Icon, component: Icon,
...@@ -21,8 +21,13 @@ const Component: Meta<typeof Icon> = { ...@@ -21,8 +21,13 @@ const Component: Meta<typeof Icon> = {
export default Component; export default Component;
type Story = StoryObj<typeof Component>; type Story = StoryObj<typeof Component>;
export const BaseIcon: Story = (args: any) => { export const MUIIcon: Story = (args: any) => {
return <Icon component={<ArrowBack />} size={24} {...args} />; return <Icon component={<ArrowBack />} size={24} {...args} />;
}; };
BaseIcon.args = {}; export const CarbonIcon: Story = (args: any) => {
return <Icon component={<CarbonStringInteger />} size={24} {...args} />;
};
MUIIcon.args = {};
CarbonIcon.args = {};
import React, { ReactElement } from 'react'; import React, { ReactElement, ReactNode } from 'react';
import { SVGProps } from 'react'; import { SVGProps } from 'react';
// Define Sizes and IconProps types
export type Sizes = 12 | 14 | 16 | 20 | 24 | 28 | 32 | 40; 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: ReactNode | ReactElement<any>;
size?: Sizes; size?: Sizes;
color?: string; color?: string;
}; };
// Icon component definition
export const Icon: React.FC<IconProps> = ({ component, size = 24, color, ...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>;
} }
return React.cloneElement(component, { style: { fontSize: size }, width: size, height: size, ...props }); const style = { fontSize: size, color };
// Check if component is a valid React element
if (React.isValidElement(component)) {
return React.cloneElement(component as ReactElement<any>, {
style: { ...style, ...(component as ReactElement<any>).props.style },
width: size,
height: size,
...props,
});
}
// Check if component is a function (assume it's a custom SVG component)
if (typeof component === 'function') {
// Render the custom SVG component directly
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 32 32" style={style} {...props}>
{(component as () => ReactNode)()}
</svg>
);
}
// Default case: render null or fallback
console.error('Unsupported icon type');
return null;
}; };
...@@ -5,16 +5,16 @@ import { InfoOutlined } from '@mui/icons-material'; ...@@ -5,16 +5,16 @@ import { InfoOutlined } from '@mui/icons-material';
type Props = { type Props = {
tooltip: string; tooltip: string;
side?: 'top' | 'bottom' | 'left' | 'right'; placement?: 'top' | 'bottom' | 'left' | 'right';
}; };
export default function Info({ tooltip, side = 'left' }: Props) { export default function Info({ tooltip, placement = 'left' }: Props) {
return ( return (
<Tooltip> <Tooltip placement={placement}>
<TooltipTrigger> <TooltipTrigger>
<Icon component={<InfoOutlined />} size={14} /> <Icon component={<InfoOutlined />} size={14} />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={side}> <TooltipContent>
<p>{tooltip}</p> <p>{tooltip}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
......
...@@ -96,7 +96,7 @@ type DropdownProps = { ...@@ -96,7 +96,7 @@ type DropdownProps = {
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
value?: number | string; value?: number | string;
options: number[] | string[] | {[key: string]: string}[]; options: number[] | string[] | { [key: string]: string }[];
onChange?: (value: number | string) => void; onChange?: (value: number | string) => void;
}; };
...@@ -153,7 +153,7 @@ export const SliderInput = ({ label, value, min, max, step, unit, showValue = tr ...@@ -153,7 +153,7 @@ export const SliderInput = ({ label, value, min, max, step, unit, showValue = tr
/> />
</div> </div>
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
...@@ -188,7 +188,7 @@ export const TextInput = ({ ...@@ -188,7 +188,7 @@ export const TextInput = ({
{label} {label}
</span> </span>
{required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>} {required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>}
{info && <Info tooltip={info} side={'left'} />} {info && <Info tooltip={info} placement={'left'} />}
</label> </label>
)} )}
<input <input
...@@ -212,7 +212,7 @@ export const TextInput = ({ ...@@ -212,7 +212,7 @@ export const TextInput = ({
disabled={disabled} disabled={disabled}
/> />
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
...@@ -248,7 +248,7 @@ export const NumberInput = ({ ...@@ -248,7 +248,7 @@ export const NumberInput = ({
{label} {label}
</span> </span>
{required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>} {required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>}
{info && <Info tooltip={info} side={'left'} />} {info && <Info tooltip={info} placement={'left'} />}
</label> </label>
<input <input
type="number" type="number"
...@@ -272,7 +272,7 @@ export const NumberInput = ({ ...@@ -272,7 +272,7 @@ export const NumberInput = ({
min={min} min={min}
/> />
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
...@@ -301,7 +301,7 @@ export const RadioInput = ({ label, value, options, onChange, tooltip }: RadioPr ...@@ -301,7 +301,7 @@ export const RadioInput = ({ label, value, options, onChange, tooltip }: RadioPr
</label> </label>
))} ))}
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
...@@ -343,7 +343,7 @@ export const CheckboxInput = ({ label, value, options, onChange, tooltip }: Chec ...@@ -343,7 +343,7 @@ export const CheckboxInput = ({ label, value, options, onChange, tooltip }: Chec
</label> </label>
))} ))}
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
...@@ -366,23 +366,25 @@ export const BooleanInput = ({ label, value, onChange, tooltip }: BooleanProps) ...@@ -366,23 +366,25 @@ export const BooleanInput = ({ label, value, onChange, tooltip }: BooleanProps)
/> />
</label> </label>
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
function parsedValue(item: number | string | {[key: string]: string}, key: boolean = false) { function parsedValue(item: number | string | { [key: string]: string }, key: boolean = false) {
switch (typeof item) { switch (typeof item) {
case 'number': return item.toString(); case 'number':
case 'object': return key ? Object.keys(item)[0] : Object.values(item)[0]; return item.toString();
case 'object':
return key ? Object.keys(item)[0] : Object.values(item)[0];
} }
return item; return item;
} }
function currentValue(value: string | number | undefined, options?: {[key: string]: string}[]) { function currentValue(value: string | number | undefined, options?: { [key: string]: string }[]) {
if (options != null && typeof options[0] == 'object') { if (options != null && typeof options[0] == 'object') {
return parsedValue(options.find(x => x[value as string]) as {[key: string]: string}); return parsedValue(options.find((x) => x[value as string]) as { [key: string]: string });
} }
return value; return value;
...@@ -428,7 +430,7 @@ export const DropdownInput = ({ ...@@ -428,7 +430,7 @@ export const DropdownInput = ({
> >
<DropdownTrigger <DropdownTrigger
variant={buttonVariant} variant={buttonVariant}
title={overrideRender || currentValue(value, options as {[key: string]: string}[]) } title={overrideRender || currentValue(value, options as { [key: string]: string }[])}
size={size} size={size}
className="cursor-pointer" className="cursor-pointer"
disabled={disabled} disabled={disabled}
...@@ -458,7 +460,7 @@ export const DropdownInput = ({ ...@@ -458,7 +460,7 @@ export const DropdownInput = ({
)} )}
</DropdownContainer> </DropdownContainer>
</TooltipTrigger> </TooltipTrigger>
{tooltip && <TooltipContent side={'top'}>{tooltip}</TooltipContent>} {tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip> </Tooltip>
); );
}; };
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
useInteractions, useInteractions,
useMergeRefs, useMergeRefs,
FloatingPortal, FloatingPortal,
FloatingArrow 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';
...@@ -26,6 +26,7 @@ interface TooltipOptions { ...@@ -26,6 +26,7 @@ interface TooltipOptions {
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
boundaryElement?: React.RefObject<HTMLElement> | null; boundaryElement?: React.RefObject<HTMLElement> | null;
showArrow?: boolean; showArrow?: boolean;
interactive?: boolean;
} }
export function useTooltip({ export function useTooltip({
...@@ -34,12 +35,14 @@ export function useTooltip({ ...@@ -34,12 +35,14 @@ export function useTooltip({
open: controlledOpen, open: controlledOpen,
onOpenChange: setControlledOpen, onOpenChange: setControlledOpen,
boundaryElement = null, boundaryElement = null,
showArrow = false showArrow = false,
interactive = true,
}: TooltipOptions = {}): { }: TooltipOptions = {}): {
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
interactions: ReturnType<typeof useInteractions>; interactions: ReturnType<typeof useInteractions>;
data: ReturnType<typeof useFloating>; data: ReturnType<typeof useFloating>;
interactive: boolean;
} { } {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen);
...@@ -57,16 +60,16 @@ export function useTooltip({ ...@@ -57,16 +60,16 @@ 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) { if (boundaryElement != null) {
const boundary = boundaryElement?.current ?? undefined; const boundary = boundaryElement?.current ?? undefined;
config.middleware.find(x => x.name == 'flip')!.options[0].boundary = boundary; config.middleware.find((x) => x.name == 'flip')!.options[0].boundary = boundary;
config.middleware.find(x => x.name == 'shift')!.options[0].boundary = boundary; config.middleware.find((x) => x.name == 'shift')!.options[0].boundary = boundary;
config.middleware.push(hide({ boundary })); config.middleware.push(hide({ boundary }));
} }
...@@ -97,8 +100,9 @@ export function useTooltip({ ...@@ -97,8 +100,9 @@ export function useTooltip({
setOpen, setOpen,
interactions: interactions, interactions: interactions,
data: data, data: data,
interactive: interactive,
}), }),
[open, setOpen, interactions, data], [open, setOpen, interactions, data, interactive],
); );
} }
...@@ -120,72 +124,69 @@ export function Tooltip({ children, ...options }: { children: React.ReactNode } ...@@ -120,72 +124,69 @@ 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}> return <TooltipContext.Provider value={tooltip}>{children}</TooltipContext.Provider>;
{children}
</TooltipContext.Provider>;
} }
export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean, x?: number, y?: number }>(function TooltipTrigger( export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean; x?: number; y?: number }>(
{ children, asChild = false, x = null, y = null, ...props }, function TooltipTrigger({ children, asChild = false, x = null, y = null, ...props }, propRef) {
propRef, const context = useTooltipContext();
) { const childrenRef = React.useMemo(() => {
const context = useTooltipContext(); if (children == null) {
const childrenRef = React.useMemo(() => { return null;
if (children == null) { } else {
return null; return (children as any).ref;
} else {
return (children as any).ref;
}
}, [children]);
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(); }, [children]);
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
if (asChild && React.isValidElement(children)) {
return React.cloneElement(
children,
context.interactions.getReferenceProps({
ref,
...props,
...children.props,
'data-state': context.open ? 'open' : 'closed',
}),
);
} }
}, [x, y]);
// `asChild` allows the user to pass any element as the anchor
if (asChild && React.isValidElement(children)) {
return React.cloneElement(
children,
context.interactions.getReferenceProps({
ref,
...props,
...children.props,
'data-state': context.open ? 'open' : 'closed',
}),
);
}
return ( return (
<div <div
ref={ref} ref={ref}
// The user can style the trigger based on the state // The user can style the trigger based on the state
data-state={context.open ? 'open' : 'closed'} data-state={context.open ? 'open' : 'closed'}
{...context.interactions.getReferenceProps(props)} {...context.interactions.getReferenceProps(props)}
> >
{children} {children}
</div> </div>
); );
}); },
);
export const TooltipContent = React.forwardRef< export const TooltipContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
...@@ -202,20 +203,22 @@ export const TooltipContent = React.forwardRef< ...@@ -202,20 +203,22 @@ export const TooltipContent = React.forwardRef<
<FloatingPortal> <FloatingPortal>
<div <div
ref={ref} ref={ref}
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}` : ''}`} className={`z-50 max-w-64 rounded bg-light px-2 py-2 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', display: context.data.middlewareData.hide?.referenceHidden ? 'none' : 'block',
pointerEvents: context.interactive ? 'auto' : 'none',
}} }}
{...context.interactions.getFloatingProps(props)} {...context.interactions.getFloatingProps(props)}
> >
{ props.children } {props.children}
{ context.data.middlewareData.arrow ? <FloatingArrow {context.data.middlewareData.arrow ? (
ref={(context.data.refs as any).arrow} <FloatingArrow ref={(context.data.refs as any).arrow} context={context.data.context} style={{ fill: 'white' }} />
context={context.data.context} ) : null}
style={{fill: 'white'}}
/> : null }
</div> </div>
</FloatingPortal> </FloatingPortal>
); );
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
wsSchemaRequest, wsSchemaRequest,
wsSchemaSubscription, wsSchemaSubscription,
useQuerybuilderAttributesShown, useQuerybuilderAttributesShown,
wsSchemaStatsRequest,
} from '@graphpolaris/shared/lib/data-access'; } from '@graphpolaris/shared/lib/data-access';
import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker'; import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
...@@ -207,6 +208,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function } ...@@ -207,6 +208,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
// New active database // New active database
if (session.currentSaveState && session.currentSaveState !== nilUUID) { if (session.currentSaveState && session.currentSaveState !== nilUUID) {
wsSchemaRequest(session.currentSaveState); wsSchemaRequest(session.currentSaveState);
wsSchemaStatsRequest(session.currentSaveState);
wsSelectState(session.currentSaveState); wsSelectState(session.currentSaveState);
loadSaveState(session.currentSaveState, session.saveStates); loadSaveState(session.currentSaveState, session.saveStates);
} }
......
// All database related API calls // All database related API calls
import { SchemaFromBackend } from '../../schema'; import { SchemaFromBackend, SchemaStatsFromBackend } from '../../schema';
import { Broker } from './broker'; import { Broker } from './broker';
export function wsSchemaRequest(saveStateID: string) { export function wsSchemaRequest(saveStateID: string) {
...@@ -13,6 +13,7 @@ export function wsSchemaRequest(saveStateID: string) { ...@@ -13,6 +13,7 @@ export function wsSchemaRequest(saveStateID: string) {
}, },
}); });
} }
type SchemaResponse = (data: SchemaFromBackend) => void; type SchemaResponse = (data: SchemaFromBackend) => void;
export function wsSchemaSubscription(callback: SchemaResponse) { export function wsSchemaSubscription(callback: SchemaResponse) {
const id = Broker.instance().subscribe(callback, 'schema_result'); const id = Broker.instance().subscribe(callback, 'schema_result');
...@@ -20,3 +21,22 @@ export function wsSchemaSubscription(callback: SchemaResponse) { ...@@ -20,3 +21,22 @@ export function wsSchemaSubscription(callback: SchemaResponse) {
Broker.instance().unSubscribe('schema_result', id); Broker.instance().unSubscribe('schema_result', id);
}; };
} }
export function wsSchemaStatsRequest(saveStateID: string) {
Broker.instance().sendMessage({
key: 'schema',
subKey: 'getSchemaStats',
body: {
cached: false,
saveStateID: saveStateID,
},
});
}
type SchemaStatsResponse = (data: SchemaStatsFromBackend) => void;
export function wsSchemaStatsSubscription(callback: SchemaStatsResponse) {
const id = Broker.instance().subscribe(callback, 'schema_stats_result');
return () => {
Broker.instance().unSubscribe('schema_stats_result', id);
};
}
...@@ -18,7 +18,7 @@ export function InspectorPanel(props: { children?: React.ReactNode }) { ...@@ -18,7 +18,7 @@ export function InspectorPanel(props: { children?: React.ReactNode }) {
const { activeVisualizationIndex } = useVisualization(); const { activeVisualizationIndex } = useVisualization();
const inspector = useMemo(() => { const inspector = useMemo(() => {
if (selection) return <SelectionConfig />; //if (selection) return <SelectionConfig />;
// if (!focus) return <ConnectionInspector />; // if (!focus) return <ConnectionInspector />;
// if (activeVisualizationIndex !== -1) return <ConnectionInspector />; // if (activeVisualizationIndex !== -1) return <ConnectionInspector />;
return <VisualizationSettings />; return <VisualizationSettings />;
...@@ -38,7 +38,7 @@ export function InspectorPanel(props: { children?: React.ReactNode }) { ...@@ -38,7 +38,7 @@ export function InspectorPanel(props: { children?: React.ReactNode }) {
{focus && ( {focus && (
<> <>
<span className="pb-0.5">{'>'}</span> <span className="pb-0.5">{'>'}</span>
<Button variant="ghost" size="2xs" className="hover:underline p-0"> <Button variant="ghost" size="2xs" className="hover:underline p-0 capitalize">
{focus.focusType} {focus.focusType}
</Button> </Button>
</> </>
......
...@@ -474,14 +474,14 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -474,14 +474,14 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
<div className="flex items-center"> <div className="flex items-center">
<h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">Query builder</h1> <h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">Query builder</h1>
</div> </div>
<div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex"> <div className="sticky right-0 px-0.5 ml-auto items-center flex truncate">
<ControlContainer> <ControlContainer>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> <Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Fit to screen</p> <p>Fit to screen</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -489,7 +489,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -489,7 +489,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
<TooltipTrigger> <TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Delete />} onClick={() => clearAllNodes()} /> <Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Delete />} onClick={() => clearAllNodes()} />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Clear query panel</p> <p>Clear query panel</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -505,7 +505,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -505,7 +505,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
}} }}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Capture screen</p> <p>Capture screen</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -522,7 +522,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -522,7 +522,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
}} }}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Layouts</p> <p>Layouts</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -532,7 +532,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -532,7 +532,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
<TooltipTrigger> <TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Settings />} className="query-settings" /> <Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Settings />} className="query-settings" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Query builder settings</p> <p>Query builder settings</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -554,7 +554,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -554,7 +554,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
}} }}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'}> <TooltipContent>
<p>Rerun query</p> <p>Rerun query</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -572,7 +572,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -572,7 +572,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
}} }}
/> />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'} disabled={toggleSettings === 'logic'}> <TooltipContent disabled={toggleSettings === 'logic'}>
<p>Logic settings</p> <p>Logic settings</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
...@@ -582,7 +582,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { ...@@ -582,7 +582,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
<TooltipTrigger> <TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Lightbulb />} /> <Button variantType="secondary" variant="ghost" size="xs" iconComponent={<Lightbulb />} />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={'top'} disabled={toggleSettings === 'ml'}> <TooltipContent disabled={toggleSettings === 'ml'}>
<p>Machine learning</p> <p>Machine learning</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
......
...@@ -6,6 +6,18 @@ export type SchemaFromBackend = { ...@@ -6,6 +6,18 @@ export type SchemaFromBackend = {
nodes: SchemaNode[]; nodes: SchemaNode[];
}; };
export type SchemaStatsFromBackend = {
edgeStats: {
type: string;
count: number;
}[];
nodeStats: {
key: string;
labels: string[];
count: number;
}[];
};
export type SchemaAttributeTypes = 'string' | 'float' | 'int' | 'bool' | 'date' | 'time' | 'datetime' | 'duration'; export type SchemaAttributeTypes = 'string' | 'float' | 'int' | 'bool' | 'date' | 'time' | 'datetime' | 'duration';
export type DimensionType = 'categorical' | 'numerical' | 'temporal' | 'spatial'; export type DimensionType = 'categorical' | 'numerical' | 'temporal' | 'spatial';
......
...@@ -34,6 +34,9 @@ export type SchemaReactflowEntity = SchemaReactflowData & { ...@@ -34,6 +34,9 @@ export type SchemaReactflowEntity = SchemaReactflowData & {
// handles: string[]; // handles: string[];
connectedRatio: number; connectedRatio: number;
name: string; name: string;
x: number;
y: number;
reactFlowRef: any;
}; };
export type SchemaReactflowRelation = SchemaReactflowData & { export type SchemaReactflowRelation = SchemaReactflowData & {
...@@ -42,6 +45,8 @@ export type SchemaReactflowRelation = SchemaReactflowData & { ...@@ -42,6 +45,8 @@ export type SchemaReactflowRelation = SchemaReactflowData & {
collection: string; collection: string;
fromRatio: number; fromRatio: number;
toRatio: number; toRatio: number;
x: number;
y: number;
}; };
export type SchemaReactflowNodeWithFunctions = SchemaReactflowEntity & { export type SchemaReactflowNodeWithFunctions = SchemaReactflowEntity & {
......