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 (8)
Showing
with 527 additions and 282 deletions
apps/web/public/assets/sprite.png

687 B

apps/web/public/assets/sprite_selected.png

697 B

apps/web/public/assets/sprite_selected_square.png

96 B

apps/web/public/assets/sprite_square.png

344 B

......@@ -2,12 +2,13 @@ import React, { ReactElement, ReactPropTypes, useMemo } from 'react';
import styles from './buttons.module.scss';
import { Icon, Sizes } from '../icon';
import { forwardRef } from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
type ButtonProps = {
as?: 'button' | 'a' | 'div';
variantType?: 'primary' | 'secondary' | 'danger';
variant?: 'solid' | 'outline' | 'ghost';
size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg';
size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
label?: string;
rounded?: boolean;
disabled?: boolean;
......@@ -20,6 +21,7 @@ type ButtonProps = {
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
tooltip?: string;
onMouseUp?: (e: any) => void;
onMouseDown?: (e: any) => void;
onMouseEnter?: (e: any) => void;
......@@ -52,6 +54,7 @@ export const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement | H
ariaLabel,
className,
children,
tooltip,
...props
},
forwardedRef,
......@@ -94,6 +97,10 @@ export const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement | H
return styles['btn-md'];
case 'lg':
return styles['btn-lg'];
case 'xl':
return styles['btn-xl'];
case '2xl':
return styles['btn-2xl'];
default:
return styles['btn-md'];
}
......@@ -111,6 +118,10 @@ export const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement | H
return 24;
case 'lg':
return 28;
case 'xl':
return 32;
case '2xl':
return 36;
default:
return 24;
}
......@@ -134,20 +145,25 @@ export const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement | H
const isAnchor = as === 'a';
return (
<ButtonComponent
className={`${styles.btn} ${typeClass} ${variantClass} ${sizeClass} ${blockClass} ${roundedClass} ${iconOnlyClass} ${className ? className : ''}`}
onClick={onClick}
disabled={disabled}
aria-label={ariaLabel}
href={isAnchor ? href : undefined}
ref={forwardedRef as React.RefObject<any>}
{...props}
>
{iconPosition === 'leading' && icon}
{label && <span>{label}</span>}
{children && <span>{children}</span>}
{iconPosition === 'trailing' && icon}
</ButtonComponent>
<Tooltip>
{tooltip && <TooltipContent>{tooltip}</TooltipContent>}
<TooltipTrigger>
<ButtonComponent
className={`${styles.btn} ${typeClass} ${variantClass} ${sizeClass} ${blockClass} ${roundedClass} ${iconOnlyClass} ${className ? className : ''}`}
onClick={onClick}
disabled={disabled}
aria-label={ariaLabel}
href={isAnchor ? href : undefined}
ref={forwardedRef as React.RefObject<any>}
{...props}
>
{iconPosition === 'leading' && icon}
{label && <span>{label}</span>}
{children && <span>{children}</span>}
{iconPosition === 'trailing' && icon}
</ButtonComponent>
</TooltipTrigger>
</Tooltip>
);
},
);
......
......@@ -47,6 +47,16 @@
line-height: 1;
@apply p-1;
}
.btn-2xl {
@apply text-2xl h-12 gap-3;
&.btn-icon-only {
}
}
.btn-xl {
@apply text-xl h-11 gap-2;
&.btn-icon-only {
}
}
.btn-lg {
@apply text-lg h-10 gap-1.5;
&.btn-icon-only {
......
declare const classNames: {
readonly btn: 'btn';
readonly 'btn-icon-only': 'btn-icon-only';
readonly 'btn-2xl': 'btn-2xl';
readonly 'btn-xl': 'btn-xl';
readonly 'btn-lg': 'btn-lg';
readonly 'btn-md': 'btn-md';
readonly 'btn-sm': 'btn-sm';
......
import React, { useState, useEffect } from 'react';
import { Button } from '../buttons'; // Adjust the import path according to your project structure
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip';
import { setTheme, Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { useAppDispatch, useConfig } from '@graphpolaris/shared/lib/data-access/store';
const ColorMode = () => {
// Initialize theme state without setting a default yet
const [theme, setTheme] = useState<string>('');
const config = useConfig();
const dispatch = useAppDispatch();
// Function to update the body class and local storage
// Function to update the body class
const applyTheme = (themeValue: string) => {
document.body.className = themeValue;
localStorage.setItem('theme', themeValue);
};
// Load the theme preference from local storage on initial render
useEffect(() => {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
setTheme(savedTheme);
} else {
// Fallback to system preference if no saved theme
// const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const prefersDarkMode = false; // TODO: remove once dark theme is fixed
const defaultTheme = prefersDarkMode ? 'dark-mode' : 'light-mode';
setTheme(defaultTheme);
applyTheme(defaultTheme);
}
}, []);
// Update the local storage and body class whenever the theme changes
useEffect(() => {
if (theme) {
applyTheme(theme);
if (config.theme) {
applyTheme(config.theme);
}
}, [theme]);
}, [config.theme]);
// Function to toggle the theme
const toggleTheme = () => {
setTheme((currentTheme) => (currentTheme === 'light-mode' ? 'dark-mode' : 'light-mode'));
const themes = [
Theme.light,
Theme.dark,
]
const newTheme = themes[(themes.indexOf(config.theme) + 1) % themes.length];
dispatch(setTheme(newTheme));
};
const iconComponent = theme === 'dark-mode' ? 'icon-[ic--baseline-dark-mode]' : 'icon-[ic--baseline-light-mode]';
const iconComponent = config.theme === Theme.dark ? 'icon-[ic--baseline-dark-mode]' : 'icon-[ic--baseline-light-mode]';
return (
<TooltipProvider delayDuration={0}>
......@@ -47,7 +39,7 @@ const ColorMode = () => {
<Button variant="ghost" size="sm" iconComponent={iconComponent} onClick={toggleTheme} />
</TooltipTrigger>
<TooltipContent>
<p>{`Switch to ${theme === 'dark-mode' ? 'light' : 'dark'}-mode`}</p>
<p>{`Switch to ${config.theme === Theme.dark ? 'light' : 'dark'}-mode`}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
......
......@@ -4,7 +4,7 @@ import { useFloating, autoUpdate, offset, flip, shift, useInteractions, useClick
type Props = {
value: any;
updateValue: any;
updateValue: (val: [number, number, number]) => void;
};
export default function ColorPicker({ value, updateValue }: Props) {
......@@ -52,9 +52,10 @@ export default function ColorPicker({ value, updateValue }: Props) {
<TwitterPicker
triangle="top-right"
color={{ r: value[0], g: value[1], b: value[2] }}
onChangeComplete={(color: any) => {
onChangeComplete={(color) => {
console.log(color);
const rgb = color.rgb;
const newValue = [rgb.r, rgb.g, rgb.b];
const newValue: [number, number, number] = [rgb.r, rgb.g, rgb.b];
updateValue(newValue);
setOpen(false);
}}
......
......@@ -5,5 +5,5 @@ type Props = {
};
export function ControlContainer({ children }: Props) {
return <div className="top-4 right-4 flex flex-row-reverse justify-between z-50">{children}</div>;
return <div className="top-4 right-4 flex flex-row-reverse justify-between z-50 items-center">{children}</div>;
}
......@@ -2,7 +2,7 @@ import React, { ReactElement, ReactNode } 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 | 36 | 40;
export type IconProps = SVGProps<SVGSVGElement> & {
component?: ReactNode | ReactElement<any> | string;
size?: Sizes;
......
import React from 'react';
import React, { useEffect, useState } from 'react';
import styles from './inputs.module.scss';
import { DropdownTrigger, DropdownContainer, DropdownItem, DropdownItemContainer } from '../dropdowns';
import Info from '../info';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../tooltip';
import { Popover } from '../layout/Popover';
import { Button } from '../buttons';
type SliderProps = {
label: string;
......@@ -37,23 +38,28 @@ type TextProps = {
};
type NumberProps = {
label: string;
label?: string;
type: 'number';
size?: 'xs' | 'sm' | 'md' | 'xl';
variant?: 'primary' | 'secondary' | 'danger' | 'warning' | 'success' | 'info' | 'base';
placeholder?: string;
value: number;
required?: boolean;
errorText?: string;
visible?: boolean;
disabled?: boolean;
tooltip?: string;
tooltip?: string | React.ReactNode;
info?: string;
inline?: boolean;
validate?: (value: any) => boolean;
onChange?: (value: number) => void;
onRevert?: (value: number) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
max?: number;
min?: number;
className?: string;
containerClassName?: string;
lazy?: boolean;
};
type CheckboxProps = {
......@@ -88,7 +94,7 @@ type DropdownProps = {
overrideRender?: React.ReactNode;
type: 'dropdown';
size?: 'xs' | 'sm' | 'md' | 'xl';
tooltip?: string;
tooltip?: string | React.ReactNode;
required?: boolean;
inline?: boolean;
buttonVariant?: 'primary' | 'outline' | 'ghost';
......@@ -159,7 +165,7 @@ export const SliderInput = ({ label, value, min, max, step, unit, showValue = tr
};
export const TextInput = ({
label,
label = undefined,
placeholder,
value = '',
size = 'md',
......@@ -225,55 +231,108 @@ export const NumberInput = ({
inline = false,
required = false,
visible = true,
variant = 'base',
errorText,
validate,
disabled = false,
onChange,
onRevert,
tooltip,
info,
onKeyDown,
max,
min,
className,
containerClassName,
lazy = false,
}: NumberProps) => {
const [isValid, setIsValid] = React.useState<boolean>(true);
const [inputValue, setInputValue] = useState<number>(value);
if (!tooltip && inline) tooltip = label;
useEffect(() => {
setInputValue(value);
}, [value]);
if (!tooltip && inline && label) tooltip = label;
return (
<Tooltip>
<TooltipTrigger className={styles['input'] + ' form-control w-full' + (inline ? ' grid grid-cols-2 items-center gap-0.5' : '')}>
<label className="label p-0">
<span
className={`text-sm text-left truncate font-medium text-secondary-700 ${required && "after:content-['*'] after:ml-0.5 after:text-danger-500"}`}
>
{label}
</span>
{required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>}
{info && <Info tooltip={info} placement={'left'} />}
</label>
<input
type="number"
placeholder={placeholder}
className={`${size} bg-light border border-secondary-200 placeholder-secondary-400 focus:outline-none block w-full sm:text-sm focus:ring-1 ${
isValid ? '' : 'input-error'
}`}
value={value.toString()}
onChange={(e) => {
if (required && validate) {
setIsValid(validate(e.target.value));
}
if (onChange) {
onChange(Number(e.target.value));
}
}}
required={required}
disabled={disabled}
onKeyDown={onKeyDown}
max={max}
min={min}
/>
</TooltipTrigger>
{tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip>
<div className={styles['input'] + `${containerClassName ? ` ${containerClassName}` : ''}`}>
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger className={'form-control w-full' + (inline && label ? ' grid grid-cols-2 items-center gap-0.5' : '')}>
{label && (
<label className="label p-0">
<span
className={`text-sm text-left truncate font-medium text-secondary-700 ${required && "after:content-['*'] after:ml-0.5 after:text-danger-500"}`}
>
{label}
</span>
{required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>}
{info && <Info tooltip={info} placement={'left'} />}
</label>
)}
<div className="relative items-center">
<input
type="number"
placeholder={placeholder}
className={`${size} bg-light border border-secondary-200 placeholder-secondary-400 focus:outline-none block w-full sm:text-sm focus:ring-1 ${
isValid ? '' : 'input-error'
}${className ? ` ${className}` : ''}`}
value={inputValue.toString()}
onChange={(e) => {
if (required && validate) {
setIsValid(validate(e.target.value));
}
setInputValue(Number(e.target.value));
if (!lazy && onChange) {
onChange(Number(e.target.value));
}
}}
required={required}
disabled={disabled}
onKeyDown={(e) => {
if (lazy && e.key === 'Enter') {
if (onChange) {
onChange(inputValue);
}
}
if (onKeyDown) onKeyDown(e);
}}
max={max}
min={min}
/>
{lazy && value !== inputValue && (
<div className="absolute top-0 right-1 h-full flex flex-row items-center">
<Button
className="hover:bg-success-600 hover:text-white border-none m-0 p-0 h-fit"
variant="outline"
tooltip="Apply Change"
size={size}
rounded
iconComponent={'icon-[ic--round-check-circle-outline]'}
onClick={() => {
if (onChange) onChange(inputValue);
}}
></Button>
<Button
className="hover:bg-warning-600 hover:text-white border-none m-0 p-0 h-fit"
variant="outline"
tooltip="Revert Change"
size={size}
rounded
iconComponent={'icon-[ic--outline-cancel]'}
onClick={() => {
if (onRevert) onRevert(inputValue);
setInputValue(value);
}}
></Button>
</div>
)}
</div>
</TooltipTrigger>
{tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip>
</TooltipProvider>
</div>
);
};
......
......@@ -23,11 +23,16 @@
}
.input {
input[class~='xs'] {
input[class~='2xs'] {
@apply py-0;
@apply px-0;
@apply sm:text-2xs;
}
input[class~='xs'] {
@apply py-0.5;
@apply px-0.5;
@apply sm:text-xs;
}
input[class~='sm'] {
@apply py-1;
@apply px-1;
......@@ -37,10 +42,6 @@
@apply py-2;
@apply px-3;
}
input[class~='md'] {
@apply py-2;
@apply px-3;
}
input[class~='xl'] {
@apply py-3;
@apply px-5;
......
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from './store';
export enum Theme {
light = 'light-mode',
dark = 'dark-mode',
}
// Define the initial state using that type
export type ConfigStateI = {
theme: Theme;
autoSendQueries: boolean;
errors: string[];
warnings: string[];
......@@ -10,6 +16,7 @@ export type ConfigStateI = {
successes: string[];
};
export const initialState: ConfigStateI = {
theme: localStorage.getItem('theme') as Theme ?? Theme.light,
autoSendQueries: true,
errors: [],
warnings: [],
......@@ -48,10 +55,14 @@ export const configSlice = createSlice({
removeLastWarning: (state) => {
state.warnings.shift();
},
setTheme: (state, action: PayloadAction<Theme>) => {
localStorage.setItem('theme', action.payload);
state.theme = action.payload;
},
},
});
export const { addError, removeLastError, addWarning, removeLastWarning, addSuccess, removeLastSuccess, addInfo, removeLastInfo } =
export const { addError, removeLastError, addWarning, removeLastWarning, addSuccess, removeLastSuccess, addInfo, removeLastInfo, setTheme } =
configSlice.actions;
// Other code such as selectors can use the imported `RootState` type
......
import {
useConfig,
useGraphQueryResult,
useQuerybuilderGraph,
useQuerybuilderHash,
useQuerybuilderSettings,
......@@ -25,9 +26,7 @@ import ReactFlow, {
isNode,
useReactFlow,
} from 'reactflow';
import { Dialog, DialogClose, DialogContent } from '../../components/layout/Dialog';
import { Button } from '../../components/buttons';
import { ControlContainer } from '../../components/controls';
import { Dialog, DialogContent } from '../../components/layout/Dialog';
import { addError } from '../../data-access/store/configSlice';
import { toSchemaGraphology } from '../../data-access/store/schemaSlice';
import { LayoutFactory } from '../../graph-layout';
......@@ -38,25 +37,20 @@ import { dragPillStarted, movePillTo } from '../pills/dragging/dragPill';
import styles from './querybuilder.module.scss';
import { QueryBuilderLogicPillsPanel } from './querysidepanel/QueryBuilderLogicPillsPanel';
import { QueryBuilderRelatedNodesPanel } from './querysidepanel/QueryBuilderRelatedNodesPanel';
import { QueryMLDialog } from './querysidepanel/QueryMLDialog';
import { ConnectingNodeDataI } from './utils/connectorDrop';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip';
import { resultSetFocus } from '../../data-access/store/interactionSlice';
import { QueryBuilderDispatcherContext } from './QueryBuilderDispatcher';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/layout/Popover';
import { QuerySettings } from './querysidepanel/QuerySettings';
import { QueryBuilderNav, QueryBuilderToggleSettings } from './QueryBuilderNav';
export type QueryBuilderProps = {
onRunQuery?: () => void;
};
type SettingsPanel = 'settings' | 'ml' | 'logic' | 'relatedNodes' | undefined;
/**
* This is the main querybuilder component. It is responsible for holding all pills and fire off the visual part of the querybuilder panel logic
*/
export const QueryBuilderInner = (props: QueryBuilderProps) => {
const [toggleSettings, setToggleSettings] = useState<SettingsPanel>();
const [toggleSettings, setToggleSettings] = useState<QueryBuilderToggleSettings>();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const queryBuilderSettings = useQuerybuilderSettings();
......@@ -109,13 +103,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
setTimeout(() => reactFlow.fitView(), 0);
};
/**
* Clears all nodes in the graph.
*/
function clearAllNodes() {
dispatch(clearQB());
}
/**
* Clears all nodes in the graph.
* TODO: only works if the node is clicked and not moved (maybe use onSelectionChange)
......@@ -537,163 +524,19 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
}}
>
<div ref={reactFlowWrapper} className="h-full w-full flex flex-col">
<div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full">
<div className="flex items-center">
<h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">Query builder</h1>
</div>
<div className="sticky right-0 px-0.5 ml-auto items-center flex truncate">
<ControlContainer>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-fullscreen]"
onClick={fitView}
/>
</TooltipTrigger>
<TooltipContent>
<p>Fit to screen</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-delete]"
onClick={() => clearAllNodes()}
/>
</TooltipTrigger>
<TooltipContent>
<p>Clear query panel</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-camera-alt]"
onClick={(event) => {
event.stopPropagation();
}}
/>
</TooltipTrigger>
<TooltipContent>
<p>Capture screen</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-import-export]"
onClick={(event) => {
event.stopPropagation();
applyLayout();
}}
/>
</TooltipTrigger>
<TooltipContent>
<p>Layouts</p>
</TooltipContent>
</Tooltip>
<Popover>
<PopoverTrigger>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-settings]"
className="query-settings"
/>
</TooltipTrigger>
<TooltipContent>
<p>Query builder settings</p>
</TooltipContent>
</Tooltip>
</PopoverTrigger>
<PopoverContent>
<QuerySettings />
</PopoverContent>
</Popover>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-cached]"
onClick={(event) => {
event.stopPropagation();
if (props.onRunQuery) props.onRunQuery();
}}
/>
</TooltipTrigger>
<TooltipContent>
<p>Rerun query</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-difference]"
onClick={(event) => {
event.stopPropagation();
if (toggleSettings === 'logic') setToggleSettings(undefined);
else setToggleSettings('logic');
}}
/>
</TooltipTrigger>
<TooltipContent disabled={toggleSettings === 'logic'}>
<p>Logic settings</p>
</TooltipContent>
</Tooltip>
<Popover>
<PopoverTrigger>
<Tooltip>
<TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent="icon-[ic--baseline-lightbulb]" />
</TooltipTrigger>
<TooltipContent disabled={toggleSettings === 'ml'}>
<p>Machine learning</p>
</TooltipContent>
</Tooltip>
</PopoverTrigger>
<PopoverContent>
<QueryMLDialog />
</PopoverContent>
</Popover>
{/* <Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-drive-file-move]"
onClick={() => onAddSchemaToQueryBuilder()}
/>
</TooltipTrigger>
<TooltipContent>
<p>Query All Data</p>
</TooltipContent>
</Tooltip> */}
</TooltipProvider>
</ControlContainer>
</div>
</div>
<QueryBuilderNav
toggleSettings={toggleSettings}
onFitView={fitView}
onApplyLayout={() => applyLayout()}
onRunQuery={() => {
if (props.onRunQuery) props.onRunQuery();
}}
onScreenshot={() => {}}
onLogic={() => {
if (toggleSettings === 'logic') setToggleSettings(undefined);
else setToggleSettings('logic');
}}
/>
<Dialog
open={toggleSettings === 'logic'}
......
import React, { useMemo, useState } from 'react';
import { ControlContainer, TooltipProvider, Tooltip, TooltipTrigger, Button, TooltipContent, Input } from '../../components';
import { Popover, PopoverTrigger, PopoverContent } from '../../components/layout/Popover';
import { useAppDispatch, useGraphQueryResult, useQuerybuilderSettings, useSchemaStats } from '../../data-access';
import { clearQB, QueryBuilderSettings, setQuerybuilderSettings } from '../../data-access/store/querybuilderSlice';
import { QueryMLDialog } from './querysidepanel/QueryMLDialog';
import { QuerySettings } from './querysidepanel/QuerySettings';
export type QueryBuilderToggleSettings = 'settings' | 'ml' | 'logic' | 'relatedNodes' | undefined;
export type QueryBuilderNavProps = {
toggleSettings: QueryBuilderToggleSettings;
onFitView: () => void;
onApplyLayout: () => void;
onRunQuery: () => void;
onScreenshot: () => void;
onLogic: () => void;
};
export const QueryBuilderNav = (props: QueryBuilderNavProps) => {
const dispatch = useAppDispatch();
const qb = useQuerybuilderSettings();
const result = useGraphQueryResult();
const resultSize = useMemo(() => (result ? result.edges.length : 0), [result]);
/**
* Clears all nodes in the graph.
*/
function clearAllNodes() {
dispatch(clearQB());
}
return (
<div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full">
<div className="flex items-center">
<h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">Query builder</h1>
</div>
<div className="sticky right-0 px-0.5 ml-auto items-center flex truncate">
<ControlContainer>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-fullscreen]"
onClick={props.onFitView}
/>
</TooltipTrigger>
<TooltipContent>
<p>Fit to screen</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-delete]"
onClick={() => clearAllNodes()}
/>
</TooltipTrigger>
<TooltipContent>
<p>Clear query panel</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-camera-alt]"
onClick={props.onScreenshot}
/>
</TooltipTrigger>
<TooltipContent>
<p>Capture screen</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-import-export]"
onClick={props.onApplyLayout}
/>
</TooltipTrigger>
<TooltipContent>
<p>Layouts</p>
</TooltipContent>
</Tooltip>
<Popover>
<PopoverTrigger>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-settings]"
className="query-settings"
/>
</TooltipTrigger>
<TooltipContent>
<p>Query builder settings</p>
</TooltipContent>
</Tooltip>
</PopoverTrigger>
<PopoverContent>
<QuerySettings />
</PopoverContent>
</Popover>
<Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-cached]"
onClick={props.onRunQuery}
/>
</TooltipTrigger>
<TooltipContent>
<p>Rerun query</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-difference]"
onClick={props.onLogic}
/>
</TooltipTrigger>
<TooltipContent disabled={props.toggleSettings === 'logic'}>
<p>Logic settings</p>
</TooltipContent>
</Tooltip>
<Popover>
<PopoverTrigger>
<Tooltip>
<TooltipTrigger>
<Button variantType="secondary" variant="ghost" size="xs" iconComponent="icon-[ic--baseline-lightbulb]" />
</TooltipTrigger>
<TooltipContent disabled={props.toggleSettings === 'ml'}>
<p>Machine learning</p>
</TooltipContent>
</Tooltip>
</PopoverTrigger>
<PopoverContent>
<QueryMLDialog />
</PopoverContent>
</Popover>
{/* <Tooltip>
<TooltipTrigger>
<Button
variantType="secondary"
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-drive-file-move]"
onClick={() => onAddSchemaToQueryBuilder()}
/>
</TooltipTrigger>
<TooltipContent>
<p>Query All Data</p>
</TooltipContent>
</Tooltip> */}
<Popover>
<PopoverTrigger>
<Tooltip>
<TooltipTrigger>
<Button
variantType={qb.limit <= resultSize ? 'danger' : 'secondary'}
variant="ghost"
size="xs"
iconComponent="icon-[ic--baseline-filter-alt]"
className={qb.limit <= resultSize ? 'border-danger-600' : ''}
/>
</TooltipTrigger>
<TooltipContent disabled={props.toggleSettings === 'ml'}>
<p className="font-bold text-base">Limit</p>
<p>Limits the number of edges retrieved from the database.</p>
<p>Required to manage performance.</p>
<p className={`font-semibold${qb.limit <= resultSize ? ' text-danger-800' : ''}`}>
Fetched {resultSize} of a maximum of {qb.limit} edges
</p>
</TooltipContent>
</Tooltip>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col w-full gap-2 px-4 py-2">
<span className="text-xs font-bold">Limit</span>
<Input
type="number"
size="xs"
value={qb.limit}
lazy
onChange={(e) => {
dispatch(setQuerybuilderSettings({ ...qb, limit: Number(e) }));
}}
className={`w-24${qb.limit <= resultSize ? ' border-danger-600' : ''}`}
containerClassName=""
/>
</div>
</PopoverContent>
</Popover>
</TooltipProvider>
</ControlContainer>
</div>
</div>
);
};
......@@ -40,16 +40,6 @@ export const QuerySettings = React.forwardRef<HTMLDivElement, {}>((props, ref) =
setState({ ...state, autocompleteRelation: value as any });
}}
/>
<Input
type="number"
tooltip="The maximum number of results to return"
label="Limit"
inline
size="sm"
value={state.limit}
onChange={(e) => setState({ ...state, limit: e })}
/>
<Input
type="number"
label="Min Depth Default"
......
......@@ -66,7 +66,7 @@ export function VisualizationSettings({}: Props) {
};
return (
<div className="flex flex-col w-full">
<div className="flex flex-col w-full overflow-auto">
<div className="text-sm px-4 py-2 flex flex-col gap-1">
<div className="flex justify-between items-center">
<span className="font-bold">Visualization</span>
......
import React, { useEffect, useMemo } from 'react';
import { SettingsContainer } from '../../components/config';
import { layerSettings, layerTypes } from './layers';
import { Input } from '../../..';
import { VisualizationSettingsPropTypes } from '../../common';
import { MapEdgeData, MapNodeData, MapNodeOrEdgeData, MapProps } from './mapvis';
export const MapSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<MapProps>) => {
const DataLayerSettings = settings.layer && layerSettings?.[settings.layer];
const spatialAttributes = useMemo(() => {
return Object.fromEntries(
graphMetadata.nodes.labels.map((node) => [
node,
Object.entries(graphMetadata.nodes.types[node].attributes)
.filter(([, value]) => value.dimension === 'numerical')
.map(([key]) => key),
]),
);
}, [graphMetadata]);
useEffect(() => {
const nodes = Object.fromEntries(
graphMetadata.nodes.labels.map(
(node) =>
[
node,
{
color: [252, 185, 0],
hidden: false,
fixed: true,
min: 0,
max: 10,
radius: 1,
lat: undefined,
lon: undefined,
collapsed: false,
shape: 'circle',
size: 10,
...(settings?.nodes && node in settings.nodes ? settings.nodes[node] : {}),
},
] as [string, MapNodeData],
),
);
const edges = Object.fromEntries(
graphMetadata.edges.labels.map(
(edge) =>
[
edge,
{
color: [171, 184, 195],
hidden: false,
fixed: true,
min: 0,
max: 10,
radius: 1,
sizeAttribute: undefined,
collapsed: false,
size: 10,
...(settings?.edges && edge in settings.edges ? settings.edges[edge] : {}),
},
] as [string, MapEdgeData],
),
);
updateSettings({ nodes: nodes, edges: edges });
}, [graphMetadata]);
useEffect(() => {
// Autodetect a lat or lon attribute if not already set
Object.keys(settings.nodes).forEach((node) => {
if ((!settings.nodes[node].lat || !settings.nodes[node].lon) && node in spatialAttributes) {
const lat = spatialAttributes[node].find((attr) => attr.includes('latitude'));
const lon = spatialAttributes[node].find((attr) => attr.includes('longitude'));
if (lat && lon) {
updateSettings({ nodes: { ...settings.nodes, [node]: { ...settings.nodes[node], lat, lon } } });
}
}
});
}, [spatialAttributes, settings]);
return (
<SettingsContainer>
<Input
label="Data layer"
type="dropdown"
inline
value={settings.layer}
options={Object.keys(layerTypes)}
onChange={(val) => updateSettings({ layer: val as string })}
/>
{DataLayerSettings && !!spatialAttributes && (
<DataLayerSettings
settings={settings}
graphMetadata={graphMetadata}
updateSettings={updateSettings}
spatialAttributes={spatialAttributes}
/>
)}
</SettingsContainer>
);
};
......@@ -4,10 +4,10 @@ import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice
import React, { useState } from 'react';
interface SearchBarProps {
onSearch: (boundingbox: [number, number, number, number]) => void;
onSearch: (boundingBox: [number, number, number, number]) => void;
}
const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => {
export const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => {
const dispatch = useAppDispatch();
const [query, setQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
......@@ -42,5 +42,3 @@ const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => {
</div>
);
};
export default SearchBar;