wip: Xray view for editor
This commit is contained in:
parent
4b6944b72f
commit
18a91edd89
10 changed files with 393 additions and 89 deletions
|
@ -1,54 +0,0 @@
|
||||||
// Hooks
|
|
||||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
|
||||||
// Components
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { Mdx } from 'shared/components/mdx/dynamic.mjs'
|
|
||||||
import { HeartIcon } from 'shared/components/icons.mjs'
|
|
||||||
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
|
||||||
|
|
||||||
export const ns = ['account']
|
|
||||||
|
|
||||||
export const Avatar = ({ img, app = false }) => (
|
|
||||||
<div className={`mask mask-squircle bg-neutral z-10 ${app ? 'w-24' : 'w-full'}`}>
|
|
||||||
<img
|
|
||||||
src={img}
|
|
||||||
onClick={
|
|
||||||
app
|
|
||||||
? () =>
|
|
||||||
app.setModal(
|
|
||||||
<ModalWrapper app={app}>
|
|
||||||
<Avatar img={img} />
|
|
||||||
</ModalWrapper>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
className={app ? 'hover:cursor-zoom-in' : 'hover:cursor-zoom-out'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const AccountProfile = ({ app }) => {
|
|
||||||
const { account } = useAccount()
|
|
||||||
|
|
||||||
if (!account) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="my-8">
|
|
||||||
<div className="flex flex-row w-full justify-center">
|
|
||||||
<Avatar img={account.img} app={app} />
|
|
||||||
{!account.patron ? (
|
|
||||||
<Link href="/patrons/join" className="z-20">
|
|
||||||
<HeartIcon className="w-12 h-12 -ml-8 mt-2 stroke-base-100 fill-accent" stroke={1} />
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<h2 className="text-center">{account.username}</h2>
|
|
||||||
<div className="flex flex-row">
|
|
||||||
<div className="avatar -mt-6 -ml-8 flex flex-row items-end"></div>
|
|
||||||
</div>
|
|
||||||
<div className="max-w-full truncate">
|
|
||||||
<Mdx md={account.bio} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Dependencies
|
||||||
|
import React, { useContext } from 'react'
|
||||||
|
import { draft, missingMeasurements } from '../../lib/index.mjs'
|
||||||
|
// Context
|
||||||
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
|
// Components
|
||||||
|
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||||
|
import { Null } from '../Null.mjs'
|
||||||
|
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||||
|
import { PatternLayout } from '../PatternLayout.mjs'
|
||||||
|
import { DraftMenu } from '../menus/DraftMenu.mjs'
|
||||||
|
import { Xray } from '@freesewing/react/components/Xray'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inspect view allows users to inspect their pattern
|
||||||
|
*
|
||||||
|
* @param (object) props - All the props
|
||||||
|
* @param {function} props.config - The editor configuration
|
||||||
|
* @param {function} props.Design - The design constructor
|
||||||
|
* @param {array} props.missingMeasurements - List of missing measurements for the current design
|
||||||
|
* @param {object} props.state - The ViewWrapper state object
|
||||||
|
* @param {object} props.state.settings - The current settings
|
||||||
|
* @param {object} props.update - Helper object for updating the ViewWrapper state
|
||||||
|
* @return {function} DraftView - React component
|
||||||
|
*/
|
||||||
|
export const InspectView = ({ Design, state, update, config }) => {
|
||||||
|
const { setModal, clearModal, modalContent } = useContext(ModalContext)
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
set: (info) => setModal(<ModalWrapper keepOpenOnClick>{info}</ModalWrapper>),
|
||||||
|
clear: clearModal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Don't trust that we have all measurements
|
||||||
|
*
|
||||||
|
* We do not need to change the view here. That is done in the central
|
||||||
|
* ViewWrapper componenet. However, checking the measurements against
|
||||||
|
* the design takes a brief moment, so this component will typically
|
||||||
|
* render before that happens, and if measurements are missing it will
|
||||||
|
* throw and error.
|
||||||
|
*
|
||||||
|
* So when measurements are missing, we just return here and the view
|
||||||
|
* will switch on the next render loop.
|
||||||
|
*/
|
||||||
|
if (missingMeasurements(state)) return <Null />
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First, attempt to draft
|
||||||
|
*/
|
||||||
|
const { pattern } = draft(Design, state.settings)
|
||||||
|
|
||||||
|
const renderProps = pattern.getRenderProps()
|
||||||
|
const output = (
|
||||||
|
<>
|
||||||
|
<ZoomablePattern
|
||||||
|
renderProps={renderProps}
|
||||||
|
patternLocale={state.locale || 'en'}
|
||||||
|
rotate={state.ui.rotate}
|
||||||
|
>
|
||||||
|
<Xray
|
||||||
|
renderProps={renderProps}
|
||||||
|
drillProps={{ info }}
|
||||||
|
className={`freesewing pattern tw-w-full ${state.ui?.rotate ? 'tw--rotate-90' : ''}`}
|
||||||
|
/>
|
||||||
|
</ZoomablePattern>
|
||||||
|
{modalContent}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
return <PatternLayout {...{ update, Design, output, state, pattern, config }} />
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { ViewPicker } from './ViewPicker.mjs'
|
||||||
import { DesignsView } from './DesignsView.mjs'
|
import { DesignsView } from './DesignsView.mjs'
|
||||||
import { MeasurementsView } from './MeasurementsView.mjs'
|
import { MeasurementsView } from './MeasurementsView.mjs'
|
||||||
import { DraftView } from './DraftView.mjs'
|
import { DraftView } from './DraftView.mjs'
|
||||||
|
import { InspectView } from './InspectView.mjs'
|
||||||
import { SaveView } from './SaveView.mjs'
|
import { SaveView } from './SaveView.mjs'
|
||||||
import { ExportView } from './ExportView.mjs'
|
import { ExportView } from './ExportView.mjs'
|
||||||
import { UndosView } from './UndosView.mjs'
|
import { UndosView } from './UndosView.mjs'
|
||||||
|
@ -63,6 +64,7 @@ export const View = (props) => {
|
||||||
if (view === 'layout') return <LayoutView {...props} />
|
if (view === 'layout') return <LayoutView {...props} />
|
||||||
if (view === 'docs') return <DocsView {...props} />
|
if (view === 'docs') return <DocsView {...props} />
|
||||||
if (view === 'editSettings') return <EditSettingsView {...props} />
|
if (view === 'editSettings') return <EditSettingsView {...props} />
|
||||||
|
if (view === 'inspect') return <InspectView {...props} />
|
||||||
|
|
||||||
return <h1 className="tw-ext-center tw-my-12">No view component for view {props.view}</h1>
|
return <h1 className="tw-ext-center tw-my-12">No view component for view {props.view}</h1>
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { View } from './components/views/index.mjs'
|
||||||
import { Spinner } from '@freesewing/react/components/Spinner'
|
import { Spinner } from '@freesewing/react/components/Spinner'
|
||||||
import { AsideViewMenu } from './components/AsideViewMenu.mjs'
|
import { AsideViewMenu } from './components/AsideViewMenu.mjs'
|
||||||
import { LoadingStatus } from './components/LoadingStatus.mjs'
|
import { LoadingStatus } from './components/LoadingStatus.mjs'
|
||||||
|
import { ModalContextProvider } from '@freesewing/react/context/Modal'
|
||||||
|
import { LoadingStatusContextProvider } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FreeSewing's pattern editor
|
* FreeSewing's pattern editor
|
||||||
|
@ -93,11 +95,15 @@ export const Editor = ({ config = {}, design = false, preload = {} }) => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<LoadingStatus state={passDownState} update={update} />
|
<LoadingStatus state={passDownState} update={update} />
|
||||||
<View
|
<ModalContextProvider>
|
||||||
{...extraProps}
|
<LoadingStatusContextProvider>
|
||||||
{...{ view, update, designs, config: editorConfig }}
|
<View
|
||||||
state={passDownState}
|
{...extraProps}
|
||||||
/>
|
{...{ view, update, designs, config: editorConfig }}
|
||||||
|
state={passDownState}
|
||||||
|
/>
|
||||||
|
</LoadingStatusContextProvider>
|
||||||
|
</ModalContextProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React, { forwardRef } from 'react'
|
||||||
import { getId, getProps } from './utils.mjs'
|
import { getId, getProps } from './utils.mjs'
|
||||||
|
|
||||||
export const PartInner = forwardRef(
|
export const PartInner = forwardRef(
|
||||||
({ stackName, partName, part, settings, components, t }, ref) => {
|
({ stackName, partName, part, settings, components, t, drillProps }, ref) => {
|
||||||
const { Group, Path, Point, Snippet } = components
|
const { Group, Path, Point, Snippet } = components
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -16,7 +16,7 @@ export const PartInner = forwardRef(
|
||||||
topLeft={part.topLeft}
|
topLeft={part.topLeft}
|
||||||
bottomRight={part.bottomRight}
|
bottomRight={part.bottomRight}
|
||||||
units={settings[0].units}
|
units={settings[0].units}
|
||||||
{...{ stackName, partName, pathName, part, settings, components, t }}
|
{...{ stackName, partName, pathName, part, settings, components, t, drillProps }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{Object.keys(part.points).map((pointName) => (
|
{Object.keys(part.points).map((pointName) => (
|
||||||
|
@ -25,14 +25,14 @@ export const PartInner = forwardRef(
|
||||||
point={part.points[pointName]}
|
point={part.points[pointName]}
|
||||||
topLeft={part.topLeft}
|
topLeft={part.topLeft}
|
||||||
bottomRight={part.bottomRight}
|
bottomRight={part.bottomRight}
|
||||||
{...{ stackName, partName, pointName, part, settings, components, t }}
|
{...{ stackName, partName, pointName, part, settings, components, t, drillProps }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{Object.keys(part.snippets).map((snippetName) => (
|
{Object.keys(part.snippets).map((snippetName) => (
|
||||||
<Snippet
|
<Snippet
|
||||||
key={snippetName}
|
key={snippetName}
|
||||||
snippet={part.snippets[snippetName]}
|
snippet={part.snippets[snippetName]}
|
||||||
{...{ stackName, partName, snippetName, part, settings, components, t }}
|
{...{ stackName, partName, snippetName, part, settings, components, t, drillProps }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -42,12 +42,12 @@ export const PartInner = forwardRef(
|
||||||
|
|
||||||
PartInner.displayName = 'PartInner'
|
PartInner.displayName = 'PartInner'
|
||||||
|
|
||||||
export const Part = ({ stackName, partName, part, settings, components, t }) => {
|
export const Part = ({ stackName, partName, part, settings, components, t, drillProps }) => {
|
||||||
const { Group } = components
|
const { Group } = components
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group {...getProps(part)} id={getId({ settings, stackName, partName })}>
|
<Group {...getProps(part)} id={getId({ settings, stackName, partName })}>
|
||||||
<PartInner {...{ stackName, partName, part, settings, components, t }} />
|
<PartInner {...{ stackName, partName, part, settings, components, t, drillProps }} />
|
||||||
</Group>
|
</Group>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { getProps } from './utils.mjs'
|
import { getProps } from './utils.mjs'
|
||||||
|
|
||||||
export const Stack = ({ stackName, stack, settings, components, t }) => {
|
export const Stack = ({ stackName, stack, settings, components, t, drillProps }) => {
|
||||||
const { Group, Part, Grid } = components
|
const { Group, Part, Grid } = components
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group {...getProps(stack)}>
|
<Group {...getProps(stack)}>
|
||||||
{settings[0].paperless ? <Grid {...{ stack, stackName }} /> : null}
|
{settings[0].paperless ? <Grid {...{ stack, stackName, drillProps }} /> : null}
|
||||||
{[...stack.parts].map((part, key) => (
|
{[...stack.parts].map((part, key) => (
|
||||||
<Part {...{ settings, components, t, part, stackName }} key={key} />
|
<Part {...{ settings, components, t, part, stackName, drillProps }} key={key} />
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const Xray = forwardRef((props, ref) => {
|
||||||
children = false,
|
children = false,
|
||||||
className = 'freesewing pattern',
|
className = 'freesewing pattern',
|
||||||
components = {},
|
components = {},
|
||||||
|
drillProps = {},
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
// Merge pattern, default, and custom components
|
// Merge pattern, default, and custom components
|
||||||
|
@ -31,7 +32,6 @@ export const Xray = forwardRef((props, ref) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Svg, Defs, Stack, Group } = mergedComponents
|
const { Svg, Defs, Stack, Group } = mergedComponents
|
||||||
|
|
||||||
const optionalProps = {}
|
const optionalProps = {}
|
||||||
if (className) optionalProps.className = className
|
if (className) optionalProps.className = className
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ export const Xray = forwardRef((props, ref) => {
|
||||||
settings={renderProps.settings}
|
settings={renderProps.settings}
|
||||||
components={mergedComponents}
|
components={mergedComponents}
|
||||||
t={t}
|
t={t}
|
||||||
|
drillProps={drillProps}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
getProps,
|
getProps,
|
||||||
defaultComponents as patternComponents,
|
defaultComponents as patternComponents,
|
||||||
} from '@freesewing/react/components/Pattern'
|
} 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 coords = (point) => `${point.x},${point.y}`
|
||||||
|
|
||||||
|
@ -10,7 +14,25 @@ const Cp = ({ at }) => (
|
||||||
<circle cx={at.x} cy={at.y} r={0.75} className="stroke-md opacity-50 text-warning" />
|
<circle cx={at.x} cy={at.y} r={0.75} className="stroke-md opacity-50 text-warning" />
|
||||||
)
|
)
|
||||||
|
|
||||||
export const Xray = ({ path, components }) => {
|
export const PathXray = ({
|
||||||
|
stackName,
|
||||||
|
pathName,
|
||||||
|
part,
|
||||||
|
path,
|
||||||
|
settings,
|
||||||
|
components,
|
||||||
|
t,
|
||||||
|
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 = []
|
const output = []
|
||||||
let prev
|
let prev
|
||||||
let i = 0
|
let i = 0
|
||||||
|
@ -23,6 +45,11 @@ export const Xray = ({ path, components }) => {
|
||||||
key={i}
|
key={i}
|
||||||
d={`M ${coords(prev.to)} L ${coords(op.cp1)} M ${coords(op.to)} L ${coords(op.cp2)}`}
|
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`}
|
className={`text-warning stroke-sm dashed opacity-50`}
|
||||||
|
style={{
|
||||||
|
stroke: 'var(--pattern-lining)',
|
||||||
|
strokeWidth: '0.666',
|
||||||
|
}}
|
||||||
|
strokeDasharray="10 5"
|
||||||
markerStart="none"
|
markerStart="none"
|
||||||
markerEnd="none"
|
markerEnd="none"
|
||||||
/>
|
/>
|
||||||
|
@ -32,24 +59,168 @@ export const Xray = ({ path, components }) => {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
output.push(
|
output.push(
|
||||||
<path key="path" d={path.d} {...getProps(path)} markerStart="none" markerEnd="none" />
|
<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="hover:tw-cursor-pointer tw-opacity-0 hover:tw-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 output
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PathXray = ({ stackName, pathName, part, path, settings, components, t }) => {
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
<Xray path={path} />
|
{output}
|
||||||
<Path {...{ stackName, pathName, path, part, settings, components, t }} />
|
<Path {...{ stackName, pathName, path, part, settings, components, t, drillProps }} />
|
||||||
</g>
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
withinPartBounds,
|
withinPartBounds,
|
||||||
defaultComponents as patternComponents,
|
defaultComponents as patternComponents,
|
||||||
} from '@freesewing/react/components/Pattern'
|
} from '@freesewing/react/components/Pattern'
|
||||||
|
import { H5, H6 } from '@freesewing/react/components/Heading'
|
||||||
|
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
||||||
|
import { round } from '@freesewing/utils'
|
||||||
|
|
||||||
export const PointXray = ({ stackName, pointName, part, point, settings, components, t }) => {
|
export const PointXray = ({
|
||||||
|
stackName,
|
||||||
|
pointName,
|
||||||
|
part,
|
||||||
|
point,
|
||||||
|
settings,
|
||||||
|
components,
|
||||||
|
t,
|
||||||
|
drillProps = {},
|
||||||
|
}) => {
|
||||||
// Don't include parts outside the part bounding box
|
// Don't include parts outside the part bounding box
|
||||||
if (!withinPartBounds(point, part)) return null
|
if (!withinPartBounds(point, part)) return null
|
||||||
|
|
||||||
|
const { info = {} } = drillProps
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We use the Point component from Pattern here
|
* We use the Point component from Pattern here
|
||||||
* If we would extract Point from the components passed down,
|
* If we would extract Point from the components passed down,
|
||||||
|
@ -15,15 +29,80 @@ export const PointXray = ({ stackName, pointName, part, point, settings, compone
|
||||||
* would be this very PointXray component.
|
* would be this very PointXray component.
|
||||||
*/
|
*/
|
||||||
const { Point } = patternComponents
|
const { Point } = patternComponents
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Point {...{ stackName, pointName, part, point, settings, components, t }} />
|
<Point {...{ stackName, pointName, part, point, settings, components, t, drillProps }} />
|
||||||
<circle
|
<circle
|
||||||
cx={point.x}
|
cx={point.x}
|
||||||
cy={point.y}
|
cy={point.y}
|
||||||
r={1.5 * (settings.scale || 1)}
|
r={1.5 * (settings.scale || 1)}
|
||||||
className="stroke-md opacity-70"
|
className="tw-opacity-70 hover:tw-cursor-pointer hover:tw-stroke-lg"
|
||||||
|
style={{
|
||||||
|
stroke: 'var(--pattern-contrast)',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
<g className="hover:tw-cursor-pointer tw-opacity-0 hover:tw-opacity-100">
|
||||||
|
<circle
|
||||||
|
cx={point.x}
|
||||||
|
cy={point.y}
|
||||||
|
r={5 * (settings.scale || 1)}
|
||||||
|
style={{
|
||||||
|
stroke: 'none',
|
||||||
|
fill: 'var(--pattern-lining)',
|
||||||
|
}}
|
||||||
|
className="hover:tw-cursor-pointer tw-opacity-0 hover:tw-opacity-30"
|
||||||
|
onClick={() =>
|
||||||
|
info?.set
|
||||||
|
? info.set(<PointXrayInfo {...{ point, pointName, part, stackName }} />)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
></circle>
|
||||||
|
<text x={point.x + 3} y={point.y} className="text-sm">
|
||||||
|
<tspan>{pointName}</tspan>
|
||||||
|
<tspan x={point.x + 3} dy={5}>
|
||||||
|
{round(point.x)},{round(point.y)}
|
||||||
|
</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PointXrayInfo = ({ point, pointName, stackName, part }) => {
|
||||||
|
const [rounded, setRounded] = useState(true)
|
||||||
|
const rounder = rounded ? round : (val) => val
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tw-max-w-2xl tw-w-full">
|
||||||
|
<H5>
|
||||||
|
Point <code>{pointName}</code> of <code>{stackName}</code>
|
||||||
|
</H5>
|
||||||
|
<H6>
|
||||||
|
<div className="tw-w-full tw-flex tw-flex-row tw-items-center tw-gap-1 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>
|
||||||
|
</div>
|
||||||
|
</H6>
|
||||||
|
<div className="tw-flex tw-flex-row tw-flex-wrap tw-gap-1 tw-items-center">
|
||||||
|
<KeyVal k="x" val={rounder(point.x)} />
|
||||||
|
<KeyVal k="y" val={rounder(point.y)} />
|
||||||
|
</div>
|
||||||
|
{Object.keys(point.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(point.attributes.list).map(([k, val]) => (
|
||||||
|
<KeyVal color="secondary" key={k} k={k} val={val} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import _set from 'lodash/set.js'
|
||||||
import _unset from 'lodash/unset.js'
|
import _unset from 'lodash/unset.js'
|
||||||
import _orderBy from 'lodash/orderBy.js'
|
import _orderBy from 'lodash/orderBy.js'
|
||||||
import { loadingMessages } from './loading-messages.mjs'
|
import { loadingMessages } from './loading-messages.mjs'
|
||||||
|
import { Path, Point } from '@freesewing/core'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Re-export lodash utils
|
* Re-export lodash utils
|
||||||
|
@ -351,6 +352,32 @@ export const mutateObject = (obj = {}, path, val = 'unset') => {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This calculates teh length of a path that is obtained from renderprops
|
||||||
|
*
|
||||||
|
* In other words, a plain POJO with the path data, and not an instantiated Path object from core.
|
||||||
|
* This is useful if you want to know the path length after rendering.
|
||||||
|
*
|
||||||
|
* @param {object} path - A path object as available from renderProps
|
||||||
|
* @return {number} length - The path length in mm
|
||||||
|
*/
|
||||||
|
export function pathLength(path) {
|
||||||
|
let p = new Path()
|
||||||
|
for (const op of path.ops) {
|
||||||
|
if (op.type === 'move') p = p.move(new Point(op.to.x, op.to.y))
|
||||||
|
if (op.type === 'line') p = p.line(new Point(op.to.x, op.to.y))
|
||||||
|
if (op.type === 'curve')
|
||||||
|
p = p.curve(
|
||||||
|
new Point(op.cp1.x, op.cp1.y),
|
||||||
|
new Point(op.cp2.x, op.cp2.y),
|
||||||
|
new Point(op.to.x, op.to.y)
|
||||||
|
)
|
||||||
|
if (op.type === 'close') p = p.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.length()
|
||||||
|
}
|
||||||
|
|
||||||
/** Generate a URL to create a new pattern with a given design, settings, and view */
|
/** Generate a URL to create a new pattern with a given design, settings, and view */
|
||||||
export const patternUrlFromState = (state = {}, includeMeasurements = false, view = 'draft') => {
|
export const patternUrlFromState = (state = {}, includeMeasurements = false, view = 'draft') => {
|
||||||
// Avoid changing state by accident
|
// Avoid changing state by accident
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue