1
0
Fork 0

wip(shared): Points support in Inspector

This commit is contained in:
joostdecock 2023-06-02 18:21:40 +02:00
parent eef8d68781
commit 4075e35641
12 changed files with 989 additions and 280 deletions

View file

@ -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,
}

View file

@ -6,12 +6,8 @@ export const Stack = ({ stackName, stack, settings, components, t }) => {
return (
<Group {...getProps(stack)}>
{[...stack.parts].map((part) => (
<Part
{...{ settings, components, t, part, stackName }}
key={part.name}
partName={part.name}
/>
{[...stack.parts].map((part, key) => (
<Part {...{ settings, components, t, part, stackName, key }} />
))}
</Group>
)

View file

@ -47,7 +47,7 @@ export const withinPartBounds = (point, part) =>
: false
export const getId = ({
settings,
settings = {},
stackName = false,
partName = false,
pathName = false,

View file

@ -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 = ({
</div>
)
return (
<Svg {...patternProps} settings={settings} embed={true}>
<Defs {...patternProps} />
<style>{`:root { --pattern-scale: 1} ${patternProps.svg.style}`}</style>
<g>
{Object.keys(patternProps.stacks).map((stackName) => (
<Stack
{...{ showInfo, patternProps, settings, ui }}
key={stackName}
stackName={stackName}
stack={patternProps.stacks[stackName]}
/>
))}
</g>
</Svg>
)
return null
// FIXME
//return (
// <Svg {...patternProps} settings={settings} embed={true}>
// <Defs {...patternProps} />
// <style>{`:root { --pattern-scale: 1} ${patternProps.svg.style}`}</style>
// <g>
// {Object.keys(patternProps.stacks).map((stackName) => (
// <Stack
// {...{ showInfo, patternProps, settings, ui }}
// key={stackName}
// stackName={stackName}
// stack={patternProps.stacks[stackName]}
// />
// ))}
// </g>
// </Svg>
//)
}
// Returns a FreeSewing draft based on code in children

View file

@ -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 (
<g>
<circle cx={x} cy={y} r={r} className={classes} />
<path
d={`
M ${x} ${topLeft.y} L ${x} ${y - r}
m 0 ${2 * r} L ${x} ${bottomRight.y}
M ${topLeft.x} ${y} L ${x - r} ${y}
m ${2 * r} 0 L ${bottomRight.x} ${y}`}
className={classes}
/>
</g>
)
}
const pointInfo = ({ point, pointName, partName }) =>
point ? (
<div className="p-4 border bg-neutral bg-opacity-60 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Point info</h5>
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>Coordinates</KeyTd>
<ValTd>{pointCoords(point)}</ValTd>
</Tr>
<Tr>
<KeyTd>Name</KeyTd>
<ValTd>{pointName}</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Attributes</KeyTd>
<ValTd>
<Attributes list={point.attributes.list} />
</ValTd>
</Tr>
</tbody>
</table>
<div className="flex flex-col flex-wrap gap-2 mt-4">
<button className="btn btn-success" onClick={() => console.log(point)}>
console.log(point)
</button>
<button className="btn btn-success" onClick={() => console.table(point)}>
console.table(point)
</button>
</div>
</div>
) : null
const XrayPoint = ({ point, pointName, partName, scale, showInfo }) => (
<g>
<circle
cx={point.x}
cy={point.y}
r={2 * scale}
className="stroke-sm stroke-lining fill-lining fill-opacity-25"
/>
<circle
cx={point.x}
cy={point.y}
r={7.5 * scale}
className="opacity-0 stroke-lining fill-lining hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => showInfo(evt, pointInfo({ point, pointName, partName }))}
/>
</g>
)
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(
<XrayPoint
{...{ point, showInfo, pointName, partName }}
scale={settings.scale}
key={'xp-' + pointName}
/>
)
// Reveal (based on clicking the seach icon in sidebar
if (ui.xray?.reveal?.[partName]?.points?.[pointName])
output.push(<RevealPoint scale={settings.scale} key={'rp-' + pointName} />)
}
// Render text
if (point.attributes && point.attributes.get('data-text'))
output.push(
<Text {...{ point, ui, pointName, partName, showInfo }} key={'point-' + pointName} />
)
// Render circle
if (point.attributes && point.attributes.get('data-circle'))
output.push(<Circle point={point} key={'circle-' + pointName} />)
return output.length < 1 ? null : output
}

View file

@ -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 = <ReactPattern renderProps={pattern.getRenderProps()} />
} else {
output = ui.inspect ? (
<InspectorPattern renderProps={pattern.getRenderProps()} inspector={inspector} />
) : (
<ShowPattern renderProps={pattern.getRenderProps()} />
)
}
return (
<div className="flex flex-row">
<div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0">
<pre>{JSON.stringify(ui, null, 2)}</pre>
{output}
</div>
<div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0">{output}</div>
<div className="w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-screen overflow-scroll">
<DraftMenu
{...{
@ -43,6 +79,7 @@ export const DraftView = ({
language,
account,
DynamicDocs,
inspector,
}}
/>
</div>

View file

@ -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 (
<>
<div className="px-2 mt-8">
{control > 4 ? (
<div className="border-t border-solid border-base-300 pb-2 mx-36"></div>
) : (
<>
<h5 className="flex flex-row gap-2 items-center">
<WrenchIcon />
<span>{t('inspector:inspector')}</span>
</h5>
<p>{t('inspector:inspector.d')}</p>
</>
)}
</div>
{Object.values(inspector.data.show).map((props) => (
<Collapse {...props} />
))}
</>
)
}

View file

@ -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) => <Point {...props} inspector={inspector} />,
}
return <PanZoomPattern {...props} {...{ t, components }} />
}

View file

@ -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 (
<g>
<circle
cx={x}
cy={y}
r={r}
className={`${classes} fill-lining pulse-fill hover:cursor-pointer`}
onClick={() => inspector.reveal(id)}
/>
<path
d={`
M ${x} ${topLeft.y} L ${x} ${y - r}
m 0 ${2 * r} L ${x} ${bottomRight.y}
M ${topLeft.x} ${y} L ${x - r} ${y}
m ${2 * r} 0 L ${bottomRight.x} ${y}`}
className={`${classes} lashed`}
/>
</g>
)
}
const Cross = ({ point, color = 'primary' }) => (
<path
d={`M ${point.x - 2},${point.y - 2} l 4,4 m -4,0 l 4,-4`}
className={`stroke-xs ${color}`}
/>
)
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: (
<div className="flex flex-row justify-between w-full">
<span>
<b>Point</b>: {pointName} | {stackName}
</span>
{pointCoords(point)}
</div>
),
openTitle: (
<span>
<b>Point</b>: {pointName} | {stackName}
</span>
),
buttons: [
<button key={1} className="btn btn-error" onClick={(evt) => inspector.hide(id)}>
<TrashIcon />
</button>,
],
openButtons: [
<button
className="btn btn-xs btn-ghost px-0"
key="log"
onClick={(evt) => {
evt.stopPropagation()
console.log(point)
}}
>
<PrintIcon className="w-4 h-4" />
</button>,
<button
className="btn btn-xs btn-ghost px-0"
key="reveal"
onClick={(evt) => {
evt.stopPropagation()
inspector.reveal(id)
}}
>
<SearchIcon className="w-4 h-4" />
</button>,
<button
className="btn btn-xs btn-ghost px-0"
key="remove"
onClick={(evt) => {
evt.stopPropagation()
inspector.hide(id)
}}
>
<TrashIcon className="w-4 h-4" />
</button>,
],
children: (
<KeyValTable
rows={[
[t('coordinates'), pointCoords(point)],
[t('name'), pointName],
['Stack', stackName],
[t('attributes'), <Attributes list={point.attributes.list} />],
]}
/>
),
color: 'accent',
}
return (
<g>
<circle cx={point.x} cy={point.y} r={2 * scale} className={`stroke-xs stroke-${color}`} />
<circle
cx={point.x}
cy={point.y}
r={2 * scale}
className={`stroke-xs fill-${color} opacity-10`}
/>
<Cross {...{ point, color }} />
<circle
cx={point.x}
cy={point.y}
r={5 * scale}
className={`opacity-0 stroke-${color} fill-${color} hover:opacity-25 hover:cursor-pointer hover:opacity-30`}
onClick={(evt) => inspector.show(info)}
/>
{inspector.data.reveal[id] ? (
<RevealPoint {...{ point, pointName, scale, part, id, inspector }} />
) : null}
</g>
)
}
/*
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 (
<>
<ShowPoint
{...pointProps}
{...{ stackName, pointName, part, point, settings, components, t }}
/>
<InspectPoint
{...{ point, pointName, stackName, inspector, t, part }}
scale={settings.scale}
/>
</>
)
}

View file

@ -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(<ModalWrapper>{content}</ModalWrapper>)
}
return showInfo
}
export const KeyValTable = ({ rows }) => (
<table className="border-collapse h-fit">
<tbody>
{rows.map((row) => (
<tr>
<td className="font-bold p-2 pr-0 text-right">{row[0]}:</td>
<td className="p-2">{row[1]}</td>
</tr>
))}
</tbody>
</table>
)
export const pointCoords = (point) =>
point ? `[ ${round(point.x, 2)}, ${round(point.y, 2)} ]` : null
export const Tr = ({ children }) => <tr className="border border-base-300">{children}</tr>
export const KeyTd = ({ children }) => <td className="p-3 text-right">{children}:</td>
export const ValTd = ({ children }) => <td className="p-3">{children}</td>
export const TextAlongPath = ({ id, size, txt }) => (
<text>
<textPath xlinkHref={`#${id}`} startOffset="50%">
<tspan
style={{ textAnchor: 'middle', fontSize: size }}
className="fill-neutral-content fill-opacity-50"
dy={size * -0.4}
>
{txt}
</tspan>
</textPath>
</text>
)
export const PointCircle = ({ point, size, className = 'stroke-neutral-content' }) => (
<circle
cx={point.x}
cy={point.y}
r={size / 50}
className={className}
fill="none"
strokeWidth={size / 150}
strokeOpacity="0.5"
/>
)
const CpCircle = ({ point, className = 'fill-lining no-stroke' }) => (
<circle cx={point.x} cy={point.y} r={1.5} className={className} />
)
const EpCircle = ({ point, className = 'fill-note no-stroke' }) => (
<circle cx={point.x} cy={point.y} r={1.5} className={className} />
)
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 = () => (
<defs>
<marker orient="auto" refY="0.0" refX="0.0" id="arrowTo" style={{ overflow: 'visible' }}>
<path
className="fill-neutral-content"
d="M 0,0 L -12,-4 C -10,-2 -10,2 -12, 4 z"
fillOpacity="0.5"
></path>
</marker>
<marker orient="auto" refY="0.0" refX="0.0" id="arrowFrom" style={{ overflow: 'visible' }}>
<path
className="fill-neutral-content"
d="M 0,0 L 12,-4 C 10,-2 10,2 12, 4 z"
fillOpacity="0.5"
></path>
</marker>
</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 (
<svg
{...svgProps}
viewBox={`${topLeft.x - size / 10} ${topLeft.y - size / 10} ${w + size / 5} ${h + size / 5}`}
>
<path
id={`${id}_x`}
{...xyProps}
d={`M ${topLeft.x},${bottomRight.y} L ${bottomRight.x},${bottomRight.y}`}
/>
<TextAlongPath
id={`${id}_x`}
size={size / 18}
txt={formatMm(bottomRight.x - topLeft.x, units, 'notags')}
/>
<path
id={`${id}_y`}
{...xyProps}
d={`M ${topLeft.x},${bottomRight.y} L ${topLeft.x},${topLeft.y}`}
/>
<TextAlongPath
id={`${id}_y`}
size={size / 18}
txt={formatMm(bottomRight.y - topLeft.y, units, 'notags')}
/>
<path
id={id}
d={`M ${from.x},${from.y} L ${to.x},${to.y}`}
className="stroke-neutral-content"
strokeLinecap="round"
strokeWidth={size / 100}
/>
<TextAlongPath id={id} size={size / 18} txt={formatMm(path.length(), units, 'notags')} />
<PointCircle point={from} size={size} />
<PointCircle point={to} size={size} />
</svg>
)
}
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 (
<svg
{...svgProps}
viewBox={`${topLeft.x - size / 10} ${topLeft.y - size / 10} ${w + size / 5} ${h + size / 5}`}
>
<Defs />
<path
id={`${id}_x`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.bottomRight.x},${bbox.bottomRight.y}`}
/>
<TextAlongPath
id={`${id}_x`}
size={size / 18}
txt={formatMm(bbox.bottomRight.x - bbox.topLeft.x, units, 'notags')}
/>
<path
id={`${id}_y`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.topLeft.x},${bbox.topLeft.y}`}
/>
<TextAlongPath
id={`${id}_y`}
size={size / 18}
txt={formatMm(bbox.bottomRight.y - bbox.topLeft.y, units, 'notags')}
/>
<path id={`${id}_cp1`} {...cpProps} d={`M ${from.x},${from.y} L ${cp1.x},${cp1.y}`} />
<PointCircle point={cp1} size={size} className="stroke-success" />
<path id={`${id}_cp2`} {...cpProps} d={`M ${to.x},${to.y} L ${cp2.x},${cp2.y}`} />
<PointCircle point={cp2} size={size} className="stroke-success" />
<path
id={id}
d={`M ${from.x},${from.y} C ${cp1.x},${cp1.y} ${cp2.x},${cp2.y} ${to.x},${to.y}`}
className="stroke-neutral-content"
fill="none"
strokeLinecap="round"
strokeWidth={size / 100}
/>
<TextAlongPath id={id} size={size / 18} txt={formatMm(path.length(), units, 'notags')} />
<PointCircle point={from} size={size} />
<PointCircle point={to} size={size} />
</svg>
)
}
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 (
<svg
{...svgProps}
viewBox={`${bbox.topLeft.x - size / 10} ${bbox.topLeft.y - size / 10} ${w + size / 5} ${
h + size / 5
}`}
className="freesewing pattern z-50"
>
<Defs />
<path
id={`${id}_x`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.bottomRight.x},${bbox.bottomRight.y}`}
/>
<TextAlongPath
id={`${id}_x`}
size={size / 18}
txt={formatMm(bbox.bottomRight.x - bbox.topLeft.x, units, 'notags')}
/>
<path
id={`${id}_y`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.topLeft.x},${bbox.topLeft.y}`}
/>
<TextAlongPath
id={`${id}_y`}
size={size / 18}
txt={formatMm(bbox.bottomRight.y - bbox.topLeft.y, units, 'notags')}
/>
<path
id={id}
d={path.asPathstring()}
className="stroke-neutral-content"
fill="none"
strokeLinecap="round"
strokeWidth={size / 100}
/>
<TextAlongPath id={id} size={size / 18} txt={formatMm(path.length(), units, 'notags')} />
<XrayPath {...props} />)
</svg>
)
}
const lineInfo = ({ path, partName, units, i }) => (
<div className="p-4 border bg-neutral bg-opacity-60 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Line info</h5>
<div className="flex flex-row flex-wrap">
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>From</KeyTd>
<ValTd>{pointCoords(path.ops[0].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>To</KeyTd>
<ValTd>{pointCoords(path.ops[1].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>Length</KeyTd>
<ValTd>{formatMm(path.length(), units, 'notags')}</ValTd>
</Tr>
<Tr>
<KeyTd>Width</KeyTd>
<ValTd>
<RawSpan html={formatMm(Math.abs(path.ops[0].to.dx(path.ops[1].to)), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Height</KeyTd>
<ValTd>
<RawSpan html={formatMm(Math.abs(path.ops[0].to.dy(path.ops[1].to)), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Draw Op</KeyTd>
<ValTd>
{i}/{ops.length}
</ValTd>
</Tr>
</tbody>
</table>
<div className="max-w-md" style={{ maxHeight: '80vh' }}>
<Line {...props} />
</div>
</div>
</div>
)
const XrayLine = ({ i, path, partName, units, showInfo }) => (
<>
<path
d={path.asPathstring()}
{...getProps(path)}
className="opacity-0 stroke-4xl stroke-note hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => showInfo(evt, lineInfo({ path, partName, units, i }))}
/>
<EpCircle point={path.ops[0].to} />
<EpCircle point={path.ops[1].to} />
</>
)
const curveInfo = ({ i, path, partName, units }) => (
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Curve info</h5>
<div className="flex flex-row flex-wrap">
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>From</KeyTd>
<ValTd>{pointCoords(path.ops[0].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>Cp1</KeyTd>
<ValTd>{pointCoords(path.ops[1].cp1)}</ValTd>
</Tr>
<Tr>
<KeyTd>Cp2</KeyTd>
<ValTd>{pointCoords(path.ops[1].cp2)}</ValTd>
</Tr>
<Tr>
<KeyTd>To</KeyTd>
<ValTd>{pointCoords(path.ops[1].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>Length</KeyTd>
<ValTd>
<RawSpan html={formatMm(path.length(), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Width</KeyTd>
<ValTd>
<RawSpan html={formatMm(Math.abs(path.ops[0].to.dx(path.ops[1].to)), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Height</KeyTd>
<ValTd>
<RawSpan html={formatMm(Math.abs(path.ops[0].to.dy(path.ops[1].to)), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Draw Op</KeyTd>
<ValTd>
{i}/{ops.length}
</ValTd>
</Tr>
</tbody>
</table>
<div className="max-w-md" style={{ maxHeight: '80vh' }}>
<Curve {...props} />
</div>
</div>
</div>
)
export const Attributes = ({ list }) =>
list ? (
<ul>
{Object.keys(list).map((key) => (
<li key={key}>
<strong>{key}</strong>: {list[key]}
</li>
))}
</ul>
) : null
export const pathInfo = ({ pathName, path, partName, units, showInfo }) => {
const { t } = useTranslation(['workbench'])
const bbox = path.bbox()
return (
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">{t('pathInfo')}</h5>
<div className="flex flex-row flex-wrap overflow-scroll" style={{ maxHeight: '80vh' }}>
<div>
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>{t('name')}</KeyTd>
<ValTd>{pathName}</ValTd>
</Tr>
<Tr>
<KeyTd>{t('length')}</KeyTd>
<ValTd>
<RawSpan html={formatMm(path.length(), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>{t('width')}</KeyTd>
<ValTd>
<RawSpan html={formatMm(Math.abs(bbox.bottomRight.x - bbox.topLeft.x), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>{t('height')}</KeyTd>
<ValTd>
<RawSpan html={formatMm(Math.abs(bbox.bottomRight.y - bbox.topLeft.y), units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>{t('topLeft')}</KeyTd>
<ValTd>{pointCoords(bbox.topLeft)}</ValTd>
</Tr>
<Tr>
<KeyTd>{t('bottomRight')}</KeyTd>
<ValTd>{pointCoords(bbox.bottomRight)}</ValTd>
</Tr>
<Tr>
<KeyTd>{t('part')}</KeyTd>
<ValTd>{partName}</ValTd>
</Tr>
<Tr>
<KeyTd>{t('attributes')}</KeyTd>
<ValTd>
<Attributes list={path.attributes.list} />
</ValTd>
</Tr>
</tbody>
</table>
<div className="flex flex-row flex-wrap gap-2 mt-4">
<button className="btn btn-success" onClick={() => console.log(path)}>
console.log(path)
</button>
<button className="btn btn-success" onClick={() => console.table(path)}>
console.table(path)
</button>
</div>
</div>
<table className="border-collapse h-fit">
<tbody>
{path.ops.map((op, i) => (
<Tr key={i}>
<KeyTd>{i}</KeyTd>
<ValTd>
<PathOp op={op} />
</ValTd>
</Tr>
))}
</tbody>
</table>
<div className="max-w-md">
<MiniPath {...{ pathName, path, partName, units, showInfo }} />
</div>
</div>
</div>
)
}
const PathOp = ({ op }) => {
if (op.type === 'move')
return (
<span>
<strong>Move</strong> to {pointCoords(op.to)}
</span>
)
else if (op.type === 'line')
return (
<span>
<strong>Line</strong> to {pointCoords(op.to)}
</span>
)
else if (op.type === 'curve')
return (
<span>
<strong>Curve</strong> to {pointCoords(op.to)}
<br />
Cp1: {pointCoords(op.cp1)}
<br />
Cp2: {pointCoords(op.cp2)}
</span>
)
else if (op.type === 'noop') return <strong>NOOP</strong>
else if (op.type === 'close') return <strong>Close</strong>
else return <strong>FIXME: unknown path operation type: {op.type}</strong>
}
const XrayCurve = ({ i, path, ops, units, showInfo }) => {
const from = path.ops[0].to
const { cp1, cp2, to } = path.ops[1]
return (
<>
<path
d={path.asPathstring()}
{...getProps(path)}
className="opacity-0 stroke-4xl stroke-lining hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => showInfo(evt, curveInfo({ i, path, partName, units }))}
/>
<path d={`M ${from.x},${from.y} L ${cp1.x},${cp1.y}`} className="lining dotted" />
<path d={`M ${to.x},${to.y} L ${cp2.x},${cp2.y}`} className="lining dotted" />
<CpCircle point={cp1} />
<CpCircle point={cp2} />
<EpCircle point={from} />
<EpCircle point={to} />
</>
)
}
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 (
<g>
<path
d={path.asPathstring()}
{...getProps(path)}
className="opacity-0 stroke-7xl stroke-contrast hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => showInfo(evt, pathInfo({ pathName, path, partName, units }))}
markerStart="none"
markerEnd="none"
/>
{ops.length > 0
? ops.map((op, i) =>
op.ops[1].type === 'curve' ? (
<XrayCurve path={op} ops={ops} i={i} pathName={`${pathName}_test`} key={i} />
) : (
<XrayLine path={op} ops={ops} i={i} pathName={`${pathName}_test`} key={i} />
)
)
: null}
</g>
)
}
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(<path id={pathId} key={pathId} d={d} {...getProps(path)} />)
if (path.attributes.get('data-text'))
output.push(
<TextOnPath
key={'text-on-path-' + getId(pathId)}
pathId={pathId}
{...{ path, ui, showInfo }}
/>
)
if (ui.xray?.enabled) output.push(<XrayPath key={'xpath' + pathId} />)
return output
}

View file

@ -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 (
<nav className="grow mb-12">
{ui.inspect ? <Inspector {...menuProps} {...{ ui, inspector }} /> : null}
<DesignOptions {...menuProps} />
<CoreSettings {...menuProps} />
<UiSettings {...menuProps} ui={ui} />

View file

@ -255,146 +255,6 @@
svg.freesewing.pattern circle.fill-note {
@apply fs-fill-note;
}
.fs-stroke-color-0 {
stroke: var(--pattern-color-0);
}
.fs-stroke-color-1 {
stroke: var(--pattern-color-1);
}
.fs-stroke-color-2 {
stroke: var(--pattern-color-2);
}
.fs-stroke-color-3 {
stroke: var(--pattern-color-3);
}
.fs-stroke-color-4 {
stroke: var(--pattern-color-4);
}
.fs-stroke-color-5 {
stroke: var(--pattern-color-5);
}
.fs-stroke-color-6 {
stroke: var(--pattern-color-6);
}
.fs-stroke-color-7 {
stroke: var(--pattern-color-7);
}
.fs-stroke-color-8 {
stroke: var(--pattern-color-8);
}
.fs-stroke-color-9 {
stroke: var(--pattern-color-9);
}
svg.freesewing.pattern circle.stroke-color-0,
svg.freesewing.pattern path.stroke-color-0 {
@apply fs-stroke-color-0;
}
svg.freesewing.pattern circle.stroke-color-1,
svg.freesewing.pattern path.stroke-color-1 {
@apply fs-stroke-color-1;
}
svg.freesewing.pattern circle.stroke-color-2,
svg.freesewing.pattern path.stroke-color-2 {
@apply fs-stroke-color-2;
}
svg.freesewing.pattern circle.stroke-color-3,
svg.freesewing.pattern path.stroke-color-3 {
@apply fs-stroke-color-3;
}
svg.freesewing.pattern circle.stroke-color-4,
svg.freesewing.pattern path.stroke-color-4 {
@apply fs-stroke-color-4;
}
svg.freesewing.pattern circle.stroke-color-5,
svg.freesewing.pattern path.stroke-color-5 {
@apply fs-stroke-color-5;
}
svg.freesewing.pattern circle.stroke-color-6,
svg.freesewing.pattern path.stroke-color-6 {
@apply fs-stroke-color-6;
}
svg.freesewing.pattern circle.stroke-color-7,
svg.freesewing.pattern path.stroke-color-7 {
@apply fs-stroke-color-7;
}
svg.freesewing.pattern circle.stroke-color-8,
svg.freesewing.pattern path.stroke-color-8 {
@apply fs-stroke-color-8;
}
svg.freesewing.pattern circle.stroke-color-9,
svg.freesewing.pattern path.stroke-color-9 {
@apply fs-stroke-color-9;
}
.fs-fill-color-0 {
fill: var(--pattern-color-0);
}
.fs-fill-color-1 {
fill: var(--pattern-color-1);
}
.fs-fill-color-2 {
fill: var(--pattern-color-2);
}
.fs-fill-color-3 {
fill: var(--pattern-color-3);
}
.fs-fill-color-4 {
fill: var(--pattern-color-4);
}
.fs-fill-color-5 {
fill: var(--pattern-color-5);
}
.fs-fill-color-6 {
fill: var(--pattern-color-6);
}
.fs-fill-color-7 {
fill: var(--pattern-color-7);
}
.fs-fill-color-8 {
fill: var(--pattern-color-8);
}
.fs-fill-color-9 {
fill: var(--pattern-color-9);
}
svg.freesewing.pattern circle.fill-color-0,
svg.freesewing.pattern path.fill-color-0 {
@apply fs-fill-color-0;
}
svg.freesewing.pattern circle.fill-color-1,
svg.freesewing.pattern path.fill-color-1 {
@apply fs-fill-color-1;
}
svg.freesewing.pattern circle.fill-color-2,
svg.freesewing.pattern path.fill-color-2 {
@apply fs-fill-color-2;
}
svg.freesewing.pattern circle.fill-color-3,
svg.freesewing.pattern path.fill-color-3 {
@apply fs-fill-color-3;
}
svg.freesewing.pattern circle.fill-color-4,
svg.freesewing.pattern path.fill-color-4 {
@apply fs-fill-color-4;
}
svg.freesewing.pattern circle.fill-color-5,
svg.freesewing.pattern path.fill-color-5 {
@apply fs-fill-color-5;
}
svg.freesewing.pattern circle.fill-color-6,
svg.freesewing.pattern path.fill-color-6 {
@apply fs-fill-color-6;
}
svg.freesewing.pattern circle.fill-color-7,
svg.freesewing.pattern path.fill-color-7 {
@apply fs-fill-color-7;
}
svg.freesewing.pattern circle.fill-color-8,
svg.freesewing.pattern path.fill-color-8 {
@apply fs-fill-color-8;
}
svg.freesewing.pattern circle.fill-color-9,
svg.freesewing.pattern path.fill-color-9 {
@apply fs-fill-color-9;
}
.fs-fill-bg {
fill: var(--pattern-bg);
@ -414,6 +274,22 @@
.fill-opacity-50 {
fill-opacity: 0.5;
}
.pulse-fill {
fill: currentColor;
animation: pulsefill 3s infinite;
}
}
@keyframes pulsefill {
0% {
fill-opacity: 0;
}
50% {
fill-opacity: 0.3;
}
100% {
fill-opacity: 0;
}
}
/* Override DaisyUI button text color */