1
0
Fork 0

Merge branch 'develop' into eriese-imperial

This commit is contained in:
Enoch Riese 2022-06-17 22:31:11 -05:00
commit 7476d45f54
575 changed files with 2464 additions and 1196 deletions

View file

@ -0,0 +1,28 @@
import { useState } from 'react'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.js'
import { useTranslation } from 'next-i18next'
const CoreSettingBool = props => {
const { t } = useTranslation(['app'])
const [value, setValue] = useState(props.gist[props.setting])
const toggle = (evt) => {
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>
)
}
export default CoreSettingBool

View file

@ -0,0 +1,51 @@
import { useState } from 'react'
import { Deg } from 'shared/components/workbench/menu/index.js'
import { useTranslation } from 'next-i18next'
const CoreSettingList = props => {
const { t } = useTranslation(['settings'])
const { dflt, list } = 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>
)
}
export default CoreSettingList

View file

@ -0,0 +1,74 @@
import { useState } from 'react'
import { formatMm } from 'shared/utils'
import ClearIcon from 'shared/components/icons/clear'
import { useTranslation } from 'next-i18next'
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>
)
}
export default CoreSettingMm

View file

@ -0,0 +1,71 @@
import { useState } from 'react'
import ClearIcon from 'shared/components/icons/clear.js'
import EditIcon from 'shared/components/icons/edit.js'
import { useTranslation } from 'next-i18next'
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>
)
}
export default CoreSettingNr

View file

@ -0,0 +1,66 @@
import ClearIcon from 'shared/components/icons/clear.js'
import orderBy from 'lodash.orderby'
import { useTranslation } from 'next-i18next'
const CoreSettingOnly = props => {
const { t } = useTranslation(['app', 'parts', 'settings'])
const list = props.design.config.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 default CoreSettingOnly

View file

@ -0,0 +1,36 @@
import { useState } from 'react'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.js'
import { useTranslation } from 'next-i18next'
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:sa.t') }</span>
<span className="ml-4 opacity-50">
[ { t(`yes`) }/
{ t(`no`) } ]
</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}
export default CoreSettingSaBool

View file

@ -0,0 +1,76 @@
import { useState } from 'react'
import { formatMm } from 'shared/utils'
import ClearIcon from 'shared/components/icons/clear'
import { useTranslation } from 'next-i18next'
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)
setValue(newVal)
if (props.gist.sa) 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>
)
}
export default CoreSettingMm

View file

@ -0,0 +1,69 @@
import SettingsIcon from 'shared/components/icons/settings.js'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import Setting from './setting.js'
import { Ul, Details, TopSummary, TopSumTitle } from '../index.js'
import { useTranslation } from 'next-i18next'
export const settings = {
paperless: {
dflt: false,
},
saBool: {
dflt: false,
},
saMm: {
min: 0,
max: 25,
dflt: 10,
},
complete: {
dflt: false,
},
only: { },
locale: {
list: ['de', 'en', 'es', 'fr', 'nl'],
},
units: {
list: ['metric', 'imperial'],
},
margin: {
min: 0,
max: 25,
dflt: 2,
},
scale: {
min: 0.1,
max: 5,
dflt: 1,
},
renderer: {
list: ['react', 'svg'],
titles: {
react: '<Draft /> (React)',
svg: '@freesewing/core (SVG)'
}
},
debug: {
dflt: false,
},
}
const CoreSettings = props => {
const { t } = useTranslation(['app'])
return (
<Details open>
<TopSummary icon={<SettingsIcon />}>
<TopSumTitle>{t('settings')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul>
{Object.keys(settings).map(setting => (
<Setting key={setting} setting={setting} config={settings[setting]} {...props} />
))}
</Ul>
</Details>
)
}
export default CoreSettings

View file

@ -0,0 +1,118 @@
import { Chevron } from 'shared/components/navigation/primary'
import PctDegOption from 'shared/components/workbench/inputs/design-option-pct-deg'
import ListSetting from './core-setting-list'
import OnlySetting from './core-setting-only'
import MmSetting from './core-setting-mm'
import NrSetting from './core-setting-nr'
import BoolSetting from './core-setting-bool'
import SaBoolSetting from './core-setting-sa-bool'
import SaMmSetting from './core-setting-sa-mm'
import { formatMm } from 'shared/utils'
import { SecText, Li, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu/index'
import { useTranslation } from 'next-i18next'
const settings = {
paperless: props => (
<SecText>
{props.t(props.gist.paperless ? 'yes' : 'no')}
</SecText>
),
complete: props => (
<SecText>
{props.t(props.gist.complete ? 'yes' : 'no')}
</SecText>
),
debug: props => (
<SecText>
{props.t(props.gist.debug ? 'yes' : 'no')}
</SecText>
),
locale: props => (
<SecText>
{props.t(`i18n:${props.gist.locale}`)}
</SecText>
),
units: props => (
<SecText>
{props.t(`${props.gist.units}Units`)}
</SecText>
),
margin: props => <SecText raw={formatMm(props.gist.margin, props.gist.units)} />,
scale: props => props.gist.scale === 1
? <SecText>{props.gist.scale}</SecText>
: <span className="text-accent">{props.gist.scale}</span>,
saMm: props => <SecText raw={formatMm(props.gist.saMm, props.gist.units)} />,
renderer: props => (
<SecText>
{props.config.titles[props.gist.renderer]}
</SecText>
),
only: props => (props.gist?.only && props.gist.only.length > 0)
? <SecText>{props.gist.only.length}</SecText>
: <span className="text-secondary-focus">{props.t('default')}</span>
}
const inputs = {
locale: props => <ListSetting
{...props}
list={props.config.list.map(key => ({
key,
title: props.t(`i18n:${key}`)
}))}
/>,
units: props => <ListSetting
{...props}
list={props.config.list.map(key => ({
key,
title: props.t(`${key}Units`)
}))}
/>,
margin: props => <MmSetting {...props} {...props.config} />,
scale: props => <NrSetting {...props} {...props.config} />,
saMm: props => <SaMmSetting {...props} {...props.config} />,
renderer: props => <ListSetting
{...props}
list={props.config.list.map(key => ({
key,
title: props.config.titles[key]
}))}
/>,
only: props => <OnlySetting {...props} />
}
const Setting = props => {
const { t } = useTranslation(['app', 'i18n', 'settings'])
if (props.setting === 'saBool')
return <SaBoolSetting {...props} {...props.config} />
if (['paperless', 'complete', 'debug', 'xray'].indexOf(props.setting) !== -1)
return <BoolSetting {...props} {...props.config} />
const Input = inputs[props.setting]
const Value = settings[props.setting]
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
{props.setting === 'saMm'
? (
<>
<span>{t(`settings:sa.t`)}</span>
<span className="ml-4 opacity-50">[ {t(`size`)} ]</span>
</>
)
: <span>{t(`settings:${props.setting}.t`)}</span>
}
</SumDiv>
<Value setting={props.setting} {...props} t={t}/>
<Chevron />
</Summary>
<Input {...props} t={t} />
</Details>
</Li>
)
}
export default Setting

View file

@ -0,0 +1,25 @@
import OptionsIcon from 'shared/components/icons/options.js'
import { Chevron } from 'shared/components/navigation/primary.js'
import OptionGroup from './option-group'
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const DesignOptions = props => {
const { t } = useTranslation(['app'])
return (
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('designOptions')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.keys(props.design.config.optionGroups).map(group => (
<OptionGroup {...props} group={group} key={group} />
))}
</Ul>
</Details>
)
}
export default DesignOptions

View file

@ -0,0 +1,32 @@
import { Chevron } from 'shared/components/navigation/primary.js'
import Option from './option'
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const OptionGroup = props => {
const { t } = useTranslation(['optiongroups'])
const config = props.config || props.design.config.optionGroups[props.group]
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">
{ t(props.group) }
</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{config.map(option =>
typeof option === 'string' ? <Option {...props} option={option} key={option} />
: Object.keys(option).map((sub) => <OptionGroup {...props} config={option[sub]} group={sub} key={sub}/>)
)}
</Ul>
</Details>
</Li>
)
}
export default OptionGroup

View file

@ -0,0 +1,14 @@
import PctDegOption from 'shared/components/workbench/inputs/design-option-pct-deg'
import CountOption from 'shared/components/workbench/inputs/design-option-count'
import ListOption from 'shared/components/workbench/inputs/design-option-list'
export const Tmp = props => <p>not yet</p>
export const inputs = {
pct: PctDegOption,
count: CountOption,
deg: props => (<PctDegOption {...props} type='deg' />),
list: ListOption,
mm: (<p>Mm options are not supported. Please report this.</p>),
constant: Tmp,
}

View file

@ -0,0 +1,69 @@
import { useTranslation } from 'next-i18next'
import { formatMm, formatPercentage} from 'shared/utils'
export const values = {
pct: props => {
const val = (typeof props.gist?.options?.[props.option] === 'undefined')
? props.design.config.options[props.option].pct/100
: props.gist.options[props.option]
return (
<span className={
val=== props.design.config.options[props.option].pct/100
? 'text-secondary-focus'
: 'text-accent'
}>
{formatPercentage(val)}
{props.design.config.options[props.option]?.toAbs
? ' | ' +formatMm(props.design.config.options[props.option]?.toAbs(val, props.gist))
: null
}
</span>
)
},
bool: props => {
const { t } = useTranslation(['app'])
const dflt = props.design.config.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.config.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.config.options[props.option].dflt
const current = props.gist?.options?.[props.option]
const prefix = `${props.option}.o.`
return (dflt==current || typeof current === 'undefined')
? (<span className="text-secondary-focus">{props.t(prefix+dflt)}</span>)
: (<span className="text-accent">{props.t(prefix+current)}</span>)
},
deg: props => {
const dflt = props.design.config.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: props => {
return (<p>No mm val yet</p>)
},
constant: props => {
return (<p>No constant val yet</p>)
}
}

View file

@ -0,0 +1,59 @@
import { Chevron } from 'shared/components/navigation/primary'
import { optionType } from 'shared/utils'
import { Li, Ul, Details, Summary, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
import {values} from 'shared/components/workbench/menu/design-options/option-value'
import {inputs} from 'shared/components/workbench/menu/design-options/option-input'
const Option = props => {
const { t } = useTranslation([`o_${props.design.config.name}`])
const opt = props.design.config.options[props.option];
const type = optionType(opt)
const Input = inputs[type]
const Value = values[type]
const hide = opt.hide && opt.hide(props.draft.settings.options);
if (hide) {
return <Li></Li>
}
if (type === 'bool') {
const toggleBoolean = () => {
const dflt = opt.bool
const current = props.gist?.options?.[props.option]
if (typeof current === 'undefined')
props.updateGist(['options', props.option], !dflt)
else props.unsetGist(['options', props.option])
}
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>
)
}
export default Option

View file

@ -0,0 +1,101 @@
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import ViewMenu from './view.js'
import DesignOptions from './design-options'
import CoreSettings from './core-settings'
import Xray from './xray'
import TestDesignOptions from './test-design-options'
export const Ul = props => <ul className="pl-5 list-inside">{props.children}</ul>
export const Li = props => (
<li className="flex flex-row hover:border-r-2 hover:border-r-secondary">
{props.children}
</li>
)
export const Details = props => (
<details className="grow" open={props.open || false}>
{props.children}
</details>
)
export const Deg = props => <span className="text-3xl inline-block p-0 leading-3 px-2 translate-y-3">&deg;</span>
export const NoSumDiv = props => (
<div className={`
grow px-2 ml-2 border-l-2
${linkClasses}
hover:cursor-resize
hover:border-secondary
sm:hover:border-secondary-focus
text-base-content sm:text-base-content
`}>{props.children}</div>
)
export const SumDiv = props => (
<div className={`
grow pl-2 border-l-2
${linkClasses}
hover:cursor-resize
hover:border-secondary
sm:hover:border-secondary-focus
text-base-content sm:text-base-content
`}>{props.children}</div>
)
export const Summary = props => (
<summary className={`
flex flex-row
px-2
text-base-content
sm:text-base-content
hover:cursor-row-resize
items-center
`}>{props.children}</summary>
)
export const TopSummary = props => (
<summary className={`
flex flex-row gap-4 text-lg
hover:cursor-row-resize
p-2
text-base-content
sm:text-base-content
items-center
`}>
<span className="text-secondary-focus mr-4">{props.icon || null}</span>
{props.children}
</summary>
)
export const SumButton = props => (
<button className={`
flex flex-row
px-2
w-full justify-between
text-left
text-base-content
sm:text-base-content
hover:cursor-pointer
items-center
mr-4
`} onClick={props.onClick}>{props.children}</button>
)
export const TopSumTitle = props => (
<span className={`grow ${linkClasses} hover:cursor-resize font-bold uppercase`}>
{props.children}
</span>
)
export const SecText = props => props.raw
? <span className="text-secondary-focus" dangerouslySetInnerHTML={{__html: props.raw}} />
: <span className="text-secondary-focus">{props.children}</span>
const WorkbenchMenu = props => {
return (
<nav className="grow mb-12">
<ViewMenu {...props} />
{props.gist?._state?.view === 'draft' && (
<>
<DesignOptions {...props} />
<CoreSettings {...props} />
{props.gist.renderer === 'react' && <Xray {...props} />}
</>
)}
{props.gist?._state?.view === 'test' && <TestDesignOptions {...props} />}
</nav>
)
}
export default WorkbenchMenu

View file

@ -0,0 +1,25 @@
import OptionsIcon from 'shared/components/icons/options.js'
import { Chevron } from 'shared/components/navigation/primary.js'
import OptionGroup from './option-group'
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const DesignOptions = props => {
const { t } = useTranslation(['app'])
return (
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('app:designOptions')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.keys(props.design.config.optionGroups).map(group => (
<OptionGroup {...props} group={group} key={group} />
))}
</Ul>
</Details>
)
}
export default DesignOptions

View file

@ -0,0 +1,33 @@
import { Chevron } from 'shared/components/navigation/primary.js'
import Option from './option'
import OptionSubGroup from './option-sub-group'
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const OptionGroup = props => {
const { t } = useTranslation(['optiongroups'])
const config = props.config || props.design.config.optionGroups[props.group]
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">
{ t(props.group) }
</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{config.map(option => typeof option === 'string'
? <Option {...props} option={option} key={option} />
: <OptionSubGroup {...props} sub={option} config={config} />
)}
</Ul>
</Details>
</Li>
)
}
export default OptionGroup

View file

@ -0,0 +1,29 @@
import { Chevron } from 'shared/components/navigation/primary.js'
import Option from './option'
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const OptionSubGroup = props => {
const { t } = useTranslation(['optiongroups'])
return Object.keys(props.sub).map(name => (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{ t(name) }</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{props.sub[name].map(option => typeof option === 'string'
? <Option {...props} option={option} key={option} />
: <OptionSubGroup {...props} sub={option} config={config} />
)}
</Ul>
</Details>
</Li>
))
}
export default OptionSubGroup

View file

@ -0,0 +1,67 @@
import { linkClasses } from 'shared/components/navigation/primary.js'
import { Li } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const SumButton = props => (
<button className={`
flex flex-row
px-2
w-full justify-between
text-left
text-base-content
sm:text-base-content
hover:cursor-pointer
items-center
mr-4
`} onClick={props.onClick}>{props.children}</button>
)
const SumDiv = (props) => (
<div className={`
grow pl-2 border-l-2
${linkClasses}
hover:cursor-resize
hover:border-secondary
sm:hover:border-secondary-focus
text-base-content sm:text-base-content
${props.active && 'border-secondary-focus'}
`}>{props.children}</div>
)
const Option = props => {
const { t } = useTranslation([`o_${props.design.config.name}`, 'workbench'])
const active = (
props.gist.sample?.type === 'option' &&
props.gist.sample?.option === props.option
)
return (
<Li>
<SumButton onClick={() => props.updateGist(
['sample'],
{
type: 'option',
option: props.option
},
true // Close navigation on mobile
)}>
<SumDiv active={active}>
<span className={`
text-3xl inline-block p-0 leading-3 px-2
${active
? 'text-secondary sm:text-secondary-focus translate-y-1 font-bold'
: 'translate-y-3'
}`}
>
{active ? <span>&bull;</span> : <span>&deg;</span>}
</span>
<span className={active ? 'text-secondary font-bold' : ''}>
{t(`o_${props.design.config.name}:${props.option}.t`)}
</span>
</SumDiv>
</SumButton>
</Li>
)
}
export default Option

View file

@ -0,0 +1,120 @@
import MenuIcon from 'shared/components/icons/menu.js'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import { useTranslation } from 'next-i18next'
import defaultSettings from '../default-settings'
const View = 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.config.name }),
onClick: () => props.updateGist(['_state', 'view'], 'draft', true)
},
{
name: 'test',
title: t('testDesign', { design: props.design.config.name }),
onClick: () => props.updateGist(['_state', 'view'], 'test', true)
},
{
name: 'printingLayout',
title: t('layoutThing', { thing: props.design.config.name })
+ ': ' + t('forPrinting'),
onClick: () => props.updateGist(['_state', 'view'], 'printingLayout', true)
},
{
name: 'cuttingLayout',
title: t('layoutThing', { thing: props.design.config.name })
+ ': ' + t('forCutting'),
onClick: () => props.updateGist(['_state', 'view'], 'cuttingLayout', true)
},
{
name: 'export',
title: t('exportThing', { thing: props.design.config.name }),
onClick: () => props.updateGist(['_state', 'view'], 'export', true)
},
{
name: 'events',
title: t('events'),
onClick: () => props.updateGist(['_state', 'view'], 'events', 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(defaultSettings)
},
]
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>
)
}
export default View

View file

@ -0,0 +1,51 @@
import { Chevron } from 'shared/components/navigation/primary'
import { Ul, Li, Details, Summary, SumDiv, NoSumDiv, Deg } from 'shared/components/workbench/menu'
import { round } from 'shared/utils'
const XrayAttributes = ({ attr=false, t }) => {
if (!attr || !attr.list || Object.keys(attr.list).length < 1) return null
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
Attributes
</SumDiv>
<Chevron />
</Summary>
<Ul>
{Object.keys(attr.list).map(at => (
<Li key={at}>
<Details>
<Summary>
<SumDiv>
<Deg />
{at}
</SumDiv>
<Chevron />
</Summary>
<Ul>
{attr.list[at].map(val => (
<Li key={val}>
<NoSumDiv>
<Deg />
<span>{val === true
? t('app.yes')
: val
}</span>
</NoSumDiv>
</Li>
))}
</Ul>
</Details>
</Li>
))}
</Ul>
</Details>
</Li>
)
}
export default XrayAttributes

View file

@ -0,0 +1,21 @@
import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const DisableXray = props => {
const { t } = useTranslation(['cfp', 'settings'])
return (
<Li>
<SumButton onClick={() => props.updateGist(['_state', 'xray', 'enabled'], false)}>
<SumDiv>
<Deg />
<span>
{t('cfp:thingIsEnabled', { thing: t('settings:xray.t') })}
</span>
</SumDiv>
</SumButton>
</Li>
)
}
export default DisableXray

View file

@ -0,0 +1,54 @@
import XrayIcon from 'shared/components/icons/xray.js'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import Log from './log.js'
import Reset from './reset.js'
import Disable from './disable.js'
import List from './list.js'
import { Ul, Details, TopSummary } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const Xray = props => {
const { t } = useTranslation(['app', 'settings'])
return (
<Details open>
<TopSummary icon={<XrayIcon />}>
{props.gist?._state?.xray?.enabled
? (
<>
<span className={`grow ${linkClasses} hover:cursor-resize font-bold uppercase`}>
{t('settings:xray.t')}
</span>
<Chevron />
</>
) : (
<>
<button
className={`grow ${linkClasses} hover:cursor-resize uppercase font-bold text-left`}
onClick={() => props.updateGist(['_state', 'xray', 'enabled'], true)}
>
{t('settings:xray.t')}
</button>
<span className="text-normal text-secondary">
{t('cfp:thingIsDisabled', { thing: t('settings:xray.t') })}
</span>
</>
)
}
</TopSummary>
{props.gist?._state?.xray?.enabled && (
<Ul>
<Disable {...props} />
<Log {...props} />
<Reset {...props} />
{
props.gist?._state?.xray?.parts &&
Object.keys(props.gist._state.xray.parts).map(partName => <List {...props} partName={partName} />)
}
</Ul>
)}
</Details>
)
}
export default Xray

View file

@ -0,0 +1,138 @@
import { Chevron } from 'shared/components/navigation/primary.js'
import ClearIcon from 'shared/components/icons/clear.js'
import FilterIcon from 'shared/components/icons/filter.js'
import SearchIcon from 'shared/components/icons/search.js'
import { Ul, Li, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu'
import Path from './path.js'
import Point from './point.js'
import { useTranslation } from 'next-i18next'
const types = {
paths: Path,
points: Point
}
const XrayList = props => {
const { t } = useTranslation(['app', 'parts'])
const title = t(`parts:${props.partName}`) + ` (${props.partName})`
const part = props.gist._state.xray.parts[props.partName]
// Is this the only part on display?
const only = (
props.gist.only &&
props.gist.only.length === 1 &&
props.gist.only[0] === props.partName
)
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>{title}</span>
<span className="ml-2 opacity-60">[{props.partName}]</span>
</SumDiv>
<button
className={`px-3 hover:text-secondary-focus ${only ? 'text-accent' : 'text-secondary'}`}
title={t('filter')}
onClick={only
? () => props.unsetGist(['only'])
: () => props.updateGist(['only'], [props.partName])
}
>
<FilterIcon />
</button>
<button
className="text-accent px-3 hover:text-secondary-focus"
onClick={() => props.unsetGist(['_state', 'xray', 'parts', props.partName])}
>
<ClearIcon />
</button>
<Chevron w={6} m={3}/>
</Summary>
{Object.keys(types).map(type => part[type] && (
<Ul>
<Li>
<Details>
<Summary>
<SumDiv>
<span className="capitalize">{type}</span>
</SumDiv>
<button
className="text-accent px-3 hover:text-secondary-focus"
onClick={() => props.unsetGist(['_state', 'xray', 'parts', props.partName, type])}
>
<ClearIcon />
</button>
<Chevron />
</Summary>
<Ul>
{Object.keys(part[type])
.map(id => (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>{id}</span>
</SumDiv>
<button
className={`px-3 hover:text-secondary-focus"
${props.gist._state?.xray?.reveal?.[props.partName]?.[type]?.[id]
? 'text-accent'
: 'text-secondary'
}`}
onClick={props.gist._state?.xray?.reveal?.[props.partName]?.[type]?.[id]
? () => props.unsetGist(
['_state', 'xray', 'reveal', props.partName, type, id]
)
: () => props.updateGist(
['_state', 'xray', 'reveal', props.partName, type, id],
id
)
}
>
<SearchIcon />
</button>
<button
className="text-accent px-3 hover:text-secondary-focus"
onClick={() => {
props.unsetGist(['_state', 'xray', 'parts', props.partName, type, id])
props.unsetGist(['_state', 'xray', 'reveal', props.partName, type, id])
}}
>
<ClearIcon />
</button>
<Chevron />
</Summary>
{type === 'paths' && <Path
pathName={id}
partName={props.partName}
draft={props.draft}
t={t}
units={props.gist.units}
/>}
{type === 'points' && <Point
pointName={id}
partName={props.partName}
draft={props.draft}
t={t}
/>}
</Details>
</Li>
))
}
</Ul>
</Details>
</Li>
</Ul>
))}
</Details>
</Li>
)
}
export default XrayList

View file

@ -0,0 +1,33 @@
import { Chevron } from 'shared/components/navigation/primary.js'
import { Ul, Li, Details, Summary, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
const ConsoleLog = props => (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>console.log()</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{['config', 'gist', 'draft'].map(it => (
<Li key={it}>
<SumButton onClick={() => console.log(it === 'config'
? props.design.config
: props[it]
)}>
<SumDiv>
<Deg />
<span>{it}</span>
</SumDiv>
</SumButton>
</Li>
))}
</Ul>
</Details>
</Li>
)
export default ConsoleLog

View file

@ -0,0 +1,76 @@
import { Chevron } from 'shared/components/navigation/primary'
import { Ul, Li, Details, Summary, SumDiv, NoSumDiv, Deg } from 'shared/components/workbench/menu'
import { round } from 'shared/utils'
import Point from './point'
const MoveLine = ({ op }) => <Point point={op.to} />
const Curve = ({ op }) => ['cp1', 'cp2', 'to'].map(pnt => (
<Li key={pnt}>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{pnt}</span>
</SumDiv>
<Chevron />
</Summary>
<Point point={op[pnt]} />
</Details>
</Li>
))
const Close = () => (
<p>Close</p>
)
const XrayPathOp = ({ op }) => (
<Li>
{op.type === 'close'
? (
<NoSumDiv>
<Deg />
<span className="font-bold">{op.type}</span>
</NoSumDiv>
) : (
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{op.type}</span>
<span>To</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{op.type === 'curve'
? <Curve op={op} />
: <MoveLine op={op} />
}
</Ul>
</Details>
)
}
</Li>
)
const XrayPathOps = ({ ops=false }) => {
if (!ops || ops.length < 1) return null
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">path.ops</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{ops.map(op => <XrayPathOp op={op} />)}
</Ul>
</Details>
</Li>
)
}
export default XrayPathOps

View file

@ -0,0 +1,34 @@
import { Ul, Li, Details, Summary, NoSumDiv, Deg } from 'shared/components/workbench/menu'
import { formatMm } from 'shared/utils'
import Attributes from './attributes'
import Ops from './path-ops'
const XrayPath = ({ pathName, partName, draft, t, units }) => {
const path = draft?.parts?.[partName]?.paths?.[pathName]
if (!path) return null
return (
<Ul>
<Attributes attr={path.attributes} />
<Li>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">path.render =</span>
<span>{JSON.stringify(path.render)}</span>
</NoSumDiv>
</Li>
<Li>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">path.length() =</span>
<span dangerouslySetInnerHTML={{
__html: formatMm(path.length(), units)
}} />
</NoSumDiv>
</Li>
<Ops ops={path.ops} />
</Ul>
)
}
export default XrayPath

View file

@ -0,0 +1,25 @@
import { Ul, Li, Details, Summary, NoSumDiv, Deg } from 'shared/components/workbench/menu'
import { round } from 'shared/utils'
import Attributes from './attributes'
const XrayPoint = ({ pointName, partName, draft, t }) => {
const point = draft?.parts?.[partName]?.points?.[pointName]
return point
? (
<Ul>
{['x', 'y'].map(coord => (
<Li key={coord}>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">{coord} =</span>
<span>{round(point[coord])}</span>
</NoSumDiv>
</Li>
))}
<Attributes attr={point.attributes} t={t} />
</Ul>
) : null
}
export default XrayPoint

View file

@ -0,0 +1,19 @@
import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
const ResetXray = props => {
const { t } = useTranslation(['app'])
return (
<Li>
<SumButton onClick={() => props.updateGist(['_state', 'xray'], { enabled: true })}>
<SumDiv>
<Deg />
<span>{ t(`reset`) }</span>
</SumDiv>
</SumButton>
</Li>
)
}
export default ResetXray