mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Add search feature to multiple cst selector
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled
This commit is contained in:
parent
e7678c979a
commit
8f1fbcde3d
|
@ -1,10 +1,18 @@
|
||||||
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
|
||||||
import { CstMatchMode, DependencyMode } from '@/models/miscellaneous';
|
import { CstMatchMode, DependencyMode } from '@/models/miscellaneous';
|
||||||
import { ExpressionStatus } from '@/models/rsform';
|
import { CstType, ExpressionStatus } from '@/models/rsform';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IconAlias,
|
IconAlias,
|
||||||
IconBusiness,
|
IconBusiness,
|
||||||
|
IconCstAxiom,
|
||||||
|
IconCstBaseSet,
|
||||||
|
IconCstConstSet,
|
||||||
|
IconCstFunction,
|
||||||
|
IconCstPredicate,
|
||||||
|
IconCstStructured,
|
||||||
|
IconCstTerm,
|
||||||
|
IconCstTheorem,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconFormula,
|
IconFormula,
|
||||||
IconGraphCollapse,
|
IconGraphCollapse,
|
||||||
|
@ -132,3 +140,24 @@ export function StatusIcon({ value, size = '1.25rem', className }: DomIconProps<
|
||||||
return <IconStatusError size={size} className={className} />;
|
return <IconStatusError size={size} className={className} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps<CstType>) {
|
||||||
|
switch (value) {
|
||||||
|
case CstType.BASE:
|
||||||
|
return <IconCstBaseSet size={size} className={className ?? 'clr-text-green'} />;
|
||||||
|
case CstType.CONSTANT:
|
||||||
|
return <IconCstConstSet size={size} className={className ?? 'clr-text-green'} />;
|
||||||
|
case CstType.STRUCTURED:
|
||||||
|
return <IconCstStructured size={size} className={className ?? 'clr-text-green'} />;
|
||||||
|
case CstType.TERM:
|
||||||
|
return <IconCstTerm size={size} className={className ?? 'clr-text-primary'} />;
|
||||||
|
case CstType.AXIOM:
|
||||||
|
return <IconCstAxiom size={size} className={className ?? 'clr-text-red'} />;
|
||||||
|
case CstType.FUNCTION:
|
||||||
|
return <IconCstFunction size={size} className={className ?? 'clr-text-primary'} />;
|
||||||
|
case CstType.PREDICATE:
|
||||||
|
return <IconCstPredicate size={size} className={className ?? 'clr-text-red'} />;
|
||||||
|
case CstType.THEOREM:
|
||||||
|
return <IconCstTheorem size={size} className={className ?? 'clr-text-red'} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,12 +5,14 @@ import { useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
|
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
import { CstMatchMode } from '@/models/miscellaneous';
|
||||||
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
||||||
import { isBasicConcept } from '@/models/rsformAPI';
|
import { isBasicConcept, matchConstituenta } from '@/models/rsformAPI';
|
||||||
import { describeConstituenta } from '@/utils/labels';
|
import { describeConstituenta } from '@/utils/labels';
|
||||||
|
|
||||||
import BadgeConstituenta from '../info/BadgeConstituenta';
|
import BadgeConstituenta from '../info/BadgeConstituenta';
|
||||||
import NoData from '../ui/NoData';
|
import NoData from '../ui/NoData';
|
||||||
|
import SearchBar from '../ui/SearchBar';
|
||||||
import ToolbarGraphSelection from './ToolbarGraphSelection';
|
import ToolbarGraphSelection from './ToolbarGraphSelection';
|
||||||
|
|
||||||
interface PickMultiConstituentaProps {
|
interface PickMultiConstituentaProps {
|
||||||
|
@ -28,18 +30,30 @@ const columnHelper = createColumnHelper<IConstituenta>();
|
||||||
function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelected }: PickMultiConstituentaProps) {
|
function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelected }: PickMultiConstituentaProps) {
|
||||||
const { colors } = useConceptOptions();
|
const { colors } = useConceptOptions();
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
|
const [filtered, setFiltered] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||||
|
const [filterText, setFilterText] = useState('');
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!schema || selected.length === 0) {
|
if (filtered.length === 0) {
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
} else {
|
return;
|
||||||
const newRowSelection: RowSelectionState = {};
|
|
||||||
schema.items.forEach((cst, index) => {
|
|
||||||
newRowSelection[String(index)] = selected.includes(cst.id);
|
|
||||||
});
|
|
||||||
setRowSelection(newRowSelection);
|
|
||||||
}
|
}
|
||||||
}, [selected, schema]);
|
const newRowSelection: RowSelectionState = {};
|
||||||
|
filtered.forEach((cst, index) => {
|
||||||
|
newRowSelection[String(index)] = selected.includes(cst.id);
|
||||||
|
});
|
||||||
|
setRowSelection(newRowSelection);
|
||||||
|
}, [filtered, setRowSelection, selected]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!schema || schema.items.length === 0) {
|
||||||
|
setFiltered([]);
|
||||||
|
} else if (filterText) {
|
||||||
|
setFiltered(schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL)));
|
||||||
|
} else {
|
||||||
|
setFiltered(schema.items);
|
||||||
|
}
|
||||||
|
}, [filterText, schema?.items, schema]);
|
||||||
|
|
||||||
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -47,12 +61,12 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
|
||||||
} else {
|
} else {
|
||||||
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
||||||
const newSelection: ConstituentaID[] = [];
|
const newSelection: ConstituentaID[] = [];
|
||||||
schema.items.forEach((cst, index) => {
|
filtered.forEach((cst, index) => {
|
||||||
if (newRowSelection[String(index)] === true) {
|
if (newRowSelection[String(index)] === true) {
|
||||||
newSelection.push(cst.id);
|
newSelection.push(cst.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setSelected(newSelection);
|
setSelected(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +89,17 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className='flex items-end gap-3 mb-3'>
|
<div className='flex justify-between items-center gap-3 clr-input px-3 border-x border-t rounded-t-md'>
|
||||||
<span className='w-[24ch] select-none whitespace-nowrap'>
|
<div className='w-[24ch] select-none whitespace-nowrap'>
|
||||||
Выбраны {selected.length} из {schema?.items.length ?? 0}
|
Выбраны {selected.length} из {schema?.items.length ?? 0}
|
||||||
</span>
|
</div>
|
||||||
|
<SearchBar
|
||||||
|
id='dlg_constituents_search'
|
||||||
|
noBorder
|
||||||
|
className='min-w-[6rem] pr-2 flex-grow'
|
||||||
|
value={filterText}
|
||||||
|
onChange={setFilterText}
|
||||||
|
/>
|
||||||
{schema ? (
|
{schema ? (
|
||||||
<ToolbarGraphSelection
|
<ToolbarGraphSelection
|
||||||
graph={schema.graph}
|
graph={schema.graph}
|
||||||
|
@ -86,7 +107,7 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
|
||||||
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
|
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
|
||||||
setSelected={setSelected}
|
setSelected={setSelected}
|
||||||
emptySelection={selected.length === 0}
|
emptySelection={selected.length === 0}
|
||||||
className='w-full ml-8'
|
className='w-fit'
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,7 +118,7 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
|
||||||
rows={rows}
|
rows={rows}
|
||||||
contentHeight='1.3rem'
|
contentHeight='1.3rem'
|
||||||
className={clsx('cc-scroll-y', 'border', 'text-sm', 'select-none')}
|
className={clsx('cc-scroll-y', 'border', 'text-sm', 'select-none')}
|
||||||
data={schema?.items ?? []}
|
data={filtered}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
headPosition='0rem'
|
headPosition='0rem'
|
||||||
enableRowSelection
|
enableRowSelection
|
||||||
|
|
|
@ -96,6 +96,7 @@ function PickSchema({
|
||||||
<div className='border divide-y'>
|
<div className='border divide-y'>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
id={id ? `${id}__search` : undefined}
|
id={id ? `${id}__search` : undefined}
|
||||||
|
className='clr-input'
|
||||||
noBorder
|
noBorder
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={newValue => setFilterText(newValue)}
|
onChange={newValue => setFilterText(newValue)}
|
||||||
|
|
|
@ -18,7 +18,7 @@ function Dropdown({ isOpen, stretchLeft, stretchTop, className, children, ...res
|
||||||
<motion.div
|
<motion.div
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'z-modalTooltip',
|
'z-topmost',
|
||||||
'absolute mt-3',
|
'absolute mt-3',
|
||||||
'flex flex-col',
|
'flex flex-col',
|
||||||
'border rounded-md shadow-lg',
|
'border rounded-md shadow-lg',
|
||||||
|
|
|
@ -27,7 +27,7 @@ function SearchBar({ id, value, noIcon, onChange, noBorder, placeholder = 'По
|
||||||
noOutline
|
noOutline
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type='search'
|
type='search'
|
||||||
className={clsx('w-full outline-none', !noIcon && 'pl-10')}
|
className={clsx('w-full outline-none bg-transparent', !noIcon && 'pl-10')}
|
||||||
noBorder={noBorder}
|
noBorder={noBorder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={event => (onChange ? onChange(event.target.value) : undefined)}
|
onChange={event => (onChange ? onChange(event.target.value) : undefined)}
|
||||||
|
|
|
@ -29,7 +29,7 @@ function Tooltip({
|
||||||
}
|
}
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<TooltipImpl
|
<TooltipImpl
|
||||||
delayShow={1000}
|
delayShow={750}
|
||||||
delayHide={100}
|
delayHide={100}
|
||||||
opacity={1}
|
opacity={1}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { IconCSV } from '@/components/Icons';
|
import { IconCSV } from '@/components/Icons';
|
||||||
import SelectedCounter from '@/components/info/SelectedCounter';
|
|
||||||
import { type RowSelectionState } from '@/components/ui/DataTable';
|
import { type RowSelectionState } from '@/components/ui/DataTable';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
import SearchBar from '@/components/ui/SearchBar';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { ConstituentaID, CstType } from '@/models/rsform';
|
import { CstMatchMode } from '@/models/miscellaneous';
|
||||||
|
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
|
||||||
|
import { matchConstituenta } from '@/models/rsformAPI';
|
||||||
import { information } from '@/utils/labels';
|
import { information } from '@/utils/labels';
|
||||||
import { convertToCSV } from '@/utils/utils';
|
import { convertToCSV } from '@/utils/utils';
|
||||||
|
|
||||||
|
@ -29,30 +30,43 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
const controller = useRSEdit();
|
const controller = useRSEdit();
|
||||||
|
|
||||||
|
const [filtered, setFiltered] = useState<IConstituenta[]>(controller.schema?.items ?? []);
|
||||||
|
const [filterText, setFilterText] = useState('');
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!controller.schema || controller.selected.length === 0) {
|
if (filtered.length === 0) {
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
} else {
|
return;
|
||||||
const newRowSelection: RowSelectionState = {};
|
|
||||||
controller.schema.items.forEach((cst, index) => {
|
|
||||||
newRowSelection[String(index)] = controller.selected.includes(cst.id);
|
|
||||||
});
|
|
||||||
setRowSelection(newRowSelection);
|
|
||||||
}
|
}
|
||||||
}, [controller.selected, controller.schema]);
|
const newRowSelection: RowSelectionState = {};
|
||||||
|
filtered.forEach((cst, index) => {
|
||||||
|
newRowSelection[String(index)] = controller.selected.includes(cst.id);
|
||||||
|
});
|
||||||
|
setRowSelection(newRowSelection);
|
||||||
|
}, [filtered, setRowSelection, controller.selected]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!controller.schema || controller.schema.items.length === 0) {
|
||||||
|
setFiltered([]);
|
||||||
|
} else if (filterText) {
|
||||||
|
setFiltered(controller.schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL)));
|
||||||
|
} else {
|
||||||
|
setFiltered(controller.schema.items);
|
||||||
|
}
|
||||||
|
}, [filterText, controller.schema?.items, controller.schema]);
|
||||||
|
|
||||||
const handleDownloadCSV = useCallback(() => {
|
const handleDownloadCSV = useCallback(() => {
|
||||||
if (!controller.schema || controller.schema.items.length === 0) {
|
if (!controller.schema || filtered.length === 0) {
|
||||||
toast.error(information.noDataToExport);
|
toast.error(information.noDataToExport);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blob = convertToCSV(controller.schema.items);
|
const blob = convertToCSV(filtered);
|
||||||
try {
|
try {
|
||||||
fileDownload(blob, `${controller.schema.alias}.csv`, 'text/csv;charset=utf-8;');
|
fileDownload(blob, `${controller.schema.alias}.csv`, 'text/csv;charset=utf-8;');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}, [controller]);
|
}, [filtered, controller]);
|
||||||
|
|
||||||
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
||||||
if (!controller.schema) {
|
if (!controller.schema) {
|
||||||
|
@ -60,12 +74,15 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||||
} else {
|
} else {
|
||||||
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
||||||
const newSelection: ConstituentaID[] = [];
|
const newSelection: ConstituentaID[] = [];
|
||||||
controller.schema.items.forEach((cst, index) => {
|
filtered.forEach((cst, index) => {
|
||||||
if (newRowSelection[String(index)] === true) {
|
if (newRowSelection[String(index)] === true) {
|
||||||
newSelection.push(cst.id);
|
newSelection.push(cst.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controller.setSelected(newSelection);
|
controller.setSelected(prev => [
|
||||||
|
...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)),
|
||||||
|
...newSelection
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,21 +144,21 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||||
{controller.isContentEditable ? <ToolbarRSList /> : null}
|
{controller.isContentEditable ? <ToolbarRSList /> : null}
|
||||||
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
|
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
|
||||||
{controller.isContentEditable ? (
|
{controller.isContentEditable ? (
|
||||||
<SelectedCounter
|
<div className='flex items-center border-b'>
|
||||||
totalCount={controller.schema?.stats?.count_all ?? 0}
|
<div className='px-2'>
|
||||||
selectedCount={controller.selected.length}
|
Выбор {controller.selected.length} из {controller.schema?.stats?.count_all ?? 0}
|
||||||
position='top-[0.3rem] left-2'
|
</div>
|
||||||
/>
|
<SearchBar
|
||||||
|
id='constituents_search'
|
||||||
|
noBorder
|
||||||
|
className='w-[8rem]'
|
||||||
|
value={filterText}
|
||||||
|
onChange={setFilterText}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div
|
<Overlay position='top-[0.25rem] right-[1rem]' layer='z-navigation'>
|
||||||
className={clsx('border-b', {
|
|
||||||
'pt-[2.3rem]': controller.isContentEditable,
|
|
||||||
'relative top-[-1px]': !controller.isContentEditable
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Overlay position='top-[0.25rem] right-[1rem]' layer='z-tooltip'>
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Выгрузить в формате CSV'
|
title='Выгрузить в формате CSV'
|
||||||
icon={<IconCSV size='1.25rem' className='icon-green' />}
|
icon={<IconCSV size='1.25rem' className='icon-green' />}
|
||||||
|
@ -150,7 +167,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
|
||||||
<TableRSList
|
<TableRSList
|
||||||
items={controller.schema?.items}
|
items={filtered}
|
||||||
maxHeight={tableHeight}
|
maxHeight={tableHeight}
|
||||||
enableSelection={controller.isContentEditable}
|
enableSelection={controller.isContentEditable}
|
||||||
selected={rowSelection}
|
selected={rowSelection}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CstTypeIcon } from '@/components/DomainIcons';
|
||||||
import {
|
import {
|
||||||
IconClone,
|
IconClone,
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
|
@ -16,7 +17,6 @@ import Overlay from '@/components/ui/Overlay';
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
import { CstType } from '@/models/rsform';
|
import { CstType } from '@/models/rsform';
|
||||||
import { getCstTypePrefix } from '@/models/rsformAPI';
|
|
||||||
import { prefixes } from '@/utils/constants';
|
import { prefixes } from '@/utils/constants';
|
||||||
import { getCstTypeShortcut, labelCstType, prepareTooltip } from '@/utils/labels';
|
import { getCstTypeShortcut, labelCstType, prepareTooltip } from '@/utils/labels';
|
||||||
|
|
||||||
|
@ -27,7 +27,10 @@ function ToolbarRSList() {
|
||||||
const insertMenu = useDropdown();
|
const insertMenu = useDropdown();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='items-start cc-icons'>
|
<Overlay
|
||||||
|
position='top-1 right-4 translate-x-0 md:right-1/2 md:translate-x-1/2'
|
||||||
|
className='cc-icons items-start outline-none transition-all duration-500'
|
||||||
|
>
|
||||||
{controller.schema && controller.schema?.oss.length > 0 ? (
|
{controller.schema && controller.schema?.oss.length > 0 ? (
|
||||||
<MiniSelectorOSS
|
<MiniSelectorOSS
|
||||||
items={controller.schema.oss}
|
items={controller.schema.oss}
|
||||||
|
@ -52,18 +55,6 @@ function ToolbarRSList() {
|
||||||
disabled={controller.isProcessing || controller.nothingSelected}
|
disabled={controller.isProcessing || controller.nothingSelected}
|
||||||
onClick={controller.moveDown}
|
onClick={controller.moveDown}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
|
||||||
titleHtml={prepareTooltip('Клонировать конституенту', 'Alt + V')}
|
|
||||||
icon={<IconClone size='1.25rem' className='icon-green' />}
|
|
||||||
disabled={controller.isProcessing || controller.selected.length !== 1}
|
|
||||||
onClick={controller.cloneCst}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
titleHtml={prepareTooltip('Добавить новую конституенту...', 'Alt + `')}
|
|
||||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
|
||||||
disabled={controller.isProcessing}
|
|
||||||
onClick={() => controller.createCst(undefined, false)}
|
|
||||||
/>
|
|
||||||
<div ref={insertMenu.ref}>
|
<div ref={insertMenu.ref}>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Добавить пустую конституенту'
|
title='Добавить пустую конституенту'
|
||||||
|
@ -72,17 +63,30 @@ function ToolbarRSList() {
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
onClick={insertMenu.toggle}
|
onClick={insertMenu.toggle}
|
||||||
/>
|
/>
|
||||||
<Dropdown isOpen={insertMenu.isOpen}>
|
<Dropdown isOpen={insertMenu.isOpen} className='-translate-x-1/2 md:translate-x-0'>
|
||||||
{Object.values(CstType).map(typeStr => (
|
{Object.values(CstType).map(typeStr => (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
key={`${prefixes.csttype_list}${typeStr}`}
|
key={`${prefixes.csttype_list}${typeStr}`}
|
||||||
text={`${getCstTypePrefix(typeStr as CstType)}1 — ${labelCstType(typeStr as CstType)}`}
|
text={labelCstType(typeStr as CstType)}
|
||||||
|
icon={<CstTypeIcon value={typeStr as CstType} size='1.25rem' />}
|
||||||
onClick={() => controller.createCst(typeStr as CstType, true)}
|
onClick={() => controller.createCst(typeStr as CstType, true)}
|
||||||
titleHtml={getCstTypeShortcut(typeStr as CstType)}
|
titleHtml={getCstTypeShortcut(typeStr as CstType)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
<MiniButton
|
||||||
|
titleHtml={prepareTooltip('Добавить новую конституенту...', 'Alt + `')}
|
||||||
|
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||||
|
disabled={controller.isProcessing}
|
||||||
|
onClick={() => controller.createCst(undefined, false)}
|
||||||
|
/>
|
||||||
|
<MiniButton
|
||||||
|
titleHtml={prepareTooltip('Клонировать конституенту', 'Alt + V')}
|
||||||
|
icon={<IconClone size='1.25rem' className='icon-green' />}
|
||||||
|
disabled={controller.isProcessing || controller.selected.length !== 1}
|
||||||
|
onClick={controller.cloneCst}
|
||||||
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Удалить выбранные', 'Delete')}
|
titleHtml={prepareTooltip('Удалить выбранные', 'Delete')}
|
||||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||||
|
|
|
@ -267,7 +267,7 @@ function RSTabs() {
|
||||||
<TabLabel label='Граф термов' />
|
<TabLabel label='Граф термов' />
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<AnimateFade className='overflow-y-auto' style={{ maxHeight: panelHeight }}>
|
<AnimateFade className='overflow-y-auto overflow-x-hidden' style={{ maxHeight: panelHeight }}>
|
||||||
{cardPanel}
|
{cardPanel}
|
||||||
{listPanel}
|
{listPanel}
|
||||||
{editorPanel}
|
{editorPanel}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user