M: Improve UI for creating new elements
This commit is contained in:
parent
9d8405fc36
commit
cac508451d
|
@ -1,5 +1,6 @@
|
||||||
// Search new icons at https://reactsvgicons.com/
|
// Search new icons at https://reactsvgicons.com/
|
||||||
// Note: save this file using Ctrl + K, Ctrl + Shift + S to disable autoformat
|
// Note: save this file using Ctrl + K, Ctrl + Shift + S to disable autoformat
|
||||||
|
// Note: For Cursor hotkeys are Ctrl + M, Ctrl + Shift + S
|
||||||
|
|
||||||
/* eslint-disable simple-import-sort/exports */
|
/* eslint-disable simple-import-sort/exports */
|
||||||
// ==== General actions =======
|
// ==== General actions =======
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { urls, useConceptNavigation } from '@/app';
|
|
||||||
import { useLibrary } from '@/features/library/backend/use-library';
|
import { useLibrary } from '@/features/library/backend/use-library';
|
||||||
|
|
||||||
import { DropdownButton } from '@/components/dropdown';
|
import { DropdownButton } from '@/components/dropdown';
|
||||||
|
@ -33,7 +32,6 @@ interface MenuOperationProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
||||||
const router = useConceptNavigation();
|
|
||||||
const { items: libraryItems } = useLibrary();
|
const { items: libraryItems } = useLibrary();
|
||||||
const { schema, navigateOperationSchema, isMutable, canDeleteOperation } = useOssEdit();
|
const { schema, navigateOperationSchema, isMutable, canDeleteOperation } = useOssEdit();
|
||||||
const isProcessing = useMutatingOss();
|
const isProcessing = useMutatingOss();
|
||||||
|
@ -134,7 +132,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
||||||
void inputCreate({
|
void inputCreate({
|
||||||
itemID: schema.id,
|
itemID: schema.id,
|
||||||
data: { target: operation.id, layout: getLayout() }
|
data: { target: operation.id, layout: getLayout() }
|
||||||
}).then(new_schema => router.push({ path: urls.schema(new_schema.id), force: true }));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRelocateConstituents() {
|
function handleRelocateConstituents() {
|
||||||
|
|
|
@ -163,14 +163,16 @@ export function OssFlow() {
|
||||||
handleSavePositions();
|
handleSavePositions();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyQ') {
|
if (event.altKey && event.code === 'Key1') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (event.shiftKey) {
|
handleCreateBlock();
|
||||||
handleCreateBlock();
|
return;
|
||||||
} else {
|
}
|
||||||
handleCreateOperation();
|
if (event.altKey && event.code === 'Key2') {
|
||||||
}
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handleCreateOperation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key === 'Delete') {
|
if (event.key === 'Delete') {
|
||||||
|
@ -200,8 +202,10 @@ export function OssFlow() {
|
||||||
|
|
||||||
<ToolbarOssGraph
|
<ToolbarOssGraph
|
||||||
className='absolute z-pop top-8 right-1/2 translate-x-1/2'
|
className='absolute z-pop top-8 right-1/2 translate-x-1/2'
|
||||||
onCreateOperation={handleCreateOperation}
|
|
||||||
onCreateBlock={handleCreateBlock}
|
onCreateBlock={handleCreateBlock}
|
||||||
|
onCreateSchema={handleCreateOperation}
|
||||||
|
onImportSchema={handleCreateOperation}
|
||||||
|
onCreateSynthesis={handleCreateOperation}
|
||||||
onDelete={handleDeleteSelected}
|
onDelete={handleDeleteSelected}
|
||||||
onResetPositions={resetGraph}
|
onResetPositions={resetGraph}
|
||||||
openContextMenu={openContextMenu}
|
openContextMenu={openContextMenu}
|
||||||
|
|
|
@ -2,16 +2,21 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { useAuthSuspense } from '@/features/auth/backend/use-auth';
|
||||||
import { HelpTopic } from '@/features/help';
|
import { HelpTopic } from '@/features/help';
|
||||||
import { BadgeHelp } from '@/features/help/components/badge-help';
|
import { BadgeHelp } from '@/features/help/components/badge-help';
|
||||||
import { IconShowSidebar } from '@/features/library/components/icon-show-sidebar';
|
import { IconShowSidebar } from '@/features/library/components/icon-show-sidebar';
|
||||||
import { type OssNode } from '@/features/oss/models/oss-layout';
|
import { type OssNode } from '@/features/oss/models/oss-layout';
|
||||||
|
|
||||||
import { MiniButton } from '@/components/control';
|
import { MiniButton } from '@/components/control';
|
||||||
|
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
|
||||||
import {
|
import {
|
||||||
|
IconClustering,
|
||||||
IconConceptBlock,
|
IconConceptBlock,
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
|
IconDownload,
|
||||||
IconEdit2,
|
IconEdit2,
|
||||||
|
IconFilter,
|
||||||
IconFitImage,
|
IconFitImage,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconReset,
|
IconReset,
|
||||||
|
@ -22,7 +27,7 @@ import { type Styling } from '@/components/props';
|
||||||
import { cn } from '@/components/utils';
|
import { cn } from '@/components/utils';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { isIOS, prepareTooltip } from '@/utils/utils';
|
import { isIOS, notImplemented, prepareTooltip } from '@/utils/utils';
|
||||||
|
|
||||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||||
import { useUpdateLayout } from '../../../backend/use-update-layout';
|
import { useUpdateLayout } from '../../../backend/use-update-layout';
|
||||||
|
@ -33,8 +38,10 @@ import { useOssFlow } from './oss-flow-context';
|
||||||
import { useGetLayout } from './use-get-layout';
|
import { useGetLayout } from './use-get-layout';
|
||||||
|
|
||||||
interface ToolbarOssGraphProps extends Styling {
|
interface ToolbarOssGraphProps extends Styling {
|
||||||
onCreateOperation: () => void;
|
|
||||||
onCreateBlock: () => void;
|
onCreateBlock: () => void;
|
||||||
|
onCreateSchema: () => void;
|
||||||
|
onImportSchema: () => void;
|
||||||
|
onCreateSynthesis: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onResetPositions: () => void;
|
onResetPositions: () => void;
|
||||||
|
|
||||||
|
@ -44,8 +51,10 @@ interface ToolbarOssGraphProps extends Styling {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToolbarOssGraph({
|
export function ToolbarOssGraph({
|
||||||
onCreateOperation,
|
|
||||||
onCreateBlock,
|
onCreateBlock,
|
||||||
|
onCreateSchema,
|
||||||
|
onImportSchema,
|
||||||
|
onCreateSynthesis,
|
||||||
onDelete,
|
onDelete,
|
||||||
onResetPositions,
|
onResetPositions,
|
||||||
|
|
||||||
|
@ -58,18 +67,25 @@ export function ToolbarOssGraph({
|
||||||
const { schema, selectedItems, isMutable, canDeleteOperation: canDelete } = useOssEdit();
|
const { schema, selectedItems, isMutable, canDeleteOperation: canDelete } = useOssEdit();
|
||||||
const isProcessing = useMutatingOss();
|
const isProcessing = useMutatingOss();
|
||||||
const { resetView, nodes } = useOssFlow();
|
const { resetView, nodes } = useOssFlow();
|
||||||
const selectedOperation =
|
|
||||||
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.OPERATION ? selectedItems[0] : null;
|
|
||||||
const selectedBlock =
|
|
||||||
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK ? selectedItems[0] : null;
|
|
||||||
const getLayout = useGetLayout();
|
const getLayout = useGetLayout();
|
||||||
|
|
||||||
const { updateLayout } = useUpdateLayout();
|
const { updateLayout } = useUpdateLayout();
|
||||||
|
const { user } = useAuthSuspense();
|
||||||
|
const menu = useDropdown();
|
||||||
|
|
||||||
const showOptions = useDialogsStore(state => state.showOssOptions);
|
const showOptions = useDialogsStore(state => state.showOssOptions);
|
||||||
const showSidePanel = usePreferencesStore(state => state.showOssSidePanel);
|
const showSidePanel = usePreferencesStore(state => state.showOssSidePanel);
|
||||||
const toggleShowSidePanel = usePreferencesStore(state => state.toggleShowOssSidePanel);
|
const toggleShowSidePanel = usePreferencesStore(state => state.toggleShowOssSidePanel);
|
||||||
|
|
||||||
|
const selectedOperation =
|
||||||
|
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.OPERATION ? selectedItems[0] : null;
|
||||||
|
const selectedBlock =
|
||||||
|
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK ? selectedItems[0] : null;
|
||||||
|
|
||||||
|
function handleMenuToggle() {
|
||||||
|
hideContextMenu();
|
||||||
|
menu.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
function handleShowOptions() {
|
function handleShowOptions() {
|
||||||
showOptions();
|
showOptions();
|
||||||
}
|
}
|
||||||
|
@ -127,40 +143,80 @@ export function ToolbarOssGraph({
|
||||||
<BadgeHelp topic={HelpTopic.UI_OSS_GRAPH} contentClass='sm:max-w-160' offset={4} />
|
<BadgeHelp topic={HelpTopic.UI_OSS_GRAPH} contentClass='sm:max-w-160' offset={4} />
|
||||||
</div>
|
</div>
|
||||||
{isMutable ? (
|
{isMutable ? (
|
||||||
<div className='cc-icons'>
|
<div className='cc-icons items-start'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
|
||||||
aria-label='Сохранить изменения'
|
aria-label='Сохранить изменения'
|
||||||
|
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||||
|
hideTitle={menu.isOpen}
|
||||||
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
icon={<IconSave size='1.25rem' className='icon-primary' />}
|
||||||
onClick={handleSavePositions}
|
onClick={handleSavePositions}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Редактировать выбранную', isIOS() ? '' : 'Правый клик')}
|
|
||||||
hideTitle={isContextMenuOpen}
|
|
||||||
aria-label='Редактировать выбранную'
|
aria-label='Редактировать выбранную'
|
||||||
|
titleHtml={prepareTooltip('Редактировать выбранную', isIOS() ? '' : 'Правый клик')}
|
||||||
|
hideTitle={isContextMenuOpen || menu.isOpen}
|
||||||
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
||||||
onClick={handleEditItem}
|
onClick={handleEditItem}
|
||||||
disabled={selectedItems.length !== 1 || isProcessing}
|
disabled={selectedItems.length !== 1 || isProcessing}
|
||||||
/>
|
/>
|
||||||
|
<div ref={menu.ref} onBlur={menu.handleBlur} className='relative'>
|
||||||
|
<MiniButton
|
||||||
|
title='Добавить...'
|
||||||
|
hideTitle={menu.isOpen}
|
||||||
|
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||||
|
onClick={handleMenuToggle}
|
||||||
|
disabled={isProcessing}
|
||||||
|
/>
|
||||||
|
<Dropdown isOpen={menu.isOpen} className='-translate-x-1/2'>
|
||||||
|
<DropdownButton
|
||||||
|
text='Новый блок'
|
||||||
|
titleHtml={prepareTooltip('Новый блок', 'Alt + 1')}
|
||||||
|
icon={<IconConceptBlock size='1.25rem' className='text-constructive' />}
|
||||||
|
onClick={onCreateBlock}
|
||||||
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='Новая КС'
|
||||||
|
titleHtml={prepareTooltip('Новая концептуальная схема', 'Alt + 2')}
|
||||||
|
icon={<IconNewItem size='1.25rem' className='text-constructive' />}
|
||||||
|
onClick={onCreateSchema}
|
||||||
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='Импорт КС'
|
||||||
|
titleHtml={prepareTooltip('Импорт концептуальной схемы', 'Alt + 3')}
|
||||||
|
icon={<IconDownload size='1.25rem' className='text-constructive' />}
|
||||||
|
onClick={onImportSchema}
|
||||||
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='Синтез'
|
||||||
|
titleHtml={prepareTooltip('Синтез концептуальных схем', 'Alt + 4')}
|
||||||
|
icon={<IconConceptBlock size='1.25rem' className='text-primary' />}
|
||||||
|
onClick={onCreateSynthesis}
|
||||||
|
/>
|
||||||
|
{user.is_staff ? (
|
||||||
|
<DropdownButton
|
||||||
|
disabled
|
||||||
|
text='Фильтр'
|
||||||
|
titleHtml={prepareTooltip('Фильтрация конституент', 'Alt + 5')}
|
||||||
|
icon={<IconFilter size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={notImplemented}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{user.is_staff ? (
|
||||||
|
<DropdownButton
|
||||||
|
disabled
|
||||||
|
text='Релятивизация'
|
||||||
|
titleHtml={prepareTooltip('Релятивизация концептуальных схем', 'Alt + 6')}
|
||||||
|
icon={<IconClustering size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={notImplemented}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Новая операция', 'Ctrl + Q')}
|
|
||||||
aria-label='Новая операция'
|
|
||||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
|
||||||
onClick={onCreateOperation}
|
|
||||||
disabled={isProcessing}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
titleHtml={prepareTooltip('Новый блок', 'Ctrl + Shift + Q')}
|
|
||||||
aria-label='Новый блок'
|
|
||||||
icon={<IconConceptBlock size='1.25rem' className='icon-green' />}
|
|
||||||
onClick={onCreateBlock}
|
|
||||||
disabled={isProcessing}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MiniButton
|
|
||||||
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
|
||||||
aria-label='Удалить выбранную'
|
aria-label='Удалить выбранную'
|
||||||
|
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
||||||
|
hideTitle={menu.isOpen}
|
||||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
disabled={
|
disabled={
|
||||||
|
|
|
@ -176,6 +176,25 @@ export function sharePage() {
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show error message about not implemented function.
|
||||||
|
*/
|
||||||
|
export function notImplemented() {
|
||||||
|
toast.error('Данная функция еще не реализована');
|
||||||
|
console.error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap event handler to prevent default and stop propagation.
|
||||||
|
*/
|
||||||
|
export function withPreventDefault<T extends React.SyntheticEvent>(handler: (event: T) => void) {
|
||||||
|
return (event: T) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
handler(event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove html tags from target string.
|
* Remove html tags from target string.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue
Block a user