1
0
Fork 0

[react] feat: Improved error handling when pattern drafting fails (#205)

If drafting a part fails, the logs store contains an array [message, stacktrace] instead of a simple string. Handle this in the LogView.

Also display an error message in the DraftView when there were errors during drafting.

![image](/attachments/62cdebe3-7825-4382-88fd-395337cda58b)

after clicking the button:

![image](/attachments/f909887b-b9bd-467e-9e13-f850c38d76b3)

(could also display the stack traces, but they're quite unhelpful and you can find them in the browser console anyway, the code for that is commented out)

Closes #163

Reviewed-on: https://codeberg.org/freesewing/freesewing/pulls/205
Reviewed-by: Joost De Cock <joostdecock@noreply.codeberg.org>
Co-authored-by: Jonathan Haas <haasjona@gmail.com>
Co-committed-by: Jonathan Haas <haasjona@gmail.com>
This commit is contained in:
Jonathan Haas 2025-04-06 14:42:42 +00:00 committed by Joost De Cock
parent 40eb75ce43
commit b4a5393290
6 changed files with 96 additions and 15 deletions

View file

@ -0,0 +1,59 @@
// Dependencies
import React, { useState } from 'react'
import { Popout } from '../../../Popout/index.mjs'
import { CloseIcon, ExpandIcon } from '../../../Icon/index.mjs'
import { Link } from '../../../Link/index.mjs'
import { LogEntry } from './LogEntry.mjs'
export const DraftErrorHandler = ({ failure, errors }) => {
const [expanded, setExpanded] = useState(false)
const [hidden, setHidden] = useState(false)
if (hidden || (!failure && errors.length === 0)) {
return null
}
return (
<Popout error>
<p>
Sorry, there were problems drafting your pattern with the given measurements and options.
</p>
<p>
Please double check your measurements according to{' '}
<Link href="/docs/measurements/">our documentation</Link>. Also check{' '}
<Link href="https://v4.freesewing.org/docs/about/faq/measurements-issues/">
our common measurement issues FAQ entry
</Link>
.
</p>
<p>
If you believe your measurements are correct and/or if you'd like further assistance, you
can ask for help <Link href="https://forum.freesewing.eu">on our forum</Link>,{' '}
<Link href="https://discord.freesewing.org">our Discord server</Link>, or{' '}
<Link href="https://codeberg.org/freesewing/freesewing/issues">report an issue</Link>.
</p>
<div className={'tw-mt-4'}>
<button
className={`tw-daisy-btn tw-daisy-btn-primary`}
onClick={() => setExpanded(!expanded)}
>
<ExpandIcon />
Show error details
</button>
<button className={`tw-daisy-btn tw-ml-4`} onClick={() => setHidden(true)}>
<CloseIcon />
Hide
</button>
</div>
{expanded ? (
<div className={'tw-mt-8'}>
{failure ? <LogEntry key="failure" logEntry={failure} /> : null}
{errors.map((line, i) => (
<LogEntry key={i} logEntry={line} />
))}
</div>
) : null}
</Popout>
)
}

View file

@ -1,10 +1,11 @@
// Dependencies // Dependencies
import React from 'react' import React from 'react'
import { draft, missingMeasurements, bundlePatternTranslations } from '../../lib/index.mjs' import { bundlePatternTranslations, draft, missingMeasurements } from '../../lib/index.mjs'
// Components // Components
import { Null } from '@freesewing/react/components/Null' import { Null } from '@freesewing/react/components/Null'
import { ZoomablePattern } from '../ZoomablePattern.mjs' import { ZoomablePattern } from '../ZoomablePattern.mjs'
import { PatternLayout } from '../PatternLayout.mjs' import { PatternLayout } from '../PatternLayout.mjs'
import { DraftErrorHandler } from './DraftErrorHandler.mjs'
/** /**
* The draft view allows users to tweak their pattern * The draft view allows users to tweak their pattern
@ -38,7 +39,7 @@ export const DraftView = ({ Design, state, update, config, plugins = [], PluginO
/* /*
* First, attempt to draft * First, attempt to draft
*/ */
const { pattern } = draft(Design, state.settings, plugins) const { pattern, failure, errors } = draft(Design, state.settings, plugins)
/* /*
* Create object holding strings for translation * Create object holding strings for translation
@ -52,6 +53,7 @@ export const DraftView = ({ Design, state, update, config, plugins = [], PluginO
const __html = pattern.render() const __html = pattern.render()
output = ( output = (
<> <>
<DraftErrorHandler {...{ failure, errors }} />
<PluginOutput {...{ pattern, Design, state, update, config }} /> <PluginOutput {...{ pattern, Design, state, update, config }} />
<ZoomablePattern update={update}> <ZoomablePattern update={update}>
<div className="tw-w-full tw-h-full" dangerouslySetInnerHTML={{ __html }} /> <div className="tw-w-full tw-h-full" dangerouslySetInnerHTML={{ __html }} />
@ -65,6 +67,7 @@ export const DraftView = ({ Design, state, update, config, plugins = [], PluginO
renderProps = pattern.getRenderProps() renderProps = pattern.getRenderProps()
output = ( output = (
<> <>
<DraftErrorHandler {...{ failure, errors }} />
<PluginOutput {...{ pattern, Design, state, update, config, strings }} /> <PluginOutput {...{ pattern, Design, state, update, config, strings }} />
<ZoomablePattern <ZoomablePattern
renderProps={renderProps} renderProps={renderProps}

View file

@ -9,6 +9,7 @@ import { Null } from '@freesewing/react/components/Null'
import { ZoomablePattern } from '../ZoomablePattern.mjs' import { ZoomablePattern } from '../ZoomablePattern.mjs'
import { PatternLayout } from '../PatternLayout.mjs' import { PatternLayout } from '../PatternLayout.mjs'
import { Xray } from '@freesewing/react/components/Xray' import { Xray } from '@freesewing/react/components/Xray'
import { DraftErrorHandler } from './DraftErrorHandler.mjs'
/** /**
* The inspect view allows users to inspect their pattern * The inspect view allows users to inspect their pattern
@ -47,7 +48,7 @@ export const InspectView = ({ Design, state, update, config }) => {
/* /*
* First, attempt to draft * First, attempt to draft
*/ */
const { pattern } = draft(Design, state.settings) const { pattern, failure, errors } = draft(Design, state.settings, plugins)
/* /*
* Create object holding strings for translation * Create object holding strings for translation
@ -57,6 +58,7 @@ export const InspectView = ({ Design, state, update, config }) => {
const renderProps = pattern.getRenderProps() const renderProps = pattern.getRenderProps()
const output = ( const output = (
<> <>
<DraftErrorHandler {...{ failure, errors }} />
<ZoomablePattern <ZoomablePattern
renderProps={renderProps} renderProps={renderProps}
patternLocale={state.locale || 'en'} patternLocale={state.locale || 'en'}

View file

@ -7,6 +7,7 @@ import { draft } from '../../lib/index.mjs'
// Components // Components
import { PatternLayout } from '../PatternLayout.mjs' import { PatternLayout } from '../PatternLayout.mjs'
import { MovablePattern } from '../MovablePattern.mjs' import { MovablePattern } from '../MovablePattern.mjs'
import { DraftErrorHandler } from './DraftErrorHandler.mjs'
export const LayoutView = (props) => { export const LayoutView = (props) => {
const { config, state, update, Design } = props const { config, state, update, Design } = props
@ -23,17 +24,19 @@ export const LayoutView = (props) => {
* Now draft the pattern * Now draft the pattern
*/ */
const { pattern, failure, errors } = draft(Design, settings, [tilerPlugin(pageSettings)]) const { pattern, failure, errors } = draft(Design, settings, [tilerPlugin(pageSettings)])
if (failure) return <p>Draft failed. FIXME: Handle this gracefully.</p>
const output = ( const output = (
<MovablePattern <>
{...{ <DraftErrorHandler {...{ failure, errors }} />
update, <MovablePattern
renderProps: pattern.getRenderProps(), {...{
immovable: ['pages'], update,
state, renderProps: pattern.getRenderProps(),
}} immovable: ['pages'],
/> state,
}}
/>
</>
) )
return <PatternLayout {...{ update, Design, output, state, pattern, config }} /> return <PatternLayout {...{ update, Design, output, state, pattern, config }} />

View file

@ -0,0 +1,14 @@
import React from 'react'
import Markdown from 'react-markdown'
export const LogEntry = ({ logEntry }) => (
<>
<div className="log-entry tw-mb-2">
<Markdown>{Array.isArray(logEntry) ? logEntry[0] : logEntry}</Markdown>
</div>
{/* uncomment to enable stacktrace view
Array.isArray(logEntry) && logEntry[1].stack ? (
<Highlight title="Stacktrace" raw={logEntry[1]?.stack} />
) : null */}
</>
)

View file

@ -3,10 +3,10 @@ import { draft } from '../../lib/index.mjs'
// Hooks // Hooks
import React from 'react' import React from 'react'
// Components // Components
import Markdown from 'react-markdown'
import { H1, H3 } from '@freesewing/react/components/Heading' import { H1, H3 } from '@freesewing/react/components/Heading'
import { HeaderMenu } from '../HeaderMenu.mjs' import { HeaderMenu } from '../HeaderMenu.mjs'
import { Tabs, Tab } from '@freesewing/react/components/Tab' import { Tabs, Tab } from '@freesewing/react/components/Tab'
import { LogEntry } from './LogEntry.mjs'
// The log levels // The log levels
const levels = ['error', 'warn', 'info', 'debug'] const levels = ['error', 'warn', 'info', 'debug']
@ -36,7 +36,7 @@ export const LogView = (props) => {
<> <>
<H3>{level}</H3> <H3>{level}</H3>
{pattern.setStores[0].logs[level].map((line, i) => ( {pattern.setStores[0].logs[level].map((line, i) => (
<Markdown key={i}>{line}</Markdown> <LogEntry key={i} logEntry={line} />
))} ))}
</> </>
) : null} ) : null}
@ -50,7 +50,7 @@ export const LogView = (props) => {
<> <>
<H3>{level}</H3> <H3>{level}</H3>
{pattern.store.logs[level].map((line, i) => ( {pattern.store.logs[level].map((line, i) => (
<Markdown key={i}>{line}</Markdown> <LogEntry key={i} logEntry={line} />
))} ))}
</> </>
) : null} ) : null}