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'
// Hooks
import React, { useState } from 'react'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
// Components
import { Null } from './Null.mjs'
import { AsideViewMenuSpacer } from './AsideViewMenu.mjs'
@ -65,15 +66,16 @@ export const HeaderMenuAllViews = ({ config, state, update, open, setOpen }) =>
)
export const HeaderMenuDraftView = (props) => {
const i18n = useDesignTranslation(props.Design.designConfig.data.id)
const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags
return (
<>
<div className="tw-flex tw-flex-row tw-gap-1">
<HeaderMenuDraftViewDesignOptions {...props} />
<HeaderMenuDraftViewCoreSettings {...props} />
<HeaderMenuDraftViewUiPreferences {...props} />
{flags ? <HeaderMenuDraftViewFlags {...props} flags={flags} /> : null}
<HeaderMenuDraftViewDesignOptions {...props} i18n={i18n} />
<HeaderMenuDraftViewCoreSettings {...props} i18n={i18n} />
<HeaderMenuDraftViewUiPreferences {...props} i18n={i18n} />
{flags ? <HeaderMenuDraftViewFlags {...props} flags={flags} i18n={i18n} /> : null}
</div>
<HeaderMenuDraftViewIcons {...props} />
<HeaderMenuUndoIcons {...props} />
@ -114,7 +116,8 @@ export const HeaderMenuDropdown = (props) => {
</div>
<div
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}
</div>

View file

@ -17,13 +17,15 @@ export const PatternLayout = (props) => {
return (
<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 }} />
<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="lg:w-2/3 flex flex-col h-full grow px-4">{props.output}</div>
<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:tw-w-2/3 tw-flex tw-flex-col tw-h-full tw-grow px-4">
{props.output}
</div>
{menu ? (
<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}
</div>

View file

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

View file

@ -104,7 +104,11 @@ export const MenuItem = ({
return (
<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}
labelBR={<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">{buttons}</div>}
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.
* 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-gap-4 tw-w-full">
<ItemIcon />
<span className="tw-font-medium tw-capitalize">
{i18n && i18n.en.o[itemName]?.t ? i18n.en.o[itemName].t : itemName}
</span>
<span className="tw-font-medium tw-capitalize">{getItemLabel(i18n, itemName)}</span>
</div>
<div className="tw-font-bold">
<Value

View file

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

View file

@ -3,7 +3,6 @@ import { menuDesignOptionsStructure } from '../../lib/index.mjs'
import { designOptionType } from '@freesewing/utils'
// Hooks
import React, { useCallback, useMemo } from 'react'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
// Components
import {
MenuBoolInput,
@ -25,16 +24,16 @@ import {
import { MenuItemGroup, MenuItem } from './Container.mjs'
import { OptionsIcon } from '@freesewing/react/components/Icon'
//
/**
* The design options menu
*
* @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 {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 DesignOptionsMenu = ({ Design, isFirst = true, state, update }) => {
const i18n = useDesignTranslation(Design.designConfig.data.id)
export const DesignOptionsMenu = ({ Design, isFirst = true, state, i18n, update }) => {
const structure = useMemo(
() => menuDesignOptionsStructure(Design.patternConfig.options, state.settings),
[Design.patternConfig, state.settings]

View file

@ -1,26 +1,32 @@
export const DraftMenu = ({ Design, pattern, state, Swizzled, update }) => {
// Swizzled methods
const { t } = Swizzled.methods
// Swizzled components
const { FlagsAccordionTitle, FlagsAccordionEntries, Accordion } = Swizzled.components
import React from 'react'
import { OptionsIcon, SettingsIcon, UiIcon } from '@freesewing/react/components/Icon'
import { DesignOptionsMenu } from './DesignOptionsMenu.mjs'
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
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 = [
{
name: 'designOptions',
icon: <Swizzled.components.OptionsIcon className="w-8 h-8" />,
menu: <Swizzled.components.DesignOptionsMenu {...menuProps} />,
t: 'Design Options',
d: 'These options are specific to this design. You can use them to customize your pattern in a variety of ways.',
icon: <OptionsIcon className="tw-w-8 tw-h-8" />,
menu: <DesignOptionsMenu {...menuProps} />,
},
{
name: 'coreSettings',
icon: <Swizzled.components.SettingsIcon className="w-8 h-8" />,
menu: <Swizzled.components.CoreSettingsMenu {...menuProps} />,
t: 'Core Settings',
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.',
icon: <SettingsIcon className="tw-w-8 tw-h-8" />,
menu: <CoreSettingsMenu {...menuProps} />,
},
{
name: 'uiPreferences',
icon: <Swizzled.components.UiIcon className="w-8 h-8" />,
menu: <Swizzled.components.UiPreferencesMenu {...menuProps} />,
t: 'UI Preferences',
d: 'These preferences control the UI (User Interface) of the pattern editor',
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(
...sections.map((section) => [
<>
<h5 className="flex flex-row gap-2 items-center justify-between w-full">
<span>{t(`pe:${section.name}.t`)}</span>
<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>{section.t}</span>
{section.icon}
</h5>
<p className="text-left">{t(`pe:${section.name}.d`)}</p>
<p className="tw-text-left">{section.d}</p>
</>,
section.menu,
section.name,
@ -43,7 +49,7 @@ export const DraftMenu = ({ Design, pattern, state, Swizzled, update }) => {
if (flags)
items.push([
<FlagsAccordionTitle key={1} {...{ flags, Swizzled }} />,
<FlagsAccordionTitle key={1} {...{ flags }} />,
<FlagsAccordionEntries {...{ update, state, flags }} key={2} />,
'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
* @param {String} options.name the name of the property this input changes
@ -80,23 +115,7 @@ export const MenuListInput = ({
})
return config.list.map((entry) => {
let titleKey = config.choiceTitles
? 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 { title, desc } = getTitleAndDesc(config, i18n, isDesignOption)
const sideBySide = config.sideBySide || desc.length + title.length < 42
return (

View file

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

View file

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

View file

@ -64,12 +64,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
ux: config.uxLevels.core.sa,
list: [0, 1],
choiceTitles: {
0: 'saNo',
1: 'saYes',
0: 'Do not include seam allowance',
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: {
0: 'no',
1: 'yes',
0: 'No',
1: 'Yes',
},
dflt: 0,
icon: SaIcon,
@ -87,12 +91,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
ux: config.uxLevels.core.paperless,
list: [0, 1],
choiceTitles: {
0: 'paperlessNo',
1: 'paperlessYes',
0: 'Generate a regular pattern',
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: {
0: 'no',
1: 'yes',
0: 'No',
1: 'Yes',
},
dflt: 0,
icon: PaperlessIcon,
@ -102,12 +110,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
list: ['metric', 'imperial'],
dflt: 'metric',
choiceTitles: {
metric: 'metric',
imperial: 'imperial',
metric: 'Metric',
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: {
metric: 'metric',
imperial: 'imperial',
metric: 'Metric',
imperial: 'Imperial',
},
icon: UnitsIcon,
},
@ -116,12 +128,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
list: [1, 0],
dflt: 1,
choiceTitles: {
0: 'completeNo',
1: 'completeYes',
0: 'Generate a pattern outline',
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: {
0: 'no',
1: 'yes',
0: 'No',
1: 'Yes',
},
icon: DetailIcon,
},
@ -130,12 +146,16 @@ export function menuCoreSettingsStructure({ units = 'metric', sabool = false, pa
list: [1, 0],
dflt: 1,
choiceTitles: {
0: 'expandNo',
1: 'expandYes',
0: 'Keep pattern parts compact where possible',
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: {
0: 'no',
1: 'yes',
0: 'No',
1: 'Yes',
},
icon: ExpandIcon,
},

View file

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