wip: Work on editor
This commit is contained in:
parent
922bd04130
commit
94b8efa4a2
31 changed files with 677 additions and 614 deletions
|
@ -1,6 +1,8 @@
|
|||
// Dependencies
|
||||
import { measurements } from '@freesewing/config'
|
||||
import { cloudflareImageUrl, capitalize } from '@freesewing/utils'
|
||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||
import { requiredMeasurements as designMeasurements } from '@freesewing/collection'
|
||||
import { cloudflareImageUrl, capitalize, hasRequiredMeasurements } from '@freesewing/utils'
|
||||
// Context
|
||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||
|
@ -201,7 +203,8 @@ export const MsetCard = ({
|
|||
const s = sizes[size]
|
||||
|
||||
const wrapperProps = {
|
||||
className: `tw-bg-base-300 tw-aspect-square tw-h-${s} tw-w-${s} tw-mb-2 tw-grow
|
||||
className: `tw-bg-base-300 tw-aspect-square tw-h-${s} tw-w-${s} tw-mb-2 tw-grow tw-w-full
|
||||
hover:tw-cursor-pointer tw-border-0 tw-opacity-80 hover:tw-opacity-100
|
||||
tw-mx-auto tw-flex tw-flex-col tw-items-start tw-text-center tw-justify-between tw-rounded-none md:tw-rounded shadow`,
|
||||
style: {
|
||||
backgroundImage: `url(${cloudflareImageUrl({ type: 'w500', id: set.img })})`,
|
||||
|
@ -230,13 +233,11 @@ export const MsetCard = ({
|
|||
<NoIcon className={`${iconClasses} tw-bg-error tw-text-error-content`} stroke={3} />
|
||||
)
|
||||
if (missing.length > 0) {
|
||||
const translated = missing.map((m) => {
|
||||
return t(m)
|
||||
})
|
||||
let missingString = t('missing') + ': ' + translated.join(', ')
|
||||
const translated = missing.map((m) => measurementsTranslations[m])
|
||||
let missingString = 'Missing:' + translated.join(', ')
|
||||
if (missingString.length > maxLength) {
|
||||
const lastSpace = missingString.lastIndexOf(', ', maxLength)
|
||||
missingString = missingString.substring(0, lastSpace) + ', ' + t('andMore') + '...'
|
||||
missingString = missingString.substring(0, lastSpace) + ', and more...'
|
||||
}
|
||||
const measieClasses = 'tw-font-normal tw-text-xs'
|
||||
missingMeasies = <span className={`${measieClasses}`}>{missingString}</span>
|
||||
|
|
|
@ -5,7 +5,7 @@ import React, { useState } from 'react'
|
|||
* So instead, we handle this in React state
|
||||
*/
|
||||
const getProps = (isActive) => ({
|
||||
className: `tw-p-2 tw-px-4 tw-rounded-lg tw-bg-transparent tw-shadow
|
||||
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
|
||||
${isActive ? 'hover:tw-bg-transparent' : 'hover:tw-bg-secondary hover:tw-bg-opacity-10'}`,
|
||||
})
|
||||
|
@ -41,7 +41,10 @@ export const BaseAccordion = ({
|
|||
.map((item, i) =>
|
||||
active === item[2] ? (
|
||||
<div key={i} {...propsGetter(true)}>
|
||||
<Component onClick={setActive} className="tw-w-full hover:tw-cursor-pointer">
|
||||
<Component
|
||||
onClick={setActive}
|
||||
className="tw-w-full tw-bg-transparent tw-border-0 hover:tw-bg-secondary hover:tw-bg-opacity-20 hover:tw-cursor-pointer"
|
||||
>
|
||||
{item[0]}
|
||||
</Component>
|
||||
{item[1]}
|
||||
|
|
|
@ -8,35 +8,8 @@ import { AsideViewMenuSpacer } from './AsideViewMenu.mjs'
|
|||
import { ViewIcon, viewLabels } from './views/index.mjs'
|
||||
import { Tooltip } from './Tooltip.mjs'
|
||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||
|
||||
export const HeaderMenu = ({ config, Design, pattern, state, update }) => {
|
||||
const [open, setOpen] = useState()
|
||||
|
||||
/*
|
||||
* Guard views that require measurements agains missing measurements
|
||||
* and make sure there's a view-specific header menu
|
||||
*/
|
||||
const ViewMenu =
|
||||
!missingMeasurements(state, config) &&
|
||||
Swizzled.components[`HeaderMenu${config.viewComponents[state.view]}`]
|
||||
? Swizzled.components[`HeaderMenu${config.viewComponents[state.view]}`]
|
||||
: Null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex sticky top-0 ${
|
||||
state.ui.kiosk ? 'z-50' : 'z-20'
|
||||
} transition-[top] duration-300 ease-in-out`}
|
||||
>
|
||||
<div
|
||||
className={`flex flex-row flex-wrap gap-1 md:gap-4 w-full items-start justify-center border-b border-base-300 py-1 md:py-1.5`}
|
||||
>
|
||||
<HeaderMenuAllViews {...{ config, state, update, open, setOpen }} />
|
||||
<ViewMenu {...{ config, state, update, Design, pattern, open, setOpen }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
||||
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
||||
|
||||
export const HeaderMenuAllViews = ({ config, state, update, open, setOpen }) => (
|
||||
<HeaderMenuViewMenu {...{ config, state, update, open, setOpen }} />
|
||||
|
@ -47,7 +20,7 @@ export const HeaderMenuDraftView = (props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row gap-1">
|
||||
<div className="tw-flex tw-flex-row tw-gap-1">
|
||||
<HeaderMenuDraftViewDesignOptions {...props} />
|
||||
<HeaderMenuDraftViewCoreSettings {...props} />
|
||||
<HeaderMenuDraftViewUiPreferences {...props} />
|
||||
|
@ -74,31 +47,31 @@ export const HeaderMenuDropdown = (props) => {
|
|||
disabled
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="btn btn-ghost hover:bg-secondary hover:bg-opacity-20 hover:border-solid hover:boder-2 hover:border-secondary border border-secondary border-2 border-dotted btn-sm px-2 z-20 relative"
|
||||
className="tw-daisy-btn tw-daisy-btn-ghost hover:tw-bg-secondary hover:tw-bg-opacity-20 hover:tw-border-solid hover:tw-border-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"
|
||||
>
|
||||
{toggle}
|
||||
</button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip tip={tooltip}>
|
||||
<div className={`dropdown ${open === id ? 'dropdown-open z-20' : ''}`}>
|
||||
<div className={`tw-daisy-dropdown ${open === id ? 'tw-daisy-dropdown-open tw-z-20' : ''}`}>
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="btn btn-ghost hover:bg-secondary hover:bg-opacity-20 hover:border-solid hover:boder-2 hover:border-secondary border border-secondary border-2 border-dotted btn-sm px-2 z-20 relative"
|
||||
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"
|
||||
onClick={() => setOpen(open === id ? false : id)}
|
||||
>
|
||||
{toggle}
|
||||
</div>
|
||||
<div
|
||||
tabIndex={0}
|
||||
className="dropdown-content bg-base-100 bg-opacity-90 z-20 shadow left-0 !fixed md:!absolute top-12 w-screen md:w-96"
|
||||
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-w-96"
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
{open === id && (
|
||||
<div
|
||||
className="w-screen h-screen absolute top-10 left-0 opacity-0"
|
||||
className="tw-w-screen tw-h-screen tw-absolute tw-top-10 tw-left-0 tw-opacity-0"
|
||||
style={{ width: '200vw', transform: 'translateX(-100vw)' }}
|
||||
onClick={() => setOpen(false)}
|
||||
></div>
|
||||
|
@ -116,8 +89,8 @@ export const HeaderMenuDraftViewDesignOptions = (props) => {
|
|||
tooltip="fixme: 'pe:designOptions.d'"
|
||||
toggle={
|
||||
<>
|
||||
<HeaderMenuIcon name="options" extraClasses="text-secondary" />
|
||||
<span className="hidden lg:inline">fixme: pe:designOptions.t</span>
|
||||
<HeaderMenuIcon name="options" extraClasses="tw-text-secondary" />
|
||||
<span className="tw-hidden lg:tw-inline">fixme: pe:designOptions.t</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
@ -134,8 +107,8 @@ export const HeaderMenuDraftViewCoreSettings = (props) => {
|
|||
id="coreSettings"
|
||||
toggle={
|
||||
<>
|
||||
<HeaderMenuIcon name="settings" extraClasses="text-secondary" />
|
||||
<span className="hidden lg:inline">fixme: pe:coreSettings.t</span>
|
||||
<HeaderMenuIcon name="settings" extraClasses="tw-text-secondary" />
|
||||
<span className="tw-hidden lg:tw-inline">fixme: pe:coreSettings.t</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
@ -152,8 +125,8 @@ export const HeaderMenuDraftViewUiPreferences = (props) => {
|
|||
id="uiPreferences"
|
||||
toggle={
|
||||
<>
|
||||
<HeaderMenuIcon name="ui" extraClasses="text-secondary" />
|
||||
<span className="hidden lg:inline">fixme: pe:uiPreferences.t</span>
|
||||
<HeaderMenuIcon name="ui" extraClasses="tw-text-secondary" />
|
||||
<span className="tw-hidden lg:tw-inline">fixme: pe:uiPreferences.t</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
@ -172,8 +145,8 @@ export const HeaderMenuDraftViewFlags = (props) => {
|
|||
id="flags"
|
||||
toggle={
|
||||
<>
|
||||
<HeaderMenuIcon name="flag" extraClasses="text-secondary" />
|
||||
<span className="hidden lg:inline">
|
||||
<HeaderMenuIcon name="flag" extraClasses="tw-text-secondary" />
|
||||
<span className="tw-hidden lg:tw-inline">
|
||||
Flags
|
||||
<span>({count})</span>
|
||||
</span>
|
||||
|
@ -188,8 +161,8 @@ export const HeaderMenuDraftViewFlags = (props) => {
|
|||
export const HeaderMenuDraftViewIcons = (props) => {
|
||||
const { update } = props
|
||||
const Button = HeaderMenuButton
|
||||
const size = 'w-5 h-5'
|
||||
const muted = 'text-current opacity-50'
|
||||
const size = 'tw-w-5 tw-h-5'
|
||||
const muted = 'tw-text-current tw-opacity-50'
|
||||
const ux = props.state.ui.ux
|
||||
const levels = {
|
||||
...props.config.uxLevels.core,
|
||||
|
@ -197,13 +170,15 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center justify-center px-2">
|
||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-2">
|
||||
{ux >= levels.sa ? (
|
||||
<Button
|
||||
updateHandler={update.toggleSa}
|
||||
tooltip="Turns Seam Allowance on or off (see Core Settings)"
|
||||
>
|
||||
<SaIcon className={`${size} ${props.state.settings.sabool ? 'text-secondary' : muted}`} />
|
||||
<SaIcon
|
||||
className={`${size} ${props.state.settings.sabool ? 'tw-text-secondary' : muted}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
{ux >= levels.paperless ? (
|
||||
|
@ -212,7 +187,7 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
tooltip="Turns Paperless on or off (see Core Settings)"
|
||||
>
|
||||
<PaperlessIcon
|
||||
className={`${size} ${props.state.settings.paperless ? 'text-secondary' : muted}`}
|
||||
className={`${size} ${props.state.settings.paperless ? 'tw-text-secondary' : muted}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
|
@ -222,7 +197,7 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
tooltip="Turns Details on or off (see Core Settings)"
|
||||
>
|
||||
<DetailIcon
|
||||
className={`${size} ${!props.state.settings.complete ? 'text-secondary' : muted}`}
|
||||
className={`${size} ${!props.state.settings.complete ? 'tw-text-secondary' : muted}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
|
@ -232,7 +207,7 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
tooltip="Turns Expand on or off (see Core Settings)"
|
||||
>
|
||||
<ExpandIcon
|
||||
className={`${size} ${props.state.settings.expand ? 'text-secondary' : muted}`}
|
||||
className={`${size} ${props.state.settings.expand ? 'tw-text-secondary' : muted}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
|
@ -248,26 +223,28 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
>
|
||||
<UnitsIcon
|
||||
className={`${size} ${
|
||||
props.state.settings.units === 'imperial' ? 'text-secondary' : muted
|
||||
props.state.settings.units === 'imperial' ? 'tw-text-secondary' : muted
|
||||
}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
<HeaderMenuIconSpacer />
|
||||
{ux >= levels.ux ? (
|
||||
<div className="flex flex-row px-1">
|
||||
<div className="tw-flex tw-flex-row tw-px-1">
|
||||
<Tooltip tip="Changes your UX setting (see UI Preferences)">
|
||||
{[0, 1, 2, 3, 4].map((i) => (
|
||||
<button
|
||||
key={i}
|
||||
className="btn btn-ghost btn-sm px-0 -mx-0.5"
|
||||
className="tw-daisy-btn tw-daisy-btn-ghost tw-daisy-btn-sm tw-px-0 tw--mx-0.5"
|
||||
onClick={() => update.ui('ux', i + 1)}
|
||||
>
|
||||
<CircleIcon
|
||||
key={i}
|
||||
fill={i < props.state.ui.ux ? true : false}
|
||||
className={`${size} ${
|
||||
i < props.state.ui.ux ? 'stroke-secondary fill-secondary' : 'stroke-current'
|
||||
i < props.state.ui.ux
|
||||
? 'tw-stroke-secondary tw-fill-secondary'
|
||||
: 'tw-stroke-current'
|
||||
}`}
|
||||
fillOpacity={0.3}
|
||||
/>
|
||||
|
@ -281,7 +258,7 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
updateHandler={() => update.ui('aside', props.state.ui.aside ? 0 : 1)}
|
||||
tooltip="Turn the Aside Menu on or off (see UI Preferences)"
|
||||
>
|
||||
<MenuIcon className={`${size} ${!props.state.ui.aside ? 'text-secondary' : muted}`} />
|
||||
<MenuIcon className={`${size} ${!props.state.ui.aside ? 'tw-text-secondary' : muted}`} />
|
||||
</Button>
|
||||
) : null}
|
||||
{ux >= levels.kiosk ? (
|
||||
|
@ -289,7 +266,7 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
updateHandler={() => update.ui('kiosk', props.state.ui.kiosk ? 0 : 1)}
|
||||
tooltip="Turns Kiosk Mode on or off (see UI Preferences)"
|
||||
>
|
||||
<KioskIcon className={`${size} ${props.state.ui.kiosk ? 'text-secondary' : muted}`} />
|
||||
<KioskIcon className={`${size} ${props.state.ui.kiosk ? 'tw-text-secondary' : muted}`} />
|
||||
</Button>
|
||||
) : null}
|
||||
{ux >= levels.rotate ? (
|
||||
|
@ -297,7 +274,9 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
updateHandler={() => update.ui('rotate', props.state.ui.rotate ? 0 : 1)}
|
||||
tooltip="Turns Rotate Pattern on or off (see UI Preferences)"
|
||||
>
|
||||
<RotateIcon className={`${size} ${props.state.ui.rotate ? 'text-secondary' : muted}`} />
|
||||
<RotateIcon
|
||||
className={`${size} ${props.state.ui.rotate ? 'tw-text-secondary' : muted}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
{ux >= levels.renderer ? (
|
||||
|
@ -308,7 +287,7 @@ export const HeaderMenuDraftViewIcons = (props) => {
|
|||
tooltip="Switches the Render Engine between React and SVG (see UI Preferences)"
|
||||
>
|
||||
<RocketIcon
|
||||
className={`${size} ${props.state.ui.renderer === 'svg' ? 'text-secondary' : muted}`}
|
||||
className={`${size} ${props.state.ui.renderer === 'svg' ? 'tw-text-secondary' : muted}`}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
|
@ -323,20 +302,20 @@ export const HeaderMenuUndoIcons = (props) => {
|
|||
const undos = props.state._?.undos && props.state._.undos.length > 0 ? props.state._.undos : false
|
||||
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center justify-center px-2">
|
||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-2">
|
||||
<Button
|
||||
updateHandler={() => update.restore(0, state._)}
|
||||
tooltip="Undo the most recent change"
|
||||
disabled={undos ? false : true}
|
||||
>
|
||||
<UndoIcon className={`${size} ${undos ? 'text-secondary' : ''}`} text="1" />
|
||||
<UndoIcon className={`${size} ${undos ? 'tw-text-secondary' : ''}`} text="1" />
|
||||
</Button>
|
||||
<Button
|
||||
updateHandler={() => update.restore(undos.length - 1, state._)}
|
||||
tooltip="Undo all changes since the last save point"
|
||||
disabled={undos ? false : true}
|
||||
>
|
||||
<UndoIcon className={`${size} ${undos ? 'text-secondary' : ''}`} text="A" />
|
||||
<UndoIcon className={`${size} ${undos ? 'tw-text-secondary' : ''}`} text="A" />
|
||||
</Button>
|
||||
<HeaderMenuDropdown
|
||||
{...props}
|
||||
|
@ -345,13 +324,13 @@ export const HeaderMenuUndoIcons = (props) => {
|
|||
disabled={undos ? false : true}
|
||||
toggle={
|
||||
<>
|
||||
<UndoIcon className="w-4 h-4" stroke={3} />
|
||||
<span className="hidden lg:inline">Undo</span>
|
||||
<UndoIcon className="tw-w-4 tw-h-4" stroke={3} />
|
||||
<span className="tw-hidden lg:tw-inline">Undo</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{undos ? (
|
||||
<ul className="dropdown-content bg-base-100 bg-opacity-90 z-20 shadow left-0 !fixed md:!absolute w-screen md:w-96 px-4 md:p-2 md:pt-0">
|
||||
<ul 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-w-screen md:tw-w-96 tw-px-4 md:tw-p-2 md:tw-pt-0">
|
||||
{undos.slice(0, 9).map((step, index) => (
|
||||
<li key={index}>
|
||||
<UndoStep {...{ step, update, state, Design, index }} compact />
|
||||
|
@ -364,9 +343,9 @@ export const HeaderMenuUndoIcons = (props) => {
|
|||
return null /*update.state(index, state._) */
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row items-center align-center justify-between gap-2 w-full">
|
||||
<div className="flex flex-row items-center align-start gap-2 grow">
|
||||
<UndoIcon className="w-5 h-5 text-secondary" />
|
||||
<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">
|
||||
<UndoIcon className="tw-w-5 tw-h-5 tw-text-secondary" />
|
||||
{viewLabels.undo.t}
|
||||
</div>
|
||||
{undos.length}
|
||||
|
@ -380,10 +359,10 @@ export const HeaderMenuUndoIcons = (props) => {
|
|||
updateHandler={update.clearAll}
|
||||
tooltip="Reset all settings, but keep the design and measurements"
|
||||
>
|
||||
<TrashIcon className={`${size} text-secondary`} />
|
||||
<TrashIcon className={`${size} tw-text-secondary`} />
|
||||
</Button>
|
||||
<Button updateHandler={update.clearAll} tooltip="Reset the editor completely">
|
||||
<ResetAllIcon className={`${size} text-secondary`} />
|
||||
<ResetAllIcon className={`${size} tw-text-secondary`} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
@ -392,23 +371,23 @@ export const HeaderMenuUndoIcons = (props) => {
|
|||
export const HeaderMenuSaveIcons = (props) => {
|
||||
const { update } = props
|
||||
const Button = HeaderMenuButton
|
||||
const size = 'w-5 h-5'
|
||||
const size = 'tw-w-5 tw-h-5'
|
||||
const saveable = props.state._?.undos && props.state._.undos.length > 0
|
||||
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center justify-center px-2">
|
||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center tw-justify-center tw-px-2">
|
||||
<Button
|
||||
updateHandler={update.clearPattern}
|
||||
tooltip="Save pattern"
|
||||
disabled={saveable ? false : true}
|
||||
>
|
||||
<SaveIcon className={`${size} ${saveable ? 'text-success' : ''}`} />
|
||||
<SaveIcon className={`${size} ${saveable ? 'tw-text-success' : ''}`} />
|
||||
</Button>
|
||||
<Button updateHandler={() => update.view('save')} tooltip="Save pattern as...">
|
||||
<SaveAsIcon className={`${size} text-secondary`} />
|
||||
<SaveAsIcon className={`${size} tw-text-secondary`} />
|
||||
</Button>
|
||||
<Button updateHandler={update.clearPattern} tooltip="Export pattern">
|
||||
<ExportIcon className={`${size} text-secondary`} />
|
||||
<ExportIcon className={`${size} tw-text-secondary`} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
@ -417,14 +396,16 @@ export const HeaderMenuSaveIcons = (props) => {
|
|||
export const HeaderMenuIcon = (props) => {
|
||||
const { name, extraClasses = '' } = props
|
||||
//const Icon = Swizzled.components[`${Swizzled.methods.capitalize(name)}Icon`] || Swizzled.components.Noop
|
||||
return <ErrorIcon {...props} className={`h-5 w-5 ${extraClasses}`} />
|
||||
return <ErrorIcon {...props} className={`tw-h-5 tw-w-5 ${extraClasses}`} />
|
||||
}
|
||||
export const HeaderMenuIconSpacer = () => <span className="px-1 font-bold opacity-30">|</span>
|
||||
export const HeaderMenuIconSpacer = () => (
|
||||
<span className="tw-px-1 tw-font-bold tw-opacity-30">|</span>
|
||||
)
|
||||
|
||||
export const HeaderMenuButton = ({ updateHandler, children, tooltip, disabled = false }) => (
|
||||
<Tooltip tip={tooltip}>
|
||||
<button
|
||||
className="btn btn-ghost btn-sm px-1 disabled:bg-transparent"
|
||||
className="tw-daisy-btn tw-daisy-btn-ghost tw-daisy-btn-sm tw-px-1 disabled:tw-bg-transparent"
|
||||
onClick={updateHandler}
|
||||
disabled={disabled}
|
||||
>
|
||||
|
@ -455,18 +436,23 @@ export const HeaderMenuViewMenu = (props) => {
|
|||
(config.measurementsFreeViews.includes(viewName) || state._.missingMeasurements.length < 1)
|
||||
)
|
||||
output.push(
|
||||
<li key={i} className="mb-1 flex flex-row items-center justify-between w-full">
|
||||
<li
|
||||
key={i}
|
||||
className="tw-mb-1 tw-flex tw-flex-row tw-items-center tw-justify-between tw-w-full"
|
||||
>
|
||||
<a
|
||||
className={`w-full rounded-lg border-2 border-secondary text-base-content
|
||||
flex flex-row items-center gap-2 md:gap-4 p-2
|
||||
hover:cursor-pointer
|
||||
hover:bg-secondary hover:bg-opacity-20 hover:border-solid ${
|
||||
viewName === state.view ? 'bg-secondary border-solid bg-opacity-20' : 'border-dotted'
|
||||
className={`tw-w-full tw-rounded-lg tw-border-2 tw-border-secondary tw-text-base-content
|
||||
tw-flex tw-flex-row tw-items-center tw-gap-2 md:tw-gap-4 tw-p-2
|
||||
hover:tw-cursor-pointer
|
||||
hover:tw-bg-secondary hover:tw-bg-opacity-20 hover:tw-border-solid ${
|
||||
viewName === state.view
|
||||
? 'tw-bg-secondary tw-border-solid tw-bg-opacity-20'
|
||||
: 'tw-border-dotted'
|
||||
}`}
|
||||
onClick={() => update.view(viewName)}
|
||||
>
|
||||
<ViewIcon view={viewName} className="w-6 h-6 grow-0" />
|
||||
<b className="text-left grow">{viewLabels[viewName].t}</b>
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
|
@ -480,17 +466,52 @@ export const HeaderMenuViewMenu = (props) => {
|
|||
id="views"
|
||||
toggle={
|
||||
<>
|
||||
<HeaderMenuIcon name="right" stroke={3} extraClasses="text-secondary rotate-90" />
|
||||
<span className="hidden lg:inline">Views</span>
|
||||
<HeaderMenuIcon name="right" stroke={3} extraClasses="tw-text-secondary tw-rotate-90" />
|
||||
<span className="tw-hidden lg:tw-inline">Views</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ul
|
||||
tabIndex={i}
|
||||
className="dropdown-content bg-base-100 bg-opacity-90 z-20 shadow left-0 !fixed md:!absolute w-screen md:w-96 px-4 md:p-2 md:pt-0"
|
||||
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"
|
||||
>
|
||||
{output}
|
||||
</ul>
|
||||
</HeaderMenuDropdown>
|
||||
)
|
||||
}
|
||||
|
||||
const headerMenus = {
|
||||
draft: HeaderMenuDraftView,
|
||||
//HeaderMenuDraftViewDesignOptions,
|
||||
//HeaderMenuDraftViewCoreSettings,
|
||||
//HeaderMenuDraftViewUiPreferences,
|
||||
//HeaderMenuDraftViewFlags,
|
||||
//HeaderMenuDraftViewIcons,
|
||||
}
|
||||
|
||||
export const HeaderMenu = ({ config, Design, pattern, state, update }) => {
|
||||
const [open, setOpen] = useState()
|
||||
|
||||
/*
|
||||
* Guard views that require measurements agains missing measurements
|
||||
* and make sure there's a view-specific header menu
|
||||
*/
|
||||
const ViewMenu =
|
||||
!missingMeasurements(state) && headerMenus[state.view] ? headerMenus[state.view] : Null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`tw-flex tw-sticky tw-top-0 ${
|
||||
state.ui.kiosk ? 'tw-z-50' : 'tw-z-20'
|
||||
} tw-transition-[top] tw-duration-300 tw-ease-in-out`}
|
||||
>
|
||||
<div
|
||||
className={`tw-flex tw-flex-row tw-flex-wrap tw-gap-1 md:tw-gap-4 tw-w-full tw-items-start tw-justify-center tw-border-b tw-border-base-300 tw-py-1 md:tw-py-1.5`}
|
||||
>
|
||||
<HeaderMenuAllViews {...{ config, state, update, open, setOpen }} />
|
||||
<ViewMenu {...{ config, state, update, Design, pattern, open, setOpen }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { Spinner } from '@freesewing/react/components/Spinner'
|
||||
import { TipIcon } from '@freesewing/react/components/Icon'
|
||||
import { Null } from './Null.mjs'
|
||||
|
||||
const config = {
|
||||
timeout: 2,
|
||||
|
@ -9,6 +11,11 @@ const config = {
|
|||
},
|
||||
}
|
||||
|
||||
const icons = {
|
||||
tip: TipIcon,
|
||||
spinner: Spinner,
|
||||
}
|
||||
|
||||
export const LoadingStatus = ({ state, update }) => {
|
||||
useEffect(() => {
|
||||
if (typeof state._.loading === 'object') {
|
||||
|
@ -28,26 +35,26 @@ export const LoadingStatus = ({ state, update }) => {
|
|||
if (!state._.loading || Object.keys(state._.loading).length < 1) return null
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 md:buttom-28 left-0 w-full z-30 md:px-4 md:mx-auto mb-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="tw-fixed tw-bottom-4 md:tw-buttom-28 tw-left-0 tw-w-full tw-z-30 md:tw-px-4 md:tw-mx-auto mb-4">
|
||||
<div className="tw-flex tw-flex-col tw-gap-2">
|
||||
{Object.entries(state._.loading).map(([id, custom]) => {
|
||||
const conf = {
|
||||
...config.defaults,
|
||||
...custom,
|
||||
}
|
||||
const Icon = typeof conf.icon === 'undefined' ? Spinner : Spinner //Swizzled.components[`${Swizzled.methods.capitalize(conf.icon)}Icon`] || Swizzled.components.Noop
|
||||
const Icon = icons[conf.icon] ? icons[conf.icon] : Null
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={`w-full md:max-w-2xl m-auto bg-${
|
||||
className={`tw-w-full md:tw-max-w-2xl tw-m-auto tw-bg-${
|
||||
conf.color
|
||||
} flex flex-row items-center gap-4 p-4 px-4 ${
|
||||
conf.fading ? 'opacity-0' : 'opacity-100'
|
||||
} tw-flex tw-flex-row tw-items-center tw-gap-4 tw-p-4 tw-px-4 ${
|
||||
conf.fading ? 'tw-opacity-0' : 'tw-opacity-100'
|
||||
}
|
||||
transition-opacity delay-[${config.timeout * 1000 - 400}ms] duration-300
|
||||
md:rounded-lg shadow text-secondary-content text-lg lg:text-xl font-medium md:bg-opacity-90`}
|
||||
tw-transition-opacity tw-delay-[${config.timeout * 1000 - 400}ms] tw-duration-300
|
||||
md:tw-rounded-lg tw-shadow tw-text-secondary-content tw-text-lg lg:tw-text-xl tw-font-medium md:tw-bg-opacity-90`}
|
||||
>
|
||||
<span className="shrink-0">
|
||||
<span className="tw-shrink-0">
|
||||
<Icon />
|
||||
</span>
|
||||
{conf.msg}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import React from 'react'
|
||||
import { MeasurementInput } from '@freesewing/react/components/Input'
|
||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||
|
||||
/**
|
||||
* This MeasurementsEditor component allows inline-editing of the measurements
|
||||
|
@ -19,8 +21,8 @@ export const MeasurementsEditor = ({ Design, update, state }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<h5>Required Measurments</h5>
|
||||
<div className="tw-max-w-2xl tw-mx-auto">
|
||||
<h4>Required Measurements</h4>
|
||||
{Object.keys(Design.patternConfig.measurements).length === 0 ? (
|
||||
<p>This design does not require any measurements.</p>
|
||||
) : (
|
||||
|
@ -38,7 +40,7 @@ export const MeasurementsEditor = ({ Design, update, state }) => {
|
|||
<br />
|
||||
</div>
|
||||
)}
|
||||
<h5>Optional Measurements</h5>
|
||||
<h4>Optional Measurements</h4>
|
||||
{Object.keys(Design.patternConfig.optionalMeasurements).length === 0 ? (
|
||||
<p>This design does not use any optional measurements.</p>
|
||||
) : (
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import React from 'react'
|
||||
import { ZoomContextProvider } from './ZoomablePattern.mjs'
|
||||
import { HeaderMenu } from './HeaderMenu.mjs'
|
||||
|
||||
/**
|
||||
* A layout for views that include a drafted pattern
|
||||
*
|
||||
* @param {object} config - The editor configuration
|
||||
* @param {object} settings - The pattern settings/state
|
||||
* @param {object} ui - The UI settings/state
|
||||
* @param {object} update - Object holding methods to manipulate state
|
||||
* @param {function} Design - The Design contructor
|
||||
* @param {object] pattern - The drafted pattern
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
*/
|
||||
export const PatternLayout = (props) => {
|
||||
const { menu = null, Design, pattern, update, Swizzled } = props
|
||||
const { menu = null, Design, pattern, update, config } = props
|
||||
|
||||
return (
|
||||
<Swizzled.components.ZoomContextProvider>
|
||||
<ZoomContextProvider>
|
||||
<div className="flex flex-col h-full">
|
||||
<Swizzled.components.HeaderMenu
|
||||
state={props.state}
|
||||
{...{ Swizzled, update, Design, pattern }}
|
||||
/>
|
||||
<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>
|
||||
{menu ? (
|
||||
|
@ -29,6 +30,6 @@ export const PatternLayout = (props) => {
|
|||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Swizzled.components.ZoomContextProvider>
|
||||
</ZoomContextProvider>
|
||||
)
|
||||
}
|
|
@ -1,33 +1,32 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import orderBy from 'lodash.orderby'
|
||||
// Dependencies
|
||||
import { capitalize, cloudflareImageUrl, hasRequiredMeasurements, orderBy } from '@freesewing/utils'
|
||||
// Hooks
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||
// Components
|
||||
import { Popout } from '@freesewing/react/components/Popout'
|
||||
import { MsetCard } from '@freesewing/react/components/Account'
|
||||
|
||||
export const UserSetPicker = ({
|
||||
Design,
|
||||
clickHandler,
|
||||
missingClickHandler,
|
||||
size = 'lg',
|
||||
Swizzled,
|
||||
config,
|
||||
}) => {
|
||||
// Swizzled components
|
||||
const { Popout, MeasurementsSetCard } = Swizzled.components
|
||||
// Swizzled hooks
|
||||
const { useBackend } = Swizzled.hooks
|
||||
// Hooks
|
||||
const backend = useBackend()
|
||||
// Swizzled methods
|
||||
const { t, hasRequiredMeasurements } = Swizzled.methods
|
||||
// Swizzled config
|
||||
const { config } = Swizzled
|
||||
|
||||
// Local state
|
||||
// State (local)
|
||||
const [sets, setSets] = useState({})
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
const getSets = async () => {
|
||||
const result = await backend.getSets()
|
||||
if (result.success) {
|
||||
const [status, body] = await backend.getSets()
|
||||
if (status === 200 && body.result === 'success') {
|
||||
const all = {}
|
||||
for (const set of result.data.sets) all[set.id] = set
|
||||
for (const set of body.sets) all[set.id] = set
|
||||
setSets(all)
|
||||
}
|
||||
}
|
||||
|
@ -47,21 +46,26 @@ export const UserSetPicker = ({
|
|||
|
||||
if (!hasSets)
|
||||
return (
|
||||
<div className="w-full max-w-3xl mx-auto">
|
||||
<div className="tw-w-full tw-max-w-3xl tw-mx-auto">
|
||||
<Popout tip>
|
||||
<h5>{t('pe:noOwnSets')}</h5>
|
||||
<p className="">{t('pe:noOwnSetsMsg')}</p>
|
||||
<h5> You do not (yet) have any of your own measurements sets</h5>
|
||||
<p>
|
||||
You can store your measurements as a measurements set, after which you can generate as
|
||||
many patterns as you want for them.
|
||||
</p>
|
||||
{config.hrefNewSet ? (
|
||||
<a
|
||||
href={config.hrefNewSet}
|
||||
className="btn btn-accent capitalize"
|
||||
className="tw-daisy-btn tw-daisy-btn-accent tw-capitalize"
|
||||
target="_BLANK"
|
||||
rel="nofollow"
|
||||
>
|
||||
{t('pe:newSet')}
|
||||
Create a new measurements set
|
||||
</a>
|
||||
) : null}
|
||||
<p className="text-sm">{t('pe:pleaseMtm')}</p>
|
||||
<p className="tw-text-sm">
|
||||
Because our patterns are bespoke, we strongly suggest you take accurate measurements.
|
||||
</p>
|
||||
</Popout>
|
||||
</div>
|
||||
)
|
||||
|
@ -69,10 +73,11 @@ export const UserSetPicker = ({
|
|||
return (
|
||||
<>
|
||||
{okSets.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-gap-2">
|
||||
{okSets.map((set) => (
|
||||
<MeasurementsSetCard
|
||||
<MsetCard
|
||||
href={false}
|
||||
design={Design.designConfig.data.id}
|
||||
{...{ set, Design, config }}
|
||||
onClick={clickHandler}
|
||||
key={set.id}
|
||||
|
@ -82,14 +87,17 @@ export const UserSetPicker = ({
|
|||
</div>
|
||||
)}
|
||||
{lackingSets.length > 0 ? (
|
||||
<div className="my-4">
|
||||
<Popout note compact>
|
||||
{t('pe:someSetsLacking')}
|
||||
<div className="tw-my-4">
|
||||
<Popout note>
|
||||
<h5>
|
||||
Some of your measurements sets lack the measurements required to generate this pattern
|
||||
</h5>
|
||||
</Popout>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-2">
|
||||
<div className="tw-grid tw-grid-cols-2 md:tw-grid-cols-4 lg:tw-grid-cols-6 tw-gap-2">
|
||||
{lackingSets.map((set) => (
|
||||
<MeasurementsSetCard
|
||||
<MsetCard
|
||||
{...{ set, Design }}
|
||||
design={Design.designConfig.data.id}
|
||||
onClick={missingClickHandler}
|
||||
href={false}
|
||||
key={set.id}
|
||||
|
@ -103,41 +111,26 @@ export const UserSetPicker = ({
|
|||
)
|
||||
}
|
||||
|
||||
export const BookmarkedSetPicker = ({
|
||||
Design,
|
||||
clickHandler,
|
||||
missingClickHandler,
|
||||
size = 'lg',
|
||||
Swizzled,
|
||||
}) => {
|
||||
// Swizzled components
|
||||
const { Popout, MeasurementsSetCard } = Swizzled.components
|
||||
// Swizzled hooks
|
||||
const { useBackend } = Swizzled.hooks
|
||||
export const BookmarkedSetPicker = ({ Design, clickHandler, missingClickHandler, size = 'lg' }) => {
|
||||
// Hooks
|
||||
const backend = useBackend()
|
||||
// Swizzled methods
|
||||
const { t, hasRequiredMeasurements } = Swizzled.methods
|
||||
// Swizzled config
|
||||
const { config } = Swizzled
|
||||
|
||||
// Local state
|
||||
// State (local)
|
||||
const [sets, setSets] = useState({})
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
const getBookmarks = async () => {
|
||||
const result = await backend.getBookmarks()
|
||||
const [status, body] = await backend.getBookmarks()
|
||||
const loadedSets = {}
|
||||
if (result.success) {
|
||||
for (const bookmark of result.data.bookmarks.filter(
|
||||
(bookmark) => bookmark.type === 'set'
|
||||
)) {
|
||||
if (status === 200 && body.result === 'success') {
|
||||
for (const bookmark of body.bookmarks.filter((bookmark) => bookmark.type === 'set')) {
|
||||
let set
|
||||
try {
|
||||
set = await backend.getSet(bookmark.url.slice(6))
|
||||
if (set.success) {
|
||||
const [hasMeasies] = hasRequiredMeasurements(Design, set.data.set.measies)
|
||||
loadedSets[set.data.set.id] = { ...set.data.set, hasMeasies }
|
||||
const [status, body] = await backend.getSet(bookmark.url.slice(6))
|
||||
if (status === 200 && body.result === 'success') {
|
||||
const [hasMeasies] = hasRequiredMeasurements(Design, body.set.measies)
|
||||
loadedSets[body.set.id] = { ...body.set, hasMeasies }
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
@ -155,11 +148,12 @@ export const BookmarkedSetPicker = ({
|
|||
return (
|
||||
<>
|
||||
{okSets.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-2">
|
||||
<div className="tw-grid tw-grid-cols-2 md:tw-grid-cols-4 lg:tw-grid-cols-6 tw-gap-2">
|
||||
{okSets.map((set) => (
|
||||
<MeasurementsSetCard
|
||||
<MsetCard
|
||||
href={false}
|
||||
{...{ set, Design, config }}
|
||||
design={Design.designConfig.data.id}
|
||||
onClick={clickHandler}
|
||||
key={set.id}
|
||||
size={size}
|
||||
|
@ -168,15 +162,19 @@ export const BookmarkedSetPicker = ({
|
|||
</div>
|
||||
)}
|
||||
{lackingSets.length > 0 && (
|
||||
<div className="my-4">
|
||||
<Popout note compact>
|
||||
{t('pe:someSetsLacking')}
|
||||
<div className="tw-my-4">
|
||||
<Popout note>
|
||||
<h5>
|
||||
Some of these measurements sets lack the measurements required to generate this
|
||||
pattern
|
||||
</h5>
|
||||
</Popout>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-2">
|
||||
<div className="tw-grid tw-grid-cols-2 md:tw-grid-cols-4 lg:tw-grid-cols-6 tw-gap-2">
|
||||
{lackingSets.map((set) => (
|
||||
<MeasurementsSetCard
|
||||
<MsetCard
|
||||
href={false}
|
||||
{...{ set, Design }}
|
||||
design={Design.designConfig.data.id}
|
||||
onClick={missingClickHandler}
|
||||
key={set.id}
|
||||
size={size}
|
||||
|
@ -189,23 +187,20 @@ export const BookmarkedSetPicker = ({
|
|||
)
|
||||
}
|
||||
|
||||
export const CuratedSetPicker = ({ clickHandler, Swizzled, locale }) => {
|
||||
// Swizzled components
|
||||
const { CuratedMeasurementsSetLineup } = Swizzled.components
|
||||
// Swizzled hooks
|
||||
const { useBackend } = Swizzled.hooks
|
||||
export const CuratedSetPicker = ({ clickHandler }) => {
|
||||
// Hooks
|
||||
const backend = useBackend()
|
||||
|
||||
// Local state
|
||||
// State (local)
|
||||
const [sets, setSets] = useState([])
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
const getSets = async () => {
|
||||
const result = await backend.getCuratedSets()
|
||||
if (result.success) {
|
||||
const [status, body] = await backend.getCuratedSets()
|
||||
if (status === 200 && body.result === 'success') {
|
||||
const allSets = {}
|
||||
for (const set of result.data.curatedSets) {
|
||||
for (const set of body.curatedSets) {
|
||||
if (set.published) allSets[set.id] = set
|
||||
}
|
||||
setSets(allSets)
|
||||
|
@ -215,11 +210,50 @@ export const CuratedSetPicker = ({ clickHandler, Swizzled, locale }) => {
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl xl:pl-4">
|
||||
<div className="tw-max-w-7xl">
|
||||
<CuratedMeasurementsSetLineup
|
||||
{...{ locale, clickHandler }}
|
||||
clickHandler={clickHandler}
|
||||
sets={orderBy(sets, 'height', 'asc')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CuratedMeasurementsSetLineup = ({ sets = [], clickHandler }) => (
|
||||
<div
|
||||
className={`tw-w-full tw-flex tw-flex-row ${
|
||||
sets.length > 1 ? 'tw-justify-start tw-px-8' : 'tw-justify-center'
|
||||
} tw-overflow-x-scroll`}
|
||||
style={{
|
||||
backgroundImage: `url(/img/lineup-backdrop.svg)`,
|
||||
width: 'auto',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundRepeat: 'repeat-x',
|
||||
}}
|
||||
>
|
||||
{sets.map((set) => {
|
||||
const props = {
|
||||
className:
|
||||
'tw-aspect-[1/3] tw-w-auto tw-h-96 tw-bg-transparent tw-border-0 hover:tw-cursor-pointer tw-grayscale hover:tw-grayscale-0',
|
||||
style: {
|
||||
backgroundImage: `url(${cloudflareImageUrl({
|
||||
id: `cset-${set.id}`,
|
||||
type: 'lineup',
|
||||
})})`,
|
||||
width: 'auto',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
},
|
||||
onClick: () => clickHandler(set),
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tw-flex tw-flex-col tw-items-center" key={set.id}>
|
||||
<button {...props} key={set.id}></button>
|
||||
<b>{set.nameEn}</b>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { useState, useMemo } from 'react'
|
||||
// Dependencies
|
||||
import { menuValueWasChanged } from '../../lib/index.mjs'
|
||||
// Hooks
|
||||
import React, { useState, useMemo } from 'react'
|
||||
// Components
|
||||
import { SubAccordion } from '../Accordion.mjs'
|
||||
import { GroupIcon, OptionsIcon } from '@freesewing/react/components/Icon'
|
||||
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
||||
|
||||
/** @type {String} class to apply to buttons on open menu items */
|
||||
const iconButtonClass = 'btn btn-xs btn-ghost px-0 text-accent'
|
||||
|
@ -17,11 +24,9 @@ const iconButtonClass = 'btn btn-xs btn-ghost px-0 text-accent'
|
|||
* @param {React.Component} Value a value display component this menu item will use
|
||||
* @param {Boolean} allowOverride all a text input to be used to override the given input component
|
||||
* @param {Number} ux the user-defined ux level
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
*/
|
||||
export const MenuItem = ({
|
||||
name,
|
||||
Swizzled,
|
||||
current,
|
||||
updateHandler,
|
||||
passProps = {},
|
||||
|
@ -44,7 +49,6 @@ export const MenuItem = ({
|
|||
ux,
|
||||
current,
|
||||
updateHandler,
|
||||
t: Swizzled.methods.t,
|
||||
changed,
|
||||
override,
|
||||
Design,
|
||||
|
@ -73,7 +77,7 @@ export const MenuItem = ({
|
|||
setOverride(!override)
|
||||
}}
|
||||
>
|
||||
<Swizzled.components.EditIcon
|
||||
<EditIcon
|
||||
className={`w-6 h-6 ${
|
||||
override ? 'bg-secondary text-secondary-content rounded' : 'text-secondary'
|
||||
}`}
|
||||
|
@ -89,28 +93,28 @@ export const MenuItem = ({
|
|||
updateHandler([name], '__UNSET__')
|
||||
}}
|
||||
>
|
||||
<Swizzled.components.ResetIcon />
|
||||
<ResetIcon />
|
||||
</button>
|
||||
)
|
||||
|
||||
buttons.push(<ResetButton open disabled={!changed} key="clear" />)
|
||||
|
||||
return (
|
||||
<Swizzled.components.FormControl
|
||||
label={<span className="text-base font-normal">{Swizzled.methods.t(`${name}.d`)}</span>}
|
||||
<FormControl
|
||||
label={<span className="text-base font-normal">{name}.d</span>}
|
||||
id={config.name}
|
||||
labelBR={<div className="flex flex-row items-center gap-2">{buttons}</div>}
|
||||
labelBL={
|
||||
<span
|
||||
className={`text-base font-medium -mt-2 block ${changed ? 'text-accent' : 'opacity-50'}`}
|
||||
>
|
||||
{Swizzled.methods.t(`pe:youAreUsing${changed ? 'ACustom' : 'TheDefault'}Value`)}
|
||||
pe:youAreUsing{changed ? 'ACustom' : 'TheDefault'}Value
|
||||
</span>
|
||||
}
|
||||
docs={docs}
|
||||
>
|
||||
<Input {...drillProps} />
|
||||
</Swizzled.components.FormControl>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -132,8 +136,6 @@ export const MenuItem = ({
|
|||
* @param {Function} updateHandler the function called by change handlers on inputs within menu items
|
||||
* @param {Boolean} topLevel is this group the top level group? false for nested
|
||||
* @param {Function} t translation function
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
|
||||
*/
|
||||
export const MenuItemGroup = ({
|
||||
collapsible = true,
|
||||
|
@ -149,10 +151,9 @@ export const MenuItemGroup = ({
|
|||
topLevel = false,
|
||||
isDesignOptionsGroup = false,
|
||||
Design,
|
||||
Swizzled,
|
||||
state,
|
||||
}) => {
|
||||
if (!Item) Item = Swizzled.components.MenuItem
|
||||
if (!Item) Item = MenuItem
|
||||
|
||||
// map the entries in the structure
|
||||
const content = Object.entries(structure).map(([itemName, item]) => {
|
||||
|
@ -164,7 +165,7 @@ export const MenuItemGroup = ({
|
|||
const ItemIcon = item.icon
|
||||
? item.icon
|
||||
: item.isGroup
|
||||
? Swizzled.components.GroupIcon
|
||||
? GroupIcon
|
||||
: Icon
|
||||
? Icon
|
||||
: () => <span role="img">fixme-icon</span>
|
||||
|
@ -172,11 +173,11 @@ export const MenuItemGroup = ({
|
|||
? () => (
|
||||
<div className="flex flex-row gap-2 items-center font-medium">
|
||||
{Object.keys(item).filter((i) => i !== 'isGroup').length}
|
||||
<Swizzled.components.OptionsIcon className="w-5 h-5" />
|
||||
<OptionsIcon className="w-5 h-5" />
|
||||
</div>
|
||||
)
|
||||
: isDesignOptionsGroup
|
||||
? values[Swizzled.methods.designOptionType(item)]
|
||||
? values[designOptionType(item)]
|
||||
: values[itemName]
|
||||
? values[itemName]
|
||||
: () => <span>¯\_(ツ)_/¯</span>
|
||||
|
@ -186,15 +187,14 @@ export const MenuItemGroup = ({
|
|||
<div className="flex flex-row items-center gap-4 w-full">
|
||||
<ItemIcon />
|
||||
<span className="font-medium">
|
||||
{Swizzled.methods.t([`pe:${itemName}.t`, `pe:${itemName}`])}
|
||||
pe:{itemName}.t pe:{itemName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-bold">
|
||||
<Value
|
||||
current={currentValues[itemName]}
|
||||
config={item}
|
||||
t={Swizzled.methods.t}
|
||||
changed={Swizzled.methods.menuValueWasChanged(currentValues[itemName], item)}
|
||||
changed={menuValueWasChanged(currentValues[itemName], item)}
|
||||
Design={Design}
|
||||
/>
|
||||
</div>
|
||||
|
@ -218,7 +218,6 @@ export const MenuItemGroup = ({
|
|||
updateHandler,
|
||||
isDesignOptionsGroup,
|
||||
Design,
|
||||
Swizzled,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
@ -229,13 +228,12 @@ export const MenuItemGroup = ({
|
|||
current: currentValues[itemName],
|
||||
config: item,
|
||||
state,
|
||||
changed: Swizzled.methods.menuValueWasChanged(currentValues[itemName], item),
|
||||
changed: menuValueWasChanged(currentValues[itemName], item),
|
||||
Value: values[itemName],
|
||||
Input: inputs[itemName],
|
||||
updateHandler,
|
||||
passProps,
|
||||
Design,
|
||||
Swizzled,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
@ -243,7 +241,7 @@ export const MenuItemGroup = ({
|
|||
]
|
||||
})
|
||||
|
||||
return <Swizzled.components.SubAccordion items={content.filter((item) => item !== null)} />
|
||||
return <SubAccordion items={content.filter((item) => item !== null)} />
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,18 +252,11 @@ export const MenuItemGroup = ({
|
|||
* @param {Boolean} options.open is the menu item open?
|
||||
* @param {String} options.emoji the emoji icon of the menu item
|
||||
*/
|
||||
export const MenuItemTitle = ({
|
||||
name,
|
||||
current = null,
|
||||
open = false,
|
||||
emoji = '',
|
||||
Icon = false,
|
||||
Swizzled,
|
||||
}) => (
|
||||
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'}`}>
|
||||
<span className="font-medium capitalize flex flex-row gap-2">
|
||||
{Icon ? <Icon /> : <span role="img">{emoji}</span>}
|
||||
{Swizzled.methods.t([`${name}.t`, name])}
|
||||
fixme: {name}
|
||||
</span>
|
||||
<span className="font-bold">{current}</span>
|
||||
</div>
|
|
@ -1,3 +1,11 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
menuCoreSettingsOnlyHandler,
|
||||
menuCoreSettingsSaboolHandler,
|
||||
menuCoreSettingsSammHandler,
|
||||
menuCoreSettingsStructure,
|
||||
} from '../../lib/index.mjs'
|
||||
|
||||
/**
|
||||
* The core settings menu
|
||||
* @param {Object} options.update settings and ui update functions
|
||||
|
@ -7,8 +15,8 @@
|
|||
* @param {Object} options.account the user account data
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
*/
|
||||
export const CoreSettingsMenu = ({ update, state, language, Design, Swizzled }) => {
|
||||
const structure = Swizzled.methods.menuCoreSettingsStructure({
|
||||
export const CoreSettingsMenu = ({ update, state, language, Design }) => {
|
||||
const structure = menuCoreSettingsStructure({
|
||||
language,
|
||||
units: state.settings?.units,
|
||||
sabool: state.settings?.sabool,
|
||||
|
@ -16,42 +24,38 @@ export const CoreSettingsMenu = ({ update, state, language, Design, Swizzled })
|
|||
})
|
||||
|
||||
const inputs = {
|
||||
complete: Swizzled.components.MenuListInput,
|
||||
expand: Swizzled.components.MenuListInput,
|
||||
margin: Swizzled.components.MenuMmInput,
|
||||
only: Swizzled.components.MenuOnlySettingInput,
|
||||
paperless: Swizzled.components.MenuBoolInput,
|
||||
sabool: Swizzled.components.MenuBoolInput,
|
||||
samm: Swizzled.components.MenuMmInput,
|
||||
scale: Swizzled.components.MenuSliderInput,
|
||||
units: Swizzled.components.MenuBoolInput,
|
||||
complete: MenuListInput,
|
||||
expand: MenuListInput,
|
||||
margin: MenuMmInput,
|
||||
only: MenuOnlySettingInput,
|
||||
paperless: MenuBoolInput,
|
||||
sabool: MenuBoolInput,
|
||||
samm: MenuMmInput,
|
||||
scale: MenuSliderInput,
|
||||
units: MenuBoolInput,
|
||||
}
|
||||
|
||||
const values = {
|
||||
complete: Swizzled.components.MenuListValue,
|
||||
expand: Swizzled.components.MenuListValue,
|
||||
margin: Swizzled.components.MenuMmValue,
|
||||
only: Swizzled.components.MenuOnlySettingValue,
|
||||
paperless: Swizzled.components.MenuListValue,
|
||||
sabool: Swizzled.components.MenuListValue,
|
||||
samm: Swizzled.components.MenuMmValue,
|
||||
scale: Swizzled.components.MenuScaleSettingValue,
|
||||
units: Swizzled.components.MenuListValue,
|
||||
complete: MenuListValue,
|
||||
expand: MenuListValue,
|
||||
margin: MenuMmValue,
|
||||
only: MenuOnlySettingValue,
|
||||
paperless: MenuListValue,
|
||||
sabool: MenuListValue,
|
||||
samm: MenuMmValue,
|
||||
scale: MenuScaleSettingValue,
|
||||
units: MenuListValue,
|
||||
}
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuItemGroup
|
||||
<MenuItemGroup
|
||||
{...{
|
||||
structure,
|
||||
ux: state.ui.ux,
|
||||
currentValues: state.settings || {},
|
||||
Icon: Swizzled.components.SettingsIcon,
|
||||
Icon: SettingsIcon,
|
||||
Item: (props) => (
|
||||
<Swizzled.components.CoreSetting
|
||||
updateHandler={update}
|
||||
{...{ inputs, values, Swizzled, Design }}
|
||||
{...props}
|
||||
/>
|
||||
<CoreSetting updateHandler={update} {...{ inputs, values, Design }} {...props} />
|
||||
),
|
||||
isFirst: true,
|
||||
name: 'pe:designOptions',
|
||||
|
@ -64,7 +68,6 @@ export const CoreSettingsMenu = ({ update, state, language, Design, Swizzled })
|
|||
},
|
||||
updateHandler: update.settings,
|
||||
isDesignOptionsGroup: false,
|
||||
Swizzled,
|
||||
state,
|
||||
Design,
|
||||
inputs,
|
||||
|
@ -74,17 +77,15 @@ export const CoreSettingsMenu = ({ update, state, language, Design, Swizzled })
|
|||
)
|
||||
}
|
||||
|
||||
// Facilitate custom handlers for core settings
|
||||
const coreSettingsHandlerMethods = {
|
||||
only: menuCoreSettingsOnlyHandler,
|
||||
sabool: menuCoreSettingsSaboolHandler,
|
||||
samm: menuCoreSettingsSammHandler,
|
||||
}
|
||||
|
||||
/** A wrapper for {@see MenuItem} to handle core settings-specific business */
|
||||
export const CoreSetting = ({
|
||||
Swizzled,
|
||||
name,
|
||||
config,
|
||||
ux,
|
||||
updateHandler,
|
||||
current,
|
||||
passProps,
|
||||
...rest
|
||||
}) => {
|
||||
export const CoreSetting = ({ name, config, ux, updateHandler, current, passProps, ...rest }) => {
|
||||
// is toggling allowed?
|
||||
const allowToggle = ux > 3 && config.list?.length === 2
|
||||
|
||||
|
@ -98,10 +99,8 @@ export const CoreSetting = ({
|
|||
/*
|
||||
* Load a specific update handler if one is configured
|
||||
*/
|
||||
const handler = Swizzled.config.menuCoreSettingsHandlerMethods?.[name.toLowerCase()]
|
||||
? Swizzled.methods[Swizzled.config.menuCoreSettingsHandlerMethods[name.toLowerCase()]](
|
||||
handlerArgs
|
||||
)
|
||||
const handler = coreSettingsHandlerMethods[name.toLowerCase()]
|
||||
? coreSettingsHandlerMethods[name.toLowerCase()](handlerArgs)
|
||||
: updateHandler
|
||||
|
||||
return (
|
|
@ -1,16 +1,39 @@
|
|||
import { useCallback, useMemo } from 'react'
|
||||
// Dependencies
|
||||
import { menuDesignOptionsStructure } from '../../lib/index.mjs'
|
||||
// Hooks
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
// Components
|
||||
import {
|
||||
MenuBoolInput,
|
||||
MenuConstantInput,
|
||||
MenuSliderInput,
|
||||
MenuDegInput,
|
||||
MenuListInput,
|
||||
MenuPctInput,
|
||||
} from './Input.mjs'
|
||||
import {
|
||||
MenuBoolValue,
|
||||
MenuConstantOptionValue,
|
||||
MenuCountOptionValue,
|
||||
MenuDegOptionValue,
|
||||
MenuListOptionValue,
|
||||
MenyMmOptionValue,
|
||||
MenuPctOptionValue,
|
||||
} from './Value.mjs'
|
||||
import { MenuItemGroup } 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.update - Object holding state handlers
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
*/
|
||||
export const DesignOptionsMenu = ({ Design, isFirst = true, state, update, Swizzled }) => {
|
||||
export const DesignOptionsMenu = ({ Design, isFirst = true, state, update }) => {
|
||||
const structure = useMemo(
|
||||
() => Swizzled.methods.menuDesignOptionsStructure(Design.patternConfig.options, state.settings),
|
||||
() => menuDesignOptionsStructure(Design.patternConfig.options, state.settings),
|
||||
[Design.patternConfig, state.settings]
|
||||
)
|
||||
const updateHandler = useCallback(
|
||||
|
@ -20,45 +43,34 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, update, Swizz
|
|||
|
||||
const drillProps = { Design, state, update }
|
||||
const inputs = {
|
||||
bool: (props) => <Swizzled.components.MenuBoolInput {...drillProps} {...props} />,
|
||||
constant: (props) => <Swizzled.components.MenuConstantInput {...drillProps} {...props} />,
|
||||
bool: (props) => <MenuBoolInput {...drillProps} {...props} />,
|
||||
constant: (props) => <MenuConstantInput {...drillProps} {...props} />,
|
||||
count: (props) => (
|
||||
<Swizzled.components.MenuSliderInput
|
||||
{...drillProps}
|
||||
{...props}
|
||||
config={{ ...props.config, step: 1 }}
|
||||
/>
|
||||
),
|
||||
deg: (props) => <Swizzled.components.MenuDegInput {...drillProps} {...props} />,
|
||||
list: (props) => (
|
||||
<Swizzled.components.MenuListInput {...drillProps} {...props} isDesignOption />
|
||||
<MenuSliderInput {...drillProps} {...props} config={{ ...props.config, step: 1 }} />
|
||||
),
|
||||
deg: (props) => <MenuDegInput {...drillProps} {...props} />,
|
||||
list: (props) => <MenuListInput {...drillProps} {...props} isDesignOption />,
|
||||
mm: () => <span>FIXME: Mm options are deprecated. Please report this </span>,
|
||||
pct: (props) => <Swizzled.components.MenuPctInput {...drillProps} {...props} />,
|
||||
pct: (props) => <MenuPctInput {...drillProps} {...props} />,
|
||||
}
|
||||
const values = {
|
||||
bool: (props) => <Swizzled.components.MenuBoolValue {...drillProps} {...props} />,
|
||||
constant: (props) => <Swizzled.components.MenuConstantOptionValue {...drillProps} {...props} />,
|
||||
count: (props) => <Swizzled.components.MenuCountOptionValue {...drillProps} {...props} />,
|
||||
deg: (props) => <Swizzled.components.MenuDegOptionValue {...drillProps} {...props} />,
|
||||
list: (props) => <Swizzled.components.MenuListOptionValue {...drillProps} {...props} />,
|
||||
mm: (props) => <Swizzled.components.MenuMmOptionValue {...drillProps} {...props} />,
|
||||
pct: (props) => <Swizzled.components.MenuPctOptionValue {...drillProps} {...props} />,
|
||||
bool: (props) => <MenuBoolValue {...drillProps} {...props} />,
|
||||
constant: (props) => <MenuConstantOptionValue {...drillProps} {...props} />,
|
||||
count: (props) => <MenuCountOptionValue {...drillProps} {...props} />,
|
||||
deg: (props) => <MenuDegOptionValue {...drillProps} {...props} />,
|
||||
list: (props) => <MenuListOptionValue {...drillProps} {...props} />,
|
||||
mm: (props) => <MenuMmOptionValue {...drillProps} {...props} />,
|
||||
pct: (props) => <MenuPctOptionValue {...drillProps} {...props} />,
|
||||
}
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuItemGroup
|
||||
<MenuItemGroup
|
||||
{...{
|
||||
structure,
|
||||
ux: state.ui.ux,
|
||||
currentValues: state.settings.options || {},
|
||||
Icon: Swizzled.components.OptionsIcon,
|
||||
Item: (props) => (
|
||||
<Swizzled.components.DesignOption
|
||||
{...{ inputs, values, Swizzled, update, Design }}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
Icon: OptionsIcon,
|
||||
Item: (props) => <DesignOption {...{ inputs, values, update, Design }} {...props} />,
|
||||
isFirst,
|
||||
name: 'pe:designOptions',
|
||||
language: state.locale,
|
||||
|
@ -69,7 +81,6 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, update, Swizz
|
|||
},
|
||||
updateHandler,
|
||||
isDesignOptionsGroup: true,
|
||||
Swizzled,
|
||||
state,
|
||||
Design,
|
||||
inputs,
|
||||
|
@ -85,8 +96,8 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, update, Swizz
|
|||
* @param {Object} options.settings core settings
|
||||
* @param {Object} options.rest the rest of the props
|
||||
*/
|
||||
export const DesignOption = ({ config, settings, ux, inputs, values, Swizzled, ...rest }) => {
|
||||
const type = Swizzled.methods.designOptionType(config)
|
||||
export const DesignOption = ({ config, settings, ux, inputs, values, ...rest }) => {
|
||||
const type = designOptionType(config)
|
||||
const Input = inputs[type]
|
||||
const Value = values[type]
|
||||
const allowOverride = ['pct', 'count', 'deg'].includes(type)
|
||||
|
@ -96,12 +107,11 @@ export const DesignOption = ({ config, settings, ux, inputs, values, Swizzled, .
|
|||
if (config?.hide || (typeof config?.hide === 'function' && config.hide(settings))) return null
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuItem
|
||||
<MenuItem
|
||||
{...{
|
||||
config,
|
||||
ux,
|
||||
...rest,
|
||||
Swizzled,
|
||||
Input,
|
||||
Value,
|
||||
allowOverride,
|
|
@ -1,11 +1,13 @@
|
|||
import { useMemo, useCallback, useState } from 'react'
|
||||
import React, { useMemo, useCallback, useState } from 'react'
|
||||
import { round } from '@freesewing/utils'
|
||||
import { ButtonFrame } from '@freesewing/react/components/Input'
|
||||
|
||||
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
|
||||
export const MenuBoolInput = (props) => {
|
||||
const { name, config, Swizzled } = props
|
||||
const { name, config } = props
|
||||
const boolConfig = useBoolConfig(name, config)
|
||||
|
||||
return <Swizzled.components.MenuListInput {...props} config={boolConfig} />
|
||||
return <MenuListInput {...props} config={boolConfig} />
|
||||
}
|
||||
|
||||
/** A placeholder for an input to handle constant values */
|
||||
|
@ -33,8 +35,6 @@ export const MenuConstantInput = ({
|
|||
/** A {@see MenuSliderInput} to handle degree values */
|
||||
export const MenuDegInput = (props) => {
|
||||
const { updateHandler } = props
|
||||
const { MenuSliderInput } = props.Swizzled.components
|
||||
const { round } = props.Swizzled.methods
|
||||
const degUpdateHandler = useCallback(
|
||||
(path, newVal) => {
|
||||
updateHandler(path, newVal === undefined ? undefined : Number(newVal))
|
||||
|
@ -67,7 +67,6 @@ export const MenuListInput = ({
|
|||
changed,
|
||||
design,
|
||||
isDesignOption = false,
|
||||
Swizzled,
|
||||
}) => {
|
||||
const handleChange = useSharedHandlers({
|
||||
dflt: config.dflt,
|
||||
|
@ -87,7 +86,7 @@ export const MenuListInput = ({
|
|||
const sideBySide = config.sideBySide || desc.length + title.length < 42
|
||||
|
||||
return (
|
||||
<Swizzled.components.ButtonFrame
|
||||
<ButtonFrame
|
||||
dense={config.dense || false}
|
||||
key={entry}
|
||||
active={
|
||||
|
@ -107,7 +106,7 @@ export const MenuListInput = ({
|
|||
<div className="font-bold text-lg shrink-0">{title}</div>
|
||||
{compact ? null : <div className="text-base font-normal">{desc}</div>}
|
||||
</div>
|
||||
</Swizzled.components.ButtonFrame>
|
||||
</ButtonFrame>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -137,13 +136,10 @@ export const MenuListToggle = ({ config, changed, updateHandler, name }) => {
|
|||
|
||||
export const MenuMmInput = (props) => {
|
||||
const { units, updateHandler, current, config } = props
|
||||
const { MenuSliderInput } = props.Swizzled.components
|
||||
const mmUpdateHandler = useCallback(
|
||||
(path, newCurrent) => {
|
||||
const calcCurrent =
|
||||
typeof newCurrent === 'undefined'
|
||||
? undefined
|
||||
: props.Swizzled.methods.measurementAsMm(newCurrent, units)
|
||||
typeof newCurrent === 'undefined' ? undefined : measurementAsMm(newCurrent, units)
|
||||
updateHandler(path, calcCurrent)
|
||||
},
|
||||
[updateHandler, units]
|
||||
|
@ -159,15 +155,11 @@ export const MenuMmInput = (props) => {
|
|||
config: {
|
||||
step: defaultStep,
|
||||
...config,
|
||||
dflt: props.Swizzled.methods.measurementAsUnits(config.dflt, units),
|
||||
dflt: measurementAsUnits(config.dflt, units),
|
||||
},
|
||||
current:
|
||||
current === undefined
|
||||
? undefined
|
||||
: props.Swizzled.methods.measurementAsUnits(current, units),
|
||||
current: current === undefined ? undefined : measurementAsUnits(current, units),
|
||||
updateHandler: mmUpdateHandler,
|
||||
valFormatter: (val) =>
|
||||
units === 'imperial' ? props.Swizzle.methods.formatFraction128(val, null) : val,
|
||||
valFormatter: (val) => (units === 'imperial' ? formatFraction128(val, null) : val),
|
||||
suffix: units === 'imperial' ? '"' : 'cm',
|
||||
}}
|
||||
/>
|
||||
|
@ -243,27 +235,24 @@ export const MenuMmInput = (props) => {
|
|||
//}
|
||||
|
||||
/** A {@see SliderInput} to handle percentage values */
|
||||
export const MenuPctInput = ({ current, changed, updateHandler, config, Swizzled, ...rest }) => {
|
||||
export const MenuPctInput = ({ current, changed, updateHandler, config, ...rest }) => {
|
||||
const factor = 100
|
||||
let pctCurrent = changed ? Swizzled.methods.menuRoundPct(current, factor) : current
|
||||
let pctCurrent = changed ? menuRoundPct(current, factor) : current
|
||||
const pctUpdateHandler = useCallback(
|
||||
(path, newVal) =>
|
||||
updateHandler(
|
||||
path,
|
||||
newVal === undefined ? undefined : Swizzled.methods.menuRoundPct(newVal, 1 / factor)
|
||||
),
|
||||
updateHandler(path, newVal === undefined ? undefined : menuRoundPct(newVal, 1 / factor)),
|
||||
[updateHandler]
|
||||
)
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuSliderInput
|
||||
<MenuSliderInput
|
||||
{...{
|
||||
...rest,
|
||||
config: { ...config, dflt: Swizzled.methods.menuRoundPct(config.dflt, factor) },
|
||||
config: { ...config, dflt: menuRoundPct(config.dflt, factor) },
|
||||
current: pctCurrent,
|
||||
updateHandler: pctUpdateHandler,
|
||||
suffix: '%',
|
||||
valFormatter: Swizzled.methods.round,
|
||||
valFormatter: round,
|
||||
changed,
|
||||
}}
|
||||
/>
|
||||
|
@ -293,7 +282,6 @@ export const MenuSliderInput = ({
|
|||
setReset,
|
||||
children,
|
||||
changed,
|
||||
Swizzled,
|
||||
}) => {
|
||||
const { max, min } = config
|
||||
const handleChange = useSharedHandlers({
|
||||
|
@ -310,10 +298,9 @@ export const MenuSliderInput = ({
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-row justify-between">
|
||||
<Swizzled.components.MenuEditOption
|
||||
<MenuEditOption
|
||||
{...{
|
||||
config,
|
||||
Swizzled,
|
||||
current: val,
|
||||
handleChange,
|
||||
min,
|
||||
|
@ -354,8 +341,8 @@ export const MenuSliderInput = ({
|
|||
|
||||
export const MenuEditOption = (props) => {
|
||||
const [manualEdit, setManualEdit] = useState(props.current)
|
||||
const { config, handleChange, Swizzled } = props
|
||||
const type = Swizzled.methods.designOptionType(config)
|
||||
const { config, handleChange } = props
|
||||
const type = designOptionType(config)
|
||||
|
||||
const onUpdate = useCallback(
|
||||
(validVal) => {
|
||||
|
@ -370,14 +357,12 @@ export const MenuEditOption = (props) => {
|
|||
return (
|
||||
<div className="form-control mb-2 w-full">
|
||||
<label className="label font-medium text-accent">
|
||||
<em>
|
||||
{Swizzled.methods.t('pe:enterCustomValue')} ({Swizzled.config.menuOptionEditLabels[type]})
|
||||
</em>
|
||||
<em>Enter a custom value ({config.menuOptionEditLabels[type]})</em>
|
||||
</label>
|
||||
<label className="input-group input-group-sm flex flex-row items-center gap-2 -mt-4">
|
||||
<Swizzled.components.NumberInput value={manualEdit} update={setManualEdit} />
|
||||
<NumberInput value={manualEdit} update={setManualEdit} />
|
||||
<button className="btn btn-secondary mt-4" onClick={() => onUpdate(manualEdit)}>
|
||||
<Swizzled.components.ApplyIcon />
|
||||
<ApplyIcon />
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -427,16 +412,13 @@ const useBoolConfig = (name, config) => {
|
|||
|
||||
/** an input for the 'only' setting. toggles individual parts*/
|
||||
export const MenuOnlySettingInput = (props) => {
|
||||
const { Swizzled, config } = props
|
||||
const { t } = Swizzled.methods
|
||||
const { config } = props
|
||||
config.sideBySide = true
|
||||
config.titleMethod = (entry, t) => {
|
||||
const chunks = entry.split('.')
|
||||
return <span className="font-medium text-base">{t(`${chunks[0]}:${chunks[1]}`)}</span>
|
||||
}
|
||||
config.valueMethod = (entry) => (
|
||||
<span className="text-sm">{Swizzled.methods.capitalize(entry.split('.')[0])}</span>
|
||||
)
|
||||
config.valueMethod = (entry) => <span className="text-sm">{capitalize(entry.split('.')[0])}</span>
|
||||
config.dense = true
|
||||
// Sort alphabetically (translated)
|
||||
const order = []
|
||||
|
@ -447,13 +429,11 @@ export const MenuOnlySettingInput = (props) => {
|
|||
order.sort()
|
||||
config.list = order.map((entry) => entry.split('|')[1])
|
||||
|
||||
return <Swizzled.components.MenuListInput {...props} />
|
||||
return <MenuListInput {...props} />
|
||||
}
|
||||
|
||||
export const MenuUxSettingInput = (props) => {
|
||||
const { state, update, Swizzled } = props
|
||||
const { state, update } = props
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuListInput {...props} updateHandler={update.ui} current={state.ui.ux} />
|
||||
)
|
||||
return <MenuListInput {...props} updateHandler={update.ui} current={state.ui.ux} />
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react'
|
||||
import { mergeOptions } from '@freesewing/core'
|
||||
|
||||
/** Displays that constant values are not implemented in the front end */
|
||||
|
@ -6,16 +7,13 @@ export const MenuConstantOptionValue = () => (
|
|||
)
|
||||
|
||||
/** Displays a count value*/
|
||||
export const MenuCountOptionValue = ({ Swizzled, config, current, changed }) => (
|
||||
<Swizzled.components.MenuShowValue {...{ current, changed, dflt: config.count }} />
|
||||
export const MenuCountOptionValue = ({ config, current, changed }) => (
|
||||
<MenuShowValue {...{ current, changed, dflt: config.count }} />
|
||||
)
|
||||
|
||||
/** Displays a degree value */
|
||||
export const MenuDegOptionValue = ({ config, current, changed, Swizzled }) => (
|
||||
<Swizzled.components.MenuHighlightValue changed={changed}>
|
||||
{' '}
|
||||
{changed ? current : config.deg}°
|
||||
</Swizzled.components.MenuHighlightValue>
|
||||
export const MenuDegOptionValue = ({ config, current, changed }) => (
|
||||
<MenuHighlightValue changed={changed}> {changed ? current : config.deg}°</MenuHighlightValue>
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -29,10 +27,7 @@ export const MenuHighlightValue = ({ changed, children }) => (
|
|||
|
||||
/** Displays a list option value */
|
||||
export const MenuListOptionValue = (props) => (
|
||||
<MenuListValue
|
||||
{...props}
|
||||
t={(input) => props.Swizzled.methods.t(`${props.design}:${props.config.name}.${input}.t`)}
|
||||
/>
|
||||
<MenuListValue {...props} t={(input) => 'fixme handle option translation'} />
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -41,9 +36,8 @@ export const MenuListOptionValue = (props) => (
|
|||
* @param {Function} options.t a translation function
|
||||
* @param {Object} options.config the item config
|
||||
* @param {Boolean} options.changed has the value been changed?
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
*/
|
||||
export const MenuListValue = ({ current, config, changed, Swizzled }) => {
|
||||
export const MenuListValue = ({ current, config, changed }) => {
|
||||
// get the values
|
||||
const val = changed ? current : config.dflt
|
||||
|
||||
|
@ -54,17 +48,12 @@ export const MenuListValue = ({ current, config, changed, Swizzled }) => {
|
|||
// if not, is the value a string
|
||||
else if (typeof val === 'string') key = val
|
||||
// otherwise stringify booleans
|
||||
else if (val) key = <Swizzled.components.BoolYesIcon />
|
||||
else key = <Swizzled.components.BoolNoIcon />
|
||||
else if (val) key = <BoolYesIcon />
|
||||
else key = <BoolNoIcon />
|
||||
|
||||
const translated =
|
||||
config.doNotTranslate || typeof key !== 'string' ? key : Swizzled.methods.t(key)
|
||||
const translated = config.doNotTranslate || typeof key !== 'string' ? key : t(key)
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuHighlightValue changed={changed}>
|
||||
{translated}
|
||||
</Swizzled.components.MenuHighlightValue>
|
||||
)
|
||||
return <MenuHighlightValue changed={changed}>{translated}</MenuHighlightValue>
|
||||
}
|
||||
|
||||
/** Displays the corrent, translated value for a boolean */
|
||||
|
@ -76,14 +65,14 @@ export const MenuMmOptionValue = () => (
|
|||
)
|
||||
|
||||
/** Displays a formated mm value based on the current units */
|
||||
export const MenuMmValue = ({ current, config, units, changed, Swizzled }) => (
|
||||
<Swizzled.components.MenuHighlightValue changed={changed}>
|
||||
export const MenuMmValue = ({ current, config, units, changed }) => (
|
||||
<MenuHighlightValue changed={changed}>
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Swizzled.methods.formatMm(changed ? current : config.dflt, units),
|
||||
__html: formatMm(changed ? current : config.dflt, units),
|
||||
}}
|
||||
/>
|
||||
</Swizzled.components.MenuHighlightValue>
|
||||
</MenuHighlightValue>
|
||||
)
|
||||
|
||||
/** Displays the current percentage value, and the absolute value if configured
|
||||
|
@ -95,25 +84,18 @@ export const MenuMmValue = ({ current, config, units, changed, Swizzled }) => (
|
|||
* msg PencilIcon ResetIcon *
|
||||
**************************************************************************
|
||||
* */
|
||||
export const MenuPctOptionValue = ({
|
||||
config,
|
||||
current,
|
||||
settings,
|
||||
changed,
|
||||
patternConfig,
|
||||
Swizzled,
|
||||
}) => {
|
||||
export const MenuPctOptionValue = ({ config, current, settings, changed, patternConfig }) => {
|
||||
const val = changed ? current : config.pct / 100
|
||||
|
||||
return (
|
||||
<Swizzled.components.MenuHighlightValue changed={changed}>
|
||||
{Swizzled.methods.formatPercentage(val)}
|
||||
<MenuHighlightValue changed={changed}>
|
||||
{formatPercentage(val)}
|
||||
{config.toAbs && settings?.measurements
|
||||
? ` | ${Swizzled.methods.formatMm(
|
||||
? ` | ${formatMm(
|
||||
config.toAbs(val, settings, mergeOptions(settings, patternConfig.options))
|
||||
)}`
|
||||
: null}
|
||||
</Swizzled.components.MenuHighlightValue>
|
||||
</MenuHighlightValue>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -123,18 +105,16 @@ export const MenuPctOptionValue = ({
|
|||
* @param {Number|String|Boolean} options.dflt - The default value
|
||||
* @param {Boolean} options.changed - Has the value been changed?
|
||||
*/
|
||||
export const MenuShowValue = ({ Swizzled, current, dflt, changed }) => {
|
||||
const { MenuHighlightValue } = Swizzled.components
|
||||
|
||||
export const MenuShowValue = ({ current, dflt, changed }) => {
|
||||
return <MenuHighlightValue changed={changed}> {changed ? current : dflt} </MenuHighlightValue>
|
||||
}
|
||||
|
||||
export const MenuScaleSettingValue = ({ Swizzled, current, config, changed }) => (
|
||||
<Swizzled.components.MenuHighlightValue current={current} dflt={config.dflt} changed={changed} />
|
||||
export const MenuScaleSettingValue = ({ current, config, changed }) => (
|
||||
<MenuHighlightValue current={current} dflt={config.dflt} changed={changed} />
|
||||
)
|
||||
|
||||
export const MenuOnlySettingValue = ({ Swizzled, current, config }) => (
|
||||
<Swizzled.components.MenuHighlightValue
|
||||
export const MenuOnlySettingValue = ({ current, config }) => (
|
||||
<MenuHighlightValue
|
||||
current={current?.length}
|
||||
dflt={config.parts.length}
|
||||
changed={current !== undefined}
|
|
@ -1,34 +1,43 @@
|
|||
// Dependencies
|
||||
import React from 'react'
|
||||
import { draft, missingMeasurements } from '../../lib/index.mjs'
|
||||
// Components
|
||||
import { Null } from '../Null.mjs'
|
||||
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||
import { PatternLayout } from '../PatternLayout.mjs'
|
||||
import { DraftMenu } from '../menus/DraftMenu.mjs'
|
||||
|
||||
/**
|
||||
* The draft view allows users to tweak their pattern
|
||||
*
|
||||
* @param (object) props - All the props
|
||||
* @param {function} props.config - The editor configuration
|
||||
* @param {function} props.Design - The design constructor
|
||||
* @param {array} props.missingMeasurements - List of missing measurements for the current design
|
||||
* @param {object} props.state - The ViewWrapper state object
|
||||
* @param {object} props.state.settings - The current settings
|
||||
* @param {object} props.update - Helper object for updating the ViewWrapper state
|
||||
* @param {object} props.Swizzled - An object holding swizzled code
|
||||
* @return {function} DraftView - React component
|
||||
*/
|
||||
export const DraftView = ({ Design, state, update, Swizzled }) => {
|
||||
export const DraftView = ({ Design, state, update, config }) => {
|
||||
/*
|
||||
* Don't trust that we have all measurements
|
||||
*
|
||||
* We do not need to change the view here. That is done in the central
|
||||
* ViewWrapper componenet. However, checking the measurements against
|
||||
* the design takes a brief moment, so this component will typically
|
||||
* render before that happens, and if measurments are missing it will
|
||||
* render before that happens, and if measurements are missing it will
|
||||
* throw and error.
|
||||
*
|
||||
* So when measurements are missing, we just return here and the view
|
||||
* will switch on the next render loop.
|
||||
*/
|
||||
if (Swizzled.methods.missingMeasurements(state)) return null
|
||||
if (missingMeasurements(state)) return <Null />
|
||||
|
||||
/*
|
||||
* First, attempt to draft
|
||||
*/
|
||||
const { pattern } = Swizzled.methods.draft(Design, state.settings)
|
||||
const { pattern } = draft(Design, state.settings)
|
||||
|
||||
let output = null
|
||||
let renderProps = false
|
||||
|
@ -36,9 +45,9 @@ export const DraftView = ({ Design, state, update, Swizzled }) => {
|
|||
try {
|
||||
const __html = pattern.render()
|
||||
output = (
|
||||
<Swizzled.components.ZoomablePattern>
|
||||
<ZoomablePattern>
|
||||
<div className="w-full h-full" dangerouslySetInnerHTML={{ __html }} />
|
||||
</Swizzled.components.ZoomablePattern>
|
||||
</ZoomablePattern>
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
|
@ -46,7 +55,7 @@ export const DraftView = ({ Design, state, update, Swizzled }) => {
|
|||
} else {
|
||||
renderProps = pattern.getRenderProps()
|
||||
output = (
|
||||
<Swizzled.components.ZoomablePattern
|
||||
<ZoomablePattern
|
||||
renderProps={renderProps}
|
||||
patternLocale={state.locale || 'en'}
|
||||
rotate={state.ui.rotate}
|
||||
|
@ -55,13 +64,9 @@ export const DraftView = ({ Design, state, update, Swizzled }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Swizzled.components.PatternLayout
|
||||
{...{ update, Design, output, state, pattern }}
|
||||
menu={
|
||||
state.ui?.aside ? (
|
||||
<Swizzled.components.DraftMenu {...{ Design, pattern, update, state }} />
|
||||
) : null
|
||||
}
|
||||
<PatternLayout
|
||||
{...{ update, Design, output, state, pattern, config }}
|
||||
menu={state.ui?.aside ? <DraftMenu {...{ Design, pattern, update, state }} /> : null}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Dependencies
|
||||
import { horFlexClasses } from '../../utils.mjs'
|
||||
import { t, designMeasurements } from '../../lib/index.mjs'
|
||||
import { capitalize } from '@freesewing/utils'
|
||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||
// Hooks
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
// Components
|
||||
|
@ -17,7 +17,11 @@ import { MeasurementsEditor } from '../MeasurementsEditor.mjs'
|
|||
import { SetPicker, BookmarkedSetPicker, CuratedSetPicker, UserSetPicker } from '../Set.mjs'
|
||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||
|
||||
const iconClasses = { className: 'w-8 h-8 md:w-10 md:h-10 lg:w-12 lg:h-12 shrink-0', stroke: 1.5 }
|
||||
const iconClasses = {
|
||||
className: 'tw-w-8 tw-h-8 md:tw-w-10 md:tw-h-10 lg:tw-w-12 lg:tw-h-12 tw-shrink-0',
|
||||
stroke: 1.5,
|
||||
}
|
||||
const horFlexClasses = 'tw-flex tw-flex-row tw-items-center tw-justify-between tw-gap-4 tw-w-full'
|
||||
|
||||
/**
|
||||
* The measurements view is loaded to update/set measurements
|
||||
|
@ -40,7 +44,13 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
useEffect(() => {
|
||||
if (!config?.views || !config.views.includes(state.view)) update.view('measurements')
|
||||
if (state._.missingMeasurements && state._.missingMeasurements.length > 0)
|
||||
update.notify({ msg: t('pe:missingMeasurementsNotify'), icon: 'tip' }, 'missingMeasurements')
|
||||
update.notify(
|
||||
{
|
||||
msg: 'To generate this pattern, we need some additional measurements',
|
||||
icon: 'tip',
|
||||
},
|
||||
'missingMeasurements'
|
||||
)
|
||||
else update.notifySuccess(t('pe:measurementsAreOk'))
|
||||
}, [state.view, update])
|
||||
|
||||
|
@ -48,9 +58,9 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
update.settings(['measurements'], designMeasurements(Design, set.measies))
|
||||
update.settings(['units'], set.imperial ? 'imperial' : 'metric')
|
||||
// Save the measurement set name to pattern settings
|
||||
if (set[`name${capitalize(locale)}`])
|
||||
if (set.nameEn)
|
||||
// Curated measurement set
|
||||
update.settings(['metadata'], { setName: set[`name${capitalize(locale)}`] })
|
||||
update.settings(['metadata'], { setName: set.nameEn })
|
||||
else if (set.name)
|
||||
// User measurement set
|
||||
update.settings(['metadata'], { setName: set.name })
|
||||
|
@ -63,10 +73,13 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
[
|
||||
<Fragment key={1}>
|
||||
<div className={horFlexClasses}>
|
||||
<h5 id="ownsets">{t('pe:chooseFromOwnSets')}</h5>
|
||||
<h4 id="ownsets">Choose one of your own measurements sets</h4>
|
||||
<MeasurementsSetIcon {...iconClasses} />
|
||||
</div>
|
||||
<p className="text-left">{t('pe:chooseFromOwnSetsDesc')}</p>
|
||||
<p className="tw-text-left">
|
||||
Pick any of your own measurements sets that have all required measurements to generate
|
||||
this pattern.
|
||||
</p>
|
||||
</Fragment>,
|
||||
<UserSetPicker
|
||||
key={2}
|
||||
|
@ -80,10 +93,12 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
[
|
||||
<Fragment key={1}>
|
||||
<div className={horFlexClasses}>
|
||||
<h5 id="bookmarkedsets">{t('pe:chooseFromBookmarkedSets')}</h5>
|
||||
<h4 id="bookmarkedsets">Choose one of the measurements sets you have bookmarked</h4>
|
||||
<BookmarkIcon {...iconClasses} />
|
||||
</div>
|
||||
<p className="text-left">{t('pe:chooseFromBookmarkedSetsDesc')}</p>
|
||||
<p className="tw-text-left">
|
||||
If you have bookmarked any measurements sets, you can select from those too.
|
||||
</p>
|
||||
</Fragment>,
|
||||
<BookmarkedSetPicker
|
||||
key={2}
|
||||
|
@ -97,10 +112,13 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
[
|
||||
<Fragment key={1}>
|
||||
<div className={horFlexClasses}>
|
||||
<h5 id="curatedsets">{t('pe:chooseFromCuratedSets')}</h5>
|
||||
<h4 id="curatedsets">Choose one of FreeSewing's curated measurements sets</h4>
|
||||
<CuratedMeasurementsSetIcon {...iconClasses} />
|
||||
</div>
|
||||
<p className="text-left">{t('pe:chooseFromCuratedSetsDesc')}</p>
|
||||
<p className="tw-text-left">
|
||||
If you're just looking to try out our platform, you can select from our list of
|
||||
curated measurements sets.
|
||||
</p>
|
||||
</Fragment>,
|
||||
<CuratedSetPicker key={2} clickHandler={loadMeasurements} {...{ config, Design }} />,
|
||||
'csets',
|
||||
|
@ -110,10 +128,10 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
items.push([
|
||||
<Fragment key={1}>
|
||||
<div className={horFlexClasses}>
|
||||
<h5 id="editmeasurements">{t('pe:editMeasurements')}</h5>
|
||||
<h4 id="editmeasurements">Edit Measurements</h4>
|
||||
<EditIcon {...iconClasses} />
|
||||
</div>
|
||||
<p className="text-left">{t('pe:editMeasurementsDesc')}</p>
|
||||
<p className="tw-text-left">You can manually set or override measurements below.</p>
|
||||
</Fragment>,
|
||||
<MeasurementsEditor key={2} {...{ Design, config, update, state }} />,
|
||||
'edit',
|
||||
|
@ -122,35 +140,38 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
|||
return (
|
||||
<>
|
||||
<HeaderMenu state={state} {...{ config, update }} />
|
||||
<div className="max-w-7xl mt-8 mx-auto px-4">
|
||||
<h2>{t('pe:measurements')}</h2>
|
||||
<div className="tw-max-w-7xl tw-mt-8 tw-mx-auto tw-px-4 tw-mb-4">
|
||||
<h1 className="tw-text-center">Measurements</h1>
|
||||
{missingMeasurements && missingMeasurements.length > 0 ? (
|
||||
<Popout note dense noP>
|
||||
<h5>{t('pe:missingMeasurementsInfo')}:</h5>
|
||||
<ol className="list list-inside flex flex-row flex-wrap">
|
||||
<h3>
|
||||
To generate this pattern, we need {missingMeasurements.length} additional measurement
|
||||
{missingMeasurements.length === 1 ? '' : 's'}:
|
||||
</h3>
|
||||
<ol className="tw-list tw-list-inside tw-flex tw-flex-row tw-flex-wrap tw-ml-0 tw-pl-0">
|
||||
{missingMeasurements.map((m, i) => (
|
||||
<li key={i}>
|
||||
{i > 0 ? <span className="pr-2">,</span> : null}
|
||||
<span className="font-medium">{t(`measurements:${m}`)}</span>
|
||||
<li key={i} className="tw-flex">
|
||||
{i > 0 ? <span className="tw-pr-2">,</span> : null}
|
||||
<span className="tw-font-medium">{measurementsTranslations[m]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<p className="text-sm m-0 p-0 pt-2">
|
||||
({missingMeasurements.length} {t('pe:missingMeasurements')})
|
||||
</p>
|
||||
</Popout>
|
||||
) : (
|
||||
<Popout tip dense noP>
|
||||
<h5>{t('pe:measurementsAreOk')}</h5>
|
||||
<div className="flex flex-row flex-wrap gap-2 mt-2">
|
||||
<button className="btn btn-primary lg:btn-lg" onClick={() => update.view('draft')}>
|
||||
{t('pe:view.draft.t')}
|
||||
<h5>We have all required measurements to draft this pattern</h5>
|
||||
<div className="tw-flex tw-flex-row tw-flex-wrap tw-gap-2 tw-mt-2">
|
||||
<button
|
||||
className="tw-daisy-btn tw-daisy-btn-primary lg:tw-daisy-btn-lg"
|
||||
onClick={() => update.view('draft')}
|
||||
>
|
||||
{viewLabels.draft.t}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-outline lg:btn-lg"
|
||||
className="tw-daisy-btn tw-daisy-btn-primary tw-daisy-btn-outline lg:tw-daisy-btn-lg"
|
||||
onClick={() => update.view('picker')}
|
||||
>
|
||||
{t('pe:chooseAnotherActivity')}
|
||||
Choose a different view
|
||||
</button>
|
||||
</div>
|
||||
</Popout>
|
||||
|
|
|
@ -2,20 +2,9 @@ import React from 'react'
|
|||
import { ViewPicker } from './ViewPicker.mjs'
|
||||
import { DesignsView } from './DesignsView.mjs'
|
||||
import { MeasurementsView } from './MeasurementsView.mjs'
|
||||
import { DraftView } from './DraftView.mjs'
|
||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||
|
||||
/*
|
||||
* This allows us to load a view component from the view name
|
||||
*/
|
||||
export const viewComponents = {
|
||||
// DraftView,
|
||||
DesignsView,
|
||||
// SaveView,
|
||||
ViewPicker,
|
||||
MeasurementsView,
|
||||
// UndosView,
|
||||
}
|
||||
|
||||
/*
|
||||
* This returns a view-specific component
|
||||
*/
|
||||
|
@ -24,6 +13,7 @@ export const View = (props) => {
|
|||
|
||||
if (view === 'designs') return <DesignsView {...props} />
|
||||
if (view === 'measurements') return <MeasurementsView {...props} />
|
||||
if (view === 'draft') return <DraftView {...props} />
|
||||
/*
|
||||
viewComponents: {
|
||||
draft: 'DraftView',
|
||||
|
@ -44,7 +34,7 @@ export const View = (props) => {
|
|||
},
|
||||
*/
|
||||
|
||||
return <p>No view component for view {props.view}</p>
|
||||
return <h1 className="tw-ext-center tw-my-12">No view component for view {props.view}</h1>
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -5,7 +5,7 @@ export const defaultConfig = {
|
|||
// Enable use of a (FreeSewing) backend to load data from
|
||||
enableBackend: true,
|
||||
// Link to create a new measurements set, set to false to disable
|
||||
hrefNewSet: 'https://freesewing.org/new/set',
|
||||
hrefNewSet: '/new/set',
|
||||
// Cloud default image
|
||||
cloudImageDflt:
|
||||
'https://imagedelivery.net/ouSuR9yY1bHt-fuAokSA5Q/365cc64e-1502-4d2b-60e0-cc8beee73f00/public',
|
||||
|
@ -56,12 +56,6 @@ export const defaultConfig = {
|
|||
mm: 'MmOptionValue',
|
||||
pct: 'PctOptionValue',
|
||||
},
|
||||
// Facilitate custom handlers for core settings
|
||||
menuCoreSettingsHandlerMethods: {
|
||||
only: 'menuCoreSettingsOnlyHandler',
|
||||
sabool: 'menuCoreSettingsSaboolHandler',
|
||||
samm: 'menuCoreSettingsSammHandler',
|
||||
},
|
||||
menuGroupEmojis: {
|
||||
advanced: '🤓',
|
||||
fit: '👕',
|
||||
|
|
|
@ -34,7 +34,7 @@ export const useEditorState = (init = {}, setEphemeralState, config) => {
|
|||
if (typeof URLSearchParams !== 'undefined') {
|
||||
try {
|
||||
const data = getHashData()
|
||||
if (data.s === 'object') setState(data.s)
|
||||
if (typeof data.s === 'object') setState(data.s)
|
||||
else setState(init)
|
||||
} catch (err) {
|
||||
setState(init)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Dependencies
|
||||
import { designs } from '@freesewing/collection'
|
||||
import { hasRequiredMeasurements, initialEditorState } from './lib/index.mjs'
|
||||
import { hasRequiredMeasurements } from '@freesewing/utils'
|
||||
import { initialEditorState } from './lib/index.mjs'
|
||||
import { mergeConfig } from './config/index.mjs'
|
||||
// Hooks
|
||||
import React, { useState } from 'react'
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
export function defaultSa(Swizzled, units, inMm = true) {
|
||||
// Dependencies
|
||||
import { defaultConfig as config } from '../config/index.mjs'
|
||||
import { measurementAsMm } from '@freesewing/utils'
|
||||
/*
|
||||
* Components
|
||||
* Note that these are only used as returns values
|
||||
* There's no JSX in here so no React import needed
|
||||
*/
|
||||
import {
|
||||
DetailIcon,
|
||||
ExpandIcon,
|
||||
IncludeIcon,
|
||||
MarginIcon,
|
||||
PaperlessIcon,
|
||||
SaIcon,
|
||||
ScaleIcon,
|
||||
UnitsIcon,
|
||||
} from '@freesewing/react/components/Icon'
|
||||
|
||||
export function defaultSa(units, inMm = true) {
|
||||
const dflt = units === 'imperial' ? 0.5 : 1
|
||||
return inMm ? Swizzled.methods.measurementAsMm(dflt, units) : dflt
|
||||
return inMm ? measurementAsMm(dflt, units) : dflt
|
||||
}
|
||||
export function defaultSamm(Swizzled, units, inMm = true) {
|
||||
export function defaultSamm(units, inMm = true) {
|
||||
const dflt = units === 'imperial' ? 0.5 : 1
|
||||
return inMm ? Swizzled.methods.measurementAsMm(dflt, units) : dflt
|
||||
return inMm ? measurementAsMm(dflt, units) : dflt
|
||||
}
|
||||
/** custom event handlers for inputs that need them */
|
||||
export function menuCoreSettingsOnlyHandler(Swizzled, { updateHandler, current }) {
|
||||
export function menuCoreSettingsOnlyHandler({ updateHandler, current }) {
|
||||
return function (path, part) {
|
||||
// Is this a reset?
|
||||
if (part === undefined || part === '__UNSET__') return updateHandler(path, part)
|
||||
|
@ -26,7 +45,7 @@ export function menuCoreSettingsOnlyHandler(Swizzled, { updateHandler, current }
|
|||
}
|
||||
}
|
||||
|
||||
export function menuCoreSettingsSammHandler(Swizzled, { updateHandler, config }) {
|
||||
export function menuCoreSettingsSammHandler({ updateHandler, config }) {
|
||||
return function (_path, newCurrent) {
|
||||
// convert to millimeters if there's a value
|
||||
newCurrent = newCurrent === undefined ? config.dflt : newCurrent
|
||||
|
@ -36,16 +55,13 @@ export function menuCoreSettingsSammHandler(Swizzled, { updateHandler, config })
|
|||
}
|
||||
}
|
||||
|
||||
export function menuCoreSettingsSaboolHandler(Swizzled, { toggleSa }) {
|
||||
export function menuCoreSettingsSaboolHandler({ toggleSa }) {
|
||||
return toggleSa
|
||||
}
|
||||
export function menuCoreSettingsStructure(
|
||||
Swizzled,
|
||||
{ units = 'metric', sabool = false, parts = [] }
|
||||
) {
|
||||
export function menuCoreSettingsStructure({ units = 'metric', sabool = false, parts = [] }) {
|
||||
return {
|
||||
sabool: {
|
||||
ux: Swizzled.config.uxLevels.core.sa,
|
||||
ux: config.uxLevels.core.sa,
|
||||
list: [0, 1],
|
||||
choiceTitles: {
|
||||
0: 'saNo',
|
||||
|
@ -56,19 +72,19 @@ export function menuCoreSettingsStructure(
|
|||
1: 'yes',
|
||||
},
|
||||
dflt: 0,
|
||||
icon: Swizzled.components.SaIcon,
|
||||
icon: SaIcon,
|
||||
},
|
||||
samm: sabool
|
||||
? {
|
||||
ux: Swizzled.config.uxLevels.core.sa,
|
||||
ux: config.uxLevels.core.sa,
|
||||
min: 0,
|
||||
max: units === 'imperial' ? 2 : 2.5,
|
||||
dflt: Swizzled.methods.defaultSamm(units),
|
||||
icon: Swizzled.components.SaIcon,
|
||||
dflt: defaultSamm(units),
|
||||
icon: SaIcon,
|
||||
}
|
||||
: false,
|
||||
paperless: {
|
||||
ux: Swizzled.config.uxLevels.core.paperless,
|
||||
ux: config.uxLevels.core.paperless,
|
||||
list: [0, 1],
|
||||
choiceTitles: {
|
||||
0: 'paperlessNo',
|
||||
|
@ -79,10 +95,10 @@ export function menuCoreSettingsStructure(
|
|||
1: 'yes',
|
||||
},
|
||||
dflt: 0,
|
||||
icon: Swizzled.components.PaperlessIcon,
|
||||
icon: PaperlessIcon,
|
||||
},
|
||||
units: {
|
||||
ux: Swizzled.config.uxLevels.core.units,
|
||||
ux: config.uxLevels.core.units,
|
||||
list: ['metric', 'imperial'],
|
||||
dflt: 'metric',
|
||||
choiceTitles: {
|
||||
|
@ -93,10 +109,10 @@ export function menuCoreSettingsStructure(
|
|||
metric: 'metric',
|
||||
imperial: 'imperial',
|
||||
},
|
||||
icon: Swizzled.components.UnitsIcon,
|
||||
icon: UnitsIcon,
|
||||
},
|
||||
complete: {
|
||||
ux: Swizzled.config.uxLevels.core.complete,
|
||||
ux: config.uxLevels.core.complete,
|
||||
list: [1, 0],
|
||||
dflt: 1,
|
||||
choiceTitles: {
|
||||
|
@ -107,10 +123,10 @@ export function menuCoreSettingsStructure(
|
|||
0: 'no',
|
||||
1: 'yes',
|
||||
},
|
||||
icon: Swizzled.components.DetailIcon,
|
||||
icon: DetailIcon,
|
||||
},
|
||||
expand: {
|
||||
ux: Swizzled.config.uxLevels.core.expand,
|
||||
ux: config.uxLevels.core.expand,
|
||||
list: [1, 0],
|
||||
dflt: 1,
|
||||
choiceTitles: {
|
||||
|
@ -121,29 +137,29 @@ export function menuCoreSettingsStructure(
|
|||
0: 'no',
|
||||
1: 'yes',
|
||||
},
|
||||
icon: Swizzled.components.ExpandIcon,
|
||||
icon: ExpandIcon,
|
||||
},
|
||||
only: {
|
||||
ux: Swizzled.config.uxLevels.core.only,
|
||||
ux: config.uxLevels.core.only,
|
||||
dflt: false,
|
||||
list: parts,
|
||||
parts,
|
||||
icon: Swizzled.components.IncludeIcon,
|
||||
icon: IncludeIcon,
|
||||
},
|
||||
scale: {
|
||||
ux: Swizzled.config.uxLevels.core.scale,
|
||||
ux: config.uxLevels.core.scale,
|
||||
min: 0.1,
|
||||
max: 5,
|
||||
dflt: 1,
|
||||
step: 0.1,
|
||||
icon: Swizzled.components.ScaleIcon,
|
||||
icon: ScaleIcon,
|
||||
},
|
||||
margin: {
|
||||
ux: Swizzled.config.uxLevels.core.margin,
|
||||
ux: config.uxLevels.core.margin,
|
||||
min: 0,
|
||||
max: 2.5,
|
||||
dflt: Swizzled.methods.measurementAsMm(units === 'imperial' ? 0.125 : 0.2, units),
|
||||
icon: Swizzled.components.MarginIcon,
|
||||
dflt: measurementAsMm(units === 'imperial' ? 0.125 : 0.2, units),
|
||||
icon: MarginIcon,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function designOptionType(Swizzled, option) {
|
||||
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'
|
||||
|
@ -12,7 +12,7 @@ import { mergeOptions } from '@freesewing/core'
|
|||
import set from 'lodash.set'
|
||||
import orderBy from 'lodash.orderby'
|
||||
|
||||
export function menuDesignOptionsStructure(Swizzled, options, settings, asFullList = false) {
|
||||
export function menuDesignOptionsStructure(options, settings, asFullList = false) {
|
||||
if (!options) return options
|
||||
const sorted = {}
|
||||
for (const [name, option] of Object.entries(options)) {
|
||||
|
@ -23,7 +23,7 @@ export function menuDesignOptionsStructure(Swizzled, options, settings, asFullLi
|
|||
// Fixme: One day we should sort this based on the translation
|
||||
for (const option of orderBy(sorted, ['order', 'menu', 'name'], ['asc', 'asc', 'asc'])) {
|
||||
if (typeof option === 'object') {
|
||||
const oType = Swizzled.methods.designOptionType(option)
|
||||
const oType = designOptionType(option)
|
||||
option.dflt = option.dflt || option[oType]
|
||||
if (oType === 'pct') option.dflt /= 100
|
||||
if (typeof option.menu === 'function')
|
||||
|
@ -64,17 +64,14 @@ export function menuDesignOptionsStructure(Swizzled, options, settings, asFullLi
|
|||
*
|
||||
* Since these structures can be nested with option groups, this needs some extra logic
|
||||
*/
|
||||
export function getOptionStructure(Swizzled, option, Design, state) {
|
||||
const structure = Swizzled.methods.menuDesignOptionsStructure(
|
||||
Design.patternConfig.options,
|
||||
state.settings
|
||||
)
|
||||
export function getOptionStructure(option, Design, state) {
|
||||
const structure = menuDesignOptionsStructure(Design.patternConfig.options, state.settings)
|
||||
console.log({ structure })
|
||||
|
||||
return Swizzled.methods.findOption(structure, option)
|
||||
return findOption(structure, option)
|
||||
}
|
||||
|
||||
export function findOption(Swizzled, structure, option) {
|
||||
export function findOption(structure, option) {
|
||||
for (const [key, val] of Object.entries(structure)) {
|
||||
if (key === option) return val
|
||||
if (val.isGroup) {
|
||||
|
|
|
@ -55,13 +55,7 @@ import {
|
|||
shortDate,
|
||||
parseDistanceInput,
|
||||
} from './formatting.mjs'
|
||||
import {
|
||||
designMeasurements,
|
||||
hasRequiredMeasurements,
|
||||
isDegreeMeasurement,
|
||||
missingMeasurements,
|
||||
structureMeasurementsAsDesign,
|
||||
} from './measurements.mjs'
|
||||
import { designMeasurements, missingMeasurements } from './measurements.mjs'
|
||||
import { menuUiPreferencesStructure } from './ui-preferences.mjs'
|
||||
|
||||
/*
|
||||
|
@ -120,10 +114,7 @@ export {
|
|||
parseDistanceInput,
|
||||
// measurements.mjs
|
||||
designMeasurements,
|
||||
hasRequiredMeasurements,
|
||||
isDegreeMeasurement,
|
||||
missingMeasurements,
|
||||
structureMeasurementsAsDesign,
|
||||
// ui-preferences.mjs
|
||||
menuUiPreferencesStructure,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Dependencies
|
||||
import { defaultConfig as config } from '../config/index.mjs'
|
||||
import { defaultConfig } from '../config/index.mjs'
|
||||
import { degreeMeasurements } from '@freesewing/config'
|
||||
|
||||
/*
|
||||
|
@ -16,46 +16,9 @@ export function designMeasurements(Design, measies = {}) {
|
|||
|
||||
return measurements
|
||||
}
|
||||
/**
|
||||
* Helper method to determine whether all required measurements for a design are present
|
||||
*
|
||||
* @param {object} Design - The FreeSewing design (or a plain object holding measurements)
|
||||
* @param {object} measurements - An object holding the user's measurements
|
||||
* @return {array} result - An array where the first element is true when we
|
||||
* have all measurements, and false if not. The second element is a list of
|
||||
* missing measurements.
|
||||
*/
|
||||
export function hasRequiredMeasurements(Design, measurements = {}) {
|
||||
/*
|
||||
* If design is just a plain object holding measurements, we restructure it as a Design
|
||||
* AS it happens, this method is smart enough to test for this, so we call it always
|
||||
*/
|
||||
Design = structureMeasurementsAsDesign(Design)
|
||||
|
||||
/*
|
||||
* Walk required measuremnets, and keep track of what's missing
|
||||
*/
|
||||
const missing = []
|
||||
for (const m of Design.patternConfig?.measurements || []) {
|
||||
if (typeof measurements[m] === 'undefined') missing.push(m)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true or false, plus a list of missing measurements
|
||||
*/
|
||||
return [missing.length === 0, missing]
|
||||
}
|
||||
/**
|
||||
* Helper method to determine whether a measurement uses degrees
|
||||
*
|
||||
* @param {string} name - The name of the measurement
|
||||
* @return {bool} isDegree - True if the measurment is a degree measurement
|
||||
*/
|
||||
export function isDegreeMeasurement(name) {
|
||||
return degreeMeasurements.indexOf(name) !== -1
|
||||
}
|
||||
/*
|
||||
* Helper method to check whether measururements are missing
|
||||
* Helper method to check whether measurements are missing
|
||||
*
|
||||
* Note that this does not actually check the settings against
|
||||
* the chosen design, but rather relies on the missing measurements
|
||||
|
@ -63,22 +26,12 @@ export function isDegreeMeasurement(name) {
|
|||
* so we do it only once.
|
||||
*
|
||||
* @param {object} state - The Editor state
|
||||
* @param {object} config - The Editor configuration
|
||||
* @return {bool} missing - True if there are missing measurments, false if not
|
||||
*/
|
||||
export function missingMeasurements(state, config) {
|
||||
export function missingMeasurements(state) {
|
||||
return (
|
||||
!config.measurementsFreeViews.includes(state.view) &&
|
||||
!defaultConfig.measurementsFreeViews.includes(state.view) &&
|
||||
state._.missingMeasurements &&
|
||||
state._.missingMeasurements.length > 0
|
||||
)
|
||||
}
|
||||
/*
|
||||
* This takes a POJO of measurements, and turns it into a structure that matches a design object
|
||||
*
|
||||
* @param {object} measurements - The POJO of measurments
|
||||
* @return {object} design - The measurements structured as a design object
|
||||
*/
|
||||
export function structureMeasurementsAsDesign(measurements) {
|
||||
return measurements.patternConfig ? measurements : { patternConfig: { measurements } }
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
export const CuratedMeasurementsSetLineup = ({ sets = [], locale, clickHandler, Swizzled }) => (
|
||||
<div
|
||||
className={`w-full flex flex-row ${
|
||||
sets.length > 1 ? 'justify-start px-8' : 'justify-center'
|
||||
} overflow-x-scroll`}
|
||||
style={{
|
||||
backgroundImage: `url(/img/lineup-backdrop.svg)`,
|
||||
width: 'auto',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundRepeat: 'repeat-x',
|
||||
}}
|
||||
>
|
||||
{sets.map((set) => {
|
||||
const props = {
|
||||
className: 'aspect-[1/3] w-auto h-96',
|
||||
style: {
|
||||
backgroundImage: `url(${Swizzled.methods.cloudImageUrl({
|
||||
id: `cset-${set.id}`,
|
||||
type: 'lineup',
|
||||
})})`,
|
||||
width: 'auto',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
},
|
||||
onClick: () => clickHandler(set),
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center" key={set.id}>
|
||||
<button {...props} key={set.id}></button>
|
||||
<b>{set[`name${Swizzled.methods.capitalize(locale)}`]}</b>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
|
@ -6,6 +6,7 @@ import {
|
|||
distanceAsMm,
|
||||
} from '@freesewing/utils'
|
||||
import { collection } from '@freesewing/collection'
|
||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||
// Context
|
||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||
|
@ -527,7 +528,7 @@ export const MeasurementInput = ({
|
|||
let inputClasses = 'daisy-input-secondary'
|
||||
let bottomLeftLabel = null
|
||||
if (valid === true) {
|
||||
inputClasses = 'daisy-input-success'
|
||||
inputClasses = 'daisy-input-success tw-outline-success'
|
||||
const val = `${validatedVal}${isDegree ? '°' : imperial ? '"' : 'cm'}`
|
||||
bottomLeftLabel = (
|
||||
<span className="tw-font-medium tw-text-base tw-text-success tw--mt-2 tw-block">{val}</span>
|
||||
|
@ -549,17 +550,26 @@ export const MeasurementInput = ({
|
|||
* See: https://github.com/facebook/react/issues/16554
|
||||
*/
|
||||
return (
|
||||
<FormControl label={m + (isDegree ? ' (°)' : '')} forId={id} labelBL={bottomLeftLabel}>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
placeholder={placeholder}
|
||||
value={localVal}
|
||||
onChange={(evt) => localUpdate(evt.target.value)}
|
||||
className={`tw-daisy-input tw-w-full tw-daisy-input-bordered ${inputClasses}`}
|
||||
/>
|
||||
<FormControl
|
||||
label={measurementsTranslations[m] + (isDegree ? ' (°)' : '')}
|
||||
forId={id}
|
||||
labelBL={bottomLeftLabel}
|
||||
>
|
||||
<label
|
||||
className={`tw-daisy-input tw-daisy-input-bordered tw-flex tw-items-center tw-gap-2 tw-border ${inputClasses} tw-mb-1 tw-outline tw-outline-base-300 tw-bg-transparent tw-outline-2 tw-outline-offset-2`}
|
||||
>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
placeholder={placeholder}
|
||||
value={localVal}
|
||||
onChange={(evt) => localUpdate(evt.target.value)}
|
||||
className={`tw-border-0 tw-grow-2 tw-w-full`}
|
||||
/>
|
||||
{isDegree ? '°' : imperial ? 'inch' : 'cm'}
|
||||
</label>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export const Popout = (props) => {
|
|||
>
|
||||
<div
|
||||
className={`
|
||||
tw-border-y-4 sm:tw-border-0 sm:tw-border-l-4 tw-px-6 sm:tw-px-8 tw-py-4 sm:tw-py-2
|
||||
tw-border-y-4 tw-border-x-0 sm:tw-border-0 sm:tw-border-l-4 tw-px-6 sm:tw-px-8 tw-py-4 sm:tw-py-2
|
||||
tw-shadow tw-text-base tw-border-${color} tw-border-solid
|
||||
`}
|
||||
>
|
||||
|
|
|
@ -3,15 +3,15 @@ import React from 'react'
|
|||
/*
|
||||
* A simple spinner
|
||||
*/
|
||||
export const Spinner = ({ className = 'h-6 w-6' }) => (
|
||||
export const Spinner = ({ className = 'tw-h-6 tw-w-6' }) => (
|
||||
<svg
|
||||
className={`animate-spin ${className}`}
|
||||
className={`tw-animate-spin ${className}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
className="tw-opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
|
@ -19,7 +19,7 @@ export const Spinner = ({ className = 'h-6 w-6' }) => (
|
|||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
className="tw-opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
|
|
|
@ -227,6 +227,36 @@ export function getSearchParam(name = 'id') {
|
|||
return new URLSearchParams(window.location.search).get(name) // eslint-disable-line
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine whether all required measurements for a design are present
|
||||
*
|
||||
* @param {object} Design - The FreeSewing design (or a plain object holding measurements)
|
||||
* @param {object} measurements - An object holding the user's measurements
|
||||
* @return {array} result - An array where the first element is true when we
|
||||
* have all measurements, and false if not. The second element is a list of
|
||||
* missing measurements.
|
||||
*/
|
||||
export function hasRequiredMeasurements(Design, measurements = {}) {
|
||||
/*
|
||||
* If design is just a plain object holding measurements, we restructure it as a Design
|
||||
* As it happens, this method is smart enough to test for this, so we call it always
|
||||
*/
|
||||
Design = structureMeasurementsAsDesign(Design)
|
||||
|
||||
/*
|
||||
* Walk required measurements, and keep track of what's missing
|
||||
*/
|
||||
const missing = []
|
||||
for (const m of Design.patternConfig?.measurements || []) {
|
||||
if (typeof measurements[m] === 'undefined') missing.push(m)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true or false, plus a list of missing measurements
|
||||
*/
|
||||
return [missing.length === 0, missing]
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a measurement to millimeter
|
||||
*
|
||||
|
@ -413,6 +443,15 @@ export function shortDate(timestamp = false, withTime = true) {
|
|||
*/
|
||||
export const shortUuid = (uuid) => uuid.slice(0, 5)
|
||||
|
||||
/*
|
||||
* This takes a POJO of measurements, and turns it into a structure that matches a design object
|
||||
*
|
||||
* @param {object} measurements - The POJO of measurments
|
||||
* @return {object} design - The measurements structured as a design object
|
||||
*/
|
||||
export function structureMeasurementsAsDesign(measurements) {
|
||||
return measurements.patternConfig ? measurements : { patternConfig: { measurements } }
|
||||
}
|
||||
/*
|
||||
* We used to use react-timeago but that's too much overhead
|
||||
* This is a drop-in replacement that does not rerender
|
||||
|
|
54
sites/org/static/img/lineup-backdrop.svg
Normal file
54
sites/org/static/img/lineup-backdrop.svg
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 -20 300 220" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<path d="M 0 200 L 300 200" id="floor" stroke="#ccc" stroke-width="1"/>
|
||||
<g id="metric">
|
||||
<g fill="#000" fill-opacity="0.1">
|
||||
<rect x="0" y="0" width="300" height="20"/>
|
||||
<rect x="0" y="40" width="300" height="20"/>
|
||||
<rect x="0" y="80" width="300" height="20"/>
|
||||
<rect x="0" y="120" width="300" height="20"/>
|
||||
<rect x="0" y="160" width="300" height="20"/>
|
||||
</g>
|
||||
<g fill="#fff" fill-opacity="0.1">
|
||||
<rect x="0" y="20" width="300" height="20"/>
|
||||
<rect x="0" y="60" width="300" height="20"/>
|
||||
<rect x="0" y="100" width="300" height="20"/>
|
||||
<rect x="0" y="140" width="300" height="20"/>
|
||||
<rect x="0" y="180" width="300" height="20"/>
|
||||
</g>
|
||||
<text fill="#000" font-size="4">
|
||||
<tspan x="1" y="140">0.60m</tspan>
|
||||
<tspan x="1" y="120">0.80m</tspan>
|
||||
<tspan x="1" y="100">1.00m</tspan>
|
||||
<tspan x="1" y="80">1.20m</tspan>
|
||||
<tspan x="1" y="60">1.40m</tspan>
|
||||
<tspan x="1" y="40">1.60m</tspan>
|
||||
<tspan x="1" y="20">1.80m</tspan>
|
||||
<tspan x="1" y="0">2.00m</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="imperial">
|
||||
<path d="M 0 -13.36 h 300" stroke-width="0.5" stroke="#000" stroke-dasharray="10 10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 -13.36 h 300" stroke-width="0.5" stroke="#fff" stroke-dasharray="10 10" stroke-dashoffset="10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 17.12 h 300" stroke-width="0.5" stroke="#000" stroke-dasharray="10 10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 17.12 h 300" stroke-width="0.5" stroke="#fff" stroke-dasharray="10 10" stroke-dashoffset="10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 47.6 h 300" stroke-width="0.5" stroke="#000" stroke-dasharray="10 10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 47.6 h 300" stroke-width="0.5" stroke="#fff" stroke-dasharray="10 10" stroke-dashoffset="10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 78.08 h 300" stroke-width="0.5" stroke="#000" stroke-dasharray="10 10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 78.08 h 300" stroke-width="0.5" stroke="#fff" stroke-dasharray="10 10" stroke-dashoffset="10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 108.56 h 300" stroke-width="0.5" stroke="#000" stroke-dasharray="10 10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 108.56 h 300" stroke-width="0.5" stroke="#fff" stroke-dasharray="10 10" stroke-dashoffset="10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 139.31 h 300" stroke-width="0.5" stroke="#000" stroke-dasharray="10 10" stroke-opacity="0.4"/>
|
||||
<path d="M 0 139.31 h 300" stroke-width="0.5" stroke="#fff" stroke-dasharray="10 10" stroke-dashoffset="10" stroke-opacity="0.4"/>
|
||||
<text fill="#000" font-size="4" text-anchor="end">
|
||||
<tspan x="299" y="139.31">2f</tspan>
|
||||
<tspan x="299" y="108.56">3f</tspan>
|
||||
<tspan x="299" y="78.08">4f</tspan>
|
||||
<tspan x="299" y="47.6">5f</tspan>
|
||||
<tspan x="299" y="17.12">6f</tspan>
|
||||
<tspan x="299" y="-13.36">7f</tspan>
|
||||
</text>
|
||||
</g>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 3 KiB |
Loading…
Add table
Add a link
Reference in a new issue