diff --git a/markdown/org/docs/patterns/noble/options/dartposition/en.md b/markdown/org/docs/patterns/noble/options/dartposition/en.md index 2d9e2e6429c..af41560fa50 100644 --- a/markdown/org/docs/patterns/noble/options/dartposition/en.md +++ b/markdown/org/docs/patterns/noble/options/dartposition/en.md @@ -1,9 +1,5 @@ --- -title: Dart position +title: "Dart position" --- -Controls whether to split at the shoulder or armhole - - -## Effect of this option on the pattern -![This image shows the effect of this option by superimposing several variants that have a different value for this option](noble_dartposition_sample.svg "Effect of this option on the pattern") +The **Dart position** option controls whether to create the princess seam at the shoulder or armhole. diff --git a/sites/shared/components/error-boundary.js b/sites/shared/components/error/error-boundary.js similarity index 77% rename from sites/shared/components/error-boundary.js rename to sites/shared/components/error/error-boundary.js index 995ce81df2c..68bfb34efbf 100644 --- a/sites/shared/components/error-boundary.js +++ b/sites/shared/components/error/error-boundary.js @@ -1,4 +1,5 @@ import React from 'react'; +import ResetButtons from './reset-buttons' class ErrorBoundary extends React.Component { constructor(props) { @@ -25,7 +26,11 @@ class ErrorBoundary extends React.Component { render() { if (this.state.hasError) { // You can render any custom fallback UI - return this.props.errorView || (

Something went wrong.

); + return (
+ {this.props.errorView || (

Something went wrong.

)} + +
) + return ; } try { diff --git a/sites/shared/components/error/reset-buttons.js b/sites/shared/components/error/reset-buttons.js new file mode 100644 index 00000000000..0e04c2e91d7 --- /dev/null +++ b/sites/shared/components/error/reset-buttons.js @@ -0,0 +1,10 @@ +import { useTranslation } from 'next-i18next' + +export default function ({resetGist, undoGist}) { + const {t} = useTranslation(['app']) + + return (<> + + + +)} diff --git a/sites/shared/components/workbench/layout/draft/index.js b/sites/shared/components/workbench/layout/draft/index.js index 5a4c83dcfe6..e6df4fbb917 100644 --- a/sites/shared/components/workbench/layout/draft/index.js +++ b/sites/shared/components/workbench/layout/draft/index.js @@ -18,14 +18,14 @@ const Draft = props => { ...patternProps.autoLayout, width: patternProps.width, height: patternProps.height - }) + }, false, false) } }, [layout]) if (!patternProps || !layout) return null // Helper method to update part layout and re-calculate width * height - const updateLayout = (name, config) => { + const updateLayout = (name, config, history=true) => { // Start creating new layout const newLayout = {...layout} newLayout.parts[name] = config @@ -49,7 +49,7 @@ const Draft = props => { newLayout.height = bottomRight.y - topLeft.y newLayout.bottomRight = bottomRight newLayout.topLeft = topLeft - updateGist(['layout'], newLayout) + updateGist(['layout'], newLayout, false, history) } diff --git a/sites/shared/components/workbench/layout/draft/part.js b/sites/shared/components/workbench/layout/draft/part.js index 564bf0b9edd..8c473dcfe47 100644 --- a/sites/shared/components/workbench/layout/draft/part.js +++ b/sites/shared/components/workbench/layout/draft/part.js @@ -94,7 +94,7 @@ const Part = props => { // update the layout on mount useEffect(() => { - if (partRef.current) updateLayout() + if (partRef.current) updateLayout(false) }, [partRef]) // Initialize drag handler @@ -168,7 +168,7 @@ const Part = props => { updateLayout() setRotate(!rotate) } - const updateLayout = () => { + const updateLayout = (history=true) => { const partRect = partRef.current.getBoundingClientRect(); const matrix = partRef.current.ownerSVGElement.getScreenCTM().inverse(); @@ -188,7 +188,7 @@ const Part = props => { flipY, tl, br - }) + }, history) } // Method to flip (mirror) the part along the X or Y axis diff --git a/sites/shared/components/workbench/menu/index.js b/sites/shared/components/workbench/menu/index.js index 1f4d7d70e24..88a8881b8e7 100644 --- a/sites/shared/components/workbench/menu/index.js +++ b/sites/shared/components/workbench/menu/index.js @@ -4,7 +4,6 @@ import DesignOptions from './design-options' import CoreSettings from './core-settings' import Xray from './xray' import TestDesignOptions from './test-design-options' -import ErrorBoundary from 'shared/components/error-boundary' export const Ul = props => export const Li = props => ( @@ -87,7 +86,6 @@ const WorkbenchMenu = props => { return ( ) } diff --git a/sites/shared/components/wrappers/workbench.js b/sites/shared/components/wrappers/workbench.js index 880e3a5ccbe..e2623e85839 100644 --- a/sites/shared/components/wrappers/workbench.js +++ b/sites/shared/components/wrappers/workbench.js @@ -18,7 +18,7 @@ import DraftEvents from 'shared/components/workbench/events.js' import CutLayout from 'shared/components/workbench/layout/cut' import PrintLayout from 'shared/components/workbench/layout/print' -import ErrorBoundary from 'shared/components/error-boundary'; +import ErrorBoundary from 'shared/components/error/error-boundary'; const views = { measurements: Measurements, @@ -57,7 +57,7 @@ const doPreload = async (preload, from, design, gist, setGist, setPreloaded) => const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false }) => { // State for gist - const {gist, setGist, unsetGist, updateGist, gistReady} = useGist(design, app); + const {gist, setGist, unsetGist, updateGist, gistReady, undoGist, resetGist} = useGist(design, app); const [messages, setMessages] = useState([]) const [popup, setPopup] = useState(false) const [preloaded, setPreloaded] = useState(false) @@ -87,8 +87,8 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false }, [preload, preloaded, from, design]) // Helper methods to manage the gist state - const updateWBGist = useMemo(() => (path, value, closeNav=false) => { - updateGist(path, value) + const updateWBGist = useMemo(() => (path, value, closeNav=false, addToHistory=true) => { + updateGist(path, value, addToHistory) // Force close of menu on mobile if it is open if (closeNav && app.primaryMenu) app.setPrimaryMenu(false) }, [app]) @@ -142,6 +142,12 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false showInfo: setPopup, } + const errorProps = { + undoGist, + resetGist, + gist + } + // Layout to use const LayoutComponent = layout ? layout @@ -153,7 +159,7 @@ const WorkbenchWrapper = ({ app, design, preload=false, from=false, layout=false return {messages} - + {popup && setPopup(false)}>{popup}} diff --git a/sites/shared/hooks/useGist.js b/sites/shared/hooks/useGist.js index b5ec1cad75d..6413c263e84 100644 --- a/sites/shared/hooks/useGist.js +++ b/sites/shared/hooks/useGist.js @@ -1,7 +1,9 @@ import useLocalStorage from './useLocalStorage'; import set from 'lodash.set' import unset from 'lodash.unset' +import cloneDeep from 'lodash.clonedeep' import defaultSettings from 'shared/components/workbench/default-settings.js' +import {useState, useEffect} from 'react' // Generates a default design gist to start from export const defaultGist = (design, locale='en') => { @@ -19,25 +21,68 @@ export const defaultGist = (design, locale='en') => { // generate the gist state and its handlers export function useGist(design, app) { // get the localstorage state and setter - const [gist, setGist, gistReady] = useLocalStorage(`${design.config.name}_gist`, defaultGist(design, app.locale)); + const [gist, _setGist, gistReady] = useLocalStorage(`${design.config.name}_gist`, defaultGist(design, app.locale)); + const [gistHistory, setGistHistory] = useState([]); + const [gistFuture, setGistFuture] = useState([]); + + const setGist = (newGist, addToHistory=true) => { + let oldGist + _setGist((gistState) => { + // have to clone it or nested objects will be referenced instead of copied, which defeats the purpose + oldGist = cloneDeep(gistState); + return typeof newGist === 'function' ? newGist(cloneDeep(gistState)) : newGist + }) + + if (addToHistory) { + setGistHistory((history) => { + return [...history, oldGist] + }) + setGistFuture([]) + } + } /** update a single gist value */ - const updateGist = (path, value) => { + const updateGist = (path, value, addToHistory=true) => { setGist((gistState) => { const newGist = {...gistState}; set(newGist, path, value); return newGist; - }) + }, addToHistory) } /** unset a single gist value */ - const unsetGist = (path) => { + const unsetGist = (path, addToHistory=true) => { setGist((gistState) => { - const newGist = {... gistState}; + const newGist = {...gistState}; unset(newGist, path); return newGist; - }) + }, addToHistory) } - return {gist, setGist, unsetGist, gistReady, updateGist}; + const undoGist = () => { + _setGist((gistState) => { + let prevGist; + setGistHistory((history) => { + const newHistory = [...history] + prevGist = newHistory.pop() || defaultGist(design, app.locale); + return newHistory; + }) + setGistFuture((future) => [gistState, ...future]); + + return {...prevGist} + }) + } + + const redoGist = () => { + const newHistory = [...gistHistory, gist] + const newFuture = [...gistFuture] + const newGist = newFuture.shift() + setGistHistory(newHistory) + setGistFuture(newFuture) + _setGist(newGist) + } + + const resetGist = () => setGist(defaultGist(design, app.locale)) + + return {gist, setGist, unsetGist, gistReady, updateGist, undoGist, redoGist, resetGist}; } diff --git a/sites/shared/package.json b/sites/shared/package.json index c184cb5e5b5..9d998732d34 100644 --- a/sites/shared/package.json +++ b/sites/shared/package.json @@ -23,6 +23,7 @@ "file-saver": "^2.0.5", "front-matter": "^4.0.2", "highlight.js": "^11.4.0", + "lodash.clonedeep": "^4.5.0", "lodash.orderby": "^4.6.0", "lodash.unset": "^4.5.2", "mdast-util-toc": "^6.1.0", diff --git a/yarn.lock b/yarn.lock index 291a8ed1872..9ac0bfde436 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13805,6 +13805,11 @@ lodash.castarray@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" integrity sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"