Add image save function

This commit is contained in:
IRBorisov 2024-04-07 21:31:38 +03:00
parent 69a3cdc7aa
commit ac85bfdba7
4 changed files with 39 additions and 1 deletions

View File

@ -25,6 +25,7 @@ export { FiBellOff as IconFollowOff } from 'react-icons/fi';
export { LuChevronDown as IconDropArrow } from 'react-icons/lu';
export { LuChevronUp as IconDropArrowUp } from 'react-icons/lu';
export { LuImage as IconImage } from 'react-icons/lu';
export { TbColumns as IconList } from 'react-icons/tb';
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
export { BiFontFamily as IconText } from 'react-icons/bi';

View File

@ -2,6 +2,7 @@
import clsx from 'clsx';
import { AnimatePresence } from 'framer-motion';
import fileDownload from 'js-file-download';
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import InfoConstituenta from '@/components/info/InfoConstituenta';
@ -16,6 +17,7 @@ import { applyNodeSizing } from '@/models/miscellaneousAPI';
import { ConstituentaID, CstType } from '@/models/rsform';
import { colorBgGraphNode } from '@/styling/color';
import { PARAMETER, storage } from '@/utils/constants';
import { convertBase64ToBlob } from '@/utils/utils';
import { useRSEdit } from '../RSEditContext';
import GraphSelectors from './GraphSelectors';
@ -149,6 +151,18 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
[setFilterParams]
);
const handleSaveImage = useCallback(() => {
if (!graphRef?.current) {
return;
}
const data = graphRef.current.exportCanvas();
try {
fileDownload(convertBase64ToBlob(data), 'graph.png', 'data:image/png;base64');
} catch (error) {
console.error(error);
}
}, [graphRef]);
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
// Hotkeys implementation
if (!controller.isContentEditable || controller.isProcessing) {
@ -235,6 +249,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
onCreate={handleCreateCst}
onDelete={handleDeleteCst}
onResetViewpoint={() => setToggleResetView(prev => !prev)}
onSaveImage={handleSaveImage}
toggleOrbit={() => setOrbit(prev => !prev)}
toggleFoldDerived={handleFoldDerived}
toggleNoText={() =>

View File

@ -6,6 +6,7 @@ import {
IconDestroy,
IconFilter,
IconFitImage,
IconImage,
IconNewItem,
IconRotate3D,
IconText,
@ -31,6 +32,7 @@ interface GraphToolbarProps {
onCreate: () => void;
onDelete: () => void;
onResetViewpoint: () => void;
onSaveImage: () => void;
toggleFoldDerived: () => void;
toggleNoText: () => void;
@ -48,7 +50,8 @@ function GraphToolbar({
showParamsDialog,
onCreate,
onDelete,
onResetViewpoint
onResetViewpoint,
onSaveImage
}: GraphToolbarProps) {
const controller = useRSEdit();
@ -112,6 +115,11 @@ function GraphToolbar({
onClick={onDelete}
/>
) : null}
<MiniButton
icon={<IconImage size='1.25rem' className='icon-primary' />}
title='Сохранить изображение'
onClick={onSaveImage}
/>
<BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} />
</div>
<SelectGraphToolbar

View File

@ -81,3 +81,17 @@ export function isResponseHtml(response?: AxiosResponse) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return header.includes('text/html');
}
/**
* Convert base64 string to Blob uint8.
*/
export function convertBase64ToBlob(base64String: string): Uint8Array {
const arr = base64String.split(',');
const bstr = atob(arr[1]);
let n = bstr.length;
const uint8Array = new Uint8Array(n);
while (n--) {
uint8Array[n] = bstr.charCodeAt(n);
}
return uint8Array;
}