feat: add zoom handler

This commit is contained in:
fantasticit 2023-09-03 12:46:58 +08:00
parent 53c37ae052
commit e7259db747
5 changed files with 117 additions and 7 deletions

View File

@ -30,4 +30,16 @@
align-items: center; align-items: center;
} }
} }
.handlerWrap {
position: absolute;
right: 10px;
bottom: 10px;
z-index: 2;
padding: 2px 4px;
background-color: var(--semi-color-bg-2);
border: 1px solid var(--node-border-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
} }

View File

@ -2,15 +2,17 @@ import React from 'react';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import VisibilitySensor from 'react-visibility-sensor'; import VisibilitySensor from 'react-visibility-sensor';
import { Space, Spin, Typography } from '@douyinfe/semi-ui'; import { Button, Space, Spin, Typography } from '@douyinfe/semi-ui';
import { NodeViewWrapper } from '@tiptap/react'; import { NodeViewWrapper } from '@tiptap/react';
import { Excalidraw } from 'tiptap/core/extensions/excalidraw'; import { Excalidraw } from 'tiptap/core/extensions/excalidraw';
import { getEditorContainerDOMSize } from 'tiptap/prose-utils'; import { MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from 'tiptap/core/menus/mind/constant';
import { clamp, getEditorContainerDOMSize } from 'tiptap/prose-utils';
import cls from 'classnames'; import cls from 'classnames';
import { IconMind } from 'components/icons'; import { IconMind, IconZoomIn, IconZoomOut } from 'components/icons';
import { Resizeable } from 'components/resizeable'; import { Resizeable } from 'components/resizeable';
import { Tooltip } from 'components/tooltip';
import deepEqual from 'deep-equal'; import deepEqual from 'deep-equal';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
@ -30,6 +32,7 @@ export const _ExcalidrawWrapper = ({ editor, node, updateAttributes }) => {
const [loading, toggleLoading] = useToggle(true); const [loading, toggleLoading] = useToggle(true);
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
const [visible, toggleVisible] = useToggle(false); const [visible, toggleVisible] = useToggle(false);
const [zoom, setZoomState] = useState(100);
const onResize = useCallback( const onResize = useCallback(
(size) => { (size) => {
@ -47,6 +50,14 @@ export const _ExcalidrawWrapper = ({ editor, node, updateAttributes }) => {
[toggleVisible] [toggleVisible]
); );
const setZoom = useCallback((type: 'minus' | 'plus') => {
return () => {
setZoomState((currentZoom) =>
clamp(type === 'minus' ? currentZoom - ZOOM_STEP : currentZoom + ZOOM_STEP, MIN_ZOOM, MAX_ZOOM)
);
};
}, []);
useEffect(() => { useEffect(() => {
let isUnmount = false; let isUnmount = false;
@ -114,6 +125,8 @@ export const _ExcalidrawWrapper = ({ editor, node, updateAttributes }) => {
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
transform: `scale(${zoom / 100})`,
transition: `all ease-in-out .3s`,
}} }}
dangerouslySetInnerHTML={{ __html: Svg?.outerHTML ?? '' }} dangerouslySetInnerHTML={{ __html: Svg?.outerHTML ?? '' }}
/> />
@ -127,6 +140,27 @@ export const _ExcalidrawWrapper = ({ editor, node, updateAttributes }) => {
</Space> </Space>
</div> </div>
<div className={styles.handlerWrap}>
<Tooltip content="缩小">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomOut />}
onClick={setZoom('minus')}
/>
</Tooltip>
<Tooltip content="放大">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomIn />}
onClick={setZoom('plus')}
/>
</Tooltip>
</div>
</div> </div>
</Resizeable> </Resizeable>
</VisibilitySensor> </VisibilitySensor>

View File

@ -1,9 +1,23 @@
.wrap { .wrap {
position: relative;
display: flex; display: flex;
height: 100%;
padding: 8px 16px; padding: 8px 16px;
cursor: pointer; cursor: pointer;
border: 1px solid var(--node-border-color); border: 1px solid var(--node-border-color);
border-radius: var(--border-radius); border-radius: var(--border-radius);
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.handlerWrap {
position: absolute;
right: 10px;
bottom: 10px;
z-index: 2;
padding: 2px 4px;
background-color: var(--semi-color-bg-2);
border: 1px solid var(--node-border-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
} }

View File

@ -1,17 +1,21 @@
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component'; import { LazyLoadImage } from 'react-lazy-load-image-component';
import { Spin, Typography } from '@douyinfe/semi-ui'; import { Button, Spin, Typography } from '@douyinfe/semi-ui';
import { NodeViewWrapper } from '@tiptap/react'; import { NodeViewWrapper } from '@tiptap/react';
import { MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from 'tiptap/core/menus/mind/constant';
import { import {
clamp,
extractFileExtension, extractFileExtension,
extractFilename, extractFilename,
getEditorContainerDOMSize, getEditorContainerDOMSize,
getImageWidthHeight, getImageWidthHeight,
} from 'tiptap/prose-utils'; } from 'tiptap/prose-utils';
import { IconZoomIn, IconZoomOut } from 'components/icons';
import { Resizeable } from 'components/resizeable'; import { Resizeable } from 'components/resizeable';
import { Tooltip } from 'components/tooltip';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { uploadFile } from 'services/file'; import { uploadFile } from 'services/file';
@ -25,6 +29,7 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
const { width: maxWidth } = getEditorContainerDOMSize(editor); const { width: maxWidth } = getEditorContainerDOMSize(editor);
const $upload = useRef<HTMLInputElement>(); const $upload = useRef<HTMLInputElement>();
const [loading, toggleLoading] = useToggle(false); const [loading, toggleLoading] = useToggle(false);
const [zoom, setZoomState] = useState(100);
const onResize = useCallback( const onResize = useCallback(
(size) => { (size) => {
@ -64,6 +69,14 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
[updateAttributes, toggleLoading] [updateAttributes, toggleLoading]
); );
const setZoom = useCallback((type: 'minus' | 'plus') => {
return () => {
setZoomState((currentZoom) =>
clamp(type === 'minus' ? currentZoom - ZOOM_STEP : currentZoom + ZOOM_STEP, MIN_ZOOM, MAX_ZOOM)
);
};
}, []);
useEffect(() => { useEffect(() => {
if (!src && !hasTrigger) { if (!src && !hasTrigger) {
selectFile(); selectFile();
@ -93,7 +106,44 @@ export const ImageWrapper = ({ editor, node, updateAttributes }) => {
</Spin> </Spin>
</div> </div>
) : ( ) : (
<LazyLoadImage src={src} alt={alt} width={'100%'} height={'100%'} /> <div className={styles.wrap}>
<div
style={{
height: '100%',
maxHeight: '100%',
padding: 24,
overflow: 'hidden',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
transform: `scale(${zoom / 100})`,
transition: `all ease-in-out .3s`,
}}
>
<LazyLoadImage src={src} alt={alt} width={'100%'} height={'100%'} />
</div>
<div className={styles.handlerWrap}>
<Tooltip content="缩小">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomOut />}
onClick={setZoom('minus')}
/>
</Tooltip>
<Tooltip content="放大">
<Button
size="small"
theme="borderless"
type="tertiary"
icon={<IconZoomIn />}
onClick={setZoom('plus')}
/>
</Tooltip>
</div>
</div>
)} )}
</Resizeable> </Resizeable>
</NodeViewWrapper> </NodeViewWrapper>

View File

@ -10,7 +10,7 @@ export class UpdateWikiDto {
@IsString({ message: '知识库描述类型错误正确类型为String' }) @IsString({ message: '知识库描述类型错误正确类型为String' })
@IsNotEmpty({ message: '知识库描述不能为空' }) @IsNotEmpty({ message: '知识库描述不能为空' })
@MinLength(3, { message: '知识库描述至少3个字符' }) @MinLength(1, { message: '知识库描述至少1个字符' })
@IsOptional() @IsOptional()
description: string; description: string;