From 9e50312d230502398c27eacc7c5ccec66aec83aa Mon Sep 17 00:00:00 2001
From: Ivan <8611739+IRBorisov@users.noreply.github.com>
Date: Sun, 23 Feb 2025 16:54:11 +0300
Subject: [PATCH] R: Refactor menu bars and fix QR dialog styling
---
.../src/components/Modal/ModalForm.tsx | 17 +-
.../src/components/Modal/ModalView.tsx | 17 +-
.../features/library/components/IconRole.tsx | 21 +
.../features/library/components/MenuRole.tsx | 90 ++++
.../frontend/src/features/library/index.ts | 1 +
.../oss/pages/OssPage/MenuEditOss.tsx | 50 ++
.../features/oss/pages/OssPage/MenuMain.tsx | 99 ++++
.../oss/pages/OssPage/MenuOssTabs.tsx | 207 +-------
.../src/features/rsform/dialogs/DlgShowQR.tsx | 2 +-
.../pages/RSFormPage/MenuEditSchema.tsx | 182 +++++++
.../rsform/pages/RSFormPage/MenuMain.tsx | 197 ++++++++
.../rsform/pages/RSFormPage/MenuRSTabs.tsx | 445 +-----------------
rsconcept/frontend/vite.config.ts | 2 +-
13 files changed, 678 insertions(+), 652 deletions(-)
create mode 100644 rsconcept/frontend/src/features/library/components/IconRole.tsx
create mode 100644 rsconcept/frontend/src/features/library/components/MenuRole.tsx
create mode 100644 rsconcept/frontend/src/features/oss/pages/OssPage/MenuEditOss.tsx
create mode 100644 rsconcept/frontend/src/features/oss/pages/OssPage/MenuMain.tsx
create mode 100644 rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuEditSchema.tsx
create mode 100644 rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuMain.tsx
diff --git a/rsconcept/frontend/src/components/Modal/ModalForm.tsx b/rsconcept/frontend/src/components/Modal/ModalForm.tsx
index 72fdbbe7..6c919ab7 100644
--- a/rsconcept/frontend/src/components/Modal/ModalForm.tsx
+++ b/rsconcept/frontend/src/components/Modal/ModalForm.tsx
@@ -9,6 +9,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
+import { Overlay } from '../Container';
import { Button, MiniButton, SubmitButton } from '../Control';
import { IconClose } from '../Icons';
import { type Styling } from '../props';
@@ -103,13 +104,15 @@ export function ModalForm({
) : null}
- }
- className='float-right mt-2 mr-2'
- onClick={handleCancel}
- />
+
+ }
+ className='float-right mt-2 mr-2'
+ onClick={handleCancel}
+ />
+
{header ?
{header}
: null}
diff --git a/rsconcept/frontend/src/components/Modal/ModalView.tsx b/rsconcept/frontend/src/components/Modal/ModalView.tsx
index 91020202..bcf029ee 100644
--- a/rsconcept/frontend/src/components/Modal/ModalView.tsx
+++ b/rsconcept/frontend/src/components/Modal/ModalView.tsx
@@ -9,6 +9,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
+import { Overlay } from '../Container';
import { Button, MiniButton } from '../Control';
import { IconClose } from '../Icons';
@@ -48,13 +49,15 @@ export function ModalView({
) : null}
- }
- className='float-right mt-2 mr-2'
- onClick={hideDialog}
- />
+
+ }
+ className='float-right mt-2 mr-2'
+ onClick={hideDialog}
+ />
+
{header ? {header}
: null}
diff --git a/rsconcept/frontend/src/features/library/components/IconRole.tsx b/rsconcept/frontend/src/features/library/components/IconRole.tsx
new file mode 100644
index 00000000..f76d0078
--- /dev/null
+++ b/rsconcept/frontend/src/features/library/components/IconRole.tsx
@@ -0,0 +1,21 @@
+import { UserRole } from '@/features/users';
+
+import { IconAdmin, IconEditor, IconOwner, IconReader } from '@/components/Icons';
+
+interface IconRoleProps {
+ role: UserRole;
+ size?: string;
+}
+
+export function IconRole({ role, size = '1.25rem' }: IconRoleProps) {
+ switch (role) {
+ case UserRole.ADMIN:
+ return ;
+ case UserRole.OWNER:
+ return ;
+ case UserRole.EDITOR:
+ return ;
+ case UserRole.READER:
+ return ;
+ }
+}
diff --git a/rsconcept/frontend/src/features/library/components/MenuRole.tsx b/rsconcept/frontend/src/features/library/components/MenuRole.tsx
new file mode 100644
index 00000000..fd057760
--- /dev/null
+++ b/rsconcept/frontend/src/features/library/components/MenuRole.tsx
@@ -0,0 +1,90 @@
+import { urls, useConceptNavigation } from '@/app';
+import { useAuthSuspense } from '@/features/auth';
+import { useRoleStore, UserRole } from '@/features/users';
+import { describeUserRole, labelUserRole } from '@/features/users/labels';
+
+import { Button } from '@/components/Control';
+import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
+import { IconAlert } from '@/components/Icons';
+
+import { IconRole } from './IconRole';
+
+interface MenuRoleProps {
+ isOwned: boolean;
+ isEditor: boolean;
+}
+
+export function MenuRole({ isOwned, isEditor }: MenuRoleProps) {
+ const { user, isAnonymous } = useAuthSuspense();
+ const router = useConceptNavigation();
+ const accessMenu = useDropdown();
+
+ const role = useRoleStore(state => state.role);
+ const setRole = useRoleStore(state => state.setRole);
+
+ function handleChangeMode(newMode: UserRole) {
+ accessMenu.hide();
+ setRole(newMode);
+ }
+
+ if (isAnonymous) {
+ return (
+ }
+ onClick={() => router.push(urls.login)}
+ />
+ );
+ }
+
+ return (
+
+ }
+ onClick={accessMenu.toggle}
+ />
+
+ }
+ onClick={() => handleChangeMode(UserRole.READER)}
+ />
+ }
+ disabled={!isOwned && !isEditor}
+ onClick={() => handleChangeMode(UserRole.EDITOR)}
+ />
+ }
+ disabled={!isOwned}
+ onClick={() => handleChangeMode(UserRole.OWNER)}
+ />
+ }
+ disabled={!user.is_staff}
+ onClick={() => handleChangeMode(UserRole.ADMIN)}
+ />
+
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/library/index.ts b/rsconcept/frontend/src/features/library/index.ts
index 439be626..2fbcde57 100644
--- a/rsconcept/frontend/src/features/library/index.ts
+++ b/rsconcept/frontend/src/features/library/index.ts
@@ -7,6 +7,7 @@ export { useUpdateItem } from './backend/useUpdateItem';
export { useUpdateTimestamp } from './backend/useUpdateTimestamp';
export { useVersionRestore } from './backend/useVersionRestore';
export { EditorLibraryItem } from './components/EditorLibraryItem';
+export { MenuRole } from './components/MenuRole';
export { MiniSelectorOSS } from './components/MiniSelectorOSS';
export { PickSchema } from './components/PickSchema';
export { SelectLibraryItem } from './components/SelectLibraryItem';
diff --git a/rsconcept/frontend/src/features/oss/pages/OssPage/MenuEditOss.tsx b/rsconcept/frontend/src/features/oss/pages/OssPage/MenuEditOss.tsx
new file mode 100644
index 00000000..4d74934c
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/pages/OssPage/MenuEditOss.tsx
@@ -0,0 +1,50 @@
+import { useAuthSuspense } from '@/features/auth';
+
+import { Button } from '@/components/Control';
+import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
+import { IconChild, IconEdit2 } from '@/components/Icons';
+
+import { useMutatingOss } from '../../backend/useMutatingOss';
+
+import { useOssEdit } from './OssEditContext';
+
+export function MenuEditOss() {
+ const { isAnonymous } = useAuthSuspense();
+ const editMenu = useDropdown();
+ const { promptRelocateConstituents, isMutable } = useOssEdit();
+ const isProcessing = useMutatingOss();
+
+ function handleRelocate() {
+ editMenu.hide();
+ promptRelocateConstituents(undefined, []);
+ }
+
+ if (isAnonymous) {
+ return null;
+ }
+
+ return (
+
+ }
+ onClick={editMenu.toggle}
+ />
+
+ }
+ disabled={isProcessing}
+ onClick={handleRelocate}
+ />
+
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/pages/OssPage/MenuMain.tsx b/rsconcept/frontend/src/features/oss/pages/OssPage/MenuMain.tsx
new file mode 100644
index 00000000..b603eaa4
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/pages/OssPage/MenuMain.tsx
@@ -0,0 +1,99 @@
+import { urls, useConceptNavigation } from '@/app';
+import { useAuthSuspense } from '@/features/auth';
+import { useRoleStore, UserRole } from '@/features/users';
+
+import { Divider } from '@/components/Container';
+import { Button } from '@/components/Control';
+import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
+import { IconDestroy, IconLibrary, IconMenu, IconNewItem, IconQR, IconShare } from '@/components/Icons';
+import { useDialogsStore } from '@/stores/dialogs';
+import { generatePageQR, sharePage } from '@/utils/utils';
+
+import { useMutatingOss } from '../../backend/useMutatingOss';
+
+import { useOssEdit } from './OssEditContext';
+
+export function MenuMain() {
+ const router = useConceptNavigation();
+ const { isMutable, deleteSchema } = useOssEdit();
+ const isProcessing = useMutatingOss();
+
+ const { isAnonymous } = useAuthSuspense();
+
+ const role = useRoleStore(state => state.role);
+
+ const showQR = useDialogsStore(state => state.showQR);
+
+ const schemaMenu = useDropdown();
+
+ function handleDelete() {
+ schemaMenu.hide();
+ deleteSchema();
+ }
+
+ function handleShare() {
+ schemaMenu.hide();
+ sharePage();
+ }
+
+ function handleCreateNew() {
+ router.push(urls.create_schema);
+ }
+
+ function handleShowQR() {
+ schemaMenu.hide();
+ showQR({ target: generatePageQR() });
+ }
+
+ return (
+
+
}
+ className='h-full pl-2'
+ onClick={schemaMenu.toggle}
+ />
+
+ }
+ onClick={handleShare}
+ />
+ }
+ onClick={handleShowQR}
+ />
+ {isMutable ? (
+ }
+ disabled={isProcessing || role < UserRole.OWNER}
+ onClick={handleDelete}
+ />
+ ) : null}
+
+
+
+ {!isAnonymous ? (
+ }
+ onClick={handleCreateNew}
+ />
+ ) : null}
+ }
+ onClick={() => router.push(urls.library)}
+ />
+
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/pages/OssPage/MenuOssTabs.tsx b/rsconcept/frontend/src/features/oss/pages/OssPage/MenuOssTabs.tsx
index 8fc563bd..44ab82e8 100644
--- a/rsconcept/frontend/src/features/oss/pages/OssPage/MenuOssTabs.tsx
+++ b/rsconcept/frontend/src/features/oss/pages/OssPage/MenuOssTabs.tsx
@@ -1,213 +1,22 @@
'use client';
-import { urls, useConceptNavigation } from '@/app';
import { useAuthSuspense } from '@/features/auth';
-import { useRoleStore, UserRole } from '@/features/users';
-import { describeUserRole, labelUserRole } from '@/features/users/labels';
-
-import { Divider } from '@/components/Container';
-import { Button } from '@/components/Control';
-import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
-import {
- IconAdmin,
- IconAlert,
- IconChild,
- IconDestroy,
- IconEdit2,
- IconEditor,
- IconLibrary,
- IconMenu,
- IconNewItem,
- IconOwner,
- IconReader,
- IconShare
-} from '@/components/Icons';
-import { sharePage } from '@/utils/utils';
-
-import { useMutatingOss } from '../../backend/useMutatingOss';
+import { MenuRole } from '@/features/library';
+import { MenuEditOss } from './MenuEditOss';
+import { MenuMain } from './MenuMain';
import { useOssEdit } from './OssEditContext';
export function MenuOssTabs() {
- const { deleteSchema, promptRelocateConstituents, isMutable, isOwned, schema } = useOssEdit();
- const router = useConceptNavigation();
- const { user, isAnonymous } = useAuthSuspense();
-
- const isProcessing = useMutatingOss();
-
- const role = useRoleStore(state => state.role);
- const setRole = useRoleStore(state => state.setRole);
-
- const schemaMenu = useDropdown();
- const editMenu = useDropdown();
- const accessMenu = useDropdown();
-
- function handleDelete() {
- schemaMenu.hide();
- deleteSchema();
- }
-
- function handleShare() {
- schemaMenu.hide();
- sharePage();
- }
-
- function handleChangeRole(newMode: UserRole) {
- accessMenu.hide();
- setRole(newMode);
- }
-
- function handleCreateNew() {
- router.push(urls.create_schema);
- }
-
- function handleLogin() {
- router.push(urls.login);
- }
-
- function handleRelocate() {
- editMenu.hide();
- promptRelocateConstituents(undefined, []);
- }
-
+ const { isOwned, schema } = useOssEdit();
+ const { user } = useAuthSuspense();
return (
-
-
}
- className='h-full pl-2'
- onClick={schemaMenu.toggle}
- />
-
- }
- onClick={handleShare}
- />
- {isMutable ? (
- }
- disabled={isProcessing || role < UserRole.OWNER}
- onClick={handleDelete}
- />
- ) : null}
+
-
+
- {!isAnonymous ? (
- }
- onClick={handleCreateNew}
- />
- ) : null}
- }
- onClick={() => router.push(urls.library)}
- />
-
-
-
- {!isAnonymous ? (
-
- }
- onClick={editMenu.toggle}
- />
-
- }
- disabled={isProcessing}
- onClick={handleRelocate}
- />
-
-
- ) : null}
-
- {!isAnonymous ? (
-
-
- ) : role === UserRole.OWNER ? (
-
- ) : role === UserRole.EDITOR ? (
-
- ) : (
-
- )
- }
- onClick={accessMenu.toggle}
- />
-
- }
- onClick={() => handleChangeRole(UserRole.READER)}
- />
- }
- disabled={!isOwned && (!user.id || !schema.editors.includes(user.id))}
- onClick={() => handleChangeRole(UserRole.EDITOR)}
- />
- }
- disabled={!isOwned}
- onClick={() => handleChangeRole(UserRole.OWNER)}
- />
- }
- disabled={!user.is_staff}
- onClick={() => handleChangeRole(UserRole.ADMIN)}
- />
-
-
- ) : null}
- {isAnonymous ? (
-
}
- onClick={handleLogin}
- />
- ) : null}
+
);
}
diff --git a/rsconcept/frontend/src/features/rsform/dialogs/DlgShowQR.tsx b/rsconcept/frontend/src/features/rsform/dialogs/DlgShowQR.tsx
index 718144c3..85ca639e 100644
--- a/rsconcept/frontend/src/features/rsform/dialogs/DlgShowQR.tsx
+++ b/rsconcept/frontend/src/features/rsform/dialogs/DlgShowQR.tsx
@@ -13,7 +13,7 @@ export interface DlgShowQRProps {
export function DlgShowQR() {
const { target } = useDialogsStore(state => state.props as DlgShowQRProps);
return (
-
+
diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuEditSchema.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuEditSchema.tsx
new file mode 100644
index 00000000..bc0bdf47
--- /dev/null
+++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuEditSchema.tsx
@@ -0,0 +1,182 @@
+import { urls, useConceptNavigation } from '@/app';
+import { useAuthSuspense } from '@/features/auth';
+
+import { Divider } from '@/components/Container';
+import { Button } from '@/components/Control';
+import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
+import {
+ IconArchive,
+ IconEdit2,
+ IconGenerateNames,
+ IconGenerateStructure,
+ IconInlineSynthesis,
+ IconReplace,
+ IconSortList,
+ IconTemplates
+} from '@/components/Icons';
+import { useDialogsStore } from '@/stores/dialogs';
+import { useModificationStore } from '@/stores/modification';
+import { promptUnsaved } from '@/utils/utils';
+
+import { useMutatingRSForm } from '../../backend/useMutatingRSForm';
+import { useProduceStructure } from '../../backend/useProduceStructure';
+import { useResetAliases } from '../../backend/useResetAliases';
+import { useRestoreOrder } from '../../backend/useRestoreOrder';
+import { type IConstituenta } from '../../models/rsform';
+import { canProduceStructure } from '../../models/rsformAPI';
+
+import { useRSEdit } from './RSEditContext';
+
+export function MenuEditSchema() {
+ const { isAnonymous } = useAuthSuspense();
+ const { isModified } = useModificationStore();
+ const router = useConceptNavigation();
+ const editMenu = useDropdown();
+ const { schema, activeCst, setSelected, isArchive, isContentEditable, promptTemplate, deselectAll } = useRSEdit();
+ const isProcessing = useMutatingRSForm();
+
+ const { resetAliases } = useResetAliases();
+ const { restoreOrder } = useRestoreOrder();
+ const { produceStructure } = useProduceStructure();
+
+ const showInlineSynthesis = useDialogsStore(state => state.showInlineSynthesis);
+ const showSubstituteCst = useDialogsStore(state => state.showSubstituteCst);
+
+ function handleReindex() {
+ editMenu.hide();
+ void resetAliases({ itemID: schema.id });
+ }
+
+ function handleRestoreOrder() {
+ editMenu.hide();
+ void restoreOrder({ itemID: schema.id });
+ }
+
+ function handleSubstituteCst() {
+ editMenu.hide();
+ if (isModified && !promptUnsaved()) {
+ return;
+ }
+ showSubstituteCst({
+ schema: schema,
+ onSubstitute: data => setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)))
+ });
+ }
+
+ function handleTemplates() {
+ editMenu.hide();
+ promptTemplate();
+ }
+
+ function handleProduceStructure(targetCst: IConstituenta | null) {
+ editMenu.hide();
+ if (!targetCst) {
+ return;
+ }
+ if (isModified && !promptUnsaved()) {
+ return;
+ }
+ void produceStructure({
+ itemID: schema.id,
+ cstID: targetCst.id
+ }).then(cstList => {
+ if (cstList.length !== 0) {
+ setSelected(cstList);
+ }
+ });
+ }
+
+ function handleInlineSynthesis() {
+ editMenu.hide();
+ if (isModified && !promptUnsaved()) {
+ return;
+ }
+ showInlineSynthesis({
+ receiver: schema,
+ onSynthesis: () => deselectAll()
+ });
+ }
+
+ if (isAnonymous) {
+ return null;
+ }
+
+ if (isArchive) {
+ return (
+ }
+ onClick={event => router.push(urls.schema(schema.id), event.ctrlKey || event.metaKey)}
+ />
+ );
+ }
+
+ return (
+
+
}
+ onClick={editMenu.toggle}
+ />
+
+ }
+ disabled={!isContentEditable || isProcessing}
+ onClick={handleTemplates}
+ />
+ }
+ disabled={!isContentEditable || isProcessing}
+ onClick={handleInlineSynthesis}
+ />
+
+
+
+ }
+ disabled={!isContentEditable || isProcessing}
+ onClick={handleRestoreOrder}
+ />
+ }
+ disabled={!isContentEditable || isProcessing}
+ onClick={handleReindex}
+ />
+ }
+ disabled={!isContentEditable || isProcessing || !activeCst || !canProduceStructure(activeCst)}
+ onClick={() => handleProduceStructure(activeCst)}
+ />
+ }
+ onClick={handleSubstituteCst}
+ disabled={!isContentEditable || isProcessing}
+ />
+
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuMain.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuMain.tsx
new file mode 100644
index 00000000..bfe88869
--- /dev/null
+++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuMain.tsx
@@ -0,0 +1,197 @@
+import fileDownload from 'js-file-download';
+
+import { urls, useConceptNavigation } from '@/app';
+import { useAuthSuspense } from '@/features/auth';
+import { AccessPolicy } from '@/features/library';
+import { LocationHead } from '@/features/library/models/library';
+import { useRoleStore, UserRole } from '@/features/users';
+
+import { Divider } from '@/components/Container';
+import { Button } from '@/components/Control';
+import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
+import {
+ IconClone,
+ IconDestroy,
+ IconDownload,
+ IconLibrary,
+ IconMenu,
+ IconNewItem,
+ IconOSS,
+ IconQR,
+ IconShare,
+ IconUpload
+} from '@/components/Icons';
+import { useDialogsStore } from '@/stores/dialogs';
+import { useModificationStore } from '@/stores/modification';
+import { EXTEOR_TRS_FILE } from '@/utils/constants';
+import { tooltipText } from '@/utils/labels';
+import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
+
+import { useDownloadRSForm } from '../../backend/useDownloadRSForm';
+import { useMutatingRSForm } from '../../backend/useMutatingRSForm';
+
+import { useRSEdit } from './RSEditContext';
+
+export function MenuMain() {
+ const router = useConceptNavigation();
+ const { schema, selected, deleteSchema, isArchive, isMutable, isContentEditable } = useRSEdit();
+ const isProcessing = useMutatingRSForm();
+
+ const { user, isAnonymous } = useAuthSuspense();
+
+ const role = useRoleStore(state => state.role);
+ const { isModified } = useModificationStore();
+
+ const { download } = useDownloadRSForm();
+
+ const showQR = useDialogsStore(state => state.showQR);
+ const showClone = useDialogsStore(state => state.showCloneLibraryItem);
+ const showUpload = useDialogsStore(state => state.showUploadRSForm);
+
+ const schemaMenu = useDropdown();
+
+ function calculateCloneLocation() {
+ const location = schema.location;
+ const head = location.substring(0, 2) as LocationHead;
+ if (head === LocationHead.LIBRARY) {
+ return user.is_staff ? location : LocationHead.USER;
+ }
+ if (schema.owner === user.id) {
+ return location;
+ }
+ return head === LocationHead.USER ? LocationHead.USER : location;
+ }
+
+ function handleDelete() {
+ schemaMenu.hide();
+ deleteSchema();
+ }
+
+ function handleDownload() {
+ schemaMenu.hide();
+ if (isModified && !promptUnsaved()) {
+ return;
+ }
+ const fileName = (schema.alias ?? 'Schema') + EXTEOR_TRS_FILE;
+ void download({
+ itemID: schema.id,
+ version: schema.version === 'latest' ? undefined : schema.version
+ }).then((data: Blob) => {
+ try {
+ fileDownload(data, fileName);
+ } catch (error) {
+ console.error(error);
+ }
+ });
+ }
+
+ function handleUpload() {
+ schemaMenu.hide();
+ showUpload({ itemID: schema.id });
+ }
+
+ function handleClone() {
+ schemaMenu.hide();
+ if (isModified && !promptUnsaved()) {
+ return;
+ }
+ showClone({
+ base: schema,
+ initialLocation: calculateCloneLocation(),
+ selected: selected,
+ totalCount: schema.items.length
+ });
+ }
+
+ function handleShare() {
+ schemaMenu.hide();
+ sharePage();
+ }
+
+ function handleShowQR() {
+ schemaMenu.hide();
+ showQR({ target: generatePageQR() });
+ }
+
+ return (
+
+
}
+ className='h-full pl-2'
+ onClick={schemaMenu.toggle}
+ />
+
+ }
+ onClick={handleShare}
+ disabled={schema.access_policy !== AccessPolicy.PUBLIC}
+ />
+ }
+ onClick={handleShowQR}
+ />
+ {!isAnonymous ? (
+ }
+ disabled={isArchive}
+ onClick={handleClone}
+ />
+ ) : null}
+ }
+ onClick={handleDownload}
+ />
+ {isContentEditable ? (
+ }
+ disabled={isProcessing || schema.oss.length !== 0}
+ onClick={handleUpload}
+ />
+ ) : null}
+ {isMutable ? (
+ }
+ disabled={isProcessing || role < UserRole.OWNER}
+ onClick={handleDelete}
+ />
+ ) : null}
+
+
+
+ {!isAnonymous ? (
+ }
+ onClick={() => router.push(urls.create_schema)}
+ />
+ ) : null}
+ {schema.oss.length > 0 ? (
+ }
+ onClick={() => router.push(urls.oss(schema.oss[0].id))}
+ />
+ ) : null}
+ }
+ onClick={() => router.push(urls.library)}
+ />
+
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx
index b733dbf8..b740b37f 100644
--- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx
+++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/MenuRSTabs.tsx
@@ -1,450 +1,21 @@
'use client';
-import fileDownload from 'js-file-download';
-
-import { urls, useConceptNavigation } from '@/app';
import { useAuthSuspense } from '@/features/auth';
-import { AccessPolicy } from '@/features/library';
-import { LocationHead } from '@/features/library/models/library';
-import { useRoleStore, UserRole } from '@/features/users';
-import { describeUserRole, labelUserRole } from '@/features/users/labels';
-
-import { Divider } from '@/components/Container';
-import { Button } from '@/components/Control';
-import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
-import {
- IconAdmin,
- IconAlert,
- IconArchive,
- IconClone,
- IconDestroy,
- IconDownload,
- IconEdit2,
- IconEditor,
- IconGenerateNames,
- IconGenerateStructure,
- IconInlineSynthesis,
- IconLibrary,
- IconMenu,
- IconNewItem,
- IconOSS,
- IconOwner,
- IconQR,
- IconReader,
- IconReplace,
- IconShare,
- IconSortList,
- IconTemplates,
- IconUpload
-} from '@/components/Icons';
-import { useDialogsStore } from '@/stores/dialogs';
-import { useModificationStore } from '@/stores/modification';
-import { EXTEOR_TRS_FILE } from '@/utils/constants';
-import { tooltipText } from '@/utils/labels';
-import { generatePageQR, promptUnsaved, sharePage } from '@/utils/utils';
-
-import { useDownloadRSForm } from '../../backend/useDownloadRSForm';
-import { useMutatingRSForm } from '../../backend/useMutatingRSForm';
-import { useProduceStructure } from '../../backend/useProduceStructure';
-import { useResetAliases } from '../../backend/useResetAliases';
-import { useRestoreOrder } from '../../backend/useRestoreOrder';
-import { canProduceStructure } from '../../models/rsformAPI';
+import { MenuRole } from '@/features/library';
+import { MenuEditSchema } from './MenuEditSchema';
+import { MenuMain } from './MenuMain';
import { useRSEdit } from './RSEditContext';
export function MenuRSTabs() {
- const router = useConceptNavigation();
- const { user, isAnonymous } = useAuthSuspense();
- const {
- activeCst,
- schema,
- selected,
- setSelected,
- deleteSchema,
- promptTemplate,
- deselectAll,
- isArchive,
- isMutable,
- isContentEditable,
- isOwned
- } = useRSEdit();
-
- const role = useRoleStore(state => state.role);
- const setRole = useRoleStore(state => state.setRole);
- const { isModified } = useModificationStore();
- const isProcessing = useMutatingRSForm();
-
- const { resetAliases } = useResetAliases();
- const { restoreOrder } = useRestoreOrder();
- const { produceStructure } = useProduceStructure();
- const { download } = useDownloadRSForm();
-
- const showInlineSynthesis = useDialogsStore(state => state.showInlineSynthesis);
- const showQR = useDialogsStore(state => state.showQR);
- const showSubstituteCst = useDialogsStore(state => state.showSubstituteCst);
- const showClone = useDialogsStore(state => state.showCloneLibraryItem);
- const showUpload = useDialogsStore(state => state.showUploadRSForm);
-
- const schemaMenu = useDropdown();
- const editMenu = useDropdown();
- const accessMenu = useDropdown();
-
- const structureEnabled = !!activeCst && canProduceStructure(activeCst);
-
- function calculateCloneLocation() {
- const location = schema.location;
- const head = location.substring(0, 2) as LocationHead;
- if (head === LocationHead.LIBRARY) {
- return user.is_staff ? location : LocationHead.USER;
- }
- if (schema.owner === user.id) {
- return location;
- }
- return head === LocationHead.USER ? LocationHead.USER : location;
- }
-
- function handleDelete() {
- schemaMenu.hide();
- deleteSchema();
- }
-
- function handleDownload() {
- schemaMenu.hide();
- if (isModified && !promptUnsaved()) {
- return;
- }
- const fileName = (schema.alias ?? 'Schema') + EXTEOR_TRS_FILE;
- void download({
- itemID: schema.id,
- version: schema.version === 'latest' ? undefined : schema.version
- }).then((data: Blob) => {
- try {
- fileDownload(data, fileName);
- } catch (error) {
- console.error(error);
- }
- });
- }
-
- function handleUpload() {
- schemaMenu.hide();
- showUpload({ itemID: schema.id });
- }
-
- function handleClone() {
- schemaMenu.hide();
- if (isModified && !promptUnsaved()) {
- return;
- }
- showClone({
- base: schema,
- initialLocation: calculateCloneLocation(),
- selected: selected,
- totalCount: schema.items.length
- });
- }
-
- function handleShare() {
- schemaMenu.hide();
- sharePage();
- }
-
- function handleShowQR() {
- schemaMenu.hide();
- showQR({ target: generatePageQR() });
- }
-
- function handleReindex() {
- editMenu.hide();
- void resetAliases({ itemID: schema.id });
- }
-
- function handleRestoreOrder() {
- editMenu.hide();
- void restoreOrder({ itemID: schema.id });
- }
-
- function handleSubstituteCst() {
- editMenu.hide();
- if (isModified && !promptUnsaved()) {
- return;
- }
- showSubstituteCst({
- schema: schema,
- onSubstitute: data => setSelected(prev => prev.filter(id => !data.substitutions.find(sub => sub.original === id)))
- });
- }
-
- function handleTemplates() {
- editMenu.hide();
- promptTemplate();
- }
-
- function handleProduceStructure() {
- editMenu.hide();
- if (!activeCst) {
- return;
- }
- if (isModified && !promptUnsaved()) {
- return;
- }
- void produceStructure({
- itemID: schema.id,
- cstID: activeCst.id
- }).then(cstList => {
- if (cstList.length !== 0) {
- setSelected(cstList);
- }
- });
- }
-
- function handleInlineSynthesis() {
- editMenu.hide();
- if (isModified && !promptUnsaved()) {
- return;
- }
- showInlineSynthesis({
- receiver: schema,
- onSynthesis: () => deselectAll()
- });
- }
-
- function handleChangeMode(newMode: UserRole) {
- accessMenu.hide();
- setRole(newMode);
- }
-
- function handleCreateNew() {
- router.push(urls.create_schema);
- }
-
- function handleLogin() {
- router.push(urls.login);
- }
+ const { user } = useAuthSuspense();
+ const { schema, isOwned } = useRSEdit();
return (
-
-
}
- className='h-full pl-2'
- onClick={schemaMenu.toggle}
- />
-
- }
- onClick={handleShare}
- disabled={schema.access_policy !== AccessPolicy.PUBLIC}
- />
- }
- onClick={handleShowQR}
- />
- {!isAnonymous ? (
- }
- disabled={isArchive}
- onClick={handleClone}
- />
- ) : null}
- }
- onClick={handleDownload}
- />
- {isContentEditable ? (
- }
- disabled={isProcessing || schema.oss.length !== 0}
- onClick={handleUpload}
- />
- ) : null}
- {isMutable ? (
- }
- disabled={isProcessing || role < UserRole.OWNER}
- onClick={handleDelete}
- />
- ) : null}
-
-
-
- {!isAnonymous ? (
- }
- onClick={handleCreateNew}
- />
- ) : null}
- {schema.oss.length > 0 ? (
- }
- onClick={() => router.push(urls.oss(schema.oss[0].id))}
- />
- ) : null}
- }
- onClick={() => router.push(urls.library)}
- />
-
-
- {!isArchive && !isAnonymous ? (
-
-
}
- onClick={editMenu.toggle}
- />
-
- }
- disabled={!isContentEditable || isProcessing}
- onClick={handleTemplates}
- />
- }
- disabled={!isContentEditable || isProcessing}
- onClick={handleInlineSynthesis}
- />
-
-
-
- }
- disabled={!isContentEditable || isProcessing}
- onClick={handleRestoreOrder}
- />
- }
- disabled={!isContentEditable || isProcessing}
- onClick={handleReindex}
- />
- }
- disabled={!isContentEditable || !structureEnabled || isProcessing}
- onClick={handleProduceStructure}
- />
- }
- onClick={handleSubstituteCst}
- disabled={!isContentEditable || isProcessing}
- />
-
-
- ) : null}
- {isArchive && !isAnonymous ? (
-
}
- onClick={event => router.push(urls.schema(schema.id), event.ctrlKey || event.metaKey)}
- />
- ) : null}
- {!isAnonymous ? (
-
-
- ) : role === UserRole.OWNER ? (
-
- ) : role === UserRole.EDITOR ? (
-
- ) : (
-
- )
- }
- onClick={accessMenu.toggle}
- />
-
- }
- onClick={() => handleChangeMode(UserRole.READER)}
- />
- }
- disabled={!isOwned && (!user.id || !schema.editors.includes(user.id))}
- onClick={() => handleChangeMode(UserRole.EDITOR)}
- />
- }
- disabled={!isOwned}
- onClick={() => handleChangeMode(UserRole.OWNER)}
- />
- }
- disabled={!user.is_staff}
- onClick={() => handleChangeMode(UserRole.ADMIN)}
- />
-
-
- ) : null}
- {isAnonymous ? (
-
}
- onClick={handleLogin}
- />
- ) : null}
+
+
+
);
}
diff --git a/rsconcept/frontend/vite.config.ts b/rsconcept/frontend/vite.config.ts
index 8bbc5460..0dea8028 100644
--- a/rsconcept/frontend/vite.config.ts
+++ b/rsconcept/frontend/vite.config.ts
@@ -11,7 +11,7 @@ const reactCompilerConfig = {
};
// Packages to include in main app bundle
-const inlinePackages = ['react', 'react-router', 'react-dom'];
+const inlinePackages = ['react', 'react-router', 'react-dom', 'global', 'react-scan'];
// Rollup warnings that should not be displayed
const warningsToIgnore = [