diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index cb7388cd4cb7339684c7d3aecaa383037e00f3e6..bde42e867c7ebd7a7ddbf2ff148c9b571853b0d8 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -20,7 +20,8 @@ import { URLParams, setParam } from '@graphpolaris/shared/lib/data-access/api/ur import { VisualizationPanel } from '@graphpolaris/shared/lib/vis'; import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder'; import { SideNavTab, Sidebar } from '@graphpolaris/shared/lib/sidebar'; -import { InspectorPanel } from '@graphpolaris/shared/lib/inspector'; +import { VisualizationManager } from '@graphpolaris/shared/lib/vis/manager'; +import { ConfigPanel } from '@graphpolaris/shared/lib/vis/components/config'; import { SearchBar } from '@graphpolaris/shared/lib/sidebar/search/SearchBar'; import { Schema } from '@graphpolaris/shared/lib/schema/panel'; @@ -35,6 +36,7 @@ export function App(props: App) { const session = useSessionCache(); const dispatch = useAppDispatch(); const queryBuilderSettings = useQuerybuilderSettings(); + const manager = VisualizationManager(); const schema = useSchemaGraph(); const runQuery = () => { @@ -56,7 +58,7 @@ export function App(props: App) { const [authCheck, setAuthCheck] = useState(false); const [tab, setTab] = useState<SideNavTab>('Schema'); - // const [visFullSize, setVisFullSize] = useState<boolean>(false); + const [visFullSize, setVisFullSize] = useState<boolean>(false); return ( <div className="h-screen w-screen overflow-clip"> @@ -77,30 +79,30 @@ export function App(props: App) { </aside> <main className="grow flex flex-row h-screen pt-12"> <Sidebar onTab={(tab) => setTab(tab)} tab={tab} /> - <Resizable divisorSize={3} horizontal={true} defaultProportion={0.85} maxProportion={0.85}> - <Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}> - {tab !== undefined ? ( - <div className="flex flex-col w-full h-full"> - {tab === 'Search' && <SearchBar onRemove={() => setTab(undefined)} />} - {tab === 'Schema' && <Schema auth={authCheck} onRemove={() => setTab(undefined)} />} - </div> - ) : null} + <Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}> + {tab !== undefined ? ( + <div className="flex flex-col w-full h-full"> + {tab === 'Search' && <SearchBar onRemove={() => setTab(undefined)} />} + {tab === 'Schema' && <Schema auth={authCheck} onRemove={() => setTab(undefined)} />} + </div> + ) : null} - <Resizable divisorSize={3} horizontal={false}> - <VisualizationPanel - fullSize={() => { - // setVisFullSize(!visFullSize); - // tab === undefined && setTab('Schema'); - // tab !== undefined && setTab(undefined); - }} - /> + <Resizable divisorSize={3} horizontal={false}> + <VisualizationPanel + manager={manager} + fullSize={() => { + setVisFullSize(!visFullSize); + tab === undefined && setTab('Schema'); + tab !== undefined && setTab(undefined); + }} + /> - <QueryBuilder onRunQuery={runQuery} /> - </Resizable> + <QueryBuilder onRunQuery={runQuery} /> </Resizable> - {/* <ConfigPanel /> */} - <InspectorPanel /> </Resizable> + <div className="info-panel flex h-full w-60 ml-[3px] shrink-0 overflow-auto bg-light border"> + <ConfigPanel manager={manager} /> + </div> </main> </div> </div> diff --git a/libs/shared/lib/components/charts/Axis/axis.stories.tsx b/libs/shared/lib/components/charts/Axis/axis.stories.tsx index 918d14c1d6890d7a069948d09e402363b2d98d0f..00c13d2fb1ca6ba3dfa01bbd05fc94e3d88c8b88 100644 --- a/libs/shared/lib/components/charts/Axis/axis.stories.tsx +++ b/libs/shared/lib/components/charts/Axis/axis.stories.tsx @@ -9,7 +9,7 @@ const Component: Meta<AxisComponentProps> = { component: AxisComponent, decorators: [ (Story) => ( - <div className="w-full h-full flex flex-row justify-center flex-grow"> + <div className="w-full h-full flex flex-row justify-center"> <svg className="border border-secondary-300" width="300" height="200"> <Story /> </svg> diff --git a/libs/shared/lib/components/forms/index.tsx b/libs/shared/lib/components/forms/index.tsx index a4bb743278f6c96fe45302628733d1699d10b7fd..643126cacb855854cca41d87e5e90769dfd681ec 100644 --- a/libs/shared/lib/components/forms/index.tsx +++ b/libs/shared/lib/components/forms/index.tsx @@ -79,48 +79,27 @@ export const FormBody = ({ {children} </form> ); -export const FormTitle = ({ children, title, onClose }: PropsWithChildren<{ title: string; onClose?: () => void }>) => { +export const FormTitle = ({ children, title, onClose }: PropsWithChildren<{ title: string; onClose: () => void }>) => { return ( <div className="card-title p-5 py-0 mt-2 flex w-full"> <h2 className="w-full">{title}</h2> - {onClose && <Button rounded variant="ghost" iconComponent={<Close />} onClick={() => onClose()} />} + <Button rounded variant="ghost" iconComponent={<Close />} onClick={() => onClose()} /> </div> ); }; export const FormHBar = () => <div className="divider m-0"></div>; export const FormControl = ({ children }: PropsWithChildren) => <div className="form-control px-5">{children}</div>; -export const FormActions = (props: { onClose?: () => void; onApply?: () => void }) => ( - <> - {props.onClose && ( - <div className="grid grid-cols-2 px-5 gap-2 mb-2"> - <Button - type="secondary" - variant="outline" - label="Cancel" - onClick={(e) => { - e.preventDefault(); - if (props.onClose) props.onClose(); - }} - /> - <Button - type="primary" - label="Apply" - onClick={() => { - if (props.onApply) props.onApply(); - }} - className="flex-grow" - /> - </div> - )} - {!props.onClose && ( - <Button - type="primary" - label="Apply" - onClick={() => { - if (props.onApply) props.onApply(); - }} - className="w-full" - /> - )} - </> +export const FormActions = (props: { onClose: () => void }) => ( + <div className="grid grid-cols-2 px-5 gap-2 mb-2"> + <Button + type="secondary" + variant="outline" + label="Cancel" + onClick={(e) => { + e.preventDefault(); + props.onClose(); + }} + /> + <Button type="primary" label="Apply" onClick={() => {}} /> + </div> ); diff --git a/libs/shared/lib/components/inputs/index.tsx b/libs/shared/lib/components/inputs/index.tsx index cb8c073e78eb127b05c52f673136187f1d54dc5f..02cf813b78680385a8879fb6ba9ae812beadb3a7 100644 --- a/libs/shared/lib/components/inputs/index.tsx +++ b/libs/shared/lib/components/inputs/index.tsx @@ -26,7 +26,6 @@ type TextProps = { visible?: boolean; disabled?: boolean; tooltip?: string; - inline?: boolean; info?: string; className?: string; validate?: (value: any) => boolean; @@ -36,7 +35,6 @@ type TextProps = { type NumberProps = { label: string; type: 'number'; - size?: 'xs' | 'sm' | 'md' | 'xl'; placeholder?: string; value: number; required?: boolean; @@ -45,12 +43,8 @@ type NumberProps = { disabled?: boolean; tooltip?: string; info?: string; - inline?: boolean; validate?: (value: any) => boolean; onChange?: (value: number) => void; - onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void; - max?: number; - min?: number; }; type CheckboxProps = { @@ -90,7 +84,6 @@ type DropdownProps = { tooltip?: string; onChange?: (value: string | number) => void; required?: boolean; - inline?: boolean; info?: string; disabled?: boolean; }; @@ -158,21 +151,16 @@ export const TextInput = ({ validate, disabled = false, onChange, - inline = false, tooltip, info, className, }: TextProps) => { const [isValid, setIsValid] = React.useState<boolean>(true); - if (!tooltip && inline) tooltip = label; return ( - <div - data-tip={tooltip || null} - className={'form-control w-full' + (inline ? ' grid grid-cols-2 items-center' : '') + (tooltip ? ' tooltip' : '')} - > + <div data-tip={tooltip || null} className={'form-control w-full' + (tooltip ? ' tooltip' : '')}> {label && ( - <label className="label p-0"> + <label className="label"> <span className={`text-sm font-medium text-secondary-700 ${required && "after:content-['*'] after:ml-0.5 after:text-danger-500"}`} > @@ -209,9 +197,7 @@ export const TextInput = ({ export const NumberInput = ({ label, placeholder, - size = 'md', value = 0, - inline = false, required = false, visible = true, errorText, @@ -220,22 +206,13 @@ export const NumberInput = ({ onChange, tooltip, info, - onKeyDown, - max, - min, }: NumberProps) => { const [isValid, setIsValid] = React.useState<boolean>(true); - if (!tooltip && inline) tooltip = label; return ( - <div - data-tip={tooltip || null} - className={styles['input'] + ' form-control w-full' + (inline ? ' grid grid-cols-2 items-center' : '') + (tooltip ? ' tooltip' : '')} - > - <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"}`} - > + <div data-tip={tooltip || null} className={'form-control w-full' + (tooltip ? ' tooltip' : '')}> + <label className="label"> + <span className={`text-sm 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>} @@ -244,7 +221,7 @@ export const NumberInput = ({ <input type="number" placeholder={placeholder} - className={`${size} bg-light border border-secondary-300 placeholder-secondary-400 focus:outline-none block w-full sm:text-sm focus:ring-1 ${ + className={`px-3 py-2 bg-light border border-secondary-300 placeholder-secondary-400 focus:outline-none block w-full sm:text-sm focus:ring-1 ${ isValid ? '' : 'input-error' }`} value={value.toString()} @@ -258,9 +235,6 @@ export const NumberInput = ({ }} required={required} disabled={disabled} - onKeyDown={onKeyDown} - max={max} - min={min} /> </div> ); @@ -269,7 +243,7 @@ export const NumberInput = ({ export const RadioInput = ({ label, value, options, onChange, tooltip }: RadioProps) => { return ( <div data-tip={tooltip || null} className={tooltip ? 'tooltip' : ''}> - <label className="label p-0"> + <label className="label"> <span className="label-text">{label}</span> </label> {options.map((option, index) => ( @@ -296,7 +270,7 @@ export const CheckboxInput = ({ label, value, options, onChange, tooltip }: Chec return ( <div data-tip={tooltip || null} className={tooltip ? 'tooltip' : ''}> {label && ( - <label className="label p-0"> + <label className="label"> <span className="label-text">{label}</span> </label> )} @@ -360,14 +334,12 @@ export const DropDownInput = ({ required = false, tooltip, size = 'sm', - inline = false, disabled = false, info, }: DropdownProps) => { const dropdownRef = React.useRef<HTMLDivElement>(null); const [isDropdownOpen, setIsDropdownOpen] = React.useState<boolean>(false); - if (!tooltip && inline) tooltip = label; React.useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { @@ -381,18 +353,18 @@ export const DropDownInput = ({ }, [isDropdownOpen]); return ( - <div data-tip={tooltip || null} className={'w-full' + (inline ? ' grid grid-cols-2 items-center' : '') + (tooltip ? ' tooltip' : '')}> + <div data-tip={tooltip || null} className={'w-full' + (tooltip ? ' tooltip' : '')}> {label && ( - <label className="label p-0"> + <label className="label"> <span - className={`text-sm text-left truncate font-medium text-secondary-700 ${required && "after:content-['*'] after:ml-0.5 after:text-danger-500"}`} + className={`text-sm font-medium text-secondary-700 ${required && "after:content-['*'] after:ml-0.5 after:text-danger-500"}`} > {label} </span> {info && <Info tooltip={info} />} </label> )} - <DropdownContainer className="w-full right-0 left-auto" ref={dropdownRef}> + <DropdownContainer className="w-full" ref={dropdownRef}> <DropdownButton title={overrideRender || value} size={size} diff --git a/libs/shared/lib/components/inputs/inputs.module.scss b/libs/shared/lib/components/inputs/inputs.module.scss index f1733a0919d7f410ee4fd8d66d0dacce259437de..32f8e811dc08b054d619c2cb78845ad0ecf90cb8 100644 --- a/libs/shared/lib/components/inputs/inputs.module.scss +++ b/libs/shared/lib/components/inputs/inputs.module.scss @@ -21,26 +21,3 @@ } } } - -.input { - input[class~='xs'] { - @apply py-0; - @apply px-0; - } - input[class~='sm'] { - @apply py-1; - @apply px-1; - } - input[class~='md'] { - @apply py-2; - @apply px-3; - } - input[class~='md'] { - @apply py-2; - @apply px-3; - } - input[class~='xl'] { - @apply py-3; - @apply px-5; - } -} diff --git a/libs/shared/lib/components/inputs/inputs.module.scss.d.ts b/libs/shared/lib/components/inputs/inputs.module.scss.d.ts index 8c532fb8ca4715a6256da080809cbbb942944317..c7907e61e52e19fff7e5ff9b9d08a1d1756bc1ae 100644 --- a/libs/shared/lib/components/inputs/inputs.module.scss.d.ts +++ b/libs/shared/lib/components/inputs/inputs.module.scss.d.ts @@ -1,5 +1,4 @@ declare const classNames: { readonly slider: 'slider'; - readonly input: 'input'; }; export = classNames; diff --git a/libs/shared/lib/components/layout/Panel.tsx b/libs/shared/lib/components/layout/Panel.tsx index 34556eac9b25620e4df799d8b3a4fc6667476c71..191afbf50c19ce21a8eef874c4314db50e9337b9 100644 --- a/libs/shared/lib/components/layout/Panel.tsx +++ b/libs/shared/lib/components/layout/Panel.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react'; import { ControlContainer } from '..'; export type Panel = { - title: string | React.ReactNode; - tooltips?: React.ReactNode; + title: string; + tooltips: React.ReactNode; children: React.ReactNode; }; @@ -14,11 +14,9 @@ export function Panel(props: Panel) { <div className="flex items-center"> <h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">{props.title}</h1> </div> - {props.tooltips && ( - <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex"> - <ControlContainer>{props.tooltips}</ControlContainer> - </div> - )} + <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex"> + <ControlContainer>{props.tooltips}</ControlContainer> + </div> </div> {props.children} </div> diff --git a/libs/shared/lib/components/layout/Resizable.tsx b/libs/shared/lib/components/layout/Resizable.tsx index 061620796f8d15ca9f5b008974bb401d2c13dfd0..9b88169e611817c6a22b0de5c3f85c08559eb2b2 100644 --- a/libs/shared/lib/components/layout/Resizable.tsx +++ b/libs/shared/lib/components/layout/Resizable.tsx @@ -9,7 +9,6 @@ type Props = { defaultProportion?: number; classNameLeft?: string; classNameRight?: string; - maxProportion?: number; }; function convertRemToPixels(rem: number) { @@ -25,7 +24,6 @@ export const Resizable = ({ defaultProportion, classNameLeft, classNameRight, - maxProportion, ...props }: Props) => { const ref = useRef<HTMLDivElement>(null); @@ -83,13 +81,11 @@ export const Resizable = ({ if (horizontal) { const newFirstSize = Math.max(minSizeX, relativeX); - if (maxProportion && newFirstSize / rect.width > maxProportion) return; setFirstSize(newFirstSize); setSecondSize(Math.max(minSizeX, rect.width - relativeX)); setCurrentProportion(newFirstSize / rect.width); } else { const newFirstSize = Math.max(minSizeY, relativeY); - if (maxProportion && newFirstSize / rect.height > maxProportion) return; setFirstSize(newFirstSize); setSecondSize(Math.max(minSizeY, rect.height - relativeY)); setCurrentProportion(newFirstSize / rect.height); diff --git a/libs/shared/lib/components/tabs/Tab.tsx b/libs/shared/lib/components/tabs/Tab.tsx deleted file mode 100644 index 855da1029f65bc4c47c461c59624ae4f3226c269..0000000000000000000000000000000000000000 --- a/libs/shared/lib/components/tabs/Tab.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { MouseEventHandler } from 'react'; - -export const Tabs = (props: { children: React.ReactNode }) => { - return ( - <div className="flex items-stretch divide-x divide-secondary-200 border-x border-secondary-200 overflow-x-auto -my-px"> - {props.children} - </div> - ); -}; - -export const Tab = ( - props: React.ButtonHTMLAttributes<HTMLDivElement> & { - active: boolean; - children: React.ReactNode; - text: string; - key?: string; - }, -) => { - return ( - <div - key={props.key} - className={`flex items-center pl-2 pr-1 gap-1 cursor-pointer relative border-secondary-200 before:content-[''] before:absolute before:left-0 before:bottom-0 before:h-[2px] before:w-full ${props.active ? 'before:bg-primary-500' : 'before:bg-transparent hover:before:bg-secondary-300 hover:bg-secondary-200'}`} - {...props} - > - <p className={`text-xs text-secondary-500 font-semibold ${props.active && 'text-secondary-950'}`}>{props.text}</p> - {props.children} - </div> - ); -}; diff --git a/libs/shared/lib/components/tabs/index.ts b/libs/shared/lib/components/tabs/index.ts deleted file mode 100644 index 2e2986cb59f1c24fb72c71a72d7d523dc3b41a13..0000000000000000000000000000000000000000 --- a/libs/shared/lib/components/tabs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Tab'; diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts index 3cf9e53fae3640d6b699d6def5d892bd86d95c20..247785872841be2af1eea649f6fbb9bb353b1e65 100644 --- a/libs/shared/lib/data-access/store/hooks.ts +++ b/libs/shared/lib/data-access/store/hooks.ts @@ -29,7 +29,7 @@ import { AllLayoutAlgorithms } from '../../graph-layout'; import { QueryGraphEdgeHandle, QueryMultiGraph } from '../../querybuilder'; import { SchemaGraph } from '../../schema'; import { GraphMetadata } from '../statistics'; -import { SelectionStateI, FocusStateI, focusState, selectionState } from './interactionSlice'; +import { SelectionStateI, selectionState } from './interactionSlice'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch; @@ -73,4 +73,3 @@ export const useVisualization: () => VisState = () => useAppSelector(visualizati // Interaction Slices export const useSelection: () => SelectionStateI | undefined = () => useAppSelector(selectionState); -export const useFocus: () => FocusStateI | undefined = () => useAppSelector(focusState); diff --git a/libs/shared/lib/data-access/store/interactionSlice.ts b/libs/shared/lib/data-access/store/interactionSlice.ts index 145cd7a327f6238453883a886a478d249e416c34..4a7434768c787722e3ee4a712e7c167e24b7ff90 100644 --- a/libs/shared/lib/data-access/store/interactionSlice.ts +++ b/libs/shared/lib/data-access/store/interactionSlice.ts @@ -17,21 +17,15 @@ export type SelectionStateI = content: Edge[]; }; -export type FocusStateI = { - focusType: 'schema' | 'query' | 'visualization'; -}; - // Define the initial state using that type export type InteractionsType = { hover?: HoverStateI; selection?: SelectionStateI; - focus?: FocusStateI; }; export const initialState: InteractionsType = { hover: undefined, selection: undefined, - focus: undefined, }; export const interactionSlice = createSlice({ @@ -56,16 +50,12 @@ export const interactionSlice = createSlice({ }; } }, - resultSetFocus: (state, action: PayloadAction<FocusStateI | undefined>) => { - state.focus = action.payload; - }, }, }); -export const { addHover, unSelect, resultSetSelection, resultSetFocus } = interactionSlice.actions; +export const { addHover, unSelect, resultSetSelection } = interactionSlice.actions; export const interactionState = (state: RootState) => state.interaction; export const selectionState = (state: RootState) => state.interaction.selection; -export const focusState = (state: RootState) => state.interaction.focus; export default interactionSlice.reducer; diff --git a/libs/shared/lib/index.ts b/libs/shared/lib/index.ts index eae28d420a2be24ac9748a00818a3561962f8fe0..82d44d1eaf7386cfd4bf346169226922a997a926 100644 --- a/libs/shared/lib/index.ts +++ b/libs/shared/lib/index.ts @@ -4,4 +4,3 @@ export * from './graph-layout'; export * from './querybuilder'; export * from './schema'; export * from './vis'; -export * from './inspector'; diff --git a/libs/shared/lib/inspector/ConnectionInspector.tsx b/libs/shared/lib/inspector/ConnectionInspector.tsx deleted file mode 100644 index 16988b41ffdf72845bd5705fc3be7294d16291f2..0000000000000000000000000000000000000000 --- a/libs/shared/lib/inspector/ConnectionInspector.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { useSessionCache } from '../data-access'; - -export function ConnectionInspector() { - const session = useSessionCache(); - - return ( - <div> - {session && session.currentSaveState && ( - <div className="flex flex-col p-4 border-b"> - <span className="text-sm font-bold">Connection details</span> - <span className="text-xs font-semibold">Name</span> - <span className="text-xs">{session.saveStates[session.currentSaveState].name}</span> - <span className="text-xs font-semibold">Database</span> - <span className="text-xs">{session.saveStates[session.currentSaveState].db.internalDatabaseName}</span> - <span className="text-xs font-semibold">Protocol</span> - <span className="text-xs">{session.saveStates[session.currentSaveState].db.protocol}</span> - <span className="text-xs font-semibold">Hostname</span> - <span className="text-xs">{session.saveStates[session.currentSaveState].db.url}</span> - <span className="text-xs font-semibold">Port</span> - <span className="text-xs">{session.saveStates[session.currentSaveState].db.port}</span> - </div> - )} - </div> - ); -} diff --git a/libs/shared/lib/inspector/InspectorPanel.tsx b/libs/shared/lib/inspector/InspectorPanel.tsx deleted file mode 100644 index 703af6cee516cd7415ad83b0fcb00f2a161df3e0..0000000000000000000000000000000000000000 --- a/libs/shared/lib/inspector/InspectorPanel.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useMemo } from 'react'; -import { Button, Panel } from '../components'; -import { useFocus, useSelection } from '../data-access'; -import { resultSetFocus } from '../data-access/store/interactionSlice'; -import { useDispatch } from 'react-redux'; -import { ConnectionInspector } from './ConnectionInspector'; -import { VisualizationConfigPanel } from '../vis/components/config/panel'; -import { SelectionConfig } from '../vis/components/config/SelectionConfig'; -import { SchemaDialog } from '../schema/panel/SchemaSettings'; -import { QuerySettings } from '../querybuilder/panel/querysidepanel/QuerySettings'; - -export function InspectorPanel(props: { children?: React.ReactNode }) { - const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION; - const selection = useSelection(); - const focus = useFocus(); - const dispatch = useDispatch(); - - const inspector = useMemo(() => { - if (selection) return <SelectionConfig />; - if (!focus) return <ConnectionInspector />; - if (focus.focusType === 'visualization') return <VisualizationConfigPanel />; - else if (focus.focusType === 'schema') return <SchemaDialog />; - else if (focus.focusType === 'query') return <QuerySettings />; - return null; - }, [focus, selection]); - - return ( - <Panel - title={ - <div className="flex flex-row gap-0.5 items-center align-middle"> - <Button variant="ghost" size="2xs" className="hover:underline p-0" onClick={() => dispatch(resultSetFocus(undefined))}> - GP - </Button> - {focus && ( - <> - <span className="pb-0.5">{'>'}</span> - <Button variant="ghost" size="2xs" className="hover:underline p-0"> - {focus.focusType} - </Button> - </> - )} - {selection && ( - <> - <span className="pb-0.5">{'>'}</span> - <Button variant="ghost" size="2xs" className="hover:underline p-0"> - Selection - </Button> - </> - )} - </div> - } - > - {inspector} - - <div className="flex flex-col w-full"> - {buildInfo === 'dev' && ( - <div className="mt-auto p-2 bg-light"> - <Button - type="primary" - variant="outline" - size="xs" - label="Report an issue" - onClick={() => - window.open( - 'https://app.asana.com/-/login?u=https%3A%2F%2Fform.asana.com%2F%3Fk%3D2QEC88Dl7ETs2wYYWjkMXg%26d%3D1206648675960041&error=01', - '_blank', - ) - } - className="block w-full" - /> - </div> - )} - </div> - </Panel> - ); -} diff --git a/libs/shared/lib/inspector/InspectorTab.tsx b/libs/shared/lib/inspector/InspectorTab.tsx deleted file mode 100644 index b2f11a384c48ab67aebe30ede6a785fed336b223..0000000000000000000000000000000000000000 --- a/libs/shared/lib/inspector/InspectorTab.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { Button } from '../components'; - -export function InspectorTab(props: { children: React.ReactNode }) { - return <div className="flex flex-col w-full"></div>; -} diff --git a/libs/shared/lib/inspector/index.ts b/libs/shared/lib/inspector/index.ts deleted file mode 100644 index 83f3c61a604d072287c482dcc5efa60fed85d155..0000000000000000000000000000000000000000 --- a/libs/shared/lib/inspector/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './InspectorPanel'; -export * from './InspectorTab'; diff --git a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx index f5b3872b7ba26e7313c7f7f883f33166ae81c331..5c3b9a459401e449d5e554015481ab31c2796669 100644 --- a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx @@ -38,10 +38,10 @@ import styles from './querybuilder.module.scss'; import { QueryBuilderLogicPillsPanel } from './querysidepanel/queryBuilderLogicPillsPanel'; import { QueryBuilderRelatedNodesPanel } from './querysidepanel/queryBuilderRelatedNodesPanel'; import { QueryMLDialog } from './querysidepanel/queryMLDialog'; +import { QuerySettingsDialog } from './querysidepanel/querySettingsDialog'; import { ConnectingNodeDataI } from './utils/connectorDrop'; import { CameraAlt, Cached, Difference, ImportExport, Lightbulb, Settings, Fullscreen, Delete } from '@mui/icons-material'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; -import { resultSetFocus } from '../../data-access/store/interactionSlice'; export type QueryBuilderProps = { onRunQuery?: () => void; @@ -428,15 +428,12 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { }; useEffect(() => { - try { - applyLayout(); - } catch (e) { - console.error(e); - } + applyLayout(); }, [queryBuilderSettings]); return ( <div ref={reactFlowWrapper} className="h-full w-full flex flex-col"> + <QuerySettingsDialog open={toggleSettings === 'settings'} onClose={() => setToggleSettings(undefined)} /> <QueryMLDialog open={toggleSettings === 'ml'} onClose={() => setToggleSettings(undefined)} /> <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"> @@ -626,7 +623,6 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { nodeTypes={nodeTypes} edgeTypes={edgeTypes} connectionLineComponent={ConnectionDragLine} - onMouseDownCapture={() => dispatch(resultSetFocus({ focusType: 'query' }))} // connectionMode={ConnectionMode.Loose} onInit={(reactFlowInstance) => { reactFlowInstanceRef.current = reactFlowInstance; diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/QuerySettings.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/QuerySettings.tsx deleted file mode 100644 index d3b36d1d94ce98c95bc4f859483c1a596bdc259d..0000000000000000000000000000000000000000 --- a/libs/shared/lib/querybuilder/panel/querysidepanel/QuerySettings.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useEffect } from 'react'; -import React from 'react'; -import { useAppDispatch, useQuerybuilderSettings } from '../../../data-access'; -import { QueryBuilderSettings, setQuerybuilderSettings } from '../../../data-access/store/querybuilderSlice'; -import { addWarning } from '../../../data-access/store/configSlice'; -import { FormActions, FormBody, FormCard, FormControl, FormDiv, FormHBar, FormTitle } from '../../../components/forms'; -import { Layouts } from '@graphpolaris/shared/lib/graph-layout'; -import { Input } from '@graphpolaris/shared/lib/components/inputs'; - -export const QuerySettings = React.forwardRef<HTMLDivElement, {}>((props, ref) => { - const qb = useQuerybuilderSettings(); - const dispatch = useAppDispatch(); - const [state, setState] = React.useState<QueryBuilderSettings>(qb); - - useEffect(() => { - setState(qb); - }, [qb]); - - function submit() { - if (state.depth.min < 0) { - dispatch(addWarning('The minimum depth cannot be smaller than 0')); - } else if (state.depth.max > 99) { - dispatch(addWarning('The maximum depth cannot be larger than 99')); - } else if (state.depth.min > state.depth.max) { - dispatch(addWarning('The minimum depth cannot be larger than the maximum depth')); - } else { - dispatch(setQuerybuilderSettings(state)); - } - } - - return ( - <div className="flex flex-col w-full gap-2 px-4 py-2"> - <span className="text-xs font-bold">Query Settings</span> - <Input - type="boolean" - value={state.autocompleteRelation} - label="Autocomplete Edges" - tooltip="When enabled, if you drag a relationship to the query, the query builder will automatically add the entity nodes it is connected to." - onChange={(value: boolean) => { - 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" - size="sm" - inline - value={state.depth.min} - onChange={(e) => setState({ ...state, depth: { min: e, max: state.depth.max } })} - placeholder="0" - min={0} - max={state.depth.max} - onKeyDown={(e) => { - if (e.key === 'Enter') { - submit(); - } - }} - /> - <Input - type="number" - label="Max Depth Default" - size="sm" - inline - value={state.depth.max} - onChange={(e) => setState({ ...state, depth: { max: e, min: state.depth.min } })} - placeholder="0" - min={state.depth.min} - max={99} - onKeyDown={(e) => { - if (e.key === 'Enter') { - submit(); - } - }} - /> - - <Input - type="dropdown" - inline - label="Default Layout" - value={state.layout} - onChange={(e) => setState({ ...state, layout: e as any })} - options={['manual', ...Object.entries(Layouts).map(([k, v]) => v)]} - /> - - <FormActions - onApply={() => { - submit(); - }} - /> - </div> - ); -}); diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..81e7567c16a9e0e10a195e54f740f7d3feb030c2 --- /dev/null +++ b/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx @@ -0,0 +1,145 @@ +import { useEffect } from 'react'; +import { DialogProps } from '../../../components/layout/Dialog'; +import React from 'react'; +import { useAppDispatch, useQuerybuilderSettings } from '../../../data-access'; +import { QueryBuilderSettings, setQuerybuilderSettings } from '../../../data-access/store/querybuilderSlice'; +import { addWarning } from '../../../data-access/store/configSlice'; +import { FormActions, FormBody, FormCard, FormControl, FormDiv, FormHBar, FormTitle } from '../../../components/forms'; +import { Layouts } from '@graphpolaris/shared/lib/graph-layout'; +import { Input } from '@graphpolaris/shared/lib/components/inputs'; + +type QuerySettingsDialogProps = DialogProps; + +export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, QuerySettingsDialogProps>((props, ref) => { + const qb = useQuerybuilderSettings(); + const dispatch = useAppDispatch(); + const [state, setState] = React.useState<QueryBuilderSettings>(qb); + + useEffect(() => { + setState(qb); + }, [qb, props.open]); + + function submit() { + if (state.depth.min < 0) { + dispatch(addWarning('The minimum depth cannot be smaller than 0')); + } else if (state.depth.max > 99) { + dispatch(addWarning('The maximum depth cannot be larger than 99')); + } else if (state.depth.min > state.depth.max) { + dispatch(addWarning('The minimum depth cannot be larger than the maximum depth')); + } else { + dispatch(setQuerybuilderSettings(state)); + props.onClose(); + } + } + + return ( + <> + {props.open && ( + <FormDiv hAnchor="right" ref={ref}> + <FormCard> + <FormBody + onSubmit={(e) => { + e.preventDefault(); + submit(); + }} + > + <FormTitle title="Query Settings" onClose={props.onClose} /> + <FormHBar /> + <FormControl> + <Input + type="boolean" + value={state.autocompleteRelation} + label="Autocomplete Edges" + tooltip="When enabled, if you drag a relationship to the query, the query builder will automatically add the entity nodes it is connected to." + onChange={(value: boolean) => { + setState({ ...state, autocompleteRelation: value as any }); + }} + /> + </FormControl> + + <FormHBar /> + <div className="form-control px-5"> + <label className="label"> + <span className="label-text">Limit - Max number of results</span> + </label> + <input + type="number" + className="input input-sm input-bordered" + placeholder="500" + value={state.limit} + onChange={(e) => setState({ ...state, limit: parseInt(e.target.value) })} + /> + </div> + <FormHBar /> + <div className="form-control px-5 flex flex-row gap-3"> + <div className=""> + <label className="label"> + <span className="label-text">Min Depth Default</span> + </label> + <input + type="number" + className="input input-sm input-bordered w-full" + placeholder="0" + min={0} + max={state.depth.max} + value={state.depth.min} + onChange={(e) => setState({ ...state, depth: { min: parseInt(e.target.value), max: state.depth.max } })} + onKeyDown={(e) => { + if (e.key === 'Enter') { + submit(); + } + }} + /> + </div> + <div className=""> + <label className="label"> + <span className="label-text">Max Depth Default</span> + </label> + <input + type="number" + className="input input-sm input-bordered w-full" + placeholder="0" + min={state.depth.min} + max={99} + value={state.depth.max} + onChange={(e) => setState({ ...state, depth: { max: parseInt(e.target.value), min: state.depth.min } })} + onKeyDown={(e) => { + if (e.key === 'Enter') { + submit(); + } + }} + /> + </div> + </div> + <FormHBar /> + <div className="form-control px-5 "> + <label className="label"> + <span className="label-text">Layout Type</span> + </label> + <select + className="select select-primary select-sm " + value={state.layout} + onChange={(e) => { + setState({ ...state, layout: e.target.value as any }); + }} + > + <option className="option" value={'manual'}> + Manual + </option> + {Object.entries(Layouts).map(([k, v]) => ( + <option className="option" value={v} key={v}> + {k} + </option> + ))} + </select> + </div> + <FormHBar /> + + <FormActions onClose={props.onClose} /> + </FormBody> + </FormCard> + </FormDiv> + )} + </> + ); +}); diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx index bb72abda7f9aa69f05b92183d6daf2bb240cd75c..d96f52f5ed44003bad181c092d53a2ac1d174b7e 100644 --- a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx +++ b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx @@ -46,7 +46,7 @@ export const PillDropdown = (props: PillDropdownProps) => { return ( <div - className={'border-[1px] border-secondary-200 divide-y divide-secondary-200 !z-50'} + className={'border-[1px] border-secondary-200 divide-y divide-secondary-200'} onMouseEnter={(e) => { if (props.onMouseEnterDropdown) props.onMouseEnterDropdown(e); }} diff --git a/libs/shared/lib/schema/panel/Schema.tsx b/libs/shared/lib/schema/panel/Schema.tsx index bb9a9c409ab827f3167975889e4631a4949a5431..2e29acd2675b01f1da981f15b0e82f35f4c60aa1 100644 --- a/libs/shared/lib/schema/panel/Schema.tsx +++ b/libs/shared/lib/schema/panel/Schema.tsx @@ -9,14 +9,12 @@ import { NodeEdge } from '../pills/edges/node-edge'; import { SelfEdge } from '../pills/edges/self-edge'; import { SchemaEntityPill } from '../pills/nodes/entity/SchemaEntityPill'; import { SchemaRelationPill } from '../pills/nodes/relation/SchemaRelationPill'; -import { SchemaDialog } from './SchemaSettings'; +import { SchemaDialog } from './SchemaDialog'; import { ContentCopy, FitScreen, Fullscreen, KeyboardArrowDown, KeyboardArrowRight, Remove } from '@mui/icons-material'; import { AlgorithmToLayoutProvider, AllLayoutAlgorithms, LayoutFactory } from '../../graph-layout'; import { ConnectionLine, ConnectionDragLine } from '../../querybuilder'; import { schemaExpandRelation, schemaGraphology2Reactflow } from '../schema-utils'; import { Panel, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components'; -import { resultSetFocus } from '../../data-access/store/interactionSlice'; -import { useDispatch } from 'react-redux'; interface Props { content?: string; @@ -44,7 +42,6 @@ const edgeTypes = { export const Schema = (props: Props) => { const settings = useSchemaSettings(); const searchResults = useSearchResultSchema(); - const dispatch = useDispatch(); const [toggleSchemaSettings, setToggleSchemaSettings] = useState(false); const [nodes, setNodes, onNodesChange] = useNodesState([] as Node[]); const [edges, setEdges, onEdgesChange] = useEdgesState([] as Edge[]); @@ -176,7 +173,6 @@ export const Schema = (props: Props) => { connectionLineComponent={ConnectionDragLine} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} - onMouseDownCapture={() => dispatch(resultSetFocus({ focusType: 'schema' }))} nodes={nodes} edges={edges} onInit={(reactFlowInstance) => { @@ -187,7 +183,7 @@ export const Schema = (props: Props) => { ></ReactFlow> </ReactFlowProvider> )} - {/* <div> + <div> <div className="w-full py-0 px-2 bg-secondary-50 cursor-pointer border-y flex items-center gap-1" onClick={() => setExpanded(!expanded)} @@ -205,7 +201,7 @@ export const Schema = (props: Props) => { <SchemaDialog open={toggleSchemaSettings} onClose={() => setToggleSchemaSettings(false)} /> </div> )} - </div> */} + </div> </div> </Panel> ); diff --git a/libs/shared/lib/schema/panel/SchemaDialog.tsx b/libs/shared/lib/schema/panel/SchemaDialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..492e7be6e2d75c307a4b4ffa755c8824eabb7638 --- /dev/null +++ b/libs/shared/lib/schema/panel/SchemaDialog.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; +import { Dialog, DialogProps } from '../../components/layout/Dialog'; +import React from 'react'; +import { useAppDispatch, useSchemaSettings } from '../../data-access'; +import { SchemaConnectionTypes, SchemaSettings, schemaConnectionTypeArray, setSchemaSettings } from '../../data-access/store/schemaSlice'; +import { FormActions, FormBody, FormCard, FormControl, FormHBar, FormTitle, FormDiv } from '../../components/forms'; +import { Layouts } from '../../graph-layout'; +import { Input } from '../../components/inputs'; + +export const SchemaDialog = (props: DialogProps) => { + const settings = useSchemaSettings(); + const dispatch = useAppDispatch(); + const [state, setState] = React.useState<SchemaSettings>(settings); + + useEffect(() => { + setState(settings); + }, [settings, props.open]); + + function submit() { + dispatch(setSchemaSettings(state)); + props.onClose(); + } + + return ( + <form + className="w-full" + onSubmit={(e) => { + e.preventDefault(); + submit(); + }} + > + <FormControl> + <Input + type="dropdown" + label="Type of Connection" + value={state.connectionType} + options={schemaConnectionTypeArray} + onChange={(value: string | number) => { + setState({ ...state, connectionType: value as SchemaConnectionTypes }); + }} + /> + </FormControl> + <FormHBar /> + <FormControl> + <Input + type="boolean" + value={state.animatedEdges} + label="Animated Edges" + onChange={(value: boolean) => { + setState({ ...state, animatedEdges: value as any }); + }} + /> + </FormControl> + <FormHBar /> + <FormControl> + <Input + type="dropdown" + label="Layout Type" + value={state.layoutName} + options={Object.values(Layouts)} + onChange={(value: string | number) => { + setState({ ...state, layoutName: value as any }); + }} + /> + </FormControl> + <FormHBar /> + + <FormActions onClose={props.onClose} /> + </form> + ); +}; diff --git a/libs/shared/lib/schema/panel/SchemaSettings.tsx b/libs/shared/lib/schema/panel/SchemaSettings.tsx deleted file mode 100644 index 270cb50d47b24b1fc7d47cd454f51bfa5edbf32e..0000000000000000000000000000000000000000 --- a/libs/shared/lib/schema/panel/SchemaSettings.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Dialog, DialogProps } from '../../components/layout/Dialog'; -import React from 'react'; -import { useAppDispatch, useSchemaSettings } from '../../data-access'; -import { SchemaConnectionTypes, SchemaSettings, schemaConnectionTypeArray, setSchemaSettings } from '../../data-access/store/schemaSlice'; -import { FormActions, FormBody, FormCard, FormControl, FormHBar, FormTitle, FormDiv } from '../../components/forms'; -import { Layouts } from '../../graph-layout'; -import { Input } from '../../components/inputs'; - -export const SchemaDialog = () => { - const settings = useSchemaSettings(); - const dispatch = useAppDispatch(); - - return ( - <div className="flex flex-col w-full gap-2 px-4 py-2"> - <span className="text-xs font-bold">Schema Settings</span> - <Input - type="boolean" - value={settings.animatedEdges} - label="Animated Edges" - onChange={(value: boolean) => { - dispatch(setSchemaSettings({ ...settings, animatedEdges: value as any })); - }} - /> - <Input - type="dropdown" - label="Type of Connection" - inline - size="sm" - value={settings.connectionType} - options={schemaConnectionTypeArray} - onChange={(value: string | number) => { - dispatch(setSchemaSettings({ ...settings, connectionType: value as SchemaConnectionTypes })); - }} - /> - <Input - type="dropdown" - label="Layout Type" - inline - size="sm" - value={settings.layoutName} - options={Object.values(Layouts)} - onChange={(value: string | number) => { - dispatch(setSchemaSettings({ ...settings, layoutName: value as any })); - }} - /> - </div> - ); -}; diff --git a/libs/shared/lib/vis/components/bar.tsx b/libs/shared/lib/vis/components/bar.tsx index 76cca247925f555cef1e4da560865bb60a9488ba..269d25f5753df057a0edcc952180edf687bef741 100644 --- a/libs/shared/lib/vis/components/bar.tsx +++ b/libs/shared/lib/vis/components/bar.tsx @@ -6,7 +6,6 @@ import { Add, Close, Fullscreen } from '@mui/icons-material'; import { ControlContainer } from '../../components/controls'; import { Visualizations } from '../manager'; import { VisualizationManagerType } from '../manager'; -import { Tabs, Tab } from '../../components/tabs'; type Props = { manager: VisualizationManagerType; @@ -65,20 +64,20 @@ export default function VisualizationBar({ manager, fullSize }: Props) { </DropdownMenu.Portal> </DropdownMenu.Root> </div> - <Tabs> + <div className="flex items-stretch divide-x divide-secondary-200 border-x border-secondary-200 overflow-x-auto -my-px"> {manager.tabs.map((visId: string) => { const isActive = manager.activeVisualization === visId; return ( - <Tab + <div key={visId} - active={isActive} - text={visId} + className={`flex items-center pl-2 pr-1 gap-1 cursor-pointer relative border-secondary-200 before:content-[''] before:absolute before:left-0 before:bottom-0 before:h-[2px] before:w-full ${isActive && 'before:bg-primary-500'} ${!isActive && 'before:bg-transparent hover:before:bg-secondary-300 hover:bg-secondary-200'}`} onClick={() => manager.changeActive(visId)} onDragStart={(e) => handleDragStart(e, visId)} onDragOver={(e) => handleDragOver(e)} onDrop={(e) => handleDrop(e, visId)} draggable > + <p className={`text-xs text-secondary-500 font-semibold ${isActive && 'text-secondary-950'}`}>{visId}</p> <Button type="secondary" variant="ghost" @@ -90,10 +89,10 @@ export default function VisualizationBar({ manager, fullSize }: Props) { manager.deleteVisualization(visId); }} /> - </Tab> + </div> ); })} - </Tabs> + </div> <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex"> <ControlContainer> <TooltipProvider delayDuration={0}> diff --git a/libs/shared/lib/vis/components/config/ActiveVisualizationConfig.tsx b/libs/shared/lib/vis/components/config/ActiveVisualizationConfig.tsx index 88ed5de608d618f5e211a43b574d4b0c5fbd1e4f..fa36ceed168fc9e210965eab2bf9aad84491fcbd 100644 --- a/libs/shared/lib/vis/components/config/ActiveVisualizationConfig.tsx +++ b/libs/shared/lib/vis/components/config/ActiveVisualizationConfig.tsx @@ -1,5 +1,5 @@ import { Delete } from '@mui/icons-material'; -import { Button, Input, Panel } from '../../..'; +import { Button, Input } from '../../..'; import { VisualizationManagerType, VISUALIZATION_TYPES } from '../../manager'; import { SettingsHeader } from './components'; @@ -10,27 +10,29 @@ type Props = { export const ActiveVisualizationConfig = ({ manager }: Props) => { return ( <> - <div className="flex justify-between items-center px-4 py-2"> - <span className="text-xs font-bold">Visualization</span> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Delete />} - onClick={() => { - if (manager.activeVisualization) manager.deleteVisualization(manager.activeVisualization); - }} - /> - </div> - <div className="flex justify-between items-center px-4 py-1"> - <span className="text-xs font-normal">Type</span> - <div className="w-36"> - <Input type="dropdown" size="xs" options={VISUALIZATION_TYPES} value={manager.activeVisualization} onChange={() => {}} /> + <div className="border-b py-2"> + <div className="flex justify-between items-center px-4 py-2"> + <span className="text-xs font-bold">Visualization</span> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Delete />} + onClick={() => { + if (manager.activeVisualization) manager.deleteVisualization(manager.activeVisualization); + }} + /> + </div> + <div className="flex justify-between items-center px-4 py-1"> + <span className="text-xs font-normal">Type</span> + <div className="w-36"> + <Input type="dropdown" size="xs" options={VISUALIZATION_TYPES} value={manager.activeVisualization} onChange={() => {}} /> + </div> + </div> + <div className="flex justify-between items-center px-4 py-1"> + <span className="text-xs font-normal">Name</span> + <input type="text" className="border rouded text-xs w-36" value={manager.activeVisualization} onChange={() => {}} /> </div> - </div> - <div className="flex justify-between items-center px-4 py-1"> - <span className="text-xs font-normal">Name</span> - <input type="text" className="border rounded text-xs w-36" value={manager.activeVisualization} onChange={() => {}} /> </div> {manager.activeVisualization && ( <div className="border-b p-4 w-full"> diff --git a/libs/shared/lib/vis/components/config/SelectionConfig.tsx b/libs/shared/lib/vis/components/config/SelectionConfig.tsx index 08dcadd7736db9583d2aedfd89607b0dede8bba6..d790d651f713193df09b685592dc11c51f340b6f 100644 --- a/libs/shared/lib/vis/components/config/SelectionConfig.tsx +++ b/libs/shared/lib/vis/components/config/SelectionConfig.tsx @@ -1,16 +1,13 @@ import { SelectionStateI, unSelect } from '@graphpolaris/shared/lib/data-access/store/interactionSlice'; import { Delete } from '@mui/icons-material'; import { useDispatch } from 'react-redux'; -import { Button, EntityPill, useSelection } from '../../..'; +import { Button, EntityPill } from '../../..'; import { VISUALIZATION_TYPES } from '../../manager'; import { SettingsHeader } from './components'; -export const SelectionConfig = () => { - const selection = useSelection(); +export const SelectionConfig = (props: { selection: SelectionStateI }) => { const dispatch = useDispatch(); - if (!selection) return null; - return ( <div className="border-b py-2"> <div className="flex justify-between items-center px-4 py-2"> @@ -25,7 +22,7 @@ export const SelectionConfig = () => { }} /> </div> - {selection.content.map((item, index) => ( + {props.selection.content.map((item, index) => ( <> <div key={index + 'id'} className="flex justify-between items-center px-4 py-1 gap-1"> <span className="text-xs font-normal">ID</span> diff --git a/libs/shared/lib/vis/components/config/index.tsx b/libs/shared/lib/vis/components/config/index.tsx index 4bf7db1e41dccc34dd73fc88e0013137b85d19be..2f98579c75ad64611ab47810bdcfc20c8bb14bbe 100644 --- a/libs/shared/lib/vis/components/config/index.tsx +++ b/libs/shared/lib/vis/components/config/index.tsx @@ -1,2 +1,2 @@ -export { VisualizationConfigPanel as ConfigPanel } from './panel'; +export { ConfigPanel } from './panel'; export { SettingsContainer, SettingsHeader } from './components'; diff --git a/libs/shared/lib/vis/components/config/panel.tsx b/libs/shared/lib/vis/components/config/panel.tsx index 4f0bd9e03026cf14258e906a68617c846780719f..5aae132cdf385752a62621daa7bbad0a194ae762 100644 --- a/libs/shared/lib/vis/components/config/panel.tsx +++ b/libs/shared/lib/vis/components/config/panel.tsx @@ -1,18 +1,61 @@ import React from 'react'; import { Button } from '../../../components'; -import { VisualizationManager, VisualizationManagerType } from '../../manager'; +import { VisualizationManagerType } from '../../manager'; import { useSelection, useSessionCache } from '../../../data-access'; import { SelectionConfig } from './SelectionConfig'; import { ActiveVisualizationConfig } from './ActiveVisualizationConfig'; -type Props = {}; +type Props = { + manager: VisualizationManagerType; +}; -export function VisualizationConfigPanel({}: Props) { - const manager = VisualizationManager(); +export function ConfigPanel({ manager }: Props) { + const session = useSessionCache(); + const selection = useSelection(); + + const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION; return ( <div className="flex flex-col w-full"> - <ActiveVisualizationConfig manager={manager}></ActiveVisualizationConfig> + {!!selection && <SelectionConfig selection={selection} />} + {!selection && manager.activeVisualization && <ActiveVisualizationConfig manager={manager} />} + {!selection && !manager.activeVisualization && ( + <div> + {session && session.currentSaveState && ( + <div className="flex flex-col p-4 border-b"> + <span className="text-sm font-bold">Connection details</span> + <span className="text-xs font-semibold">Name</span> + <span className="text-xs">{session.saveStates[session.currentSaveState].name}</span> + <span className="text-xs font-semibold">Database</span> + <span className="text-xs">{session.saveStates[session.currentSaveState].db.internalDatabaseName}</span> + <span className="text-xs font-semibold">Protocol</span> + <span className="text-xs">{session.saveStates[session.currentSaveState].db.protocol}</span> + <span className="text-xs font-semibold">Hostname</span> + <span className="text-xs">{session.saveStates[session.currentSaveState].db.url}</span> + <span className="text-xs font-semibold">Port</span> + <span className="text-xs">{session.saveStates[session.currentSaveState].db.port}</span> + </div> + )} + </div> + )} + + {buildInfo === 'dev' && ( + <div className="mt-auto p-2 bg-light"> + <Button + type="primary" + variant="outline" + size="xs" + label="Report an issue" + onClick={() => + window.open( + 'https://app.asana.com/-/login?u=https%3A%2F%2Fform.asana.com%2F%3Fk%3D2QEC88Dl7ETs2wYYWjkMXg%26d%3D1206648675960041&error=01', + '_blank', + ) + } + className="block w-full" + /> + </div> + )} </div> ); } diff --git a/libs/shared/lib/vis/components/VisualizationPanel.tsx b/libs/shared/lib/vis/components/panel.tsx similarity index 66% rename from libs/shared/lib/vis/components/VisualizationPanel.tsx rename to libs/shared/lib/vis/components/panel.tsx index 61bcfb78eaefbce2f099d28041a83d5751aa3922..38a37bab4c119ddf3e8ca9893787c2bde2fb6cae 100644 --- a/libs/shared/lib/vis/components/VisualizationPanel.tsx +++ b/libs/shared/lib/vis/components/panel.tsx @@ -1,16 +1,12 @@ import React, { useMemo } from 'react'; import { useGraphQueryResult, useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access'; import VisualizationBar from './bar'; -import { VisualizationManager, VisualizationManagerType } from '../manager'; +import { VisualizationManagerType } from '../manager'; import { Recommender, NoData, Querying } from '../views'; -import { resultSetFocus } from '../../data-access/store/interactionSlice'; -import { useDispatch } from 'react-redux'; -export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => { - const manager = VisualizationManager(); +export const VisualizationPanel = ({ manager, fullSize }: { manager: VisualizationManagerType; fullSize: () => void }) => { const query = useQuerybuilderGraph(); const graphQueryResult = useGraphQueryResult(); - const dispatch = useDispatch(); const renderContent = useMemo(() => { if (graphQueryResult.queryingBackend) { @@ -24,10 +20,7 @@ export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => { }, [graphQueryResult, manager]); return ( - <div - className="vis-panel h-full w-full flex flex-col border bg-light" - onMouseDownCapture={() => dispatch(resultSetFocus({ focusType: 'visualization' }))} - > + <div className="vis-panel h-full w-full flex flex-col border bg-light"> <VisualizationBar manager={manager} fullSize={fullSize} /> <div className="grow overflow-y-auto" style={graphQueryResult.nodes.length === 0 ? { overflow: 'hidden' } : {}}> {renderContent} diff --git a/libs/shared/lib/vis/index.ts b/libs/shared/lib/vis/index.ts index b08eef1b81170e65094001e6c97a4f515c26fd38..ac473812c0eb2ee567344f3224e7dfa6c367b18f 100644 --- a/libs/shared/lib/vis/index.ts +++ b/libs/shared/lib/vis/index.ts @@ -1 +1 @@ -export * from './components/VisualizationPanel'; +export * from './components/panel'; diff --git a/libs/shared/lib/vis/visualizations/matrixvis/matrix.stories.tsx b/libs/shared/lib/vis/visualizations/matrixvis/matrix.stories.tsx index 1a961e2934c7546c925f32f7bcbea8787572a5a1..5fdecb2410f6d7a268d6ce69e6b39e7b80e3b0ba 100644 --- a/libs/shared/lib/vis/visualizations/matrixvis/matrix.stories.tsx +++ b/libs/shared/lib/vis/visualizations/matrixvis/matrix.stories.tsx @@ -3,7 +3,7 @@ import { Meta } from '@storybook/react'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; import { big2ndChamberQueryResult, smallFlightsQueryResults, mockLargeQueryResults } from '../../../mock-data'; -import { VisualizationPanel } from '../../components/VisualizationPanel'; +import { VisualizationPanel } from '../../components/panel'; import { setNewGraphQueryResult,