diff --git a/packages/react-components/src/index.mjs b/packages/react-components/src/index.mjs index 80ab7b1c173..57f3fadfbea 100644 --- a/packages/react-components/src/index.mjs +++ b/packages/react-components/src/index.mjs @@ -1,3 +1,4 @@ +// Components import { Pattern as PatternComponent } from './pattern/index.mjs' import { Svg as SvgComponent } from './pattern/svg.mjs' import { Defs as DefsComponent } from './pattern/defs.mjs' @@ -9,6 +10,8 @@ import { Snippet as SnippetComponent } from './pattern/snippet.mjs' import { Path as PathComponent } from './pattern/path.mjs' import { Grid as GridComponent } from './pattern/grid.mjs' import { Text as TextComponent, TextOnPath as TextOnPathComponent } from './pattern/text.mjs' +// Pattern Utils +import { getProps, withinPartBounds, getId, translateStrings } from './pattern/utils.mjs' /* * Export all components as named exports @@ -25,3 +28,13 @@ export const Snippet = SnippetComponent export const Grid = GridComponent export const Text = TextComponent export const TextOnPath = TextOnPathComponent + +/* + * Export pattern utils + */ +export const utils = { + getProps, + withinPartBounds, + getId, + translateStrings, +} diff --git a/packages/react-components/src/pattern/stack.mjs b/packages/react-components/src/pattern/stack.mjs index 7dc7da6956a..b4b51cb0ddb 100644 --- a/packages/react-components/src/pattern/stack.mjs +++ b/packages/react-components/src/pattern/stack.mjs @@ -6,12 +6,8 @@ export const Stack = ({ stackName, stack, settings, components, t }) => { return ( - {[...stack.parts].map((part) => ( - + {[...stack.parts].map((part, key) => ( + ))} ) diff --git a/packages/react-components/src/pattern/utils.mjs b/packages/react-components/src/pattern/utils.mjs index b2d724d438f..faf9fed0605 100644 --- a/packages/react-components/src/pattern/utils.mjs +++ b/packages/react-components/src/pattern/utils.mjs @@ -47,7 +47,7 @@ export const withinPartBounds = (point, part) => : false export const getId = ({ - settings, + settings = {}, stackName = false, partName = false, pathName = false, diff --git a/sites/shared/components/mdx/tabbed-example.mjs b/sites/shared/components/mdx/tabbed-example.mjs index b4d2c4db3a8..c2e50ce812a 100644 --- a/sites/shared/components/mdx/tabbed-example.mjs +++ b/sites/shared/components/mdx/tabbed-example.mjs @@ -5,8 +5,8 @@ import { pluginFlip } from '@freesewing/plugin-flip' import { pluginGore } from '@freesewing/plugin-gore' import { Design } from '@freesewing/core' import { Svg } from 'pkgs/react-components/src/index.mjs' -import { Defs } from '../workbench/pattern/defs' -import { Stack } from '../workbench/pattern/stack' +//import { Defs } from '../workbench/pattern/defs' +//import { Stack } from '../workbench/pattern/stack' import { useState, useEffect } from 'react' import yaml from 'js-yaml' @@ -40,22 +40,26 @@ export const Example = ({ ) - return ( - - - - - {Object.keys(patternProps.stacks).map((stackName) => ( - - ))} - - - ) + return null + + // FIXME + + //return ( + // + // + // + // + // {Object.keys(patternProps.stacks).map((stackName) => ( + // + // ))} + // + // + //) } // Returns a FreeSewing draft based on code in children diff --git a/sites/shared/components/workbench/pattern/point.mjs b/sites/shared/components/workbench/pattern/point.mjs deleted file mode 100644 index 58b9a1b9425..00000000000 --- a/sites/shared/components/workbench/pattern/point.mjs +++ /dev/null @@ -1,108 +0,0 @@ -import { Text } from './text.mjs' -import { Circle } from './circle.mjs' -import { Tr, KeyTd, ValTd, Attributes, pointCoords } from './path.mjs' -import { withinPartBounds } from './utils.mjs' - -const RevealPoint = ({ point, pointName, scale, part, partName, ui }) => { - const r = 15 * scale - const { x, y } = point - const { topLeft, bottomRight } = part - const i = Object.keys(ui.xray.reveal[partName].points).indexOf(pointName) % 10 - const classes = `stroke-sm stroke-color-${i} stroke-dashed` - return ( - - - - - ) -} -const pointInfo = ({ point, pointName, partName }) => - point ? ( -
-
Point info
- - - - Coordinates - {pointCoords(point)} - - - Name - {pointName} - - - Part - {partName} - - - Attributes - - - - - -
-
- - -
-
- ) : null - -const XrayPoint = ({ point, pointName, partName, scale, showInfo }) => ( - - - showInfo(evt, pointInfo({ point, pointName, partName }))} - /> - -) - -export const Point = ({ point, pointName, partName, settings, part, ui, showInfo }) => { - // Don't include parts outside the part bounding box - if (!withinPartBounds(point, part)) return null - const output = [] - if (ui.xray?.enabled) { - // Xray for points - output.push( - - ) - // Reveal (based on clicking the seach icon in sidebar - if (ui.xray?.reveal?.[partName]?.points?.[pointName]) - output.push() - } - // Render text - if (point.attributes && point.attributes.get('data-text')) - output.push( - - ) - // Render circle - if (point.attributes && point.attributes.get('data-circle')) - output.push() - - return output.length < 1 ? null : output -} diff --git a/sites/shared/components/workbench/views/draft/index.mjs b/sites/shared/components/workbench/views/draft/index.mjs index 98b4fe08d7e..608d904245d 100644 --- a/sites/shared/components/workbench/views/draft/index.mjs +++ b/sites/shared/components/workbench/views/draft/index.mjs @@ -1,5 +1,8 @@ -import { PanZoomPattern as ReactPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' +import { useState } from 'react' +import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' +import { InspectorPattern } from './inspector/pattern.mjs' import { DraftMenu, ns as menuNs } from './menu.mjs' +import { objUpdate } from 'shared/utils.mjs' export const ns = menuNs @@ -15,6 +18,36 @@ export const DraftView = ({ account, DynamicDocs, }) => { + // State for inspector + const [inspect, setInspect] = useState({ + show: {}, + reveal: {}, + }) + const inspector = { + show: (data) => { + const newInspect = { ...inspect } + newInspect.show[data.id] = data + setInspect(newInspect) + }, + hide: (id) => { + const newInspect = { ...inspect } + delete newInspect.show[id] + delete newInspect.reveal[id] + setInspect(newInspect) + }, + reveal: (id) => { + const newInspect = { ...inspect } + if (newInspect.reveal[id]) delete newInspect.reveal[id] + else newInspect.reveal[id] = 1 + setInspect(newInspect) + }, + update: (path, val) => { + const newInspect = objUpdate({ ...inspect }, path, val) + setInspect(newInspect) + }, + data: inspect, + } + let output = null if (ui.renderer === 'svg') { try { @@ -23,14 +56,17 @@ export const DraftView = ({ } catch (err) { console.log(err) } - } else output = + } else { + output = ui.inspect ? ( + + ) : ( + + ) + } return (
-
-
{JSON.stringify(ui, null, 2)}
- {output} -
+
{output}
diff --git a/sites/shared/components/workbench/views/draft/inspector/menu.mjs b/sites/shared/components/workbench/views/draft/inspector/menu.mjs new file mode 100644 index 00000000000..84848c051e2 --- /dev/null +++ b/sites/shared/components/workbench/views/draft/inspector/menu.mjs @@ -0,0 +1,49 @@ +// Hooks +//import { useContext } from 'react' +import { useTranslation } from 'next-i18next' +// Context +//import { ModalContext } from 'shared/context/modal-context.mjs' +//Dependencies +//import { loadSettingsConfig } from './config.mjs' +// Components +//import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' +import { WrenchIcon, ClearIcon, HelpIcon } from 'shared/components/icons.mjs' +import { Collapse } from 'shared/components/collapse.mjs' + +export const ns = ['inspector'] + +export const Inspector = ({ + design, + update, + settings, + patternConfig, + language, + DynamicDocs, + control, + ui, + inspector, +}) => { + // FIXME: Update this namespace + const { t } = useTranslation(ns) + + return ( + <> +
+ {control > 4 ? ( +
+ ) : ( + <> +
+ + {t('inspector:inspector')} +
+

{t('inspector:inspector.d')}

+ + )} +
+ {Object.values(inspector.data.show).map((props) => ( + + ))} + + ) +} diff --git a/sites/shared/components/workbench/views/draft/inspector/pattern.mjs b/sites/shared/components/workbench/views/draft/inspector/pattern.mjs new file mode 100644 index 00000000000..8e39d1adc01 --- /dev/null +++ b/sites/shared/components/workbench/views/draft/inspector/pattern.mjs @@ -0,0 +1,17 @@ +// Hooks +import { useState, useEffect, useContext } from 'react' +import { useTranslation } from 'next-i18next' +// Components +import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' +import { Point } from './point.mjs' + +export const InspectorPattern = (props) => { + const { t } = useTranslation(['workbench']) + const inspector = props.inspector + + const components = { + Point: (props) => , + } + + return +} diff --git a/sites/shared/components/workbench/views/draft/inspector/point.mjs b/sites/shared/components/workbench/views/draft/inspector/point.mjs new file mode 100644 index 00000000000..0ed904db60d --- /dev/null +++ b/sites/shared/components/workbench/views/draft/inspector/point.mjs @@ -0,0 +1,191 @@ +// Hooks +import { useState, useEffect, useContext } from 'react' +// Context +import { ModalContext } from 'shared/context/modal-context.mjs' +// Components +import { Point as ShowPoint, Text, Circle, utils } from 'pkgs/react-components/src/index.mjs' +import { Tr, KeyTd, ValTd, Attributes, pointCoords, useInfoLoader, KeyValTable } from './shared.mjs' +import { round } from 'shared/utils.mjs' +import { TrashIcon, PrintIcon, SearchIcon } from 'shared/components/icons.mjs' + +const { withinPartBounds, getId, getProps } = utils + +const RevealPoint = ({ point, pointName, scale, part, id, inspector }) => { + const r = 15 * scale + const { x, y } = point + const { topLeft, bottomRight } = part + const classes = `stroke-sm stroke-lining` + return ( + + inspector.reveal(id)} + /> + + + ) +} + +const Cross = ({ point, color = 'primary' }) => ( + +) + +const InspectPoint = ({ + point, + part, + pointName, + stackName, + scale = 1, + t, + inspector, + color = 'lining', +}) => { + const id = utils.getId({ stackName, pointName, settings: { idPrefix: 'point-' } }) + const info = { + id, + title: ( +
+ + Point: {pointName} | {stackName} + + {pointCoords(point)} +
+ ), + openTitle: ( + + Point: {pointName} | {stackName} + + ), + buttons: [ + , + ], + openButtons: [ + , + , + , + ], + children: ( + ], + ]} + /> + ), + color: 'accent', + } + + return ( + + + + + inspector.show(info)} + /> + {inspector.data.reveal[id] ? ( + + ) : null} + + ) +} +/* + title, + openTitle = false, + children = [], + buttons = [], + top = true, + bottom = false, + color = 'primary', + opened = false, + toggle = false, + toggleClasses = '', + onClick = false, + openButtons = null, + className = '', +*/ +export const Point = ({ + stackName, + pointName, + part, + point, + settings, + components, + t, + ui, + update, + inspector, +}) => { + const showInfo = useInfoLoader() + + // Don't include parts outside the part bounding box + if (!withinPartBounds(point, part)) return null + + const pointProps = { stackName, pointName, part, point, settings, components, t } + + return ( + <> + + + + ) +} diff --git a/sites/shared/components/workbench/views/draft/inspector/shared.mjs b/sites/shared/components/workbench/views/draft/inspector/shared.mjs new file mode 100644 index 00000000000..96c754ef11f --- /dev/null +++ b/sites/shared/components/workbench/views/draft/inspector/shared.mjs @@ -0,0 +1,631 @@ +// Hooks +import { useContext } from 'react' +// Context +import { ModalContext } from 'shared/context/modal-context.mjs' +// Dependencies +import { round } from 'shared/utils.mjs' +// Components +import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' + +export const useInfoLoader = () => { + const { setModal } = useContext(ModalContext) + + const showInfo = (content) => { + setModal({content}) + } + + return showInfo +} + +export const KeyValTable = ({ rows }) => ( + + + {rows.map((row) => ( + + + + + ))} + +
{row[0]}:{row[1]}
+) + +export const pointCoords = (point) => + point ? `[ ${round(point.x, 2)}, ${round(point.y, 2)} ]` : null + +export const Tr = ({ children }) => {children} +export const KeyTd = ({ children }) => {children}: +export const ValTd = ({ children }) => {children} + +export const TextAlongPath = ({ id, size, txt }) => ( + + + + {txt} + + + +) +export const PointCircle = ({ point, size, className = 'stroke-neutral-content' }) => ( + +) + +const CpCircle = ({ point, className = 'fill-lining no-stroke' }) => ( + +) + +const EpCircle = ({ point, className = 'fill-note no-stroke' }) => ( + +) + +const pathDimensions = (from, to, cp1 = false, cp2 = false, path = false) => { + const topLeft = { + x: to.x < from.x ? to.x : from.x, + y: to.y < from.y ? to.y : from.y, + } + const bottomRight = { + x: to.x > from.x ? to.x : from.x, + y: to.y > from.y ? to.y : from.y, + } + let bbox = false + // Deal with curves + if (cp1 && cp2) { + if (cp1.x < topLeft.x) topLeft.x = cp1.x + if (cp2.x < topLeft.x) topLeft.x = cp2.x + if (cp1.x > bottomRight.x) bottomRight.x = cp1.x + if (cp2.x > bottomRight.x) bottomRight.x = cp2.x + if (cp1.y < topLeft.y) topLeft.y = cp1.y + if (cp2.y < topLeft.y) topLeft.y = cp2.y + if (cp1.y > bottomRight.y) bottomRight.y = cp1.y + if (cp2.y > bottomRight.y) bottomRight.y = cp2.y + // This undocumented core methods returns the curve's bounding box + bbox = path.bbox() + } + const w = bottomRight.x - topLeft.x + const h = bottomRight.y - topLeft.y + const size = w > h ? w : h + + return { + topLeft, + bottomRight, + w, + h, + size, + bbox, + } +} + +export const Defs = () => ( + + + + + + + + +) + +export const svgProps = { + xmlns: 'http://www.w3.org/2000/svg', + xmlnsSvg: 'http://www.w3.org/2000/svg', + xmlnsXlink: 'http://www.w3.org/1999/xlink', + style: { maxHeight: 'inherit', strokeLinecap: 'round', strokeLinejoin: 'round' }, +} + +const Line = ({ path, pathName, partName, i, units }) => { + const ops = path.ops + const from = ops[0].to + const to = ops[1].to + const { topLeft, bottomRight, w, h, size } = pathDimensions(from, to) + const id = `${partName}_${pathName}_${i}` + + const xyProps = { + className: 'stroke-neutral-content', + strokeOpacity: '0.5', + fill: 'none', + strokeWidth: size / 300, + } + + return ( + + + + + + + + + + + ) +} + +const Curve = ({ path, pathName, partName, i }) => { + const ops = path.ops + const from = ops[0].to + const { to, cp1, cp2 } = ops[1] + const { topLeft, w, h, size, bbox } = pathDimensions(from, to, cp1, cp2, path) + const id = `${partName}_${pathName}_${i}` + + const cpProps = { + className: 'stroke-success', + strokeOpacity: '0.85', + fill: 'none', + strokeWidth: size / 300, + } + const xyProps = { + ...cpProps, + strokeOpacity: '0.5', + className: 'stroke-neutral-content', + markerEnd: 'url(#arrowTo)', + markerStart: 'url(#arrowFrom)', + } + + return ( + + + + + + + + + + + + + + + + ) +} + +const MiniPath = ({ path, pathName, partName, units }) => { + const bbox = path.bbox() + const id = `${partName}_${pathName}_mini}` + const w = bbox.bottomRight.x - bbox.topLeft.x + const h = bbox.bottomRight.y - bbox.topLeft.y + const size = w > h ? w : h + + const xyProps = { + fill: 'none', + strokeWidth: size / 300, + strokeOpacity: '0.5', + className: 'stroke-neutral-content', + markerEnd: 'url(#arrowTo)', + markerStart: 'url(#arrowFrom)', + } + + return ( + + + + + + + + + ) + + ) +} + +const lineInfo = ({ path, partName, units, i }) => ( +
+
Line info
+
+ + + + From + {pointCoords(path.ops[0].to)} + + + To + {pointCoords(path.ops[1].to)} + + + Length + {formatMm(path.length(), units, 'notags')} + + + Width + + + + + + Height + + + + + + Part + {partName} + + + Draw Op + + {i}/{ops.length} + + + +
+
+ +
+
+
+) + +const XrayLine = ({ i, path, partName, units, showInfo }) => ( + <> + showInfo(evt, lineInfo({ path, partName, units, i }))} + /> + + + +) + +const curveInfo = ({ i, path, partName, units }) => ( +
+
Curve info
+
+ + + + From + {pointCoords(path.ops[0].to)} + + + Cp1 + {pointCoords(path.ops[1].cp1)} + + + Cp2 + {pointCoords(path.ops[1].cp2)} + + + To + {pointCoords(path.ops[1].to)} + + + Length + + + + + + Width + + + + + + Height + + + + + + Part + {partName} + + + Draw Op + + {i}/{ops.length} + + + +
+
+ +
+
+
+) + +export const Attributes = ({ list }) => + list ? ( +
    + {Object.keys(list).map((key) => ( +
  • + {key}: {list[key]} +
  • + ))} +
+ ) : null + +export const pathInfo = ({ pathName, path, partName, units, showInfo }) => { + const { t } = useTranslation(['workbench']) + const bbox = path.bbox() + + return ( +
+
{t('pathInfo')}
+
+
+ + + + {t('name')} + {pathName} + + + {t('length')} + + + + + + {t('width')} + + + + + + {t('height')} + + + + + + {t('topLeft')} + {pointCoords(bbox.topLeft)} + + + {t('bottomRight')} + {pointCoords(bbox.bottomRight)} + + + {t('part')} + {partName} + + + {t('attributes')} + + + + + +
+
+ + +
+
+ + + {path.ops.map((op, i) => ( + + {i} + + + + + ))} + +
+
+ +
+
+
+ ) +} + +const PathOp = ({ op }) => { + if (op.type === 'move') + return ( + + Move to {pointCoords(op.to)} + + ) + else if (op.type === 'line') + return ( + + Line to {pointCoords(op.to)} + + ) + else if (op.type === 'curve') + return ( + + Curve to {pointCoords(op.to)} +
+ Cp1: {pointCoords(op.cp1)} +
+ Cp2: {pointCoords(op.cp2)} +
+ ) + else if (op.type === 'noop') return NOOP + else if (op.type === 'close') return Close + else return FIXME: unknown path operation type: {op.type} +} + +const XrayCurve = ({ i, path, ops, units, showInfo }) => { + const from = path.ops[0].to + const { cp1, cp2, to } = path.ops[1] + + return ( + <> + showInfo(evt, curveInfo({ i, path, partName, units }))} + /> + + + + + + + + ) +} + +const XrayPath = ({ path, pathName, partName, units, showInfo }) => { + const classes = path.attributes.get('class') + if (typeof classes === 'string' && classes.includes('noxray')) return null + const ops = path.divide() + + return ( + + showInfo(evt, pathInfo({ pathName, path, partName, units }))} + markerStart="none" + markerEnd="none" + /> + {ops.length > 0 + ? ops.map((op, i) => + op.ops[1].type === 'curve' ? ( + + ) : ( + + ) + ) + : null} + + ) +} + +export const Path = ({ pathName, path, partName, part, units, showInfo, ui, update }) => { + if (path.hidden) return null + const output = [] + const pathId = 'path-' + partName + '-' + pathName + let d = '' + try { + d = path.asPathstring() + } catch (err) { + // Bail out + console.log(`Failed to generate pathstring for path ${pathId} in part ${partName}`, err) + return null + } + + output.push() + if (path.attributes.get('data-text')) + output.push( + + ) + if (ui.xray?.enabled) output.push() + + return output +} diff --git a/sites/shared/components/workbench/views/draft/menu.mjs b/sites/shared/components/workbench/views/draft/menu.mjs index bebbcde673f..8fc208461d6 100644 --- a/sites/shared/components/workbench/views/draft/menu.mjs +++ b/sites/shared/components/workbench/views/draft/menu.mjs @@ -7,8 +7,9 @@ import { ns as coreMenuNs, } from 'shared/components/workbench/menus/core-settings/index.mjs' import { UiSettings, ns as uiNs } from 'shared/components/workbench/menus/ui-settings/index.mjs' +import { Inspector, ns as inspectorNs } from './inspector/menu.mjs' -export const ns = [...coreMenuNs, ...designMenuNs, ...uiNs] +export const ns = [...coreMenuNs, ...designMenuNs, ...uiNs, inspectorNs] export const DraftMenu = ({ design, @@ -19,6 +20,7 @@ export const DraftMenu = ({ language, account, DynamicDocs, + inspector = false, }) => { // Default control level is 2 (in case people are not logged in) const control = account.control || 2 @@ -35,6 +37,7 @@ export const DraftMenu = ({ return (