wip(shared): Re-factoring workbench
This commit is contained in:
parent
0dece4d70e
commit
517fa3f5e3
57 changed files with 1033 additions and 1382 deletions
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 }} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<>°</>
|
|
||||||
</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
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<>°</>
|
|
||||||
</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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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}°</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-accent">{current}°</span>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Mm: () => {
|
|
||||||
return <p>No mm val yet</p>
|
|
||||||
},
|
|
||||||
Constant: () => {
|
|
||||||
return <p>No constant val yet</p>
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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 ? <>•</> : <>°</>}
|
|
||||||
</span>
|
|
||||||
<span className={entry.name === props.gist?._state?.view ? 'font-bold' : ''}>
|
|
||||||
{entry.title}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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>
|
|
@ -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 = {
|
|
@ -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
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<>°</>
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
180
sites/shared/components/workbench/menus/design-options/index.mjs
Normal file
180
sites/shared/components/workbench/menus/design-options/index.mjs
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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">
|
||||||
|
<>°</>
|
||||||
|
</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>
|
|
@ -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}°</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-accent">{current}°</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>
|
||||||
|
)
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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}
|
|
@ -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
|
|
@ -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
|
|
@ -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) => {
|
|
@ -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) => {
|
|
@ -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'
|
|
@ -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>
|
|
@ -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} />
|
|
@ -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'
|
|
@ -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'
|
||||||
|
|
|
@ -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) => {
|
12
sites/shared/components/workbench/pattern/index.mjs
Normal file
12
sites/shared/components/workbench/pattern/index.mjs
Normal 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()} />
|
||||||
|
)
|
||||||
|
}
|
|
@ -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}} ${
|
|
@ -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'])
|
||||||
|
|
13
sites/shared/components/workbench/views/draft/index.mjs
Normal file
13
sites/shared/components/workbench/views/draft/index.mjs
Normal 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>
|
||||||
|
)
|
11
sites/shared/components/workbench/views/draft/menu.mjs
Normal file
11
sites/shared/components/workbench/views/draft/menu.mjs
Normal 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>
|
||||||
|
)
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue