R: Split context and state dependencies to improve hot reload
This commit is contained in:
parent
e308a52b35
commit
531c44d3c8
|
@ -1,19 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, use, useEffect, useState } from 'react';
|
import { createContext, use } from 'react';
|
||||||
|
|
||||||
import { urls, useConceptNavigation } from '@/app';
|
|
||||||
import { useAuthSuspense } from '@/features/auth';
|
|
||||||
import { useLibrarySearchStore } from '@/features/library';
|
|
||||||
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
|
||||||
import { RSTabID } from '@/features/rsform/pages/rsform-page/rsedit-context';
|
|
||||||
import { useRoleStore, UserRole } from '@/features/users';
|
|
||||||
|
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
|
||||||
import { promptText } from '@/utils/labels';
|
|
||||||
|
|
||||||
import { OperationType } from '../../backend/types';
|
|
||||||
import { useOssSuspense } from '../../backend/use-oss';
|
|
||||||
import { type IOperation, type IOperationSchema } from '../../models/oss';
|
import { type IOperation, type IOperationSchema } from '../../models/oss';
|
||||||
|
|
||||||
export const OssTabID = {
|
export const OssTabID = {
|
||||||
|
@ -37,7 +25,7 @@ export interface IOssEditContext {
|
||||||
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
export const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||||
export const useOssEdit = () => {
|
export const useOssEdit = () => {
|
||||||
const context = use(OssEditContext);
|
const context = use(OssEditContext);
|
||||||
if (context === null) {
|
if (context === null) {
|
||||||
|
@ -45,97 +33,3 @@ export const useOssEdit = () => {
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface OssEditStateProps {
|
|
||||||
itemID: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEditStateProps>) => {
|
|
||||||
const router = useConceptNavigation();
|
|
||||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
|
||||||
|
|
||||||
const role = useRoleStore(state => state.role);
|
|
||||||
const adjustRole = useRoleStore(state => state.adjustRole);
|
|
||||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
|
||||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
|
||||||
|
|
||||||
const { user } = useAuthSuspense();
|
|
||||||
const { schema } = useOssSuspense({ itemID: itemID });
|
|
||||||
|
|
||||||
const isOwned = !!user.id && user.id === schema.owner;
|
|
||||||
const isMutable = role > UserRole.READER && !schema.read_only;
|
|
||||||
|
|
||||||
const [selected, setSelected] = useState<number[]>([]);
|
|
||||||
|
|
||||||
const { deleteItem } = useDeleteItem();
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
adjustRole({
|
|
||||||
isOwner: isOwned,
|
|
||||||
isEditor: !!user.id && schema.editors.includes(user.id),
|
|
||||||
isStaff: user.is_staff,
|
|
||||||
adminMode: adminMode
|
|
||||||
}),
|
|
||||||
[schema, adjustRole, isOwned, user, adminMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
function navigateTab(tab: OssTabID) {
|
|
||||||
const url = urls.oss_props({
|
|
||||||
id: schema.id,
|
|
||||||
tab: tab
|
|
||||||
});
|
|
||||||
router.push({ path: url });
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateOperationSchema(target: number) {
|
|
||||||
const node = schema.operationByID.get(target);
|
|
||||||
if (!node?.result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.push({ path: urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST }) });
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteSchema() {
|
|
||||||
if (!window.confirm(promptText.deleteOSS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
void deleteItem({
|
|
||||||
target: schema.id,
|
|
||||||
beforeInvalidate: () => {
|
|
||||||
if (searchLocation === schema.location) {
|
|
||||||
setSearchLocation('');
|
|
||||||
}
|
|
||||||
return router.pushAsync({ path: urls.library, force: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function canDeleteOperation(target: IOperation) {
|
|
||||||
if (target.operation_type === OperationType.INPUT) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return schema.graph.expandOutputs([target.id]).length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OssEditContext
|
|
||||||
value={{
|
|
||||||
schema,
|
|
||||||
selected,
|
|
||||||
|
|
||||||
isOwned,
|
|
||||||
isMutable,
|
|
||||||
|
|
||||||
navigateTab,
|
|
||||||
navigateOperationSchema,
|
|
||||||
|
|
||||||
canDeleteOperation,
|
|
||||||
deleteSchema,
|
|
||||||
setSelected
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</OssEditContext>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { urls, useConceptNavigation } from '@/app';
|
||||||
|
import { useAuthSuspense } from '@/features/auth';
|
||||||
|
import { useLibrarySearchStore } from '@/features/library';
|
||||||
|
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
||||||
|
import { RSTabID } from '@/features/rsform/pages/rsform-page/rsedit-context';
|
||||||
|
import { useRoleStore, UserRole } from '@/features/users';
|
||||||
|
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
import { promptText } from '@/utils/labels';
|
||||||
|
|
||||||
|
import { OperationType } from '../../backend/types';
|
||||||
|
import { useOssSuspense } from '../../backend/use-oss';
|
||||||
|
import { type IOperation } from '../../models/oss';
|
||||||
|
|
||||||
|
import { OssEditContext, type OssTabID } from './oss-edit-context';
|
||||||
|
|
||||||
|
interface OssEditStateProps {
|
||||||
|
itemID: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEditStateProps>) => {
|
||||||
|
const router = useConceptNavigation();
|
||||||
|
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||||
|
|
||||||
|
const role = useRoleStore(state => state.role);
|
||||||
|
const adjustRole = useRoleStore(state => state.adjustRole);
|
||||||
|
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||||
|
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||||
|
|
||||||
|
const { user } = useAuthSuspense();
|
||||||
|
const { schema } = useOssSuspense({ itemID: itemID });
|
||||||
|
|
||||||
|
const isOwned = !!user.id && user.id === schema.owner;
|
||||||
|
const isMutable = role > UserRole.READER && !schema.read_only;
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const { deleteItem } = useDeleteItem();
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
adjustRole({
|
||||||
|
isOwner: isOwned,
|
||||||
|
isEditor: !!user.id && schema.editors.includes(user.id),
|
||||||
|
isStaff: user.is_staff,
|
||||||
|
adminMode: adminMode
|
||||||
|
}),
|
||||||
|
[schema, adjustRole, isOwned, user, adminMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
function navigateTab(tab: OssTabID) {
|
||||||
|
const url = urls.oss_props({
|
||||||
|
id: schema.id,
|
||||||
|
tab: tab
|
||||||
|
});
|
||||||
|
router.push({ path: url });
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateOperationSchema(target: number) {
|
||||||
|
const node = schema.operationByID.get(target);
|
||||||
|
if (!node?.result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push({ path: urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSchema() {
|
||||||
|
if (!window.confirm(promptText.deleteOSS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void deleteItem({
|
||||||
|
target: schema.id,
|
||||||
|
beforeInvalidate: () => {
|
||||||
|
if (searchLocation === schema.location) {
|
||||||
|
setSearchLocation('');
|
||||||
|
}
|
||||||
|
return router.pushAsync({ path: urls.library, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function canDeleteOperation(target: IOperation) {
|
||||||
|
if (target.operation_type === OperationType.INPUT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return schema.graph.expandOutputs([target.id]).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OssEditContext
|
||||||
|
value={{
|
||||||
|
schema,
|
||||||
|
selected,
|
||||||
|
|
||||||
|
isOwned,
|
||||||
|
isMutable,
|
||||||
|
|
||||||
|
navigateTab,
|
||||||
|
navigateOperationSchema,
|
||||||
|
|
||||||
|
canDeleteOperation,
|
||||||
|
deleteSchema,
|
||||||
|
setSelected
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</OssEditContext>
|
||||||
|
);
|
||||||
|
};
|
|
@ -16,7 +16,8 @@ import { useModificationStore } from '@/stores/modification';
|
||||||
|
|
||||||
import { OperationTooltip } from '../../components/operation-tooltip';
|
import { OperationTooltip } from '../../components/operation-tooltip';
|
||||||
|
|
||||||
import { OssEditState, OssTabID } from './oss-edit-context';
|
import { OssTabID } from './oss-edit-context';
|
||||||
|
import { OssEditState } from './oss-edit-state';
|
||||||
import { OssTabs } from './oss-tabs';
|
import { OssTabs } from './oss-tabs';
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { FormCreateCst } from '../dlg-create-cst/form-create-cst';
|
||||||
|
|
||||||
import { TabArguments } from './tab-arguments';
|
import { TabArguments } from './tab-arguments';
|
||||||
import { TabTemplate } from './tab-template';
|
import { TabTemplate } from './tab-template';
|
||||||
import { TemplateState } from './template-context';
|
import { TemplateState } from './template-state';
|
||||||
|
|
||||||
export interface DlgCstTemplateProps {
|
export interface DlgCstTemplateProps {
|
||||||
schema: IRSForm;
|
schema: IRSForm;
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, use, useState } from 'react';
|
import { createContext, use } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
|
||||||
|
|
||||||
import { type ICstCreateDTO } from '../../backend/types';
|
|
||||||
import { type IConstituenta } from '../../models/rsform';
|
import { type IConstituenta } from '../../models/rsform';
|
||||||
import { generateAlias } from '../../models/rsform-api';
|
|
||||||
import { type IArgumentValue } from '../../models/rslang';
|
import { type IArgumentValue } from '../../models/rslang';
|
||||||
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslang-api';
|
|
||||||
|
|
||||||
import { type DlgCstTemplateProps } from './dlg-cst-template';
|
|
||||||
|
|
||||||
export interface ITemplateContext {
|
export interface ITemplateContext {
|
||||||
args: IArgumentValue[];
|
args: IArgumentValue[];
|
||||||
|
@ -25,7 +17,7 @@ export interface ITemplateContext {
|
||||||
onChangeFilterCategory: (newFilterCategory: IConstituenta | null) => void;
|
onChangeFilterCategory: (newFilterCategory: IConstituenta | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TemplateContext = createContext<ITemplateContext | null>(null);
|
export const TemplateContext = createContext<ITemplateContext | null>(null);
|
||||||
export const useTemplateContext = () => {
|
export const useTemplateContext = () => {
|
||||||
const context = use(TemplateContext);
|
const context = use(TemplateContext);
|
||||||
if (context === null) {
|
if (context === null) {
|
||||||
|
@ -33,63 +25,3 @@ export const useTemplateContext = () => {
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TemplateState = ({ children }: React.PropsWithChildren) => {
|
|
||||||
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
|
|
||||||
const { setValue } = useFormContext<ICstCreateDTO>();
|
|
||||||
const [templateID, setTemplateID] = useState<number | null>(null);
|
|
||||||
const [args, setArguments] = useState<IArgumentValue[]>([]);
|
|
||||||
const [prototype, setPrototype] = useState<IConstituenta | null>(null);
|
|
||||||
const [filterCategory, setFilterCategory] = useState<IConstituenta | null>(null);
|
|
||||||
|
|
||||||
function onChangeArguments(newArgs: IArgumentValue[]) {
|
|
||||||
setArguments(newArgs);
|
|
||||||
if (newArgs.length === 0 || !prototype) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newType = inferTemplatedType(prototype.cst_type, newArgs);
|
|
||||||
setValue('definition_formal', substituteTemplateArgs(prototype.definition_formal, newArgs));
|
|
||||||
setValue('cst_type', newType);
|
|
||||||
setValue('alias', generateAlias(newType, schema));
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChangePrototype(newPrototype: IConstituenta) {
|
|
||||||
setPrototype(newPrototype);
|
|
||||||
setArguments(
|
|
||||||
newPrototype.parse.args.map(arg => ({
|
|
||||||
alias: arg.alias,
|
|
||||||
typification: arg.typification,
|
|
||||||
value: ''
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
setValue('cst_type', newPrototype.cst_type);
|
|
||||||
setValue('alias', generateAlias(newPrototype.cst_type, schema));
|
|
||||||
setValue('definition_formal', newPrototype.definition_formal);
|
|
||||||
setValue('term_raw', newPrototype.term_raw);
|
|
||||||
setValue('definition_raw', newPrototype.definition_raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChangeTemplateID(newTemplateID: number | null) {
|
|
||||||
setTemplateID(newTemplateID);
|
|
||||||
setPrototype(null);
|
|
||||||
setArguments([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TemplateContext
|
|
||||||
value={{
|
|
||||||
templateID,
|
|
||||||
prototype,
|
|
||||||
filterCategory,
|
|
||||||
args,
|
|
||||||
onChangeArguments,
|
|
||||||
onChangePrototype,
|
|
||||||
onChangeFilterCategory: setFilterCategory,
|
|
||||||
onChangeTemplateID
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</TemplateContext>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
|
||||||
|
import { type ICstCreateDTO } from '../../backend/types';
|
||||||
|
import { type IConstituenta } from '../../models/rsform';
|
||||||
|
import { generateAlias } from '../../models/rsform-api';
|
||||||
|
import { type IArgumentValue } from '../../models/rslang';
|
||||||
|
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslang-api';
|
||||||
|
|
||||||
|
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 { setValue } = useFormContext<ICstCreateDTO>();
|
||||||
|
const [templateID, setTemplateID] = useState<number | null>(null);
|
||||||
|
const [args, setArguments] = useState<IArgumentValue[]>([]);
|
||||||
|
const [prototype, setPrototype] = useState<IConstituenta | null>(null);
|
||||||
|
const [filterCategory, setFilterCategory] = useState<IConstituenta | null>(null);
|
||||||
|
|
||||||
|
function onChangeArguments(newArgs: IArgumentValue[]) {
|
||||||
|
setArguments(newArgs);
|
||||||
|
if (newArgs.length === 0 || !prototype) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newType = inferTemplatedType(prototype.cst_type, newArgs);
|
||||||
|
setValue('definition_formal', substituteTemplateArgs(prototype.definition_formal, newArgs));
|
||||||
|
setValue('cst_type', newType);
|
||||||
|
setValue('alias', generateAlias(newType, schema));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangePrototype(newPrototype: IConstituenta) {
|
||||||
|
setPrototype(newPrototype);
|
||||||
|
setArguments(
|
||||||
|
newPrototype.parse.args.map(arg => ({
|
||||||
|
alias: arg.alias,
|
||||||
|
typification: arg.typification,
|
||||||
|
value: ''
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
setValue('cst_type', newPrototype.cst_type);
|
||||||
|
setValue('alias', generateAlias(newPrototype.cst_type, schema));
|
||||||
|
setValue('definition_formal', newPrototype.definition_formal);
|
||||||
|
setValue('term_raw', newPrototype.term_raw);
|
||||||
|
setValue('definition_raw', newPrototype.definition_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeTemplateID(newTemplateID: number | null) {
|
||||||
|
setTemplateID(newTemplateID);
|
||||||
|
setPrototype(null);
|
||||||
|
setArguments([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TemplateContext
|
||||||
|
value={{
|
||||||
|
templateID,
|
||||||
|
prototype,
|
||||||
|
filterCategory,
|
||||||
|
args,
|
||||||
|
onChangeArguments,
|
||||||
|
onChangePrototype,
|
||||||
|
onChangeFilterCategory: setFilterCategory,
|
||||||
|
onChangeTemplateID
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TemplateContext>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,26 +1,9 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, use, useEffect, useState } from 'react';
|
import { createContext, use } from 'react';
|
||||||
|
|
||||||
import { urls, useConceptNavigation } from '@/app';
|
import { type CstType } from '../../backend/types';
|
||||||
import { useAuthSuspense } from '@/features/auth';
|
|
||||||
import { useLibrarySearchStore } from '@/features/library';
|
|
||||||
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
|
||||||
import { useRoleStore, UserRole } from '@/features/users';
|
|
||||||
|
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
|
||||||
import { useModificationStore } from '@/stores/modification';
|
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
|
||||||
import { PARAMETER, prefixes } from '@/utils/constants';
|
|
||||||
import { promptText } from '@/utils/labels';
|
|
||||||
import { promptUnsaved } from '@/utils/utils';
|
|
||||||
|
|
||||||
import { CstType, type IConstituentaBasicsDTO, type ICstCreateDTO } from '../../backend/types';
|
|
||||||
import { useCstCreate } from '../../backend/use-cst-create';
|
|
||||||
import { useCstMove } from '../../backend/use-cst-move';
|
|
||||||
import { useRSFormSuspense } from '../../backend/use-rsform';
|
|
||||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||||
import { generateAlias } from '../../models/rsform-api';
|
|
||||||
|
|
||||||
export const RSTabID = {
|
export const RSTabID = {
|
||||||
CARD: 0,
|
CARD: 0,
|
||||||
|
@ -67,7 +50,7 @@ export interface IRSEditContext {
|
||||||
promptTemplate: () => void;
|
promptTemplate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RSEditContext = createContext<IRSEditContext | null>(null);
|
export const RSEditContext = createContext<IRSEditContext | null>(null);
|
||||||
export const useRSEdit = () => {
|
export const useRSEdit = () => {
|
||||||
const context = use(RSEditContext);
|
const context = use(RSEditContext);
|
||||||
if (context === null) {
|
if (context === null) {
|
||||||
|
@ -75,316 +58,3 @@ export const useRSEdit = () => {
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RSEditStateProps {
|
|
||||||
itemID: number;
|
|
||||||
activeTab: RSTabID;
|
|
||||||
activeVersion?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RSEditState = ({
|
|
||||||
itemID,
|
|
||||||
activeVersion,
|
|
||||||
activeTab,
|
|
||||||
children
|
|
||||||
}: React.PropsWithChildren<RSEditStateProps>) => {
|
|
||||||
const router = useConceptNavigation();
|
|
||||||
const adminMode = usePreferencesStore(state => state.adminMode);
|
|
||||||
const role = useRoleStore(state => state.role);
|
|
||||||
const adjustRole = useRoleStore(state => state.adjustRole);
|
|
||||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
|
||||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
|
||||||
|
|
||||||
const { user } = useAuthSuspense();
|
|
||||||
const { schema } = useRSFormSuspense({ itemID: itemID, version: activeVersion });
|
|
||||||
const { isModified } = useModificationStore();
|
|
||||||
|
|
||||||
const isOwned = !!user.id && user.id === schema.owner;
|
|
||||||
const isArchive = !!activeVersion;
|
|
||||||
const isMutable = role > UserRole.READER && !schema.read_only;
|
|
||||||
const isContentEditable = isMutable && !isArchive;
|
|
||||||
const isAttachedToOSS = schema.oss.length > 0;
|
|
||||||
|
|
||||||
const [selected, setSelected] = useState<number[]>([]);
|
|
||||||
const canDeleteSelected = selected.length > 0 && selected.every(id => !schema.cstByID.get(id)?.is_inherited);
|
|
||||||
const [focusCst, setFocusCst] = useState<IConstituenta | null>(null);
|
|
||||||
|
|
||||||
const activeCst = selected.length === 0 ? null : schema.cstByID.get(selected[selected.length - 1])!;
|
|
||||||
|
|
||||||
const { cstCreate } = useCstCreate();
|
|
||||||
const { cstMove } = useCstMove();
|
|
||||||
const { deleteItem } = useDeleteItem();
|
|
||||||
|
|
||||||
const showCreateCst = useDialogsStore(state => state.showCreateCst);
|
|
||||||
const showDeleteCst = useDialogsStore(state => state.showDeleteCst);
|
|
||||||
const showCstTemplate = useDialogsStore(state => state.showCstTemplate);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
adjustRole({
|
|
||||||
isOwner: isOwned,
|
|
||||||
isEditor: !!user.id && schema.editors.includes(user.id),
|
|
||||||
isStaff: user.is_staff,
|
|
||||||
adminMode: adminMode
|
|
||||||
}),
|
|
||||||
[schema, adjustRole, isOwned, user, adminMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleSetFocus(newValue: IConstituenta | null) {
|
|
||||||
setFocusCst(newValue);
|
|
||||||
setSelected([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateVersion(versionID?: number) {
|
|
||||||
router.push({ path: urls.schema(schema.id, versionID) });
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateOss(ossID: number, newTab?: boolean) {
|
|
||||||
router.push({ path: urls.oss(ossID), newTab: newTab });
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateRSForm({ tab, activeID }: { tab: RSTabID; activeID?: number }) {
|
|
||||||
const data = {
|
|
||||||
id: schema.id,
|
|
||||||
tab: tab,
|
|
||||||
active: activeID,
|
|
||||||
version: activeVersion
|
|
||||||
};
|
|
||||||
const url = urls.schema_props(data);
|
|
||||||
if (activeID) {
|
|
||||||
if (tab === activeTab && tab !== RSTabID.CST_EDIT) {
|
|
||||||
router.replace({ path: url });
|
|
||||||
} else {
|
|
||||||
router.push({ path: url });
|
|
||||||
}
|
|
||||||
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
|
|
||||||
data.active = schema.items[0].id;
|
|
||||||
router.replace({ path: urls.schema_props(data) });
|
|
||||||
} else {
|
|
||||||
router.push({ path: url });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateCst(cstID: number) {
|
|
||||||
if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
|
|
||||||
navigateRSForm({ tab: RSTabID.CST_EDIT, activeID: cstID });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteSchema() {
|
|
||||||
if (!window.confirm(promptText.deleteLibraryItem)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ossID = schema.oss.length > 0 ? schema.oss[0].id : null;
|
|
||||||
void deleteItem({
|
|
||||||
target: schema.id,
|
|
||||||
beforeInvalidate: () => {
|
|
||||||
if (ossID) {
|
|
||||||
return router.pushAsync({ path: urls.oss(ossID), force: true });
|
|
||||||
} else {
|
|
||||||
if (searchLocation === schema.location) {
|
|
||||||
setSearchLocation('');
|
|
||||||
}
|
|
||||||
return router.pushAsync({ path: urls.library, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCreateCst(newCst: IConstituentaBasicsDTO) {
|
|
||||||
setSelected([newCst.id]);
|
|
||||||
navigateRSForm({ tab: activeTab, activeID: newCst.id });
|
|
||||||
if (activeTab === RSTabID.CST_LIST) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const element = document.getElementById(`${prefixes.cst_list}${newCst.id}`);
|
|
||||||
if (element) {
|
|
||||||
element.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'nearest',
|
|
||||||
inline: 'end'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, PARAMETER.refreshTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveUp() {
|
|
||||||
if (selected.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
|
||||||
if (!selected.includes(cst.id)) {
|
|
||||||
return prev;
|
|
||||||
} else if (prev === -1) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
return Math.min(prev, index);
|
|
||||||
}, -1);
|
|
||||||
const target = Math.max(0, currentIndex - 1);
|
|
||||||
void cstMove({
|
|
||||||
itemID: itemID,
|
|
||||||
data: {
|
|
||||||
items: selected,
|
|
||||||
move_to: target
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveDown() {
|
|
||||||
if (selected.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let count = 0;
|
|
||||||
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
|
||||||
if (!selected.includes(cst.id)) {
|
|
||||||
return prev;
|
|
||||||
} else {
|
|
||||||
count += 1;
|
|
||||||
if (prev === -1) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
return Math.max(prev, index);
|
|
||||||
}
|
|
||||||
}, -1);
|
|
||||||
const target = Math.min(schema.items.length - 1, currentIndex - count + 2);
|
|
||||||
void cstMove({
|
|
||||||
itemID: itemID,
|
|
||||||
data: {
|
|
||||||
items: selected,
|
|
||||||
move_to: target
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCst(type: CstType | null, skipDialog: boolean, definition?: string) {
|
|
||||||
const targetType = type ?? activeCst?.cst_type ?? CstType.BASE;
|
|
||||||
const data: ICstCreateDTO = {
|
|
||||||
insert_after: activeCst?.id ?? null,
|
|
||||||
cst_type: targetType,
|
|
||||||
alias: generateAlias(targetType, schema),
|
|
||||||
term_raw: '',
|
|
||||||
definition_formal: definition ?? '',
|
|
||||||
definition_raw: '',
|
|
||||||
convention: '',
|
|
||||||
term_forms: []
|
|
||||||
};
|
|
||||||
if (skipDialog) {
|
|
||||||
void cstCreate({ itemID: schema.id, data }).then(onCreateCst);
|
|
||||||
} else {
|
|
||||||
showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cloneCst() {
|
|
||||||
if (!activeCst) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
void cstCreate({
|
|
||||||
itemID: schema.id,
|
|
||||||
data: {
|
|
||||||
insert_after: activeCst.id,
|
|
||||||
cst_type: activeCst.cst_type,
|
|
||||||
alias: generateAlias(activeCst.cst_type, schema),
|
|
||||||
term_raw: activeCst.term_raw,
|
|
||||||
definition_formal: activeCst.definition_formal,
|
|
||||||
definition_raw: activeCst.definition_raw,
|
|
||||||
convention: activeCst.convention,
|
|
||||||
term_forms: activeCst.term_forms
|
|
||||||
}
|
|
||||||
}).then(onCreateCst);
|
|
||||||
}
|
|
||||||
|
|
||||||
function promptDeleteCst() {
|
|
||||||
showDeleteCst({
|
|
||||||
schema: schema,
|
|
||||||
selected: selected,
|
|
||||||
afterDelete: (schema, deleted) => {
|
|
||||||
const isEmpty = deleted.length === schema.items.length;
|
|
||||||
const nextActive = isEmpty ? null : getNextActiveOnDelete(activeCst?.id ?? null, schema.items, deleted);
|
|
||||||
setSelected(nextActive ? [nextActive] : []);
|
|
||||||
if (!nextActive) {
|
|
||||||
navigateRSForm({ tab: RSTabID.CST_LIST });
|
|
||||||
} else if (activeTab === RSTabID.CST_EDIT) {
|
|
||||||
navigateRSForm({ tab: activeTab, activeID: nextActive });
|
|
||||||
} else {
|
|
||||||
navigateRSForm({ tab: activeTab });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function promptTemplate() {
|
|
||||||
if (isModified && !promptUnsaved()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showCstTemplate({ schema: schema, onCreate: onCreateCst, insertAfter: activeCst?.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RSEditContext
|
|
||||||
value={{
|
|
||||||
schema,
|
|
||||||
focusCst,
|
|
||||||
selected,
|
|
||||||
activeCst,
|
|
||||||
activeVersion,
|
|
||||||
|
|
||||||
isOwned,
|
|
||||||
isArchive,
|
|
||||||
isMutable,
|
|
||||||
isContentEditable,
|
|
||||||
isAttachedToOSS,
|
|
||||||
canDeleteSelected,
|
|
||||||
|
|
||||||
navigateVersion,
|
|
||||||
navigateRSForm,
|
|
||||||
navigateCst,
|
|
||||||
navigateOss,
|
|
||||||
|
|
||||||
deleteSchema,
|
|
||||||
|
|
||||||
setFocus: handleSetFocus,
|
|
||||||
setSelected,
|
|
||||||
select: (target: number) => setSelected(prev => [...prev, target]),
|
|
||||||
deselect: (target: number) => setSelected(prev => prev.filter(id => id !== target)),
|
|
||||||
toggleSelect: (target: number) =>
|
|
||||||
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
|
|
||||||
deselectAll: () => setSelected([]),
|
|
||||||
|
|
||||||
moveUp,
|
|
||||||
moveDown,
|
|
||||||
createCst,
|
|
||||||
createCstDefault: () => createCst(null, false),
|
|
||||||
cloneCst,
|
|
||||||
promptDeleteCst,
|
|
||||||
|
|
||||||
promptTemplate
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</RSEditContext>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ====== Internals =========
|
|
||||||
function getNextActiveOnDelete(activeID: number | null, items: IConstituenta[], deleted: number[]): number | null {
|
|
||||||
if (items.length === deleted.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeIndex = items.findIndex(cst => cst.id === activeID);
|
|
||||||
if (activeIndex === -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (activeIndex < items.length && deleted.find(id => id === items[activeIndex].id)) {
|
|
||||||
++activeIndex;
|
|
||||||
}
|
|
||||||
if (activeIndex >= items.length) {
|
|
||||||
activeIndex = items.length - 1;
|
|
||||||
while (activeIndex >= 0 && deleted.find(id => id === items[activeIndex].id)) {
|
|
||||||
--activeIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items[activeIndex].id;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { urls, useConceptNavigation } from '@/app';
|
||||||
|
import { useAuthSuspense } from '@/features/auth';
|
||||||
|
import { useLibrarySearchStore } from '@/features/library';
|
||||||
|
import { useDeleteItem } from '@/features/library/backend/use-delete-item';
|
||||||
|
import { useRoleStore, UserRole } from '@/features/users';
|
||||||
|
|
||||||
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
import { useModificationStore } from '@/stores/modification';
|
||||||
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
|
import { PARAMETER, prefixes } from '@/utils/constants';
|
||||||
|
import { promptText } from '@/utils/labels';
|
||||||
|
import { promptUnsaved } from '@/utils/utils';
|
||||||
|
|
||||||
|
import { CstType, type IConstituentaBasicsDTO, type ICstCreateDTO } from '../../backend/types';
|
||||||
|
import { useCstCreate } from '../../backend/use-cst-create';
|
||||||
|
import { useCstMove } from '../../backend/use-cst-move';
|
||||||
|
import { useRSFormSuspense } from '../../backend/use-rsform';
|
||||||
|
import { type IConstituenta } from '../../models/rsform';
|
||||||
|
import { generateAlias } from '../../models/rsform-api';
|
||||||
|
|
||||||
|
import { RSEditContext, RSTabID } from './rsedit-context';
|
||||||
|
|
||||||
|
interface RSEditStateProps {
|
||||||
|
itemID: number;
|
||||||
|
activeTab: RSTabID;
|
||||||
|
activeVersion?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RSEditState = ({
|
||||||
|
itemID,
|
||||||
|
activeVersion,
|
||||||
|
activeTab,
|
||||||
|
children
|
||||||
|
}: React.PropsWithChildren<RSEditStateProps>) => {
|
||||||
|
const router = useConceptNavigation();
|
||||||
|
const adminMode = usePreferencesStore(state => state.adminMode);
|
||||||
|
const role = useRoleStore(state => state.role);
|
||||||
|
const adjustRole = useRoleStore(state => state.adjustRole);
|
||||||
|
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||||
|
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||||
|
|
||||||
|
const { user } = useAuthSuspense();
|
||||||
|
const { schema } = useRSFormSuspense({ itemID: itemID, version: activeVersion });
|
||||||
|
const { isModified } = useModificationStore();
|
||||||
|
|
||||||
|
const isOwned = !!user.id && user.id === schema.owner;
|
||||||
|
const isArchive = !!activeVersion;
|
||||||
|
const isMutable = role > UserRole.READER && !schema.read_only;
|
||||||
|
const isContentEditable = isMutable && !isArchive;
|
||||||
|
const isAttachedToOSS = schema.oss.length > 0;
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
|
const canDeleteSelected = selected.length > 0 && selected.every(id => !schema.cstByID.get(id)?.is_inherited);
|
||||||
|
const [focusCst, setFocusCst] = useState<IConstituenta | null>(null);
|
||||||
|
|
||||||
|
const activeCst = selected.length === 0 ? null : schema.cstByID.get(selected[selected.length - 1])!;
|
||||||
|
|
||||||
|
const { cstCreate } = useCstCreate();
|
||||||
|
const { cstMove } = useCstMove();
|
||||||
|
const { deleteItem } = useDeleteItem();
|
||||||
|
|
||||||
|
const showCreateCst = useDialogsStore(state => state.showCreateCst);
|
||||||
|
const showDeleteCst = useDialogsStore(state => state.showDeleteCst);
|
||||||
|
const showCstTemplate = useDialogsStore(state => state.showCstTemplate);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
adjustRole({
|
||||||
|
isOwner: isOwned,
|
||||||
|
isEditor: !!user.id && schema.editors.includes(user.id),
|
||||||
|
isStaff: user.is_staff,
|
||||||
|
adminMode: adminMode
|
||||||
|
}),
|
||||||
|
[schema, adjustRole, isOwned, user, adminMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleSetFocus(newValue: IConstituenta | null) {
|
||||||
|
setFocusCst(newValue);
|
||||||
|
setSelected([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateVersion(versionID?: number) {
|
||||||
|
router.push({ path: urls.schema(schema.id, versionID) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateOss(ossID: number, newTab?: boolean) {
|
||||||
|
router.push({ path: urls.oss(ossID), newTab: newTab });
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateRSForm({ tab, activeID }: { tab: RSTabID; activeID?: number }) {
|
||||||
|
const data = {
|
||||||
|
id: schema.id,
|
||||||
|
tab: tab,
|
||||||
|
active: activeID,
|
||||||
|
version: activeVersion
|
||||||
|
};
|
||||||
|
const url = urls.schema_props(data);
|
||||||
|
if (activeID) {
|
||||||
|
if (tab === activeTab && tab !== RSTabID.CST_EDIT) {
|
||||||
|
router.replace({ path: url });
|
||||||
|
} else {
|
||||||
|
router.push({ path: url });
|
||||||
|
}
|
||||||
|
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
|
||||||
|
data.active = schema.items[0].id;
|
||||||
|
router.replace({ path: urls.schema_props(data) });
|
||||||
|
} else {
|
||||||
|
router.push({ path: url });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateCst(cstID: number) {
|
||||||
|
if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
|
||||||
|
navigateRSForm({ tab: RSTabID.CST_EDIT, activeID: cstID });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSchema() {
|
||||||
|
if (!window.confirm(promptText.deleteLibraryItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ossID = schema.oss.length > 0 ? schema.oss[0].id : null;
|
||||||
|
void deleteItem({
|
||||||
|
target: schema.id,
|
||||||
|
beforeInvalidate: () => {
|
||||||
|
if (ossID) {
|
||||||
|
return router.pushAsync({ path: urls.oss(ossID), force: true });
|
||||||
|
} else {
|
||||||
|
if (searchLocation === schema.location) {
|
||||||
|
setSearchLocation('');
|
||||||
|
}
|
||||||
|
return router.pushAsync({ path: urls.library, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreateCst(newCst: IConstituentaBasicsDTO) {
|
||||||
|
setSelected([newCst.id]);
|
||||||
|
navigateRSForm({ tab: activeTab, activeID: newCst.id });
|
||||||
|
if (activeTab === RSTabID.CST_LIST) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.getElementById(`${prefixes.cst_list}${newCst.id}`);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'end'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, PARAMETER.refreshTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUp() {
|
||||||
|
if (selected.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
||||||
|
if (!selected.includes(cst.id)) {
|
||||||
|
return prev;
|
||||||
|
} else if (prev === -1) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
return Math.min(prev, index);
|
||||||
|
}, -1);
|
||||||
|
const target = Math.max(0, currentIndex - 1);
|
||||||
|
void cstMove({
|
||||||
|
itemID: itemID,
|
||||||
|
data: {
|
||||||
|
items: selected,
|
||||||
|
move_to: target
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDown() {
|
||||||
|
if (selected.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let count = 0;
|
||||||
|
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
||||||
|
if (!selected.includes(cst.id)) {
|
||||||
|
return prev;
|
||||||
|
} else {
|
||||||
|
count += 1;
|
||||||
|
if (prev === -1) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
return Math.max(prev, index);
|
||||||
|
}
|
||||||
|
}, -1);
|
||||||
|
const target = Math.min(schema.items.length - 1, currentIndex - count + 2);
|
||||||
|
void cstMove({
|
||||||
|
itemID: itemID,
|
||||||
|
data: {
|
||||||
|
items: selected,
|
||||||
|
move_to: target
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCst(type: CstType | null, skipDialog: boolean, definition?: string) {
|
||||||
|
const targetType = type ?? activeCst?.cst_type ?? CstType.BASE;
|
||||||
|
const data: ICstCreateDTO = {
|
||||||
|
insert_after: activeCst?.id ?? null,
|
||||||
|
cst_type: targetType,
|
||||||
|
alias: generateAlias(targetType, schema),
|
||||||
|
term_raw: '',
|
||||||
|
definition_formal: definition ?? '',
|
||||||
|
definition_raw: '',
|
||||||
|
convention: '',
|
||||||
|
term_forms: []
|
||||||
|
};
|
||||||
|
if (skipDialog) {
|
||||||
|
void cstCreate({ itemID: schema.id, data }).then(onCreateCst);
|
||||||
|
} else {
|
||||||
|
showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneCst() {
|
||||||
|
if (!activeCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void cstCreate({
|
||||||
|
itemID: schema.id,
|
||||||
|
data: {
|
||||||
|
insert_after: activeCst.id,
|
||||||
|
cst_type: activeCst.cst_type,
|
||||||
|
alias: generateAlias(activeCst.cst_type, schema),
|
||||||
|
term_raw: activeCst.term_raw,
|
||||||
|
definition_formal: activeCst.definition_formal,
|
||||||
|
definition_raw: activeCst.definition_raw,
|
||||||
|
convention: activeCst.convention,
|
||||||
|
term_forms: activeCst.term_forms
|
||||||
|
}
|
||||||
|
}).then(onCreateCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptDeleteCst() {
|
||||||
|
showDeleteCst({
|
||||||
|
schema: schema,
|
||||||
|
selected: selected,
|
||||||
|
afterDelete: (schema, deleted) => {
|
||||||
|
const isEmpty = deleted.length === schema.items.length;
|
||||||
|
const nextActive = isEmpty ? null : getNextActiveOnDelete(activeCst?.id ?? null, schema.items, deleted);
|
||||||
|
setSelected(nextActive ? [nextActive] : []);
|
||||||
|
if (!nextActive) {
|
||||||
|
navigateRSForm({ tab: RSTabID.CST_LIST });
|
||||||
|
} else if (activeTab === RSTabID.CST_EDIT) {
|
||||||
|
navigateRSForm({ tab: activeTab, activeID: nextActive });
|
||||||
|
} else {
|
||||||
|
navigateRSForm({ tab: activeTab });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptTemplate() {
|
||||||
|
if (isModified && !promptUnsaved()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showCstTemplate({ schema: schema, onCreate: onCreateCst, insertAfter: activeCst?.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RSEditContext
|
||||||
|
value={{
|
||||||
|
schema,
|
||||||
|
focusCst,
|
||||||
|
selected,
|
||||||
|
activeCst,
|
||||||
|
activeVersion,
|
||||||
|
|
||||||
|
isOwned,
|
||||||
|
isArchive,
|
||||||
|
isMutable,
|
||||||
|
isContentEditable,
|
||||||
|
isAttachedToOSS,
|
||||||
|
canDeleteSelected,
|
||||||
|
|
||||||
|
navigateVersion,
|
||||||
|
navigateRSForm,
|
||||||
|
navigateCst,
|
||||||
|
navigateOss,
|
||||||
|
|
||||||
|
deleteSchema,
|
||||||
|
|
||||||
|
setFocus: handleSetFocus,
|
||||||
|
setSelected,
|
||||||
|
select: (target: number) => setSelected(prev => [...prev, target]),
|
||||||
|
deselect: (target: number) => setSelected(prev => prev.filter(id => id !== target)),
|
||||||
|
toggleSelect: (target: number) =>
|
||||||
|
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
|
||||||
|
deselectAll: () => setSelected([]),
|
||||||
|
|
||||||
|
moveUp,
|
||||||
|
moveDown,
|
||||||
|
createCst,
|
||||||
|
createCstDefault: () => createCst(null, false),
|
||||||
|
cloneCst,
|
||||||
|
promptDeleteCst,
|
||||||
|
|
||||||
|
promptTemplate
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RSEditContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====== Internals =========
|
||||||
|
function getNextActiveOnDelete(activeID: number | null, items: IConstituenta[], deleted: number[]): number | null {
|
||||||
|
if (items.length === deleted.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeIndex = items.findIndex(cst => cst.id === activeID);
|
||||||
|
if (activeIndex === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (activeIndex < items.length && deleted.find(id => id === items[activeIndex].id)) {
|
||||||
|
++activeIndex;
|
||||||
|
}
|
||||||
|
if (activeIndex >= items.length) {
|
||||||
|
activeIndex = items.length - 1;
|
||||||
|
while (activeIndex >= 0 && deleted.find(id => id === items[activeIndex].id)) {
|
||||||
|
--activeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items[activeIndex].id;
|
||||||
|
}
|
|
@ -16,7 +16,8 @@ import { useModificationStore } from '@/stores/modification';
|
||||||
|
|
||||||
import { ConstituentaTooltip } from '../../components/constituenta-tooltip';
|
import { ConstituentaTooltip } from '../../components/constituenta-tooltip';
|
||||||
|
|
||||||
import { RSEditState, RSTabID } from './rsedit-context';
|
import { RSTabID } from './rsedit-context';
|
||||||
|
import { RSEditState } from './rsedit-state';
|
||||||
import { RSTabs } from './rstabs';
|
import { RSTabs } from './rstabs';
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
|
|
Loading…
Reference in New Issue
Block a user