From 2137b2fa8deeb53b95e8a8d5c349e9eaebc8bcb1 Mon Sep 17 00:00:00 2001 From: Misode Date: Mon, 27 Feb 2023 09:12:42 +0100 Subject: [PATCH] Fix #272 split generator page --- src/app/components/ErrorPanel.tsx | 8 +- .../components/generator/SchemaGenerator.tsx | 365 +++++++++++++++++ src/app/pages/Generator.tsx | 381 ++---------------- src/locales/en.json | 5 + src/styles/global.css | 30 +- 5 files changed, 427 insertions(+), 362 deletions(-) create mode 100644 src/app/components/generator/SchemaGenerator.tsx diff --git a/src/app/components/ErrorPanel.tsx b/src/app/components/ErrorPanel.tsx index c8e6c60b..e14062d1 100644 --- a/src/app/components/ErrorPanel.tsx +++ b/src/app/components/ErrorPanel.tsx @@ -1,3 +1,4 @@ +import type { ComponentChildren } from 'preact' import { getCurrentUrl } from 'preact-router' import { useEffect, useMemo, useState } from 'preact/hooks' import { useVersion } from '../contexts/Version.jsx' @@ -8,9 +9,11 @@ import { Octicon } from './index.js' type ErrorPanelProps = { error: string | Error, + reportable?: boolean, onDismiss?: () => unknown, + children?: ComponentChildren, } -export function ErrorPanel({ error, onDismiss }: ErrorPanelProps) { +export function ErrorPanel({ error, reportable, onDismiss, children }: ErrorPanelProps) { const { version } = useVersion() const [stackVisible, setStackVisible] = useState(false) const [stack, setStack] = useState(undefined) @@ -64,6 +67,7 @@ export function ErrorPanel({ error, onDismiss }: ErrorPanelProps) { } {stack && stackVisible &&
{stack}
} -

If you think this is a bug, you can report it on GitHub

+ {reportable !== false &&

If you think this is a bug, you can report it on GitHub

} + {children} } diff --git a/src/app/components/generator/SchemaGenerator.tsx b/src/app/components/generator/SchemaGenerator.tsx new file mode 100644 index 00000000..07c87f57 --- /dev/null +++ b/src/app/components/generator/SchemaGenerator.tsx @@ -0,0 +1,365 @@ +import { DataModel, Path } from '@mcschema/core' +import { route } from 'preact-router' +import { useCallback, useEffect, useErrorBoundary, useMemo, useRef, useState } from 'preact/hooks' +import { Analytics } from '../../Analytics.js' +import type { ConfigGenerator } from '../../Config.js' +import config from '../../Config.js' +import { DRAFT_PROJECT, useLocale, useProject, useVersion } from '../../contexts/index.js' +import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../../hooks/index.js' +import { getOutput } from '../../schema/transformOutput.js' +import type { VersionId } from '../../services/index.js' +import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../../services/index.js' +import { Store } from '../../Store.js' +import { cleanUrl, deepEqual } from '../../Utils.js' +import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js' + +export const SHARE_KEY = 'share' + +interface Props { + gen: ConfigGenerator + allowedVersions: VersionId[], +} +export function SchemaGenerator({ gen, allowedVersions }: Props) { + const { locale } = useLocale() + const { version, changeVersion, changeTargetVersion } = useVersion() + const { projects, project, file, updateProject, updateFile } = useProject() + const [error, setError] = useState(null) + const [errorBoundary, errorRetry] = useErrorBoundary() + if (errorBoundary) { + errorBoundary.message = `Something went wrong rendering the generator: ${errorBoundary.message}` + return
+ } + + useEffect(() => Store.visitGenerator(gen.id), [gen.id]) + + const [currentPreset, setCurrentPreset] = useSearchParam('preset') + const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY) + const ignoreChange = useRef(false) + const backup = useMemo(() => Store.getBackup(gen.id), [gen.id]) + + const loadBackup = () => { + if (backup !== undefined) { + model?.reset(DataModel.wrapLists(backup), false) + } + } + + const { value } = useAsync(async () => { + let data: unknown = undefined + if (currentPreset && sharedSnippetId) { + setSharedSnippetId(undefined) + return AsyncCancel + } + if (currentPreset) { + data = await loadPreset(currentPreset) + } else if (sharedSnippetId) { + const snippet = await getSnippet(sharedSnippetId) + let cancel = false + if (snippet.version && snippet.version !== version) { + changeVersion(snippet.version, false) + cancel = true + } + if (snippet.type && snippet.type !== gen.id) { + const snippetGen = config.generators.find(g => g.id === snippet.type) + if (snippetGen) { + route(`${cleanUrl(snippetGen.url)}?${SHARE_KEY}=${snippet.id}`) + cancel = true + } + } + if (cancel) { + return AsyncCancel + } + if (snippet.show_preview && !previewShown) { + setPreviewShown(true) + setSourceShown(false) + } + Analytics.openSnippet(gen.id, sharedSnippetId, version) + data = snippet.data + } else if (file) { + if (project.version && project.version !== version) { + changeVersion(project.version, false) + return AsyncCancel + } + data = file.data + } + const [model, blockStates] = await Promise.all([ + getModel(version, gen.id), + getBlockStates(version), + ]) + if (data) { + ignoreChange.current = true + model.reset(DataModel.wrapLists(data), false) + } + Analytics.setGenerator(gen.id) + return { model, blockStates } + }, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id]) + + const model = value?.model + const blockStates = value?.blockStates + + useModel(model, model => { + if (!ignoreChange.current) { + setCurrentPreset(undefined, true) + setSharedSnippetId(undefined, true) + } + if (file && model && blockStates) { + const data = getOutput(model, blockStates) + updateFile(gen.id, file.id, { id: file.id, data }) + } + ignoreChange.current = false + Store.setBackup(gen.id, DataModel.unwrapLists(model.data)) + setError(null) + }, [gen.id, setCurrentPreset, setSharedSnippetId, blockStates, file?.id]) + + const reset = () => { + Analytics.resetGenerator(gen.id, model?.historyIndex ?? 1, 'menu') + model?.reset(DataModel.wrapLists(model.schema.default()), true) + } + const undo = (e: MouseEvent) => { + e.stopPropagation() + Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'menu') + model?.undo() + } + const redo = (e: MouseEvent) => { + e.stopPropagation() + Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'menu') + model?.redo() + } + + const onKeyUp = (e: KeyboardEvent) => { + if (e.ctrlKey && e.key === 'z') { + Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey') + model?.undo() + } else if (e.ctrlKey && e.key === 'y') { + Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey') + model?.redo() + } + } + const onKeyDown = (e: KeyboardEvent) => { + if (e.ctrlKey && e.key === 's') { + setFileSaving('hotkey') + e.preventDefault() + e.stopPropagation() + } + } + useEffect(() => { + document.addEventListener('keyup', onKeyUp) + document.addEventListener('keydown', onKeyDown) + return () => { + document.removeEventListener('keyup', onKeyUp) + document.removeEventListener('keydown', onKeyDown) + } + }, [model, blockStates, file]) + + const [presets, setPresets] = useState([]) + useEffect(() => { + getCollections(version).then(collections => { + setPresets(collections.get(gen.id).map(p => p.startsWith('minecraft:') ? p.slice(10) : p)) + }) + .catch(e => { console.error(e); setError(e) }) + }, [version, gen.id]) + + const selectPreset = (id: string) => { + Analytics.loadPreset(gen.id, id) + setSharedSnippetId(undefined, true) + changeTargetVersion(version, true) + setCurrentPreset(id) + } + + const loadPreset = async (id: string) => { + try { + const preset = await fetchPreset(version, gen.path ?? gen.id, id) + const seed = model?.get(new Path(['generator', 'seed'])) + if (preset?.generator?.seed !== undefined && seed !== undefined) { + preset.generator.seed = seed + if (preset.generator.biome_source?.seed !== undefined) { + preset.generator.biome_source.seed = seed + } + } + return preset + } catch (e) { + setError(`Cannot load preset ${id} in ${version}`) + setCurrentPreset(undefined, true) + } + } + + const selectVersion = (version: VersionId) => { + setSharedSnippetId(undefined, true) + changeVersion(version) + if (project.name !== DRAFT_PROJECT.name && project.version !== version) { + updateProject({ version }) + } + } + + const [shareUrl, setShareUrl] = useState(undefined) + const [shareShown, setShareShown] = useState(false) + const [shareCopyActive, shareCopySuccess] = useActiveTimeout({ cooldown: 3000 }) + const share = () => { + if (shareShown) { + setShareShown(false) + return + } + if (currentPreset) { + setShareUrl(`${location.origin}/${gen.url}/?version=${version}&preset=${currentPreset}`) + setShareShown(true) + copySharedId() + } else if (model && blockStates) { + const output = getOutput(model, blockStates) + if (deepEqual(output, model.schema.default())) { + setShareUrl(`${location.origin}/${gen.url}/?version=${version}`) + setShareShown(true) + } else { + shareSnippet(gen.id, version, output, previewShown) + .then(({ id, length, compressed, rate }) => { + Analytics.createSnippet(gen.id, id, version, length, compressed, rate) + const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}` + setShareUrl(url) + setShareShown(true) + }) + .catch(e => { + if (e instanceof Error) { + setError(e) + } + }) + } + } + } + const copySharedId = () => { + navigator.clipboard.writeText(shareUrl ?? '') + shareCopySuccess() + } + useEffect(() => { + if (!shareCopyActive) { + setShareUrl(undefined) + setShareShown(false) + } + }, [shareCopyActive]) + + const [sourceShown, setSourceShown] = useState(window.innerWidth > 820) + const [doCopy, setCopy] = useState(0) + const [doDownload, setDownload] = useState(0) + const [doImport, setImport] = useState(0) + + const copySource = () => { + Analytics.copyOutput(gen.id, 'menu') + setCopy(doCopy + 1) + } + const downloadSource = () => { + Analytics.downloadOutput(gen.id, 'menu') + setDownload(doDownload + 1) + } + const importSource = () => { + Analytics.generatorEvent('import') + setSourceShown(true) + setImport(doImport + 1) + } + const toggleSource = () => { + if (sourceShown) { + Analytics.hideOutput(gen.id, 'menu') + } else { + Analytics.showOutput(gen.id, 'menu') + } + setSourceShown(!sourceShown) + setCopy(0) + setDownload(0) + setImport(0) + } + + const [copyActive, copySuccess] = useActiveTimeout() + + const [previewShown, setPreviewShown] = useState(Store.getPreviewPanelOpen() ?? window.innerWidth > 800) + const hasPreview = HasPreview.includes(gen.id) && !(gen.id === 'worldgen/configured_feature' && checkVersion(version, '1.18')) + if (previewShown && !hasPreview) setPreviewShown(false) + let actionsShown = 2 + if (hasPreview) actionsShown += 1 + if (sourceShown) actionsShown += 2 + + const togglePreview = () => { + if (sourceShown) { + Analytics.hidePreview(gen.id, 'menu') + } else { + Analytics.showPreview(gen.id, 'menu') + } + setPreviewShown(!previewShown) + if (!previewShown && sourceShown) { + setSourceShown(false) + } + } + + const [projectShown, setProjectShown] = useState(Store.getProjectPanelOpen() ?? window.innerWidth > 1000) + const toggleProjectShown = useCallback(() => { + if (projectShown) { + Analytics.hideProject(gen.id, projects.length, project.files.length, 'menu') + } else { + Analytics.showProject(gen.id, projects.length, project.files.length, 'menu') + } + Store.setProjectPanelOpen(!projectShown) + setProjectShown(!projectShown) + }, [projectShown]) + + const [projectCreating, setProjectCreating] = useState(false) + const [projectDeleting, setprojectDeleting] = useState(false) + const [fileSaving, setFileSaving] = useState(undefined) + const [fileRenaming, setFileRenaming] = useState<{ type: string, id: string } | undefined>(undefined) + + return <> +
+ {!gen.partner && } +
+ + + + + + + + {backup !== undefined && } + + + setFileSaving('menu')} /> + +
+ {error && setError(null)} />} + +
+
+ +
+ +
+
+ +
+
+ + +
+ +
+ setprojectDeleting(true)} onRename={setFileRenaming} onCreate={() => setProjectCreating(true)} /> +
+ {projectCreating && setProjectCreating(false)} />} + {projectDeleting && setprojectDeleting(false)} />} + {model && fileSaving && setFileSaving(undefined)} />} + {fileRenaming && setFileRenaming(undefined)} />} + +} diff --git a/src/app/pages/Generator.tsx b/src/app/pages/Generator.tsx index 94c3847e..c2edd3ed 100644 --- a/src/app/pages/Generator.tsx +++ b/src/app/pages/Generator.tsx @@ -1,16 +1,12 @@ -import { DataModel, Path } from '@mcschema/core' import { getCurrentUrl, route } from 'preact-router' -import { useCallback, useEffect, useErrorBoundary, useMemo, useRef, useState } from 'preact/hooks' -import { Analytics } from '../Analytics.js' -import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../components/index.js' +import { useMemo } from 'preact/hooks' +import { SchemaGenerator } from '../components/generator/SchemaGenerator.jsx' +import { ErrorPanel, Octicon } from '../components/index.js' import config from '../Config.js' -import { DRAFT_PROJECT, useLocale, useProject, useTitle, useVersion } from '../contexts/index.js' -import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../hooks/index.js' -import { getOutput } from '../schema/transformOutput.js' +import { useLocale, useTitle, useVersion } from '../contexts/index.js' import type { VersionId } from '../services/index.js' -import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../services/index.js' -import { Store } from '../Store.js' -import { cleanUrl, deepEqual, getGenerator } from '../Utils.js' +import { checkVersion } from '../services/index.js' +import { getGenerator } from '../Utils.js' export const SHARE_KEY = 'share' @@ -19,18 +15,11 @@ interface Props { } export function Generator({}: Props) { const { locale } = useLocale() - const { version, changeVersion, changeTargetVersion } = useVersion() - const { projects, project, file, updateProject, updateFile } = useProject() - const [error, setError] = useState(null) - const [errorBoundary, errorRetry] = useErrorBoundary() - if (errorBoundary) { - errorBoundary.message = `Something went wrong rendering the generator: ${errorBoundary.message}` - return
- } + const { version, changeVersion } = useVersion() const gen = getGenerator(getCurrentUrl()) if (!gen) { - return
+ return
} const allowedVersions = useMemo(() => { @@ -42,343 +31,21 @@ export function Generator({}: Props) { useTitle(locale('title.generator', locale(gen.partner ? `partner.${gen.partner}.${gen.id}` : gen.id)), allowedVersions) - if (!checkVersion(version, gen.minVersion)) { - setError(`The minimum version for this generator is ${gen.minVersion}`) - } - if (!checkVersion(version, undefined, gen.maxVersion)) { - setError(`This generator is not available in versions above ${gen.maxVersion}`) - } - - useEffect(() => Store.visitGenerator(gen.id), [gen.id]) - - const [currentPreset, setCurrentPreset] = useSearchParam('preset') - const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY) - const ignoreChange = useRef(false) - const backup = useMemo(() => Store.getBackup(gen.id), [gen.id]) - - const loadBackup = () => { - if (backup !== undefined) { - model?.reset(DataModel.wrapLists(backup), false) - } - } - - const { value } = useAsync(async () => { - let data: unknown = undefined - if (currentPreset && sharedSnippetId) { - setSharedSnippetId(undefined) - return AsyncCancel - } - if (currentPreset) { - data = await loadPreset(currentPreset) - } else if (sharedSnippetId) { - const snippet = await getSnippet(sharedSnippetId) - let cancel = false - if (snippet.version && snippet.version !== version) { - changeVersion(snippet.version, false) - cancel = true - } - if (snippet.type && snippet.type !== gen.id) { - const snippetGen = config.generators.find(g => g.id === snippet.type) - if (snippetGen) { - route(`${cleanUrl(snippetGen.url)}?${SHARE_KEY}=${snippet.id}`) - cancel = true - } - } - if (cancel) { - return AsyncCancel - } - if (snippet.show_preview && !previewShown) { - setPreviewShown(true) - setSourceShown(false) - } - Analytics.openSnippet(gen.id, sharedSnippetId, version) - data = snippet.data - } else if (file) { - if (project.version && project.version !== version) { - changeVersion(project.version, false) - return AsyncCancel - } - data = file.data - } - const [model, blockStates] = await Promise.all([ - getModel(version, gen.id), - getBlockStates(version), - ]) - if (data) { - ignoreChange.current = true - model.reset(DataModel.wrapLists(data), false) - } - Analytics.setGenerator(gen.id) - return { model, blockStates } - }, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id]) - - const model = value?.model - const blockStates = value?.blockStates - - useModel(model, model => { - if (!ignoreChange.current) { - setCurrentPreset(undefined, true) - setSharedSnippetId(undefined, true) - } - if (file && model && blockStates) { - const data = getOutput(model, blockStates) - updateFile(gen.id, file.id, { id: file.id, data }) - } - ignoreChange.current = false - Store.setBackup(gen.id, DataModel.unwrapLists(model.data)) - setError(null) - }, [gen.id, setCurrentPreset, setSharedSnippetId, blockStates, file?.id]) - - const reset = () => { - Analytics.resetGenerator(gen.id, model?.historyIndex ?? 1, 'menu') - model?.reset(DataModel.wrapLists(model.schema.default()), true) - } - const undo = (e: MouseEvent) => { - e.stopPropagation() - Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'menu') - model?.undo() - } - const redo = (e: MouseEvent) => { - e.stopPropagation() - Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'menu') - model?.redo() - } - - const onKeyUp = (e: KeyboardEvent) => { - if (e.ctrlKey && e.key === 'z') { - Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey') - model?.undo() - } else if (e.ctrlKey && e.key === 'y') { - Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey') - model?.redo() - } - } - const onKeyDown = (e: KeyboardEvent) => { - if (e.ctrlKey && e.key === 's') { - setFileSaving('hotkey') - e.preventDefault() - e.stopPropagation() - } - } - useEffect(() => { - document.addEventListener('keyup', onKeyUp) - document.addEventListener('keydown', onKeyDown) - return () => { - document.removeEventListener('keyup', onKeyUp) - document.removeEventListener('keydown', onKeyDown) - } - }, [model, blockStates, file]) - - const [presets, setPresets] = useState([]) - useEffect(() => { - getCollections(version).then(collections => { - setPresets(collections.get(gen.id).map(p => p.startsWith('minecraft:') ? p.slice(10) : p)) - }) - .catch(e => { console.error(e); setError(e) }) - }, [version, gen.id]) - - const selectPreset = (id: string) => { - Analytics.loadPreset(gen.id, id) - setSharedSnippetId(undefined, true) - changeTargetVersion(version, true) - setCurrentPreset(id) - } - - const loadPreset = async (id: string) => { - try { - const preset = await fetchPreset(version, gen.path ?? gen.id, id) - const seed = model?.get(new Path(['generator', 'seed'])) - if (preset?.generator?.seed !== undefined && seed !== undefined) { - preset.generator.seed = seed - if (preset.generator.biome_source?.seed !== undefined) { - preset.generator.biome_source.seed = seed - } - } - return preset - } catch (e) { - setError(`Cannot load preset ${id} in ${version}`) - setCurrentPreset(undefined, true) - } - } - - const selectVersion = (version: VersionId) => { - setSharedSnippetId(undefined, true) - changeVersion(version) - if (project.name !== DRAFT_PROJECT.name && project.version !== version) { - updateProject({ version }) - } - } - - const [shareUrl, setShareUrl] = useState(undefined) - const [shareShown, setShareShown] = useState(false) - const [shareCopyActive, shareCopySuccess] = useActiveTimeout({ cooldown: 3000 }) - const share = () => { - if (shareShown) { - setShareShown(false) - return - } - if (currentPreset) { - setShareUrl(`${location.origin}/${gen.url}/?version=${version}&preset=${currentPreset}`) - setShareShown(true) - copySharedId() - } else if (model && blockStates) { - const output = getOutput(model, blockStates) - if (deepEqual(output, model.schema.default())) { - setShareUrl(`${location.origin}/${gen.url}/?version=${version}`) - setShareShown(true) - } else { - shareSnippet(gen.id, version, output, previewShown) - .then(({ id, length, compressed, rate }) => { - Analytics.createSnippet(gen.id, id, version, length, compressed, rate) - const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}` - setShareUrl(url) - setShareShown(true) - }) - .catch(e => { - if (e instanceof Error) { - setError(e) - } - }) - } - } - } - const copySharedId = () => { - navigator.clipboard.writeText(shareUrl ?? '') - shareCopySuccess() - } - useEffect(() => { - if (!shareCopyActive) { - setShareUrl(undefined) - setShareShown(false) - } - }, [shareCopyActive]) - - const [sourceShown, setSourceShown] = useState(window.innerWidth > 820) - const [doCopy, setCopy] = useState(0) - const [doDownload, setDownload] = useState(0) - const [doImport, setImport] = useState(0) - - const copySource = () => { - Analytics.copyOutput(gen.id, 'menu') - setCopy(doCopy + 1) - } - const downloadSource = () => { - Analytics.downloadOutput(gen.id, 'menu') - setDownload(doDownload + 1) - } - const importSource = () => { - Analytics.generatorEvent('import') - setSourceShown(true) - setImport(doImport + 1) - } - const toggleSource = () => { - if (sourceShown) { - Analytics.hideOutput(gen.id, 'menu') - } else { - Analytics.showOutput(gen.id, 'menu') - } - setSourceShown(!sourceShown) - setCopy(0) - setDownload(0) - setImport(0) - } - - const [copyActive, copySuccess] = useActiveTimeout() - - const [previewShown, setPreviewShown] = useState(Store.getPreviewPanelOpen() ?? window.innerWidth > 800) - const hasPreview = HasPreview.includes(gen.id) && !(gen.id === 'worldgen/configured_feature' && checkVersion(version, '1.18')) - if (previewShown && !hasPreview) setPreviewShown(false) - let actionsShown = 2 - if (hasPreview) actionsShown += 1 - if (sourceShown) actionsShown += 2 - - const togglePreview = () => { - if (sourceShown) { - Analytics.hidePreview(gen.id, 'menu') - } else { - Analytics.showPreview(gen.id, 'menu') - } - setPreviewShown(!previewShown) - if (!previewShown && sourceShown) { - setSourceShown(false) - } - } - - const [projectShown, setProjectShown] = useState(Store.getProjectPanelOpen() ?? window.innerWidth > 1000) - const toggleProjectShown = useCallback(() => { - if (projectShown) { - Analytics.hideProject(gen.id, projects.length, project.files.length, 'menu') - } else { - Analytics.showProject(gen.id, projects.length, project.files.length, 'menu') - } - Store.setProjectPanelOpen(!projectShown) - setProjectShown(!projectShown) - }, [projectShown]) - - const [projectCreating, setProjectCreating] = useState(false) - const [projectDeleting, setprojectDeleting] = useState(false) - const [fileSaving, setFileSaving] = useState(undefined) - const [fileRenaming, setFileRenaming] = useState<{ type: string, id: string } | undefined>(undefined) - - return <> -
- {!gen.partner && } -
- - - - - - - - {backup !== undefined && } - - - setFileSaving('menu')} /> - -
- {error && setError(null)} />} - -