1
0
Fork 0

chore(shared): Adapted (part of) workbench for UI consistency

This commit is contained in:
joostdecock 2023-08-27 16:24:18 +02:00
parent 084e6e534c
commit 5849e6d931
22 changed files with 490 additions and 230 deletions

View file

@ -0,0 +1,58 @@
import { useState } from 'react'
/*
* DaisyUI's accordion seems rather unreliable.
* So instead, we handle this in React state
*/
export const Accordion = ({
items, // Items in the accordion
}) => {
const [active, setActive] = useState()
return (
<nav>
{items.map((item, i) => (
<button
className={`
p-2 px-4 rounded-lg bg-transparent shadow
w-full mt-2 py-4 h-auto content-start text-left bg-opacity-20
${active === i ? 'hover:bg-transparent' : 'hover:bg-secondary hover:bg-opacity-10'}
`}
onClick={() => setActive(i)}
>
{item[0]}
{active === i ? item[1] : null}
</button>
))}
</nav>
)
}
export const SubAccordion = ({
items, // Items in the accordion
}) => {
const [active, setActive] = useState()
return (
<nav>
{items.map((item, i) => (
<button
className={`
p-2 px-4 rounded
bg-transparent
w-full mt-2 py-4 h-auto content-start bg-secondary
text-left bg-opacity-20
${
active === i
? 'bg-opacity-100 hover:bg-transparent shadow'
: 'hover:bg-opacity-10 hover:bg-secondary '
}
`}
onClick={() => setActive(i)}
>
{item[0]}
{active === i ? item[1] : null}
</button>
))}
</nav>
)
}

View file

@ -109,14 +109,17 @@ export const ButtonFrame = ({
children, // Children of the button
onClick, // onClick handler
active, // Whether or not to render the button as active/selected
accordion = false, // Set this to true to not set a background color when active
}) => (
<button
className={`
btn btn-ghost btn-secondary
w-full mt-2 py-4 h-auto content-start
border-2 border-secondary text-left bg-opacity-20
hover:bg-secondary hover:text-secondary-content hover:border-secondary hover:border-solid hover:border-2
${active ? 'bg-secondary border-solid' : 'bg-transparent border-dotted'}
${accordion ? 'hover:bg-transparent' : 'hover:bg-secondary hover:bg-opacity-10'}
hover:border-secondary hover:border-solid hover:border-2
${active ? 'border-solid' : 'border-dotted'}
${active && !accordion ? 'bg-secondary' : 'bg-transparent'}
`}
onClick={onClick}
>
@ -398,9 +401,11 @@ export const ListInput = ({
<ButtonFrame key={i} active={item.val === current} onClick={() => update(item.val)}>
<div className="w-full flex flex-col gap-2">
<div className="w-full text-lg leading-5">{item.label}</div>
{item.desc ? (
<div className="w-full text-normal font-normal normal-case pt-1 leading-5">
{item.desc}
</div>
) : null}
</div>
</ButtonFrame>
))}

View file

@ -57,4 +57,11 @@ yamlEditViewTitleThing: 'Edit Pattern Configuration for {thing}'
yamlEditViewError: Issues with YAML
yamlEditViewErrorDesc: We saved your input, but it might not work for the following reasons
saveSettings: Save Settings
youUseDefaultValue: You are using a custom value
youUseCustomValue: You are using the default value
testOptions: Test design options
testOptionsDesc: Test how the design adapts to changes in a specific desing option
testMeasurements: Test measurements
testMeasurementsDesc: Test how the design adapts to changes to a specific measurement
testSets: Test measurements sets
testSetsDesc: Test how the design adapts across different measurements sets

View file

@ -16,6 +16,9 @@ import {
DocsIcon,
SearchIcon,
MeasieIcon,
XrayIcon,
EditIcon,
ExportIcon,
} from 'shared/components/icons.mjs'
import Link from 'next/link'
import { MenuWrapper } from 'shared/components/workbench/menus/shared/menu-wrapper.mjs'
@ -24,14 +27,14 @@ export const ns = ['workbench', 'sections']
const icons = {
test: BeakerIcon,
export: BriefcaseIcon,
edit: CodeIcon,
export: ExportIcon,
Edit: EditIcon,
cut: CutIcon,
draft: OptionsIcon,
print: PrintIcon,
save: UploadIcon,
logs: DocsIcon,
inspect: SearchIcon,
logs: CodeIcon,
inspect: XrayIcon,
measies: MeasieIcon,
}
@ -128,31 +131,31 @@ const NavIcons = ({ setView, setDense, dense, view }) => {
label={t('workbench:exportPattern')}
active={view === 'export'}
>
<BriefcaseIcon className={iconSize} />
<ExportIcon className={iconSize} />
</NavButton>
<NavButton
onClick={() => setView('edit')}
label={t('workbench:editSettings')}
active={view === 'edit'}
>
<CodeIcon className={iconSize} />
<EditIcon className={iconSize} />
</NavButton>
<NavButton
onClick={() => setView('logs')}
label={t('workbench:patternLogs')}
active={view === 'logs'}
>
<DocsIcon className={iconSize} />
<CodeIcon className={iconSize} />
</NavButton>
<NavButton
onClick={() => setView('inspect')}
label={t('workbench:patternInspector')}
active={view === 'inspect'}
>
<SearchIcon className={iconSize} />
<XrayIcon className={iconSize} />
</NavButton>
<NavButton label={t('workbench:docs')} href="/docs/site/draft">
<HelpIcon className={iconSize} />
<DocsIcon className={iconSize} />
</NavButton>
</>
)

View file

@ -49,4 +49,5 @@ saNo.t: Do not include seam allowance
saNo.d: This generates a pattern which does not include any seam allowance. The size of the seam allowance does not matter as no seam allowancce will be included.
saYes.t: Include seam allowance
saYes.d: This generates a pattern that will include seam allowance. The size of the seam allowance is set individually.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -71,7 +71,7 @@ export const DesignOptions = ({
isFirst = true,
DynamicDocs = false,
}) => {
const menuNs = [`o_${design}`, ...ns]
const menuNs = [design, ...ns]
const optionsMenu = useMemo(() => optionsMenuStructure(patternConfig.options), [patternConfig])
const updateFunc = useCallback(
(name, value) => update.settings(['options', ...name], value),
@ -101,6 +101,8 @@ export const DesignOptions = ({
ns: menuNs,
passProps: { settings, patternConfig },
updateFunc,
values,
isDesignOptionsGroup: true,
}}
/>
)

View file

@ -35,7 +35,7 @@ const PctOptionInput = (props) => {
export const inputs = {
bool: BoolInput,
constant: ConstantInput,
count: SliderInput,
count: (props) => <SliderInput {...props} config={{ ...props.config, step: 1 }} />,
deg: DegInput,
list: ListInput,
mm: () => <span>FIXME: Mm options are deprecated. Please report this </span>,

View file

@ -2,7 +2,7 @@ import { formatMm, formatPercentage } from 'shared/utils.mjs'
import { ListValue, HighlightedValue, PlainValue, BoolValue } from '../shared/values'
import { mergeOptions } from '@freesewing/core'
/** Displays the current percentatge value, and the absolute value if configured */
/** Displays the current percentage value, and the absolute value if configured */
export const PctOptionValue = ({ config, current, settings, changed, patternConfig }) => {
const val = changed ? current : config.pct / 100

View file

@ -1,8 +1,8 @@
import { useContext } from 'react'
//import { useContext } from 'react'
import { MenuItemGroup } from './menu-item.mjs'
import { useTranslation } from 'next-i18next'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { ModalContext } from 'shared/context/modal-context.mjs'
//import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
//import { ModalContext } from 'shared/context/modal-context.mjs'
/**
* get a loadDocs method for a menu
@ -11,6 +11,7 @@ import { ModalContext } from 'shared/context/modal-context.mjs'
* @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
@ -27,6 +28,7 @@ export const useDocsLoader = (DynamicDocs, getDocsPath, language) => {
}
: false
}
*/
/**
* A component for a collapsible sidebar menu in workbench views
@ -65,33 +67,18 @@ export const WorkbenchMenu = ({
Item,
isFirst,
children,
docsPath,
isDesignOptionsGroup,
}) => {
// get translation for the menu
const { t } = useTranslation(ns)
// get a documentation loader
const loadDocs = useDocsLoader(DynamicDocs, getDocsPath, language)
//const loadDocs = useDocsLoader(DynamicDocs, getDocsPath, language)
return (
<>
<div className="px-2" key="header">
{control > 4 ? (
isFirst ? (
''
return children ? (
children
) : (
<div className="border-t border-solid border-base-300 mx-36"></div>
)
) : (
<>
<h5 className="flex flex-row gap-2 items-center">
<Icon />
<span>{t(`${name}`)}</span>
</h5>
<p>{t(`${name}.d`)}</p>
</>
)}
</div>
{children || (
<MenuItemGroup
{...{
collapsible: false,
@ -103,14 +90,16 @@ export const WorkbenchMenu = ({
Icon,
values,
inputs,
loadDocs,
//loadDocs,
passProps,
updateFunc,
emojis,
t,
DynamicDocs,
getDocsPath,
language,
isDesignOptionsGroup,
}}
/>
)}
</>
)
}

View file

@ -9,6 +9,8 @@ import {
import { ChoiceButton } from 'shared/components/choice-button.mjs'
import debounce from 'lodash.debounce'
import { ButtonFrame } from 'shared/components/inputs.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:
@ -229,25 +231,29 @@ export const ListInput = ({ name, config, current, updateFunc, compact = false,
name,
})
return (
<>
<p>{t(`${name}.d`)}</p>
{config.list.map((entry) => {
return config.list.map((entry) => {
const titleKey = config.choiceTitles ? config.choiceTitles[entry] : `${name}.o.${entry}`
const title = t(`${titleKey}.t`)
const desc = t(`${titleKey}.d`)
const sideBySide = desc.length + title.length < 70
return (
<ChoiceButton
<ButtonFrame
key={entry}
title={t(`${titleKey}.t`)}
color={entry === config.dflt ? 'primary' : 'secondary'}
active={changed ? current === entry : entry === config.dflt}
onClick={() => handleChange(entry)}
>
{compact ? null : t(`${titleKey}.d`)}
</ChoiceButton>
)
})}
</>
<div
className={`w-full flex items-start ${
sideBySide ? 'flex-row justify-between gap-2' : 'flex-col'
}`}
>
<div className="font-bold text-lg shrink-0">{title}</div>
{compact ? null : <div className="text-base font-normal">{desc}</div>}
</div>
</ButtonFrame>
)
})
}
/** A boolean version of {@see ListInput} that sets up the necessary configuration */
@ -328,7 +334,6 @@ export const SliderInput = ({
return (
<>
<p>{t(`${name}.d`)}</p>
<div className="flex flex-row justify-between">
{override ? (
<EditCount
@ -475,7 +480,6 @@ export const ConstantInput = ({
config,
}) => (
<>
<p>{t(`${name}.d`)}</p>
<input
type={type}
className={`

View file

@ -1,7 +1,11 @@
import { ClearIcon, HelpIcon, EditIcon } from 'shared/components/icons.mjs'
import { ResetIcon, HelpIcon, EditIcon } from 'shared/components/icons.mjs'
import { Collapse } from 'shared/components/collapse.mjs'
import { useState, useMemo } from 'react'
import { ListToggle } from './inputs.mjs'
import { SubAccordion } from 'shared/components/accordion.mjs'
import { FormControl } from 'shared/components/inputs.mjs'
import { BoxIcon as GroupIcon, OptionsIcon } from 'shared/components/icons.mjs'
import { optionType } from 'shared/utils.mjs'
/**
* Check to see if a value is different from its default
@ -34,7 +38,7 @@ export const ItemTitle = ({ name, t, current = null, open = false, emoji = '', I
)
/** @type {String} class to apply to buttons on open menu items */
const openButtonClass = 'btn btn-xs btn-ghost px-0'
const iconButtonClass = 'btn btn-xs btn-ghost px-0 text-accent'
/**
* A generic component for handling a menu item.
@ -66,6 +70,9 @@ export const MenuItem = ({
allowOverride = false,
allowToggle = false,
control = Infinity,
DynamicDocs,
docsPath,
language,
}) => {
// state for knowing whether the override input should be shown
const [override, setOverride] = useState(false)
@ -91,68 +98,54 @@ export const MenuItem = ({
// get buttons for open and closed states
const buttons = []
const openButtons = []
if (loadDocs)
openButtons.push(
<button className={openButtonClass} key="help" onClick={(evt) => loadDocs(evt, name)}>
<HelpIcon className="w-6 h-6" />
</button>
)
if (allowOverride)
openButtons.push(
buttons.push(
<button
key="edit"
className={openButtonClass}
className={iconButtonClass}
onClick={(evt) => {
evt.stopPropagation()
setOverride(!override)
}}
>
<EditIcon className={`w-6 h-6 ${override ? 'bg-base-100 text-accent rounded' : ''}`} />
<EditIcon
className={`w-6 h-6 ${
override ? 'bg-secondary text-secondary-content rounded' : 'text-secondary'
}`}
/>
</button>
)
const ResetButton = ({ open, disabled = false }) => (
<button
className={`${open ? openButtonClass : 'btn btn-accent'} disabled:bg-opacity-0`}
className={`${iconButtonClass} disabled:bg-opacity-0`}
disabled={disabled}
onClick={(evt) => {
evt.stopPropagation()
updateFunc([name])
}}
>
<ClearIcon />
<ResetIcon />
</button>
)
if (changed && !allowToggle) {
buttons.push(<ResetButton key="clear" />)
}
if (allowToggle) {
buttons.push(<ListToggle key="toggle" {...{ config, changed, updateFunc, name }} />)
} else {
openButtons.push(<ResetButton open disabled={!changed} key="clear" />)
}
// props to pass to the ItemTitle
const titleProps = {
name,
t,
current: <Value {...drillProps} />,
emoji: config.emoji,
Icon: config.icon,
}
buttons.push(<ResetButton open disabled={!changed} key="clear" />)
return (
<Collapse
color={changed ? 'accent' : 'secondary'}
openTitle={<ItemTitle open {...titleProps} />}
title={<ItemTitle {...titleProps} />}
buttons={buttons}
openButtons={openButtons}
<FormControl
label={<span className="text-base font-normal">{t([`${name}.d`, name])}</span>}
docs={<DynamicDocs path={docsPath} language={language} />}
id={config.name}
labelBR={<div className="flex flex-row items-center gap-2">{buttons}</div>}
labelBL={
<span
className={`text-base font-medium -mt-2 block ${changed ? 'text-accent' : 'opacity-50'}`}
>
{t(`workbench:youUse${changed ? 'Default' : 'Custom'}Value`)}
</span>
}
>
<Input {...drillProps} />
</Collapse>
</FormControl>
)
}
@ -175,6 +168,7 @@ export const MenuItem = ({
* @param {Function} updateFunc the function called by change handlers on inputs within menu items
* @param {Boolean} topLevel is this group the top level group? false for nested
* @param {Function} t translation function
* @param {Function} getDocsPath returns the path to the docs for the current item
*/
export const MenuItemGroup = ({
collapsible = true,
@ -192,35 +186,53 @@ export const MenuItemGroup = ({
updateFunc,
topLevel = false,
t,
DynamicDocs,
language,
getDocsPath,
isDesignOptionsGroup = false,
}) => {
// map the entries in the structure
const content = Object.entries(structure).map(([itemName, item]) => {
// if it's the isGroup property, or it is false, it shouldn't be shown
if (itemName === 'isGroup' || item === false) return null
if (!item) return null
// if the item is not a menu, it's an Item
if (!item.isGroup)
return (
<Item
key={itemName}
{...{
name: itemName,
current: currentValues[itemName],
config: item,
control,
changed: wasChanged(currentValues[itemName], item),
Value: values[itemName],
Input: inputs[itemName],
t,
loadDocs,
updateFunc,
passProps,
}}
/>
const ItemIcon = item.icon
? item.icon
: item.isGroup
? GroupIcon
: Icon
? Icon
: () => <span role="img">{emoji}</span>
const Value = item.isGroup
? () => (
<div className="flex flex-row gap-2 items-center font-medium">
{Object.keys(item).filter((i) => i !== 'isGroup').length}
<OptionsIcon className="w-5 h-5" />
</div>
)
: isDesignOptionsGroup
? values[optionType(item)]
: values[itemName]
? values[itemName]
: () => <span>¯\_()_/¯</span>
// otherwise, it's a group
return (
return [
<div className="flex flex-row items-center justify-between" key="a">
<div className="flex flex-row items-center gap-4">
<ItemIcon />
<h6>{t([`${itemName}.t`, itemName])}</h6>
</div>
<div className="font-bold">
<Value
current={currentValues[itemName]}
config={item}
t={t}
changed={wasChanged(currentValues[itemName], item)}
/>
</div>
</div>,
item.isGroup ? (
<MenuItemGroup
key={itemName}
{...{
@ -240,12 +252,38 @@ export const MenuItemGroup = ({
emojis,
updateFunc,
t,
DynamicDocs,
language,
getDocsPath,
isDesignOptionsGroup,
}}
/>
)
) : (
<Item
key={itemName}
{...{
name: itemName,
current: currentValues[itemName],
config: item,
control,
changed: wasChanged(currentValues[itemName], item),
Value: values[itemName],
Input: inputs[itemName],
t,
loadDocs,
updateFunc,
passProps,
DynamicDocs,
docsPath: getDocsPath(itemName),
language,
}}
/>
),
]
})
// if it should be wrapped in a collapsible
/*
if (collapsible) {
// props to give to the group title
const titleProps = {
@ -269,7 +307,8 @@ export const MenuItemGroup = ({
</Collapse>
)
}
*/
//otherwise just return the content
return content
return <SubAccordion items={content.filter((item) => item !== null)} />
}

View file

@ -7,7 +7,7 @@ import { formatMm } from 'shared/utils.mjs'
/** The basis of it all. Handles the changed/unchanged styling for the wrapped value */
export const HighlightedValue = ({ changed, children }) => (
<span className={changed ? 'text-info' : ''}> {children} </span>
<span className={changed ? 'text-accent' : ''}> {children} </span>
)
/**

View file

@ -11,6 +11,7 @@ import {
useMaterialList,
useMaterialLength,
} from './hooks'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = [...menuNs, ...wrapperNs]
@ -110,6 +111,8 @@ export const CutView = ({
</div>
),
menu: (
<>
<V3Wip />
<CutMenu
{...{
design,
@ -125,6 +128,7 @@ export const CutView = ({
setSettings,
}}
/>
</>
),
}}
/>

View file

@ -4,12 +4,15 @@ import {
} from 'shared/components/workbench/menus/design-options/index.mjs'
import {
CoreSettings,
ClearAllButton,
ns as coreMenuNs,
} from 'shared/components/workbench/menus/core-settings/index.mjs'
import { UiSettings, ns as uiNs } from 'shared/components/workbench/menus/ui-settings/index.mjs'
import { useTranslation } from 'next-i18next'
import { nsMerge } from 'shared/utils.mjs'
import { SettingsIcon, OptionsIcon, DesktopIcon } from 'shared/components/icons.mjs'
import { Accordion } from 'shared/components/accordion.mjs'
export const ns = [...coreMenuNs, ...designMenuNs, ...uiNs]
export const ns = nsMerge(coreMenuNs, designMenuNs, uiNs)
export const DraftMenu = ({
design,
@ -24,6 +27,7 @@ export const DraftMenu = ({
view,
setView,
}) => {
const { t } = useTranslation()
const control = account.control
const menuProps = {
design,
@ -36,12 +40,39 @@ export const DraftMenu = ({
control,
}
const sections = [
{
name: 'designOptions',
ns: 'design-options',
icon: <OptionsIcon className="w-8 h-8" />,
menu: <DesignOptions {...menuProps} />,
},
{
name: 'coreSettings',
ns: 'core-settings',
icon: <SettingsIcon className="w-8 h-8" />,
menu: <CoreSettings {...menuProps} />,
},
{
name: 'uiSettings',
ns: 'ui-settings',
icon: <DesktopIcon className="w-8 h-8" />,
menu: <UiSettings {...menuProps} {...{ ui, view, setView }} />,
},
]
return (
<nav>
<DesignOptions {...menuProps} />
<CoreSettings {...menuProps} />
<UiSettings {...menuProps} {...{ ui, view, setView }} />
<ClearAllButton setSettings={setSettings} />
</nav>
<Accordion
items={sections.map((section) => [
<>
<h5 className="flex flex-row gap-2 items-center justify-between w-full">
<span>{t(`${section.ns}:${section.name}.t`)}</span>
{section.icon}
</h5>
<p>{t(`${section.ns}:${section.name}.d`)}</p>
</>,
section.menu,
])}
/>
)
}

View file

@ -5,6 +5,7 @@ import { useTranslation } from 'next-i18next'
import { useToast } from 'shared/hooks/use-toast.mjs'
import { CloseIcon } from 'shared/components/icons.mjs'
import { capitalize } from 'shared/utils.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = ['wbedit']
@ -52,6 +53,7 @@ export const EditView = ({ settings, setSettings, design, Design }) => {
return (
<div className="max-w-screen-xl m-auto h-screen form-control mt-4 flex flex-col">
<h2>{t('yamlEditViewTitleThing', { thing: capitalize(design) })}</h2>
<V3Wip />
<div id="editor" className="h-2/3 my-2 overflow-auto flex flex-col">
{error && (
<div className={`w-full shadow bg-base-100 p-0 my-4`}>

View file

@ -9,6 +9,7 @@ import {
handleExport,
ns as exportNs,
} from 'shared/components/workbench/exporting/export-handler.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = ['exporting', exportNs]
@ -45,6 +46,7 @@ export const ExportView = ({ settings, ui, design, Design }) => {
return (
<div className="max-w-screen-xl m-auto py-8">
<h2>{t('export')}</h2>
<V3Wip />
<p className="text-lg sm:text-xl">{t('exportPattern-txt')}</p>
{link && (
<Popout link compact>

View file

@ -3,6 +3,7 @@ import { InspectorPattern } from './inspector/pattern.mjs'
import { DraftMenu, ns as menuNs } from './menu.mjs'
import { objUpdate } from 'shared/utils.mjs'
import { PatternWithMenu, ns as wrapperNs } from '../pattern-with-menu.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = [...menuNs, ...wrapperNs]
@ -76,6 +77,8 @@ export const InspectView = ({
setSettings,
pattern: output,
menu: (
<>
<V3Wip />
<DraftMenu
{...{
design,
@ -94,6 +97,7 @@ export const InspectView = ({
setView,
}}
/>
</>
),
}}
/>

View file

@ -5,6 +5,7 @@ import {
ClearAllButton,
ns as coreMenuNs,
} from 'shared/components/workbench/menus/core-settings/index.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = ['logs', ...coreMenuNs]
@ -88,6 +89,7 @@ export const LogView = ({ pattern, settings, setSettings }) => {
<h1 className="grow">{t('logs')}</h1>
<ClearAllButton setSettings={setSettings} />
</div>
<V3Wip />
{Object.entries(logs).map(([type, lines], key) => (
<DraftLogs key={key} {...{ type, lines, t }} />
))}

View file

@ -13,6 +13,7 @@ import { PrintIcon, RightIcon } from 'shared/components/icons.mjs'
import { LoadingContext } from 'shared/context/loading-context.mjs'
import { useToast } from 'shared/hooks/use-toast.mjs'
import { PatternWithMenu, ns as wrapperNs } from '../pattern-with-menu.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
const viewNs = ['print', ...exportNs]
export const ns = [...viewNs, ...menuNs, ...wrapperNs]
@ -114,6 +115,8 @@ export const PrintView = ({
/>
),
menu: (
<>
<V3Wip />
<PrintMenu
{...{
design,
@ -129,6 +132,7 @@ export const PrintView = ({
exportIt,
}}
/>
</>
),
}}
/>

View file

@ -10,6 +10,7 @@ import { useToast } from 'shared/hooks/use-toast.mjs'
import { LoadingContext } from 'shared/context/loading-context.mjs'
// Components
import { Spinner } from 'shared/components/spinner.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = ['wbsave']
@ -245,6 +246,7 @@ export const SaveView = ({ design, settings, from = false }) => {
return (
<div className="m-auto mt-24">
<h1 className="max-w-6xl m-auto text-center">{t('wbsave:title')}</h1>
<V3Wip />
<div className="px-4 lg:px-12 flex flex-row flex-wrap gap-4 lg:gap-8 justify-around">
{info.new ? <SaveNewPattern {...saveProps} /> : null}
{info.edit ? <SaveExistingPattern {...saveProps} from={from} /> : null}

View file

@ -1,6 +1,29 @@
import { TestOptions, ns as optionsNs } from './options.mjs'
import { TestMeasurements, ns as measieNs } from './measurements.mjs'
export const ns = [...optionsNs, ...measieNs]
import { ns as optionsNs } from './options.mjs'
import { ns as measieNs } from './measurements.mjs'
import { Accordion } from 'shared/components/accordion.mjs'
import { useTranslation } from 'next-i18next'
import { nsMerge } from 'shared/utils.mjs'
import { OptionsIcon, MeasieIcon, CommunityIcon } from 'shared/components/icons.mjs'
import { ListInput } from 'shared/components/inputs.mjs'
import { optionsMenuStructure } from 'shared/utils.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
export const ns = nsMerge('workbench', optionsNs, measieNs)
const flattenOptions = (options, list = false, path = []) => {
if (list === false) return flattenOptions(optionsMenuStructure(options), new Set())
for (const [key, option] of Object.entries(options)) {
if (key !== 'isGroup') {
if (!option.isGroup) list.add({ key, option, path })
else list = flattenOptions(option, list, [...path, key])
}
}
return list
}
const spacer = <span className="px-2 opacity-50">/</span>
export const TestMenu = ({
design,
@ -11,22 +34,72 @@ export const TestMenu = ({
account,
DynamicDocs,
}) => {
const control = account.control
const menuProps = {
design,
patternConfig,
settings,
update,
language,
account,
DynamicDocs,
control,
}
const { t } = useTranslation(ns)
const allOptions = flattenOptions(patternConfig.options)
return (
<nav>
<TestOptions {...menuProps} />
<TestMeasurements {...menuProps} />
</nav>
<Accordion
items={[
[
<>
<h5 className="flex flex-row gap-2 items-center justify-between w-full">
<span>{t('workbench:testOptions')}</span>
<OptionsIcon className="w-8 h-8" />
</h5>
<p>{t('workbench:testOptionsDesc')}</p>
</>,
<ListInput
list={[...allOptions].map((option) => ({
label: [
...option.path.map((p) => (
<>
<span>{t(`${p}.t`)}</span>
{spacer}
</>
)),
<span>{t(`${design}:${option.key}.t`)}</span>,
],
val: option.key,
}))}
update={(value) => {
if (value) update.settings(['sample'], { type: 'option', option: value })
else update.settings(['sample'])
}}
current={settings.sample.option}
/>,
],
[
<>
<h5 className="flex flex-row gap-2 items-center justify-between w-full">
<span>{t('workbench:testMeasurements')}</span>
<MeasieIcon className="w-8 h-8" />
</h5>
<p>{t('workbench:testOptionsDesc')}</p>
</>,
<ListInput
list={patternConfig.measurements.map((m) => ({
label: t(m),
val: m,
}))}
update={(value) => {
if (value) update.settings(['sample'], { type: 'measurement', measurement: value })
else update.settings(['sample'])
}}
current={settings.sample.measurement}
/>,
],
[
<>
<h5 className="flex flex-row gap-2 items-center justify-between w-full">
<span>{t('workbench:testSets')}</span>
<CommunityIcon className="w-8 h-8" />
</h5>
<p>{t('workbench:testSetsDesc')}</p>
</>,
<V3Wip />,
],
]}
/>
)
}

View file

@ -11,6 +11,7 @@ import {
DetailIcon,
IconWrapper,
ClearIcon,
ResetIcon,
} from 'shared/components/icons.mjs'
import { ClearAllButton } from 'shared/components/workbench/menus/core-settings/index.mjs'
import { shownHeaderSelector } from 'shared/components/wrappers/header.mjs'
@ -94,7 +95,7 @@ export const ViewHeader = ({ update, settings, ui, control, setSettings }) => {
'lg:top-24'
)} transition-[top] duration-300 ease-in-out`}
>
<div className="hidden lg:flex flex-row flex-wrap gap-4 py-4 pt-4 w-full bg-neutral text-neutral-content items-center justify-center">
<div className="hidden lg:flex flex-row flex-wrap gap-4 py-4 pt-8 w-full bg-neutral text-neutral-content items-center justify-center">
{headerZoomButtons}
<Spacer />
<div className="flex flex-row items-center gap-4">
@ -156,7 +157,34 @@ export const ViewHeader = ({ update, settings, ui, control, setSettings }) => {
</div>
<Spacer />
<div className="flex flex-row items-center gap-4">
<ClearAllButton setSettings={setSettings} compact />
<button
onClick={() => setSettings({ measurements: settings.measurements })}
className={`tooltip tooltip-primary tooltip-bottom flex flex-row items-center`}
data-tip={t('core-settings:clearSettingsNotMeasurements')}
disabled={typeof settings.options === 'undefined'}
>
<ResetIcon
stroke={3.5}
className={`w-6 h-6 ${
typeof settings.options === 'undefined' ? 'text-base-100 opacity-30' : 'text-accent'
}`}
/>
</button>
<button
onClick={() => setSettings({})}
className="tooltip tooltip-primary tooltip-bottom flex flex-row items-center text-warning"
data-tip={t('core-settings:clearSettingsAndMeasurements')}
disabled={!(settings.measurements && Object.keys(settings.measurements).length > 0)}
>
<ResetIcon
stroke={3.5}
className={`w-6 h-6 ${
!(settings.measurements && Object.keys(settings.measurements).length > 0)
? 'text-base-100 opacity-30'
: 'text-warning'
}`}
/>
</button>
</div>
</div>
</div>