Skip to content

Commit

Permalink
jscad-web: show error name (#63)
Browse files Browse the repository at this point in the history
This PR updates some of the error handling to give nicer error messages
in the error bar. It shows the error name in bold up front, and
preserves stacktraces a little better.

Before:

![error-pre](https://github.com/hrgdavor/jscadui/assets/1766297/2ed874bd-de69-4165-8ede-dbd9e8486f1f)

After:

![error-post](https://github.com/hrgdavor/jscadui/assets/1766297/72f0027e-b6e0-4179-849d-2e287c932084)

I also fixed a bug where generating a bunch of designs in a row (such as
by changing parameters), the progress spinner would disappear after the
first job even though jobs are still pending. I changed this to track
the number of pending jobs and only hide the spinner when there are no
pending jobs.

I also fixed some code style issues from prettier, mostly single quote
strings.
  • Loading branch information
platypii authored Sep 20, 2023
1 parent 82a6da5 commit 1c5b160
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 71 deletions.
24 changes: 15 additions & 9 deletions apps/jscad-web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const viewState = new ViewState()
const gizmo = (window.gizmo = new Gizmo())
byId('overlay').parentNode.appendChild(gizmo)

let projectName = 'jscad'
let model = []

// load default model unless another model was already loaded
Expand Down Expand Up @@ -101,9 +102,10 @@ const setError = error => {
const errorBar = byId('error-bar')
if (error) {
console.error(error)
const message = formatStacktrace(error).replace(/^Error: /, '')
const errorMessage = byId('error-message')
errorMessage.innerText = message
const name = (error.name || 'Error') + ': '
byId('error-name').innerText = name
const message = formatStacktrace(error)
byId('error-message').innerText = message
errorBar.classList.add('visible')
} else {
errorBar.classList.remove('visible')
Expand Down Expand Up @@ -139,15 +141,19 @@ const handlers = {
const { sendCmd, sendNotify } = initMessaging(worker, handlers)

const spinner = byId('spinner')
let jobs = 0
async function sendCmdAndSpin(method, params) {
jobs++
spinner.style.display = 'block'
try {
return await sendCmd(method, params)
} catch (error) {
setError(error)
throw error
} finally {
spinner.style.display = 'none'
if (--jobs === 0) {
spinner.style.display = 'none'
}
}
}

Expand All @@ -173,8 +179,6 @@ const runScript = async ({ script, url = './index.js', base, root }) => {
genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback })
}

let projectName = 'jscad'

const loadExample = source => {
editor.setSource(source)
runScript({ script: source })
Expand All @@ -192,7 +196,7 @@ editor.init(defaultCode, async (script, path) => {
// it is expected if multiple files require same file/module that first time it is loaded
// but for others resolved module is returned
// if not cleared by calling clearFileCache, require will not try to reload the file
await sendCmd('clearFileCache',{files:[path]})
await sendCmd('clearFileCache', { files: [path] })
if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base })
} else {
runScript({ script })
Expand All @@ -213,9 +217,11 @@ remote.init((script) => {
})
exporter.init(exportModel)

try{
try {
await initFs()
}catch(err){setError(err)}
} catch (err) {
setError(err)
}

if ('serviceWorker' in navigator && !navigator.serviceWorker.controller) {
// service workers are disabled on hard-refresh, so need to reload.
Expand Down
29 changes: 15 additions & 14 deletions apps/jscad-web/src/editor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { EditorView, basicSetup } from "codemirror"
import { javascript } from "@codemirror/lang-javascript"
import { defaultKeymap } from "@codemirror/commands"
import { keymap } from "@codemirror/view"
import { defaultKeymap } from '@codemirror/commands'
import { javascript } from '@codemirror/lang-javascript'
import { keymap } from '@codemirror/view'
import { EditorView, basicSetup } from 'codemirror'

import * as drawer from './drawer.js'

let view
Expand All @@ -17,31 +18,31 @@ const compile = (code, path) => {
if (compileFn) {
compileFn(code, path)
} else {
console.log("not ready to compile")
console.log('not ready to compile')
}
}

export const init = (defaultCode, fn) => {
compileFn = fn
// Initialize codemirror
const editorDiv = document.getElementById("editor-container")
const editorDiv = document.getElementById('editor-container')
view = new EditorView({
extensions: [
basicSetup,
javascript(),
keymap.of([
{
key: "Shift-Enter",
key: 'Shift-Enter',
run: () => compile(view.state.doc.toString(), currentFile),
preventDefault: true
preventDefault: true,
},
{
key: "Mod-s",
key: 'Mod-s',
run: () => compile(view.state.doc.toString(), currentFile),
preventDefault: true
preventDefault: true,
},
...defaultKeymap
])
...defaultKeymap,
]),
],
parent: editorDiv,
})
Expand All @@ -56,7 +57,7 @@ export const init = (defaultCode, fn) => {
})

// Setup file selector
editorFile.addEventListener('click', (e) => {
editorFile.addEventListener('click', () => {
editorNav.classList.toggle('open')
})
// Close file selector on click outside
Expand All @@ -68,7 +69,7 @@ export const init = (defaultCode, fn) => {
}

export const setSource = (source, path = '/index.js') => {
view.dispatch({changes: {from: 0, to: view.state.doc.length, insert: source}})
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: source } })
currentFile = path
}

Expand Down
3 changes: 2 additions & 1 deletion apps/jscad-web/src/engine.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RenderThreejs } from '@jscadui/render-threejs'

// import * as THREE from 'three'

export const init = async () => {
Expand Down Expand Up @@ -28,7 +29,7 @@ const addScript = async (source, module = false) => {
var tag = document.createElement('script')
tag.type = module ? 'module' : 'text/javascript'
tag.src = source
tag.onload = ()=>resolve()
tag.onload = () => resolve()
tag.onerror = err => reject(err)
document.head.append(tag)
})
Expand Down
27 changes: 13 additions & 14 deletions apps/jscad-web/src/exporter.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@

const exportFormats = [
{ name: "stla", extension: "stl", label: "STL (ascii)" },
{ name: "stlb", extension: "stl", label: "STL (binary)" },
{ name: "amf", extension: "amf", label: "AMF" },
{ name: "dxf", extension: "dxf", label: "DXF" },
{ name: "json", extension: "json", label: "JSON" },
{ name: "obj", extension: "obj", label: "OBJ" },
{ name: "x3d", extension: "x3d", label: "X3D" },
{ name: "svg", extension: "svg", label: "SVG" },
{ name: "3mf", extension: "3mf", label: "3MF" },
{ name: 'stla', extension: 'stl', label: 'STL (ascii)' },
{ name: 'stlb', extension: 'stl', label: 'STL (binary)' },
{ name: 'amf', extension: 'amf', label: 'AMF' },
{ name: 'dxf', extension: 'dxf', label: 'DXF' },
{ name: 'json', extension: 'json', label: 'JSON' },
{ name: 'obj', extension: 'obj', label: 'OBJ' },
{ name: 'x3d', extension: 'x3d', label: 'X3D' },
{ name: 'svg', extension: 'svg', label: 'SVG' },
{ name: '3mf', extension: '3mf', label: '3MF' },
]

export const init = (exportFn) => {
populateFormats(exportFormats)
// Bind export buttons
document.getElementById("export-button").addEventListener("click", () => {
document.getElementById('export-button').addEventListener('click', () => {
// Export model in selected format
const formatSelect = document.getElementById("export-format")
const formatSelect = document.getElementById('export-format')
const format = exportFormats.find((f) => f.name === formatSelect.value)
exportFn(format.name, format.extension)
})
}

const populateFormats = (formats) => {
const select = document.getElementById("export-format")
const select = document.getElementById('export-format')
formats.forEach((format) => {
const option = document.createElement("option")
const option = document.createElement('option')
option.value = format.name
option.text = format.label
select.appendChild(option)
Expand Down
28 changes: 14 additions & 14 deletions apps/jscad-web/src/menu.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
import { examples } from './examples.js'

const menu = document.getElementById("menu")
const menu = document.getElementById('menu')

export const init = (loadExample) => {
const button = document.getElementById("menu-button")
const content = document.getElementById("menu-content")
const button = document.getElementById('menu-button')
const content = document.getElementById('menu-content')

// Menu button
button.addEventListener("click", () => {
menu.classList.toggle("open")
button.addEventListener('click', () => {
menu.classList.toggle('open')
})

// Close menu when anything else is clicked
window.addEventListener("click", (e) => {
window.addEventListener('click', (e) => {
if (!button.contains(e.target) && !content.contains(e.target)) {
dismiss()
}
})
window.addEventListener("drop", () => dismiss())
window.addEventListener("dragstart", () => dismiss())
window.addEventListener("dragover", () => dismiss())
window.addEventListener('drop', () => dismiss())
window.addEventListener('dragstart', () => dismiss())
window.addEventListener('dragover', () => dismiss())

// Add examples to menu
const exampleDiv = document.getElementById("examples")
const exampleDiv = document.getElementById('examples')
examples.forEach(({ name, source }) => {
const a = document.createElement("a")
const a = document.createElement('a')
a.innerText = name
a.addEventListener("click", async () => {
a.addEventListener('click', async () => {
console.log(`load example ${name}`)
loadExample(await (await fetch(source)).text())
})
const li = document.createElement("li")
const li = document.createElement('li')
li.appendChild(a)
exampleDiv.appendChild(li)
})
}

const dismiss = () => {
menu.classList.remove("open")
menu.classList.remove('open')
}
3 changes: 1 addition & 2 deletions apps/jscad-web/src/remote.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

export const init = (compileFn, setError) => {
const load = loadFromUrl(compileFn, setError)
load() // on load
Expand Down Expand Up @@ -28,7 +27,7 @@ export const loadFromUrl = (compileFn, setError) => async () => {
*/
const fetchUrl = async (url) => {
// Try to fetch url directly
const res = await fetch(url).catch((err) => {
const res = await fetch(url).catch(() => {
// Failed to fetch directly, try proxy
return fetch(`/remote?url=${url}`)
})
Expand Down
15 changes: 8 additions & 7 deletions apps/jscad-web/src/stacktrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ export const formatStacktrace = (error) => {
// firefox stacktrace:
// main@http://localhost:5120/build/bundle.worker.js line 14 > eval line 1 > eval:13:3
// @http://localhost:5120/build/bundle.worker.js line 14 > eval:1:37
const cleaned = error.stack.split('\n')
.filter((line) => line.includes('eval'))
.map((line) => line.replace(/eval at <anonymous> \(.*?\), /, '')) // chrome
.map((line) => line.replace(/^@/, '<anonymous>@')) // firefox
.map((line) => line.replace(/@http.*?bundle.worker.js.* > eval:/, ' ')) // firefox
.map((line) => line.replace(/^\s*(at )?/, ' at ')) // indent
const cleaned = error.stack
.split('\n')
.filter(line => line.includes('eval'))
.map(line => line.replace(/eval at <anonymous> \(.*?\), /, '')) // chrome
.map(line => line.replace(/^@/, '<anonymous>@')) // firefox
.map(line => line.replace(/@http.*?bundle.worker.js.* > eval:/, ' ')) // firefox
.map(line => line.replace(/^\s*(at )?/, ' at ')) // indent

return [error.toString(), ...cleaned].join('\n')
return [error.message, ...cleaned].join('\n')
}
13 changes: 7 additions & 6 deletions apps/jscad-web/src/welcome.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
const welcome = document.getElementById("welcome")
const welcome = document.getElementById('welcome')
let showing = true

export const init = () => {
// hello devs
console.log('Welcome to JSCAD! Like JavaScript and want to help? Join us at https://github.com/jscad/OpenJSCAD.org')
if (!welcome) return
// hide the welcome menu when anything is clicked
window.addEventListener("mousedown", (e) => dismiss(e))
window.addEventListener("click", (e) => dismiss(e))
window.addEventListener("drop", () => dismiss())
window.addEventListener("dragstart", () => dismiss())
window.addEventListener("dragover", () => dismiss())
window.addEventListener('mousedown', dismiss)
window.addEventListener('click', dismiss)
window.addEventListener('drop', dismiss)
window.addEventListener('dragstart', dismiss)
window.addEventListener('dragover', dismiss)
// permanently hide the welcome menu
document.getElementById('welcome-dismiss').addEventListener('click', () => {
localStorage.setItem('welcome.dismissed', true)
dismiss()
Expand Down
2 changes: 1 addition & 1 deletion apps/jscad-web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ <h1>JSCAD</h1>
</div>

<div id="error-bar">
<label>Error:</label>
<label id="error-name">Error:</label>
<span id="error-message"></span>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion packages/fs-provider/fs-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ const getWorkspaceAliases = async (sw) => {
alias.push({ name, path: `/${w}/${main}` })
}
} catch (error) {
throw new Error(`failed to parse package.json\n ${error}`)
error.message = `failed to parse package.json\n ${error}`
throw error
}
}
return alias
Expand Down
4 changes: 2 additions & 2 deletions packages/require/src/require.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ const requireModule = (id, url, source, _require) => {
runModule(_require, exports, module, source)
return module
} catch (err) {
console.error('error loading module ' + url, err)
throw new Error(`failed loading module ${id}\n ${err}`)
err.message = `failed loading module ${id}\n ${err}`
throw err
}
}

Expand Down

0 comments on commit 1c5b160

Please sign in to comment.