mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implementing inline synthesis pt1. Frontend
This commit is contained in:
parent
56cb7236ca
commit
f8364d1a06
117
rsconcept/frontend/src/components/SchemaPicker.tsx
Normal file
117
rsconcept/frontend/src/components/SchemaPicker.tsx
Normal 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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default } from './DlgInlineSynthesis';
|
|
@ -21,14 +21,15 @@ import DlgCreateVersion from '@/dialogs/DlgCreateVersion';
|
|||
import DlgDeleteCst from '@/dialogs/DlgDeleteCst';
|
||||
import DlgEditVersions from '@/dialogs/DlgEditVersions';
|
||||
import DlgEditWordForms from '@/dialogs/DlgEditWordForms';
|
||||
import DlgInlineSynthesis from '@/dialogs/DlgInlineSynthesis';
|
||||
import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
||||
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
|
||||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
||||
import { IVersionData } from '@/models/library';
|
||||
import { UserAccessMode } from '@/models/miscellaneous';
|
||||
import {
|
||||
ConstituentaID,
|
||||
CstType,
|
||||
EntityID,
|
||||
IConstituenta,
|
||||
IConstituentaMeta,
|
||||
ICstCreateData,
|
||||
|
@ -69,6 +70,7 @@ interface IRSEditContext {
|
|||
download: () => void;
|
||||
reindex: () => void;
|
||||
produceStructure: () => void;
|
||||
inlineSynthesis: () => void;
|
||||
substitute: () => void;
|
||||
|
||||
createVersion: () => void;
|
||||
|
@ -85,13 +87,13 @@ export const useRSEdit = () => {
|
|||
};
|
||||
|
||||
interface RSEditStateProps {
|
||||
selected: EntityID[];
|
||||
selected: ConstituentaID[];
|
||||
isModified: boolean;
|
||||
setSelected: React.Dispatch<React.SetStateAction<EntityID[]>>;
|
||||
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
||||
activeCst?: IConstituenta;
|
||||
|
||||
onCreateCst?: (newCst: IConstituentaMeta) => void;
|
||||
onDeleteCst?: (newActive?: EntityID) => void;
|
||||
onDeleteCst?: (newActive?: ConstituentaID) => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
|
@ -127,6 +129,7 @@ export const RSEditState = ({
|
|||
const [showSubstitute, setShowSubstitute] = useState(false);
|
||||
const [showCreateVersion, setShowCreateVersion] = useState(false);
|
||||
const [showEditVersions, setShowEditVersions] = useState(false);
|
||||
const [showInlineSynthesis, setShowInlineSynthesis] = useState(false);
|
||||
|
||||
const [createInitialData, setCreateInitialData] = useState<ICstCreateData>();
|
||||
const [showCreateCst, setShowCreateCst] = useState(false);
|
||||
|
@ -134,7 +137,7 @@ export const RSEditState = ({
|
|||
const [renameInitialData, setRenameInitialData] = useState<ICstRenameData>();
|
||||
const [showRenameCst, setShowRenameCst] = useState(false);
|
||||
|
||||
const [insertCstID, setInsertCstID] = useState<EntityID | undefined>(undefined);
|
||||
const [insertCstID, setInsertCstID] = useState<ConstituentaID | undefined>(undefined);
|
||||
const [showTemplates, setShowTemplates] = useState(false);
|
||||
|
||||
useLayoutEffect(
|
||||
|
@ -192,7 +195,7 @@ export const RSEditState = ({
|
|||
);
|
||||
|
||||
const handleDeleteCst = useCallback(
|
||||
(deleted: EntityID[]) => {
|
||||
(deleted: ConstituentaID[]) => {
|
||||
if (!model.schema) {
|
||||
return;
|
||||
}
|
||||
|
@ -479,6 +482,7 @@ export const RSEditState = ({
|
|||
share,
|
||||
toggleSubscribe,
|
||||
reindex,
|
||||
inlineSynthesis: () => setShowInlineSynthesis(true),
|
||||
produceStructure,
|
||||
substitute,
|
||||
|
||||
|
@ -549,6 +553,13 @@ export const RSEditState = ({
|
|||
onUpdate={handleUpdateVersion}
|
||||
/>
|
||||
) : null}
|
||||
{showInlineSynthesis ? (
|
||||
<DlgInlineSynthesis
|
||||
receiver={model.schema}
|
||||
hideWindow={() => setShowInlineSynthesis(false)}
|
||||
onInlineSynthesis={() => toast('Testing')}
|
||||
/>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
) : null}
|
||||
|
||||
|
@ -586,10 +597,10 @@ function ProcessError({
|
|||
}
|
||||
|
||||
function getNextActiveOnDelete(
|
||||
activeID: number | undefined,
|
||||
activeID: ConstituentaID | undefined,
|
||||
items: IConstituenta[],
|
||||
deleted: number[]
|
||||
): number | undefined {
|
||||
deleted: ConstituentaID[]
|
||||
): ConstituentaID | undefined {
|
||||
if (items.length === deleted.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
BiAnalyse,
|
||||
BiDiamond,
|
||||
BiDownload,
|
||||
BiDuplicate,
|
||||
|
@ -13,7 +12,16 @@ import {
|
|||
BiUpload
|
||||
} from 'react-icons/bi';
|
||||
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 Button from '@/components/ui/Button';
|
||||
|
@ -95,6 +103,11 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
|||
controller.produceStructure();
|
||||
}
|
||||
|
||||
function handleInlineSynthesis() {
|
||||
editMenu.hide();
|
||||
controller.inlineSynthesis();
|
||||
}
|
||||
|
||||
function handleChangeMode(newMode: UserAccessMode) {
|
||||
accessMenu.hide();
|
||||
setMode(newMode);
|
||||
|
@ -201,12 +214,19 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
|||
icon={<BiDiamond size='1rem' className='icon-green' />}
|
||||
onClick={handleTemplates}
|
||||
/>
|
||||
<DropdownButton
|
||||
disabled={!controller.isContentEditable || !user.is_staff}
|
||||
text='Применить конструкт'
|
||||
title='Импортировать совокупность конституент из другой схемы'
|
||||
icon={<LuBookCopy size='1rem' className='icon-green' />}
|
||||
onClick={handleInlineSynthesis}
|
||||
/>
|
||||
<DropdownButton
|
||||
disabled={!controller.isContentEditable}
|
||||
className='border-t-2'
|
||||
text='Сброс имён'
|
||||
title='Присвоить порядковые имена и обновить выражения'
|
||||
icon={<BiAnalyse size='1rem' className='icon-primary' />}
|
||||
icon={<LuWand2 size='1rem' className='icon-primary' />}
|
||||
onClick={handleReindex}
|
||||
/>
|
||||
<DropdownButton
|
||||
|
@ -220,7 +240,7 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
|||
disabled={!controller.isContentEditable}
|
||||
text='Отождествление'
|
||||
title='Заменить вхождения одной конституенты на другую'
|
||||
icon={<LuReplace size='1rem' className='icon-primary' />}
|
||||
icon={<LuReplace size='1rem' className='icon-red' />}
|
||||
onClick={handleSubstituteCst}
|
||||
/>
|
||||
</Dropdown>
|
||||
|
|
Loading…
Reference in New Issue
Block a user