clear
This commit is contained in:
48
src/App.tsx
48
src/App.tsx
@@ -1,13 +1,53 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { basename } from './modules/basename';
|
||||
import { getHistoryState, setHistoryState } from '@kevisual/store/web-page.js';
|
||||
import { Draw } from './pages/Draw';
|
||||
import { App as Manager } from './manager/Manager';
|
||||
// import { App as Manager } from './manager/Manager';
|
||||
import { Manager } from '@kevisual/mark/src/Module';
|
||||
|
||||
console.log('basename', basename);
|
||||
export const App = () => {
|
||||
const [id, setId] = useState('');
|
||||
useEffect(() => {
|
||||
const state = getHistoryState();
|
||||
if (state?.id) {
|
||||
setId(state.id);
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<div className='bg-slate-200 w-full h-full'>
|
||||
{/* <Draw /> */}
|
||||
<Manager />
|
||||
<div className='bg-slate-200 w-full h-full flex'>
|
||||
<Manager
|
||||
showSearch={true}
|
||||
showAdd={true}
|
||||
markType={'excalidraw'}
|
||||
onClick={(data) => {
|
||||
console.log('data', data);
|
||||
setHistoryState({
|
||||
id: data.id,
|
||||
});
|
||||
if (data.id !== id) {
|
||||
setId('');
|
||||
setTimeout(() => {
|
||||
setId(data.id);
|
||||
}, 200);
|
||||
}
|
||||
}}>
|
||||
<div className='h-full grow'>
|
||||
{id ? (
|
||||
<Draw
|
||||
id={id}
|
||||
onClose={() => {
|
||||
setId('');
|
||||
setHistoryState({
|
||||
id: '',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div>请选择一个画布</div>
|
||||
)}
|
||||
</div>
|
||||
</Manager>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/components/theme/wind-theme.css';
|
||||
|
||||
/* .sidebar-trigger__label-element {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
.HelpDialog__btn[href='https://plus.excalidraw.com/blog'] {
|
||||
display: none;
|
||||
}
|
||||
.HelpDialog__btn[href='https://youtube.com/@excalidraw'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .Excalidraw__loading {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
13
src/main.tsx
13
src/main.tsx
@@ -1,6 +1,15 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from './App.tsx';
|
||||
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import './index.css';
|
||||
import { I18NextProvider, initI18n } from '@kevisual/components/translate/index.tsx';
|
||||
import { basename } from './modules/basename';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />);
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<>
|
||||
<I18NextProvider basename={basename} noUse={false}>
|
||||
<App />
|
||||
</I18NextProvider>
|
||||
<ToastContainer />
|
||||
</>,
|
||||
);
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useManagerStore } from './store';
|
||||
import { useEffect } from 'react';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { ManagerProvider } from './Provider';
|
||||
|
||||
export const Manager = () => {
|
||||
const { list, init } = useManagerStore(
|
||||
useShallow((state) => {
|
||||
console.log('state', state);
|
||||
return {
|
||||
list: state.list,
|
||||
init: state.init,
|
||||
};
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
init('md');
|
||||
}, []);
|
||||
return <div>Manager</div>;
|
||||
};
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<ManagerProvider>
|
||||
<Manager />
|
||||
</ManagerProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { StoreContextProvider } from '@kevisual/store/react';
|
||||
import { createManagerStore } from './store/index';
|
||||
export const ManagerProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<StoreContextProvider id='manager' stateCreator={createManagerStore}>
|
||||
{children}
|
||||
</StoreContextProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
# 左侧的管理界面
|
||||
|
||||
获取列表,筛选,选中。
|
||||
|
||||
## 右侧的是画布
|
||||
@@ -1,55 +0,0 @@
|
||||
import { StoreManager } from '@kevisual/store';
|
||||
import { useContextKey } from '@kevisual/store/context';
|
||||
// import { StateCreator, StoreApi, UseBoundStore } from 'zustand';
|
||||
import { queryMark, queryClient } from '../../modules/query';
|
||||
import { QueryMark } from '@kevisual/query-mark';
|
||||
import { useStore, BoundStore } from '@kevisual/store/react';
|
||||
export const store = useContextKey('store', () => {
|
||||
return new StoreManager();
|
||||
});
|
||||
|
||||
type ManagerStore = {
|
||||
/** 当前选中的Mark */
|
||||
currrentMark: any;
|
||||
setCurrentMark: (mark: any) => void;
|
||||
/** 获取Mark列表 */
|
||||
getList: () => Promise<void>;
|
||||
/** Mark列表 */
|
||||
list: any[];
|
||||
setList: (list: any[]) => void;
|
||||
/** 初始化 */
|
||||
init: (markType: string) => Promise<void>;
|
||||
queryMark?: QueryMark;
|
||||
markType: string;
|
||||
};
|
||||
export const createManagerStore = (set: any, get: any, store: any): ManagerStore => {
|
||||
return {
|
||||
currrentMark: null,
|
||||
setCurrentMark: (mark: any) => set(() => ({ currrentMark: mark })),
|
||||
getList: async () => {
|
||||
const queryMark = get().queryMark;
|
||||
const res = await queryMark.getMarkList({ page: 1, pageSize: 10 });
|
||||
if (res.code === 200) {
|
||||
set(() => ({ list: res.data }));
|
||||
}
|
||||
},
|
||||
list: [],
|
||||
setList: (list: any[]) => set(() => ({ list })),
|
||||
init: async (markType: string = 'wallnote') => {
|
||||
// await get().getList();
|
||||
// console.log('init', set, );
|
||||
const queryMark = new QueryMark({
|
||||
query: queryClient as any,
|
||||
markType,
|
||||
});
|
||||
set({ queryMark, markType });
|
||||
setTimeout(() => {
|
||||
queryMark.getMarkList({ page: 1, pageSize: 10 });
|
||||
}, 1000);
|
||||
},
|
||||
queryMark: undefined,
|
||||
markType: 'simple',
|
||||
};
|
||||
};
|
||||
|
||||
export const useManagerStore = useStore as BoundStore<ManagerStore>;
|
||||
@@ -5,53 +5,36 @@ import { StoreContextProvider } from '@kevisual/store/react';
|
||||
import { LineChart } from 'lucide-react';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { Core } from './core/Excalidraw';
|
||||
export const DrawLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
export const Draw = ({ id, onClose }: { id: string; onClose: () => void }) => {
|
||||
useLayoutEffect(() => {
|
||||
// @ts-ignore
|
||||
window.EXCALIDRAW_ASSET_PATH = 'https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/';
|
||||
// window.EXCALIDRAW_ASSET_PATH = '/';
|
||||
}, []);
|
||||
return (
|
||||
<StoreContextProvider id='draw'>
|
||||
<div className='h-full w-full'>{children}</div>
|
||||
<DrawHeader />
|
||||
<StoreContextProvider id={id} stateCreator={createMarkStore}>
|
||||
<ExcaliDrawComponent id={id} onClose={onClose} />
|
||||
</StoreContextProvider>
|
||||
);
|
||||
};
|
||||
export const Draw = () => {
|
||||
useLayoutEffect(() => {
|
||||
// @ts-ignore
|
||||
// window.EXCALIDRAW_ASSET_PATH = 'https://esm.sh/@excalidraw/excalidraw@0.18.0/dist/prod/';
|
||||
window.EXCALIDRAW_ASSET_PATH = '/';
|
||||
}, []);
|
||||
return (
|
||||
<DrawLayout>
|
||||
<ExcaliDrawComponent />
|
||||
</DrawLayout>
|
||||
);
|
||||
|
||||
type ExcaliDrawComponentProps = {
|
||||
/** 修改的id */
|
||||
id: string;
|
||||
/** 关闭 */
|
||||
onClose: () => void;
|
||||
};
|
||||
export const DrawHeader = () => {
|
||||
export const ExcaliDrawComponent = ({ id, onClose }: ExcaliDrawComponentProps) => {
|
||||
const store = useMarkStore(
|
||||
useShallow((value) => {
|
||||
useShallow((state) => {
|
||||
return {
|
||||
mark: value.mark,
|
||||
setMark: value.setMark,
|
||||
setInfo: value.setInfo,
|
||||
getList: value.getList,
|
||||
id: state.id,
|
||||
setId: state.setId,
|
||||
getMark: state.getMark,
|
||||
};
|
||||
}),
|
||||
);
|
||||
console.log('store show', store);
|
||||
useEffect(() => {
|
||||
store.getList();
|
||||
}, []);
|
||||
return (
|
||||
<div className='fixed left-0 top-0 z-10 h-10 w-10 bg-red-500'>
|
||||
<button>
|
||||
<LineChart />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const ExcaliDrawComponent = () => {
|
||||
return (
|
||||
<StoreContextProvider id='draw2' stateCreator={createMarkStore}>
|
||||
<Core />
|
||||
</StoreContextProvider>
|
||||
);
|
||||
|
||||
return <Core onClose={onClose} id={id} />;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,245 @@
|
||||
import { Excalidraw } from '@excalidraw/excalidraw';
|
||||
import '@excalidraw/excalidraw/index.css';
|
||||
import { OrderedExcalidrawElement } from '@excalidraw/excalidraw/element/types';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const Core = () => {
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types';
|
||||
import { Languages, LogOut, Save } from 'lucide-react';
|
||||
import { MainMenu, Sidebar, Footer } from '@excalidraw/excalidraw';
|
||||
import { throttle } from 'lodash-es';
|
||||
import { useMarkStore } from '@/store';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useListenLang } from './hooks/listen-lang';
|
||||
import { toast } from 'react-toastify';
|
||||
type ImageResource = {};
|
||||
export const ImagesResources = (props: ImageResource) => {
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
useEffect(() => {
|
||||
initImages();
|
||||
}, []);
|
||||
const refFiles = useRef<any>({});
|
||||
const { api } = useMarkStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
api: state.api,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const initImages = async () => {
|
||||
if (!api) return;
|
||||
const res = api.getFiles();
|
||||
console.log('res from ImageResource', res);
|
||||
setImages(Object.keys(res));
|
||||
refFiles.current = res;
|
||||
};
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={{ elements: [] }}
|
||||
onChange={(elements) => {
|
||||
console.log('elements', elements);
|
||||
}}
|
||||
/>
|
||||
<div className='w-full h-full'>
|
||||
{images.map((image) => {
|
||||
const isUrl = refFiles.current[image]?.dataURL?.startsWith?.('http');
|
||||
const dataURL = refFiles.current[image]?.dataURL;
|
||||
return (
|
||||
<div className='flex items-center gap-2 w-full overflow-hidden p-2 m-2 border border-gray-200 rounded-md shadow ' key={image}>
|
||||
<img className='w-10 h-10 m-4' src={dataURL} alt='image' />
|
||||
<div className='flex flex-col gap-1'>
|
||||
{isUrl && <div className='text-xs line-clamp-4 break-all'> {dataURL}</div>}
|
||||
<div className='text-xs'>{refFiles.current[image]?.name}</div>
|
||||
<div className='text-xs'>{refFiles.current[image]?.type}</div>
|
||||
<div className='text-xs'>{refFiles.current[image]?.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExcalidrawExpand = (props: { onClose: () => void }) => {
|
||||
const { onClose } = props;
|
||||
const [docked, setDocked] = useState(false);
|
||||
const store = useMarkStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
loading: state.loading,
|
||||
updateMark: state.updateMark,
|
||||
api: state.api,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const { lang, setLang, isZh } = useListenLang();
|
||||
return (
|
||||
<>
|
||||
{store.loading && (
|
||||
<div className='w-full h-full flex items-center justify-center absolute top-0 left-0 z-10'>
|
||||
<div className='w-full h-full bg-black opacity-10 absolute top-0 left-0 z-1'></div>
|
||||
</div>
|
||||
)}
|
||||
<MainMenu>
|
||||
<MainMenu.Item
|
||||
onSelect={() => {
|
||||
store.updateMark();
|
||||
}}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Save />
|
||||
<span>{isZh ? '保存' : 'Save'}</span>
|
||||
</div>
|
||||
</MainMenu.Item>
|
||||
<MainMenu.DefaultItems.LoadScene />
|
||||
<MainMenu.DefaultItems.Export />
|
||||
<MainMenu.DefaultItems.SaveToActiveFile />
|
||||
<MainMenu.DefaultItems.SaveAsImage />
|
||||
{/* <MainMenu.DefaultItems.LiveCollaborationTrigger onSelect={() => {}} isCollaborating={false} /> */}
|
||||
{/* <MainMenu.DefaultItems.CommandPalette /> */}
|
||||
<MainMenu.DefaultItems.SearchMenu />
|
||||
<MainMenu.DefaultItems.Help />
|
||||
<MainMenu.DefaultItems.ClearCanvas />
|
||||
<MainMenu.Separator />
|
||||
<MainMenu.DefaultItems.ChangeCanvasBackground />
|
||||
<MainMenu.Separator />
|
||||
<MainMenu.DefaultItems.ToggleTheme />
|
||||
<MainMenu.Separator />
|
||||
|
||||
<MainMenu.Item
|
||||
onSelect={(e) => {
|
||||
const newLang = lang === 'zh-CN' ? 'en' : 'zh-CN';
|
||||
setLang(newLang);
|
||||
}}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Languages />
|
||||
<span>{isZh ? 'English' : '中文'}</span>
|
||||
</div>
|
||||
</MainMenu.Item>
|
||||
{onClose && (
|
||||
<>
|
||||
<MainMenu.Separator />
|
||||
<MainMenu.Item onSelect={onClose}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<LogOut />
|
||||
<span>{isZh ? '退出当前画布' : 'Exit current canvas'}</span>
|
||||
</div>
|
||||
</MainMenu.Item>
|
||||
</>
|
||||
)}
|
||||
</MainMenu>
|
||||
<Sidebar name='custom' docked={docked} onDock={setDocked}>
|
||||
<Sidebar.Header />
|
||||
<Sidebar.Tabs>
|
||||
<Sidebar.Tab tab='one'>{<ImagesResources />}</Sidebar.Tab>
|
||||
<Sidebar.Tab tab='two'>Tab two!</Sidebar.Tab>
|
||||
<Sidebar.TabTriggers>
|
||||
<Sidebar.TabTrigger tab='one'>Image Resources</Sidebar.TabTrigger>
|
||||
<Sidebar.TabTrigger tab='two'>Two</Sidebar.TabTrigger>
|
||||
</Sidebar.TabTriggers>
|
||||
</Sidebar.Tabs>
|
||||
</Sidebar>
|
||||
|
||||
<Footer>
|
||||
<Sidebar.Trigger
|
||||
name='custom'
|
||||
tab='one'
|
||||
style={{
|
||||
marginLeft: '0.5rem',
|
||||
background: '#70b1ec',
|
||||
color: 'white',
|
||||
}}>
|
||||
{isZh ? '图片资源' : 'Image Resources'}
|
||||
</Sidebar.Trigger>
|
||||
</Footer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
type CoreProps = {
|
||||
onClose: () => void;
|
||||
id: string;
|
||||
};
|
||||
export const Core = ({ onClose, id }: CoreProps) => {
|
||||
const ref = useRef<ExcalidrawImperativeAPI>(null);
|
||||
const { lang } = useListenLang();
|
||||
const store = useMarkStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
id: state.id,
|
||||
loading: state.loading,
|
||||
getMark: state.getMark,
|
||||
api: state.api,
|
||||
setApi: state.setApi,
|
||||
getCache: state.getCache,
|
||||
updateMark: state.updateMark,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const cacheDataRef = useRef<{
|
||||
elements: { [key: string]: number };
|
||||
filesObject: Record<string, any>;
|
||||
}>({
|
||||
elements: {},
|
||||
filesObject: {},
|
||||
});
|
||||
useEffect(() => {
|
||||
id && store.getMark(id);
|
||||
}, [id]);
|
||||
const onSave = throttle(async (elements, appState, filesObject) => {
|
||||
const { elements: cacheElements, filesObject: cacheFiles } = cacheDataRef.current;
|
||||
const { setCache, loading } = useMarkStore.getState(id);
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
let isChange = false;
|
||||
const elementsObj = elements.reduce((acc, e) => {
|
||||
acc[e.id] = e.version;
|
||||
return acc;
|
||||
}, {});
|
||||
if (JSON.stringify(elementsObj) !== JSON.stringify(cacheElements)) {
|
||||
isChange = true;
|
||||
}
|
||||
if (!isChange) {
|
||||
if (JSON.stringify(cacheFiles) !== JSON.stringify(filesObject)) {
|
||||
isChange = true;
|
||||
}
|
||||
}
|
||||
console.log('onSave', elements, appState, filesObject, 'isChange', isChange);
|
||||
if (!isChange) {
|
||||
return;
|
||||
}
|
||||
cacheDataRef.current = { elements: elementsObj, filesObject };
|
||||
setCache({
|
||||
data: {
|
||||
elements,
|
||||
filesObject,
|
||||
},
|
||||
});
|
||||
}, 2000);
|
||||
return (
|
||||
<Excalidraw
|
||||
initialData={{}}
|
||||
onChange={(elements, appState, filesObject) => {
|
||||
const story = useMarkStore.getState(id);
|
||||
if (story.loading) return;
|
||||
onSave(elements, appState, filesObject);
|
||||
}}
|
||||
langCode={lang || 'en'}
|
||||
onPaste={async (e) => {
|
||||
toast.info('paste is not allowed, is development');
|
||||
return false;
|
||||
}}
|
||||
renderTopRightUI={() => {
|
||||
return <div></div>;
|
||||
}}
|
||||
excalidrawAPI={async (api) => {
|
||||
ref.current = api;
|
||||
store.setApi(api);
|
||||
const cache = await store.getCache(id, true);
|
||||
if (!cache) return;
|
||||
const elementsObj = cache.elements.reduce((acc, e) => {
|
||||
acc[e.id] = e.version;
|
||||
return acc;
|
||||
}, {});
|
||||
cacheDataRef.current = {
|
||||
elements: elementsObj,
|
||||
filesObject: cache.filesObject,
|
||||
};
|
||||
}}
|
||||
generateIdForFile={async (file) => {
|
||||
return '1.png';
|
||||
}}>
|
||||
<ExcalidrawExpand onClose={onClose} />
|
||||
</Excalidraw>
|
||||
);
|
||||
};
|
||||
|
||||
29
src/pages/core/hooks/listen-lang.ts
Normal file
29
src/pages/core/hooks/listen-lang.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useListenLang = () => {
|
||||
const [lang, setLang] = useState('zh-CN');
|
||||
useEffect(() => {
|
||||
const lang = localStorage.getItem('excalidrawLang');
|
||||
if (lang) {
|
||||
setLang(lang);
|
||||
}
|
||||
// 监听 localStorage中excalidrawLang的变化
|
||||
const onStorage = (e: StorageEvent) => {
|
||||
if (e.key === 'excalidrawLang') {
|
||||
e.newValue && setLang(e.newValue);
|
||||
}
|
||||
};
|
||||
window.addEventListener('storage', onStorage);
|
||||
return () => {
|
||||
window.removeEventListener('storage', onStorage);
|
||||
};
|
||||
}, []);
|
||||
return {
|
||||
lang,
|
||||
setLang: (lang: string) => {
|
||||
// setLang(lang);
|
||||
localStorage.setItem('excalidrawLang', lang);
|
||||
},
|
||||
isZh: lang === 'zh-CN',
|
||||
};
|
||||
};
|
||||
30
src/pages/core/hooks/listen-library.ts
Normal file
30
src/pages/core/hooks/listen-library.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useListenLibrary = () => {
|
||||
useEffect(() => {
|
||||
addLibraryItem();
|
||||
}, []);
|
||||
|
||||
const addLibraryItem = async () => {
|
||||
const hash = window.location.hash; // 获取哈希值
|
||||
const addLibrary = hash.split('addLibrary=')[1];
|
||||
if (!addLibrary || addLibrary === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const token = hash.split('token=')[1];
|
||||
console.log('addLibrary', addLibrary, token);
|
||||
const _fetchURL = decodeURIComponent(addLibrary);
|
||||
const fetchURL = _fetchURL.split('&')[0];
|
||||
|
||||
console.log('fetchURL', fetchURL);
|
||||
const res = await fetch(fetchURL, {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log('data', data);
|
||||
};
|
||||
return {
|
||||
addLibraryItem,
|
||||
};
|
||||
};
|
||||
@@ -3,31 +3,212 @@ import { useContextKey } from '@kevisual/store/context';
|
||||
import { StateCreator, StoreApi, UseBoundStore } from 'zustand';
|
||||
import { queryMark } from '../modules/query';
|
||||
import { useStore, BoundStore } from '@kevisual/store/react';
|
||||
import { createStore, set as setCache, get as getCache } from 'idb-keyval';
|
||||
import { OrderedExcalidrawElement } from '@excalidraw/excalidraw/element/types';
|
||||
import { toast } from 'react-toastify';
|
||||
import { BinaryFileData, ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types';
|
||||
export const cacheStore = createStore('excalidraw-store', 'excalidraw');
|
||||
|
||||
export const store = useContextKey('store', () => {
|
||||
return new StoreManager();
|
||||
});
|
||||
|
||||
type MarkStore = {
|
||||
mark: string;
|
||||
setMark: (mark: string) => void;
|
||||
info: string;
|
||||
setInfo: (info: string) => void;
|
||||
id: string;
|
||||
setId: (id: string) => void;
|
||||
mark: any;
|
||||
setMark: (mark: any) => void;
|
||||
info: any;
|
||||
setInfo: (info: any) => void;
|
||||
getList: () => Promise<void>;
|
||||
list: any[];
|
||||
setList: (list: any[]) => void;
|
||||
getMark: (markId: string) => Promise<void>;
|
||||
updateMark: () => Promise<void>;
|
||||
getCache: (
|
||||
id: string,
|
||||
updateApiData?: boolean,
|
||||
) => Promise<
|
||||
| {
|
||||
elements: OrderedExcalidrawElement[];
|
||||
filesObject: Record<string, any>;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
setCache: (cache: any, version?: number) => Promise<void>;
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
// excalidraw
|
||||
|
||||
api: ExcalidrawImperativeAPI | null;
|
||||
setApi: (api: ExcalidrawImperativeAPI) => void;
|
||||
};
|
||||
export const createMarkStore = (get: any, set: any, store: any): MarkStore => {
|
||||
export const createMarkStore: StateCreator<MarkStore, [], [], MarkStore> = (set, get, store) => {
|
||||
return {
|
||||
mark: 'test',
|
||||
setMark: (mark: string) => set(() => ({ mark })),
|
||||
info: 'test info',
|
||||
setInfo: (info: string) => set(() => ({ info })),
|
||||
id: '',
|
||||
setId: (id: string) => set(() => ({ id })),
|
||||
mark: null,
|
||||
setMark: (mark: any) => set(() => ({ mark })),
|
||||
loading: true,
|
||||
setLoading: (loading: boolean) => set(() => ({ loading })),
|
||||
info: null,
|
||||
setCache: async (cache: any, version?: number) => {
|
||||
const { id, mark } = get();
|
||||
console.log('cacheData setCache ,id', cache, id);
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
const cacheData = (await getCache(`${id}`, cacheStore)) || {};
|
||||
await setCache(
|
||||
`${id}`,
|
||||
{
|
||||
...cacheData,
|
||||
...cache,
|
||||
data: {
|
||||
...cacheData?.data,
|
||||
...cache?.data,
|
||||
},
|
||||
version: version || mark?.version || 0,
|
||||
},
|
||||
cacheStore,
|
||||
);
|
||||
},
|
||||
updateMark: async () => {
|
||||
const { id } = get();
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheData = await getCache(id, cacheStore);
|
||||
let mark = cacheData || {};
|
||||
if (!mark) {
|
||||
return;
|
||||
}
|
||||
const { data } = mark;
|
||||
|
||||
const res = await queryMark.updateMark({ id, data });
|
||||
if (res.code === 200) {
|
||||
set(() => ({ mark: res.data }));
|
||||
toast.success('更新成功');
|
||||
get().setCache({}, res.data!.version);
|
||||
}
|
||||
},
|
||||
getCache: async (id: string, updateApiData?: boolean) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
// 获取缓存
|
||||
let cacheData = (await getCache(`${id}`, cacheStore)) || { data: { elements: [], filesObject: {} } };
|
||||
console.log('getCache', id, cacheData);
|
||||
if (cacheData) {
|
||||
if (updateApiData) {
|
||||
const api = get().api;
|
||||
if (api) {
|
||||
const files = Object.values(cacheData.data.filesObject || {}) as BinaryFileData[];
|
||||
api.addFiles(files || []);
|
||||
api.updateScene({
|
||||
elements: [...(cacheData.data?.elements || [])],
|
||||
appState: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
elements: cacheData.data.elements || [],
|
||||
filesObject: cacheData.data.filesObject || {},
|
||||
};
|
||||
},
|
||||
setInfo: (info: any) => set(() => ({ info })),
|
||||
getList: async () => {
|
||||
const res = await queryMark.getMarkList({ page: 1, pageSize: 10 });
|
||||
console.log(res);
|
||||
},
|
||||
list: [],
|
||||
setList: (list: any[]) => set(() => ({ list })),
|
||||
getMark: async (markId: string) => {
|
||||
set(() => ({ loading: true, id: markId }));
|
||||
const toastId = toast.loading(`获取数据中...`);
|
||||
const now = new Date().getTime();
|
||||
const cacheData = await getCache(markId, cacheStore);
|
||||
const checkVersion = await queryMark.checkVersion(markId, cacheData?.version);
|
||||
if (checkVersion) {
|
||||
const res = await queryMark.getMark(markId);
|
||||
if (res.code === 200) {
|
||||
set(() => ({
|
||||
mark: res.data,
|
||||
id: markId,
|
||||
}));
|
||||
const mark = res.data!;
|
||||
const excalidrawData = mark.data || {};
|
||||
await get().setCache({
|
||||
data: {
|
||||
elements: excalidrawData.elements,
|
||||
filesObject: excalidrawData.filesObject,
|
||||
},
|
||||
version: mark.version,
|
||||
});
|
||||
get().getCache(markId, true);
|
||||
} else {
|
||||
toast.error(res.message || '获取数据失败');
|
||||
}
|
||||
}
|
||||
|
||||
const end = new Date().getTime();
|
||||
const getTime = end - now;
|
||||
if (getTime < 2 * 1000) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2 * 1000 - getTime));
|
||||
}
|
||||
toast.dismiss(toastId);
|
||||
set(() => ({ loading: false }));
|
||||
},
|
||||
api: null,
|
||||
setApi: (api: ExcalidrawImperativeAPI) => set(() => ({ api })),
|
||||
};
|
||||
};
|
||||
|
||||
export const useMarkStore = useStore as BoundStore<MarkStore>;
|
||||
|
||||
export const fileDemo = {
|
||||
abc: {
|
||||
dataURL: 'https://kevisual.xiongxiao.me/root/center/panda.png' as any,
|
||||
// @ts-ignore
|
||||
id: 'abc',
|
||||
name: 'test2.png',
|
||||
type: 'image/png',
|
||||
},
|
||||
};
|
||||
export const demoElements: OrderedExcalidrawElement[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'image',
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fileId: 'abc' as any,
|
||||
version: 2,
|
||||
versionNonce: 28180243,
|
||||
index: 'a0' as any,
|
||||
isDeleted: false,
|
||||
fillStyle: 'solid',
|
||||
strokeWidth: 2,
|
||||
strokeStyle: 'solid',
|
||||
roughness: 1,
|
||||
opacity: 100,
|
||||
angle: 0,
|
||||
strokeColor: '#1e1e1e',
|
||||
backgroundColor: 'transparent',
|
||||
seed: 1,
|
||||
groupIds: [],
|
||||
frameId: null,
|
||||
roundness: null,
|
||||
boundElements: [],
|
||||
updated: 1743219351869,
|
||||
link: null,
|
||||
locked: false,
|
||||
status: 'pending',
|
||||
scale: [1, 1],
|
||||
crop: null,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user