F: Remove unusable save to image feature

This commit is contained in:
Ivan 2025-02-25 22:15:02 +03:00
parent 0fec8f9d61
commit de60e6f77b
10 changed files with 5 additions and 116 deletions

View File

@ -45,7 +45,6 @@ This readme file is used mostly to document project dependencies and conventions
- js-file-download
- use-debounce
- qrcode.react
- html-to-image
- zustand
- zod
- @hookform/resolvers

View File

@ -8,9 +8,10 @@ For more specific TODOs see comments in code
- Landing page
- Design first user experience
- Demo sandbox for anonymous users
- Save react-flow to vector image
User profile:
- Settings + settings server persistency
- Settings server persistency
- Profile pictures
- Custom LibraryItem lists
- Custom user filters and sharing filters
@ -39,7 +40,6 @@ User profile:
[Tech]
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
- Testing E2E playwright
[Deployment]
@ -60,7 +60,6 @@ Research and consider integration
- skeleton loading
https://react.dev/reference/react/Suspense
- backend error message unification
- drf-messages
https://drf-standardized-errors.readthedocs.io/en/latest/error_response.html

View File

@ -19,7 +19,6 @@
"axios": "^1.7.9",
"clsx": "^2.1.1",
"global": "^4.4.0",
"html-to-image": "^1.11.13",
"js-file-download": "^0.4.12",
"qrcode.react": "^4.2.0",
"react": "^19.0.0",
@ -6719,12 +6718,6 @@
"dev": true,
"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": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",

View File

@ -24,7 +24,6 @@
"axios": "^1.7.9",
"clsx": "^2.1.1",
"global": "^4.4.0",
"html-to-image": "^1.11.13",
"js-file-download": "^0.4.12",
"qrcode.react": "^4.2.0",
"react": "^19.0.0",

View File

@ -10,7 +10,6 @@ import {
IconExecute,
IconFitImage,
IconGrid,
IconImage,
IconLineStraight,
IconLineWave,
IconNewItem,
@ -82,9 +81,6 @@ export function HelpOssGraph() {
<li>
<IconSave className='inline-icon' /> Сохранить положения
</li>
<li>
<IconImage className='inline-icon' /> Сохранить в SVG
</li>
</div>
<Divider vertical margins='mx-3' className='hidden sm:block' />

View File

@ -11,7 +11,6 @@ import {
IconGraphInputs,
IconGraphMaximize,
IconGraphOutputs,
IconImage,
IconNewItem,
IconOSS,
IconPredecessor,
@ -85,9 +84,6 @@ export function HelpRSGraphTerm() {
<IconTypeGraph className='inline-icon' /> Открыть{' '}
<LinkTopic text='граф ступеней' topic={HelpTopic.UI_TYPE_GRAPH} />
</li>
<li>
<IconImage className='inline-icon' /> Сохранить в формат PNG
</li>
</div>
<Divider vertical margins='mx-3' className='hidden sm:block' />

View File

@ -4,8 +4,6 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import {
Background,
getNodesBounds,
getViewportForBounds,
type Node,
ReactFlow,
useEdgesState,
@ -13,7 +11,6 @@ import {
useOnSelectionChange,
useReactFlow
} from 'reactflow';
import { toPng } from 'html-to-image';
import { urls, useConceptNavigation } from '@/app';
import { useLibrary } from '@/features/library';
@ -21,7 +18,6 @@ import { useLibrary } from '@/features/library';
import { Overlay } from '@/components/Container';
import { useMainHeight } from '@/stores/appLayout';
import { useTooltipsStore } from '@/stores/tooltips';
import { APP_COLORS } from '@/styling/colors';
import { PARAMETER } from '@/utils/constants';
import { errorMsg } from '@/utils/labels';
@ -203,39 +199,6 @@ export function OssFlow() {
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) {
event.preventDefault();
event.stopPropagation();
@ -306,7 +269,6 @@ export function OssFlow() {
onExecute={handleExecuteSelected}
onResetPositions={() => setToggleReset(prev => !prev)}
onSavePositions={handleSavePositions}
onSaveImage={handleSaveImage}
/>
</Overlay>
{menuProps ? (

View File

@ -13,7 +13,6 @@ import {
IconExecute,
IconFitImage,
IconGrid,
IconImage,
IconLineStraight,
IconLineWave,
IconNewItem,
@ -34,7 +33,6 @@ interface ToolbarOssGraphProps {
onEdit: () => void;
onExecute: () => void;
onFitView: () => void;
onSaveImage: () => void;
onSavePositions: () => void;
onResetPositions: () => void;
}
@ -45,7 +43,6 @@ export function ToolbarOssGraph({
onEdit,
onExecute,
onFitView,
onSaveImage,
onSavePositions,
onResetPositions
}: ToolbarOssGraphProps) {
@ -127,11 +124,6 @@ export function ToolbarOssGraph({
}
onClick={toggleEdgeAnimate}
/>
<MiniButton
icon={<IconImage size='1.25rem' className='icon-primary' />}
title='Сохранить изображение'
onClick={onSaveImage}
/>
<BadgeHelp
topic={HelpTopic.UI_OSS_GRAPH}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}

View File

@ -1,7 +1,5 @@
import { toast } from 'react-toastify';
import { getNodesBounds, getViewportForBounds, useReactFlow } from 'reactflow';
import { useReactFlow } from 'reactflow';
import clsx from 'clsx';
import { toPng } from 'html-to-image';
import { BadgeHelp, HelpTopic } from '@/features/help';
import { MiniSelectorOSS } from '@/features/library';
@ -15,26 +13,21 @@ import {
IconDestroy,
IconFilter,
IconFitImage,
IconImage,
IconNewItem,
IconText,
IconTextOff,
IconTypeGraph
} from '@/components/Icons';
import { useDialogsStore } from '@/stores/dialogs';
import { usePreferencesStore } from '@/stores/preferences';
import { APP_COLORS } from '@/styling/colors';
import { PARAMETER } from '@/utils/constants';
import { errorMsg } from '@/utils/labels';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
import { useRSEdit } from '../RSEditContext';
import { VIEW_PADDING, ZOOM_MAX, ZOOM_MIN } from './TGFlow';
import { VIEW_PADDING } from './TGFlow';
export function ToolbarTermGraph() {
const isProcessing = useMutatingRSForm();
const darkMode = usePreferencesStore(state => state.darkMode);
const {
schema, //
selected,
@ -49,7 +42,7 @@ export function ToolbarTermGraph() {
const filter = useTermGraphStore(state => state.filter);
const setFilter = useTermGraphStore(state => state.setFilter);
const { fitView, getNodes } = useReactFlow();
const { fitView } = useReactFlow();
function handleShowTypeGraph() {
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() {
setTimeout(() => {
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
@ -186,11 +146,6 @@ export function ToolbarTermGraph() {
title='Граф ступеней'
onClick={handleShowTypeGraph}
/>
<MiniButton
icon={<IconImage size='1.25rem' className='icon-primary' />}
title='Сохранить изображение'
onClick={handleSaveImage}
/>
<BadgeHelp
topic={HelpTopic.UI_GRAPH_TERM}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}

View File

@ -5,8 +5,6 @@
/** Semantic colors for application. */
// prettier-ignore
export const APP_COLORS = {
bgDefaultLight: '#fafafa',
bgDefaultDark: '#171717',
bgDefault: 'var(--clr-prim-100)',
bgInput: 'var(--clr-prim-0)',
bgControls: 'var(--clr-prim-200)',