wip(lab): Working on crash reporting in the new dev env
This commit is contained in:
parent
f100691a92
commit
8a2a13f240
7 changed files with 164 additions and 61 deletions
|
@ -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} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle broken drafts
|
||||||
|
let error = null
|
||||||
|
if (patternProps.events.error.length > 0) {
|
||||||
|
error = (
|
||||||
|
<Popout warning>
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<div>
|
||||||
|
<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 (
|
return (
|
||||||
<div>
|
<>
|
||||||
<SvgWrapper
|
{error}
|
||||||
draft={draft}
|
<SvgWrapper {...{ draft, patternProps, gist, updateGist, unsetGist, app, feedback }} />
|
||||||
patternProps={patternProps}
|
</>
|
||||||
gist={gist}
|
|
||||||
updateGist={updateGist}
|
|
||||||
unsetGist={unsetGist}
|
|
||||||
app={app}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -38,44 +62,53 @@ const Part = props => {
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g {...getProps(part)} id={`part-${partName}`}>
|
<g {...getProps(part)} id={`part-${partName}`}>
|
||||||
{grid}
|
{grid}
|
||||||
{
|
{
|
||||||
props.gist?._state?.xray?.enabled &&
|
props.gist?._state?.xray?.enabled &&
|
||||||
props.gist?._state?.xray?.reveal?.[partName]
|
props.gist?._state?.xray?.reveal?.[partName]
|
||||||
&& <XrayPart {...props} />
|
&& <XrayPart {...props} />
|
||||||
}
|
}
|
||||||
{Object.keys(part.paths).map((pathName) => (
|
{Object.keys(part.paths).map((pathName) => (
|
||||||
<Path
|
<Path
|
||||||
key={pathName}
|
key={pathName}
|
||||||
pathName={pathName}
|
pathName={pathName}
|
||||||
path={part.paths[pathName]}
|
path={part.paths[pathName]}
|
||||||
topLeft={props.part.topLeft}
|
topLeft={props.part.topLeft}
|
||||||
bottomRight={props.part.bottomRight}
|
bottomRight={props.part.bottomRight}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{Object.keys(props.part.points).map((pointName) => (
|
{Object.keys(props.part.points).map((pointName) => (
|
||||||
<Point
|
<Point
|
||||||
key={pointName}
|
key={pointName}
|
||||||
pointName={pointName}
|
pointName={pointName}
|
||||||
point={props.part.points[pointName]}
|
point={props.part.points[pointName]}
|
||||||
topLeft={props.part.topLeft}
|
topLeft={props.part.topLeft}
|
||||||
bottomRight={props.part.bottomRight}
|
bottomRight={props.part.bottomRight}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{Object.keys(props.part.snippets).map((snippetName) => (
|
{Object.keys(props.part.snippets).map((snippetName) => (
|
||||||
<Snippet
|
<Snippet
|
||||||
key={snippetName}
|
key={snippetName}
|
||||||
snippetName={snippetName}
|
snippetName={snippetName}
|
||||||
snippet={props.part.snippets[snippetName]}
|
snippet={props.part.snippets[snippetName]}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{focus}
|
{focus}
|
||||||
</g>
|
</g>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
<ErrorBoundary
|
||||||
|
x={part.topLeft.x}
|
||||||
|
y={part.topLeft.y}
|
||||||
|
width={part.width}
|
||||||
|
height={part.height}
|
||||||
|
>
|
||||||
|
</ErrorBoundary>
|
||||||
|
*/
|
||||||
|
|
||||||
export default Part
|
export default Part
|
||||||
|
|
|
@ -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} />)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue