1
0
Fork 0

wip(shared): Re-factoring workbench

This commit is contained in:
joostdecock 2023-05-11 19:14:48 +02:00
parent 0dece4d70e
commit 517fa3f5e3
57 changed files with 1033 additions and 1382 deletions

View file

@ -1,7 +1,7 @@
export const ns = [] export const ns = []
export const WorkbenchLayout = (props) => ( export const WorkbenchLayout = (props) => (
<section id="fs-workbench" className="my-2 lg:mt-32 lg:px-8"> <section id="fs-workbench" className="m-0 lg:mt-24 p-0">
{props.children} {props.children}
</section> </section>
) )

View file

@ -1,62 +0,0 @@
import { SvgWrapper } from './svg.mjs'
import { DraftError } from './error.mjs'
export const DraftView = ({ pattern, setView, gist, updateGist }) => {
//const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } = props
if (!pattern) return null
// Render as SVG
if (gist?.renderer === 'svg') {
let svg
try {
svg = pattern.render()
} catch (error) {
console.log('Failed to render design', error)
return <DraftError error={error} {...props} />
}
return <div dangerouslySetInnerHTML={{ __html: svg }} />
}
// Render as React
let patternProps = {}
try {
patternProps = pattern.getRenderProps()
} catch (error) {
console.log('Failed to get render props for design', error)
return (
<DraftError
error={error}
patternLogs={patternProps.store.logs}
setLogs={patternProps.setStores[0].logs}
updateGist={updateGist}
/>
)
}
const errors = []
errors.push(...patternProps.logs.pattern.error)
for (const set of patternProps.logs.sets) {
errors.push(...set.error)
}
console.log(patternProps)
return (
<>
{errors.length > 0 ? (
<DraftError
{...{
pattern,
patternProps,
updateGist,
patternLogs: pattern.store.logs,
setLogs: pattern.setStores[0].logs,
errors,
}}
/>
) : null}
<SvgWrapper {...{ pattern, patternProps, gist, updateGist }} />
</>
)
}

View file

@ -1,6 +1,5 @@
// Hooks // Hooks
import { useEffect, useState, useMemo } from 'react' import { useEffect, useState } from 'react'
import { useGist } from 'shared/hooks/useGist'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { useView } from 'shared/hooks/use-view.mjs' import { useView } from 'shared/hooks/use-view.mjs'
import { useAccount } from 'shared/hooks/use-account.mjs' import { useAccount } from 'shared/hooks/use-account.mjs'
@ -8,48 +7,45 @@ import { useBackend } from 'shared/hooks/use-backend.mjs'
// Dependencies // Dependencies
import { pluginTheme } from '@freesewing/plugin-theme' import { pluginTheme } from '@freesewing/plugin-theme'
import { pluginI18n } from '@freesewing/plugin-i18n' import { pluginI18n } from '@freesewing/plugin-i18n'
import { preloaders } from 'shared/components/workbench/preloaders.mjs' //import { preloaders } from 'shared/components/workbench/preloaders.mjs'
import _set from 'lodash.set' import _set from 'lodash.set'
import _unset from 'lodash.unset'
// Components // Components
import { WorkbenchMenu } from 'shared/components/workbench/menu/index.mjs' //import { DraftError } from 'shared/components/workbench/pattern/error.mjs'
import { DraftError } from 'shared/components/workbench/draft/error.mjs'
import { Modal } from 'shared/components/modal/modal.mjs' import { Modal } from 'shared/components/modal/modal.mjs'
import { ErrorBoundary } from 'shared/components/error/error-boundary.mjs' import { ErrorBoundary } from 'shared/components/error/error-boundary.mjs'
// Views // Views
import { WorkbenchMeasurements } from 'shared/components/workbench/measurements/index.mjs' //import { LabSample } from 'shared/components/workbench/sample.mjs'
import { LabSample } from 'shared/components/workbench/sample.mjs' //import { ExportDraft } from 'shared/components/workbench/exporting/index.mjs'
import { ExportDraft } from 'shared/components/workbench/exporting/index.mjs' //import { GistAsJson, GistAsYaml } from 'shared/components/workbench/gist.mjs'
import { GistAsJson, GistAsYaml } from 'shared/components/workbench/gist.mjs' //import { DraftLogs } from 'shared/components/workbench/logs.mjs'
import { DraftLogs } from 'shared/components/workbench/logs.mjs' //import { CutLayout } from 'shared/components/workbench/layout/cut/index.mjs'
import { CutLayout } from 'shared/components/workbench/layout/cut/index.mjs' //import { PrintLayout } from 'shared/components/workbench/layout/print/index.mjs'
import { PrintLayout } from 'shared/components/workbench/layout/print/index.mjs' //import { EditYaml } from 'shared/components/workbench/edit/index.mjs'
import { EditYaml } from 'shared/components/workbench/edit/index.mjs'
// Components // Components
import { WorkbenchHeader } from './header.mjs' import { WorkbenchHeader } from './header.mjs'
import { ErrorView } from 'shared/components/error/view.mjs' import { ErrorView } from 'shared/components/error/view.mjs'
// Views // Views
import { DraftView } from 'shared/components/workbench/draft/index.mjs' import { DraftView } from 'shared/components/workbench/views/draft/index.mjs'
export const ns = ['workbench'] export const ns = ['workbench']
const loadDefaultSettings = ({ locale = 'en', units = 'metric' }) => ({ const loadDefaultSettings = ({ locale = 'en', units = 'metric' }) => ({
settings: { sa: 0,
sa: 0, scale: 1,
scale: 1, complete: true,
complete: true, paperless: false,
paperless: false, margin: 2,
margin: 2, units,
units, locale,
locale, embed: true,
embed: true,
},
renderer: 'react',
//saBool: false,
//saMm: 10,
//debug: true,
}) })
const defaultUi = {
renderer: 'react',
}
const draftViews = ['draft', 'test'] const draftViews = ['draft', 'test']
export const Workbench = ({ design, Design, set = false }) => { export const Workbench = ({ design, Design, set = false }) => {
@ -59,20 +55,21 @@ export const Workbench = ({ design, Design, set = false }) => {
const { account, token } = useAccount() const { account, token } = useAccount()
const { backend } = useBackend(token) const { backend } = useBackend(token)
const defaults = loadDefaultSettings({ const defaultSettings = loadDefaultSettings({
units: account.imperial ? 'imperial' : 'metric', units: account.imperial ? 'imperial' : 'metric',
locale: language, locale: language,
}) })
if (set) defaults.settings.measurements = set.measies if (set) defaultSettings.measurements = set.measies
// State // State
const [view, setView] = useView() const [view, setView] = useView()
const [gist, setGist] = useState({ ...defaults, embed: true, renderer: 'react' }) const [settings, setSettings] = useState({ ...defaultSettings, embed: true })
const [ui, setUi] = useState({ ...defaultUi })
const [error, setError] = useState(false) const [error, setError] = useState(false)
// Effects // Effects
useEffect(() => { useEffect(() => {
if (set.measies) updateGist('settings.measurements', set.measies) if (set.measies) update.settings('measurements', set.measies)
}, [set]) }, [set])
// Don't bother without a set or Design // Don't bother without a set or Design
@ -87,18 +84,34 @@ export const Workbench = ({ design, Design, set = false }) => {
</> </>
) )
// Helper method to update the gist // Helper methods for settings/ui updates
const updateGist = (path, val) => { const update = {
const newGist = { ...gist } settings: (path, val = 'unset') => {
_set(newGist, path, val) const newSettings = { ...settings }
setGist(newGist) if (val === 'unset') {
if (Array.isArray(path) && Array.isArray(path[0])) {
for (const item of path) update.settings(...item)
} else _unset(newSettings, path)
} else _set(newSettings, path, val)
setSettings(newSettings)
},
ui: (path, val = 'unset') => {
const newUi = { ...ui }
if (val === 'unset') {
if (Array.isArray(path) && Array.isArray(path[0])) {
for (const item of path) update.ui(...item)
} else _unset(newUi, path)
} else _set(newUi, path, val)
setUi(newUi)
},
} }
// Generate the pattern here so we can pass it down to both the view and the options menu // Generate the pattern here so we can pass it down to both the view and the options menu
const pattern = draftViews.includes(view) ? new Design(gist.settings) : false const pattern = draftViews.includes(view) ? new Design(settings) : false
const patternConfig = pattern.getConfig()
if (pattern) { if (pattern) {
// add theme to svg renderer // add theme to svg renderer
if (gist.renderer === 'svg') { if (ui.renderer === 'svg') {
pattern.use(pluginI18n, { t }) pattern.use(pluginI18n, { t })
pattern.use(pluginTheme, { skipGrid: ['pages'] }) pattern.use(pluginTheme, { skipGrid: ['pages'] })
} }
@ -115,14 +128,15 @@ export const Workbench = ({ design, Design, set = false }) => {
return ( return (
<> <>
<WorkbenchHeader setView={setView} view={view} /> <WorkbenchHeader setView={setView} view={view} />
{view === 'draft' && <DraftView {...{ pattern, setView, gist, updateGist }} />} {view === 'draft' && (
<p>view is {view}</p> <DraftView {...{ design, pattern, patternConfig, setView, update, settings, ui }} />
<button onClick={() => setView('alt')}>alt</button> )}
<button onClick={() => setView('draft')}>draft</button> <pre>{JSON.stringify(settings, null, 2)}</pre>
<pre>{JSON.stringify(ui, null, 2)}</pre>
</> </>
) )
} }
/*
const views = { const views = {
measurements: WorkbenchMeasurements, measurements: WorkbenchMeasurements,
//draft: LabDraft, //draft: LabDraft,
@ -157,7 +171,6 @@ const doPreload = async (preload, from, design, gist, setGist, setPreloaded) =>
* This component wraps the workbench and is in charge of * This component wraps the workbench and is in charge of
* keeping the gist state, which will trickle down * keeping the gist state, which will trickle down
* to all workbench subcomponents * to all workbench subcomponents
*/
export const WorkbenchWrapper = ({ export const WorkbenchWrapper = ({
app, app,
design, design,
@ -290,3 +303,4 @@ export const WorkbenchWrapper = ({
</LayoutComponent> </LayoutComponent>
) )
} }
*/

View file

@ -1,106 +0,0 @@
import { useState } from 'react'
import { ClearIcon, EditIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
const EditCount = (props) => (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">{props.min}</span>
<span className="label-text font-bold text-base-content">{props.value}</span>
<span className="label-text text-base-content">{props.max}</span>
</label>
<label className="input-group input-group-sm">
<input
type="number"
className={`
input input-sm input-bordered grow text-base-content
`}
value={props.value}
onChange={props.handleChange}
/>
<span className="text-base-content font-bold">#</span>
</label>
</div>
)
export const DesignOptionCount = (props) => {
const { t } = useTranslation(['app'])
const { count, max, min } = props.design.patternConfig.options[props.option]
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? count
: props.gist.options[props.option]
const [value, setValue] = useState(val)
const [editCount, setEditCount] = useState(false)
const handleChange = (evt) => {
const newVal = evt.target.value
setValue(newVal)
props.updateGist(['options', props.option], newVal)
}
const reset = () => {
setValue(count)
props.unsetGist(['options', props.option])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<div className="flex flex-row justify-between">
{editCount ? (
<EditCount
value={value}
handleChange={handleChange}
min={min}
max={max}
setEditCount={setEditCount}
t={t}
/>
) : (
<>
<span className="opacity-50">{min}</span>
<span className={`font-bold ${val === count ? 'text-secondary' : 'text-accent'}`}>
{val}
</span>
<span className="opacity-50">{max}</span>
</>
)}
</div>
<input
type="range"
max={max}
min={min}
step={1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === count ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span></span>
<div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === count}
onClick={reset}
>
<ClearIcon />
</button>
<button
title={t('editThing', { thing: '#' })}
className={`
btn btn-ghost btn-xs hover:text-secondary-focus
${editCount ? 'text-accent' : 'text-secondary'}
`}
onClick={() => setEditCount(!editCount)}
>
<EditIcon />
</button>
</div>
</div>
</div>
)
}

View file

@ -1,70 +0,0 @@
import { useState } from 'react'
import { ClearIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
export const DesignOptionList = (props) => {
const { t } = useTranslation([`o_${props.design.designConfig.data.name}`])
const { dflt, list, doNotTranslate = false } = props.design.patternConfig.options[props.option]
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? dflt
: props.gist.options[props.option]
const [value, setValue] = useState(val)
const handleChange = (newVal) => {
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist(['options', props.option], newVal)
}
}
const reset = () => {
setValue(dflt)
props.unsetGist(['options', props.option])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<div className="flex flex-row">
<div className="grow">
{list.map((choice) => (
<button
key={choice}
onClick={() => handleChange(choice)}
className={`
mr-1 mb-1 text-left text-lg w-full
${
choice === value
? choice === dflt
? 'text-secondary'
: 'text-accent'
: 'text-base-content'
}
`}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
translate-y-3
`}
>
<>&deg;</>
</span>
{doNotTranslate
? choice
: props.ot(`o_${props.design.designConfig.data.name}:${props.option}.o.${choice}`)}
</button>
))}
</div>
<button title={t('reset')} className="" disabled={val === dflt} onClick={reset}>
<span className={val === dflt ? 'text-base' : 'text-accent'}>
<ClearIcon />
</span>
</button>
</div>
</div>
)
}
export default DesignOptionList

View file

@ -1,136 +0,0 @@
import { useState } from 'react'
import { ClearIcon, EditIcon } from 'shared/components/icons.mjs'
import { formatMm, round } from 'shared/utils.mjs'
import { useTranslation } from 'next-i18next'
const EditOption = (props) => (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">
{props.min}
{props.suffix}
</span>
<span className="label-text font-bold text-base-content">
{props.value}
{props.suffix}
</span>
<span className="label-text text-base-content">
{props.max}
{props.suffix}
</span>
</label>
<label className="input-group input-group-sm">
<input
type="number"
className={`
input input-sm input-bordered grow text-base-content
`}
value={props.value}
onChange={props.handleChange}
/>
<span className="text-base-content font-bold">{props.suffix}</span>
</label>
</div>
)
export const DesignOptionPctDeg = (props) => {
const { t } = useTranslation(['app'])
const suffix = props.type === 'deg' ? '°' : '%'
const factor = props.type === 'deg' ? 1 : 100
const { max, min } = props.design.patternConfig.options[props.option]
const dflt = props.design.patternConfig.options[props.option][props.type || 'pct']
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? dflt
: props.gist.options[props.option] * factor
const [value, setValue] = useState(val)
const [editOption, setEditOption] = useState(false)
const handleChange = (evt) => {
const newVal = evt.target.value
setValue(newVal)
props.updateGist(['options', props.option], newVal / factor)
}
const reset = () => {
setValue(dflt)
props.unsetGist(['options', props.option])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{props.ot(`${props.option}.d`)}
</p>
<div className="flex flex-row justify-between">
{editOption ? (
<EditOption
value={value}
handleChange={handleChange}
min={min}
max={max}
setEditOption={setEditOption}
t={t}
suffix={suffix}
/>
) : (
<>
<span className="opacity-50">
{round(min)}
{suffix}
</span>
<span className={`font-bold ${val === dflt ? 'text-secondary' : 'text-accent'}`}>
{round(val)}
{suffix}
</span>
<span className="opacity-50">
{round(max)}
{suffix}
</span>
</>
)}
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span className={val === dflt ? 'text-secondary' : 'text-accent'}>
{props.design.patternConfig.options[props.option]?.toAbs && props.gist.measurements
? formatMm(
props.design.patternConfig.options[props.option].toAbs(value / 100, props.gist)
)
: ' '}
</span>
<div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
<button
title={t('editThing', { thing: suffix })}
className={`
btn btn-ghost btn-xs hover:text-secondary-focus
${editOption ? 'text-accent' : 'text-secondary'}
`}
onClick={() => setEditOption(!editOption)}
>
<EditIcon />
</button>
</div>
</div>
</div>
)
}

View file

@ -1,139 +0,0 @@
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { useTranslation } from 'next-i18next'
import { isDegreeMeasurement } from '../../../config/measurements'
import { measurementAsMm } from 'shared/utils.mjs'
/*
* This is a single input for a measurements
* Note that it keeps local state with whatever the user types
* but will only trigger a gist update if the input is valid.
*
* m holds the measurement name. It's just so long to type
* measurement and I always have some typo in it because dyslexia.
*/
export const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => {
const { t } = useTranslation(['app', 'measurements'])
const prefix = app.site === 'org' ? '' : 'https://freesewing.org'
const title = t(`measurements:${m}`)
const isDegree = isDegreeMeasurement(m)
const factor = useMemo(() => (isDegree ? 1 : gist.units == 'imperial' ? 25.4 : 10), [gist.units])
const isValValid = (val) =>
typeof val === 'undefined' || val === '' ? null : val != false && !isNaN(val)
const isValid = (newVal) => (typeof newVal === 'undefined' ? isValValid(val) : isValValid(newVal))
const [val, setVal] = useState(gist.measurements?.[m] / factor || '')
// keep a single reference to a debounce timer
const debounceTimeout = useRef(null)
const input = useRef(null)
// onChange
const update = useCallback(
(evt) => {
evt.stopPropagation()
let evtVal = evt.target.value
// set Val immediately so that the input reflects it
setVal(evtVal)
let useVal = isDegree ? evtVal : measurementAsMm(evtVal, gist.units)
const ok = isValid(useVal)
// only set to the gist if it's valid
if (ok) {
// debounce in case it's still changing
if (debounceTimeout.current !== null) {
clearTimeout(debounceTimeout.current)
}
debounceTimeout.current = setTimeout(() => {
// clear the timeout reference
debounceTimeout.current = null
updateMeasurements(useVal, m)
}, 500)
}
},
[gist.units]
)
// use this for better update efficiency
const memoVal = useMemo(() => gist.measurements?.[m], [gist])
// track validity against the value and the units
const valid = useMemo(
() => isValid(isDegree ? val : measurementAsMm(val, gist.units)),
[val, gist.units]
)
// hook to update the value or format when the gist changes
useEffect(() => {
// set the value to the proper value and format
if (memoVal) {
let gistVal = +(memoVal / factor).toFixed(2)
setVal(gistVal)
}
}, [memoVal, factor])
// focus when prompted by parent
useEffect(() => {
if (focus) {
input.current.focus()
}
}, [focus])
// cleanup
useEffect(() => {
clearTimeout(debounceTimeout.current)
}, [])
if (!m) return null
return (
<div className="form-control mb-2" key={`wrap-${m}`}>
<label className="label">
<span className="label-text font-bold text-xl">{title}</span>
<a
href={`${prefix}/docs/measurements/${m.toLowerCase()}`}
className="label-text-alt text-secondary hover:text-secondary-focus hover:underline"
title={`${t('docs')}: ${t(m)}`}
tabIndex="-1"
>
{t('docs')}
</a>
</label>
<label className="input-group input-group-lg">
<input
key={`input-${m}`}
ref={input}
type="text"
placeholder={title}
className={`
input input-lg input-bordered grow text-base-content border-r-0
${valid === false && 'input-error'}
${valid === true && 'input-success'}
`}
value={val}
onChange={update}
/>
<span
role="img"
className={`bg-transparent border-y
${valid === false && 'border-error text-neutral-content'}
${valid === true && 'border-success text-neutral-content'}
${valid === null && 'border-base-200 text-base-content'}
`}
>
{valid === true && '👍'}
{valid === false && '🤔'}
</span>
<span
className={`
${valid === false && 'bg-error text-neutral-content'}
${valid === true && 'bg-success text-neutral-content'}
${valid === null && 'bg-base-200 text-base-content'}
`}
>
{isDegree ? '° ' : gist.units == 'metric' ? 'cm' : 'in'}
</span>
</label>
</div>
)
}

View file

@ -1,7 +1,7 @@
import { useRef } from 'react' import { useRef } from 'react'
import { Stack } from './stack.mjs' import { Stack } from './stack.mjs'
import { SvgWrapper } from '../../draft/svg.mjs' import { SvgWrapper } from '../../pattern/svg.mjs'
import { PartInner } from '../../draft/part.mjs' import { PartInner } from '../../pattern/part.mjs'
import get from 'lodash.get' import get from 'lodash.get'
export const Draft = (props) => { export const Draft = (props) => {

View file

@ -45,8 +45,8 @@
*/ */
import { useRef, useState, useEffect } from 'react' import { useRef, useState, useEffect } from 'react'
import { generateStackTransform } from '@freesewing/core' import { generateStackTransform } from '@freesewing/core'
import { Part } from '../../draft/part.mjs' import { Part } from '../../pattern/part.mjs'
import { getProps, angle } from '../../draft/utils.mjs' import { getProps, angle } from '../../pattern/utils.mjs'
import { drag } from 'd3-drag' import { drag } from 'd3-drag'
import { select } from 'd3-selection' import { select } from 'd3-selection'
import { Buttons } from './buttons.mjs' import { Buttons } from './buttons.mjs'

View file

@ -1,113 +0,0 @@
import React, { useMemo, useEffect, useState } from 'react'
import { MeasurementInput } from '../inputs/measurement.mjs'
import { adult, doll, giant } from '@freesewing/models'
import {
CisFemaleIcon as WomenswearIcon,
CisMaleIcon as MenswearIcon,
} from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
import { Setting } from '../menu/core-settings/setting.mjs'
import { settings } from '../menu/core-settings/index.mjs'
import { Tab, Tabs } from 'shared/components/mdx/tabs.mjs'
const groups = { adult, doll, giant }
const icons = {
cisFemale: <WomenswearIcon />,
cisMale: <MenswearIcon />,
}
export const WorkbenchMeasurements = ({ app, design, gist, updateGist, gistReady }) => {
const { t } = useTranslation(['app', 'cfp'])
// Method to handle measurement updates
const updateMeasurements = (value, m = false) => {
if (m === false) {
// Set all measurements
updateGist('measurements', value)
} else {
// Set one measurement
updateGist(['measurements', m], value)
}
}
const [firstInvalid, setFirstInvalid] = useState(undefined)
useEffect(() => {
if (!gistReady) {
return
}
for (const m of design.patternConfig?.measurements || []) {
if (!gist?.measurements?.[m]) {
setFirstInvalid(m)
return
}
setFirstInvalid(undefined)
}
}, [gistReady])
// Save us some typing
const inputProps = useMemo(() => ({ app, updateMeasurements, gist }), [app, gist])
const shortname = design.designConfig.data.name.replace('@freesewing/', '')
return (
<div className="m-auto max-w-2xl">
<h1>
<span className="capitalize mr-4 opacity-70">{shortname}:</span> {t('measurements')}
</h1>
<h2>{t('cfp:preloadMeasurements')}</h2>
<Tabs tabs="Adults, Dolls, Giants">
{Object.keys(groups).map((group) => (
<Tab tabId={group} key={group}>
{Object.keys(icons).map((type) => (
<React.Fragment key={type}>
<h4 className="mt-4">{t(type)}</h4>
<ul className="flex flex-row flex-wrap gap-2">
{Object.keys(groups[group][type]).map((m) => (
<li key={`${m}-${type}-${group}`} className="">
<button
className="flex flex-row btn btn-outline"
onClick={() => updateMeasurements(groups[group][type][m], false)}
>
{icons[type]}
{group === 'adult' ? `${t('size')} ${m}` : `${m}%`}
</button>
</li>
))}
</ul>
</React.Fragment>
))}
</Tab>
))}
</Tabs>
<h2 className="mt-8">{t('cfp:enterMeasurements')}</h2>
<div className="my-2 border p-4 rounded-lg shadow bg-base-200">
<Setting
key={'units'}
setting={'units'}
config={settings.units}
updateGist={updateGist}
{...inputProps}
/>
</div>
{design.patternConfig.measurements.length > 0 && (
<>
<h3>{t('requiredMeasurements')}</h3>
{design.patternConfig.measurements.map((m) => (
<MeasurementInput key={m} m={m} focus={m == firstInvalid} {...inputProps} />
))}
</>
)}
{design.patternConfig.optionalMeasurements.length > 0 && (
<>
<h3>{t('optionalMeasurements')}</h3>
{design.patternConfig.optionalMeasurements.map((m) => (
<MeasurementInput key={m} m={m} {...inputProps} />
))}
</>
)}
</div>
)
}

View file

@ -1,25 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
export const CoreSettingBool = (props) => {
const { t } = useTranslation(['app'])
const [value, setValue] = useState(props.gist[props.setting])
const toggle = () => {
props.updateGist([props.setting], !value)
setValue(!value)
}
return (
<Li>
<SumButton onClick={toggle}>
<SumDiv>
<Deg />
<span>{t(`settings:${props.setting}.t`)}</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}

View file

@ -1,49 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { Deg } from 'shared/components/workbench/menu/index.mjs'
export const CoreSettingList = (props) => {
const { t } = useTranslation(['settings'])
const { dflt } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (newVal) => {
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row">
<div className="grow">
{props.list.map((entry) => (
<button
key={entry.key}
onClick={() => handleChange(entry.key)}
className={`
mr-1 mb-1 text-left text-lg w-full hover:text-secondary-focus px-2
${entry.key === value && 'font-bold text-secondary'}
`}
>
<Deg />
{entry.title}
</button>
))}
</div>
</div>
</div>
)
}

View file

@ -1,71 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { formatMm } from 'shared/utils.mjs'
import { ClearIcon } from 'shared/components/icons.mjs'
export const CoreSettingMm = (props) => {
const { t } = useTranslation(['app', 'settings'])
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row justify-between">
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(min, props.gist.units) }}
/>
<span
className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}
dangerouslySetInnerHTML={{ __html: formatMm(val, props.gist.units) }}
/>
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(max, props.gist.units) }}
/>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -1,63 +0,0 @@
import { useState } from 'react'
import { ClearIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
export const CoreSettingNr = (props) => {
const { t } = useTranslation(['app', 'settings'])
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row justify-between">
<span className="opacity-50">{min}</span>
<span className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}>
{val}
</span>
<span className="opacity-50">{max}</span>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -1,68 +0,0 @@
import { ClearIcon } from 'shared/components/icons.mjs'
import orderBy from 'lodash.orderby'
import { useTranslation } from 'next-i18next'
export const CoreSettingOnly = (props) => {
const { t } = useTranslation(['app', 'parts', 'settings'])
const list = props.design.patternConfig.draftOrder
const partNames = list.map((part) => ({ id: part, name: t(`parts:${part}`) }))
const togglePart = (part) => {
const parts = props.gist.only || []
const newParts = new Set(parts)
if (newParts.has(part)) newParts.delete(part)
else newParts.add(part)
if (newParts.size < 1) reset()
else props.updateGist(['only'], [...newParts])
}
const reset = () => {
props.unsetGist(['only'])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:only.d`)}
</p>
<div className="flex flex-row">
<div className="grow">
{orderBy(partNames, ['name'], ['asc']).map((part) => (
<button
key={part.id}
onClick={() => togglePart(part.id)}
className={`
mr-1 mb-1 text-left text-lg w-full hover:text-secondary-focus px-2
${
props.gist?.only &&
props.gist.only.indexOf(part.id) !== -1 &&
'font-bold text-secondary-focus'
}
`}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
translate-y-3
`}
>
<>&deg;</>
</span>
{part.name}
</button>
))}
</div>
</div>
<div className="flex flex-row-reverse">
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={!props.gist.only || props.gist.only.length < 1}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -1,29 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
export const CoreSettingSaBool = (props) => {
const { t } = useTranslation(['app', 'settings'])
const [value, setValue] = useState(props.gist.saBool || false)
const toggle = () => {
props.setGist({
...props.gist,
saBool: !value,
sa: value ? 0 : props.gist.saMm,
})
setValue(!value)
}
return (
<Li>
<SumButton onClick={toggle}>
<SumDiv>
<Deg />
<span>{t('settings:sabool.t')}</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}

View file

@ -1,31 +0,0 @@
import { OptionsIcon } from 'shared/components/icons.mjs'
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { OptionGroup } from './option-group.mjs'
import { OptionComponent } from './option.mjs'
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
import { optionsMenuStructure } from 'shared/utils.mjs'
export const DesignOptions = (props) => {
const { t } = useTranslation(['app'])
const Option = props.Option ? props.Option : OptionComponent
const optionsMenu = optionsMenuStructure(props.design.patternConfig.options)
return (
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('designOptions')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.entries(optionsMenu).map(([group, options]) =>
typeof options === 'string' ? (
<Option {...props} type={options} option={group} key={group} />
) : (
<OptionGroup {...props} group={group} options={options} key={group} Option={Option} />
)
)}
</Ul>
</Details>
)
}

View file

@ -1,31 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
export const OptionGroup = (props) => {
const { t } = useTranslation(['optiongroups'])
const Option = props.Option
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{t(props.group)}</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{Object.entries(props.options).map(([option, type]) =>
typeof type === 'string' ? (
<Option {...props} type={type} option={option} key={option} />
) : (
<OptionGroup {...props} group={option} options={type} key={option} Option={Option} />
)
)}
</Ul>
</Details>
</Li>
)
}

View file

@ -1,19 +0,0 @@
import { DesignOptionPctDeg } from 'shared/components/workbench/inputs/design-option-pct-deg.mjs'
import { DesignOptionCount } from 'shared/components/workbench/inputs/design-option-count.mjs'
import { DesignOptionList } from 'shared/components/workbench/inputs/design-option-list.mjs'
import { Popout } from 'shared/components/popout.mjs'
export const Tmp = () => <p>not yet</p>
export const inputs = {
Pct: DesignOptionPctDeg,
Count: DesignOptionCount,
Deg: (props) => <DesignOptionPctDeg {...props} type="deg" />,
List: DesignOptionList,
Mm: () => (
<Popout fixme compact>
Mm options are deprecated. Please report this
</Popout>
),
Constant: Tmp,
}

View file

@ -1,78 +0,0 @@
import { useTranslation } from 'next-i18next'
import { formatMm, formatPercentage } from 'shared/utils.mjs'
export const values = {
Pct: (props) => {
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? props.design.patternConfig.options[props.option].pct / 100
: props.gist.options[props.option]
return (
<span
className={
val === props.design.patternConfig.options[props.option].pct / 100
? 'text-secondary-focus'
: 'text-accent'
}
>
{formatPercentage(val)}
{props.design.patternConfig.options[props.option]?.toAbs && props.gist.measurements
? ' | ' +
formatMm(props.design.patternConfig.options[props.option]?.toAbs(val, props.gist))
: null}
</span>
)
},
Bool: (props) => {
const { t } = useTranslation(['app'])
const dflt = props.design.patternConfig.options[props.option].bool
let current = props.gist?.options?.[props.option]
current = current === undefined ? dflt : current
return (
<span
className={
dflt == current || typeof current === 'undefined' ? 'text-secondary-focus' : 'text-accent'
}
>
{current ? t('yes') : t('no')}
</span>
)
},
Count: (props) => {
const dflt = props.design.patternConfig.options[props.option].count
const current = props.gist?.options?.[props.option]
return dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{dflt}</span>
) : (
<span className="text-accent">{current}</span>
)
},
List: (props) => {
const dflt = props.design.patternConfig.options[props.option].dflt
const current = props.gist?.options?.[props.option]
const prefix = `${props.option}.o.`
const translate = props.design.patternConfig.options[props.option]?.doNotTranslate
? (input) => input
: (input) => props.t(prefix + input)
return dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{translate(dflt)}</span>
) : (
<span className="text-accent">{translate(current)}</span>
)
},
Deg: (props) => {
const dflt = props.design.patternConfig.options[props.option].deg
const current = props.gist?.options?.[props.option]
return dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{dflt}&deg;</span>
) : (
<span className="text-accent">{current}&deg;</span>
)
},
Mm: () => {
return <p>No mm val yet</p>
},
Constant: () => {
return <p>No constant val yet</p>
},
}

View file

@ -1,67 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { optionType } from 'shared/utils.mjs'
import {
Li,
Details,
Summary,
SumButton,
SumDiv,
Deg,
} from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
import { values } from 'shared/components/workbench/menu/design-options/option-value.mjs'
import { inputs } from 'shared/components/workbench/menu/design-options/option-input.mjs'
import { capitalize } from 'shared/utils.mjs'
export const OptionComponent = (props) => {
const { t } = useTranslation([`o_${props.design.designConfig.data.name}`])
const opt = props.design.patternConfig.options[props.option]
const type = optionType(opt)
const Input = inputs[capitalize(type)]
const Value = values[capitalize(type)]
try {
const hide = opt.hide && opt.hide(props.gist)
if (hide) return null
} catch (e) {
console.warn(`error occurred in hide method for ${props.option}, so we'll just show it`, e)
}
if (type === 'bool') {
const toggleBoolean = () => {
const dflt = opt.bool
const current = props.gist?.options?.[props.option]
const newVal = typeof current === 'undefined' ? !dflt : !current
props.updateGist(['options', props.option], newVal)
}
return (
<Li>
<SumButton onClick={toggleBoolean}>
<SumDiv>
<Deg />
<span>{t(`${props.option}.t`)}</span>
</SumDiv>
<Value type={type} {...props} t={t} />
</SumButton>
</Li>
)
}
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>{t(`${props.option}.t`)}</span>
</SumDiv>
<Value type={type} {...props} t={t} />
<Chevron w={6} m={3} />
</Summary>
<Input {...props} ot={t} />
</Details>
</Li>
)
}

View file

@ -1,128 +0,0 @@
import { MenuIcon } from 'shared/components/icons.mjs'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.mjs'
import { useTranslation } from 'next-i18next'
import { defaultGist } from 'shared/hooks/useGist.mjs'
export const ViewMenu = (props) => {
const { t } = useTranslation(['app'])
const entries = [
{
name: 'measurements',
title: t('measurements'),
onClick: () => props.updateGist(['_state', 'view'], 'measurements', true),
},
{
name: 'draft',
title: t('draftDesign', { design: props.design.designConfig.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'draft', true),
},
{
name: 'test',
title: t('testDesign', { design: props.design.designConfig.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'test', true),
},
{
name: 'printingLayout',
title:
t('layoutThing', { thing: props.design.designConfig.data.name }) + ': ' + t('forPrinting'),
onClick: () => props.updateGist(['_state', 'view'], 'printingLayout', true),
},
{
name: 'cuttingLayout',
title:
t('layoutThing', { thing: props.design.designConfig.data.name }) + ': ' + t('forCutting'),
onClick: () => props.updateGist(['_state', 'view'], 'cuttingLayout', true),
},
{
name: 'export',
title: t('exportThing', { thing: props.design.designConfig.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'export', true),
},
{
name: 'logs',
title: t('logs'),
onClick: () => props.updateGist(['_state', 'view'], 'logs', true),
},
{
name: 'yaml',
title: t('YAML'),
onClick: () => props.updateGist(['_state', 'view'], 'yaml', true),
},
{
name: 'json',
title: t('JSON'),
onClick: () => props.updateGist(['_state', 'view'], 'json', true),
},
{
name: 'edit',
title: t('editThing', { thing: 'YAML' }),
onClick: () => props.updateGist(['_state', 'view'], 'edit', true),
},
{
name: 'clear',
title: t('clearThing', { thing: 'YAML' }),
onClick: () => props.setGist(defaultGist(props.design, props.gist.locale)),
},
]
return (
<details className="py-1" open>
<summary
className={`
flex flex-row uppercase gap-4 font-bold text-lg
hover:cursor-row-resize
p-2
text-base-content
sm:text-base-content
items-center
`}
>
<span className="text-secondary-focus mr-4">
<MenuIcon />
</span>
<span className={`grow ${linkClasses} hover:cursor-resize`}>{t('view')}</span>
<Chevron />
</summary>
<ul className="pl-5 list-inside">
{entries.map((entry) => (
<li key={entry.title} className="flex flex-row">
<button
title={entry.title}
className={`
grow pl-2 border-l-2
${linkClasses}
hover:cursor-pointer
hover:border-secondary
sm:hover:border-secondary-focus
text-left
capitalize
${
entry.name === props.gist?._state?.view
? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus'
: 'text-base-content sm:text-base-content'
}
`}
onClick={entry.onClick}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
${
entry.name === props.gist?._state?.view
? 'text-secondary sm:text-secondary-focus translate-y-1 font-bold'
: 'translate-y-3'
}
`}
>
{entry.name === props.gist?._state?.view ? <>&bull;</> : <>&deg;</>}
</span>
<span className={entry.name === props.gist?._state?.view ? 'font-bold' : ''}>
{entry.title}
</span>
</button>
</li>
))}
</ul>
</details>
)
}

View file

@ -48,7 +48,8 @@ export const settings = {
}, },
} }
export const CoreSettings = (props) => { export const CoreSettings = ({ design, update, settings }) => {
// FIXME: Update this namespace
const { t } = useTranslation(['app']) const { t } = useTranslation(['app'])
return ( return (
@ -58,8 +59,8 @@ export const CoreSettings = (props) => {
<Chevron /> <Chevron />
</TopSummary> </TopSummary>
<Ul> <Ul>
{Object.keys(settings).map((setting) => ( {Object.keys(settings).map((name) => (
<Setting key={setting} setting={setting} config={settings[setting]} {...props} /> <Setting key={name} {...{ name, design, t }} config={settings[name]} />
))} ))}
</Ul> </Ul>
</Details> </Details>

View file

@ -1,12 +1,13 @@
import { Chevron } from 'shared/components/navigation/primary.mjs' import { Chevron } from 'shared/components/navigation/primary.mjs'
import { CoreSettingList as ListSetting } from './core-setting-list.mjs' import {
import { CoreSettingOnly as OnlySetting } from './core-setting-only.mjs' ListSetting,
import { CoreSettingMm as MmSetting } from './core-setting-mm.mjs' OnlySetting,
import { CoreSettingNr as NrSetting } from './core-setting-nr.mjs' MmSetting,
import { CoreSettingBool as BoolSetting } from './core-setting-bool.mjs' NrSetting,
import { CoreSettingSaBool as SaBoolSetting } from './core-setting-sa-bool.mjs' BoolSetting,
import { CoreSettingSaMm as SaMmSetting } from './core-setting-sa-mm.mjs' SaBoolSetting,
import { formatMm } from 'shared/utils.mjs' SaMmSetting,
} from './settings.mjs'
import { import {
SecText, SecText,
Li, Li,
@ -14,7 +15,7 @@ import {
Summary, Summary,
SumDiv, SumDiv,
Deg, Deg,
} from 'shared/components/workbench/menu/index.mjs' } from 'shared/components/workbench/menus/index.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
const settings = { const settings = {

View file

@ -0,0 +1,354 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menus/index.mjs'
import { formatMm } from 'shared/utils.mjs'
import { ClearIcon } from 'shared/components/icons.mjs'
import orderBy from 'lodash.orderby'
export const BoolSetting = ({ gist, updateGist, setting }) => {
const { t } = useTranslation(['app'])
const [value, setValue] = useState(gist[setting])
const toggle = () => {
updateGist(['settings', setting], !value)
setValue(!value)
}
return (
<Li>
<SumButton onClick={toggle}>
<SumDiv>
<Deg />
<span>{t(`settings:${setting}.t`)}</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}
export const ListSetting = ({ gist, updateGist, dflt, setting, list }) => {
const { t } = useTranslation(['settings'])
const val = gist.settings?.[setting]
const [value, setValue] = useState(val)
const handleChange = (newVal) => {
if (newVal === dflt) reset()
else {
setValue(newVal)
updateGist(['settings', setting], newVal)
}
}
const reset = () => {
setValue(dflt)
updateGist(['settings', setting], dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${setting}.d`)}
</p>
<div className="flex flex-row">
<div className="grow">
{list.map((entry) => (
<button
key={entry.key}
onClick={() => handleChange(entry.key)}
className={`
mr-1 mb-1 text-left text-lg w-full hover:text-secondary-focus px-2
${entry.key === value && 'font-bold text-secondary'}
`}
>
<Deg />
{entry.title}
</button>
))}
</div>
</div>
</div>
)
}
export const MmSetting = (props) => {
const { t } = useTranslation(['app', 'settings'])
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row justify-between">
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(min, props.gist.units) }}
/>
<span
className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}
dangerouslySetInnerHTML={{ __html: formatMm(val, props.gist.units) }}
/>
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(max, props.gist.units) }}
/>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}
export const NrSetting = ({ gist, updateGist, dflt, setting, min = 0, max = 1 }) => {
const { t } = useTranslation(['app', 'settings'])
const val = gist.settings?.[setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
updateGist(['settings', setting], newVal)
}
}
const reset = () => {
setValue(dflt)
updateGist(['settings', setting], dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${setting}.d`)}
</p>
<div className="flex flex-row justify-between">
<span className="opacity-50">{min}</span>
<span className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}>
{val}
</span>
<span className="opacity-50">{max}</span>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}
export const OnlySetting = (props) => {
const { t } = useTranslation(['app', 'parts', 'settings'])
const list = props.patternConfig.draftOrder
const partNames = list.map((part) => ({ id: part, name: t(`parts:${part}`) }))
const togglePart = (part) => {
const parts = props.gist.only || []
const newParts = new Set(parts)
if (newParts.has(part)) newParts.delete(part)
else newParts.add(part)
if (newParts.size < 1) reset()
else props.updateGist(['only'], [...newParts])
}
const reset = () => {
props.unsetGist(['only'])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:only.d`)}
</p>
<div className="flex flex-row">
<div className="grow">
{orderBy(partNames, ['name'], ['asc']).map((part) => (
<button
key={part.id}
onClick={() => togglePart(part.id)}
className={`
mr-1 mb-1 text-left text-lg w-full hover:text-secondary-focus px-2
${
props.gist?.only &&
props.gist.only.indexOf(part.id) !== -1 &&
'font-bold text-secondary-focus'
}
`}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
translate-y-3
`}
>
<>&deg;</>
</span>
{part.name}
</button>
))}
</div>
</div>
<div className="flex flex-row-reverse">
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={!props.gist.only || props.gist.only.length < 1}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}
export const SaBoolSetting = ({ gist, updateGist }) => {
const { t } = useTranslation(['app', 'settings'])
const [value, setValue] = useState(gist.saBool || false)
const toggle = () => {
setValue(!value)
updateGist([['saBool', !value][(['settings', 'sa'], value ? 0 : gist.saMm)]])
}
return (
<Li>
<SumButton onClick={toggle}>
<SumDiv>
<Deg />
<span>{t('settings:sabool.t')}</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}
export const SaMmSetting = (props) => {
const { t } = useTranslation(['app', 'settings'])
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
setValue(newVal)
if (props.gist.saBool)
props.setGist({
...props.gist,
saMm: newVal,
sa: newVal,
})
else props.updateGist(['saMm'], newVal)
}
const reset = () => {
setValue(dflt)
props.updateGist(['saMm'], dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">{t(`settings:sa.d`)}</p>
<div className="flex flex-row justify-between">
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(min, props.gist.units) }}
/>
<span
className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}
dangerouslySetInnerHTML={{ __html: formatMm(val, props.gist.units) }}
/>
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(max, props.gist.units) }}
/>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -0,0 +1,180 @@
import { OptionsIcon } from 'shared/components/icons.mjs'
import { Chevron } from 'shared/components/navigation/primary.mjs'
import {
Li,
Ul,
Details,
Summary,
SumDiv,
SumButton,
Deg,
TopSummary,
TopSumTitle,
} from 'shared/components/workbench/menus/index.mjs'
import { useTranslation } from 'next-i18next'
import { optionsMenuStructure } from 'shared/utils.mjs'
import { optionType } from 'shared/utils.mjs'
import {
ConstantOptionInput,
CountOptionInput,
DegOptionInput,
ListOptionInput,
MmOptionInput,
PctOptionInput,
} from './inputs.mjs'
import {
BoolOptionValue,
ConstantOptionValue,
CountOptionValue,
DegOptionValue,
ListOptionValue,
MmOptionValue,
PctOptionValue,
} from './values.mjs'
// Facilitate lookup of the input component
const inputs = {
constant: ConstantOptionInput,
count: CountOptionInput,
deg: DegOptionInput,
list: ListOptionInput,
mm: MmOptionInput,
pct: PctOptionInput,
}
// Facilitate lookup of the value component
const values = {
bool: BoolOptionValue,
constant: ConstantOptionValue,
count: CountOptionValue,
deg: DegOptionValue,
list: ListOptionValue,
mm: MmOptionValue,
pct: PctOptionValue,
}
export const DesignOption = ({ design, name, current, config, settings, update, t }) => {
const type = optionType(config)
const Input = inputs[type]
const Value = values[type]
// Hide option?
if (config?.hide || (typeof config?.hide === 'function' && config.hide(settings))) return null
if (type === 'bool') {
const toggleBoolean = () => {
const dflt = config.bool
const current = settings.options?.[name]
const newVal = typeof current === 'undefined' ? !dflt : !current
update.settings(['options', name], newVal)
}
return (
<Li>
<SumButton onClick={toggleBoolean}>
<SumDiv>
<Deg />
<span>{t(`${name}.t`)}</span>
</SumDiv>
<Value {...{ t, name, config, current, design, settings }} />
</SumButton>
</Li>
)
}
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>{t(`${name}.t`)}</span>
</SumDiv>
<Value {...{ t, name, config, current, design, settings }} />
<Chevron w={6} m={3} />
</Summary>
<Input {...{ t, name, config, settings, current, design, update }} ot={t} />
</Details>
</Li>
)
}
export const DesignOptionGroup = ({
design,
patternConfig,
settings,
update,
group,
options,
Option,
t,
}) => (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{t(group)}</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{Object.entries(options).map(([option, type]) =>
typeof type === 'string' ? (
<Option
{...{ t, design, update, settings }}
key={option}
name={option}
settings={settings}
current={settings.options?.[option]}
config={patternConfig.options[option]}
/>
) : (
<OptionGroup
{...{ design, patternConfig, settings, update, Option, t }}
group={option}
options={type}
key={option}
/>
)
)}
</Ul>
</Details>
</Li>
)
export const DesignOptions = ({ design, patternConfig, settings, update, Option = false }) => {
const { t } = useTranslation(['optiongroups', design])
// FIXME: Do we still care about passing in an Option component?
if (!Option) Option = DesignOption
const optionsMenu = optionsMenuStructure(patternConfig.options)
return (
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('designOptions')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.entries(optionsMenu).map(([group, option]) =>
typeof option === 'string' ? (
<Option
{...{ t, design, update, settings }}
key={group}
name={group}
current={settings.options?.[group]}
config={patternConfig.options[group]}
/>
) : (
<DesignOptionGroup
{...{ design, patternConfig, settings, update, group, Option, t }}
options={option}
key={group}
/>
)
)}
</Ul>
</Details>
)
}

View file

@ -0,0 +1,269 @@
import { useState } from 'react'
import { ClearIcon, EditIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
import { formatMm, round } from 'shared/utils.mjs'
const EditCount = (props) => (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">{props.min}</span>
<span className="label-text font-bold text-base-content">{props.value}</span>
<span className="label-text text-base-content">{props.max}</span>
</label>
<label className="input-group input-group-sm">
<input
type="number"
className={`
input input-sm input-bordered grow text-base-content
`}
value={props.value}
onChange={props.handleChange}
/>
<span className="text-base-content font-bold">#</span>
</label>
</div>
)
export const CountOptionInput = ({ name, config, current, update, t }) => {
const { count, max, min } = config
if (typeof current === 'undefined') current = count
const [value, setValue] = useState(current)
const [editCount, setEditCount] = useState(false)
const handleChange = (evt) => {
const newCurrent = evt.target.value
setValue(newCurrent)
update.settings(['options', name], newCurrent)
}
const reset = () => {
setValue(count)
update.settings(['options', name])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<div className="flex flex-row justify-between">
{editCount ? (
<EditCount {...{ value, handleChange, min, max, setEditCount, t }} />
) : (
<>
<span className="opacity-50">{min}</span>
<span className={`font-bold ${current === count ? 'text-secondary' : 'text-accent'}`}>
{current}
</span>
<span className="opacity-50">{max}</span>
</>
)}
</div>
<input
type="range"
{...{ min, max, value }}
step={1}
onChange={handleChange}
className={`
range range-sm mt-1
${val === count ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span></span>
<div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={current === count}
onClick={reset}
>
<ClearIcon />
</button>
<button
title={t('editThing', { thing: '#' })}
className={`
btn btn-ghost btn-xs hover:text-secondary-focus
${editCount ? 'text-accent' : 'text-secondary'}
`}
onClick={() => setEditCount(!editCount)}
>
<EditIcon />
</button>
</div>
</div>
</div>
)
}
export const ListOptionInput = ({ design, name, config, current, update, t }) => {
const { dflt, list, doNotTranslate = false } = config
if (typeof current === 'undefined') current = dflt
const [value, setValue] = useState(current)
const handleChange = (newCurrent) => {
if (newCurrent === dflt) reset()
else {
setValue(newCurrent)
update.settings(['options', name], newCurrent)
}
}
const reset = () => {
setValue(dflt)
update.settings(['options', name])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<div className="flex flex-row">
<div className="grow">
{list.map((choice) => (
<button
key={choice}
onClick={() => handleChange(choice)}
className={`
mr-1 mb-1 text-left text-lg w-full
${
choice === value
? choice === dflt
? 'text-secondary'
: 'text-accent'
: 'text-base-content'
}
`}
>
<span className="text-3xl mr-2 inline-block p-0 leading-3 translate-y-3">
<>&deg;</>
</span>
{doNotTranslate ? choice : t(`o_${design}:${name}.o.${choice}`)}
</button>
))}
</div>
<button title={t('reset')} className="" disabled={current === dflt} onClick={reset}>
<span className={current === dflt ? 'text-base' : 'text-accent'}>
<ClearIcon />
</span>
</button>
</div>
</div>
)
}
const EditOption = (props) => (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">
{props.min}
{props.suffix}
</span>
<span className="label-text font-bold text-base-content">
{props.value}
{props.suffix}
</span>
<span className="label-text text-base-content">
{props.max}
{props.suffix}
</span>
</label>
<label className="input-group input-group-sm">
<input
type="number"
className={`
input input-sm input-bordered grow text-base-content
`}
value={props.value}
onChange={props.handleChange}
/>
<span className="text-base-content font-bold">{props.suffix}</span>
</label>
</div>
)
export const PctOptionInput = ({ name, config, settings, current, update, t, type = 'pct' }) => {
const suffix = type === 'deg' ? '°' : '%'
const factor = type === 'deg' ? 1 : 100
const { max, min } = config
const dflt = config[type]
if (typeof current === 'undefined') current = dflt
else current = current * factor
const [value, setValue] = useState(current)
const [editOption, setEditOption] = useState(false)
const handleChange = (evt) => {
const newCurrent = evt.target.value
setValue(newCurrent)
update.settings(['options', name], newCurrent / factor)
}
const reset = () => {
setValue(dflt)
update.settings(['options', name])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">{t(`${name}.d`)}</p>
<div className="flex flex-row justify-between">
{editOption ? (
<EditOption {...{ value, handleChange, min, max, setEditOption, t, suffix }} />
) : (
<>
<span className="opacity-50">
{round(min)}
{suffix}
</span>
<span className={`font-bold ${current === dflt ? 'text-secondary' : 'text-accent'}`}>
{round(current)}
{suffix}
</span>
<span className="opacity-50">
{round(max)}
{suffix}
</span>
</>
)}
</div>
<input
{...{ min, max, value }}
type="range"
step={0.1}
onChange={handleChange}
className={`
range range-sm mt-1
${current === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span className={current === dflt ? 'text-secondary' : 'text-accent'}>
{config.toAbs && settings.measurements
? formatMm(config.toAbs(value / 100, settings))
: ' '}
</span>
<div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={current === dflt}
onClick={reset}
>
<ClearIcon />
</button>
<button
title={t('editThing', { thing: suffix })}
className={`
btn btn-ghost btn-xs hover:text-secondary-focus
${editOption ? 'text-accent' : 'text-secondary'}
`}
onClick={() => setEditOption(!editOption)}
>
<EditIcon />
</button>
</div>
</div>
</div>
)
}
export const DegOptionInput = (props) => <DesignOptionPctInput {...props} type="deg" />
export const MmOptionInput = () => (
<span>FIXME: Mm options are deprecated. Please report this </span>
)
export const ConstantOptionInput = () => <p>FIXME: Constant options are not implemented (yet)</p>

View file

@ -0,0 +1,59 @@
import { formatMm, formatPercentage } from 'shared/utils.mjs'
export const PctOptionValue = ({ name, config, current, settings }) => {
const val = typeof current === 'undefined' ? config.pct / 100 : current
return (
<span className={val === config.pct / 100 ? 'text-secondary-focus' : 'text-accent'}>
{formatPercentage(val)}
{config?.toAbs && settings.measurements
? ` | ${formatMm(config.toAbs(val, settings))}`
: null}
</span>
)
}
export const BoolOptionValue = ({ name, config, current, t }) => {
const dflt = config.bool
current = current === undefined ? dflt : current
return (
<span
className={
dflt == current || typeof current === 'undefined' ? 'text-secondary-focus' : 'text-accent'
}
>
{current ? t('yes') : t('no')}
</span>
)
}
export const CountOptionValue = ({ name, config, current }) =>
config.count == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{config.count}</span>
) : (
<span className="text-accent">{current}</span>
)
export const ListOptionValue = ({ name, config, current, t }) => {
const translate = config.doNotTranslate ? (input) => input : (input) => t(`${option}.o.${input}`)
return config.dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{translate(config.dflt)}</span>
) : (
<span className="text-accent">{translate(current)}</span>
)
}
export const DegOptionValue = ({ name, config, current }) =>
config.deg == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{config.deg}&deg;</span>
) : (
<span className="text-accent">{current}&deg;</span>
)
export const MmOptionValue = () => (
<span className="text-error">FIXME: No MmOptionvalue implemented</span>
)
export const ConstantOptionValue = () => (
<span className="text-error">FIXME: No ConstantOptionvalue implemented</span>
)

View file

@ -1,5 +1,4 @@
import { linkClasses } from 'shared/components/navigation/primary.mjs' import { linkClasses } from 'shared/components/navigation/primary.mjs'
import { ViewMenu } from './view.mjs'
import { DesignOptions } from './design-options/index.mjs' import { DesignOptions } from './design-options/index.mjs'
import { CoreSettings } from './core-settings/index.mjs' import { CoreSettings } from './core-settings/index.mjs'
import { XrayMenu } from './xray/index.mjs' import { XrayMenu } from './xray/index.mjs'
@ -7,7 +6,9 @@ import { TestDesignOptions } from './test-design-options/index.mjs'
export const Ul = (props) => <ul className="pl-5 list-inside">{props.children}</ul> export const Ul = (props) => <ul className="pl-5 list-inside">{props.children}</ul>
export const Li = (props) => ( export const Li = (props) => (
<li className="flex flex-row hover:border-r-2 hover:border-r-secondary">{props.children}</li> <li className="flex flex-row border-r-2 border-r-transparent hover:border-r-secondary">
{props.children}
</li>
) )
export const Details = (props) => ( export const Details = (props) => (
<details className="grow" open={props.open || false}> <details className="grow" open={props.open || false}>
@ -22,7 +23,7 @@ export const NoSumDiv = (props) => (
className={` className={`
grow px-2 ml-2 border-l-2 grow px-2 ml-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-resize hover:cursor-pointer
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-base-content sm:text-base-content text-base-content sm:text-base-content
@ -36,7 +37,7 @@ export const SumDiv = (props) => (
className={` className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-resize hover:cursor-pointer
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-base-content sm:text-base-content text-base-content sm:text-base-content
@ -52,7 +53,7 @@ export const Summary = (props) => (
px-2 px-2
text-base-content text-base-content
sm:text-base-content sm:text-base-content
hover:cursor-row-resize hover:cursor-pointer
items-center items-center
`} `}
> >
@ -63,7 +64,7 @@ export const TopSummary = (props) => (
<summary <summary
className={` className={`
flex flex-row gap-4 text-lg flex flex-row gap-4 text-lg
hover:cursor-row-resize hover:cursor-pointer
p-2 p-2
text-base-content text-base-content
sm:text-base-content sm:text-base-content
@ -93,7 +94,7 @@ export const SumButton = (props) => (
</button> </button>
) )
export const TopSumTitle = (props) => ( export const TopSumTitle = (props) => (
<span className={`grow ${linkClasses} hover:cursor-resize font-bold uppercase`}> <span className={`grow ${linkClasses} hover:cursor-pointer font-bold uppercase`}>
{props.children} {props.children}
</span> </span>
) )
@ -104,18 +105,22 @@ export const SecText = (props) =>
<span className="text-secondary-focus">{props.children}</span> <span className="text-secondary-focus">{props.children}</span>
) )
export const WorkbenchMenu = (props) => { export const DraftMenu = (props) => {
const { design, patternConfig, settings, ui, update, Option = false } = props
return ( return (
<nav className="grow mb-12"> <nav className="grow mb-12">
<ViewMenu {...props} /> <DesignOptions {...props} />
{['draft', 'cuttingLayout', 'printingLayout'].indexOf(props.gist?._state?.view) > -1 && ( <CoreSettings {...props} />
<> {ui.renderer === 'react' ? <XrayMenu {...props} /> : null}
<DesignOptions {...props} /> </nav>
<CoreSettings {...props} /> )
{props.gist.renderer === 'react' && <XrayMenu {...props} />} }
</>
)} export const TestMenu = (props) => {
{props.gist?._state?.view === 'test' && <TestDesignOptions {...props} />} return (
<nav className="grow mb-12">
<TestDesignOptions {...props} />
</nav> </nav>
) )
} }

View file

@ -1,8 +1,8 @@
import { OptionsIcon } from 'shared/components/icons.mjs' import { OptionsIcon } from 'shared/components/icons.mjs'
import { Chevron } from 'shared/components/navigation/primary.mjs' import { Chevron } from 'shared/components/navigation/primary.mjs'
import { OptionGroup } from '../design-options/option-group.mjs' import { DesignOptionGroup } from '../design-options/index.mjs'
import { Option } from './option.mjs' import { Option } from './option.mjs'
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu/index.mjs' import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menus/index.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { optionsMenuStructure } from 'shared/utils.mjs' import { optionsMenuStructure } from 'shared/utils.mjs'
import { adult, doll, giant } from '@freesewing/models' import { adult, doll, giant } from '@freesewing/models'
@ -48,7 +48,7 @@ export const TestDesignOptions = (props) => {
sampleSettings={{ type: 'option', options }} sampleSettings={{ type: 'option', options }}
/> />
) : ( ) : (
<OptionGroup <DesignOptionGroup
{...props} {...props}
group={group} group={group}
options={options} options={options}

View file

@ -1,4 +1,4 @@
import { Li, SumButton, SumDiv } from 'shared/components/workbench/menu/index.mjs' import { Li, SumButton, SumDiv } from 'shared/components/workbench/menus/index.mjs'
export const Option = (props) => { export const Option = (props) => {
const active = props.sampleSettings?.type === 'option' && props.active === props.option const active = props.sampleSettings?.type === 'option' && props.active === props.option

View file

@ -1,5 +1,13 @@
import { Chevron } from 'shared/components/navigation/primary' import { Chevron } from 'shared/components/navigation/primary'
import { Ul, Li, Details, Summary, SumDiv, NoSumDiv, Deg } from 'shared/components/workbench/menu' import {
Ul,
Li,
Details,
Summary,
SumDiv,
NoSumDiv,
Deg,
} from 'shared/components/workbench/menus/index.mjs'
export const XrayAttributes = ({ attr = false, t }) => { export const XrayAttributes = ({ attr = false, t }) => {
if (!attr || !attr.list || Object.keys(attr.list).length < 1) return null if (!attr || !attr.list || Object.keys(attr.list).length < 1) return null

View file

@ -1,4 +1,4 @@
import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu' import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menus/index.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
export const XrayDisable = (props) => { export const XrayDisable = (props) => {

View file

@ -4,7 +4,7 @@ import { ConsoleLog } from './log.mjs'
import { XrayReset } from './reset.mjs' import { XrayReset } from './reset.mjs'
import { XrayDisable } from './disable.mjs' import { XrayDisable } from './disable.mjs'
import { XrayList } from './list.mjs' import { XrayList } from './list.mjs'
import { Ul, Details, TopSummary } from 'shared/components/workbench/menu' import { Ul, Details, TopSummary } from 'shared/components/workbench/menus/index.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
export const XrayMenu = (props) => { export const XrayMenu = (props) => {

View file

@ -1,6 +1,6 @@
import { Chevron } from 'shared/components/navigation/primary.mjs' import { Chevron } from 'shared/components/navigation/primary.mjs'
import { ClearIcon, FilterIcon, SearchIcon } from 'shared/components/icons.mjs' import { ClearIcon, FilterIcon, SearchIcon } from 'shared/components/icons.mjs'
import { Ul, Li, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs' import { Ul, Li, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menus/index.mjs'
import { XrayPath } from './path.mjs' import { XrayPath } from './path.mjs'
import { XrayPoint } from './point.mjs' import { XrayPoint } from './point.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'

View file

@ -7,7 +7,7 @@ import {
SumButton, SumButton,
SumDiv, SumDiv,
Deg, Deg,
} from 'shared/components/workbench/menu/index.mjs' } from 'shared/components/workbench/menus/index.mjs'
export const ConsoleLog = (props) => ( export const ConsoleLog = (props) => (
<Li> <Li>

View file

@ -1,5 +1,13 @@
import { Chevron } from 'shared/components/navigation/primary' import { Chevron } from 'shared/components/navigation/primary'
import { Ul, Li, Details, Summary, SumDiv, NoSumDiv, Deg } from 'shared/components/workbench/menu' import {
Ul,
Li,
Details,
Summary,
SumDiv,
NoSumDiv,
Deg,
} from 'shared/components/workbench/menus/index.mjs'
import { XrayPoint } from './point' import { XrayPoint } from './point'
const MoveLine = ({ op }) => <XrayPoint point={op.to} /> const MoveLine = ({ op }) => <XrayPoint point={op.to} />

View file

@ -1,4 +1,4 @@
import { Ul, Li, NoSumDiv, Deg } from 'shared/components/workbench/menu/index.mjs' import { Ul, Li, NoSumDiv, Deg } from 'shared/components/workbench/menus/index.mjs'
import { formatMm } from 'shared/utils.mjs' import { formatMm } from 'shared/utils.mjs'
import { XrayAttributes } from './attributes.mjs' import { XrayAttributes } from './attributes.mjs'
import { XrayPathOps } from './path-ops.mjs' import { XrayPathOps } from './path-ops.mjs'

View file

@ -1,4 +1,4 @@
import { Ul, Li, NoSumDiv, Deg } from 'shared/components/workbench/menu' import { Ul, Li, NoSumDiv, Deg } from 'shared/components/workbench/menus/index.mjs'
import { round } from 'shared/utils' import { round } from 'shared/utils'
import { XrayAttributes } from './attributes' import { XrayAttributes } from './attributes'

View file

@ -1,4 +1,4 @@
import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu' import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menus/index.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
export const XrayReset = (props) => { export const XrayReset = (props) => {

View file

@ -0,0 +1,12 @@
import { SvgWrapper } from './svg.mjs'
export const Pattern = ({ pattern, setView, settings, ui, update }) => {
if (!pattern) return null
// Render as SVG
return ui.renderer === 'svg' ? (
<div dangerouslySetInnerHTML={{ __html: patern.render() }} />
) : (
<SvgWrapper {...pattern.getRenderProps()} />
)
}

View file

@ -74,7 +74,7 @@ export const SvgWrapper = forwardRef((props, ref) => {
if (!patternProps) return null if (!patternProps) return null
return ( return (
<SizeMe> <SizeMe refreshRate={64}>
{({ size }) => ( {({ size }) => (
<TransformWrapper <TransformWrapper
minScale={0.1} minScale={0.1}
@ -82,7 +82,7 @@ export const SvgWrapper = forwardRef((props, ref) => {
wheel={{ activationKeys: ['Control'] }} wheel={{ activationKeys: ['Control'] }}
> >
<TransformComponent> <TransformComponent>
<div style={{ width: size.width + 'px' }}> <div style={{ width: size.width + 'px' }} className="max-h-screen">
<Svg {...patternProps} viewBox={viewBox} embed={gist.embed} ref={ref}> <Svg {...patternProps} viewBox={viewBox} embed={gist.embed} ref={ref}>
<Defs {...patternProps} /> <Defs {...patternProps} />
<style>{`:root { --pattern-scale: ${gist.scale || 1}} ${ <style>{`:root { --pattern-scale: ${gist.scale || 1}} ${

View file

@ -1,7 +1,7 @@
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { svgattrPlugin } from '@freesewing/plugin-svgattr' import { svgattrPlugin } from '@freesewing/plugin-svgattr'
import { SvgWrapper } from './draft/svg.mjs' import { SvgWrapper } from './pattern/svg.mjs'
import { DraftError } from './draft/error.mjs' import { DraftError } from './pattern/error.mjs'
export const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback }) => { export const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback }) => {
const { t } = useTranslation(['workbench']) const { t } = useTranslation(['workbench'])

View file

@ -0,0 +1,13 @@
import { Pattern } from 'shared/components/workbench/pattern/index.mjs'
import { DraftMenu } from './menu.mjs'
export const DraftView = ({ design, pattern, patternConfig, setView, settings, ui, update }) => (
<div className="flex flex-row items-top">
<div className="w2/3 shrink-0 grow lg:p-4">
<Pattern {...{ pattern, setView, settings, ui, update }} />
</div>
<div className="w1/3 shrink-0 grow border-l-2 border-dotted border-secondary lg:p-4">
<DraftMenu {...{ design, pattern, patternConfig, settings, ui, update }} />
</div>
</div>
)

View file

@ -0,0 +1,11 @@
import { DesignOptions } from 'shared/components/workbench/menus/design-options/index.mjs'
import { CoreSettings } from 'shared/components/workbench/menus/core-settings/index.mjs'
import { XrayMenu } from 'shared/components/workbench/menus/xray/index.mjs'
export const DraftMenu = ({ design, pattern, patternConfig, settings, ui, update }) => (
<nav className="grow mb-12">
<DesignOptions {...{ design, patternConfig, settings, update }} />
<CoreSettings {...{ patternConfig, settings, update }} />
{ui.renderer === 'react' && <XrayMenu {...{ ui, update }} />}
</nav>
)

View file

@ -20,7 +20,8 @@ export const SwipeWrapper = ({ children, app }) => {
onSwipedRight: (evt) => { onSwipedRight: (evt) => {
// Only process the first swipe event // Only process the first swipe event
evt.event.stopPropagation() evt.event.stopPropagation()
setModal(<ModalMenu app={app} />) // FIXME: Make this not be such a PITA
//setModal(<ModalMenu app={app} />)
}, },
trackMouse: true, trackMouse: true,
}) })