feat: Added test view
This commit is contained in:
parent
0dba8dcac0
commit
a97f9b6e8a
8 changed files with 470 additions and 2 deletions
|
@ -41,6 +41,7 @@ import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
||||||
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
||||||
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
|
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
|
||||||
import { LayoutSettingsMenu } from './menus/LayoutMenu.mjs'
|
import { LayoutSettingsMenu } from './menus/LayoutMenu.mjs'
|
||||||
|
import { TestOptionsMenu, TestMeasurementsMenu } from './menus/TestMenu.mjs'
|
||||||
import { FlagsAccordionEntries } from './Flag.mjs'
|
import { FlagsAccordionEntries } from './Flag.mjs'
|
||||||
import { UndoStep } from './views/UndosView.mjs'
|
import { UndoStep } from './views/UndosView.mjs'
|
||||||
|
|
||||||
|
@ -85,6 +86,54 @@ export const HeaderMenuDraftView = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HeaderMenuTestView = (props) => {
|
||||||
|
const i18n = useDesignTranslation(props.Design.designConfig.data.id)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderMenuTestViewDesignOptions {...props} i18n={i18n} />
|
||||||
|
<HeaderMenuTestViewDesignMeasurements {...props} />
|
||||||
|
<HeaderMenuTestIcons {...props} i18n={i18n} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HeaderMenuTestViewDesignOptions = (props) => {
|
||||||
|
return (
|
||||||
|
<HeaderMenuDropdown
|
||||||
|
{...props}
|
||||||
|
id="designOptions"
|
||||||
|
tooltip="These options are specific to this design. You can use them to customize your pattern in a variety of ways."
|
||||||
|
toggle={
|
||||||
|
<>
|
||||||
|
<HeaderMenuIcon name="options" extraClasses="tw-text-secondary" />
|
||||||
|
<span className="tw-hidden lg:tw-inline">Test Options</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TestOptionsMenu {...props} />
|
||||||
|
</HeaderMenuDropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HeaderMenuTestViewDesignMeasurements = (props) => {
|
||||||
|
return (
|
||||||
|
<HeaderMenuDropdown
|
||||||
|
{...props}
|
||||||
|
id="designMeasurements"
|
||||||
|
tooltip="These options are specific to this design. You can use them to customize your pattern in a variety of ways."
|
||||||
|
toggle={
|
||||||
|
<>
|
||||||
|
<HeaderMenuIcon name="options" extraClasses="tw-text-secondary" />
|
||||||
|
<span className="tw-hidden lg:tw-inline">Test Measurements</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TestMeasurementsMenu {...props} />
|
||||||
|
</HeaderMenuDropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const HeaderMenuDropdown = (props) => {
|
export const HeaderMenuDropdown = (props) => {
|
||||||
const { tooltip, toggle, open, setOpen, id, end = false } = props
|
const { tooltip, toggle, open, setOpen, id, end = false } = props
|
||||||
/*
|
/*
|
||||||
|
@ -381,6 +430,24 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HeaderMenuTestIcons = (props) => {
|
||||||
|
const { update, state, Design } = props
|
||||||
|
const Button = HeaderMenuButton
|
||||||
|
const size = 'tw-w-5 tw-h-5'
|
||||||
|
const undos = state._?.undos && state._.undos.length > 0 ? state._.undos : false
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-0.5 lg:tw-px-1">
|
||||||
|
<Button
|
||||||
|
updateHandler={() => update.settings('sample', undefined)}
|
||||||
|
tooltip="Clear the test so you can select another"
|
||||||
|
>
|
||||||
|
Clear Test
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const HeaderMenuSaveIcons = (props) => {
|
export const HeaderMenuSaveIcons = (props) => {
|
||||||
const { update, state } = props
|
const { update, state } = props
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
|
@ -498,7 +565,9 @@ export const HeaderMenuViewMenu = (props) => {
|
||||||
toggle={
|
toggle={
|
||||||
<>
|
<>
|
||||||
<HeaderMenuIcon name="right" stroke={3} extraClasses="tw-text-secondary tw-rotate-90" />
|
<HeaderMenuIcon name="right" stroke={3} extraClasses="tw-text-secondary tw-rotate-90" />
|
||||||
<span className="tw-hidden lg:tw-inline">Views</span>
|
<span className="tw-hidden lg:tw-inline">
|
||||||
|
{viewLabels[state.view] ? viewLabels[state.view].t : 'Views'}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -611,6 +680,7 @@ export const HeaderMenuLayoutViewIcons = (props) => {
|
||||||
|
|
||||||
const headerMenus = {
|
const headerMenus = {
|
||||||
draft: HeaderMenuDraftView,
|
draft: HeaderMenuDraftView,
|
||||||
|
test: HeaderMenuTestView,
|
||||||
layout: HeaderMenuLayoutView,
|
layout: HeaderMenuLayoutView,
|
||||||
//HeaderMenuDraftViewDesignOptions,
|
//HeaderMenuDraftViewDesignOptions,
|
||||||
//HeaderMenuDraftViewCoreSettings,
|
//HeaderMenuDraftViewCoreSettings,
|
||||||
|
|
|
@ -174,6 +174,7 @@ export const MenuItemGroup = ({
|
||||||
i18n,
|
i18n,
|
||||||
}) => {
|
}) => {
|
||||||
if (!Item) Item = MenuItem
|
if (!Item) Item = MenuItem
|
||||||
|
console.log(structure)
|
||||||
|
|
||||||
// map the entries in the structure
|
// map the entries in the structure
|
||||||
const content = Object.entries(structure).map(([itemName, item]) => {
|
const content = Object.entries(structure).map(([itemName, item]) => {
|
||||||
|
@ -285,3 +286,79 @@ export const MenuItemTitle = ({ name, current = null, open = false, emoji = '',
|
||||||
<span className="tw-font-bold">{current}</span>
|
<span className="tw-font-bold">{current}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component for recursively displaying groups of menu buttons.
|
||||||
|
*
|
||||||
|
* This is a lot simpler than the options menu structure
|
||||||
|
*
|
||||||
|
* @param {object} props - All the React props
|
||||||
|
* @param {object} props.structure - The menu structure
|
||||||
|
* @param {React.Component} props.Button - The component to use for menu items
|
||||||
|
* @param {freesewing.Design} props.Design - The FreeSewing design
|
||||||
|
* @param {object} props.i18n - The translations object
|
||||||
|
*/
|
||||||
|
export const MenuButtonGroup = ({ structure, Button = false, Design, Icon, i18n }) => {
|
||||||
|
if (!Button) return null
|
||||||
|
/*
|
||||||
|
* Create structure, push groups to the end
|
||||||
|
*/
|
||||||
|
const content = []
|
||||||
|
for (const [itemName, item] of Object.entries(structure)) {
|
||||||
|
if (item && !item.isGroup && !['isGroup', 'title'].includes(itemName))
|
||||||
|
content.push(<Button key={itemName} {...{ name: itemName }} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [itemName, item] of Object.entries(structure)) {
|
||||||
|
if (item && item.isGroup) {
|
||||||
|
const ItemIcon = item.icon
|
||||||
|
? item.icon
|
||||||
|
: item.isGroup
|
||||||
|
? GroupIcon
|
||||||
|
: Icon
|
||||||
|
? Icon
|
||||||
|
: () => <span role="img">fixme-icon</span>
|
||||||
|
const Value = item.isGroup
|
||||||
|
? () => (
|
||||||
|
<div className="tw-flex tw-flex-row tw-gap-2 tw-items-center tw-font-medium">
|
||||||
|
{Object.keys(item).filter((i) => i !== 'isGroup').length}
|
||||||
|
<OptionsIcon className="tw-w-5 tw-h-5" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
|
||||||
|
content.push([
|
||||||
|
<div
|
||||||
|
className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full tw-pl-0 tw-pr-4 tw-py-2"
|
||||||
|
key="a"
|
||||||
|
>
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-4 tw-w-full">
|
||||||
|
<ItemIcon />
|
||||||
|
<span className="tw-font-medium tw-capitalize">
|
||||||
|
{item.title ? item.title : getItemLabel(i18n, itemName)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="tw-font-bold">
|
||||||
|
<Value config={item} Design={Design} />
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
<MenuButtonGroup
|
||||||
|
key={itemName}
|
||||||
|
{...{
|
||||||
|
structure: item,
|
||||||
|
Icon,
|
||||||
|
Button,
|
||||||
|
Design,
|
||||||
|
i18n,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-0.5 tw-ml-4">
|
||||||
|
{content.filter((item) => item !== null)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
106
packages/react/components/Editor/components/menus/TestMenu.mjs
Normal file
106
packages/react/components/Editor/components/menus/TestMenu.mjs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// Dependencies
|
||||||
|
import { menuDesignOptionsStructure } from '../../lib/index.mjs'
|
||||||
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
|
import { orderBy } from '@freesewing/utils'
|
||||||
|
// Hooks
|
||||||
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
// Components
|
||||||
|
import { MenuButtonGroup } from './Container.mjs'
|
||||||
|
import { BeakerIcon, OptionsIcon } from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test design options menu
|
||||||
|
*
|
||||||
|
* @param {object} props.Design - An object holding the Design instance
|
||||||
|
* @param {Object} props.state - Object holding state
|
||||||
|
* @param {Object} props.i18n - Object holding translations loaded from the design
|
||||||
|
* @param {Object} props.update - Object holding state handlers
|
||||||
|
*/
|
||||||
|
export const TestOptionsMenu = ({ Design, state, i18n, update }) => {
|
||||||
|
const structure = useMemo(
|
||||||
|
() =>
|
||||||
|
menuDesignOptionsStructure(
|
||||||
|
Design.designConfig.data.id,
|
||||||
|
Design.patternConfig.options,
|
||||||
|
state.settings
|
||||||
|
),
|
||||||
|
[Design.designConfig.data.id, Design.patternConfig, state.settings]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuButtonGroup
|
||||||
|
{...{
|
||||||
|
structure,
|
||||||
|
ux: state.ui.ux,
|
||||||
|
Icon: OptionsIcon,
|
||||||
|
Button: (props) => <SampleOptionButton {...{ i18n, update }} {...props} />,
|
||||||
|
name: 'Design Options',
|
||||||
|
isDesignOptionsGroup: true,
|
||||||
|
state,
|
||||||
|
Design,
|
||||||
|
i18n,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test measurements options menu
|
||||||
|
*
|
||||||
|
* @param {object} props.Design - An object holding the Design instance
|
||||||
|
* @param {Object} props.state - Object holding state
|
||||||
|
* @param {Object} props.update - Object holding state handlers
|
||||||
|
*/
|
||||||
|
export const TestMeasurementsMenu = ({ Design, state, update }) => {
|
||||||
|
const structure = {}
|
||||||
|
if (Design.patternConfig.measurements.length > 0)
|
||||||
|
structure.required = { isGroup: true, title: 'Required Measurements' }
|
||||||
|
for (const m of Design.patternConfig.measurements) {
|
||||||
|
structure.required[m] = { isGroup: false, name: m, title: m }
|
||||||
|
}
|
||||||
|
if (Design.patternConfig.optionalMeasurements.length > 0)
|
||||||
|
structure.optional = { isGroup: true, title: 'Optional Measurements' }
|
||||||
|
for (const m of Design.patternConfig.optionalMeasurements) {
|
||||||
|
structure.optional[m] = { isGroup: false, name: m, title: m }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuButtonGroup
|
||||||
|
{...{
|
||||||
|
structure,
|
||||||
|
Icon: OptionsIcon,
|
||||||
|
Button: (props) => <SampleMeasurementButton {...{ update }} {...props} />,
|
||||||
|
name: 'Design Measurments',
|
||||||
|
isDesignOptionsGroup: true,
|
||||||
|
state,
|
||||||
|
Design,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SampleOptionButton = ({ name, i18n, update }) => (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
'tw-daisy-btn tw-daisy-btn-outline tw-daisy-btn-sm tw-mx-2 ' +
|
||||||
|
'tw-daisy-btn-secondary tw-flex tw-flex-row tw-items-center tw-justify-between'
|
||||||
|
}
|
||||||
|
onClick={() => update.settings('sample', { type: 'option', option: name })}
|
||||||
|
>
|
||||||
|
<BeakerIcon className="tw-w-5 tw-h-5" />
|
||||||
|
<span>{i18n.en.o[name].t}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SampleMeasurementButton = ({ name, i18n, update }) => (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
'tw-daisy-btn tw-daisy-btn-outline tw-daisy-btn-sm tw-mx-2 ' +
|
||||||
|
'tw-daisy-btn-secondary tw-flex tw-flex-row tw-items-center tw-justify-between'
|
||||||
|
}
|
||||||
|
onClick={() => update.settings('sample', { type: 'option', option: name })}
|
||||||
|
>
|
||||||
|
<BeakerIcon className="tw-w-5 tw-h-5" />
|
||||||
|
<span>{measurementsTranslations[name]}</span>
|
||||||
|
</button>
|
||||||
|
)
|
|
@ -5,7 +5,6 @@ import { draft, missingMeasurements } from '../../lib/index.mjs'
|
||||||
import { Null } from '../Null.mjs'
|
import { Null } from '../Null.mjs'
|
||||||
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||||
import { PatternLayout } from '../PatternLayout.mjs'
|
import { PatternLayout } from '../PatternLayout.mjs'
|
||||||
import { DraftMenu } from '../menus/DraftMenu.mjs'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The draft view allows users to tweak their pattern
|
* The draft view allows users to tweak their pattern
|
||||||
|
|
181
packages/react/components/Editor/components/views/TestView.mjs
Normal file
181
packages/react/components/Editor/components/views/TestView.mjs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// Dependencies
|
||||||
|
import React, { useMemo, useCallback } from 'react'
|
||||||
|
import { sample, missingMeasurements, menuDesignOptionsStructure } from '../../lib/index.mjs'
|
||||||
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
|
import { orderBy } from '@freesewing/utils'
|
||||||
|
// Components
|
||||||
|
import { Null } from '../Null.mjs'
|
||||||
|
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||||
|
import { PatternLayout } from '../PatternLayout.mjs'
|
||||||
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
|
import { H1, H3, H4, H5 } from '@freesewing/react/components/Heading'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test view allows users to test options and measurements
|
||||||
|
*
|
||||||
|
* @param (object) props - All the props
|
||||||
|
* @param {function} props.config - The editor configuration
|
||||||
|
* @param {function} props.Design - The design constructor
|
||||||
|
* @param {array} props.missingMeasurements - List of missing measurements for the current design
|
||||||
|
* @param {object} props.state - The ViewWrapper state object
|
||||||
|
* @param {object} props.state.settings - The current settings
|
||||||
|
* @param {object} props.update - Helper object for updating the ViewWrapper state
|
||||||
|
* @return {function} DraftView - React component
|
||||||
|
*/
|
||||||
|
export const TestView = ({ Design, state, update, config }) => {
|
||||||
|
/*
|
||||||
|
* Don't trust that we have all measurements
|
||||||
|
*
|
||||||
|
* We do not need to change the view here. That is done in the central
|
||||||
|
* ViewWrapper componenet. However, checking the measurements against
|
||||||
|
* the design takes a brief moment, so this component will typically
|
||||||
|
* render before that happens, and if measurements are missing it will
|
||||||
|
* throw and error.
|
||||||
|
*
|
||||||
|
* So when measurements are missing, we just return here and the view
|
||||||
|
* will switch on the next render loop.
|
||||||
|
*/
|
||||||
|
if (missingMeasurements(state)) return <Null />
|
||||||
|
|
||||||
|
const { settings } = state
|
||||||
|
if (settings.sample) {
|
||||||
|
const { pattern } = sample(Design, settings)
|
||||||
|
const renderProps = pattern.getRenderProps()
|
||||||
|
const output = (
|
||||||
|
<ZoomablePattern
|
||||||
|
renderProps={renderProps}
|
||||||
|
patternLocale={state.locale || 'en'}
|
||||||
|
rotate={state.ui.rotate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return <PatternLayout {...{ update, Design, output, state, pattern, config }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translated measurements
|
||||||
|
const trm = orderBy(
|
||||||
|
Design.patternConfig.measurements.map((m) => ({ m, t: measurementsTranslations[m] })),
|
||||||
|
't',
|
||||||
|
'ASC'
|
||||||
|
)
|
||||||
|
const tom = orderBy(
|
||||||
|
Design.patternConfig.optionalMeasurements.map((m) => ({ m, t: measurementsTranslations[m] })),
|
||||||
|
't',
|
||||||
|
'ASC'
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderMenu state={state} {...{ config, update, Design }} />
|
||||||
|
<div className="tw-m-auto tw-mt-8 tw-max-w-4xl tw-px-4 tw-mb-8">
|
||||||
|
<H1>Test Pattern</H1>
|
||||||
|
<div className="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 lg:tw-gap-4">
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-4">
|
||||||
|
<H3>Test Design Options</H3>
|
||||||
|
<SampleOptionsMenu {...{ Design, state, update }} />
|
||||||
|
</div>
|
||||||
|
{trm.length > 0 ? (
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-4">
|
||||||
|
<H3>Test Measurements</H3>
|
||||||
|
<H4>Required Measurements</H4>
|
||||||
|
<div className="">
|
||||||
|
{trm.map(({ t, m }) => (
|
||||||
|
<button
|
||||||
|
key={m}
|
||||||
|
className="tw-my-0.5 tw-block tw-daisy-btn tw-daisy-btn-primary tw-daisy-btn-outline tw-daisy-btn-xs"
|
||||||
|
onClick={() =>
|
||||||
|
update.settings(['sample'], { type: 'measurement', measurement: m })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{tom.length > 0 ? (
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-4">
|
||||||
|
<H4>Optional Measurements</H4>
|
||||||
|
<div className="">
|
||||||
|
{tom.map(({ t, m }) => (
|
||||||
|
<button
|
||||||
|
key={m}
|
||||||
|
className="tw-my-0.5 tw-block tw-daisy-btn tw-daisy-btn-primary tw-daisy-btn-outline tw-daisy-btn-xs"
|
||||||
|
onClick={() =>
|
||||||
|
update.settings(['sample'], { type: 'measurement', measurement: m })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SampleOptionsMenu = ({ Design, state, update }) => {
|
||||||
|
const structure = useMemo(
|
||||||
|
() =>
|
||||||
|
menuDesignOptionsStructure(
|
||||||
|
Design.designConfig.data.id,
|
||||||
|
Design.patternConfig.options,
|
||||||
|
state.settings
|
||||||
|
),
|
||||||
|
[Design.designConfig.data.id, Design.patternConfig, state.settings]
|
||||||
|
)
|
||||||
|
|
||||||
|
return <SampleOptionsSubMenu structure={structure} update={update} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const SampleOptionsSubMenu = ({ structure, update, level = 1 }) => {
|
||||||
|
const output = []
|
||||||
|
/*
|
||||||
|
* Show entries alphabetic, but force groups last, and advanced last among them
|
||||||
|
*/
|
||||||
|
const titles = Object.keys(structure)
|
||||||
|
.filter((key) => key !== 'isGroup')
|
||||||
|
.sort()
|
||||||
|
const order = [
|
||||||
|
...titles.filter((key) => key !== 'advanced'),
|
||||||
|
...titles.filter((key) => key === 'advanced'),
|
||||||
|
]
|
||||||
|
// Non-groups first
|
||||||
|
for (const name of order) {
|
||||||
|
const struct = structure[name]
|
||||||
|
if (!struct.isGroup)
|
||||||
|
output.push(
|
||||||
|
<button
|
||||||
|
key={name}
|
||||||
|
className="tw-my-0.5 tw-block tw-daisy-btn tw-daisy-btn-primary tw-daisy-btn-outline tw-daisy-btn-xs"
|
||||||
|
onClick={() => update.settings(['sample'], { type: 'option', option: name })}
|
||||||
|
>
|
||||||
|
{struct.title}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Then groups
|
||||||
|
for (const name of order) {
|
||||||
|
const struct = structure[name]
|
||||||
|
if (struct.isGroup) {
|
||||||
|
output.push(
|
||||||
|
<H5 key={name}>
|
||||||
|
<span className="tw-capitalize">{name}</span>
|
||||||
|
</H5>
|
||||||
|
)
|
||||||
|
output.push(
|
||||||
|
<SampleOptionsSubMenu
|
||||||
|
structure={struct}
|
||||||
|
update={update}
|
||||||
|
level={level + 1}
|
||||||
|
key={name + 's'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="tw-pl-2 tw-border-l-2">{output}</div>
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import { UndosView } from './UndosView.mjs'
|
||||||
import { LayoutView } from './LayoutView.mjs'
|
import { LayoutView } from './LayoutView.mjs'
|
||||||
import { DocsView } from './DocsView.mjs'
|
import { DocsView } from './DocsView.mjs'
|
||||||
import { LogView } from './LogView.mjs'
|
import { LogView } from './LogView.mjs'
|
||||||
|
import { TestView } from './TestView.mjs'
|
||||||
import { EditSettingsView } from './EditSettingsView.mjs'
|
import { EditSettingsView } from './EditSettingsView.mjs'
|
||||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||||
import {
|
import {
|
||||||
|
@ -67,6 +68,7 @@ export const View = (props) => {
|
||||||
if (view === 'editSettings') return <EditSettingsView {...props} />
|
if (view === 'editSettings') return <EditSettingsView {...props} />
|
||||||
if (view === 'inspect') return <InspectView {...props} />
|
if (view === 'inspect') return <InspectView {...props} />
|
||||||
if (view === 'logs') return <LogView {...props} />
|
if (view === 'logs') return <LogView {...props} />
|
||||||
|
if (view === 'test') return <TestView {...props} />
|
||||||
|
|
||||||
return <h1 className="tw-ext-center tw-my-12">No view component for view {props.view}</h1>
|
return <h1 className="tw-ext-center tw-my-12">No view component for view {props.view}</h1>
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,37 @@ export function draft(Design, settings, plugins = []) {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This method samples a pattern option
|
||||||
|
*
|
||||||
|
* @param {function} Design - The Design constructor
|
||||||
|
* @param {object} settings - The settings for the pattern
|
||||||
|
* @param {array} plugins - Any (extra) plugins to load into the pattern
|
||||||
|
* @return {object} data - The drafted pattern, along with errors and failure data
|
||||||
|
*/
|
||||||
|
export function sample(Design, settings, plugins = []) {
|
||||||
|
const pattern = new Design(settings)
|
||||||
|
for (const plugin of plugins) pattern.use(plugin)
|
||||||
|
const data = {
|
||||||
|
// The pattern
|
||||||
|
pattern,
|
||||||
|
// Any errors logged by the pattern
|
||||||
|
errors: [],
|
||||||
|
// If the pattern fails to draft, this will hold the error
|
||||||
|
failure: false,
|
||||||
|
}
|
||||||
|
// Draft the pattern or die trying
|
||||||
|
try {
|
||||||
|
data.pattern.sample()
|
||||||
|
data.errors.push(...data.pattern.store.logs.error)
|
||||||
|
for (const store of data.pattern.setStores) data.errors.push(...store.logs.error)
|
||||||
|
} catch (error) {
|
||||||
|
data.failure = error
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
export function flattenFlags(flags) {
|
export function flattenFlags(flags) {
|
||||||
const all = {}
|
const all = {}
|
||||||
for (const type of defaultConfig.flagTypes) {
|
for (const type of defaultConfig.flagTypes) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
notEmpty,
|
notEmpty,
|
||||||
nsMerge,
|
nsMerge,
|
||||||
objUpdate,
|
objUpdate,
|
||||||
|
sample,
|
||||||
settingsValueIsCustom,
|
settingsValueIsCustom,
|
||||||
settingsValueCustomOrDefault,
|
settingsValueCustomOrDefault,
|
||||||
statePrefixPath,
|
statePrefixPath,
|
||||||
|
@ -89,6 +90,7 @@ export {
|
||||||
notEmpty,
|
notEmpty,
|
||||||
nsMerge,
|
nsMerge,
|
||||||
objUpdate,
|
objUpdate,
|
||||||
|
sample,
|
||||||
settingsValueIsCustom,
|
settingsValueIsCustom,
|
||||||
settingsValueCustomOrDefault,
|
settingsValueCustomOrDefault,
|
||||||
statePrefixPath,
|
statePrefixPath,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue