+
+ )
+}
+
// Facilitate lookup of the input component
const inputs = {
bool: BoolInput,
@@ -32,7 +48,7 @@ const inputs = {
deg: DegInput,
list: ListInput,
mm: () => FIXME: Mm options are deprecated. Please report this ,
- pct: PctInput,
+ pct: PctOptionInput,
}
// Facilitate lookup of the value component
@@ -55,16 +71,13 @@ const emojis = {
groupDflt: '📁',
}
-export const DesignOption = ({
- name,
- current,
- config,
- settings,
- updateFunc,
- t,
- loadDocs,
- changed = false,
-}) => {
+/**
+ * A wrapper for {@see MenuItem} to handle design option-specific business
+ * @param {Object} options.config the config for the item
+ * @param {Object} options.settings core settings
+ * @param {Object} options.rest the rest of the props
+ */
+export const DesignOption = ({ config, settings, ...rest }) => {
const type = optionType(config)
const Input = inputs[type]
const Value = values[type]
@@ -76,22 +89,26 @@ export const DesignOption = ({
return (
)
}
+/**
+ * The design options menu
+ * @param {String} options.design the name of the design
+ * @param {Object} options.patternConfig the configuration from the pattern
+ * @param {Object} options.settings core settings
+ * @param {Object} options.update settings and ui update functions
+ * @param {String} options.language the menu language
+ * @param {Object} options.account the user account data
+ * @param {Boolean|React.component} options.DynamicDocs A docs component
+ */
export const DesignOptions = ({
design,
patternConfig,
diff --git a/sites/shared/components/workbench/menus/design-options/values.mjs b/sites/shared/components/workbench/menus/design-options/values.mjs
index 9d6d313008f..0275187a0e2 100644
--- a/sites/shared/components/workbench/menus/design-options/values.mjs
+++ b/sites/shared/components/workbench/menus/design-options/values.mjs
@@ -1,5 +1,7 @@
import { formatMm, formatPercentage } from 'shared/utils.mjs'
-import { ListValue, HighlightedValue, PlainValue } from '../shared/values'
+import { ListValue, HighlightedValue, PlainValue, BoolValue } from '../shared/values'
+
+/** Displays the current percentatge value, and the absolute value if configured */
export const PctOptionValue = ({ config, current, settings, changed }) => {
const val = changed ? current : config.pct / 100
@@ -11,34 +13,30 @@ export const PctOptionValue = ({ config, current, settings, changed }) => {
)
}
-export const BoolOptionValue = ({ config, current, t, changed }) => (
-
-)
+/** Displays a boolean value */
+export const BoolOptionValue = BoolValue
+/** Displays a count value*/
export const CountOptionValue = ({ config, current, changed }) => (
)
-export const ListOptionValue = ({ name, config, current, t, changed }) => {
- const translate = config.doNotTranslate ? (input) => input : (input) => t(`${name}.o.${input}`)
- const value = translate(changed ? current : config.dflt)
- return {value}
-}
+/** Displays a list option value */
+export const ListOptionValue = (props) => (
+ props.t(`${props.name}.o.${input}`)} />
+)
+/** Displays a degree value */
export const DegOptionValue = ({ config, current, changed }) => (
{changed ? current : config.deg}°
)
+/** Displays the MmOptions are not supported */
export const MmOptionValue = () => (
- FIXME: No MmOptionvalue implemented
+ FIXME: No Mm Options are not supported
)
+
+/** Displays that constant values are not implemented in the front end */
export const ConstantOptionValue = () => (
FIXME: No ConstantOptionvalue implemented
)
diff --git a/sites/shared/components/workbench/menus/shared/index.mjs b/sites/shared/components/workbench/menus/shared/index.mjs
index e4947c0802e..e9a23179018 100644
--- a/sites/shared/components/workbench/menus/shared/index.mjs
+++ b/sites/shared/components/workbench/menus/shared/index.mjs
@@ -6,6 +6,13 @@ import { HelpIcon } from 'shared/components/icons.mjs'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { ModalContext } from 'shared/context/modal-context.mjs'
+/**
+ * get a loadDocs method for a menu
+ * @param {DynamicDocs} DynamicDocs the docs component to use
+ * @param {Function} getDocsPath a function that accepts an item name and returns a path to its documentation
+ * @param {string} language the language to get documentation in
+ * @return {Function | false} an event handler that loads does into a modal
+ */
export const useDocsLoader = (DynamicDocs, getDocsPath, language) => {
const { setModal } = useContext(ModalContext)
return DynamicDocs
@@ -23,6 +30,25 @@ export const useDocsLoader = (DynamicDocs, getDocsPath, language) => {
: false
}
+/**
+ * A component for a collapsible sidebar menu in workbench views
+ * @param {Function} options.updateFunc a function the menu's inputs will use to update their values
+ * @param {String[]} options.ns namespaces used by this menu
+ * @param {React.Component} options.Icon the menu's icon
+ * @param {String} options.name the translation key for the menu's title
+ * @param {Object} options.config the structure of the menu's options
+ * @param {Number} options.control the user's control level setting
+ * @param {Object} options.inputs a map of input components to use, keyed by option name
+ * @param {Object} options.values a map of value components to use, keyed by option name
+ * @param {Object} options.currentValues a map of the values of the menu's options
+ * @param {Object} options.passProps any additional properties to pass the the inputs
+ * @param {DynamicDocs | Boolean} DynamicDocs the docs component to use for loading documentation
+ * @param {Function} getDocsPath a function that accepts an item name and returns a path to its documentation
+ * @param {string} language the language to use for the menu
+ * @param {Object} emojis a map of the emojis to use, keyed by option name
+ * @param {React.component} Item the component to use for menu items
+ * @return {[type]} [description]
+ */
export const WorkbenchMenu = ({
updateFunc,
ns,
@@ -41,10 +67,13 @@ export const WorkbenchMenu = ({
Item,
children,
}) => {
+ // get translation for the menu
const { t } = useTranslation(ns)
+ // get a documentation loader
const loadDocs = useDocsLoader(DynamicDocs, getDocsPath, language)
+ // get the appropriate buttons for the menu
const openButtons = []
if (loadDocs)
openButtons.push(
diff --git a/sites/shared/components/workbench/menus/shared/inputs.mjs b/sites/shared/components/workbench/menus/shared/inputs.mjs
index 37f1e2e734a..ec498e41fcb 100644
--- a/sites/shared/components/workbench/menus/shared/inputs.mjs
+++ b/sites/shared/components/workbench/menus/shared/inputs.mjs
@@ -8,6 +8,16 @@ import {
} from 'shared/utils.mjs'
import { ChoiceButton } from 'shared/components/choice-button.mjs'
+/*******************************************************************************************
+ * This file contains the base components to be used by inputs in menus in the workbench
+ * For the purposes of our menus, we have two main types:
+ * Sliders for changing numbers
+ * Lists for changing everything else
+ *
+ * Inputs that deal with more specific use cases should wrap one of the above base inputs
+ *******************************************************************************************/
+
+/** A component that shows a number input to edit a value */
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
+ */
const useSharedHandlers = ({ dflt, updateFunc, name, setReset }) => {
const reset = useCallback(() => updateFunc(name), [updateFunc, name])
@@ -49,6 +68,16 @@ const useSharedHandlers = ({ dflt, updateFunc, name, setReset }) => {
return { handleChange, reset }
}
+/**
+ * An input for selecting and item from a list
+ * @param {String} options.name the name of the property this input changes
+ * @param {Object} options.config configuration for the input
+ * @param {String|Number} options.current the current value of the input
+ * @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({
dflt: config.dflt,
@@ -78,6 +107,7 @@ 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],
@@ -96,6 +126,18 @@ export const BoolInput = (props) => {
return
}
+/**
+ * An input component that uses a slider to change a number value
+ * @param {String} options.name the name of the property being changed by the input
+ * @param {Object} options.config configuration for the input
+ * @param {Number} options.current the current value of the input
+ * @param {Function} options.updateFunc the function called by the event handler to update the value
+ * @param {Function} options.t translation function
+ * @param {Boolean} options.override open the text input to allow override of the slider?
+ * @param {String} options.suffix a suffix to append to value labels
+ * @param {Function} options.valFormatter a function that accepts a value and formats it for display as a label
+ * @param {Function} options.setReset a setter for the reset function on the parent component
+ */
export const SliderInput = ({
name,
config,
@@ -107,6 +149,7 @@ export const SliderInput = ({
valFormatter = (val) => val,
setReset,
children,
+ changed,
}) => {
const { max, min } = config
const { handleChange } = useSharedHandlers({
@@ -117,7 +160,7 @@ export const SliderInput = ({
setReset,
})
- let currentOrDefault = current === undefined ? config.dflt : current
+ let currentOrDefault = changed ? current : config.dflt
return (
<>
@@ -153,7 +196,7 @@ export const SliderInput = ({
handleChange(evt.target.value)}
className={`
range range-sm mt-1
@@ -165,12 +208,10 @@ export const SliderInput = ({
)
}
-export const PctInput = ({ config, settings, current, updateFunc, type = 'pct', ...rest }) => {
- const suffix = type === 'deg' ? '°' : '%'
- const factor = type === 'deg' ? 1 : 100
- let pctCurrent = typeof current === 'undefined' ? config.dflt : current * factor
-
- const valFormatter = (val) => round(val)
+/** A {@see SliderInput} to handle percentage values */
+export const PctInput = ({ current, changed, updateFunc, ...rest }) => {
+ const factor = 100
+ let pctCurrent = changed ? current * factor : current
const pctUpdateFunc = useCallback(
(path, newVal) => updateFunc(path, newVal === undefined ? undefined : newVal / factor),
[updateFunc, factor]
@@ -180,28 +221,18 @@ export const PctInput = ({ config, settings, current, updateFunc, type = 'pct',
-
diff --git a/sites/shared/components/workbench/menus/shared/menu-item.mjs b/sites/shared/components/workbench/menus/shared/menu-item.mjs
index 4fccf34ff31..81c4284c26d 100644
--- a/sites/shared/components/workbench/menus/shared/menu-item.mjs
+++ b/sites/shared/components/workbench/menus/shared/menu-item.mjs
@@ -2,13 +2,26 @@ import { ClearIcon, HelpIcon, EditIcon } from 'shared/components/icons.mjs'
import { Collapse } from 'shared/components/collapse.mjs'
import { useState, useMemo } from 'react'
+/**
+ * Check to see if a value is different from its default
+ * @param {Number|String|Boolean} current the current value
+ * @param {Object} config configuration containing a dflt key
+ * @return {Boolean} was the value changed?
+ */
export const wasChanged = (current, config) => {
if (typeof current === 'undefined') return false
if (current === config.dflt) return false
return true
}
-
+/**
+ * A generic component to present the title of a menu item
+ * @param {String} options.name the name of the item, to act as its translation key
+ * @param {Function} options.t the translation function
+ * @param {String|React.Component} options.current a the current value, or a Value component to display it
+ * @param {Boolean} options.open is the menu item open?
+ * @param {String} options.emoji the emoji icon of the menu item
+ */
export const ItemTitle = ({ name, t, current = null, open = false, emoji = '' }) => (
)
+/** @type {String} class to apply to buttons on open menu items */
const openButtonClass = 'btn btn-xs btn-ghost px-0'
+
+/**
+ * A generic component for handling a menu item.
+ * Wraps the given input in a {@see Collapse} with the appropriate buttons
+ * @param {String} options.name the name of the item, for using as a key
+ * @param {Object} options.config the configuration for the input
+ * @param {Sting|Boolean|Number} options.current the current value of the item
+ * @param {Function} options.updateFunc the function that will be called by event handlers to update the value
+ * @param {Function} options.t the translation function
+ * @param {Object} options.passProps props to pass to the Input component
+ * @param {Boolean} changed has the value changed from default?
+ * @param {Function} loadDocs a function to load documentation for the item into a modal
+ * @param {React.Component} Input the input component this menu item will use
+ * @param {React.Component} Value a value display component this menu item will use
+ * @param {Boolean} allowOverride all a text input to be used to override the given input component
+ * @param {Number} control the user-defined control level
+ */
export const MenuItem = ({
name,
config,
@@ -36,9 +67,12 @@ export const MenuItem = ({
allowOverride = 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,
@@ -48,14 +82,16 @@ export const MenuItem = ({
t,
changed,
override,
- setReset,
+ setReset, // allow setting of the reset function
...passProps,
}),
[name, config, current, updateFunc, t, changed, override, setReset, passProps]
)
+ // don't render if this item is more advanced than the user has chosen to see
if (config.control && config.control > control) return null
+ // get buttons for open and closed states
const buttons = []
const openButtons = []
if (loadDocs)
@@ -93,6 +129,7 @@ export const MenuItem = ({
openButtons.push()
}
+ // props to pass to the ItemTitle
const titleProps = { name, t, current: , emoji: config.emoji }
return (
@@ -108,6 +145,24 @@ export const MenuItem = ({
)
}
+/**
+ * A component for recursively displaying groups of menu items.
+ * Accepts any object where menu item configurations are keyed by name
+ * Items that are group headings are expected to have an isGroup: true property
+ * @param {Boolean} options.collapsible Should this group be collapsible (use false for the top level of a menu)
+ * @param {Number} options.control the user-defined control level
+ * @param {String} options.name the name of the group or item
+ * @param {Object} options.currentValues a map of current values for items in the group, keyed by name
+ * @param {Object} structure the configuration for the group.
+ * @param {React.Component} Item the component to use for menu items
+ * @param {Object} values a map of Value display components to be used by menu items in the group
+ * @param {Object} inputs a map of Input components to be used by menu items in the group
+ * @param {Function} loadDocs a function to load item documentation into a modal
+ * @param {Object} passProps properties to pass to Inputs within menu items
+ * @param {Object} emojis a map of emojis to use as icons for groups or items
+ * @param {Function} updateFunc the function called by change handlers on inputs within menu items
+ * @param {Function} t translation function
+ */
export const MenuItemGroup = ({
collapsible = true,
control,
@@ -123,9 +178,13 @@ export const MenuItemGroup = ({
updateFunc,
t,
}) => {
+ // map the entries in the structure
const content = Object.entries(structure).map(([itemName, item]) => {
- if (itemName === 'isMenu' || item === false) return null
- if (!item.isMenu)
+ // if it's the isGroup property, or it is false, it shouldn't be shown
+ if (itemName === 'isGroup' || item === false) return null
+
+ // if the item is not a menu, it's an Item
+ if (!item.isGroup)
return (
)
+ // otherwise, it's a group
return (
}
+ openTitle={}
+ >
+ {content}
+
+ )
}
- return collapsible ? (
- }
- openTitle={}
- >
- {content}
-
- ) : (
- content
- )
+
+ //otherwise just return the content
+ return content
}
diff --git a/sites/shared/components/workbench/menus/shared/values.mjs b/sites/shared/components/workbench/menus/shared/values.mjs
index 1fd456deba6..0abd6964fd6 100644
--- a/sites/shared/components/workbench/menus/shared/values.mjs
+++ b/sites/shared/components/workbench/menus/shared/values.mjs
@@ -1,26 +1,55 @@
import { formatMm, formatFraction128 } from 'shared/utils.mjs'
+/*********************************************************************************************************
+ * This file contains the base components to be used for displaying values in menu titles in the workbench
+ * Values that deal with more specific use cases should wrap one of the below components
+ *********************************************************************************************************/
+
+/** The basis of it all. Handles the changed/unchanged styling for the wrapped value */
export const HighlightedValue = ({ changed, children }) => (
{children}
)
+/**
+ * A wrapper for displaying the correct value based on whether or not the value has changed
+ * @param {Number|String|Boolean} options.current the current value, if it has been changed
+ * @param {Number|String|Boolean} options.dflt the default value
+ * @param {Boolean} options.changed has the value been changed?
+ */
export const PlainValue = ({ current, dflt, changed }) => (
{changed ? current : dflt}
)
+/**
+ * Displays the correct, translated value for a list
+ * @param {String|Boolean} options.current the current value, if it has been changed
+ * @param {Function} options.t a translation function
+ * @param {Object} options.config the item config
+ * @param {Boolean} options.changed has the value been changed?
+ */
export const ListValue = ({ current, t, config, changed }) => {
+ // get the values
const val = changed ? current : config.dflt
+
+ // key will be based on a few factors
let key
+ // are valueTitles configured?
if (config.valueTitles) key = config.valueTitles[val]
+ // if not, is the value a string
else if (typeof val === 'string') key = val
+ // otherwise stringify booleans
else if (val) key = 'yes'
else key = 'no'
- return {t(key)}
+ const translated = config.doNotTranslate ? key : t(key)
+
+ return {translated}
}
+/** Displays the corrent, translated value for a boolean */
export const BoolValue = ListValue
+/** Displays a formated mm value based on the current units */
export const MmValue = ({ current, config, units, changed }) => (
{
return crumbs
}
+/** convert a millimeter value to a Number value in the given units */
export const measurementAsUnits = (mmValue, units = 'metric') =>
mmValue / (units === 'imperial' ? 25.4 : 10)
@@ -199,7 +200,7 @@ export const optionsMenuStructure = (options) => {
if (typeof option === 'object') {
option.dflt = option.dflt || option[optionType(option)]
if (option.menu) {
- set(menu, `${option.menu}.isMenu`, true)
+ set(menu, `${option.menu}.isGroup`, true)
set(menu, `${option.menu}.${option.name}`, option)
} else if (typeof option.menu === 'undefined') {
console.log(