mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Add image save function
This commit is contained in:
parent
69a3cdc7aa
commit
ac85bfdba7
|
@ -25,6 +25,7 @@ export { FiBellOff as IconFollowOff } from 'react-icons/fi';
|
||||||
export { LuChevronDown as IconDropArrow } from 'react-icons/lu';
|
export { LuChevronDown as IconDropArrow } from 'react-icons/lu';
|
||||||
export { LuChevronUp as IconDropArrowUp } 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 { TbColumns as IconList } from 'react-icons/tb';
|
||||||
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
|
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
|
||||||
export { BiFontFamily as IconText } from 'react-icons/bi';
|
export { BiFontFamily as IconText } from 'react-icons/bi';
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
import fileDownload from 'js-file-download';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import InfoConstituenta from '@/components/info/InfoConstituenta';
|
import InfoConstituenta from '@/components/info/InfoConstituenta';
|
||||||
|
@ -16,6 +17,7 @@ import { applyNodeSizing } from '@/models/miscellaneousAPI';
|
||||||
import { ConstituentaID, CstType } from '@/models/rsform';
|
import { ConstituentaID, CstType } from '@/models/rsform';
|
||||||
import { colorBgGraphNode } from '@/styling/color';
|
import { colorBgGraphNode } from '@/styling/color';
|
||||||
import { PARAMETER, storage } from '@/utils/constants';
|
import { PARAMETER, storage } from '@/utils/constants';
|
||||||
|
import { convertBase64ToBlob } from '@/utils/utils';
|
||||||
|
|
||||||
import { useRSEdit } from '../RSEditContext';
|
import { useRSEdit } from '../RSEditContext';
|
||||||
import GraphSelectors from './GraphSelectors';
|
import GraphSelectors from './GraphSelectors';
|
||||||
|
@ -149,6 +151,18 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
[setFilterParams]
|
[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>) {
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
// Hotkeys implementation
|
// Hotkeys implementation
|
||||||
if (!controller.isContentEditable || controller.isProcessing) {
|
if (!controller.isContentEditable || controller.isProcessing) {
|
||||||
|
@ -235,6 +249,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
||||||
onCreate={handleCreateCst}
|
onCreate={handleCreateCst}
|
||||||
onDelete={handleDeleteCst}
|
onDelete={handleDeleteCst}
|
||||||
onResetViewpoint={() => setToggleResetView(prev => !prev)}
|
onResetViewpoint={() => setToggleResetView(prev => !prev)}
|
||||||
|
onSaveImage={handleSaveImage}
|
||||||
toggleOrbit={() => setOrbit(prev => !prev)}
|
toggleOrbit={() => setOrbit(prev => !prev)}
|
||||||
toggleFoldDerived={handleFoldDerived}
|
toggleFoldDerived={handleFoldDerived}
|
||||||
toggleNoText={() =>
|
toggleNoText={() =>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconFitImage,
|
IconFitImage,
|
||||||
|
IconImage,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconRotate3D,
|
IconRotate3D,
|
||||||
IconText,
|
IconText,
|
||||||
|
@ -31,6 +32,7 @@ interface GraphToolbarProps {
|
||||||
onCreate: () => void;
|
onCreate: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onResetViewpoint: () => void;
|
onResetViewpoint: () => void;
|
||||||
|
onSaveImage: () => void;
|
||||||
|
|
||||||
toggleFoldDerived: () => void;
|
toggleFoldDerived: () => void;
|
||||||
toggleNoText: () => void;
|
toggleNoText: () => void;
|
||||||
|
@ -48,7 +50,8 @@ function GraphToolbar({
|
||||||
showParamsDialog,
|
showParamsDialog,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onResetViewpoint
|
onResetViewpoint,
|
||||||
|
onSaveImage
|
||||||
}: GraphToolbarProps) {
|
}: GraphToolbarProps) {
|
||||||
const controller = useRSEdit();
|
const controller = useRSEdit();
|
||||||
|
|
||||||
|
@ -112,6 +115,11 @@ function GraphToolbar({
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : 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} />
|
<BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} />
|
||||||
</div>
|
</div>
|
||||||
<SelectGraphToolbar
|
<SelectGraphToolbar
|
||||||
|
|
|
@ -81,3 +81,17 @@ export function isResponseHtml(response?: AxiosResponse) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
|
||||||
return header.includes('text/html');
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user