Implementing inline synthesis pt1. Frontend

This commit is contained in:
IRBorisov 2024-03-18 16:22:27 +03:00
parent 56cb7236ca
commit f8364d1a06
8 changed files with 335 additions and 13 deletions

View File

@ -0,0 +1,117 @@
import { useLayoutEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/DataTable';
import SearchBar from '@/components/ui/SearchBar';
import { useLibrary } from '@/context/LibraryContext';
import { useConceptTheme } from '@/context/ThemeContext';
import { ILibraryItem, LibraryItemID } from '@/models/library';
import { ILibraryFilter } from '@/models/miscellaneous';
import FlexColumn from './ui/FlexColumn';
interface SchemaPickerProps {
id?: string;
initialFilter?: string;
rows?: number;
value?: LibraryItemID;
onSelectValue: (newValue: LibraryItemID) => void;
}
const columnHelper = createColumnHelper<ILibraryItem>();
function SchemaPicker({ id, initialFilter = '', rows = 4, value, onSelectValue }: SchemaPickerProps) {
const intl = useIntl();
const { colors } = useConceptTheme();
const library = useLibrary();
const [filterText, setFilterText] = useState(initialFilter);
const [filter, setFilter] = useState<ILibraryFilter>({});
const [items, setItems] = useState<ILibraryItem[]>([]);
useLayoutEffect(() => {
setFilter({
query: filterText
});
}, [filterText]);
useLayoutEffect(() => {
setItems(library.applyFilter(filter));
}, [library, filter, filter.query]);
const columns = useMemo(
() => [
columnHelper.accessor('alias', {
id: 'alias',
header: 'Шифр',
size: 150,
minSize: 80,
maxSize: 150
}),
columnHelper.accessor('title', {
id: 'title',
header: 'Название',
size: 1200,
minSize: 200,
maxSize: 1200,
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.accessor('time_update', {
id: 'time_update',
header: 'Дата',
cell: props => (
<div className='whitespace-nowrap'>
{new Date(props.getValue()).toLocaleString(intl.locale, {
year: '2-digit',
month: '2-digit',
day: '2-digit'
})}
</div>
)
})
],
[intl]
);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<ILibraryItem>[] => [
{
when: (item: ILibraryItem) => item.id === value,
style: { backgroundColor: colors.bgSelected }
}
],
[value, colors]
);
return (
<div className='border divide-y'>
<SearchBar
id={id ? `${id}__search` : undefined}
noBorder
value={filterText}
onChange={newValue => setFilterText(newValue)}
/>
<DataTable
id={id}
rows={rows}
dense
noHeader
noFooter
className='overflow-y-auto text-sm select-none'
data={items}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<FlexColumn className='p-3 items-center min-h-[6rem]'>
<p>Список схем пуст</p>
<p>Измените параметры фильтра</p>
</FlexColumn>
}
onRowClicked={rowData => onSelectValue(rowData.id)}
/>
</div>
);
}
export default SchemaPicker;

View File

@ -0,0 +1,18 @@
'use client';
import { LibraryItemID } from '@/models/library';
import { IRSForm } from '@/models/rsform';
interface ConstituentsTabProps {
schema?: IRSForm;
loading?: boolean;
selected: LibraryItemID[];
setSelected: React.Dispatch<LibraryItemID[]>;
}
// { schema, loading, selected, setSelected }: ConstituentsTabProps
function ConstituentsTab(props: ConstituentsTabProps) {
return <>2 - {props.loading}</>;
}
export default ConstituentsTab;

View File

@ -0,0 +1,101 @@
'use client';
import clsx from 'clsx';
import { useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal, { ModalProps } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel';
import useRSFormDetails from '@/hooks/useRSFormDetails';
import { LibraryItemID } from '@/models/library';
import { ICstSubstituteData, IRSForm, IRSFormInlineData } from '@/models/rsform';
import ConstituentsTab from './ConstituentsTab';
import SchemaTab from './SchemaTab';
import SubstitutionsTab from './SubstitutionsTab';
interface DlgInlineSynthesisProps extends Pick<ModalProps, 'hideWindow'> {
receiver: IRSForm;
onInlineSynthesis: (data: IRSFormInlineData) => void;
}
export enum TabID {
SCHEMA = 0,
SELECTIONS = 1,
SUBSTITUTIONS = 2
}
function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInlineSynthesisProps) {
const [activeTab, setActiveTab] = useState(TabID.SCHEMA);
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
const [selected, setSelected] = useState<LibraryItemID[]>([]);
const [substitutions, setSubstitutions] = useState<ICstSubstituteData[]>([]);
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });
const validated = useMemo(() => false, []);
function handleSubmit() {
if (!source.schema) {
return;
}
const data: IRSFormInlineData = {
source: source.schema?.id,
receiver: receiver.id,
items: selected,
substitutions: substitutions
};
onInlineSynthesis(data);
}
return (
<Modal
header='Импорт концептуальной схем'
submitText='Добавить конституенты'
className='w-[35rem] h-[30rem] px-6'
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
>
<Tabs
forceRenderTabPanel
selectedTabClassName='clr-selected'
className='flex flex-col'
selectedIndex={activeTab}
onSelect={setActiveTab}
>
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<TabLabel label='Схема' title='Выбор импортируемой схемы' className='w-[8rem]' />
<TabLabel label='Содержание' title='Выбор переносимого содержания' className='w-[8rem]' />
<TabLabel label='Отождествления' title='Отождествление добавляемый конституент' className='w-[8rem]' />
</TabList>
<TabPanel style={{ display: activeTab === TabID.SCHEMA ? '' : 'none' }}>
<SchemaTab selected={donorID} setSelected={setDonorID} />
</TabPanel>
<TabPanel style={{ display: activeTab === TabID.SELECTIONS ? '' : 'none' }}>
<ConstituentsTab
schema={source.schema}
loading={source.loading}
selected={selected}
setSelected={setSelected}
/>
</TabPanel>
<TabPanel style={{ display: activeTab === TabID.SUBSTITUTIONS ? '' : 'none' }}>
<SubstitutionsTab
receiver={receiver}
source={source.schema}
loading={source.loading}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>
</TabPanel>
</Tabs>
</Modal>
);
}
export default DlgInlineSynthesis;

View File

@ -0,0 +1,36 @@
'use client';
import { useMemo } from 'react';
import SchemaPicker from '@/components/SchemaPicker';
import FlexColumn from '@/components/ui/FlexColumn';
import TextInput from '@/components/ui/TextInput';
import { useLibrary } from '@/context/LibraryContext';
import { LibraryItemID } from '@/models/library';
interface SchemaTabProps {
selected?: LibraryItemID;
setSelected: React.Dispatch<LibraryItemID | undefined>;
}
function SchemaTab({ selected, setSelected }: SchemaTabProps) {
const library = useLibrary();
const selectedInfo = useMemo(() => library.items.find(item => item.id === selected), [selected, library.items]);
return (
<FlexColumn>
<TextInput
id='dlg_selected_schema_title'
label='Выбрана'
noBorder
placeholder='Выберите из списка ниже'
value={selectedInfo?.title}
disabled
dense
/>
<SchemaPicker rows={6} value={selected} onSelectValue={setSelected} />
</FlexColumn>
);
}
export default SchemaTab;

View File

@ -0,0 +1,18 @@
'use client';
import { ICstSubstituteData, IRSForm } from '@/models/rsform';
interface SubstitutionsTabProps {
receiver?: IRSForm;
source?: IRSForm;
loading?: boolean;
substitutions: ICstSubstituteData[];
setSubstitutions: React.Dispatch<ICstSubstituteData[]>;
}
// { source, receiver, loading, substitutions, setSubstitutions }: SubstitutionsTabProps
function SubstitutionsTab(props: SubstitutionsTabProps) {
return <>3 - {props.loading}</>;
}
export default SubstitutionsTab;

View File

@ -0,0 +1 @@
export { default } from './DlgInlineSynthesis';

View File

@ -21,14 +21,15 @@ import DlgCreateVersion from '@/dialogs/DlgCreateVersion';
import DlgDeleteCst from '@/dialogs/DlgDeleteCst'; import DlgDeleteCst from '@/dialogs/DlgDeleteCst';
import DlgEditVersions from '@/dialogs/DlgEditVersions'; import DlgEditVersions from '@/dialogs/DlgEditVersions';
import DlgEditWordForms from '@/dialogs/DlgEditWordForms'; import DlgEditWordForms from '@/dialogs/DlgEditWordForms';
import DlgInlineSynthesis from '@/dialogs/DlgInlineSynthesis';
import DlgRenameCst from '@/dialogs/DlgRenameCst'; import DlgRenameCst from '@/dialogs/DlgRenameCst';
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst'; import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm'; import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
import { IVersionData } from '@/models/library'; import { IVersionData } from '@/models/library';
import { UserAccessMode } from '@/models/miscellaneous'; import { UserAccessMode } from '@/models/miscellaneous';
import { import {
ConstituentaID,
CstType, CstType,
EntityID,
IConstituenta, IConstituenta,
IConstituentaMeta, IConstituentaMeta,
ICstCreateData, ICstCreateData,
@ -69,6 +70,7 @@ interface IRSEditContext {
download: () => void; download: () => void;
reindex: () => void; reindex: () => void;
produceStructure: () => void; produceStructure: () => void;
inlineSynthesis: () => void;
substitute: () => void; substitute: () => void;
createVersion: () => void; createVersion: () => void;
@ -85,13 +87,13 @@ export const useRSEdit = () => {
}; };
interface RSEditStateProps { interface RSEditStateProps {
selected: EntityID[]; selected: ConstituentaID[];
isModified: boolean; isModified: boolean;
setSelected: React.Dispatch<React.SetStateAction<EntityID[]>>; setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
activeCst?: IConstituenta; activeCst?: IConstituenta;
onCreateCst?: (newCst: IConstituentaMeta) => void; onCreateCst?: (newCst: IConstituentaMeta) => void;
onDeleteCst?: (newActive?: EntityID) => void; onDeleteCst?: (newActive?: ConstituentaID) => void;
children: React.ReactNode; children: React.ReactNode;
} }
@ -127,6 +129,7 @@ export const RSEditState = ({
const [showSubstitute, setShowSubstitute] = useState(false); const [showSubstitute, setShowSubstitute] = useState(false);
const [showCreateVersion, setShowCreateVersion] = useState(false); const [showCreateVersion, setShowCreateVersion] = useState(false);
const [showEditVersions, setShowEditVersions] = useState(false); const [showEditVersions, setShowEditVersions] = useState(false);
const [showInlineSynthesis, setShowInlineSynthesis] = useState(false);
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>(); const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
const [showCreateCst, setShowCreateCst] = useState(false); const [showCreateCst, setShowCreateCst] = useState(false);
@ -134,7 +137,7 @@ export const RSEditState = ({
const [renameInitialData, setRenameInitialData] = useState<ICstRenameData>(); const [renameInitialData, setRenameInitialData] = useState<ICstRenameData>();
const [showRenameCst, setShowRenameCst] = useState(false); const [showRenameCst, setShowRenameCst] = useState(false);
const [insertCstID, setInsertCstID] = useState<EntityID | undefined>(undefined); const [insertCstID, setInsertCstID] = useState<ConstituentaID | undefined>(undefined);
const [showTemplates, setShowTemplates] = useState(false); const [showTemplates, setShowTemplates] = useState(false);
useLayoutEffect( useLayoutEffect(
@ -192,7 +195,7 @@ export const RSEditState = ({
); );
const handleDeleteCst = useCallback( const handleDeleteCst = useCallback(
(deleted: EntityID[]) => { (deleted: ConstituentaID[]) => {
if (!model.schema) { if (!model.schema) {
return; return;
} }
@ -479,6 +482,7 @@ export const RSEditState = ({
share, share,
toggleSubscribe, toggleSubscribe,
reindex, reindex,
inlineSynthesis: () => setShowInlineSynthesis(true),
produceStructure, produceStructure,
substitute, substitute,
@ -549,6 +553,13 @@ export const RSEditState = ({
onUpdate={handleUpdateVersion} onUpdate={handleUpdateVersion}
/> />
) : null} ) : null}
{showInlineSynthesis ? (
<DlgInlineSynthesis
receiver={model.schema}
hideWindow={() => setShowInlineSynthesis(false)}
onInlineSynthesis={() => toast('Testing')}
/>
) : null}
</AnimatePresence> </AnimatePresence>
) : null} ) : null}
@ -586,10 +597,10 @@ function ProcessError({
} }
function getNextActiveOnDelete( function getNextActiveOnDelete(
activeID: number | undefined, activeID: ConstituentaID | undefined,
items: IConstituenta[], items: IConstituenta[],
deleted: number[] deleted: ConstituentaID[]
): number | undefined { ): ConstituentaID | undefined {
if (items.length === deleted.length) { if (items.length === deleted.length) {
return undefined; return undefined;
} }

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import { import {
BiAnalyse,
BiDiamond, BiDiamond,
BiDownload, BiDownload,
BiDuplicate, BiDuplicate,
@ -13,7 +12,16 @@ import {
BiUpload BiUpload
} from 'react-icons/bi'; } from 'react-icons/bi';
import { FiEdit } from 'react-icons/fi'; import { FiEdit } from 'react-icons/fi';
import { LuAlertTriangle, LuArchive, LuCrown, LuGlasses, LuNetwork, LuReplace } from 'react-icons/lu'; import {
LuAlertTriangle,
LuArchive,
LuBookCopy,
LuCrown,
LuGlasses,
LuNetwork,
LuReplace,
LuWand2
} from 'react-icons/lu';
import { VscLibrary } from 'react-icons/vsc'; import { VscLibrary } from 'react-icons/vsc';
import Button from '@/components/ui/Button'; import Button from '@/components/ui/Button';
@ -95,6 +103,11 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
controller.produceStructure(); controller.produceStructure();
} }
function handleInlineSynthesis() {
editMenu.hide();
controller.inlineSynthesis();
}
function handleChangeMode(newMode: UserAccessMode) { function handleChangeMode(newMode: UserAccessMode) {
accessMenu.hide(); accessMenu.hide();
setMode(newMode); setMode(newMode);
@ -201,12 +214,19 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
icon={<BiDiamond size='1rem' className='icon-green' />} icon={<BiDiamond size='1rem' className='icon-green' />}
onClick={handleTemplates} onClick={handleTemplates}
/> />
<DropdownButton
disabled={!controller.isContentEditable || !user.is_staff}
text='Применить конструкт'
title='Импортировать совокупность конституент из другой схемы'
icon={<LuBookCopy size='1rem' className='icon-green' />}
onClick={handleInlineSynthesis}
/>
<DropdownButton <DropdownButton
disabled={!controller.isContentEditable} disabled={!controller.isContentEditable}
className='border-t-2' className='border-t-2'
text='Сброс имён' text='Сброс имён'
title='Присвоить порядковые имена и обновить выражения' title='Присвоить порядковые имена и обновить выражения'
icon={<BiAnalyse size='1rem' className='icon-primary' />} icon={<LuWand2 size='1rem' className='icon-primary' />}
onClick={handleReindex} onClick={handleReindex}
/> />
<DropdownButton <DropdownButton
@ -220,7 +240,7 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
disabled={!controller.isContentEditable} disabled={!controller.isContentEditable}
text='Отождествление' text='Отождествление'
title='Заменить вхождения одной конституенты на другую' title='Заменить вхождения одной конституенты на другую'
icon={<LuReplace size='1rem' className='icon-primary' />} icon={<LuReplace size='1rem' className='icon-red' />}
onClick={handleSubstituteCst} onClick={handleSubstituteCst}
/> />
</Dropdown> </Dropdown>