1
0
Fork 0

feat: Handle absolute input on pct options

This commit is contained in:
joostdecock 2025-02-08 12:45:42 +01:00
parent 987ba5419c
commit 752dd69c50
14 changed files with 645 additions and 394 deletions

View file

@ -0,0 +1,370 @@
import React, { useRef } from 'react'
import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
import { MovableStack } from './stack.mjs'
export const MovablePattern = ({
renderProps,
showButtons = true,
update,
fitImmovable = false,
immovable = [],
layoutPath,
}) => {
const svgRef = useRef(null)
if (!renderProps) return null
// keep a fresh copy of the layout because we might manipulate it without saving to the gist
let layout =
renderProps.settings[0].layout === true
? {
...renderProps.autoLayout,
width: renderProps.width,
height: renderProps.height,
}
: renderProps.settings[0].layout
// Helper method to update part layout and re-calculate width * height
const updateLayout = (name, config, history = true) => {
// Start creating new layout
const newLayout = { ...layout }
newLayout.stacks[name] = config
// Pattern topLeft and bottomRight
let topLeft = { x: 0, y: 0 }
let bottomRight = { x: 0, y: 0 }
for (const pname in renderProps.stacks) {
if (immovable.includes(pname) && !fitImmovable) continue
let partLayout = newLayout.stacks[pname]
// Pages part does not have its topLeft and bottomRight set by core since it's added post-draft
if (partLayout?.tl) {
// set the pattern extremes
topLeft.x = Math.min(topLeft.x, partLayout.tl.x)
topLeft.y = Math.min(topLeft.y, partLayout.tl.y)
bottomRight.x = Math.max(bottomRight.x, partLayout.br.x)
bottomRight.y = Math.max(bottomRight.y, partLayout.br.y)
}
}
newLayout.width = bottomRight.x - topLeft.x
newLayout.height = bottomRight.y - topLeft.y
newLayout.bottomRight = bottomRight
newLayout.topLeft = topLeft
if (history) {
update.ui(layoutPath, newLayout)
} else {
// we don't put it in the gist if it shouldn't contribute to history because we need some of the data calculated here for rendering purposes on the initial layout, but we don't want to actually save a layout until the user manipulates it. This is what allows the layout to respond appropriately to settings changes. Once the user has starting playing with the layout, all bets are off
layout = newLayout
}
}
const sortedStacks = {}
Object.keys(renderProps.stacks)
.sort((a, b) => {
const hasA = immovable.includes(a)
const hasB = immovable.includes(b)
if (hasA && !hasB) return -1
if (!hasA && hasB) return 1
return 0
})
.forEach((s) => (sortedStacks[s] = renderProps.stacks[s]))
const sortedRenderProps = { ...renderProps, stacks: sortedStacks }
const Stack = ({ stackName, stack, settings, components, t }) => (
<MovableStack
{...{
stackName,
stack,
components,
t,
movable: !immovable.includes(stackName),
layout: layout.stacks[stackName],
updateLayout,
showButtons,
settings,
}}
/>
)
return (
<PanZoomPattern
{...{
renderProps: sortedRenderProps,
components: { Stack },
}}
ref={svgRef}
/>
)
}
/*
* This React component is a long way from perfect, but it's a start for
* handling custom layouts.
*
* There are a few reasons that (at least in my opinion) implementing this is non-trivial:
*
* 1) React re-render vs DOM updates
*
* For performance reasons, we can't re-render with React when the user drags a
* pattern part (or rotates it). It would kill performance.
* So, we don't re-render with React upon dragging/rotating, but instead manipulate
* the DOM directly.
*
* So far so good, but of course we don't want a pattern that's only correctly laid
* out in the DOM. We want to update the pattern gist so that the new layout is stored.
* For this, we re-render with React on the end of the drag (or rotate).
*
* Handling this balance between DOM updates and React re-renders is a first contributing
* factor to why this component is non-trivial
*
* 2) SVG vs DOM coordinates
*
* When we drag or rotate with the mouse, all the events are giving us coordinates of
* where the mouse is in the DOM.
*
* The layout uses coordinates from the embedded SVG which are completely different.
*
* We run `getScreenCTM().inverse()` on the svg element to pass to
* `matrixTransform` on a `DOMPointReadOnly` for dom to svg space
* conversions.
*
* 3) Part-level transforms
*
* All parts use their center as the transform-origin to simplify
* transforms, especially flipping and rotating.
*
* 4) Bounding box
*
* We use `getBoundingClientRect` rather than `getBBox` because it provides
* more data and factors in the transforms. We then use our `domToSvg`
* function to move the points back into the SVG space.
*/
//import { useRef, useState, useEffect, useCallback } from 'react'
//import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
//import { getProps } from 'pkgs/react-components/src/pattern/utils.mjs'
//import { angle } from '../utils.mjs'
//import { drag } from 'd3-drag'
//import { select } from 'd3-selection'
//import { Buttons } from './transform-buttons.mjs'
export const MovableStack = ({
stackName,
stack,
components,
t,
movable = true,
layout,
updateLayout,
showButtons,
settings,
}) => {
const stackExists = !movable || typeof layout?.move?.x !== 'undefined'
// Use a ref for direct DOM manipulation
const stackRef = useRef(null)
const innerRef = useRef(null)
// State variable to switch between moving or rotating the part
const [rotate, setRotate] = useState(false)
// This is kept as state to avoid re-rendering on drag, which would kill performance
// It's a bit of an anti-pattern, but we'll directly manipulate the properties instead of updating the state
// Managing the difference between re-render and direct DOM updates makes this
// whole thing a bit tricky to wrap your head around
const stackRotation = layout?.rotate || 0
const [liveTransforms] = useState({
translateX: layout?.move.x,
translateY: layout?.move.y,
rotation: stackRotation,
flipX: !!layout?.flipX,
flipY: !!layout?.flipY,
})
const center = stack.topLeft && {
x: stack.topLeft.x + (stack.bottomRight.x - stack.topLeft.x) / 2,
y: stack.topLeft.y + (stack.bottomRight.y - stack.topLeft.y) / 2,
}
const setTransforms = useCallback(() => {
// get the transform attributes
const { translateX, translateY, rotation, flipX, flipY } = liveTransforms
const transforms = generateStackTransform(translateX, translateY, rotation, flipX, flipY, stack)
const me = select(stackRef.current)
me.attr('transform', transforms.join(' '))
return transforms
}, [liveTransforms, stackRef, stack])
/** update the layout either locally or in the gist */
const updateStacklayout = useCallback(
(history = true) => {
/** don't mess with what we don't lay out */
if (!stackRef.current || !movable) return
// set the transforms on the stack in order to calculate from the latest position
const transforms = setTransforms()
// apply the transforms to the bounding box to get the new extents of the stack
const { topLeft, bottomRight } = getTransformedBounds(stack, transforms)
// update it on the draft component
updateLayout(
stackName,
{
move: {
x: liveTransforms.translateX,
y: liveTransforms.translateY,
},
rotate: liveTransforms.rotation % 360,
flipX: liveTransforms.flipX,
flipY: liveTransforms.flipY,
tl: topLeft,
br: bottomRight,
},
history
)
},
[stackRef, setTransforms, updateLayout, liveTransforms, movable, stack, stackName]
)
// update the layout on mount
useEffect(() => {
// only update if there's a rendered part and it's not an imovable part
if (stackRef.current && movable) {
updateStacklayout(false)
}
}, [stackRef, movable, updateStacklayout])
/** reset the part's transforms */
const resetPart = () => {
liveTransforms.rotation = 0
liveTransforms.flipX = 0
liveTransforms.flipY = 0
updateStacklayout()
}
/** toggle between dragging and rotating */
const toggleDragRotate = () => {
// only respond if the part should be able to drag/rotate
if (!stackRef.current || !movable) {
return
}
setRotate(!rotate)
}
/** Method to flip (mirror) the part along the X or Y axis */
const flip = (axis) => {
if (axis === 'x') liveTransforms.flipX = !liveTransforms.flipX
else liveTransforms.flipY = !liveTransforms.flipY
updateStacklayout()
}
/** method to rotate 90 degrees */
const rotate90 = (direction = 1) => {
if (liveTransforms.flipX) direction *= -1
if (liveTransforms.flipY) direction *= -1
liveTransforms.rotation += 90 * direction
updateStacklayout()
}
/** get the delta rotation from the start of the drag event to now */
const getRotation = (event) =>
angle(center, event.subject) - angle(center, { x: event.x, y: event.y })
let didDrag = false
const handleDrag =
movable &&
drag()
// subject allows us to save data from the start of the event to use throughout event handing
.subject(function (event) {
return rotate
? // if we're rotating, the subject is the mouse position
{ x: event.x, y: event.y }
: // if we're moving, the subject is the part's x,y coordinates
{ x: liveTransforms.translateX, y: liveTransforms.translateY }
})
.on('drag', function (event) {
if (!event.dx && !event.dy) return
if (rotate) {
let newRotation = getRotation(event)
// shift key to snap the rotation
if (event.sourceEvent.shiftKey) {
newRotation = Math.ceil(newRotation / 15) * 15
}
// reverse the rotation direction one time per flip. if we're flipped both directions, rotation will be positive again
if (liveTransforms.flipX) newRotation *= -1
if (liveTransforms.flipY) newRotation *= -1
liveTransforms.rotation = stackRotation + newRotation
} else {
liveTransforms.translateX = event.x
liveTransforms.translateY = event.y
}
// a drag happened, so we should update the layout when we're done
didDrag = true
setTransforms()
})
.on('end', function () {
// save to gist if anything actually changed
if (didDrag) updateStacklayout()
didDrag = false
})
// Initialize drag handler
useEffect(() => {
// don't drag the pages
if (!movable || !stackExists) return
handleDrag(select(stackRef.current))
}, [stackRef, movable, stackExists, handleDrag])
// // Don't just assume this makes sense
if (!stackExists) return null
const { Group, Part } = components
return (
<Group id={`stack-${stackName}`} {...getProps(stack)} ref={stackRef}>
<Group id={`stack-inner-${stackName}`} ref={innerRef}>
{[...stack.parts].map((part, key) => (
<Part {...{ components, t, part, stackName, settings }} key={key} />
))}
</Group>
{movable && (
<>
<rect
x={stack.topLeft.x}
y={stack.topLeft.y}
width={stack.width}
height={stack.height}
className={`layout-rect ${rotate ? 'rotate' : 'move'}`}
id={`${stackName}-layout-rect`}
onClick={toggleDragRotate}
/>
{showButtons ? (
<Buttons
transform={`translate(${center.x}, ${
center.y
}) rotate(${-liveTransforms.rotation}) scale(${liveTransforms.flipX ? -1 : 1},${
liveTransforms.flipY ? -1 : 1
})`}
flip={flip}
rotate={rotate}
setRotate={setRotate}
resetPart={resetPart}
rotate90={rotate90}
partName={stackName}
/>
) : null}
</>
)}
</Group>
)
}

View file

@ -106,11 +106,6 @@ export const MenuItem = ({
return (
<>
<FormControl
FIXME_REMOVED_label={
<span className="tw-text-base tw-font-normal">
{config.choiceTitles ? config.choiceTitles[current] : i18n?.en?.o?.[name]?.d}
</span>
}
label={false}
id={config.name}
labelBR={<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">{buttons}</div>}

View file

@ -10,6 +10,7 @@ import {
MenuSliderInput,
MenuDegInput,
MenuListInput,
MenuMmInput,
MenuPctInput,
} from './Input.mjs'
import {
@ -18,7 +19,7 @@ import {
MenuCountOptionValue,
MenuDegOptionValue,
MenuListOptionValue,
MenyMmOptionValue,
MenuMmOptionValue,
MenuPctOptionValue,
} from './Value.mjs'
import { MenuItemGroup, MenuItem } from './Container.mjs'
@ -57,7 +58,7 @@ export const DesignOptionsMenu = ({ Design, isFirst = true, state, i18n, update
),
deg: (props) => <MenuDegInput {...drillProps} {...props} />,
list: (props) => <MenuListInput {...drillProps} {...props} isDesignOption />,
mm: () => <span>FIXME: Mm options are deprecated. Please report this </span>,
mm: (props) => <MenuMmInput {...drillProps} {...props} />,
pct: (props) => <MenuPctInput {...drillProps} {...props} />,
}
const values = {
@ -107,7 +108,7 @@ export const DesignOption = ({ config, settings, ux, inputs, values, ...rest })
const type = designOptionType(config)
const Input = inputs[type]
const Value = values[type]
const allowOverride = ['pct', 'count', 'deg'].includes(type)
const allowOverride = ['pct', 'count', 'deg', 'mm'].includes(type)
const allowToggle = (ux > 3 && type === 'bool') || (type == 'list' && config.list.length === 2)
// Hide option?

View file

@ -1,11 +1,19 @@
import React, { useMemo, useCallback, useState } from 'react'
import { i18n } from '@freesewing/collection'
import { designOptionType, round, measurementAsUnits, measurementAsMm } from '@freesewing/utils'
import {
designOptionType,
round,
measurementAsUnits,
measurementAsMm,
formatMm,
formatFraction128,
} from '@freesewing/utils'
import { menuRoundPct } from '../../lib/index.mjs'
import { ButtonFrame, NumberInput } from '@freesewing/react/components/Input'
import { defaultConfig } from '../../config/index.mjs'
import { ApplyIcon } from '@freesewing/react/components/Icon'
import { capitalize } from '@freesewing/core'
import { capitalize, mergeOptions } from '@freesewing/core'
import { KeyVal } from '@freesewing/react/components/KeyVal'
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
export const MenuBoolInput = (props) => {
@ -67,7 +75,6 @@ const getTitleAndDesc = (config = {}, i18n = {}, isDesignOption = false) => {
: `${name}.o.${entry}`
if (!config.choiceTitles && i18n && i18n.en.o[`${name}.${entry}`])
titleKey = i18n.en.o[`${name}.${entry}`]
console.log({ titleKey, titles: config.choiceTitles, isDesignOption })
const title = config.title
? config.title
: config.titleMethod
@ -180,7 +187,9 @@ export const MenuListToggle = ({ config, changed, updateHandler, name }) => {
}
export const MenuMmInput = (props) => {
const { units, updateHandler, current, config } = props
const { updateHandler, current, config } = props
const units = props.state.settings?.units
const imperial = units === 'imperial'
const mmUpdateHandler = useCallback(
(path, newCurrent) => {
const calcCurrent =
@ -200,6 +209,8 @@ export const MenuMmInput = (props) => {
config: {
step: defaultStep,
...config,
min: imperial ? config.min / 25.4 : config.min,
max: imperial ? config.max / 25.4 : config.min,
dflt: measurementAsUnits(config.dflt, units),
},
current: current === undefined ? undefined : measurementAsUnits(current, units),
@ -260,6 +271,8 @@ export const MenuSliderInput = ({
children,
changed,
i18n,
state,
Design,
}) => {
const { max, min } = config
const handleChange = useSharedHandlers({
@ -283,6 +296,7 @@ export const MenuSliderInput = ({
handleChange,
min,
max,
state,
}}
/>
</div>
@ -296,11 +310,28 @@ export const MenuSliderInput = ({
<span className="tw-opacity-50">
<span dangerouslySetInnerHTML={{ __html: valFormatter(min) + suffix }} />
</span>
<span
<div
className={`tw-font-bold ${val === config.dflt ? 'tw-text-secondary' : 'tw-text-accent'}`}
>
<span dangerouslySetInnerHTML={{ __html: valFormatter(val) + suffix }} />
</span>
{typeof config.toAbs === 'function' ? (
<span>
<span className="tw-px-2">|</span>
<span
dangerouslySetInnerHTML={{
__html: formatMm(
config.toAbs(
val / 100,
state.settings,
mergeOptions(state.settings, Design.patternConfig.options)
),
state.settings?.units
),
}}
/>
</span>
) : null}
</div>
<span className="tw-opacity-50">
<span dangerouslySetInnerHTML={{ __html: valFormatter(max) + suffix }} />
</span>
@ -320,30 +351,59 @@ export const MenuSliderInput = ({
}
export const MenuEditOption = (props) => {
const [manualEdit, setManualEdit] = useState(props.current)
const { config, handleChange } = props
const type = designOptionType(config)
const [manualEdit, setManualEdit] = useState(props.current)
const [abs, setAbs] = useState(false)
const [units, setUnits] = useState(
abs
? props.state.settings?.units === 'imperial'
? 'inch'
: 'cm'
: defaultConfig.menuOptionEditLabels[type]
)
const onUpdate = useCallback(
(validVal) => {
if (validVal !== null && validVal !== false) handleChange(validVal)
(validVal, units) => {
if (validVal !== null && validVal !== false) {
if (type === 'pct' && units === 'cm')
return handleChange(config.fromAbs(Number(validVal) * 1000, props.state.settings))
if (type === 'pct' && units === 'inch')
return handleChange(config.fromAbs(Number(validVal) * 2540, props.state.settings))
return handleChange(validVal)
}
},
[handleChange]
)
const toggleInputUnits = () => {
if (abs) setUnits(defaultConfig.menuOptionEditLabels[type])
else setUnits(props.state.settings?.units === 'imperial' ? 'inch' : 'cm')
setAbs(!abs)
console.log('in toogg;e')
}
if (!['pct', 'count', 'deg', 'mm'].includes(type))
return <p>This design option type does not have a component to handle manual input.</p>
return (
<div className="tw-daisy-form-control tw-mb-2 tw-w-full">
<label className="tw-daisy-label tw-font-medium tw-text-accent">
<em>Enter a custom value ({defaultConfig.menuOptionEditLabels[type]})</em>
</label>
<label className="tw-daisy-input-group tw-daisy-input-group-sm tw-flex tw-flex-row tw-items-center tw-gap-2 tw--mt-4">
<div className="tw-daisy-label tw-font-medium tw-text-accent">
<label className="tw-daisy-label-text">
<em>Enter a custom value</em> {units}
</label>
{type === 'pct' && typeof config.fromAbs === 'function' ? (
<label className="tw-daisy-label-text">
<KeyVal k="units" val={units} onClick={toggleInputUnits} color="secondary" />
</label>
) : null}
</div>
<label className="tw-daisy-input-group tw-daisy-input-group-sm tw-flex tw-flex-row tw-items-end tw-gap-2 tw--mt-4">
<NumberInput value={manualEdit} update={setManualEdit} />
<button
className="tw-daisy-btn tw-daisy-btn-secondary tw-mt-4"
onClick={() => onUpdate(manualEdit)}
onClick={() => onUpdate(manualEdit, units)}
>
<ApplyIcon />
</button>

View file

@ -92,8 +92,14 @@ export const MenuBoolValue = MenuListOptionValue
/**
* Displays the MmOptions are not supported
*/
export const MenuMmOptionValue = () => (
<span className="text-error">FIXME: No Mm Options are not supported</span>
export const MenuMmOptionValue = ({ config, changed, current, state }) => (
<MenuHighlightValue changed={changed}>
<span
dangerouslySetInnerHTML={{
__html: formatMm(changed ? current : config.dflt, state.settings?.units),
}}
/>
</MenuHighlightValue>
)
/**
@ -111,13 +117,6 @@ export const MenuMmValue = ({ current, config, units, changed }) => (
/**
* Displays the current percentage value, and the absolute value if configured
*
**************************************************************************
* SliderIcon Title THIS *
* min% max% *
* ----------------------0----------------------------------------------- *
* msg PencilIcon ResetIcon *
**************************************************************************
*/
export const MenuPctOptionValue = ({ config, current, settings, changed, patternConfig }) => {
const val = changed ? current : config.pct / 100

View file

@ -0,0 +1,142 @@
// Dependencies
import { defaultPrintSettings, printSettingsPath, handleExport } from '../../lib/export/index.mjs'
import { tilerPlugin } from '../../lib/export/plugin-tiler.mjs'
import { get } from '@freesewing/utils'
import { draft } from '../../lib/index.mjs'
import React from 'react'
//import {
// handleExport,
// ns as exportNs,
//} from 'shared/components/workbench/exporting/export-handler.mjs'
//import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs'
//import get from 'lodash.get'
//import { defaultPrintSettings, printSettingsPath } from './config.mjs'
//// Hooks
//import { useContext } from 'react'
//import { useTranslation } from 'next-i18next'
//// Context
//import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs'
// Components
import { ZoomablePattern } from '../ZoomablePattern.mjs'
import { PatternLayout } from '../PatternLayout.mjs'
//import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs'
//import { PrintMenu, ns as menuNs } from './menu.mjs'
//import { PatternWithMenu, ns as wrapperNs } from '../pattern-with-menu.mjs'
export const LayoutView = (props) => {
// design,
// pattern,
// patternConfig,
// settings,
// setSettings,
// ui,
// update,
// language,
// account,
// Design,
//}) => {
const { config, state, update, Design } = props
const defaultSettings = defaultPrintSettings(state.settings?.units)
// Settings for the tiler plugin
const pageSettings = {
...defaultSettings,
...get(state.ui, printSettingsPath, {}),
}
/*
* Now draft the pattern
*/
const { pattern, failure, errors } = draft(Design, state.settings, [tilerPlugin(pageSettings)])
if (failure) return <p>Draft failed. FIXME: Handle this gracefully.</p>
const renderProps = pattern.getRenderProps()
const exportIt = () => {
update.startLoading('layout', { msg: 'Generating PDF' })
handleExport({
format: pageSettings.size,
settings,
design,
t,
Design,
ui,
startLoading: loading.startLoading,
stopLoading: loading.stopLoading,
onComplete: () => {
setLoadingStatus([true, 'pdfReady', true, true])
},
onError: (err) => {
setLoadingStatus([true, 'pdfFailed', true, true])
console.log(err)
},
})
}
const output = (
<MovablePattern
{...{
renderProps,
update,
immovable: ['pages'],
layoutPath: ['layouts', 'print'],
showButtons: !ui.hideMovableButtons,
}}
/>
//<ZoomablePattern
// renderProps={renderProps}
// patternLocale="en"
// rotate={state.ui.rotate}
///>
)
return <PatternLayout {...{ update, Design, output, state, pattern, config }} />
return (
<>
<PatternWithMenu
noHeader
{...{
settings,
ui,
update,
control: account.control,
account,
design,
setSettings,
title: (
<h2 className="text-center lg:text-left capitalize">{t('workbench:printLayout')}</h2>
),
pattern: (
<MovablePattern
{...{
renderProps,
update,
immovable: ['pages'],
layoutPath: ['layouts', 'print'],
showButtons: !ui.hideMovableButtons,
}}
/>
),
menu: (
<PrintMenu
{...{
design,
pattern,
patternConfig,
setSettings,
settings,
ui,
update,
language,
account,
exportIt,
}}
/>
),
}}
/>
</>
)
}

View file

@ -6,6 +6,7 @@ import { DraftView } from './DraftView.mjs'
import { SaveView } from './SaveView.mjs'
import { ExportView } from './ExportView.mjs'
import { UndosView } from './UndosView.mjs'
//import { LayoutView } from './LayoutView.mjs'
import { ErrorIcon } from '@freesewing/react/components/Icon'
import {
OptionsIcon,
@ -33,7 +34,7 @@ const viewIcons = {
measurements: MeasurementsIcon,
test: BeakerIcon,
timing: GaugeIcon,
printLayout: PrintIcon,
layout: PrintIcon,
save: SaveIcon,
export: ExportIcon,
editSettings: EditIcon,
@ -57,6 +58,7 @@ export const View = (props) => {
if (view === 'save') return <SaveView {...props} />
if (view === 'export') return <ExportView {...props} />
if (view === 'undos') return <UndosView {...props} />
//if (view === 'layout') return <LayoutView {...props} />
/*
viewComponents: {
draft: 'DraftView',
@ -65,7 +67,7 @@ export const View = (props) => {
export: 'ViewPicker',
measurements: 'MeasurementsView',
undos: 'UndosView',
printLayout: 'ViewPicker',
layout: 'ViewPicker',
editSettings: 'ViewPicker',
docs: 'ViewPicker',
inspect: 'ViewPicker',
@ -106,7 +108,7 @@ export const viewLabels = {
t: 'Time Design',
d: 'Shows detailed timing of the pattern being drafted, allowing you to find bottlenecks in performance',
},
printLayout: {
layout: {
t: 'Pattern Layout',
d: 'Organize your pattern parts to minimize paper use',
},

View file

@ -15,7 +15,7 @@ export const defaultConfig = {
cloudImageVariants: ['public', 'sq100', 'sq200', 'sq500', 'w200', 'w500', 'w1000', 'w2000'],
// Views
mainViews: ['draft', 'designs', 'save', 'export'],
extraViews: ['measurements', 'undos', 'printLayout', 'docs'],
extraViews: ['measurements', 'undos', 'layout', 'docs'],
devViews: ['editSettings', 'inspect', 'logs', 'test', 'timing'],
utilViews: ['picker'],
measurementsFreeViews: ['designs', 'measurements', 'docs', 'picker'],
@ -36,7 +36,7 @@ export const defaultConfig = {
export: 'ViewPicker',
measurements: 'MeasurementsView',
undos: 'UndosView',
printLayout: 'ViewPicker',
layout: 'ViewPicker',
editSettings: 'ViewPicker',
docs: 'ViewPicker',
inspect: 'ViewPicker',
@ -97,7 +97,7 @@ export const defaultConfig = {
measurements: 1,
test: 4,
timing: 4,
printLayout: 2,
layout: 2,
export: 1,
save: 1,
undos: 2,

View file

@ -1,290 +0,0 @@
# Popout component
comment: Comment
note: Note
tip: Tip
warning: Warning
fixme: FIXME
link: Link
related: Related
# Designs view
chooseADesign: Choose a Design
# Mesurements view
measurements: Measurements
measurementsAreOk: We have all required measurements to draft this pattern.
editMeasurements: Edit Measurements
editMeasurementsDesc: You can manually set or override measurements below.
requiredMeasurements: Required Measurements
optionalMeasurements: Optional Measurements
missingMeasurements: Missing Measurements
missingMeasurementsInfo: To generate this pattern, we need the following additional measurements
missingMeasurementsNotify: To generate this pattern, we need some additional measurements
# Measurements sets
noOwnSets: You do not have any of your own measurements sets (yet)
pleaseMtm: Because our patterns are bespoke, we strongly suggest you take accurate measurements.
noOwnSetsMsg: You can store your measurements as a measurements set, after which you can generate as many patterns as you want for them.
chooseFromOwnSets: Choose one of your own measurements sets
chooseFromOwnSetsDesc: Pick any of your own measurements sets that have all required measurements to generate this pattern.
newSet: Create a new measurements set
someSetsLacking: Some of your measurements sets lack the measurements required to generate this pattern
chooseFromBookmarkedSets: Choose one of the measurements sets you've bookmarked
chooseFromBookmarkedSetsDesc: If you've bookmarked any measurements sets, you can select from those too.
chooseFromCuratedSets: Choose one of FreeSewing's curated measurements sets
chooseFromCuratedSetsDesc: If you're just looking to try out our platform, you can select from our list of curated measurements sets.
# View wrapper
requiredPropsMissing.t: Required props are missing
requiredPropsMissing.d: The FreeSewing pattern editor needs to be initialized properly. It currently lacks some props to be able to bootstrap.
# View picker
chooseAnActivity: Choose an activity
chooseAnotherActivity: Choose a different activity
view.draft.t: Draft Pattern
view.draft.d: Choose this if you are not certain what to pick
view.measurements.t: Pattern Measurements
view.measurements.d: Update or load measurements to generate a pattern for
view.test.t: Test Design
view.test.d: See how different options or changes in measurements influence the design
view.timing.t: Time Design
view.timing.d: Shows detailed timing of the pattern being drafted, allowing you to find bottlenecks in performance
view.printLayout.t: Print Layout
view.printLayout.d: Organize your pattern parts to minimize paper use
view.save.t: Save pattern as...
view.save.d: Save the changes to this pattern in your account, or save it as a new pattern
view.export.t: Export Pattern
view.export.d: Export this pattern into a variety of formats
view.editSettings.t: Edit settings by hand
view.editSettings.d: Throw caution to the wind, and hand-edit the pattern's settings
view.logs.t: Pattern Logs
view.logs.d: Show the logs generated by the pattern, useful to troubleshoot problems
view.inspect.t: Pattern inspector
view.inspect.d: Load the pattern in the inspector, giving you in-depth info about a pattern's components
view.docs.t: Documentation
view.docs.d: More information and links to documentation
view.designs.t: Choose a different Design
view.designs.d: "Current design: {- design }"
view.picker.t: Choose a different view
view.undos.t: Undo History
view.undos.d: Time-travel through your recent pattern changes
showAdvancedOptions: Show advanced options
hideAdvancedOptions: Hide advanced options
views.t: Views
views.d: Choose between the main views of the pattern editor
measurementsFreeViewsOnly.t: We are only showing activities that do not require measurements
measurementsFreeViewsOnly.d: Once we have all measurements required to generate a pattern, you will have more choices here.
# menus
youAreUsingTheDefaultValue: You are using the default value
youAreUsingACustomValue: You are using a custom value
designOptions.t: Design Options
designOptions.d: These options are specific to this design. You can use them to customize your pattern in a variety of ways.
fit.t: Fit
style.t: Style
advanced.t: Advanced
coreSettings.t: Core Settings
coreSettings.d: These settings are not specific to the design, but instead allow you to customize various parameters of the FreeSewing core library, which generates the design for you.
paperless.t: Paperless
paperless.d: Trees are awesome, and taping together sewing patterns is not much fun. Try our paperless mode to avoid the need to print out your pattern altogether.
samm.t: Seam Allowance Size
samm.d: Controls the amount of seam allowance used in your pattern
sabool.t: Include Seam Allowance
sabool.d: Controls whether or not to include seam allowance in your pattern
complete.t: Details
complete.d: Controls how detailed the pattern is; Either a complete pattern with all details, or a basic outline of the pattern parts
expand.t: Expand
expand.d: Controls efforts to save paper. Disable this to expand all pattern parts at the cost of using more space.
only.t: Included Parts
only.d: Use this to control exactly which pattern parts will be included in your pattern
units.t: Units
units.d: This setting determines how unit are displayed on your pattern
margin.t: Margin
margin.d: Controls the margin around pattern parts
scale.t: Scale
scale.d: Controls the overall line width, font size, and other elements that do not scale with the pattern's measurements
yes: Yes
no: No
completeYes.t: Generate a complete pattern
completeYes.d: This will generate a complete pattern with all notations, lines, markings. Use this if you are not certain what to choose.
completeNo.t: Generate a pattern outline
completeNo.d: Only generate the outline of the pattern parts. Use this if you are looking to use a laser cutter or have other specific needs.
expandYes.t: Expand all pattern parts
expandYes.d: This will generate a pattern where all pattern parts are drawn to their full size, even if they are simple rectangles.
expandNo.t: Keep patterns parts compact where possible
expandNo.d: This will draw a more dense representation of the pattern which includes all info without using up too much space & paper.
saNo.t: Do not include seam allowance
saNo.d: This generates a pattern which does not include any seam allowance. The size of the seam allowance does not matter as no seam allowance will be included.
saYes.t: Include seam allowance
saYes.d: This generates a pattern that will include seam allowance. The size of the seam allowance is set individually.
paperlessNo.t: Generate a regular pattern
paperlessNo.d: This will generate a regular pattern, which you can then print out.
paperlessYes.t: Generate a paperless pattern
paperlessYes.d: This generates a pattern with dimensions and a grid, which allows you to transfer it on fabric or another medium without the need to print out the pattern.
metric: Metric
uiPreferences.t: UI Preferences
uiPreferences.d: These preferences control the UI (User Interface) of the pattern editor
renderer.t: Render Engine
renderer.d: Controls how the pattern is rendered (drawn) on the screen
renderWithReact.t: Render with FreeSewing's React components
renderWithReact.d: Render as SVG through our React components. Allows interactivity and is optimized for screen. Use this if you are not sure what to pick.
renderWithCore.t: Render with FreeSewing's Core library
renderWithCore.d: Render directly to SVG from Core. Allows no interactivity and is optimized for print. Use this if you want to know what it will look like when exported.
kiosk.t: Kiosk Mode
kiosk.d: Controls how the pattern editor is embedded in the web page.
aside.t: Aside Menu
aside.d: Controls whether or not to show menus on the side of larger screens
withAside.t: Also show menus on the side
withAside.d: Shows menus both on the side of the screen, as well as the drop-downs in the header (this only applies to larger screens)
noAside.t: Only show menus in the header
noAside.d: Only shows the drop-down variant of the menus, making more room for your pattern
rotate.t: Rotate pattern
rotate.d: Allows you to rotate your pattern 90 degrees to better fit your screen
rotateNo.t: Do not rotate pattern
rotateNo.d: Show the pattern as it is
rotateYes.t: Rotate pattern 90 degrees
rotateYes.d: Rotate the pattern 90 degrees counter clockwise
websiteMode.t: Use inline mode
websiteMode.d: Embeds the pattern editor in the natural flow of the web page.
kioskMode.t: Use kiosk mode
kioskMode.d: Breaks out the pattern editor to fill the entire page.
ux.t: User Experience
ux.d: Which user experience do you prefer? From keep it simple, to give me all the powers.
inspect.t: Inspect
inspect.d: Enabling this will allow you to drill down into the pattern, and pull up information about its various parts, paths, and points.
inspectNo.t: Disable the inspector
inspectNo.d: This is the default, the pattern inspector is disabled and the pattern is displayed as usual.
inspectYes.t: Enable the inspector
inspectYes.d: With the pattern inspector enabled and the React rendering engine selected, we will add interactivity to the pattern to allow you to inspect the various elements that make up the pattern.
draft: Draft
test: Test
print: Print layout
cut: Cut Layout
save: Save
export: Export
edit: Edit
draft.t: Draft your pattern
draft.d: Launches FreeSewing flagship pattern editor, where you can tweak your pattern to your heart's desire
test.t: Test your pattern
test.d: See how your pattern adapts to changes in options, or measurements
print.t: Print Layout
print.d: Allows you to arrange your pattern pieces so you can printing your pattern on as little pages as possible
cut.t: Cutting layout
cut.d: Allows you to arrange your pattern pieces so you can determine exactly how much fabric you need to make it.
save.t: Save your pattern
save.d: Save the current pattern to your FreeSewing account
export.t: Export your pattern
export.d: Allows you to export this pattern to a variety of formats
logs.t: Pattern logs
enterCustomValue: Enter a custom value
# ux
ux1.t: Keep it as simple as possible
ux1.d: Hides all but the most essential features.
ux2.t: Keep it simple, but not too simple
ux2.d: Hides the majority of features.
ux3.t: Balance simplicity with power
ux3.d: Reveals the majority of features, but not all.
ux4.t: Give me all powers, but keep me safe
ux4.d: Reveals all features, keeps handrails and safety checks.
ux5.t: Get out of my way
ux5.d: Reveals all features, removes all handrails and safety checks.
# Tooltips
tt.changeEditorView: Change to a different view
tt.toggleSa: Turns Seam Allowance on or off (see Core Settings)
tt.togglePaperless: Turns Paperless on or off (see Core Settings)
tt.toggleComplete: Turns Details on or off (see Core Settings)
tt.toggleExpand: Turns Expand on or off (see Core Settings)
tt.toggleUnits: Switches Units between metric and imperial (see Core Settings)
tt.changeUx: Changes your UX setting (see UI Preferences)
tt.toggleAside: Turn the Aside Menu on or off (see UI Preferences)
tt.toggleKiosk: Turns Kiosk Mode on or off (see UI Preferences)
tt.toggleRotate: Turns Rotate Pattern on or off (see UI Preferences)
tt.toggleRenderer: Switches the Render Engine between React and SVG (see UI Preferences)
tt.exportPattern: Export pattern
tt.savePattern: Save pattern
tt.savePatternAs: Save pattern as...
tt.undo: Undo most recent change
tt.undoAll: Undo all changes since the last save point
tt.resetDesign: Reset all settings, but keep the design and measurements
tt.resetAll: Reset the editor completely
# flags
apply: Apply
decrease: Decrease
disable: Disable
dismiss: Dismiss
expandIsOff.t: This design saves space (and trees) because expand is disabled
expandIsOff.d: "Because the **expand** core setting is currently disabled, some parts are not fully drawn or not shown at all. Typically, these are simple rectangles that only take up space, or things that can be cut on the fold. \n\nTo expand all pattern parts to their full size, enable the expand setting."
expandIsOn.t: This design can save space (and trees)
expandIsOn.d: "Because the **expand** core setting is currently enabled, all parts are fully drawn. You can display this design in a more compact way by disabling the **expand** setting. \n\nDoing so will mean that some parts are not fully drawn or not shown at all. Typically, these are simple rectangles that only take up space, or things that can be cut on the fold."
enable: Enable
flags: Flags
flagMenu.t: Flags
flagMenuOne.d: A specific issue about your current pattern needs your attention.
flagMenuMany.d: Some issues about your current pattern need your attention.
hide: Hide
increase: Increase
show: Show
saIncluded: (This includes seam allowance)
saExcluded: (This does not include seam allowance)
saUnused: (This part does not require any seam allowance)
partHiddenByExpand: This part is not shown because the **expand** core setting is currently disabled. Enable it to show this pattern part.
# Auth
authRequired: Authentication required
membersOnly: This functionality requires a FreeSewing account.
signUp: Sign Up
signIn: Sign In
statusUnknown: Account status warning
statusUnknownMsg: Your account status prohibits us from processing your data. Please contact support.
consentLacking: Consent lacking
consentLackingMsg: We do not have your consent for processing your data. Without consent, we have no legal basis to process your data.
accountProhibited: Your account has been disabled
accountProhibitedMsg: Your account has been administratively disabled.
accountDisabled: Account disabled
accountDisabledMsg: You cannot re-enable a disabled account. You need to contact support to address this.
accountInactive: Your account is inactive
accountInactiveMsg: You must activate your account via the signup link we sent you.
signupAgain: If you cannot find the link, you can receive a new one by signing up again.
cannotUse: A disabled account cannot be used.
contactSupport: Contact support
reviewConsent: Review your consent
roleLacking: You lack the required role to access this content
roleLackingMsg: This content requires the <b>{ requiredRole }</b> role. Your role is <b>{ role }</b> which does not grant you access to this content.
# save pattern
bookmarkPattern: Bookmark pattern
savePattern: Save pattern
saveAsNewPattern: Save as a New Pattern
savePatternAs: Save pattern as...
savePatternAsHellip: Save pattern as...
patternBookmarkCreated: Pattern bookmark created
see: See
addNotes: Add notes
addSettingsToNotes: Add settings to notes
exporting: Exporting
exportAsData: Export as data
exportForEditing: Export for editing
exportForPrinting: Export for printing
exportPattern-txt: Export a PDF suitable for your printer, or download this pattern in a variety of formats
exportPattern: Export pattern
settings: Settings
patternTitle: Pattern Title
patternNotes: Pattern Notes
toAccessPatternsGoTo: To access your patterns, go to
genericLoadingMessage: Hang tight, we're working on it...
patternSavedAs: Pattern saved as
cancel: Cancel
# undo history
secondsAgo: seconds ago
minutesAgo: minutes ago
hoursAgo: hours ago
undos.unknown.t: Unknown Change
defaultRestored: Cleared (default restored)
includeAllParts: Include all parts
allFirstLetter: A
undo: Undo
xMeasurementsChanged: "{ count } Measurements changed"

View file

@ -22,10 +22,10 @@ export function menuDesignOptionsStructure(design, options, settings, asFullList
sorted[name] = {
...option,
name,
title: i18n[design].en.o[name].t,
title: i18n[design]?.en?.o?.[name]?.t || name,
about: (
<span>
{i18n[design].en.o[name].d}
{i18n[design]?.en?.o?.[name]?.d || name}
<DesignDocsLink item={name} design={design} />
</span>
),

View file

@ -20,12 +20,15 @@ import { Spinner } from '@freesewing/react/components/Spinner'
*
* @param {function} Design - The Design constructor
* @param {object} settings - The settings for the pattern
* @param {array} plugins - Any (extra) plugins to load into the pattern
* @return {object} data - The drafted pattern, along with errors and failure data
*/
export function draft(Design, settings) {
export function draft(Design, settings, plugins = []) {
const pattern = new Design(settings)
for (const plugin of plugins) pattern.use(plugin)
const data = {
// The pattern
pattern: new Design(settings),
pattern,
// Any errors logged by the pattern
errors: [],
// If the pattern fails to draft, this will hold the error

View file

@ -1,23 +0,0 @@
# Popout component
comment: Opmerking
note: Notitie
tip: Tip
warning: Waarschuwing
fixme: FIXME
link: Link
related: Gerelateerd
# Mesurements view
measurements: Maten
measurementsAreOk: We hebben alle benodigde maten om dit patroon te tekenen.
editMeasurements: Maten Aanpassen
editMeasurementsDesc: Hier kan je manueel de maten aanpassen.
requiredMeasurements: Vereiste Maten
optionalMeasurements: Optionele Maten
# Designs view
pickADesign: Kies een ontwerp
# View wrapper
requiredPropsMissing.t: Vereiste props ontbreken
requiredPropsMissing.d: De FreeSewing patroon editor moet correct geinitialiseerd worden. Momenteel ontbreken een aantal props die noodzakelijk zijn om de editor te starten.

View file

@ -1,26 +0,0 @@
# List of props that can be passed to the pattern editor
| Prop | Default | Description |
| ---- | ------- | ----------- |
| `design` | `undefined` | Name of the current design (key in the `objects` prop).<br>Note that this will set the initial state, but it can be changed by the user. |
| `designs` | `{}` |Object holding all designs that are available. |
| `locale` | `en` | Language code |
| `imperial`| `false` | Whether to use imperial units as the default, or not |
| `components` | `{}` | Object holding swizzled components |
| `hooks` | `{}` | Object holding swizzled hooks |
| `methods` | `{}` | Object holding swizzled methods |
## Defaults object
```mjs
{
locale: 'en',
imperial: 'false',
ui: {
renderer: 'react',
kiosk: false,
}
}
```

View file

@ -2,7 +2,14 @@ import React, { useState, useContext } from 'react'
import { CopyToClipboard as Copy } from 'react-copy-to-clipboard'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
export const KeyVal = ({ k, val, color = 'primary', small = false, href = false }) => {
export const KeyVal = ({
k,
val,
color = 'primary',
small = false,
href = false,
onClick = false,
}) => {
const [copied, setCopied] = useState(false)
const { setLoadingStatus } = useContext(LoadingStatusContext)
@ -24,21 +31,32 @@ export const KeyVal = ({ k, val, color = 'primary', small = false, href = false
if (href) return <LinkKeyVal {...{ k, val, color, small, href, colorClasses1, colorClasses2 }} />
return (
const inner = (
<>
<span
className={`${sharedClasses} ${small ? 'tw-rounded-l' : 'tw-rounded-l-lg'} ${colorClasses1} ${small ? 'tw-text-xs' : ''} tw-pr-0.5`}
>
{k}
</span>
<span
className={`${sharedClasses} ${small ? 'tw-rounded-r' : 'tw-rounded-r-lg'} ${colorClasses2} ${small ? 'tw-text-xs' : ''} tw-pl-0.5`}
>
{val}
</span>
</>
)
return onClick === false ? (
<Copy text={val} onCopy={() => (noCopy ? null : handleCopied(setCopied, setLoadingStatus, k))}>
<button className="tw-daisy-btn-ghost tw-p-0">
<span
className={`${sharedClasses} ${small ? 'tw-rounded-l' : 'tw-rounded-l-lg'} ${colorClasses1} ${small ? 'tw-text-xs' : ''} tw-pr-0.5`}
>
{k}
</span>
<span
className={`${sharedClasses} ${small ? 'tw-rounded-r' : 'tw-rounded-r-lg'} ${colorClasses2} ${small ? 'tw-text-xs' : ''} tw-pl-0.5`}
>
{val}
</span>
</button>
<button className="tw-daisy-btn-ghost tw-p-0">{inner}</button>
</Copy>
) : (
<button
className="tw-daisy-btn-ghost tw-p-0"
onClick={typeof onClick === 'function' ? onClick : null}
>
{inner}
</button>
)
}