1
0
Fork 0

wip: work on editor

This commit is contained in:
joostdecock 2025-01-19 18:45:41 +01:00
parent 7f41bf391a
commit 07ba6df1c4
12 changed files with 170 additions and 92 deletions

View file

@ -2,6 +2,7 @@
import { missingMeasurements, flattenFlags } from '../lib/index.mjs' import { missingMeasurements, flattenFlags } from '../lib/index.mjs'
// Hooks // Hooks
import React, { useState } from 'react' import React, { useState } from 'react'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
// Components // Components
import { Null } from './Null.mjs' import { Null } from './Null.mjs'
import { AsideViewMenuSpacer } from './AsideViewMenu.mjs' import { AsideViewMenuSpacer } from './AsideViewMenu.mjs'
@ -65,15 +66,16 @@ export const HeaderMenuAllViews = ({ config, state, update, open, setOpen }) =>
) )
export const HeaderMenuDraftView = (props) => { export const HeaderMenuDraftView = (props) => {
const i18n = useDesignTranslation(props.Design.designConfig.data.id)
const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags
return ( return (
<> <>
<div className="tw-flex tw-flex-row tw-gap-1"> <div className="tw-flex tw-flex-row tw-gap-1">
<HeaderMenuDraftViewDesignOptions {...props} /> <HeaderMenuDraftViewDesignOptions {...props} i18n={i18n} />
<HeaderMenuDraftViewCoreSettings {...props} /> <HeaderMenuDraftViewCoreSettings {...props} i18n={i18n} />
<HeaderMenuDraftViewUiPreferences {...props} /> <HeaderMenuDraftViewUiPreferences {...props} i18n={i18n} />
{flags ? <HeaderMenuDraftViewFlags {...props} flags={flags} /> : null} {flags ? <HeaderMenuDraftViewFlags {...props} flags={flags} i18n={i18n} /> : null}
</div> </div>
<HeaderMenuDraftViewIcons {...props} /> <HeaderMenuDraftViewIcons {...props} />
<HeaderMenuUndoIcons {...props} /> <HeaderMenuUndoIcons {...props} />
@ -114,7 +116,8 @@ export const HeaderMenuDropdown = (props) => {
</div> </div>
<div <div
tabIndex={0} tabIndex={0}
className="tw-daisy-dropdown-content tw-bg-base-100 tw-bg-opacity-90 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-top-12 tw-w-screen md:tw-max-w-lg" className="tw-daisy-dropdown-content tw-bg-base-100 tw-bg-opacity-90 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-top-12 tw-w-screen md:tw-max-w-lg tw-overflow-y-scroll tw-mb-12"
style={{ maxHeight: 'calc(100vh - 12rem)' }}
> >
{props.children} {props.children}
</div> </div>

View file

@ -17,13 +17,15 @@ export const PatternLayout = (props) => {
return ( return (
<ZoomContextProvider> <ZoomContextProvider>
<div className="flex flex-col h-full"> <div className="tw-flex tw-flex-col tw-h-full">
<HeaderMenu state={props.state} {...{ update, Design, pattern, config }} /> <HeaderMenu state={props.state} {...{ update, Design, pattern, config }} />
<div className="flex lg:flex-row grow lg:max-h-[90vh] max-h-[calc(100vh-3rem)] h-full py-4 lg:mt-6"> <div className="tw-flex lg:tw-flex-row tw-grow lg:tw-max-h-[90vh] tw-max-h-[calc(100vh-3rem)] tw-h-full tw-py-4 lg:tw-mt-6">
<div className="lg:w-2/3 flex flex-col h-full grow px-4">{props.output}</div> <div className="lg:tw-w-2/3 tw-flex tw-flex-col tw-h-full tw-grow px-4">
{props.output}
</div>
{menu ? ( {menu ? (
<div <div
className={`hidden xl:block w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-full overflow-scroll`} className={`tw-hidden xl:tw-block tw-w-1/3 tw-shrink tw-grow-0 lg:tw-p-4 tw-max-w-2xl tw-h-full tw-overflow-scroll`}
> >
{menu} {menu}
</div> </div>

View file

@ -28,7 +28,7 @@ export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref
<Pattern <Pattern
{...{ renderProps }} {...{ renderProps }}
ref={ref} ref={ref}
className={`freesewing pattern w-full ${rotate ? '-rotate-90' : ''}`} className={`freesewing pattern tw-w-full ${rotate ? 'tw--rotate-90' : ''}`}
/> />
)} )}
</TransformComponent> </TransformComponent>

View file

@ -104,7 +104,11 @@ export const MenuItem = ({
return ( return (
<FormControl <FormControl
label={<span className="tw-text-base tw-font-normal">{i18n.en.o[name].d}</span>} label={
<span className="tw-text-base tw-font-normal">
{config.choiceTitles ? config.choiceTitles[current] : i18n.en.o[name].d}
</span>
}
id={config.name} id={config.name}
labelBR={<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">{buttons}</div>} labelBR={<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">{buttons}</div>}
labelBL={ labelBL={
@ -120,6 +124,19 @@ export const MenuItem = ({
) )
} }
const coreLabels = {
complete: 'Details',
only: 'Included Parts',
paperless: 'Paperless',
sabool: 'Include Seam Allowance',
}
const getItemLabel = (i18n, name) => {
if (Object.keys(coreLabels).includes(name)) return coreLabels[name]
return i18n && i18n.en.o[name]?.t ? i18n.en.o[name].t : name
}
/** /**
* A component for recursively displaying groups of menu items. * A component for recursively displaying groups of menu items.
* Accepts any object where menu item configurations are keyed by name * Accepts any object where menu item configurations are keyed by name
@ -189,9 +206,7 @@ export const MenuItemGroup = ({
<div className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full" key="a"> <div className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full" key="a">
<div className="tw-flex tw-flex-row tw-items-center tw-gap-4 tw-w-full"> <div className="tw-flex tw-flex-row tw-items-center tw-gap-4 tw-w-full">
<ItemIcon /> <ItemIcon />
<span className="tw-font-medium tw-capitalize"> <span className="tw-font-medium tw-capitalize">{getItemLabel(i18n, itemName)}</span>
{i18n && i18n.en.o[itemName]?.t ? i18n.en.o[itemName].t : itemName}
</span>
</div> </div>
<div className="tw-font-bold"> <div className="tw-font-bold">
<Value <Value

View file

@ -19,21 +19,19 @@ import {
MenuOnlySettingValue, MenuOnlySettingValue,
MenuScaleSettingValue, MenuScaleSettingValue,
} from './Value.mjs' } from './Value.mjs'
import { MenuItemGroup } from './Container.mjs' import { MenuItemGroup, MenuItem } from './Container.mjs'
import { SettingsIcon } from '@freesewing/react/components/Icon' import { SettingsIcon } from '@freesewing/react/components/Icon'
/** /**
* The core settings menu * The core settings menu
* @param {Object} options.update settings and ui update functions *
* @param {Object} options.settings core settings * @param {object} props.Design - An object holding the Design instance
* @param {Object} options.patternConfig the configuration from the pattern * @param {Object} props.state - Object holding state
* @param {String} options.language the menu language * @param {Object} props.i18n - Object holding translations loaded from the design
* @param {Object} options.account the user account data * @param {Object} props.update - Object holding state handlers
* @param {object} props.Swizzled - An object holding swizzled code
*/ */
export const CoreSettingsMenu = ({ update, state, language, Design }) => { export const CoreSettingsMenu = ({ Design, state, i18n, update }) => {
const structure = menuCoreSettingsStructure({ const structure = menuCoreSettingsStructure({
language,
units: state.settings?.units, units: state.settings?.units,
sabool: state.settings?.sabool, sabool: state.settings?.sabool,
parts: Design.patternConfig.draftOrder, parts: Design.patternConfig.draftOrder,
@ -71,11 +69,10 @@ export const CoreSettingsMenu = ({ update, state, language, Design }) => {
currentValues: state.settings || {}, currentValues: state.settings || {},
Icon: SettingsIcon, Icon: SettingsIcon,
Item: (props) => ( Item: (props) => (
<CoreSetting updateHandler={update} {...{ inputs, values, Design }} {...props} /> <CoreSetting updateHandler={update} {...{ inputs, values, Design, i18n }} {...props} />
), ),
isFirst: true, isFirst: true,
name: 'pe:designOptions', name: 'Core Settings',
language: state.locale,
passProps: { passProps: {
ux: state.ui.ux, ux: state.ui.ux,
settings: state.settings, settings: state.settings,
@ -88,6 +85,7 @@ export const CoreSettingsMenu = ({ update, state, language, Design }) => {
Design, Design,
inputs, inputs,
values, values,
i18n,
}} }}
/> />
) )
@ -120,7 +118,7 @@ export const CoreSetting = ({ name, config, ux, updateHandler, current, passProp
: updateHandler : updateHandler
return ( return (
<Swizzled.components.MenuItem <MenuItem
{...{ {...{
name, name,
config, config,
@ -135,15 +133,15 @@ export const CoreSetting = ({ name, config, ux, updateHandler, current, passProp
) )
} }
export const ClearAllButton = ({ setSettings, compact = false, Swizzled }) => { export const ClearAllButton = ({ setSettings, compact = false }) => {
return ( return (
<div className={`${compact ? '' : 'text-center mt-8'}`}> <div className={`${compact ? '' : 'text-center mt-8'}`}>
<button <button
className={`justify-self-center btn btn-error btn-outline ${compact ? 'btn-sm' : ''}`} className={`justify-self-center btn btn-error btn-outline ${compact ? 'btn-sm' : ''}`}
onClick={() => setSettings({})} onClick={() => setSettings({})}
> >
<Swizzled.components.TrashIcon /> <TrashIcon />
{Swizzled.methods.t('clearSettings')} Clear Settings
</button> </button>
</div> </div>
) )

View file

@ -3,7 +3,6 @@ import { menuDesignOptionsStructure } from '../../lib/index.mjs'
import { designOptionType } from '@freesewing/utils' import { designOptionType } from '@freesewing/utils'
// Hooks // Hooks
import React, { useCallback, useMemo } from 'react' import React, { useCallback, useMemo } from 'react'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
// Components // Components
import { import {
MenuBoolInput, MenuBoolInput,
@ -25,16 +24,16 @@ import {
import { MenuItemGroup, MenuItem } from './Container.mjs' import { MenuItemGroup, MenuItem } from './Container.mjs'
import { OptionsIcon } from '@freesewing/react/components/Icon' import { OptionsIcon } from '@freesewing/react/components/Icon'
//
/** /**
* The design options menu * The design options menu
*
* @param {object} props.Design - An object holding the Design instance * @param {object} props.Design - An object holding the Design instance
* @param {String} props.isFirst - Boolean indicating whether this is the first/top entry of the menu * @param {String} props.isFirst - Boolean indicating whether this is the first/top entry of the menu
* @param {Object} props.state - Object holding state * @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 * @param {Object} props.update - Object holding state handlers
*/ */
export const DesignOptionsMenu = ({ Design, isFirst = true, state, update }) => { export const DesignOptionsMenu = ({ Design, isFirst = true, state, i18n, update }) => {
const i18n = useDesignTranslation(Design.designConfig.data.id)
const structure = useMemo( const structure = useMemo(
() => menuDesignOptionsStructure(Design.patternConfig.options, state.settings), () => menuDesignOptionsStructure(Design.patternConfig.options, state.settings),
[Design.patternConfig, state.settings] [Design.patternConfig, state.settings]

View file

@ -1,26 +1,32 @@
export const DraftMenu = ({ Design, pattern, state, Swizzled, update }) => { import React from 'react'
// Swizzled methods import { OptionsIcon, SettingsIcon, UiIcon } from '@freesewing/react/components/Icon'
const { t } = Swizzled.methods import { DesignOptionsMenu } from './DesignOptionsMenu.mjs'
// Swizzled components import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
const { FlagsAccordionTitle, FlagsAccordionEntries, Accordion } = Swizzled.components import { UiPreferencesMenu } from './UiPreferencesMenu.mjs'
import { FlagsAccordionEntries } from '../Flag.mjs'
import { Accordion } from '../Accordion.mjs'
const menuProps = { Design, state, Swizzled, pattern, update } export const DraftMenu = ({ Design, pattern, state, update }) => {
const menuProps = { Design, state, pattern, update }
const sections = [ const sections = [
{ {
name: 'designOptions', t: 'Design Options',
icon: <Swizzled.components.OptionsIcon className="w-8 h-8" />, d: 'These options are specific to this design. You can use them to customize your pattern in a variety of ways.',
menu: <Swizzled.components.DesignOptionsMenu {...menuProps} />, icon: <OptionsIcon className="tw-w-8 tw-h-8" />,
menu: <DesignOptionsMenu {...menuProps} />,
}, },
{ {
name: 'coreSettings', t: 'Core Settings',
icon: <Swizzled.components.SettingsIcon className="w-8 h-8" />, d: 'These settings are not specific to the design, but instead allow you to customize various parameters of the FreeSewing core library, which generates the design for you.',
menu: <Swizzled.components.CoreSettingsMenu {...menuProps} />, icon: <SettingsIcon className="tw-w-8 tw-h-8" />,
menu: <CoreSettingsMenu {...menuProps} />,
}, },
{ {
name: 'uiPreferences', t: 'UI Preferences',
icon: <Swizzled.components.UiIcon className="w-8 h-8" />, d: 'These preferences control the UI (User Interface) of the pattern editor',
menu: <Swizzled.components.UiPreferencesMenu {...menuProps} />, icon: <UiIcon className="tw-w-8 tw-h-8" />,
menu: <UiPreferencesMenu {...menuProps} />,
}, },
] ]
@ -28,11 +34,11 @@ export const DraftMenu = ({ Design, pattern, state, Swizzled, update }) => {
items.push( items.push(
...sections.map((section) => [ ...sections.map((section) => [
<> <>
<h5 className="flex flex-row gap-2 items-center justify-between w-full"> <h5 className="tw-flex tw-flex-row tw-gap-2 tw-items-center tw-justify-between tw-w-full tw-font-bold tw-text-lg">
<span>{t(`pe:${section.name}.t`)}</span> <span>{section.t}</span>
{section.icon} {section.icon}
</h5> </h5>
<p className="text-left">{t(`pe:${section.name}.d`)}</p> <p className="tw-text-left">{section.d}</p>
</>, </>,
section.menu, section.menu,
section.name, section.name,
@ -43,7 +49,7 @@ export const DraftMenu = ({ Design, pattern, state, Swizzled, update }) => {
if (flags) if (flags)
items.push([ items.push([
<FlagsAccordionTitle key={1} {...{ flags, Swizzled }} />, <FlagsAccordionTitle key={1} {...{ flags }} />,
<FlagsAccordionEntries {...{ update, state, flags }} key={2} />, <FlagsAccordionEntries {...{ update, state, flags }} key={2} />,
'flags', 'flags',
]) ])

View file

@ -49,6 +49,41 @@ export const MenuDegInput = (props) => {
) )
} }
const getTitleAndDesc = (config = {}, i18n = {}, isDesignOption = false) => {
if (config.choiceTitles && config.choiceDescriptions) {
const current = typeof config.current === 'undefined' ? config.dflt : config.current
return {
title: config.choiceTitles[current],
desc: config.choiceDescriptions[current],
}
}
console.log(config)
let titleKey = config.choiceTitles
? config.choiceTitles[entry]
: isDesignOption
? i18n.en.o[name]
: `${name}.o.${entry}`
if (!config.choiceTitles && i18n && i18n.en.o[`${name}.${entry}`])
titleKey = i18n.en.o[`${name}.${entry}`]
console.log({ titleKey, titles: config.choiceTitles, isDesignOption })
const title = config.titleMethod
? config.titleMethod(entry)
: typeof titleKey === 'string'
? i18n.en.o[titleKey]?.t
: titleKey.t
const desc = config.valueMethod
? config.valueMethod(entry)
: typeof titleKey === 'string'
? i18n.en.o[titleKey]?.d
: titleKey.d
return {
title: 'fixmeTitle',
desc: 'fixmeDesc',
}
}
/** /**
* An input for selecting and item from a list * An input for selecting and item from a list
* @param {String} options.name the name of the property this input changes * @param {String} options.name the name of the property this input changes
@ -80,23 +115,7 @@ export const MenuListInput = ({
}) })
return config.list.map((entry) => { return config.list.map((entry) => {
let titleKey = config.choiceTitles const { title, desc } = getTitleAndDesc(config, i18n, isDesignOption)
? config.choiceTitles[entry]
: isDesignOption
? i18n.en.o[name]
: `${name}.o.${entry}`
if (i18n.en.o[`${name}.${entry}`]) titleKey = i18n.en.o[`${name}.${entry}`]
console.log({ titleKey, titles: config.choiceTitles, isDesignOption })
const title = config.titleMethod
? config.titleMethod(entry)
: typeof titleKey === 'string'
? i18n.en.o[titleKey]?.t
: titleKey.t
const desc = config.valueMethod
? config.valueMethod(entry)
: typeof titleKey === 'string'
? i18n.en.o[titleKey]?.d
: titleKey.d
const sideBySide = config.sideBySide || desc.length + title.length < 42 const sideBySide = config.sideBySide || desc.length + title.length < 42
return ( return (

View file

@ -4,7 +4,7 @@ import { menuUiPreferencesStructure } from '../../lib/index.mjs'
// Components // Components
import { MenuUxSettingInput, MenuListInput } from './Input.mjs' import { MenuUxSettingInput, MenuListInput } from './Input.mjs'
import { MenuListValue } from './Value.mjs' import { MenuListValue } from './Value.mjs'
import { MenuItemGroup } from './Container.mjs' import { MenuItemGroup, MenuItem } from './Container.mjs'
import { Ux } from '@freesewing/react/components/Ux' import { Ux } from '@freesewing/react/components/Ux'
export const UiPreferencesMenu = ({ update, state, Design }) => { export const UiPreferencesMenu = ({ update, state, Design }) => {

View file

@ -46,7 +46,7 @@ export const DraftView = ({ Design, state, update, config }) => {
const __html = pattern.render() const __html = pattern.render()
output = ( output = (
<ZoomablePattern> <ZoomablePattern>
<div className="w-full h-full" dangerouslySetInnerHTML={{ __html }} /> <div className="tw-w-full tw-h-full" dangerouslySetInnerHTML={{ __html }} />
</ZoomablePattern> </ZoomablePattern>
) )
} catch (err) { } catch (err) {

View file

@ -64,12 +64,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
ux: config.uxLevels.core.sa, ux: config.uxLevels.core.sa,
list: [0, 1], list: [0, 1],
choiceTitles: { choiceTitles: {
0: 'saNo', 0: 'Do not include seam allowance',
1: 'saYes', 1: 'Include seam allowance',
},
choiceDescriptions: {
0: 'This generates a pattern which does not include any seam allowance. The size of the seam allowance does not matter as no seam allowance will be included',
1: 'This generates a pattern that will include seam allowance. The size of the seam allowance is set individually',
}, },
valueTitles: { valueTitles: {
0: 'no', 0: 'No',
1: 'yes', 1: 'Yes',
}, },
dflt: 0, dflt: 0,
icon: SaIcon, icon: SaIcon,
@ -87,12 +91,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
ux: config.uxLevels.core.paperless, ux: config.uxLevels.core.paperless,
list: [0, 1], list: [0, 1],
choiceTitles: { choiceTitles: {
0: 'paperlessNo', 0: 'Generate a regular pattern',
1: 'paperlessYes', 1: 'Generate a paperless pattern',
},
choiceDescriptions: {
0: 'This will generate a regular pattern, which you can them print out.',
1: 'This will generate a pattern with dimensions and a grid, which allows you to transfer it on fabric or another medium without the need to print the pattern.',
}, },
valueTitles: { valueTitles: {
0: 'no', 0: 'No',
1: 'yes', 1: 'Yes',
}, },
dflt: 0, dflt: 0,
icon: PaperlessIcon, icon: PaperlessIcon,
@ -102,12 +110,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
list: ['metric', 'imperial'], list: ['metric', 'imperial'],
dflt: 'metric', dflt: 'metric',
choiceTitles: { choiceTitles: {
metric: 'metric', metric: 'Metric',
imperial: 'imperial', imperial: 'Imperial',
},
choiceDescriptions: {
0: 'Use this if you use the metric system, and centimeters are something you are familiar with. This is the best choice for most people around the world.',
1: 'Use this if inches are more familiar to you. This is often the preferred choice for people based in the UK & US.',
}, },
valueTitles: { valueTitles: {
metric: 'metric', metric: 'Metric',
imperial: 'imperial', imperial: 'Imperial',
}, },
icon: UnitsIcon, icon: UnitsIcon,
}, },
@ -116,12 +128,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
list: [1, 0], list: [1, 0],
dflt: 1, dflt: 1,
choiceTitles: { choiceTitles: {
0: 'completeNo', 0: 'Generate a pattern outline',
1: 'completeYes', 1: 'Generate a complete pattern',
},
choiceDescriptions: {
0: 'Only generate the outline of the pattern parts. Use this if you are looking to use a laser cutter or have other specific needs.',
1: 'This will generate a complete pattern with all annotations, markings and lines. Use this if you are not certain what to choose.',
}, },
valueTitles: { valueTitles: {
0: 'no', 0: 'No',
1: 'yes', 1: 'Yes',
}, },
icon: DetailIcon, icon: DetailIcon,
}, },
@ -130,12 +146,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
list: [1, 0], list: [1, 0],
dflt: 1, dflt: 1,
choiceTitles: { choiceTitles: {
0: 'expandNo', 0: 'Keep pattern parts compact where possible',
1: 'expandYes', 1: 'Expand all pattern parts',
},
choiceDescriptions: {
0: 'This will generate a more dense representation of the pattern which includes all info without using up too much space.',
1: 'This will generate a pattern where all parts are drown to their full size, even if they are simple rectangles.',
}, },
valueTitles: { valueTitles: {
0: 'no', 0: 'No',
1: 'yes', 1: 'Yes',
}, },
icon: ExpandIcon, icon: ExpandIcon,
}, },

View file

@ -25,6 +25,10 @@ export function menuUiPreferencesStructure() {
0: 'pe:noAside', 0: 'pe:noAside',
1: 'pe:withAside', 1: 'pe:withAside',
}, },
choiceDescriptions: {
0: 'pe:noAside',
1: 'pe:withAside',
},
dflt: 1, dflt: 1,
icon: MenuIcon, icon: MenuIcon,
}, },
@ -35,6 +39,10 @@ export function menuUiPreferencesStructure() {
0: 'pe:websiteMode', 0: 'pe:websiteMode',
1: 'pe:kioskMode', 1: 'pe:kioskMode',
}, },
choiceDescriptions: {
0: 'pe:noAside',
1: 'pe:withAside',
},
dflt: 0, dflt: 0,
icon: KioskIcon, icon: KioskIcon,
}, },
@ -45,6 +53,10 @@ export function menuUiPreferencesStructure() {
0: 'pe:rotateNo', 0: 'pe:rotateNo',
1: 'pe:rotateYes', 1: 'pe:rotateYes',
}, },
choiceDescriptions: {
0: 'pe:noAside',
1: 'pe:withAside',
},
dflt: 0, dflt: 0,
icon: RotateIcon, icon: RotateIcon,
}, },
@ -55,6 +67,10 @@ export function menuUiPreferencesStructure() {
react: 'pe:renderWithReact', react: 'pe:renderWithReact',
svg: 'pe:renderWithCore', svg: 'pe:renderWithCore',
}, },
choiceDescriptions: {
0: 'pe:noAside',
1: 'pe:withAside',
},
valueTitles: { valueTitles: {
react: 'React', react: 'React',
svg: 'SVG', svg: 'SVG',