mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 15:17:09 +00:00
Expose error stack traces (#211)
This commit is contained in:
34
package-lock.json
generated
34
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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[]>([])
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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('')
|
||||||
|
|||||||
@@ -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[]>([])
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 => ({
|
||||||
|
|||||||
@@ -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}`));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user