feat: switch back to codemirror with jsonc support

This commit is contained in:
Aarnav Tale
2024-10-22 00:41:44 -04:00
parent a9e8394dec
commit d1fa76971b
7 changed files with 1456 additions and 1098 deletions
+103
View 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>
)
}
-99
View File
@@ -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>
</>
)
}
+3 -3
View File
@@ -1,11 +1,11 @@
import Spinner from '~/components/Spinner'
import { cn } from '~/utils/cn'
interface FallbackProps {
interface Props {
readonly acl: string
}
export default function Fallback({ acl }: FallbackProps) {
export default function Fallback({ acl }: Props) {
return (
<div className="inline-block relative w-full h-editor">
<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',
'text-sm text-gray-600 dark:text-gray-300',
'bg-ui-100 dark:bg-ui-800',
'pl-16 pr-8 pt-0.5 leading-snug',
'pl-10 pt-1 leading-snug',
)}
value={acl}
/>
+7 -10
View File
@@ -17,7 +17,7 @@ import { loadContext } from '~/utils/config/headplane'
import { HeadscaleError, pull, put } from '~/utils/headscale'
import { getSession } from '~/utils/sessions'
import Monaco from './editor'
import { Editor, Differ } from './cm'
export async function loader({ request }: LoaderFunctionArgs) {
const session = await getSession(request.headers.get('Cookie'))
@@ -254,19 +254,16 @@ export default function Page() {
</Tab>
</TabList>
<TabPanel id="edit">
<Monaco
<Editor
isDisabled={!data.hasAclWrite}
variant="edit"
language={data.aclType}
state={[acl, setAcl]}
defaultValue={data.currentAcl}
onChange={setAcl}
/>
</TabPanel>
<TabPanel id="diff">
<Monaco
variant="diff"
language={data.aclType}
state={[acl, setAcl]}
policy={data.currentAcl}
<Differ
left={data.currentAcl}
right={acl}
/>
</TabPanel>
<TabPanel id="preview">