feat(workflow): Monaco editor для proposed schema (C)
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@phosphor-icons/react": "^2.1.10",
|
"@phosphor-icons/react": "^2.1.10",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
|||||||
Generated
+57
@@ -11,6 +11,9 @@ importers:
|
|||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^3.9.1
|
specifier: ^3.9.1
|
||||||
version: 3.10.0(react-hook-form@7.75.0(react@19.2.5))
|
version: 3.10.0(react-hook-form@7.75.0(react@19.2.5))
|
||||||
|
'@monaco-editor/react':
|
||||||
|
specifier: ^4.7.0
|
||||||
|
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
'@phosphor-icons/react':
|
'@phosphor-icons/react':
|
||||||
specifier: ^2.1.10
|
specifier: ^2.1.10
|
||||||
version: 2.1.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
version: 2.1.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
@@ -720,6 +723,16 @@ packages:
|
|||||||
'@keyv/serialize@1.1.1':
|
'@keyv/serialize@1.1.1':
|
||||||
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
||||||
|
|
||||||
|
'@monaco-editor/loader@1.7.0':
|
||||||
|
resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==}
|
||||||
|
|
||||||
|
'@monaco-editor/react@4.7.0':
|
||||||
|
resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==}
|
||||||
|
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
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -1697,6 +1710,9 @@ packages:
|
|||||||
'@types/topojson-specification@1.0.5':
|
'@types/topojson-specification@1.0.5':
|
||||||
resolution: {integrity: sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==}
|
resolution: {integrity: sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==}
|
||||||
|
|
||||||
|
'@types/trusted-types@2.0.7':
|
||||||
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
|
|
||||||
'@types/webxr@0.5.24':
|
'@types/webxr@0.5.24':
|
||||||
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||||
|
|
||||||
@@ -2158,6 +2174,9 @@ packages:
|
|||||||
dom-accessibility-api@0.6.3:
|
dom-accessibility-api@0.6.3:
|
||||||
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
||||||
|
|
||||||
|
dompurify@3.2.7:
|
||||||
|
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
|
||||||
|
|
||||||
dot-prop@5.3.0:
|
dot-prop@5.3.0:
|
||||||
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
|
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2817,6 +2836,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
marked: '>=1 <16'
|
marked: '>=1 <16'
|
||||||
|
|
||||||
|
marked@14.0.0:
|
||||||
|
resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
marked@15.0.12:
|
marked@15.0.12:
|
||||||
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
|
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
@@ -2879,6 +2903,9 @@ packages:
|
|||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
|
monaco-editor@0.55.1:
|
||||||
|
resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -3482,6 +3509,9 @@ packages:
|
|||||||
stackback@0.0.2:
|
stackback@0.0.2:
|
||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
|
state-local@1.0.7:
|
||||||
|
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
|
||||||
|
|
||||||
std-env@3.10.0:
|
std-env@3.10.0:
|
||||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
@@ -4312,6 +4342,17 @@ snapshots:
|
|||||||
|
|
||||||
'@keyv/serialize@1.1.1': {}
|
'@keyv/serialize@1.1.1': {}
|
||||||
|
|
||||||
|
'@monaco-editor/loader@1.7.0':
|
||||||
|
dependencies:
|
||||||
|
state-local: 1.0.7
|
||||||
|
|
||||||
|
'@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@monaco-editor/loader': 1.7.0
|
||||||
|
monaco-editor: 0.55.1
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@@ -5285,6 +5326,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/geojson': 7946.0.16
|
'@types/geojson': 7946.0.16
|
||||||
|
|
||||||
|
'@types/trusted-types@2.0.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@types/webxr@0.5.24': {}
|
'@types/webxr@0.5.24': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))':
|
'@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||||
@@ -5746,6 +5790,10 @@ snapshots:
|
|||||||
|
|
||||||
dom-accessibility-api@0.6.3: {}
|
dom-accessibility-api@0.6.3: {}
|
||||||
|
|
||||||
|
dompurify@3.2.7:
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/trusted-types': 2.0.7
|
||||||
|
|
||||||
dot-prop@5.3.0:
|
dot-prop@5.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-obj: 2.0.0
|
is-obj: 2.0.0
|
||||||
@@ -6407,6 +6455,8 @@ snapshots:
|
|||||||
node-emoji: 2.2.0
|
node-emoji: 2.2.0
|
||||||
supports-hyperlinks: 3.2.0
|
supports-hyperlinks: 3.2.0
|
||||||
|
|
||||||
|
marked@14.0.0: {}
|
||||||
|
|
||||||
marked@15.0.12: {}
|
marked@15.0.12: {}
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
@@ -6444,6 +6494,11 @@ snapshots:
|
|||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
|
monaco-editor@0.55.1:
|
||||||
|
dependencies:
|
||||||
|
dompurify: 3.2.7
|
||||||
|
marked: 14.0.0
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
mz@2.7.0:
|
mz@2.7.0:
|
||||||
@@ -6957,6 +7012,8 @@ snapshots:
|
|||||||
|
|
||||||
stackback@0.0.2: {}
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
|
state-local@1.0.7: {}
|
||||||
|
|
||||||
std-env@3.10.0: {}
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
stream-combiner2@1.1.1:
|
stream-combiner2@1.1.1:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { ArrowsClockwiseIcon, CheckSquareIcon } from '@phosphor-icons/react'
|
import { ArrowsClockwiseIcon, CheckSquareIcon } from '@phosphor-icons/react'
|
||||||
import type { DictionaryDetail, SchemaDraft } from '@/api/client'
|
import type { DictionaryDetail, SchemaDraft } from '@/api/client'
|
||||||
import { useCreateSchemaDraft, useUpdateSchemaDraft } from '@/api/mutations'
|
import { useCreateSchemaDraft, useUpdateSchemaDraft } from '@/api/mutations'
|
||||||
|
import { JsonEditor } from '@/components/workflow/JsonEditor'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -247,18 +248,18 @@ export function CreateSchemaDraftModal({ open, onClose, detail, editDraft, onCre
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Custom monospace textarea — для full Monaco editor'а Phase 1d.
|
{/* Monaco editor — lazy-loaded (~1.2MB gzip). Syntax highlight,
|
||||||
Высота min-h-[400px] чтобы средняя schema ~30-50 полей помещалась
|
bracket matching, fold/unfold по структуре JSON. Высота 420px —
|
||||||
без скролла. */}
|
средняя schema (~30-50 полей) помещается без вертикального
|
||||||
<textarea
|
скролла. */}
|
||||||
|
<JsonEditor
|
||||||
value={json}
|
value={json}
|
||||||
onChange={(e) => {
|
onChange={(v) => {
|
||||||
setJson(e.target.value)
|
setJson(v)
|
||||||
if (parseError) setParseError(null)
|
if (parseError) setParseError(null)
|
||||||
}}
|
}}
|
||||||
spellCheck={false}
|
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
className="w-full min-h-[400px] max-h-[60vh] text-mono text-cell rounded-md border border-line bg-[#0c0a23] text-[#f0eaff] p-3 focus:outline-none focus:ring-1 focus:ring-accent"
|
height={420}
|
||||||
/>
|
/>
|
||||||
{parseError && (
|
{parseError && (
|
||||||
<Alert variant="error" className="mt-2">
|
<Alert variant="error" className="mt-2">
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { lazy, Suspense } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { LoadingBlock } from '@/ui'
|
||||||
|
|
||||||
|
const MonacoEditor = lazy(() => import('@monaco-editor/react').then((m) => ({ default: m.Editor })))
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string
|
||||||
|
onChange: (v: string) => void
|
||||||
|
disabled?: boolean
|
||||||
|
/** Pixel height; default 400. Используется minHeight CSS — editor сам
|
||||||
|
* растягивается до этого значения. */
|
||||||
|
height?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-loaded Monaco editor для JSON. Bundle ~1.2MB gzip — lazy через
|
||||||
|
* dynamic import. Suspense fallback показывает loading block.
|
||||||
|
*
|
||||||
|
* <p>Theme подставляется fixed 'vs-dark' чтобы match'илось с нашим
|
||||||
|
* space-theme background (#0c0a23). Monaco поддерживает custom themes
|
||||||
|
* через monaco.editor.defineTheme — это можно добавить отдельно если
|
||||||
|
* захочется precise color match.
|
||||||
|
*
|
||||||
|
* <p>Options:
|
||||||
|
* <ul>
|
||||||
|
* <li>JSON language с syntax highlighting + bracket matching.</li>
|
||||||
|
* <li>Auto-format on paste/type, format on save (через
|
||||||
|
* monaco.languages.json.jsonDefaults).</li>
|
||||||
|
* <li>Minimap скрыт — narrow modal viewport, minimap занимает место.</li>
|
||||||
|
* <li>Read-only режим через disabled prop.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
export function JsonEditor({ value, onChange, disabled = false, height = 400 }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<LoadingBlock size="md" label={t('loading')} />}>
|
||||||
|
<div
|
||||||
|
className="rounded-md border border-line overflow-hidden bg-[#0c0a23]"
|
||||||
|
style={{ height: typeof height === 'number' ? `${height}px` : height }}
|
||||||
|
>
|
||||||
|
<MonacoEditor
|
||||||
|
height="100%"
|
||||||
|
defaultLanguage="json"
|
||||||
|
language="json"
|
||||||
|
value={value}
|
||||||
|
onChange={(v) => onChange(v ?? '')}
|
||||||
|
theme="vs-dark"
|
||||||
|
options={{
|
||||||
|
readOnly: disabled,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily:
|
||||||
|
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
||||||
|
lineNumbers: 'on',
|
||||||
|
renderLineHighlight: 'line',
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
wordWrap: 'on',
|
||||||
|
tabSize: 2,
|
||||||
|
insertSpaces: true,
|
||||||
|
formatOnPaste: true,
|
||||||
|
formatOnType: true,
|
||||||
|
bracketPairColorization: { enabled: true },
|
||||||
|
// Monaco autoclosing для JSON: brackets/quotes на open auto-close,
|
||||||
|
// помогает писать большие schema'ы без потери indentation.
|
||||||
|
autoClosingBrackets: 'always',
|
||||||
|
autoClosingQuotes: 'always',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -129,6 +129,15 @@ export default defineConfig({
|
|||||||
// Axios + парсеры
|
// Axios + парсеры
|
||||||
if (id.match(/node_modules\/(axios|jose|crypto-js)\//)) return 'vendor-net'
|
if (id.match(/node_modules\/(axios|jose|crypto-js)\//)) return 'vendor-net'
|
||||||
|
|
||||||
|
// Monaco editor — ~1.2MB gzip. Lazy через React.lazy в JsonEditor.tsx;
|
||||||
|
// separate chunk даёт on-demand загрузку только когда CreateModal
|
||||||
|
// открыт. Без isolated chunk Monaco попадал бы в vendor-misc
|
||||||
|
// (главная страница). Аналог graph3d HOTFIX'а ниже — НО Monaco
|
||||||
|
// не зависит от d3 utils, поэтому circular chunk риска нет.
|
||||||
|
if (id.match(/node_modules\/(@monaco-editor|monaco-editor)\//)) {
|
||||||
|
return 'vendor-monaco'
|
||||||
|
}
|
||||||
|
|
||||||
// HOTFIX: graph3d isolated chunk был причиной runtime "Super
|
// HOTFIX: graph3d isolated chunk был причиной runtime "Super
|
||||||
// expression must either be null or a function" (cyclic chunk
|
// expression must either be null or a function" (cyclic chunk
|
||||||
// dependency vendor-misc ↔ vendor-graph3d ломает порядок
|
// dependency vendor-misc ↔ vendor-graph3d ломает порядок
|
||||||
|
|||||||
Reference in New Issue
Block a user