feat: 添加i18n,美化界面
This commit is contained in:
parent
27d9bdf54e
commit
c206add7eb
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
@ -18,7 +18,7 @@
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@icon-park/react": "^1.4.2",
|
||||
"@kevisual/center-components": "workspace:*",
|
||||
"@kevisual/codemirror": "^0.0.2",
|
||||
"@kevisual/codemirror": "workspace:*",
|
||||
"@kevisual/container": "1.0.0",
|
||||
"@kevisual/query": "^0.0.8",
|
||||
"@kevisual/resources": "workspace:*",
|
||||
@ -36,12 +36,16 @@
|
||||
"d3": "^7.9.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"i18next": "^24.2.3",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"immer": "^10.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^15.0.7",
|
||||
"nanoid": "^5.1.4",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-router": "^7.3.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
|
27
packages/codemirror/.gitignore
vendored
Normal file
27
packages/codemirror/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
tsconfig.app.tsbuildinfo
|
||||
tsconfig.node.tsbuildinfo
|
33
packages/codemirror/index.html
Normal file
33
packages/codemirror/index.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Apps</title>
|
||||
<link rel="stylesheet" href="./src/global.css">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#ai-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<!-- <script src="/system/lib/app.js"></script> -->
|
||||
<script src="https://kevisual.xiongxiao.me/system/lib/app.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="ai-root"></div>
|
||||
<!-- <div id="ai-bot-root"></div> -->
|
||||
</body>
|
||||
<script src="./src/main.ts" type="module"></script>
|
||||
|
||||
</html>
|
70
packages/codemirror/package.json
Normal file
70
packages/codemirror/package.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "@kevisual/codemirror",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"basename": "/root/codemirror",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"pub": "envision deploy ./dist -k codemirror -v 0.0.1 -u -o root"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/basic-setup": "^0.20.0",
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/history": "^0.19.2",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-markdown": "^6.3.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.11.0",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.36.4",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@kevisual/center-components": "workspace:*",
|
||||
"@kevisual/router": "^0.0.9",
|
||||
"@kevisual/store": "^0.0.2",
|
||||
"@mui/material": "^6.4.8",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@uiw/codemirror-theme-duotone": "^4.23.10",
|
||||
"@uiw/codemirror-theme-vscode": "^4.23.10",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"highlight.js": "^11.11.1",
|
||||
"immer": "^10.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.483.0",
|
||||
"marked": "^15.0.7",
|
||||
"marked-highlight": "^2.2.1",
|
||||
"nanoid": "^5.1.5",
|
||||
"nprogress": "^0.2.0",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"react": "19.0.0",
|
||||
"react-datepicker": "^8.2.1",
|
||||
"react-dom": "19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-toastify": "^11.0.5",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/types": "^0.0.6"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
"./*": "./src/*"
|
||||
}
|
||||
}
|
3
packages/codemirror/src/assets/style.css
Normal file
3
packages/codemirror/src/assets/style.css
Normal file
@ -0,0 +1,3 @@
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
}
|
120
packages/codemirror/src/editor/editor.ts
Normal file
120
packages/codemirror/src/editor/editor.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { EditorView, keymap } from '@codemirror/view';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { history } from '@codemirror/history';
|
||||
import { vscodeLight } from '@uiw/codemirror-theme-vscode';
|
||||
import { formatKeymap } from './modules/keymap';
|
||||
import { Compartment, EditorState, Extension } from '@codemirror/state';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { autocompletion, Completion } from '@codemirror/autocomplete';
|
||||
import { getFileType } from './utils/get-file-type';
|
||||
type BaseEditorOpts = {
|
||||
language?: string;
|
||||
filename: string;
|
||||
autoComplete?: {
|
||||
props?: any;
|
||||
open?: boolean;
|
||||
overrideList?: Completion[];
|
||||
};
|
||||
};
|
||||
export class BaseEditor {
|
||||
editor?: EditorView;
|
||||
onChangeCompartment?: Compartment;
|
||||
language: string;
|
||||
filename: string;
|
||||
el?: HTMLElement;
|
||||
autoComplete?: {
|
||||
props?: any;
|
||||
open?: boolean;
|
||||
overrideList?: Completion[];
|
||||
};
|
||||
|
||||
constructor(opts?: BaseEditorOpts) {
|
||||
this.filename = opts?.filename || '';
|
||||
this.language = opts?.language || 'typescript';
|
||||
if (this.filename && !opts?.language) {
|
||||
// 根据文件名自动判断语言
|
||||
const fileType = getFileType(this.filename);
|
||||
this.language = fileType || 'typescript';
|
||||
}
|
||||
this.autoComplete = opts?.autoComplete || { open: true, overrideList: [] };
|
||||
this.onChangeCompartment = new Compartment(); // 创建一个用于 onChange 的独立扩展
|
||||
}
|
||||
onEditorChange(view: EditorView) {
|
||||
console.log('onChange', view);
|
||||
}
|
||||
getFileType(filename: string) {
|
||||
return getFileType(filename);
|
||||
}
|
||||
createEditor(el: HTMLElement) {
|
||||
this.el = el;
|
||||
const onChangeCompartment = this.onChangeCompartment!;
|
||||
const language = this.language;
|
||||
const extensions: Extension[] = [
|
||||
vscodeLight,
|
||||
formatKeymap,
|
||||
keymap.of(defaultKeymap), //
|
||||
// history(),
|
||||
];
|
||||
if (this.autoComplete?.open) {
|
||||
extensions.push(
|
||||
autocompletion({
|
||||
activateOnTyping: true, // 输入时触发补全
|
||||
closeOnBlur: true, // 失去焦点时关闭补全
|
||||
activateOnTypingDelay: 2000, // 输入时触发补全
|
||||
override: this.autoComplete?.overrideList || [],
|
||||
...this.autoComplete?.props,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (language === 'typescript') {
|
||||
extensions.push(javascript({ typescript: true }));
|
||||
} else if (language === 'javascript') {
|
||||
extensions.push(javascript());
|
||||
} else if (language === 'markdown') {
|
||||
extensions.push(markdown());
|
||||
} else if (language === 'html') {
|
||||
extensions.push(html());
|
||||
} else if (language === 'css') {
|
||||
extensions.push(css());
|
||||
} else if (language === 'json') {
|
||||
extensions.push(json());
|
||||
} else if (language === 'yaml') {
|
||||
extensions.push(yaml());
|
||||
}
|
||||
|
||||
this.editor = new EditorView({
|
||||
parent: el,
|
||||
extensions: [
|
||||
...extensions, //
|
||||
onChangeCompartment.of([]),
|
||||
],
|
||||
});
|
||||
}
|
||||
resetEditor(el?: HTMLElement) {
|
||||
this.editor?.destroy?.();
|
||||
this.createEditor(el || (this.el as HTMLElement));
|
||||
}
|
||||
setLanguage(language: string, el?: HTMLElement) {
|
||||
this.language = language;
|
||||
this.editor?.destroy?.();
|
||||
this.createEditor(el || (this.el as HTMLElement));
|
||||
}
|
||||
setContent(content: string) {
|
||||
this.editor?.dispatch({
|
||||
changes: { from: 0, to: this.editor?.state.doc.length, insert: content },
|
||||
});
|
||||
}
|
||||
getContent() {
|
||||
return this.editor?.state.doc.toString();
|
||||
}
|
||||
destroyEditor() {
|
||||
this.editor?.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseEditor;
|
94
packages/codemirror/src/editor/modules/ai-completions.ts
Normal file
94
packages/codemirror/src/editor/modules/ai-completions.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { CompletionResult, CompletionSource } from '@codemirror/autocomplete';
|
||||
import { Transaction } from '@codemirror/state';
|
||||
|
||||
export async function fetchAICompletions(context) {
|
||||
const userInput = context.matchBefore(/\w*/);
|
||||
if (!userInput) return null;
|
||||
|
||||
const docText = context.state.doc.toString(); // 获取完整文档内容
|
||||
const cursorPos = context.pos; // 获取光标位置
|
||||
|
||||
// 添加提示词
|
||||
const promptText = `请根据以下代码和光标位置提供代码补全建议:
|
||||
|
||||
代码:
|
||||
${docText}
|
||||
|
||||
光标位置:${cursorPos}
|
||||
|
||||
请提供适当的代码补全。`;
|
||||
|
||||
const aiResponse = await fetch('http://192.168.31.220:11434/api/generate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: promptText,
|
||||
// 如果 Ollama 支持,可以传递其他参数,如模型名称
|
||||
// model: 'your-model-name',
|
||||
model: 'qwen2.5-coder:14b',
|
||||
// model: 'qwen2.5:14b',
|
||||
stream: false,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await aiResponse.json();
|
||||
const data = result;
|
||||
// 提取字段
|
||||
const createdAt = new Date(data.created_at); // 转换为日期对象
|
||||
const isDone = data.done;
|
||||
const doneReason = data.done_reason;
|
||||
const modelName = data.model;
|
||||
const responseText = data.response;
|
||||
const matchCodeBlock = responseText.match(/```javascript\n([\s\S]*?)\n```/);
|
||||
let codeBlock = '';
|
||||
if (matchCodeBlock) {
|
||||
const codeBlock = matchCodeBlock[1]; // 提取代码块
|
||||
console.log('补全代码:', codeBlock);
|
||||
}
|
||||
|
||||
const suggestions = [
|
||||
{
|
||||
label: codeBlock,
|
||||
type: 'text',
|
||||
},
|
||||
];
|
||||
return {
|
||||
from: userInput.from,
|
||||
options: suggestions,
|
||||
validFor: /^\w*$/,
|
||||
};
|
||||
}
|
||||
|
||||
// type AiCompletion = Readonly<CompletionSource>;
|
||||
export const testAiCompletion = async (context: any): Promise<CompletionResult | null> => {
|
||||
const userInput = context.matchBefore(/\w*/);
|
||||
if (!userInput) return null;
|
||||
const docText = context.state.doc.toString(); // 获取完整文档内容
|
||||
const cursorPos = context.pos; // 获取光标位置
|
||||
console.log('testAiCompletion', userInput, docText, cursorPos);
|
||||
|
||||
return {
|
||||
from: userInput.from,
|
||||
options: [
|
||||
{
|
||||
label: '123 测试 skflskdf ',
|
||||
type: 'text',
|
||||
apply: (state, completion, from, to) => {
|
||||
console.log('apply', state, completion, from, to);
|
||||
const newText = '123 测试 skflskdf ';
|
||||
state.dispatch({
|
||||
changes: { from, to, insert: newText },
|
||||
});
|
||||
},
|
||||
info: '这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母这是一个长文本上的反馈是冷酷的父母离开时的父母 这是一个长文本上的反馈是冷酷的父母离开时的父母',
|
||||
},
|
||||
{
|
||||
label: '456',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
validFor: /^\w*$/,
|
||||
};
|
||||
};
|
92
packages/codemirror/src/editor/modules/keymap.ts
Normal file
92
packages/codemirror/src/editor/modules/keymap.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { EditorView, keymap } from '@codemirror/view';
|
||||
import { defaultKeymap, indentSelection, indentWithTab, undo, history } from '@codemirror/commands';
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import prettier from 'prettier';
|
||||
// import parserBabel from 'prettier/plugins/babel';
|
||||
import parserEstree from 'prettier/plugins/estree';
|
||||
// import parserHtml from 'prettier/plugins/html';
|
||||
import parserTypescript from 'prettier/plugins/typescript';
|
||||
import { autocompletion, CompletionContext } from '@codemirror/autocomplete';
|
||||
|
||||
// 格式化函数
|
||||
// Function to format the code using Prettier
|
||||
async function formatCode(view: EditorView) {
|
||||
const editor = view;
|
||||
const code = editor.state.doc.toString();
|
||||
try {
|
||||
const formattedCode = await prettier.format(code, {
|
||||
parser: 'typescript',
|
||||
plugins: [parserEstree, parserTypescript],
|
||||
});
|
||||
|
||||
editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor.state.doc.length,
|
||||
insert: formattedCode.trim(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error formatting code:', error);
|
||||
}
|
||||
}
|
||||
// export const TabFn = (view: EditorView) => {
|
||||
// const { state } = view;
|
||||
// const selection = state.selection.main;
|
||||
// // 判断是否存在补全上下文
|
||||
// if (state.field(autocompletion).active?.length) {
|
||||
// // 插入当前选中的补全内容
|
||||
// const active = state.field(autocompletion).active[0];
|
||||
// const selected = active?.options[active.selected]?.label;
|
||||
// if (selected) {
|
||||
// view.dispatch({
|
||||
// changes: { from: selection.from, to: selection.to, insert: selected },
|
||||
// });
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// };
|
||||
|
||||
// 自定义 Tab 键行为
|
||||
function customIndentWithTab(view: EditorView) {
|
||||
const { state, dispatch } = view;
|
||||
const { from, to } = state.selection.main;
|
||||
const _indentUnit = state.facet(indentUnit) as string;
|
||||
|
||||
// 计算插入的缩进
|
||||
const indent = _indentUnit.repeat(1);
|
||||
|
||||
dispatch({
|
||||
changes: { from, to, insert: indent },
|
||||
selection: { anchor: from + indent.length },
|
||||
});
|
||||
|
||||
return true; // 表示按键事件被处理
|
||||
}
|
||||
export const formatKeymap = keymap.of([
|
||||
{
|
||||
// bug, 必须小写
|
||||
key: 'alt-shift-f', // 快捷键绑定
|
||||
// mac: 'cmd-shift-f',
|
||||
run: (view) => {
|
||||
formatCode(view);
|
||||
return true; // 表示按键事件被处理
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Tab',
|
||||
run: customIndentWithTab, // 使用自定义的缩进函数
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-z', // Windows 撤销快捷键
|
||||
// key: 'Alt-z',
|
||||
mac: 'Cmd-z', // Mac 撤销快捷键
|
||||
run: (view) => {
|
||||
console.log('undo', view);
|
||||
return undo(view); // 表示按键事件被处理
|
||||
},
|
||||
},
|
||||
...defaultKeymap, // 默认快捷键
|
||||
]);
|
||||
// console.log('formatKeymap', defaultKeymap);
|
31
packages/codemirror/src/editor/utils/get-file-type.ts
Normal file
31
packages/codemirror/src/editor/utils/get-file-type.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export const getExtension = (filename: string) => {
|
||||
const extension = filename.split('.').pop();
|
||||
if (!extension) return '';
|
||||
return extension;
|
||||
};
|
||||
export const supportedExtensions = ['ts', 'js', 'md', 'json', 'yaml', 'yml', 'css', 'html'];
|
||||
|
||||
export function getFileType(filename: string) {
|
||||
const extension = getExtension(filename);
|
||||
if (!supportedExtensions.includes(extension)) return '';
|
||||
switch (extension) {
|
||||
case 'ts':
|
||||
return 'typescript';
|
||||
case 'js':
|
||||
return 'javascript';
|
||||
case 'md':
|
||||
return 'markdown';
|
||||
case 'json':
|
||||
return 'json';
|
||||
case 'yaml':
|
||||
return 'yaml';
|
||||
case 'yml':
|
||||
return 'yaml';
|
||||
case 'css':
|
||||
return 'css';
|
||||
case 'html':
|
||||
return 'html';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
3
packages/codemirror/src/global.css
Normal file
3
packages/codemirror/src/global.css
Normal file
@ -0,0 +1,3 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
@import './style.css';
|
4
packages/codemirror/src/main.ts
Normal file
4
packages/codemirror/src/main.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { render } from './pages/Bootstrap';
|
||||
import './styles.css';
|
||||
|
||||
render('#ai-root');
|
40
packages/codemirror/src/pages/App.tsx
Normal file
40
packages/codemirror/src/pages/App.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { useMemo } from 'react';
|
||||
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import { FileEditor } from './file-editor/FileEditor';
|
||||
|
||||
export const InitProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
type AppProvider = {
|
||||
key: string;
|
||||
use: boolean;
|
||||
};
|
||||
export type AppProps = {
|
||||
providers?: AppProvider[];
|
||||
noProvider?: boolean;
|
||||
/**
|
||||
* 是否是单个应用
|
||||
* 默认是单个应用模块。
|
||||
*/
|
||||
isSingleApp?: boolean;
|
||||
};
|
||||
export const App = ({ providers, noProvider, isSingleApp = true }: AppProps) => {
|
||||
const children = useMemo(() => {
|
||||
return (
|
||||
<InitProvider>
|
||||
<FileEditor />
|
||||
</InitProvider>
|
||||
);
|
||||
}, []);
|
||||
if (noProvider) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
return (
|
||||
<CustomThemeProvider>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</CustomThemeProvider>
|
||||
);
|
||||
};
|
88
packages/codemirror/src/pages/Bootstrap.tsx
Normal file
88
packages/codemirror/src/pages/Bootstrap.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App, AppProps } from './App';
|
||||
import React from 'react';
|
||||
|
||||
export class ReactRenderer {
|
||||
component: any;
|
||||
element: HTMLElement;
|
||||
ref: React.RefObject<any>;
|
||||
props: any;
|
||||
root: any;
|
||||
constructor(component: any, { props, className }: any) {
|
||||
this.component = component;
|
||||
const el = document.createElement('div');
|
||||
this.element = el;
|
||||
this.ref = React.createRef();
|
||||
this.props = {
|
||||
...props,
|
||||
ref: this.ref,
|
||||
};
|
||||
el.className = className;
|
||||
this.root = createRoot(this.element);
|
||||
this.render();
|
||||
}
|
||||
|
||||
updateProps(props: any) {
|
||||
this.props = {
|
||||
...this.props,
|
||||
...props,
|
||||
};
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.root.render(React.createElement(this.component, this.props));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.root.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactRenderer;
|
||||
export const randomId = () => {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
};
|
||||
export const render = (el: HTMLElement | string, props?: AppProps) => {
|
||||
const root = typeof el === 'string' ? document.querySelector(el) : el;
|
||||
if (!root) {
|
||||
console.error('root not found');
|
||||
return;
|
||||
}
|
||||
const hasResourceApp = window.context?.codemirrorApp;
|
||||
if (hasResourceApp) {
|
||||
const render = hasResourceApp as ReactRenderer;
|
||||
render.updateProps({
|
||||
props: { t: randomId(), ...props },
|
||||
});
|
||||
root.innerHTML = '';
|
||||
root.appendChild(render.element);
|
||||
} else {
|
||||
const renderer = new ReactRenderer(App, {
|
||||
props: { ...props },
|
||||
className: 'codemirror-root w-full h-full',
|
||||
});
|
||||
if (window.context) {
|
||||
window.context.codemirrorApp = renderer;
|
||||
} else {
|
||||
window.context = {
|
||||
codemirrorApp: renderer,
|
||||
};
|
||||
}
|
||||
root.appendChild(renderer.element);
|
||||
}
|
||||
};
|
||||
|
||||
export const unmount = (el: HTMLElement | string) => {
|
||||
const root = typeof el === 'string' ? document.querySelector(el) : el;
|
||||
if (!root) {
|
||||
console.error('root not found');
|
||||
return;
|
||||
}
|
||||
const hasResourceApp = window.context?.codemirrorApp;
|
||||
if (hasResourceApp) {
|
||||
const render = hasResourceApp as ReactRenderer;
|
||||
render.destroy();
|
||||
window.context.codemirrorApp = null;
|
||||
}
|
||||
};
|
17
packages/codemirror/src/pages/file-editor/FileEditor.tsx
Normal file
17
packages/codemirror/src/pages/file-editor/FileEditor.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { BaseEditor } from '../../editor/editor';
|
||||
|
||||
export const FileEditor = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
const editor = new BaseEditor();
|
||||
if (containerRef.current) {
|
||||
editor.createEditor(containerRef.current);
|
||||
}
|
||||
return () => {
|
||||
editor.destroyEditor();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={containerRef} className='w-full h-full'></div>;
|
||||
};
|
3
packages/codemirror/src/style.css
Normal file
3
packages/codemirror/src/style.css
Normal file
@ -0,0 +1,3 @@
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
}
|
39
packages/codemirror/tsconfig.json
Normal file
39
packages/codemirror/tsconfig.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"baseUrl": "./",
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"node_modules/@kevisual/types",
|
||||
],
|
||||
"paths": {
|
||||
"@kevisual/codemirror/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
]
|
||||
}
|
83
packages/codemirror/vite.config.mjs
Normal file
83
packages/codemirror/vite.config.mjs
Normal file
@ -0,0 +1,83 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||
|
||||
const plugins = [basicSsl()];
|
||||
// const plugins = [];
|
||||
plugins.push(tailwindcss());
|
||||
let proxy = {};
|
||||
if (true) {
|
||||
proxy = {
|
||||
'/api': {
|
||||
target: 'https://kevisual.silkyai.cn',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
cookieDomainRewrite: 'localhost',
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
},
|
||||
'/api/router': {
|
||||
target: 'wss://kevisual.silkyai.cn',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewriteWsOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
},
|
||||
'/user/login': {
|
||||
target: 'https://kevisual.silkyai.cn',
|
||||
changeOrigin: true,
|
||||
cookieDomainRewrite: 'localhost',
|
||||
rewrite: (path) => path.replace(/^\/user/, '/user'),
|
||||
},
|
||||
};
|
||||
}
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), ...plugins],
|
||||
|
||||
css: {
|
||||
postcss: {},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@kevisual/codemirror': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
define: {
|
||||
DEV_SERVER: JSON.stringify(process.env.NODE_ENV === 'development'),
|
||||
BASE_NAME: JSON.stringify('/root/codemirror/'),
|
||||
},
|
||||
base: './',
|
||||
// base: isDev ? '/' : '/root/codemirror/',
|
||||
build: {
|
||||
rollupOptions: {
|
||||
// external: ['react', 'react-dom'],
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 6022,
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/system/lib': {
|
||||
target: 'https://kevisual.xiongxiao.me',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/api': {
|
||||
target: 'http://localhost:4005',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
cookieDomainRewrite: 'localhost',
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
},
|
||||
'/api/router': {
|
||||
target: 'ws://localhost:4005',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewriteWsOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
},
|
||||
...proxy,
|
||||
},
|
||||
},
|
||||
});
|
@ -17,7 +17,8 @@
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^6.4.7",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.54.2"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
|
47
packages/components/src/input/index.tsx
Normal file
47
packages/components/src/input/index.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { FormControlLabel, TextField } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
export const InputControl = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
return (
|
||||
<TextField
|
||||
variant='outlined'
|
||||
size='small'
|
||||
name={name}
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
type FormProps = {
|
||||
onSubmit?: (data: any) => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const FormDemo = (props: FormProps) => {
|
||||
const { control, handleSubmit } = useForm();
|
||||
const { onSubmit = () => {}, children } = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
name='name'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
rules={{ required: 'Name is required' }}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
label='Name'
|
||||
variant='outlined'
|
||||
margin='normal'
|
||||
fullWidth //
|
||||
error={!!error}
|
||||
helperText={<>{error?.message}</>}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
91
packages/components/src/modal/Confirm.tsx
Normal file
91
packages/components/src/modal/Confirm.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
export const Confirm = ({
|
||||
open,
|
||||
onClose,
|
||||
title,
|
||||
content,
|
||||
onConfirm,
|
||||
confirmText = '确认',
|
||||
cancelText = '取消',
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
content: string;
|
||||
onConfirm?: () => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} aria-labelledby='alert-dialog-title' aria-describedby='alert-dialog-description'>
|
||||
<DialogTitle id='alert-dialog-title' className='text-secondary min-w-[300px]'>
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id='alert-dialog-description'>{content}</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color='primary'>
|
||||
{cancelText || '取消'}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} variant='contained' color='primary' autoFocus>
|
||||
{confirmText || '确认'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
type Fn = () => void;
|
||||
export const useConfirm = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
const fns = useRef<{
|
||||
onConfirm: Fn;
|
||||
onCancel: Fn;
|
||||
confirmText: string;
|
||||
cancelText: string;
|
||||
}>({
|
||||
onConfirm: () => {},
|
||||
onCancel: () => {},
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
});
|
||||
return {
|
||||
contextHolder: (
|
||||
<Confirm
|
||||
open={open}
|
||||
confirmText={fns.current.confirmText}
|
||||
cancelText={fns.current.cancelText}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
fns.current.onCancel();
|
||||
}}
|
||||
title={title}
|
||||
content={content}
|
||||
onConfirm={fns.current.onConfirm}
|
||||
/>
|
||||
),
|
||||
confirm: (
|
||||
title: string,
|
||||
content: string,
|
||||
opts?: {
|
||||
onConfirm: () => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
onCancel?: () => void;
|
||||
},
|
||||
) => {
|
||||
setOpen(true);
|
||||
setTitle(title);
|
||||
setContent(content);
|
||||
fns.current.onConfirm = opts?.onConfirm || (() => {});
|
||||
fns.current.onCancel = opts?.onCancel || (() => {});
|
||||
fns.current.confirmText = opts?.confirmText || '确认';
|
||||
fns.current.cancelText = opts?.cancelText || '取消';
|
||||
},
|
||||
};
|
||||
};
|
@ -6,3 +6,66 @@
|
||||
--color-success: #28a745;
|
||||
--scrollbar-color: #ffc107; /* 滚动条颜色 */
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
/* font-family */
|
||||
@utility font-family-mon {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
@utility font-family-rob {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
@utility font-family-int {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
@utility font-family-orb {
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
}
|
||||
@utility font-family-din {
|
||||
font-family: 'DIN', sans-serif;
|
||||
}
|
||||
|
||||
@utility flex-row-center {
|
||||
@apply flex flex-row items-center justify-center;
|
||||
}
|
||||
@utility flex-col-center {
|
||||
@apply flex flex-col items-center justify-center;
|
||||
}
|
||||
|
||||
@utility scrollbar {
|
||||
overflow: auto;
|
||||
/* 整个滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
/* 滚动条有滑块的轨道部分 */
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: transparent;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
cursor: pointer;
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块hover */
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #999999;
|
||||
}
|
||||
|
||||
/* 同时有垂直和水平滚动条时交汇的部分 */
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: block; /* 修复交汇时出现的白块 */
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Apps</title>
|
||||
<link rel="stylesheet" href="./src/assets/index.css">
|
||||
<link rel="stylesheet" href="./src/global.css">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
|
@ -1,67 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
@layer components {
|
||||
.test-loading {
|
||||
@apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
|
||||
}
|
||||
}
|
||||
:root {
|
||||
--scrollbar-color: #ffbf00;
|
||||
--primary-color: #ffc107;
|
||||
--secondary-color: #ffa000;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#ai-bot-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -100px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) #fff;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-track {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.ant-select-outlined.ant-select-multiple .ant-select-selection-item {
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
svg {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
.ant-select-selection-item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.ant-select {
|
||||
.ant-select-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.ant-picker-input {
|
||||
.ant-picker-suffix,
|
||||
.ant-picker-clear {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
4
packages/resources/src/global.css
Normal file
4
packages/resources/src/global.css
Normal file
@ -0,0 +1,4 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
@import './style.css';
|
@ -1,6 +1,5 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { theme } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { Left } from './layout/Left';
|
||||
import { Main } from './main/index';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
@ -120,11 +119,11 @@ export const App = ({ providers, noProvider, isSingleApp = true }: AppProps) =>
|
||||
return <>{children}</>;
|
||||
}
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CustomThemeProvider>
|
||||
<AntdConfigProvider>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</AntdConfigProvider>
|
||||
</ThemeProvider>
|
||||
</CustomThemeProvider>
|
||||
);
|
||||
};
|
||||
|
@ -6,107 +6,116 @@ import { getIcon } from '../FileIcon';
|
||||
import { Download, Trash } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file';
|
||||
import { useConfirm } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
|
||||
export const FileTable = () => {
|
||||
const { list, prefix, download, onOpenPrefix, deleteFile } = useResourceStore();
|
||||
const { setOpenDrawer, setPrefix } = useResourceFileStore();
|
||||
const { confirm, contextHolder } = useConfirm();
|
||||
return (
|
||||
<TableContainer
|
||||
className='scrollbar'
|
||||
sx={{
|
||||
'&': {
|
||||
// scrollbarWidth: 'none',
|
||||
// scrollbarColor: '#888 #fff',
|
||||
},
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px !important',
|
||||
height: '4px !important',
|
||||
background: '#fff',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#888',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
}}
|
||||
component={Paper}>
|
||||
<Table
|
||||
<>
|
||||
{contextHolder}
|
||||
<TableContainer
|
||||
className='scrollbar'
|
||||
sx={{
|
||||
minWidth: 650,
|
||||
'&': {
|
||||
// scrollbarWidth: 'none',
|
||||
// scrollbarColor: '#888 #fff',
|
||||
},
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px !important',
|
||||
height: '4px !important',
|
||||
background: '#fff',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#888',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
}}
|
||||
aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
|
||||
<TableCell sx={{ minWidth: 180 }}>Last Modified</TableCell>
|
||||
<TableCell sx={{ minWidth: 110 }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{list.map((row) => {
|
||||
const isFile = !!row.name;
|
||||
return (
|
||||
<TableRow key={row.etag || row.name || row.prefix}>
|
||||
<TableCell>
|
||||
<div
|
||||
className={clsx('flex items-center gap-2 max-w-[300px] line-clamp-2 text-ellipsis', {
|
||||
'cursor-pointer': true,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
if (!row.name) {
|
||||
onOpenPrefix(row.prefix || '');
|
||||
} else {
|
||||
setPrefix(row.name || '');
|
||||
setOpenDrawer(true);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<div className='shrink-0'>{getIcon(row.name)}</div>
|
||||
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
|
||||
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
|
||||
<TableCell>
|
||||
{isFile && (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
component={Paper}>
|
||||
<Table
|
||||
sx={{
|
||||
minWidth: 650,
|
||||
}}
|
||||
aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
|
||||
<TableCell sx={{ minWidth: 180 }}>Last Modified</TableCell>
|
||||
<TableCell sx={{ minWidth: 110 }}>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{list.map((row) => {
|
||||
const isFile = !!row.name;
|
||||
return (
|
||||
<TableRow key={row.etag || row.name || row.prefix}>
|
||||
<TableCell>
|
||||
<div
|
||||
className={clsx('flex items-center gap-2 max-w-[300px] line-clamp-2 text-ellipsis', {
|
||||
'cursor-pointer': true,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
if (!row.name) {
|
||||
onOpenPrefix(row.prefix || '');
|
||||
} else {
|
||||
setPrefix(row.name || '');
|
||||
setOpenDrawer(true);
|
||||
}
|
||||
e.stopPropagation();
|
||||
download(row);
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Download />
|
||||
</Button>
|
||||
)}
|
||||
{isFile && (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='error'
|
||||
className='ml-2!'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteFile(row);
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Trash />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<div className='shrink-0'>{getIcon(row.name)}</div>
|
||||
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
|
||||
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
|
||||
<TableCell>
|
||||
{isFile && (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
download(row);
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Download />
|
||||
</Button>
|
||||
)}
|
||||
{isFile && (
|
||||
<Button
|
||||
variant='contained'
|
||||
color='error'
|
||||
className='ml-2!'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
confirm('删除文件', '确定删除该文件吗?', {
|
||||
onConfirm: () => {
|
||||
deleteFile(row);
|
||||
},
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
color: 'white',
|
||||
minWidth: '32px',
|
||||
padding: '4px',
|
||||
}}>
|
||||
<Trash />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,37 +1,16 @@
|
||||
import React, { Fragment, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
CssBaseline,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Button,
|
||||
useTheme,
|
||||
ListItemIcon,
|
||||
Tooltip,
|
||||
Container,
|
||||
} from '@mui/material';
|
||||
import { Box, Divider, List, ListItem, ListItemButton, ListItemText, useTheme, ListItemIcon, Tooltip, Container } from '@mui/material';
|
||||
import { ActiveMenu, useLayoutStore } from '../store/layout';
|
||||
import { activeMenuList } from '../modules/MenuList';
|
||||
import { amber } from '@mui/material/colors';
|
||||
import { lighten, rgbToHex } from '@mui/material/styles';
|
||||
import { SquareMenu } from 'lucide-react';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
export type LeftProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const Left = ({ children }: LeftProps) => {
|
||||
const { setActiveMenu, init, showLabel, setShowLabel } = useLayoutStore();
|
||||
const theme = useTheme();
|
||||
// console.log(theme.palette.primary.main, rgbToHex(lighten(theme.palette.primary.main, 0.4)));
|
||||
// console.log(theme.palette.divider);
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
@ -49,23 +28,24 @@ export const Left = ({ children }: LeftProps) => {
|
||||
}, [activeMenuList]);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', backgroundColor: amber[50] }}>
|
||||
<Box sx={{ height: '100%', display: 'flex', backgroundColor: amber[50] }}>
|
||||
<Box component='nav' sx={{ flexShrink: { sm: 0 } }}>
|
||||
<Box
|
||||
sx={{
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
|
||||
height: '100%',
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
height: '100vh',
|
||||
borderRight: 1,
|
||||
borderColor: theme.palette.divider,
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<List sx={{ flexGrow: 1 }} key={list.length}>
|
||||
<List sx={{ flexGrow: 1, height: '100%' }} key={list.length}>
|
||||
{list.map((item) => (
|
||||
<Fragment key={item.value}>
|
||||
<ListItem disablePadding>
|
||||
@ -106,7 +86,7 @@ export const Left = ({ children }: LeftProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, height: '100vh', overflow: 'hidden' }}>
|
||||
<Box sx={{ flexGrow: 1, height: '100%', overflow: 'hidden' }}>
|
||||
<Container sx={{ width: '100%', height: '100%' }}>{children}</Container>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -18,5 +18,5 @@ export const Main = () => {
|
||||
if (activeMenu === ActiveMenu.Statistic) {
|
||||
return <Statistic />;
|
||||
}
|
||||
return <div className='h-full'>{activeMenu}</div>;
|
||||
return <div className='h-full overflow-hidden'>{activeMenu}</div>;
|
||||
};
|
||||
|
@ -9,10 +9,12 @@ type ConvertOpts = {
|
||||
version?: string;
|
||||
username?: string;
|
||||
directory?: string;
|
||||
isPublic?: boolean;
|
||||
filename?: string;
|
||||
};
|
||||
|
||||
export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const { directory, appKey, version, username } = opts;
|
||||
const { directory, appKey, version, username, isPublic } = opts;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
@ -21,11 +23,16 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
}
|
||||
|
||||
const taskId = nanoid();
|
||||
const filename = file.name;
|
||||
const filename = opts.filename || file.name;
|
||||
const load = toast.loading(`${filename} 上传中...`);
|
||||
NProgress.start();
|
||||
// const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||
const eventSource = new EventSource('/api/s1/events?taskId=' + taskId);
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('taskId', taskId);
|
||||
if (isPublic) {
|
||||
searchParams.set('public', 'true');
|
||||
}
|
||||
const eventSource = new EventSource('/api/s1/events?' + searchParams.toString());
|
||||
// 监听服务器推送的进度更新
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log('Progress update:', event.data);
|
||||
@ -60,9 +67,10 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', chunk, file.name);
|
||||
formData.append('file', chunk, filename);
|
||||
formData.append('chunkIndex', currentChunk.toString());
|
||||
formData.append('totalChunks', totalChunks.toString());
|
||||
const isLast = currentChunk === totalChunks - 1;
|
||||
if (directory) {
|
||||
formData.append('directory', directory);
|
||||
}
|
||||
@ -83,10 +91,12 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
},
|
||||
}).then((response) => response.json());
|
||||
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||
eventSource.close();
|
||||
NProgress.done();
|
||||
toast.dismiss(load);
|
||||
resolve(res);
|
||||
if (isLast) {
|
||||
NProgress.done();
|
||||
eventSource.close();
|
||||
toast.dismiss(load);
|
||||
resolve(res);
|
||||
}
|
||||
// console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
|
||||
} catch (error) {
|
||||
console.log('Error uploading chunk', error);
|
||||
|
@ -1,3 +1,22 @@
|
||||
:root {
|
||||
--scrollbar-color: #ffbf00;
|
||||
--primary-color: #ffc107;
|
||||
--secondary-color: #ffa000;
|
||||
}
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#ai-bot-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -100px;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) #fff;
|
||||
@ -16,4 +35,3 @@
|
||||
.scrollbar::-webkit-scrollbar-track {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
|
736
pnpm-lock.yaml
generated
736
pnpm-lock.yaml
generated
@ -24,8 +24,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:packages/components
|
||||
'@kevisual/codemirror':
|
||||
specifier: ^0.0.2
|
||||
version: 0.0.2
|
||||
specifier: workspace:*
|
||||
version: link:packages/codemirror
|
||||
'@kevisual/container':
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0(@emotion/css@11.13.4)(@types/react@19.0.11)(crypto-js@4.2.0)(eventemitter3@5.0.1)(immer@10.1.1)(react@19.0.0)(rollup@4.34.7)(typescript@5.8.2)
|
||||
@ -52,7 +52,7 @@ importers:
|
||||
version: 4.0.14(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1))
|
||||
'@uiw/react-textarea-code-editor':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(@babel/runtime@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
version: 3.1.0(@babel/runtime@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@xyflow/react':
|
||||
specifier: ^12.4.4
|
||||
version: 12.4.4(@types/react@19.0.11)(immer@10.1.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -77,6 +77,12 @@ importers:
|
||||
eventemitter3:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
i18next:
|
||||
specifier: ^24.2.3
|
||||
version: 24.2.3(typescript@5.8.2)
|
||||
i18next-http-backend:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
immer:
|
||||
specifier: ^10.1.1
|
||||
version: 10.1.1
|
||||
@ -95,6 +101,12 @@ importers:
|
||||
react-dom:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-hook-form:
|
||||
specifier: ^7.54.2
|
||||
version: 7.54.2(react@19.0.0)
|
||||
react-i18next:
|
||||
specifier: ^15.4.1
|
||||
version: 15.4.1(i18next@24.2.3(typescript@5.8.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react-resizable-panels:
|
||||
specifier: ^2.1.7
|
||||
version: 2.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -199,6 +211,139 @@ importers:
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1)
|
||||
|
||||
packages/codemirror:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete':
|
||||
specifier: ^6.18.6
|
||||
version: 6.18.6
|
||||
'@codemirror/basic-setup':
|
||||
specifier: ^0.20.0
|
||||
version: 0.20.0
|
||||
'@codemirror/commands':
|
||||
specifier: ^6.8.0
|
||||
version: 6.8.0
|
||||
'@codemirror/history':
|
||||
specifier: ^0.19.2
|
||||
version: 0.19.2
|
||||
'@codemirror/lang-css':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1
|
||||
'@codemirror/lang-html':
|
||||
specifier: ^6.4.9
|
||||
version: 6.4.9
|
||||
'@codemirror/lang-javascript':
|
||||
specifier: ^6.2.3
|
||||
version: 6.2.3
|
||||
'@codemirror/lang-json':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
'@codemirror/lang-markdown':
|
||||
specifier: ^6.3.2
|
||||
version: 6.3.2
|
||||
'@codemirror/lang-yaml':
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2
|
||||
'@codemirror/language':
|
||||
specifier: ^6.11.0
|
||||
version: 6.11.0
|
||||
'@codemirror/state':
|
||||
specifier: ^6.5.2
|
||||
version: 6.5.2
|
||||
'@codemirror/view':
|
||||
specifier: ^6.36.4
|
||||
version: 6.36.4
|
||||
'@emotion/react':
|
||||
specifier: ^11.14.0
|
||||
version: 11.14.0(@types/react@19.0.11)(react@19.0.0)
|
||||
'@emotion/styled':
|
||||
specifier: ^11.14.0
|
||||
version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.11)(react@19.0.0))(@types/react@19.0.11)(react@19.0.0)
|
||||
'@kevisual/center-components':
|
||||
specifier: workspace:*
|
||||
version: link:../components
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.9
|
||||
version: 0.0.9
|
||||
'@kevisual/store':
|
||||
specifier: ^0.0.2
|
||||
version: 0.0.2(rollup@4.34.7)
|
||||
'@mui/material':
|
||||
specifier: ^6.4.8
|
||||
version: 6.4.8(@emotion/react@11.14.0(@types/react@19.0.11)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.11)(react@19.0.0))(@types/react@19.0.11)(react@19.0.0))(@types/react@19.0.11)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
'@types/nprogress':
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3
|
||||
'@uiw/codemirror-theme-duotone':
|
||||
specifier: ^4.23.10
|
||||
version: 4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
|
||||
'@uiw/codemirror-theme-vscode':
|
||||
specifier: ^4.23.10
|
||||
version: 4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
|
||||
'@vitejs/plugin-basic-ssl':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(vite@6.2.2(@types/node@22.13.10)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.5.1))
|
||||
codemirror:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
highlight.js:
|
||||
specifier: ^11.11.1
|
||||
version: 11.11.1
|
||||
immer:
|
||||
specifier: ^10.1.1
|
||||
version: 10.1.1
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
lucide-react:
|
||||
specifier: ^0.483.0
|
||||
version: 0.483.0(react@19.0.0)
|
||||
marked:
|
||||
specifier: ^15.0.7
|
||||
version: 15.0.7
|
||||
marked-highlight:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(marked@15.0.7)
|
||||
nanoid:
|
||||
specifier: ^5.1.5
|
||||
version: 5.1.5
|
||||
nprogress:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
version: 3.5.3
|
||||
pretty-bytes:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
react:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0
|
||||
react-datepicker:
|
||||
specifier: ^8.2.1
|
||||
version: 8.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react-dom:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-dropzone:
|
||||
specifier: ^14.3.8
|
||||
version: 14.3.8(react@19.0.0)
|
||||
react-toastify:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
zustand:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(@types/react@19.0.11)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0))
|
||||
devDependencies:
|
||||
'@kevisual/types':
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
|
||||
packages/components:
|
||||
dependencies:
|
||||
'@emotion/react':
|
||||
@ -216,6 +361,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-hook-form:
|
||||
specifier: ^7.54.2
|
||||
version: 7.54.2(react@19.0.0)
|
||||
devDependencies:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
@ -441,6 +589,10 @@ packages:
|
||||
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.26.10':
|
||||
resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -457,6 +609,88 @@ packages:
|
||||
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@codemirror/autocomplete@0.20.3':
|
||||
resolution: {integrity: sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==}
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
|
||||
|
||||
'@codemirror/basic-setup@0.20.0':
|
||||
resolution: {integrity: sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==}
|
||||
deprecated: In version 6.0, this package has been renamed to just 'codemirror'
|
||||
|
||||
'@codemirror/commands@0.20.0':
|
||||
resolution: {integrity: sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==}
|
||||
|
||||
'@codemirror/commands@6.8.0':
|
||||
resolution: {integrity: sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==}
|
||||
|
||||
'@codemirror/history@0.19.2':
|
||||
resolution: {integrity: sha512-unhP4t3N2smzmHoo/Yio6ueWi+il8gm9VKrvi6wlcdGH5fOfVDNkmjHQ495SiR+EdOG35+3iNebSPYww0vN7ow==}
|
||||
deprecated: As of 0.20.0, this package has been merged into @codemirror/commands
|
||||
|
||||
'@codemirror/lang-css@6.3.1':
|
||||
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
|
||||
|
||||
'@codemirror/lang-html@6.4.9':
|
||||
resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==}
|
||||
|
||||
'@codemirror/lang-javascript@6.2.3':
|
||||
resolution: {integrity: sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==}
|
||||
|
||||
'@codemirror/lang-json@6.0.1':
|
||||
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
|
||||
|
||||
'@codemirror/lang-markdown@6.3.2':
|
||||
resolution: {integrity: sha512-c/5MYinGbFxYl4itE9q/rgN/sMTjOr8XL5OWnC+EaRMLfCbVUmmubTJfdgpfcSS2SCaT7b+Q+xi3l6CgoE+BsA==}
|
||||
|
||||
'@codemirror/lang-yaml@6.1.2':
|
||||
resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==}
|
||||
|
||||
'@codemirror/language@0.20.2':
|
||||
resolution: {integrity: sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==}
|
||||
|
||||
'@codemirror/language@6.11.0':
|
||||
resolution: {integrity: sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==}
|
||||
|
||||
'@codemirror/lint@0.20.3':
|
||||
resolution: {integrity: sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==}
|
||||
|
||||
'@codemirror/lint@6.8.4':
|
||||
resolution: {integrity: sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==}
|
||||
|
||||
'@codemirror/rangeset@0.19.9':
|
||||
resolution: {integrity: sha512-V8YUuOvK+ew87Xem+71nKcqu1SXd5QROMRLMS/ljT5/3MCxtgrRie1Cvild0G/Z2f1fpWxzX78V0U4jjXBorBQ==}
|
||||
deprecated: As of 0.20.0, this package has been merged into @codemirror/state
|
||||
|
||||
'@codemirror/search@0.20.1':
|
||||
resolution: {integrity: sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==}
|
||||
|
||||
'@codemirror/search@6.5.10':
|
||||
resolution: {integrity: sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==}
|
||||
|
||||
'@codemirror/state@0.19.9':
|
||||
resolution: {integrity: sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==}
|
||||
|
||||
'@codemirror/state@0.20.1':
|
||||
resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==}
|
||||
|
||||
'@codemirror/state@6.5.2':
|
||||
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
|
||||
|
||||
'@codemirror/text@0.19.6':
|
||||
resolution: {integrity: sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==}
|
||||
deprecated: As of 0.20.0, this package has been merged into @codemirror/state
|
||||
|
||||
'@codemirror/view@0.19.48':
|
||||
resolution: {integrity: sha512-0eg7D2Nz4S8/caetCTz61rK0tkHI17V/d15Jy0kLOT8dTLGGNJUponDnW28h2B6bERmPlVHKh8MJIr5OCp1nGw==}
|
||||
|
||||
'@codemirror/view@0.20.7':
|
||||
resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==}
|
||||
|
||||
'@codemirror/view@6.36.4':
|
||||
resolution: {integrity: sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==}
|
||||
|
||||
'@ctrl/tinycolor@3.6.1':
|
||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||
engines: {node: '>=10'}
|
||||
@ -781,9 +1015,6 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@kevisual/codemirror@0.0.2':
|
||||
resolution: {integrity: sha512-z+hINypSarYBj+13en8+Hj/CqTAMVIYN3Rs0ICHRzbikvrDnsnT4ohpnFgcbFPQ/zX3E1mS9ANbx4jGSJEHaPQ==}
|
||||
|
||||
'@kevisual/container@1.0.0':
|
||||
resolution: {integrity: sha512-zt9ldlyxkIuMcuU1YcvO/dDhbG7p6X4uVKlVzr5UzORIlC/ILdKxq9JN8/6oQ/Mwwja/LXFL33jH2xVqgBxjGQ==}
|
||||
peerDependencies:
|
||||
@ -812,6 +1043,45 @@ packages:
|
||||
'@kevisual/ui@0.0.2':
|
||||
resolution: {integrity: sha512-MDZDQTrYToLyj3WhiVJQLJ0PUHiN4D0Z5yJIyGzzPewPGpP2xwNgKO1BFX37J95cGZckzCdZwTKP0XKAOq0QtA==}
|
||||
|
||||
'@lezer/common@0.16.1':
|
||||
resolution: {integrity: sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==}
|
||||
|
||||
'@lezer/common@1.2.3':
|
||||
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
||||
|
||||
'@lezer/css@1.1.10':
|
||||
resolution: {integrity: sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==}
|
||||
|
||||
'@lezer/highlight@0.16.0':
|
||||
resolution: {integrity: sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==}
|
||||
|
||||
'@lezer/highlight@1.2.1':
|
||||
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
|
||||
|
||||
'@lezer/html@1.3.10':
|
||||
resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==}
|
||||
|
||||
'@lezer/javascript@1.4.21':
|
||||
resolution: {integrity: sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==}
|
||||
|
||||
'@lezer/json@1.0.3':
|
||||
resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==}
|
||||
|
||||
'@lezer/lr@0.16.3':
|
||||
resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==}
|
||||
|
||||
'@lezer/lr@1.4.2':
|
||||
resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
|
||||
|
||||
'@lezer/markdown@1.4.2':
|
||||
resolution: {integrity: sha512-iYewCigG/517D0xJPQd7RGaCjZAFwROiH8T9h7OTtz0bRVtkxzFhGBFJ9JGKgBBs4uuo1cvxzyQ5iKhDLMcLUQ==}
|
||||
|
||||
'@lezer/yaml@1.0.3':
|
||||
resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
'@monaco-editor/loader@1.5.0':
|
||||
resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==}
|
||||
|
||||
@ -1466,6 +1736,19 @@ packages:
|
||||
resolution: {integrity: sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@uiw/codemirror-theme-duotone@4.23.10':
|
||||
resolution: {integrity: sha512-h2lIubEMNBZNEvYVMjt08QUt0DhFKXIl44hTU+a60AoL+YLlDJw26KlNbUvHxyqTDZwaX2TZKR5APF14lU+sYA==}
|
||||
|
||||
'@uiw/codemirror-theme-vscode@4.23.10':
|
||||
resolution: {integrity: sha512-d9qGC6/yq6d+REMZUs7jrs2kGZoAAyNu0USOxsDa3Mqhh/dSUfC+ErDqwF02OfylsdcuPSzelu99EAvkjorpmQ==}
|
||||
|
||||
'@uiw/codemirror-themes@4.23.10':
|
||||
resolution: {integrity: sha512-dU0UgEEgEXCAYpxuVDQ6fovE82XsqgHZckTJOH6Bs8xCi3Z7dwBKO4pXuiA8qGDwTOXOMjSzfi+pRViDm7OfWw==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': '>=6.0.0'
|
||||
'@codemirror/state': '>=6.0.0'
|
||||
'@codemirror/view': '>=6.0.0'
|
||||
|
||||
'@uiw/react-textarea-code-editor@3.1.0':
|
||||
resolution: {integrity: sha512-lcAb5foG8K32ntg8dzY0A8vFyQP33SxuN8oatVYxJaxf2WDeiV0Jzk8/8lsOzthafEpgxc/X3RS2elYmvHn8TQ==}
|
||||
peerDependencies:
|
||||
@ -1617,6 +1900,9 @@ packages:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
codemirror@6.0.1:
|
||||
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
|
||||
|
||||
color-convert@1.9.3:
|
||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
|
||||
@ -1663,11 +1949,17 @@ packages:
|
||||
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
cross-env@7.0.3:
|
||||
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||
hasBin: true
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -2067,12 +2359,30 @@ packages:
|
||||
hastscript@8.0.0:
|
||||
resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==}
|
||||
|
||||
highlight.js@11.11.1:
|
||||
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
i18next-http-backend@3.0.2:
|
||||
resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==}
|
||||
|
||||
i18next@24.2.3:
|
||||
resolution: {integrity: sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2275,9 +2585,19 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
lucide-react@0.483.0:
|
||||
resolution: {integrity: sha512-WldsY17Qb/T3VZdMnVQ9C3DDIP7h1ViDTHVdVGnLZcvHNg30zH/MTQ04RTORjexoGmpsXroiQXZ4QyR0kBy0FA==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
marked-highlight@2.2.1:
|
||||
resolution: {integrity: sha512-SiCIeEiQbs9TxGwle9/OwbOejHCZsohQRaNTY2u8euEXYt2rYUFoiImUirThU3Gd/o6Q1gHGtH9qloHlbJpNIA==}
|
||||
peerDependencies:
|
||||
marked: '>=4 <16'
|
||||
|
||||
marked@15.0.7:
|
||||
resolution: {integrity: sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==}
|
||||
engines: {node: '>= 18'}
|
||||
@ -2332,9 +2652,23 @@ packages:
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
nanoid@5.1.5:
|
||||
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-forge@1.3.1:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
@ -2439,6 +2773,11 @@ packages:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
prettier@3.5.3:
|
||||
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-bytes@6.1.1:
|
||||
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
@ -2710,6 +3049,25 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>= 16.8 || 18.0.0'
|
||||
|
||||
react-hook-form@7.54.2:
|
||||
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-i18next@15.4.1:
|
||||
resolution: {integrity: sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==}
|
||||
peerDependencies:
|
||||
i18next: '>= 23.2.3'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@ -2903,6 +3261,9 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
style-mod@4.1.2:
|
||||
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
|
||||
|
||||
style-to-object@1.0.8:
|
||||
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
||||
|
||||
@ -2962,6 +3323,9 @@ packages:
|
||||
toggle-selection@1.0.6:
|
||||
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
@ -3096,9 +3460,22 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
web-namespaces@2.0.1:
|
||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -3361,6 +3738,10 @@ snapshots:
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/runtime@7.26.10':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
@ -3390,6 +3771,182 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@codemirror/autocomplete@0.20.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 0.20.2
|
||||
'@codemirror/state': 0.20.1
|
||||
'@codemirror/view': 0.20.7
|
||||
'@lezer/common': 0.16.1
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@codemirror/basic-setup@0.20.0':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 0.20.3
|
||||
'@codemirror/commands': 0.20.0
|
||||
'@codemirror/language': 0.20.2
|
||||
'@codemirror/lint': 0.20.3
|
||||
'@codemirror/search': 0.20.1
|
||||
'@codemirror/state': 0.20.1
|
||||
'@codemirror/view': 0.20.7
|
||||
|
||||
'@codemirror/commands@0.20.0':
|
||||
dependencies:
|
||||
'@codemirror/language': 0.20.2
|
||||
'@codemirror/state': 0.20.1
|
||||
'@codemirror/view': 0.20.7
|
||||
'@lezer/common': 0.16.1
|
||||
|
||||
'@codemirror/commands@6.8.0':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@codemirror/history@0.19.2':
|
||||
dependencies:
|
||||
'@codemirror/state': 0.19.9
|
||||
'@codemirror/view': 0.19.48
|
||||
|
||||
'@codemirror/lang-css@6.3.1':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/css': 1.1.10
|
||||
|
||||
'@codemirror/lang-html@6.4.9':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/lang-javascript': 6.2.3
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/css': 1.1.10
|
||||
'@lezer/html': 1.3.10
|
||||
|
||||
'@codemirror/lang-javascript@6.2.3':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/lint': 6.8.4
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/javascript': 1.4.21
|
||||
|
||||
'@codemirror/lang-json@6.0.1':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.0
|
||||
'@lezer/json': 1.0.3
|
||||
|
||||
'@codemirror/lang-markdown@6.3.2':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/lang-html': 6.4.9
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/markdown': 1.4.2
|
||||
|
||||
'@codemirror/lang-yaml@6.1.2':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
'@lezer/yaml': 1.0.3
|
||||
|
||||
'@codemirror/language@0.20.2':
|
||||
dependencies:
|
||||
'@codemirror/state': 0.20.1
|
||||
'@codemirror/view': 0.20.7
|
||||
'@lezer/common': 0.16.1
|
||||
'@lezer/highlight': 0.16.0
|
||||
'@lezer/lr': 0.16.3
|
||||
style-mod: 4.1.2
|
||||
|
||||
'@codemirror/language@6.11.0':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
style-mod: 4.1.2
|
||||
|
||||
'@codemirror/lint@0.20.3':
|
||||
dependencies:
|
||||
'@codemirror/state': 0.20.1
|
||||
'@codemirror/view': 0.20.7
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/lint@6.8.4':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/rangeset@0.19.9':
|
||||
dependencies:
|
||||
'@codemirror/state': 0.19.9
|
||||
|
||||
'@codemirror/search@0.20.1':
|
||||
dependencies:
|
||||
'@codemirror/state': 0.20.1
|
||||
'@codemirror/view': 0.20.7
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/search@6.5.10':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/state@0.19.9':
|
||||
dependencies:
|
||||
'@codemirror/text': 0.19.6
|
||||
|
||||
'@codemirror/state@0.20.1': {}
|
||||
|
||||
'@codemirror/state@6.5.2':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.2
|
||||
|
||||
'@codemirror/text@0.19.6': {}
|
||||
|
||||
'@codemirror/view@0.19.48':
|
||||
dependencies:
|
||||
'@codemirror/rangeset': 0.19.9
|
||||
'@codemirror/state': 0.19.9
|
||||
'@codemirror/text': 0.19.6
|
||||
style-mod: 4.1.2
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@codemirror/view@0.20.7':
|
||||
dependencies:
|
||||
'@codemirror/state': 0.20.1
|
||||
style-mod: 4.1.2
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@codemirror/view@6.36.4':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
style-mod: 4.1.2
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@ctrl/tinycolor@3.6.1': {}
|
||||
|
||||
'@emotion/babel-plugin@11.13.5':
|
||||
@ -3673,14 +4230,12 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@kevisual/codemirror@0.0.2': {}
|
||||
|
||||
'@kevisual/container@1.0.0(@emotion/css@11.13.4)(@types/react@19.0.11)(crypto-js@4.2.0)(eventemitter3@5.0.1)(immer@10.1.1)(react@19.0.0)(rollup@4.34.7)(typescript@5.8.2)':
|
||||
dependencies:
|
||||
'@emotion/css': 11.13.4
|
||||
crypto-js: 4.2.0
|
||||
eventemitter3: 5.0.1
|
||||
nanoid: 5.1.4
|
||||
nanoid: 5.1.5
|
||||
rollup-plugin-dts: 6.1.1(rollup@4.34.7)(typescript@5.8.2)
|
||||
scheduler: 0.23.2
|
||||
zustand: 4.5.5(@types/react@19.0.11)(immer@10.1.1)(react@19.0.0)
|
||||
@ -3736,6 +4291,63 @@ snapshots:
|
||||
lodash-es: 4.17.21
|
||||
style-to-object: 1.0.8
|
||||
|
||||
'@lezer/common@0.16.1': {}
|
||||
|
||||
'@lezer/common@1.2.3': {}
|
||||
|
||||
'@lezer/css@1.1.10':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/highlight@0.16.0':
|
||||
dependencies:
|
||||
'@lezer/common': 0.16.1
|
||||
|
||||
'@lezer/highlight@1.2.1':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@lezer/html@1.3.10':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/javascript@1.4.21':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/json@1.0.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@lezer/lr@0.16.3':
|
||||
dependencies:
|
||||
'@lezer/common': 0.16.1
|
||||
|
||||
'@lezer/lr@1.4.2':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@lezer/markdown@1.4.2':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
|
||||
'@lezer/yaml@1.0.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@monaco-editor/loader@1.5.0':
|
||||
dependencies:
|
||||
state-local: 1.0.7
|
||||
@ -4399,9 +5011,31 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.26.1
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
||||
'@uiw/react-textarea-code-editor@3.1.0(@babel/runtime@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
'@uiw/codemirror-theme-duotone@4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
'@uiw/codemirror-themes': 4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/language'
|
||||
- '@codemirror/state'
|
||||
- '@codemirror/view'
|
||||
|
||||
'@uiw/codemirror-theme-vscode@4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)':
|
||||
dependencies:
|
||||
'@uiw/codemirror-themes': 4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/language'
|
||||
- '@codemirror/state'
|
||||
- '@codemirror/view'
|
||||
|
||||
'@uiw/codemirror-themes@4.23.10(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
|
||||
'@uiw/react-textarea-code-editor@3.1.0(@babel/runtime@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.10
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
rehype: 13.0.1
|
||||
@ -4614,6 +5248,16 @@ snapshots:
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
codemirror@6.0.1:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.18.6
|
||||
'@codemirror/commands': 6.8.0
|
||||
'@codemirror/language': 6.11.0
|
||||
'@codemirror/lint': 6.8.4
|
||||
'@codemirror/search': 6.5.10
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.4
|
||||
|
||||
color-convert@1.9.3:
|
||||
dependencies:
|
||||
color-name: 1.1.3
|
||||
@ -4656,10 +5300,18 @@ snapshots:
|
||||
path-type: 4.0.0
|
||||
yaml: 1.10.2
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cross-env@7.0.3:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@ -5144,12 +5796,30 @@ snapshots:
|
||||
property-information: 6.5.0
|
||||
space-separated-tokens: 2.0.2
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
|
||||
hoist-non-react-statics@3.3.2:
|
||||
dependencies:
|
||||
react-is: 16.13.1
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
i18next-http-backend@3.0.2:
|
||||
dependencies:
|
||||
cross-fetch: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
i18next@24.2.3(typescript@5.8.2):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.10
|
||||
optionalDependencies:
|
||||
typescript: 5.8.2
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@ -5302,10 +5972,18 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
lucide-react@0.483.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
marked-highlight@2.2.1(marked@15.0.7):
|
||||
dependencies:
|
||||
marked: 15.0.7
|
||||
|
||||
marked@15.0.7: {}
|
||||
|
||||
mdast-util-to-hast@13.2.0:
|
||||
@ -5360,8 +6038,14 @@ snapshots:
|
||||
|
||||
nanoid@5.1.4: {}
|
||||
|
||||
nanoid@5.1.5: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-forge@1.3.1: {}
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
@ -5459,6 +6143,8 @@ snapshots:
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier@3.5.3: {}
|
||||
|
||||
pretty-bytes@6.1.1: {}
|
||||
|
||||
prop-types@15.8.1:
|
||||
@ -5824,6 +6510,19 @@ snapshots:
|
||||
prop-types: 15.8.1
|
||||
react: 19.0.0
|
||||
|
||||
react-hook-form@7.54.2(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
react-i18next@15.4.1(i18next@24.2.3(typescript@5.8.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 24.2.3(typescript@5.8.2)
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@18.3.1: {}
|
||||
@ -6043,6 +6742,8 @@ snapshots:
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
style-mod@4.1.2: {}
|
||||
|
||||
style-to-object@1.0.8:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
@ -6091,6 +6792,8 @@ snapshots:
|
||||
|
||||
toggle-selection@1.0.6: {}
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
trough@2.2.0: {}
|
||||
@ -6221,8 +6924,19 @@ snapshots:
|
||||
terser: 5.39.0
|
||||
yaml: 2.5.1
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
45
public/locales/en/translation.json
Normal file
45
public/locales/en/translation.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"container": "Container",
|
||||
"currentTime": "Current time: {{time, YYYY-MM-DD}}",
|
||||
"Your profile": "Your profile",
|
||||
"Your orgs": "Your orgs",
|
||||
"Site Map": "Site Map",
|
||||
"User": "User",
|
||||
"Login Out": "Login Out",
|
||||
"Switch Org": "Switch Org",
|
||||
"Save": "Save",
|
||||
"Profile": "Profile",
|
||||
"Edit your profile": "Edit your profile",
|
||||
"Name": "Name",
|
||||
"Email": "Email",
|
||||
"Phone": "Phone",
|
||||
"Description": "Description",
|
||||
"Avatar": "Avatar",
|
||||
"Avatar Upload": "Avatar Upload",
|
||||
"Edit": "Edit",
|
||||
"Add": "Add",
|
||||
"Delete": "Delete",
|
||||
"Cancel": "Cancel",
|
||||
"Confirm": "Confirm",
|
||||
"Add Org": "Add Org",
|
||||
"Home": "Home",
|
||||
"User App": "User App",
|
||||
"File App": "File App",
|
||||
"Org": "Org",
|
||||
"About": "About",
|
||||
"Contact": "Contact",
|
||||
"Terms": "Terms",
|
||||
"Privacy": "Privacy",
|
||||
"Language": "Language",
|
||||
"Theme": "Theme",
|
||||
"Dark": "Dark",
|
||||
"Light": "Light",
|
||||
"Container": "Container",
|
||||
"Submit": "Submit",
|
||||
"Username": "Username",
|
||||
"Back": "Back",
|
||||
"User List": "User List",
|
||||
"Switch to Org": "Switch to Org",
|
||||
"Login": "Login",
|
||||
"uploadDirectory": "Upload Directory"
|
||||
}
|
45
public/locales/zh/translation.json
Normal file
45
public/locales/zh/translation.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"container": "容器",
|
||||
"currentTime": "当前时间: {{time, YYYY-MM-DD}}",
|
||||
"Your profile": "你的个人资料",
|
||||
"Your orgs": "你的组织",
|
||||
"Site Map": "站点地图",
|
||||
"User": "用户",
|
||||
"Login Out": "退出登录",
|
||||
"Switch Org": "切换组织",
|
||||
"Save": "保存",
|
||||
"Profile": "个人资料",
|
||||
"Edit your profile": "编辑你的个人资料",
|
||||
"Name": "姓名",
|
||||
"Email": "邮箱",
|
||||
"Phone": "电话",
|
||||
"Description": "描述",
|
||||
"Avatar": "头像",
|
||||
"Avatar Upload": "头像上传",
|
||||
"Edit": "编辑",
|
||||
"Add": "添加",
|
||||
"Delete": "删除",
|
||||
"Cancel": "取消",
|
||||
"Confirm": "确认",
|
||||
"Add Org": "添加组织",
|
||||
"Home": "首页",
|
||||
"User App": "用户应用",
|
||||
"File App": "文件应用",
|
||||
"Org": "组织",
|
||||
"About": "关于",
|
||||
"Contact": "联系",
|
||||
"Terms": "条款",
|
||||
"Privacy": "隐私",
|
||||
"Language": "语言",
|
||||
"Theme": "主题",
|
||||
"Dark": "暗黑",
|
||||
"Light": "明亮",
|
||||
"Container": "容器",
|
||||
"Submit": "提交",
|
||||
"Username": "用户名",
|
||||
"Back": "返回",
|
||||
"User List": "用户列表",
|
||||
"Switch to Org": "切换组织",
|
||||
"Login": "登录",
|
||||
"uploadDirectory": "上传文件夹"
|
||||
}
|
@ -10,7 +10,6 @@ import { Redirect } from './modules/Redirect';
|
||||
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
// import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
@ -62,6 +61,7 @@ export const App = () => {
|
||||
<Route path='/container/*' element={<ContainerApp />} />
|
||||
<Route path='/map/*' element={<MapApp />} />
|
||||
<Route path='/user/*' element={<UserApp />} />
|
||||
<Route path='/user1/*' element={<UserApp />} />
|
||||
<Route path='/org/*' element={<OrgApp />} />
|
||||
<Route path='/app/*' element={<UserAppApp />} />
|
||||
<Route path='/file/*' element={<FileApp />} />
|
||||
|
34
src/I18Next.tsx
Normal file
34
src/I18Next.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import Backend from 'i18next-http-backend'; // 引入 Backend 插件
|
||||
|
||||
type I18NextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const initI18n = (basename: string) => {
|
||||
// 初始化 i18n
|
||||
i18n
|
||||
.use(Backend) // 使用 Backend 插件
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: `${basename}/locales/{{lng}}/{{ns}}.json`, // 指定 JSON 文件的路径
|
||||
},
|
||||
lng: 'zh', // 默认语言
|
||||
fallbackLng: 'en', // 备用语言
|
||||
interpolation: {
|
||||
escapeValue: false, // react 已经安全地处理了转义
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 国际化组件,初始化
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const I18NextProvider = (props: I18NextProviderProps) => {
|
||||
const { children } = props;
|
||||
return <>{children}</>;
|
||||
};
|
@ -3,13 +3,6 @@
|
||||
@import './index.css';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
h1 {
|
||||
@apply text-2xl font-bold;
|
||||
}
|
||||
@ -38,7 +31,7 @@ h3 {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
@utility card-key {
|
||||
@apply text-xs text-gray-400;
|
||||
@apply text-xs;
|
||||
}
|
||||
@utility card-footer {
|
||||
@apply text-sm text-gray-500;
|
||||
@ -48,7 +41,7 @@ h3 {
|
||||
}
|
||||
|
||||
@utility layout-menu {
|
||||
@apply bg-secondary p-2 text-white flex justify-between h-12 ;
|
||||
@apply bg-secondary p-2 text-white flex justify-between h-12;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
@utility no-drag {
|
||||
@ -64,58 +57,8 @@ h3 {
|
||||
}
|
||||
}
|
||||
|
||||
/* font-family */
|
||||
@utility font-family-mon {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
@utility font-family-rob {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
@utility font-family-int {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
@utility font-family-orb {
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
}
|
||||
@utility font-family-din {
|
||||
font-family: 'DIN', sans-serif;
|
||||
}
|
||||
|
||||
@utility flex-row-center {
|
||||
@apply flex flex-row items-center justify-center;
|
||||
}
|
||||
@utility flex-col-center {
|
||||
@apply flex flex-col items-center justify-center;
|
||||
}
|
||||
|
||||
@utility scrollbar {
|
||||
overflow: auto;
|
||||
/* 整个滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
/* 滚动条有滑块的轨道部分 */
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: transparent;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
cursor: pointer;
|
||||
background-color: #c1c1c1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块hover */
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #999999;
|
||||
}
|
||||
|
||||
/* 同时有垂直和水平滚动条时交汇的部分 */
|
||||
&::-webkit-scrollbar-corner {
|
||||
display: block; /* 修复交汇时出现的白块 */
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
@apply h-full;
|
||||
}
|
||||
|
13
src/main.tsx
13
src/main.tsx
@ -1,5 +1,16 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from './App.tsx';
|
||||
import './globals.css';
|
||||
import { initI18n, I18NextProvider } from './I18Next.tsx';
|
||||
import { basename } from './modules/basename.ts';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />);
|
||||
initI18n(basename);
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<I18NextProvider>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</I18NextProvider>,
|
||||
);
|
||||
|
@ -1,42 +1,18 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useLayoutStore } from './store';
|
||||
import clsx from 'clsx';
|
||||
import { Button, Dropdown } from 'antd';
|
||||
import { Menu, MenuItem, Tooltip } from '@mui/material';
|
||||
import { Button } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { message } from '@/modules/message';
|
||||
import {
|
||||
CloseOutlined,
|
||||
CodeOutlined,
|
||||
DashboardOutlined,
|
||||
HomeOutlined,
|
||||
LogoutOutlined,
|
||||
MessageOutlined,
|
||||
ReadOutlined,
|
||||
RocketOutlined,
|
||||
SmileOutlined,
|
||||
SwapOutlined,
|
||||
SwitcherOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { DashboardOutlined, HomeOutlined, LogoutOutlined, SmileOutlined } from '@ant-design/icons';
|
||||
import { useMemo } from 'react';
|
||||
import { query } from '../query';
|
||||
import { useNewNavigate } from '../navicate';
|
||||
const meun = [
|
||||
{
|
||||
title: 'Your profile',
|
||||
icon: <HomeOutlined />,
|
||||
link: '/user/profile',
|
||||
},
|
||||
{
|
||||
title: 'Your orgs',
|
||||
icon: <DashboardOutlined />,
|
||||
link: '/org/edit/list',
|
||||
},
|
||||
{
|
||||
title: 'Site Map',
|
||||
icon: <HomeOutlined />,
|
||||
link: '/map',
|
||||
},
|
||||
];
|
||||
import { Users, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
|
||||
export const LayoutUser = () => {
|
||||
const { open, setOpen, ...store } = useLayoutStore(
|
||||
useShallow((state) => ({
|
||||
@ -47,6 +23,24 @@ export const LayoutUser = () => {
|
||||
})),
|
||||
);
|
||||
const navigate = useNewNavigate();
|
||||
const { t } = useTranslation();
|
||||
const meun = [
|
||||
{
|
||||
title: t('Your profile'),
|
||||
icon: <HomeOutlined />,
|
||||
link: '/user/profile',
|
||||
},
|
||||
{
|
||||
title: t('Your orgs'),
|
||||
icon: <DashboardOutlined />,
|
||||
link: '/org/edit/list',
|
||||
},
|
||||
{
|
||||
title: t('Site Map'),
|
||||
icon: <HomeOutlined />,
|
||||
link: '/map',
|
||||
},
|
||||
];
|
||||
const items = useMemo(() => {
|
||||
const orgs = store.me?.orgs || [];
|
||||
return orgs.map((item) => {
|
||||
@ -57,30 +51,41 @@ export const LayoutUser = () => {
|
||||
};
|
||||
});
|
||||
}, [store.me]);
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('w-full h-full absolute z-20 no-drag', !open && 'hidden')}>
|
||||
<div className={clsx('w-full h-full absolute z-20 no-drag ', !open && 'hidden')}>
|
||||
<div
|
||||
className='bg-white w-full absolute h-full opacity-60 z-0'
|
||||
className='w-full absolute h-full opacity-60 z-0'
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}></div>
|
||||
<div className='w-[400px] h-full absolute top-0 right-0 bg-white rounded-l-lg'>
|
||||
<div className='w-[400px] bg-amber-900 text-primary transition-all duration-300 h-full absolute top-0 right-0 rounded-l-lg'>
|
||||
<div className='flex justify-between p-6 mt-4 font-bold items-center border-b'>
|
||||
User: {store.me?.username}
|
||||
<div className='flex items-center gap-2'>
|
||||
{t('User')}: <span className='text-primary'>{store.me?.username}</span>
|
||||
</div>
|
||||
<div className='flex gap-4'>
|
||||
{items.length > 0 && (
|
||||
<Dropdown
|
||||
placement='bottomRight'
|
||||
menu={{
|
||||
items: items,
|
||||
onClick: (item) => {
|
||||
store.switchOrg(item.key, 'org');
|
||||
},
|
||||
}}>
|
||||
<Button icon={<SwapOutlined />} onClick={() => {}}></Button>
|
||||
</Dropdown>
|
||||
<Tooltip title={t('Switch Org')}>
|
||||
<Button aria-controls='switch-org-menu' aria-haspopup='true' onClick={handleClick}>
|
||||
<Users />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Button icon={<CloseOutlined />} onClick={() => setOpen(false)}></Button>
|
||||
|
||||
<Button onClick={() => setOpen(false)}>
|
||||
<X />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-3 font-medium'>
|
||||
@ -88,7 +93,7 @@ export const LayoutUser = () => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='flex items-center p-4 hover:bg-gray-100 cursor-pointer'
|
||||
className='flex items-center p-4 hover:bg-secondary hover:text-white cursor-pointer'
|
||||
onClick={() => {
|
||||
if (item.link) {
|
||||
navigate(item.link);
|
||||
@ -104,7 +109,7 @@ export const LayoutUser = () => {
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center p-4 hover:bg-gray-100 cursor-pointer'
|
||||
className='flex items-center p-4 hover:bg-secondary hover:text-white cursor-pointer'
|
||||
onClick={() => {
|
||||
query.removeToken();
|
||||
window.open('/user/login', '_self');
|
||||
@ -112,9 +117,24 @@ export const LayoutUser = () => {
|
||||
<div className='mr-4'>
|
||||
<LogoutOutlined />
|
||||
</div>
|
||||
<div>Login Out</div>
|
||||
<div>{t('Login Out')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Menu id='simple-menu' anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
onClick={() => {
|
||||
store.switchOrg(item.key, 'org');
|
||||
handleClose();
|
||||
}}>
|
||||
<div className='mr-4'>{item.icon}</div>
|
||||
<div>{item.label}</div>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -3,53 +3,44 @@ import { useLayoutStore } from './store';
|
||||
import clsx from 'clsx';
|
||||
import { Button } from '@mui/material';
|
||||
import { message } from '@/modules/message';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
CloseOutlined,
|
||||
CodeOutlined,
|
||||
DashboardOutlined,
|
||||
FolderOutlined,
|
||||
HomeOutlined,
|
||||
MessageOutlined,
|
||||
ReadOutlined,
|
||||
RocketOutlined,
|
||||
SmileOutlined,
|
||||
SwitcherOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { AppstoreOutlined, CodeOutlined, FolderOutlined, HomeOutlined, SmileOutlined, SwitcherOutlined } from '@ant-design/icons';
|
||||
import { X } from 'lucide-react';
|
||||
import { useNewNavigate } from '../navicate';
|
||||
const meun = [
|
||||
{
|
||||
title: 'Home',
|
||||
icon: <HomeOutlined />,
|
||||
link: '/map',
|
||||
},
|
||||
{
|
||||
title: 'User App',
|
||||
icon: <AppstoreOutlined />,
|
||||
link: '/app/edit/list',
|
||||
},
|
||||
{
|
||||
title: 'File App',
|
||||
icon: <FolderOutlined />,
|
||||
link: '/file/edit/list',
|
||||
},
|
||||
{
|
||||
title: 'Container',
|
||||
icon: <CodeOutlined />,
|
||||
link: '/container/edit/list',
|
||||
},
|
||||
{
|
||||
title: 'Org',
|
||||
icon: <SwitcherOutlined />,
|
||||
link: '/org/edit/list',
|
||||
},
|
||||
{
|
||||
title: 'About',
|
||||
icon: <SmileOutlined />,
|
||||
},
|
||||
];
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const LayoutMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const meun = [
|
||||
{
|
||||
title: t('Home'),
|
||||
icon: <HomeOutlined />,
|
||||
link: '/map',
|
||||
},
|
||||
{
|
||||
title: t('User App'),
|
||||
icon: <AppstoreOutlined />,
|
||||
link: '/app/edit/list',
|
||||
},
|
||||
{
|
||||
title: t('File App'),
|
||||
icon: <FolderOutlined />,
|
||||
link: '/file/edit/list',
|
||||
},
|
||||
{
|
||||
title: t('Container'),
|
||||
icon: <CodeOutlined />,
|
||||
link: '/container/edit/list',
|
||||
},
|
||||
{
|
||||
title: t('Org'),
|
||||
icon: <SwitcherOutlined />,
|
||||
link: '/org/edit/list',
|
||||
},
|
||||
{
|
||||
title: t('About'),
|
||||
icon: <SmileOutlined />,
|
||||
},
|
||||
];
|
||||
const { open, setOpen } = useLayoutStore(useShallow((state) => ({ open: state.open, setOpen: state.setOpen })));
|
||||
const navigate = useNewNavigate();
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { MenuOutlined, SwapOutlined } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { LayoutMenu } from './Menu';
|
||||
import { useLayoutStore, usePlatformStore } from './store';
|
||||
@ -7,9 +7,14 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { LayoutUser } from './LayoutUser';
|
||||
import PandaPNG from '@/assets/panda.png';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||
import clsx from 'clsx';
|
||||
import { IconButton as Button } from '@mui/material';
|
||||
import { Button, Menu, MenuItem } from '@mui/material';
|
||||
import i18n from 'i18next';
|
||||
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { Languages } from 'lucide-react';
|
||||
|
||||
type LayoutMainProps = {
|
||||
title?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
@ -46,6 +51,23 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
||||
useEffect(() => {
|
||||
menuStore.getMe();
|
||||
}, []);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
i18n.changeLanguage(lng);
|
||||
handleClose();
|
||||
};
|
||||
const currentLanguage = i18n.language;
|
||||
|
||||
return (
|
||||
<div className='flex w-full h-full flex-col relative'>
|
||||
<LayoutMenu />
|
||||
@ -54,16 +76,31 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
||||
style={{
|
||||
cursor: isElectron ? 'move' : 'default',
|
||||
}}>
|
||||
<Button
|
||||
<IconButton
|
||||
className={clsx('mr-4 cursor-pointer no-drag', isMac && 'ml-16')}
|
||||
onClick={() => {
|
||||
menuStore.setOpen(true);
|
||||
}}>
|
||||
<MenuOutlined />
|
||||
</Button>
|
||||
</IconButton>
|
||||
<div className='flex grow justify-between pl-4 items-center'>
|
||||
{props.title}
|
||||
<div className='mr-4 flex gap-4 items-center no-drag'>
|
||||
<div>
|
||||
<Tooltip title={currentLanguage === 'en' ? 'English' : 'Chinese'}>
|
||||
<IconButton onClick={handleClick} variant='contained'>
|
||||
<Languages />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
|
||||
<MenuItem selected={currentLanguage === 'en'} onClick={() => changeLanguage('en')}>
|
||||
English
|
||||
</MenuItem>
|
||||
<MenuItem selected={currentLanguage === 'zh'} onClick={() => changeLanguage('zh')}>
|
||||
中文
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
{menuStore.me?.type === 'org' && (
|
||||
<div>
|
||||
<Tooltip title='Switch To User'>
|
||||
@ -97,16 +134,11 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
||||
<PanelGroup className='w-full h-full panel-layout' autoSaveId='editor-layout-main' direction='horizontal'>
|
||||
<Panel style={{ height: '100%' }}>
|
||||
<div className='h-full overflow-hidden'>
|
||||
<div className='w-full h-full rounded-lg'>
|
||||
<div className='w-full h-full rounded-lg text-primary'>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
{/* <PanelResizeHandle />
|
||||
<Panel style={{ height: '100%' }} defaultSize={25} className={clsx('bg-gray-100')}>
|
||||
侧边栏
|
||||
</Panel> */}
|
||||
</PanelGroup>
|
||||
</div>
|
||||
<LayoutUser />
|
||||
|
@ -2,14 +2,19 @@ import { useNavigation, useParams } from 'react-router';
|
||||
import { useAppVersionStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Form, Input, Modal, Space, Tooltip } from 'antd';
|
||||
import { Form, Input, Modal, Tooltip } from 'antd';
|
||||
import { CloudUploadOutlined, DeleteOutlined, EditOutlined, FileOutlined, LeftOutlined, LinkOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { FileUpload } from '../modules/FileUpload';
|
||||
import clsx from 'clsx';
|
||||
import { message } from '@/modules/message';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { Button } from '@mui/material';
|
||||
import { Dialog, DialogContent, DialogTitle, ButtonGroup } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
const FormModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const containerStore = useAppVersionStore(
|
||||
useShallow((state) => {
|
||||
@ -68,11 +73,11 @@ const FormModal = () => {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
<Button type='submit' variant='contained' color='primary'>
|
||||
{t('submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
@ -115,24 +120,25 @@ export const AppVersionList = () => {
|
||||
return (
|
||||
<div className='w-full h-full flex bg-slate-100'>
|
||||
<div className='p-2 bg-white'>
|
||||
<Button
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
versionStore.setFormData({ key: appKey });
|
||||
versionStore.setShowEdit(true);
|
||||
}}
|
||||
icon={<PlusOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<div className='grow h-full relative'>
|
||||
<div className='absolute top-2 left-4'>
|
||||
<Tooltip title='返回' placement='bottom'>
|
||||
<Button
|
||||
<IconButton
|
||||
variant='contained'
|
||||
onClick={() => {
|
||||
navigate('/app/edit/list');
|
||||
}}
|
||||
icon={<LeftOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@ -144,7 +150,7 @@ export const AppVersionList = () => {
|
||||
const color = isPublish ? 'bg-green-500' : '';
|
||||
const isRunning = item.status === 'running';
|
||||
return (
|
||||
<div className='card border-t w-[300px]' key={index}>
|
||||
<div className='card w-[300px]' key={index}>
|
||||
<div className={'flex items-center justify-between'}>
|
||||
{item.version}
|
||||
|
||||
@ -153,7 +159,10 @@ export const AppVersionList = () => {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='mt-4'>
|
||||
<Space.Compact>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
{/* <Button
|
||||
onClick={() => {
|
||||
versionStore.setFormData(item);
|
||||
@ -172,21 +181,20 @@ export const AppVersionList = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='使用当前版本,发布为此版本'>
|
||||
<Button
|
||||
icon={<CloudUploadOutlined />}
|
||||
onClick={() => {
|
||||
versionStore.publishVersion({ id: item.id });
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
<CloudUploadOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={'To Test App'}>
|
||||
<Button
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => {
|
||||
if (isRunning) {
|
||||
const link = new URL(`/test/${item.id}`, location.origin);
|
||||
@ -194,18 +202,20 @@ export const AppVersionList = () => {
|
||||
} else {
|
||||
message.error('The app is not running');
|
||||
}
|
||||
}}></Button>
|
||||
}}>
|
||||
<LinkOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='文件管理'>
|
||||
<Button
|
||||
icon={<FileOutlined />}
|
||||
onClick={() => {
|
||||
versionStore.setFormData(item);
|
||||
setIsUpload(true);
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
<FileOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -220,12 +230,12 @@ export const AppVersionList = () => {
|
||||
<div className='bg-white p-2 w-[600px] h-full flex flex-col'>
|
||||
<div className='header flex items-center gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
<Button
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setIsUpload(false);
|
||||
}}
|
||||
icon={<LeftOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className='font-bold'>{versionStore.key}</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Button } from 'antd';
|
||||
import { Button } from '@mui/material';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useAppVersionStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { message } from '@/modules/message';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export type FileType = {
|
||||
name: string;
|
||||
size: number;
|
||||
@ -12,6 +13,7 @@ export type FileType = {
|
||||
|
||||
export const FileUpload = () => {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const { t } = useTranslation();
|
||||
const appVersionStore = useAppVersionStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -27,11 +29,21 @@ export const FileUpload = () => {
|
||||
// webkitRelativePath
|
||||
let files = Array.from(e.target.files) as any[];
|
||||
console.log(files);
|
||||
if (files.length === 0) {
|
||||
message.error('请选择文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤 文件 .DS_Store
|
||||
files = files.filter((file) => {
|
||||
if (file.webkitRelativePath.startsWith('__MACOSX')) {
|
||||
return false;
|
||||
}
|
||||
// 过滤node_modules
|
||||
if (file.webkitRelativePath.includes('node_modules')) {
|
||||
return false;
|
||||
}
|
||||
// 过滤以.开头的文件
|
||||
return !file.name.startsWith('.');
|
||||
});
|
||||
if (files.length === 0) {
|
||||
@ -89,7 +101,7 @@ export const FileUpload = () => {
|
||||
}
|
||||
ref.current!.click();
|
||||
}}>
|
||||
上传文件夹
|
||||
{t('uploadDirectory')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,30 +1,26 @@
|
||||
import { Input, Modal, Select, Space, Switch } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { Input, Modal, Select } from 'antd';
|
||||
import { Fragment, Suspense, useEffect, useState } from 'react';
|
||||
import { TextArea } from '../components/TextArea';
|
||||
import { useContainerStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import copy from 'copy-to-clipboard';
|
||||
// import copy from 'copy-to-clipboard';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog, DialogTitle, DialogContent, Tooltip, Button, ButtonGroup } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { getDirectoryAndName, toFile, uploadFileChunked } from '@kevisual/resources/index.ts';
|
||||
import {
|
||||
EditOutlined,
|
||||
SettingOutlined,
|
||||
LinkOutlined,
|
||||
SaveOutlined,
|
||||
DeleteOutlined,
|
||||
LeftOutlined,
|
||||
MessageOutlined,
|
||||
PlusOutlined,
|
||||
DashboardOutlined,
|
||||
CloudUploadOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import clsx from 'clsx';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import CloudUploadOutlined from '@ant-design/icons/CloudUploadOutlined';
|
||||
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { Settings } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
const DrawEdit = React.lazy(() => import('../module/DrawEdit'));
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const containerStore = useContainerStore(
|
||||
@ -81,9 +77,11 @@ const FormModal = () => {
|
||||
<Form.Item name='tags' label='tags'>
|
||||
<Select mode='tags' />
|
||||
</Form.Item>
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
{!isEdit && (
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button variant='contained' type='submit'>
|
||||
提交
|
||||
@ -122,10 +120,9 @@ const PublishFormModal = () => {
|
||||
}, [containerStore.showEdit]);
|
||||
const onFinish = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
const success = await containerStore.updateData(values, { closePublish: false });
|
||||
if (success) {
|
||||
const formData = containerStore.formData;
|
||||
const code = formData.code;
|
||||
const containerRes = await containerStore.updateData(values, { closePublish: false });
|
||||
if (containerRes.code === 200) {
|
||||
const code = containerRes.data?.code || '-';
|
||||
const fileName = values['publish']?.['fileName'];
|
||||
let directoryAndName: ReturnType<typeof getDirectoryAndName> | null = null;
|
||||
try {
|
||||
@ -142,7 +139,6 @@ const PublishFormModal = () => {
|
||||
const key = values['publish']['key'];
|
||||
const version = values['publish']['version'];
|
||||
const file = toFile(code, directoryAndName.name);
|
||||
console.log('key', key, version, directoryAndName.directory, directoryAndName.name);
|
||||
const res = await uploadFileChunked(file, {
|
||||
appKey: key,
|
||||
version,
|
||||
@ -167,15 +163,15 @@ const PublishFormModal = () => {
|
||||
return (
|
||||
<Dialog open={containerStore.showEdit} onClose={() => containerStore.setShowEdit(false)}>
|
||||
<DialogTitle>Publish</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<DialogContent sx={{ padding: '10px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
span: 6,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
span: 18,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
@ -229,12 +225,14 @@ export const ContainerList = () => {
|
||||
setShowPublish: state.setShowPublish,
|
||||
updateData: state.updateData,
|
||||
formData: state.formData,
|
||||
getOne: state.getOne,
|
||||
setOpenDrawEdit: state.setOpenDrawEdit,
|
||||
openDrawEdit: state.openDrawEdit,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const [codeEdit, setCodeEdit] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
// const [codeEdit, setCodeEdit] = useState(false);
|
||||
useEffect(() => {
|
||||
containerStore.getList();
|
||||
}, []);
|
||||
@ -248,7 +246,7 @@ export const ContainerList = () => {
|
||||
<div className='w-full h-full flex '>
|
||||
<div className='p-2 flex flex-col gap-2'>
|
||||
<Tooltip title='添加'>
|
||||
<IconButton variant='contained' onClick={onAdd} sx={{ padding: '8px' }}>
|
||||
<IconButton variant='contained' onClick={onAdd}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@ -264,56 +262,47 @@ export const ContainerList = () => {
|
||||
className='flex text-sm gap flex-col w-[400px] max-h-[400px] bg-white p-4 rounded-lg'
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
setCode(item.code);
|
||||
containerStore.setFormData(item);
|
||||
setCodeEdit(true);
|
||||
// containerStore.setFormData(item);
|
||||
}}>
|
||||
<div className='px-4 cursor-pointer'>
|
||||
<div
|
||||
className='font-bold flex items-center'
|
||||
onClick={(e) => {
|
||||
copy(item.code);
|
||||
e.stopPropagation();
|
||||
message.success('copy code success');
|
||||
}}>
|
||||
{item.title || '-'}
|
||||
<div className='font-thin card-key ml-3 text-xs'>{item.tags ? item.tags.join(', ') : ''}</div>
|
||||
<div className='font-thin card-key ml-3'>{item.tags ? item.tags.join(', ') : ''}</div>
|
||||
</div>
|
||||
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||
</div>
|
||||
<div className='w-full text-xs'>
|
||||
<TextArea className='max-h-[240px] scrollbar' value={item.code} readonly />
|
||||
</div>
|
||||
|
||||
<div className='flex mt-2 '>
|
||||
<ButtonGroup variant='contained' color='primary'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// containerStore.publishData(item);
|
||||
}}>
|
||||
<SettingOutlined />
|
||||
</Button>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title='编辑代码'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
containerStore.getOne(item.id);
|
||||
containerStore.setFormData(item);
|
||||
containerStore.setOpenDrawEdit(true);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<Settings size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='编辑'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
containerStore.setFormData(item);
|
||||
containerStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{/* <Tooltip title='预览'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
// navicate('/container/preview/' + item.id);
|
||||
window.open('/container/preview/' + item.id);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<LinkOutlined />
|
||||
</Button>
|
||||
</Tooltip> */}
|
||||
<Tooltip title='发布到 user app当中'>
|
||||
<Tooltip title='添加到 user app当中'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
// containerStore.publishData(item);
|
||||
@ -355,42 +344,12 @@ export const ContainerList = () => {
|
||||
<CardBlank className='w-[400px]' />
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx('bg-gray-100 border-l-gray-200 border-bg-slate-300 w-[600px] shark-0', !codeEdit && 'hidden')}>
|
||||
<div className='bg-white p-2'>
|
||||
<div className='mt-2 ml-2 flex gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
containerStore.setFormData({});
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='保存'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
containerStore.updateData({ ...containerStore.formData, code });
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
<TextArea
|
||||
value={code}
|
||||
onChange={(value) => {
|
||||
setCode(value);
|
||||
}}
|
||||
className='h-full max-h-full scrollbar'
|
||||
style={{ overflow: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormModal />
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<DrawEdit />
|
||||
</Suspense>
|
||||
<PublishFormModal />
|
||||
<FormModal />
|
||||
{contextHolder}
|
||||
</div>
|
||||
);
|
||||
|
103
src/pages/container/module/DrawEdit.tsx
Normal file
103
src/pages/container/module/DrawEdit.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { BaseEditor } from '@kevisual/codemirror/editor/editor.ts';
|
||||
import { Drawer } from '@mui/material';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useContainerStore } from '../store';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { LeftOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
|
||||
export const DrawEdit = () => {
|
||||
const editorElRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<BaseEditor>(null);
|
||||
const containerStore = useContainerStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
openDrawEdit: state.openDrawEdit,
|
||||
setOpenDrawEdit: state.setOpenDrawEdit,
|
||||
data: state.data,
|
||||
updateData: state.updateData,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const { openDrawEdit, setOpenDrawEdit } = containerStore;
|
||||
const [mount, setMount] = useState(false);
|
||||
useEffect(() => {
|
||||
if (openDrawEdit && editorElRef.current && !mount) {
|
||||
editorRef.current = new BaseEditor();
|
||||
editorRef.current.createEditor(editorElRef.current);
|
||||
setMount(true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.destroyEditor();
|
||||
}
|
||||
};
|
||||
}, [openDrawEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (openDrawEdit && containerStore.data?.id && mount) {
|
||||
const editor = editorRef.current;
|
||||
const formData = containerStore.data;
|
||||
const fileType = editor?.getFileType(formData.title);
|
||||
const language = editor?.language;
|
||||
if (fileType && fileType !== language) {
|
||||
editor?.setLanguage(fileType, editorElRef.current!);
|
||||
} else {
|
||||
editor?.resetEditor(editorElRef.current!);
|
||||
}
|
||||
editor?.setContent(formData.code || '');
|
||||
}
|
||||
}, [openDrawEdit, containerStore.data?.id, mount]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={openDrawEdit}
|
||||
ModalProps={{
|
||||
keepMounted: true, // 保持挂载
|
||||
hideBackdrop: true, // 移除mask
|
||||
disableEnforceFocus: true, // 允许操作背景内容
|
||||
disableAutoFocus: true, // 防止自动聚焦
|
||||
}}
|
||||
anchor='right'>
|
||||
<div className='bg-secondary w-[600px] h-[48px]'>
|
||||
<div className='text-white flex p-2'>
|
||||
<div className='ml-2 flex gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
<IconButton
|
||||
sx={{
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpenDrawEdit(false);
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title='保存'>
|
||||
<IconButton
|
||||
sx={{
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
const code = editorRef.current?.getContent();
|
||||
containerStore.updateData({ id: containerStore.data.id, code }, { refresh: false });
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex-1 ml-2 flex items-center'>{containerStore.data?.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-primary' style={{ height: 'calc(100% - 50px)' }} ref={editorElRef}></div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawEdit;
|
@ -12,8 +12,13 @@ type ContainerStore = {
|
||||
setLoading: (loading: boolean) => void;
|
||||
list: any[];
|
||||
getList: () => Promise<void>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean }) => Promise<Boolean>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean, refresh?: boolean }) => Promise<any>;
|
||||
deleteData: (id: string) => Promise<void>;
|
||||
openDrawEdit: boolean;
|
||||
setOpenDrawEdit: (openDrawEdit: boolean) => void;
|
||||
getOne: (id: string) => Promise<void>;
|
||||
data: any;
|
||||
setData: (data: any) => void;
|
||||
};
|
||||
export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
return {
|
||||
@ -43,6 +48,7 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
const { getList } = get();
|
||||
const closePublish = opts?.closePublish ?? true;
|
||||
const closeEdit = opts?.closeEdit ?? true;
|
||||
const refresh = opts?.refresh ?? true;
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'update',
|
||||
@ -51,7 +57,9 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
set({ formData: res.data });
|
||||
getList();
|
||||
if (refresh) {
|
||||
getList();
|
||||
}
|
||||
if (closePublish) {
|
||||
set({ showPublish: false });
|
||||
}
|
||||
@ -61,7 +69,7 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
return res.code === 200;
|
||||
return res;
|
||||
},
|
||||
deleteData: async (id) => {
|
||||
const { getList } = get();
|
||||
@ -77,5 +85,23 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
openDrawEdit: false,
|
||||
setOpenDrawEdit: (openDrawEdit) => set({ openDrawEdit }),
|
||||
getOne: async (id) => {
|
||||
set({ loading: true, data: {} });
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'get',
|
||||
id,
|
||||
});
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
set({ data: res.data });
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
data: {},
|
||||
setData: (data) => set({ data }),
|
||||
};
|
||||
});
|
||||
|
@ -1,33 +1,35 @@
|
||||
import clsx from 'clsx';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
const serverList = ['container', 'map'];
|
||||
const serverPath = [
|
||||
{
|
||||
path: 'container',
|
||||
links: ['edit/list', 'preview/:id', 'edit/:id'],
|
||||
},
|
||||
{
|
||||
path: 'app',
|
||||
links: ['edit/list', ':app/version/list'],
|
||||
},
|
||||
{
|
||||
path: 'file',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
{
|
||||
path: 'map',
|
||||
links: ['/'],
|
||||
},
|
||||
{
|
||||
path: 'org',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
];
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ServerPath = () => {
|
||||
const navigate = useNewNavigate();
|
||||
const { t } = useTranslation();
|
||||
const serverPath = [
|
||||
{
|
||||
path: 'container',
|
||||
links: ['edit/list', 'preview/:id', 'edit/:id'],
|
||||
},
|
||||
{
|
||||
path: 'app',
|
||||
links: ['edit/list', ':app/version/list'],
|
||||
},
|
||||
{
|
||||
path: 'file',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
{
|
||||
path: 'map',
|
||||
links: ['/'],
|
||||
},
|
||||
{
|
||||
path: 'org',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className='p-2 w-full h-full bg-gray-200'>
|
||||
<h1 className='p-4 w-1/2 m-auto h1'>Site Map</h1>
|
||||
<div className='p-2 w-full h-full bg-gray-200 text-primary'>
|
||||
<h1 className='p-4 w-1/2 m-auto h1'>{t('Site Map')}</h1>
|
||||
<div className='w-1/2 m-auto bg-white p-4 border rounded-md shadow-md min-w-[700px] max-h-[80vh] overflow-auto scrollbar'>
|
||||
<div className='flex flex-col w-full'>
|
||||
{serverPath.map((item) => {
|
||||
@ -42,7 +44,6 @@ const ServerPath = () => {
|
||||
if (hasId) {
|
||||
return;
|
||||
}
|
||||
console.log('link', link);
|
||||
if (link === '/') {
|
||||
navigate(`/${item.path}`);
|
||||
return;
|
||||
@ -69,18 +70,3 @@ const ServerPath = () => {
|
||||
);
|
||||
};
|
||||
export const App = ServerPath;
|
||||
export const ServerList = () => {
|
||||
return (
|
||||
<div className='p-2 w-full h-full bg-gray-200'>
|
||||
<div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'>
|
||||
{serverList.map((item) => {
|
||||
return (
|
||||
<div key={item} className='flex flex-col'>
|
||||
<div>{item}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,21 +1,19 @@
|
||||
import { Button, Input, Modal, Table, Tooltip } from 'antd';
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { message } from '@/modules/message';
|
||||
import { Input, Modal } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useOrgStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import {
|
||||
EditOutlined,
|
||||
SettingOutlined,
|
||||
LinkOutlined,
|
||||
SaveOutlined,
|
||||
DeleteOutlined,
|
||||
LeftOutlined,
|
||||
PlusOutlined,
|
||||
SwapOutlined,
|
||||
UnorderedListOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import SwapOutlined from '@ant-design/icons/SwapOutlined';
|
||||
import UnorderedListOutlined from '@ant-design/icons/UnorderedListOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
@ -23,6 +21,7 @@ import { useLayoutStore } from '@/modules/layout/store';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const userStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -53,42 +52,40 @@ const FormModal = () => {
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input disabled={isEdit} />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Dialog open={userStore.showEdit} onClose={() => userStore.setShowEdit(false)}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input disabled={isEdit} />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='contained' type='submit'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const List = () => {
|
||||
@ -125,10 +122,15 @@ export const List = () => {
|
||||
userStore.setFormData({});
|
||||
userStore.setShowEdit(true);
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className='w-full h-full flex'>
|
||||
<div className='p-2'>
|
||||
<Button onClick={onAdd} icon={<PlusOutlined />}></Button>
|
||||
<Tooltip title={t('Add Org')}>
|
||||
<IconButton onClick={onAdd} variant='contained'>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
<div className='grow overflow-auto scrollbar bg-gray-100'>
|
||||
@ -157,24 +159,29 @@ export const List = () => {
|
||||
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||
</div>
|
||||
<div className='flex mt-2 '>
|
||||
<Button.Group>
|
||||
<Tooltip title='Edit'>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title={t('Edit')}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<EditOutlined />}></Button>
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='User List'>
|
||||
<Tooltip title={t('User List')}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
navicate(`/org/edit/user/${item.id}`);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<UnorderedListOutlined />}></Button>
|
||||
}}>
|
||||
<UnorderedListOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
@ -186,16 +193,18 @@ export const List = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
<Tooltip title='Switch to Org'>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
<Tooltip title={t('Switch to Org')}>
|
||||
<Button
|
||||
icon={<SwapOutlined />}
|
||||
onClick={() => {
|
||||
layoutStore.switchOrg(item.username);
|
||||
}}></Button>
|
||||
}}>
|
||||
<SwapOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Button.Group>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
@ -212,14 +221,15 @@ export const List = () => {
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
userStore.setFormData({});
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log('save', userStore.formData);
|
||||
userStore.updateData({ ...userStore.formData, code });
|
||||
}}
|
||||
icon={<SaveOutlined />}></Button>
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
|
@ -1,14 +1,20 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useOrgStore } from '../store';
|
||||
import { useNavigation, useParams } from 'react-router';
|
||||
import { useParams } from 'react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { Button, Input, Modal, Select, Space, Tooltip } from 'antd';
|
||||
import { Input, Modal, Select } from 'antd';
|
||||
import { message } from '@/modules/message';
|
||||
import { DeleteOutlined, EditOutlined, LeftOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { Form } from 'antd';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
@ -48,53 +54,52 @@ const FormModal = () => {
|
||||
userStore.setFormData({});
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input disabled={isEdit} />
|
||||
</Form.Item>
|
||||
<Form.Item name='role' label='role'>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'member',
|
||||
value: 'member',
|
||||
},
|
||||
]}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Dialog open={userStore.showEdit} onClose={() => userStore.setShowEdit(false)}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input disabled={isEdit} />
|
||||
</Form.Item>
|
||||
<Form.Item name='role' label='role'>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'member',
|
||||
value: 'member',
|
||||
},
|
||||
]}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='contained' type='submit'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@ -102,7 +107,7 @@ export const UserList = () => {
|
||||
const param = useParams();
|
||||
const navicate = useNewNavigate();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const orgStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -128,56 +133,50 @@ export const UserList = () => {
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div className='w-full h-full bg-gray-200 flex'>
|
||||
<div className='w-full h-full bg-gray-100 flex text-primary'>
|
||||
<div className='p-2 bg-white mr-2'>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
orgStore.setUserFormData({});
|
||||
orgStore.setShowUserEdit(true);
|
||||
}}></Button>
|
||||
}}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className='p-4 pt-12 grow relative'>
|
||||
<div className='absolute top-2'>
|
||||
<Tooltip title='返回'>
|
||||
<Button
|
||||
<Tooltip title={t('Back')}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
navicate(-1);
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='p-4 bg-white rounded-lg border border-gray-200 shadow-md h-full'>
|
||||
<div className='p-4 bg-white rounded-lg border shadow-md h-full'>
|
||||
<div className='flex gap-4'>
|
||||
{orgStore.users.map((item) => {
|
||||
const isOwner = item.role === 'owner';
|
||||
return (
|
||||
<div key={item.id} className='card w-[300px] border-t border-gray-200 justify-between p-2 border-b'>
|
||||
<div key={item.id} className='card w-[300px] justify-between p-2 '>
|
||||
<div
|
||||
className='card-title capitalize truncate cursor-pointer'
|
||||
onClick={() => {
|
||||
copy(item.username);
|
||||
}}>
|
||||
username: {item.username}
|
||||
{t('Username')}: {item.username}
|
||||
</div>
|
||||
<div className='flex gap-2 capitalize'>{item.role || '-'}</div>
|
||||
<div className='mt-2'>
|
||||
<Space.Compact>
|
||||
{/* <Tooltip title='Edit'>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
disabled={isOwner}
|
||||
onClick={() => {
|
||||
orgStore.setShowEdit(true);
|
||||
orgStore.setUserFormData(item);
|
||||
}}></Button>
|
||||
</Tooltip> */}
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title='delete'>
|
||||
<Button
|
||||
icon={<DeleteOutlined />}
|
||||
<IconButton
|
||||
disabled={isOwner}
|
||||
onClick={() => {
|
||||
// o-NDO62XGeyEQoz_Sytz-1UUB7kw
|
||||
modal.confirm({
|
||||
title: 'Delete',
|
||||
content: 'Are you sure?',
|
||||
@ -185,9 +184,11 @@ export const UserList = () => {
|
||||
orgStore.removeUser(item.id);
|
||||
},
|
||||
});
|
||||
}}></Button>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -96,7 +96,6 @@ export const useOrgStore = create<OrgStore>((set, get) => {
|
||||
if (res.code === 200) {
|
||||
const { org, users } = res.data || {};
|
||||
set({ org, users });
|
||||
message.success('load success');
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { Button, Input, Modal, Space } from 'antd';
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { Input, Modal } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useUserStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SettingOutlined from '@ant-design/icons/SettingOutlined';
|
||||
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
@ -14,8 +11,9 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@kevisual/center-components/card/CardBlank.tsx';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog } from '@mui/material';
|
||||
import { Dialog, ButtonGroup, Button, DialogContent, DialogTitle } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const userStore = useUserStore(
|
||||
@ -47,9 +45,9 @@ const FormModal = () => {
|
||||
userStore.setFormData({});
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
sx={{
|
||||
@ -57,33 +55,36 @@ const FormModal = () => {
|
||||
width: '800px',
|
||||
},
|
||||
}}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<DialogTitle>{isEdit ? t('Edit') : t('Add')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@ -116,7 +117,9 @@ export const List = () => {
|
||||
return (
|
||||
<div className='w-full h-full flex'>
|
||||
<div className='p-2'>
|
||||
<Button onClick={onAdd} icon={<PlusOutlined />}></Button>
|
||||
<IconButton onClick={onAdd} variant='contained'>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
<div className='grow overflow-auto scrollbar bg-gray-100'>
|
||||
@ -145,15 +148,19 @@ export const List = () => {
|
||||
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||
</div>
|
||||
<div className='flex mt-2 '>
|
||||
<Space.Compact>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<EditOutlined />}></Button>
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
modal.confirm({
|
||||
@ -164,9 +171,10 @@ export const List = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
</Space.Compact>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
@ -187,14 +195,16 @@ export const List = () => {
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
userStore.setFormData({});
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log('save', userStore.formData);
|
||||
userStore.updateData({ ...userStore.formData, code });
|
||||
}}
|
||||
icon={<SaveOutlined />}></Button>
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
|
@ -1,15 +1,25 @@
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import { TextField } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Button } from '@mui/material';
|
||||
import { useUserStore } from '../store';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { useLayoutStore } from '@/modules/layout/store';
|
||||
import { AvatarUpload } from '../module/AvatarUpload';
|
||||
import UploadOutlined from '@ant-design/icons/UploadOutlined';
|
||||
import PandaPNG from '@/assets/panda.png';
|
||||
import { FileUpload } from '../module/FileUpload';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const Profile = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const { control, handleSubmit, setValue, reset, formState, getValues } = useForm({
|
||||
defaultValues: {
|
||||
username: '',
|
||||
avatar: '',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
const ref = useRef<any>(null);
|
||||
const userStore = useUserStore(
|
||||
useShallow((state) => {
|
||||
@ -32,21 +42,29 @@ export const Profile = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
// const avatar = layoutStore.me?.avatar;
|
||||
useEffect(() => {
|
||||
const fromData = layoutStore.me;
|
||||
if (isObjectNull(fromData)) {
|
||||
form.setFieldsValue({});
|
||||
reset({
|
||||
username: '',
|
||||
avatar: '',
|
||||
description: '',
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue(fromData);
|
||||
reset({
|
||||
username: fromData.username,
|
||||
avatar: fromData.avatar || '',
|
||||
description: fromData.description || '',
|
||||
});
|
||||
}
|
||||
setAvatar(fromData.avatar || '');
|
||||
}, [layoutStore.me]);
|
||||
}, [layoutStore.me, setValue]);
|
||||
const onChange = (path: string) => {
|
||||
let url = '/resources/' + path;
|
||||
console.log('path', url);
|
||||
form.setFieldsValue({ avatar: url });
|
||||
setAvatar(url + '?t=' + new Date().getTime());
|
||||
const url = path + '?t=' + new Date().getTime();
|
||||
setAvatar(url);
|
||||
setValue('avatar', url);
|
||||
const values = getValues();
|
||||
onFinish(values);
|
||||
};
|
||||
const onFinish = async (values) => {
|
||||
const newMe = await userStore.updateSelf(values);
|
||||
@ -55,48 +73,51 @@ export const Profile = () => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='w-full h-full bg-gray-200 p-4'>
|
||||
<div className='border shadow-lg p-4 bg-white rounded-lg'>
|
||||
<div className='text-2xl'>Profile</div>
|
||||
<div className='text-sm text-gray-500'>Edit your profile</div>
|
||||
<div className='w-full h-full bg-amber-50 p-4 text-primary'>
|
||||
<div className=' shadow-lg p-4 bg-white rounded-lg'>
|
||||
<div className='text-2xl'>{t('Profile')}</div>
|
||||
<div className='text-sm text-secondary'>{t('Edit your profile')}</div>
|
||||
<div className='flex gap-4'>
|
||||
<div className='w-[600px] p-4 border mt-2 '>
|
||||
<div className='w-full my-4'>
|
||||
{avatar && <img className='w-20 h-20 mx-auto rounded-full' src={avatar} alt='avatar' />}
|
||||
{!avatar && <img className='w-20 h-20 mx-auto rounded-full' src={PandaPNG} alt='avatar' />}
|
||||
</div>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}>
|
||||
<Form.Item label='Name' name='username'>
|
||||
<Input className='w-full border rounded-lg p-2' disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label='Avatar' name='avatar'>
|
||||
<Input
|
||||
addonAfter={
|
||||
<div>
|
||||
<UploadOutlined
|
||||
onClick={() => {
|
||||
ref.current?.open?.();
|
||||
}}
|
||||
/>
|
||||
<FileUpload ref={ref} onChange={onChange} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label='Description' name='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button className='' htmlType='submit'>
|
||||
保存
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<form onSubmit={handleSubmit(onFinish)} className='flex flex-col gap-4'>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label={t('Name')} className='w-full border rounded-lg p-2' disabled />}
|
||||
/>
|
||||
<Controller
|
||||
name='avatar'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
label={t('Avatar')}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<div>
|
||||
<UploadOutlined
|
||||
onClick={() => {
|
||||
ref.current?.open?.();
|
||||
}}
|
||||
/>
|
||||
<FileUpload ref={ref} onChange={onChange} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label={t('Description')} multiline rows={4} />} />
|
||||
<Button className='' type='submit' variant='contained'>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import { useLoginStore } from '../store/login';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useEffect } from 'react';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import LockOutlined from '@ant-design/icons/LockOutlined';
|
||||
import UserOutlined from '@ant-design/icons/UserOutlined';
|
||||
import { Button } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
export const Login = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const { control, handleSubmit } = useForm();
|
||||
const loginStore = useLoginStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -15,50 +18,68 @@ export const Login = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
const isNull = isObjectNull(loginStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else {
|
||||
form.setFieldsValue(loginStore.formData);
|
||||
}
|
||||
}, [loginStore.formData]);
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
loginStore.setFormData(values);
|
||||
loginStore.login();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='bg-slate-200 text-slate-900 w-full h-full overflow-hidden'>
|
||||
<div className='bg-gray-100 text-primary w-full h-full overflow-hidden'>
|
||||
<div className='w-full h-full absolute top-[10%] xl:top-[15%] 2xl:top-[18%] 3xl:top-[20%] '>
|
||||
<div className='w-[400px] mx-auto'>
|
||||
<h1 className='mb-4 tracking-widest text-center'>Login</h1>
|
||||
<h1 className='mb-4 tracking-widest text-center'>{t('Login')}</h1>
|
||||
<div className='card border-t-2 border-gray-200 pt-8 px-8'>
|
||||
<Form
|
||||
className='mt-2'
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 6,
|
||||
}}>
|
||||
<Form.Item label='' name='username'>
|
||||
<Input addonBefore={<UserOutlined />} placeholder='Username' />
|
||||
</Form.Item>
|
||||
<Form.Item label='' name='password'>
|
||||
<Input type='password' addonBefore={<LockOutlined />} placeholder='Password' />
|
||||
</Form.Item>
|
||||
<Form.Item label='' colon={false}>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
style={{
|
||||
background: '#84d5e8',
|
||||
}}>
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<form className='mt-2 flex flex-col gap-8' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
label={t('Username')}
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position='start'>
|
||||
<UserOutlined className='text-primary!' />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name='password'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type='password'
|
||||
label={t('Password')}
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position='start'>
|
||||
<LockOutlined className='text-primary!' />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className='flex gap-2'>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Login')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,74 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Flex, Upload } from 'antd';
|
||||
import { message } from '@/modules/message';
|
||||
import type { GetProp, UploadProps } from 'antd';
|
||||
|
||||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
|
||||
const getBase64 = (img: FileType, callback: (url: string) => void) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => callback(reader.result as string));
|
||||
reader.readAsDataURL(img);
|
||||
};
|
||||
|
||||
const beforeUpload = (file: FileType) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
message.error('You can only upload JPG/PNG file!');
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('Image must smaller than 2MB!');
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
};
|
||||
|
||||
export const AvatarUpload = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [imageUrl, setImageUrl] = useState<string>();
|
||||
|
||||
const handleChange: UploadProps['onChange'] = (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
setLoading(true);
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
// Get this url from response in real world.
|
||||
getBase64(info.file.originFileObj as FileType, (url) => {
|
||||
setLoading(false);
|
||||
setImageUrl(url);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const uploadButton = (
|
||||
<button style={{ border: 0, background: 'none' }} type='button'>
|
||||
{loading ? <LoadingOutlined /> : <PlusOutlined />}
|
||||
<div style={{ marginTop: 8 }}>Upload</div>
|
||||
</button>
|
||||
);
|
||||
const onAciton = async (file) => {
|
||||
console.log('file', file);
|
||||
return '';
|
||||
};
|
||||
const customAction = (file) => {
|
||||
console.log('file', file);
|
||||
};
|
||||
return (
|
||||
<Flex gap='middle' wrap>
|
||||
<Upload
|
||||
name='avatar'
|
||||
listType='picture-circle'
|
||||
className='avatar-uploader'
|
||||
multiple={false}
|
||||
showUploadList={false}
|
||||
action={onAciton}
|
||||
customRequest={customAction}
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={handleChange}>
|
||||
{imageUrl ? <img src={imageUrl} alt='avatar' style={{ width: '100%' }} /> : uploadButton}
|
||||
</Upload>
|
||||
</Flex>
|
||||
);
|
||||
};
|
@ -1,7 +1,6 @@
|
||||
import { message } from '@/modules/message';
|
||||
import { useImperativeHandle, useRef, forwardRef } from 'react';
|
||||
import type { GetProp, UploadProps } from 'antd';
|
||||
type FileTypeOrg = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
import { uploadFileChunked } from '@kevisual/resources/index.ts';
|
||||
|
||||
export type FileType = {
|
||||
name: string;
|
||||
@ -10,7 +9,7 @@ export type FileType = {
|
||||
webkitRelativePath: string; // 包含name
|
||||
};
|
||||
|
||||
const beforeUpload = (file: FileTypeOrg) => {
|
||||
const beforeUpload = (file: any) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
message.error('You can only upload JPG/PNG file!');
|
||||
@ -33,22 +32,15 @@ export const FileUpload = forwardRef<any, Props>((props, ref) => {
|
||||
console.log(e.target.files);
|
||||
const file = e.target.files[0];
|
||||
const endType = file.name.split('.').pop();
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, `avatar.${endType}`); // 保留文件夹路径
|
||||
const res = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData, //
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token'),
|
||||
},
|
||||
}).then((res) => res.json());
|
||||
const filename = `avatar.${endType}`;
|
||||
const res = (await uploadFileChunked(file, {
|
||||
isPublic: true,
|
||||
filename,
|
||||
})) as any;
|
||||
console.log('res', res);
|
||||
if (res?.code === 200) {
|
||||
console.log('res', res);
|
||||
//
|
||||
const [file] = res.data;
|
||||
const { path } = file || {};
|
||||
props?.onChange?.(path);
|
||||
//
|
||||
const resource = res.data?.resource;
|
||||
props?.onChange?.(resource);
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
|
@ -53,13 +53,11 @@ export const useUserStore = create<UserStore>((set, get) => {
|
||||
}
|
||||
},
|
||||
updateSelf: async (data) => {
|
||||
const loaded = message.loading('Action in progress..', 0);
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'updateSelf',
|
||||
data,
|
||||
});
|
||||
loaded();
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
set({ formData: res.data });
|
||||
|
Loading…
x
Reference in New Issue
Block a user