feat(fs.lab): LabDraft component
This commit is contained in:
parent
83ee572a36
commit
82c4242088
11 changed files with 443 additions and 0 deletions
|
@ -0,0 +1,10 @@
|
|||
const Circle = (props) => (
|
||||
<circle
|
||||
cx={props.point.x}
|
||||
cy={props.point.y}
|
||||
r={props.point.attributes.get('data-circle')}
|
||||
{...props.point.attributes.asPropsIfPrefixIs('data-circle-')}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Circle
|
|
@ -0,0 +1,69 @@
|
|||
const style = ` style="fill: none; stroke: currentColor;" `
|
||||
const grids = {
|
||||
imperial: `
|
||||
<pattern id="grid" height="25.4" width="25.4" patternUnits="userSpaceOnUse" key="grid">
|
||||
<path class="gridline lg imperial" d="M 0 0 L 0 25.4 L 25.4 25.4" ${style} />
|
||||
<path
|
||||
class="gridline lg imperial"
|
||||
d="M 12.7 0 L 12.7 25.4 M 0 12.7 L 25.4 12.7"
|
||||
${style}
|
||||
/>
|
||||
<path
|
||||
class="gridline sm imperial"
|
||||
d="M 3.175 0 L 3.175 25.4 M 6.32 0 L 6.35 25.4 M 9.525 0 L 9.525 25.4 M 15.875 0 L 15.875 25.4 M 19.05 0 L 19.05 25.4 M 22.225 0 L 22.225 25.4"
|
||||
${style}
|
||||
/>
|
||||
<path
|
||||
class="gridline sm imperial"
|
||||
d="M 0 3.175 L 25.4 3.175 M 0 6.32 L 25.4 6.35 M 0 9.525 L 25.4 9.525 M 0 15.875 L 25.4 15.875 M 0 19.05 L 25.4 19.05 M 0 22.225 L 25.4 22.225"
|
||||
${style}
|
||||
/>
|
||||
</pattern>
|
||||
`,
|
||||
metric: `
|
||||
<pattern id="grid" height="100" width="100" patternUnits="userSpaceOnUse" key="grid">
|
||||
<path class="gridline lg metric" d="M 0 0 L 0 100 L 100 100" ${style} />
|
||||
<path class="gridline metric" d="M 50 0 L 50 100 M 0 50 L 100 50" ${style} />
|
||||
<path
|
||||
class="gridline sm metric"
|
||||
d="M 10 0 L 10 100 M 20 0 L 20 100 M 30 0 L 30 100 M 40 0 L 40 100 M 60 0 L 60 100 M 70 0 L 70 100 M 80 0 L 80 100 M 90 0 L 90 100"
|
||||
${style}
|
||||
/>
|
||||
<path
|
||||
class="gridline sm metric"
|
||||
d="M 0 10 L 100 10 M 0 20 L 100 20 M 0 30 L 100 30 M 0 40 L 100 40 M 0 60 L 100 60 M 0 70 L 100 70 M 0 80 L 100 80 M 0 90 L 100 90"
|
||||
${style}
|
||||
/>
|
||||
<path
|
||||
class="gridline xs metric"
|
||||
d="M 5 0 L 5 100 M 15 0 L 15 100 M 25 0 L 25 100 M 35 0 L 35 100 M 45 0 L 45 100 M 55 0 L 55 100 M 65 0 L 65 100 M 75 0 L 75 100 M 85 0 L 85 100 M 95 0 L 95 100"
|
||||
${style}
|
||||
/>
|
||||
<path
|
||||
class="gridline xs metric"
|
||||
d="M 0 5 L 100 5 M 0 15 L 100 15 M 0 25 L 100 25 M 0 35 L 100 35 M 0 45 L 100 45 M 0 55 L 100 55 M 0 65 L 100 65 M 0 75 L 100 75 M 0 85 L 100 85 M 0 95 L 100 95"
|
||||
${style}
|
||||
/>
|
||||
</pattern>
|
||||
`
|
||||
}
|
||||
|
||||
const Defs = (props) => {
|
||||
let defs = props.svg.defs
|
||||
if (props.settings.paperless) {
|
||||
defs += grids[props.settings.units || 'metric']
|
||||
for (let p in props.parts) {
|
||||
let anchor = { x: 0, y: 0 }
|
||||
if (typeof props.parts[p].points.gridAnchor !== 'undefined')
|
||||
anchor = props.parts[p].points.gridAnchor
|
||||
else if (typeof props.parts[p].points.anchor !== 'undefined')
|
||||
anchor = props.parts[p].points.anchor
|
||||
|
||||
defs += `<pattern id="grid-${p}" key="grid-${p}" xlink:href="#grid" x="${anchor.x}" y="${anchor.y}" />`
|
||||
}
|
||||
}
|
||||
|
||||
return <defs dangerouslySetInnerHTML={{ __html: defs }} />
|
||||
}
|
||||
|
||||
export default Defs
|
|
@ -0,0 +1,35 @@
|
|||
import React, { useState } from 'react'
|
||||
import Svg from './svg'
|
||||
import Defs from './defs'
|
||||
import Part from './part'
|
||||
|
||||
const LabDraft = ({ app, pattern, gist, updateGist }) => {
|
||||
|
||||
const patternInstance = new pattern(gist)
|
||||
patternInstance.draft()
|
||||
const patternProps = patternInstance.getRenderProps()
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Svg {...patternProps}>
|
||||
<Defs {...patternProps} />
|
||||
<style>{`:root { --pattern-scale: ${gist.settings.scale || 1}}`}</style>
|
||||
<g>
|
||||
{Object.keys(patternProps.parts).map((name) => (
|
||||
<Part
|
||||
key={name}
|
||||
part={patternProps.parts[name]}
|
||||
language={gist.settings.locale}
|
||||
paperless={gist.settings.paperless}
|
||||
units={gist.settings.units}
|
||||
name={name}
|
||||
app={app}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabDraft
|
|
@ -0,0 +1,139 @@
|
|||
import Path from '../path'
|
||||
import Point from '../point'
|
||||
import Snippet from '../snippet'
|
||||
import { getProps } from '../utils'
|
||||
|
||||
const Part = (props) => {
|
||||
const focusPoint = (point, i) => {
|
||||
const p = props.part.points[point]
|
||||
const pathString = `M ${p.x} ${props.part.topLeft.y} `
|
||||
+ `L ${p.x} ${props.part.bottomRight.y} `
|
||||
+ `M ${props.part.topLeft.x} ${p.y} `
|
||||
+ `L ${props.part.bottomRight.x} ${p.y} `
|
||||
const classes = 'focus point c' + (i % 8) // Cycle through 8 colors
|
||||
return (
|
||||
<React.Fragment key={'fp' + point}>
|
||||
<path d={pathString} className={classes} />
|
||||
<circle
|
||||
cx={p.x}
|
||||
cy={p.y}
|
||||
r="5"
|
||||
className="contrast"
|
||||
onClick={() =>
|
||||
props.raiseEvent('clearFocus', {
|
||||
part: props.name,
|
||||
type: 'points',
|
||||
name: point
|
||||
})
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const focusCoords = (p, i) => {
|
||||
let pathString = `M ${p.x} ${props.part.topLeft.y} `
|
||||
pathString += `L ${p.x} ${props.part.bottomRight.y} `
|
||||
pathString += `M ${props.part.topLeft.x} ${p.y} `
|
||||
pathString += `L ${props.part.bottomRight.x} ${p.y} `
|
||||
let classes = 'focus coords c' + (i % 4) // Cycle through 4 CSS classes
|
||||
return (
|
||||
<React.Fragment key={'cp' + i}>
|
||||
<path d={pathString} className={classes} />
|
||||
<circle
|
||||
cx={p.x}
|
||||
cy={p.y}
|
||||
r="5"
|
||||
className={classes}
|
||||
onClick={() =>
|
||||
props.raiseEvent('clearFocus', {
|
||||
part: props.name,
|
||||
type: 'coords',
|
||||
data: p
|
||||
})
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
let grid = props.paperless ? (
|
||||
<rect
|
||||
x={props.part.topLeft.x}
|
||||
y={props.part.topLeft.y}
|
||||
width={props.part.width}
|
||||
height={props.part.height}
|
||||
className="grid"
|
||||
fill={'url(#grid-' + props.name + ')'}
|
||||
/>
|
||||
) : null
|
||||
|
||||
let focus = []
|
||||
if (props.develop) {
|
||||
if (props.focus && typeof props.focus[props.name] !== 'undefined') {
|
||||
for (let i in props.focus[props.name].points)
|
||||
focus.push(focusPoint(props.focus[props.name].points[i], i))
|
||||
for (let i in props.focus[props.name].paths) {
|
||||
let name = props.focus[props.name].paths[i]
|
||||
focus.push(
|
||||
<path
|
||||
key={'fpa-' + name}
|
||||
d={props.part.paths[name].asPathstring()}
|
||||
className={'focus path c' + (i % 4)}
|
||||
onClick={() =>
|
||||
props.raiseEvent('clearFocus', {
|
||||
part: props.name,
|
||||
type: 'paths',
|
||||
name
|
||||
})
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
for (let i in props.focus[props.name].coords)
|
||||
focus.push(focusCoords(props.focus[props.name].coords[i], i))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<g {...getProps(props.part)} id={`part-${props.name}`}>
|
||||
{grid}
|
||||
{Object.keys(props.part.paths).map((name) => (
|
||||
<Path
|
||||
key={name}
|
||||
name={name}
|
||||
part={props.name}
|
||||
language={props.language}
|
||||
path={props.part.paths[name]}
|
||||
focus={props.focus}
|
||||
topLeft={props.part.topLeft}
|
||||
bottomRight={props.part.bottomRight}
|
||||
develop={props.develop}
|
||||
raiseEvent={props.raiseEvent}
|
||||
app={props.app}
|
||||
/>
|
||||
))}
|
||||
{Object.keys(props.part.points).map((name) => (
|
||||
<Point
|
||||
key={name}
|
||||
name={name}
|
||||
part={props.name}
|
||||
language={props.language}
|
||||
point={props.part.points[name]}
|
||||
focus={props.focus}
|
||||
topLeft={props.part.topLeft}
|
||||
bottomRight={props.part.bottomRight}
|
||||
develop={props.develop}
|
||||
raiseEvent={props.raiseEvent}
|
||||
app={props.app}
|
||||
/>
|
||||
))}
|
||||
{Object.keys(props.part.snippets).map((name) => (
|
||||
<Snippet key={name} name={name} snippet={props.part.snippets[name]} />
|
||||
))}
|
||||
{focus}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
export default Part
|
|
@ -0,0 +1,17 @@
|
|||
import TextOnPath from '../text-on-path'
|
||||
import { getProps } from '../utils'
|
||||
|
||||
const Path = (props) => {
|
||||
if (!props.path.render) return null
|
||||
const output = []
|
||||
const pathId = 'path-' + props.part + '-' + props.name
|
||||
output.push(
|
||||
<path id={pathId} key={pathId} d={props.path.asPathstring()} {...getProps(props.path)} />
|
||||
)
|
||||
if (props.path.attributes.get('data-text'))
|
||||
output.push(<TextOnPath key={'text-on-path-' + props.name} pathId={pathId} {...props} />)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
export default Path
|
|
@ -0,0 +1,14 @@
|
|||
import Text from '../text'
|
||||
import Circle from '../circle'
|
||||
|
||||
const Point = (props) => {
|
||||
const output = []
|
||||
if (props.point.attributes && props.point.attributes.get('data-text'))
|
||||
output.push(<Text {...props} key={'point-' + props.name} />)
|
||||
if (props.point.attributes && props.point.attributes.get('data-circle'))
|
||||
output.push(<Circle point={props.point} key={'circle-' + props.name} />)
|
||||
|
||||
return output.length < 1 ? null : output
|
||||
}
|
||||
|
||||
export default Point
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react'
|
||||
import { getProps } from '../utils'
|
||||
|
||||
const Snippet = (props) => {
|
||||
const snippetProps = {
|
||||
xlinkHref: '#' + props.snippet.def,
|
||||
x: props.snippet.anchor.x,
|
||||
y: props.snippet.anchor.y
|
||||
}
|
||||
let scale = props.snippet.attributes.get('data-scale')
|
||||
let rotate = props.snippet.attributes.get('data-rotate')
|
||||
if (scale || rotate) {
|
||||
snippetProps.transform = ''
|
||||
if (scale) {
|
||||
snippetProps.transform += `translate(${snippetProps.x}, ${snippetProps.y}) `
|
||||
snippetProps.transform += `scale(${scale}) `
|
||||
snippetProps.transform += `translate(${snippetProps.x * -1}, ${snippetProps.y * -1}) `
|
||||
}
|
||||
if (rotate) {
|
||||
snippetProps.transform += `rotate(${rotate}, ${snippetProps.x}, ${snippetProps.y}) `
|
||||
}
|
||||
}
|
||||
|
||||
return <use {...snippetProps} {...getProps(props.snippet)} />
|
||||
}
|
||||
|
||||
export default Snippet
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react'
|
||||
|
||||
const Svg = ({
|
||||
embed = true,
|
||||
develop = false,
|
||||
language = 'en',
|
||||
className = 'freesewing pattern',
|
||||
style = {},
|
||||
viewBox = false,
|
||||
width,
|
||||
height,
|
||||
children
|
||||
}) => {
|
||||
let attributes = {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
xmlnsXlink: 'http://www.w3.org/1999/xlink',
|
||||
xmlLang: language,
|
||||
viewBox: viewBox || `0 0 ${width} ${height}`,
|
||||
className,
|
||||
style
|
||||
}
|
||||
|
||||
if (!embed) {
|
||||
attributes.width = width + 'mm'
|
||||
attributes.height = height + 'mm'
|
||||
}
|
||||
if (develop) attributes.className += ' develop'
|
||||
|
||||
return <svg {...attributes}>{children}</svg>
|
||||
}
|
||||
|
||||
export default Svg
|
|
@ -0,0 +1,25 @@
|
|||
const TextOnPath = (props) => {
|
||||
const text = []
|
||||
// Handle translation
|
||||
let translated = ''
|
||||
for (let string of props.path.attributes.getAsArray('data-text')) {
|
||||
translated += props.app.t(string).replace(/"/g, '"') + ' '
|
||||
}
|
||||
const textPathProps = {
|
||||
xlinkHref: '#' + props.pathId,
|
||||
startOffset: '0%'
|
||||
}
|
||||
const align = props.path.attributes.get('data-text-class')
|
||||
if (align && align.indexOf('center') > -1) textPathProps.startOffset = '50%'
|
||||
else if (align && align.indexOf('right') > -1) textPathProps.startOffset = '100%'
|
||||
|
||||
return (
|
||||
<text>
|
||||
<textPath {...textPathProps}>
|
||||
<tspan {...props.path.attributes.asPropsIfPrefixIs('data-text-')}>{translated}</tspan>
|
||||
</textPath>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextOnPath
|
|
@ -0,0 +1,38 @@
|
|||
const Text = (props) => {
|
||||
let text = []
|
||||
// Handle translation
|
||||
let translated = ''
|
||||
for (let string of props.point.attributes.getAsArray('data-text')) {
|
||||
translated += props.app.t(string.toString()).replace(/"/g, '"') + ' '
|
||||
}
|
||||
// Handle muti-line text
|
||||
if (translated.indexOf('\n') !== -1) {
|
||||
let key = 0
|
||||
let lines = translated.split('\n')
|
||||
text.push(<tspan key={'tspan-' + key}>{lines.shift()}</tspan>)
|
||||
for (let line of lines) {
|
||||
key++
|
||||
text.push(
|
||||
<tspan
|
||||
key={'tspan-' + key}
|
||||
x={props.point.x}
|
||||
dy={props.point.attributes.get('data-text-lineheight') || 12}
|
||||
>
|
||||
{line.toString().replace(/"/g, '"')}
|
||||
</tspan>
|
||||
)
|
||||
}
|
||||
} else text.push(<tspan key="tspan-1">{translated}</tspan>)
|
||||
|
||||
return (
|
||||
<text
|
||||
x={props.point.x}
|
||||
y={props.point.y}
|
||||
{...props.point.attributes.asPropsIfPrefixIs('data-text-')}
|
||||
>
|
||||
{text}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
export default Text
|
|
@ -0,0 +1,36 @@
|
|||
export const getProps = (obj) => {
|
||||
/** I can't believe it but there seems to be no method on NPM todo this */
|
||||
const cssKey = (key) => {
|
||||
let chunks = key.split('-')
|
||||
if (chunks.length > 1) {
|
||||
key = chunks.shift()
|
||||
for (let s of chunks) key += s.charAt(0).toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
const convert = (css) => {
|
||||
let style = {}
|
||||
let rules = css.split(';')
|
||||
for (let rule of rules) {
|
||||
let chunks = rule.split(':')
|
||||
if (chunks.length === 2) style[cssKey(chunks[0].trim())] = chunks[1].trim()
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
let rename = {
|
||||
class: 'className',
|
||||
'marker-start': 'markerStart',
|
||||
'marker-end': 'markerEnd'
|
||||
}
|
||||
let props = {}
|
||||
for (let key in obj.attributes.list) {
|
||||
if (key === 'style') props[key] = convert(obj.attributes.get(key))
|
||||
if (Object.keys(rename).indexOf(key) !== -1) props[rename[key]] = obj.attributes.get(key)
|
||||
else if (key !== 'style') props[key] = obj.attributes.get(key)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue