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

View file

@ -2,62 +2,45 @@
import { loadSettingsConfig, defaultSamm } from './config.mjs'
// Components
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'
// Facilitate lookup of the value component
const values = {
complete: CompleteSettingValue,
locale: LocaleSettingValue,
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,
}
import { MenuItem } from '../shared/menu-item.mjs'
// input components and event handlers
import { inputs, handlers } from './inputs.mjs'
// values
import { values } from './values.mjs'
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
* @param {Object} options.update settings and ui update functions
@ -82,6 +65,11 @@ export const CoreSettings = ({
parts: patternConfig.draftOrder,
})
const passProps = {
samm: typeof settings.samm === 'undefined' ? defaultSamm(settings.units) : settings.samm,
units: settings.units,
}
return (
<WorkbenchMenu
{...{
@ -95,12 +83,10 @@ export const CoreSettings = ({
language,
name: 'coreSettings',
ns,
passProps: {
samm: typeof settings.samm === 'undefined' ? defaultSamm(settings.units) : settings.samm,
units: settings.units,
},
passProps,
updateFunc: update.settings,
values,
Item: CoreSetting,
}}
/>
)

View file

@ -1,26 +1,33 @@
import { useCallback } from 'react'
import { measurementAsMm } from 'shared/utils.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*/
export const OnlySettingInput = (props) => {
const { config, updateFunc, current } = props
const OnlySettingInput = (props) => {
const { config } = props
// set up choice titles
config.choiceTitles = {}
config.list.forEach((p) => (config.choiceTitles[p] = p))
// make an update function that toggles the parts
const onlyUpdateFunc = useCallback(
return <ListInput {...props} />
}
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) => {
// if there's no part being set, it's a reset
if (part === undefined) return updateFunc(path, part)
@ -37,18 +44,8 @@ export const OnlySettingInput = (props) => {
updateFunc(path, newParts)
},
[updateFunc, current]
)
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(
samm:
({ updateFunc, config, units }) =>
(_path, newCurrent) => {
// convert to millimeters if there's a value
newCurrent = newCurrent === undefined ? measurementAsMm(config.dflt, units) : newCurrent
@ -58,25 +55,8 @@ export const SaMmSettingInput = (props) => {
[['sa'], newCurrent],
])
},
[updateFunc, units, config.dflt]
)
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(
sabool:
({ updateFunc, samm }) =>
(_path, newCurrent) => {
updateFunc([
// update sabool to the new current
@ -85,16 +65,4 @@ export const SaBoolSettingInput = (props) => {
[['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'
export const RendererSettingValue = ListValue
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 }) => (
const ScaleSettingValue = ({ current, config, changed }) => (
<PlainValue current={current} dflt={config.dflt} changed={changed} />
)
export const OnlySettingValue = ({ current, config }) => (
const OnlySettingValue = ({ current, config }) => (
<PlainValue
current={current?.length}
dflt={config.parts.length}
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
import { OptionsIcon } from 'shared/components/icons.mjs'
import { optionsMenuStructure } from 'shared/utils.mjs'
import { optionType, formatMm } from 'shared/utils.mjs'
import {
BoolInput,
ConstantInput,
SliderInput,
DegInput,
ListInput,
PctInput,
} from '../shared/inputs.mjs'
import {
BoolOptionValue,
ConstantOptionValue,
CountOptionValue,
DegOptionValue,
ListOptionValue,
MmOptionValue,
PctOptionValue,
} from './values.mjs'
import { optionsMenuStructure, optionType } from 'shared/utils.mjs'
import { values } from './values.mjs'
import { inputs } from './inputs.mjs'
import { WorkbenchMenu } from '../shared/index.mjs'
import { MenuItem } from '../shared/menu-item.mjs'
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 :)
const emojis = {
advanced: '🤓',
@ -77,11 +24,13 @@ const emojis = {
* @param {Object} options.settings core settings
* @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 Input = inputs[type]
const Value = values[type]
const allowOverride = ['pct', 'count', 'deg'].includes(type)
const allowToggle =
(control > 3 && type === 'bool') || (type == 'list' && config.list.length === 2)
// Hide option?
if (config?.hide || (typeof config?.hide === 'function' && config.hide(settings))) return null
@ -90,10 +39,12 @@ export const DesignOption = ({ config, settings, ...rest }) => {
<MenuItem
{...{
config,
control,
...rest,
Input,
Value,
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*/
export const CountOptionValue = ({ config, current, changed }) => (
<PlainValue {...{ current, changed, dflt: config.count }} />
@ -40,3 +37,14 @@ export const MmOptionValue = () => (
export const ConstantOptionValue = () => (
<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 { ChoiceButton } from 'shared/components/choice-button.mjs'
@ -35,31 +35,55 @@ const EditCount = (props) => (
/**
* 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 {Function} options.updateFunc the onChange
* @param {string} options.name the name of the property being changed
* @param {Function} options.setReset the setReset function passed from the parent component
* @return {ret.handleChange} the change handler for the input
* @return the change handler for the input
*/
const useSharedHandlers = ({ dflt, updateFunc, name, setReset }) => {
const reset = useCallback(() => updateFunc(name), [updateFunc, name])
const handleChange = useCallback(
const useSharedHandlers = ({ dflt, updateFunc, name }) => {
return useCallback(
(newCurrent) => {
if (newCurrent === dflt) reset()
if (newCurrent === dflt) newCurrent = undefined
else {
updateFunc(name, newCurrent)
}
},
[dflt, updateFunc, name, reset]
[dflt, updateFunc, name]
)
}
useEffect(() => {
if (typeof setReset === 'function') setReset(() => reset)
}, [reset, setReset])
/** get the configuration that allows a boolean value to use the list input */
const useBoolConfig = (name, config) => {
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 {Boolean} options.compact include descriptions with the list items?
* @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 }) => {
const { handleChange } = useSharedHandlers({
export const ListInput = ({ name, config, current, updateFunc, compact = false, t, changed }) => {
const handleChange = useSharedHandlers({
dflt: config.dflt,
updateFunc,
name,
setReset,
})
return (
@ -90,7 +112,7 @@ export const ListInput = ({ name, config, current, updateFunc, compact = false,
key={entry}
title={t(`${titleKey}${compact ? '' : '.t'}`)}
color={entry === config.dflt ? 'primary' : 'accent'}
active={current === entry}
active={changed ? current === entry : entry === config.dflt}
onClick={() => handleChange(entry)}
>
{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 */
export const BoolInput = (props) => {
const boolConfig = {
list: [0, 1],
choiceTitles: {
0: `${props.name}No`,
1: `${props.name}Yes`,
},
valueTitles: {
0: 'no',
1: 'yes',
},
dflt: props.config.dflt ? 1 : 0,
...props.config,
}
const { name, config } = props
const boolConfig = useBoolConfig(name, config)
return <ListInput {...props} config={boolConfig} />
}
@ -146,7 +157,7 @@ export const SliderInput = ({
changed,
}) => {
const { max, min } = config
const { handleChange } = useSharedHandlers({
const handleChange = useSharedHandlers({
current,
dflt: config.dflt,
updateFunc,

View file

@ -1,6 +1,7 @@
import { ClearIcon, HelpIcon, EditIcon } from 'shared/components/icons.mjs'
import { Collapse } from 'shared/components/collapse.mjs'
import { useState, useMemo } from 'react'
import { ListToggle } from './inputs.mjs'
/**
* 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) => {
if (typeof current === 'undefined') return false
if (current === config.dflt) return false
if (current == config.dflt) return false
return true
}
@ -65,27 +66,26 @@ export const MenuItem = ({
Input,
Value,
allowOverride = false,
allowToggle = false,
control = Infinity,
}) => {
// state for knowing whether the override input should be shown
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
const drillProps = useMemo(
() => ({
name,
config,
control,
current,
updateFunc,
t,
changed,
override,
setReset, // allow setting of the reset function
...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
@ -113,13 +113,13 @@ export const MenuItem = ({
<EditIcon className={`w-6 h-6 ${override ? 'bg-base-100 text-accent rounded' : ''}`} />
</button>
)
if (changed) {
if (changed && !allowToggle) {
const ResetButton = ({ open }) => (
<button
className={open ? openButtonClass : 'btn btn-accent'}
onClick={(evt) => {
evt.stopPropagation()
reset()
updateFunc(name)
}}
>
<ClearIcon />
@ -129,6 +129,10 @@ export const MenuItem = ({
openButtons.push(<ResetButton open key="clear" />)
}
if (allowToggle) {
buttons.push(<ListToggle {...{ config, changed, updateFunc, name }} />)
}
// props to pass to the ItemTitle
const titleProps = { name, t, current: <Value {...drillProps} />, emoji: config.emoji }

View file

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