From 8d062baa8b694efad0befc5406168a9f3b9db491 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:39:32 +0300 Subject: [PATCH] Rework Search panels UI --- TODO.txt | 2 +- .../frontend/src/components/Common/Button.tsx | 10 +- .../src/components/Common/SelectorButton.tsx | 34 +++++++ .../components/RefsInput/DlgEditReference.tsx | 24 +++-- .../src/pages/LibraryPage/PickerStrategy.tsx | 91 ++++++++---------- .../src/pages/LibraryPage/SearchPanel.tsx | 14 +-- .../elements/DependencyModePicker.tsx | 60 ------------ .../RSFormPage/elements/MatchModePicker.tsx | 58 ------------ .../elements/ViewSideConstituents.tsx | 94 +++++++++++++++---- rsconcept/frontend/src/utils/constants.ts | 3 + rsconcept/frontend/src/utils/labels.ts | 53 ++++++++++- 11 files changed, 230 insertions(+), 213 deletions(-) create mode 100644 rsconcept/frontend/src/components/Common/SelectorButton.tsx delete mode 100644 rsconcept/frontend/src/pages/RSFormPage/elements/DependencyModePicker.tsx delete mode 100644 rsconcept/frontend/src/pages/RSFormPage/elements/MatchModePicker.tsx diff --git a/TODO.txt b/TODO.txt index 54cd97c4..6b125c76 100644 --- a/TODO.txt +++ b/TODO.txt @@ -12,7 +12,7 @@ For more specific TODOs see comments in code - система обработки ошибок backend [Tech] -- reload react-data-table-component +- multilevel modals / rework modal system [deployment] - logs collection diff --git a/rsconcept/frontend/src/components/Common/Button.tsx b/rsconcept/frontend/src/components/Common/Button.tsx index 3ba5ce94..efb5476b 100644 --- a/rsconcept/frontend/src/components/Common/Button.tsx +++ b/rsconcept/frontend/src/components/Common/Button.tsx @@ -1,5 +1,5 @@ interface ButtonProps -extends Omit, 'className' | 'children' | 'title'> { +extends Omit, 'className' | 'children' | 'title'| 'type'> { text?: string icon?: React.ReactNode tooltip?: string @@ -11,21 +11,19 @@ extends Omit, 'className' | 'child } function Button({ - id, text, icon, tooltip, + text, icon, tooltip, dense, disabled, borderClass = 'border rounded', colorClass = 'clr-btn-default', dimensions = 'w-fit h-fit', - loading, onClick, + loading, ...props }: ButtonProps) { const padding = dense ? 'px-1' : 'px-3 py-2'; const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ' : 'cursor-pointer '); return ( - + ); +} + +export default SelectorButton; diff --git a/rsconcept/frontend/src/components/RefsInput/DlgEditReference.tsx b/rsconcept/frontend/src/components/RefsInput/DlgEditReference.tsx index a225f127..9d7651dc 100644 --- a/rsconcept/frontend/src/components/RefsInput/DlgEditReference.tsx +++ b/rsconcept/frontend/src/components/RefsInput/DlgEditReference.tsx @@ -16,7 +16,7 @@ import SelectMulti from '../Common/SelectMulti'; import TextInput from '../Common/TextInput'; import DataTable, { IConditionalStyle } from '../DataTable'; import HelpTerminologyControl from '../Help/HelpTerminologyControl'; -import { HelpIcon } from '../Icons'; +import { HelpIcon, MagnifyingGlassIcon } from '../Icons'; import ReferenceTypeButton from './ReferenceTypeButton'; import WordformButton from './WordformButton'; @@ -284,12 +284,17 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen } {type === ReferenceType.ENTITY &&
- setFilter(event.target.value)} - /> +
+
+ +
+ setFilter(event.target.value)} + /> +
setAlias(event.target.value)} diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx index a1b7be94..3c81ccd9 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -1,12 +1,14 @@ import { useCallback } from 'react'; -import Button from '../../components/Common/Button'; import Dropdown from '../../components/Common/Dropdown'; import DropdownCheckbox from '../../components/Common/DropdownCheckbox'; +import SelectorButton from '../../components/Common/SelectorButton'; import { FilterCogIcon } from '../../components/Icons'; import { useAuth } from '../../context/AuthContext'; import useDropdown from '../../hooks/useDropdown'; import { LibraryFilterStrategy } from '../../models/miscelanious'; +import { prefixes } from '../../utils/constants'; +import { describeLibraryFilter, labelLibraryFilter } from '../../utils/labels'; interface PickerStrategyProps { value: LibraryFilterStrategy @@ -14,65 +16,52 @@ interface PickerStrategyProps { } function PickerStrategy({ value, onChange }: PickerStrategyProps) { - const pickerMenu = useDropdown(); + const strategyMenu = useDropdown(); const { user } = useAuth(); const handleChange = useCallback( (newValue: LibraryFilterStrategy) => { - pickerMenu.hide(); + strategyMenu.hide(); onChange(newValue); - }, [pickerMenu, onChange]); + }, [strategyMenu, onChange]); + + function isStrategyDisabled(strategy: LibraryFilterStrategy): boolean { + if ( + strategy === LibraryFilterStrategy.PERSONAL || + strategy === LibraryFilterStrategy.SUBSCRIBE || + strategy === LibraryFilterStrategy.OWNED + ) { + return !user; + } else { + return false; + } + } return ( -
-
-
- -
+
+
@@ -89,10 +85,14 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setStrategy, type='text' value={query} className='w-full p-2 pl-10 text-sm outline-none clr-input' - placeholder='Поиск схемы...' + placeholder='Поиск' onChange={handleChangeQuery} />
+
); diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/DependencyModePicker.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/DependencyModePicker.tsx deleted file mode 100644 index 10771c93..00000000 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/DependencyModePicker.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { useCallback } from 'react'; - -import Dropdown from '../../../components/Common/Dropdown'; -import DropdownButton from '../../../components/Common/DropdownButton'; -import { CogIcon } from '../../../components/Icons'; -import useDropdown from '../../../hooks/useDropdown'; -import { DependencyMode } from '../../../models/miscelanious'; -import { labelDependencyMode } from '../../../utils/labels'; - -interface DependencyModePickerProps { - value: DependencyMode - onChange: (value: DependencyMode) => void -} - -function DependencyModePicker({ value, onChange }: DependencyModePickerProps) { - const pickerMenu = useDropdown(); - - const handleChange = useCallback( - (newValue: DependencyMode) => { - pickerMenu.hide(); - onChange(newValue); - }, [pickerMenu, onChange]); - - return ( -
- - { pickerMenu.isActive && - - handleChange(DependencyMode.ALL)}> -

вся схема: список всех конституент схемы

-
- handleChange(DependencyMode.EXPRESSION)}> -

выражение: список идентификаторов из выражения

-
- handleChange(DependencyMode.OUTPUTS)}> -

потребители: конституенты, ссылающиеся на данную

-
- handleChange(DependencyMode.INPUTS)}> -

поставщики: конституенты, на которые ссылается данная

-
- handleChange(DependencyMode.EXPAND_OUTPUTS)}> -

зависимые: конституенты, зависящие по цепочке

-
- handleChange(DependencyMode.EXPAND_INPUTS)}> -

влияющие: конституенты, влияющие на данную (цепочка)

-
-
} -
- ); -} - -export default DependencyModePicker; diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/MatchModePicker.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/MatchModePicker.tsx deleted file mode 100644 index cf94bf67..00000000 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/MatchModePicker.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useCallback } from 'react'; - -import Dropdown from '../../../components/Common/Dropdown'; -import DropdownButton from '../../../components/Common/DropdownButton'; -import { FilterCogIcon } from '../../../components/Icons'; -import useDropdown from '../../../hooks/useDropdown'; -import { CstMatchMode } from '../../../models/miscelanious'; -import { labelCstMathchMode } from '../../../utils/labels'; - -interface MatchModePickerProps { - value: CstMatchMode - onChange: (value: CstMatchMode) => void -} - -function MatchModePicker({ value, onChange }: MatchModePickerProps) { - const pickerMenu = useDropdown(); - - const handleChange = useCallback( - (newValue: CstMatchMode) => { - pickerMenu.hide(); - onChange(newValue); - }, [pickerMenu, onChange]); - - return ( -
- - { pickerMenu.isActive && - - handleChange(CstMatchMode.ALL)}> -

везде: искать во всех атрибутах

-
- handleChange(CstMatchMode.EXPR)}> -

выраж: искать в формальных выражениях

-
- handleChange(CstMatchMode.TERM)}> -

термин: искать в терминах

-
- handleChange(CstMatchMode.TEXT)}> -

текст: искать в определениях и конвенциях

-
- handleChange(CstMatchMode.NAME)}> -

имя: искать в идентификаторах конституент

-
-
- } -
- ); -} - -export default MatchModePicker; diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx index dc28af95..d6b7fba0 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx @@ -1,22 +1,28 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; +import Dropdown from '../../../components/Common/Dropdown'; +import DropdownButton from '../../../components/Common/DropdownButton'; +import SelectorButton from '../../../components/Common/SelectorButton'; import DataTable, { createColumnHelper, IConditionalStyle, VisibilityState } from '../../../components/DataTable'; -import { MagnifyingGlassIcon } from '../../../components/Icons'; +import { CogIcon, FilterCogIcon, MagnifyingGlassIcon } from '../../../components/Icons'; import { useRSForm } from '../../../context/RSFormContext'; import { useConceptTheme } from '../../../context/ThemeContext'; +import useDropdown from '../../../hooks/useDropdown'; import useLocalStorage from '../../../hooks/useLocalStorage'; import useWindowSize from '../../../hooks/useWindowSize'; -import { DependencyMode } from '../../../models/miscelanious'; +import { DependencyMode as CstSource } from '../../../models/miscelanious'; import { CstMatchMode } from '../../../models/miscelanious'; import { applyGraphFilter } from '../../../models/miscelanious'; import { CstType, extractGlobals, IConstituenta, matchConstituenta } from '../../../models/rsform'; import { createMockConstituenta } from '../../../models/rsform'; import { colorfgCstStatus } from '../../../utils/color'; import { prefixes } from '../../../utils/constants'; -import { describeConstituenta } from '../../../utils/labels'; +import { + describeConstituenta, describeCstMathchMode, + describeCstSource, labelCstMathchMode, + labelCstSource +} from '../../../utils/labels'; import ConstituentaTooltip from './ConstituentaTooltip'; -import DependencyModePicker from './DependencyModePicker'; -import MatchModePicker from './MatchModePicker'; // Height that should be left to accomodate navigation panel + bottom margin const LOCAL_NAVIGATION_H = '2.1rem'; @@ -46,10 +52,13 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL); const [filterText, setFilterText] = useLocalStorage('side-filter-text', ''); - const [filterSource, setFilterSource] = useLocalStorage('side-filter-dependency', DependencyMode.ALL); + const [filterSource, setFilterSource] = useLocalStorage('side-filter-dependency', CstSource.ALL); const [filteredData, setFilteredData] = useState(schema?.items ?? []); + const matchModeMenu = useDropdown(); + const sourceMenu = useDropdown(); + useLayoutEffect( () => { setColumnVisibility(prev => { @@ -69,7 +78,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: return; } let filtered: IConstituenta[] = []; - if (filterSource === DependencyMode.EXPRESSION) { + if (filterSource === CstSource.EXPRESSION) { const aliases = extractGlobals(expression); filtered = schema.items.filter((cst) => aliases.has(cst.alias)); const names = filtered.map(cst => cst.alias) @@ -113,6 +122,18 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: } }, [onOpenEdit]); + const handleMatchModeChange = useCallback( + (newValue: CstMatchMode) => { + matchModeMenu.hide(); + setFilterMatch(newValue); + }, [matchModeMenu, setFilterMatch]); + + const handleSourceChange = useCallback( + (newValue: CstSource) => { + sourceMenu.hide(); + setFilterSource(newValue); + }, [sourceMenu, setFilterSource]); + const columns = useMemo( () => [ columnHelper.accessor('alias', { @@ -196,18 +217,59 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
setFilterText(event.target.value)} /> - - +
+ } + text={labelCstMathchMode(filterMatch)} + tabIndex={-1} + onClick={matchModeMenu.toggle} + /> + { matchModeMenu.isActive && + + { Object.values(CstMatchMode).filter(value => !isNaN(Number(value))).map( + (value, index) => { + const matchMode = value as CstMatchMode; + return ( + handleMatchModeChange(matchMode)} + > +

{labelCstMathchMode(matchMode)}: {describeCstMathchMode(matchMode)}

+
); + })} +
} +
+ +
+ } + text={labelCstSource(filterSource)} + tabIndex={-1} + onClick={sourceMenu.toggle} + /> + { sourceMenu.isActive && + + { Object.values(CstSource).filter(value => !isNaN(Number(value))).map( + (value, index) => { + const source = value as CstSource; + return ( + handleSourceChange(source)} + > +

{labelCstSource(source)}: {describeCstSource(source)}

+
); + })} +
} +
= new Map([ ['forceatlas2', 'Граф: Атлас 2D'],