import React, { useState } from 'react' import { getProps, defaultComponents as patternComponents, } from '@freesewing/react/components/Pattern' import { H5, H6 } from '@freesewing/react/components/Heading' import { KeyVal } from '@freesewing/react/components/KeyVal' import { Highlight } from '@freesewing/react/components/Highlight' import { round, pathLength } from '@freesewing/utils' const coords = (point) => `${point.x},${point.y}` const Cp = ({ at }) => ( <circle cx={at.x} cy={at.y} r={0.75} className="stroke-md opacity-50 text-warning" /> ) export const PathXray = ({ stackName, pathName, part, path, settings, components, strings, drillProps = {}, }) => { /* * We use the Path component from Pattern here * If we would extract Path from the components passed down, * we'd create a recursion loop as the Path we call below * would be this very PathXray component. */ const { Path } = patternComponents const { info = {} } = drillProps const output = [] let prev let i = 0 for (const op of path.ops) { if (op.type === 'curve') { output.push( <Cp at={op.cp1} key={`${i}-cp1`} />, <Cp at={op.cp2} key={`${i}-cp2`} />, <path key={i} d={`M ${coords(prev.to)} L ${coords(op.cp1)} M ${coords(op.to)} L ${coords(op.cp2)}`} className={`text-warning stroke-sm dashed opacity-50`} style={{ stroke: 'var(--pattern-lining)', strokeWidth: '0.666', }} strokeDasharray="10 5" markerStart="none" markerEnd="none" /> ) } prev = op i++ } output.push( <path key="path" d={path.d} {...getProps(path)} markerStart="none" markerEnd="none" />, <path key="hovertrap" d={path.d} style={{ stroke: 'var(--pattern-lining)', strokeWidth: '5', }} strokeWidth="12" strokeDasharray="20 10" className="tw:hover:cursor-pointer tw:opacity-0 tw:hover:opacity-30" onClick={() => info?.set ? info.set(<PathXrayInfo {...{ path, pathName, part, stackName }} />) : null } > <animate attributeName="stroke-dashoffset" from="0" to="30" dur="2s" repeatCount="indefinite" /> </path> ) return ( <g> {output} <Path {...{ stackName, pathName, path, part, settings, components, strings, drillProps }} /> </g> ) } const PathXrayInfo = ({ path, pathName, stackName, part }) => { const [rounded, setRounded] = useState(true) const log = (val) => console.log(val) const rounder = rounded ? round : (val) => val return ( <div className="tw:max-w-2xl"> <H5> Path <code>{pathName}</code> of <code>{stackName}</code> </H5> {Object.keys(path.attributes.list).length > 0 ? ( <> <H6>Attributes</H6> <div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> {Object.entries(path.attributes.list).map(([k, val]) => ( <KeyVal color="secondary" key={k} k={k} val={val} /> ))} <KeyVal color={path.hidden ? 'error' : 'success'} k="hidden" val={path.hidden ? 'yes' : 'no'} /> </div> </> ) : null} <H6>Dimensions</H6> <table className="tw:table tw:table-auto tw:font-fixed tw:w-full"> <thead> <tr> <th>Type</th> <th className="tw:flex tw:flex-row tw:flex-wrap tw:items-center tw:justify-between"> <span>Coordinates</span> <button className="tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-sm tw:daisy-btn-outline" onClick={() => setRounded(!rounded)} > {rounded ? 'Show raw' : 'Show rounded'} </button> </th> </tr> </thead> <tbody> <tr> <td className="tw:text-right tw:font-bold tw:w-16">TopLeft</td> <td className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> <KeyVal k="x" val={rounder(path.topLeft.x)} /> <KeyVal k="y" val={rounder(path.topLeft.y)} /> </td> </tr> <tr> <td className="tw:text-right tw:font-bold tw:w-16">BottomRight</td> <td className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> <KeyVal k="x" val={rounder(path.bottomRight.x)} /> <KeyVal k="y" val={rounder(path.bottomRight.y)} /> </td> </tr> <tr> <td className="tw:text-right tw:font-bold tw:w-16">Width</td> <td> <KeyVal k="mm" val={rounder(path.width)} /> </td> </tr> <tr> <td className="tw:text-right tw:font-bold tw:w-16">Height</td> <td> <KeyVal k="mm" val={rounder(path.height)} /> </td> </tr> <tr> <td className="tw:text-right tw:font-bold tw:w-16">Path Length</td> <td> <KeyVal k="mm" val={rounder(pathLength(path))} /> </td> </tr> </tbody> </table> <H6>Drawing operations</H6> <table className="tw:table tw:table-auto tw:font-fixed tw:w-full"> <thead> <tr> <th>Type</th> <th className="tw:flex tw:flex-row tw:flex-wrap tw:items-center tw:justify-between"> <span>Coordinates</span> <button className="tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-sm tw:daisy-btn-outline" onClick={() => setRounded(!rounded)} > {rounded ? 'Show raw' : 'Show rounded'} </button> </th> </tr> </thead> <tbody> {path.ops.map((op, i) => ( <tr key={i}> <td className="tw:text-right tw:font-bold tw:w-16">{op.type}</td> {['move', 'line'].includes(op.type) ? ( <td className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> <b className="tw:text-xs tw:opacity-80 tw:block tw:w-8">To:</b> <KeyVal k="x" val={rounder(op.to.x)} /> <KeyVal k="y" val={rounder(op.to.y)} /> </td> ) : null} {op.type === 'close' ? <td></td> : null} {op.type === 'curve' ? ( <td className="tw:flex tw:flex-col tw:gap-1"> <div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> <b className="tw:text-xs tw:opacity-80 tw:block tw:w-8">Cp1:</b> <KeyVal k="x" val={rounder(op.cp1.x)} /> <KeyVal k="y" val={rounder(op.cp1.y)} /> </div> <div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> <b className="tw:text-xs tw:opacity-80 tw:block tw:w-8">Cp2:</b> <KeyVal k="x" val={rounder(op.cp2.x)} /> <KeyVal k="y" val={rounder(op.cp2.y)} /> </div> <div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:items-center"> <b className="tw:text-xs tw:opacity-80 tw:block tw:w-8">To:</b> <KeyVal k="x" val={rounder(op.to.x)} /> <KeyVal k="y" val={rounder(op.to.y)} /> </div> </td> ) : null} </tr> ))} </tbody> </table> <H6>Pathstring</H6> <Highlight>{path.d}</Highlight> </div> ) }