temp: fix bugs

This commit is contained in:
2025-02-25 13:19:38 +08:00
parent 5ef42ee9de
commit a92e377d9f
15 changed files with 366 additions and 126 deletions

View File

@@ -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={{

View File

@@ -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
}
/>
</>
);

View File

@@ -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;

View File

@@ -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);