From 2862c2f3c08175ae62e2d71b0a4f16ddd8ea81be Mon Sep 17 00:00:00 2001 From: 2427021 <s.a.vink@students.uu.nl> Date: Thu, 28 Mar 2024 10:29:42 +0100 Subject: [PATCH] feat(visManager): paohvis settings --- .../src/components/navbar/databasemenu.tsx | 17 ----- apps/web/src/components/navbar/navbar.tsx | 14 ++-- libs/shared/lib/components/Dialog.tsx | 10 +-- libs/shared/lib/components/inputs/index.tsx | 65 ++++++++++++++++++- .../lib/components/inputs/number.stories.tsx | 36 ++++++++++ libs/shared/lib/data-viewer/index.tsx | 2 +- .../vis/visualizations/paohvis/paohvis.tsx | 19 +++++- 7 files changed, 128 insertions(+), 35 deletions(-) delete mode 100644 apps/web/src/components/navbar/databasemenu.tsx create mode 100644 libs/shared/lib/components/inputs/number.stories.tsx diff --git a/apps/web/src/components/navbar/databasemenu.tsx b/apps/web/src/components/navbar/databasemenu.tsx deleted file mode 100644 index 15a2e2f18..000000000 --- a/apps/web/src/components/navbar/databasemenu.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { SaveStateI, useSessionCache } from '@graphpolaris/shared/lib/data-access'; - -export const DatabaseMenu = (props: { onClick: (database: string) => void }) => { - const session = useSessionCache(); - - return ( - <ul className="menu dropdown-content absolute right-48 z-[1] p-2 shadow-xl bg-secondary-50 rounded-box w-52" tabIndex={0}> - {session.saveStates && - Object.values(session.saveStates).map((ss: SaveStateI) => ( - <li key={ss.name}> - <button onClick={() => props.onClick(ss.name)}>{ss.name}</button> - </li> - ))} - </ul> - ); -}; diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx index 24843ddfa..68cbbf023 100644 --- a/apps/web/src/components/navbar/navbar.tsx +++ b/apps/web/src/components/navbar/navbar.tsx @@ -59,8 +59,8 @@ export const Navbar = () => { {menuOpen && ( <DropdownItemContainer className="w-56" align="right-7"> - <div className="menu-title border-b"> - <h2>user: {authCache.username}</h2> + <div className="p-2 text-sm border-b"> + <h2 className="font-bold">user: {authCache.username}</h2> <h3 className="text-xs break-words">session: {authCache.sessionID}</h3> </div> @@ -71,12 +71,6 @@ export const Navbar = () => { onClick={() => { auth.newShareRoom(); }} - // submenu={ - // <> - // <DropdownItem value="Visual" onClick={() => {}} /> - // <DropdownItem value="Knowledge base" onClick={() => {}} /> - // </> - // } /> <DropdownItem value="Advanced" @@ -96,11 +90,11 @@ export const Navbar = () => { )} {authCache?.roomID && ( - <div className="menu-title border-b"> + <div className="p-2 border-b"> <h3 className="text-xs break-words">Share ID: {authCache.roomID}</h3> </div> )} - <div className="menu-title border-t"> + <div className="p-2 border-t"> <h3 className="text-xs">Version: {buildInfo}</h3> </div> </DropdownItemContainer> diff --git a/libs/shared/lib/components/Dialog.tsx b/libs/shared/lib/components/Dialog.tsx index c0eec2797..0aecd77cc 100644 --- a/libs/shared/lib/components/Dialog.tsx +++ b/libs/shared/lib/components/Dialog.tsx @@ -16,13 +16,13 @@ export const Dialog = (props: DialogProps) => { }, [props.open]); return ( - <dialog data-modal className={'modal'} ref={ref} onClose={() => props.onClose()}> - <form method="dialog" className={'modal-box card flex gap-4 ' + (props?.className ? props?.className : '')}> + <dialog className={'fixed inset-0 z-10 overflow-y-auto rounded p-4'} ref={ref} onClose={() => props.onClose()}> + <form method="dialog" className={'flex flex-col gap-4 ' + (props?.className ? props?.className : '')}> {props.children} </form> - <form method="dialog" className="modal-backdrop"> - <button>close</button> - </form> + {/* <form method="dialog" className=""> */} + {/* <button>close</button> */} + {/* </form> */} </dialog> ); }; diff --git a/libs/shared/lib/components/inputs/index.tsx b/libs/shared/lib/components/inputs/index.tsx index e4576c2c0..ee43877ad 100644 --- a/libs/shared/lib/components/inputs/index.tsx +++ b/libs/shared/lib/components/inputs/index.tsx @@ -31,6 +31,21 @@ type TextProps = { onChange?: (value: string) => void; }; +type NumberProps = { + label: string; + type: 'number'; + placeholder?: string; + value: number; + required?: boolean; + errorText?: string; + visible?: boolean; + disabled?: boolean; + tooltip?: string; + info?: string; + validate?: (value: any) => boolean; + onChange?: (value: number) => void; +}; + type CheckboxProps = { label?: string; type: 'checkbox'; @@ -71,7 +86,7 @@ type DropdownProps = { disabled?: boolean; }; -export type InputProps = TextProps | SliderProps | CheckboxProps | DropdownProps | RadioProps | BooleanProps; +export type InputProps = TextProps | SliderProps | CheckboxProps | DropdownProps | RadioProps | BooleanProps | NumberProps; const Input = (props: InputProps) => { switch (props.type) { @@ -87,6 +102,8 @@ const Input = (props: InputProps) => { return <RadioInput {...(props as RadioProps)} />; case 'boolean': return <BooleanInput {...(props as BooleanProps)} />; + case 'number': + return <NumberInput {...(props as NumberProps)} />; default: return null; } @@ -168,6 +185,52 @@ export const TextInput = ({ ); }; +export const NumberInput = ({ + label, + placeholder, + value = 0, + required = false, + visible = true, + errorText, + validate, + disabled = false, + onChange, + tooltip, + info, +}: NumberProps) => { + const [isValid, setIsValid] = React.useState<boolean>(true); + + return ( + <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>} + {info && <Info tooltip={info} side={'left'} />} + </label> + <input + type="number" + placeholder={placeholder} + 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()} + onChange={(e) => { + if (required && validate) { + setIsValid(validate(e.target.value)); + } + if (onChange) { + onChange(Number(e.target.value)); + } + }} + required={required} + disabled={disabled} + /> + </div> + ); +}; + export const RadioInput = ({ label, value, options, onChange, tooltip }: RadioProps) => { return ( <div data-tip={tooltip || null} className={tooltip ? 'tooltip' : ''}> diff --git a/libs/shared/lib/components/inputs/number.stories.tsx b/libs/shared/lib/components/inputs/number.stories.tsx new file mode 100644 index 000000000..e31f0c745 --- /dev/null +++ b/libs/shared/lib/components/inputs/number.stories.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { NumberInput } from '.'; + +const Component: Meta<typeof NumberInput> = { + title: 'Components/Inputs', + component: NumberInput, + argTypes: { onChange: {} }, + decorators: [(Story) => <div className="w-52 m-5">{Story()}</div>], +}; + +export default Component; +type Story = StoryObj<typeof Component>; + +export const NumberInputStory: Story = { + args: { + type: 'number', + label: 'Number input', + placeholder: 'Put here a number', + required: true, + onChange: (value) => {}, + }, +}; + +export const RequiredNumberInputStory: Story = { + args: { + type: 'number', + label: 'Number input', + placeholder: 'Put here a number', + errorText: 'This field is required', + validate: (value) => { + return value.length > 0; + }, + onChange: (value) => {}, + }, +}; diff --git a/libs/shared/lib/data-viewer/index.tsx b/libs/shared/lib/data-viewer/index.tsx index bf99a0abf..fb7b6c83e 100644 --- a/libs/shared/lib/data-viewer/index.tsx +++ b/libs/shared/lib/data-viewer/index.tsx @@ -59,7 +59,7 @@ export default function DataViewer({ auth }: { auth: boolean }) { <ColorMode /> </div> {showDialog && ( - <div className="flex-1 border border-secondary-200 overflow-hidden"> + <div className="flex-1 border overflow-hidden"> <div className="relative flex items-center justify-between z-[2] py-0 px-2 bg-secondary-100 border-b border-secondary-200"> <h1 className="text-xs font-semibold text-secondary-800">{tab}</h1> <ControlContainer> diff --git a/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx b/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx index cd8836da1..caca7777a 100644 --- a/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx +++ b/libs/shared/lib/vis/visualizations/paohvis/paohvis.tsx @@ -30,6 +30,7 @@ import { RowLabelColumn } from './components/RowLabelColumn'; import { VISComponentType, VisualizationPropTypes } from '../../types'; import { GraphMetaData } from '@graphpolaris/shared/lib/data-access/statistics'; import { SettingsContainer, SettingsHeader } from '@graphpolaris/shared/lib/vis/configuration'; +import Input from '@graphpolaris/shared/lib/components/inputs'; type PaohvisViewModelState = { rowHeight: number; @@ -655,7 +656,23 @@ const PaohSettings = ({ graph: GraphMetaData; updateSettings: (val: any) => void; }) => { - return <SettingsContainer>To be implemented</SettingsContainer>; + return ( + <SettingsContainer> + <Input type="number" label="Row height" value={configuration.rowHeight} onChange={(val) => updateSettings({ rowHeight: val })} /> + <Input + type="number" + label="Hyper edge column width" + value={configuration.hyperedgeColumnWidth} + onChange={(val) => updateSettings({ hyperedgeColumnWidth: val })} + /> + <Input + type="number" + label="Gap between ranges" + value={configuration.gapBetweenRanges} + onChange={(val) => updateSettings({ gapBetweenRanges: val })} + /> + </SettingsContainer> + ); }; export const PaohVisComponent: VISComponentType = { -- GitLab