wip: work on editor
This commit is contained in:
parent
761ad0d5c9
commit
4916a6e805
19 changed files with 423 additions and 286 deletions
|
@ -115,6 +115,8 @@ packageJson:
|
||||||
"./hooks/useAccount": "./hooks/useAccount/index.mjs"
|
"./hooks/useAccount": "./hooks/useAccount/index.mjs"
|
||||||
"./hooks/useBackend": "./hooks/useBackend/index.mjs"
|
"./hooks/useBackend": "./hooks/useBackend/index.mjs"
|
||||||
"./hooks/useControl": "./hooks/useControl/index.mjs"
|
"./hooks/useControl": "./hooks/useControl/index.mjs"
|
||||||
|
"./hooks/useDesign": "./hooks/useDesign/index.mjs"
|
||||||
|
"./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs"
|
||||||
"./hooks/useSelection": "./hooks/useSelection/index.mjs"
|
"./hooks/useSelection": "./hooks/useSelection/index.mjs"
|
||||||
# Lib
|
# Lib
|
||||||
"./lib/RestClient": "./lib/RestClient/index.mjs"
|
"./lib/RestClient": "./lib/RestClient/index.mjs"
|
||||||
|
|
|
@ -4,14 +4,14 @@ import React, { useState } from 'react'
|
||||||
* DaisyUI's accordion seems rather unreliable.
|
* DaisyUI's accordion seems rather unreliable.
|
||||||
* So instead, we handle this in React state
|
* So instead, we handle this in React state
|
||||||
*/
|
*/
|
||||||
const getProps = (isActive) => ({
|
const getProps = (isActive = false) => ({
|
||||||
className: `tw-p-2 tw-px-4 tw-rounded-lg tw-bg-transparent tw-shadow hover:tw-cursor-pointer
|
className: `tw-p-2 tw-px-4 tw-rounded-lg tw-bg-transparent tw-shadow hover:tw-cursor-pointer
|
||||||
tw-w-full tw-mt-2 tw-py-4 tw-h-auto tw-content-start tw-text-left tw-bg-opacity-20
|
tw-w-full tw-h-auto tw-content-start tw-text-left tw-bg-opacity-20
|
||||||
${isActive ? 'hover:tw-bg-transparent' : 'hover:tw-bg-secondary hover:tw-bg-opacity-10'}`,
|
${isActive ? 'hover:tw-bg-transparent' : 'hover:tw-bg-secondary hover:tw-bg-opacity-10'}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getSubProps = (isActive) => ({
|
const getSubProps = (isActive) => ({
|
||||||
className: ` tw-p-2 tw-px-4 tw-rounded tw-bg-transparent tw-w-full tw-mt-2 tw-py-4 tw-h-auto
|
className: `tw-p-2 tw-px-4 tw-rounded-none tw-bg-transparent tw-w-full tw-h-auto
|
||||||
tw-content-start tw-bg-secondary tw-text-left tw-bg-opacity-20
|
tw-content-start tw-bg-secondary tw-text-left tw-bg-opacity-20
|
||||||
${
|
${
|
||||||
isActive
|
isActive
|
||||||
|
|
|
@ -14,8 +14,10 @@ import {
|
||||||
ExpandIcon,
|
ExpandIcon,
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
FixmeIcon,
|
FixmeIcon,
|
||||||
|
FlagIcon,
|
||||||
KioskIcon,
|
KioskIcon,
|
||||||
MenuIcon,
|
MenuIcon,
|
||||||
|
OptionsIcon,
|
||||||
PaperlessIcon,
|
PaperlessIcon,
|
||||||
ResetAllIcon,
|
ResetAllIcon,
|
||||||
RightIcon,
|
RightIcon,
|
||||||
|
@ -24,24 +26,28 @@ import {
|
||||||
SaIcon,
|
SaIcon,
|
||||||
SaveAsIcon,
|
SaveAsIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
|
SettingsIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
|
UiIcon,
|
||||||
UndoIcon,
|
UndoIcon,
|
||||||
UnitsIcon,
|
UnitsIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
|
import { ButtonFrame } from '@freesewing/react/components/Input'
|
||||||
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
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 { FlagsAccordionEntries } from './Flag.mjs'
|
import { FlagsAccordionEntries } from './Flag.mjs'
|
||||||
|
import { UndoStep } from './views/UndosView.mjs'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lookup object for header menu icons
|
* Lookup object for header menu icons
|
||||||
*/
|
*/
|
||||||
const headerMenuIcons = {
|
const headerMenuIcons = {
|
||||||
flag: RocketIcon,
|
flag: FlagIcon,
|
||||||
options: RocketIcon,
|
options: OptionsIcon,
|
||||||
right: RightIcon,
|
right: RightIcon,
|
||||||
settings: RocketIcon,
|
settings: SettingsIcon,
|
||||||
ui: RocketIcon,
|
ui: UiIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuIcon = (props) => {
|
export const HeaderMenuIcon = (props) => {
|
||||||
|
@ -101,7 +107,7 @@ export const HeaderMenuDropdown = (props) => {
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
className="tw-daisy-btn tw-daisy-btn-ghost hover:tw-bg-secondary hover:tw-bg-opacity-20 hover:tw-border-solid hover:tw-boder-2 hover:tw-border-secondary tw-border tw-border-secondary tw-border-2 tw-border-dotted tw-daisy-btn-sm tw-px-2 tw-z-20 tw-relative"
|
className="tw-daisy-btn tw-daisy-btn-ghost hover:tw-bg-secondary hover:tw-bg-opacity-20 tw-border-secondary/10 hover:tw-boder-2 hover:tw-border-secondary tw-border tw-border-secondary tw-border-2 tw-border-solid tw-daisy-btn-sm tw-px-2 tw-z-20 tw-relative"
|
||||||
onClick={() => setOpen(open === id ? false : id)}
|
onClick={() => setOpen(open === id ? false : id)}
|
||||||
>
|
>
|
||||||
{toggle}
|
{toggle}
|
||||||
|
@ -129,11 +135,11 @@ export const HeaderMenuDraftViewDesignOptions = (props) => {
|
||||||
<HeaderMenuDropdown
|
<HeaderMenuDropdown
|
||||||
{...props}
|
{...props}
|
||||||
id="designOptions"
|
id="designOptions"
|
||||||
tooltip="fixme: 'pe:designOptions.d'"
|
tooltip="These options are specific to this design. You can use them to customize your pattern in a variety of ways."
|
||||||
toggle={
|
toggle={
|
||||||
<>
|
<>
|
||||||
<HeaderMenuIcon name="options" extraClasses="tw-text-secondary" />
|
<HeaderMenuIcon name="options" extraClasses="tw-text-secondary" />
|
||||||
<span className="tw-hidden lg:tw-inline">fixme: pe:designOptions.t</span>
|
<span className="tw-hidden lg:tw-inline">Design Options</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -146,12 +152,12 @@ export const HeaderMenuDraftViewCoreSettings = (props) => {
|
||||||
return (
|
return (
|
||||||
<HeaderMenuDropdown
|
<HeaderMenuDropdown
|
||||||
{...props}
|
{...props}
|
||||||
tooltip="fixme: pe:coreSettings.d"
|
tooltip="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."
|
||||||
id="coreSettings"
|
id="coreSettings"
|
||||||
toggle={
|
toggle={
|
||||||
<>
|
<>
|
||||||
<HeaderMenuIcon name="settings" extraClasses="tw-text-secondary" />
|
<HeaderMenuIcon name="settings" extraClasses="tw-text-secondary" />
|
||||||
<span className="tw-hidden lg:tw-inline">fixme: pe:coreSettings.t</span>
|
<span className="tw-hidden lg:tw-inline">Core Settings</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -164,12 +170,12 @@ export const HeaderMenuDraftViewUiPreferences = (props) => {
|
||||||
return (
|
return (
|
||||||
<HeaderMenuDropdown
|
<HeaderMenuDropdown
|
||||||
{...props}
|
{...props}
|
||||||
tooltip="fixme: pe:uiPreferences.d"
|
tooltip="These preferences control the UI (User Interface) of the pattern editor"
|
||||||
id="uiPreferences"
|
id="uiPreferences"
|
||||||
toggle={
|
toggle={
|
||||||
<>
|
<>
|
||||||
<HeaderMenuIcon name="ui" extraClasses="tw-text-secondary" />
|
<HeaderMenuIcon name="ui" extraClasses="tw-text-secondary" />
|
||||||
<span className="tw-hidden lg:tw-inline">fixme: pe:uiPreferences.t</span>
|
<span className="tw-hidden lg:tw-inline">UI Preferences</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -184,7 +190,7 @@ export const HeaderMenuDraftViewFlags = (props) => {
|
||||||
return (
|
return (
|
||||||
<HeaderMenuDropdown
|
<HeaderMenuDropdown
|
||||||
{...props}
|
{...props}
|
||||||
tooltip="fixme: pe:flagMenuMany.d"
|
tooltip="Some issues about your current pattern need your attention."
|
||||||
id="flags"
|
id="flags"
|
||||||
toggle={
|
toggle={
|
||||||
<>
|
<>
|
||||||
|
@ -389,7 +395,7 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
<div className="tw-flex tw-flex-row tw-items-center tw-align-center tw-justify-between tw-gap-2 tw-w-full">
|
<div className="tw-flex tw-flex-row tw-items-center tw-align-center tw-justify-between tw-gap-2 tw-w-full">
|
||||||
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-grow">
|
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-grow">
|
||||||
<UndoIcon className="tw-w-5 tw-h-5 tw-text-secondary" />
|
<UndoIcon className="tw-w-5 tw-h-5 tw-text-secondary" />
|
||||||
{viewLabels.undo.t}
|
{viewLabels.undos.t}
|
||||||
</div>
|
</div>
|
||||||
{undos.length}
|
{undos.length}
|
||||||
</div>
|
</div>
|
||||||
|
@ -479,18 +485,18 @@ export const HeaderMenuViewMenu = (props) => {
|
||||||
className="tw-mb-1 tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full"
|
className="tw-mb-1 tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
className={`tw-w-full tw-rounded-lg tw-border-2 tw-border-secondary tw-text-base-content
|
className={`tw-w-full tw-text-base-content
|
||||||
tw-flex tw-flex-row tw-items-center tw-gap-2 md:tw-gap-4 tw-p-2
|
tw-flex tw-flex-row tw-items-center tw-gap-2 md:tw-gap-4 tw-p-2 tw-px-4
|
||||||
hover:tw-cursor-pointer hover:tw-text-base-content
|
hover:tw-cursor-pointer hover:tw-text-base-content
|
||||||
hover:tw-bg-secondary hover:tw-bg-opacity-10 hover:tw-border-solid ${
|
hover:tw-bg-secondary hover:tw-bg-opacity-20 ${
|
||||||
viewName === state.view
|
viewName === state.view ? 'tw-bg-secondary tw-bg-opacity-20' : ''
|
||||||
? 'tw-bg-secondary tw-border-solid tw-bg-opacity-20'
|
|
||||||
: 'tw-border-dotted'
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => update.view(viewName)}
|
onClick={() => update.view(viewName)}
|
||||||
>
|
>
|
||||||
<ViewIcon view={viewName} className="tw-w-6 tw-h-6 tw-grow-0" />
|
<ViewIcon view={viewName} className="tw-w-6 tw-h-6 tw-grow-0" />
|
||||||
<b className="tw-text-left tw-grow">{viewLabels[viewName]?.t || viewName}</b>
|
<span className="tw-text-left tw-grow tw-font-medium">
|
||||||
|
{viewLabels[viewName]?.t || viewName}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
@ -511,7 +517,7 @@ export const HeaderMenuViewMenu = (props) => {
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={i}
|
tabIndex={i}
|
||||||
className="tw-dropdown-content tw-bg-base-100 tw-bg-opacity-90 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-w-screen md:tw-w-96 tw-px-4 md:tw-p-2 md:tw-pt-0"
|
className="tw-dropdown-content tw-bg-base-100 tw-bg-opacity-95 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-w-screen md:tw-w-96 md:tw-pt-0 tw-mt-14 md:tw-mt-0"
|
||||||
>
|
>
|
||||||
{output}
|
{output}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { menuValueWasChanged } from '../../lib/index.mjs'
|
import { menuValueWasChanged } from '../../lib/index.mjs'
|
||||||
|
import { designOptionType } from '@freesewing/utils'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useMemo } from 'react'
|
import React, { useState, useMemo } from 'react'
|
||||||
// Components
|
// Components
|
||||||
import { SubAccordion } from '../Accordion.mjs'
|
import { SubAccordion } from '../Accordion.mjs'
|
||||||
import { GroupIcon, OptionsIcon } from '@freesewing/react/components/Icon'
|
import { EditIcon, GroupIcon, OptionsIcon, ResetIcon } from '@freesewing/react/components/Icon'
|
||||||
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
||||||
|
import { FormControl } from '@freesewing/react/components/Input'
|
||||||
|
|
||||||
/** @type {String} class to apply to buttons on open menu items */
|
/** @type {String} class to apply to buttons on open menu items */
|
||||||
const iconButtonClass = 'btn btn-xs btn-ghost px-0 text-accent'
|
const iconButtonClass = 'tw-daisy-btn tw-daisy-btn-xs tw-daisy-btn-ghost tw-px-0 tw-text-accent'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic component for handling a menu item.
|
* A generic component for handling a menu item.
|
||||||
|
@ -37,6 +39,7 @@ export const MenuItem = ({
|
||||||
docs,
|
docs,
|
||||||
config,
|
config,
|
||||||
Design,
|
Design,
|
||||||
|
i18n,
|
||||||
}) => {
|
}) => {
|
||||||
// Local state - whether the override input should be shown
|
// Local state - whether the override input should be shown
|
||||||
const [override, setOverride] = useState(false)
|
const [override, setOverride] = useState(false)
|
||||||
|
@ -78,15 +81,15 @@ export const MenuItem = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon
|
<EditIcon
|
||||||
className={`w-6 h-6 ${
|
className={`tw-w-6 tw-h-6 ${
|
||||||
override ? 'bg-secondary text-secondary-content rounded' : 'text-secondary'
|
override ? 'tw-bg-secondary tw-text-secondary-content tw-rounded' : 'tw-text-secondary'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
const ResetButton = ({ disabled = false }) => (
|
const ResetButton = ({ disabled = false }) => (
|
||||||
<button
|
<button
|
||||||
className={`${iconButtonClass} disabled:bg-opacity-0`}
|
className={`${iconButtonClass} disabled:tw-bg-opacity-0`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={(evt) => {
|
onClick={(evt) => {
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
|
@ -101,17 +104,16 @@ export const MenuItem = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
label={<span className="text-base font-normal">{name}.d</span>}
|
label={<span className="tw-text-base tw-font-normal">{i18n.en.o[name].d}</span>}
|
||||||
id={config.name}
|
id={config.name}
|
||||||
labelBR={<div className="flex flex-row items-center gap-2">{buttons}</div>}
|
labelBR={<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">{buttons}</div>}
|
||||||
labelBL={
|
labelBL={
|
||||||
<span
|
<span
|
||||||
className={`text-base font-medium -mt-2 block ${changed ? 'text-accent' : 'opacity-50'}`}
|
className={`tw-text-base tw-font-medium tw--mt-2 tw-block ${changed ? 'tw-text-accent' : 'tw-opacity-50'}`}
|
||||||
>
|
>
|
||||||
pe:youAreUsing{changed ? 'ACustom' : 'TheDefault'}Value
|
{changed ? 'This is a custom value' : 'This is the default value'}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
docs={docs}
|
|
||||||
>
|
>
|
||||||
<Input {...drillProps} />
|
<Input {...drillProps} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -152,6 +154,7 @@ export const MenuItemGroup = ({
|
||||||
isDesignOptionsGroup = false,
|
isDesignOptionsGroup = false,
|
||||||
Design,
|
Design,
|
||||||
state,
|
state,
|
||||||
|
i18n,
|
||||||
}) => {
|
}) => {
|
||||||
if (!Item) Item = MenuItem
|
if (!Item) Item = MenuItem
|
||||||
|
|
||||||
|
@ -171,9 +174,9 @@ export const MenuItemGroup = ({
|
||||||
: () => <span role="img">fixme-icon</span>
|
: () => <span role="img">fixme-icon</span>
|
||||||
const Value = item.isGroup
|
const Value = item.isGroup
|
||||||
? () => (
|
? () => (
|
||||||
<div className="flex flex-row gap-2 items-center font-medium">
|
<div className="tw-flex tw-flex-row tw-gap-2 tw-items-center tw-font-medium">
|
||||||
{Object.keys(item).filter((i) => i !== 'isGroup').length}
|
{Object.keys(item).filter((i) => i !== 'isGroup').length}
|
||||||
<OptionsIcon className="w-5 h-5" />
|
<OptionsIcon className="tw-w-5 tw-h-5" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: isDesignOptionsGroup
|
: isDesignOptionsGroup
|
||||||
|
@ -183,14 +186,14 @@ export const MenuItemGroup = ({
|
||||||
: () => <span>¯\_(ツ)_/¯</span>
|
: () => <span>¯\_(ツ)_/¯</span>
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<div className="flex flex-row items-center justify-between w-full" key="a">
|
<div className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full" key="a">
|
||||||
<div className="flex flex-row items-center gap-4 w-full">
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-4 tw-w-full">
|
||||||
<ItemIcon />
|
<ItemIcon />
|
||||||
<span className="font-medium">
|
<span className="tw-font-medium tw-capitalize">
|
||||||
pe:{itemName}.t pe:{itemName}
|
{i18n && i18n.en.o[itemName]?.t ? i18n.en.o[itemName].t : itemName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold">
|
<div className="tw-font-bold">
|
||||||
<Value
|
<Value
|
||||||
current={currentValues[itemName]}
|
current={currentValues[itemName]}
|
||||||
config={item}
|
config={item}
|
||||||
|
@ -218,6 +221,7 @@ export const MenuItemGroup = ({
|
||||||
updateHandler,
|
updateHandler,
|
||||||
isDesignOptionsGroup,
|
isDesignOptionsGroup,
|
||||||
Design,
|
Design,
|
||||||
|
i18n,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -234,6 +238,7 @@ export const MenuItemGroup = ({
|
||||||
updateHandler,
|
updateHandler,
|
||||||
passProps,
|
passProps,
|
||||||
Design,
|
Design,
|
||||||
|
i18n,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -241,7 +246,7 @@ export const MenuItemGroup = ({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
return <SubAccordion items={content.filter((item) => item !== null)} />
|
return <SubAccordion items={content.filter((item) => item !== null)} dense />
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -253,11 +258,13 @@ export const MenuItemGroup = ({
|
||||||
* @param {String} options.emoji the emoji icon of the menu item
|
* @param {String} options.emoji the emoji icon of the menu item
|
||||||
*/
|
*/
|
||||||
export const MenuItemTitle = ({ name, current = null, open = false, emoji = '', Icon = false }) => (
|
export const MenuItemTitle = ({ name, current = null, open = false, emoji = '', Icon = false }) => (
|
||||||
<div className={`flex flex-row gap-1 items-center w-full ${open ? '' : 'justify-between'}`}>
|
<div
|
||||||
<span className="font-medium capitalize flex flex-row gap-2">
|
className={`tw-flex tw-flex-row tw-gap-1 tw-items-center tw-w-full ${open ? '' : 'tw-justify-between'}`}
|
||||||
|
>
|
||||||
|
<span className="tw-font-medium tw-capitalize tw-flex tw-flex-row tw-gap-2">
|
||||||
{Icon ? <Icon /> : <span role="img">{emoji}</span>}
|
{Icon ? <Icon /> : <span role="img">{emoji}</span>}
|
||||||
fixme: {name}
|
fixme: {name}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-bold">{current}</span>
|
<span className="tw-font-bold">{current}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { menuDesignOptionsStructure } from '../../lib/index.mjs'
|
import { menuDesignOptionsStructure } from '../../lib/index.mjs'
|
||||||
|
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,
|
||||||
|
@ -20,7 +22,7 @@ import {
|
||||||
MenyMmOptionValue,
|
MenyMmOptionValue,
|
||||||
MenuPctOptionValue,
|
MenuPctOptionValue,
|
||||||
} from './Value.mjs'
|
} from './Value.mjs'
|
||||||
import { MenuItemGroup } from './Container.mjs'
|
import { MenuItemGroup, MenuItem } from './Container.mjs'
|
||||||
import { OptionsIcon } from '@freesewing/react/components/Icon'
|
import { OptionsIcon } from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -32,6 +34,7 @@ import { OptionsIcon } from '@freesewing/react/components/Icon'
|
||||||
* @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, 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]
|
||||||
|
@ -41,7 +44,7 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, update }) =>
|
||||||
[update.settings]
|
[update.settings]
|
||||||
)
|
)
|
||||||
|
|
||||||
const drillProps = { Design, state, update }
|
const drillProps = { Design, state, update, i18n }
|
||||||
const inputs = {
|
const inputs = {
|
||||||
bool: (props) => <MenuBoolInput {...drillProps} {...props} />,
|
bool: (props) => <MenuBoolInput {...drillProps} {...props} />,
|
||||||
constant: (props) => <MenuConstantInput {...drillProps} {...props} />,
|
constant: (props) => <MenuConstantInput {...drillProps} {...props} />,
|
||||||
|
@ -72,8 +75,7 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, update }) =>
|
||||||
Icon: OptionsIcon,
|
Icon: OptionsIcon,
|
||||||
Item: (props) => <DesignOption {...{ inputs, values, update, Design }} {...props} />,
|
Item: (props) => <DesignOption {...{ inputs, values, update, Design }} {...props} />,
|
||||||
isFirst,
|
isFirst,
|
||||||
name: 'pe:designOptions',
|
name: 'Design Options',
|
||||||
language: state.locale,
|
|
||||||
passProps: {
|
passProps: {
|
||||||
ux: state.ui.ux,
|
ux: state.ui.ux,
|
||||||
settings: state.settings,
|
settings: state.settings,
|
||||||
|
@ -85,6 +87,7 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, update }) =>
|
||||||
Design,
|
Design,
|
||||||
inputs,
|
inputs,
|
||||||
values,
|
values,
|
||||||
|
i18n,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import React, { useMemo, useCallback, useState } from 'react'
|
import React, { useMemo, useCallback, useState } from 'react'
|
||||||
import { round } from '@freesewing/utils'
|
import { designOptionType, round } from '@freesewing/utils'
|
||||||
import { ButtonFrame } from '@freesewing/react/components/Input'
|
import { menuRoundPct } from '../../lib/index.mjs'
|
||||||
|
import { ButtonFrame, NumberInput } from '@freesewing/react/components/Input'
|
||||||
|
import { defaultConfig } from '../../config/index.mjs'
|
||||||
|
import { ApplyIcon } from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
|
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
|
||||||
export const MenuBoolInput = (props) => {
|
export const MenuBoolInput = (props) => {
|
||||||
|
@ -23,8 +26,8 @@ export const MenuConstantInput = ({
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={`
|
className={`
|
||||||
input input-bordered w-full text-base-content
|
tw-daisy-input tw-daisy-input-bordered tw-w-full tw-text-base-content
|
||||||
input-${changed ? 'secondary' : 'accent'}
|
${changed ? 'tw-daisy-input-secondary' : 'tw-daisy-input-accent'}
|
||||||
`}
|
`}
|
||||||
value={changed ? current : config.dflt}
|
value={changed ? current : config.dflt}
|
||||||
onChange={(evt) => updateHandler([name], evt.target.value)}
|
onChange={(evt) => updateHandler([name], evt.target.value)}
|
||||||
|
@ -99,12 +102,12 @@ export const MenuListInput = ({
|
||||||
onClick={() => handleChange(entry)}
|
onClick={() => handleChange(entry)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-full flex items-start ${
|
className={`tw-w-full tw-flex tw-items-start ${
|
||||||
sideBySide ? 'flex-row justify-between gap-2' : 'flex-col'
|
sideBySide ? 'tw-flex-row tw-justify-between tw-gap-2' : 'tw-flex-col'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="font-bold text-lg shrink-0">{title}</div>
|
<div className="tw-font-bold tw-text-lg tw-shrink-0">{title}</div>
|
||||||
{compact ? null : <div className="text-base font-normal">{desc}</div>}
|
{compact ? null : <div className="tw-text-base tw-font-normal">{desc}</div>}
|
||||||
</div>
|
</div>
|
||||||
</ButtonFrame>
|
</ButtonFrame>
|
||||||
)
|
)
|
||||||
|
@ -126,7 +129,7 @@ export const MenuListToggle = ({ config, changed, updateHandler, name }) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className={`toggle ${changed ? 'toggle-accent' : 'toggle-secondary'}`}
|
className={`tw-daisy-toggle ${changed ? 'tw-daisy-toggle-accent' : 'tw-daisy-toggle-secondary'}`}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onChange={doToggle}
|
onChange={doToggle}
|
||||||
onClick={(evt) => evt.stopPropagation()}
|
onClick={(evt) => evt.stopPropagation()}
|
||||||
|
@ -282,6 +285,7 @@ export const MenuSliderInput = ({
|
||||||
setReset,
|
setReset,
|
||||||
children,
|
children,
|
||||||
changed,
|
changed,
|
||||||
|
i18n,
|
||||||
}) => {
|
}) => {
|
||||||
const { max, min } = config
|
const { max, min } = config
|
||||||
const handleChange = useSharedHandlers({
|
const handleChange = useSharedHandlers({
|
||||||
|
@ -297,7 +301,7 @@ export const MenuSliderInput = ({
|
||||||
if (override)
|
if (override)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row justify-between">
|
<div className="tw-flex tw-flex-row tw-justify-between">
|
||||||
<MenuEditOption
|
<MenuEditOption
|
||||||
{...{
|
{...{
|
||||||
config,
|
config,
|
||||||
|
@ -314,14 +318,16 @@ export const MenuSliderInput = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row justify-between">
|
<div className="tw-flex tw-flex-row tw-justify-between">
|
||||||
<span className="opacity-50">
|
<span className="tw-opacity-50">
|
||||||
<span dangerouslySetInnerHTML={{ __html: valFormatter(min) + suffix }} />
|
<span dangerouslySetInnerHTML={{ __html: valFormatter(min) + suffix }} />
|
||||||
</span>
|
</span>
|
||||||
<span className={`font-bold ${val === config.dflt ? 'text-secondary' : 'text-accent'}`}>
|
<span
|
||||||
|
className={`tw-font-bold ${val === config.dflt ? 'tw-text-secondary' : 'tw-text-accent'}`}
|
||||||
|
>
|
||||||
<span dangerouslySetInnerHTML={{ __html: valFormatter(val) + suffix }} />
|
<span dangerouslySetInnerHTML={{ __html: valFormatter(val) + suffix }} />
|
||||||
</span>
|
</span>
|
||||||
<span className="opacity-50">
|
<span className="tw-opacity-50">
|
||||||
<span dangerouslySetInnerHTML={{ __html: valFormatter(max) + suffix }} />
|
<span dangerouslySetInnerHTML={{ __html: valFormatter(max) + suffix }} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -330,8 +336,8 @@ export const MenuSliderInput = ({
|
||||||
{...{ min, max, value: val, step: config.step || 0.1 }}
|
{...{ min, max, value: val, step: config.step || 0.1 }}
|
||||||
onChange={(evt) => handleChange(evt.target.value)}
|
onChange={(evt) => handleChange(evt.target.value)}
|
||||||
className={`
|
className={`
|
||||||
range range-sm mt-1
|
tw-daisy-range tw-daisy-range-sm tw-mt-1
|
||||||
${changed ? 'range-accent' : 'range-secondary'}
|
${changed ? 'tw-daisy-range-accent' : 'tw-daisy-range-secondary'}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
|
@ -355,13 +361,16 @@ export const MenuEditOption = (props) => {
|
||||||
return <p>This design option type does not have a component to handle manual input.</p>
|
return <p>This design option type does not have a component to handle manual input.</p>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-control mb-2 w-full">
|
<div className="tw-daisy-form-control tw-mb-2 tw-w-full">
|
||||||
<label className="label font-medium text-accent">
|
<label className="tw-daisy-label tw-font-medium tw-text-accent">
|
||||||
<em>Enter a custom value ({config.menuOptionEditLabels[type]})</em>
|
<em>Enter a custom value ({defaultConfig.menuOptionEditLabels[type]})</em>
|
||||||
</label>
|
</label>
|
||||||
<label className="input-group input-group-sm flex flex-row items-center gap-2 -mt-4">
|
<label className="tw-daisy-input-group tw-daisy-input-group-sm tw-flex tw-flex-row tw-items-center tw-gap-2 tw--mt-4">
|
||||||
<NumberInput value={manualEdit} update={setManualEdit} />
|
<NumberInput value={manualEdit} update={setManualEdit} />
|
||||||
<button className="btn btn-secondary mt-4" onClick={() => onUpdate(manualEdit)}>
|
<button
|
||||||
|
className="tw-daisy-btn tw-daisy-btn-secondary tw-mt-4"
|
||||||
|
onClick={() => onUpdate(manualEdit)}
|
||||||
|
>
|
||||||
<ApplyIcon />
|
<ApplyIcon />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
|
@ -416,9 +425,11 @@ export const MenuOnlySettingInput = (props) => {
|
||||||
config.sideBySide = true
|
config.sideBySide = true
|
||||||
config.titleMethod = (entry, t) => {
|
config.titleMethod = (entry, t) => {
|
||||||
const chunks = entry.split('.')
|
const chunks = entry.split('.')
|
||||||
return <span className="font-medium text-base">{t(`${chunks[0]}:${chunks[1]}`)}</span>
|
return <span className="tw-font-medium tw-text-base">{t(`${chunks[0]}:${chunks[1]}`)}</span>
|
||||||
}
|
}
|
||||||
config.valueMethod = (entry) => <span className="text-sm">{capitalize(entry.split('.')[0])}</span>
|
config.valueMethod = (entry) => (
|
||||||
|
<span className="tw-text-sm">{capitalize(entry.split('.')[0])}</span>
|
||||||
|
)
|
||||||
config.dense = true
|
config.dense = true
|
||||||
// Sort alphabetically (translated)
|
// Sort alphabetically (translated)
|
||||||
const order = []
|
const order = []
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { MenuItemGroup } 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 }) => {
|
||||||
console.log(state)
|
|
||||||
const structure = menuUiPreferencesStructure()
|
const structure = menuUiPreferencesStructure()
|
||||||
|
|
||||||
const drillProps = { Design, state, update }
|
const drillProps = { Design, state, update }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { mergeOptions } from '@freesewing/core'
|
import { mergeOptions } from '@freesewing/core'
|
||||||
import { formatMm } from '@freesewing/utils'
|
import { formatMm, formatPercentage } from '@freesewing/utils'
|
||||||
import { BoolYesIcon, BoolNoIcon } from '@freesewing/react/components/Icon'
|
import { BoolYesIcon, BoolNoIcon } from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
134
packages/react/components/Editor/components/views/UndosView.mjs
Normal file
134
packages/react/components/Editor/components/views/UndosView.mjs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { orderBy } from '@freesewing/utils'
|
||||||
|
import { ButtonFrame } from '@freesewing/react/components/Input'
|
||||||
|
import { UndoIcon } from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
|
import { getUndoStepData } from '../../lib/index.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The undos view shows the undo history, and allows restoring any undo state
|
||||||
|
*
|
||||||
|
* @param (object) props - All the props
|
||||||
|
* @param {object} designs - Object holding all designs
|
||||||
|
* @param {object} update - ViewWrapper state update object
|
||||||
|
*/
|
||||||
|
export const UndosView = ({ Design, update, state }) => {
|
||||||
|
const steps = orderBy(state._.undos, 'time', 'desc')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderMenu state={state} {...{ update, Design }} />
|
||||||
|
<div className="tw-text-left tw-mt-8 tw-mb-24 tw-px-4 tw-max-w-xl tw-mx-auto">
|
||||||
|
<h2>Undo History</h2>
|
||||||
|
<p>Time-travel through your recent pattern changes</p>
|
||||||
|
<small>
|
||||||
|
<b>Tip:</b> Click on any change to undo all changes up to, and including, that change.
|
||||||
|
</small>
|
||||||
|
{steps.length < 1 ? (
|
||||||
|
<Popout note>
|
||||||
|
<h4>Your undo history is currently empty</h4>
|
||||||
|
<p>When you make changes to your pattern, they will show up here.</p>
|
||||||
|
<p>For example, you can click the button below to change the pattern rotation:</p>
|
||||||
|
<button
|
||||||
|
className="tw-daisy-btn tw-daisy-btn-primary tw-capitalize"
|
||||||
|
onClick={() => update.settings('ui.rotate', state.settings?.ui?.rotate ? 0 : 1)}
|
||||||
|
>
|
||||||
|
Example: Rotate pattern
|
||||||
|
</button>
|
||||||
|
<p>As soon as you do, the change will show up here, and you can undo it.</p>
|
||||||
|
</Popout>
|
||||||
|
) : (
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-2 tw-mt-4">
|
||||||
|
{steps.map((step, index) => (
|
||||||
|
<UndoStep key={step.time} {...{ step, update, state, Design, index }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UndoStepTimeAgo = ({ step }) => {
|
||||||
|
if (!step.time) return null
|
||||||
|
const secondsAgo = Math.floor((Date.now() - step.time) / 100) / 10
|
||||||
|
const minutesAgo = Math.floor(secondsAgo / 60)
|
||||||
|
const hoursAgo = Math.floor(minutesAgo / 60)
|
||||||
|
|
||||||
|
return hoursAgo ? (
|
||||||
|
<span>{hoursAgo} hours ago</span>
|
||||||
|
) : minutesAgo ? (
|
||||||
|
<span>{minutesAgo} minutes ago</span>
|
||||||
|
) : (
|
||||||
|
<span>{secondsAgo} seconds ago</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UndoStep = ({ update, state, step, Design, compact = false, index = 0 }) => {
|
||||||
|
/*
|
||||||
|
* Ensure path is always an array
|
||||||
|
*/
|
||||||
|
if (!Array.isArray(step.path)) step.path = step.path.split('.')
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Figure this out once
|
||||||
|
*/
|
||||||
|
const imperial = state.settings?.units === 'imperial' ? true : false
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metadata can be ignored
|
||||||
|
*/
|
||||||
|
if (step.name === 'settings' && step.path[1] === 'metadata') return null
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Defer for anything else to this method
|
||||||
|
*/
|
||||||
|
const data = getUndoStepData({ step, state, Design, imperial })
|
||||||
|
|
||||||
|
if (data === false) return <pre>{JSON.stringify(step, null, 2)}</pre> //null
|
||||||
|
if (data === null) return <p>Unsupported</p>
|
||||||
|
|
||||||
|
if (compact)
|
||||||
|
return (
|
||||||
|
<ButtonFrame dense onClick={() => update.restore(index, state._)}>
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-w-full">
|
||||||
|
<UndoIcon text={index} className="tw-w-5 tw-h-5 tw-text-secondary" />
|
||||||
|
{data.msg ? data.msg : `pe:${data.optCode}`}
|
||||||
|
</div>
|
||||||
|
</ButtonFrame>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className="tw-text-sm tw-italic tw-font-medium tw-opacity-70 tw-text-right tw-p-0 tw-tw-m-0 tw--mb-2 tw-pr-2">
|
||||||
|
<UndoStepTimeAgo step={step} />
|
||||||
|
</p>
|
||||||
|
<ButtonFrame onClick={() => update.restore(index, state._)}>
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-gap-2 tw-w-full tw-m-0 tw-p-0 tw--mt-2 tw-text-lg">
|
||||||
|
<span className="tw-flex tw-flex-row tw-gap-2 tw-items-center">
|
||||||
|
{data.fieldIcon || null}
|
||||||
|
{`pe:${data.optCode}`}
|
||||||
|
</span>
|
||||||
|
<span className="tw-opacity-70 tw-flex tw-flex-row tw-gap-1 tw-items-center tw-text-base">
|
||||||
|
{data.icon || null} {`pe:${data.titleCode}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="tw-flex tw-flex-row tw-gap-1 tw-items-center tw-align-start tw-w-full">
|
||||||
|
{data.msg ? (
|
||||||
|
data.msg
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="">
|
||||||
|
{Array.isArray(data.newVal) ? data.newVal.join(', ') : data.newVal}
|
||||||
|
</span>
|
||||||
|
<LeftIcon className="tw-w-4 tw-h-4 tw-text-secondary tw-shrink-0" stroke={4} />
|
||||||
|
<span className="tw-line-through tw-decoration-1 tw-opacity-70">
|
||||||
|
{Array.isArray(data.oldVal) ? data.oldVal.join(', ') : data.oldVal}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ButtonFrame>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,16 +1,5 @@
|
||||||
export function designOptionType(option) {
|
|
||||||
if (typeof option?.pct !== 'undefined') return 'pct'
|
|
||||||
if (typeof option?.bool !== 'undefined') return 'bool'
|
|
||||||
if (typeof option?.count !== 'undefined') return 'count'
|
|
||||||
if (typeof option?.deg !== 'undefined') return 'deg'
|
|
||||||
if (typeof option?.list !== 'undefined') return 'list'
|
|
||||||
if (typeof option?.mm !== 'undefined') return 'mm'
|
|
||||||
|
|
||||||
return 'constant'
|
|
||||||
}
|
|
||||||
import { mergeOptions } from '@freesewing/core'
|
import { mergeOptions } from '@freesewing/core'
|
||||||
import set from 'lodash.set'
|
import { designOptionType, set, orderBy } from '@freesewing/utils'
|
||||||
import orderBy from 'lodash.orderby'
|
|
||||||
|
|
||||||
export function menuDesignOptionsStructure(options, settings, asFullList = false) {
|
export function menuDesignOptionsStructure(options, settings, asFullList = false) {
|
||||||
if (!options) return options
|
if (!options) return options
|
||||||
|
@ -59,6 +48,7 @@ export function menuDesignOptionsStructure(options, settings, asFullList = false
|
||||||
|
|
||||||
return menu
|
return menu
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helper method to grab an option from an Design options structure
|
* Helper method to grab an option from an Design options structure
|
||||||
*
|
*
|
||||||
|
@ -66,7 +56,6 @@ export function menuDesignOptionsStructure(options, settings, asFullList = false
|
||||||
*/
|
*/
|
||||||
export function getOptionStructure(option, Design, state) {
|
export function getOptionStructure(option, Design, state) {
|
||||||
const structure = menuDesignOptionsStructure(Design.patternConfig.options, state.settings)
|
const structure = menuDesignOptionsStructure(Design.patternConfig.options, state.settings)
|
||||||
console.log({ structure })
|
|
||||||
|
|
||||||
return findOption(structure, option)
|
return findOption(structure, option)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
|
import React from 'react'
|
||||||
import { defaultConfig } from '../config/index.mjs'
|
import { defaultConfig } from '../config/index.mjs'
|
||||||
|
import { round } from '@freesewing/utils'
|
||||||
|
import { formatDesignOptionValue } from './index.mjs'
|
||||||
// Components
|
// Components
|
||||||
import {
|
import {
|
||||||
ErrorIcon,
|
ErrorIcon,
|
||||||
|
|
|
@ -1,28 +1,22 @@
|
||||||
|
import { designOptionType } from '@freesewing/utils'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Method that capitalizes a string (make the first character uppercase)
|
* Method that capitalizes a string (make the first character uppercase)
|
||||||
*
|
*
|
||||||
* @param {object} Swizzled - Swizzled code, not used here
|
|
||||||
* @param {string} string - The input string to capitalize
|
* @param {string} string - The input string to capitalize
|
||||||
* @return {string} String - The capitalized input string
|
* @return {string} String - The capitalized input string
|
||||||
*/
|
*/
|
||||||
export function capitalize(Swizzled, string) {
|
export function capitalize(string) {
|
||||||
return typeof string === 'string' ? string.charAt(0).toUpperCase() + string.slice(1) : ''
|
return typeof string === 'string' ? string.charAt(0).toUpperCase() + string.slice(1) : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDesignOptionValue(Swizzled, option, value, imperial) {
|
export function formatDesignOptionValue(option, value, imperial) {
|
||||||
const oType = Swizzled.methods.designOptionType(option)
|
const oType = designOptionType(option)
|
||||||
if (oType === 'pct') return Swizzled.methods.formatPercentage(value ? value : option.pct / 100)
|
if (oType === 'pct') return formatPercentage(value ? value : option.pct / 100)
|
||||||
if (oType === 'deg') return `${value ? value : option.deg}°`
|
if (oType === 'deg') return `${value ? value : option.deg}°`
|
||||||
if (oType === 'bool')
|
if (oType === 'bool')
|
||||||
return typeof value === 'undefined' ? (
|
return typeof value === 'undefined' ? option.bool : value ? <BoolYesIcon /> : <BoolNoIcon />
|
||||||
option.bool
|
if (oType === 'mm') return formatMm(typeof value === 'undefined' ? option.mm : value, imperial)
|
||||||
) : value ? (
|
|
||||||
<Swizzled.components.BoolYesIcon />
|
|
||||||
) : (
|
|
||||||
<Swizzled.components.BoolNoIcon />
|
|
||||||
)
|
|
||||||
if (oType === 'mm')
|
|
||||||
return Swizzled.methods.formatMm(typeof value === 'undefined' ? option.mm : value, imperial)
|
|
||||||
if (oType === 'list') return typeof value === 'undefined' ? option.dflt : value
|
if (oType === 'list') return typeof value === 'undefined' ? option.dflt : value
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
@ -36,7 +30,7 @@ export function formatDesignOptionValue(Swizzled, option, value, imperial) {
|
||||||
* fraction: the value to process
|
* fraction: the value to process
|
||||||
* format: the type of formatting to apply. html, notags, or anything else which will only return numbers
|
* format: the type of formatting to apply. html, notags, or anything else which will only return numbers
|
||||||
*/
|
*/
|
||||||
export function formatFraction128(Swizzled, fraction, format = 'html') {
|
export function formatFraction128(fraction, format = 'html') {
|
||||||
let negative = ''
|
let negative = ''
|
||||||
let inches = ''
|
let inches = ''
|
||||||
let rest = ''
|
let rest = ''
|
||||||
|
@ -50,19 +44,12 @@ export function formatFraction128(Swizzled, fraction, format = 'html') {
|
||||||
rest = fraction - inches
|
rest = fraction - inches
|
||||||
}
|
}
|
||||||
let fraction128 = Math.round(rest * 128)
|
let fraction128 = Math.round(rest * 128)
|
||||||
if (fraction128 == 0)
|
if (fraction128 == 0) return formatImperial(negative, inches || fraction128, false, false, format)
|
||||||
return Swizzled.methods.formatImperial(negative, inches || fraction128, false, false, format)
|
|
||||||
|
|
||||||
for (let i = 1; i < 7; i++) {
|
for (let i = 1; i < 7; i++) {
|
||||||
const numoFactor = Math.pow(2, 7 - i)
|
const numoFactor = Math.pow(2, 7 - i)
|
||||||
if (fraction128 % numoFactor === 0)
|
if (fraction128 % numoFactor === 0)
|
||||||
return Swizzled.methods.formatImperial(
|
return formatImperial(negative, inches, fraction128 / numoFactor, Math.pow(2, i), format)
|
||||||
negative,
|
|
||||||
inches,
|
|
||||||
fraction128 / numoFactor,
|
|
||||||
Math.pow(2, i),
|
|
||||||
format
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -73,7 +60,7 @@ export function formatFraction128(Swizzled, fraction, format = 'html') {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formatting for imperial values
|
// Formatting for imperial values
|
||||||
export function formatImperial(Swizzled, neg, inch, numo = false, deno = false, format = 'html') {
|
export function formatImperial(neg, inch, numo = false, deno = false, format = 'html') {
|
||||||
if (format === 'html') {
|
if (format === 'html') {
|
||||||
if (numo) return `${neg}${inch} <sup>${numo}</sup>/<sub>${deno}</sub>"`
|
if (numo) return `${neg}${inch} <sup>${numo}</sup>/<sub>${deno}</sub>"`
|
||||||
else return `${neg}${inch}"`
|
else return `${neg}${inch}"`
|
||||||
|
@ -88,28 +75,27 @@ export function formatImperial(Swizzled, neg, inch, numo = false, deno = false,
|
||||||
|
|
||||||
// Format a value in mm based on the user's units
|
// Format a value in mm based on the user's units
|
||||||
// Format can be html, notags, or anything else which will only return numbers
|
// Format can be html, notags, or anything else which will only return numbers
|
||||||
export function formatMm(Swizzled, val, units, format = 'html') {
|
export function formatMm(val, units, format = 'html') {
|
||||||
val = Swizzled.methods.roundMm(val)
|
val = roundMm(val)
|
||||||
if (units === 'imperial' || units === true) {
|
if (units === 'imperial' || units === true) {
|
||||||
if (val == 0) return Swizzled.methods.formatImperial('', 0, false, false, format)
|
if (val == 0) return formatImperial('', 0, false, false, format)
|
||||||
|
|
||||||
let fraction = val / 25.4
|
let fraction = val / 25.4
|
||||||
return Swizzled.methods.formatFraction128(fraction, format)
|
return formatFraction128(fraction, format)
|
||||||
} else {
|
} else {
|
||||||
if (format === 'html' || format === 'notags') return Swizzled.methods.roundMm(val / 10) + 'cm'
|
if (format === 'html' || format === 'notags') return roundMm(val / 10) + 'cm'
|
||||||
else return Swizzled.methods.roundMm(val / 10)
|
else return roundMm(val / 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format a percentage (as in, between 0 and 1)
|
// Format a percentage (as in, between 0 and 1)
|
||||||
export function formatPercentage(Swizzled, val) {
|
export function formatPercentage(val) {
|
||||||
return Math.round(1000 * val) / 10 + '%'
|
return Math.round(1000 * val) / 10 + '%'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic rounding method
|
* A generic rounding method
|
||||||
*
|
*
|
||||||
* @param {object} Swizzled - Swizzled code, not used here
|
|
||||||
* @param {number} val - The input number to round
|
* @param {number} val - The input number to round
|
||||||
* @param {number} decimals - The number of decimal points to use when rounding
|
* @param {number} decimals - The number of decimal points to use when rounding
|
||||||
* @return {number} result - The rounded number
|
* @return {number} result - The rounded number
|
||||||
|
@ -119,7 +105,7 @@ export function round(methods, val, decimals = 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rounds a value in mm
|
// Rounds a value in mm
|
||||||
export function roundMm(Swizzled, val, units) {
|
export function roundMm(val, units) {
|
||||||
if (units === 'imperial') return Math.round(val * 1000000) / 1000000
|
if (units === 'imperial') return Math.round(val * 1000000) / 1000000
|
||||||
else return Math.round(val * 10) / 10
|
else return Math.round(val * 10) / 10
|
||||||
}
|
}
|
||||||
|
@ -127,11 +113,10 @@ export function roundMm(Swizzled, val, units) {
|
||||||
/**
|
/**
|
||||||
* Converts a value that contain a fraction to a decimal
|
* Converts a value that contain a fraction to a decimal
|
||||||
*
|
*
|
||||||
* @param {object} Swizzled - Swizzled code, not used here
|
|
||||||
* @param {number} value - The input value
|
* @param {number} value - The input value
|
||||||
* @return {number} result - The resulting decimal value
|
* @return {number} result - The resulting decimal value
|
||||||
*/
|
*/
|
||||||
export function fractionToDecimal(Swizzled, value) {
|
export function fractionToDecimal(value) {
|
||||||
// if it's just a number, return it
|
// if it's just a number, return it
|
||||||
if (!isNaN(value)) return value
|
if (!isNaN(value)) return value
|
||||||
|
|
||||||
|
@ -170,12 +155,11 @@ export function fractionToDecimal(Swizzled, value) {
|
||||||
/**
|
/**
|
||||||
* Helper method to turn a measurement in millimeter regardless of units
|
* Helper method to turn a measurement in millimeter regardless of units
|
||||||
*
|
*
|
||||||
* @param {object} Swizzled - Swizzled code, including methods
|
|
||||||
* @param {number} value - The input value
|
* @param {number} value - The input value
|
||||||
* @param {string} units - One of 'metric' or 'imperial'
|
* @param {string} units - One of 'metric' or 'imperial'
|
||||||
* @return {number} result - Value in millimeter
|
* @return {number} result - Value in millimeter
|
||||||
*/
|
*/
|
||||||
export function measurementAsMm(Swizzled, value, units = 'metric') {
|
export function measurementAsMm(value, units = 'metric') {
|
||||||
if (typeof value === 'number') return value * (units === 'imperial' ? 25.4 : 10)
|
if (typeof value === 'number') return value * (units === 'imperial' ? 25.4 : 10)
|
||||||
|
|
||||||
if (String(value).endsWith('.')) return false
|
if (String(value).endsWith('.')) return false
|
||||||
|
@ -185,7 +169,7 @@ export function measurementAsMm(Swizzled, value, units = 'metric') {
|
||||||
if (isNaN(value)) return false
|
if (isNaN(value)) return false
|
||||||
return value * 10
|
return value * 10
|
||||||
} else {
|
} else {
|
||||||
const decimal = Swizzled.methods.fractionToDecimal(value)
|
const decimal = fractionToDecimal(value)
|
||||||
if (isNaN(decimal)) return false
|
if (isNaN(decimal)) return false
|
||||||
return decimal * 24.5
|
return decimal * 24.5
|
||||||
}
|
}
|
||||||
|
@ -194,13 +178,12 @@ export function measurementAsMm(Swizzled, value, units = 'metric') {
|
||||||
/**
|
/**
|
||||||
* Converts a millimeter value to a Number value in the given units
|
* Converts a millimeter value to a Number value in the given units
|
||||||
*
|
*
|
||||||
* @param {object} Swizzled - Swizzled code, not used here
|
|
||||||
* @param {number} mmValue - The input value in millimeter
|
* @param {number} mmValue - The input value in millimeter
|
||||||
* @param {string} units - One of 'metric' or 'imperial'
|
* @param {string} units - One of 'metric' or 'imperial'
|
||||||
* @result {number} result - The result in millimeter
|
* @result {number} result - The result in millimeter
|
||||||
*/
|
*/
|
||||||
export function measurementAsUnits(Swizzled, mmValue, units = 'metric') {
|
export function measurementAsUnits(mmValue, units = 'metric') {
|
||||||
return Swizzled.methods.round(mmValue / (units === 'imperial' ? 25.4 : 10), 3)
|
return round(mmValue / (units === 'imperial' ? 25.4 : 10), 3)
|
||||||
}
|
}
|
||||||
export function shortDate(locale = 'en', timestamp = false, withTime = true) {
|
export function shortDate(locale = 'en', timestamp = false, withTime = true) {
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -220,12 +203,11 @@ export function shortDate(locale = 'en', timestamp = false, withTime = true) {
|
||||||
/*
|
/*
|
||||||
* Parses value that should be a distance (cm or inch)
|
* Parses value that should be a distance (cm or inch)
|
||||||
*
|
*
|
||||||
* @param {object} Swizzled - Swizzled code, including methods
|
|
||||||
* @param {number} val - The input value
|
* @param {number} val - The input value
|
||||||
* @param {bool} imperial - True if the units are imperial, false for metric
|
* @param {bool} imperial - True if the units are imperial, false for metric
|
||||||
* @return {number} result - The distance in the relevant units
|
* @return {number} result - The distance in the relevant units
|
||||||
*/
|
*/
|
||||||
export function parseDistanceInput(Swizzled, val = false, imperial = false) {
|
export function parseDistanceInput(val = false, imperial = false) {
|
||||||
// No input is not valid
|
// No input is not valid
|
||||||
if (!val) return false
|
if (!val) return false
|
||||||
|
|
||||||
|
@ -239,7 +221,7 @@ export function parseDistanceInput(Swizzled, val = false, imperial = false) {
|
||||||
if (!val.match(regex)) return false
|
if (!val.match(regex)) return false
|
||||||
|
|
||||||
// if fractions are allowed, parse for fractions, otherwise use the number as a value
|
// if fractions are allowed, parse for fractions, otherwise use the number as a value
|
||||||
if (imperial) val = Swizzled.methods.fractionToDecimal(val)
|
if (imperial) val = fractionToDecimal(val)
|
||||||
|
|
||||||
return isNaN(val) ? false : Number(val)
|
return isNaN(val) ? false : Number(val)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,7 @@ import {
|
||||||
menuCoreSettingsSammHandler,
|
menuCoreSettingsSammHandler,
|
||||||
menuCoreSettingsStructure,
|
menuCoreSettingsStructure,
|
||||||
} from './core-settings.mjs'
|
} from './core-settings.mjs'
|
||||||
import {
|
import { findOption, getOptionStructure, menuDesignOptionsStructure } from './design-options.mjs'
|
||||||
designOptionType,
|
|
||||||
findOption,
|
|
||||||
getOptionStructure,
|
|
||||||
menuDesignOptionsStructure,
|
|
||||||
} from './design-options.mjs'
|
|
||||||
import {
|
import {
|
||||||
addUndoStep,
|
addUndoStep,
|
||||||
cloneObject,
|
cloneObject,
|
||||||
|
@ -70,7 +65,6 @@ export {
|
||||||
menuCoreSettingsSammHandler,
|
menuCoreSettingsSammHandler,
|
||||||
menuCoreSettingsStructure,
|
menuCoreSettingsStructure,
|
||||||
// design-options.mjs
|
// design-options.mjs
|
||||||
designOptionType,
|
|
||||||
findOption,
|
findOption,
|
||||||
getOptionStructure,
|
getOptionStructure,
|
||||||
menuDesignOptionsStructure,
|
menuDesignOptionsStructure,
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
import orderBy from 'lodash.orderby'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The undos view shows the undo history, and allows restoring any undo state
|
|
||||||
*
|
|
||||||
* @param (object) props - All the props
|
|
||||||
* @param {object} props.swizzled - An object with swizzled components, hooks, methods, config, and defaults
|
|
||||||
* @param {object} designs - Object holding all designs
|
|
||||||
* @param {object} update - ViewWrapper state update object
|
|
||||||
*/
|
|
||||||
export const UndosView = ({ Design, Swizzled, update, state }) => {
|
|
||||||
const steps = orderBy(state._.undos, 'time', 'desc')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Swizzled.components.HeaderMenu state={state} {...{ Swizzled, update, Design }} />
|
|
||||||
<div className="text-left mt-8 mb-24 px-4 max-w-xl mx-auto">
|
|
||||||
<h2>{Swizzled.methods.t('pe:view.undos.t')}</h2>
|
|
||||||
<p>{Swizzled.methods.t('pe:view.undos.d')}</p>
|
|
||||||
<small>
|
|
||||||
<b>Tip:</b> Click on any change to undo all changes up to, and including, that change.
|
|
||||||
</small>
|
|
||||||
{steps.length < 1 ? (
|
|
||||||
<Swizzled.components.Popout note>
|
|
||||||
<h4>Your undo history is currently empty</h4>
|
|
||||||
<p>When you make changes to your pattern, they will show up here.</p>
|
|
||||||
<p>For example, you can click the button below to change the pattern rotation:</p>
|
|
||||||
<button
|
|
||||||
className="btn btn-primary capitalize"
|
|
||||||
onClick={() => update.settings('ui.rotate', state.settings?.ui?.rotate ? 0 : 1)}
|
|
||||||
>
|
|
||||||
{Swizzled.methods.t('pe:example')}: {Swizzled.methods.t('pe:rotate.t')}
|
|
||||||
</button>
|
|
||||||
<p>As soon as you do, the change will show up here, and you can undo it.</p>
|
|
||||||
</Swizzled.components.Popout>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col gap-2 mt-4">
|
|
||||||
{steps.map((step, index) => (
|
|
||||||
<Swizzled.components.UndoStep
|
|
||||||
key={step.time}
|
|
||||||
{...{ step, update, state, Design, index }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UndoStepTimeAgo = ({ Swizzled, step }) => {
|
|
||||||
if (!step.time) return null
|
|
||||||
const secondsAgo = Math.floor((Date.now() - step.time) / 100) / 10
|
|
||||||
const minutesAgo = Math.floor(secondsAgo / 60)
|
|
||||||
const hoursAgo = Math.floor(minutesAgo / 60)
|
|
||||||
|
|
||||||
return hoursAgo ? (
|
|
||||||
<span>
|
|
||||||
{hoursAgo} {Swizzled.methods.t('pe:hoursAgo')}
|
|
||||||
</span>
|
|
||||||
) : minutesAgo ? (
|
|
||||||
<span>
|
|
||||||
{minutesAgo} {Swizzled.methods.t('pe:minutesAgo')}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
{secondsAgo} {Swizzled.methods.t('pe:secondsAgo')}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UndoStep = ({ Swizzled, update, state, step, Design, compact = false, index = 0 }) => {
|
|
||||||
const { t } = Swizzled.methods
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ensure path is always an array
|
|
||||||
*/
|
|
||||||
if (!Array.isArray(step.path)) step.path = step.path.split('.')
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Figure this out once
|
|
||||||
*/
|
|
||||||
const imperial = state.settings?.units === 'imperial' ? true : false
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Metadata can be ignored
|
|
||||||
*/
|
|
||||||
if (step.name === 'settings' && step.path[1] === 'metadata') return null
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Defer for anything else to this method
|
|
||||||
*/
|
|
||||||
const data = Swizzled.methods.getUndoStepData({ step, state, Design, imperial })
|
|
||||||
|
|
||||||
if (data === false) return <pre>{JSON.stringify(step, null, 2)}</pre> //null
|
|
||||||
if (data === null) return <p>Unsupported</p>
|
|
||||||
|
|
||||||
if (compact)
|
|
||||||
return (
|
|
||||||
<Swizzled.components.ButtonFrame dense onClick={() => update.restore(index, state._)}>
|
|
||||||
<div className="flex flex-row items-center align-start gap-2 w-full">
|
|
||||||
<Swizzled.components.UndoIcon text={index} className="w-5 h-5 text-secondary" />
|
|
||||||
{data.msg ? data.msg : t(`pe:${data.optCode}`)}
|
|
||||||
</div>
|
|
||||||
</Swizzled.components.ButtonFrame>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className="text-sm italic font-medium opacity-70 text-right p-0 m-0 -mb-2 pr-2">
|
|
||||||
<Swizzled.components.UndoStepTimeAgo step={step} />
|
|
||||||
</p>
|
|
||||||
<Swizzled.components.ButtonFrame onClick={() => update.restore(index, state._)}>
|
|
||||||
<div className="flex flex-row items-center justify-between gap-2 w-full m-0 p-0 -mt-2 text-lg">
|
|
||||||
<span className="flex flex-row gap-2 items-center">
|
|
||||||
{data.fieldIcon || null}
|
|
||||||
{t(`pe:${data.optCode}`)}
|
|
||||||
</span>
|
|
||||||
<span className="opacity-70 flex flex-row gap-1 items-center text-base">
|
|
||||||
{data.icon || null} {t(`pe:${data.titleCode}`)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row gap-1 items-center align-start w-full">
|
|
||||||
{data.msg ? (
|
|
||||||
data.msg
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="">
|
|
||||||
{Array.isArray(data.newVal) ? data.newVal.join(', ') : data.newVal}
|
|
||||||
</span>
|
|
||||||
<Swizzled.components.LeftIcon
|
|
||||||
className="w-4 h-4 text-secondary shrink-0"
|
|
||||||
stroke={4}
|
|
||||||
/>
|
|
||||||
<span className="line-through decoration-1 opacity-70">
|
|
||||||
{Array.isArray(data.oldVal) ? data.oldVal.join(', ') : data.oldVal}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Swizzled.components.ButtonFrame>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -493,10 +493,10 @@ export const ReloadIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
// Looks like a single rewind arrow
|
// Looks like a backspace key
|
||||||
export const ResetIcon = (props) => (
|
export const ResetIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M16 18 l 0 -12 l -8 6 z M 6 6 l 0 12 l 1 0 l 0 -10 z" />
|
<path d="M12 9.75 14.25 12m0 0 2.25 2.25M14.25 12l2.25-2.25M14.25 12 12 14.25m-2.58 4.92-6.374-6.375a1.125 1.125 0 0 1 0-1.59L9.42 4.83c.21-.211.497-.33.795-.33H19.5a2.25 2.25 0 0 1 2.25 2.25v10.5a2.25 2.25 0 0 1-2.25 2.25h-9.284c-.298 0-.585-.119-.795-.33Z" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
122
packages/react/hooks/useDesignTranslation/index.mjs
Normal file
122
packages/react/hooks/useDesignTranslation/index.mjs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import { i18n as aaron } from '@freesewing/aaron'
|
||||||
|
import { i18n as albert } from '@freesewing/albert'
|
||||||
|
import { i18n as bee } from '@freesewing/bee'
|
||||||
|
import { i18n as bella } from '@freesewing/bella'
|
||||||
|
import { i18n as benjamin } from '@freesewing/benjamin'
|
||||||
|
import { i18n as bent } from '@freesewing/bent'
|
||||||
|
import { i18n as bibi } from '@freesewing/bibi'
|
||||||
|
import { i18n as bob } from '@freesewing/bob'
|
||||||
|
import { i18n as breanna } from '@freesewing/breanna'
|
||||||
|
import { i18n as brian } from '@freesewing/brian'
|
||||||
|
import { i18n as bruce } from '@freesewing/bruce'
|
||||||
|
import { i18n as carlita } from '@freesewing/carlita'
|
||||||
|
import { i18n as carlton } from '@freesewing/carlton'
|
||||||
|
import { i18n as cathrin } from '@freesewing/cathrin'
|
||||||
|
import { i18n as charlie } from '@freesewing/charlie'
|
||||||
|
import { i18n as cornelius } from '@freesewing/cornelius'
|
||||||
|
import { i18n as diana } from '@freesewing/diana'
|
||||||
|
import { i18n as florence } from '@freesewing/florence'
|
||||||
|
import { i18n as florent } from '@freesewing/florent'
|
||||||
|
import { i18n as gozer } from '@freesewing/gozer'
|
||||||
|
import { i18n as hi } from '@freesewing/hi'
|
||||||
|
import { i18n as holmes } from '@freesewing/holmes'
|
||||||
|
import { i18n as hortensia } from '@freesewing/hortensia'
|
||||||
|
import { i18n as huey } from '@freesewing/huey'
|
||||||
|
import { i18n as hugo } from '@freesewing/hugo'
|
||||||
|
import { i18n as jaeger } from '@freesewing/jaeger'
|
||||||
|
import { i18n as jane } from '@freesewing/jane'
|
||||||
|
import { i18n as lucy } from '@freesewing/lucy'
|
||||||
|
import { i18n as lumina } from '@freesewing/lumina'
|
||||||
|
import { i18n as lumira } from '@freesewing/lumira'
|
||||||
|
import { i18n as lunetius } from '@freesewing/lunetius'
|
||||||
|
import { i18n as noble } from '@freesewing/noble'
|
||||||
|
import { i18n as octoplushy } from '@freesewing/octoplushy'
|
||||||
|
import { i18n as onyx } from '@freesewing/onyx'
|
||||||
|
import { i18n as opal } from '@freesewing/opal'
|
||||||
|
import { i18n as otis } from '@freesewing/otis'
|
||||||
|
import { i18n as paco } from '@freesewing/paco'
|
||||||
|
import { i18n as penelope } from '@freesewing/penelope'
|
||||||
|
import { i18n as sandy } from '@freesewing/sandy'
|
||||||
|
import { i18n as shelly } from '@freesewing/shelly'
|
||||||
|
import { i18n as shin } from '@freesewing/shin'
|
||||||
|
import { i18n as simon } from '@freesewing/simon'
|
||||||
|
import { i18n as simone } from '@freesewing/simone'
|
||||||
|
import { i18n as skully } from '@freesewing/skully'
|
||||||
|
import { i18n as sven } from '@freesewing/sven'
|
||||||
|
import { i18n as tamiko } from '@freesewing/tamiko'
|
||||||
|
import { i18n as teagan } from '@freesewing/teagan'
|
||||||
|
import { i18n as tiberius } from '@freesewing/tiberius'
|
||||||
|
import { i18n as titan } from '@freesewing/titan'
|
||||||
|
import { i18n as trayvon } from '@freesewing/trayvon'
|
||||||
|
import { i18n as tristan } from '@freesewing/tristan'
|
||||||
|
import { i18n as uma } from '@freesewing/uma'
|
||||||
|
import { i18n as umbra } from '@freesewing/umbra'
|
||||||
|
import { i18n as wahid } from '@freesewing/wahid'
|
||||||
|
import { i18n as walburga } from '@freesewing/walburga'
|
||||||
|
import { i18n as waralee } from '@freesewing/waralee'
|
||||||
|
import { i18n as yuri } from '@freesewing/yuri'
|
||||||
|
import { i18n as lily } from '@freesewing/lily'
|
||||||
|
|
||||||
|
export const designTranslations = {
|
||||||
|
aaron,
|
||||||
|
albert,
|
||||||
|
bee,
|
||||||
|
bella,
|
||||||
|
benjamin,
|
||||||
|
bent,
|
||||||
|
bibi,
|
||||||
|
bob,
|
||||||
|
breanna,
|
||||||
|
brian,
|
||||||
|
bruce,
|
||||||
|
carlita,
|
||||||
|
carlton,
|
||||||
|
cathrin,
|
||||||
|
charlie,
|
||||||
|
cornelius,
|
||||||
|
diana,
|
||||||
|
florence,
|
||||||
|
florent,
|
||||||
|
gozer,
|
||||||
|
hi,
|
||||||
|
holmes,
|
||||||
|
hortensia,
|
||||||
|
huey,
|
||||||
|
hugo,
|
||||||
|
jaeger,
|
||||||
|
jane,
|
||||||
|
lucy,
|
||||||
|
lumina,
|
||||||
|
lumira,
|
||||||
|
lunetius,
|
||||||
|
noble,
|
||||||
|
octoplushy,
|
||||||
|
onyx,
|
||||||
|
opal,
|
||||||
|
otis,
|
||||||
|
paco,
|
||||||
|
penelope,
|
||||||
|
sandy,
|
||||||
|
shelly,
|
||||||
|
shin,
|
||||||
|
simon,
|
||||||
|
simone,
|
||||||
|
skully,
|
||||||
|
sven,
|
||||||
|
tamiko,
|
||||||
|
teagan,
|
||||||
|
tiberius,
|
||||||
|
titan,
|
||||||
|
trayvon,
|
||||||
|
tristan,
|
||||||
|
uma,
|
||||||
|
umbra,
|
||||||
|
wahid,
|
||||||
|
walburga,
|
||||||
|
waralee,
|
||||||
|
yuri,
|
||||||
|
lily,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDesignTranslation = (design) =>
|
||||||
|
designTranslations[design] ? designTranslations[design] : false
|
|
@ -66,6 +66,8 @@
|
||||||
"./hooks/useAccount": "./hooks/useAccount/index.mjs",
|
"./hooks/useAccount": "./hooks/useAccount/index.mjs",
|
||||||
"./hooks/useBackend": "./hooks/useBackend/index.mjs",
|
"./hooks/useBackend": "./hooks/useBackend/index.mjs",
|
||||||
"./hooks/useControl": "./hooks/useControl/index.mjs",
|
"./hooks/useControl": "./hooks/useControl/index.mjs",
|
||||||
|
"./hooks/useDesign": "./hooks/useDesign/index.mjs",
|
||||||
|
"./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs",
|
||||||
"./hooks/useSelection": "./hooks/useSelection/index.mjs",
|
"./hooks/useSelection": "./hooks/useSelection/index.mjs",
|
||||||
"./lib/RestClient": "./lib/RestClient/index.mjs",
|
"./lib/RestClient": "./lib/RestClient/index.mjs",
|
||||||
"./lib/logoPath": "./components/Logo/path.mjs"
|
"./lib/logoPath": "./components/Logo/path.mjs"
|
||||||
|
|
|
@ -69,6 +69,23 @@ export function cloudflareImageUrl({ id = 'default-avatar', variant = 'public' }
|
||||||
return `${cloudflareConfig.url}${id}/${variant}`
|
return `${cloudflareConfig.url}${id}/${variant}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the design optino type based on the option's config
|
||||||
|
*
|
||||||
|
* @param {object} option - The option config
|
||||||
|
* @return {string} type - The option type
|
||||||
|
*/
|
||||||
|
export function designOptionType(option) {
|
||||||
|
if (typeof option?.pct !== 'undefined') return 'pct'
|
||||||
|
if (typeof option?.bool !== 'undefined') return 'bool'
|
||||||
|
if (typeof option?.count !== 'undefined') return 'count'
|
||||||
|
if (typeof option?.deg !== 'undefined') return 'deg'
|
||||||
|
if (typeof option?.list !== 'undefined') return 'list'
|
||||||
|
if (typeof option?.mm !== 'undefined') return 'mm'
|
||||||
|
|
||||||
|
return 'constant'
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parses value that should be a distance (cm or inch) into a value in mm
|
* Parses value that should be a distance (cm or inch) into a value in mm
|
||||||
*
|
*
|
||||||
|
@ -179,6 +196,16 @@ export function formatMm(val, units, format = 'html') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a percentage (as in, between 0 and 1)
|
||||||
|
*
|
||||||
|
* @param {number} val - The value
|
||||||
|
* @return {string} pct - The value formatted as percentage
|
||||||
|
*/
|
||||||
|
export function formatPercentage(val) {
|
||||||
|
return Math.round(1000 * val) / 10 + '%'
|
||||||
|
}
|
||||||
|
|
||||||
/** convert a value that may contain a fraction to a decimal */
|
/** convert a value that may contain a fraction to a decimal */
|
||||||
export function fractionToDecimal(value) {
|
export function fractionToDecimal(value) {
|
||||||
// if it's just a number, return it
|
// if it's just a number, return it
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
||||||
'./tailwind-force.html',
|
'./tailwind-force.html',
|
||||||
],
|
],
|
||||||
plugins: [daisyui],
|
plugins: [daisyui],
|
||||||
corePlugins: { preflight: false },
|
//corePlugins: { preflight: false },
|
||||||
darkMode: ['class', "[data-theme='dark']"],
|
darkMode: ['class', "[data-theme='dark']"],
|
||||||
prefix: 'tw-',
|
prefix: 'tw-',
|
||||||
daisyui: {
|
daisyui: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue