2024-06-07 20:17:03 +03:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
import fileDownload from 'js-file-download';
|
2025-01-23 19:41:31 +03:00
|
|
|
import { createContext, useContext, useEffect, useState } from 'react';
|
2024-06-07 20:17:03 +03:00
|
|
|
import { toast } from 'react-toastify';
|
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
|
2024-06-07 20:17:03 +03:00
|
|
|
import { urls } from '@/app/urls';
|
2025-01-21 20:33:05 +03:00
|
|
|
import { useAuth } from '@/backend/auth/useAuth';
|
2025-01-23 19:41:31 +03:00
|
|
|
import { useSetAccessPolicy } from '@/backend/library/useSetAccessPolicy';
|
|
|
|
import { useSetEditors } from '@/backend/library/useSetEditors';
|
|
|
|
import { useSetLocation } from '@/backend/library/useSetLocation';
|
|
|
|
import { useSetOwner } from '@/backend/library/useSetOwner';
|
|
|
|
import { useFindPredecessor } from '@/backend/oss/useFindPredecessor';
|
|
|
|
import { ICstCreateDTO, ICstRenameDTO, ICstUpdateDTO, IInlineSynthesisDTO } from '@/backend/rsform/api';
|
|
|
|
import { useCstCreate } from '@/backend/rsform/useCstCreate';
|
|
|
|
import { useCstDelete } from '@/backend/rsform/useCstDelete';
|
|
|
|
import { useCstMove } from '@/backend/rsform/useCstMove';
|
|
|
|
import { useCstRename } from '@/backend/rsform/useCstRename';
|
|
|
|
import { useCstSubstitute } from '@/backend/rsform/useCstSubstitute';
|
|
|
|
import { useCstUpdate } from '@/backend/rsform/useCstUpdate';
|
|
|
|
import { useDownloadRSForm } from '@/backend/rsform/useDownloadRSForm';
|
|
|
|
import { useInlineSynthesis } from '@/backend/rsform/useInlineSynthesis';
|
|
|
|
import { useIsProcessingRSForm } from '@/backend/rsform/useIsProcessingRSForm';
|
|
|
|
import { useProduceStructure } from '@/backend/rsform/useProduceStructure';
|
|
|
|
import { useResetAliases } from '@/backend/rsform/useResetAliases';
|
|
|
|
import { useRestoreOrder } from '@/backend/rsform/useRestoreOrder';
|
|
|
|
import { useRSFormSuspense } from '@/backend/rsform/useRSForm';
|
2024-08-01 00:35:49 +03:00
|
|
|
import {
|
|
|
|
AccessPolicy,
|
2024-08-05 23:53:07 +03:00
|
|
|
ILibraryItemEditor,
|
2024-08-01 00:35:49 +03:00
|
|
|
IVersionData,
|
|
|
|
LibraryItemID,
|
|
|
|
LocationHead,
|
|
|
|
VersionID
|
|
|
|
} from '@/models/library';
|
2025-01-23 19:41:31 +03:00
|
|
|
import { ICstSubstitutions } from '@/models/oss';
|
|
|
|
import { ConstituentaID, CstType, IConstituenta, IConstituentaMeta, IRSForm, TermForm } from '@/models/rsform';
|
2024-06-07 20:17:03 +03:00
|
|
|
import { generateAlias } from '@/models/rsformAPI';
|
2025-01-15 23:03:23 +03:00
|
|
|
import { UserID, UserRole } from '@/models/user';
|
2025-01-16 16:31:03 +03:00
|
|
|
import { useDialogsStore } from '@/stores/dialogs';
|
2025-01-14 21:57:32 +03:00
|
|
|
import { usePreferencesStore } from '@/stores/preferences';
|
2025-01-15 23:03:23 +03:00
|
|
|
import { useRoleStore } from '@/stores/role';
|
2024-06-07 20:17:03 +03:00
|
|
|
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
|
|
|
import { information, prompts } from '@/utils/labels';
|
|
|
|
import { promptUnsaved } from '@/utils/utils';
|
|
|
|
|
2024-08-01 21:16:26 +03:00
|
|
|
import { RSTabID } from './RSTabs';
|
|
|
|
|
2024-08-05 23:53:07 +03:00
|
|
|
export interface IRSEditContext extends ILibraryItemEditor {
|
2024-06-07 20:17:03 +03:00
|
|
|
schema?: IRSForm;
|
|
|
|
selected: ConstituentaID[];
|
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
isOwned: boolean;
|
|
|
|
isArchive: boolean;
|
2024-06-07 20:17:03 +03:00
|
|
|
isMutable: boolean;
|
|
|
|
isContentEditable: boolean;
|
|
|
|
isProcessing: boolean;
|
2024-08-05 23:53:07 +03:00
|
|
|
isAttachedToOSS: boolean;
|
2024-06-07 20:17:03 +03:00
|
|
|
canProduceStructure: boolean;
|
2024-08-01 11:55:45 +03:00
|
|
|
canDeleteSelected: boolean;
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
|
|
setOwner: (newOwner: UserID) => void;
|
|
|
|
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
|
|
|
promptEditors: () => void;
|
|
|
|
promptLocation: () => void;
|
|
|
|
|
|
|
|
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
|
|
|
select: (target: ConstituentaID) => void;
|
|
|
|
deselect: (target: ConstituentaID) => void;
|
|
|
|
toggleSelect: (target: ConstituentaID) => void;
|
|
|
|
deselectAll: () => void;
|
|
|
|
|
2024-08-01 00:35:49 +03:00
|
|
|
viewOSS: (target: LibraryItemID, newTab?: boolean) => void;
|
2024-06-07 20:17:03 +03:00
|
|
|
viewVersion: (version?: VersionID, newTab?: boolean) => void;
|
2024-08-01 21:16:26 +03:00
|
|
|
viewPredecessor: (target: ConstituentaID) => void;
|
2024-06-07 20:17:03 +03:00
|
|
|
createVersion: () => void;
|
|
|
|
restoreVersion: () => void;
|
2024-08-01 11:55:45 +03:00
|
|
|
promptEditVersions: () => void;
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
|
|
moveUp: () => void;
|
|
|
|
moveDown: () => void;
|
|
|
|
createCst: (type: CstType | undefined, skipDialog: boolean, definition?: string) => void;
|
|
|
|
renameCst: () => void;
|
|
|
|
cloneCst: () => void;
|
2024-08-01 11:55:45 +03:00
|
|
|
promptDeleteCst: () => void;
|
2024-06-07 20:17:03 +03:00
|
|
|
editTermForms: () => void;
|
|
|
|
|
|
|
|
promptTemplate: () => void;
|
|
|
|
promptClone: () => void;
|
|
|
|
promptUpload: () => void;
|
|
|
|
share: () => void;
|
|
|
|
download: () => void;
|
|
|
|
|
|
|
|
reindex: () => void;
|
|
|
|
reorder: () => void;
|
|
|
|
produceStructure: () => void;
|
|
|
|
inlineSynthesis: () => void;
|
|
|
|
substitute: () => void;
|
2024-11-20 00:32:26 +03:00
|
|
|
|
|
|
|
showTypeGraph: () => void;
|
2024-12-18 12:35:17 +03:00
|
|
|
showQR: () => void;
|
2024-06-07 20:17:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const RSEditContext = createContext<IRSEditContext | null>(null);
|
|
|
|
export const useRSEdit = () => {
|
|
|
|
const context = useContext(RSEditContext);
|
|
|
|
if (context === null) {
|
2024-12-12 21:58:07 +03:00
|
|
|
throw new Error('useRSEdit has to be used within <RSEditState>');
|
2024-06-07 20:17:03 +03:00
|
|
|
}
|
|
|
|
return context;
|
|
|
|
};
|
|
|
|
|
|
|
|
interface RSEditStateProps {
|
2025-01-23 19:41:31 +03:00
|
|
|
itemID: LibraryItemID;
|
|
|
|
versionID?: VersionID;
|
|
|
|
|
2024-06-07 20:17:03 +03:00
|
|
|
selected: ConstituentaID[];
|
|
|
|
isModified: boolean;
|
|
|
|
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
|
|
|
activeCst?: IConstituenta;
|
|
|
|
|
|
|
|
onCreateCst?: (newCst: IConstituentaMeta) => void;
|
|
|
|
onDeleteCst?: (newActive?: ConstituentaID) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const RSEditState = ({
|
2025-01-23 19:41:31 +03:00
|
|
|
itemID,
|
|
|
|
versionID,
|
2024-06-07 20:17:03 +03:00
|
|
|
selected,
|
|
|
|
setSelected,
|
|
|
|
activeCst,
|
|
|
|
isModified,
|
|
|
|
onCreateCst,
|
|
|
|
onDeleteCst,
|
|
|
|
children
|
2024-09-19 17:48:48 +03:00
|
|
|
}: React.PropsWithChildren<RSEditStateProps>) => {
|
2024-06-07 20:17:03 +03:00
|
|
|
const router = useConceptNavigation();
|
|
|
|
const { user } = useAuth();
|
2025-01-14 21:57:32 +03:00
|
|
|
const adminMode = usePreferencesStore(state => state.adminMode);
|
2025-01-15 23:03:23 +03:00
|
|
|
const role = useRoleStore(state => state.role);
|
|
|
|
const adjustRole = useRoleStore(state => state.adjustRole);
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
const { schema } = useRSFormSuspense({ itemID: itemID, version: versionID });
|
|
|
|
const isProcessing = useIsProcessingRSForm();
|
|
|
|
|
|
|
|
const { download: downloadFile } = useDownloadRSForm();
|
|
|
|
const { findPredecessor } = useFindPredecessor();
|
|
|
|
|
|
|
|
const { setOwner: setItemOwner } = useSetOwner();
|
|
|
|
const { setLocation: setItemLocation } = useSetLocation();
|
|
|
|
const { setAccessPolicy: setItemAccessPolicy } = useSetAccessPolicy();
|
|
|
|
const { setEditors: setItemEditors } = useSetEditors();
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
const { cstCreate } = useCstCreate();
|
|
|
|
const { cstRename } = useCstRename();
|
|
|
|
const { cstSubstitute } = useCstSubstitute();
|
|
|
|
const { cstMove } = useCstMove();
|
|
|
|
const { cstDelete } = useCstDelete();
|
|
|
|
const { cstUpdate } = useCstUpdate();
|
|
|
|
|
|
|
|
const { produceStructure: produceStructureInternal } = useProduceStructure();
|
|
|
|
const { inlineSynthesis: inlineSynthesisInternal } = useInlineSynthesis();
|
|
|
|
const { restoreOrder: restoreOrderInternal } = useRestoreOrder();
|
|
|
|
const { resetAliases: resetAliasesInternal } = useResetAliases();
|
|
|
|
|
|
|
|
const isOwned = user?.id === schema?.owner || false;
|
|
|
|
|
|
|
|
const isArchive = !!versionID;
|
|
|
|
|
|
|
|
const isMutable = role > UserRole.READER && !schema?.read_only;
|
|
|
|
const isContentEditable = isMutable && !isArchive;
|
|
|
|
const canDeleteSelected = selected.length > 0 && selected.every(id => !schema?.cstByID.get(id)?.is_inherited);
|
|
|
|
const isAttachedToOSS =
|
|
|
|
!!schema && schema.oss.length > 0 && (schema.stats.count_inherited > 0 || schema.items.length === 0);
|
|
|
|
|
|
|
|
const [renameInitialData, setRenameInitialData] = useState<ICstRenameDTO>();
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-16 16:31:03 +03:00
|
|
|
const showClone = useDialogsStore(state => state.showCloneLibraryItem);
|
|
|
|
const showCreateVersion = useDialogsStore(state => state.showCreateVersion);
|
|
|
|
const showEditVersions = useDialogsStore(state => state.showEditVersions);
|
|
|
|
const showEditEditors = useDialogsStore(state => state.showEditEditors);
|
|
|
|
const showEditLocation = useDialogsStore(state => state.showChangeLocation);
|
|
|
|
|
|
|
|
const showCreateCst = useDialogsStore(state => state.showCreateCst);
|
|
|
|
const showDeleteCst = useDialogsStore(state => state.showDeleteCst);
|
|
|
|
|
|
|
|
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
|
|
|
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
|
|
|
|
|
|
|
const showSubstituteCst = useDialogsStore(state => state.showSubstituteCst);
|
|
|
|
const showCstTemplate = useDialogsStore(state => state.showCstTemplate);
|
|
|
|
const showInlineSynthesis = useDialogsStore(state => state.showInlineSynthesis);
|
|
|
|
const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph);
|
|
|
|
const showUpload = useDialogsStore(state => state.showUploadRSForm);
|
|
|
|
const showQR = useDialogsStore(state => state.showQR);
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
const typeInfo = schema
|
|
|
|
? schema.items.map(item => ({
|
|
|
|
alias: item.alias,
|
|
|
|
result: item.parse.typification,
|
|
|
|
args: item.parse.args
|
|
|
|
}))
|
|
|
|
: [];
|
|
|
|
|
|
|
|
const canProduceStructure =
|
|
|
|
!!activeCst &&
|
|
|
|
!!activeCst.parse.typification &&
|
|
|
|
activeCst.cst_type !== CstType.BASE &&
|
|
|
|
activeCst.cst_type !== CstType.CONSTANT;
|
2024-11-20 00:32:26 +03:00
|
|
|
|
2024-12-12 20:57:45 +03:00
|
|
|
useEffect(
|
2024-06-07 20:17:03 +03:00
|
|
|
() =>
|
2025-01-15 23:03:23 +03:00
|
|
|
adjustRole({
|
2025-01-23 19:41:31 +03:00
|
|
|
isOwner: isOwned,
|
|
|
|
isEditor: (user && schema?.editors.includes(user?.id)) ?? false,
|
2025-01-15 23:03:23 +03:00
|
|
|
isStaff: user?.is_staff ?? false,
|
|
|
|
adminMode: adminMode
|
2024-06-07 20:17:03 +03:00
|
|
|
}),
|
2025-01-23 19:41:31 +03:00
|
|
|
[schema, adjustRole, isOwned, user, adminMode]
|
2024-06-07 20:17:03 +03:00
|
|
|
);
|
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function viewVersion(version?: VersionID, newTab?: boolean) {
|
|
|
|
router.push(urls.schema(itemID, version), newTab);
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function viewPredecessor(target: ConstituentaID) {
|
|
|
|
findPredecessor({ target: target }, reference =>
|
|
|
|
router.push(
|
|
|
|
urls.schema_props({
|
|
|
|
id: reference.schema,
|
|
|
|
active: reference.id,
|
|
|
|
tab: RSTabID.CST_EDIT
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2024-08-01 21:16:26 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function viewOSS(target: LibraryItemID, newTab?: boolean) {
|
|
|
|
router.push(urls.oss(target), newTab);
|
|
|
|
}
|
2024-08-01 00:35:49 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function restoreVersion() {
|
|
|
|
if (!versionID || !window.confirm(prompts.restoreArchive)) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
model.versionRestore(versionID, () => {
|
2024-06-07 20:17:03 +03:00
|
|
|
toast.success(information.versionRestored);
|
|
|
|
viewVersion(undefined);
|
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function calculateCloneLocation() {
|
|
|
|
if (!schema) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return LocationHead.USER;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
const location = schema.location;
|
|
|
|
const head = schema.location.substring(0, 2) as LocationHead;
|
2024-06-07 20:17:03 +03:00
|
|
|
if (head === LocationHead.LIBRARY) {
|
|
|
|
return user?.is_staff ? location : LocationHead.USER;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
if (schema.owner === user?.id) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return location;
|
|
|
|
}
|
|
|
|
return head === LocationHead.USER ? LocationHead.USER : location;
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleCreateCst(data: ICstCreateDTO) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
data.alias = data.alias || generateAlias(data.cst_type, schema);
|
|
|
|
cstCreate({ itemID: itemID, data }, newCst => {
|
|
|
|
toast.success(information.newConstituent(newCst.alias));
|
|
|
|
setSelected([newCst.id]);
|
|
|
|
if (onCreateCst) onCreateCst(newCst);
|
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleRenameCst(data: ICstRenameDTO) {
|
|
|
|
const oldAlias = renameInitialData?.alias ?? '';
|
|
|
|
cstRename({ itemID: itemID, data }, () => toast.success(information.renameComplete(oldAlias, data.alias)));
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleSubstituteCst(data: ICstSubstitutions) {
|
|
|
|
cstSubstitute({ itemID: itemID, data }, () => {
|
|
|
|
setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)));
|
|
|
|
toast.success(information.substituteSingle);
|
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleDeleteCst(deleted: ConstituentaID[]) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const data = {
|
|
|
|
items: deleted
|
|
|
|
};
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
const deletedNames = deleted.map(id => schema.cstByID.get(id)!.alias).join(', ');
|
|
|
|
const isEmpty = deleted.length === schema.items.length;
|
|
|
|
const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeCst?.id, schema.items, deleted);
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
cstDelete({ itemID: itemID, data }, () => {
|
|
|
|
toast.success(information.constituentsDestroyed(deletedNames));
|
|
|
|
setSelected(nextActive ? [nextActive] : []);
|
|
|
|
onDeleteCst?.(nextActive);
|
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleSaveWordforms(forms: TermForm[]) {
|
|
|
|
if (!activeCst) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const data: ICstUpdateDTO = {
|
|
|
|
target: activeCst.id,
|
|
|
|
item_data: { term_forms: forms }
|
|
|
|
};
|
|
|
|
cstUpdate({ itemID: itemID, data }, () => toast.success(information.changesSaved));
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleCreateVersion(data: IVersionData) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
model.versionCreate(data, () => {
|
|
|
|
toast.success(information.newVersion(data.version));
|
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleDeleteVersion(versionID: VersionID) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
model.versionDelete(versionID, () => {
|
|
|
|
toast.success(information.versionDestroyed);
|
|
|
|
if (versionID === versionID) {
|
|
|
|
viewVersion(undefined);
|
2024-06-07 20:17:03 +03:00
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleUpdateVersion(versionID: VersionID, data: IVersionData) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
model.versionUpdate(versionID, data, () => toast.success(information.changesSaved));
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleSetLocation = (newLocation: string) =>
|
|
|
|
setItemLocation({ itemID: itemID, location: newLocation }, () => toast.success(information.moveComplete));
|
|
|
|
|
|
|
|
function handleInlineSynthesis(data: IInlineSynthesisDTO) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const oldCount = schema.items.length;
|
|
|
|
inlineSynthesisInternal({ itemID: itemID, data }, newSchema => {
|
|
|
|
setSelected([]);
|
|
|
|
toast.success(information.addedConstituents(newSchema.items.length - oldCount));
|
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function createVersion() {
|
|
|
|
if (!schema || (isModified && !promptUnsaved())) {
|
2025-01-16 16:31:03 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
showCreateVersion({
|
2025-01-23 19:41:31 +03:00
|
|
|
versions: schema.versions,
|
2025-01-16 16:31:03 +03:00
|
|
|
onCreate: handleCreateVersion,
|
|
|
|
selected: selected,
|
2025-01-23 19:41:31 +03:00
|
|
|
totalCount: schema.items.length
|
2025-01-16 16:31:03 +03:00
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2025-01-16 16:31:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function promptEditVersions() {
|
|
|
|
if (!schema) {
|
2025-01-16 16:31:03 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
showEditVersions({
|
2025-01-23 19:41:31 +03:00
|
|
|
versions: schema.versions,
|
2025-01-16 16:31:03 +03:00
|
|
|
onDelete: handleDeleteVersion,
|
|
|
|
onUpdate: handleUpdateVersion
|
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2025-01-16 16:31:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function moveUp() {
|
|
|
|
if (!schema?.items || selected.length === 0) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
2024-06-07 20:17:03 +03:00
|
|
|
if (!selected.includes(cst.id)) {
|
|
|
|
return prev;
|
|
|
|
} else if (prev === -1) {
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
return Math.min(prev, index);
|
|
|
|
}, -1);
|
2024-09-11 20:06:01 +03:00
|
|
|
const target = Math.max(0, currentIndex - 1);
|
2025-01-23 19:41:31 +03:00
|
|
|
cstMove({
|
|
|
|
itemID: itemID,
|
|
|
|
data: {
|
|
|
|
items: selected,
|
|
|
|
move_to: target
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function moveDown() {
|
|
|
|
if (!schema?.items || selected.length === 0) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let count = 0;
|
2025-01-23 19:41:31 +03:00
|
|
|
const currentIndex = schema.items.reduce((prev, cst, index) => {
|
2024-06-07 20:17:03 +03:00
|
|
|
if (!selected.includes(cst.id)) {
|
|
|
|
return prev;
|
|
|
|
} else {
|
|
|
|
count += 1;
|
|
|
|
if (prev === -1) {
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
return Math.max(prev, index);
|
|
|
|
}
|
|
|
|
}, -1);
|
2025-01-23 19:41:31 +03:00
|
|
|
const target = Math.min(schema.items.length - 1, currentIndex - count + 2);
|
|
|
|
cstMove({
|
|
|
|
itemID: itemID,
|
|
|
|
data: {
|
|
|
|
items: selected,
|
|
|
|
move_to: target
|
2024-08-29 12:41:59 +03:00
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function createCst(type: CstType | undefined, skipDialog: boolean, definition?: string) {
|
|
|
|
if (!schema) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
handleCreateCst(data);
|
|
|
|
} else {
|
|
|
|
showCreateCst({ schema: schema, onCreate: handleCreateCst, initial: data });
|
|
|
|
}
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function cloneCst() {
|
|
|
|
if (!activeCst || !schema) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
const data: ICstCreateDTO = {
|
2024-06-07 20:17:03 +03:00
|
|
|
insert_after: activeCst.id,
|
|
|
|
cst_type: activeCst.cst_type,
|
2025-01-23 19:41:31 +03:00
|
|
|
alias: generateAlias(activeCst.cst_type, schema),
|
2024-06-07 20:17:03 +03:00
|
|
|
term_raw: activeCst.term_raw,
|
|
|
|
definition_formal: activeCst.definition_formal,
|
|
|
|
definition_raw: activeCst.definition_raw,
|
|
|
|
convention: activeCst.convention,
|
|
|
|
term_forms: activeCst.term_forms
|
|
|
|
};
|
|
|
|
handleCreateCst(data);
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function renameCst() {
|
|
|
|
if (!activeCst || !schema) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
const data: ICstRenameDTO = {
|
2024-06-07 20:17:03 +03:00
|
|
|
target: activeCst.id,
|
|
|
|
alias: activeCst.alias,
|
|
|
|
cst_type: activeCst.cst_type
|
|
|
|
};
|
|
|
|
setRenameInitialData(data);
|
2025-01-16 16:31:03 +03:00
|
|
|
showRenameCst({
|
2025-01-23 19:41:31 +03:00
|
|
|
schema: schema,
|
2025-01-16 16:31:03 +03:00
|
|
|
initial: data,
|
|
|
|
allowChangeType: !activeCst.is_inherited,
|
|
|
|
onRename: handleRenameCst
|
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function substitute() {
|
|
|
|
if (!schema || (isModified && !promptUnsaved())) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
showSubstituteCst({ schema: schema, onSubstitute: handleSubstituteCst });
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function inlineSynthesis() {
|
|
|
|
if (!schema || (isModified && !promptUnsaved())) {
|
2025-01-16 16:31:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
showInlineSynthesis({ receiver: schema, onInlineSynthesis: handleInlineSynthesis });
|
|
|
|
}
|
2025-01-16 16:31:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function promptDeleteCst() {
|
|
|
|
if (!schema) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
showDeleteCst({ schema: schema, selected: selected, onDelete: handleDeleteCst });
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function editTermForms() {
|
2024-06-07 20:17:03 +03:00
|
|
|
if (!activeCst) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (isModified && !promptUnsaved()) {
|
|
|
|
return;
|
|
|
|
}
|
2025-01-16 16:31:03 +03:00
|
|
|
showEditTerm({ target: activeCst, onSave: handleSaveWordforms });
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function reindex() {
|
|
|
|
if (!itemID) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resetAliasesInternal(itemID, () => toast.success(information.reindexComplete));
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function reorder() {
|
|
|
|
if (!itemID) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
restoreOrderInternal(itemID, () => toast.success(information.reorderComplete));
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function produceStructure() {
|
2024-06-07 20:17:03 +03:00
|
|
|
if (!activeCst) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (isModified && !promptUnsaved()) {
|
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
produceStructureInternal({ itemID: itemID, data: { target: activeCst.id } }, cstList => {
|
2024-06-07 20:17:03 +03:00
|
|
|
toast.success(information.addedConstituents(cstList.length));
|
|
|
|
if (cstList.length !== 0) {
|
|
|
|
setSelected(cstList);
|
|
|
|
}
|
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function handleSetEditors(newEditors: UserID[]) {
|
|
|
|
setItemEditors({ itemID: itemID, editors: newEditors }, () => toast.success(information.changesSaved));
|
|
|
|
}
|
2025-01-16 16:31:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function promptTemplate() {
|
|
|
|
if ((isModified && !promptUnsaved()) || !schema) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
showCstTemplate({ schema: schema, onCreate: handleCreateCst, insertAfter: activeCst?.id });
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function promptClone() {
|
|
|
|
if (!schema || (isModified && !promptUnsaved())) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-16 16:31:03 +03:00
|
|
|
showClone({
|
2025-01-23 19:41:31 +03:00
|
|
|
base: schema,
|
2025-01-16 16:31:03 +03:00
|
|
|
initialLocation: calculateCloneLocation(),
|
|
|
|
selected: selected,
|
2025-01-23 19:41:31 +03:00
|
|
|
totalCount: schema.items.length
|
2025-01-16 16:31:03 +03:00
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function promptEditors() {
|
|
|
|
if (!schema) {
|
2025-01-16 16:31:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
showEditEditors({ editors: schema.editors, setEditors: handleSetEditors });
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function promptLocation() {
|
|
|
|
if (!schema) {
|
2025-01-16 16:31:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
showEditLocation({ initial: schema.location, onChangeLocation: handleSetLocation });
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function download() {
|
|
|
|
if ((isModified && !promptUnsaved()) || !schema) {
|
2024-06-07 20:17:03 +03:00
|
|
|
return;
|
|
|
|
}
|
2025-01-23 19:41:31 +03:00
|
|
|
const fileName = (schema.alias ?? 'Schema') + EXTEOR_TRS_FILE;
|
|
|
|
downloadFile({ itemID: itemID, version: versionID }, (data: Blob) => {
|
2024-06-07 20:17:03 +03:00
|
|
|
try {
|
|
|
|
fileDownload(data, fileName);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
});
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function share() {
|
2024-06-07 20:17:03 +03:00
|
|
|
const currentRef = window.location.href;
|
|
|
|
const url = currentRef.includes('?') ? currentRef + '&share' : currentRef + '?share';
|
|
|
|
navigator.clipboard
|
|
|
|
.writeText(url)
|
|
|
|
.then(() => toast.success(information.linkReady))
|
|
|
|
.catch(console.error);
|
2025-01-23 19:41:31 +03:00
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function setOwner(newOwner: UserID) {
|
|
|
|
setItemOwner({ itemID: itemID, owner: newOwner }, () => toast.success(information.changesSaved));
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2025-01-23 19:41:31 +03:00
|
|
|
function setAccessPolicy(newPolicy: AccessPolicy) {
|
|
|
|
setItemAccessPolicy({ itemID: itemID, policy: newPolicy }, () => toast.success(information.changesSaved));
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
2024-12-18 12:35:17 +03:00
|
|
|
function generateQR(): string {
|
|
|
|
const currentRef = window.location.href;
|
|
|
|
return currentRef.includes('?') ? currentRef + '&qr' : currentRef + '?qr';
|
|
|
|
}
|
|
|
|
|
2024-06-07 20:17:03 +03:00
|
|
|
return (
|
2024-12-12 21:58:07 +03:00
|
|
|
<RSEditContext
|
2024-06-07 20:17:03 +03:00
|
|
|
value={{
|
2025-01-23 19:41:31 +03:00
|
|
|
schema: schema,
|
2024-06-07 20:17:03 +03:00
|
|
|
selected,
|
2025-01-23 19:41:31 +03:00
|
|
|
|
|
|
|
isOwned,
|
|
|
|
isArchive,
|
2024-06-07 20:17:03 +03:00
|
|
|
isMutable,
|
|
|
|
isContentEditable,
|
2025-01-23 19:41:31 +03:00
|
|
|
isProcessing,
|
2024-08-05 23:53:07 +03:00
|
|
|
isAttachedToOSS,
|
2024-06-07 20:17:03 +03:00
|
|
|
canProduceStructure,
|
2024-08-01 11:55:45 +03:00
|
|
|
canDeleteSelected,
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
|
|
setOwner,
|
|
|
|
setAccessPolicy,
|
|
|
|
promptEditors,
|
|
|
|
promptLocation,
|
|
|
|
|
|
|
|
setSelected: setSelected,
|
|
|
|
select: (target: ConstituentaID) => setSelected(prev => [...prev, target]),
|
|
|
|
deselect: (target: ConstituentaID) => setSelected(prev => prev.filter(id => id !== target)),
|
|
|
|
toggleSelect: (target: ConstituentaID) =>
|
|
|
|
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
|
|
|
|
deselectAll: () => setSelected([]),
|
|
|
|
|
2024-08-01 00:35:49 +03:00
|
|
|
viewOSS,
|
2024-06-07 20:17:03 +03:00
|
|
|
viewVersion,
|
2024-08-01 21:16:26 +03:00
|
|
|
viewPredecessor,
|
2024-06-07 20:17:03 +03:00
|
|
|
createVersion,
|
|
|
|
restoreVersion,
|
2025-01-16 16:31:03 +03:00
|
|
|
promptEditVersions,
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
|
|
moveUp,
|
|
|
|
moveDown,
|
|
|
|
createCst,
|
|
|
|
cloneCst,
|
|
|
|
renameCst,
|
2025-01-16 16:31:03 +03:00
|
|
|
promptDeleteCst,
|
2024-06-07 20:17:03 +03:00
|
|
|
editTermForms,
|
|
|
|
|
|
|
|
promptTemplate,
|
|
|
|
promptClone,
|
2025-01-23 19:41:31 +03:00
|
|
|
promptUpload: () => showUpload({ itemID: model.itemID! }),
|
2024-06-07 20:17:03 +03:00
|
|
|
download,
|
|
|
|
share,
|
|
|
|
|
|
|
|
reindex,
|
|
|
|
reorder,
|
|
|
|
inlineSynthesis,
|
|
|
|
produceStructure,
|
2024-11-20 00:32:26 +03:00
|
|
|
substitute,
|
|
|
|
|
2025-01-16 16:31:03 +03:00
|
|
|
showTypeGraph: () => showTypeGraph({ items: typeInfo }),
|
|
|
|
showQR: () => showQR({ target: generateQR() })
|
2024-06-07 20:17:03 +03:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{children}
|
2024-12-12 21:58:07 +03:00
|
|
|
</RSEditContext>
|
2024-06-07 20:17:03 +03:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// ====== Internals =========
|
|
|
|
function getNextActiveOnDelete(
|
|
|
|
activeID: ConstituentaID | undefined,
|
|
|
|
items: IConstituenta[],
|
|
|
|
deleted: ConstituentaID[]
|
|
|
|
): ConstituentaID | undefined {
|
|
|
|
if (items.length === deleted.length) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
let activeIndex = items.findIndex(cst => cst.id === activeID);
|
|
|
|
if (activeIndex === -1) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|