+
{
@@ -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
+ }
/>
>
);
diff --git a/src/pages/wall/modules/Drawer.tsx b/src/pages/wall/modules/Drawer.tsx
index eb929f4..a537a71 100644
--- a/src/pages/wall/modules/Drawer.tsx
+++ b/src/pages/wall/modules/Drawer.tsx
@@ -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;
diff --git a/src/pages/wall/modules/FormDialog.tsx b/src/pages/wall/modules/FormDialog.tsx
index 7cb30b3..2057dff 100644
--- a/src/pages/wall/modules/FormDialog.tsx
+++ b/src/pages/wall/modules/FormDialog.tsx
@@ -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);
diff --git a/src/pages/wall/store/user-wall.ts b/src/pages/wall/store/user-wall.ts
index 20d6057..800937b 100644
--- a/src/pages/wall/store/user-wall.ts
+++ b/src/pages/wall/store/user-wall.ts
@@ -6,7 +6,7 @@ type User = {
username: string;
avatar: string;
};
-type Wall = {
+export type Wall = {
id?: string;
title?: string;
description?: string;
diff --git a/src/pages/wall/store/wall.ts b/src/pages/wall/store/wall.ts
index 9c9255b..e77b749 100644
--- a/src/pages/wall/store/wall.ts
+++ b/src/pages/wall/store/wall.ts
@@ -4,7 +4,10 @@ import { getWallData, setWallData } from '../utils/db';
import { useUserWallStore } from './user-wall';
import { redirectToLogin } from '@/modules/require-to-login';
import { message } from '@/modules/message';
-
+import { randomId } from '../utils/random';
+import { DOCS_NODE } from '../docs';
+import { toast } from 'react-toastify';
+import { SplitButtons } from '../components/SplitToast';
type NodeData = {
id: string;
position: XYPosition;
@@ -27,10 +30,13 @@ interface WallState {
saveNodes: (nodes: NodeData[], opts?: { showMessage?: boolean }) => Promise;
open: boolean;
setOpen: (open: boolean) => void;
+ checkAndOpen: (open?: boolean, data?: any) => void;
selectedNode: NodeData | null;
setSelectedNode: (node: NodeData | null) => void;
editValue: string;
- setEditValue: (value: string) => void;
+ setEditValue: (value: string, init?: boolean) => void;
+ hasEdited: boolean;
+ setHasEdited: (hasEdited: boolean) => void;
data?: any;
setData: (data: any) => void;
init: (id?: string | null) => Promise;
@@ -48,17 +54,8 @@ interface WallState {
clear: () => Promise;
exportWall: (nodes: NodeData[]) => Promise;
clearQueryWall: () => Promise;
+ clearId: () => Promise;
}
-const initialNodes = [
- // { id: '1', type: 'wall', position: { x: 0, y: 0 }, data: { html: '1' } },
- {
- id: '1',
- type: 'wall',
- position: { x: 0, y: 0 },
- data: { html: 'sadfsdaf1 sadfsdaf1 sadfsdaf1 sadfsdaf1 sadfsdaf1 sadfsdaf1 sadfsdaf1 sadfsdaf1', width: 410, height: 212 },
- },
- // { id: '2', type: 'wall', position: { x: 0, y: 100 }, data: { html: '3332' } },
-];
export const useWallStore = create((set, get) => ({
nodes: [],
@@ -69,10 +66,11 @@ export const useWallStore = create((set, get) => ({
},
saveNodes: async (nodes: NodeData[], opts) => {
console.log('nodes', nodes, opts, opts?.showMessage ?? true);
+ const showMessage = opts?.showMessage ?? true;
+ set({ hasEdited: false });
if (!get().id) {
const covertData = getNodeData(nodes);
setWallData({ nodes: covertData });
- const showMessage = opts?.showMessage ?? true;
showMessage && message.success('保存到本地');
} else {
const { id } = get();
@@ -88,19 +86,45 @@ export const useWallStore = create((set, get) => ({
});
if (res.code === 200) {
// console.log('saveNodes res', res);
- message.success('保存成功', {
- closeOnClick: true,
- });
+ showMessage &&
+ message.success('保存成功', {
+ closeOnClick: true,
+ });
}
}
}
},
open: false,
- setOpen: (open) => set({ open }),
+ setOpen: (open) => {
+ set({ open });
+ },
+ checkAndOpen: (open, data) => {
+ const state = get();
+ if (state.hasEdited || state.open) {
+ toast(SplitButtons, {
+ closeButton: false,
+ className: 'p-0 w-[400px] border border-purple-600/40',
+ ariaLabel: 'Email received',
+ onClose: (reason) => {
+ if (reason === 'success') {
+ set({ open: true, selectedNode: data, hasEdited: false });
+ }
+ },
+ });
+ return;
+ } else set({ open, selectedNode: data });
+ },
selectedNode: null,
setSelectedNode: (node) => set({ selectedNode: node }),
editValue: '',
- setEditValue: (value) => set({ editValue: value }),
+ setEditValue: (value, init = false) => {
+ set({ editValue: value });
+ if (!init) {
+ set({ hasEdited: true });
+ }
+ },
+ hasEdited: false,
+ setHasEdited: (hasEdited) => set({ hasEdited }),
data: null,
setData: (data) => set({ data }),
id: null,
@@ -124,7 +148,17 @@ export const useWallStore = create((set, get) => ({
redirectToLogin();
} else {
const data = await getWallData();
- set({ nodes: data?.nodes || [], loaded: true });
+ const nodes = data?.nodes || [];
+ if (nodes.length === 0) {
+ set({
+ nodes: [DOCS_NODE], //
+ loaded: true,
+ id: null,
+ data: null,
+ });
+ } else {
+ set({ nodes, loaded: true, id: null, data: null });
+ }
}
},
toolbarOpen: false,
@@ -135,7 +169,7 @@ export const useWallStore = create((set, get) => ({
setFormDialogData: (data) => set({ formDialogData: data }),
clear: async () => {
if (get().id) {
- set({ nodes: initialNodes, id: null, selectedNode: null, editValue: '', data: null });
+ set({ nodes: [], selectedNode: null, editValue: '', data: null });
await useUserWallStore.getState().saveWall({
id: get().id!,
data: {
@@ -143,10 +177,13 @@ export const useWallStore = create((set, get) => ({
},
});
} else {
- set({ nodes: initialNodes, id: null, selectedNode: null, editValue: '', data: null });
+ set({ nodes: [], id: null, selectedNode: null, editValue: '', data: null });
await setWallData({ nodes: [] });
}
},
+ clearId: async () => {
+ set({ id: null, data: null });
+ },
exportWall: async (nodes: NodeData[]) => {
const covertData = getNodeData(nodes);
setWallData({ nodes: covertData });
@@ -160,6 +197,6 @@ export const useWallStore = create((set, get) => ({
a.click();
},
clearQueryWall: async () => {
- set({ nodes: initialNodes, id: null, selectedNode: null, editValue: '', data: null, toolbarOpen: false, loaded: false });
+ set({ nodes: [], id: null, selectedNode: null, editValue: '', data: null, toolbarOpen: false, loaded: false });
},
}));
diff --git a/src/pages/wall/utils/get-image-rect.ts b/src/pages/wall/utils/get-image-rect.ts
index 4737d02..e3cd129 100644
--- a/src/pages/wall/utils/get-image-rect.ts
+++ b/src/pages/wall/utils/get-image-rect.ts
@@ -29,3 +29,49 @@ export const getImageWidthHeightByBase64 = async (
img.src = b64str;
});
};
+
+/**
+ * 我有一个字符串,在在宽度为width的元素当中,自动换行,需要知道最后有多少行。
+ * 不使用canvas,通过文本font-size=16px,计算有多少行
+ * @param str
+ * @param width
+ */
+export const getTextWidthHeight = async ({
+ str,
+ width,
+ fontSize = 16,
+ maxHeight = 600,
+ minHeight = 100,
+}: {
+ str: string;
+ width: number;
+ fontSize?: number;
+ maxHeight?: number;
+ minHeight?: number;
+}) => {
+ function calculateTextHeight(text: string, width: number, fontSize: number = 16): number {
+ // 创建一个隐藏的 DOM 元素来测量文本高度
+ const element = document.createElement('div');
+ element.style.position = 'absolute';
+ element.style.visibility = 'hidden';
+ element.style.width = `${width}px`;
+ element.style.fontSize = `${fontSize}px`;
+ element.style.lineHeight = '1.2'; // 假设行高为 1.2 倍字体大小
+ element.style.whiteSpace = 'pre-wrap'; // 保留空白并允许换行
+ element.style.wordWrap = 'break-word'; // 允许长单词换行
+ element.innerText = text;
+
+ document.body.appendChild(element);
+ const height = element.offsetHeight;
+ document.body.removeChild(element);
+
+ return height;
+ }
+ const height = calculateTextHeight(str, width, fontSize);
+ if (height > maxHeight) {
+ return { width, height: maxHeight };
+ } else if (height < minHeight) {
+ return { width, height: minHeight };
+ }
+ return { width, height };
+};