diff --git a/config/exceptions.yaml b/config/exceptions.yaml index 74d3d57d23a..2983bdbdd1d 100644 --- a/config/exceptions.yaml +++ b/config/exceptions.yaml @@ -93,6 +93,7 @@ packageJson: "./components/LineDrawing": "./components/LineDrawing/index.mjs" "./components/Link": "./components/Link/index.mjs" "./components/Logo": "./components/Logo/index.mjs" + "./components/Mini": "./components/Mini/index.mjs" "./components/Modal": "./components/Modal/index.mjs" "./components/New": "./components/New/index.mjs" "./components/Number": "./components/Number/index.mjs" diff --git a/packages/react/components/Editor/components/HeaderMenu.mjs b/packages/react/components/Editor/components/HeaderMenu.mjs index 5d7e7185ff7..c513ed1c736 100644 --- a/packages/react/components/Editor/components/HeaderMenu.mjs +++ b/packages/react/components/Editor/components/HeaderMenu.mjs @@ -82,7 +82,7 @@ export const HeaderMenuDraftView = (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 * classes on dropdown-content to force the dropdown to use the available @@ -95,14 +95,16 @@ export const HeaderMenuDropdown = (props) => { disabled tabIndex={0} 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} ) : ( -
+
{
{props.children} @@ -329,6 +331,7 @@ export const HeaderMenuUndoIcons = (props) => { { } > {undos ? ( -
    +
      {undos.slice(0, 9).map((step, index) => (
    • ))}
    • - { - return null /*update.state(index, state._) */ - }} - > + update.view('undos')}>
      @@ -502,7 +500,7 @@ export const HeaderMenuViewMenu = (props) => { >
        {output}
      diff --git a/packages/react/components/Editor/components/LoadingStatus.mjs b/packages/react/components/Editor/components/LoadingStatus.mjs index ac9c00d8015..82c92ad8c91 100644 --- a/packages/react/components/Editor/components/LoadingStatus.mjs +++ b/packages/react/components/Editor/components/LoadingStatus.mjs @@ -4,7 +4,7 @@ import { TipIcon, OkIcon } from '@freesewing/react/components/Icon' import { Null } from './Null.mjs' const config = { - timeout: 2, + timeout: 2.5, defaults: { color: 'secondary', icon: 'Spinner', diff --git a/packages/react/components/Editor/components/Set.mjs b/packages/react/components/Editor/components/Set.mjs index 619b6a170e5..86cfea151bc 100644 --- a/packages/react/components/Editor/components/Set.mjs +++ b/packages/react/components/Editor/components/Set.mjs @@ -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 const backend = useBackend() diff --git a/packages/react/components/Editor/components/menus/Container.mjs b/packages/react/components/Editor/components/menus/Container.mjs index 19b163aa10a..d758cdc51ad 100644 --- a/packages/react/components/Editor/components/menus/Container.mjs +++ b/packages/react/components/Editor/components/menus/Container.mjs @@ -5,15 +5,10 @@ import { designOptionType } from '@freesewing/utils' import React, { useState, useMemo } from 'react' // Components import { SubAccordion } from '../Accordion.mjs' -import { - EditIcon, - GroupIcon, - OptionsIcon, - ResetIcon, - TipIcon, -} from '@freesewing/react/components/Icon' +import { EditIcon, GroupIcon, OptionsIcon, ResetIcon } from '@freesewing/react/components/Icon' import { CoreSettingsMenu } from './CoreSettingsMenu.mjs' 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 */ 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 = ({ > - {config.about ? ( -
      -
      - -
      -
      - {config.about} -
      -
      - ) : null} + {config.about ? {config.about} : null} ) } diff --git a/packages/react/components/Editor/components/views/MeasurementsView.mjs b/packages/react/components/Editor/components/views/MeasurementsView.mjs index d36aa6a8e6c..96a937b35f7 100644 --- a/packages/react/components/Editor/components/views/MeasurementsView.mjs +++ b/packages/react/components/Editor/components/views/MeasurementsView.mjs @@ -3,19 +3,23 @@ import { t, designMeasurements } from '../../lib/index.mjs' import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils' import { measurements as measurementsTranslations } from '@freesewing/i18n' // Hooks -import React, { Fragment, useEffect } from 'react' +import React, { Fragment, useState, useEffect } from 'react' +import { useBackend } from '@freesewing/react/hooks/useBackend' // Components import { Popout } from '@freesewing/react/components/Popout' +import { NumberInput } from '@freesewing/react/components/Input' import { BookmarkIcon, CuratedMeasurementsSetIcon, EditIcon, MeasurementsSetIcon, + FingerprintIcon, } from '@freesewing/react/components/Icon' import { Accordion } from '../Accordion.mjs' import { MeasurementsEditor } from '../MeasurementsEditor.mjs' import { SetPicker, BookmarkedSetPicker, CuratedSetPicker, UserSetPicker } from '../Set.mjs' import { HeaderMenu } from '../HeaderMenu.mjs' +import { H1, H5 } from '@freesewing/react/components/Heading' 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', @@ -35,7 +39,14 @@ const iconClasses = { * @param {Object} props.update - Helper object for updating the editor state * @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 * 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' ) - else update.notifySuccess(t('pe:measurementsAreOk')) + else update.notifySuccess(`We have all measurements to draft ${capitalize(design)}`) }, [state.view, update]) const loadMeasurements = (set) => { @@ -72,7 +83,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u [
      -

      Choose one of your own measurements sets

      +
      Choose one of your own measurements sets

      @@ -92,7 +103,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u [

      -

      Choose one of the measurements sets you have bookmarked

      +
      Choose one of the measurements sets you have bookmarked

      @@ -111,7 +122,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u [

      -

      Choose one of FreeSewing's curated measurements sets

      +
      Choose one of FreeSewing's curated measurements sets

      @@ -121,13 +132,27 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u , , 'csets', + ], + [ + +

      +
      Load a measurements set by ID
      + +
      +

      + If you know the ID of a measurements set — either one of your own or a public set — we + can load it for you. +

      +
      , + , + 'setid', ] ) // Manual editing is always an option items.push([
      -

      Edit Measurements

      +
      Edit measurements by hand

      You can manually set or override measurements below.

      @@ -140,7 +165,7 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u <>
      -

      Measurements

      +

      Measurements

      {missingMeasurements && missingMeasurements.length > 0 ? (

      @@ -158,16 +183,16 @@ export const MeasurementsView = ({ config, Design, missingMeasurements, state, u ) : ( -
      We have all required measurements to draft this pattern
      +
      We have all required measurements to draft this pattern
      +
      +

      + ) +} + +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') + } +} diff --git a/packages/react/components/Editor/components/views/UndosView.mjs b/packages/react/components/Editor/components/views/UndosView.mjs index 83acfe17e7d..f03d0b31c89 100644 --- a/packages/react/components/Editor/components/views/UndosView.mjs +++ b/packages/react/components/Editor/components/views/UndosView.mjs @@ -1,7 +1,11 @@ import React from 'react' import { orderBy } from '@freesewing/utils' 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' @@ -12,18 +16,15 @@ import { getUndoStepData } from '../../lib/index.mjs' * @param {object} designs - Object holding all designs * @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') return ( <> - +
      -

      Undo History

      -

      Time-travel through your recent pattern changes

      - - Tip: Click on any change to undo all changes up to, and including, that change. - +

      Undo History

      +

      Time-travel through your recent pattern changes.

      {steps.length < 1 ? (

      Your undo history is currently empty

      @@ -38,11 +39,16 @@ export const UndosView = ({ Design, update, state }) => {

      As soon as you do, the change will show up here, and you can undo it.

      ) : ( -
      - {steps.map((step, index) => ( - - ))} -
      + <> + + Click on any change to undo all changes up to, and including, that change. + +
      + {steps.map((step, index) => ( + + ))} +
      + )}
      @@ -93,7 +99,7 @@ export const UndoStep = ({ update, state, step, Design, compact = false, index = update.restore(index, state._)}>
      - {data.msg ? data.msg : `pe:${data.optCode}`} + {data.msg ? data.msg : data.title}
      ) @@ -107,10 +113,10 @@ export const UndoStep = ({ update, state, step, Design, compact = false, index =
      {data.fieldIcon || null} - {`pe:${data.optCode}`} + {data.title} - {data.icon || null} {`pe:${data.titleCode}`} + {data.icon || null} {data.menu}
      diff --git a/packages/react/components/Editor/components/views/index.mjs b/packages/react/components/Editor/components/views/index.mjs index dea155d0300..455856ace81 100644 --- a/packages/react/components/Editor/components/views/index.mjs +++ b/packages/react/components/Editor/components/views/index.mjs @@ -5,6 +5,7 @@ import { MeasurementsView } from './MeasurementsView.mjs' import { DraftView } from './DraftView.mjs' import { SaveView } from './SaveView.mjs' import { ExportView } from './ExportView.mjs' +import { UndosView } from './UndosView.mjs' import { ErrorIcon } from '@freesewing/react/components/Icon' import { OptionsIcon, @@ -55,6 +56,7 @@ export const View = (props) => { if (view === 'draft') return if (view === 'save') return if (view === 'export') return + if (view === 'undos') return /* viewComponents: { draft: 'DraftView', diff --git a/packages/react/components/Editor/config/index.mjs b/packages/react/components/Editor/config/index.mjs index 04cf9d970f7..72b68b643ec 100644 --- a/packages/react/components/Editor/config/index.mjs +++ b/packages/react/components/Editor/config/index.mjs @@ -138,7 +138,7 @@ export const defaultConfig = { base: 'user', }, // Ms before to fade out a notification - notifyTimeout: 6660, + notifyTimeout: 2500, degreeMeasurements: ['shoulderSlope'], } diff --git a/packages/react/components/Editor/lib/editor.mjs b/packages/react/components/Editor/lib/editor.mjs index d1df7358eed..35e7ffad1fc 100644 --- a/packages/react/components/Editor/lib/editor.mjs +++ b/packages/react/components/Editor/lib/editor.mjs @@ -71,8 +71,8 @@ export function getUiPreferenceUndoStepData({ step }) { const data = { icon: , field, - optCode: `${field}.t`, - titleCode: 'uiPreferences.t', + title: structure.title, + menu: 'UI Preferences', structure: menuUiPreferencesStructure()[field], } const FieldIcon = data.structure.icon @@ -85,7 +85,7 @@ export function getUiPreferenceUndoStepData({ step }) { data[key + 'Val'] = t( structure.choiceTitles[ structure.choiceTitles[String(step[key])] ? String(step[key]) : String(structure.dflt) - ] + '.t' + ] ) return data @@ -102,8 +102,8 @@ export function getCoreSettingUndoStepData({ step, state, Design }) { const data = { field, - titleCode: 'coreSettings.t', - optCode: `${field}.t`, + menu: 'Core Settings', + title: structure?.[field] ? structure[field].title : '', icon: , structure: structure[field], } @@ -125,9 +125,7 @@ export function getCoreSettingUndoStepData({ step, state, Design }) { case 'margin': case 'sa': case 'samm': - if (data.field !== 'margin') { - data.optCode = `samm.t` - } + if (data.field !== 'margin') data.title = 'Seam Allowance' data.oldVal = data.newVal = return data @@ -136,23 +134,23 @@ export function getCoreSettingUndoStepData({ step, state, Design }) { data.newVal = cord(step.new, data.structure.dflt) return data case 'units': - data.oldVal = t(step.new === 'imperial' ? 'pe:metricUnits' : 'pe:imperialUnits') - data.newVal = t(step.new === 'imperial' ? 'pe:imperialUnits' : 'pe:metricUnits') + data.oldVal = t(step.new === 'imperial' ? 'Metric Units' : 'Imperial Units') + data.newVal = t(step.new === 'imperial' ? 'Imperial Units' : 'Metric Units') return data case 'only': - data.oldVal = cord(step.old, data.structure.dflt) || t('pe:includeAllParts') - data.newVal = cord(step.new, 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) || 'Include all parts' return data default: 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(data.structure.dflt)]) + '.t' + : data.structure.choiceTitles[String(data.structure.dflt)] ) 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(data.structure.dflt)]) + '.t' + : data.structure.choiceTitles[String(data.structure.dflt)] ) return data } @@ -163,8 +161,8 @@ export function getDesignOptionUndoStepData({ step, state, Design }) { const data = { icon: , field: step.path[2], - optCode: `${state.design}:${step.path[2]}.t`, - titleCode: `designOptions.t`, + title: `${state.design}:${step.path[2]}`, + menu: `Design Options`, oldVal: formatDesignOptionValue(option, step.old, state.units === 'imperial'), newVal: formatDesignOptionValue(option, step.new, state.units === 'imperial'), } @@ -212,8 +210,8 @@ export function getUndoStepData(props) { const data = { icon: , field: 'measurements', - optCode: `measurements`, - titleCode: 'measurements', + title: `measurements`, + menu: 'measurements', } /* * Single measurements change? @@ -229,7 +227,7 @@ export function getUndoStepData(props) { for (const m of Object.keys(props.step.new)) { if (props.step.new[m] !== props.step.old?.[m]) count++ } - return { ...data, msg: t('pe:xMeasurementsChanged', { count }) } + return { ...data, msg: `${count} measurements updated` } } /* diff --git a/packages/react/components/Mini/index.mjs b/packages/react/components/Mini/index.mjs new file mode 100644 index 00000000000..14b17a2c5d6 --- /dev/null +++ b/packages/react/components/Mini/index.mjs @@ -0,0 +1,13 @@ +import React from 'react' +import { TipIcon } from '@freesewing/react/components/Icon' + +export const MiniTip = ({ children }) => ( +
      +
      + +
      +
      + {children} +
      +
      +) diff --git a/packages/react/package.json b/packages/react/package.json index b6437391267..2d5c7dad0c6 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -46,6 +46,7 @@ "./components/LineDrawing": "./components/LineDrawing/index.mjs", "./components/Link": "./components/Link/index.mjs", "./components/Logo": "./components/Logo/index.mjs", + "./components/Mini": "./components/Mini/index.mjs", "./components/Modal": "./components/Modal/index.mjs", "./components/New": "./components/New/index.mjs", "./components/Number": "./components/Number/index.mjs", @@ -86,12 +87,12 @@ "html-react-parser": "^5.0.7", "luxon": "^3.5.0", "nuqs": "^1.17.6", - "pdfkit": "^0.16.0", "react-markdown": "^9.0.1", "tlds": "^1.255.0", "use-local-storage-state": "19.1.0", "use-session-storage-state": "^19.0.0" }, + "devDependencies": {}, "files": [ "components/**", "hooks/**",