Merge branch 'feat/monaco-editor-schema-draft' into 'main'
feat(workflow): Monaco editor для proposed schema (C) See merge request 2-6/2-6-4/terravault/ordinis!132
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
|
||||
Generated
+57
@@ -11,6 +11,9 @@ importers:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.9.1
|
||||
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':
|
||||
specifier: ^2.1.10
|
||||
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':
|
||||
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':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1697,6 +1710,9 @@ packages:
|
||||
'@types/topojson-specification@1.0.5':
|
||||
resolution: {integrity: sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@types/webxr@0.5.24':
|
||||
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||
|
||||
@@ -2158,6 +2174,9 @@ packages:
|
||||
dom-accessibility-api@0.6.3:
|
||||
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
||||
|
||||
dompurify@3.2.7:
|
||||
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
|
||||
|
||||
dot-prop@5.3.0:
|
||||
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2817,6 +2836,11 @@ packages:
|
||||
peerDependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -2879,6 +2903,9 @@ packages:
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -3482,6 +3509,9 @@ packages:
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
state-local@1.0.7:
|
||||
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
|
||||
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
@@ -4312,6 +4342,17 @@ snapshots:
|
||||
|
||||
'@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':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -5285,6 +5326,9 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/geojson': 7946.0.16
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
'@types/webxr@0.5.24': {}
|
||||
|
||||
'@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: {}
|
||||
|
||||
dompurify@3.2.7:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
dot-prop@5.3.0:
|
||||
dependencies:
|
||||
is-obj: 2.0.0
|
||||
@@ -6407,6 +6455,8 @@ snapshots:
|
||||
node-emoji: 2.2.0
|
||||
supports-hyperlinks: 3.2.0
|
||||
|
||||
marked@14.0.0: {}
|
||||
|
||||
marked@15.0.12: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
@@ -6444,6 +6494,11 @@ snapshots:
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
dependencies:
|
||||
dompurify: 3.2.7
|
||||
marked: 14.0.0
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mz@2.7.0:
|
||||
@@ -6957,6 +7012,8 @@ snapshots:
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
state-local@1.0.7: {}
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
stream-combiner2@1.1.1:
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { ArrowsClockwiseIcon, CheckSquareIcon } from '@phosphor-icons/react'
|
||||
import type { DictionaryDetail, SchemaDraft } from '@/api/client'
|
||||
import { useCreateSchemaDraft, useUpdateSchemaDraft } from '@/api/mutations'
|
||||
import { JsonEditor } from '@/components/workflow/JsonEditor'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
@@ -247,18 +248,18 @@ export function CreateSchemaDraftModal({ open, onClose, detail, editDraft, onCre
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Custom monospace textarea — для full Monaco editor'а Phase 1d.
|
||||
Высота min-h-[400px] чтобы средняя schema ~30-50 полей помещалась
|
||||
без скролла. */}
|
||||
<textarea
|
||||
{/* Monaco editor — lazy-loaded (~1.2MB gzip). Syntax highlight,
|
||||
bracket matching, fold/unfold по структуре JSON. Высота 420px —
|
||||
средняя schema (~30-50 полей) помещается без вертикального
|
||||
скролла. */}
|
||||
<JsonEditor
|
||||
value={json}
|
||||
onChange={(e) => {
|
||||
setJson(e.target.value)
|
||||
onChange={(v) => {
|
||||
setJson(v)
|
||||
if (parseError) setParseError(null)
|
||||
}}
|
||||
spellCheck={false}
|
||||
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 && (
|
||||
<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 + парсеры
|
||||
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
|
||||
// expression must either be null or a function" (cyclic chunk
|
||||
// dependency vendor-misc ↔ vendor-graph3d ломает порядок
|
||||
|
||||
Reference in New Issue
Block a user