diff --git a/web/package-lock.json b/web/package-lock.json index ea3738a6f..3813fd1f1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,9 @@ "name": "web-new", "version": "0.0.0", "dependencies": { + "@codemirror/lang-yaml": "^6.1.1", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.28.1", "@cycjimmy/jsmpeg-player": "^6.0.5", "@hookform/resolvers": "^3.4.2", "@radix-ui/react-alert-dialog": "^1.0.5", @@ -29,10 +32,12 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", + "@uiw/codemirror-themes-all": "^4.22.2", "apexcharts": "^3.49.1", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "codemirror": "^6.0.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", "hls.js": "^1.5.8", @@ -226,6 +231,103 @@ "statuses": "^2.0.1" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.16.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.3.tgz", + "integrity": "sha512-Vl/tIeRVVUCRDuOG48lttBasNQu8usGgXQawBXI7WJAiUDSFOfzflmEsZFZo48mAvAaa4FZ/4/yLLxFtdJaKYA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz", + "integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.1.tgz", + "integrity": "sha512-HV2NzbK9bbVnjWxwObuZh5FuPCowx51mEfoFT9y3y+M37fA3+pbxx4I7uePuygFzDsAmCTwQSc/kXh/flab4uw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", + "integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.1.tgz", + "integrity": "sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.6", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz", + "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.1.tgz", + "integrity": "sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@cycjimmy/jsmpeg-player": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.5.tgz", @@ -846,6 +948,41 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", + "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz", + "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.1.tgz", + "integrity": "sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", + "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@mswjs/cookies": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz", @@ -2856,6 +2993,489 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@uiw/codemirror-theme-abcdef": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abcdef/-/codemirror-theme-abcdef-4.22.2.tgz", + "integrity": "sha512-ouordQh/zUT1Y1htGZkWMtGTKgA5Zrb0Wxq6quS2cddx/4/ADAUAKzZCrOL0O6AB7XOH6WCnJWCqEbU2s1K6gg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-abyss": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-abyss/-/codemirror-theme-abyss-4.22.2.tgz", + "integrity": "sha512-jTLq7xUwVOlv0+t516eNgEaJ4xbePXs8KryEsmFQ5sTh4hzKP0wxW/P3X0gxtz+ENXxaGYz8wiC4yeqJntuzjA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-androidstudio": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-androidstudio/-/codemirror-theme-androidstudio-4.22.2.tgz", + "integrity": "sha512-GAk8Ux4FFUiqauXKKdvHsrcYQ26si/+98zheIc0K18kEr9KB1i0WP1mczrFO4yAQsMbpDBedQPJ1LTLhZZNyow==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-andromeda": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-andromeda/-/codemirror-theme-andromeda-4.22.2.tgz", + "integrity": "sha512-CE9IvVjnPpRN+ZRbz7kA26mlQWxqHw3QqWc/3Z26mZwrQ2zNC3+oQJyy47GJSCGLyVrvvsKbvVqg9XPw5yrrvQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-atomone": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-atomone/-/codemirror-theme-atomone-4.22.2.tgz", + "integrity": "sha512-3LKzF04rUuli0H6FZhxWicaYkKiucGEDERmkvr7mMPLMW7uwzRjv26GxhLCggtRD5dI1+XbAm9QstyweOWYAeg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-aura": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-aura/-/codemirror-theme-aura-4.22.2.tgz", + "integrity": "sha512-/womOFwkrDFxKzqe13YZCtUmtljYeaYABpsDEyrJZnhtgW26i71tX4laDoI58DE9yuiyiAfgxWSDZhvmK/1mUA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-basic": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-basic/-/codemirror-theme-basic-4.22.2.tgz", + "integrity": "sha512-RHWhWMhCzTGscfbJvJtIRW9dccPWYS/BJiE5DtCPk1IYPnJHrXF3hzCpTK2CYz1/d+6V/XRyIzujhyxrMs0+mw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-bbedit": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bbedit/-/codemirror-theme-bbedit-4.22.2.tgz", + "integrity": "sha512-adeyK7fbdfDS3zELSUPbce7xMEMNhJbSX0hXxaL6L4jdmt2/OTHCwovNPzlH6AWyMupE+WTQmVY6y67m14dikg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-bespin": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-bespin/-/codemirror-theme-bespin-4.22.2.tgz", + "integrity": "sha512-NZ+kyWNzZgJq38Ba2FGl+nnZDAPdy97w5y160QiiAW+Vuwii24YnkYvoHVmMxsXDphwd/sLfVktfahbAh4ntpQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-console": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-console/-/codemirror-theme-console-4.22.2.tgz", + "integrity": "sha512-K8fir17aBiwkTBwMbyVbnPD+3Sx5irRtFzwG6TSK+EciWofsVkWGbkIZpDQqbUYbf4WBf5dw3j5byl3isNTWnA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + } + }, + "node_modules/@uiw/codemirror-theme-copilot": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-copilot/-/codemirror-theme-copilot-4.22.2.tgz", + "integrity": "sha512-M1mif1uqZdc49rPNCpXcs4gANr30w/I5C+dK+Mxwe6+RAkwcqNB/uHuIUj0UvARiSbwNUTpTMkMNXeuT//f3Pg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-darcula": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-darcula/-/codemirror-theme-darcula-4.22.2.tgz", + "integrity": "sha512-YF3LTTc1Rus3atNuFvkLsZvhdtxRwGEAOcEGknu7by5jzZxLPHpDLzAF9VFOIKg6j/KBZ2RNmMicVTjrB8LXHg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-dracula": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-dracula/-/codemirror-theme-dracula-4.22.2.tgz", + "integrity": "sha512-OEB5Tu0EHNDf99m5g39hoSqf0UICzWn+z3HLsOfdjzJz7hhZpQ99x/YEZ+j+utfLQbHcusvk06DrV+C88kRyyg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-duotone": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-duotone/-/codemirror-theme-duotone-4.22.2.tgz", + "integrity": "sha512-bZ8CvPcnHDIbMLhDrmF4M084Zf5daKiGyrmCFhHeFUeKGbIgIUu+UnkmaNjz6jteOmYEZR05wh8h3NK4Ty2OtA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-eclipse": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-eclipse/-/codemirror-theme-eclipse-4.22.2.tgz", + "integrity": "sha512-iYdl3wJ9D0e+z6opw5fLWzrJ1ge83/WpbPvtA++hqPZnFdoPfKynzdEq8U6V+uQpoVQnZuo0DRQljnWW070y2g==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-github": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-github/-/codemirror-theme-github-4.22.2.tgz", + "integrity": "sha512-tqGOOgVzbStJWUQmMLRWyymyD3DPF4TUSJx2DcXpeCF3YStWMd957I26uQQaqR1ppAcWFsprjkl2oL3piBxFdA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-gruvbox-dark": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-gruvbox-dark/-/codemirror-theme-gruvbox-dark-4.22.2.tgz", + "integrity": "sha512-U2YHFguSOX5xmCW/E5h7sAWrnkYRlJB2yB9ucOs/u1TohG7wD9zbGu5lCN40xflAB1BJ+r5y1j5uSQNgQrNheA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-kimbie": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-kimbie/-/codemirror-theme-kimbie-4.22.2.tgz", + "integrity": "sha512-dzXmOlgM8Ho6M4fzs95Zbttj9+VEFZtbS5nF98aoFqaqNeQU1iEk3EAtuNz9KRu9ogPvAeUsHqc76jnxB2J63g==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-material": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.22.2.tgz", + "integrity": "sha512-YJPNkNZo1RqtGgF3KNxFkSY7LDY8RqzJ7N5DR6o0d0IHCQSeTNk3TE14Gmc66GqfoPXfEQVLtPk/MiRZ/JQSpQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-monokai": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai/-/codemirror-theme-monokai-4.22.2.tgz", + "integrity": "sha512-lkpG9W2eDBylcqqD7w8zDXib9ydZOCnGOKRsZh651F4SN1iK4Za1hGrzx+g9PD+rpBBrGZ3UAomoAb2BZRV0og==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-monokai-dimmed": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-monokai-dimmed/-/codemirror-theme-monokai-dimmed-4.22.2.tgz", + "integrity": "sha512-q/jopOSfttwh/uc04XMLXKKs2EsqRpbL6krBLQhQKahDXRR54G1UcLQNG+KglBLWSuCc5dYlQwcTjzwAFQJWgA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-noctis-lilac": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-noctis-lilac/-/codemirror-theme-noctis-lilac-4.22.2.tgz", + "integrity": "sha512-nU+XC5NwbwZ+8X29OSMRV0e8eb1YCiwGkzuXN/cEnEnIcW5udNPydHalOwObOaDphC3dO+x8WSIf7LLycjpUsw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-nord": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-nord/-/codemirror-theme-nord-4.22.2.tgz", + "integrity": "sha512-aqvS4WqFT7NWmWZ42m75SOfOGQE0n1+Hryb4ZxGpBweo9FQjxq7sswUACZMqH0gY8+JTUSCoRu7fFq0QqCykCQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-okaidia": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-okaidia/-/codemirror-theme-okaidia-4.22.2.tgz", + "integrity": "sha512-X8QR4FB/GihU9Q/YTtpFGICLGQ7fY+zGJsmt7WAdvomBcfNe4grvj08MU+KoJ967iB1LvluH8cFVDRdu4Nh8sw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-quietlight": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-quietlight/-/codemirror-theme-quietlight-4.22.2.tgz", + "integrity": "sha512-NtHUttXgnDE8F+lrJA6HfiTNjY8GVJjUwtpvOQywqgNoBDwBpuEfdpvZ/OI0EW0sqZpbVlWfmANT4H5oj3ODaw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-red": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-red/-/codemirror-theme-red-4.22.2.tgz", + "integrity": "sha512-q+AZv6i9FrAXEFipcGsc754QPhWlLFIW/FEjcKz9fHXtmeSHR1e1ZONG/xzFcjRBor0rQizrZ/Buk3cFaCCNUQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-solarized": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-solarized/-/codemirror-theme-solarized-4.22.2.tgz", + "integrity": "sha512-WSjLMzgXXUFxdDUmAyOyX1NmxdTdcLtQXZzZkgkJAm1e/F8OKlbPAkDcrNsTrMV4SGpcM2iwJiauNIxG48evJA==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-sublime": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-sublime/-/codemirror-theme-sublime-4.22.2.tgz", + "integrity": "sha512-9qcRyflyhc4J9veTVAY3JfXXgs3QOPvIIGm3YrZTwcx8s0OTD+iLUSUY/UMwyqEGCIHhyrNr6dr8qisdIL0Zvg==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tokyo-night": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night/-/codemirror-theme-tokyo-night-4.22.2.tgz", + "integrity": "sha512-e6Z2r+TJsI+HY4vbozSOGkyoRQpcKb2BPY/7EHv+BRMzvUC62b8ZDbgYfctQBrEw50+vBRcs91slfMCWQ19AbQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tokyo-night-day": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-day/-/codemirror-theme-tokyo-night-day-4.22.2.tgz", + "integrity": "sha512-bP48DXp+tmFhGoRVyBtntkZl2a7tNacHwE2fDgj/Le97pHfzxS/DQ3iHqcUq49I0ZefQnswCZEASfupApqucvQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tokyo-night-storm": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tokyo-night-storm/-/codemirror-theme-tokyo-night-storm-4.22.2.tgz", + "integrity": "sha512-gnBxYrf0hVvE57yPaFX7esZsyRoDYWD3NBc6+YngBjGAq8CZ/JZphs13UBY3RNZq0ka9jJTkVanQOp+qb8cF1w==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-tomorrow-night-blue": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-tomorrow-night-blue/-/codemirror-theme-tomorrow-night-blue-4.22.2.tgz", + "integrity": "sha512-VFcqGpeXKS2laV4er9DiqnpOthmfMYQ85Jfmf44qe8T9iqFdE0YVyUNnKqH6vXrzaF17/PJgAxZFuohxAz381w==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-vscode": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.22.2.tgz", + "integrity": "sha512-wy+rd27Pz1f979QT8wHlBu7+XuwduSGDjqE972JRVf+Wqn2MbhXfTqD5YDx0lQJ+u05Y2IJKKbgPrWS+wt1a6A==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-white": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-white/-/codemirror-theme-white-4.22.2.tgz", + "integrity": "sha512-jFilUJLyNZkvMld+UFc/j27CHcMxxENiYHYrovBIO8lWN0qssvemvPNr/fKMFWuTHXwx6uGGUKP2MunxkrN0Fw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-theme-xcode": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-xcode/-/codemirror-theme-xcode-4.22.2.tgz", + "integrity": "sha512-pHOeWHMK7lmxGjrwBnvaIsFppMaKjDbZ6fgWCItWUFI+JeO4/Orrn+q0r1tRbsbRAMPaVuxl+SUZUhPGuo8GCw==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.22.2.tgz", + "integrity": "sha512-gsLHn6SUuV5iboBvGrM7YimzLFHQmsNlkGIYs3UaVUJTo/A/ZrKoSJNyPziShLRjBXA2UwKdBTIU6VhHyyaChw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/codemirror-themes-all": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes-all/-/codemirror-themes-all-4.22.2.tgz", + "integrity": "sha512-/57s1Pt8BUS+nBrFm8F4R1vpSs8FWhENL2PkfvO8opedTtFUzsIdV88jZHG8PDl7ydJta0ijO+w3qr4Z0IJ5ZQ==", + "license": "MIT", + "dependencies": { + "@uiw/codemirror-theme-abcdef": "4.22.2", + "@uiw/codemirror-theme-abyss": "4.22.2", + "@uiw/codemirror-theme-androidstudio": "4.22.2", + "@uiw/codemirror-theme-andromeda": "4.22.2", + "@uiw/codemirror-theme-atomone": "4.22.2", + "@uiw/codemirror-theme-aura": "4.22.2", + "@uiw/codemirror-theme-basic": "4.22.2", + "@uiw/codemirror-theme-bbedit": "4.22.2", + "@uiw/codemirror-theme-bespin": "4.22.2", + "@uiw/codemirror-theme-console": "4.22.2", + "@uiw/codemirror-theme-copilot": "4.22.2", + "@uiw/codemirror-theme-darcula": "4.22.2", + "@uiw/codemirror-theme-dracula": "4.22.2", + "@uiw/codemirror-theme-duotone": "4.22.2", + "@uiw/codemirror-theme-eclipse": "4.22.2", + "@uiw/codemirror-theme-github": "4.22.2", + "@uiw/codemirror-theme-gruvbox-dark": "4.22.2", + "@uiw/codemirror-theme-kimbie": "4.22.2", + "@uiw/codemirror-theme-material": "4.22.2", + "@uiw/codemirror-theme-monokai": "4.22.2", + "@uiw/codemirror-theme-monokai-dimmed": "4.22.2", + "@uiw/codemirror-theme-noctis-lilac": "4.22.2", + "@uiw/codemirror-theme-nord": "4.22.2", + "@uiw/codemirror-theme-okaidia": "4.22.2", + "@uiw/codemirror-theme-quietlight": "4.22.2", + "@uiw/codemirror-theme-red": "4.22.2", + "@uiw/codemirror-theme-solarized": "4.22.2", + "@uiw/codemirror-theme-sublime": "4.22.2", + "@uiw/codemirror-theme-tokyo-night": "4.22.2", + "@uiw/codemirror-theme-tokyo-night-day": "4.22.2", + "@uiw/codemirror-theme-tokyo-night-storm": "4.22.2", + "@uiw/codemirror-theme-tomorrow-night-blue": "4.22.2", + "@uiw/codemirror-theme-vscode": "4.22.2", + "@uiw/codemirror-theme-white": "4.22.2", + "@uiw/codemirror-theme-xcode": "4.22.2", + "@uiw/codemirror-themes": "4.22.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -3565,6 +4185,21 @@ "node": ">=6" } }, + "node_modules/codemirror": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3629,6 +4264,12 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7200,6 +7841,12 @@ "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", "dev": true }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -8008,6 +8655,12 @@ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/web/package.json b/web/package.json index bbc2e1229..6d5cbd238 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,9 @@ "coverage": "vitest run --coverage" }, "dependencies": { + "@codemirror/lang-yaml": "^6.1.1", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.28.1", "@cycjimmy/jsmpeg-player": "^6.0.5", "@hookform/resolvers": "^3.4.2", "@radix-ui/react-alert-dialog": "^1.0.5", @@ -35,10 +38,12 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", + "@uiw/codemirror-themes-all": "^4.22.2", "apexcharts": "^3.49.1", "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "codemirror": "^6.0.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", "hls.js": "^1.5.8", diff --git a/web/src/components/code_editor/CodeEditor.tsx b/web/src/components/code_editor/CodeEditor.tsx new file mode 100644 index 000000000..22df5ae88 --- /dev/null +++ b/web/src/components/code_editor/CodeEditor.tsx @@ -0,0 +1,21 @@ +import { isDesktop } from "react-device-detect"; +import { MonacoEditor } from "./MonacoEditor"; +import { CodeMirrorEditor } from "./CodeMirrorEditor"; + +/** + * Provides an interface for a Code Editor, to enable easy swapping between different implementations + */ +export type CodeEditorProps = { + initialContent: string; + yamlSchemaUrl: string; + onDidChangeContent: (content: string) => void; +}; + +/** Used to swap between the Monaco editor and the CodeMirror editor depending on device type. + * The Monaco editor doesn't work well on mobile devices, see Github issue: https://github.com/microsoft/monaco-editor/issues/246 + * + * A common solution is to switch to CodeMirror on mobile devices as they have invested significantly in touch support. + * + * It would be great to revisit this in in the future if/when Monaco improves mobile support. + */ +export const CodeEditor: (props: CodeEditorProps) => JSX.Element = isDesktop ? MonacoEditor : CodeMirrorEditor; diff --git a/web/src/components/code_editor/CodeMirrorEditor.tsx b/web/src/components/code_editor/CodeMirrorEditor.tsx new file mode 100644 index 000000000..6df71d6cb --- /dev/null +++ b/web/src/components/code_editor/CodeMirrorEditor.tsx @@ -0,0 +1,109 @@ +import { EditorState } from "@codemirror/state"; +import { yaml } from "@codemirror/lang-yaml"; +import { + EditorView, + crosshairCursor, + drawSelection, + dropCursor, + highlightActiveLine, + highlightActiveLineGutter, + highlightSpecialChars, + keymap, + lineNumbers, + rectangularSelection, +} from "@codemirror/view"; +import { useEffect, useRef } from "react"; +import type { CodeEditorProps } from "./CodeEditor"; +import { + indentOnInput, + bracketMatching, + foldGutter, + foldKeymap, +} from "@codemirror/language"; +import { + defaultKeymap, + history, + historyKeymap, + indentWithTab, +} from "@codemirror/commands"; +import { searchKeymap, highlightSelectionMatches } from "@codemirror/search"; +import { + autocompletion, + completionKeymap, + closeBrackets, + closeBracketsKeymap, +} from "@codemirror/autocomplete"; +import { lintKeymap } from "@codemirror/lint"; +import { vscodeLight, githubDark } from "@uiw/codemirror-themes-all"; +import { useTheme } from "@/context/theme-provider"; + +/** + * Uses `CodeMirror` as a code editor implementation. + * Currently doesn't support yaml schema validation or linting. + */ +export function CodeMirrorEditor({ + initialContent, + onDidChangeContent, +}: Omit) { + const containerRef = useRef(null); + const { theme, systemTheme } = useTheme(); + + useEffect(() => { + if (containerRef.current == null) { + return; + } + + const onChangeListener = EditorView.updateListener.of((update) => { + if (update.docChanged) { + onDidChangeContent(update.state.doc.toString()); + } + }); + + const state = EditorState.create({ + doc: initialContent, + extensions: [ + // vscodeDark isn't highlighting colors properly in yaml + (systemTheme || theme) == "dark" ? githubDark : vscodeLight, + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + dropCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + crosshairCursor(), + highlightActiveLine(), + highlightSelectionMatches(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...foldKeymap, + ...completionKeymap, + ...lintKeymap, + indentWithTab, + ]), + yaml(), + onChangeListener, + ], + }); + + const view = new EditorView({ + state, + parent: containerRef.current, + }); + + return () => { + view.destroy(); + }; + }, [initialContent, onDidChangeContent, systemTheme, theme]); + + return
; +} diff --git a/web/src/components/code_editor/MonacoEditor.tsx b/web/src/components/code_editor/MonacoEditor.tsx new file mode 100644 index 000000000..1b56b9f34 --- /dev/null +++ b/web/src/components/code_editor/MonacoEditor.tsx @@ -0,0 +1,80 @@ +import { useTheme } from "@/context/theme-provider"; +import * as monaco from "monaco-editor"; +import { configureMonacoYaml } from "monaco-yaml"; +import { useEffect, useRef, useState } from "react"; +import type { CodeEditorProps } from "./CodeEditor"; + +export function MonacoEditor({ + initialContent, + yamlSchemaUrl, + onDidChangeContent, +}: CodeEditorProps) { + const [editor, setEditor] = + useState(null); + const [model, setModel] = useState(null); + const containerRef = useRef(null); + + const { theme, systemTheme } = useTheme(); + + useEffect(() => { + const disposeOnDidChangeContent = model?.onDidChangeContent(() => { + onDidChangeContent(model.getValue()); + }).dispose; + return () => { + disposeOnDidChangeContent?.(); + }; + }, [model, onDidChangeContent]); + + useEffect(() => { + if (!initialContent) { + return; + } + + if (model != null && editor != null) { + // we don't need to recreate the editor if it already exists + editor.layout(); + return; + } + + const modelUri = monaco.Uri.parse("a://b/api/config/schema.json"); + + let tempModel: monaco.editor.ITextModel; + if (monaco.editor.getModels().length > 0) { + tempModel = monaco.editor.getModel(modelUri)!; + } else { + tempModel = monaco.editor.createModel(initialContent, "yaml", modelUri); + } + setModel(tempModel); + + configureMonacoYaml(monaco, { + enableSchemaRequest: true, + hover: true, + completion: true, + validate: true, + format: true, + schemas: [ + { + uri: yamlSchemaUrl, + fileMatch: [String(modelUri)], + }, + ], + }); + + if (containerRef.current != null) { + setEditor( + monaco.editor.create(containerRef.current, { + language: "yaml", + model: tempModel, + scrollBeyondLastLine: false, + theme: (systemTheme || theme) == "dark" ? "vs-dark" : "vs-light", + }), + ); + } + + return () => { + containerRef.current = null; + }; + }, [initialContent, model, editor, yamlSchemaUrl, systemTheme, theme]); + + return
; +} diff --git a/web/src/pages/ConfigEditor.tsx b/web/src/pages/ConfigEditor.tsx index 55df04e34..c8908e009 100644 --- a/web/src/pages/ConfigEditor.tsx +++ b/web/src/pages/ConfigEditor.tsx @@ -1,18 +1,16 @@ import useSWR from "swr"; -import * as monaco from "monaco-editor"; -import { configureMonacoYaml } from "monaco-yaml"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useApiHost } from "@/api"; import Heading from "@/components/ui/heading"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import { Button } from "@/components/ui/button"; import axios from "axios"; import copy from "copy-to-clipboard"; -import { useTheme } from "@/context/theme-provider"; import { Toaster } from "@/components/ui/sonner"; import { toast } from "sonner"; import { LuCopy, LuSave } from "react-icons/lu"; import { MdOutlineRestartAlt } from "react-icons/md"; +import { CodeEditor } from "@/components/code_editor/CodeEditor"; type SaveOptions = "saveonly" | "restart"; @@ -25,27 +23,17 @@ function ConfigEditor() { const { data: config } = useSWR("config/raw"); - const { theme, systemTheme } = useTheme(); const [error, setError] = useState(); - const editorRef = useRef(null); - const modelRef = useRef(null); - const configRef = useRef(null); + /** Contains the config once touched by the user */ + const [dirtyConfig, setDirtyConfig] = useState(); const onHandleSaveConfig = useCallback( async (save_option: SaveOptions) => { - if (!editorRef.current) { - return; - } - axios - .post( - `config/save?save_option=${save_option}`, - editorRef.current.getValue(), - { - headers: { "Content-Type": "text/plain" }, - }, - ) + .post(`config/save?save_option=${save_option}`, dirtyConfig, { + headers: { "Content-Type": "text/plain" }, + }) .then((response) => { if (response.status === 200) { setError(""); @@ -62,73 +50,16 @@ function ConfigEditor() { } }); }, - [editorRef], + [dirtyConfig], ); const handleCopyConfig = useCallback(async () => { - if (!editorRef.current) { + if (!dirtyConfig) { return; } - - copy(editorRef.current.getValue()); + copy(dirtyConfig); toast.success("Config copied to clipboard.", { position: "top-center" }); - }, [editorRef]); - - useEffect(() => { - if (!config) { - return; - } - - if (modelRef.current != null) { - // we don't need to recreate the editor if it already exists - editorRef.current?.layout(); - return; - } - - const modelUri = monaco.Uri.parse("a://b/api/config/schema.json"); - - if (monaco.editor.getModels().length > 0) { - modelRef.current = monaco.editor.getModel(modelUri); - } else { - modelRef.current = monaco.editor.createModel(config, "yaml", modelUri); - } - - configureMonacoYaml(monaco, { - enableSchemaRequest: true, - hover: true, - completion: true, - validate: true, - format: true, - schemas: [ - { - uri: `${apiHost}api/config/schema.json`, - fileMatch: [String(modelUri)], - }, - ], - }); - - const container = configRef.current; - - if (container != null) { - editorRef.current = monaco.editor.create(container, { - language: "yaml", - model: modelRef.current, - scrollBeyondLastLine: false, - theme: (systemTheme || theme) == "dark" ? "vs-dark" : "vs-light", - }); - } - - return () => { - configRef.current = null; - modelRef.current = null; - }; - }); - - useEffect(() => { - if (config && modelRef.current) { - modelRef.current.setValue(config); - } - }, [config]); + }, [dirtyConfig]); if (!config) { return ; @@ -178,7 +109,13 @@ function ConfigEditor() {
)} -
+
+ +