F: Remove unusable save to image feature
This commit is contained in:
parent
091182cf5f
commit
957313dd43
|
@ -45,7 +45,6 @@ This readme file is used mostly to document project dependencies and conventions
|
||||||
- js-file-download
|
- js-file-download
|
||||||
- use-debounce
|
- use-debounce
|
||||||
- qrcode.react
|
- qrcode.react
|
||||||
- html-to-image
|
|
||||||
- zustand
|
- zustand
|
||||||
- zod
|
- zod
|
||||||
- @hookform/resolvers
|
- @hookform/resolvers
|
||||||
|
|
5
TODO.txt
5
TODO.txt
|
@ -8,9 +8,10 @@ For more specific TODOs see comments in code
|
||||||
- Landing page
|
- Landing page
|
||||||
- Design first user experience
|
- Design first user experience
|
||||||
- Demo sandbox for anonymous users
|
- Demo sandbox for anonymous users
|
||||||
|
- Save react-flow to vector image
|
||||||
|
|
||||||
User profile:
|
User profile:
|
||||||
- Settings + settings server persistency
|
- Settings server persistency
|
||||||
- Profile pictures
|
- Profile pictures
|
||||||
- Custom LibraryItem lists
|
- Custom LibraryItem lists
|
||||||
- Custom user filters and sharing filters
|
- Custom user filters and sharing filters
|
||||||
|
@ -39,7 +40,6 @@ User profile:
|
||||||
|
|
||||||
[Tech]
|
[Tech]
|
||||||
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
|
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
|
||||||
- Testing E2E playwright
|
|
||||||
|
|
||||||
|
|
||||||
[Deployment]
|
[Deployment]
|
||||||
|
@ -60,7 +60,6 @@ Research and consider integration
|
||||||
- skeleton loading
|
- skeleton loading
|
||||||
https://react.dev/reference/react/Suspense
|
https://react.dev/reference/react/Suspense
|
||||||
|
|
||||||
- backend error message unification
|
|
||||||
- drf-messages
|
- drf-messages
|
||||||
https://drf-standardized-errors.readthedocs.io/en/latest/error_response.html
|
https://drf-standardized-errors.readthedocs.io/en/latest/error_response.html
|
||||||
|
|
||||||
|
|
7
rsconcept/frontend/package-lock.json
generated
7
rsconcept/frontend/package-lock.json
generated
|
@ -19,7 +19,6 @@
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"html-to-image": "^1.11.13",
|
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
@ -6719,12 +6718,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/html-to-image": {
|
|
||||||
"version": "1.11.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
|
|
||||||
"integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"html-to-image": "^1.11.13",
|
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
IconExecute,
|
IconExecute,
|
||||||
IconFitImage,
|
IconFitImage,
|
||||||
IconGrid,
|
IconGrid,
|
||||||
IconImage,
|
|
||||||
IconLineStraight,
|
IconLineStraight,
|
||||||
IconLineWave,
|
IconLineWave,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
@ -82,9 +81,6 @@ export function HelpOssGraph() {
|
||||||
<li>
|
<li>
|
||||||
<IconSave className='inline-icon' /> Сохранить положения
|
<IconSave className='inline-icon' /> Сохранить положения
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<IconImage className='inline-icon' /> Сохранить в SVG
|
|
||||||
</li>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider vertical margins='mx-3' className='hidden sm:block' />
|
<Divider vertical margins='mx-3' className='hidden sm:block' />
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
IconGraphInputs,
|
IconGraphInputs,
|
||||||
IconGraphMaximize,
|
IconGraphMaximize,
|
||||||
IconGraphOutputs,
|
IconGraphOutputs,
|
||||||
IconImage,
|
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconOSS,
|
IconOSS,
|
||||||
IconPredecessor,
|
IconPredecessor,
|
||||||
|
@ -85,9 +84,6 @@ export function HelpRSGraphTerm() {
|
||||||
<IconTypeGraph className='inline-icon' /> Открыть{' '}
|
<IconTypeGraph className='inline-icon' /> Открыть{' '}
|
||||||
<LinkTopic text='граф ступеней' topic={HelpTopic.UI_TYPE_GRAPH} />
|
<LinkTopic text='граф ступеней' topic={HelpTopic.UI_TYPE_GRAPH} />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<IconImage className='inline-icon' /> Сохранить в формат PNG
|
|
||||||
</li>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider vertical margins='mx-3' className='hidden sm:block' />
|
<Divider vertical margins='mx-3' className='hidden sm:block' />
|
||||||
|
|
|
@ -4,8 +4,6 @@ import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import {
|
import {
|
||||||
Background,
|
Background,
|
||||||
getNodesBounds,
|
|
||||||
getViewportForBounds,
|
|
||||||
type Node,
|
type Node,
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
|
@ -13,7 +11,6 @@ import {
|
||||||
useOnSelectionChange,
|
useOnSelectionChange,
|
||||||
useReactFlow
|
useReactFlow
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { toPng } from 'html-to-image';
|
|
||||||
|
|
||||||
import { urls, useConceptNavigation } from '@/app';
|
import { urls, useConceptNavigation } from '@/app';
|
||||||
import { useLibrary } from '@/features/library';
|
import { useLibrary } from '@/features/library';
|
||||||
|
@ -21,7 +18,6 @@ import { useLibrary } from '@/features/library';
|
||||||
import { Overlay } from '@/components/Container';
|
import { Overlay } from '@/components/Container';
|
||||||
import { useMainHeight } from '@/stores/appLayout';
|
import { useMainHeight } from '@/stores/appLayout';
|
||||||
import { useTooltipsStore } from '@/stores/tooltips';
|
import { useTooltipsStore } from '@/stores/tooltips';
|
||||||
import { APP_COLORS } from '@/styling/colors';
|
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { errorMsg } from '@/utils/labels';
|
import { errorMsg } from '@/utils/labels';
|
||||||
|
|
||||||
|
@ -203,39 +199,6 @@ export function OssFlow() {
|
||||||
promptRelocateConstituents(target, getPositions());
|
promptRelocateConstituents(target, getPositions());
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSaveImage() {
|
|
||||||
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
|
|
||||||
if (canvas === null) {
|
|
||||||
toast.error(errorMsg.imageFailed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageWidth = PARAMETER.ossImageWidth;
|
|
||||||
const imageHeight = PARAMETER.ossImageHeight;
|
|
||||||
const nodesBounds = getNodesBounds(nodes);
|
|
||||||
const viewport = getViewportForBounds(nodesBounds, imageWidth, imageHeight, ZOOM_MIN, ZOOM_MAX);
|
|
||||||
toPng(canvas, {
|
|
||||||
backgroundColor: APP_COLORS.bgDefault,
|
|
||||||
width: imageWidth,
|
|
||||||
height: imageHeight,
|
|
||||||
style: {
|
|
||||||
width: String(imageWidth),
|
|
||||||
height: String(imageHeight),
|
|
||||||
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom})`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(dataURL => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.setAttribute('download', `${schema.alias}.png`);
|
|
||||||
a.setAttribute('href', dataURL);
|
|
||||||
a.click();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(errorMsg.imageFailed);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleContextMenu(event: React.MouseEvent<Element>, node: OssNode) {
|
function handleContextMenu(event: React.MouseEvent<Element>, node: OssNode) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -306,7 +269,6 @@ export function OssFlow() {
|
||||||
onExecute={handleExecuteSelected}
|
onExecute={handleExecuteSelected}
|
||||||
onResetPositions={() => setToggleReset(prev => !prev)}
|
onResetPositions={() => setToggleReset(prev => !prev)}
|
||||||
onSavePositions={handleSavePositions}
|
onSavePositions={handleSavePositions}
|
||||||
onSaveImage={handleSaveImage}
|
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
{menuProps ? (
|
{menuProps ? (
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
IconExecute,
|
IconExecute,
|
||||||
IconFitImage,
|
IconFitImage,
|
||||||
IconGrid,
|
IconGrid,
|
||||||
IconImage,
|
|
||||||
IconLineStraight,
|
IconLineStraight,
|
||||||
IconLineWave,
|
IconLineWave,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
@ -34,7 +33,6 @@ interface ToolbarOssGraphProps {
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onExecute: () => void;
|
onExecute: () => void;
|
||||||
onFitView: () => void;
|
onFitView: () => void;
|
||||||
onSaveImage: () => void;
|
|
||||||
onSavePositions: () => void;
|
onSavePositions: () => void;
|
||||||
onResetPositions: () => void;
|
onResetPositions: () => void;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +43,6 @@ export function ToolbarOssGraph({
|
||||||
onEdit,
|
onEdit,
|
||||||
onExecute,
|
onExecute,
|
||||||
onFitView,
|
onFitView,
|
||||||
onSaveImage,
|
|
||||||
onSavePositions,
|
onSavePositions,
|
||||||
onResetPositions
|
onResetPositions
|
||||||
}: ToolbarOssGraphProps) {
|
}: ToolbarOssGraphProps) {
|
||||||
|
@ -127,11 +124,6 @@ export function ToolbarOssGraph({
|
||||||
}
|
}
|
||||||
onClick={toggleEdgeAnimate}
|
onClick={toggleEdgeAnimate}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
|
||||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
|
||||||
title='Сохранить изображение'
|
|
||||||
onClick={onSaveImage}
|
|
||||||
/>
|
|
||||||
<BadgeHelp
|
<BadgeHelp
|
||||||
topic={HelpTopic.UI_OSS_GRAPH}
|
topic={HelpTopic.UI_OSS_GRAPH}
|
||||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { toast } from 'react-toastify';
|
import { useReactFlow } from 'reactflow';
|
||||||
import { getNodesBounds, getViewportForBounds, useReactFlow } from 'reactflow';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { toPng } from 'html-to-image';
|
|
||||||
|
|
||||||
import { BadgeHelp, HelpTopic } from '@/features/help';
|
import { BadgeHelp, HelpTopic } from '@/features/help';
|
||||||
import { MiniSelectorOSS } from '@/features/library';
|
import { MiniSelectorOSS } from '@/features/library';
|
||||||
|
@ -15,26 +13,21 @@ import {
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconFitImage,
|
IconFitImage,
|
||||||
IconImage,
|
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconText,
|
IconText,
|
||||||
IconTextOff,
|
IconTextOff,
|
||||||
IconTypeGraph
|
IconTypeGraph
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
|
||||||
import { APP_COLORS } from '@/styling/colors';
|
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { errorMsg } from '@/utils/labels';
|
|
||||||
|
|
||||||
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
|
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
|
||||||
import { useRSEdit } from '../RSEditContext';
|
import { useRSEdit } from '../RSEditContext';
|
||||||
|
|
||||||
import { VIEW_PADDING, ZOOM_MAX, ZOOM_MIN } from './TGFlow';
|
import { VIEW_PADDING } from './TGFlow';
|
||||||
|
|
||||||
export function ToolbarTermGraph() {
|
export function ToolbarTermGraph() {
|
||||||
const isProcessing = useMutatingRSForm();
|
const isProcessing = useMutatingRSForm();
|
||||||
const darkMode = usePreferencesStore(state => state.darkMode);
|
|
||||||
const {
|
const {
|
||||||
schema, //
|
schema, //
|
||||||
selected,
|
selected,
|
||||||
|
@ -49,7 +42,7 @@ export function ToolbarTermGraph() {
|
||||||
const filter = useTermGraphStore(state => state.filter);
|
const filter = useTermGraphStore(state => state.filter);
|
||||||
const setFilter = useTermGraphStore(state => state.setFilter);
|
const setFilter = useTermGraphStore(state => state.setFilter);
|
||||||
|
|
||||||
const { fitView, getNodes } = useReactFlow();
|
const { fitView } = useReactFlow();
|
||||||
|
|
||||||
function handleShowTypeGraph() {
|
function handleShowTypeGraph() {
|
||||||
const typeInfo = schema.items.map(item => ({
|
const typeInfo = schema.items.map(item => ({
|
||||||
|
@ -79,39 +72,6 @@ export function ToolbarTermGraph() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSaveImage() {
|
|
||||||
const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
|
|
||||||
if (canvas === null) {
|
|
||||||
toast.error(errorMsg.imageFailed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageWidth = PARAMETER.ossImageWidth;
|
|
||||||
const imageHeight = PARAMETER.ossImageHeight;
|
|
||||||
const nodesBounds = getNodesBounds(getNodes());
|
|
||||||
const viewport = getViewportForBounds(nodesBounds, imageWidth, imageHeight, ZOOM_MIN, ZOOM_MAX);
|
|
||||||
toPng(canvas, {
|
|
||||||
backgroundColor: darkMode ? APP_COLORS.bgDefaultDark : APP_COLORS.bgDefaultLight,
|
|
||||||
width: imageWidth,
|
|
||||||
height: imageHeight,
|
|
||||||
style: {
|
|
||||||
width: String(imageWidth),
|
|
||||||
height: String(imageHeight),
|
|
||||||
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom * 2})`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(dataURL => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.setAttribute('download', `${schema.alias}.png`);
|
|
||||||
a.setAttribute('href', dataURL);
|
|
||||||
a.click();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(errorMsg.imageFailed);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFitView() {
|
function handleFitView() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
|
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
|
||||||
|
@ -186,11 +146,6 @@ export function ToolbarTermGraph() {
|
||||||
title='Граф ступеней'
|
title='Граф ступеней'
|
||||||
onClick={handleShowTypeGraph}
|
onClick={handleShowTypeGraph}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
|
||||||
icon={<IconImage size='1.25rem' className='icon-primary' />}
|
|
||||||
title='Сохранить изображение'
|
|
||||||
onClick={handleSaveImage}
|
|
||||||
/>
|
|
||||||
<BadgeHelp
|
<BadgeHelp
|
||||||
topic={HelpTopic.UI_GRAPH_TERM}
|
topic={HelpTopic.UI_GRAPH_TERM}
|
||||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
/** Semantic colors for application. */
|
/** Semantic colors for application. */
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export const APP_COLORS = {
|
export const APP_COLORS = {
|
||||||
bgDefaultLight: '#fafafa',
|
|
||||||
bgDefaultDark: '#171717',
|
|
||||||
bgDefault: 'var(--clr-prim-100)',
|
bgDefault: 'var(--clr-prim-100)',
|
||||||
bgInput: 'var(--clr-prim-0)',
|
bgInput: 'var(--clr-prim-0)',
|
||||||
bgControls: 'var(--clr-prim-200)',
|
bgControls: 'var(--clr-prim-200)',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user