feat: use local Monaco editor for config values
This commit is contained in:
101
ConfigCenter.jsx
101
ConfigCenter.jsx
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import Editor, { DiffEditor } from '@monaco-editor/react';
|
||||||
import {
|
import {
|
||||||
Package, FolderTree, Globe, SlidersHorizontal, Plus, Pencil, Trash2,
|
Package, FolderTree, Globe, SlidersHorizontal, Plus, Pencil, Trash2,
|
||||||
Search, X, UploadCloud, History, RotateCcw, AlertTriangle, Check,
|
Search, X, UploadCloud, History, RotateCcw, AlertTriangle, Check,
|
||||||
@@ -40,6 +41,12 @@ const TYPE_BADGE = {
|
|||||||
json: 'bg-amber-50 text-amber-600',
|
json: 'bg-amber-50 text-amber-600',
|
||||||
xml: 'bg-violet-50 text-violet-600',
|
xml: 'bg-violet-50 text-violet-600',
|
||||||
};
|
};
|
||||||
|
const MONACO_LANGUAGE = {
|
||||||
|
properties: 'ini',
|
||||||
|
yaml: 'yaml',
|
||||||
|
json: 'json',
|
||||||
|
xml: 'xml',
|
||||||
|
};
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* 种子数据
|
* 种子数据
|
||||||
@@ -109,22 +116,83 @@ const initialReleases = [
|
|||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* 通用小组件
|
* 通用小组件
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
function Field({ label, required, children }) {
|
function Field({ label, required, children, composite = false }) {
|
||||||
return (
|
const content = (
|
||||||
<label className="block">
|
<>
|
||||||
<span className="block text-xs font-medium text-slate-500 mb-1">
|
<span className="block text-xs font-medium text-slate-500 mb-1">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="text-rose-500 ml-0.5">*</span>}
|
{required && <span className="text-rose-500 ml-0.5">*</span>}
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</label>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Monaco 等复合控件内部包含自己的 textarea / 可聚焦元素。
|
||||||
|
// 不能包在 <label> 中,否则浏览器会在点击后执行 label 的默认聚焦行为,
|
||||||
|
// 抢走 Monaco 自己刚获得的焦点,表现为“点击编辑器立即失焦”。
|
||||||
|
return composite ? <div className="block">{content}</div> : <label className="block">{content}</label>;
|
||||||
}
|
}
|
||||||
function inputCls(disabled) {
|
function inputCls(disabled) {
|
||||||
return `w-full rounded-lg border px-3 py-1.5 text-sm outline-none transition ${
|
return `w-full rounded-lg border px-3 py-1.5 text-sm outline-none transition ${
|
||||||
disabled ? 'bg-slate-50 text-slate-400 border-slate-200 cursor-not-allowed' : 'border-slate-200 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100'
|
disabled ? 'bg-slate-50 text-slate-400 border-slate-200 cursor-not-allowed' : 'border-slate-200 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100'
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
function ConfigValueEditor({ mode, namespaceType, value, releasedValue, onChange }) {
|
||||||
|
const language = MONACO_LANGUAGE[namespaceType] || 'plaintext';
|
||||||
|
const commonOptions = {
|
||||||
|
automaticLayout: true,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 20,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
wordWrap: 'on',
|
||||||
|
tabSize: 2,
|
||||||
|
insertSpaces: true,
|
||||||
|
padding: { top: 12, bottom: 12 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-hidden rounded-lg border border-slate-200 bg-white focus-within:border-indigo-400 focus-within:ring-2 focus-within:ring-indigo-100">
|
||||||
|
<div className="flex items-center justify-between gap-3 border-b border-slate-100 bg-slate-50 px-3 py-2 text-[11px] text-slate-400">
|
||||||
|
<span className={`rounded px-1.5 py-0.5 font-mono ${TYPE_BADGE[namespaceType] || TYPE_BADGE.properties}`}>
|
||||||
|
{namespaceType || 'properties'}
|
||||||
|
</span>
|
||||||
|
<span>{mode === 'edit' ? '左侧:已发布值 · 右侧:当前编辑值' : '编辑器语法跟随命名空间格式'}</span>
|
||||||
|
</div>
|
||||||
|
{mode === 'edit' ? (
|
||||||
|
<DiffEditor
|
||||||
|
key={`diff-${namespaceType}`}
|
||||||
|
height="300px"
|
||||||
|
original={releasedValue ?? ''}
|
||||||
|
modified={value ?? ''}
|
||||||
|
originalLanguage={language}
|
||||||
|
modifiedLanguage={language}
|
||||||
|
theme="vs"
|
||||||
|
options={{
|
||||||
|
...commonOptions,
|
||||||
|
renderSideBySide: true,
|
||||||
|
originalEditable: false,
|
||||||
|
readOnly: false,
|
||||||
|
}}
|
||||||
|
onMount={(diffEditor) => {
|
||||||
|
const modifiedEditor = diffEditor.getModifiedEditor();
|
||||||
|
modifiedEditor.onDidChangeModelContent(() => onChange(modifiedEditor.getValue()));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Editor
|
||||||
|
key={`editor-${namespaceType}`}
|
||||||
|
height="260px"
|
||||||
|
language={language}
|
||||||
|
value={value ?? ''}
|
||||||
|
theme="vs"
|
||||||
|
options={commonOptions}
|
||||||
|
onChange={(nextValue) => onChange(nextValue ?? '')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
function EmptyHint({ text }) {
|
function EmptyHint({ text }) {
|
||||||
return <div className="bg-white rounded-xl border border-dashed border-slate-300 p-12 text-center text-sm text-slate-400">{text}</div>;
|
return <div className="bg-white rounded-xl border border-dashed border-slate-300 p-12 text-center text-sm text-slate-400">{text}</div>;
|
||||||
}
|
}
|
||||||
@@ -199,7 +267,7 @@ function ConfirmDialog({ confirm, onCancel }) {
|
|||||||
* 新建 / 编辑 弹窗(应用、命名空间、环境、配置项 共用)
|
* 新建 / 编辑 弹窗(应用、命名空间、环境、配置项 共用)
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
function EntityModal({ modal, apps, onClose, onSubmit }) {
|
function EntityModal({ modal, apps, onClose, onSubmit }) {
|
||||||
const { entityType, mode, data } = modal;
|
const { entityType, mode, data, namespaceType = 'properties' } = modal;
|
||||||
const [form, setForm] = useState(() => data || {});
|
const [form, setForm] = useState(() => data || {});
|
||||||
const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
|
const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
|
||||||
|
|
||||||
@@ -217,7 +285,7 @@ function EntityModal({ modal, apps, onClose, onSubmit }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/40 backdrop-blur-sm p-4" onClick={onClose}>
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/40 backdrop-blur-sm p-4" onClick={onClose}>
|
||||||
<form onClick={(e) => e.stopPropagation()} onSubmit={handleSubmit} className="bg-white rounded-xl shadow-xl w-full max-w-md overflow-hidden">
|
<form onClick={(e) => e.stopPropagation()} onSubmit={handleSubmit} className={`bg-white rounded-xl shadow-xl w-full overflow-hidden ${entityType === 'config' ? 'max-w-4xl' : 'max-w-md'}`}>
|
||||||
<div className="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
|
<div className="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
|
||||||
<h3 className="text-sm font-semibold text-slate-800">{titles[entityType]}</h3>
|
<h3 className="text-sm font-semibold text-slate-800">{titles[entityType]}</h3>
|
||||||
<button type="button" onClick={onClose} className="text-slate-400 hover:text-slate-600">
|
<button type="button" onClick={onClose} className="text-slate-400 hover:text-slate-600">
|
||||||
@@ -295,8 +363,14 @@ function EntityModal({ modal, apps, onClose, onSubmit }) {
|
|||||||
<Field label="Key" required>
|
<Field label="Key" required>
|
||||||
<input required disabled={mode === 'edit'} value={form.key || ''} onChange={(e) => set('key', e.target.value)} placeholder="如 server.port" className={inputCls(mode === 'edit') + ' font-mono'} />
|
<input required disabled={mode === 'edit'} value={form.key || ''} onChange={(e) => set('key', e.target.value)} placeholder="如 server.port" className={inputCls(mode === 'edit') + ' font-mono'} />
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Value" required>
|
<Field label="Value" required composite>
|
||||||
<textarea required value={form.value || ''} onChange={(e) => set('value', e.target.value)} rows={4} className={inputCls() + ' font-mono'} />
|
<ConfigValueEditor
|
||||||
|
mode={mode}
|
||||||
|
namespaceType={namespaceType}
|
||||||
|
value={form.value || ''}
|
||||||
|
releasedValue={form.releasedValue}
|
||||||
|
onChange={(value) => set('value', value)}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="备注">
|
<Field label="备注">
|
||||||
<input value={form.comment || ''} onChange={(e) => set('comment', e.target.value)} className={inputCls()} />
|
<input value={form.comment || ''} onChange={(e) => set('comment', e.target.value)} className={inputCls()} />
|
||||||
@@ -1339,8 +1413,15 @@ export default function ConfigCenter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 配置项 CRUD ---------- */
|
/* ---------- 配置项 CRUD ---------- */
|
||||||
function openAddConfig() { if (!selectedNsId) return; setModal({ entityType: 'config', mode: 'add', data: {} }); }
|
function openAddConfig() {
|
||||||
function openEditConfig(c) { setModal({ entityType: 'config', mode: 'edit', data: { ...c } }); }
|
if (!selectedNsId) return;
|
||||||
|
const namespaceType = namespaces.find((item) => item.id === selectedNsId)?.type || 'properties';
|
||||||
|
setModal({ entityType: 'config', mode: 'add', namespaceType, data: {} });
|
||||||
|
}
|
||||||
|
function openEditConfig(c) {
|
||||||
|
const namespaceType = namespaces.find((item) => item.id === c.nsId)?.type || 'properties';
|
||||||
|
setModal({ entityType: 'config', mode: 'edit', namespaceType, data: { ...c } });
|
||||||
|
}
|
||||||
async function toggleDeleteConfig(c) {
|
async function toggleDeleteConfig(c) {
|
||||||
try {
|
try {
|
||||||
if (c.pendingDelete) await api.restoreConfigItem(c.id);
|
if (c.pendingDelete) await api.restoreConfigItem(c.id);
|
||||||
|
|||||||
72
web/package-lock.json
generated
72
web/package-lock.json
generated
@@ -8,16 +8,17 @@
|
|||||||
"name": "configcenter-web",
|
"name": "configcenter-web",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@vitejs/plugin-react": "latest",
|
"@vitejs/plugin-react": "latest",
|
||||||
"autoprefixer": "latest",
|
"autoprefixer": "latest",
|
||||||
"lucide-react": "latest",
|
"lucide-react": "latest",
|
||||||
|
"monaco-editor": "^0.56.0",
|
||||||
"postcss": "latest",
|
"postcss": "latest",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"vite": "latest"
|
"vite": "latest"
|
||||||
},
|
}
|
||||||
"devDependencies": {}
|
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
@@ -66,6 +67,29 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@monaco-editor/loader": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"state-local": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@monaco-editor/react": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
|
||||||
|
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@monaco-editor/loader": "^1.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"monaco-editor": ">= 0.25.0 < 1",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -374,6 +398,13 @@
|
|||||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
|
||||||
@@ -640,6 +671,15 @@
|
|||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz",
|
||||||
|
"integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.416",
|
"version": "1.5.416",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz",
|
||||||
@@ -1127,6 +1167,18 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@@ -1149,6 +1201,16 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/monaco-editor": {
|
||||||
|
"version": "0.56.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.56.0.tgz",
|
||||||
|
"integrity": "sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dompurify": "3.4.8",
|
||||||
|
"marked": "14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mz": {
|
"node_modules/mz": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
@@ -1564,6 +1626,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/state-local": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.35.1",
|
"version": "3.35.1",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
||||||
|
|||||||
@@ -9,14 +9,15 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@vitejs/plugin-react": "latest",
|
"@vitejs/plugin-react": "latest",
|
||||||
"autoprefixer": "latest",
|
"autoprefixer": "latest",
|
||||||
"lucide-react": "latest",
|
"lucide-react": "latest",
|
||||||
|
"monaco-editor": "^0.56.0",
|
||||||
"postcss": "latest",
|
"postcss": "latest",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"vite": "latest"
|
"vite": "latest"
|
||||||
},
|
}
|
||||||
"devDependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import './monaco.js';
|
||||||
import ConfigCenter from '../../ConfigCenter.jsx';
|
import ConfigCenter from '../../ConfigCenter.jsx';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
|
|||||||
39
web/src/monaco-loader-local.js
Normal file
39
web/src/monaco-loader-local.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
let monacoInstance = null;
|
||||||
|
|
||||||
|
function cancelable(promise) {
|
||||||
|
let canceled = false;
|
||||||
|
const wrapped = new Promise((resolve, reject) => {
|
||||||
|
promise.then(
|
||||||
|
(value) => {
|
||||||
|
if (canceled) reject({ type: 'cancelation', msg: 'operation is manually canceled' });
|
||||||
|
else resolve(value);
|
||||||
|
},
|
||||||
|
reject,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapped.cancel = () => { canceled = true; };
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = {
|
||||||
|
config(config) {
|
||||||
|
if (config?.monaco) monacoInstance = config.monaco;
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (!monacoInstance && typeof window !== 'undefined' && window.monaco?.editor) {
|
||||||
|
monacoInstance = window.monaco;
|
||||||
|
}
|
||||||
|
if (!monacoInstance) {
|
||||||
|
return cancelable(Promise.reject(new Error('Local Monaco instance is not configured')));
|
||||||
|
}
|
||||||
|
return cancelable(Promise.resolve(monacoInstance));
|
||||||
|
},
|
||||||
|
|
||||||
|
__getMonacoInstance() {
|
||||||
|
return monacoInstance;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { loader };
|
||||||
|
export default loader;
|
||||||
14
web/src/monaco.js
Normal file
14
web/src/monaco.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { loader } from '@monaco-editor/react';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import EditorWorker from 'monaco-editor/editor/editor.worker?worker';
|
||||||
|
import JsonWorker from 'monaco-editor/language/json/json.worker?worker';
|
||||||
|
|
||||||
|
// 完全使用随前端产物打包的 Monaco,不依赖 jsDelivr 等公网 CDN。
|
||||||
|
self.MonacoEnvironment = {
|
||||||
|
getWorker(_moduleId, label) {
|
||||||
|
if (label === 'json') return new JsonWorker();
|
||||||
|
return new EditorWorker();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
loader.config({ monaco });
|
||||||
@@ -8,6 +8,8 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
react: fileURLToPath(new URL('./node_modules/react', import.meta.url)),
|
react: fileURLToPath(new URL('./node_modules/react', import.meta.url)),
|
||||||
'lucide-react': fileURLToPath(new URL('./node_modules/lucide-react', import.meta.url)),
|
'lucide-react': fileURLToPath(new URL('./node_modules/lucide-react', import.meta.url)),
|
||||||
|
'@monaco-editor/react': fileURLToPath(new URL('./node_modules/@monaco-editor/react', import.meta.url)),
|
||||||
|
'@monaco-editor/loader': fileURLToPath(new URL('./src/monaco-loader-local.js', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in New Issue
Block a user