1
0
Fork 0

wip(lab): Work on workbench

- Moved the various tabs on the draft view to their own views.
- Renames modes to views
- Started to group various aspects of the workbench state under
  `_state` in the gist to prevent it from getting all mixed up
  with the core settings.
- Updated events title to make it more clear not all events might
  be present
- Removed valid state in measurements input since it was only
  getting updated on keyboard input but now when preloading
  measurements (which it does now)
This commit is contained in:
Joost De Cock 2022-02-12 14:31:17 +01:00
parent bbb2b2c23f
commit 3653700572
12 changed files with 155 additions and 156 deletions

View file

@ -121,7 +121,7 @@ const DefaultLayout = ({
sm:flex-row-reverse sm:flex-row-reverse
${workbench && collapsePrimaryNav ${workbench && collapsePrimaryNav
? 'sm:px-0 sm:w-16' ? 'sm:px-0 sm:w-16'
: 'sm:px-1 md:px-4 lg:px-8 xl:px-16 2xl:px-32 sm:w-[38.2%]' : 'sm:px-1 md:px-4 lg:px-8 sm:w-[38.2%]'
} }
`}> `}>
{workbench && ( {workbench && (
@ -144,7 +144,7 @@ const DefaultLayout = ({
sm:px-1 md:px-4 lg:px-8 sm:px-1 md:px-4 lg:px-8
${workbench && collapsePrimaryNav ${workbench && collapsePrimaryNav
? '' ? ''
: 'max-w-61.8% xl:px-16 2xl:px-32' : 'max-w-61.8%'
} }
`}> `}>
<div className={workbench ? '' : "max-w-5xl"}> <div className={workbench ? '' : "max-w-5xl"}>
@ -164,7 +164,7 @@ const DefaultLayout = ({
sm:flex-row sm:flex-row
${workbench && collapseAltMenu ${workbench && collapseAltMenu
? 'sm:px-0 sm:w-16' ? 'sm:px-0 sm:w-16'
: 'sm:px-1 md:px-4 lg:px-8 xl:px-16 2xl:px-32 sm:w-[38.2%]' : 'sm:px-1 md:px-4 lg:px-8 sm:w-[38.2%]'
} }
`}> `}>
<div className={`hidden sm:flex`}> <div className={`hidden sm:flex`}>

View file

@ -1,5 +1,5 @@
import Robot from 'shared/components/robot/index.js' import Robot from 'shared/components/robot/index.js'
import Events from './events.js' import Events from '../events.js'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
const Error = props => { const Error = props => {

View file

@ -1,36 +0,0 @@
import Markdown from 'react-markdown'
import { linkClasses } from 'shared/components/navigation/primary.js'
const eventBlock = events => events.join(" \n")
const EventGroup = ({ type='info', events=[] }) => events.length > 0 ? (
<div className="">
<h3 className="capitalize" id={`events-${type}`}>{type}</h3>
<div className="mdx">
<Markdown>{eventBlock(events)}</Markdown>
</div>
</div>
) : null
const order = [
'error',
'warning',
'info',
'debug'
]
const Events = props => (
<div className="flex flex-col">
<ul className="flex flex-row row-wrap">
{order.map(type => (
<li key={type} className="">
<a href={`#events-${type}`} className={`text-secondary font-bold capitalize text-xl`}>{type}</a>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
))}
</ul>
{order.map(type => <EventGroup type={type} events={props.events[type]} />)}
</div>
)
export default Events

View file

@ -1,25 +1,12 @@
import React, { useState } from 'react'
import SvgWrapper from './svg-wrapper' import SvgWrapper from './svg-wrapper'
import Error from './error.js' import Error from './error.js'
import Events from './events.js'
import Json from 'shared/components/json.js'
import Yaml from 'shared/components/yaml.js'
import { capitalize } from 'shared/utils.js' import { capitalize } from 'shared/utils.js'
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch" import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"
const tabClasses = active => `
tab tab-bordered font-bold text-4xl pb-12 capitalize
${active && 'text-base-content tab-active'}
`
const Wrap = props => <div className="max-w-screen-xl m-auto">{props.children}</div>
const LabDraft = props => { const LabDraft = props => {
const { app, draft, pattern, gist, updateGist, unsetGist } = props const { app, draft, pattern, gist, updateGist, unsetGist } = props
if (!draft) return null if (!draft) return null
const [tab, setTab] = useState(props.pattern.config.name)
if (gist?.renderer === 'svg') { if (gist?.renderer === 'svg') {
// Render as SVG // Render as SVG
let svg let svg
@ -41,24 +28,14 @@ const LabDraft = props => {
return ( return (
<div> <div>
<div className="tabs my-8 mx-auto justify-center"> <SvgWrapper
{[props.pattern.config.name, 'events', 'yaml', 'json'].map(name => <button
key={name}
onClick={() => setTab(name)}
className={tabClasses(tab === name)}
>{name}</button>)}
</div>
{tab === 'events' && <Wrap><Events events={draft.events} /></Wrap>}
{tab === 'json' && <Wrap><Json>{JSON.stringify(props.gist, null, 2)}</Json></Wrap>}
{tab === 'yaml' && <Wrap><Yaml json={JSON.stringify(props.gist, null, 2)} /></Wrap>}
{tab === props.pattern.config.name && <SvgWrapper
draft={draft} draft={draft}
patternProps={patternProps} patternProps={patternProps}
gist={gist} gist={gist}
updateGist={updateGist} updateGist={updateGist}
unsetGist={unsetGist} unsetGist={unsetGist}
app={app} app={app}
/>} />
</div> </div>
) )
} }

View file

@ -0,0 +1,44 @@
import Markdown from 'react-markdown'
import { linkClasses } from 'shared/components/navigation/primary.js'
const eventBlock = events => events.join(" \n")
const EventGroup = ({ type='info', events=[] }) => events.length > 0 ? (
<div className="">
<h3 className="capitalize" id={`events-${type}`}>{type}</h3>
<div className="mdx ml-2">
<Markdown>{eventBlock(events)}</Markdown>
</div>
</div>
) : null
const order = [
'error',
'warning',
'info',
'debug'
]
const Events = props => (
<div className="max-w-screen-xl m-auto">
<div className="flex flex-col">
<ul className="flex flex-row row-wrap">
{order.map(type => (props.draft.events[type].length > 0)
? (
<li key={type} className="">
<a href={`#events-${type}`} className={`text-secondary font-bold capitalize text-xl`}>{type}</a>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
) : (
<li key={type} className="text-base-content font-bold capitalize text-xl">
<span className="opacity-50">{type}</span>
{type === 'debug' ? '' : <span className="px-2 font-bold">|</span>}
</li>
)
)}
</ul>
{order.map(type => <EventGroup type={type} events={props.draft.events[type]} />)}
</div>
</div>
)
export default Events

View file

@ -13,15 +13,17 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements }) => {
const { t } = useTranslation(['app', 'measurements']) const { t } = useTranslation(['app', 'measurements'])
const prefix = (app.site === 'org') ? '' : 'https://freesewing.org' const prefix = (app.site === 'org') ? '' : 'https://freesewing.org'
const title = t(`measurements:${m}`) const title = t(`measurements:${m}`)
const isValid = input => {
if (input === '') return '' const isValValid = val => (typeof val === 'undefined' || val === '')
return !isNaN(input) ? null
} : !isNaN(val)
const isValid = (newVal) => (typeof newVal === 'undefined')
? isValValid(gist?.measurements?.[m])
: isValValid(newVal)
const update = evt => { const update = evt => {
setVal(evt.target.value) setVal(evt.target.value)
const ok = isValid(evt.target.value) const ok = isValid(evt.target.value)
console.log({ok})
if (ok) { if (ok) {
setValid(true) setValid(true)
updateMeasurements(evt.target.value*10, m) updateMeasurements(evt.target.value*10, m)
@ -29,10 +31,6 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements }) => {
} }
const [val, setVal] = useState(gist?.measurements?.[m] || '') const [val, setVal] = useState(gist?.measurements?.[m] || '')
const [valid, setValid] = useState(typeof gist?.measurements?.[m] === 'undefined'
? '' :
isValid(gist.measurements[m])
)
useEffect(() => { useEffect(() => {
if (gist?.measurements?.[m]) setVal(gist.measurements[m]/10) if (gist?.measurements?.[m]) setVal(gist.measurements[m]/10)
@ -40,6 +38,7 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements }) => {
if (!m) return null if (!m) return null
const valid = isValid()
return ( return (
<div className="form-control mb-2" key={`wrap-${m}`}> <div className="form-control mb-2" key={`wrap-${m}`}>
<label className="label"> <label className="label">
@ -59,20 +58,19 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements }) => {
type="text" type="text"
placeholder={title} placeholder={title}
className={` className={`
input input-lg input-bordered grow text-base-content input input-lg input-bordered grow text-base-content border-r-0
${valid === false && 'input-error'} ${isValid() === false && 'input-error'}
${valid === true && 'input-success'} ${isValid() === true && 'input-success'}
border-r-0
`} `}
value={val} value={val}
onChange={update} onChange={update}
/> />
<span role="img" className={`bg-transparent border-y <span role="img" className={`bg-transparent border-y
${valid === false && 'border-error text-neutral-content'} ${isValid() === false && 'border-error text-neutral-content'}
${valid === true && 'border-success text-neutral-content'} ${isValid() === true && 'border-success text-neutral-content'}
${valid === '' && 'border-base-200 text-base-content'} ${isValid() === null && 'border-base-200 text-base-content'}
`}> `}>
{valid === '' {isValid() === null
? '' ? ''
: valid : valid
? '👍' ? '👍'
@ -82,7 +80,7 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements }) => {
<span className={` <span className={`
${valid === false && 'bg-error text-neutral-content'} ${valid === false && 'bg-error text-neutral-content'}
${valid === true && 'bg-success text-neutral-content'} ${valid === true && 'bg-success text-neutral-content'}
${valid === '' && 'bg-base-200 text-base-content'} ${valid === null && 'bg-base-200 text-base-content'}
`}> `}>
cm cm
</span> </span>

View file

@ -0,0 +1,9 @@
import Json from 'shared/components/json.js'
const GistAsJson = props => (
<div className="max-w-screen-xl m-auto">
<Json>{JSON.stringify(props.gist, null, 2)}</Json>
</div>
)
export default GistAsJson

View file

@ -44,19 +44,19 @@ const WorkbenchMeasurements = ({ app, pattern, gist, updateGist }) => {
const inputProps = { app, updateMeasurements, gist } const inputProps = { app, updateMeasurements, gist }
return ( return (
<div className="m-auto max-w-prose"> <div className="m-auto max-w-2xl">
<h1> <h1>
<span className='capitalize mr-4 opacity-70'> <span className='capitalize mr-4 opacity-70'>
{pattern.config.name}: {pattern.config.name}:
</span> </span>
{t('measurements')} {t('measurements')}
</h1> </h1>
<details open> <details open className="my-2">
<summary><h2 className="inline-block">{t('cfp:preloadMeasurements')}</h2></summary> <summary><h2 className="inline pl-1">{t('cfp:preloadMeasurements')}</h2></summary>
<div className="ml-2 pl-4 border-l-2"> <div className="ml-2 pl-4 border-l-2">
{Object.keys(groups).map(group => ( {Object.keys(groups).map(group => (
<details key={group}> <details key={group}>
<summary><h3 className="inline-block">{t(group)}</h3></summary> <summary><h3 className="inline pl-1">{t(group)}</h3></summary>
<div className="ml-2 pl-4 border-l-2"> <div className="ml-2 pl-4 border-l-2">
{Object.keys(icons).map(type => ( {Object.keys(icons).map(type => (
<React.Fragment key={type}> <React.Fragment key={type}>
@ -86,8 +86,8 @@ const WorkbenchMeasurements = ({ app, pattern, gist, updateGist }) => {
</div> </div>
</details> </details>
<details> <details className="my-2">
<summary><h2 className="inline-block">{t('cfp:enterMeasurements')}</h2></summary> <summary><h2 className="inline pl-2">{t('cfp:enterMeasurements')}</h2></summary>
<div className="ml-2 pl-4 border-l-2"> <div className="ml-2 pl-4 border-l-2">
{pattern.config.measurements && ( {pattern.config.measurements && (
<> <>

View file

@ -1,5 +1,5 @@
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js' import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import ModesMenu from './modes.js' import ViewMenu from './view.js'
import DesignOptions from './design-options' import DesignOptions from './design-options'
import CoreSettings from './core-settings' import CoreSettings from './core-settings'
import Xray from './xray' import Xray from './xray'
@ -84,8 +84,8 @@ export const SecText = props => props.raw
const WorkbenchMenu = props => { const WorkbenchMenu = props => {
return ( return (
<nav className="smmax-w-96 grow mb-12"> <nav className="smmax-w-96 grow mb-12">
<ModesMenu {...props} /> <ViewMenu {...props} />
{props.mode === 'draft' && ( {props.gist?._state?.view === 'draft' && (
<> <>
<DesignOptions {...props} /> <DesignOptions {...props} />
<CoreSettings {...props} /> <CoreSettings {...props} />

View file

@ -3,28 +3,43 @@ import OptionsIcon from 'shared/components/icons/options.js'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js' import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
const Modes = props => { const View = props => {
const { t } = useTranslation(['app']) const { t } = useTranslation(['app'])
const entries = [ const entries = [
{ {
name: 'measurements', name: 'measurements',
title: t('measurements'), title: t('measurements'),
onClick: () => props.setMode('measurements') onClick: () => props.updateGist(['_state', 'view'], 'measurements')
}, },
{ {
name: 'draft', name: 'draft',
title: t('draftPattern', { pattern: props.pattern.config.name }), title: t('draftPattern', { pattern: props.pattern.config.name }),
onClick: () => props.setMode('draft') onClick: () => props.updateGist(['_state', 'view'], 'draft')
}, },
{ {
name: 'test', name: 'test',
title: t('testPattern', { pattern: props.pattern.config.name }), title: t('testPattern', { pattern: props.pattern.config.name }),
onClick: () => props.setMode('test') onClick: () => props.updateGist(['_state', 'view'], 'test')
}, },
{ {
name: 'export', name: 'export',
title: t('export'), title: t('export'),
onClick: () => props.setMode('export') onClick: () => props.updateGist(['_state', 'view'], 'export')
},
{
name: 'events',
title: t('events'),
onClick: () => props.updateGist(['_state', 'view'], 'events')
},
{
name: 'yaml',
title: t('YAML'),
onClick: () => props.updateGist(['_state', 'view'], 'yaml')
},
{
name: 'json',
title: t('JSON'),
onClick: () => props.updateGist(['_state', 'view'], 'json')
}, },
] ]
@ -40,7 +55,7 @@ const Modes = props => {
`}> `}>
<span className="text-secondary-focus mr-4"><MenuIcon /></span> <span className="text-secondary-focus mr-4"><MenuIcon /></span>
<span className={`grow ${linkClasses} hover:cursor-resize`}> <span className={`grow ${linkClasses} hover:cursor-resize`}>
{t('modes')} {t('view')}
</span> </span>
<Chevron /> <Chevron />
</summary> </summary>
@ -55,21 +70,21 @@ const Modes = props => {
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-left text-left
capitalize capitalize
${entry.name === props.mode ${entry.name === props.gist?._state?.view
? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus' ? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus'
: 'text-base-content sm:text-neutral-content' : 'text-base-content sm:text-neutral-content'
} }
`} onClick={entry.onClick}> `} onClick={entry.onClick}>
<span className={` <span className={`
text-3xl mr-2 inline-block p-0 leading-3 text-3xl mr-2 inline-block p-0 leading-3
${entry.name === props.mode ${entry.name === props.gist?._state?.view
? 'text-secondary sm:text-secondary-focus translate-y-1' ? 'text-secondary sm:text-secondary-focus translate-y-1'
: 'translate-y-3' : 'translate-y-3'
} }
`}> `}>
{entry.name === props.mode ? <>&bull;</> : <>&deg;</>} {entry.name === props.gist?._state?.view ? <>&bull;</> : <>&deg;</>}
</span> </span>
<span className={entry.name === props.mode ? 'font-bold' : ''}> <span className={entry.name === props.gist?._state?.view ? 'font-bold' : ''}>
{ entry.title } { entry.title }
</span> </span>
</button> </button>
@ -80,4 +95,4 @@ const Modes = props => {
) )
} }
export default Modes export default View

View file

@ -0,0 +1,9 @@
import Yaml from 'shared/components/yaml.js'
const GistAsYaml = props => (
<div className="max-w-screen-xl m-auto">
<Yaml json={JSON.stringify(props.gist, null, 2)} />
</div>
)
export default GistAsYaml

View file

@ -2,14 +2,29 @@ import { useState, useEffect } 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'
import Measurements, { Input } from 'shared/components/workbench/measurements/index.js'
import LabDraft from 'shared/components/workbench/draft/index.js'
import set from 'lodash.set' import set from 'lodash.set'
import unset from 'lodash.unset' import unset from 'lodash.unset'
import defaultSettings from 'shared/components/workbench/default-settings.js' import defaultSettings from 'shared/components/workbench/default-settings.js'
import DraftError from 'shared/components/workbench/draft/error.js' import DraftError from 'shared/components/workbench/draft/error.js'
import theme from 'pkgs/plugin-theme/src/index.js' import theme from 'pkgs/plugin-theme/src/index.js'
// Views
import Measurements, { Input } from 'shared/components/workbench/measurements/index.js'
import LabDraft from 'shared/components/workbench/draft/index.js'
import GistAsJson from 'shared/components/workbench/json.js'
import GistAsYaml from 'shared/components/workbench/yaml.js'
import DraftEvents from 'shared/components/workbench/events.js'
const views = {
measurements: Measurements,
draft: LabDraft,
test: () => <p>TODO</p>,
export: () => <p>TODO</p>,
events: DraftEvents,
yaml: GistAsYaml,
json: GistAsJson,
welcome: () => <p>TODO</p>,
}
// Generates a default pattern gist to start from // Generates a default pattern gist to start from
const defaultGist = (pattern, locale='en') => { const defaultGist = (pattern, locale='en') => {
@ -33,30 +48,24 @@ const hasRequiredMeasurements = (pattern, gist) => {
/* /*
* This component wraps the workbench and is in charge of * This component wraps the workbench and is in charge of
* keeping the mode & gist state, which will trickly down * keeping the gist state, which will trickly down
* to all workbench subcomponents * to all workbench subcomponents
*
* mode: What to display (draft, sample, measurements, ...)
* gist: The runtime pattern configuration
*/ */
const WorkbenchWrapper = ({ app, pattern }) => { const WorkbenchWrapper = ({ app, pattern }) => {
// State for display mode and gist // State for gist
const [mode, setMode] = useState('measurements') const [gist, setGist] = useLocalStorage(`${pattern.config.name}_gist`, defaultGist(pattern, app.locale))
const [gist, setGist] = useLocalStorage('gist', defaultGist(pattern, app.locale))
// If we don't have the requiremed measurements, // If we don't have the required measurements,
// force mode to measurements // force view to measurements
useEffect(() => { useEffect(() => {
if ( if (
mode !== 'measurements' gist?._state?.view !== 'measurements'
&& !hasRequiredMeasurements(pattern, gist) && !hasRequiredMeasurements(pattern, gist)
) setMode('measurements') ) updateGist(['_state', 'view'], 'measurements')
}) })
/* // Helper methods to manage the gist state
* Update gist method. See lodash.set
*/
const updateGist = (path, content) => { const updateGist = (path, content) => {
const newGist = {...gist} const newGist = {...gist}
set(newGist, path, content) set(newGist, path, content)
@ -68,10 +77,9 @@ const WorkbenchWrapper = ({ app, pattern }) => {
setGist(newGist) setGist(newGist)
} }
// Generate the draft here so we can pass it to both Menu // Generate the draft here so we can pass it down
// and LabDraft
let draft = false let draft = false
if (mode === 'draft') { if (['draft', 'events'].indexOf(gist?._state?.view) !== -1) {
draft = new pattern(gist) draft = new pattern(gist)
if (gist?.renderer === 'svg') draft.use(theme) if (gist?.renderer === 'svg') draft.use(theme)
try { draft.draft() } try { draft.draft() }
@ -81,48 +89,23 @@ const WorkbenchWrapper = ({ app, pattern }) => {
} }
} }
// Props to pass down
const componentProps = { app, pattern, gist, updateGist, unsetGist, setGist, draft }
// Required props for layout // Required props for layout
const layoutProps = { const layoutProps = {
app: app, app: app,
noSearch: true, noSearch: true,
workbench: true, workbench: true,
AltMenu: <Menu AltMenu: <Menu {...componentProps }/>
app={app}
pattern={pattern}
mode={mode}
setMode={setMode}
gist={gist}
updateGist={updateGist}
unsetGist={unsetGist}
setGist={setGist}
draft={draft}
/>
} }
const Component = views[gist?._state?.view]
? views[gist._state.view]
: views.welcome
return <Layout {...layoutProps}>
return ( <Component {...componentProps} />
<Layout {...layoutProps}> </Layout>
{mode === 'measurements' && (
<Measurements
app={app}
pattern={pattern}
gist={gist}
updateGist={updateGist}
/>
)}
{mode === 'draft' && (
<LabDraft
app={app}
pattern={pattern}
draft={draft}
gist={gist}
updateGist={updateGist}
unsetGist={unsetGist}
/>
)}
</Layout>
)
} }
export default WorkbenchWrapper export default WorkbenchWrapper