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 SvgWrapper from './svg-wrapper'
import Error from './error.js' import Error from './error.js'
import Popout from 'shared/components/popout.js'
import Robot from 'shared/components/robot/index.js'
const LabDraft = props => { 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 (!draft) return null
if (gist?.renderer === 'svg') { if (gist?.renderer === 'svg') {
@ -24,17 +29,66 @@ const LabDraft = props => {
return <Error error={error} {...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> <div>
<SvgWrapper <h3>Got {patternProps.events.error.length} problems and a stitch ain't one</h3>
draft={draft} <p>Don't be alarmed, but we ran into some trouble while drafting this pattern.</p>
patternProps={patternProps} <h4>Help us make FreeSewing better</h4>
gist={gist} <p>
updateGist={updateGist} If you like puzzles, you can try to figure out what went wrong:
unsetGist={unsetGist} </p>
app={app} <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> </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 Path from '../path'
import Point from '../point' import Point from '../point'
import Snippet from '../snippet' import Snippet from '../snippet'
import { getProps } from '../utils' 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 => { const XrayPart = props => {
// Don't bother if this is the only part on display // Don't bother if this is the only part on display
if (props.gist.only && props.gist.only.length === 1) return null if (props.gist.only && props.gist.only.length === 1) return null
@ -77,5 +101,14 @@ const Part = props => {
</g> </g>
) )
} }
/*
<ErrorBoundary
x={part.topLeft.x}
y={part.topLeft.y}
width={part.width}
height={part.height}
>
</ErrorBoundary>
*/
export default Part export default Part

View file

@ -1,3 +1,4 @@
import React from 'react'
import TextOnPath from '../text-on-path' import TextOnPath from '../text-on-path'
import { getProps } from '../utils' import { getProps } from '../utils'
@ -21,9 +22,15 @@ const Path = props => {
if (!path.render) return null if (!path.render) return null
const output = [] const output = []
const pathId = 'path-' + partName + '-' + pathName const pathId = 'path-' + partName + '-' + pathName
output.push( let d = ''
<path id={pathId} key={pathId} d={path.asPathstring()} {...getProps(path)} /> 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')) if (path.attributes.get('data-text'))
output.push(<TextOnPath key={'text-on-path-' + name} pathId={pathId} {...props} />) output.push(<TextOnPath key={'text-on-path-' + name} pathId={pathId} {...props} />)
if (props.gist._state?.xray?.enabled) output.push(<XrayPath {...props} key={'xpath'+pathId} />) 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> <style>{`:root { --pattern-scale: ${gist.scale || 1}}`}</style>
<g> <g>
{Object.keys(patternProps.parts).map((name) => ( {Object.keys(patternProps.parts).map((name) => (
<Part <Part {...{ app, gist, updateGist, unsetGist }}
key={name} key={name}
partName={name} partName={name}
part={patternProps.parts[name]} part={patternProps.parts[name]}
app={app}
gist={gist}
updateGist={updateGist}
unsetGist={unsetGist}
/> />
))} ))}
</g> </g>

View file

@ -152,7 +152,7 @@ const Part = props => {
const partLayout= layout.parts[name] const partLayout= layout.parts[name]
// Don't just assume this makes sense // 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 // Use a ref for direct DOM manipulation
const partRef = useRef(null) const partRef = useRef(null)

View file

@ -15,7 +15,7 @@ const preload = {
if (result.data.files['pattern.yaml'].content) { if (result.data.files['pattern.yaml'].content) {
let g = yaml.load(result.data.files['pattern.yaml'].content) let g = yaml.load(result.data.files['pattern.yaml'].content)
if (g.design !== pattern.config.name) return [ 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 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 useLocalStorage from 'shared/hooks/useLocalStorage.js'
import Layout from 'shared/components/layouts/default' import Layout from 'shared/components/layouts/default'
import Menu from 'shared/components/workbench/menu/index.js' import Menu from 'shared/components/workbench/menu/index.js'
@ -62,6 +62,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
// State for gist // State for gist
const [gist, setGist] = useLocalStorage(`${pattern.config.name}_gist`, defaultGist(pattern, app.locale)) const [gist, setGist] = useLocalStorage(`${pattern.config.name}_gist`, defaultGist(pattern, app.locale))
const [messages, setMessages] = useState([])
// If we don't have the required measurements, // If we don't have the required measurements,
// force view to measurements // force view to measurements
@ -76,7 +77,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
useEffect(async () => { useEffect(async () => {
if (preload && from && preloaders[from]) { if (preload && from && preloaders[from]) {
const g = await preloaders[from](preload, pattern) const g = await preloaders[from](preload, pattern)
// FIXME: Continue here setGist({ ...gist, ...g.settings })
} }
}, [preload, from]) }, [preload, from])
@ -91,6 +92,17 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
unset(newGist, path) unset(newGist, path)
setGist(newGist) 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 // Generate the draft here so we can pass it down
let draft = false let draft = false
@ -107,7 +119,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
} }
// Props to pass down // 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 // Required props for layout
const layoutProps = { const layoutProps = {
app: app, app: app,
@ -121,6 +133,7 @@ const WorkbenchWrapper = ({ app, pattern, preload=false, from=false }) => {
: views.welcome : views.welcome
return <Layout {...layoutProps}> return <Layout {...layoutProps}>
{messages}
<Component {...componentProps} /> <Component {...componentProps} />
</Layout> </Layout>
} }