generated from template/vite-react-template
temp: fix bugs
This commit is contained in:
@@ -1,24 +1,41 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { ToolbarItem, MenuItem } from './toolbar/Toolbar';
|
||||
import { ClipboardPaste } from 'lucide-react';
|
||||
import { ClipboardPaste, Copy } from 'lucide-react';
|
||||
import { clipboardRead } from '../hooks/listen-copy';
|
||||
import { useReactFlow, useStore } from '@xyflow/react';
|
||||
import { randomId } from '../utils/random';
|
||||
import { message } from '@/modules/message';
|
||||
import { useWallStore } from '../store/wall';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { min, max } from 'lodash-es';
|
||||
import { getImageWidthHeightByBase64 } from '../utils/get-image-rect';
|
||||
import { getImageWidthHeightByBase64, getTextWidthHeight } from '../utils/get-image-rect';
|
||||
interface ContextMenuProps {
|
||||
x: number;
|
||||
y: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
type NewNodeData = {
|
||||
id: string;
|
||||
type: 'wallnote';
|
||||
position: { x: number; y: number };
|
||||
data: {
|
||||
width: number;
|
||||
height: number;
|
||||
html: string;
|
||||
dataType?: string;
|
||||
};
|
||||
};
|
||||
class HasTypeCheck {
|
||||
constructor(list: any[]) {
|
||||
newNodeData: NewNodeData;
|
||||
constructor(list: any[], position: { x: number; y: number }) {
|
||||
this.list = list;
|
||||
this.newNodeData = {
|
||||
id: randomId(),
|
||||
type: 'wallnote',
|
||||
position,
|
||||
data: { width: 0, height: 0, html: '' },
|
||||
};
|
||||
}
|
||||
list: { type?: string; data: any }[];
|
||||
list: { type?: string; data: any; base64?: string }[];
|
||||
hasType = (type = 'type/html') => {
|
||||
return this.list.some((item) => item.type === type);
|
||||
};
|
||||
@@ -30,14 +47,20 @@ class HasTypeCheck {
|
||||
if (hasHtml) {
|
||||
return {
|
||||
code: 200,
|
||||
data: this.getType('text/html')?.data || '',
|
||||
data: {
|
||||
html: this.getType('text/html')?.data || '',
|
||||
dataType: 'text/html',
|
||||
},
|
||||
};
|
||||
}
|
||||
const hasText = this.hasType('text/plain');
|
||||
if (hasText) {
|
||||
return {
|
||||
code: 200,
|
||||
data: this.getType('text/plain')?.data || '',
|
||||
data: {
|
||||
html: this.getType('text/plain')?.data || '',
|
||||
dataType: 'text/plain',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -57,6 +80,53 @@ class HasTypeCheck {
|
||||
code: 404,
|
||||
};
|
||||
}
|
||||
async getData() {
|
||||
const json = this.getJson();
|
||||
if (json.code === 200) {
|
||||
if (json.data.type === 'wallnote') {
|
||||
const { selected, ...rest } = json.data;
|
||||
const newNodeData = {
|
||||
...this.newNodeData,
|
||||
...rest,
|
||||
id: this.newNodeData.id,
|
||||
position: this.newNodeData.position,
|
||||
};
|
||||
this.newNodeData = newNodeData;
|
||||
return this.newNodeData;
|
||||
} else {
|
||||
this.newNodeData.data.html = JSON.stringify(json.data, null, 2);
|
||||
return this.newNodeData;
|
||||
}
|
||||
}
|
||||
|
||||
const text = this.getText();
|
||||
if (text.code === 200) {
|
||||
const { html, dataType } = text.data || { html: '', dataType: 'text/html' };
|
||||
this.newNodeData.data.html = html;
|
||||
let maxWidth = 600;
|
||||
let fontSize = 16;
|
||||
let maxHeight = 400;
|
||||
let minHeight = 100;
|
||||
if (dataType === 'text/html') {
|
||||
maxWidth = 400;
|
||||
fontSize = 10;
|
||||
maxHeight = 200;
|
||||
minHeight = 50;
|
||||
}
|
||||
const wh = await getTextWidthHeight({ str: html, width: 400, maxHeight, minHeight, fontSize });
|
||||
this.newNodeData.data.width = wh.width;
|
||||
this.newNodeData.data.height = wh.height;
|
||||
return this.newNodeData;
|
||||
}
|
||||
// 图片
|
||||
const { base64, type } = this.list[0];
|
||||
const rect = await getImageWidthHeightByBase64(base64);
|
||||
this.newNodeData.data.width = rect.width;
|
||||
this.newNodeData.data.height = rect.height;
|
||||
this.newNodeData.data.dataType = type;
|
||||
this.newNodeData.data.html = `<img src="${base64}" alt="图片" />`;
|
||||
return this.newNodeData;
|
||||
}
|
||||
}
|
||||
export const ContextMenu: React.FC<ContextMenuProps> = ({ x, y, onClose }) => {
|
||||
const reactFlowInstance = useReactFlow();
|
||||
@@ -69,71 +139,53 @@ export const ContextMenu: React.FC<ContextMenuProps> = ({ x, y, onClose }) => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
// const
|
||||
const menuList: MenuItem[] = [
|
||||
{
|
||||
label: '粘贴',
|
||||
icon: <ClipboardPaste />,
|
||||
key: 'paste',
|
||||
onClick: async () => {
|
||||
const readList = await clipboardRead();
|
||||
const check = new HasTypeCheck(readList);
|
||||
if (readList.length <= 0) {
|
||||
message.error('粘贴为空');
|
||||
return;
|
||||
}
|
||||
let content: string = '';
|
||||
let hasContent = false;
|
||||
const text = check.getText();
|
||||
let width = 100;
|
||||
let height = 100;
|
||||
if (text.code === 200) {
|
||||
content = text.data;
|
||||
hasContent = true;
|
||||
width = min([content.length * 16, 600])!;
|
||||
height = max([200, (content.length * 16) / 400])!;
|
||||
}
|
||||
console.log('result', readList);
|
||||
if (!hasContent) {
|
||||
const json = check.getJson();
|
||||
if (json.code === 200) {
|
||||
content = JSON.stringify(json.data, null, 2);
|
||||
hasContent = true;
|
||||
}
|
||||
}
|
||||
let noEdit = false;
|
||||
if (!hasContent) {
|
||||
content = readList[0].data || '';
|
||||
const base64 = readList[0].base64;
|
||||
const rect = await getImageWidthHeightByBase64(base64);
|
||||
width = rect.width;
|
||||
height = rect.height;
|
||||
noEdit = true;
|
||||
}
|
||||
const copyMenu = {
|
||||
label: '复制',
|
||||
icon: <Copy />,
|
||||
key: 'copy',
|
||||
onClick: async () => {
|
||||
const nodes = reactFlowInstance.getNodes();
|
||||
const selectedNode = nodes.find((node) => node.selected);
|
||||
if (!selectedNode) {
|
||||
message.error('没有选中节点');
|
||||
return;
|
||||
}
|
||||
const copyData = JSON.stringify(selectedNode);
|
||||
navigator.clipboard.writeText(copyData);
|
||||
message.success('复制成功');
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 1000);
|
||||
},
|
||||
};
|
||||
const pasteMenu = {
|
||||
label: '粘贴',
|
||||
icon: <ClipboardPaste />,
|
||||
key: 'paste',
|
||||
onClick: async () => {
|
||||
const readList = await clipboardRead();
|
||||
const flowPosition = reactFlowInstance.screenToFlowPosition({ x, y });
|
||||
const check = new HasTypeCheck(readList, flowPosition);
|
||||
if (readList.length <= 0) {
|
||||
message.error('粘贴为空');
|
||||
return;
|
||||
}
|
||||
const newNodeData = await check.getData();
|
||||
const nodes = store.nodes;
|
||||
const _nodes = [...nodes, newNodeData];
|
||||
wallStore.setNodes(_nodes);
|
||||
wallStore.saveNodes(_nodes);
|
||||
// reactFlowInstance.setNodes(_nodes);
|
||||
},
|
||||
};
|
||||
const menuList = useMemo(() => {
|
||||
const selected = store.nodes.find((node) => node.selected);
|
||||
if (selected) {
|
||||
return [copyMenu, pasteMenu] as MenuItem[];
|
||||
}
|
||||
return [pasteMenu] as MenuItem[];
|
||||
}, [store.nodes]);
|
||||
|
||||
const flowPosition = reactFlowInstance.screenToFlowPosition({ x, y });
|
||||
const nodes = store.nodes;
|
||||
const newNodeData: any = {
|
||||
id: randomId(),
|
||||
type: 'wallnote',
|
||||
position: flowPosition,
|
||||
data: {
|
||||
width,
|
||||
height,
|
||||
html: content,
|
||||
},
|
||||
};
|
||||
if (noEdit) {
|
||||
newNodeData.data.noEdit = true;
|
||||
}
|
||||
const newNodes = [newNodeData];
|
||||
const _nodes = [...nodes, ...newNodes];
|
||||
wallStore.setNodes(_nodes);
|
||||
wallStore.saveNodes(_nodes);
|
||||
// reactFlowInstance.setNodes(_nodes);
|
||||
},
|
||||
}, //
|
||||
];
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useRef, memo, useEffect, useMemo, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { NodeResizer, useStore } from '@xyflow/react';
|
||||
import { NodeResizer, useStore, useReactFlow } from '@xyflow/react';
|
||||
import { useWallStore } from '../store/wall';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { toast } from 'react-toastify';
|
||||
@@ -45,13 +45,14 @@ const ShowContent = (props: { data: WallData; selected: boolean }) => {
|
||||
export const CustomNode = (props: { id: string; data: WallData; selected: boolean }) => {
|
||||
const data = props.data;
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const selected = props.selected;
|
||||
const reactFlowInstance = useReactFlow();
|
||||
const zoom = reactFlowInstance.getViewport().zoom;
|
||||
const wallStore = useWallStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
setOpen: state.setOpen,
|
||||
setSelectedNode: state.setSelectedNode,
|
||||
saveNodes: state.saveNodes,
|
||||
checkAndOpen: state.checkAndOpen,
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -102,16 +103,17 @@ export const CustomNode = (props: { id: string; data: WallData; selected: boolea
|
||||
const node = store.getNode(props.id);
|
||||
console.log('node eidt', node);
|
||||
if (node) {
|
||||
if (node.data?.noEdit) {
|
||||
message.error('不支持编辑');
|
||||
const dataType: string = (node?.data?.dataType as string) || '';
|
||||
if (dataType && dataType?.startsWith('image')) {
|
||||
message.error('不支持编辑图片');
|
||||
return;
|
||||
}
|
||||
wallStore.setOpen(true);
|
||||
wallStore.setSelectedNode(node);
|
||||
wallStore.checkAndOpen(true, node);
|
||||
} else {
|
||||
message.error('节点不存在');
|
||||
}
|
||||
};
|
||||
const handleSize = Math.max(10, 10 / zoom);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -125,7 +127,7 @@ export const CustomNode = (props: { id: string; data: WallData; selected: boolea
|
||||
style={style}>
|
||||
<ShowContent data={data} selected={props.selected} />
|
||||
</div>
|
||||
<div className={clsx('absolute top-0 right-0', props.selected ? 'opacity-100' : 'opacity-0')}>
|
||||
<div className={clsx('absolute top-0 right-0 cursor-pointer', props.selected ? 'opacity-100' : 'opacity-0')}>
|
||||
<button
|
||||
className='w-6 h-6 flex items-center justify-center'
|
||||
onClick={() => {
|
||||
@@ -149,6 +151,14 @@ export const CustomNode = (props: { id: string; data: WallData; selected: boolea
|
||||
if (!heightNum || !widthNum) return;
|
||||
store.updateWallRect(props.id, { width: widthNum, height: heightNum });
|
||||
}}
|
||||
handleStyle={
|
||||
props.selected
|
||||
? {
|
||||
width: handleSize,
|
||||
height: handleSize,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { message } from '@/modules/message';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { isMac } from '../utils/is-mac';
|
||||
const Drawer = () => {
|
||||
const { open, setOpen, selectedNode, setSelectedNode, editValue, setEditValue } = useWallStore(
|
||||
const { open, setOpen, selectedNode, setSelectedNode, editValue, setEditValue, hasEdited, setHasEdited } = useWallStore(
|
||||
useShallow((state) => ({
|
||||
open: state.open,
|
||||
setOpen: state.setOpen,
|
||||
@@ -17,18 +17,23 @@ const Drawer = () => {
|
||||
setSelectedNode: state.setSelectedNode,
|
||||
editValue: state.editValue,
|
||||
setEditValue: state.setEditValue,
|
||||
hasEdited: state.hasEdited,
|
||||
setHasEdited: state.setHasEdited,
|
||||
})),
|
||||
);
|
||||
const store = useStore((state) => state);
|
||||
const storeApi = useStoreApi();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => {
|
||||
if (open && selectedNode) {
|
||||
setEditValue(selectedNode?.data.html);
|
||||
setEditValue(selectedNode?.data.html, true);
|
||||
}
|
||||
}, [open, selectedNode]);
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
return () => {
|
||||
setOpen(false);
|
||||
setHasEdited(false);
|
||||
setSelectedNode(null);
|
||||
};
|
||||
}, []);
|
||||
@@ -52,6 +57,15 @@ const Drawer = () => {
|
||||
window.removeEventListener('keydown', listener);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
console.log('editValue', editValue, open, mounted);
|
||||
if (!open && mounted) {
|
||||
console.log('hasEdited', hasEdited);
|
||||
if (hasEdited) {
|
||||
onSave();
|
||||
}
|
||||
}
|
||||
}, [open, hasEdited, mounted]);
|
||||
const onSave = () => {
|
||||
const wallStore = useWallStore.getState();
|
||||
const selectedNode = wallStore.selectedNode;
|
||||
|
||||
@@ -3,9 +3,10 @@ import { Dialog, DialogTitle, DialogContent, TextField, DialogActions, Button, C
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { getNodeData, useWallStore } from '../store/wall';
|
||||
import { useReactFlow, useStore } from '@xyflow/react';
|
||||
import { useUserWallStore } from '../store/user-wall';
|
||||
import { useUserWallStore, Wall } from '../store/user-wall';
|
||||
import { message } from '@/modules/message';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { WallData } from './CustomNode';
|
||||
|
||||
function FormDialog({ open, handleClose, handleSubmit, initialData }) {
|
||||
const [data, setData] = useState(initialData || { title: '', description: '', summary: '', tags: [] });
|
||||
@@ -106,7 +107,10 @@ export const SaveModal = () => {
|
||||
tags: values.tags,
|
||||
markType: 'wallnote' as 'wallnote',
|
||||
data,
|
||||
};
|
||||
} as Wall;
|
||||
if (id) {
|
||||
fromData.id = id;
|
||||
}
|
||||
const loading = message.loading('保存中...');
|
||||
const res = await userWallStore.saveWall(fromData, { refresh: false });
|
||||
message.close(loading);
|
||||
|
||||
Reference in New Issue
Block a user