1
0
Fork 0

add toggler to boolean menu items

This commit is contained in:
Enoch Riese 2023-06-02 09:41:00 -05:00
parent f37274ee70
commit 2ad0ac3491
10 changed files with 197 additions and 231 deletions

View file

@ -23,7 +23,7 @@ const defaultUi = {
const draftViews = ['draft', 'test'] const draftViews = ['draft', 'test']
export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) => { export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from, set }) => {
// Hooks // Hooks
const { t, i18n } = useTranslation(ns) const { t, i18n } = useTranslation(ns)
const { language } = i18n const { language } = i18n
@ -38,8 +38,9 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
// Effect // Effect
useEffect(() => { useEffect(() => {
// Force re-render when baseSettings changes. Required when they are loaded async. // Force re-render when baseSettings changes. Required when they are loaded async.
setSettings({ ...baseSettings, embed: true }) console.log('in effect')
}, [baseSettings]) setSettings({ ...baseSettings, embed: true, measurements: set.measies })
}, [baseSettings, set])
// Helper methods for settings/ui updates // Helper methods for settings/ui updates
const update = { const update = {

View file

@ -2,62 +2,45 @@
import { loadSettingsConfig, defaultSamm } from './config.mjs' import { loadSettingsConfig, defaultSamm } from './config.mjs'
// Components // Components
import { SettingsIcon } from 'shared/components/icons.mjs' import { SettingsIcon } from 'shared/components/icons.mjs'
import {
CompleteSettingInput,
LocaleSettingInput,
MarginSettingInput,
OnlySettingInput,
PaperlessSettingInput,
RendererSettingInput,
SaBoolSettingInput,
SaMmSettingInput,
ScaleSettingInput,
UnitsSettingInput,
} from './inputs.mjs'
import {
CompleteSettingValue,
LocaleSettingValue,
MarginSettingValue,
OnlySettingValue,
PaperlessSettingValue,
RendererSettingValue,
SaBoolSettingValue,
SaMmSettingValue,
ScaleSettingValue,
UnitsSettingValue,
} from './values.mjs'
import { WorkbenchMenu } from '../shared/index.mjs' import { WorkbenchMenu } from '../shared/index.mjs'
import { MenuItem } from '../shared/menu-item.mjs'
// Facilitate lookup of the value component // input components and event handlers
const values = { import { inputs, handlers } from './inputs.mjs'
complete: CompleteSettingValue, // values
locale: LocaleSettingValue, import { values } from './values.mjs'
margin: MarginSettingValue,
only: OnlySettingValue,
paperless: PaperlessSettingValue,
renderer: RendererSettingValue,
sabool: SaBoolSettingValue,
samm: SaMmSettingValue,
scale: ScaleSettingValue,
units: UnitsSettingValue,
}
// Facilitate lookup of the input component
const inputs = {
complete: CompleteSettingInput,
locale: LocaleSettingInput,
margin: MarginSettingInput,
only: OnlySettingInput,
paperless: PaperlessSettingInput,
renderer: RendererSettingInput,
sabool: SaBoolSettingInput,
samm: SaMmSettingInput,
scale: ScaleSettingInput,
units: UnitsSettingInput,
}
export const ns = ['core-settings', 'modal'] export const ns = ['core-settings', 'modal']
/** A wrapper for {@see MenuItem} to handle core settings-specific business */
const CoreSetting = ({ name, config, control, updateFunc, current, passProps, ...rest }) => {
// is toggling allowed?
const allowToggle = control > 3 && config.list?.length === 2
const handlerArgs = {
updateFunc,
current,
config,
...passProps,
}
// get the appropriate event handler if there is one
const handler = handlers[name] ? handlers[name](handlerArgs) : updateFunc
return (
<MenuItem
{...{
name,
config,
control,
current,
passProps,
...rest,
allowToggle,
updateFunc: handler,
}}
/>
)
}
/** /**
* The core settings menu * The core settings menu
* @param {Object} options.update settings and ui update functions * @param {Object} options.update settings and ui update functions
@ -82,6 +65,11 @@ export const CoreSettings = ({
parts: patternConfig.draftOrder, parts: patternConfig.draftOrder,
}) })
const passProps = {
samm: typeof settings.samm === 'undefined' ? defaultSamm(settings.units) : settings.samm,
units: settings.units,
}
return ( return (
<WorkbenchMenu <WorkbenchMenu
{...{ {...{
@ -95,12 +83,10 @@ export const CoreSettings = ({
language, language,
name: 'coreSettings', name: 'coreSettings',
ns, ns,
passProps: { passProps,
samm: typeof settings.samm === 'undefined' ? defaultSamm(settings.units) : settings.samm,
units: settings.units,
},
updateFunc: update.settings, updateFunc: update.settings,
values, values,
Item: CoreSetting,
}} }}
/> />
) )

View file

@ -1,26 +1,33 @@
import { useCallback } from 'react'
import { measurementAsMm } from 'shared/utils.mjs' import { measurementAsMm } from 'shared/utils.mjs'
import { ListInput, SliderInput, BoolInput, MmInput } from '../shared/inputs.mjs' import { ListInput, SliderInput, BoolInput, MmInput } from '../shared/inputs.mjs'
export const LocaleSettingInput = ListInput
export const UnitsSettingInput = ListInput
export const RendererSettingInput = ListInput
export const CompleteSettingInput = ListInput
export const PaperlessSettingInput = ListInput
export const MarginSettingInput = MmInput
export const ScaleSettingInput = SliderInput
/** an input for the 'only' setting. toggles individual parts*/ /** an input for the 'only' setting. toggles individual parts*/
export const OnlySettingInput = (props) => { const OnlySettingInput = (props) => {
const { config, updateFunc, current } = props const { config } = props
// set up choice titles // set up choice titles
config.choiceTitles = {} config.choiceTitles = {}
config.list.forEach((p) => (config.choiceTitles[p] = p)) config.list.forEach((p) => (config.choiceTitles[p] = p))
// make an update function that toggles the parts return <ListInput {...props} />
const onlyUpdateFunc = useCallback( }
export const inputs = {
complete: ListInput,
locale: ListInput,
margin: MmInput,
only: OnlySettingInput,
paperless: BoolInput,
sabool: BoolInput,
samm: MmInput,
scale: SliderInput,
units: BoolInput,
}
/** custom event handlers for inputs that need them */
export const handlers = {
only:
({ updateFunc, current }) =>
(path, part) => { (path, part) => {
// if there's no part being set, it's a reset // if there's no part being set, it's a reset
if (part === undefined) return updateFunc(path, part) if (part === undefined) return updateFunc(path, part)
@ -37,18 +44,8 @@ export const OnlySettingInput = (props) => {
updateFunc(path, newParts) updateFunc(path, newParts)
}, },
[updateFunc, current] samm:
) ({ updateFunc, config, units }) =>
return <ListInput {...props} updateFunc={onlyUpdateFunc} />
}
/** An input for the samm setting */
export const SaMmSettingInput = (props) => {
const { updateFunc, units, config } = props
// the update function to switch the 'sa' setting along with samm
const mmUpdateFunc = useCallback(
(_path, newCurrent) => { (_path, newCurrent) => {
// convert to millimeters if there's a value // convert to millimeters if there's a value
newCurrent = newCurrent === undefined ? measurementAsMm(config.dflt, units) : newCurrent newCurrent = newCurrent === undefined ? measurementAsMm(config.dflt, units) : newCurrent
@ -58,25 +55,8 @@ export const SaMmSettingInput = (props) => {
[['sa'], newCurrent], [['sa'], newCurrent],
]) ])
}, },
[updateFunc, units, config.dflt] sabool:
) ({ updateFunc, samm }) =>
return (
<MmInput
{...{
...props,
updateFunc: mmUpdateFunc,
}}
/>
)
}
/** An input for the sabool setting */
export const SaBoolSettingInput = (props) => {
const { updateFunc, samm } = props
// the update function to toggle the 'sa' setting based on 'sabool'
const saUpdateFunc = useCallback(
(_path, newCurrent) => { (_path, newCurrent) => {
updateFunc([ updateFunc([
// update sabool to the new current // update sabool to the new current
@ -85,16 +65,4 @@ export const SaBoolSettingInput = (props) => {
[['sa'], newCurrent ? samm : undefined], [['sa'], newCurrent ? samm : undefined],
]) ])
}, },
[updateFunc, samm]
)
return (
<BoolInput
{...{
...props,
name: 'sabool',
updateFunc: saUpdateFunc,
}}
/>
)
} }

View file

@ -1,23 +1,25 @@
import { ListValue, MmValue, PlainValue } from '../shared/values' import { ListValue, MmValue, PlainValue } from '../shared/values'
export const RendererSettingValue = ListValue const ScaleSettingValue = ({ current, config, changed }) => (
export const LocaleSettingValue = ListValue
export const CompleteSettingValue = ListValue
export const PaperlessSettingValue = ListValue
export const SaBoolSettingValue = ListValue
export const UnitsSettingValue = ListValue
export const MarginSettingValue = MmValue
export const SaMmSettingValue = MmValue
export const ScaleSettingValue = ({ current, config, changed }) => (
<PlainValue current={current} dflt={config.dflt} changed={changed} /> <PlainValue current={current} dflt={config.dflt} changed={changed} />
) )
export const OnlySettingValue = ({ current, config }) => ( const OnlySettingValue = ({ current, config }) => (
<PlainValue <PlainValue
current={current?.length} current={current?.length}
dflt={config.parts.length} dflt={config.parts.length}
changed={current !== undefined} changed={current !== undefined}
/> />
) )
export const values = {
complete: ListValue,
locale: ListValue,
margin: MmValue,
only: OnlySettingValue,
paperless: ListValue,
sabool: ListValue,
samm: MmValue,
scale: ScaleSettingValue,
units: ListValue,
}

View file

@ -1,67 +1,14 @@
// Components // Components
import { OptionsIcon } from 'shared/components/icons.mjs' import { OptionsIcon } from 'shared/components/icons.mjs'
import { optionsMenuStructure } from 'shared/utils.mjs' import { optionsMenuStructure, optionType } from 'shared/utils.mjs'
import { optionType, formatMm } from 'shared/utils.mjs'
import { import { values } from './values.mjs'
BoolInput, import { inputs } from './inputs.mjs'
ConstantInput,
SliderInput,
DegInput,
ListInput,
PctInput,
} from '../shared/inputs.mjs'
import {
BoolOptionValue,
ConstantOptionValue,
CountOptionValue,
DegOptionValue,
ListOptionValue,
MmOptionValue,
PctOptionValue,
} from './values.mjs'
import { WorkbenchMenu } from '../shared/index.mjs' import { WorkbenchMenu } from '../shared/index.mjs'
import { MenuItem } from '../shared/menu-item.mjs' import { MenuItem } from '../shared/menu-item.mjs'
export const ns = ['design-options'] export const ns = ['design-options']
const PctOptionInput = (props) => {
const { config, settings, changed } = props
const currentOrDefault = changed ? props.current : config.dflt / 100
return (
<PctInput {...props}>
<div className="flex flex-row justify-around">
<span className={changed ? 'text-accent' : 'text-secondary'}>
{config.toAbs && settings.measurements
? formatMm(config.toAbs(currentOrDefault, settings))
: ' '}
</span>
</div>
</PctInput>
)
}
// Facilitate lookup of the input component
const inputs = {
bool: BoolInput,
constant: ConstantInput,
count: SliderInput,
deg: DegInput,
list: ListInput,
mm: () => <span>FIXME: Mm options are deprecated. Please report this </span>,
pct: PctOptionInput,
}
// Facilitate lookup of the value component
const values = {
bool: BoolOptionValue,
constant: ConstantOptionValue,
count: CountOptionValue,
deg: DegOptionValue,
list: ListOptionValue,
mm: MmOptionValue,
pct: PctOptionValue,
}
// Emojis for option groups :) // Emojis for option groups :)
const emojis = { const emojis = {
advanced: '🤓', advanced: '🤓',
@ -77,11 +24,13 @@ const emojis = {
* @param {Object} options.settings core settings * @param {Object} options.settings core settings
* @param {Object} options.rest the rest of the props * @param {Object} options.rest the rest of the props
*/ */
export const DesignOption = ({ config, settings, ...rest }) => { const DesignOption = ({ config, settings, control, ...rest }) => {
const type = optionType(config) const type = optionType(config)
const Input = inputs[type] const Input = inputs[type]
const Value = values[type] const Value = values[type]
const allowOverride = ['pct', 'count', 'deg'].includes(type) const allowOverride = ['pct', 'count', 'deg'].includes(type)
const allowToggle =
(control > 3 && type === 'bool') || (type == 'list' && config.list.length === 2)
// Hide option? // Hide option?
if (config?.hide || (typeof config?.hide === 'function' && config.hide(settings))) return null if (config?.hide || (typeof config?.hide === 'function' && config.hide(settings))) return null
@ -90,10 +39,12 @@ export const DesignOption = ({ config, settings, ...rest }) => {
<MenuItem <MenuItem
{...{ {...{
config, config,
control,
...rest, ...rest,
Input, Input,
Value, Value,
allowOverride, allowOverride,
allowToggle,
}} }}
/> />
) )

View file

@ -0,0 +1,36 @@
import { formatMm } from 'shared/utils.mjs'
import {
BoolInput,
ConstantInput,
SliderInput,
DegInput,
ListInput,
PctInput,
} from '../shared/inputs.mjs'
const PctOptionInput = (props) => {
const { config, settings, changed } = props
const currentOrDefault = changed ? props.current : config.dflt / 100
return (
<PctInput {...props}>
<div className="flex flex-row justify-around">
<span className={changed ? 'text-accent' : 'text-secondary'}>
{config.toAbs && settings.measurements
? formatMm(config.toAbs(currentOrDefault, settings))
: ' '}
</span>
</div>
</PctInput>
)
}
// Facilitate lookup of the input component
export const inputs = {
bool: BoolInput,
constant: ConstantInput,
count: SliderInput,
deg: DegInput,
list: ListInput,
mm: () => <span>FIXME: Mm options are deprecated. Please report this </span>,
pct: PctOptionInput,
}

View file

@ -13,9 +13,6 @@ export const PctOptionValue = ({ config, current, settings, changed }) => {
) )
} }
/** Displays a boolean value */
export const BoolOptionValue = BoolValue
/** Displays a count value*/ /** Displays a count value*/
export const CountOptionValue = ({ config, current, changed }) => ( export const CountOptionValue = ({ config, current, changed }) => (
<PlainValue {...{ current, changed, dflt: config.count }} /> <PlainValue {...{ current, changed, dflt: config.count }} />
@ -40,3 +37,14 @@ export const MmOptionValue = () => (
export const ConstantOptionValue = () => ( export const ConstantOptionValue = () => (
<span className="text-error">FIXME: No ConstantOptionvalue implemented</span> <span className="text-error">FIXME: No ConstantOptionvalue implemented</span>
) )
// Facilitate lookup of the value component
export const values = {
bool: BoolValue,
constant: ConstantOptionValue,
count: CountOptionValue,
deg: DegOptionValue,
list: ListOptionValue,
mm: MmOptionValue,
pct: PctOptionValue,
}

View file

@ -1,4 +1,4 @@
import { useEffect, useCallback } from 'react' import { useCallback, useMemo } from 'react'
import { round, measurementAsMm, measurementAsUnits, formatFraction128 } from 'shared/utils.mjs' import { round, measurementAsMm, measurementAsUnits, formatFraction128 } from 'shared/utils.mjs'
import { ChoiceButton } from 'shared/components/choice-button.mjs' import { ChoiceButton } from 'shared/components/choice-button.mjs'
@ -35,31 +35,55 @@ const EditCount = (props) => (
/** /**
* A hook to get the change handler for an input. * A hook to get the change handler for an input.
* Also sets the reset function on a parent component
* @param {Number|String|Boolean} options.dflt the default value for the input * @param {Number|String|Boolean} options.dflt the default value for the input
* @param {Function} options.updateFunc the onChange * @param {Function} options.updateFunc the onChange
* @param {string} options.name the name of the property being changed * @param {string} options.name the name of the property being changed
* @param {Function} options.setReset the setReset function passed from the parent component * @return the change handler for the input
* @return {ret.handleChange} the change handler for the input
*/ */
const useSharedHandlers = ({ dflt, updateFunc, name, setReset }) => { const useSharedHandlers = ({ dflt, updateFunc, name }) => {
const reset = useCallback(() => updateFunc(name), [updateFunc, name]) return useCallback(
const handleChange = useCallback(
(newCurrent) => { (newCurrent) => {
if (newCurrent === dflt) reset() if (newCurrent === dflt) newCurrent = undefined
else { else {
updateFunc(name, newCurrent) updateFunc(name, newCurrent)
} }
}, },
[dflt, updateFunc, name, reset] [dflt, updateFunc, name]
) )
}
useEffect(() => { /** get the configuration that allows a boolean value to use the list input */
if (typeof setReset === 'function') setReset(() => reset) const useBoolConfig = (name, config) => {
}, [reset, setReset]) return useMemo(
() => ({
list: [false, true],
choiceTitles: {
false: `${name}No`,
true: `${name}Yes`,
},
valueTitles: {
false: 'no',
true: 'yes',
},
...config,
}),
[name, config]
)
}
return { handleChange, reset } /** a toggle input for list/boolean values */
export const ListToggle = ({ config, changed, updateFunc, name }) => {
const boolConfig = useBoolConfig(name, config)
const handleChange = useSharedHandlers({ dflt: boolConfig.dflt, updateFunc, name })
const dfltIndex = boolConfig.list.indexOf(boolConfig.dflt)
const doToggle = () =>
handleChange(boolConfig.list[changed ? dfltIndex : Math.abs(dfltIndex - 1)])
const checked = boolConfig.dflt == false ? changed : !changed
return <input type="checkbox" className="toggle" checked={checked} onChange={doToggle} />
} }
/** /**
@ -70,14 +94,12 @@ const useSharedHandlers = ({ dflt, updateFunc, name, setReset }) => {
* @param {Function} options.updateFunc the function called by the event handler to update the value * @param {Function} options.updateFunc the function called by the event handler to update the value
* @param {Boolean} options.compact include descriptions with the list items? * @param {Boolean} options.compact include descriptions with the list items?
* @param {Function} options.t translation function * @param {Function} options.t translation function
* @param {Function} options.setReset a setter for the reset function on the parent component
*/ */
export const ListInput = ({ name, config, current, updateFunc, compact = false, t, setReset }) => { export const ListInput = ({ name, config, current, updateFunc, compact = false, t, changed }) => {
const { handleChange } = useSharedHandlers({ const handleChange = useSharedHandlers({
dflt: config.dflt, dflt: config.dflt,
updateFunc, updateFunc,
name, name,
setReset,
}) })
return ( return (
@ -90,7 +112,7 @@ export const ListInput = ({ name, config, current, updateFunc, compact = false,
key={entry} key={entry}
title={t(`${titleKey}${compact ? '' : '.t'}`)} title={t(`${titleKey}${compact ? '' : '.t'}`)}
color={entry === config.dflt ? 'primary' : 'accent'} color={entry === config.dflt ? 'primary' : 'accent'}
active={current === entry} active={changed ? current === entry : entry === config.dflt}
onClick={() => handleChange(entry)} onClick={() => handleChange(entry)}
> >
{compact ? null : t(`${titleKey}.d`)} {compact ? null : t(`${titleKey}.d`)}
@ -103,19 +125,8 @@ export const ListInput = ({ name, config, current, updateFunc, compact = false,
/** A boolean version of {@see ListInput} that sets up the necessary configuration */ /** A boolean version of {@see ListInput} that sets up the necessary configuration */
export const BoolInput = (props) => { export const BoolInput = (props) => {
const boolConfig = { const { name, config } = props
list: [0, 1], const boolConfig = useBoolConfig(name, config)
choiceTitles: {
0: `${props.name}No`,
1: `${props.name}Yes`,
},
valueTitles: {
0: 'no',
1: 'yes',
},
dflt: props.config.dflt ? 1 : 0,
...props.config,
}
return <ListInput {...props} config={boolConfig} /> return <ListInput {...props} config={boolConfig} />
} }
@ -146,7 +157,7 @@ export const SliderInput = ({
changed, changed,
}) => { }) => {
const { max, min } = config const { max, min } = config
const { handleChange } = useSharedHandlers({ const handleChange = useSharedHandlers({
current, current,
dflt: config.dflt, dflt: config.dflt,
updateFunc, updateFunc,

View file

@ -1,6 +1,7 @@
import { ClearIcon, HelpIcon, EditIcon } from 'shared/components/icons.mjs' import { ClearIcon, HelpIcon, EditIcon } from 'shared/components/icons.mjs'
import { Collapse } from 'shared/components/collapse.mjs' import { Collapse } from 'shared/components/collapse.mjs'
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { ListToggle } from './inputs.mjs'
/** /**
* Check to see if a value is different from its default * Check to see if a value is different from its default
@ -10,7 +11,7 @@ import { useState, useMemo } from 'react'
*/ */
export const wasChanged = (current, config) => { export const wasChanged = (current, config) => {
if (typeof current === 'undefined') return false if (typeof current === 'undefined') return false
if (current === config.dflt) return false if (current == config.dflt) return false
return true return true
} }
@ -65,27 +66,26 @@ export const MenuItem = ({
Input, Input,
Value, Value,
allowOverride = false, allowOverride = false,
allowToggle = false,
control = Infinity, control = Infinity,
}) => { }) => {
// state for knowing whether the override input should be shown // state for knowing whether the override input should be shown
const [override, setOverride] = useState(false) const [override, setOverride] = useState(false)
// store the reset function in state because the Input may set a custom one
const [reset, setReset] = useState(() => () => updateFunc(name))
// generate properties to pass to the Input // generate properties to pass to the Input
const drillProps = useMemo( const drillProps = useMemo(
() => ({ () => ({
name, name,
config, config,
control,
current, current,
updateFunc, updateFunc,
t, t,
changed, changed,
override, override,
setReset, // allow setting of the reset function
...passProps, ...passProps,
}), }),
[name, config, current, updateFunc, t, changed, override, setReset, passProps] [name, config, current, updateFunc, t, changed, override, passProps, control]
) )
// don't render if this item is more advanced than the user has chosen to see // don't render if this item is more advanced than the user has chosen to see
@ -113,13 +113,13 @@ export const MenuItem = ({
<EditIcon className={`w-6 h-6 ${override ? 'bg-base-100 text-accent rounded' : ''}`} /> <EditIcon className={`w-6 h-6 ${override ? 'bg-base-100 text-accent rounded' : ''}`} />
</button> </button>
) )
if (changed) { if (changed && !allowToggle) {
const ResetButton = ({ open }) => ( const ResetButton = ({ open }) => (
<button <button
className={open ? openButtonClass : 'btn btn-accent'} className={open ? openButtonClass : 'btn btn-accent'}
onClick={(evt) => { onClick={(evt) => {
evt.stopPropagation() evt.stopPropagation()
reset() updateFunc(name)
}} }}
> >
<ClearIcon /> <ClearIcon />
@ -129,6 +129,10 @@ export const MenuItem = ({
openButtons.push(<ResetButton open key="clear" />) openButtons.push(<ResetButton open key="clear" />)
} }
if (allowToggle) {
buttons.push(<ListToggle {...{ config, changed, updateFunc, name }} />)
}
// props to pass to the ItemTitle // props to pass to the ItemTitle
const titleProps = { name, t, current: <Value {...drillProps} />, emoji: config.emoji } const titleProps = { name, t, current: <Value {...drillProps} />, emoji: config.emoji }

View file

@ -22,8 +22,7 @@ export const DraftMenu = ({
DynamicDocs, DynamicDocs,
inspector = false, inspector = false,
}) => { }) => {
// Default control level is 2 (in case people are not logged in) const control = account.control
const control = account.control || 2
const menuProps = { const menuProps = {
design, design,
patternConfig, patternConfig,