Implement undo and redo

This commit is contained in:
Misode
2024-10-22 22:55:04 +02:00
parent ea37eb168f
commit c358c871da
5 changed files with 140 additions and 56 deletions

View File

@@ -3,6 +3,7 @@ import { getCurrentUrl } from 'preact-router'
import { useEffect, useMemo, useState } from 'preact/hooks'
import { useSpyglass } from '../contexts/Spyglass.jsx'
import { useVersion } from '../contexts/Version.jsx'
import { useAsync } from '../hooks/useAsync.js'
import { latestVersion } from '../services/DataFetcher.js'
import { getGenerator } from '../Utils.js'
import { Octicon } from './index.js'
@@ -21,9 +22,14 @@ export function ErrorPanel({ error, prefix, reportable, onDismiss, body: body_,
const [stackVisible, setStackVisible] = useState(false)
const [stack, setStack] = useState<string | undefined>(undefined)
const gen = getGenerator(getCurrentUrl())
const source = gen ? spyglass?.getFileContents(spyglass.getUnsavedFileUri(version, gen)) : undefined
const name = (prefix ?? '') + (error instanceof Error ? error.message : error)
const gen = getGenerator(getCurrentUrl())
const { value: source } = useAsync(async () => {
if (gen) {
return await spyglass?.readFile(spyglass.getUnsavedFileUri(version, gen))
}
return undefined
}, [spyglass, version, gen])
useEffect(() => {
if (error instanceof Error) {
@@ -57,7 +63,7 @@ export function ErrorPanel({ error, prefix, reportable, onDismiss, body: body_,
body += `\n### Stack trace\n\`\`\`\n${fullName}\n${stack}\n\`\`\`\n`
}
if (source) {
body += `\n### Generator JSON\n<details>\n<pre>\n${JSON.stringify(source, null, 2)}\n</pre>\n</details>\n`
body += `\n### Generator JSON\n<details>\n<pre>\n${source}\n</pre>\n</details>\n`
}
if (body_) {
body += body_

View File

@@ -3,23 +3,25 @@ import { JsonArrayNode, JsonBooleanNode, JsonNumberNode, JsonObjectNode, JsonStr
import type { ListType, LiteralType, McdocType } from '@spyglassmc/mcdoc'
import type { SimplifiedStructType } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js'
import { useLocale } from '../../contexts/Locale.jsx'
import type { Edit } from '../../services/Spyglass.js'
import { Octicon } from '../Octicon.jsx'
interface Props {
node: JsonNode | undefined
makeEdits: (edits: Edit[]) => void
}
export function McdocRoot({ node } : Props) {
export function McdocRoot({ node, makeEdits } : Props) {
const type = node?.typeDef ?? { kind: 'unsafe' }
if (type.kind === 'struct') {
return <StructBody type={type} node={node} />
return <StructBody type={type} node={node} makeEdits={makeEdits} />
}
return <>
<div class="node-header">
<Head simpleType={type} node={node} />
<Head simpleType={type} node={node} makeEdits={makeEdits} />
</div>
<Body simpleType={type} node={node} />
<Body simpleType={type} node={node} makeEdits={makeEdits} />
</>
}
@@ -89,19 +91,23 @@ function Head({ simpleType, optional, node }: HeadProps) {
interface BodyProps extends Props {
simpleType: McdocType
}
function Body({ simpleType, node }: BodyProps) {
function Body({ simpleType, node, makeEdits }: BodyProps) {
const type = node?.typeDef ?? simpleType
if (node?.typeDef?.kind === 'struct') {
if (node.typeDef.fields.length === 0) {
return <></>
}
return <div class="node-body">
<StructBody type={node.typeDef} node={node} />
<StructBody type={node.typeDef} node={node} makeEdits={makeEdits} />
</div>
}
if (node?.typeDef?.kind === 'list') {
const fixedRange = node.typeDef.lengthRange?.min !== undefined && node.typeDef.lengthRange.min === node.typeDef.lengthRange.max
if (!fixedRange && node.children?.length === 0) {
return <></>
}
return <div class="node-body">
<ListBody type={node.typeDef} node={node} />
<ListBody type={node.typeDef} node={node} makeEdits={makeEdits} />
</div>
}
if (type.kind === 'byte' || type.kind === 'short' || type.kind === 'int' || type.kind === 'boolean') {
@@ -114,7 +120,7 @@ function Body({ simpleType, node }: BodyProps) {
interface StructBodyProps extends Props {
type: SimplifiedStructType
}
function StructBody({ type, node }: StructBodyProps) {
function StructBody({ type, node, makeEdits }: StructBodyProps) {
if (!JsonObjectNode.is(node)) {
return <></>
}
@@ -133,9 +139,9 @@ function StructBody({ type, node }: StructBodyProps) {
return <div class="node">
<div class="node-header">
<Key label={key} />
<Head simpleType={field.type} node={child} optional={field.optional} />
<Head simpleType={field.type} node={child} optional={field.optional} makeEdits={makeEdits} />
</div>
<Body simpleType={field.type} node={child} />
<Body simpleType={field.type} node={child} makeEdits={makeEdits} />
</div>
})}
</>
@@ -150,7 +156,7 @@ function Key({ label }: { label: string | number | boolean }) {
interface ListBodyProps extends Props {
type: ListType
}
function ListBody({ type, node }: ListBodyProps) {
function ListBody({ type, node, makeEdits }: ListBodyProps) {
const { locale } = useLocale()
if (!JsonArrayNode.is(node)) {
return <></>
@@ -160,7 +166,7 @@ function ListBody({ type, node }: ListBodyProps) {
const child = item.value
return <div class="node">
<div class="node-header">
<button class="remove tooltipped tip-se" aria-label={locale('remove')}>
<button class="remove tooltipped tip-se" aria-label={locale('remove')} onClick={() => makeEdits([{ range: item.range, text: '' }])}>
{Octicon.trashcan}
</button>
{node.children.length > 1 && <div class="node-move">
@@ -172,9 +178,9 @@ function ListBody({ type, node }: ListBodyProps) {
</button>
</div>}
<Key label="entry" />
<Head simpleType={type.item} node={child} />
<Head simpleType={type.item} node={child} makeEdits={makeEdits} />
</div>
<Body simpleType={type.item} node={child} />
<Body simpleType={type.item} node={child} makeEdits={makeEdits} />
</div>
})}
</>

View File

@@ -110,25 +110,25 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
Analytics.resetGenerator(gen.id, 1, 'menu')
// TODO
}
const undo = (e: MouseEvent) => {
const undo = async (e: MouseEvent) => {
e.stopPropagation()
Analytics.undoGenerator(gen.id, 1, 'menu')
// TODO
await spyglass.undoEdits(version, uri)
}
const redo = (e: MouseEvent) => {
const redo = async (e: MouseEvent) => {
e.stopPropagation()
Analytics.redoGenerator(gen.id, 1, 'menu')
// TODO
await spyglass.redoEdits(version, uri)
}
useEffect(() => {
const onKeyUp = (e: KeyboardEvent) => {
const onKeyUp = async (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'z') {
Analytics.undoGenerator(gen.id, 1, 'hotkey')
// TODO
await spyglass.undoEdits(version, uri)
} else if (e.ctrlKey && e.key === 'y') {
Analytics.redoGenerator(gen.id, 1, 'hotkey')
// TODO
await spyglass.redoEdits(version, uri)
}
}
const onKeyDown = (e: KeyboardEvent) => {
@@ -145,7 +145,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
document.removeEventListener('keyup', onKeyUp)
document.removeEventListener('keydown', onKeyDown)
}
}, [gen.id])
}, [gen.id, spyglass, version, uri])
const { value: presets } = useAsync(async () => {
const registries = await fetchRegistries(version)

View File

@@ -1,8 +1,9 @@
import type { DocAndNode } from '@spyglassmc/core'
import { JsonFileNode } from '@spyglassmc/json'
import { useErrorBoundary } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useDocAndNode } from '../../contexts/Spyglass.jsx'
import { useCallback, useErrorBoundary } from 'preact/hooks'
import { useLocale, useVersion } from '../../contexts/index.js'
import { useDocAndNode, useSpyglass } from '../../contexts/Spyglass.jsx'
import type { Edit } from '../../services/Spyglass.js'
import { McdocRoot } from './McdocRenderer.jsx'
type TreePanelProps = {
@@ -11,6 +12,9 @@ type TreePanelProps = {
}
export function Tree({ docAndNode, onError }: TreePanelProps) {
const { lang } = useLocale()
const { version } = useVersion()
const { spyglass } = useSpyglass()
if (lang === 'none') return <></>
const fileChild = useDocAndNode(docAndNode).node.children[0]
@@ -24,7 +28,11 @@ export function Tree({ docAndNode, onError }: TreePanelProps) {
})
if (error) return <></>
const makeEdits = useCallback((edits: Edit[]) => {
spyglass.applyEdits(version, docAndNode.doc.uri, edits)
}, [spyglass, version, docAndNode])
return <div class="tree node-root" data-cy="tree">
<McdocRoot node={fileChild.children[0]} />
<McdocRoot node={fileChild.children[0]} makeEdits={makeEdits} />
</div>
}