wip: Undo view
This commit is contained in:
parent
3ec1cbd01b
commit
987ba5419c
12 changed files with 158 additions and 82 deletions
|
@ -93,6 +93,7 @@ packageJson:
|
||||||
"./components/LineDrawing": "./components/LineDrawing/index.mjs"
|
"./components/LineDrawing": "./components/LineDrawing/index.mjs"
|
||||||
"./components/Link": "./components/Link/index.mjs"
|
"./components/Link": "./components/Link/index.mjs"
|
||||||
"./components/Logo": "./components/Logo/index.mjs"
|
"./components/Logo": "./components/Logo/index.mjs"
|
||||||
|
"./components/Mini": "./components/Mini/index.mjs"
|
||||||
"./components/Modal": "./components/Modal/index.mjs"
|
"./components/Modal": "./components/Modal/index.mjs"
|
||||||
"./components/New": "./components/New/index.mjs"
|
"./components/New": "./components/New/index.mjs"
|
||||||
"./components/Number": "./components/Number/index.mjs"
|
"./components/Number": "./components/Number/index.mjs"
|
||||||
|
|
|
@ -82,7 +82,7 @@ export const HeaderMenuDraftView = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuDropdown = (props) => {
|
export const HeaderMenuDropdown = (props) => {
|
||||||
const { tooltip, toggle, open, setOpen, id } = props
|
const { tooltip, toggle, open, setOpen, id, end = false } = props
|
||||||
/*
|
/*
|
||||||
* We need to use both !fixed and md:!absolute here to override DaisyUI's
|
* We need to use both !fixed and md:!absolute here to override DaisyUI's
|
||||||
* classes on dropdown-content to force the dropdown to use the available
|
* classes on dropdown-content to force the dropdown to use the available
|
||||||
|
@ -95,14 +95,16 @@ export const HeaderMenuDropdown = (props) => {
|
||||||
disabled
|
disabled
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
className="tw-daisy-btn tw-daisy-btn-ghost hover:tw-bg-secondary hover:tw-bg-opacity-20 hover:tw-border-solid hover:tw-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"
|
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}
|
{toggle}
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip tip={tooltip}>
|
<Tooltip tip={tooltip}>
|
||||||
<div className={`tw-daisy-dropdown ${open === id ? 'tw-daisy-dropdown-open tw-z-20' : ''}`}>
|
<div
|
||||||
|
className={`tw-daisy-dropdown ${open === id ? 'tw-daisy-dropdown-open tw-z-20' : ''} ${end ? ' tw-daisy-dropdown-end' : ''}`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -113,7 +115,7 @@ export const HeaderMenuDropdown = (props) => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="tw-daisy-dropdown-content tw-bg-base-100 tw-bg-opacity-90 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-top-12 tw-w-screen md:tw-max-w-lg tw-overflow-y-scroll tw-mb-12 tw-h-fit"
|
className="tw-daisy-dropdown-content tw-bg-base-100 tw-bg-opacity-90 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-top-12 tw-w-screen md:tw-max-w-md tw-overflow-y-scroll tw-mb-12 tw-h-fit"
|
||||||
style={{ maxHeight: 'calc(100vh - 12rem)' }}
|
style={{ maxHeight: 'calc(100vh - 12rem)' }}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -329,6 +331,7 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
<UndoIcon className={`${size} ${undos ? 'tw-text-secondary' : ''}`} text="A" />
|
<UndoIcon className={`${size} ${undos ? 'tw-text-secondary' : ''}`} text="A" />
|
||||||
</Button>
|
</Button>
|
||||||
<HeaderMenuDropdown
|
<HeaderMenuDropdown
|
||||||
|
end
|
||||||
{...props}
|
{...props}
|
||||||
tooltip={viewLabels.undos.t}
|
tooltip={viewLabels.undos.t}
|
||||||
id="undos"
|
id="undos"
|
||||||
|
@ -341,19 +344,14 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{undos ? (
|
{undos ? (
|
||||||
<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">
|
<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 tw-contents">
|
||||||
{undos.slice(0, 9).map((step, index) => (
|
{undos.slice(0, 9).map((step, index) => (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<UndoStep {...{ step, update, state, Design, index }} compact />
|
<UndoStep {...{ step, update, state, Design, index }} compact />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<li key="view">
|
<li key="view">
|
||||||
<ButtonFrame
|
<ButtonFrame dense onClick={() => update.view('undos')}>
|
||||||
dense
|
|
||||||
onClick={() => {
|
|
||||||
return null /*update.state(index, state._) */
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="tw-flex tw-flex-row tw-items-center tw-align-center tw-justify-between tw-gap-2 tw-w-full">
|
<div className="tw-flex tw-flex-row tw-items-center tw-align-center tw-justify-between tw-gap-2 tw-w-full">
|
||||||
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-grow">
|
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-grow">
|
||||||
<UndoIcon className="tw-w-5 tw-h-5 tw-text-secondary" />
|
<UndoIcon className="tw-w-5 tw-h-5 tw-text-secondary" />
|
||||||
|
@ -502,7 +500,7 @@ export const HeaderMenuViewMenu = (props) => {
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={i}
|
tabIndex={i}
|
||||||
className="tw-dropdown-content tw-bg-base-100 tw-bg-opacity-95 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-w-screen md:tw-max-w-lg md:tw-pt-0 tw-mt-14 md:tw-mt-0 tw-contents"
|
className="tw-daisy-dropdown-content tw-bg-base-100 tw-bg-opacity-95 tw-z-20 tw-shadow tw-left-0 !tw-fixed md:!tw-absolute tw-w-screen md:tw-max-w-lg md:tw-pt-0 tw-mt-14 md:tw-mt-0 tw-contents"
|
||||||
>
|
>
|
||||||
{output}
|
{output}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { TipIcon, OkIcon } from '@freesewing/react/components/Icon'
|
||||||
import { Null } from './Null.mjs'
|
import { Null } from './Null.mjs'
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
timeout: 2,
|
timeout: 2.5,
|
||||||
defaults: {
|
defaults: {
|
||||||
color: 'secondary',
|
color: 'secondary',
|
||||||
icon: 'Spinner',
|
icon: 'Spinner',
|
||||||
|
|
|
@ -111,7 +111,13 @@ export const UserSetPicker = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BookmarkedSetPicker = ({ Design, clickHandler, missingClickHandler, size = 'lg' }) => {
|
export const BookmarkedSetPicker = ({
|
||||||
|
Design,
|
||||||
|
config,
|
||||||
|
clickHandler,
|
||||||
|
missingClickHandler,
|
||||||
|
size = 'lg',
|
||||||
|
}) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,10 @@ import { designOptionType } from '@freesewing/utils'
|
||||||
import React, { useState, useMemo } from 'react'
|
import React, { useState, useMemo } from 'react'
|
||||||
// Components
|
// Components
|
||||||
import { SubAccordion } from '../Accordion.mjs'
|
import { SubAccordion } from '../Accordion.mjs'
|
||||||
import {
|
import { EditIcon, GroupIcon, OptionsIcon, ResetIcon } from '@freesewing/react/components/Icon'
|
||||||
EditIcon,
|
|
||||||
GroupIcon,
|
|
||||||
OptionsIcon,
|
|
||||||
ResetIcon,
|
|
||||||
TipIcon,
|
|
||||||
} from '@freesewing/react/components/Icon'
|
|
||||||
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
||||||
import { FormControl } from '@freesewing/react/components/Input'
|
import { FormControl } from '@freesewing/react/components/Input'
|
||||||
|
import { MiniTip } from '@freesewing/react/components/Mini'
|
||||||
|
|
||||||
/** @type {String} class to apply to buttons on open menu items */
|
/** @type {String} class to apply to buttons on open menu items */
|
||||||
const iconButtonClass = 'tw-daisy-btn tw-daisy-btn-xs tw-daisy-btn-ghost tw-px-0 tw-text-accent'
|
const iconButtonClass = 'tw-daisy-btn tw-daisy-btn-xs tw-daisy-btn-ghost tw-px-0 tw-text-accent'
|
||||||
|
@ -129,16 +124,7 @@ export const MenuItem = ({
|
||||||
>
|
>
|
||||||
<Input {...drillProps} />
|
<Input {...drillProps} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{config.about ? (
|
{config.about ? <MiniTip>{config.about}</MiniTip> : null}
|
||||||
<div className="tw-flex tw-flex-row tw-border tw-border-success tw-rounded">
|
|
||||||
<div className="tw-bg-success tw-text-success-content tw-p-1 tw-rounded-l tw-flex tw-flex-row tw-items-center">
|
|
||||||
<TipIcon className="tw-w-6 tw-h-6 tw-text-success-content" />
|
|
||||||
</div>
|
|
||||||
<div className="tw-p-1 tw-text-sm tw-font-medium tw-bg-success/10 tw-grow tw-rounded-r">
|
|
||||||
{config.about}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,23 @@ import { t, designMeasurements } from '../../lib/index.mjs'
|
||||||
import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils'
|
import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils'
|
||||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { Fragment, useEffect } from 'react'
|
import React, { Fragment, useState, useEffect } from 'react'
|
||||||
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
// Components
|
// Components
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
|
import { NumberInput } from '@freesewing/react/components/Input'
|
||||||
import {
|
import {
|
||||||
BookmarkIcon,
|
BookmarkIcon,
|
||||||
CuratedMeasurementsSetIcon,
|
CuratedMeasurementsSetIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
MeasurementsSetIcon,
|
MeasurementsSetIcon,
|
||||||
|
FingerprintIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
import { Accordion } from '../Accordion.mjs'
|
import { Accordion } from '../Accordion.mjs'
|
||||||
import { MeasurementsEditor } from '../MeasurementsEditor.mjs'
|
import { MeasurementsEditor } from '../MeasurementsEditor.mjs'
|
||||||
import { SetPicker, BookmarkedSetPicker, CuratedSetPicker, UserSetPicker } from '../Set.mjs'
|
import { SetPicker, BookmarkedSetPicker, CuratedSetPicker, UserSetPicker } from '../Set.mjs'
|
||||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
|
import { H1, H5 } from '@freesewing/react/components/Heading'
|
||||||
|
|
||||||
const iconClasses = {
|
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',
|
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',
|
||||||
|
@ -35,7 +39,14 @@ const iconClasses = {
|
||||||
* @param {Object} props.update - Helper object for updating the editor state
|
* @param {Object} props.update - Helper object for updating the editor state
|
||||||
* @return {Function} MeasurementsView - React component
|
* @return {Function} MeasurementsView - React component
|
||||||
*/
|
*/
|
||||||
export const MeasurementsView = ({ config, Design, missingMeasurements, state, update }) => {
|
export const MeasurementsView = ({
|
||||||
|
config,
|
||||||
|
Design,
|
||||||
|
missingMeasurements,
|
||||||
|
state,
|
||||||
|
update,
|
||||||
|
design,
|
||||||
|
}) => {
|
||||||
/*
|
/*
|
||||||
* If there is no view set, completing measurements will switch to the view picker
|
* If there is no view set, completing measurements will switch to the view picker
|
||||||
* Which is a bit confusing. So in this case, set the view to measurements.
|
* Which is a bit confusing. So in this case, set the view to measurements.
|
||||||
|
@ -50,7 +61,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
},
|
},
|
||||||
'missingMeasurements'
|
'missingMeasurements'
|
||||||
)
|
)
|
||||||
else update.notifySuccess(t('pe:measurementsAreOk'))
|
else update.notifySuccess(`We have all measurements to draft ${capitalize(design)}`)
|
||||||
}, [state.view, update])
|
}, [state.view, update])
|
||||||
|
|
||||||
const loadMeasurements = (set) => {
|
const loadMeasurements = (set) => {
|
||||||
|
@ -72,7 +83,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
[
|
[
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={`${horFlexClasses} tw-w-full`}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="ownsets">Choose one of your own measurements sets</h4>
|
<H5 id="ownsets">Choose one of your own measurements sets</H5>
|
||||||
<MeasurementsSetIcon {...iconClasses} />
|
<MeasurementsSetIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
<p className="tw-text-left">
|
<p className="tw-text-left">
|
||||||
|
@ -92,7 +103,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
[
|
[
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={`${horFlexClasses} tw-w-full`}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="bookmarkedsets">Choose one of the measurements sets you have bookmarked</h4>
|
<H5 id="bookmarkedsets">Choose one of the measurements sets you have bookmarked</H5>
|
||||||
<BookmarkIcon {...iconClasses} />
|
<BookmarkIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
<p className="tw-text-left">
|
<p className="tw-text-left">
|
||||||
|
@ -111,7 +122,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
[
|
[
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={`${horFlexClasses} tw-w-full`}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="curatedsets">Choose one of FreeSewing's curated measurements sets</h4>
|
<H5 id="curatedsets">Choose one of FreeSewing's curated measurements sets</H5>
|
||||||
<CuratedMeasurementsSetIcon {...iconClasses} />
|
<CuratedMeasurementsSetIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
<p className="tw-text-left">
|
<p className="tw-text-left">
|
||||||
|
@ -121,13 +132,27 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
</Fragment>,
|
</Fragment>,
|
||||||
<CuratedSetPicker key={2} clickHandler={loadMeasurements} {...{ config, Design }} />,
|
<CuratedSetPicker key={2} clickHandler={loadMeasurements} {...{ config, Design }} />,
|
||||||
'csets',
|
'csets',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
<Fragment key={1}>
|
||||||
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
|
<H5 id="loadid">Load a measurements set by ID</H5>
|
||||||
|
<FingerprintIcon {...iconClasses} />
|
||||||
|
</div>
|
||||||
|
<p className="tw-text-left">
|
||||||
|
If you know the ID of a measurements set — either one of your own or a public set — we
|
||||||
|
can load it for you.
|
||||||
|
</p>
|
||||||
|
</Fragment>,
|
||||||
|
<LoadMeasurementsSetById key={2} {...{ loadMeasurements, update }} />,
|
||||||
|
'setid',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
// Manual editing is always an option
|
// Manual editing is always an option
|
||||||
items.push([
|
items.push([
|
||||||
<Fragment key={1}>
|
<Fragment key={1}>
|
||||||
<div className={`${horFlexClasses} tw-w-full`}>
|
<div className={`${horFlexClasses} tw-w-full`}>
|
||||||
<h4 id="editmeasurements">Edit Measurements</h4>
|
<H5 id="editmeasurements">Edit measurements by hand</H5>
|
||||||
<EditIcon {...iconClasses} />
|
<EditIcon {...iconClasses} />
|
||||||
</div>
|
</div>
|
||||||
<p className="tw-text-left">You can manually set or override measurements below.</p>
|
<p className="tw-text-left">You can manually set or override measurements below.</p>
|
||||||
|
@ -140,7 +165,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
<>
|
<>
|
||||||
<HeaderMenu state={state} {...{ config, update }} />
|
<HeaderMenu state={state} {...{ config, update }} />
|
||||||
<div className="tw-max-w-7xl tw-mt-8 tw-mx-auto tw-px-4 tw-mb-4">
|
<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>
|
<H1>Measurements</H1>
|
||||||
{missingMeasurements && missingMeasurements.length > 0 ? (
|
{missingMeasurements && missingMeasurements.length > 0 ? (
|
||||||
<Popout note dense noP>
|
<Popout note dense noP>
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -158,16 +183,16 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
</Popout>
|
</Popout>
|
||||||
) : (
|
) : (
|
||||||
<Popout tip dense noP>
|
<Popout tip dense noP>
|
||||||
<h5>We have all required measurements to draft this pattern</h5>
|
<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">
|
<div className="tw-flex tw-flex-row tw-flex-wrap tw-gap-2 tw-mt-2">
|
||||||
<button
|
<button
|
||||||
className="tw-daisy-btn tw-daisy-btn-primary lg:tw-daisy-btn-lg"
|
className="tw-daisy-btn tw-daisy-btn-primary"
|
||||||
onClick={() => update.view('draft')}
|
onClick={() => update.view('draft')}
|
||||||
>
|
>
|
||||||
{viewLabels.draft.t}
|
Draft Pattern
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="tw-daisy-btn tw-daisy-btn-primary tw-daisy-btn-outline lg:tw-daisy-btn-lg"
|
className="tw-daisy-btn tw-daisy-btn-primary tw-daisy-btn-outline"
|
||||||
onClick={() => update.view('picker')}
|
onClick={() => update.view('picker')}
|
||||||
>
|
>
|
||||||
Choose a different view
|
Choose a different view
|
||||||
|
@ -180,3 +205,43 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LoadMeasurementsSetById = ({ loadMeasurements, update }) => {
|
||||||
|
const backend = useBackend()
|
||||||
|
const [id, setId] = useState('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="tw-flex tw-flex-row tw-gap-2 tw-items-end">
|
||||||
|
<NumberInput
|
||||||
|
label="Measurements Set ID"
|
||||||
|
update={setId}
|
||||||
|
current={id}
|
||||||
|
valid={(val) => Number(val) == val}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="tw-daisy-btn tw-daisy-btn-primary"
|
||||||
|
onClick={() => loadMeasurementsSet(id, backend, loadMeasurements, update)}
|
||||||
|
>
|
||||||
|
Load set
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMeasurementsSet(id, backend, loadMeasurements, update) {
|
||||||
|
update.startLoading('getset', {
|
||||||
|
msg: 'Loading measurements set from the FreeSewing backend',
|
||||||
|
icon: 'spinner',
|
||||||
|
})
|
||||||
|
const result = await backend.getSet(id)
|
||||||
|
if (result[0] === 200 && result[1].set) {
|
||||||
|
loadMeasurements(result[1].set)
|
||||||
|
update.clearLoading()
|
||||||
|
update.notifySuccess('Measurements set loaded', 'getsetok')
|
||||||
|
} else {
|
||||||
|
update.clearLoading()
|
||||||
|
update.notifySuccess('Measurements set loaded', 'getsetko')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { orderBy } from '@freesewing/utils'
|
import { orderBy } from '@freesewing/utils'
|
||||||
import { ButtonFrame } from '@freesewing/react/components/Input'
|
import { ButtonFrame } from '@freesewing/react/components/Input'
|
||||||
import { UndoIcon } from '@freesewing/react/components/Icon'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
|
import { UndoIcon, TipIcon, LeftIcon } from '@freesewing/react/components/Icon'
|
||||||
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
|
import { H1 } from '@freesewing/react/components/Heading'
|
||||||
|
import { MiniTip } from '@freesewing/react/components/Mini'
|
||||||
|
|
||||||
import { getUndoStepData } from '../../lib/index.mjs'
|
import { getUndoStepData } from '../../lib/index.mjs'
|
||||||
|
|
||||||
|
@ -12,18 +16,15 @@ import { getUndoStepData } from '../../lib/index.mjs'
|
||||||
* @param {object} designs - Object holding all designs
|
* @param {object} designs - Object holding all designs
|
||||||
* @param {object} update - ViewWrapper state update object
|
* @param {object} update - ViewWrapper state update object
|
||||||
*/
|
*/
|
||||||
export const UndosView = ({ Design, update, state }) => {
|
export const UndosView = ({ Design, update, state, config }) => {
|
||||||
const steps = orderBy(state._.undos, 'time', 'desc')
|
const steps = orderBy(state._.undos, 'time', 'desc')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderMenu state={state} {...{ update, Design }} />
|
<HeaderMenu {...{ update, Design, config, state }} />
|
||||||
<div className="tw-text-left tw-mt-8 tw-mb-24 tw-px-4 tw-max-w-xl tw-mx-auto">
|
<div className="tw-text-left tw-mt-8 tw-mb-24 tw-px-4 tw-max-w-xl tw-mx-auto">
|
||||||
<h2>Undo History</h2>
|
<H1>Undo History</H1>
|
||||||
<p>Time-travel through your recent pattern changes</p>
|
<p className="tw-mb-4">Time-travel through your recent pattern changes.</p>
|
||||||
<small>
|
|
||||||
<b>Tip:</b> Click on any change to undo all changes up to, and including, that change.
|
|
||||||
</small>
|
|
||||||
{steps.length < 1 ? (
|
{steps.length < 1 ? (
|
||||||
<Popout note>
|
<Popout note>
|
||||||
<h4>Your undo history is currently empty</h4>
|
<h4>Your undo history is currently empty</h4>
|
||||||
|
@ -38,11 +39,16 @@ export const UndosView = ({ Design, update, state }) => {
|
||||||
<p>As soon as you do, the change will show up here, and you can undo it.</p>
|
<p>As soon as you do, the change will show up here, and you can undo it.</p>
|
||||||
</Popout>
|
</Popout>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
<MiniTip>
|
||||||
|
Click on any change to undo all changes up to, and including, that change.
|
||||||
|
</MiniTip>
|
||||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-mt-4">
|
<div className="tw-flex tw-flex-col tw-gap-2 tw-mt-4">
|
||||||
{steps.map((step, index) => (
|
{steps.map((step, index) => (
|
||||||
<UndoStep key={step.time} {...{ step, update, state, Design, index }} />
|
<UndoStep key={step.time} {...{ step, update, state, Design, index }} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -93,7 +99,7 @@ export const UndoStep = ({ update, state, step, Design, compact = false, index =
|
||||||
<ButtonFrame dense onClick={() => update.restore(index, state._)}>
|
<ButtonFrame dense onClick={() => update.restore(index, state._)}>
|
||||||
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-w-full">
|
<div className="tw-flex tw-flex-row tw-items-center tw-align-start tw-gap-2 tw-w-full">
|
||||||
<UndoIcon text={index} className="tw-w-5 tw-h-5 tw-text-secondary" />
|
<UndoIcon text={index} className="tw-w-5 tw-h-5 tw-text-secondary" />
|
||||||
{data.msg ? data.msg : `pe:${data.optCode}`}
|
{data.msg ? data.msg : data.title}
|
||||||
</div>
|
</div>
|
||||||
</ButtonFrame>
|
</ButtonFrame>
|
||||||
)
|
)
|
||||||
|
@ -107,10 +113,10 @@ export const UndoStep = ({ update, state, step, Design, compact = false, index =
|
||||||
<div className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-gap-2 tw-w-full tw-m-0 tw-p-0 tw--mt-2 tw-text-lg">
|
<div className="tw-flex tw-flex-row tw-items-center tw-justify-between tw-gap-2 tw-w-full tw-m-0 tw-p-0 tw--mt-2 tw-text-lg">
|
||||||
<span className="tw-flex tw-flex-row tw-gap-2 tw-items-center">
|
<span className="tw-flex tw-flex-row tw-gap-2 tw-items-center">
|
||||||
{data.fieldIcon || null}
|
{data.fieldIcon || null}
|
||||||
{`pe:${data.optCode}`}
|
{data.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="tw-opacity-70 tw-flex tw-flex-row tw-gap-1 tw-items-center tw-text-base">
|
<span className="tw-opacity-70 tw-flex tw-flex-row tw-gap-1 tw-items-center tw-text-base">
|
||||||
{data.icon || null} {`pe:${data.titleCode}`}
|
{data.icon || null} {data.menu}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-flex tw-flex-row tw-gap-1 tw-items-center tw-align-start tw-w-full">
|
<div className="tw-flex tw-flex-row tw-gap-1 tw-items-center tw-align-start tw-w-full">
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { MeasurementsView } from './MeasurementsView.mjs'
|
||||||
import { DraftView } from './DraftView.mjs'
|
import { DraftView } from './DraftView.mjs'
|
||||||
import { SaveView } from './SaveView.mjs'
|
import { SaveView } from './SaveView.mjs'
|
||||||
import { ExportView } from './ExportView.mjs'
|
import { ExportView } from './ExportView.mjs'
|
||||||
|
import { UndosView } from './UndosView.mjs'
|
||||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||||
import {
|
import {
|
||||||
OptionsIcon,
|
OptionsIcon,
|
||||||
|
@ -55,6 +56,7 @@ export const View = (props) => {
|
||||||
if (view === 'draft') return <DraftView {...props} />
|
if (view === 'draft') return <DraftView {...props} />
|
||||||
if (view === 'save') return <SaveView {...props} />
|
if (view === 'save') return <SaveView {...props} />
|
||||||
if (view === 'export') return <ExportView {...props} />
|
if (view === 'export') return <ExportView {...props} />
|
||||||
|
if (view === 'undos') return <UndosView {...props} />
|
||||||
/*
|
/*
|
||||||
viewComponents: {
|
viewComponents: {
|
||||||
draft: 'DraftView',
|
draft: 'DraftView',
|
||||||
|
|
|
@ -138,7 +138,7 @@ export const defaultConfig = {
|
||||||
base: 'user',
|
base: 'user',
|
||||||
},
|
},
|
||||||
// Ms before to fade out a notification
|
// Ms before to fade out a notification
|
||||||
notifyTimeout: 6660,
|
notifyTimeout: 2500,
|
||||||
degreeMeasurements: ['shoulderSlope'],
|
degreeMeasurements: ['shoulderSlope'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ export function getUiPreferenceUndoStepData({ step }) {
|
||||||
const data = {
|
const data = {
|
||||||
icon: <UiIcon />,
|
icon: <UiIcon />,
|
||||||
field,
|
field,
|
||||||
optCode: `${field}.t`,
|
title: structure.title,
|
||||||
titleCode: 'uiPreferences.t',
|
menu: 'UI Preferences',
|
||||||
structure: menuUiPreferencesStructure()[field],
|
structure: menuUiPreferencesStructure()[field],
|
||||||
}
|
}
|
||||||
const FieldIcon = data.structure.icon
|
const FieldIcon = data.structure.icon
|
||||||
|
@ -85,7 +85,7 @@ export function getUiPreferenceUndoStepData({ step }) {
|
||||||
data[key + 'Val'] = t(
|
data[key + 'Val'] = t(
|
||||||
structure.choiceTitles[
|
structure.choiceTitles[
|
||||||
structure.choiceTitles[String(step[key])] ? String(step[key]) : String(structure.dflt)
|
structure.choiceTitles[String(step[key])] ? String(step[key]) : String(structure.dflt)
|
||||||
] + '.t'
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -102,8 +102,8 @@ export function getCoreSettingUndoStepData({ step, state, Design }) {
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
field,
|
field,
|
||||||
titleCode: 'coreSettings.t',
|
menu: 'Core Settings',
|
||||||
optCode: `${field}.t`,
|
title: structure?.[field] ? structure[field].title : '',
|
||||||
icon: <SettingsIcon />,
|
icon: <SettingsIcon />,
|
||||||
structure: structure[field],
|
structure: structure[field],
|
||||||
}
|
}
|
||||||
|
@ -125,9 +125,7 @@ export function getCoreSettingUndoStepData({ step, state, Design }) {
|
||||||
case 'margin':
|
case 'margin':
|
||||||
case 'sa':
|
case 'sa':
|
||||||
case 'samm':
|
case 'samm':
|
||||||
if (data.field !== 'margin') {
|
if (data.field !== 'margin') data.title = 'Seam Allowance'
|
||||||
data.optCode = `samm.t`
|
|
||||||
}
|
|
||||||
data.oldVal = <Html html={formatMm(cord(step.old, data.structure.dflt))} />
|
data.oldVal = <Html html={formatMm(cord(step.old, data.structure.dflt))} />
|
||||||
data.newVal = <Html html={formatMm(cord(step.new, data.structure.dflt))} />
|
data.newVal = <Html html={formatMm(cord(step.new, data.structure.dflt))} />
|
||||||
return data
|
return data
|
||||||
|
@ -136,23 +134,23 @@ export function getCoreSettingUndoStepData({ step, state, Design }) {
|
||||||
data.newVal = cord(step.new, data.structure.dflt)
|
data.newVal = cord(step.new, data.structure.dflt)
|
||||||
return data
|
return data
|
||||||
case 'units':
|
case 'units':
|
||||||
data.oldVal = t(step.new === 'imperial' ? 'pe:metricUnits' : 'pe:imperialUnits')
|
data.oldVal = t(step.new === 'imperial' ? 'Metric Units' : 'Imperial Units')
|
||||||
data.newVal = t(step.new === 'imperial' ? 'pe:imperialUnits' : 'pe:metricUnits')
|
data.newVal = t(step.new === 'imperial' ? 'Imperial Units' : 'Metric Units')
|
||||||
return data
|
return data
|
||||||
case 'only':
|
case 'only':
|
||||||
data.oldVal = cord(step.old, data.structure.dflt) || t('pe:includeAllParts')
|
data.oldVal = cord(step.old, data.structure.dflt) || 'Include all parts'
|
||||||
data.newVal = cord(step.new, data.structure.dflt) || t('pe:includeAllParts')
|
data.newVal = cord(step.new, data.structure.dflt) || 'Include all parts'
|
||||||
return data
|
return data
|
||||||
default:
|
default:
|
||||||
data.oldVal = t(
|
data.oldVal = t(
|
||||||
(data.structure.choiceTitles[String(step.old)]
|
data.structure.choiceTitles[String(step.old)]
|
||||||
? data.structure.choiceTitles[String(step.old)]
|
? data.structure.choiceTitles[String(step.old)]
|
||||||
: data.structure.choiceTitles[String(data.structure.dflt)]) + '.t'
|
: data.structure.choiceTitles[String(data.structure.dflt)]
|
||||||
)
|
)
|
||||||
data.newVal = t(
|
data.newVal = t(
|
||||||
(data.structure.choiceTitles[String(step.new)]
|
data.structure.choiceTitles[String(step.new)]
|
||||||
? data.structure.choiceTitles[String(step.new)]
|
? data.structure.choiceTitles[String(step.new)]
|
||||||
: data.structure.choiceTitles[String(data.structure.dflt)]) + '.t'
|
: data.structure.choiceTitles[String(data.structure.dflt)]
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
@ -163,8 +161,8 @@ export function getDesignOptionUndoStepData({ step, state, Design }) {
|
||||||
const data = {
|
const data = {
|
||||||
icon: <OptionsIcon />,
|
icon: <OptionsIcon />,
|
||||||
field: step.path[2],
|
field: step.path[2],
|
||||||
optCode: `${state.design}:${step.path[2]}.t`,
|
title: `${state.design}:${step.path[2]}`,
|
||||||
titleCode: `designOptions.t`,
|
menu: `Design Options`,
|
||||||
oldVal: formatDesignOptionValue(option, step.old, state.units === 'imperial'),
|
oldVal: formatDesignOptionValue(option, step.old, state.units === 'imperial'),
|
||||||
newVal: formatDesignOptionValue(option, step.new, state.units === 'imperial'),
|
newVal: formatDesignOptionValue(option, step.new, state.units === 'imperial'),
|
||||||
}
|
}
|
||||||
|
@ -212,8 +210,8 @@ export function getUndoStepData(props) {
|
||||||
const data = {
|
const data = {
|
||||||
icon: <MeasurementsIcon />,
|
icon: <MeasurementsIcon />,
|
||||||
field: 'measurements',
|
field: 'measurements',
|
||||||
optCode: `measurements`,
|
title: `measurements`,
|
||||||
titleCode: 'measurements',
|
menu: 'measurements',
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Single measurements change?
|
* Single measurements change?
|
||||||
|
@ -229,7 +227,7 @@ export function getUndoStepData(props) {
|
||||||
for (const m of Object.keys(props.step.new)) {
|
for (const m of Object.keys(props.step.new)) {
|
||||||
if (props.step.new[m] !== props.step.old?.[m]) count++
|
if (props.step.new[m] !== props.step.old?.[m]) count++
|
||||||
}
|
}
|
||||||
return { ...data, msg: t('pe:xMeasurementsChanged', { count }) }
|
return { ...data, msg: `${count} measurements updated` }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
13
packages/react/components/Mini/index.mjs
Normal file
13
packages/react/components/Mini/index.mjs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { TipIcon } from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
|
export const MiniTip = ({ children }) => (
|
||||||
|
<div className="tw-flex tw-flex-row tw-border tw-border-success tw-rounded">
|
||||||
|
<div className="tw-bg-success tw-text-success-content tw-p-1 tw-rounded-l tw-flex tw-flex-row tw-items-center">
|
||||||
|
<TipIcon className="tw-w-6 tw-h-6 tw-text-success-content" />
|
||||||
|
</div>
|
||||||
|
<div className="tw-p-1 tw-px-2 tw-text-sm tw-font-medium tw-bg-success/10 tw-grow tw-rounded-r">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
|
@ -46,6 +46,7 @@
|
||||||
"./components/LineDrawing": "./components/LineDrawing/index.mjs",
|
"./components/LineDrawing": "./components/LineDrawing/index.mjs",
|
||||||
"./components/Link": "./components/Link/index.mjs",
|
"./components/Link": "./components/Link/index.mjs",
|
||||||
"./components/Logo": "./components/Logo/index.mjs",
|
"./components/Logo": "./components/Logo/index.mjs",
|
||||||
|
"./components/Mini": "./components/Mini/index.mjs",
|
||||||
"./components/Modal": "./components/Modal/index.mjs",
|
"./components/Modal": "./components/Modal/index.mjs",
|
||||||
"./components/New": "./components/New/index.mjs",
|
"./components/New": "./components/New/index.mjs",
|
||||||
"./components/Number": "./components/Number/index.mjs",
|
"./components/Number": "./components/Number/index.mjs",
|
||||||
|
@ -86,12 +87,12 @@
|
||||||
"html-react-parser": "^5.0.7",
|
"html-react-parser": "^5.0.7",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"nuqs": "^1.17.6",
|
"nuqs": "^1.17.6",
|
||||||
"pdfkit": "^0.16.0",
|
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"tlds": "^1.255.0",
|
"tlds": "^1.255.0",
|
||||||
"use-local-storage-state": "19.1.0",
|
"use-local-storage-state": "19.1.0",
|
||||||
"use-session-storage-state": "^19.0.0"
|
"use-session-storage-state": "^19.0.0"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {},
|
||||||
"files": [
|
"files": [
|
||||||
"components/**",
|
"components/**",
|
||||||
"hooks/**",
|
"hooks/**",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue