mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-13 20:30:36 +03:00
F: Implement association editing UI and fix dialogs caching
This commit is contained in:
parent
41e0ba64ba
commit
03ade4fee1
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
|
||||
|
@ -9,20 +11,32 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command
|
|||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||
import { cn } from '../utils';
|
||||
|
||||
interface ComboMultiProps<Option> extends Styling {
|
||||
interface ComboMultiPropsBase<Option> extends Styling {
|
||||
id?: string;
|
||||
items?: Option[];
|
||||
value: Option[];
|
||||
onChange: (newValue: Option[]) => void;
|
||||
|
||||
idFunc: (item: Option) => string;
|
||||
labelValueFunc: (item: Option) => string;
|
||||
labelOptionFunc: (item: Option) => string;
|
||||
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
noSearch?: boolean;
|
||||
}
|
||||
|
||||
interface ComboMultiPropsFull<Option> extends ComboMultiPropsBase<Option> {
|
||||
onChange: (newValue: Option[]) => void;
|
||||
}
|
||||
|
||||
interface ComboMultiPropsSplit<Option> extends ComboMultiPropsBase<Option> {
|
||||
onClear: () => void;
|
||||
onAdd: (item: Option) => void;
|
||||
onRemove: (item: Option) => void;
|
||||
}
|
||||
|
||||
type ComboMultiProps<Option> = ComboMultiPropsFull<Option> | ComboMultiPropsSplit<Option>;
|
||||
|
||||
/**
|
||||
* Displays a combo-box component with multiple selection.
|
||||
*/
|
||||
|
@ -30,14 +44,15 @@ export function ComboMulti<Option>({
|
|||
id,
|
||||
items,
|
||||
value,
|
||||
onChange,
|
||||
labelValueFunc,
|
||||
labelOptionFunc,
|
||||
idFunc,
|
||||
placeholder,
|
||||
className,
|
||||
style,
|
||||
noSearch
|
||||
disabled,
|
||||
noSearch,
|
||||
...restProps
|
||||
}: ComboMultiProps<Option>) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [popoverWidth, setPopoverWidth] = useState<number | undefined>(undefined);
|
||||
|
@ -54,19 +69,34 @@ export function ComboMulti<Option>({
|
|||
if (value.includes(newValue)) {
|
||||
handleRemoveValue(newValue);
|
||||
} else {
|
||||
onChange([...value, newValue]);
|
||||
if ('onAdd' in restProps && typeof restProps.onAdd === 'function') {
|
||||
restProps.onAdd(newValue);
|
||||
} else {
|
||||
assert('onChange' in restProps);
|
||||
restProps.onChange([...value, newValue]);
|
||||
}
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRemoveValue(delValue: Option) {
|
||||
onChange(value.filter(v => v !== delValue));
|
||||
if ('onRemove' in restProps && typeof restProps.onRemove === 'function') {
|
||||
restProps.onRemove(delValue);
|
||||
} else {
|
||||
assert('onChange' in restProps);
|
||||
restProps.onChange(value.filter(v => v !== delValue));
|
||||
}
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
function handleClear(event: React.MouseEvent<SVGElement>) {
|
||||
event.stopPropagation();
|
||||
onChange([]);
|
||||
if ('onClear' in restProps && typeof restProps.onClear === 'function') {
|
||||
restProps.onClear();
|
||||
} else {
|
||||
assert('onChange' in restProps);
|
||||
restProps.onChange([]);
|
||||
}
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
|
@ -81,7 +111,7 @@ export function ComboMulti<Option>({
|
|||
className={cn(
|
||||
'relative h-9',
|
||||
'flex gap-2 px-3 py-2 items-center justify-between',
|
||||
'bg-input disabled:opacity-50',
|
||||
'bg-input disabled:bg-transparent',
|
||||
'cursor-pointer disabled:cursor-auto',
|
||||
'whitespace-nowrap',
|
||||
'focus-outline border',
|
||||
|
@ -91,32 +121,39 @@ export function ComboMulti<Option>({
|
|||
className
|
||||
)}
|
||||
style={style}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className='flex flex-wrap gap-1 items-center'>
|
||||
<div className='flex flex-wrap gap-2 items-center'>
|
||||
{value.length === 0 ? <div className='text-muted-foreground'>{placeholder}</div> : null}
|
||||
{value.map(item => (
|
||||
<div key={idFunc(item)} className='flex px-1 items-center border rounded-lg bg-accent text-sm'>
|
||||
{labelValueFunc(item)}
|
||||
<IconRemove
|
||||
tabIndex={-1}
|
||||
size='1rem'
|
||||
className='cc-remove cc-hover-pulse'
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
handleRemoveValue(item);
|
||||
}}
|
||||
/>
|
||||
{!disabled ? (
|
||||
<IconRemove
|
||||
tabIndex={-1}
|
||||
size='1rem'
|
||||
className='cc-remove cc-hover-pulse'
|
||||
onClick={
|
||||
disabled
|
||||
? undefined
|
||||
: event => {
|
||||
event.stopPropagation();
|
||||
handleRemoveValue(item);
|
||||
}
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<ChevronDownIcon className={cn('text-muted-foreground', !!value && 'opacity-0')} />
|
||||
{!!value ? (
|
||||
{!!value && !disabled ? (
|
||||
<IconRemove
|
||||
tabIndex={-1}
|
||||
size='1rem'
|
||||
className='cc-remove absolute pointer-events-auto right-3 cc-hover-pulse hover:text-primary'
|
||||
onClick={handleClear}
|
||||
onClick={value.length === 0 ? undefined : handleClear}
|
||||
/>
|
||||
) : null}
|
||||
</button>
|
||||
|
@ -127,16 +164,19 @@ export function ComboMulti<Option>({
|
|||
<CommandList>
|
||||
<CommandEmpty>Список пуст</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{items?.map(item => (
|
||||
<CommandItem
|
||||
key={idFunc(item)}
|
||||
value={labelOptionFunc(item)}
|
||||
onSelect={() => handleAddValue(item)}
|
||||
className={cn(value === item && 'bg-selected text-selected-foreground')}
|
||||
>
|
||||
{labelOptionFunc(item)}
|
||||
</CommandItem>
|
||||
))}
|
||||
{items
|
||||
?.filter(item => !value.includes(item))
|
||||
.map(item => (
|
||||
<CommandItem
|
||||
key={idFunc(item)}
|
||||
value={labelOptionFunc(item)}
|
||||
onSelect={() => handleAddValue(item)}
|
||||
disabled={disabled}
|
||||
className={cn(value === item && 'bg-selected text-selected-foreground')}
|
||||
>
|
||||
{labelOptionFunc(item)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { type IRSForm } from '@/features/rsform';
|
||||
import { useRSFormSuspense } from '@/features/rsform/backend/use-rsform';
|
||||
import { RSTabID } from '@/features/rsform/pages/rsform-page/rsedit-context';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
|
@ -14,11 +14,12 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { TGReadonlyFlow } from './tg-readonly-flow';
|
||||
|
||||
export interface DlgShowTermGraphProps {
|
||||
schema: IRSForm;
|
||||
schemaID: number;
|
||||
}
|
||||
|
||||
export function DlgShowTermGraph() {
|
||||
const { schema } = useDialogsStore(state => state.props as DlgShowTermGraphProps);
|
||||
const { schemaID } = useDialogsStore(state => state.props as DlgShowTermGraphProps);
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
const hideDialog = useDialogsStore(state => state.hideDialog);
|
||||
const router = useConceptNavigation();
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ export function ToolbarSchema({
|
|||
convention: '',
|
||||
term_forms: []
|
||||
};
|
||||
showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data });
|
||||
showCreateCst({ schemaID: schema.id, onCreate: onCreateCst, initial: data });
|
||||
}
|
||||
|
||||
function cloneCst() {
|
||||
|
@ -126,7 +126,7 @@ export function ToolbarSchema({
|
|||
return;
|
||||
}
|
||||
showDeleteCst({
|
||||
schema: schema,
|
||||
schemaID: schema.id,
|
||||
selected: [activeCst.id],
|
||||
afterDelete: resetActive
|
||||
});
|
||||
|
@ -192,7 +192,7 @@ export function ToolbarSchema({
|
|||
}
|
||||
|
||||
function handleShowTermGraph() {
|
||||
showTermGraph({ schema: schema });
|
||||
showTermGraph({ schemaID: schema.id });
|
||||
}
|
||||
|
||||
function handleReindex() {
|
||||
|
|
|
@ -33,7 +33,7 @@ export function ViewSchema({ schemaID, isMutable }: ViewSchemaProps) {
|
|||
}, [schema, setCurrentSchema]);
|
||||
|
||||
function handleEditCst(cst: IConstituenta) {
|
||||
showEditCst({ schema: schema, target: cst });
|
||||
showEditCst({ schemaID: schema.id, targetID: cst.id });
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,7 +5,7 @@ import { DELAYS, KEYS } from '@/backend/configuration';
|
|||
import { infoMsg } from '@/utils/labels';
|
||||
|
||||
import {
|
||||
type IAssociationDataDTO,
|
||||
type IAssociation,
|
||||
type IAssociationTargetDTO,
|
||||
type ICheckConstituentaDTO,
|
||||
type IConstituentaCreatedResponse,
|
||||
|
@ -154,8 +154,8 @@ export const rsformsApi = {
|
|||
request: { data: data }
|
||||
}),
|
||||
|
||||
createAssociation: ({ itemID, data }: { itemID: number; data: IAssociationDataDTO }) =>
|
||||
axiosPost<IAssociationDataDTO, IRSFormDTO>({
|
||||
createAssociation: ({ itemID, data }: { itemID: number; data: IAssociation }) =>
|
||||
axiosPost<IAssociation, IRSFormDTO>({
|
||||
schema: schemaRSForm,
|
||||
endpoint: `/api/rsforms/${itemID}/create-association`,
|
||||
request: {
|
||||
|
@ -163,8 +163,8 @@ export const rsformsApi = {
|
|||
successMessage: infoMsg.changesSaved
|
||||
}
|
||||
}),
|
||||
deleteAssociation: ({ itemID, data }: { itemID: number; data: IAssociationDataDTO }) =>
|
||||
axiosPatch<IAssociationDataDTO, IRSFormDTO>({
|
||||
deleteAssociation: ({ itemID, data }: { itemID: number; data: IAssociation }) =>
|
||||
axiosPatch<IAssociation, IRSFormDTO>({
|
||||
schema: schemaRSForm,
|
||||
endpoint: `/api/rsforms/${itemID}/delete-association`,
|
||||
request: {
|
||||
|
|
|
@ -21,6 +21,8 @@ import { CstType, type IRSFormDTO, ParsingStatus, ValueClass } from './types';
|
|||
export class RSFormLoader {
|
||||
private schema: IRSForm;
|
||||
private graph: Graph = new Graph();
|
||||
private association_graph: Graph = new Graph();
|
||||
private full_graph: Graph = new Graph();
|
||||
private cstByAlias = new Map<string, IConstituenta>();
|
||||
private cstByID = new Map<number, IConstituenta>();
|
||||
|
||||
|
@ -39,6 +41,8 @@ export class RSFormLoader {
|
|||
result.graph = this.graph;
|
||||
result.cstByAlias = this.cstByAlias;
|
||||
result.cstByID = this.cstByID;
|
||||
result.association_graph = this.association_graph;
|
||||
result.full_graph = this.full_graph;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -47,6 +51,8 @@ export class RSFormLoader {
|
|||
this.cstByAlias.set(cst.alias, cst);
|
||||
this.cstByID.set(cst.id, cst);
|
||||
this.graph.addNode(cst.id);
|
||||
this.association_graph.addNode(cst.id);
|
||||
this.full_graph.addNode(cst.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -57,6 +63,7 @@ export class RSFormLoader {
|
|||
const source = this.cstByAlias.get(alias);
|
||||
if (source) {
|
||||
this.graph.addEdge(source.id, cst.id);
|
||||
this.full_graph.addEdge(source.id, cst.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -83,6 +90,7 @@ export class RSFormLoader {
|
|||
cst.is_template = inferTemplate(cst.definition_formal);
|
||||
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
|
||||
cst.spawn = [];
|
||||
cst.associations = [];
|
||||
cst.spawn_alias = [];
|
||||
cst.parent_schema = schemaByCst.get(cst.id);
|
||||
cst.parent_schema_index = cst.parent_schema ? parents.indexOf(cst.parent_schema) + 1 : 0;
|
||||
|
@ -102,6 +110,12 @@ export class RSFormLoader {
|
|||
parent.spawn_alias.push(cst.alias);
|
||||
}
|
||||
});
|
||||
this.schema.association.forEach(assoc => {
|
||||
const container = this.cstByID.get(assoc.container)!;
|
||||
container.associations.push(assoc.associate);
|
||||
this.full_graph.addEdge(container.id, assoc.associate);
|
||||
this.association_graph.addEdge(container.id, assoc.associate);
|
||||
});
|
||||
}
|
||||
|
||||
private inferSimpleExpression(target: IConstituenta): boolean {
|
||||
|
|
|
@ -95,7 +95,7 @@ export interface ICheckConstituentaDTO {
|
|||
export type ISubstitutionsDTO = z.infer<typeof schemaSubstitutions>;
|
||||
|
||||
/** Represents data for creating or deleting an association. */
|
||||
export type IAssociationDataDTO = z.infer<typeof schemaAssociationData>;
|
||||
export type IAssociation = z.infer<typeof schemaAssociation>;
|
||||
|
||||
/** Represents data for clearing all associations for a target constituenta. */
|
||||
export type IAssociationTargetDTO = z.infer<typeof schemaAssociationTarget>;
|
||||
|
@ -308,6 +308,11 @@ export const schemaConstituenta = schemaConstituentaBasics.extend({
|
|||
.optional()
|
||||
});
|
||||
|
||||
export const schemaAssociation = z.strictObject({
|
||||
container: z.number(),
|
||||
associate: z.number()
|
||||
});
|
||||
|
||||
export const schemaRSForm = schemaLibraryItem.extend({
|
||||
editors: z.array(z.number()),
|
||||
|
||||
|
@ -315,7 +320,7 @@ export const schemaRSForm = schemaLibraryItem.extend({
|
|||
versions: z.array(schemaVersionInfo),
|
||||
|
||||
items: z.array(schemaConstituenta),
|
||||
association: z.array(z.strictObject({ container: z.number(), associate: z.number() })),
|
||||
association: z.array(schemaAssociation),
|
||||
inheritance: z.array(
|
||||
z.strictObject({
|
||||
child: z.number(),
|
||||
|
@ -392,11 +397,6 @@ export const schemaSubstitutions = z.strictObject({
|
|||
substitutions: z.array(schemaSubstituteConstituents).min(1, { message: errorMsg.emptySubstitutions })
|
||||
});
|
||||
|
||||
export const schemaAssociationData = z.strictObject({
|
||||
container: z.number(),
|
||||
associate: z.number()
|
||||
});
|
||||
|
||||
export const schemaAssociationTarget = z.strictObject({
|
||||
target: z.number()
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
|
|||
import { KEYS } from '@/backend/configuration';
|
||||
|
||||
import { rsformsApi } from './api';
|
||||
import { type IAssociationDataDTO } from './types';
|
||||
import { type IAssociation } from './types';
|
||||
|
||||
export const useCreateAssociation = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -24,6 +24,6 @@ export const useCreateAssociation = () => {
|
|||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
createAssociation: (data: { itemID: number; data: IAssociationDataDTO }) => mutation.mutateAsync(data)
|
||||
createAssociation: (data: { itemID: number; data: IAssociation }) => mutation.mutateAsync(data)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
|
|||
import { KEYS } from '@/backend/configuration';
|
||||
|
||||
import { rsformsApi } from './api';
|
||||
import { type IAssociationDataDTO } from './types';
|
||||
import { type IAssociation } from './types';
|
||||
|
||||
export const useDeleteAssociation = () => {
|
||||
const client = useQueryClient();
|
||||
|
@ -24,6 +24,6 @@ export const useDeleteAssociation = () => {
|
|||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
deleteAssociation: (data: { itemID: number; data: IAssociationDataDTO }) => mutation.mutateAsync(data)
|
||||
deleteAssociation: (data: { itemID: number; data: IAssociation }) => mutation.mutateAsync(data)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -175,7 +175,7 @@ export const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
|
||||
setIsEditing(true);
|
||||
showEditReference({
|
||||
schema: schema,
|
||||
schemaID: schema.id,
|
||||
initial: data,
|
||||
onCancel: () => {
|
||||
setIsEditing(false);
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { ComboMulti } from '@/components/input/combo-multi';
|
||||
import { type Styling } from '@/components/props';
|
||||
|
||||
import { labelConstituenta } from '../labels';
|
||||
import { type IConstituenta } from '../models/rsform';
|
||||
|
||||
interface SelectMultiCstProps extends Styling {
|
||||
id?: string;
|
||||
value: IConstituenta[];
|
||||
items: IConstituenta[];
|
||||
onClear: () => void;
|
||||
onAdd: (item: IConstituenta) => void;
|
||||
onRemove: (item: IConstituenta) => void;
|
||||
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function SelectMultiConstituenta({ value, items, onClear, onAdd, onRemove, ...restProps }: SelectMultiCstProps) {
|
||||
return (
|
||||
<ComboMulti
|
||||
noSearch
|
||||
items={items}
|
||||
value={value}
|
||||
onClear={onClear}
|
||||
onAdd={onAdd}
|
||||
onRemove={onRemove}
|
||||
idFunc={cst => String(cst.id)}
|
||||
labelOptionFunc={cst => labelConstituenta(cst)}
|
||||
labelValueFunc={cst => cst.alias}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -14,20 +14,21 @@ import {
|
|||
schemaCreateConstituenta
|
||||
} from '../../backend/types';
|
||||
import { useCreateConstituenta } from '../../backend/use-create-constituenta';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { validateNewAlias } from '../../models/rsform-api';
|
||||
|
||||
import { FormCreateCst } from './form-create-cst';
|
||||
|
||||
export interface DlgCreateCstProps {
|
||||
initial: ICreateConstituentaDTO;
|
||||
schema: IRSForm;
|
||||
schemaID: number;
|
||||
onCreate: (data: RO<IConstituentaBasicsDTO>) => void;
|
||||
}
|
||||
|
||||
export function DlgCreateCst() {
|
||||
const { initial, schema, onCreate } = useDialogsStore(state => state.props as DlgCreateCstProps);
|
||||
const { initial, schemaID, onCreate } = useDialogsStore(state => state.props as DlgCreateCstProps);
|
||||
const { createConstituenta: cstCreate } = useCreateConstituenta();
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
|
||||
const methods = useForm<ICreateConstituentaDTO>({
|
||||
resolver: zodResolver(schemaCreateConstituenta),
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
schemaCreateConstituenta
|
||||
} from '../../backend/types';
|
||||
import { useCreateConstituenta } from '../../backend/use-create-constituenta';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { generateAlias, validateNewAlias } from '../../models/rsform-api';
|
||||
import { FormCreateCst } from '../dlg-create-cst/form-create-cst';
|
||||
|
||||
|
@ -28,7 +28,7 @@ import { TabTemplate } from './tab-template';
|
|||
import { TemplateState } from './template-state';
|
||||
|
||||
export interface DlgCstTemplateProps {
|
||||
schema: IRSForm;
|
||||
schemaID: number;
|
||||
onCreate: (data: RO<IConstituentaBasicsDTO>) => void;
|
||||
insertAfter?: number;
|
||||
}
|
||||
|
@ -41,8 +41,9 @@ export const TabID = {
|
|||
export type TabID = (typeof TabID)[keyof typeof TabID];
|
||||
|
||||
export function DlgCstTemplate() {
|
||||
const { schema, onCreate, insertAfter } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const { schemaID, onCreate, insertAfter } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const { createConstituenta: cstCreate } = useCreateConstituenta();
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
|
||||
const methods = useForm<ICreateConstituentaDTO>({
|
||||
resolver: zodResolver(schemaCreateConstituenta),
|
||||
|
@ -92,7 +93,7 @@ export function DlgCstTemplate() {
|
|||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabArguments />
|
||||
<TabArguments schema={schema} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
|
|
|
@ -8,21 +8,22 @@ import { MiniButton } from '@/components/control';
|
|||
import { DataTable, type IConditionalStyle } from '@/components/data-table';
|
||||
import { IconAccept, IconRemove, IconReset } from '@/components/icons';
|
||||
import { NoData } from '@/components/view';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type ICreateConstituentaDTO } from '../../backend/types';
|
||||
import { PickConstituenta } from '../../components/pick-constituenta';
|
||||
import { RSInput } from '../../components/rs-input';
|
||||
import { type IConstituenta } from '../../models/rsform';
|
||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||
import { type IArgumentValue } from '../../models/rslang';
|
||||
|
||||
import { type DlgCstTemplateProps } from './dlg-cst-template';
|
||||
import { useTemplateContext } from './template-context';
|
||||
|
||||
const argumentsHelper = createColumnHelper<IArgumentValue>();
|
||||
|
||||
export function TabArguments() {
|
||||
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
interface TabArgumentsProps {
|
||||
schema: IRSForm;
|
||||
}
|
||||
|
||||
export function TabArguments({ schema }: TabArgumentsProps) {
|
||||
const { control } = useFormContext<ICreateConstituentaDTO>();
|
||||
const { args, onChangeArguments } = useTemplateContext();
|
||||
const definition = useWatch({ control, name: 'definition_formal' });
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useFormContext } from 'react-hook-form';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type ICreateConstituentaDTO } from '../../backend/types';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { type IConstituenta } from '../../models/rsform';
|
||||
import { generateAlias } from '../../models/rsform-api';
|
||||
import { type IArgumentValue } from '../../models/rslang';
|
||||
|
@ -15,7 +16,8 @@ import { type DlgCstTemplateProps } from './dlg-cst-template';
|
|||
import { TemplateContext } from './template-context';
|
||||
|
||||
export const TemplateState = ({ children }: React.PropsWithChildren) => {
|
||||
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const { schemaID } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
const { setValue } = useFormContext<ICreateConstituentaDTO>();
|
||||
const [templateID, setTemplateID] = useState<number | null>(null);
|
||||
const [args, setArguments] = useState<IArgumentValue[]>([]);
|
||||
|
|
|
@ -8,19 +8,21 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { useDeleteConstituents } from '../../backend/use-delete-constituents';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
|
||||
import { ListConstituents } from './list-constituents';
|
||||
|
||||
export interface DlgDeleteCstProps {
|
||||
schema: IRSForm;
|
||||
schemaID: number;
|
||||
selected: number[];
|
||||
afterDelete?: (initialSchema: IRSForm, deleted: number[]) => void;
|
||||
}
|
||||
|
||||
export function DlgDeleteCst() {
|
||||
const { selected, schema, afterDelete } = useDialogsStore(state => state.props as DlgDeleteCstProps);
|
||||
const { selected, schemaID, afterDelete } = useDialogsStore(state => state.props as DlgDeleteCstProps);
|
||||
const { deleteConstituents: cstDelete } = useDeleteConstituents();
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
|
||||
const [expandOut, setExpandOut] = useState(false);
|
||||
const expansion: number[] = schema.graph.expandAllOutputs(selected);
|
||||
|
@ -31,7 +33,7 @@ export function DlgDeleteCst() {
|
|||
|
||||
function handleSubmit() {
|
||||
const deleted = expandOut ? selected.concat(expansion) : selected;
|
||||
void cstDelete({ itemID: schema.id, data: { items: deleted } }).then(() => afterDelete?.(schema, deleted));
|
||||
void cstDelete({ itemID: schemaID, data: { items: deleted } }).then(() => afterDelete?.(schema, deleted));
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -13,20 +13,22 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
import { type IUpdateConstituentaDTO, schemaUpdateConstituenta } from '../../backend/types';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { useUpdateConstituenta } from '../../backend/use-update-constituenta';
|
||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||
import { validateNewAlias } from '../../models/rsform-api';
|
||||
import { RSTabID } from '../../pages/rsform-page/rsedit-context';
|
||||
|
||||
import { FormEditCst } from './form-edit-cst';
|
||||
|
||||
export interface DlgEditCstProps {
|
||||
schema: IRSForm;
|
||||
target: IConstituenta;
|
||||
schemaID: number;
|
||||
targetID: number;
|
||||
}
|
||||
|
||||
export function DlgEditCst() {
|
||||
const { schema, target } = useDialogsStore(state => state.props as DlgEditCstProps);
|
||||
const { schemaID, targetID } = useDialogsStore(state => state.props as DlgEditCstProps);
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
const target = schema.cstByID.get(targetID)!;
|
||||
const hideDialog = useDialogsStore(state => state.hideDialog);
|
||||
const { updateConstituenta } = useUpdateConstituenta();
|
||||
const router = useConceptNavigation();
|
||||
|
|
|
@ -5,12 +5,16 @@ import { HelpTopic } from '@/features/help';
|
|||
import { BadgeHelp } from '@/features/help/components/badge-help';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import { TextArea, TextInput } from '@/components/input';
|
||||
import { Label, TextArea, TextInput } from '@/components/input';
|
||||
|
||||
import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
|
||||
import { useClearAssociations } from '../../backend/use-clear-associations';
|
||||
import { useCreateAssociation } from '../../backend/use-create-association';
|
||||
import { useDeleteAssociation } from '../../backend/use-delete-association';
|
||||
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||
import { RSInput } from '../../components/rs-input';
|
||||
import { SelectCstType } from '../../components/select-cst-type';
|
||||
import { SelectMultiConstituenta } from '../../components/select-multi-constituenta';
|
||||
import { getRSDefinitionPlaceholder, labelCstTypification, labelRSExpression } from '../../labels';
|
||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||
import { generateAlias, isBaseSet, isBasicConcept } from '../../models/rsform-api';
|
||||
|
@ -21,6 +25,10 @@ interface FormEditCstProps {
|
|||
}
|
||||
|
||||
export function FormEditCst({ target, schema }: FormEditCstProps) {
|
||||
const { createAssociation } = useCreateAssociation();
|
||||
const { deleteAssociation } = useDeleteAssociation();
|
||||
const { clearAssociations } = useClearAssociations();
|
||||
|
||||
const {
|
||||
setValue,
|
||||
control,
|
||||
|
@ -36,6 +44,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
const isBasic = isBasicConcept(cst_type) || cst_type === CstType.NOMINAL;
|
||||
const isElementary = isBaseSet(cst_type);
|
||||
const showConvention = !!convention || forceComment || isBasic;
|
||||
const associations = target.associations.map(id => schema.cstByID.get(id)!);
|
||||
|
||||
function handleTypeChange(newValue: CstType) {
|
||||
setValue('item_data.cst_type', newValue);
|
||||
|
@ -47,6 +56,35 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
setValue('item_data.crucial', !crucial);
|
||||
}
|
||||
|
||||
function handleAddAssociation(item: IConstituenta) {
|
||||
void createAssociation({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: target.id,
|
||||
associate: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleRemoveAssociation(item: IConstituenta) {
|
||||
void deleteAssociation({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: target.id,
|
||||
associate: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleClearAssociations() {
|
||||
void clearAssociations({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
target: target.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex items-center self-center gap-3'>
|
||||
|
@ -83,6 +121,20 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
error={errors.item_data?.term_raw}
|
||||
/>
|
||||
|
||||
{target.cst_type === CstType.NOMINAL || target.associations.length > 0 ? (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Label text='Ассоциируемые конституенты' />
|
||||
<SelectMultiConstituenta
|
||||
items={schema.items.filter(item => item.id !== target.id)}
|
||||
value={associations}
|
||||
onAdd={handleAddAssociation}
|
||||
onClear={handleClearAssociations}
|
||||
onRemove={handleRemoveAssociation}
|
||||
placeholder={'Выберите конституенты'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{cst_type !== CstType.NOMINAL ? (
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
supportedGrammemes
|
||||
} from '../../models/language';
|
||||
import { parseEntityReference, parseGrammemes, parseSyntacticReference } from '../../models/language-api';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
|
||||
import { TabEntityReference } from './tab-entity-reference';
|
||||
import { TabSyntacticReference } from './tab-syntactic-reference';
|
||||
|
@ -51,7 +50,7 @@ const schemaEditReferenceState = z
|
|||
export type IEditReferenceState = z.infer<typeof schemaEditReferenceState>;
|
||||
|
||||
export interface DlgEditReferenceProps {
|
||||
schema: IRSForm;
|
||||
schemaID: number;
|
||||
initial: IReferenceInputState;
|
||||
onSave: (newRef: IReference) => void;
|
||||
onCancel: () => void;
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
|||
import { Label, TextInput } from '@/components/input';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { PickConstituenta } from '../../components/pick-constituenta';
|
||||
import { SelectMultiGrammeme } from '../../components/select-multi-grammeme';
|
||||
import { SelectWordForm } from '../../components/select-word-form';
|
||||
|
@ -15,7 +16,8 @@ import { CstMatchMode } from '../../stores/cst-search';
|
|||
import { type DlgEditReferenceProps, type IEditReferenceState } from './dlg-edit-reference';
|
||||
|
||||
export function TabEntityReference() {
|
||||
const { schema, initial } = useDialogsStore(state => state.props as DlgEditReferenceProps);
|
||||
const { schemaID, initial } = useDialogsStore(state => state.props as DlgEditReferenceProps);
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
const { setValue, control, register } = useFormContext<IEditReferenceState>();
|
||||
const alias = useWatch({ control, name: 'entity.entity' });
|
||||
|
||||
|
|
|
@ -15,21 +15,23 @@ import { useGenerateLexeme } from '../../backend/cctext/use-generate-lexeme';
|
|||
import { useInflectText } from '../../backend/cctext/use-inflect-text';
|
||||
import { useIsProcessingCctext } from '../../backend/cctext/use-is-processing-cctext';
|
||||
import { useParseText } from '../../backend/cctext/use-parse-text';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { useUpdateConstituenta } from '../../backend/use-update-constituenta';
|
||||
import { SelectMultiGrammeme } from '../../components/select-multi-grammeme';
|
||||
import { type Grammeme, type IWordForm, supportedGrammemes } from '../../models/language';
|
||||
import { parseGrammemes, wordFormEquals } from '../../models/language-api';
|
||||
import { type IConstituenta } from '../../models/rsform';
|
||||
|
||||
import { TableWordForms } from './table-word-forms';
|
||||
|
||||
export interface DlgEditWordFormsProps {
|
||||
itemID: number;
|
||||
target: IConstituenta;
|
||||
targetID: number;
|
||||
}
|
||||
|
||||
export function DlgEditWordForms() {
|
||||
const { itemID, target } = useDialogsStore(state => state.props as DlgEditWordFormsProps);
|
||||
const { itemID, targetID } = useDialogsStore(state => state.props as DlgEditWordFormsProps);
|
||||
const { schema } = useRSFormSuspense({ itemID: itemID });
|
||||
const target = schema.cstByID.get(targetID)!;
|
||||
const { updateConstituenta: cstUpdate } = useUpdateConstituenta();
|
||||
|
||||
const isProcessing = useIsProcessingCctext();
|
||||
|
|
|
@ -11,14 +11,14 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
|
||||
import { type IInlineSynthesisDTO, schemaInlineSynthesis } from '../../backend/types';
|
||||
import { useInlineSynthesis } from '../../backend/use-inline-synthesis';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
|
||||
import { TabConstituents } from './tab-constituents';
|
||||
import { TabSource } from './tab-source';
|
||||
import { TabSubstitutions } from './tab-substitutions';
|
||||
|
||||
export interface DlgInlineSynthesisProps {
|
||||
receiver: IRSForm;
|
||||
receiverID: number;
|
||||
onSynthesis: () => void;
|
||||
}
|
||||
|
||||
|
@ -30,9 +30,10 @@ export const TabID = {
|
|||
export type TabID = (typeof TabID)[keyof typeof TabID];
|
||||
|
||||
export function DlgInlineSynthesis() {
|
||||
const { receiver, onSynthesis } = useDialogsStore(state => state.props as DlgInlineSynthesisProps);
|
||||
const { receiverID, onSynthesis } = useDialogsStore(state => state.props as DlgInlineSynthesisProps);
|
||||
const [activeTab, setActiveTab] = useState<TabID>(TabID.SCHEMA);
|
||||
const { inlineSynthesis } = useInlineSynthesis();
|
||||
const { schema: receiver } = useRSFormSuspense({ itemID: receiverID });
|
||||
|
||||
const methods = useForm<IInlineSynthesisDTO>({
|
||||
resolver: zodResolver(schemaInlineSynthesis),
|
||||
|
@ -81,7 +82,7 @@ export function DlgInlineSynthesis() {
|
|||
|
||||
<FormProvider {...methods}>
|
||||
<TabPanel>
|
||||
<TabSource />
|
||||
<TabSource receiver={receiver} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
|
@ -95,7 +96,7 @@ export function DlgInlineSynthesis() {
|
|||
<TabPanel>
|
||||
{!!sourceID ? (
|
||||
<Suspense fallback={<Loader />}>
|
||||
<TabSubstitutions />
|
||||
<TabSubstitutions receiver={receiver} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</TabPanel>
|
||||
|
|
|
@ -7,16 +7,17 @@ import { useLibrary } from '@/features/library/backend/use-library';
|
|||
import { PickSchema } from '@/features/library/components/pick-schema';
|
||||
|
||||
import { TextInput } from '@/components/input';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type IInlineSynthesisDTO } from '../../backend/types';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
import { sortItemsForInlineSynthesis } from '../../models/rsform-api';
|
||||
|
||||
import { type DlgInlineSynthesisProps } from './dlg-inline-synthesis';
|
||||
interface TabSourceProps {
|
||||
receiver: IRSForm;
|
||||
}
|
||||
|
||||
export function TabSource() {
|
||||
export function TabSource({ receiver }: TabSourceProps) {
|
||||
const { items: libraryItems } = useLibrary();
|
||||
const { receiver } = useDialogsStore(state => state.props as DlgInlineSynthesisProps);
|
||||
const { setValue, control } = useFormContext<IInlineSynthesisDTO>();
|
||||
const sourceID = useWatch({ control, name: 'source' });
|
||||
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type IInlineSynthesisDTO } from '../../backend/types';
|
||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||
import { PickSubstitutions } from '../../components/pick-substitutions';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
|
||||
import { type DlgInlineSynthesisProps } from './dlg-inline-synthesis';
|
||||
interface TabSubstitutionsProps {
|
||||
receiver: IRSForm;
|
||||
}
|
||||
|
||||
export function TabSubstitutions() {
|
||||
const { receiver } = useDialogsStore(state => state.props as DlgInlineSynthesisProps);
|
||||
export function TabSubstitutions({ receiver }: TabSubstitutionsProps) {
|
||||
const { control } = useFormContext<IInlineSynthesisDTO>();
|
||||
const sourceID = useWatch({ control, name: 'source' });
|
||||
const selected = useWatch({ control, name: 'items' });
|
||||
|
|
|
@ -10,24 +10,26 @@ import { ModalForm } from '@/components/modal';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type CstType, type IUpdateConstituentaDTO, schemaUpdateConstituenta } from '../backend/types';
|
||||
import { useRSFormSuspense } from '../backend/use-rsform';
|
||||
import { useUpdateConstituenta } from '../backend/use-update-constituenta';
|
||||
import { SelectCstType } from '../components/select-cst-type';
|
||||
import { type IConstituenta, type IRSForm } from '../models/rsform';
|
||||
import { generateAlias, validateNewAlias } from '../models/rsform-api';
|
||||
|
||||
export interface DlgRenameCstProps {
|
||||
schema: IRSForm;
|
||||
target: IConstituenta;
|
||||
schemaID: number;
|
||||
targetID: number;
|
||||
}
|
||||
|
||||
export function DlgRenameCst() {
|
||||
const { schema, target } = useDialogsStore(state => state.props as DlgRenameCstProps);
|
||||
const { schemaID, targetID } = useDialogsStore(state => state.props as DlgRenameCstProps);
|
||||
const { updateConstituenta: cstUpdate } = useUpdateConstituenta();
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
const target = schema.cstByID.get(targetID)!;
|
||||
|
||||
const { register, setValue, handleSubmit, control } = useForm<IUpdateConstituentaDTO>({
|
||||
resolver: zodResolver(schemaUpdateConstituenta),
|
||||
defaultValues: {
|
||||
target: target.id,
|
||||
target: targetID,
|
||||
item_data: {
|
||||
alias: target.alias,
|
||||
cst_type: target.cst_type
|
||||
|
@ -39,7 +41,7 @@ export function DlgRenameCst() {
|
|||
const isValid = alias !== target.alias && validateNewAlias(alias, cst_type, schema);
|
||||
|
||||
function onSubmit(data: IUpdateConstituentaDTO) {
|
||||
return cstUpdate({ itemID: schema.id, data: data });
|
||||
return cstUpdate({ itemID: schemaID, data: data });
|
||||
}
|
||||
|
||||
function handleChangeType(newType: CstType) {
|
||||
|
|
|
@ -12,18 +12,19 @@ import { ModalForm } from '@/components/modal';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { type ISubstitutionsDTO, schemaSubstitutions } from '../backend/types';
|
||||
import { useRSFormSuspense } from '../backend/use-rsform';
|
||||
import { useSubstituteConstituents } from '../backend/use-substitute-constituents';
|
||||
import { PickSubstitutions } from '../components/pick-substitutions';
|
||||
import { type IRSForm } from '../models/rsform';
|
||||
|
||||
export interface DlgSubstituteCstProps {
|
||||
schema: IRSForm;
|
||||
schemaID: number;
|
||||
onSubstitute: (data: ISubstitutionsDTO) => void;
|
||||
}
|
||||
|
||||
export function DlgSubstituteCst() {
|
||||
const { onSubstitute, schema } = useDialogsStore(state => state.props as DlgSubstituteCstProps);
|
||||
const { onSubstitute, schemaID } = useDialogsStore(state => state.props as DlgSubstituteCstProps);
|
||||
const { substituteConstituents: cstSubstitute } = useSubstituteConstituents();
|
||||
const { schema } = useRSFormSuspense({ itemID: schemaID });
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
|
||||
import { type Graph } from '@/models/graph';
|
||||
|
||||
import { CstType, type ParsingStatus, type ValueClass } from '../backend/types';
|
||||
import { CstType, type IAssociation, type ParsingStatus, type ValueClass } from '../backend/types';
|
||||
|
||||
import { type IArgumentInfo } from './rslang';
|
||||
|
||||
|
@ -58,6 +58,7 @@ export interface IConstituenta {
|
|||
term_raw: string;
|
||||
term_resolved: string;
|
||||
term_forms: TermForm[];
|
||||
associations: number[];
|
||||
|
||||
parse?: {
|
||||
status: ParsingStatus;
|
||||
|
@ -141,10 +142,13 @@ export interface IRSForm extends ILibraryItemData {
|
|||
|
||||
items: IConstituenta[];
|
||||
inheritance: IInheritanceInfo[];
|
||||
association: IAssociation[];
|
||||
oss: ILibraryItemReference[];
|
||||
|
||||
stats: IRSFormStats;
|
||||
graph: Graph;
|
||||
association_graph: Graph;
|
||||
full_graph: Graph;
|
||||
cstByAlias: Map<string, IConstituenta>;
|
||||
cstByID: Map<number, IConstituenta>;
|
||||
}
|
||||
|
|
|
@ -25,8 +25,17 @@ const SIDELIST_LAYOUT_THRESHOLD = 1000; // px
|
|||
const COLUMN_DENSE_SEARCH_THRESHOLD = 1100;
|
||||
|
||||
export function EditorConstituenta() {
|
||||
const { schema, activeCst, isContentEditable, selected, setSelected, moveUp, moveDown, cloneCst, navigateCst } =
|
||||
useRSEdit();
|
||||
const {
|
||||
schema, //
|
||||
activeCst,
|
||||
isContentEditable,
|
||||
selected,
|
||||
setSelected,
|
||||
moveUp,
|
||||
moveDown,
|
||||
cloneCst,
|
||||
navigateCst
|
||||
} = useRSEdit();
|
||||
const windowSize = useWindowSize();
|
||||
const mainHeight = useMainHeight();
|
||||
|
||||
|
|
|
@ -6,13 +6,10 @@ import { Controller, useForm } from 'react-hook-form';
|
|||
import { toast } from 'react-toastify';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
import { useUpdateCrucial } from '@/features/rsform/backend/use-update-crucial';
|
||||
import { IconCrucialValue } from '@/features/rsform/components/icon-crucial-value';
|
||||
|
||||
import { MiniButton, SubmitButton } from '@/components/control';
|
||||
import { TextButton } from '@/components/control/text-button';
|
||||
import { IconChild, IconPredecessor, IconSave } from '@/components/icons';
|
||||
import { TextArea } from '@/components/input';
|
||||
import { Label, TextArea } from '@/components/input';
|
||||
import { Indicator } from '@/components/view';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { useModificationStore } from '@/stores/modification';
|
||||
|
@ -27,9 +24,15 @@ import {
|
|||
ParsingStatus,
|
||||
schemaUpdateConstituenta
|
||||
} from '../../../backend/types';
|
||||
import { useClearAssociations } from '../../../backend/use-clear-associations';
|
||||
import { useCreateAssociation } from '../../../backend/use-create-association';
|
||||
import { useDeleteAssociation } from '../../../backend/use-delete-association';
|
||||
import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
|
||||
import { useUpdateConstituenta } from '../../../backend/use-update-constituenta';
|
||||
import { useUpdateCrucial } from '../../../backend/use-update-crucial';
|
||||
import { IconCrucialValue } from '../../../components/icon-crucial-value';
|
||||
import { RefsInput } from '../../../components/refs-input';
|
||||
import { SelectMultiConstituenta } from '../../../components/select-multi-constituenta';
|
||||
import {
|
||||
getRSDefinitionPlaceholder,
|
||||
labelCstTypification,
|
||||
|
@ -57,6 +60,9 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
|
||||
const { updateConstituenta } = useUpdateConstituenta();
|
||||
const { updateCrucial } = useUpdateCrucial();
|
||||
const { createAssociation } = useCreateAssociation();
|
||||
const { deleteAssociation } = useDeleteAssociation();
|
||||
const { clearAssociations } = useClearAssociations();
|
||||
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
|
||||
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
||||
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
||||
|
@ -106,6 +112,11 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
[activeCst, localParse]
|
||||
);
|
||||
|
||||
const associations = useMemo(
|
||||
() => activeCst.associations.map(id => schema.cstByID.get(id)!),
|
||||
[activeCst.associations, schema.cstByID]
|
||||
);
|
||||
|
||||
const isBasic = isBasicConcept(activeCst.cst_type) || activeCst.cst_type === CstType.NOMINAL;
|
||||
const isElementary = isBaseSet(activeCst.cst_type);
|
||||
const showConvention = !!activeCst.convention || forceComment || isBasic;
|
||||
|
@ -168,11 +179,11 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
if (isModified && !promptUnsaved()) {
|
||||
return;
|
||||
}
|
||||
showEditTerm({ itemID: schema.id, target: activeCst });
|
||||
showEditTerm({ itemID: schema.id, targetID: activeCst.id });
|
||||
}
|
||||
|
||||
function handleRenameCst() {
|
||||
showRenameCst({ schema: schema, target: activeCst });
|
||||
showRenameCst({ schemaID: schema.id, targetID: activeCst.id });
|
||||
}
|
||||
|
||||
function handleToggleCrucial() {
|
||||
|
@ -185,6 +196,35 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
});
|
||||
}
|
||||
|
||||
function handleAddAssociation(item: IConstituenta) {
|
||||
void createAssociation({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: activeCst.id,
|
||||
associate: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleRemoveAssociation(item: IConstituenta) {
|
||||
void deleteAssociation({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
container: activeCst.id,
|
||||
associate: item.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleClearAssociations() {
|
||||
void clearAssociations({
|
||||
itemID: schema.id,
|
||||
data: {
|
||||
target: activeCst.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
id={id}
|
||||
|
@ -239,6 +279,21 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
)}
|
||||
/>
|
||||
|
||||
{activeCst.cst_type === CstType.NOMINAL || activeCst.associations.length > 0 ? (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Label text='Ассоциируемые конституенты' />
|
||||
<SelectMultiConstituenta
|
||||
items={schema.items.filter(item => item.id !== activeCst.id)}
|
||||
value={associations}
|
||||
onAdd={handleAddAssociation}
|
||||
onClear={handleClearAssociations}
|
||||
onRemove={handleRemoveAssociation}
|
||||
disabled={disabled || isModified}
|
||||
placeholder={disabled ? '' : 'Выберите конституенты'}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeCst.cst_type !== CstType.NOMINAL ? (
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
|
|
|
@ -60,7 +60,7 @@ export function MenuEditSchema() {
|
|||
return;
|
||||
}
|
||||
showSubstituteCst({
|
||||
schema: schema,
|
||||
schemaID: schema.id,
|
||||
onSubstitute: data => setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)))
|
||||
});
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ export function MenuEditSchema() {
|
|||
return;
|
||||
}
|
||||
showInlineSynthesis({
|
||||
receiver: schema,
|
||||
receiverID: schema.id,
|
||||
onSynthesis: () => deselectAll()
|
||||
});
|
||||
}
|
||||
|
|
|
@ -231,7 +231,7 @@ export const RSEditState = ({
|
|||
if (skipDialog) {
|
||||
void cstCreate({ itemID: schema.id, data }).then(onCreateCst);
|
||||
} else {
|
||||
showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data });
|
||||
showCreateCst({ schemaID: schema.id, onCreate: onCreateCst, initial: data });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +260,7 @@ export const RSEditState = ({
|
|||
return;
|
||||
}
|
||||
showDeleteCst({
|
||||
schema: schema,
|
||||
schemaID: schema.id,
|
||||
selected: selected,
|
||||
afterDelete: (schema, deleted) => {
|
||||
const isEmpty = deleted.length === schema.items.length;
|
||||
|
@ -281,7 +281,7 @@ export const RSEditState = ({
|
|||
if (isModified && !promptUnsaved()) {
|
||||
return;
|
||||
}
|
||||
showCstTemplate({ schema: schema, onCreate: onCreateCst, insertAfter: activeCst?.id });
|
||||
showCstTemplate({ schemaID: schema.id, onCreate: onCreateCst, insertAfter: activeCst?.id });
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue
Block a user