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