mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Rework Search panels UI
This commit is contained in:
parent
a44a43214c
commit
8d062baa8b
2
TODO.txt
2
TODO.txt
|
@ -12,7 +12,7 @@ For more specific TODOs see comments in code
|
||||||
- система обработки ошибок backend
|
- система обработки ошибок backend
|
||||||
|
|
||||||
[Tech]
|
[Tech]
|
||||||
- reload react-data-table-component
|
- multilevel modals / rework modal system
|
||||||
|
|
||||||
[deployment]
|
[deployment]
|
||||||
- logs collection
|
- logs collection
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
interface ButtonProps
|
interface ButtonProps
|
||||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title'> {
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title'| 'type'> {
|
||||||
text?: string
|
text?: string
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
|
@ -11,21 +11,19 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
|
||||||
}
|
}
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
id, text, icon, tooltip,
|
text, icon, tooltip,
|
||||||
dense, disabled,
|
dense, disabled,
|
||||||
borderClass = 'border rounded',
|
borderClass = 'border rounded',
|
||||||
colorClass = 'clr-btn-default',
|
colorClass = 'clr-btn-default',
|
||||||
dimensions = 'w-fit h-fit',
|
dimensions = 'w-fit h-fit',
|
||||||
loading, onClick,
|
loading,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
const padding = dense ? 'px-1' : 'px-3 py-2';
|
const padding = dense ? 'px-1' : 'px-3 py-2';
|
||||||
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ' : 'cursor-pointer ');
|
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ' : 'cursor-pointer ');
|
||||||
return (
|
return (
|
||||||
<button id={id}
|
<button type='button'
|
||||||
type='button'
|
|
||||||
disabled={disabled ?? loading}
|
disabled={disabled ?? loading}
|
||||||
onClick={onClick}
|
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colorClass} ${dimensions} ${borderClass} ${cursor}`}
|
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colorClass} ${dimensions} ${borderClass} ${cursor}`}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
34
rsconcept/frontend/src/components/Common/SelectorButton.tsx
Normal file
34
rsconcept/frontend/src/components/Common/SelectorButton.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
interface SelectorButtonProps
|
||||||
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title' | 'type'> {
|
||||||
|
text?: string
|
||||||
|
icon?: React.ReactNode
|
||||||
|
tooltip?: string
|
||||||
|
dimensions?: string
|
||||||
|
borderClass?: string
|
||||||
|
colorClass?: string
|
||||||
|
transparent?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SelectorButton({
|
||||||
|
text, icon, tooltip,
|
||||||
|
colorClass = 'clr-btn-default',
|
||||||
|
dimensions = 'w-fit h-fit',
|
||||||
|
transparent,
|
||||||
|
...props
|
||||||
|
}: SelectorButtonProps) {
|
||||||
|
const cursor = 'disabled:cursor-not-allowed cursor-pointer';
|
||||||
|
const position = `px-1 flex flex-start items-center gap-1 ${dimensions}`
|
||||||
|
return (
|
||||||
|
<button type='button'
|
||||||
|
className={`text-sm small-caps ${!transparent && 'border'} ${cursor} ${position} text-btn text-controls ${!transparent && colorClass}`}
|
||||||
|
title={tooltip}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon && icon}
|
||||||
|
{text && <div className={'font-semibold whitespace-nowrap pb-1'}>{text}</div>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectorButton;
|
|
@ -16,7 +16,7 @@ import SelectMulti from '../Common/SelectMulti';
|
||||||
import TextInput from '../Common/TextInput';
|
import TextInput from '../Common/TextInput';
|
||||||
import DataTable, { IConditionalStyle } from '../DataTable';
|
import DataTable, { IConditionalStyle } from '../DataTable';
|
||||||
import HelpTerminologyControl from '../Help/HelpTerminologyControl';
|
import HelpTerminologyControl from '../Help/HelpTerminologyControl';
|
||||||
import { HelpIcon } from '../Icons';
|
import { HelpIcon, MagnifyingGlassIcon } from '../Icons';
|
||||||
import ReferenceTypeButton from './ReferenceTypeButton';
|
import ReferenceTypeButton from './ReferenceTypeButton';
|
||||||
import WordformButton from './WordformButton';
|
import WordformButton from './WordformButton';
|
||||||
|
|
||||||
|
@ -284,12 +284,17 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
||||||
</div>}
|
</div>}
|
||||||
{type === ReferenceType.ENTITY &&
|
{type === ReferenceType.ENTITY &&
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
|
<div className='relative'>
|
||||||
|
<div className='absolute inset-y-0 flex items-center pl-3 pointer-events-none text-controls'>
|
||||||
|
<MagnifyingGlassIcon />
|
||||||
|
</div>
|
||||||
<TextInput
|
<TextInput
|
||||||
dimensions='w-full'
|
dimensions='w-full pl-10'
|
||||||
placeholder='текст фильтра'
|
placeholder='Поиск'
|
||||||
value={filter}
|
value={filter}
|
||||||
onChange={event => setFilter(event.target.value)}
|
onChange={event => setFilter(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto'>
|
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto'>
|
||||||
<DataTable
|
<DataTable
|
||||||
data={filteredData}
|
data={filteredData}
|
||||||
|
@ -311,8 +316,9 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
||||||
</div>
|
</div>
|
||||||
<div className='flex gap-4 flex-start'>
|
<div className='flex gap-4 flex-start'>
|
||||||
<TextInput
|
<TextInput
|
||||||
label='Отсылаемый идентификатор'
|
label='Отсылаемая конституента'
|
||||||
dimensions='max-w-[18rem] min-w-[18rem] whitespace-nowrap'
|
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
|
||||||
|
placeholder='Имя'
|
||||||
singleRow
|
singleRow
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import Button from '../../components/Common/Button';
|
|
||||||
import Dropdown from '../../components/Common/Dropdown';
|
import Dropdown from '../../components/Common/Dropdown';
|
||||||
import DropdownCheckbox from '../../components/Common/DropdownCheckbox';
|
import DropdownCheckbox from '../../components/Common/DropdownCheckbox';
|
||||||
|
import SelectorButton from '../../components/Common/SelectorButton';
|
||||||
import { FilterCogIcon } from '../../components/Icons';
|
import { FilterCogIcon } from '../../components/Icons';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import useDropdown from '../../hooks/useDropdown';
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
import { LibraryFilterStrategy } from '../../models/miscelanious';
|
import { LibraryFilterStrategy } from '../../models/miscelanious';
|
||||||
|
import { prefixes } from '../../utils/constants';
|
||||||
|
import { describeLibraryFilter, labelLibraryFilter } from '../../utils/labels';
|
||||||
|
|
||||||
interface PickerStrategyProps {
|
interface PickerStrategyProps {
|
||||||
value: LibraryFilterStrategy
|
value: LibraryFilterStrategy
|
||||||
|
@ -14,65 +16,52 @@ interface PickerStrategyProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function PickerStrategy({ value, onChange }: PickerStrategyProps) {
|
function PickerStrategy({ value, onChange }: PickerStrategyProps) {
|
||||||
const pickerMenu = useDropdown();
|
const strategyMenu = useDropdown();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(newValue: LibraryFilterStrategy) => {
|
(newValue: LibraryFilterStrategy) => {
|
||||||
pickerMenu.hide();
|
strategyMenu.hide();
|
||||||
onChange(newValue);
|
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 (
|
return (
|
||||||
<div ref={pickerMenu.ref} className='h-full text-right'>
|
<div ref={strategyMenu.ref} className='h-full text-right'>
|
||||||
<Button
|
<SelectorButton
|
||||||
icon={<FilterCogIcon color='text-controls' size={6} />}
|
tooltip='Список фильтров'
|
||||||
dense
|
transparent
|
||||||
tooltip='Фильтры'
|
icon={<FilterCogIcon size={5} />}
|
||||||
colorClass='clr-input clr-hover text-btn'
|
text={labelLibraryFilter(value)}
|
||||||
dimensions='h-full py-1 px-2 border-none'
|
tabIndex={-1}
|
||||||
onClick={pickerMenu.toggle}
|
onClick={strategyMenu.toggle}
|
||||||
/>
|
/>
|
||||||
{ pickerMenu.isActive &&
|
{ strategyMenu.isActive &&
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
|
{ Object.values(LibraryFilterStrategy).map(
|
||||||
|
(enumValue, index) => {
|
||||||
|
const strategy = enumValue as LibraryFilterStrategy;
|
||||||
|
return (
|
||||||
<DropdownCheckbox
|
<DropdownCheckbox
|
||||||
setValue={() => handleChange(LibraryFilterStrategy.MANUAL)}
|
key={`${prefixes.library_filters_list}${index}`}
|
||||||
value={value === LibraryFilterStrategy.MANUAL}
|
value={value === strategy}
|
||||||
label='Отображать все'
|
setValue={() => handleChange(strategy)}
|
||||||
/>
|
label={labelLibraryFilter(strategy)}
|
||||||
<DropdownCheckbox
|
tooltip={describeLibraryFilter(strategy)}
|
||||||
setValue={() => handleChange(LibraryFilterStrategy.COMMON)}
|
disabled={isStrategyDisabled(strategy)}
|
||||||
value={value === LibraryFilterStrategy.COMMON}
|
/>);
|
||||||
label='Общедоступные'
|
})}
|
||||||
tooltip='Отображать только общедоступные схемы'
|
|
||||||
/>
|
|
||||||
<DropdownCheckbox
|
|
||||||
setValue={() => handleChange(LibraryFilterStrategy.CANONICAL)}
|
|
||||||
value={value === LibraryFilterStrategy.CANONICAL}
|
|
||||||
label='Неизменные'
|
|
||||||
tooltip='Отображать только стандартные схемы'
|
|
||||||
/>
|
|
||||||
<DropdownCheckbox
|
|
||||||
setValue={() => handleChange(LibraryFilterStrategy.PERSONAL)}
|
|
||||||
value={value === LibraryFilterStrategy.PERSONAL}
|
|
||||||
label='Личные'
|
|
||||||
disabled={!user}
|
|
||||||
tooltip='Отображать только подписки и владеемые схемы'
|
|
||||||
/>
|
|
||||||
<DropdownCheckbox
|
|
||||||
setValue={() => handleChange(LibraryFilterStrategy.SUBSCRIBE)}
|
|
||||||
value={value === LibraryFilterStrategy.SUBSCRIBE}
|
|
||||||
label='Подписки'
|
|
||||||
disabled={!user}
|
|
||||||
tooltip='Отображать только подписки'
|
|
||||||
/>
|
|
||||||
<DropdownCheckbox
|
|
||||||
setValue={() => handleChange(LibraryFilterStrategy.OWNED)}
|
|
||||||
value={value === LibraryFilterStrategy.OWNED}
|
|
||||||
disabled={!user}
|
|
||||||
label='Я - Владелец!'
|
|
||||||
tooltip='Отображать только владеемые схемы'
|
|
||||||
/>
|
|
||||||
</Dropdown>}
|
</Dropdown>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -76,12 +76,8 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setStrategy,
|
||||||
{filtered} из {total}
|
{filtered} из {total}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center justify-center w-full ml-8'>
|
<div className='flex items-center justify-center w-full gap-1'>
|
||||||
<PickerStrategy
|
<div className='relative min-w-[10rem] select-none'>
|
||||||
value={strategy}
|
|
||||||
onChange={handleChangeStrategy}
|
|
||||||
/>
|
|
||||||
<div className='relative w-96 min-w-[10rem] select-none'>
|
|
||||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
|
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-controls'>
|
||||||
<MagnifyingGlassIcon />
|
<MagnifyingGlassIcon />
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,10 +85,14 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setStrategy,
|
||||||
type='text'
|
type='text'
|
||||||
value={query}
|
value={query}
|
||||||
className='w-full p-2 pl-10 text-sm outline-none clr-input'
|
className='w-full p-2 pl-10 text-sm outline-none clr-input'
|
||||||
placeholder='Поиск схемы...'
|
placeholder='Поиск'
|
||||||
onChange={handleChangeQuery}
|
onChange={handleChangeQuery}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<PickerStrategy
|
||||||
|
value={strategy}
|
||||||
|
onChange={handleChangeStrategy}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 (
|
|
||||||
<div ref={pickerMenu.ref} className='h-full'>
|
|
||||||
<button
|
|
||||||
className='h-full w-[7.5rem] px-1 py-1 border clr-input clr-hover clr-btn-default text-btn inline-flex align-middle gap-1'
|
|
||||||
title='Настройка фильтрации по графу термов'
|
|
||||||
tabIndex={-1}
|
|
||||||
onClick={pickerMenu.toggle}
|
|
||||||
>
|
|
||||||
<CogIcon color='text-controls' size={5} />
|
|
||||||
<span className='text-sm font-semibold whitespace-nowrap'>{labelDependencyMode(value)}</span>
|
|
||||||
</button>
|
|
||||||
{ pickerMenu.isActive &&
|
|
||||||
<Dropdown stretchLeft >
|
|
||||||
<DropdownButton onClick={() => handleChange(DependencyMode.ALL)}>
|
|
||||||
<p><b>вся схема:</b> список всех конституент схемы</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(DependencyMode.EXPRESSION)}>
|
|
||||||
<p><b>выражение:</b> список идентификаторов из выражения</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(DependencyMode.OUTPUTS)}>
|
|
||||||
<p><b>потребители:</b> конституенты, ссылающиеся на данную</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(DependencyMode.INPUTS)}>
|
|
||||||
<p><b>поставщики:</b> конституенты, на которые ссылается данная</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(DependencyMode.EXPAND_OUTPUTS)}>
|
|
||||||
<p><b>зависимые:</b> конституенты, зависящие по цепочке</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(DependencyMode.EXPAND_INPUTS)}>
|
|
||||||
<p><b>влияющие:</b> конституенты, влияющие на данную (цепочка)</p>
|
|
||||||
</DropdownButton>
|
|
||||||
</Dropdown>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DependencyModePicker;
|
|
|
@ -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 (
|
|
||||||
<div ref={pickerMenu.ref} className='h-full'>
|
|
||||||
<button
|
|
||||||
className='h-full w-[6rem] px-1 py-1 border clr-input clr-hover clr-btn-default text-btn inline-flex align-middle gap-1'
|
|
||||||
title='Настройка атрибутов для фильтрации'
|
|
||||||
tabIndex={-1}
|
|
||||||
onClick={pickerMenu.toggle}
|
|
||||||
>
|
|
||||||
<FilterCogIcon color='text-controls' size={5} />
|
|
||||||
<span className='text-sm font-semibold whitespace-nowrap'>{labelCstMathchMode(value)}</span>
|
|
||||||
</button>
|
|
||||||
{ pickerMenu.isActive &&
|
|
||||||
<Dropdown stretchLeft>
|
|
||||||
<DropdownButton onClick={() => handleChange(CstMatchMode.ALL)}>
|
|
||||||
<p><b>везде:</b> искать во всех атрибутах</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(CstMatchMode.EXPR)}>
|
|
||||||
<p><b>выраж:</b> искать в формальных выражениях</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(CstMatchMode.TERM)}>
|
|
||||||
<p><b>термин:</b> искать в терминах</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(CstMatchMode.TEXT)}>
|
|
||||||
<p><b>текст:</b> искать в определениях и конвенциях</p>
|
|
||||||
</DropdownButton>
|
|
||||||
<DropdownButton onClick={() => handleChange(CstMatchMode.NAME)}>
|
|
||||||
<p><b>имя:</b> искать в идентификаторах конституент</p>
|
|
||||||
</DropdownButton>
|
|
||||||
</Dropdown>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MatchModePicker;
|
|
|
@ -1,22 +1,28 @@
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
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 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 { useRSForm } from '../../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
|
import useDropdown from '../../../hooks/useDropdown';
|
||||||
import useLocalStorage from '../../../hooks/useLocalStorage';
|
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||||
import useWindowSize from '../../../hooks/useWindowSize';
|
import useWindowSize from '../../../hooks/useWindowSize';
|
||||||
import { DependencyMode } from '../../../models/miscelanious';
|
import { DependencyMode as CstSource } from '../../../models/miscelanious';
|
||||||
import { CstMatchMode } from '../../../models/miscelanious';
|
import { CstMatchMode } from '../../../models/miscelanious';
|
||||||
import { applyGraphFilter } from '../../../models/miscelanious';
|
import { applyGraphFilter } from '../../../models/miscelanious';
|
||||||
import { CstType, extractGlobals, IConstituenta, matchConstituenta } from '../../../models/rsform';
|
import { CstType, extractGlobals, IConstituenta, matchConstituenta } from '../../../models/rsform';
|
||||||
import { createMockConstituenta } from '../../../models/rsform';
|
import { createMockConstituenta } from '../../../models/rsform';
|
||||||
import { colorfgCstStatus } from '../../../utils/color';
|
import { colorfgCstStatus } from '../../../utils/color';
|
||||||
import { prefixes } from '../../../utils/constants';
|
import { prefixes } from '../../../utils/constants';
|
||||||
import { describeConstituenta } from '../../../utils/labels';
|
import {
|
||||||
|
describeConstituenta, describeCstMathchMode,
|
||||||
|
describeCstSource, labelCstMathchMode,
|
||||||
|
labelCstSource
|
||||||
|
} from '../../../utils/labels';
|
||||||
import ConstituentaTooltip from './ConstituentaTooltip';
|
import ConstituentaTooltip from './ConstituentaTooltip';
|
||||||
import DependencyModePicker from './DependencyModePicker';
|
|
||||||
import MatchModePicker from './MatchModePicker';
|
|
||||||
|
|
||||||
// Height that should be left to accomodate navigation panel + bottom margin
|
// Height that should be left to accomodate navigation panel + bottom margin
|
||||||
const LOCAL_NAVIGATION_H = '2.1rem';
|
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 [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
||||||
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '');
|
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<IConstituenta[]>(schema?.items ?? []);
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||||
|
|
||||||
|
const matchModeMenu = useDropdown();
|
||||||
|
const sourceMenu = useDropdown();
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
setColumnVisibility(prev => {
|
setColumnVisibility(prev => {
|
||||||
|
@ -69,7 +78,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let filtered: IConstituenta[] = [];
|
let filtered: IConstituenta[] = [];
|
||||||
if (filterSource === DependencyMode.EXPRESSION) {
|
if (filterSource === CstSource.EXPRESSION) {
|
||||||
const aliases = extractGlobals(expression);
|
const aliases = extractGlobals(expression);
|
||||||
filtered = schema.items.filter((cst) => aliases.has(cst.alias));
|
filtered = schema.items.filter((cst) => aliases.has(cst.alias));
|
||||||
const names = filtered.map(cst => cst.alias)
|
const names = filtered.map(cst => cst.alias)
|
||||||
|
@ -113,6 +122,18 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
}
|
}
|
||||||
}, [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(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.accessor('alias', {
|
columnHelper.accessor('alias', {
|
||||||
|
@ -196,18 +217,59 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
</div>
|
</div>
|
||||||
<input type='text'
|
<input type='text'
|
||||||
className='w-[14rem] pr-2 pl-8 py-1 outline-none select-none hover:text-clip clr-input'
|
className='w-[14rem] pr-2 pl-8 py-1 outline-none select-none hover:text-clip clr-input'
|
||||||
placeholder='текст фильтра'
|
placeholder='Поиск'
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={event => setFilterText(event.target.value)}
|
onChange={event => setFilterText(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<MatchModePicker
|
<div ref={matchModeMenu.ref}>
|
||||||
value={filterMatch}
|
<SelectorButton
|
||||||
onChange={setFilterMatch}
|
tooltip='Настройка атрибутов для фильтрации'
|
||||||
|
transparent
|
||||||
|
icon={<FilterCogIcon size={5} />}
|
||||||
|
text={labelCstMathchMode(filterMatch)}
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={matchModeMenu.toggle}
|
||||||
/>
|
/>
|
||||||
<DependencyModePicker
|
{ matchModeMenu.isActive &&
|
||||||
value={filterSource}
|
<Dropdown stretchLeft>
|
||||||
onChange={setFilterSource}
|
{ Object.values(CstMatchMode).filter(value => !isNaN(Number(value))).map(
|
||||||
|
(value, index) => {
|
||||||
|
const matchMode = value as CstMatchMode;
|
||||||
|
return (
|
||||||
|
<DropdownButton
|
||||||
|
key={`${prefixes.cst_match_mode_list}${index}`}
|
||||||
|
onClick={() => handleMatchModeChange(matchMode)}
|
||||||
|
>
|
||||||
|
<p><span className='font-semibold'>{labelCstMathchMode(matchMode)}:</span> {describeCstMathchMode(matchMode)}</p>
|
||||||
|
</DropdownButton>);
|
||||||
|
})}
|
||||||
|
</Dropdown>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref={sourceMenu.ref}>
|
||||||
|
<SelectorButton
|
||||||
|
tooltip='Настройка фильтрации по графу термов'
|
||||||
|
transparent
|
||||||
|
icon={<CogIcon size={4} />}
|
||||||
|
text={labelCstSource(filterSource)}
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={sourceMenu.toggle}
|
||||||
/>
|
/>
|
||||||
|
{ sourceMenu.isActive &&
|
||||||
|
<Dropdown stretchLeft>
|
||||||
|
{ Object.values(CstSource).filter(value => !isNaN(Number(value))).map(
|
||||||
|
(value, index) => {
|
||||||
|
const source = value as CstSource;
|
||||||
|
return (
|
||||||
|
<DropdownButton
|
||||||
|
key={`${prefixes.cst_source_list}${index}`}
|
||||||
|
onClick={() => handleSourceChange(source)}
|
||||||
|
>
|
||||||
|
<p><span className='font-semibold'>{labelCstSource(source)}:</span> {describeCstSource(source)}</p>
|
||||||
|
</DropdownButton>);
|
||||||
|
})}
|
||||||
|
</Dropdown>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='overflow-y-auto text-sm' style={{maxHeight : `${maxHeight}`}}>
|
<div className='overflow-y-auto text-sm' style={{maxHeight : `${maxHeight}`}}>
|
||||||
<DataTable
|
<DataTable
|
||||||
|
|
|
@ -36,6 +36,9 @@ export const prefixes = {
|
||||||
cst_list: 'cst-list-',
|
cst_list: 'cst-list-',
|
||||||
cst_wordform_list: 'cst-wordform-list-',
|
cst_wordform_list: 'cst-wordform-list-',
|
||||||
cst_status_list: 'cst-status-list-',
|
cst_status_list: 'cst-status-list-',
|
||||||
|
cst_match_mode_list: 'cst-match-mode-list-',
|
||||||
|
cst_source_list: 'cst-source-list-',
|
||||||
|
library_filters_list: 'library-filters-list',
|
||||||
topic_list: 'topic-list-',
|
topic_list: 'topic-list-',
|
||||||
library_list: 'library-list-',
|
library_list: 'library-list-',
|
||||||
wordform_list: 'wordform-list'
|
wordform_list: 'wordform-list'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// =========== Modules contains all text descriptors ==========
|
// =========== Modules contains all text descriptors ==========
|
||||||
|
|
||||||
import { GramData,Grammeme, ReferenceType } from '../models/language';
|
import { GramData,Grammeme, ReferenceType } from '../models/language';
|
||||||
import { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
|
import { CstMatchMode, DependencyMode, HelpTopic, LibraryFilterStrategy } from '../models/miscelanious';
|
||||||
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
||||||
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
||||||
|
|
||||||
|
@ -128,17 +128,27 @@ export function describeToken(id: TokenID): string {
|
||||||
|
|
||||||
export function labelCstMathchMode(mode: CstMatchMode): string {
|
export function labelCstMathchMode(mode: CstMatchMode): string {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case CstMatchMode.ALL: return 'везде';
|
case CstMatchMode.ALL: return 'общий';
|
||||||
case CstMatchMode.EXPR: return 'выраж';
|
case CstMatchMode.EXPR: return 'выражение';
|
||||||
case CstMatchMode.TERM: return 'термин';
|
case CstMatchMode.TERM: return 'термин';
|
||||||
case CstMatchMode.TEXT: return 'текст';
|
case CstMatchMode.TEXT: return 'текст';
|
||||||
case CstMatchMode.NAME: return 'имя';
|
case CstMatchMode.NAME: return 'имя';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function labelDependencyMode(mode: DependencyMode): string {
|
export function describeCstMathchMode(mode: CstMatchMode): string {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case DependencyMode.ALL: return 'вся схема';
|
case CstMatchMode.ALL: return 'искать во всех атрибутах';
|
||||||
|
case CstMatchMode.EXPR: return 'искать в формальных выражениях';
|
||||||
|
case CstMatchMode.TERM: return 'искать в терминах';
|
||||||
|
case CstMatchMode.TEXT: return 'искать в определениях и конвенциях';
|
||||||
|
case CstMatchMode.NAME: return 'искать в идентификаторах конституент';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function labelCstSource(mode: DependencyMode): string {
|
||||||
|
switch (mode) {
|
||||||
|
case DependencyMode.ALL: return 'не ограничен';
|
||||||
case DependencyMode.EXPRESSION: return 'выражение';
|
case DependencyMode.EXPRESSION: return 'выражение';
|
||||||
case DependencyMode.OUTPUTS: return 'потребители';
|
case DependencyMode.OUTPUTS: return 'потребители';
|
||||||
case DependencyMode.INPUTS: return 'поставщики';
|
case DependencyMode.INPUTS: return 'поставщики';
|
||||||
|
@ -147,6 +157,39 @@ export function labelDependencyMode(mode: DependencyMode): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function describeCstSource(mode: DependencyMode): string {
|
||||||
|
switch (mode) {
|
||||||
|
case DependencyMode.ALL: return 'все конституенты';
|
||||||
|
case DependencyMode.EXPRESSION: return 'идентификаторы из выражения';
|
||||||
|
case DependencyMode.OUTPUTS: return 'конституенты, ссылающиеся на данную';
|
||||||
|
case DependencyMode.INPUTS: return 'конституенты, на которые ссылается данная';
|
||||||
|
case DependencyMode.EXPAND_INPUTS: return 'конституенты, зависящие по цепочке';
|
||||||
|
case DependencyMode.EXPAND_OUTPUTS: return 'конституенты, влияющие на данную по цепочке';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function labelLibraryFilter(strategy: LibraryFilterStrategy): string {
|
||||||
|
switch (strategy) {
|
||||||
|
case LibraryFilterStrategy.MANUAL: return 'отображать все';
|
||||||
|
case LibraryFilterStrategy.COMMON: return 'общедоступные';
|
||||||
|
case LibraryFilterStrategy.CANONICAL: return 'неизменные';
|
||||||
|
case LibraryFilterStrategy.PERSONAL: return 'личные';
|
||||||
|
case LibraryFilterStrategy.SUBSCRIBE: return 'подписки';
|
||||||
|
case LibraryFilterStrategy.OWNED: return 'владелец';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeLibraryFilter(strategy: LibraryFilterStrategy): string {
|
||||||
|
switch (strategy) {
|
||||||
|
case LibraryFilterStrategy.MANUAL: return 'Отображать все схемы';
|
||||||
|
case LibraryFilterStrategy.COMMON: return 'Отображать общедоступные схемы';
|
||||||
|
case LibraryFilterStrategy.CANONICAL: return 'Отображать стандартные схемы';
|
||||||
|
case LibraryFilterStrategy.PERSONAL: return 'Отображать подписки и владеемые схемы';
|
||||||
|
case LibraryFilterStrategy.SUBSCRIBE: return 'Отображать подписки';
|
||||||
|
case LibraryFilterStrategy.OWNED: return 'Отображать владеемые схемы';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const mapLableLayout: Map<string, string> =
|
export const mapLableLayout: Map<string, string> =
|
||||||
new Map([
|
new Map([
|
||||||
['forceatlas2', 'Граф: Атлас 2D'],
|
['forceatlas2', 'Граф: Атлас 2D'],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user