Expose error stack traces (#211)

This commit is contained in:
Misode
2022-03-11 00:05:24 +01:00
committed by GitHub
parent 4122991b3b
commit 7ab0ec2e7e
14 changed files with 131 additions and 31 deletions

34
package-lock.json generated
View File

@@ -25,7 +25,8 @@
"howler": "^2.2.3", "howler": "^2.2.3",
"js-yaml": "^3.14.1", "js-yaml": "^3.14.1",
"marked": "^4.0.10", "marked": "^4.0.10",
"rfdc": "^1.3.0" "rfdc": "^1.3.0",
"sourcemapped-stacktrace": "^1.1.11"
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.1.0", "@preact/preset-vite": "^2.1.0",
@@ -3945,6 +3946,22 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/sourcemapped-stacktrace": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.11.tgz",
"integrity": "sha512-O0pcWjJqzQFVsisPlPXuNawJHHg9N9UgpJ/aDmvi9+vnS3x1C0NhwkVFzzZ1VN0Xo+bekyweoqYvBw5ZBKiNnQ==",
"dependencies": {
"source-map": "0.5.6"
}
},
"node_modules/sourcemapped-stacktrace/node_modules/source-map": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sprintf-js": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -7367,6 +7384,21 @@
"integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
"dev": true "dev": true
}, },
"sourcemapped-stacktrace": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.11.tgz",
"integrity": "sha512-O0pcWjJqzQFVsisPlPXuNawJHHg9N9UgpJ/aDmvi9+vnS3x1C0NhwkVFzzZ1VN0Xo+bekyweoqYvBw5ZBKiNnQ==",
"requires": {
"source-map": "0.5.6"
},
"dependencies": {
"source-map": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
}
}
},
"sprintf-js": { "sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",

View File

@@ -25,13 +25,14 @@
"brace": "^0.11.1", "brace": "^0.11.1",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"comment-json": "^4.1.1", "comment-json": "^4.1.1",
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
"deepslate": "^0.9.0-beta.13", "deepslate": "^0.9.0-beta.13",
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
"deepslate-rs": "^0.1.6", "deepslate-rs": "^0.1.6",
"howler": "^2.2.3", "howler": "^2.2.3",
"js-yaml": "^3.14.1", "js-yaml": "^3.14.1",
"marked": "^4.0.10", "marked": "^4.0.10",
"rfdc": "^1.3.0" "rfdc": "^1.3.0",
"sourcemapped-stacktrace": "^1.1.11"
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.1.0", "@preact/preset-vite": "^2.1.0",

View File

@@ -1,13 +1,52 @@
import { useEffect, useMemo, useState } from 'preact/hooks'
import { mapStackTrace } from 'sourcemapped-stacktrace'
import { Octicon } from './Octicon' import { Octicon } from './Octicon'
type ErrorPanelProps = { type ErrorPanelProps = {
error: string, error: string | Error,
onDismiss?: () => unknown, onDismiss?: () => unknown,
} }
export function ErrorPanel({ error, onDismiss }: ErrorPanelProps) { export function ErrorPanel({ error, onDismiss }: ErrorPanelProps) {
const [stackVisible, setStackVisible] = useState(false)
const [stack, setStack] = useState<string | undefined>(undefined)
useEffect(() => {
if (error instanceof Error) {
const stack = error.stack!.split('\n').map(line => {
return line.replace(/^(\s+)at (?:async )?(https?:.*)/, '$1at ($2)')
})
setStack(stack.join('\n'))
mapStackTrace(stack.join('\n'), (mapped) => {
const mappedStack = mapped.map(line => {
return line.replace(/..\/..\/src\//, 'src/')
}).join('\n')
setStack(mappedStack)
})
}
}, [error])
const url = useMemo(() => {
let url ='https://github.com/misode/misode.github.io/issues/new'
if (error instanceof Error) {
url += `?title=${encodeURIComponent(`${error.name}: ${error.message}`)}`
if (stack) {
url += `&body=${encodeURIComponent(`\`\`\`\n${error.name}: ${error.message}\n${stack}\n\`\`\`\n`)}`
}
} else {
url += `?title=${encodeURIComponent(error.toString())}`
}
return url
}, [error, stack])
return <div class="error"> return <div class="error">
{onDismiss && <div class="error-dismiss" onClick={onDismiss}>{Octicon.x}</div>} {onDismiss && <div class="error-dismiss" onClick={onDismiss}>{Octicon.x}</div>}
<h3>{error}</h3> <h3>
<p>If you think this is a bug, you can report it <a href="https://github.com/misode/misode.github.io/issues/new" target="_blank">on GitHub</a></p> {error instanceof Error ? error.message : error}
{stack && <span onClick={() => setStackVisible(!stackVisible)}>
{Octicon.info}
</span>}
</h3>
{stack && stackVisible && <pre>{stack}</pre>}
<p>If you think this is a bug, you can report it <a href={url} target="_blank">on GitHub</a></p>
</div> </div>
} }

View File

@@ -52,7 +52,7 @@ type SourcePanelProps = {
doDownload?: number, doDownload?: number,
doImport?: number, doImport?: number,
copySuccess: () => unknown, copySuccess: () => unknown,
onError: (message: string) => unknown, onError: (message: string | Error) => unknown,
} }
export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) { export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
const { locale } = useLocale() const { locale } = useLocale()
@@ -78,7 +78,12 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
const output = getSerializedOutput(model, blockStates) const output = getSerializedOutput(model, blockStates)
editor.current.setValue(output) editor.current.setValue(output)
} catch (e) { } catch (e) {
if (e instanceof Error) {
e.message = `Error getting JSON output: ${e.message}`
onError(e)
} else {
onError(`Error getting JSON output: ${message(e)}`) onError(`Error getting JSON output: ${message(e)}`)
}
console.error(e) console.error(e)
editor.current.setValue('') editor.current.setValue('')
} }
@@ -91,7 +96,12 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
const data = FORMATS[format].parse(value) const data = FORMATS[format].parse(value)
model?.reset(DataModel.wrapLists(data), false) model?.reset(DataModel.wrapLists(data), false)
} catch (e) { } catch (e) {
if (e instanceof Error) {
e.message = `Error importing: ${e.message}`
onError(e)
} else {
onError(`Error importing: ${message(e)}`) onError(`Error importing: ${message(e)}`)
}
console.error(e) console.error(e)
} }
} }

View File

@@ -48,13 +48,19 @@ export const NoiseSettingsPreview = ({ data, shown, version }: PreviewProps) =>
clearInterval(scrollInterval.current) clearInterval(scrollInterval.current)
} }
if (shown) { if (shown) {
redraw() (async () => {
try {
await redraw()
if (autoScroll) { if (autoScroll) {
scrollInterval.current = setInterval(() => { scrollInterval.current = setInterval(() => {
offset.current -= 8 offset.current -= 8
redraw() redraw()
}, 100) as any }, 100) as any
} }
} catch (e) {
throw e
}
})()
} }
}, [state, seed, shown, biome, biomeScale, biomeDepth, autoScroll]) }, [state, seed, shown, biome, biomeScale, biomeDepth, autoScroll])

View File

@@ -76,7 +76,11 @@ export function useCanvas({ size, draw, onDrag, onHover, onLeave }: {
canvas.current.height = s[1] canvas.current.height = s[1]
const img = ctx.getImageData(0, 0, s[0], s[1]) const img = ctx.getImageData(0, 0, s[0], s[1])
const ownCount = redrawCount.current += 1 const ownCount = redrawCount.current += 1
try {
await draw(img) await draw(img)
} catch (e) {
throw e
}
if (ownCount === redrawCount.current) { if (ownCount === redrawCount.current) {
ctx.putImageData(img, 0, 0) ctx.putImageData(img, 0, 0)
} }

View File

@@ -9,7 +9,7 @@ interface Props {
} }
export function Changelog({}: Props) { export function Changelog({}: Props) {
const { locale } = useLocale() const { locale } = useLocale()
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<Error | null>(null)
useTitle(locale('title.changelog')) useTitle(locale('title.changelog'))
const [changelogs, setChangelogs] = useState<Change[]>([]) const [changelogs, setChangelogs] = useState<Change[]>([])

View File

@@ -18,10 +18,11 @@ export function Generator({}: Props) {
const { locale } = useLocale() const { locale } = useLocale()
const { version, changeVersion } = useVersion() const { version, changeVersion } = useVersion()
const { project, file, updateFile, openFile, closeFile } = useProject() const { project, file, updateFile, openFile, closeFile } = useProject()
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<Error | string | null>(null)
const [errorBoundary, errorRetry] = useErrorBoundary() const [errorBoundary, errorRetry] = useErrorBoundary()
if (errorBoundary) { if (errorBoundary) {
return <main><ErrorPanel error={`Something went wrong rendering the generator: ${errorBoundary.message}`} onDismiss={errorRetry} /></main> errorBoundary.message = `Something went wrong rendering the generator: ${errorBoundary.message}`
return <main><ErrorPanel error={errorBoundary} onDismiss={errorRetry} /></main>
} }
const gen = getGenerator(getCurrentUrl()) const gen = getGenerator(getCurrentUrl())
@@ -69,7 +70,7 @@ export function Generator({}: Props) {
} }
setModel(m) setModel(m)
}) })
.catch(e => { console.error(e); setError(message(e)) }) .catch(e => { console.error(e); setError(e) })
}, [version, gen.id]) }, [version, gen.id])
const [dirty, setDirty] = useState(false) const [dirty, setDirty] = useState(false)
@@ -172,7 +173,7 @@ export function Generator({}: Props) {
getCollections(version).then(collections => { getCollections(version).then(collections => {
setPresets(collections.get(gen.id).map(p => p.slice(10))) setPresets(collections.get(gen.id).map(p => p.slice(10)))
}) })
.catch(e => { console.error(e); setError(e.message) }) .catch(e => { console.error(e); setError(e) })
}, [version, gen.id]) }, [version, gen.id])
const selectPreset = (id: string) => { const selectPreset = (id: string) => {
@@ -192,7 +193,7 @@ export function Generator({}: Props) {
} }
return preset return preset
} catch (e) { } catch (e) {
setError(message(e)) setError(e instanceof Error ? e : message(e))
} }
} }

View File

@@ -4,7 +4,7 @@ import { Btn, BtnMenu, ErrorPanel, SoundConfig, TextInput } from '../components'
import { useLocale, useTitle, useVersion } from '../contexts' import { useLocale, useTitle, useVersion } from '../contexts'
import type { SoundEvents, VersionId } from '../services' import type { SoundEvents, VersionId } from '../services'
import { fetchSounds } from '../services' import { fetchSounds } from '../services'
import { hexId, message } from '../Utils' import { hexId } from '../Utils'
interface Props { interface Props {
path?: string, path?: string,
@@ -12,7 +12,7 @@ interface Props {
export function Sounds({}: Props) { export function Sounds({}: Props) {
const { locale } = useLocale() const { locale } = useLocale()
const { version, changeVersion } = useVersion() const { version, changeVersion } = useVersion()
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<Error | null>(null)
useTitle(locale('title.sounds')) useTitle(locale('title.sounds'))
const [sounds, setSounds] = useState<SoundEvents>({}) const [sounds, setSounds] = useState<SoundEvents>({})
@@ -20,7 +20,7 @@ export function Sounds({}: Props) {
useEffect(() => { useEffect(() => {
fetchSounds(version) fetchSounds(version)
.then(setSounds) .then(setSounds)
.catch(e => { console.error(e); setError(message(e)) }) .catch(e => { console.error(e); setError(e) })
}, [version]) }, [version])
const [search, setSearch] = useState('') const [search, setSearch] = useState('')

View File

@@ -11,7 +11,7 @@ interface Props {
} }
export function Versions({}: Props) { export function Versions({}: Props) {
const { locale } = useLocale() const { locale } = useLocale()
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<Error | null>(null)
useTitle(locale('title.versions')) useTitle(locale('title.versions'))
const [versions, setVersions] = useState<VersionMeta[]>([]) const [versions, setVersions] = useState<VersionMeta[]>([])

View File

@@ -224,6 +224,7 @@ class LevelSlice {
public generate(generator: NoiseChunkGenerator, forcedBiome?: string) { public generate(generator: NoiseChunkGenerator, forcedBiome?: string) {
this.chunks.forEach((chunk, i) => { this.chunks.forEach((chunk, i) => {
if (!this.done[i]) { if (!this.done[i]) {
throw new Error('Test')
generator.fill(chunk, true) generator.fill(chunk, true)
generator.buildSurface(chunk, forcedBiome) generator.buildSurface(chunk, forcedBiome)
this.done[i] = true this.done[i] = true

View File

@@ -20,6 +20,7 @@ let Changelogs: Change[] | Promise<Change[]> | null = null
export async function getChangelogs() { export async function getChangelogs() {
if (!Changelogs) { if (!Changelogs) {
const index = await (await fetch(`${repo}/index.json`)).json() as string[] const index = await (await fetch(`${repo}/index.json`)).json() as string[]
throw new Error('Test')
Changelogs = (await Promise.all( Changelogs = (await Promise.all(
index.map((group, i) => fetchGroup(parseVersion(group), i)) index.map((group, i) => fetchGroup(parseVersion(group), i))
)).flat().map<Change>(change => ({ )).flat().map<Change>(change => ({

View File

@@ -1,7 +1,7 @@
import type { CollectionRegistry } from '@mcschema/core' import type { CollectionRegistry } from '@mcschema/core';
import config from '../../config.json' import config from '../../config.json';
import { message } from '../Utils' import { message } from '../Utils';
import type { BlockStateRegistry, VersionId } from './Schemas' import type { BlockStateRegistry, VersionId } from './Schemas';
// Cleanup old caches // Cleanup old caches
['1.15', '1.16', '1.17'].forEach(v => localStorage.removeItem(`cache_${v}`)); ['1.15', '1.16', '1.17'].forEach(v => localStorage.removeItem(`cache_${v}`));

View File

@@ -749,6 +749,11 @@ main.has-preview {
margin: 10px 0; margin: 10px 0;
} }
.error h3 span {
margin-left: 8px;
cursor: pointer;
}
.error .error-dismiss { .error .error-dismiss {
float: right; float: right;
cursor: pointer; cursor: pointer;