feat: switch back to codemirror with jsonc support
This commit is contained in:
parent
a9e8394dec
commit
d1fa76971b
103
app/routes/_data.acls._index/cm.tsx
Normal file
103
app/routes/_data.acls._index/cm.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import Merge from 'react-codemirror-merge'
|
||||||
|
import CodeMirror from '@uiw/react-codemirror'
|
||||||
|
import { ClientOnly } from 'remix-utils/client-only'
|
||||||
|
import { jsonc } from '@shopify/lang-jsonc'
|
||||||
|
import { githubDark, githubLight } from '@uiw/codemirror-theme-github'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { cn } from '~/utils/cn'
|
||||||
|
|
||||||
|
import Fallback from './fallback'
|
||||||
|
|
||||||
|
interface EditorProps {
|
||||||
|
isDisabled?: boolean
|
||||||
|
onChange: (value: string) => void
|
||||||
|
defaultValue?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Editor(props: EditorProps) {
|
||||||
|
const [value, setValue] = useState(props.defaultValue ?? '')
|
||||||
|
const [light, setLight] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
const theme = window.matchMedia('(prefers-color-scheme: light)')
|
||||||
|
setLight(theme.matches)
|
||||||
|
theme.addEventListener('change', (theme) => {
|
||||||
|
setLight(theme.matches)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'border border-gray-200 dark:border-gray-700',
|
||||||
|
'rounded-b-lg rounded-tr-lg mb-2 z-10 overflow-x-hidden',
|
||||||
|
)}>
|
||||||
|
<div className="overflow-y-scroll h-editor text-sm">
|
||||||
|
<ClientOnly fallback={<Fallback acl={value} />}>
|
||||||
|
{() => (
|
||||||
|
<CodeMirror
|
||||||
|
value={value}
|
||||||
|
height="100%"
|
||||||
|
extensions={[jsonc()]}
|
||||||
|
theme={light ? githubLight : githubDark}
|
||||||
|
onChange={(value) => props.onChange(value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DifferProps {
|
||||||
|
left: string
|
||||||
|
right: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Differ(props: DifferProps) {
|
||||||
|
const [light, setLight] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
const theme = window.matchMedia('(prefers-color-scheme: light)')
|
||||||
|
setLight(theme.matches)
|
||||||
|
theme.addEventListener('change', (theme) => {
|
||||||
|
setLight(theme.matches)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'border border-gray-200 dark:border-gray-700',
|
||||||
|
'rounded-b-lg rounded-tr-lg mb-2 z-10 overflow-x-hidden',
|
||||||
|
)}>
|
||||||
|
<div className="overflow-y-scroll h-editor text-sm">
|
||||||
|
{props.left === props.right ? (
|
||||||
|
<p className={cn(
|
||||||
|
'w-full h-full flex items-center justify-center',
|
||||||
|
'text-gray-400 dark:text-gray-500 text-xl',
|
||||||
|
)}>
|
||||||
|
No changes
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<ClientOnly fallback={<Fallback acl={props.right} />}>
|
||||||
|
{() => (
|
||||||
|
<Merge
|
||||||
|
orientation="a-b"
|
||||||
|
theme={light ? githubLight : githubDark}
|
||||||
|
>
|
||||||
|
<Merge.Original
|
||||||
|
readOnly
|
||||||
|
value={props.left}
|
||||||
|
extensions={[jsonc()]}
|
||||||
|
/>
|
||||||
|
<Merge.Modified
|
||||||
|
readOnly
|
||||||
|
value={props.right}
|
||||||
|
extensions={[jsonc()]}
|
||||||
|
/>
|
||||||
|
</Merge>
|
||||||
|
)}
|
||||||
|
</ClientOnly>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import Editor, { DiffEditor, Monaco } from '@monaco-editor/react'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { ClientOnly } from 'remix-utils/client-only'
|
|
||||||
|
|
||||||
import Fallback from '~/routes/_data.acls._index/fallback'
|
|
||||||
import { cn } from '~/utils/cn'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
variant: 'edit' | 'diff'
|
|
||||||
language: 'json' | 'yaml'
|
|
||||||
state: [string, (value: string) => void]
|
|
||||||
policy?: string
|
|
||||||
isDisabled?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function monacoCallback(monaco: Monaco) {
|
|
||||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
|
||||||
validate: true,
|
|
||||||
allowComments: true,
|
|
||||||
schemas: [],
|
|
||||||
enableSchemaRequest: true,
|
|
||||||
trailingCommas: 'ignore',
|
|
||||||
})
|
|
||||||
|
|
||||||
monaco.languages.register({ id: 'json' })
|
|
||||||
monaco.languages.register({ id: 'yaml' })
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MonacoEditor({ variant, language, state, policy, isDisabled }: Props) {
|
|
||||||
const [light, setLight] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const theme = window.matchMedia('(prefers-color-scheme: light)')
|
|
||||||
setLight(theme.matches)
|
|
||||||
|
|
||||||
theme.addEventListener('change', (theme) => {
|
|
||||||
setLight(theme.matches)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={cn(
|
|
||||||
'border border-gray-200 dark:border-gray-700',
|
|
||||||
'rounded-b-lg rounded-tr-lg mb-2 z-10 overflow-x-hidden',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="overflow-y-scroll h-editor text-sm">
|
|
||||||
<ClientOnly fallback={<Fallback acl={state[0]} />}>
|
|
||||||
{() => variant === 'edit'
|
|
||||||
? (
|
|
||||||
<Editor
|
|
||||||
height="100%"
|
|
||||||
language={language}
|
|
||||||
theme={light ? 'light' : 'vs-dark'}
|
|
||||||
value={state[0]}
|
|
||||||
onChange={(updated) => {
|
|
||||||
if (!updated) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated !== state[0]) {
|
|
||||||
state[1](updated)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
loading={<Fallback acl={state[0]} />}
|
|
||||||
beforeMount={monacoCallback}
|
|
||||||
options={{
|
|
||||||
wordWrap: 'on',
|
|
||||||
minimap: { enabled: false },
|
|
||||||
fontSize: 14,
|
|
||||||
readOnly: isDisabled,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<DiffEditor
|
|
||||||
height="100%"
|
|
||||||
language={language}
|
|
||||||
theme={light ? 'light' : 'vs-dark'}
|
|
||||||
original={policy}
|
|
||||||
modified={state[0]}
|
|
||||||
loading={<Fallback acl={state[0]} />}
|
|
||||||
beforeMount={monacoCallback}
|
|
||||||
options={{
|
|
||||||
wordWrap: 'on',
|
|
||||||
minimap: { enabled: false },
|
|
||||||
fontSize: 13,
|
|
||||||
readOnly: isDisabled,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import Spinner from '~/components/Spinner'
|
import Spinner from '~/components/Spinner'
|
||||||
import { cn } from '~/utils/cn'
|
import { cn } from '~/utils/cn'
|
||||||
|
|
||||||
interface FallbackProps {
|
interface Props {
|
||||||
readonly acl: string
|
readonly acl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Fallback({ acl }: FallbackProps) {
|
export default function Fallback({ acl }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="inline-block relative w-full h-editor">
|
<div className="inline-block relative w-full h-editor">
|
||||||
<Spinner className="w-4 h-4 absolute p-2" />
|
<Spinner className="w-4 h-4 absolute p-2" />
|
||||||
@ -15,7 +15,7 @@ export default function Fallback({ acl }: FallbackProps) {
|
|||||||
'w-full h-editor font-mono resize-none',
|
'w-full h-editor font-mono resize-none',
|
||||||
'text-sm text-gray-600 dark:text-gray-300',
|
'text-sm text-gray-600 dark:text-gray-300',
|
||||||
'bg-ui-100 dark:bg-ui-800',
|
'bg-ui-100 dark:bg-ui-800',
|
||||||
'pl-16 pr-8 pt-0.5 leading-snug',
|
'pl-10 pt-1 leading-snug',
|
||||||
)}
|
)}
|
||||||
value={acl}
|
value={acl}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { loadContext } from '~/utils/config/headplane'
|
|||||||
import { HeadscaleError, pull, put } from '~/utils/headscale'
|
import { HeadscaleError, pull, put } from '~/utils/headscale'
|
||||||
import { getSession } from '~/utils/sessions'
|
import { getSession } from '~/utils/sessions'
|
||||||
|
|
||||||
import Monaco from './editor'
|
import { Editor, Differ } from './cm'
|
||||||
|
|
||||||
export async function loader({ request }: LoaderFunctionArgs) {
|
export async function loader({ request }: LoaderFunctionArgs) {
|
||||||
const session = await getSession(request.headers.get('Cookie'))
|
const session = await getSession(request.headers.get('Cookie'))
|
||||||
@ -254,19 +254,16 @@ export default function Page() {
|
|||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel id="edit">
|
<TabPanel id="edit">
|
||||||
<Monaco
|
<Editor
|
||||||
isDisabled={!data.hasAclWrite}
|
isDisabled={!data.hasAclWrite}
|
||||||
variant="edit"
|
defaultValue={data.currentAcl}
|
||||||
language={data.aclType}
|
onChange={setAcl}
|
||||||
state={[acl, setAcl]}
|
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="diff">
|
<TabPanel id="diff">
|
||||||
<Monaco
|
<Differ
|
||||||
variant="diff"
|
left={data.currentAcl}
|
||||||
language={data.aclType}
|
right={acl}
|
||||||
state={[acl, setAcl]}
|
|
||||||
policy={data.currentAcl}
|
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="preview">
|
<TabPanel id="preview">
|
||||||
|
|||||||
16
package.json
16
package.json
@ -16,20 +16,23 @@
|
|||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@kubernetes/client-node": "^0.21.0",
|
"@kubernetes/client-node": "^0.21.0",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
|
||||||
"@primer/octicons-react": "^19.10.0",
|
"@primer/octicons-react": "^19.10.0",
|
||||||
"@react-aria/toast": "3.0.0-beta.12",
|
"@react-aria/toast": "3.0.0-beta.12",
|
||||||
"@react-stately/toast": "3.0.0-beta.4",
|
"@react-stately/toast": "3.0.0-beta.4",
|
||||||
"@remix-run/node": "^2.10.2",
|
"@remix-run/node": "^2.10.2",
|
||||||
"@remix-run/react": "^2.10.2",
|
"@remix-run/react": "^2.10.2",
|
||||||
"@remix-run/serve": "^2.10.2",
|
"@remix-run/serve": "^2.10.2",
|
||||||
|
"@shopify/lang-jsonc": "^1.0.0",
|
||||||
|
"@uiw/codemirror-theme-github": "^4.23.5",
|
||||||
|
"@uiw/react-codemirror": "^4.23.5",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"isbot": "^5.1.11",
|
"isbot": "^5.1.11",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"react": "19.0.0-rc-f38c22b244-20240704",
|
"react": "^18.3.1",
|
||||||
"react-aria-components": "^1.2.1",
|
"react-aria-components": "^1.2.1",
|
||||||
"react-dom": "19.0.0-rc-f38c22b244-20240704",
|
"react-codemirror-merge": "^4.23.5",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
"remix-utils": "^7.6.0",
|
"remix-utils": "^7.6.0",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss-react-aria-components": "^1.1.3",
|
"tailwindcss-react-aria-components": "^1.1.3",
|
||||||
@ -43,8 +46,8 @@
|
|||||||
"@types/react": "npm:types-react@beta",
|
"@types/react": "npm:types-react@beta",
|
||||||
"@types/react-dom": "npm:types-react-dom@beta",
|
"@types/react-dom": "npm:types-react-dom@beta",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"babel-plugin-react-compiler": "0.0.0-experimental-c23de8d-20240515",
|
"babel-plugin-react-compiler": "0.0.0-experimental-fa06e2c-20241016",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-tale": "^2.0.4",
|
"eslint-config-tale": "^2.0.4",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
@ -64,7 +67,8 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@react-aria/overlays@3.22.1": "patches/@react-aria__overlays@3.22.1.patch"
|
"@react-aria/overlays@3.22.1": "patches/@react-aria__overlays@3.22.1.patch",
|
||||||
|
"@shopify/lang-jsonc@1.0.0": "patches/@shopify__lang-jsonc@1.0.0.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
patches/@shopify__lang-jsonc@1.0.0.patch
Normal file
44
patches/@shopify__lang-jsonc@1.0.0.patch
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
diff --git a/dist/esm/index.js b/dist/esm/index.js
|
||||||
|
index 8b5b71f8eee6606ca8de47c15eef0ce73c01d93f..2bfa4e7c7ff650b35b1bdee75c28bd6fbf6c6704 100644
|
||||||
|
--- a/dist/esm/index.js
|
||||||
|
+++ b/dist/esm/index.js
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
-export { jsonc, jsoncLanguage } from './jsonc';
|
||||||
|
-export { parser } from './parser';
|
||||||
|
+export { jsonc, jsoncLanguage } from './jsonc.js';
|
||||||
|
+export { parser } from './parser.js';
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/dist/esm/jsonc.js b/dist/esm/jsonc.js
|
||||||
|
index 5f968887d47696122c6bbfc4b98f94af6477f37a..b144021c4b5d4504c73f6c2172e8d1c2ddbfe3ac 100644
|
||||||
|
--- a/dist/esm/jsonc.js
|
||||||
|
+++ b/dist/esm/jsonc.js
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-import { parser } from './parser';
|
||||||
|
+import { parser } from './parser.js';
|
||||||
|
import { continuedIndent, indentNodeProp, foldNodeProp, foldInside, LRLanguage, LanguageSupport, } from '@codemirror/language';
|
||||||
|
/// A language provider that provides JSON parsing.
|
||||||
|
export const jsoncLanguage = LRLanguage.define({
|
||||||
|
diff --git a/dist/esm/parser.js b/dist/esm/parser.js
|
||||||
|
index 3d966fcf7c55003b1ba6c2b3f531b7c9b8045cb8..f6e8f56c3506f76f31571a49af8cd44364a864a3 100644
|
||||||
|
--- a/dist/esm/parser.js
|
||||||
|
+++ b/dist/esm/parser.js
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
import {LRParser} from "@lezer/lr"
|
||||||
|
-import {jsonHighlighting} from "./highlight"
|
||||||
|
+import {jsonHighlighting} from "./highlight.js"
|
||||||
|
export const parser = LRParser.deserialize({
|
||||||
|
version: 14,
|
||||||
|
states: "$zO]QPOOOOQO'#Cd'#CdOtQPO'#CgO|QPO'#ClOOQO'#Cr'#CrQOQPOOOOQO'#Ci'#CiO!TQPO'#ChO!YQPO'#CtOOQO,59R,59RO!bQPO,59RO!gQPO'#CwOOQO,59W,59WO!oQPO,59WO]QPO,59SO!tQPO,59`O!|QPO,59`OOQO1G.m1G.mO#UQPO,59cO#]QPO,59cOOQO1G.r1G.rOOQO1G.n1G.nOOQO,59X,59XO#eQPO1G.zOOQO-E6k-E6kOOQO,59Y,59YO#mQPO1G.}OOQO-E6l-E6lPwQPO'#CmP]QPO'#Cn",
|
||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index 9c3a56db25535b7eb0b1f951abe486d530a2714f..5e858500f155c16ecb068854552a71e1da5f04fc 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -1,5 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "@shopify/lang-jsonc",
|
||||||
|
+ "type": "module",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "JSONC language support for CodeMirror",
|
||||||
|
"publishConfig": {
|
||||||
2269
pnpm-lock.yaml
2269
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user