1
0
Fork 0

wip(lab): Working on crash reporting in the new dev env

This commit is contained in:
Joost De Cock 2022-03-18 19:11:00 +01:00
parent f100691a92
commit 8a2a13f240
7 changed files with 164 additions and 61 deletions

View file

@ -1,8 +1,13 @@
import { useState } from 'react'
import SvgWrapper from './svg-wrapper'
import Error from './error.js'
import Popout from 'shared/components/popout.js'
import Robot from 'shared/components/robot/index.js'
const LabDraft = props => {
const { app, draft, pattern, gist, updateGist, unsetGist } = props
const { app, draft, pattern, gist, updateGist, unsetGist, feedback } = props
const [share, setShare] = useState(false)
if (!draft) return null
if (gist?.renderer === 'svg') {
@ -24,17 +29,66 @@ const LabDraft = props => {
return <Error error={error} {...props} />
}
return (
// Handle broken drafts
let error = null
if (patternProps.events.error.length > 0) {
error = (
<Popout warning>
<div className="flex flex-row justify-between">
<div>
<SvgWrapper
draft={draft}
patternProps={patternProps}
gist={gist}
updateGist={updateGist}
unsetGist={unsetGist}
app={app}
/>
<h3>Got {patternProps.events.error.length} problems and a stitch ain't one</h3>
<p>Don't be alarmed, but we ran into some trouble while drafting this pattern.</p>
<h4>Help us make FreeSewing better</h4>
<p>
If you like puzzles, you can try to figure out what went wrong:
</p>
<ul className="list-disc list-inside ml-4 text-xl">
<li>
Check the <button className="btn-link" onClick={() => updateGist(['_state', 'view'], 'events')}>
<strong>{patternProps.events.error.length} errors</strong> and <strong>
{patternProps.events.warning.length} warnings</strong></button>
</li>
<li>Check the partially rendered pattern below to see which areas are problematic</li>
</ul>
<p>
Alternatively, you can escalate this. Which means that:
</p>
<ul className="list-disc list-inside ml-4 text-xl">
<li>
We will compile a <strong>crash report</strong> that contains everything needed <strong>to recreate this problem</strong>
</li>
<li>
We will include <strong>personal data</strong> such as your <strong>username</strong>, <strong>
email address</strong> and <strong>measurements</strong>
</li>
<li>
We will share this report and the data in it with <a className="text-primary font-bold"
href="https://github.com/orgs/freesewing/teams/bughunters">FreeSewing's bughunters team</a>
</li>
</ul>
<div className="form-control">
<label className="cursor-pointer flex flex-row gap-4 my-4">
<input type="checkbox" checked={share} className="checkbox checkbox-primary" onChange={() => setShare(!share)}/>
<span className="label-text text-xl">I agree to the use of my personal data for this purpose</span>
</label>
</div>
<p>
<button disabled={!share} className="btn btn-primary">Escalate this</button>
</p>
</div>
<Robot pose='fail' />
</div>
</Popout>
)
}
console.log(patternProps)
return (
<>
{error}
<SvgWrapper {...{ draft, patternProps, gist, updateGist, unsetGist, app, feedback }} />
</>
)
}

View file

@ -1,8 +1,32 @@
import React from 'react'
import Path from '../path'
import Point from '../point'
import Snippet from '../snippet'
import { getProps } from '../utils'
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
console.log(error)
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo)
}
render() {
if (this.state.hasError) {
console.log('in error boundary', props)
return <text>Something went wrong.</text>
}
return this.props.children
}
}
const XrayPart = props => {
// Don't bother if this is the only part on display
if (props.gist.only && props.gist.only.length === 1) return null
@ -77,5 +101,14 @@ const Part = props => {
</g>
)
}
/*
<ErrorBoundary
x={part.topLeft.x}
y={part.topLeft.y}
width={part.width}
height={part.height}
>
</ErrorBoundary>
*/
export default Part

View file

@ -1,3 +1,4 @@
import React from 'react'
import TextOnPath from '../text-on-path'
import { getProps } from '../utils'
@ -21,9 +22,15 @@ const Path = props => {
if (!path.render) return null
const output = []
const pathId = 'path-' + partName + '-' + pathName
output.push(
<path id={pathId} key={pathId} d={path.asPathstring()} {...getProps(path)} />
)
let d = ''
try { d = path.asPathstring() }
catch (err) {
// Bail out
console.log(`Failed to generate pathstring for path ${pathId} in part ${partName}`, err)
return null
}
output.push(<path id={pathId} key={pathId} d={d} {...getProps(path)} />)
if (path.attributes.get('data-text'))
output.push(<TextOnPath key={'text-on-path-' + name} pathId={pathId} {...props} />)
if (props.gist._state?.xray?.enabled) output.push(<XrayPath {...props} key={'xpath'+pathId} />)

View file

@ -40,14 +40,10 @@ const SvgWrapper = props => {
<style>{`:root { --pattern-scale: ${gist.scale || 1}}`}</style>
<g>
{Object.keys(patternProps.parts).map((name) => (
<Part
<Part {...{ app, gist, updateGist, unsetGist }}
key={name}
partName={name}
part={patternProps.parts[name]}
app={app}
gist={gist}
updateGist={updateGist}
unsetGist={unsetGist}
/>
))}
</g>

View file

@ -152,7 +152,7 @@ const Part = props => {
const partLayout= layout.parts[name]
// Don't just assume this makes sense
if (typeof layout.parts[name].move?.x === 'undefined') return null
if (typeof layout.parts?.[name]?.move?.x === 'undefined') return null
// Use a ref for direct DOM manipulation
const partRef = useRef(null)

View file

@ -15,7 +15,7 @@ const preload = {
if (result.data.files['pattern.yaml'].content) {
let g = yaml.load(result.data.files['pattern.yaml'].content)
if (g.design !== pattern.config.name) return [
false, `You tried loading a configuration for ${g.design} into a ${design} development environment`
false, `You tried loading a configuration for ${g.design} into a ${pattern.config.name} development environment`
]
return g

View file

@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import useLocalStorage from 'shared/hooks/useLocalStorage.js'
import Layout from 'shared/components/layouts/default'
import Menu from 'shared/components/workbench/menu/index.js'
@ -62,6 +62,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
// State for gist
const [gist, setGist] = useLocalStorage(`${pattern.config.name}_gist`, defaultGist(pattern, app.locale))
const [messages, setMessages] = useState([])
// If we don't have the required measurements,
// force view to measurements
@ -76,7 +77,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
useEffect(async () => {
if (preload && from && preloaders[from]) {
const g = await preloaders[from](preload, pattern)
// FIXME: Continue here
setGist({ ...gist, ...g.settings })
}
}, [preload, from])
@ -91,6 +92,17 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
unset(newGist, path)
setGist(newGist)
}
// Helper methods to handle messages
const feedback = {
add: msg => {
const newMsgs = [...messages]
if (Array.isArray(msg)) newMsgs.push(...msg)
else newMsgs.push(msg)
setMessages(newMsgs)
},
set: setMessages,
clear: () => setMessages([]),
}
// Generate the draft here so we can pass it down
let draft = false
@ -107,7 +119,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
}
// Props to pass down
const componentProps = { app, pattern, gist, updateGist, unsetGist, setGist, draft }
const componentProps = { app, pattern, gist, updateGist, unsetGist, setGist, draft, feedback }
// Required props for layout
const layoutProps = {
app: app,
@ -121,6 +133,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
: views.welcome
return <Layout {...layoutProps}>
{messages}
<Component {...componentProps} />
</Layout>
}