1
0
Fork 0

Merge branch 'joost' into plugins-scale

This commit is contained in:
Joost De Cock 2023-10-15 16:05:28 +02:00
commit d739e8f5bd
24466 changed files with 405611 additions and 707715 deletions

View file

@ -0,0 +1,129 @@
addNotes: Add notes
addSettingsToNotes: Add settings to notes
advanced: Fortgeschritten
appliedMeasies: Wir haben dieses Schnittmuster mit einem neuen Maßsatz versehen.
armhole: Armloch
attributes: Eigenschaften
backPockets: Gesäßtaschen
bookmarkPattern: Bookmark pattern
bottomRight: Unten rechts
changeMeasies: Muster ändern Messungen
chooseATest: Choose a test scenario to run
chooseATestDesc: You can test how the design adapts to changes in a specific design option or measurements.
chooseATestMenuMobileMsg: To do so, open the test menu and select the scenario you want to run.
chooseATestMenuMsg: To do so, select the test scenario you want to run in the menu on the right.
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.
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.
chooseNewSet: Wähle ein neues Maßnahmeset
closure: Verschluss
collar: Kragen
columns: columns
configurePattern: Muster konfigurieren
construction: Konstruktion
continueEditingTitle: Continue editing
continueEditingDesc: Load the newly saved pattern in the pattern editor, so you can make more changes.
cuffs: Manschetten
currentPrintLayout: Current print layout
cutLayout: Layout schneiden
darts: Abnäher
designOptions.d: Teste die Auswirkungen einer Option auf das Aussehen dieses Musters
docs: Dokumentation
draft: Entwurf
draftPattern: Entwurfsmuster
edit: bearbeiten
editCurrentMeasies: Aktuelle Maße bearbeiten
editCurrentMeasiesDesc: Änderungen, die du hier vornimmst, werden nicht in deine Maßsätze gespeichert und wirken sich nur auf dieses Schnittmuster aus.
editCurrentMeasiesHeader: Schnittmuster-Maße bearbeiten
editMeasiesByHand: Edit measurements by hand
editMeasiesByHandDesc: Manually set or override any measurements. These changes will only apply to the current pattern.
editSettings: Konfiguration bearbeiten
elastic: Gummi
export: Exportieren
exportAsData: Als Daten exportieren
exportForEditing: Für die Bearbeitung exportieren
exportForPrinting: Für den Druck exportieren
exportPattern-txt: Ein für deinen Heimdrucker geeignetes PDF exportieren, oder das Schnittmuster in verschiedenen Formaten herunterladen
exportPattern: Schnittmuster exportieren
fit: Passform
frontPockets: Vordere Taschen
generatePdf: Generate print-ready PDF
giveItAName: Gib ihm einen Namen
goToPatternTitle: Navigate to the pattern page
goToPatternDesc: Exit the pattern editor and navigate to the pattern page where you can update the pattern's metadata.
height: Höhe
help: Hilfe
layoutSettings.d: Additional options to further optimize the printing layout of your pattern.
layoutSettings.t: Layout settings
length: Länge
learnHowToUseEditor: Learn how to use FreeSewing's online pattern editor
measies: Muster-Messungen
measiesOk: Wir haben alle erforderlichen Maße, um dieses Muster zu erstellen.
measurements.d: Teste die Auswirkungen einer Messung auf das Aussehen des Musters
menu: Menü
name: Name
noDesignFound: Wir konnten dieses Design nicht finden. Das sollte nicht passieren, also würden wir uns freuen, wenn du es meldest.
noInlineDocs: Documention is not included in this build
noInlineDocsDesc: Please visit FreeSewing.org to access our documentation
notes: Notizen
pages: pages
part: Musterteil
partInfo: Musterteil Info
partTransfo: Part transformation buttons
partTransfoDesc: These buttons allow you to rotate or flip individual pattern parts.
partTransfoNo: Hide buttons
partTransfoNoDesc: Do not include these buttons on the pattern output
partTransfoYes: Show buttons
partTransfoYesDesc: Include these buttons on the pattern output (they will not be printed)
pathInfo: Pfad-Infos
patternBookmarkCreated: Pattern bookmark created
patternInspector: Muster-Inspektor
patternLogs: Musterprotokolle
patternSaved: Pattern saved
pockets: Taschen
printLayout: Layout drucken
printSettings.d: Configure your pattern so you can print it just the way you like it. Includes page size & orientation, margins, and more.
printSettings.t: Print settings
reset: Zurücksetzen
resetPrintLayout: Reset print layout
resetPrintLayoutDesc: Removes all manual changes to the print layout, and restores the default layout
rows: rows
save: Speichern
savePattern: Schnittmuster speichern
saveAsNewPattern: Save as new pattern
savePatternAs: Save pattern as...
savePatternAsHellip: Save pattern as...
saveSettings: Einstellungen speichern
saveYourPattern: Speichere dein Schnittmuster
seeMissingMeasies: Siehe fehlende Maße
show: Anzeigen
showAllParts: Alle Musterteile anzeigen
showMovableButtons: Knöpfe
showOnlyThisPart: Nur diesen Teil des Musters anzeigen
sleevecap: Armkugel
style: Stil
test: Test
testDesignMeasurement: "Test {design} measurements: {measurement}"
testDesignOption: "Test {design} design options: {option}"
testDesignSets: "Test {design} across measurements sets"
testMeasurements: Test measurements
testMeasurementsDesc: Test how the design adapts to changes to a specific measurement
testOptions: Test design options
testOptionsDesc: Test how the design adapts to changes in a specific design option
testPattern: Testmuster
testSets: Test measurements sets
testSetsDesc: Test how the design adapts across different measurements sets
topLeft: Oben links
weLackSomeMeasies: Uns fehlen { nr } Messungen, um dieses Muster zu erstellen
whereToGoAfterSaveAs: After saving the new pattern, what do you want to do?
width: Breite
xTotalPagesSomeBlank: "{total} pages in total but {blank} are blank"
yamlEditViewError: Probleme mit YAML
yamlEditViewErrorDesc: Wir haben deine Eingabe gespeichert, aber sie funktioniert möglicherweise aus folgenden Gründen nicht
yamlEditViewTitleThing: 'Musterkonfiguration bearbeiten für {thing}'
youCanPickOrEnter: Du kannst entweder einen Satz Messungen auswählen oder sie von Hand eingeben, aber ohne diese Messungen können wir nicht weitermachen.
youUseCustomValue: You are using the default value
youUseDefaultValue: You are using a custom value

View file

@ -1,14 +0,0 @@
export const Circle = (props) =>
props.point.attributes.getAsArray('data-circle').map((r, i) => {
const circleProps = props.point.attributes.asPropsIfPrefixIs('data-circle-')
const extraProps = {}
for (const prop in circleProps) {
const val = props.point.attributes.getAsArray(
`data-circle-${prop === 'className' ? 'class' : prop}`
)
if (val.length >= i) extraProps[prop] = val[i]
else extraProps[prop] = val.join(' ')
}
return <circle key={r} cx={props.point.x} cy={props.point.y} r={r} {...extraProps} />
})

View file

@ -1,67 +0,0 @@
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>
`,
}
export const Defs = (props) => {
let defs = props.svg.defs.render()
if (props.settings[0].paperless) {
defs += grids[props.settings[0].units || 'metric']
for (let p in props.parts[0]) {
let anchor = { x: 0, y: 0 }
if (typeof props.parts[0][p].points.gridAnchor !== 'undefined')
anchor = props.parts[0][p].points.gridAnchor
else if (typeof props.parts[0][p].points.anchor !== 'undefined')
anchor = props.parts[0][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 }} />
}

View file

@ -1,65 +0,0 @@
import { ErrorView } from 'shared/components/error/view.mjs'
export const DraftError = (props) => {
const errors = {
pattern: 0,
sets: 0,
}
const warnings = {
pattern: 0,
sets: 0,
}
if (props.patternLogs) {
errors.pattern += props.patternLogs.error.length
warnings.pattern += props.patternLogs.warning.length
}
if (props.setLogs) {
errors.sets += props.setLogs.error.length
warnings.sets += props.setLogs.warning.length
}
const logInfo = []
if (errors.pattern > 0)
logInfo.push(
<li>
There are <strong>{errors.pattern} errors</strong> in the pattern logs
</li>
)
if (errors.sets > 0)
logInfo.push(
<li>
There are <strong>{errors.sets} errors</strong> in the draft logs
</li>
)
if (warnings.pattern > 0)
logInfo.push(
<li>
There are <strong>{warnings.pattern} warnings</strong> in the pattern logs
</li>
)
if (warnings.sets > 0)
logInfo.push(
<li>
There are <strong>{warnings.sets} warnings</strong> in the draft logs
</li>
)
const ic = (
<ul className="list-disc list-inside ml-4 text-xl">
{logInfo}
{logInfo.length > 0 && (
<li>
<button className="btn-link" onClick={() => props.updateGist(['_state', 'view'], 'logs')}>
Check the logs for more details
</button>
</li>
)}
<li>Check the partially rendered pattern below to see which areas are problematic</li>
</ul>
)
return (
<ErrorView inspectChildren={ic} {...props}>
<p>No need to be alarmed, but we ran into some trouble while drafting this pattern.</p>
</ErrorView>
)
}

View file

@ -1,63 +0,0 @@
import { SvgWrapper } from './svg.mjs'
import { DraftError } from './error.mjs'
export const LabDraft = (props) => {
const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } =
props
if (!draft || !hasRequiredMeasurements) return null
// Render as SVG
if (gist?.renderer === 'svg') {
let svg
try {
svg = draft.render()
} catch (error) {
console.log('Failed to render design', error)
return <DraftError error={error} {...props} />
}
return <div dangerouslySetInnerHTML={{ __html: svg }} />
}
// Render as React
let patternProps = {}
try {
patternProps = draft.getRenderProps()
} catch (error) {
console.log('Failed to get render props for design', error)
return (
<DraftError
error={error}
patternLogs={patternProps.store.logs}
setLogs={patternProps.setStores[0].logs}
updateGist={updateGist}
/>
)
}
const errors = []
errors.push(...patternProps.logs.pattern.error)
for (const set of patternProps.logs.sets) {
errors.push(...set.error)
}
return (
<>
{errors.length > 0 ? (
<DraftError
{...{
draft,
patternProps,
updateGist,
patternLogs: draft.store.logs,
setLogs: draft.setStores[0].logs,
errors,
}}
/>
) : null}
<SvgWrapper
{...{ draft, patternProps, gist, updateGist, unsetGist, showInfo, app, feedback }}
/>
</>
)
}

View file

@ -1,156 +0,0 @@
import { forwardRef } from 'react'
import { Point } from './point.mjs'
import { Snippet } from './snippet.mjs'
import { getProps } from './utils.mjs'
import { round } from 'shared/utils.mjs'
import { Path, Tr, KeyTd, ValTd, Attributes, pointCoords } from './path.mjs'
const partInfo = (props) => (
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Part info</h5>
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>Name</KeyTd>
<ValTd>{props.partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Width</KeyTd>
<ValTd>{round(props.part.width, 2)}mm</ValTd>
</Tr>
<Tr>
<KeyTd>Height</KeyTd>
<ValTd>{round(props.part.height, 2)}mm</ValTd>
</Tr>
<Tr>
<KeyTd>Top Left</KeyTd>
<ValTd>{pointCoords(props.part.topLeft)}</ValTd>
</Tr>
<Tr>
<KeyTd>Bottom Right</KeyTd>
<ValTd>{pointCoords(props.part.bottomRight)}</ValTd>
</Tr>
<Tr>
<KeyTd>Attributes</KeyTd>
<ValTd>
<Attributes list={props.part.attributes.list} />
</ValTd>
</Tr>
</tbody>
</table>
<div className="flex flex-row flex-wrap gap-2 mt-4">
{props.gist?.only && props.gist.only.length > 0 ? (
<button className="btn btn-primary" onClick={() => props.unsetGist(['only'])}>
Show all parts
</button>
) : (
<button
className="btn btn-primary"
onClick={() => props.updateGist(['only'], [props.partName])}
>
Show only this part
</button>
)}
<button className="btn btn-success" onClick={() => console.log(props.part)}>
console.log(part)
</button>
<button className="btn btn-success" onClick={() => console.table(props.part.points)}>
console.table(part.points)
</button>
<button className="btn btn-success" onClick={() => console.table(props.part.paths)}>
console.table(part.paths)
</button>
</div>
</div>
)
const XrayPart = (props) => {
const { topLeft, bottomRight } = props.part
return (
<g>
<path
d={`
M ${topLeft.x} ${topLeft.y}
L ${topLeft.x} ${bottomRight.y}
L ${bottomRight.x} ${bottomRight.y}
L ${bottomRight.x} ${topLeft.y}
z
`}
className={`peer stroke-note lashed opacity-30 hover:opacity-90 fill-fabric hover:cursor-pointer hover:stroke-mark`}
style={{ fillOpacity: 0 }}
onClick={(evt) => {
evt.stopPropagation()
props.showInfo(partInfo(props))
}}
/>
</g>
)
}
export const PartInner = forwardRef((props, ref) => {
const { partName, part, gist, skipGrid } = props
const Grid =
gist.paperless && !skipGrid ? (
<rect
x={part.topLeft.x}
y={part.topLeft.y}
width={part.width}
height={part.height}
className="grid"
fill={'url(#grid-' + partName + ')'}
/>
) : null
return (
<g ref={ref}>
{Grid}
{gist._state?.xray?.enabled && <XrayPart {...props} />}
{Object.keys(part.paths).map((pathName) => (
<Path
key={pathName}
pathName={pathName}
path={part.paths[pathName]}
topLeft={props.part.topLeft}
bottomRight={props.part.bottomRight}
{...props}
/>
))}
{Object.keys(props.part.points).map((pointName) => (
<Point
key={pointName}
pointName={pointName}
point={props.part.points[pointName]}
topLeft={props.part.topLeft}
bottomRight={props.part.bottomRight}
{...props}
/>
))}
{Object.keys(props.part.snippets).map((snippetName) => (
<Snippet
key={snippetName}
snippetName={snippetName}
snippet={props.part.snippets[snippetName]}
{...props}
/>
))}
</g>
)
})
PartInner.displayName = 'PartInner'
export const Part = (props) => {
const { partName, part } = props
return (
<g
{...getProps(part)}
id={`${part.context.settings.idPrefix || ''}part-${partName}`}
className={part.context.settings.idPrefix || ''}
>
<PartInner {...props} />
</g>
)
}

View file

@ -1,657 +0,0 @@
import { TextOnPath } from './text.mjs'
import { getProps } from './utils.mjs'
import { round, formatMm } from 'shared/utils.mjs'
import { RawSpan } from 'shared/components/raw-span.mjs'
export const pointCoords = (point) =>
point ? `[ ${round(point.x, 2)}, ${round(point.y, 2)} ]` : null
export const Tr = ({ children }) => <tr className="border border-base-300">{children}</tr>
export const KeyTd = ({ children }) => <td className="p-3 text-right">{children}:</td>
export const ValTd = ({ children }) => <td className="p-3">{children}</td>
export const TextAlongPath = ({ id, size, txt }) => (
<text>
<textPath xlinkHref={`#${id}`} startOffset="50%">
<tspan
style={{ textAnchor: 'middle', fontSize: size }}
className="fill-neutral-content fill-opacity-50"
dy={size * -0.4}
>
{txt}
</tspan>
</textPath>
</text>
)
export const PointCircle = ({ point, size, className = 'stroke-neutral-content' }) => (
<circle
cx={point.x}
cy={point.y}
r={size / 50}
className={className}
fill="none"
strokeWidth={size / 150}
strokeOpacity="0.5"
/>
)
const CpCircle = ({ point, className = 'fill-lining no-stroke' }) => (
<circle cx={point.x} cy={point.y} r={1.5} className={className} />
)
const EpCircle = ({ point, className = 'fill-note no-stroke' }) => (
<circle cx={point.x} cy={point.y} r={1.5} className={className} />
)
const pathDimensions = (from, to, cp1 = false, cp2 = false, path = false) => {
const topLeft = {
x: to.x < from.x ? to.x : from.x,
y: to.y < from.y ? to.y : from.y,
}
const bottomRight = {
x: to.x > from.x ? to.x : from.x,
y: to.y > from.y ? to.y : from.y,
}
let bbox = false
// Deal with curves
if (cp1 && cp2) {
if (cp1.x < topLeft.x) topLeft.x = cp1.x
if (cp2.x < topLeft.x) topLeft.x = cp2.x
if (cp1.x > bottomRight.x) bottomRight.x = cp1.x
if (cp2.x > bottomRight.x) bottomRight.x = cp2.x
if (cp1.y < topLeft.y) topLeft.y = cp1.y
if (cp2.y < topLeft.y) topLeft.y = cp2.y
if (cp1.y > bottomRight.y) bottomRight.y = cp1.y
if (cp2.y > bottomRight.y) bottomRight.y = cp2.y
// This undocumented core methods returns the curve's bounding box
bbox = path.bbox()
}
const w = bottomRight.x - topLeft.x
const h = bottomRight.y - topLeft.y
const size = w > h ? w : h
return {
topLeft,
bottomRight,
w,
h,
size,
bbox,
}
}
export const Defs = () => (
<defs>
<marker orient="auto" refY="0.0" refX="0.0" id="arrowTo" style={{ overflow: 'visible' }}>
<path
className="fill-neutral-content"
d="M 0,0 L -12,-4 C -10,-2 -10,2 -12, 4 z"
fillOpacity="0.5"
></path>
</marker>
<marker orient="auto" refY="0.0" refX="0.0" id="arrowFrom" style={{ overflow: 'visible' }}>
<path
className="fill-neutral-content"
d="M 0,0 L 12,-4 C 10,-2 10,2 12, 4 z"
fillOpacity="0.5"
></path>
</marker>
</defs>
)
export const svgProps = {
xmlns: 'http://www.w3.org/2000/svg',
xmlnsSvg: 'http://www.w3.org/2000/svg',
xmlnsXlink: 'http://www.w3.org/1999/xlink',
style: { maxHeight: 'inherit', strokeLinecap: 'round', strokeLinejoin: 'round' },
}
const Line = (props) => {
const ops = props.path.ops
const from = ops[0].to
const to = ops[1].to
const { topLeft, bottomRight, w, h, size } = pathDimensions(from, to)
const id = `${props.partName}_${props.pathName}_${props.i}`
const xyProps = {
className: 'stroke-neutral-content',
strokeOpacity: '0.5',
fill: 'none',
strokeWidth: size / 300,
}
return (
<svg
{...svgProps}
viewBox={`${topLeft.x - size / 10} ${topLeft.y - size / 10} ${w + size / 5} ${h + size / 5}`}
>
<path
id={`${id}_x`}
{...xyProps}
d={`M ${topLeft.x},${bottomRight.y} L ${bottomRight.x},${bottomRight.y}`}
/>
<TextAlongPath
id={`${id}_x`}
size={size / 18}
txt={formatMm(bottomRight.x - topLeft.x, props.gist.units, 'notags')}
/>
<path
id={`${id}_y`}
{...xyProps}
d={`M ${topLeft.x},${bottomRight.y} L ${topLeft.x},${topLeft.y}`}
/>
<TextAlongPath
id={`${id}_y`}
size={size / 18}
txt={formatMm(bottomRight.y - topLeft.y, props.gist.units, 'notags')}
/>
<path
id={id}
d={`M ${from.x},${from.y} L ${to.x},${to.y}`}
className="stroke-neutral-content"
strokeLinecap="round"
strokeWidth={size / 100}
/>
<TextAlongPath
id={id}
size={size / 18}
txt={formatMm(props.path.length(), props.gist.units, 'notags')}
/>
<PointCircle point={from} size={size} />
<PointCircle point={to} size={size} />
</svg>
)
}
const Curve = (props) => {
const ops = props.path.ops
const from = ops[0].to
const { to, cp1, cp2 } = ops[1]
const { topLeft, w, h, size, bbox } = pathDimensions(from, to, cp1, cp2, props.path)
const id = `${props.partName}_${props.pathName}_${props.i}`
const cpProps = {
className: 'stroke-success',
strokeOpacity: '0.85',
fill: 'none',
strokeWidth: size / 300,
}
const xyProps = {
...cpProps,
strokeOpacity: '0.5',
className: 'stroke-neutral-content',
markerEnd: 'url(#arrowTo)',
markerStart: 'url(#arrowFrom)',
}
return (
<svg
{...svgProps}
viewBox={`${topLeft.x - size / 10} ${topLeft.y - size / 10} ${w + size / 5} ${h + size / 5}`}
>
<Defs />
<path
id={`${id}_x`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.bottomRight.x},${bbox.bottomRight.y}`}
/>
<TextAlongPath
id={`${id}_x`}
size={size / 18}
txt={formatMm(bbox.bottomRight.x - bbox.topLeft.x, props.gist.units, 'notags')}
/>
<path
id={`${id}_y`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.topLeft.x},${bbox.topLeft.y}`}
/>
<TextAlongPath
id={`${id}_y`}
size={size / 18}
txt={formatMm(bbox.bottomRight.y - bbox.topLeft.y, props.gist.units, 'notags')}
/>
<path id={`${id}_cp1`} {...cpProps} d={`M ${from.x},${from.y} L ${cp1.x},${cp1.y}`} />
<PointCircle point={cp1} size={size} className="stroke-success" />
<path id={`${id}_cp2`} {...cpProps} d={`M ${to.x},${to.y} L ${cp2.x},${cp2.y}`} />
<PointCircle point={cp2} size={size} className="stroke-success" />
<path
id={id}
d={`M ${from.x},${from.y} C ${cp1.x},${cp1.y} ${cp2.x},${cp2.y} ${to.x},${to.y}`}
className="stroke-neutral-content"
fill="none"
strokeLinecap="round"
strokeWidth={size / 100}
/>
<TextAlongPath
id={id}
size={size / 18}
txt={formatMm(props.path.length(), props.gist.units, 'notags')}
/>
<PointCircle point={from} size={size} />
<PointCircle point={to} size={size} />
</svg>
)
}
const MiniPath = (props) => {
const bbox = props.path.bbox()
const id = `${props.partName}_${props.pathName}_mini}`
const w = bbox.bottomRight.x - bbox.topLeft.x
const h = bbox.bottomRight.y - bbox.topLeft.y
const size = w > h ? w : h
const xyProps = {
fill: 'none',
strokeWidth: size / 300,
strokeOpacity: '0.5',
className: 'stroke-neutral-content',
markerEnd: 'url(#arrowTo)',
markerStart: 'url(#arrowFrom)',
}
return (
<svg
{...svgProps}
viewBox={`${bbox.topLeft.x - size / 10} ${bbox.topLeft.y - size / 10} ${w + size / 5} ${
h + size / 5
}`}
className="freesewing pattern z-50"
>
<Defs />
<path
id={`${id}_x`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.bottomRight.x},${bbox.bottomRight.y}`}
/>
<TextAlongPath
id={`${id}_x`}
size={size / 18}
txt={formatMm(bbox.bottomRight.x - bbox.topLeft.x, props.gist.units, 'notags')}
/>
<path
id={`${id}_y`}
{...xyProps}
d={`M ${bbox.topLeft.x},${bbox.bottomRight.y} L ${bbox.topLeft.x},${bbox.topLeft.y}`}
/>
<TextAlongPath
id={`${id}_y`}
size={size / 18}
txt={formatMm(bbox.bottomRight.y - bbox.topLeft.y, props.gist.units, 'notags')}
/>
<path
id={id}
d={props.path.asPathstring()}
className="stroke-neutral-content"
fill="none"
strokeLinecap="round"
strokeWidth={size / 100}
/>
<TextAlongPath
id={id}
size={size / 18}
txt={formatMm(props.path.length(), props.gist.units, 'notags')}
/>
<XrayPath {...props} />)
</svg>
)
}
const lineInfo = (props) => (
<div className="p-4 border bg-neutral bg-opacity-60 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Line info</h5>
<div className="flex flex-row flex-wrap">
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>From</KeyTd>
<ValTd>{pointCoords(props.path.ops[0].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>To</KeyTd>
<ValTd>{pointCoords(props.path.ops[1].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>Length</KeyTd>
<ValTd>{formatMm(props.path.length(), props.gist.units, 'notags')}</ValTd>
</Tr>
<Tr>
<KeyTd>Width</KeyTd>
<ValTd>
<RawSpan
html={formatMm(
Math.abs(props.path.ops[0].to.dx(props.path.ops[1].to)),
props.gist.units
)}
/>
</ValTd>
</Tr>
<Tr>
<KeyTd>Height</KeyTd>
<ValTd>
<RawSpan
html={formatMm(
Math.abs(props.path.ops[0].to.dy(props.path.ops[1].to)),
props.gist.units
)}
/>
</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{props.partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Draw Op</KeyTd>
<ValTd>
{props.i}/{props.ops.length}
</ValTd>
</Tr>
</tbody>
</table>
<div className="max-w-md" style={{ maxHeight: '80vh' }}>
<Line {...props} />
</div>
</div>
</div>
)
const XrayLine = (props) => (
<>
<path
d={props.path.asPathstring()}
{...getProps(props.path)}
className="opacity-0 stroke-4xl stroke-note hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => {
evt.stopPropagation()
props.showInfo(lineInfo(props))
}}
/>
<EpCircle point={props.path.ops[0].to} />
<EpCircle point={props.path.ops[1].to} />
</>
)
const curveInfo = (props) => (
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Curve info</h5>
<div className="flex flex-row flex-wrap">
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>From</KeyTd>
<ValTd>{pointCoords(props.path.ops[0].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>Cp1</KeyTd>
<ValTd>{pointCoords(props.path.ops[1].cp1)}</ValTd>
</Tr>
<Tr>
<KeyTd>Cp2</KeyTd>
<ValTd>{pointCoords(props.path.ops[1].cp2)}</ValTd>
</Tr>
<Tr>
<KeyTd>To</KeyTd>
<ValTd>{pointCoords(props.path.ops[1].to)}</ValTd>
</Tr>
<Tr>
<KeyTd>Length</KeyTd>
<ValTd>
<RawSpan html={formatMm(props.path.length(), props.gist.units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Width</KeyTd>
<ValTd>
<RawSpan
html={formatMm(
Math.abs(props.path.ops[0].to.dx(props.path.ops[1].to)),
props.gist.units
)}
/>
</ValTd>
</Tr>
<Tr>
<KeyTd>Height</KeyTd>
<ValTd>
<RawSpan
html={formatMm(
Math.abs(props.path.ops[0].to.dy(props.path.ops[1].to)),
props.gist.units
)}
/>
</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{props.partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Draw Op</KeyTd>
<ValTd>
{props.i}/{props.ops.length}
</ValTd>
</Tr>
</tbody>
</table>
<div className="max-w-md" style={{ maxHeight: '80vh' }}>
<Curve {...props} />
</div>
</div>
</div>
)
export const Attributes = ({ list }) =>
list ? (
<ul>
{Object.keys(list).map((key) => (
<li key={key}>
<strong>{key}</strong>: {list[key]}
</li>
))}
</ul>
) : null
export const pathInfo = (props) => {
const bbox = props.path.bbox()
return (
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Path info</h5>
<div className="flex flex-row flex-wrap overflow-scroll" style={{ maxHeight: '80vh' }}>
<div>
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>Name</KeyTd>
<ValTd>{props.pathName}</ValTd>
</Tr>
<Tr>
<KeyTd>Length</KeyTd>
<ValTd>
<RawSpan html={formatMm(props.path.length(), props.gist.units)} />
</ValTd>
</Tr>
<Tr>
<KeyTd>Width</KeyTd>
<ValTd>
<RawSpan
html={formatMm(Math.abs(bbox.bottomRight.x - bbox.topLeft.x), props.gist.units)}
/>
</ValTd>
</Tr>
<Tr>
<KeyTd>Height</KeyTd>
<ValTd>
<RawSpan
html={formatMm(Math.abs(bbox.bottomRight.y - bbox.topLeft.y), props.gist.units)}
/>
</ValTd>
</Tr>
<Tr>
<KeyTd>Top Left</KeyTd>
<ValTd>{pointCoords(bbox.topLeft)}</ValTd>
</Tr>
<Tr>
<KeyTd>Bottom Right</KeyTd>
<ValTd>{pointCoords(bbox.bottomRight)}</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{props.partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Attributes</KeyTd>
<ValTd>
<Attributes list={props.path.attributes.list} />
</ValTd>
</Tr>
</tbody>
</table>
<div className="flex flex-row flex-wrap gap-2 mt-4">
<button className="btn btn-success" onClick={() => console.log(props.path)}>
console.log(path)
</button>
<button className="btn btn-success" onClick={() => console.table(props.path)}>
console.table(path)
</button>
</div>
</div>
<table className="border-collapse h-fit">
<tbody>
{props.path.ops.map((op, i) => (
<Tr key={i}>
<KeyTd>{i}</KeyTd>
<ValTd>
<PathOp op={op} />
</ValTd>
</Tr>
))}
</tbody>
</table>
<div className="max-w-md">
<MiniPath {...props} />
</div>
</div>
</div>
)
}
const PathOp = ({ op }) => {
if (op.type === 'move')
return (
<span>
<strong>Move</strong> to {pointCoords(op.to)}
</span>
)
else if (op.type === 'line')
return (
<span>
<strong>Line</strong> to {pointCoords(op.to)}
</span>
)
else if (op.type === 'curve')
return (
<span>
<strong>Curve</strong> to {pointCoords(op.to)}
<br />
Cp1: {pointCoords(op.cp1)}
<br />
Cp2: {pointCoords(op.cp2)}
</span>
)
else if (op.type === 'noop') return <strong>NOOP</strong>
else if (op.type === 'close') return <strong>Close</strong>
else return <strong>FIXME: unknown path operation type: {op.type}</strong>
}
const XrayCurve = (props) => {
const from = props.path.ops[0].to
const { cp1, cp2, to } = props.path.ops[1]
return (
<>
<path
d={props.path.asPathstring()}
{...getProps(props.path)}
className="opacity-0 stroke-4xl stroke-lining hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => {
evt.stopPropagation()
props.showInfo(curveInfo(props))
}}
/>
<path d={`M ${from.x},${from.y} L ${cp1.x},${cp1.y}`} className="lining dotted" />
<path d={`M ${to.x},${to.y} L ${cp2.x},${cp2.y}`} className="lining dotted" />
<CpCircle point={cp1} />
<CpCircle point={cp2} />
<EpCircle point={from} />
<EpCircle point={to} />
</>
)
}
const XrayPath = (props) => {
const classes = props.path.attributes.get('class')
if (typeof classes === 'string' && classes.includes('noxray')) return null
const ops = props.path.divide()
return (
<g>
<path
d={props.path.asPathstring()}
{...getProps(props.path)}
className="opacity-0 stroke-7xl stroke-contrast hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => {
evt.preventDefault()
props.showInfo(pathInfo(props))
}}
markerStart="none"
markerEnd="none"
/>
{ops.length > 0
? ops.map((op, i) =>
op.ops[1].type === 'curve' ? (
<XrayCurve
{...props}
path={op}
ops={ops}
i={i}
pathName={`${props.pathName}_test`}
key={i}
/>
) : (
<XrayLine
{...props}
path={op}
ops={ops}
i={i}
pathName={`${props.pathName}_test`}
key={i}
/>
)
)
: null}
</g>
)
}
export const Path = (props) => {
const { path, partName, pathName } = props
if (path.hidden) return null
const output = []
const pathId = 'path-' + partName + '-' + pathName
let d = ''
try {
d = path.asPathstring()
} catch (err) {
// Bail out
console.log(`Failed to generate pathstring for path ${pathId} in part ${partName}`, err)
return null
}
output.push(<path id={pathId} key={pathId} d={d} {...getProps(path)} />)
if (path.attributes.get('data-text'))
output.push(<TextOnPath key={'text-on-path-' + name} pathId={pathId} {...props} />)
if (props.gist._state?.xray?.enabled) output.push(<XrayPath {...props} key={'xpath' + pathId} />)
return output
}

View file

@ -1,105 +0,0 @@
import { Text } from './text.mjs'
import { Circle } from './circle.mjs'
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from './path.mjs'
import { withinPartBounds } from './utils.mjs'
const RevealPoint = (props) => {
const r = 15 * props.gist.scale
const { x, y } = props.point
const { topLeft, bottomRight } = props.part
const i =
Object.keys(props.gist._state.xray.reveal[props.partName].points).indexOf(props.pointName) % 10
const classes = `stroke-sm stroke-color-${i} stroke-dashed`
return (
<g>
<circle cx={x} cy={y} r={r} className={classes} />
<path
d={`
M ${x} ${topLeft.y} L ${x} ${y - r}
m 0 ${2 * r} L ${x} ${bottomRight.y}
M ${topLeft.x} ${y} L ${x - r} ${y}
m ${2 * r} 0 L ${bottomRight.x} ${y}`}
className={classes}
/>
</g>
)
}
const pointInfo = (props) =>
props.point ? (
<div className="p-4 border bg-neutral bg-opacity-60 shadow rounded-lg">
<h5 className="text-neutral-content text-center pb-4">Point info</h5>
<table className="border-collapse h-fit">
<tbody>
<Tr>
<KeyTd>Coordinates</KeyTd>
<ValTd>{pointCoords(props.point)}</ValTd>
</Tr>
<Tr>
<KeyTd>Name</KeyTd>
<ValTd>{props.pointName}</ValTd>
</Tr>
<Tr>
<KeyTd>Part</KeyTd>
<ValTd>{props.partName}</ValTd>
</Tr>
<Tr>
<KeyTd>Attributes</KeyTd>
<ValTd>
<Attributes list={props.point.attributes.list} />
</ValTd>
</Tr>
</tbody>
</table>
<div className="flex flex-col flex-wrap gap-2 mt-4">
<button className="btn btn-success" onClick={() => console.log(props.point)}>
console.log(point)
</button>
<button className="btn btn-success" onClick={() => console.table(props.point)}>
console.table(point)
</button>
</div>
</div>
) : null
const XrayPoint = (props) => (
<g>
<circle
cx={props.point.x}
cy={props.point.y}
r={2 * props.gist.scale}
className="stroke-sm stroke-lining fill-lining fill-opacity-25"
/>
<circle
cx={props.point.x}
cy={props.point.y}
r={7.5 * props.gist.scale}
className="opacity-0 stroke-lining fill-lining hover:opacity-25 hover:cursor-pointer"
onClick={(evt) => {
props.showInfo(pointInfo(props))
evt.stopPropagation()
}}
/>
</g>
)
export const Point = (props) => {
const { point, pointName, partName, gist, part } = props
// Don't include parts outside the part bounding box
if (!withinPartBounds(point, part)) return null
const output = []
if (gist._state?.xray?.enabled) {
// Xray for points
output.push(<XrayPoint {...props} key={'xp-' + pointName} />)
// Reveal (based on clicking the seach icon in sidebar
if (gist._state?.xray?.reveal?.[partName]?.points?.[pointName])
output.push(<RevealPoint {...props} key={'rp-' + pointName} />)
}
// Render text
if (point.attributes && point.attributes.get('data-text'))
output.push(<Text {...props} key={'point-' + pointName} />)
// Render circle
if (point.attributes && point.attributes.get('data-circle'))
output.push(<Circle point={point} key={'circle-' + pointName} />)
return output.length < 1 ? null : output
}

View file

@ -1,19 +0,0 @@
import { Part } from './part.mjs'
import { getProps } from './utils.mjs'
export const Stack = (props) => {
const { stack, gist, updateGist, unsetGist, showInfo } = props
return (
<g {...getProps(stack)}>
{[...stack.parts].map((part) => (
<Part
{...{ gist, updateGist, unsetGist, showInfo }}
key={part.name}
partName={part.name}
part={part}
/>
))}
</g>
)
}

View file

@ -1,111 +0,0 @@
import { forwardRef } from 'react'
import { SizeMe } from 'react-sizeme'
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
import { Defs } from './defs.mjs'
import { Stack } from './stack.mjs'
export const Svg = forwardRef(
(
{
embed = true,
develop = false,
locale = 'en',
className = 'freesewing pattern',
style = {},
viewBox = false,
width,
height,
children,
},
ref
) => {
if (width < 1) width = 1000
if (height < 1) height = 1000
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: locale,
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} ref={ref}>
{children}
</svg>
)
}
)
Svg.displayName = 'Svg'
/* What's with all the wrapping?
*
* Glad you asked. The problem lies within the pan & zoom functionality
* For this to work, we need to reliably determine the container widht
* However, our SVG is optimized for embedding (no width or height)
* which means it will simply adapt to the parent container.
* So now we have a parent container adapting to the content and the
* content adapting to the parent. This creates a sort of stalemate
* where the browser will render this as zero width and height.
*
* To avoid that, we use the SizeMe which will report the size of the
* grandparent element, and then we wrap our SVG in a div that we
* set to this size. This will cause the SVG to fill in that entire
* space, and the pan and zoom to adapt to this size.
*
* Note that this also means that changing the browser window size
* will cause things to get clipped until the next render.
*
* Also still to see how this will work with SSR
*/
export const SvgWrapper = forwardRef((props, ref) => {
const { patternProps = false, gist, updateGist, unsetGist, showInfo, viewBox } = props
if (!patternProps) return null
return (
<SizeMe>
{({ size }) => (
<TransformWrapper
minScale={0.1}
centerZoomedOut={true}
wheel={{ activationKeys: ['Control'] }}
>
<TransformComponent>
<div style={{ width: size.width + 'px' }}>
<Svg {...patternProps} viewBox={viewBox} embed={gist.embed} ref={ref}>
<Defs {...patternProps} />
<style>{`:root { --pattern-scale: ${gist.scale || 1}} ${
patternProps.svg.style
}`}</style>
<g>
{props.children ||
Object.keys(patternProps.stacks).map((stackName) => (
<Stack
{...{ gist, updateGist, unsetGist, showInfo, patternProps }}
key={stackName}
stackName={stackName}
stack={patternProps.stacks[stackName]}
/>
))}
</g>
</Svg>
</div>
</TransformComponent>
</TransformWrapper>
)}
</SizeMe>
)
})
SvgWrapper.displayName = 'SvgWrapper'

View file

@ -1,116 +0,0 @@
import yaml from 'js-yaml'
import { defaultGist } from 'shared/components/workbench/gist.mjs'
import { validateGist } from './gist-validator.mjs'
import { useEffect, useState, useRef } from 'react'
import { Popout } from 'shared/components/popout.mjs'
import { useTranslation } from 'next-i18next'
import { capitalize } from '@freesewing/core'
/** a view for editing the gist as yaml */
export const EditYaml = (props) => {
let { gist, setGist, gistReady } = props
const inputRef = useRef(null)
// the gist parsed to yaml
const [gistAsYaml, setGistAsYaml] = useState(null)
// any errors as a json string
const [error, setError] = useState(null)
// success notifier
const [success, setSuccess] = useState(null)
const { t } = useTranslation(['workbench'])
// parse the current gist to yaml. this will also run when the gist gets set by input
useEffect(() => {
if (gistReady) {
// get everything but the design because it's a function and can't be serialized
// eslint-disable-next-line no-unused-vars
const { design, ...gistRest } = gist
setGistAsYaml(yaml.dump(gistRest))
}
}, [gist, gistReady])
// set the line numbers when the yaml changes
useEffect(() => {
if (gistAsYaml) {
inputRef.current.value = gistAsYaml
}
}, [gistAsYaml])
/** user-initiated save */
const onSave = () => {
// clear the errors
setError(null)
try {
// parse back to json
const editedAsJson = yaml.load(inputRef.current.value)
// make it backwards compatible so that people can paste in the yaml export from org
// the yaml export from org is missing some of the settings that are needed in the gist,
// and what it does have is under 'settings', so we merge that stuff with the existing gist view state
// and the default settings to make sure all necessary keys are accounted for,
// but we're not keeping stuff that was supposed to be cleared
const gistFromDefaults = { _state: gist._state }
for (const d in defaultGist) {
gistFromDefaults[d] = gist[d] === undefined ? defaultGist[d] : gist[d]
}
// merge it all up
const gistToCheck = {
...gistFromDefaults,
...(editedAsJson.settings ? editedAsJson.settings : editedAsJson),
}
// validate it
const validation = validateGist(gistToCheck, props.design)
// if it's not valid, show a warning about errors
if (!validation.valid) {
const newError = JSON.stringify(validation.errors, null, 2)
setError(newError)
}
// save regardless
setGist(gistToCheck)
setSuccess(true)
} catch (e) {
setError(e)
}
}
const designName = capitalize(props.design.designConfig.data.name.replace('@freesewing/', ''))
return (
<div className="max-w-screen-xl m-auto h-screen form-control">
<h2>{t('yamlEditViewTitleThing', { thing: designName })}</h2>
{error ? (
<Popout warning className="mb-4">
<h3> {t('yamlEditViewError')} </h3>
{success ? <p> {t('yamlEditViewErrorDesc')}: </p> : null}
<pre
className="language-json hljs text-base lg:text-lg whitespace-pre overflow-scroll pr-4"
dangerouslySetInnerHTML={{ __html: error }}
></pre>
</Popout>
) : null}
{success ? (
<div className="alert alert-success my-4">
<div>
<span>{t('success')}</span>
</div>
</div>
) : null}
<div id="editor" className="h-3/5 my-8">
<textarea
className="textarea textarea-primary w-full p-4 leading-7 text-lg h-full"
name="gistAsYaml"
aria-label="Configuration in YAML format"
ref={inputRef}
defaultValue={gistAsYaml}
/>
</div>
<button className="btn btn-primary" onClick={onSave}>
{' '}
Save{' '}
</button>
</div>
)
}

View file

@ -0,0 +1,129 @@
addNotes: Add notes
addSettingsToNotes: Add settings to notes
advanced: Advanced
appliedMeasies: We applied a new measurements set to this pattern.
armhole: Armhole
attributes: Attributes
backPockets: Back pockets
bookmarkPattern: Bookmark pattern
bottomRight: Bottom Right
changeMeasies: Change Pattern Measurements
chooseATest: Choose a test scenario to run
chooseATestDesc: You can test how the design adapts to changes in a specific design option or measurements.
chooseATestMenuMobileMsg: To do so, open the test menu and select the scenario you want to run.
chooseATestMenuMsg: To do so, select the test scenario you want to run in the menu on the right.
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.
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.
chooseNewSet: Choose a New Measurements Set
closure: Closure
collar: Collar
columns: columns
configurePattern: Configure pattern
construction: Construction
continueEditingTitle: Continue editing
continueEditingDesc: Load the newly saved pattern in the pattern editor, so you can make more changes.
cuffs: Cuffs
currentPrintLayout: Current print layout
cutLayout: Cut Layout
darts: Darts
designOptions.d: Test the effect of an option on the way this pattern looks
docs: Documentation
draft: Draft
draftPattern: Draft pattern
edit: Edit
editCurrentMeasies: Edit Current Measurements
editCurrentMeasiesDesc: Changes you make here will not be saved to your measurements sets, and will only affect this pattern.
editCurrentMeasiesHeader: Edit Pattern Measurements
editMeasiesByHand: Edit measurements by hand
editMeasiesByHandDesc: Manually set or override any measurements. These changes will only apply to the current pattern.
editSettings: Edit configuration
elastic: Elastic
export: Export
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
fit: Fit
frontPockets: Front pockets
generatePdf: Generate print-ready PDF
giveItAName: Give it a name
goToPatternTitle: Navigate to the pattern page
goToPatternDesc: Exit the pattern editor and navigate to the pattern page where you can update the pattern's metadata.
height: Height
help: Help
layoutSettings.d: Additional options to further optimize the printing layout of your pattern.
layoutSettings.t: Layout settings
length: Length
learnHowToUseEditor: Learn how to use FreeSewing's online pattern editor
measies: Pattern Measurements
measiesOk: We have all required measurements to create this pattern.
measurements.d: Test the effect of a measurement on the way this pattern looks
menu: Menu
name: Name
noDesignFound: We could not find this design. This shouldn't happen, so we'd really appreciate you reporting it.
noInlineDocs: Documention is not included in this build
noInlineDocsDesc: Please visit FreeSewing.org to access our documentation
notes: Notes
pages: pages
part: Pattern part
partInfo: Pattern part info
partTransfo: Part transformation buttons
partTransfoDesc: These buttons allow you to rotate or flip individual pattern parts.
partTransfoNo: Hide buttons
partTransfoNoDesc: Do not include these buttons on the pattern output
partTransfoYes: Show buttons
partTransfoYesDesc: Include these buttons on the pattern output (they will not be printed)
pathInfo: Path info
patternBookmarkCreated: Pattern bookmark created
patternInspector: Pattern Inspector
patternLogs: Pattern logs
patternSaved: Pattern saved
pockets: Pockets
printLayout: Print Layout
printSettings.d: Configure your pattern so you can print it just the way you like it. Includes page size & orientation, margins, and more.
printSettings.t: Print settings
reset: Reset
resetPrintLayout: Reset print layout
resetPrintLayoutDesc: Removes all manual changes to the print layout, and restores the default layout
rows: rows
save: Save
savePattern: Save pattern
saveAsNewPattern: Save as new pattern
savePatternAs: Save pattern as...
savePatternAsHellip: Save pattern as...
saveSettings: Save Settings
saveYourPattern: Save your pattern
seeMissingMeasies: See missing measurements
show: Show
showAllParts: Show all pattern parts
showMovableButtons: Buttons
showOnlyThisPart: Show only this pattern part
sleevecap: Sleevecap
style: Style
test: Test
testDesignMeasurement: "Test {design} measurements: {measurement}"
testDesignOption: "Test {design} design options: {option}"
testDesignSets: "Test {design} across measurements sets"
testMeasurements: Test measurements
testMeasurementsDesc: Test how the design adapts to changes to a specific measurement
testOptions: Test design options
testOptionsDesc: Test how the design adapts to changes in a specific design option
testPattern: Test pattern
testSets: Test measurements sets
testSetsDesc: Test how the design adapts across different measurements sets
topLeft: Top Left
weLackSomeMeasies: We lack { nr } measurements to create this pattern
whereToGoAfterSaveAs: After saving the new pattern, what do you want to do?
width: Width
xTotalPagesSomeBlank: "{total} pages in total but {blank} are blank"
yamlEditViewError: Issues with YAML
yamlEditViewErrorDesc: We saved your input, but it might not work for the following reasons
yamlEditViewTitleThing: 'Edit Pattern Configuration for {thing}'
youCanPickOrEnter: You can either pick a measurements set, or enter them by hand, but we cannot proceed without these measurements.
youUseCustomValue: You are using the default value
youUseDefaultValue: You are using a custom value

View file

@ -0,0 +1,129 @@
addNotes: Add notes
addSettingsToNotes: Add settings to notes
advanced: Avanzado
appliedMeasies: We applied a new measurements set to this pattern.
armhole: Agujero armado
attributes: Atributos
backPockets: Paquetes de retroceso
bookmarkPattern: Bookmark pattern
bottomRight: Abajo a la derecha
changeMeasies: Mediciones del patrón de cambio
chooseATest: Choose a test scenario to run
chooseATestDesc: You can test how the design adapts to changes in a specific design option or measurements.
chooseATestMenuMobileMsg: To do so, open the test menu and select the scenario you want to run.
chooseATestMenuMsg: To do so, select the test scenario you want to run in the menu on the right.
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.
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.
chooseNewSet: Elegir un nuevo conjunto de medidas
closure: Cierre
collar: Cuello
columns: columns
configurePattern: Configurar patrón
construction: Construcción
continueEditingTitle: Continue editing
continueEditingDesc: Load the newly saved pattern in the pattern editor, so you can make more changes.
cuffs: Puños
currentPrintLayout: Current print layout
cutLayout: Disposición del corte
darts: Dardos
designOptions.d: Prueba el efecto de una opción sobre el aspecto de este patrón
docs: Documentación
draft: Boceto
draftPattern: Proyecto de patrón
edit: Edita
editCurrentMeasies: Editar medidas actuales
editCurrentMeasiesDesc: Changes you make here will not be saved to your measurements sets, and will only affect this pattern.
editCurrentMeasiesHeader: Edit Pattern Measurements
editMeasiesByHand: Edit measurements by hand
editMeasiesByHandDesc: Manually set or override any measurements. These changes will only apply to the current pattern.
editSettings: Editar configuración
elastic: Elástico
export: Exportar
exportAsData: Exportar como datos
exportForEditing: Exportar para editar
exportForPrinting: Exportar para impresión
exportPattern-txt: Exporte un PDF adecuado para su impresora, o descargue este patrón en una variedad de formatos
exportPattern: Exportar patrón
fit: Ajuste
frontPockets: Pockets delanteros
generatePdf: Generate print-ready PDF
giveItAName: Dale un nombre
goToPatternTitle: Navigate to the pattern page
goToPatternDesc: Exit the pattern editor and navigate to the pattern page where you can update the pattern's metadata.
height: Altura
help: Ayuda
layoutSettings.d: Additional options to further optimize the printing layout of your pattern.
layoutSettings.t: Layout settings
length: Longitud
learnHowToUseEditor: Learn how to use FreeSewing's online pattern editor
measies: Medidas del patrón
measiesOk: Tenemos todas las medidas necesarias para crear este patrón.
measurements.d: Prueba el efecto de una medida en el aspecto de este patrón
menu: Menú
name: Nombre
noDesignFound: No hemos podido encontrar este diseño. Esto no debería ocurrir, así que te agradeceríamos que nos lo comunicaras.
noInlineDocs: Documention is not included in this build
noInlineDocsDesc: Please visit FreeSewing.org to access our documentation
notes: Notas
pages: pages
part: Parte del patrón
partInfo: Información sobre el patrón
partTransfo: Part transformation buttons
partTransfoDesc: These buttons allow you to rotate or flip individual pattern parts.
partTransfoNo: Hide buttons
partTransfoNoDesc: Do not include these buttons on the pattern output
partTransfoYes: Show buttons
partTransfoYesDesc: Include these buttons on the pattern output (they will not be printed)
pathInfo: Información de la ruta
patternBookmarkCreated: Pattern bookmark created
patternInspector: Inspector de Patrones
patternLogs: Registros de patrones
patternSaved: Pattern saved
pockets: Bolsillos
printLayout: Diseño de impresión
printSettings.d: Configure your pattern so you can print it just the way you like it. Includes page size & orientation, margins, and more.
printSettings.t: Print settings
reset: Reiniciar
resetPrintLayout: Reset print layout
resetPrintLayoutDesc: Removes all manual changes to the print layout, and restores the default layout
rows: rows
save: Guardar
savePattern: Guardar patrón
saveAsNewPattern: Save as new pattern
savePatternAs: Save pattern as...
savePatternAsHellip: Save pattern as...
saveSettings: Guardar ajustes
saveYourPattern: Guarda tu patrón
seeMissingMeasies: See missing measurements
show: Mostrar
showAllParts: Mostrar todas las partes del patrón
showMovableButtons: Botones
showOnlyThisPart: Mostrar sólo esta parte del patrón
sleevecap: Manga corta
style: Estilo
test: Prueba
testDesignMeasurement: "Test {design} measurements: {measurement}"
testDesignOption: "Test {design} design options: {option}"
testDesignSets: "Test {design} across measurements sets"
testMeasurements: Test measurements
testMeasurementsDesc: Test how the design adapts to changes to a specific measurement
testOptions: Test design options
testOptionsDesc: Test how the design adapts to changes in a specific design option
testPattern: Patrón de prueba
testSets: Test measurements sets
testSetsDesc: Test how the design adapts across different measurements sets
topLeft: Arriba a la izquierda
weLackSomeMeasies: Nos faltan las medidas de { nr } para crear este patrón
whereToGoAfterSaveAs: After saving the new pattern, what do you want to do?
width: Anchura
xTotalPagesSomeBlank: "{total} pages in total but {blank} are blank"
yamlEditViewError: Problemas con YAML
yamlEditViewErrorDesc: Hemos guardado tu entrada, pero puede que no funcione por las siguientes razones
yamlEditViewTitleThing: 'Editar configuración de patrones para {thing}'
youCanPickOrEnter: Puedes elegir un conjunto de medidas o introducirlas a mano, pero no podemos proceder sin estas medidas.
youUseCustomValue: You are using the default value
youUseDefaultValue: You are using a custom value

View file

@ -1,45 +1,41 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import Worker from 'web-worker'
import fileSaver from 'file-saver'
import { themePlugin } from '@freesewing/plugin-theme'
import { pluginI18n } from '@freesewing/plugin-i18n'
import { pagesPlugin, fabricPlugin } from '../layout/plugin-layout-part.mjs'
import { pluginAnnotations } from '@freesewing/plugin-annotations'
import { cutLayoutPlugin } from '../layout/cut/plugin-cut-layout.mjs'
import { fabricSettingsOrDefault } from '../layout/cut/index.mjs'
import { useFabricLength } from '../layout/cut/settings.mjs'
import { pagesPlugin, materialPlugin } from 'shared/plugins/plugin-layout-part.mjs'
import { cutLayoutPlugin } from 'shared/plugins/plugin-cut-layout.mjs'
import { materialSettingsOrDefault } from 'shared/components/workbench/views/cut/hooks.mjs'
import { useMaterialLength } from 'shared/components/workbench/views/cut/hooks.mjs'
import { capitalize, formatMm } from 'shared/utils.mjs'
import {
defaultPrintSettings,
printSettingsPath,
} from 'shared/components/workbench/views/print/config.mjs'
import get from 'lodash.get'
export const ns = ['cut', 'plugin', 'common']
export const exportTypes = {
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'],
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'legal', 'tabloid'],
exportForEditing: ['svg', 'pdf'],
exportAsData: ['json', 'yaml', 'github gist'],
}
export const defaultPdfSettings = {
size: 'a4',
orientation: 'portrait',
margin: 10,
coverPage: true,
cutlist: true,
}
/**
* Instantiate a pattern that uses plugins theme, i18n, and cutlist
* @param {Design} design the design to construct the pattern from
* @param {Object} gist the gist
* @param {Object} overwrite settings to overwrite gist settings with
* @param {Design} Design the design to construct the pattern from
* @param {Object} settings the settings
* @param {Object} overwrite settings to overwrite settings settings with
* @param {string} format the export format this pattern will be prepared for
* @param {function} t the i18n function
* @return {Pattern} a pattern
*/
const themedPattern = (design, gist, overwrite, format, t) => {
const pattern = new design({ ...gist, ...overwrite })
const themedPattern = (Design, settings, overwrite, format, t) => {
const pattern = new Design({ ...settings, ...overwrite })
// add the theme and translation to the pattern
pattern.use(themePlugin, { stripped: format !== 'svg', skipGrid: ['pages'] })
pattern.use(pluginI18n, { t })
pattern.use(pluginAnnotations)
return pattern
}
@ -48,42 +44,42 @@ const themedPattern = (design, gist, overwrite, format, t) => {
* Generate svgs of all cutting layouts for the pattern
* @param {Pattern} pattern the pattern to generate cutting layouts for
* @param {Design} design the design constructor for the pattern
* @param {Object} gist the gist
* @param {Object} settings the settings
* @param {string} format the export format this pattern will be prepared for
* @param {function} t the i18n function
* @return {Object} a dictionary of svgs and related translation strings, keyed by fabric
* @return {Object} a dictionary of svgs and related translation strings, keyed by material
*/
const generateCutLayouts = (pattern, design, gist, format, t) => {
// get the fabrics from the already drafted base pattern
const fabrics = pattern.setStores[pattern.activeSet].cutlist.getCutFabrics(
const generateCutLayouts = (pattern, Design, settings, format, t, ui) => {
// get the materials from the already drafted base pattern
const materials = pattern.setStores[pattern.activeSet].cutlist.getCutFabrics(
pattern.settings[0]
) || ['fabric']
if (!fabrics.length) return
if (!materials.length) return
const isImperial = gist.units === 'imperial'
const isImperial = settings.units === 'imperial'
const cutLayouts = {}
// each fabric
fabrics.forEach((f) => {
// get the settings and layout for that fabric
const fabricSettings = fabricSettingsOrDefault(gist, f)
const fabricLayout = get(gist, ['layouts', 'cuttingLayout', f], true)
// each material
materials.forEach((f) => {
// get the settings and layout for that material
const materialSettings = materialSettingsOrDefault(settings.units, ui, f)
const materialLayout = get(ui, ['layouts', 'cut', f], true)
// make a new pattern
const fabricPattern = themedPattern(design, gist, { layout: fabricLayout }, format, t)
// add cut layout plugin and fabric plugin
.use(cutLayoutPlugin(f, fabricSettings.grainDirection))
.use(fabricPlugin({ ...fabricSettings, printStyle: true, setPatternSize: 'width' }))
const materialPattern = themedPattern(Design, settings, { layout: materialLayout }, format, t)
// add cut layout plugin and material plugin
.use(cutLayoutPlugin(f, materialSettings.grainDirection))
.use(materialPlugin({ ...materialSettings, printStyle: true, setPatternSize: 'width' }))
// draft and render
fabricPattern.draft()
const svg = fabricPattern.render()
materialPattern.draft()
const svg = materialPattern.render()
// include translations
cutLayouts[f] = {
svg,
title: t('plugin:' + f),
dimensions: t('plugin:fabricSize', {
width: formatMm(fabricSettings.sheetWidth, gist.units, 'notags'),
length: useFabricLength(isImperial, fabricPattern.height, 'notags'),
title: t('cut:' + f),
dimensions: t('cut:materialSize', {
width: formatMm(materialSettings.sheetWidth, settings.units, 'notags'),
length: useMaterialLength(isImperial, materialPattern.height, 'notags'),
interpolation: { escapeValue: false },
}),
}
@ -92,18 +88,28 @@ const generateCutLayouts = (pattern, design, gist, format, t) => {
return cutLayouts
}
/**
* Handle exporting the draft or gist
* Handle exporting the draft or settings
* format: format to export to
* gist: the gist
* design: the pattern constructor for the design to be exported
* settings: the settings
* Design: the pattern constructor for the design to be exported
* t: a translation function to attach to the draft
* app: an app instance
* onComplete: business to perform after a successful export
* onError: business to perform on error
* */
export const handleExport = async (format, gist, design, t, app, onComplete, onError) => {
export const handleExport = async ({
format,
settings,
Design,
design,
t,
startLoading,
stopLoading,
onComplete,
onError,
ui,
}) => {
// start the loading indicator
app.startLoading()
if (typeof startLoading === 'function') startLoading()
// get a worker going
const worker = new Worker(new URL('./export-worker.js', import.meta.url), { type: 'module' })
@ -115,7 +121,7 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE
// save it out
if (e.data.blob) {
const fileType = exportTypes.exportForPrinting.indexOf(format) === -1 ? format : 'pdf'
fileSaver.saveAs(e.data.blob, `freesewing-${gist.design || 'gist'}.${fileType}`)
fileSaver.saveAs(e.data.blob, `freesewing-${design || 'pattern'}.${fileType}`)
}
// do additional business
onComplete && onComplete(e)
@ -127,37 +133,35 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE
}
// stop the loader
app.stopLoading()
if (typeof stopLoading === 'function') stopLoading()
})
// pdf settings
const settings = {
...defaultPdfSettings,
...(gist._state.layout?.forPrinting?.page || {}),
const pageSettings = {
...defaultPrintSettings(settings.units),
...get(ui, printSettingsPath, {}),
}
// arguments to pass to the worker
const workerArgs = { format, gist, settings }
const workerArgs = { format, settings, pageSettings }
// data passed to the worker must be JSON serializable, so we can't pass functions or prototypes
// that means if it's not a data export there's more work to do before we can hand off to the worker
if (exportTypes.exportAsData.indexOf(format) === -1) {
gist.embed = false
settings.embed = false
// make a pattern instance for export rendering
const layout = gist.layouts?.printingLayout || gist.layout || true
let pattern = themedPattern(design, gist, { layout }, format, t)
const layout = settings.layout || ui.layouts?.print || true
let pattern = themedPattern(Design, settings, { layout }, format, t)
// a specified size should override the gist one
if (format !== 'pdf') {
settings.size = format
}
// a specified size should override the settings one
pageSettings.size = format
try {
// add pages to pdf exports
if (format !== 'svg') {
if (!exportTypes.exportForEditing.includes(format)) {
pattern.use(
pagesPlugin({
...settings,
...pageSettings,
printStyle: true,
renderBlanks: false,
setPatternSize: true,
@ -166,10 +170,10 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE
// add the strings that are used on the cover page
workerArgs.strings = {
design: capitalize(pattern.designConfig.data.name.replace('@freesewing/', '')),
design: capitalize(design),
tagline: t('common:sloganCome') + '. ' + t('common:sloganStay'),
url: window.location.href,
cuttingLayout: t('plugin:cuttingLayout'),
cuttingLayout: t('cut:cuttingLayout'),
}
}
@ -177,20 +181,22 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE
pattern.draft()
workerArgs.svg = pattern.render()
if (format === 'pdf') pageSettings.size = [pattern.width, pattern.height]
// add the svg and pages data to the worker args
workerArgs.pages = pattern.setStores[pattern.activeSet].get('pages')
// add cutting layouts if requested
if (format !== 'svg' && settings.cutlist) {
workerArgs.cutLayouts = generateCutLayouts(pattern, design, gist, format, t)
if (!exportTypes.exportForEditing.includes(format) && pageSettings.cutlist) {
workerArgs.cutLayouts = generateCutLayouts(pattern, Design, settings, format, t, ui)
}
// post a message to the worker with all needed data
worker.postMessage(workerArgs)
} catch (err) {
console.log(err)
app.stopLoading()
if (typeof stopLoading === 'function') stopLoading()
onError && onError(err)
}
}
// post a message to the worker with all needed data
worker.postMessage(workerArgs)
}

View file

@ -1,22 +1,29 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
/**
* A web worker to handle the business of exporting pattern files
* */
import yaml from 'js-yaml'
import axios from 'axios'
import { PdfMaker } from './pdf-maker'
import { SinglePdfMaker } from './single-pdf-maker.mjs'
/** when the worker receives data from the page, do the appropriate export */
addEventListener('message', async (e) => {
const { format, gist, svg } = e.data
const { format, settings, svg } = e.data
// handle export by type
try {
if (format === 'json') return exportJson(gist)
if (format === 'yaml') return exportYaml(gist)
if (format === 'github gist') return exportGithubGist(gist)
if (format === 'svg') return exportSvg(svg)
await exportPdf(e.data)
switch (format) {
case 'json':
return exportJson(settings)
case 'yaml':
return exportYaml(settings)
case 'github gist':
return exportGithubGist(settings)
case 'svg':
return exportSvg(svg)
default:
return await exportPdf(e.data)
}
} catch (e) {
postMessage({ success: false, error: e })
close()
@ -37,14 +44,14 @@ const exportBlob = (blobContent, type) => {
postSuccess(blob)
}
const exportJson = (gist) => exportBlob(JSON.stringify(gist, null, 2), 'application/json')
const exportJson = (settings) => exportBlob(JSON.stringify(settings, null, 2), 'application/json')
const exportYaml = (gist) => exportBlob(yaml.dump(gist), 'application/x-yaml')
const exportYaml = (settings) => exportBlob(yaml.dump(settings), 'application/x-yaml')
const exportSvg = (svg) => exportBlob(svg, 'image/svg+xml')
const exportPdf = async (data) => {
const maker = new PdfMaker(data)
const maker = data.format === 'pdf' ? new SinglePdfMaker(data) : new PdfMaker(data)
await maker.makePdf()
postSuccess(await maker.toBlob())
}

View file

@ -1,66 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { Popout } from 'shared/components/popout.mjs'
import { WebLink } from 'shared/components/web-link.mjs'
import { exportTypes, handleExport } from './export-handler.mjs'
export const ExportDraft = ({ gist, design, app }) => {
const [link, setLink] = useState(false)
const [error, setError] = useState(false)
const [format, setFormat] = useState(false)
const { t } = useTranslation(['app', 'plugin'])
const doExport = (format) => {
setLink(false)
setError(false)
setFormat(format)
handleExport(
format,
gist,
design,
t,
app,
(e) => {
if (e.data.link) {
setLink(e.data.link)
}
},
(e) => {
if (e.data?.error) {
setError(true)
}
}
)
}
return (
<div className="max-w-screen-xl m-auto">
<h2>{t('export')}</h2>
<p className="text-lg sm:text-xl">{t('exportPattern-txt')}</p>
{link && (
<Popout link compact>
<span className="font-bold mr-4 uppercase text-sm">{format}:</span>
<WebLink href={link} txt={link} />
</Popout>
)}
{error && (
<Popout warning compact>
<span className="font-bold mr-4 uppercase text-sm">{t('error')}:</span>
{t('somethingWentWrong')}
</Popout>
)}
<div className="flex flex-row flex-wrap gap-8">
{Object.keys(exportTypes).map((type) => (
<div key={type} className="flex flex-col gap-2 w-full sm:w-auto">
<h4>{t(type)}</h4>
{exportTypes[type].map((format) => (
<button key={format} className="btn btn-primary" onClick={() => doExport(format)}>
{type === 'exportForPrinting' ? `${format} pdf` : format}
</button>
))}
</div>
))}
</div>
</div>
)
}

View file

@ -1,4 +1,5 @@
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { Pdf, mmToPoints } from './pdf.mjs'
import SVGtoPDF from 'svg-to-pdfkit'
import { logoPath } from 'shared/components/logos/freesewing.mjs'
@ -8,11 +9,6 @@ const logoSvg = `<svg viewBox="0 0 25 25">
<path d="${logoPath}" />
</svg>`
/**
* PdfKit, the library we're using for pdf generation, uses points as a unit, so when we tell it things like where to put the svg and how big the svg is, we need those numbers to be in points
* The svg uses mm internally, so when we do spatial reasoning inside the svg, we need to know values in mm
* */
const mmToPoints = 2.834645669291339
const lineStart = 50
/**
* Freesewing's first explicit class?
@ -22,7 +18,7 @@ export class PdfMaker {
/** the svg as text to embed in the pdf */
svg
/** the document configuration */
settings
pageSettings
/** the pdfKit instance that is writing the document */
pdf
/** the export buffer to hold pdfKit output */
@ -51,16 +47,19 @@ export class PdfMaker {
pageCount = 0
lineLevel = 50
constructor({ svg, settings, pages, strings, cutLayouts }) {
this.settings = settings
constructor({ svg, pageSettings, pages, strings, cutLayouts }) {
this.pageSettings = pageSettings
this.pagesWithContent = pages.withContent
this.svg = svg
this.strings = strings
this.cutLayouts = cutLayouts
this.initPdf()
this.pdf = Pdf({
size: this.pageSettings.size.toUpperCase(),
layout: this.pageSettings.orientation,
})
this.margin = this.settings.margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points
this.margin = this.pageSettings.margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points
this.pageHeight = this.pdf.page.height - this.margin * 2 // this is in points because it comes from pdfKit
this.pageWidth = this.pdf.page.width - this.margin * 2 // this is in points because it comes from pdfKit
@ -73,22 +72,6 @@ export class PdfMaker {
this.svgHeight = this.rows * this.pageHeight
}
/** create the pdf document */
initPdf() {
// instantiate with the correct size and orientation
this.pdf = new PDFDocument({
size: this.settings.size.toUpperCase(),
layout: this.settings.orientation,
})
// PdfKit wants to flush the buffer on each new page.
// We can't save directly from inside a worker, so we have to manage the buffers ourselves so we can return a blob
this.buffers = []
// use a listener to add new data to our buffer storage
this.pdf.on('data', this.buffers.push.bind(this.buffers))
}
/** make the pdf */
async makePdf() {
await this.generateCoverPage()
@ -97,27 +80,14 @@ export class PdfMaker {
}
/** convert the pdf to a blob */
toBlob() {
return new Promise((resolve) => {
// have to do it this way so that the document flushes everything to buffers
this.pdf.on('end', () => {
// convert buffers to a blob
resolve(
new Blob(this.buffers, {
type: 'application/pdf',
})
)
})
// end the stream
this.pdf.end()
})
async toBlob() {
return this.pdf.toBlob()
}
/** generate the cover page for the pdf */
async generateCoverPage() {
// don't make one if it's not requested
if (!this.settings.coverPage) {
if (!this.pageSettings.coverPage) {
return
}
@ -170,8 +140,8 @@ export class PdfMaker {
}
/** generate the title for a cutting layout page */
async generateCutLayoutTitle(fabricTitle, fabricDimensions) {
this.addText(this.strings.cuttingLayout, 12, 2).addText(fabricTitle, 28)
async generateCutLayoutTitle(materialTitle, materialDimensions) {
this.addText(this.strings.cuttingLayout, 12, 2).addText(materialTitle, 28)
this.pdf.lineWidth(1)
this.pdf
@ -180,16 +150,16 @@ export class PdfMaker {
.stroke()
this.lineLevel += 5
this.addText(fabricDimensions, 16)
this.addText(materialDimensions, 16)
}
/** generate all cutting layout pages */
async generateCutLayoutPages() {
if (!this.settings.cutlist || !this.cutLayouts) return
if (!this.pageSettings.cutlist || !this.cutLayouts) return
for (const fabric in this.cutLayouts) {
for (const material in this.cutLayouts) {
this.nextPage()
const { title, dimensions, svg } = this.cutLayouts[fabric]
const { title, dimensions, svg } = this.cutLayouts[material]
await this.generateCutLayoutTitle(title, dimensions)
await this.generateSvgPage(svg)
}

View file

@ -0,0 +1,46 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
/**
* PdfKit, the library we're using for pdf generation, uses points as a unit, so when we tell it things like where to put the svg and how big the svg is, we need those numbers to be in points
* The svg uses mm internally, so when we do spatial reasoning inside the svg, we need to know values in mm
* */
export const mmToPoints = 2.834645669291339
/**
* A PDFKit Pdf with a few of our utilities included
* @returns
*/
export const Pdf = ({ size, layout }) => {
const pdf = new PDFDocument({
size,
layout,
})
// PdfKit wants to flush the buffer on each new page.
// We can't save directly from inside a worker, so we have to manage the buffers ourselves so we can return a blob
const buffers = []
// use a listener to add new data to our buffer storage
pdf.on('data', buffers.push.bind(buffers))
/** convert the pdf to a blob */
pdf.toBlob = function () {
return new Promise((resolve) => {
// have to do it this way so that the document flushes everything to buffers
pdf.on('end', () => {
// convert buffers to a blob
resolve(
new Blob(buffers, {
type: 'application/pdf',
})
)
})
// end the stream
pdf.end()
})
}
return pdf
}

View file

@ -0,0 +1,24 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { Pdf, mmToPoints } from './pdf.mjs'
import SVGtoPDF from 'svg-to-pdfkit'
/**
* Basic exporter for a single-page pdf containing the rendered pattern.
* This generates a PDF that is the size of the pattern and has no additional frills*/
export class SinglePdfMaker {
pdf
svg
constructor({ svg, pageSettings }) {
this.pdf = Pdf({ size: pageSettings.size.map((s) => s * mmToPoints) })
this.svg = svg
}
async makePdf() {
await SVGtoPDF(this.pdf, this.svg)
}
async toBlob() {
return this.pdf.toBlob()
}
}

View file

@ -0,0 +1,129 @@
addNotes: Add notes
addSettingsToNotes: Add settings to notes
advanced: Avancé
appliedMeasies: We applied a new measurements set to this pattern.
armhole: Emmanchure
attributes: Attributs
backPockets: Poches arrière
bookmarkPattern: Bookmark pattern
bottomRight: En bas à droite
changeMeasies: Mesures du schéma de changement
chooseATest: Choose a test scenario to run
chooseATestDesc: You can test how the design adapts to changes in a specific design option or measurements.
chooseATestMenuMobileMsg: To do so, open the test menu and select the scenario you want to run.
chooseATestMenuMsg: To do so, select the test scenario you want to run in the menu on the right.
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.
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.
chooseNewSet: Choisis un nouvel ensemble de mesures
closure: Fermeture
collar: Col
columns: columns
configurePattern: Configurer le modèle
construction: Construction
continueEditingTitle: Continue editing
continueEditingDesc: Load the newly saved pattern in the pattern editor, so you can make more changes.
cuffs: Poignets
currentPrintLayout: Current print layout
cutLayout: Disposition des coupes
darts: Pinces
designOptions.d: Teste l'effet d'une option sur l'apparence de ce modèle.
docs: Documentation
draft: Ébauche
draftPattern: Modèle d'ébauche
edit: Éditer
editCurrentMeasies: Modifier les mesures actuelles
editCurrentMeasiesDesc: Changes you make here will not be saved to your measurements sets, and will only affect this pattern.
editCurrentMeasiesHeader: Edit Pattern Measurements
editMeasiesByHand: Edit measurements by hand
editMeasiesByHandDesc: Manually set or override any measurements. These changes will only apply to the current pattern.
editSettings: Modifier la configuration
elastic: Élastique
export: Exporter
exportAsData: Exporter en tant que données
exportForEditing: Exporter pour édition
exportForPrinting: Exporter pour l'impression
exportPattern-txt: Exporter au format PDF adapté à votre imprimante, ou télécharger ce modèle dans une variété de formats
exportPattern: Exporter le patron
fit: Ajustement
frontPockets: Poches avant
generatePdf: Generate print-ready PDF
giveItAName: Donne-lui un nom
goToPatternTitle: Navigate to the pattern page
goToPatternDesc: Exit the pattern editor and navigate to the pattern page where you can update the pattern's metadata.
height: Hauteur
help: Aide
layoutSettings.d: Additional options to further optimize the printing layout of your pattern.
layoutSettings.t: Layout settings
length: Longueur
learnHowToUseEditor: Learn how to use FreeSewing's online pattern editor
measies: Mesures du motif
measiesOk: Nous avons toutes les mesures requises pour créer ce modèle.
measurements.d: Teste l'effet d'une mesure sur l'apparence de ce motif.
menu: Menu
name: Nom
noDesignFound: Nous n'avons pas pu trouver ce modèle. Cela ne devrait pas arriver, alors nous te serions très reconnaissants de nous le signaler.
noInlineDocs: Documention is not included in this build
noInlineDocsDesc: Please visit FreeSewing.org to access our documentation
notes: Remarques
pages: pages
part: Partie du modèle
partInfo: Informations sur les pièces du modèle
partTransfo: Part transformation buttons
partTransfoDesc: These buttons allow you to rotate or flip individual pattern parts.
partTransfoNo: Hide buttons
partTransfoNoDesc: Do not include these buttons on the pattern output
partTransfoYes: Show buttons
partTransfoYesDesc: Include these buttons on the pattern output (they will not be printed)
pathInfo: Informations sur le chemin
patternBookmarkCreated: Pattern bookmark created
patternInspector: Inspecteur de modèles
patternLogs: Fiches d'information sur les modèles
patternSaved: Pattern saved
pockets: Poches
printLayout: Mise en page d'impression
printSettings.d: Configure your pattern so you can print it just the way you like it. Includes page size & orientation, margins, and more.
printSettings.t: Print settings
reset: Réinitialiser
resetPrintLayout: Reset print layout
resetPrintLayoutDesc: Removes all manual changes to the print layout, and restores the default layout
rows: rows
save: Sauvegarder
savePattern: Enregistrer le patron
saveAsNewPattern: Save as new pattern
savePatternAs: Save pattern as...
savePatternAsHellip: Save pattern as...
saveSettings: Sauvegarder les paramètres
saveYourPattern: Sauvegarde ton modèle
seeMissingMeasies: See missing measurements
show: Afficher
showAllParts: Afficher toutes les parties du modèle
showMovableButtons: Boutons
showOnlyThisPart: Ne montre que cette partie du modèle
sleevecap: Tête de manche
style: Style
test: Test
testDesignMeasurement: "Test {design} measurements: {measurement}"
testDesignOption: "Test {design} design options: {option}"
testDesignSets: "Test {design} across measurements sets"
testMeasurements: Tester les mesures
testMeasurementsDesc: Test how the design adapts to changes to a specific measurement
testOptions: Test design options
testOptionsDesc: Test how the design adapts to changes in a specific design option
testPattern: Modèle de test
testSets: Test measurements sets
testSetsDesc: Test how the design adapts across different measurements sets
topLeft: En haut à gauche
weLackSomeMeasies: Il nous manque { nr } pour créer ce modèle
whereToGoAfterSaveAs: After saving the new pattern, what do you want to do?
width: Largeur
xTotalPagesSomeBlank: "{total} pages in total but {blank} are blank"
yamlEditViewError: Problèmes avec YAML
yamlEditViewErrorDesc: Nous avons sauvegardé votre entrée, mais il se peut que cela ne fonctionne pas pour les raisons suivantes
yamlEditViewTitleThing: 'Modifier la configuration du patron pour {thing}'
youCanPickOrEnter: Tu peux soit choisir un ensemble de mesures, soit les saisir à la main, mais nous ne pouvons pas procéder sans ces mesures.
youUseCustomValue: You are using the default value
youUseDefaultValue: You are using a custom value

View file

@ -27,7 +27,7 @@ export const defaultGist = {
margin: 2,
renderer: 'react',
embed: true,
debug: true,
expand: false,
}
export const preloadGist = {

View file

@ -0,0 +1,225 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { controlLevels } from 'shared/config/freesewing.config.mjs'
// Hooks
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
// Components
import {
BeakerIcon,
CodeIcon,
CutIcon,
OptionsIcon,
PrintIcon,
SaveIcon,
SaveAsIcon,
RightIcon,
LeftIcon,
DocsIcon,
MeasieIcon,
XrayIcon,
EditIcon,
ExportIcon,
} from 'shared/components/icons.mjs'
import Link from 'next/link'
import { MenuWrapper } from 'shared/components/workbench/menus/shared/menu-wrapper.mjs'
export const ns = ['workbench', 'sections']
const icons = {
test: BeakerIcon,
export: ExportIcon,
Edit: EditIcon,
cut: CutIcon,
draft: OptionsIcon,
print: PrintIcon,
save: SaveIcon,
saveas: SaveAsIcon,
logs: CodeIcon,
inspect: XrayIcon,
measies: MeasieIcon,
}
export const NavButton = ({
href,
label,
children,
onClick = false,
active = false,
extraClasses = 'lg:hover:bg-secondary lg:hover:text-secondary-content',
}) => {
const className = `w-full flex flex-row items-center px-4 py-2 ${extraClasses} ${
active
? 'font-bold lg:font-normal bg-secondary bg-opacity-10 lg:bg-secondary lg:text-secondary-content lg:bg-opacity-50'
: 'lg:bg-neutral lg:text-neutral-content'
}`
const span = <span className="block grow text-left">{label}</span>
return onClick ? (
<button {...{ onClick, className }} title={label}>
{span}
{children}
</button>
) : (
<Link {...{ href, className }} title={label}>
{span}
{children}
</Link>
)
}
const NavIcons = ({ setView, setDense, dense, view, saveAs = false, control }) => {
const { t } = useTranslation(['header'])
const iconSize = 'h-6 w-6 grow-0'
return (
<>
<NavButton
onClick={() => setDense(!dense)}
label=""
extraClasses="hidden lg:flex text-accent bg-neutral hover:bg-accent hover:text-neutral-content"
>
{dense ? (
<RightIcon
className={`${iconSize} group-hover:animate-[bounceright_1s_infinite] animate-[bounceright_1s_5]`}
stroke={4}
/>
) : (
<LeftIcon className={`${iconSize} animate-bounce-right`} stroke={4} />
)}
</NavButton>
{control >= controlLevels.views.draft && (
<NavButton
onClick={() => setView('draft')}
label={t('workbench:patternEditor')}
active={view === 'draft'}
>
<OptionsIcon className={iconSize} />
</NavButton>
)}
{control >= controlLevels.views.measies && (
<NavButton
onClick={() => setView('measies')}
label={t('workbench:measies')}
active={view === 'measies'}
>
<MeasieIcon className={iconSize} />
</NavButton>
)}
{control >= controlLevels.views.test && (
<NavButton
onClick={() => setView('test')}
label={t('workbench:patternTests')}
active={view === 'test'}
>
<BeakerIcon className={iconSize} />
</NavButton>
)}
{control >= controlLevels.views.print && (
<NavButton
onClick={() => setView('print')}
label={t('workbench:printLayout')}
active={view === 'print'}
>
<PrintIcon className={iconSize} />
</NavButton>
)}
{/*!isProduction && (
<NavButton
onClick={() => setView('cut')}
label={t('workbench:cutLayout')}
active={view === 'cut'}
>
<CutIcon className={iconSize} />
</NavButton>
)*/}
{control >= controlLevels.views.save && (
<NavButton
onClick={() => setView('save')}
label={t(`workbench:${saveAs ? 'savePattern' : 'savePatternAsHellip'}`)}
active={view === 'save'}
>
{saveAs ? <SaveIcon className={iconSize} /> : <SaveAsIcon className={iconSize} />}
</NavButton>
)}
{control >= controlLevels.views.export && (
<NavButton
onClick={() => setView('export')}
label={t('workbench:exportPattern')}
active={view === 'export'}
>
<ExportIcon className={iconSize} />
</NavButton>
)}
{control >= controlLevels.views.edit && (
<NavButton
onClick={() => setView('edit')}
label={t('workbench:editByHand')}
active={view === 'edit'}
>
<EditIcon className={iconSize} />
</NavButton>
)}{' '}
{control >= controlLevels.views.logs && (
<NavButton
onClick={() => setView('logs')}
label={t('workbench:patternLogs')}
active={view === 'logs'}
>
<CodeIcon className={iconSize} />
</NavButton>
)}
{control >= controlLevels.views.inspect && (
<NavButton
onClick={() => setView('inspect')}
label={t('workbench:patternInspector')}
active={view === 'inspect'}
>
<XrayIcon className={iconSize} />
</NavButton>
)}
{control >= controlLevels.views.docs && (
<NavButton
onClick={() => setView('docs')}
label={t('workbench:docs')}
active={view === 'docs'}
>
<DocsIcon className={iconSize} />
</NavButton>
)}
</>
)
}
export const WorkbenchHeader = ({ view, setView, saveAs = false, control = 4 }) => {
const [dense, setDense] = useState(true)
return (
<MenuWrapper
Icon={icons[view]}
wrapperClass={`w-64 min-h-screen pt-4
bg-neutral
shrink-0 grow-0 self-stretch
transition-all
drop-shadow-xl
${dense ? '-ml-52' : 'ml-0'}`}
buttonClass={`order-last bottom-16`}
keepOpenOnClick={false}
order={0}
type="nav"
>
<header
className={`
sticky top-4 lg:top-28
group
`}
>
<div
className={`
flex flex-col
items-center w-full `}
>
<NavIcons {...{ setView, setDense, dense, view, saveAs, control }} />
</div>
</header>
</MenuWrapper>
)
}

View file

@ -1,106 +0,0 @@
import { useState } from 'react'
import { ClearIcon, EditIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
const EditCount = (props) => (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">{props.min}</span>
<span className="label-text font-bold text-base-content">{props.value}</span>
<span className="label-text text-base-content">{props.max}</span>
</label>
<label className="input-group input-group-sm">
<input
type="number"
className={`
input input-sm input-bordered grow text-base-content
`}
value={props.value}
onChange={props.handleChange}
/>
<span className="text-base-content font-bold">#</span>
</label>
</div>
)
export const DesignOptionCount = (props) => {
const { t } = useTranslation(['app'])
const { count, max, min } = props.design.patternConfig.options[props.option]
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? count
: props.gist.options[props.option]
const [value, setValue] = useState(val)
const [editCount, setEditCount] = useState(false)
const handleChange = (evt) => {
const newVal = evt.target.value
setValue(newVal)
props.updateGist(['options', props.option], newVal)
}
const reset = () => {
setValue(count)
props.unsetGist(['options', props.option])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<div className="flex flex-row justify-between">
{editCount ? (
<EditCount
value={value}
handleChange={handleChange}
min={min}
max={max}
setEditCount={setEditCount}
t={t}
/>
) : (
<>
<span className="opacity-50">{min}</span>
<span className={`font-bold ${val === count ? 'text-secondary' : 'text-accent'}`}>
{val}
</span>
<span className="opacity-50">{max}</span>
</>
)}
</div>
<input
type="range"
max={max}
min={min}
step={1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === count ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span></span>
<div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === count}
onClick={reset}
>
<ClearIcon />
</button>
<button
title={t('editThing', { thing: '#' })}
className={`
btn btn-ghost btn-xs hover:text-secondary-focus
${editCount ? 'text-accent' : 'text-secondary'}
`}
onClick={() => setEditCount(!editCount)}
>
<EditIcon />
</button>
</div>
</div>
</div>
)
}

View file

@ -1,70 +0,0 @@
import { useState } from 'react'
import { ClearIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
export const DesignOptionList = (props) => {
const { t } = useTranslation([`o_${props.design.designConfig.data.name}`])
const { dflt, list, doNotTranslate = false } = props.design.patternConfig.options[props.option]
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? dflt
: props.gist.options[props.option]
const [value, setValue] = useState(val)
const handleChange = (newVal) => {
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist(['options', props.option], newVal)
}
}
const reset = () => {
setValue(dflt)
props.unsetGist(['options', props.option])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<div className="flex flex-row">
<div className="grow">
{list.map((choice) => (
<button
key={choice}
onClick={() => handleChange(choice)}
className={`
mr-1 mb-1 text-left text-lg w-full
${
choice === value
? choice === dflt
? 'text-secondary'
: 'text-accent'
: 'text-base-content'
}
`}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
translate-y-3
`}
>
<>&deg;</>
</span>
{doNotTranslate
? choice
: props.ot(`o_${props.design.designConfig.data.name}:${props.option}.o.${choice}`)}
</button>
))}
</div>
<button title={t('reset')} className="" disabled={val === dflt} onClick={reset}>
<span className={val === dflt ? 'text-base' : 'text-accent'}>
<ClearIcon />
</span>
</button>
</div>
</div>
)
}
export default DesignOptionList

View file

@ -1,136 +0,0 @@
import { useState } from 'react'
import { ClearIcon, EditIcon } from 'shared/components/icons.mjs'
import { formatMm, round } from 'shared/utils.mjs'
import { useTranslation } from 'next-i18next'
const EditOption = (props) => (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">
{props.min}
{props.suffix}
</span>
<span className="label-text font-bold text-base-content">
{props.value}
{props.suffix}
</span>
<span className="label-text text-base-content">
{props.max}
{props.suffix}
</span>
</label>
<label className="input-group input-group-sm">
<input
type="number"
className={`
input input-sm input-bordered grow text-base-content
`}
value={props.value}
onChange={props.handleChange}
/>
<span className="text-base-content font-bold">{props.suffix}</span>
</label>
</div>
)
export const DesignOptionPctDeg = (props) => {
const { t } = useTranslation(['app'])
const suffix = props.type === 'deg' ? '°' : '%'
const factor = props.type === 'deg' ? 1 : 100
const { max, min } = props.design.patternConfig.options[props.option]
const dflt = props.design.patternConfig.options[props.option][props.type || 'pct']
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? dflt
: props.gist.options[props.option] * factor
const [value, setValue] = useState(val)
const [editOption, setEditOption] = useState(false)
const handleChange = (evt) => {
const newVal = evt.target.value
setValue(newVal)
props.updateGist(['options', props.option], newVal / factor)
}
const reset = () => {
setValue(dflt)
props.unsetGist(['options', props.option])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{props.ot(`${props.option}.d`)}
</p>
<div className="flex flex-row justify-between">
{editOption ? (
<EditOption
value={value}
handleChange={handleChange}
min={min}
max={max}
setEditOption={setEditOption}
t={t}
suffix={suffix}
/>
) : (
<>
<span className="opacity-50">
{round(min)}
{suffix}
</span>
<span className={`font-bold ${val === dflt ? 'text-secondary' : 'text-accent'}`}>
{round(val)}
{suffix}
</span>
<span className="opacity-50">
{round(max)}
{suffix}
</span>
</>
)}
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span className={val === dflt ? 'text-secondary' : 'text-accent'}>
{props.design.patternConfig.options[props.option]?.toAbs && props.gist.measurements
? formatMm(
props.design.patternConfig.options[props.option].toAbs(value / 100, props.gist)
)
: ' '}
</span>
<div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
<button
title={t('editThing', { thing: suffix })}
className={`
btn btn-ghost btn-xs hover:text-secondary-focus
${editOption ? 'text-accent' : 'text-secondary'}
`}
onClick={() => setEditOption(!editOption)}
>
<EditIcon />
</button>
</div>
</div>
</div>
)
}

View file

@ -1,139 +0,0 @@
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { useTranslation } from 'next-i18next'
import { isDegreeMeasurement } from '../../../config/measurements'
import { measurementAsMm } from 'shared/utils.mjs'
/*
* This is a single input for a measurements
* Note that it keeps local state with whatever the user types
* but will only trigger a gist update if the input is valid.
*
* m holds the measurement name. It's just so long to type
* measurement and I always have some typo in it because dyslexia.
*/
export const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => {
const { t } = useTranslation(['app', 'measurements'])
const prefix = app.site === 'org' ? '' : 'https://freesewing.org'
const title = t(`measurements:${m}`)
const isDegree = isDegreeMeasurement(m)
const factor = useMemo(() => (isDegree ? 1 : gist.units == 'imperial' ? 25.4 : 10), [gist.units])
const isValValid = (val) =>
typeof val === 'undefined' || val === '' ? null : val != false && !isNaN(val)
const isValid = (newVal) => (typeof newVal === 'undefined' ? isValValid(val) : isValValid(newVal))
const [val, setVal] = useState(gist.measurements?.[m] / factor || '')
// keep a single reference to a debounce timer
const debounceTimeout = useRef(null)
const input = useRef(null)
// onChange
const update = useCallback(
(evt) => {
evt.stopPropagation()
let evtVal = evt.target.value
// set Val immediately so that the input reflects it
setVal(evtVal)
let useVal = isDegree ? evtVal : measurementAsMm(evtVal, gist.units)
const ok = isValid(useVal)
// only set to the gist if it's valid
if (ok) {
// debounce in case it's still changing
if (debounceTimeout.current !== null) {
clearTimeout(debounceTimeout.current)
}
debounceTimeout.current = setTimeout(() => {
// clear the timeout reference
debounceTimeout.current = null
updateMeasurements(useVal, m)
}, 500)
}
},
[gist.units]
)
// use this for better update efficiency
const memoVal = useMemo(() => gist.measurements?.[m], [gist])
// track validity against the value and the units
const valid = useMemo(
() => isValid(isDegree ? val : measurementAsMm(val, gist.units)),
[val, gist.units]
)
// hook to update the value or format when the gist changes
useEffect(() => {
// set the value to the proper value and format
if (memoVal) {
let gistVal = +(memoVal / factor).toFixed(2)
setVal(gistVal)
}
}, [memoVal, factor])
// focus when prompted by parent
useEffect(() => {
if (focus) {
input.current.focus()
}
}, [focus])
// cleanup
useEffect(() => {
clearTimeout(debounceTimeout.current)
}, [])
if (!m) return null
return (
<div className="form-control mb-2" key={`wrap-${m}`}>
<label className="label">
<span className="label-text font-bold text-xl">{title}</span>
<a
href={`${prefix}/docs/measurements/${m.toLowerCase()}`}
className="label-text-alt text-secondary hover:text-secondary-focus hover:underline"
title={`${t('docs')}: ${t(m)}`}
tabIndex="-1"
>
{t('docs')}
</a>
</label>
<label className="input-group input-group-lg">
<input
key={`input-${m}`}
ref={input}
type="text"
placeholder={title}
className={`
input input-lg input-bordered grow text-base-content border-r-0
${valid === false && 'input-error'}
${valid === true && 'input-success'}
`}
value={val}
onChange={update}
/>
<span
role="img"
className={`bg-transparent border-y
${valid === false && 'border-error text-neutral-content'}
${valid === true && 'border-success text-neutral-content'}
${valid === null && 'border-base-200 text-base-content'}
`}
>
{valid === true && '👍'}
{valid === false && '🤔'}
</span>
<span
className={`
${valid === false && 'bg-error text-neutral-content'}
${valid === true && 'bg-success text-neutral-content'}
${valid === null && 'bg-base-200 text-base-content'}
`}
>
{isDegree ? '° ' : gist.units == 'metric' ? 'cm' : 'in'}
</span>
</label>
</div>
)
}

View file

@ -1,126 +0,0 @@
import { useTranslation } from 'next-i18next'
import { CutLayoutSettings } from './settings.mjs'
import { Draft } from '../draft/index.mjs'
import { fabricPlugin } from '../plugin-layout-part.mjs'
import { cutLayoutPlugin } from './plugin-cut-layout.mjs'
import { pluginAnnotations } from '@freesewing/plugin-annotations'
import { measurementAsMm } from 'shared/utils.mjs'
import { useEffect } from 'react'
import get from 'lodash.get'
export const fabricSettingsOrDefault = (gist, fabric) => {
const isImperial = gist.units === 'imperial'
const sheetHeight = measurementAsMm(isImperial ? 36 : 100, gist.units)
const gistSettings = get(gist, ['_state', 'layout', 'forCutting', 'fabric', fabric])
const sheetWidth = gistSettings?.sheetWidth || measurementAsMm(isImperial ? 54 : 120, gist.units)
const grainDirection =
gistSettings?.grainDirection === undefined ? 90 : gistSettings.grainDirection
return { activeFabric: fabric, sheetWidth, grainDirection, sheetHeight }
}
const activeFabricPath = ['_state', 'layout', 'forCutting', 'activeFabric']
const useFabricSettings = (gist) => {
const activeFabric = get(gist, activeFabricPath) || 'fabric'
return fabricSettingsOrDefault(gist, activeFabric)
}
const useFabricDraft = (gist, design, fabricSettings) => {
// get the appropriate layout for the view
const layout =
get(gist, ['layouts', gist._state.view, fabricSettings.activeFabric]) || gist.layout || true
// hand it separately to the design
const draft = new design({ ...gist, layout })
const layoutSettings = {
sheetWidth: fabricSettings.sheetWidth,
sheetHeight: fabricSettings.sheetHeight,
}
let patternProps
try {
// add the fabric plugin to the draft
draft.use(fabricPlugin(layoutSettings))
// add the cutLayout plugin
draft.use(cutLayoutPlugin(fabricSettings.activeFabric, fabricSettings.grainDirection))
// also, pluginAnnotations and pluginFlip are needed
draft.use(pluginAnnotations)
// draft the pattern
draft.draft()
patternProps = draft.getRenderProps()
} catch (err) {
console.log(err, gist)
}
return { draft, patternProps }
}
const useFabricList = (draft) => {
return draft.setStores[0].cutlist.getCutFabrics(draft.settings[0])
}
const bgProps = { fill: 'none' }
export const CutLayout = (props) => {
const { t } = useTranslation(['workbench', 'plugin'])
const { gist, design, updateGist } = props
// disable xray
useEffect(() => {
if (gist?._state?.xray?.enabled) updateGist(['_state', 'xray', 'enabled'], false)
})
const fabricSettings = useFabricSettings(gist)
const { draft, patternProps } = useFabricDraft(gist, design, fabricSettings)
const fabricList = useFabricList(draft)
const setCutFabric = (newFabric) => {
updateGist(activeFabricPath, newFabric)
}
let name = design.designConfig.data.name
name = name.replace('@freesewing/', '')
const settingsProps = {
gist,
updateGist,
patternProps,
unsetGist: props.unsetGist,
...fabricSettings,
}
return patternProps ? (
<div>
<h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forCutting')}</h2>
<CutLayoutSettings {...settingsProps} />
<div className="my-4">
{fabricList.length > 1 ? (
<div className="tabs">
{fabricList.map((title) => (
<button
key={title}
className={`text-xl font-bold capitalize tab tab-bordered grow ${
fabricSettings.activeFabric === title ? 'tab-active' : ''
}`}
onClick={() => setCutFabric(title)}
>
{t('plugin:' + title)}
</button>
))}
</div>
) : null}
<Draft
draft={draft}
gist={gist}
updateGist={updateGist}
patternProps={patternProps}
bgProps={bgProps}
gistReady={props.gistReady}
layoutPart="fabric"
layoutType={['cuttingLayout', fabricSettings.activeFabric]}
layoutSetType="forCutting"
/>
</div>
</div>
) : null
}

View file

@ -1,191 +0,0 @@
import { addToOnly } from '../plugin-layout-part.mjs'
import { pluginMirror } from '@freesewing/plugin-mirror'
const prefix = 'mirroredOnFold'
// types of path operations
const opTypes = ['to', 'from', 'cp1', 'cp2']
const avoidRegx = new RegExp(`^(cutonfold|grainline|__scalebox|__miniscale|${prefix})`)
/**
* The plugin to handle all business related to mirroring, rotating, and duplicating parts for the cutting layout
* @param {string} material the material to generate a cutting layout for
* @param {number} grainAngle the angle of the material's grain
* @return {Object} the plugin
*/
export const cutLayoutPlugin = function (material, grainAngle) {
return {
hooks: {
// after each part
postPartDraft: (pattern) => {
// if it's a duplicated cut part, the fabric part, or it's not wanted by the pattern
if (
pattern.activePart.startsWith('cut.') ||
pattern.activePart === 'fabric' ||
!pattern.__wants(pattern.activePart)
)
return
// get the part that's just been drafted
const part = pattern.parts[pattern.activeSet][pattern.activePart]
// get this part's cutlist configuration
let partCutlist = pattern.setStores[pattern.activeSet].get(['cutlist', pattern.activePart])
// if there isn't one, we're done here
if (!partCutlist) return
// if the cutlist has materials but this isn't one of them
// or it has no materials but this isn't the main fabric
if (partCutlist.materials ? !partCutlist.materials[material] : material !== 'fabric') {
// hide the part because it shouldn't be shown on this fabric
part.hide()
return
}
// get the cutlist configuration for this material, or default to one
const matCutConfig =
partCutlist.materials?.[material] || (material === 'fabric' ? [{ cut: 1 }] : [])
// get the config of the active part to be inherited by all duplicates
const activePartConfig = pattern.config.parts[pattern.activePart]
// hide the active part so that all others can inherit from it and be manipulated separately
part.hide()
// for each set of cutting instructions for this material
matCutConfig.forEach((instruction, i) => {
// for each piece that should be cut
for (let c = 0; c < instruction.cut; c++) {
const dupPartName = `cut.${pattern.activePart}.${material}_${c + i + 1}`
// make a new part that will follow these cutting instructions
pattern.addPart({
name: dupPartName,
from: activePartConfig,
draft: ({ part, macro, utils }) => {
part.attributes.remove('transform')
// if they shouldn't be identical, flip every other piece
if (!instruction.identical && c % 2 === 1) {
part.attributes.add(
'transform',
grainAngle === 90 ? 'scale(-1, 1)' : 'scale(1, -1)'
)
}
macro('handleFoldAndGrain', {
partCutlist,
instruction,
})
// combine the transforms
const combinedTransform = utils.combineTransforms(
part.attributes.getAsArray('transform')
)
part.attributes.set('transform', combinedTransform)
return part
},
})
// add it to the only list if there is one
addToOnly(pattern, dupPartName)
}
})
},
},
macros: {
...pluginMirror.macros,
// handle mirroring on the fold and rotating to sit along the grain or bias
handleFoldAndGrain: ({ partCutlist, instruction }, { points, macro }) => {
// get the grain angle for the part for this set of instructions
const grainSpec = partCutlist.grain
? partCutlist.grain + (instruction.bias ? 45 : 0)
: undefined
// if the part has cutonfold instructions
if (partCutlist.cutOnFold) {
// if we're not meant to igore those instructions, mirror on the fold
if (!instruction.ignoreOnFold) macro('mirrorOnFold', { fold: partCutlist.cutOnFold })
// if we are meant to ignore those instructions, but there's a grainline
else if (grainSpec !== undefined) {
// replace the cutonfold with a grainline
macro('grainline', { from: points.cutonfoldVia1, to: points.cutonfoldVia2 })
macro('cutonfold', false)
}
}
// if there's a grain angle, rotate the part to be along it
macro('rotateToGrain', { bias: instruction.bias, grainSpec })
},
// mirror the part across the line indicated by cutonfold
mirrorOnFold: ({ fold }, { paths, snippets, macro, points, utils, Point }) => {
// get all the paths to mirror
const mirrorPaths = []
for (const p in paths) {
// skip ones that are hidden
if (!paths[p].hidden && !p.match(avoidRegx)) mirrorPaths.push(p)
}
// store all the points to mirror
const mirrorPoints = []
// store snippets by type so we can re-sprinkle later
const snippetsByType = {}
// for each snippet
let anchorNames = 0
for (var s in snippets) {
const snip = snippets[s]
// don't mirror these ones
if (['logo'].indexOf(snip.def) > -1) continue
// get or make an array for this type of snippet
snippetsByType[snip.def] = snippetsByType[snip.def] || []
// put the anchor on the list to mirror
const anchorName = `snippetAnchors_${anchorNames++}`
points[anchorName] = new Point(snip.anchor.x, snip.anchor.y)
mirrorPoints.push(anchorName)
snippetsByType[snip.def].push(`${prefix}${utils.capitalize(anchorName)}`)
}
// mirror
macro('mirror', {
paths: mirrorPaths,
points: mirrorPoints,
mirror: fold,
prefix,
})
// sprinkle the snippets
for (var def in snippetsByType) {
macro('sprinkle', {
snippet: def,
on: snippetsByType[def],
})
}
},
/**
* rotate the part so that it is oriented properly with regard to the fabric grain
* if the part should be on the bias, this rotates the part to lie on the bias
* while keeping the grainline annotation along the grain
*/
rotateToGrain: ({ bias, grainSpec }, { part, paths, points, Point }) => {
// the amount to rotate is the difference between this part's grain angle (as drafted) and the fabric's grain angle
let toRotate = grainSpec === undefined ? 0 : grainAngle + grainSpec
// don't over rotate
toRotate = toRotate % 180
if (toRotate < 0) toRotate += 180
// if there's no difference, don't rotate
if (toRotate === 0) return
if (paths.grainline && bias) {
const pivot = points.grainlineFrom || new Point(0, 0)
paths.grainline.ops.forEach((op) => {
opTypes.forEach((t) => {
if (op[t]) op[t] = op[t].rotate(45, pivot)
})
})
}
part.attributes.add('transform', `rotate(${toRotate})`)
},
},
}
}

View file

@ -1,139 +0,0 @@
import { ClearIcon, IconWrapper } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
import { formatFraction128, measurementAsMm, round, formatMm } from 'shared/utils.mjs'
import { ShowButtonsToggle } from '../draft/buttons.mjs'
const SheetIcon = (props) => (
<IconWrapper {...props}>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16l3.5-2 3.5 2 3.5-2 3.5 2z"
/>
</IconWrapper>
)
const GrainIcon = (props) => (
<IconWrapper {...props}>
<g>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 17l-5-5 5-5" />
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12l8 0" />
<path strokeLinecap="round" strokeLinejoin="round" d="M18 7l5 5-5 5" />
</g>
</IconWrapper>
)
const FabricSizer = ({ gist, updateGist, activeFabric, sheetWidth }) => {
const { t } = useTranslation(['workbench'])
let val = formatMm(sheetWidth, gist.units, 'none')
// onChange
const update = (evt) => {
evt.stopPropagation()
let evtVal = evt.target.value
// set Val immediately so that the input reflects it
val = evtVal
let useVal = measurementAsMm(evtVal, gist.units)
// only set to the gist if it's valid
if (!isNaN(useVal)) {
updateGist(['_state', 'layout', 'forCutting', 'fabric', activeFabric, 'sheetWidth'], useVal)
}
}
return (
<label className="input-group">
<span className="label-text font-bold">{`${t(activeFabric)} ${t('width')}`}</span>
<input
key="input-fabricWidth"
type="text"
className="input input-bordered grow text-base-content border-r-0 w-20"
value={val}
onChange={update}
/>
<span className="bg-transparent border input-bordered">
{gist.units == 'metric' ? 'cm' : 'in'}
</span>
</label>
)
}
export const GrainDirectionPicker = ({ grainDirection, activeFabric, updateGist }) => {
const { t } = useTranslation(['workbench'])
return (
<button
className={`
btn btn-primary flex flex-row gap-2 items-center
hover:text-primary-content ml-4
`}
onClick={() =>
updateGist(
['_state', 'layout', 'forCutting', 'fabric', activeFabric, 'grainDirection'],
grainDirection === 0 ? 90 : 0
)
}
>
<GrainIcon className={`h-6 w-6 mr-2 ${grainDirection === 0 ? '' : 'rotate-90'}`} />
<span>{t(`grainDirection`)}</span>
</button>
)
}
export const useFabricLength = (isImperial, height, format = 'none') => {
// regular conversion from mm to inches or cm
const unit = isImperial ? 25.4 : 10
// conversion from inches or cm to yards or meters
const fabricUnit = isImperial ? 36 : 100
// for fabric, these divisions are granular enough
const rounder = isImperial ? 16 : 10
// we convert the used fabric height to the right units so we can round it
const inFabricUnits = height / (fabricUnit * unit)
// we multiply it by the rounder, round it up, then divide by the rounder again to get the rounded amount
const roundCount = Math.ceil(rounder * inFabricUnits) / rounder
// format as a fraction for imperial, a decimal for metric
const count = isImperial ? formatFraction128(roundCount, format) : round(roundCount, 1)
return `${count}${isImperial ? 'yds' : 'm'}`
}
export const CutLayoutSettings = ({
gist,
patternProps,
unsetGist,
updateGist,
activeFabric,
sheetWidth,
grainDirection,
}) => {
const { t } = useTranslation(['workbench'])
const fabricLength = useFabricLength(gist.units === 'imperial', patternProps.height)
return (
<div className="flex flex-row justify-between mb-2 items-center">
<div className="flex">
<FabricSizer {...{ gist, updateGist, activeFabric, sheetWidth }} />
<GrainDirectionPicker {...{ updateGist, activeFabric, grainDirection }} />
</div>
<div>
<SheetIcon className="h-6 w-6 mr-2 inline align-middle" />
<span className="text-xl font-bold align-middle">{fabricLength}</span>
</div>
<div className="flex">
<ShowButtonsToggle
gist={gist}
updateGist={updateGist}
layoutSetType="forCutting"
></ShowButtonsToggle>
<button
key="reset"
onClick={() => unsetGist(['layouts', 'cuttingLayout', activeFabric])}
className="btn btn-primary btn-outline ml-4"
>
<ClearIcon className="h-6 w-6 mr-2" />
{t('reset')}
</button>
</div>
</div>
)
}

View file

@ -7,8 +7,8 @@ import get from 'lodash.get'
import Link from 'next/link'
import { PrimaryNavigation } from 'shared/components/navigation/primary.mjs'
import { RightIcon, LeftIcon, FreeSewingIcon } from 'shared/components/icons.mjs'
import { Header } from 'site/components/header.mjs'
import { Footer } from 'site/components/footer.mjs'
import { Header } from 'site/components/header/index.mjs'
import { Footer } from 'shared/components/footer/index.mjs'
import { Search } from 'site/components/search.mjs'
export const PageTitle = ({ app, slug, title }) => {

View file

@ -1,116 +0,0 @@
import { useRef } from 'react'
import { Stack } from './stack.mjs'
import { SvgWrapper } from '../../draft/svg.mjs'
import { PartInner } from '../../draft/part.mjs'
import get from 'lodash.get'
export const Draft = (props) => {
const {
patternProps,
gist,
updateGist,
app,
bgProps = {},
fitLayoutPart = false,
layoutType = 'printingLayout',
layoutSetType = 'forPrinting',
} = props
const svgRef = useRef(null)
if (!patternProps) return null
// keep a fresh copy of the layout because we might manipulate it without saving to the gist
const layoutPath = ['layouts'].concat(layoutType)
let layout = get(patternProps.settings[0], layoutPath) || {
...patternProps.autoLayout,
width: patternProps.width,
height: patternProps.height,
}
// 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 patternProps.stacks) {
if (pname == props.layoutPart && !fitLayoutPart) 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) {
updateGist(layoutPath, newLayout, history)
} 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 viewBox = layout.topLeft
? `${layout.topLeft.x} ${layout.topLeft.y} ${layout.width} ${layout.height}`
: false
// We need to make sure the `pages` part is at the bottom of the pile
// so we can drag-drop all parts on top of it.
// Bottom in SVG means we need to draw it first
const stacks = [
<PartInner
{...{
part: patternProps.parts[0][props.layoutPart],
partName: props.layoutPart,
gist,
skipGrid: true,
}}
key={props.layoutPart}
/>,
]
// then make a stack component for each remaining stack
for (var stackName in patternProps.stacks) {
if (stackName === props.layoutPart) {
continue
}
let stack = patternProps.stacks[stackName]
const stackPart = (
<Stack
{...{
key: stackName,
stackName,
stack,
layout,
app,
gist,
updateLayout,
isLayoutPart: stackName === props.layoutPart,
layoutSetType: layoutSetType,
}}
/>
)
stacks.push(stackPart)
}
return (
<SvgWrapper {...{ patternProps, gist, viewBox }} ref={svgRef}>
<rect x="0" y="0" width={layout.width} height={layout.height} {...bgProps} />
{stacks}
</SvgWrapper>
)
}

View file

@ -1,260 +0,0 @@
/*
* 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.
*
*
* Known issues
* - currently none
*
* I've sort of left it at this because I'm starting to wonder if we should perhaps re-think
* how custom layouts are supported in the core. And I would like to discuss this with the core team.
*/
import { useRef, useState, useEffect } from 'react'
import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
import { Part } from '../../draft/part.mjs'
import { getProps, angle } from '../../draft/utils.mjs'
import { drag } from 'd3-drag'
import { select } from 'd3-selection'
import { Buttons } from './buttons.mjs'
import get from 'lodash.get'
export const Stack = (props) => {
const { layout, stack, stackName, gist } = props
const stackLayout = layout.stacks?.[stackName]
const stackExists = typeof stackLayout?.move?.x !== 'undefined'
// Use a ref for direct DOM manipulation
const stackRef = useRef(null)
const centerRef = useRef(null)
const innerRef = useRef(null)
// State variable to switch between moving or rotating the part
const [rotate, setRotate] = useState(false)
// update the layout on mount
useEffect(() => {
// only update if there's a rendered part and it's not the pages or fabric part
if (stackRef.current && !props.isLayoutPart) {
updateLayout(false)
}
}, [stackRef, stackLayout])
// Initialize drag handler
useEffect(() => {
// don't drag the pages
if (props.isLayoutPart || !stackExists) return
handleDrag(select(stackRef.current))
}, [rotate, stackRef, stackLayout])
// // Don't just assume this makes sense
if (!stackExists) return null
// These are kept as vars because re-rendering on drag would kill performance
// Managing the difference between re-render and direct DOM updates makes this
// whole thing a bit tricky to wrap your head around
let translateX = stackLayout.move.x
let translateY = stackLayout.move.y
let stackRotation = stackLayout.rotate || 0
let rotation = stackRotation
let flipX = !!stackLayout.flipX
let flipY = !!stackLayout.flipY
const center = {
x: stack.topLeft.x + (stack.bottomRight.x - stack.topLeft.x) / 2,
y: stack.topLeft.y + (stack.bottomRight.y - stack.topLeft.y) / 2,
}
/** 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 })
const setTransforms = () => {
// get the transform attributes
const transforms = generateStackTransform(translateX, translateY, rotation, flipX, flipY, stack)
const me = select(stackRef.current)
me.attr('transform', transforms.join(' '))
return transforms
}
let didDrag = false
const handleDrag = 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: translateX, y: 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 (flipX) newRotation *= -1
if (flipY) newRotation *= -1
rotation = stackRotation + newRotation
} else {
translateX = event.x
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) updateLayout()
didDrag = false
})
/** reset the part's transforms */
const resetPart = () => {
rotation = 0
flipX = 0
flipY = 0
updateLayout()
}
/** toggle between dragging and rotating */
const toggleDragRotate = () => {
// only respond if the part should be able to drag/rotate
if (!stackRef.current || props.isLayoutPart) {
return
}
setRotate(!rotate)
}
/** update the layout either locally or in the gist */
const updateLayout = (history = true) => {
/** don't mess with what we don't lay out */
if (!stackRef.current || props.isLayoutPart) 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 { tl, br } = getTransformedBounds(stack, transforms)
// update it on the draft component
props.updateLayout(
stackName,
{
move: {
x: translateX,
y: translateY,
},
rotate: rotation % 360,
flipX,
flipY,
tl,
br,
},
history
)
}
/** Method to flip (mirror) the part along the X or Y axis */
const flip = (axis) => {
if (axis === 'x') flipX = !flipX
else flipY = !flipY
updateLayout()
}
/** method to rotate 90 degrees */
const rotate90 = (direction = 1) => {
if (flipX) direction *= -1
if (flipY) direction *= -1
rotation += 90 * direction
updateLayout()
}
// don't render if the part is empty
// if (Object.keys(part.snippets).length === 0 && Object.keys(part.paths).length === 0) return null;
const showButtons = get(gist, ['_state', 'layout', props.layoutSetType, 'showButtons'], true)
return (
<g id={`stack-${stackName}`} {...getProps(stack)} ref={stackRef}>
<g id={`stack-inner-${stackName}`} ref={innerRef}>
{stack.parts.map((part) => (
<Part {...{ part, partName: part.name, gist }} key={part.name}></Part>
))}
</g>
{!props.isLayoutPart && (
<>
<text x={center.x} y={center.y} ref={centerRef} />
<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(${-rotation}) scale(${
flipX ? -1 : 1
},${flipY ? -1 : 1})`}
flip={flip}
rotate={rotate}
setRotate={setRotate}
resetPart={resetPart}
rotate90={rotate90}
partName={stackName}
/>
) : null}
</>
)}
</g>
)
}

View file

@ -1,432 +0,0 @@
import { pluginAnnotations } from '@freesewing/plugin-annotations'
const name = 'Pages Plugin'
const version = '1.0.0'
export const sizes = {
a4: [210, 297],
a3: [297, 420],
a2: [420, 594],
a1: [594, 841],
a0: [841, 1188],
letter: [215.9, 279.4],
tabloid: [279.4, 431.8],
}
/** get a letter to represent an index less than 26*/
const indexLetter = (i) => String.fromCharCode('A'.charCodeAt(0) + i - 1)
/** get a string of letters to represent an index */
const indexStr = (i) => {
let index = i % 26
let quotient = i / 26
let result
if (i <= 26) {
return indexLetter(i)
} //Number is within single digit bounds of our encoding letter alphabet
if (quotient >= 1) {
//This number was bigger than the alphabet, recursively perform this function until we're done
if (index === 0) {
quotient--
} //Accounts for the edge case of the last letter in the dictionary string
result = indexStr(quotient)
}
if (index === 0) {
index = 26
} //Accounts for the edge case of the final letter; avoids getting an empty string
return result + indexLetter(index)
}
/**
* A plugin to add printer pages
* */
export const pagesPlugin = ({ size = 'a4', ...settings }) => {
const ls = settings.orientation === 'landscape'
let sheetHeight = sizes[size][ls ? 0 : 1]
let sheetWidth = sizes[size][ls ? 1 : 0]
sheetWidth -= settings.margin * 2
sheetHeight -= settings.margin * 2
return basePlugin({ ...settings, sheetWidth, sheetHeight })
}
export const fabricPlugin = (settings) => {
return basePlugin({
...settings,
partName: 'fabric',
responsiveColumns: false,
})
}
/** check if there is anything to render on the given section of the svg so that we can skip empty pages */
const doScanForBlanks = (stacks, layout, x, y, w, h) => {
let hasContent = false
for (var s in stacks) {
let stack = stacks[s]
// get the position of the part
let stackLayout = layout.stacks[s]
if (!stackLayout) continue
let stackMinX = stackLayout.tl?.x || stackLayout.move.x + stack.topLeft.x
let stackMinY = stackLayout.tl?.y || stackLayout.move.y + stack.topLeft.y
let stackMaxX = stackLayout.br?.x || stackMinX + stack.width
let stackMaxY = stackLayout.br?.y || stackMinY + stack.height
// check if the stack overlaps the page extents
if (
// if the left of the stack is further left than the right end of the page
stackMinX < x + w &&
// and the top of the stack is above the bottom of the page
stackMinY < y + h &&
// and the right of the stack is further right than the left of the page
stackMaxX > x &&
// and the bottom of the stack is below the top to the page
stackMaxY > y
) {
// the stack has content inside the page
hasContent = true
// so we stop looking
break
}
}
return hasContent
}
export function addToOnly(pattern, partName) {
const only = pattern.settings[0].only
if (only && !only.includes(partName)) {
pattern.settings[0].only = [].concat(only, partName)
}
}
function removeFromOnly(pattern, partName) {
const only = pattern.settings[0].only
if (only && only.includes(partName)) {
pattern.settings[0].only.splice(only.indexOf(partName), 1)
}
}
/**
* The base plugin for adding a layout helper part like pages or fabric
* sheetWidth: the width of the helper part
* sheetHeight: the height of the helper part
* boundary: should the helper part calculate its boundary?
* responsiveColumns: should the part make more columns if the pattern exceed its width? (for pages you want this, for fabric you don't)
* printStyle: should the pages be rendered for printing or for screen viewing?
* */
const basePlugin = ({
sheetWidth,
sheetHeight,
// boundary = false,
partName = 'pages',
responsiveColumns = true,
printStyle = false,
scanForBlanks = true,
renderBlanks = true,
setPatternSize = false,
}) => ({
name,
version,
hooks: {
preDraft: function (pattern) {
if (!responsiveColumns) {
pattern.settings[0].maxWidth = sheetWidth
}
addToOnly(pattern, partName)
// Add part
pattern.addPart({
name: partName,
draft: (shorthand) => {
const layoutData = shorthand.store.get('layoutData')
// only actually draft the part if layout data has been set
if (layoutData) {
shorthand.macro('addPages', layoutData, shorthand)
shorthand.part.unhide()
} else {
shorthand.part.hide()
}
return shorthand.part
},
})
},
postLayout: function (pattern) {
let { height, width, stacks } = pattern
if (!responsiveColumns) width = sheetWidth
// get the layout
const layout =
typeof pattern.settings[pattern.activeSet].layout === 'object'
? pattern.settings[pattern.activeSet].layout
: pattern.autoLayout
// if the layout doesn't start at 0,0 we want to account for that in our height and width
if (layout?.topLeft) {
height += layout.topLeft.y
responsiveColumns && (width += layout.topLeft.x)
}
// store the layout data so the part can use it during drafting
pattern.setStores[pattern.activeSet].set('layoutData', {
size: [sheetHeight, sheetWidth],
height,
width,
layout,
stacks,
})
// draft the part
pattern.draftPartForSet(partName, pattern.activeSet)
// if the pattern size is supposed to be re-set to the full width and height of all pages, do that
const generatedPageData = pattern.setStores[pattern.activeSet].get(partName)
if (setPatternSize === true || setPatternSize === 'width')
pattern.width = Math.max(pattern.width, sheetWidth * generatedPageData.cols)
if (setPatternSize === true || setPatternSize === 'height')
pattern.height = Math.max(pattern.height, sheetHeight * generatedPageData.rows)
removeFromOnly(pattern, partName)
},
preRender: function (svg) {
addToOnly(svg.pattern, partName)
},
postRender: function (svg) {
removeFromOnly(svg.pattern, partName)
},
},
macros: {
banner: pluginAnnotations.macros.banner,
/** draft the pages */
addPages: function (so, shorthand) {
const [h, w] = so.size
const cols = Math.ceil(so.width / w)
const rows = Math.ceil(so.height / h)
const { points, Point, paths, Path, part, macro, store } = shorthand
let count = 0
let withContent = {}
part.topLeft = so.layout.topLeft || { x: 0, y: 0 }
// get the layout from the pattern
const { layout } = so
for (let row = 0; row < rows; row++) {
let y = row * h
withContent[row] = {}
for (let col = 0; col < cols; col++) {
let x = col * w
let hasContent = true
if (scanForBlanks && layout) {
hasContent = doScanForBlanks(so.stacks, layout, x, y, w, h)
}
withContent[row][col] = hasContent
if (!renderBlanks && !hasContent) continue
if (hasContent) count++
const pageName = `_pages__row${row}-col${col}`
points[`${pageName}-tl`] = new Point(x, y)
points[`${pageName}-tr`] = new Point(x + w, y)
points[`${pageName}-br`] = new Point(x + w, y + h)
points[`${pageName}-bl`] = new Point(x, y + h)
points[`${pageName}-circle`] = new Point(x + w / 2, y + h / 2)
.setCircle(56, 'stroke-4xl muted fabric')
.attr('data-circle-id', `${pageName}-circle`)
points[`${pageName}-text`] = new Point(x + w / 2, y + h / 2)
.setText(
`${responsiveColumns ? indexStr(col + 1) : ''}${row + 1}`,
'text-4xl center baseline-center bold muted fill-fabric'
)
.attr('data-text-id', `${pageName}-text`)
paths[pageName] = new Path()
.attr('id', pageName)
.move(points[`${pageName}-tl`])
.line(points[`${pageName}-bl`])
.line(points[`${pageName}-br`])
.line(points[`${pageName}-tr`])
.close()
// add an edge warning if it can't expand horizontally
if (!responsiveColumns) {
paths[pageName + '_edge'] = new Path()
.move(points[`${pageName}-tr`])
.line(points[`${pageName}-br`])
// .move(points[`${pageName}-br`].translate(20, 0))
.addClass('help contrast stroke-xl')
shorthand.macro('banner', {
path: paths[pageName + '_edge'],
text: 'plugin:edgeOf' + shorthand.utils.capitalize(partName),
className: 'text-xl center',
spaces: 20,
})
}
if (col === cols - 1 && row === rows - 1) {
const br = points[`${pageName}-br`]
part.width = br.x
part.height = br.y
part.bottomRight = { x: br.x, y: br.y }
}
if (!printStyle) {
paths[pageName]
.attr('class', 'fill-fabric')
.attr(
'style',
`stroke-opacity: 0; fill-opacity: ${(col + row) % 2 === 0 ? 0.03 : 0.15};`
)
} else {
paths[pageName].attr('class', 'interfacing stroke-xs')
// add markers and rulers
macro('addPageMarkers', { row, col, pageName, withContent }, shorthand)
macro('addRuler', { xAxis: true, pageName }, shorthand)
macro('addRuler', { xAxis: false, pageName }, shorthand)
}
}
}
// Store page count in part
store.set(partName, { cols, rows, count, withContent, width: w, height: h })
},
/** add a ruler to the top left corner of the page */
addRuler({ xAxis, pageName }, shorthand) {
const { points, paths, Path } = shorthand
const isMetric = this.context.settings.units === 'metric'
// not so arbitrary number of units for the ruler
const rulerLength = isMetric ? 10 : 2
// distance to the end of the ruler
const endPointDist = [(isMetric ? 10 : 25.4) * rulerLength, 0]
const axisName = xAxis ? 'x' : 'y'
const rulerName = `${pageName}-${axisName}`
// start by making an endpoint for the ruler based on the axis
const endPoint = [endPointDist[xAxis ? 0 : 1], endPointDist[xAxis ? 1 : 0]]
points[`${rulerName}-ruler-end`] = points[`${pageName}-tl`].translate(
endPoint[0],
endPoint[1]
)
// also make a tick for the end of the ruler
points[`${rulerName}-ruler-tick`] = points[`${rulerName}-ruler-end`]
.translate(xAxis ? 0 : 3, xAxis ? 3 : 0)
// add a label to it
.attr('data-text', rulerLength + (isMetric ? 'cm' : '&quot;'))
// space the text properly from the end of the line
.attr('data-text-class', 'fill-interfacing baseline-center' + (xAxis ? ' center' : ''))
.attr(`data-text-d${xAxis ? 'y' : 'x'}`, xAxis ? 5 : 3)
// give the text an explicit id in case we need to hide it later
.attr('data-text-id', `${rulerName}-ruler-text`)
// start the path
paths[`${rulerName}-ruler`] = new Path()
.move(points[`${pageName}-tl`])
// give it an explicit id in case we need to hide it later
.attr('id', `${rulerName}-ruler`)
.attr('class', 'interfacing stroke-xs')
// get the distance between the smaller ticks on the rule
const division = (isMetric ? 0.1 : 0.125) / rulerLength
// Set up intervals for whole and half units.
const wholeInterval = isMetric ? 10 : 8
const halfInterval = isMetric ? 5 : 4
let tickCounter = 1
// we're going to go by fraction, so we want to do this up to 1
for (var d = division; d < 1; d += division) {
// make a start point
points[`${rulerName}-ruler-${d}-end`] = points[`${pageName}-tl`].shiftFractionTowards(
points[`${rulerName}-ruler-end`],
d
)
// base tick size on whether this is a major interval or a minor one
let tick = 1
// if this tick indicates a whole unit, extra long
if (tickCounter % wholeInterval === 0) tick = 3
// if this tick indicates half a unit, long
else if (tickCounter % halfInterval === 0) tick = 2
tickCounter++
// make a point for the end of the tick
points[`${rulerName}-ruler-${d}-tick`] = points[`${rulerName}-ruler-${d}-end`].translate(
xAxis ? 0 : tick,
xAxis ? tick : 0
)
// add the whole set to the ruler path
paths[`${rulerName}-ruler`]
.line(points[`${rulerName}-ruler-${d}-end`])
.line(points[`${rulerName}-ruler-${d}-tick`])
.line(points[`${rulerName}-ruler-${d}-end`])
}
// add the end
paths[`${rulerName}-ruler`]
.line(points[`${rulerName}-ruler-end`])
.line(points[`${rulerName}-ruler-tick`])
},
/** add page markers to the given page */
addPageMarkers({ row, col, pageName, withContent }, shorthand) {
const { macro, points } = shorthand
// these markers are placed on the top and left of the page,
// so skip markers for the top row or leftmost column
if (row !== 0 && withContent[row - 1][col])
macro('addPageMarker', {
along: [points[`${pageName}-tr`], points[`${pageName}-tl`]],
label: [`${row}`, `${row + 1}`],
isRow: true,
pageName,
})
if (col !== 0 && withContent[row][col - 1])
macro('addPageMarker', {
along: [points[`${pageName}-tl`], points[`${pageName}-bl`]],
label: [indexStr(col), indexStr(col + 1)],
isRow: false,
pageName,
})
},
/** add a page marker for either the row or the column, to aid with alignment and orientation */
addPageMarker({ along, label, isRow, pageName }) {
const { points, paths, Path, scale } = this.shorthand()
const markerName = `${pageName}-${isRow ? 'row' : 'col'}-marker`
// x and y distances between corners. one will always be 0, which is helpful
const xDist = along[0].dx(along[1])
const yDist = along[0].dy(along[1])
// size of the x mark should be impacted by the scale setting
const markSize = 4 * scale
// make one at 25% and one at 75%
for (var d = 25; d < 100; d += 50) {
// get points to make an x at d% along the edge
const dName = `${markerName}-${d}`
const centerName = `${dName}-center`
points[centerName] = along[0].translate((xDist * d) / 100, (yDist * d) / 100)
points[`${dName}-tr`] = points[centerName].translate(-markSize, markSize)
points[`${dName}-tl`] = points[centerName].translate(-markSize, -markSize)
points[`${dName}-br`] = points[centerName].translate(markSize, markSize)
points[`${dName}-bl`] = points[centerName].translate(markSize, -markSize)
// make a path for the x
paths[`${dName}`] = new Path()
.move(points[`${dName}-tr`])
.line(points[`${dName}-bl`])
.move(points[`${dName}-tl`])
.line(points[`${dName}-br`])
.attr('class', 'interfacing stroke-xs')
// give it an explicit ID in case we need to hide it later
.attr('id', dName)
// add directional labels
let angle = along[0].angle(along[1]) - 90
for (var i = 0; i < 2; i++) {
points[`${dName}-label-${i + 1}`] = points[centerName]
.shift(angle, markSize)
.setText(label[i], 'text-xs center baseline-center fill-interfacing')
// give it an explicit ID in case we need to hide it later
.attr('data-text-id', `${dName}-text-${i + 1}`)
// rotate for the next one
angle += 180
}
}
},
},
})

View file

@ -1,78 +0,0 @@
import { useEffect, useState } from 'react'
import { useTranslation } from 'next-i18next'
import { PrintLayoutSettings } from './settings.mjs'
import { Draft } from '../draft/index.mjs'
import { pagesPlugin } from '../plugin-layout-part.mjs'
import {
handleExport,
defaultPdfSettings,
} from 'shared/components/workbench/exporting/export-handler.mjs'
import { Popout } from 'shared/components/popout.mjs'
export const PrintLayout = (props) => {
// disable xray
useEffect(() => {
if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false)
})
const { t } = useTranslation(['workbench', 'plugin'])
const [error, setError] = useState(false)
const draft = props.draft
// add the pages plugin to the draft
const layoutSettings = {
...defaultPdfSettings,
...props.gist?._state?.layout?.forPrinting?.page,
}
draft.use(pagesPlugin(layoutSettings))
let patternProps
try {
// draft the pattern
draft.draft()
patternProps = draft.getRenderProps()
} catch (err) {
console.log(err, props.gist)
}
const bgProps = { fill: 'none' }
const exportIt = () => {
setError(false)
handleExport(
'pdf',
props.gist,
props.design,
t,
props.app,
() => setError(false),
() => setError(true)
)
}
let name = props.design.designConfig.data.name
name = name.replace('@freesewing/', '')
return (
<div>
<h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forPrinting')}</h2>
<div className="m-4">
<PrintLayoutSettings {...{ ...props, exportIt, layoutSettings }} draft={draft} />
{error && (
<Popout warning compact>
<span className="font-bold mr-4 uppercase text-sm">{t('error')}:</span>
{t('somethingWentWrong')}
</Popout>
)}
</div>
<Draft
draft={draft}
gist={props.gist}
updateGist={props.updateGist}
patternProps={patternProps}
bgProps={bgProps}
gistReady={props.gistReady}
layoutPart="pages"
/>
</div>
)
}

View file

@ -1,32 +0,0 @@
import { PageIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
export const PageOrientationPicker = ({ gist, updateGist }) => {
const { t } = useTranslation(['workbench'])
return (
<button
className={`
btn btn-primary flex flex-row gap-2 items-center
hover:text-primary-content
`}
onClick={() =>
updateGist(
['_state', 'layout', 'forPrinting', 'page', 'orientation'],
gist._state?.layout?.forPrinting?.page?.orientation === 'portrait'
? 'landscape'
: 'portrait'
)
}
>
<span
className={
gist._state?.layout?.forPrinting?.page?.orientation === 'landscape' ? 'rotate-90' : ''
}
>
<PageIcon />
</span>
<span>{t(`pageOrientation`)}</span>
</button>
)
}

View file

@ -1,58 +0,0 @@
import { PageSizeIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
import { Popout } from 'shared/components/popout.mjs'
const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid']
export const PageSizePicker = ({ gist, updateGist }) => {
const { t } = useTranslation(['workbench'])
const setSize = (size) => {
updateGist(['_state', 'layout', 'forPrinting', 'page', 'size'], size)
if (!gist._state?.layout?.forPrinting?.page?.orientation) {
updateGist(['_state', 'layout', 'forPrinting', 'page', 'orientation'], 'portrait')
}
}
if (
!gist._state?.layout?.forPrinting?.page?.size ||
sizes.indexOf(gist._state.layout.forPrinting.page.size) === -1
)
return (
<Popout tip>
<h3>{t('startBySelectingAThing', { thing: t('pageSize') })}</h3>
<div className="flex flex-row gap-4">
{sizes.map((size) => (
<button key={size} onClick={() => setSize(size)} className="btn btn-primary">
<span className="capitalize">{size}</span>
</button>
))}
</div>
</Popout>
)
return (
<div className={`dropdown`}>
<div
tabIndex="0"
className={`
m-0 btn btn-primary flex flex-row gap-2
hover:text-primary-content
`}
>
<PageSizeIcon />
<span>{t(`pageSize`)}:</span>
<span className="ml-2 font-bold">{gist._state.layout.forPrinting.page.size}</span>
</div>
<ul tabIndex="0" className="p-2 shadow menu dropdown-content bg-base-100 rounded-box w-52">
{sizes.map((size) => (
<li key={size}>
<button onClick={() => setSize(size)} className="btn btn-ghost hover:bg-base-200">
<span className="text-base-content capitalize">{size}</span>
</button>
</li>
))}
</ul>
</div>
)
}

View file

@ -1,129 +0,0 @@
import { PageSizePicker } from './pagesize-picker.mjs'
import { PageOrientationPicker } from './orientation-picker.mjs'
import { PrintIcon, RightIcon, ClearIcon, ExportIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
import { ShowButtonsToggle } from '../draft/buttons.mjs'
export const PrintLayoutSettings = (props) => {
const { t } = useTranslation(['workbench'])
let pages = props.draft?.setStores[0].get('pages')
if (!pages) return null
const { cols, rows, count } = pages
const setMargin = (evt) => {
props.updateGist(
['_state', 'layout', 'forPrinting', 'page', 'margin'],
parseInt(evt.target.value)
)
}
const setCoverPage = () => {
props.updateGist(
['_state', 'layout', 'forPrinting', 'page', 'coverPage'],
!props.layoutSettings.coverPage
)
}
const setCutlist = () => {
props.updateGist(
['_state', 'layout', 'forPrinting', 'page', 'cutlist'],
!props.layoutSettings.cutlist
)
}
return (
<div>
<div
className="flex flex-row justify-between
mb-2"
>
<div className="flex gap-4">
<PageSizePicker {...props} />
<PageOrientationPicker {...props} />
<button
key="export"
onClick={props.exportIt}
className="btn btn-primary btn-outline"
disabled={count === 0}
aria-disabled={count === 0}
>
<ExportIcon className="h-6 w-6 mr-2" />
{`${t('export')} PDF`}
</button>
</div>
<div className="flex gap-4">
<ShowButtonsToggle
gist={props.gist}
updateGist={props.updateGist}
layoutSetType="forPrinting"
></ShowButtonsToggle>
<button
key="reset"
onClick={() => props.unsetGist(['layouts', 'printingLayout'])}
className="btn btn-primary btn-outline"
>
<ClearIcon className="h-6 w-6 mr-2" />
{t('reset')}
</button>
</div>
</div>
<div className="flex flex-row justify-between">
<div className="flex flex-row">
<label htmlFor="pageMargin" className="label">
<span className="">{t('pageMargin')}</span>
<input
type="range"
max={50}
min={0}
step={1}
onChange={setMargin}
value={props.layoutSettings.margin}
className="range range-sm mx-2"
name="pageMargin"
/>
<div className="text-center">
<span className="text-secondary">{props.layoutSettings.margin}mm</span>
</div>
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent mx-2"
disabled={props.layoutSettings.margin == 10}
onClick={() => setMargin({ target: { value: 10 } })}
>
<ClearIcon />
</button>
</label>
<label htmlFor="coverPage" className="label">
<span className="mr-2">{t('coverPage')}</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={props.layoutSettings.coverPage}
onChange={setCoverPage}
/>
</label>
<label htmlFor="cutlist" className="label">
<span className="mr-2">{t('cutlist')}</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={props.layoutSettings.cutlist}
onChange={setCutlist}
/>
</label>
</div>
<div className="flex flex-row font-bold items-center px-0 text-xl">
<PrintIcon />
<span className="ml-2">{count}</span>
<span className="mx-6 opacity-50">|</span>
<RightIcon />
<span className="ml-2">{cols}</span>
<span className="mx-6 opacity-50">|</span>
<div className="rotate-90">
<RightIcon />
</div>
<span className="text-xl ml-2">{rows}</span>
</div>
</div>
</div>
)
}

View file

@ -1,6 +1,6 @@
import Markdown from 'react-markdown'
import { Mdx } from 'shared/components/mdx/dynamic.mjs'
import { formatMm } from 'shared/utils.mjs'
import { Tab, Tabs } from '../mdx/tabs.mjs'
import { Tab, Tabs } from '../tabs.mjs'
export const Error = ({ err }) => {
// Include the error name and message info if it isn't already at the top
@ -27,9 +27,7 @@ export const Error = ({ err }) => {
}
// Markdown wrapper to suppress creation of P tags
const Md = ({ children }) => (
<Markdown components={{ p: (props) => props.children }}>{children}</Markdown>
)
const Md = ({ md }) => <Mdx components={{ p: (props) => props.children }} md={md} />
const Log = ({ log, units }) => {
if (Array.isArray(log)) {
@ -43,9 +41,9 @@ const Log = ({ log, units }) => {
)
else return log.map((l) => <Log log={l} key={l} />)
} else if (log.message) return <Error err={log} />
else if (typeof log === 'string') return <Md>{log}</Md>
else if (typeof log === 'string') return <Md md={log} />
return <Md>Unrecognized log: {JSON.stringify(log, null, 2)}</Md>
return <Md md={`Unrecognized log: ${JSON.stringify(log, null, 2)}`} />
}
export const LogGroup = ({ type = 'info', logs = [], units = 'metric' }) =>

View file

@ -1,113 +0,0 @@
import React, { useMemo, useEffect, useState } from 'react'
import { MeasurementInput } from '../inputs/measurement.mjs'
import { adult, doll, giant } from '@freesewing/models'
import {
CisFemaleIcon as WomenswearIcon,
CisMaleIcon as MenswearIcon,
} from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
import { Setting } from '../menu/core-settings/setting.mjs'
import { settings } from '../menu/core-settings/index.mjs'
import { Tab, Tabs } from 'shared/components/mdx/tabs.mjs'
const groups = { adult, doll, giant }
const icons = {
cisFemale: <WomenswearIcon />,
cisMale: <MenswearIcon />,
}
export const WorkbenchMeasurements = ({ app, design, gist, updateGist, gistReady }) => {
const { t } = useTranslation(['app', 'cfp'])
// Method to handle measurement updates
const updateMeasurements = (value, m = false) => {
if (m === false) {
// Set all measurements
updateGist('measurements', value)
} else {
// Set one measurement
updateGist(['measurements', m], value)
}
}
const [firstInvalid, setFirstInvalid] = useState(undefined)
useEffect(() => {
if (!gistReady) {
return
}
for (const m of design.patternConfig?.measurements || []) {
if (!gist?.measurements?.[m]) {
setFirstInvalid(m)
return
}
setFirstInvalid(undefined)
}
}, [gistReady])
// Save us some typing
const inputProps = useMemo(() => ({ app, updateMeasurements, gist }), [app, gist])
const shortname = design.designConfig.data.name.replace('@freesewing/', '')
return (
<div className="m-auto max-w-2xl">
<h1>
<span className="capitalize mr-4 opacity-70">{shortname}:</span> {t('measurements')}
</h1>
<h2>{t('cfp:preloadMeasurements')}</h2>
<Tabs tabs="Adults, Dolls, Giants">
{Object.keys(groups).map((group) => (
<Tab tabId={group} key={group}>
{Object.keys(icons).map((type) => (
<React.Fragment key={type}>
<h4 className="mt-4">{t(type)}</h4>
<ul className="flex flex-row flex-wrap gap-2">
{Object.keys(groups[group][type]).map((m) => (
<li key={`${m}-${type}-${group}`} className="">
<button
className="flex flex-row btn btn-outline"
onClick={() => updateMeasurements(groups[group][type][m], false)}
>
{icons[type]}
{group === 'adult' ? `${t('size')} ${m}` : `${m}%`}
</button>
</li>
))}
</ul>
</React.Fragment>
))}
</Tab>
))}
</Tabs>
<h2 className="mt-8">{t('cfp:enterMeasurements')}</h2>
<div className="my-2 border p-4 rounded-lg shadow bg-base-200">
<Setting
key={'units'}
setting={'units'}
config={settings.units}
updateGist={updateGist}
{...inputProps}
/>
</div>
{design.patternConfig.measurements.length > 0 && (
<>
<h3>{t('requiredMeasurements')}</h3>
{design.patternConfig.measurements.map((m) => (
<MeasurementInput key={m} m={m} focus={m == firstInvalid} {...inputProps} />
))}
</>
)}
{design.patternConfig.optionalMeasurements.length > 0 && (
<>
<h3>{t('optionalMeasurements')}</h3>
{design.patternConfig.optionalMeasurements.map((m) => (
<MeasurementInput key={m} m={m} {...inputProps} />
))}
</>
)}
</div>
)
}

View file

@ -1,25 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
export const CoreSettingBool = (props) => {
const { t } = useTranslation(['app'])
const [value, setValue] = useState(props.gist[props.setting])
const toggle = () => {
props.updateGist([props.setting], !value)
setValue(!value)
}
return (
<Li>
<SumButton onClick={toggle}>
<SumDiv>
<Deg />
<span>{t(`settings:${props.setting}.t`)}</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}

View file

@ -1,49 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { Deg } from 'shared/components/workbench/menu/index.mjs'
export const CoreSettingList = (props) => {
const { t } = useTranslation(['settings'])
const { dflt } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (newVal) => {
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row">
<div className="grow">
{props.list.map((entry) => (
<button
key={entry.key}
onClick={() => handleChange(entry.key)}
className={`
mr-1 mb-1 text-left text-lg w-full hover:text-secondary-focus px-2
${entry.key === value && 'font-bold text-secondary'}
`}
>
<Deg />
{entry.title}
</button>
))}
</div>
</div>
</div>
)
}

View file

@ -1,71 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { formatMm } from 'shared/utils.mjs'
import { ClearIcon } from 'shared/components/icons.mjs'
export const CoreSettingMm = (props) => {
const { t } = useTranslation(['app', 'settings'])
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row justify-between">
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(min, props.gist.units) }}
/>
<span
className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}
dangerouslySetInnerHTML={{ __html: formatMm(val, props.gist.units) }}
/>
<span
className="opacity-50"
dangerouslySetInnerHTML={{ __html: formatMm(max, props.gist.units) }}
/>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -1,63 +0,0 @@
import { useState } from 'react'
import { ClearIcon } from 'shared/components/icons.mjs'
import { useTranslation } from 'next-i18next'
export const CoreSettingNr = (props) => {
const { t } = useTranslation(['app', 'settings'])
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = (evt) => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:${props.setting}.d`)}
</p>
<div className="flex flex-row justify-between">
<span className="opacity-50">{min}</span>
<span className={`font-bold ${val === dflt ? 'text-secondary-focus' : 'text-accent'}`}>
{val}
</span>
<span className="opacity-50">{max}</span>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -1,68 +0,0 @@
import { ClearIcon } from 'shared/components/icons.mjs'
import orderBy from 'lodash.orderby'
import { useTranslation } from 'next-i18next'
export const CoreSettingOnly = (props) => {
const { t } = useTranslation(['app', 'parts', 'settings'])
const list = props.design.patternConfig.draftOrder
const partNames = list.map((part) => ({ id: part, name: t(`parts:${part}`) }))
const togglePart = (part) => {
const parts = props.gist.only || []
const newParts = new Set(parts)
if (newParts.has(part)) newParts.delete(part)
else newParts.add(part)
if (newParts.size < 1) reset()
else props.updateGist(['only'], [...newParts])
}
const reset = () => {
props.unsetGist(['only'])
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-base-content opacity-60 italic">
{t(`settings:only.d`)}
</p>
<div className="flex flex-row">
<div className="grow">
{orderBy(partNames, ['name'], ['asc']).map((part) => (
<button
key={part.id}
onClick={() => togglePart(part.id)}
className={`
mr-1 mb-1 text-left text-lg w-full hover:text-secondary-focus px-2
${
props.gist?.only &&
props.gist.only.indexOf(part.id) !== -1 &&
'font-bold text-secondary-focus'
}
`}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
translate-y-3
`}
>
<>&deg;</>
</span>
{part.name}
</button>
))}
</div>
</div>
<div className="flex flex-row-reverse">
<button
title={t('reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={!props.gist.only || props.gist.only.length < 1}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}

View file

@ -1,29 +0,0 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
export const CoreSettingSaBool = (props) => {
const { t } = useTranslation(['app', 'settings'])
const [value, setValue] = useState(props.gist.saBool || false)
const toggle = () => {
props.setGist({
...props.gist,
saBool: !value,
sa: value ? 0 : props.gist.saMm,
})
setValue(!value)
}
return (
<Li>
<SumButton onClick={toggle}>
<SumDiv>
<Deg />
<span>{t('settings:sabool.t')}</span>
</SumDiv>
<SecText>{t(value ? 'yes' : 'no')}</SecText>
</SumButton>
</Li>
)
}

View file

@ -1,107 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { CoreSettingList as ListSetting } from './core-setting-list.mjs'
import { CoreSettingOnly as OnlySetting } from './core-setting-only.mjs'
import { CoreSettingMm as MmSetting } from './core-setting-mm.mjs'
import { CoreSettingNr as NrSetting } from './core-setting-nr.mjs'
import { CoreSettingBool as BoolSetting } from './core-setting-bool.mjs'
import { CoreSettingSaBool as SaBoolSetting } from './core-setting-sa-bool.mjs'
import { CoreSettingSaMm as SaMmSetting } from './core-setting-sa-mm.mjs'
import { formatMm } from 'shared/utils.mjs'
import {
SecText,
Li,
Details,
Summary,
SumDiv,
Deg,
} from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
const settings = {
paperless: (props) => <SecText>{props.t(props.gist.paperless ? 'yes' : 'no')}</SecText>,
complete: (props) => <SecText>{props.t(props.gist.complete ? 'yes' : 'no')}</SecText>,
debug: (props) => <SecText>{props.t(props.gist.debug ? 'yes' : 'no')}</SecText>,
locale: (props) => <SecText>{props.t(`i18n:${props.gist.locale}`)}</SecText>,
units: (props) => <SecText>{props.t(`${props.gist.units}Units`)}</SecText>,
margin: (props) => <SecText raw={formatMm(props.gist.margin, props.gist.units)} />,
scale: (props) =>
props.gist.scale === 1 ? (
<SecText>{props.gist.scale}</SecText>
) : (
<span className="text-accent">{props.gist.scale}</span>
),
saMm: (props) => <SecText raw={formatMm(props.gist.saMm, props.gist.units)} />,
renderer: (props) => <SecText>{props.config.titles[props.gist.renderer]}</SecText>,
only: (props) =>
props.gist?.only && props.gist.only.length > 0 ? (
<SecText>{props.gist.only.length}</SecText>
) : (
<span className="text-secondary-focus">{props.t('default')}</span>
),
}
const inputs = {
locale: (props) => (
<ListSetting
{...props}
list={props.config.list.map((key) => ({
key,
title: props.t(`i18n:${key}`),
}))}
/>
),
units: (props) => (
<ListSetting
{...props}
list={props.config.list.map((key) => ({
key,
title: props.t(`${key}Units`),
}))}
/>
),
margin: (props) => <MmSetting {...props} {...props.config} />,
scale: (props) => <NrSetting {...props} {...props.config} />,
saMm: (props) => <SaMmSetting {...props} {...props.config} />,
renderer: (props) => (
<ListSetting
{...props}
list={props.config.list.map((key) => ({
key,
title: props.config.titles[key],
}))}
/>
),
only: (props) => <OnlySetting {...props} />,
}
export const Setting = (props) => {
const { t } = useTranslation(['app', 'i18n', 'settings'])
if (props.setting === 'saBool') return <SaBoolSetting {...props} {...props.config} />
if (['paperless', 'complete', 'debug', 'xray'].indexOf(props.setting) !== -1)
return <BoolSetting {...props} {...props.config} />
const Input = inputs[props.setting]
const Value = settings[props.setting]
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
{props.setting === 'saMm' ? (
<>
<span>{t(`settings:sa.t`)}</span>
</>
) : (
<span>{t(`settings:${props.setting}.t`)}</span>
)}
</SumDiv>
<Value setting={props.setting} {...props} t={t} />
<Chevron />
</Summary>
<Input {...props} t={t} />
</Details>
</Li>
)
}

View file

@ -1,31 +0,0 @@
import { OptionsIcon } from 'shared/components/icons.mjs'
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { OptionGroup } from './option-group.mjs'
import { OptionComponent } from './option.mjs'
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
import { optionsMenuStructure } from 'shared/utils.mjs'
export const DesignOptions = (props) => {
const { t } = useTranslation(['app'])
const Option = props.Option ? props.Option : OptionComponent
const optionsMenu = optionsMenuStructure(props.design.patternConfig.options)
return (
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('designOptions')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.entries(optionsMenu).map(([group, options]) =>
typeof options === 'string' ? (
<Option {...props} type={options} option={group} key={group} />
) : (
<OptionGroup {...props} group={group} options={options} key={group} Option={Option} />
)
)}
</Ul>
</Details>
)
}

View file

@ -1,31 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
export const OptionGroup = (props) => {
const { t } = useTranslation(['optiongroups'])
const Option = props.Option
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{t(props.group)}</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{Object.entries(props.options).map(([option, type]) =>
typeof type === 'string' ? (
<Option {...props} type={type} option={option} key={option} />
) : (
<OptionGroup {...props} group={option} options={type} key={option} Option={Option} />
)
)}
</Ul>
</Details>
</Li>
)
}

View file

@ -1,19 +0,0 @@
import { DesignOptionPctDeg } from 'shared/components/workbench/inputs/design-option-pct-deg.mjs'
import { DesignOptionCount } from 'shared/components/workbench/inputs/design-option-count.mjs'
import { DesignOptionList } from 'shared/components/workbench/inputs/design-option-list.mjs'
import { Popout } from 'shared/components/popout.mjs'
export const Tmp = () => <p>not yet</p>
export const inputs = {
Pct: DesignOptionPctDeg,
Count: DesignOptionCount,
Deg: (props) => <DesignOptionPctDeg {...props} type="deg" />,
List: DesignOptionList,
Mm: () => (
<Popout fixme compact>
Mm options are deprecated. Please report this
</Popout>
),
Constant: Tmp,
}

View file

@ -1,78 +0,0 @@
import { useTranslation } from 'next-i18next'
import { formatMm, formatPercentage } from 'shared/utils.mjs'
export const values = {
Pct: (props) => {
const val =
typeof props.gist?.options?.[props.option] === 'undefined'
? props.design.patternConfig.options[props.option].pct / 100
: props.gist.options[props.option]
return (
<span
className={
val === props.design.patternConfig.options[props.option].pct / 100
? 'text-secondary-focus'
: 'text-accent'
}
>
{formatPercentage(val)}
{props.design.patternConfig.options[props.option]?.toAbs && props.gist.measurements
? ' | ' +
formatMm(props.design.patternConfig.options[props.option]?.toAbs(val, props.gist))
: null}
</span>
)
},
Bool: (props) => {
const { t } = useTranslation(['app'])
const dflt = props.design.patternConfig.options[props.option].bool
let current = props.gist?.options?.[props.option]
current = current === undefined ? dflt : current
return (
<span
className={
dflt == current || typeof current === 'undefined' ? 'text-secondary-focus' : 'text-accent'
}
>
{current ? t('yes') : t('no')}
</span>
)
},
Count: (props) => {
const dflt = props.design.patternConfig.options[props.option].count
const current = props.gist?.options?.[props.option]
return dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{dflt}</span>
) : (
<span className="text-accent">{current}</span>
)
},
List: (props) => {
const dflt = props.design.patternConfig.options[props.option].dflt
const current = props.gist?.options?.[props.option]
const prefix = `${props.option}.o.`
const translate = props.design.patternConfig.options[props.option]?.doNotTranslate
? (input) => input
: (input) => props.t(prefix + input)
return dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{translate(dflt)}</span>
) : (
<span className="text-accent">{translate(current)}</span>
)
},
Deg: (props) => {
const dflt = props.design.patternConfig.options[props.option].deg
const current = props.gist?.options?.[props.option]
return dflt == current || typeof current === 'undefined' ? (
<span className="text-secondary-focus">{dflt}&deg;</span>
) : (
<span className="text-accent">{current}&deg;</span>
)
},
Mm: () => {
return <p>No mm val yet</p>
},
Constant: () => {
return <p>No constant val yet</p>
},
}

View file

@ -1,67 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { optionType } from 'shared/utils.mjs'
import {
Li,
Details,
Summary,
SumButton,
SumDiv,
Deg,
} from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
import { values } from 'shared/components/workbench/menu/design-options/option-value.mjs'
import { inputs } from 'shared/components/workbench/menu/design-options/option-input.mjs'
import { capitalize } from 'shared/utils.mjs'
export const OptionComponent = (props) => {
const { t } = useTranslation([`o_${props.design.designConfig.data.name}`])
const opt = props.design.patternConfig.options[props.option]
const type = optionType(opt)
const Input = inputs[capitalize(type)]
const Value = values[capitalize(type)]
try {
const hide = opt.hide && opt.hide(props.gist)
if (hide) return null
} catch (e) {
console.warn(`error occurred in hide method for ${props.option}, so we'll just show it`, e)
}
if (type === 'bool') {
const toggleBoolean = () => {
const dflt = opt.bool
const current = props.gist?.options?.[props.option]
const newVal = typeof current === 'undefined' ? !dflt : !current
props.updateGist(['options', props.option], newVal)
}
return (
<Li>
<SumButton onClick={toggleBoolean}>
<SumDiv>
<Deg />
<span>{t(`${props.option}.t`)}</span>
</SumDiv>
<Value type={type} {...props} t={t} />
</SumButton>
</Li>
)
}
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>{t(`${props.option}.t`)}</span>
</SumDiv>
<Value type={type} {...props} t={t} />
<Chevron w={6} m={3} />
</Summary>
<Input {...props} ot={t} />
</Details>
</Li>
)
}

View file

@ -1,121 +0,0 @@
import { linkClasses } from 'shared/components/navigation/primary.mjs'
import { ViewMenu } from './view.mjs'
import { DesignOptions } from './design-options/index.mjs'
import { CoreSettings } from './core-settings/index.mjs'
import { XrayMenu } from './xray/index.mjs'
import { TestDesignOptions } from './test-design-options/index.mjs'
export const Ul = (props) => <ul className="pl-5 list-inside">{props.children}</ul>
export const Li = (props) => (
<li className="flex flex-row hover:border-r-2 hover:border-r-secondary">{props.children}</li>
)
export const Details = (props) => (
<details className="grow" open={props.open || false}>
{props.children}
</details>
)
export const Deg = () => (
<span className="text-3xl inline-block p-0 leading-3 px-2 translate-y-3">&deg;</span>
)
export const NoSumDiv = (props) => (
<div
className={`
grow px-2 ml-2 border-l-2
${linkClasses}
hover:cursor-resize
hover:border-secondary
sm:hover:border-secondary-focus
text-base-content sm:text-base-content
`}
>
{props.children}
</div>
)
export const SumDiv = (props) => (
<div
className={`
grow pl-2 border-l-2
${linkClasses}
hover:cursor-resize
hover:border-secondary
sm:hover:border-secondary-focus
text-base-content sm:text-base-content
`}
>
{props.children}
</div>
)
export const Summary = (props) => (
<summary
className={`
flex flex-row
px-2
text-base-content
sm:text-base-content
hover:cursor-row-resize
items-center
`}
>
{props.children}
</summary>
)
export const TopSummary = (props) => (
<summary
className={`
flex flex-row gap-4 text-lg
hover:cursor-row-resize
p-2
text-base-content
sm:text-base-content
items-center
`}
>
<span className="text-secondary-focus mr-4">{props.icon || null}</span>
{props.children}
</summary>
)
export const SumButton = (props) => (
<button
className={`
flex flex-row
px-2
w-full justify-between
text-left
text-base-content
sm:text-base-content
hover:cursor-pointer
items-center
mr-4
`}
onClick={props.onClick}
>
{props.children}
</button>
)
export const TopSumTitle = (props) => (
<span className={`grow ${linkClasses} hover:cursor-resize font-bold uppercase`}>
{props.children}
</span>
)
export const SecText = (props) =>
props.raw ? (
<span className="text-secondary-focus" dangerouslySetInnerHTML={{ __html: props.raw }} />
) : (
<span className="text-secondary-focus">{props.children}</span>
)
export const WorkbenchMenu = (props) => {
return (
<nav className="grow mb-12">
<ViewMenu {...props} />
{['draft', 'cuttingLayout', 'printingLayout'].indexOf(props.gist?._state?.view) > -1 && (
<>
<DesignOptions {...props} />
<CoreSettings {...props} />
{props.gist.renderer === 'react' && <XrayMenu {...props} />}
</>
)}
{props.gist?._state?.view === 'test' && <TestDesignOptions {...props} />}
</nav>
)
}

View file

@ -1,105 +0,0 @@
import { OptionsIcon } from 'shared/components/icons.mjs'
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { OptionGroup } from '../design-options/option-group.mjs'
import { Option } from './option.mjs'
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu/index.mjs'
import { useTranslation } from 'next-i18next'
import { optionsMenuStructure } from 'shared/utils.mjs'
import { adult, doll, giant } from '@freesewing/models'
const groups = { adult, doll, giant }
const SampleDesignOption = (props) => {
const { t } = useTranslation(['app'])
return (
<Option
updateGist={props.updateGist}
option={props.option}
design={props.design}
active={props.gist.sample?.option}
label={t(`o_${props.design.designConfig.data.name}:${props.option}.t`)}
sampleSettings={{ type: 'option', option: props.option }}
/>
)
}
export const TestDesignOptions = (props) => {
const { t } = useTranslation(['app'])
const optionsMenu = optionsMenuStructure(props.design.patternConfig.options)
const measies = props.draft?.config?.measurements || []
return (
<>
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('designOptions')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.entries(optionsMenu).map(([group, options]) =>
typeof options === 'string' ? (
<SampleDesignOption
{...props}
type={options}
option={group}
key={group}
sampleSettings={{ type: 'option', options }}
/>
) : (
<OptionGroup
{...props}
group={group}
options={options}
key={group}
Option={SampleDesignOption}
/>
)
)}
</Ul>
</Details>
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('measurements')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{measies.map((m) => (
<Option
updateGist={props.updateGist}
option={m}
design={props.design}
active={props.gist.sample?.option}
key={m}
label={m}
sampleSettings={{ type: 'measurement', measurement: m }}
/>
))}
</Ul>
</Details>
<Details open>
<TopSummary icon={<OptionsIcon />}>
<TopSumTitle>{t('models')}</TopSumTitle>
<Chevron />
</TopSummary>
<Ul className="pl-5 list-inside">
{Object.entries(groups).map(([group, modelGroups]) =>
Object.entries(modelGroups).map(([name, models]) => (
<Option
updateGist={props.updateGist}
label={`${group} - ${name}`}
design={props.design}
active={props.gist.sample?.option}
key={name}
sampleSettings={{ type: 'models', models }}
/>
))
)}
</Ul>
</Details>
</>
)
}

View file

@ -1,34 +0,0 @@
import { Li, SumButton, SumDiv } from 'shared/components/workbench/menu/index.mjs'
export const Option = (props) => {
const active = props.sampleSettings?.type === 'option' && props.active === props.option
const setSampleSettings = () => {
props.updateGist(
['sample'],
props.sampleSettings,
true // Close navigation on mobile
)
}
return (
<Li>
<SumButton onClick={setSampleSettings}>
<SumDiv active={active}>
<span
className={`
text-3xl inline-block p-0 leading-3 px-2
${
active
? 'text-secondary sm:text-secondary-focus translate-y-1 font-bold'
: 'translate-y-3'
}`}
>
{active ? <span>&bull;</span> : <span>&deg;</span>}
</span>
<span className={active ? 'text-secondary font-bold' : ''}>{props.label}</span>
</SumDiv>
</SumButton>
</Li>
)
}

View file

@ -1,128 +0,0 @@
import { MenuIcon } from 'shared/components/icons.mjs'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.mjs'
import { useTranslation } from 'next-i18next'
import { defaultGist } from 'shared/hooks/useGist.mjs'
export const ViewMenu = (props) => {
const { t } = useTranslation(['app'])
const entries = [
{
name: 'measurements',
title: t('measurements'),
onClick: () => props.updateGist(['_state', 'view'], 'measurements', true),
},
{
name: 'draft',
title: t('draftDesign', { design: props.design.designConfig.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'draft', true),
},
{
name: 'test',
title: t('testDesign', { design: props.design.designConfig.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'test', true),
},
{
name: 'printingLayout',
title:
t('layoutThing', { thing: props.design.designConfig.data.name }) + ': ' + t('forPrinting'),
onClick: () => props.updateGist(['_state', 'view'], 'printingLayout', true),
},
{
name: 'cuttingLayout',
title:
t('layoutThing', { thing: props.design.designConfig.data.name }) + ': ' + t('forCutting'),
onClick: () => props.updateGist(['_state', 'view'], 'cuttingLayout', true),
},
{
name: 'export',
title: t('exportThing', { thing: props.design.designConfig.data.name }),
onClick: () => props.updateGist(['_state', 'view'], 'export', true),
},
{
name: 'logs',
title: t('logs'),
onClick: () => props.updateGist(['_state', 'view'], 'logs', true),
},
{
name: 'yaml',
title: t('YAML'),
onClick: () => props.updateGist(['_state', 'view'], 'yaml', true),
},
{
name: 'json',
title: t('JSON'),
onClick: () => props.updateGist(['_state', 'view'], 'json', true),
},
{
name: 'edit',
title: t('editThing', { thing: 'YAML' }),
onClick: () => props.updateGist(['_state', 'view'], 'edit', true),
},
{
name: 'clear',
title: t('clearThing', { thing: 'YAML' }),
onClick: () => props.setGist(defaultGist(props.design, props.gist.locale)),
},
]
return (
<details className="py-1" open>
<summary
className={`
flex flex-row uppercase gap-4 font-bold text-lg
hover:cursor-row-resize
p-2
text-base-content
sm:text-base-content
items-center
`}
>
<span className="text-secondary-focus mr-4">
<MenuIcon />
</span>
<span className={`grow ${linkClasses} hover:cursor-resize`}>{t('view')}</span>
<Chevron />
</summary>
<ul className="pl-5 list-inside">
{entries.map((entry) => (
<li key={entry.title} className="flex flex-row">
<button
title={entry.title}
className={`
grow pl-2 border-l-2
${linkClasses}
hover:cursor-pointer
hover:border-secondary
sm:hover:border-secondary-focus
text-left
capitalize
${
entry.name === props.gist?._state?.view
? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus'
: 'text-base-content sm:text-base-content'
}
`}
onClick={entry.onClick}
>
<span
className={`
text-3xl mr-2 inline-block p-0 leading-3
${
entry.name === props.gist?._state?.view
? 'text-secondary sm:text-secondary-focus translate-y-1 font-bold'
: 'translate-y-3'
}
`}
>
{entry.name === props.gist?._state?.view ? <>&bull;</> : <>&deg;</>}
</span>
<span className={entry.name === props.gist?._state?.view ? 'font-bold' : ''}>
{entry.title}
</span>
</button>
</li>
))}
</ul>
</details>
)
}

View file

@ -1,45 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary'
import { Ul, Li, Details, Summary, SumDiv, NoSumDiv, Deg } from 'shared/components/workbench/menu'
export const XrayAttributes = ({ attr = false, t }) => {
if (!attr || !attr.list || Object.keys(attr.list).length < 1) return null
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
Attributes
</SumDiv>
<Chevron />
</Summary>
<Ul>
{Object.keys(attr.list).map((at) => (
<Li key={at}>
<Details>
<Summary>
<SumDiv>
<Deg />
{at}
</SumDiv>
<Chevron />
</Summary>
<Ul>
{attr.list[at].map((val) => (
<Li key={val}>
<NoSumDiv>
<Deg />
<span>{val === true ? t('app.yes') : val}</span>
</NoSumDiv>
</Li>
))}
</Ul>
</Details>
</Li>
))}
</Ul>
</Details>
</Li>
)
}

View file

@ -1,17 +0,0 @@
import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
export const XrayDisable = (props) => {
const { t } = useTranslation(['cfp', 'settings'])
return (
<Li>
<SumButton onClick={() => props.updateGist(['_state', 'xray', 'enabled'], false)}>
<SumDiv>
<Deg />
<span>{t('cfp:thingIsEnabled', { thing: t('settings:xray.t') })}</span>
</SumDiv>
</SumButton>
</Li>
)
}

View file

@ -1,50 +0,0 @@
import { XrayIcon } from 'shared/components/icons.mjs'
import { linkClasses, Chevron } from 'shared/components/navigation/primary.mjs'
import { ConsoleLog } from './log.mjs'
import { XrayReset } from './reset.mjs'
import { XrayDisable } from './disable.mjs'
import { XrayList } from './list.mjs'
import { Ul, Details, TopSummary } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
export const XrayMenu = (props) => {
const { t } = useTranslation(['app', 'settings'])
return (
<Details open>
<TopSummary icon={<XrayIcon />}>
{props.gist?._state?.xray?.enabled ? (
<>
<span className={`grow ${linkClasses} hover:cursor-resize font-bold uppercase`}>
{t('settings:xray.t')}
</span>
<Chevron />
</>
) : (
<>
<button
className={`grow ${linkClasses} hover:cursor-resize uppercase font-bold text-left`}
onClick={() => props.updateGist(['_state', 'xray', 'enabled'], true)}
>
{t('settings:xray.t')}
</button>
<span className="text-normal text-secondary">
{t('cfp:thingIsDisabled', { thing: t('settings:xray.t') })}
</span>
</>
)}
</TopSummary>
{props.gist?._state?.xray?.enabled && (
<Ul>
<XrayDisable {...props} />
<ConsoleLog {...props} />
<XrayReset {...props} />
{props.gist?._state?.xray?.parts &&
Object.keys(props.gist._state.xray.parts).map((partName) => (
<XrayList {...props} partName={partName} key={partName} />
))}
</Ul>
)}
</Details>
)
}

View file

@ -1,43 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import {
Ul,
Li,
Details,
Summary,
SumButton,
SumDiv,
Deg,
} from 'shared/components/workbench/menu/index.mjs'
export const ConsoleLog = (props) => (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>console.log()</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{['designConfig', 'patternConfig', 'gist', 'draft', 'renderProps'].map((it) => (
<Li key={it}>
<SumButton
onClick={() => {
if (it === 'designConfig') return console.log(props.design.designConfig)
if (it === 'patternConfig') return console.log(props.design.patternConfig)
if (it === 'renderProps') return console.log(props.draft.getRenderProps())
return console.log(props[it])
}}
>
<SumDiv>
<Deg />
<span>{it}</span>
</SumDiv>
</SumButton>
</Li>
))}
</Ul>
</Details>
</Li>
)

View file

@ -1,66 +0,0 @@
import { Chevron } from 'shared/components/navigation/primary'
import { Ul, Li, Details, Summary, SumDiv, NoSumDiv, Deg } from 'shared/components/workbench/menu'
import { XrayPoint } from './point'
const MoveLine = ({ op }) => <XrayPoint point={op.to} />
const Curve = ({ op }) =>
['cp1', 'cp2', 'to'].map((pnt) => (
<Li key={pnt}>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{pnt}</span>
</SumDiv>
<Chevron />
</Summary>
<XrayPoint point={op[pnt]} />
</Details>
</Li>
))
const XrayPathOp = ({ op }) => (
<Li>
{op.type === 'close' ? (
<NoSumDiv>
<Deg />
<span className="font-bold">{op.type}</span>
</NoSumDiv>
) : (
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{op.type}</span>
<span>To</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>{op.type === 'curve' ? <Curve op={op} /> : <MoveLine op={op} />}</Ul>
</Details>
)}
</Li>
)
export const XrayPathOps = ({ ops = false }) => {
if (!ops || ops.length < 1) return null
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">path.ops</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{ops.map((op) => (
<XrayPathOp op={op} key={op} />
))}
</Ul>
</Details>
</Li>
)
}

View file

@ -1,34 +0,0 @@
import { Ul, Li, NoSumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
import { formatMm } from 'shared/utils.mjs'
import { XrayAttributes } from './attributes.mjs'
import { XrayPathOps } from './path-ops.mjs'
export const XrayPath = ({ pathName, partName, draft, units }) => {
const path = draft?.parts?.[partName]?.paths?.[pathName]
if (!path) return null
return (
<Ul>
<XrayAttributes attr={path.attributes} />
<Li>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">path.render =</span>
<span>{JSON.stringify(path.render)}</span>
</NoSumDiv>
</Li>
<Li>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">path.length() =</span>
<span
dangerouslySetInnerHTML={{
__html: formatMm(path.length(), units),
}}
/>
</NoSumDiv>
</Li>
<XrayPathOps ops={path.ops} />
</Ul>
)
}

View file

@ -1,22 +0,0 @@
import { Ul, Li, NoSumDiv, Deg } from 'shared/components/workbench/menu'
import { round } from 'shared/utils'
import { XrayAttributes } from './attributes'
export const XrayPoint = ({ pointName, partName, draft, t }) => {
const point = draft?.parts?.[partName]?.points?.[pointName]
return point ? (
<Ul>
{['x', 'y'].map((coord) => (
<Li key={coord}>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">{coord} =</span>
<span>{round(point[coord])}</span>
</NoSumDiv>
</Li>
))}
<XrayAttributes attr={point.attributes} t={t} />
</Ul>
) : null
}

View file

@ -1,17 +0,0 @@
import { Li, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
import { useTranslation } from 'next-i18next'
export const XrayReset = (props) => {
const { t } = useTranslation(['app'])
return (
<Li>
<SumButton onClick={() => props.updateGist(['_state', 'xray'], { enabled: true })}>
<SumDiv>
<Deg />
<span>{t(`reset`)}</span>
</SumDiv>
</SumButton>
</Li>
)
}

View file

@ -0,0 +1,148 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { measurementAsMm } from 'shared/utils.mjs'
import {
SaIcon,
ScaleIcon,
PaperlessIcon,
I18nIcon,
UnitsIcon,
DetailIcon,
IncludeIcon,
MarginIcon,
ExpandIcon,
} from 'shared/components/icons.mjs'
import { controlLevels } from 'shared/config/freesewing.config.mjs'
export const defaultSamm = (units, inMm = true) => {
const dflt = units === 'imperial' ? 0.5 : 1
return inMm ? measurementAsMm(dflt, units) : dflt
}
export const loadSettingsConfig = ({
language = 'en',
units = 'metric',
sabool = false,
parts = [],
}) => ({
sabool: {
control: controlLevels.core.sa,
list: [0, 1],
choiceTitles: {
0: 'saNo',
1: 'saYes',
},
valueTitles: {
0: 'no',
1: 'yes',
},
dflt: 0,
icon: SaIcon,
},
samm: sabool
? {
control: controlLevels.core.sa,
min: 0,
max: units === 'imperial' ? 2 : 2.5,
dflt: defaultSamm(units),
icon: SaIcon,
}
: false,
paperless: {
control: controlLevels.core.paperless,
list: [0, 1],
choiceTitles: {
0: 'paperlessNo',
1: 'paperlessYes',
},
valueTitles: {
0: 'no',
1: 'yes',
},
dflt: 0,
icon: PaperlessIcon,
},
locale: {
control: controlLevels.core.locale,
list: ['de', 'en', 'es', 'fr', 'nl'],
dflt: language,
choiceTitles: {
de: 'de',
en: 'en',
es: 'es',
fr: 'fr',
nl: 'nl',
},
valueTitles: {
de: 'de.t',
en: 'en.t',
es: 'es.t',
fr: 'fr.t',
nl: 'nl.t',
},
icon: I18nIcon,
},
units: {
control: controlLevels.core.units,
list: ['metric', 'imperial'],
dflt: 'metric',
choiceTitles: {
metric: 'metric',
imperial: 'imperial',
},
valueTitles: {
metric: 'metric',
imperial: 'imperial',
},
icon: UnitsIcon,
},
complete: {
control: controlLevels.core.complete,
list: [1, 0],
dflt: 1,
choiceTitles: {
0: 'completeNo',
1: 'completeYes',
},
valueTitles: {
0: 'no',
1: 'yes',
},
icon: DetailIcon,
},
expand: {
control: controlLevels.core.expand,
list: [1, 0],
dflt: 1,
choiceTitles: {
0: 'expandNo',
1: 'expandYes',
},
valueTitles: {
0: 'no',
1: 'yes',
},
icon: ExpandIcon,
},
only: {
control: controlLevels.core.only,
dflt: false,
list: parts,
parts,
icon: IncludeIcon,
},
scale: {
control: controlLevels.core.scale,
min: 0.1,
max: 5,
dflt: 1,
step: 0.1,
icon: ScaleIcon,
},
margin: {
control: controlLevels.core.margin,
min: 0,
max: 2.5,
dflt: measurementAsMm(units === 'imperial' ? 0.125 : 0.2, units),
icon: MarginIcon,
},
})

View file

@ -0,0 +1,59 @@
coreSettings.t: Kerneinstellungen
coreSettings.d: Diese Einstellungen sind nicht spezifisch für das Design, sondern ermöglichen es dir, verschiedene Parameter der FreeSewing-Kernbibliothek anzupassen, die das Design für dich erstellt.
designOptions.t: Diese Optionen sind spezifisch für dieses Muster. Du kannst sie verwenden, um dein Muster auf verschiedene Weise anzupassen.
paperless.t: Papierlos
paperless.d: Bäume sind toll und das Zusammenkleben von Nähmustern macht nicht viel Spaß. Probiere unseren papierlosen Modus aus, damit du dein Schnittmuster gar nicht erst ausdrucken musst.
samm.t: Nahtzugabe Größe
samm.d: Steuert die Menge der Nahtzugabe, die in deinem Muster verwendet wird
sabool.t: Nahtzugabe einbeziehen
sabool.d: Legt fest, ob eine Nahtzugabe deinem Schnittmuster hinzugefügt werden soll
complete.t: Details
complete.d: Legt fest, wie detailliert das Schnittmuster dargestellt wird; entweder ein vollständiges Schnittmuster mit allen Details oder eine einfache Kontur der Schnittmusterteile
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: Enthaltene Teile
only.d: Hier kannst du genau festlegen, welche Teile des Musters in deinem Muster enthalten sein sollen.
locale.t: Sprache
locale.d: Legt die Sprache fest, die für dein Muster verwendet wird. Dies hat keinen Einfluss auf die Sprache der Website, sondern nur auf dieses spezielle Muster.
units.t: Einheiten
units.d: Diese Einstellung bestimmt, wie die Einheiten auf deinem Muster angezeigt werden
margin.t: Randabstand
margin.d: Legt den freien Rand um die einzelnen Teile des Schnittmusters fest
scale.t: Beschriftungsgröße
scale.d: "Steuert die allgemeine Linienbreite, Schriftgröße und andere Elementgrößen, \nbeeinflusst jedoch nicht den Maßstab des Schnittmusters selbst"
de.t: Deutsch
de.d: Verwende dies, um ein deutsches Muster zu erstellen
en.t: Englisch
en.d: Benutze dies, um ein englisches Muster zu erzeugen
es.t: Spanisch
es.d: Verwende dies, um ein spanisches Muster zu erstellen
fr.t: Französisch
fr.d: Verwende dies, um ein französisches Muster zu erstellen
nl.t: Holländisch
nl.d: Verwende dies, um ein niederländisches Muster zu erstellen
yes: Yes
no: No
completeYes.t: Ein vollständiges Muster generieren
completeYes.d: Dadurch wird ein vollständiges Muster mit allen Notationen, Linien und Markierungen erstellt. Verwende dies, wenn du dir nicht sicher bist, was du wählen sollst.
completeNo.t: Erstelle einen Musterumriss
completeNo.d: Erstelle nur die Umrisse der Musterteile. Verwende dies, wenn du einen Lasercutter verwenden möchtest oder andere spezielle Anforderungen hast.
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.
paperlessNo.t: Erzeuge ein regelmäßiges Muster
paperlessNo.d: Dadurch wird ein regelmäßiges Muster erzeugt, das du dann ausdrucken kannst.
paperlessYes.t: Erstelle ein papierloses Muster
paperlessYes.d: So entsteht ein Muster mit Maßen und einem Raster, das du auf Stoff oder ein anderes Medium übertragen kannst, ohne dass du das Muster ausdrucken musst.
metric: Metrisch
imperial: Kaiserlich
metric.t: Metrische Einheiten verwenden
metric.d: Verwende dies, wenn du das metrische System verwendest und mit Zentimetern und Millimetern vertraut bist. Das ist für die meisten Menschen auf der ganzen Welt die beste Wahl.
imperial.t: Imperiale Einheiten verwenden
imperial.d: Verwende dies, wenn dir Zoll und Bruchteile von Zoll vertrauter sind als Zentimeter. Dies ist oft die bevorzugte Wahl für Menschen in Großbritannien und den USA.
saNo.t: Nahtzugabe nicht berücksichtigen
saNo.d: Dadurch wird ein Muster erstellt, das keine Nahtzugabe enthält. Die Größe der Nahtzugabe spielt keine Rolle, da keine Nahtzugabe enthalten ist.
saYes.t: Nahtzugabe hinzufügen
saYes.d: Dadurch wird ein Muster erstellt, das die Nahtzugabe enthält. Die Größe der Nahtzugabe wird individuell festgelegt.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -0,0 +1,59 @@
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.
designOptions.t: These options are specific to this design. You can use them to customize your pattern in a variety of ways.
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
locale.t: Language
locale.d: Determines the language used on your pattern. This will not influence the language of the website, only of this specific 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
de.t: German
de.d: Use this to generate a German pattern
en.t: English
en.d: Use this to generate an English pattern
es.t: Spanish
es.d: Use this to generate a Spanish pattern
fr.t: French
fr.d: Use this to generate a French pattern
nl.t: Dutch
nl.d: Use this to generate a Dutch pattern
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 lasercutter 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.
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
imperial: Imperial
metric.t: Use metric units
metric.d: Use this if you use the metric system, and centimeters and millimeters are something you are familiar with. This is the best choice for most people around the world.
imperial.t: Use imperial units
imperial.d: Use this if inches and fractions or inches are more familiar to you then centimeters. This is often the preferred choice for people based in the UK & US.
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 allowancce 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.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -0,0 +1,59 @@
coreSettings.t: Configuración del núcleo
coreSettings.d: Estos ajustes no son específicos del diseño, sino que te permiten personalizar varios parámetros de la biblioteca central de FreeSewing, que genera el diseño por ti.
designOptions.t: Estas opciones son específicas de este diseño. Puedes utilizarlas para personalizar tu patrón de diversas formas.
paperless.t: Sin papel
paperless.d: Los árboles son increíbles, y pegar patrones de costura no es muy divertido. Prueba nuestro modo sin papel para evitar por completo la necesidad de imprimir tu patrón.
samm.t: Tamaño del margen de costura
samm.d: Controla la cantidad de margen de costura utilizado en tu patrón
sabool.t: Incluir margen de costura
sabool.d: Controla si incluir o no margen de costura en tu patrón
complete.t: Detalles
complete.d: Controla qué tan detallado es el patrón. Ya sea un patrón completo con todos los detalles, o un esquema básico de las partes del patrón
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: Piezas incluidas
only.d: Utilízalo para controlar exactamente qué partes del patrón se incluirán en tu patrón
locale.t: Idioma
locale.d: Determina el idioma utilizado en tu patrón. Esto no influirá en el idioma del sitio web, sólo de este patrón específico.
units.t: Unidades
units.d: Este ajuste determina cómo se muestran las unidades en tu patrón
margin.t: Margen
margin.d: Controla el margen alrededor de las partes del patrón
scale.t: Escala
scale.d: Controla el ancho de línea general, el tamaño de fuente y otros elementos que no cambian de escala junto a las medidas del patrón
de.t: Alemán
de.d: Utilízalo para generar un patrón alemán
en.t: Inglés
en.d: Utilízalo para generar un patrón inglés
es.t: Español
es.d: Utilízalo para generar un patrón español
fr.t: Francés
fr.d: Utilízalo para generar un patrón francés
nl.t: Holandés
nl.d: Utilízalo para generar un patrón holandés
yes: Yes
no: No
completeYes.t: Generar un patrón completo
completeYes.d: Esto generará un patrón completo con todas las anotaciones, líneas y marcas. Utilízalo si no estás seguro de qué elegir.
completeNo.t: Generar un esquema de patrón
completeNo.d: Genera sólo el contorno de las piezas del patrón. Utilízalo si quieres utilizar una cortadora láser o tienes otras necesidades específicas.
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.
paperlessNo.t: Generar un patrón regular
paperlessNo.d: Esto generará un patrón regular, que luego podrás imprimir.
paperlessYes.t: Generar un patrón sin papel
paperlessYes.d: Esto genera un patrón con dimensiones y una cuadrícula, que te permite transferirlo sobre tela u otro soporte sin necesidad de imprimir el patrón.
metric: Métrica
imperial: Imperial
metric.t: Utiliza unidades métricas
metric.d: Utilízalo si usas el sistema métrico decimal, y los centímetros y milímetros son algo con lo que estás familiarizado. Esta es la mejor opción para la mayoría de la gente de todo el mundo.
imperial.t: Utiliza unidades imperiales
imperial.d: Utilízalo si las pulgadas y fracciones o centímetros te resultan más familiares que los centímetros. Suele ser la opción preferida por las personas que viven en el Reino Unido y EE.UU.
saNo.t: No incluyas el margen de costura
saNo.d: Esto genera un patrón que no incluye ningún margen de costura. El tamaño del margen de costura no importa, ya que no se incluirá ningún margen de costura.
saYes.t: Incluir margen de costura
saYes.d: Esto genera un patrón que incluirá el margen de costura. El tamaño del margen de costura se ajusta individualmente.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -0,0 +1,59 @@
coreSettings.t: Paramètres de base
coreSettings.d: Ces réglages ne sont pas spécifiques au dessin, mais te permettent plutôt de personnaliser divers paramètres de la bibliothèque de base de FreeSewing, qui génère le dessin pour toi.
designOptions.t: Ces options sont spécifiques à ce modèle. Tu peux les utiliser pour personnaliser ton motif de différentes manières.
paperless.t: Sans papier
paperless.d: Les arbres, c'est génial, et assembler des patrons de couture avec du ruban adhésif, ce n'est pas très amusant. Essaie notre mode sans papier pour éviter d'avoir à imprimer complètement ton patron.
samm.t: Taille de la marge de couture
samm.d: Contrôle la quantité de marge de couture utilisée dans ton patron.
sabool.t: Inclure la marge de couture
sabool.d: Contrôle l'inclusion ou non de la marge de couture dans le patron
complete.t: Détails
complete.d: Contrôle à quel point votre patron est détaillé ; soit un patron complet avec tous les détails, ou simplement les contours des parties du patron
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: Pièces incluses
only.d: Utilise ceci pour contrôler exactement les parties du modèle qui seront incluses dans ton modèle
locale.t: Langue
locale.d: Détermine la langue utilisée sur ton motif. Cela n'influencera pas la langue du site Internet, mais seulement celle de ce patron spécifique.
units.t: Unités
units.d: Ce réglage détermine la façon dont les unités sont affichées sur ton modèle.
margin.t: Marges
margin.d: Contrôle la marge autour des pièces du patron
scale.t: Mise à l'échelle
scale.d: Contrôle la largeur de la ligne de contour, la taille de police et les autres éléments dont la valeur est indépendante des mesures du patron
de.t: Allemand
de.d: Utilise ceci pour générer un modèle allemand
en.t: Anglais
en.d: Utilise ceci pour générer un modèle anglais
es.t: Espagnol
es.d: Utilise ceci pour générer un modèle espagnol
fr.t: Français
fr.d: Utilise ceci pour générer un modèle français
nl.t: Néerlandais
nl.d: Utilise ceci pour générer un modèle hollandais
yes: Yes
no: No
completeYes.t: Génère un modèle complet
completeYes.d: Cela générera un modèle complet avec toutes les notations, les lignes et les marques. Utilise cette fonction si tu n'es pas certain de ce que tu dois choisir.
completeNo.t: Génère une ébauche de modèle
completeNo.d: Ne génère que le contour des pièces du modèle. Utilise cette option si tu souhaites utiliser une découpeuse laser ou si tu as d'autres besoins spécifiques.
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.
paperlessNo.t: Génère un motif régulier
paperlessNo.d: Cela générera un motif régulier, que tu pourras ensuite imprimer.
paperlessYes.t: Génère un modèle sans papier
paperlessYes.d: Cela génère un patron avec des dimensions et une grille, ce qui te permet de le transférer sur du tissu ou un autre support sans avoir besoin d'imprimer le patron.
metric: Métrique
imperial: Impérial
metric.t: Utilise les unités métriques
metric.d: Utilise ceci si tu utilises le système métrique et si les centimètres et les millimètres te sont familiers. C'est le meilleur choix pour la plupart des gens dans le monde.
imperial.t: Utilise les unités impériales
imperial.d: Utilise ceci si les pouces et les fractions de pouces te sont plus familiers que les centimètres. C'est souvent le choix préféré des personnes basées au Royaume-Uni et aux États-Unis.
saNo.t: Ne pas inclure la marge de couture
saNo.d: Cela génère un patron qui ne comprend pas de marge de couture. La taille de la marge de couture n'a pas d'importance car aucune marge de couture ne sera incluse.
saYes.t: Inclure la marge de couture
saYes.d: Cela génère un modèle qui comprendra une marge de couture. La taille de la marge de couture est définie individuellement.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -0,0 +1,109 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
//Dependencies
import { loadSettingsConfig, defaultSamm } from './config.mjs'
// Components
import { SettingsIcon, TrashIcon } from 'shared/components/icons.mjs'
import { WorkbenchMenu } from '../shared/index.mjs'
import { MenuItem } from '../shared/menu-item.mjs'
import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs'
// input components and event handlers
import { inputs, handlers } from './inputs.mjs'
// values
import { values } from './values.mjs'
import { useTranslation } from 'next-i18next'
export const ns = ['core-settings', 'modal']
/** A wrapper for {@see MenuItem} to handle core settings-specific business */
const CoreSetting = ({ name, config, control, updateFunc, current, passProps, ...rest }) => {
// is toggling allowed?
const allowToggle = control > 3 && config.list?.length === 2
const handlerArgs = {
updateFunc,
current,
config,
...passProps,
}
// get the appropriate event handler if there is one
const handler = handlers[name] ? handlers[name](handlerArgs) : updateFunc
return (
<MenuItem
{...{
name,
config,
control,
current,
passProps,
...rest,
allowToggle,
updateFunc: handler,
}}
docs={
<DynamicMdx
language={rest.language}
slug={`docs/site/draft/core-settings/${name.toLowerCase()}`}
/>
}
/>
)
}
export const ClearAllButton = ({ setSettings, compact = false }) => {
const { t } = useTranslation('core-settings')
return (
<div className={`${compact ? '' : 'text-center mt-8'}`}>
<button
className={`justify-self-center btn btn-error btn-outline ${compact ? 'btn-sm' : ''}`}
onClick={() => setSettings({})}
>
<TrashIcon />
{t('clearSettings')}
</button>
</div>
)
}
/**
* The core settings menu
* @param {Object} options.update settings and ui update functions
* @param {Object} options.settings core settings
* @param {Object} options.patternConfig the configuration from the pattern
* @param {String} options.language the menu language
* @param {Object} options.account the user account data
*/
export const CoreSettings = ({ update, settings, patternConfig, language, account, design }) => {
const settingsConfig = loadSettingsConfig({
language,
units: settings.units,
sabool: settings.sabool,
parts: patternConfig.draftOrder,
})
const passProps = {
samm: typeof settings.samm === 'undefined' ? defaultSamm(settings.units) : settings.samm,
units: settings.units,
}
return (
<WorkbenchMenu
{...{
config: settingsConfig,
control: account.control,
currentValues: settings,
Icon: SettingsIcon,
inputs,
language,
name: 'coreSettings',
ns,
passProps,
design,
updateFunc: update.settings,
values,
Item: CoreSetting,
}}
/>
)
}

View file

@ -0,0 +1,84 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { capitalize } from 'shared/utils.mjs'
import { ListInput, SliderInput, BoolInput, MmInput } from '../shared/inputs.mjs'
import { useTranslation } from 'next-i18next'
import { collection } from 'site/hooks/use-design.mjs'
/** an input for the 'only' setting. toggles individual parts*/
const OnlySettingInput = (props) => {
const { t } = useTranslation(collection)
const { config } = props
config.sideBySide = true
config.titleMethod = (entry, t) => {
const chunks = entry.split('.')
return <span className="font-medium text-base">{t(`${chunks[0]}:${chunks[1]}`)}</span>
}
config.valueMethod = (entry) => <span className="text-sm">{capitalize(entry.split('.')[0])}</span>
config.dense = true
// Sort alphabetically (translated)
const order = []
for (const part of config.list) {
const [ns, name] = part.split('.')
order.push(t(`${ns}:${name}`) + `|${part}`)
}
order.sort()
config.list = order.map((entry) => entry.split('|')[1])
return <ListInput {...props} />
}
export const inputs = {
complete: ListInput,
expand: ListInput,
locale: ListInput,
margin: MmInput,
only: OnlySettingInput,
paperless: BoolInput,
sabool: BoolInput,
samm: MmInput,
scale: SliderInput,
units: BoolInput,
}
/** custom event handlers for inputs that need them */
export const handlers = {
only:
({ updateFunc, current }) =>
(path, part) => {
// if there's no part being set, it's a reset
if (part === undefined) return updateFunc(path, part)
// add or remove the part from the set
let newParts = new Set(current || [])
if (newParts.has(part)) newParts.delete(part)
else newParts.add(part)
// if the set is now empty, reset
if (newParts.size < 1) newParts = undefined
// otherwise use the new set
else newParts = [...newParts]
updateFunc(path, newParts)
},
samm:
({ updateFunc, config }) =>
(_path, newCurrent) => {
// convert to millimeters if there's a value
newCurrent = newCurrent === undefined ? config.dflt : newCurrent
// update both values to match
updateFunc([
[['samm'], newCurrent],
[['sa'], newCurrent],
])
},
sabool:
({ updateFunc, samm }) =>
(_path, newCurrent) => {
updateFunc([
// update sabool to the new current
[['sabool'], newCurrent],
// set sa based on whether there's a current value or not
[['sa'], newCurrent ? samm : undefined],
])
},
}

View file

@ -0,0 +1,59 @@
coreSettings.t: Kerninstellingen
coreSettings.d: Deze instellingen zijn niet specifiek voor het ontwerp, maar stellen je in staat om verschillende parameters aan te passen van de FreeSewing kernbibliotheek, die het ontwerp voor je genereert.
designOptions.t: Deze opties zijn specifiek voor dit ontwerp. Je kunt ze gebruiken om je patroon op verschillende manieren aan te passen.
paperless.t: Papierloos
paperless.d: Bomen zijn geweldig en naaipatronen in elkaar tikken is niet leuk. Probeer onze papierloze modus om te voorkomen dat je je patroon helemaal moet uitprinten.
samm.t: Naadtoeslag Maat
samm.d: Bepaalt de hoeveelheid naadtoeslag die in je patroon wordt gebruikt
sabool.t: Naadtoeslag opnemen
sabool.d: Bepaalt of je wel of geen naadtoeslag in je patroon wilt opnemen
complete.t: Details
complete.d: Bepaalt hoe gedetailleerd het patroon is; Ofwel een patroon met alle details, ofwel een eenvoudiger patroon met slechts de contouren van de verschillende patroondelen
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: Meegeleverde onderdelen
only.d: Gebruik dit om precies te bepalen welke patroondelen in je patroon worden opgenomen
locale.t: Taal
locale.d: Bepaalt de taal van je patroon. Dit heeft geen invloed op de taal van de website, alleen van dit specifieke patroon.
units.t: Eenheden
units.d: Deze instelling bepaalt hoe eenheden worden weergegeven op je patroon
margin.t: Marge
margin.d: Bepaalt de marge rond patroondelen
scale.t: Schaal
scale.d: Bepaalt de totale lijnbreedte, lettergrootte en andere elementen die niet schalen met de metingen van het patroon
de.t: Duits
de.d: Gebruik dit om een Duits patroon te genereren
en.t: Engels
en.d: Gebruik dit om een Engels patroon te genereren
es.t: Spaans
es.d: Gebruik dit om een Spaans patroon te genereren
fr.t: Frans
fr.d: Gebruik dit om een Frans patroon te genereren
nl.t: Nederlands
nl.d: Gebruik dit om een Nederlands patroon te genereren
yes: Yes
no: No
completeYes.t: Genereer een compleet patroon
completeYes.d: Dit genereert een compleet patroon met alle notaties, lijnen en markeringen. Gebruik dit als je niet zeker weet wat je moet kiezen.
completeNo.t: Genereer een patroonoverzicht
completeNo.d: Genereer alleen de omtrek van de patroondelen. Gebruik dit als je een lasercutter wilt gebruiken of andere specifieke wensen hebt.
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.
paperlessNo.t: Een regelmatig patroon genereren
paperlessNo.d: Dit genereert een regelmatig patroon dat je vervolgens kunt afdrukken.
paperlessYes.t: Een papierloos patroon genereren
paperlessYes.d: Dit genereert een patroon met afmetingen en een raster, waardoor je het kunt overbrengen op stof of een ander medium zonder het patroon te hoeven afdrukken.
metric: Metrisch
imperial: Keizerlijk
metric.t: Gebruik metrische eenheden
metric.d: Gebruik deze als je het metrische stelsel gebruikt en centimeters en millimeters iets zijn waar je bekend mee bent. Dit is de beste keuze voor de meeste mensen over de hele wereld.
imperial.t: Gebruik imperiale eenheden
imperial.d: Gebruik dit als je meer vertrouwd bent met inches en fracties of inches dan met centimeters. Dit is vaak de voorkeursoptie voor mensen in het Verenigd Koninkrijk en de Verenigde Staten.
saNo.t: Exclusief naadtoeslag
saNo.d: Dit genereert een patroon zonder naadtoeslag. De grootte van de naadtoeslag maakt niet uit, omdat er geen naadtoeslag wordt opgenomen.
saYes.t: Inclusief naadtoeslag
saYes.d: Dit genereert een patroon met naadtoeslag. De grootte van de naadtoeslag wordt individueel ingesteld.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -0,0 +1,59 @@
coreSettings.t: Основні налаштування
coreSettings.d: Ці налаштування не є специфічними для дизайну, а дозволяють вам налаштувати різні параметри основної бібліотеки FreeSewing, яка генерує дизайн для вас.
designOptions.t: Ці опції є специфічними для цього дизайну. Ви можете використовувати їх, щоб налаштувати свій шаблон у різний спосіб.
paperless.t: Без паперу
paperless.d: Дерева - це круто, а склеювати викрійки не дуже весело. Спробуйте наш безпаперовий режим, щоб уникнути необхідності роздруковувати викрійку.
samm.t: Розмір припуску на шов
samm.d: Керує величиною припусків на шви, що використовуються у вашій деталі
sabool.t: Включити припуски на шви
sabool.d: Керує наявністю припусків на шви в Вашій викрійці
complete.t: Деталі
complete.d: 'Керує докладність викрійки: відображувати повноцінну викрійку з усіма подробицями чи лише основний контур елементів викрійки'
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: Деталі, що входять до комплекту
only.d: Використовуйте цей параметр, щоб точно контролювати, які деталі будуть включені в деталь
locale.t: Мова
locale.d: Визначає мову, яка використовується у вашому шаблоні. Це не вплине на мову веб-сайту, лише на мову цього конкретного шаблону.
units.t: Одиниці
units.d: Цей параметр визначає спосіб відображення одиниць виміру на лекалах
margin.t: Маржа.
margin.d: Контролює відступ навколо елементів викрійки
scale.t: Масштаб
scale.d: Керує ширину контуру, кегль шрифту та інші елементи, які не масштабуються відповідно до замірів викрійки
de.t: Німецька
de.d: Скористайтеся цим, щоб згенерувати німецький шаблон
en.t: Англійська мова
en.d: Використовуйте це, щоб згенерувати англійський шаблон
es.t: Іспанська
es.d: Скористайтеся цим, щоб створити іспанський шаблон
fr.t: Французька
fr.d: Використовуйте це, щоб створити французький візерунок
nl.t: Голландська
nl.d: Використовуйте це, щоб створити голландський візерунок
yes: Yes
no: No
completeYes.t: Створіть повний шаблон
completeYes.d: В результаті буде згенеровано повну викрійку з усіма позначеннями, лініями, маркуванням. Використовуйте цю функцію, якщо ви не впевнені, що вибрати.
completeNo.t: Створіть контур деталі
completeNo.d: Створюйте лише контури деталей лекал. Використовуйте цей параметр, якщо ви збираєтеся використовувати лазерний різак або маєте інші специфічні потреби.
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.
paperlessNo.t: Створіть регулярний шаблон
paperlessNo.d: В результаті буде створено звичайний шаблон, який ви можете роздрукувати.
paperlessYes.t: Створіть безпаперовий шаблон
paperlessYes.d: При цьому генерується викрійка з розмірами і сіткою, що дозволяє перенести її на тканину або інший носій без необхідності роздруковувати викрійку.
metric: Метрика
imperial: Імперіал
metric.t: Використовуйте метричні одиниці
metric.d: Використовуйте його, якщо ви користуєтеся метричною системою, а сантиметри і міліметри - це те, що вам знайоме. Це найкращий вибір для більшості людей у всьому світі.
imperial.t: Використовуйте імперські одиниці
imperial.d: Використовуйте його, якщо дюйми та дроби або дюйми більш звичні для вас, ніж сантиметри. Це часто кращий вибір для людей, які живуть у Великій Британії та США.
saNo.t: Не враховувати припуски на шви
saNo.d: При цьому створюється деталь без припусків на шви. Розмір припуску на шов не має значення, оскільки він не буде врахований.
saYes.t: Враховувати припуски на шви
saYes.d: При цьому генерується деталь з припусками на шви. Розмір припуску на шов задається індивідуально.
clearSettingsNotMeasurements: Clear settings, but keep measurements
clearSettingsAndMeasurements: Clear settings & Clear measurements

View file

@ -0,0 +1,27 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { ListValue, MmValue, PlainValue } from '../shared/values'
const ScaleSettingValue = ({ current, config, changed }) => (
<PlainValue current={current} dflt={config.dflt} changed={changed} />
)
const OnlySettingValue = ({ current, config }) => (
<PlainValue
current={current?.length}
dflt={config.parts.length}
changed={current !== undefined}
/>
)
export const values = {
complete: ListValue,
expand: ListValue,
locale: ListValue,
margin: MmValue,
only: OnlySettingValue,
paperless: ListValue,
sabool: ListValue,
samm: MmValue,
scale: ScaleSettingValue,
units: ListValue,
}

View file

@ -0,0 +1,5 @@
designOptions.t: Gestaltungsmöglichkeiten
designOptions.d: Diese Optionen sind spezifisch für dieses Muster. Du kannst sie verwenden, um dein Muster auf verschiedene Weise anzupassen.
fit.t: Passform
style.t: Stil
advanced.t: Fortgeschritten

View file

@ -0,0 +1,5 @@
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

View file

@ -0,0 +1,5 @@
designOptions.t: Opciones de diseño
designOptions.d: Estas opciones son específicas de este diseño. Puedes utilizarlas para personalizar tu patrón de diversas formas.
fit.t: Ajuste
style.t: Estilo
advanced.t: Avanzado

View file

@ -0,0 +1,5 @@
designOptions.t: Options de conception
designOptions.d: Ces options sont spécifiques à ce modèle. Tu peux les utiliser pour personnaliser ton motif de différentes manières.
fit.t: Ajustement
style.t: Style
advanced.t: Avancé

View file

@ -0,0 +1,110 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { useCallback, useMemo } from 'react'
// Components
import { OptionsIcon } from 'shared/components/icons.mjs'
import { optionsMenuStructure, optionType } from 'shared/utils.mjs'
import { values } from './values.mjs'
import { inputs } from './inputs.mjs'
import { WorkbenchMenu } from '../shared/index.mjs'
import { MenuItem } from '../shared/menu-item.mjs'
import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs'
export const ns = ['design-options']
// Emojis for option groups :)
export const emojis = {
advanced: '🤓',
fit: '👕',
style: '💃🏽',
dflt: '🕹️',
groupDflt: '📁',
}
/**
* A wrapper for {@see MenuItem} to handle design option-specific business
* @param {Object} options.config the config for the item
* @param {Object} options.settings core settings
* @param {Object} options.rest the rest of the props
*/
const DesignOption = ({ config, settings, control, ...rest }) => {
const type = optionType(config)
const Input = inputs[type]
const Value = values[type]
const allowOverride = ['pct', 'count', 'deg'].includes(type)
const allowToggle =
(control > 3 && type === 'bool') || (type == 'list' && config.list.length === 2)
// Hide option?
if (config?.hide || (typeof config?.hide === 'function' && config.hide(settings))) return null
return (
<MenuItem
{...{
config,
control,
...rest,
Input,
Value,
allowOverride,
allowToggle,
}}
docs={
<DynamicMdx
language={rest.language}
slug={`docs/designs/${rest.design}/options/${rest.name.toLowerCase()}`}
/>
}
/>
)
}
/**
* The design options menu
* @param {String} options.design the name of the design
* @param {Object} options.patternConfig the configuration from the pattern
* @param {Object} options.settings core settings
* @param {Object} options.update settings and ui update functions
* @param {String} options.language the menu language
* @param {Object} options.account the user account data
*/
export const DesignOptions = ({
design,
patternConfig,
settings,
update,
language,
account,
isFirst = true,
}) => {
const menuNs = [design, ...ns]
const optionsMenu = useMemo(
() => optionsMenuStructure(patternConfig.options, settings),
[patternConfig, settings]
)
const updateFunc = useCallback(
(name, value) => update.settings(['options', ...name], value),
[update]
)
return (
<WorkbenchMenu
{...{
config: optionsMenu,
control: account.control,
currentValues: settings.options,
emojis,
Icon: OptionsIcon,
Item: DesignOption,
isFirst,
name: 'design-options:designOptions',
language,
ns: menuNs,
passProps: { settings, patternConfig },
updateFunc,
values,
isDesignOptionsGroup: true,
design,
}}
/>
)
}

View file

@ -0,0 +1,45 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { formatMm } from 'shared/utils.mjs'
import { mergeOptions } from '@freesewing/core'
import {
BoolInput,
ConstantInput,
SliderInput,
DegInput,
ListInput,
PctInput,
} from '../shared/inputs.mjs'
const PctOptionInput = (props) => {
const { config, settings, changed } = props
const currentOrDefault = changed ? props.current : config.dflt / 100
return (
<PctInput {...props}>
<div className="flex flex-row justify-around">
<span className={changed ? 'text-accent' : 'text-secondary'}>
{config.toAbs
? formatMm(
config.toAbs(
currentOrDefault,
settings,
mergeOptions(settings, props.patternConfig.options)
)
)
: ' '}
</span>
</div>
</PctInput>
)
}
// Facilitate lookup of the input component
export const inputs = {
bool: BoolInput,
constant: ConstantInput,
count: (props) => <SliderInput {...props} config={{ ...props.config, step: 1 }} />,
deg: DegInput,
list: (props) => <ListInput {...props} isDesignOption />,
mm: () => <span>FIXME: Mm options are deprecated. Please report this </span>,
pct: PctOptionInput,
}

View file

@ -0,0 +1,5 @@
designOptions.t: Ontwerpopties
designOptions.d: Deze opties zijn specifiek voor dit ontwerp. Je kunt ze gebruiken om je patroon op verschillende manieren aan te passen.
fit.t: Pasvorm
style.t: Stijl
advanced.t: Geavanceerd

View file

@ -0,0 +1,5 @@
designOptions.t: Варіанти дизайну
designOptions.d: Ці опції є специфічними для цього дизайну. Ви можете використовувати їх, щоб налаштувати свій шаблон у різний спосіб.
fit.t: За розміром
style.t: Стиль
advanced.t: Додатково

View file

@ -0,0 +1,56 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { formatMm, formatPercentage } from 'shared/utils.mjs'
import { ListValue, HighlightedValue, PlainValue, BoolValue } from '../shared/values'
import { mergeOptions } from '@freesewing/core'
/** Displays the current percentage value, and the absolute value if configured */
export const PctOptionValue = ({ config, current, settings, changed, patternConfig }) => {
const val = changed ? current : config.pct / 100
return (
<HighlightedValue changed={changed}>
{formatPercentage(val)}
{config.toAbs && settings?.measurements
? ` | ${formatMm(
config.toAbs(val, settings, mergeOptions(settings, patternConfig.options))
)}`
: null}
</HighlightedValue>
)
}
/** Displays a count value*/
export const CountOptionValue = ({ config, current, changed }) => (
<PlainValue {...{ current, changed, dflt: config.count }} />
)
/** Displays a list option value */
export const ListOptionValue = (props) => (
<ListValue {...props} t={(input) => props.t(`${props.design}:${props.config.name}.${input}.t`)} />
)
/** Displays a degree value */
export const DegOptionValue = ({ config, current, changed }) => (
<HighlightedValue changed={changed}> {changed ? current : config.deg}&deg;</HighlightedValue>
)
/** Displays the MmOptions are not supported */
export const MmOptionValue = () => (
<span className="text-error">FIXME: No Mm Options are not supported</span>
)
/** Displays that constant values are not implemented in the front end */
export const ConstantOptionValue = () => (
<span className="text-error">FIXME: No ConstantOptionvalue implemented</span>
)
// Facilitate lookup of the value component
export const values = {
bool: BoolValue,
constant: ConstantOptionValue,
count: CountOptionValue,
deg: DegOptionValue,
list: ListOptionValue,
mm: MmOptionValue,
pct: PctOptionValue,
}

View file

@ -0,0 +1,95 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { useContext, useState, useEffect } from 'react'
import { ModalContext } from 'shared/context/modal-context.mjs'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { CloseIcon } from 'shared/components/icons.mjs'
import { MobileMenubarContext } from 'shared/context/mobile-menubar-context.mjs'
import { MenuAltIcon } from 'shared/components/icons.mjs'
/**
* A component to display menu buttons and actions in mobile.
* Draws its contents from items added to the {@link MobileMenubarContext}
* @returns
*/
export const MobileMenubar = () => {
const { setModal, clearModal, modalContent } = useContext(ModalContext)
const { menus, actions } = useContext(MobileMenubarContext)
const [selectedModal, setSelectedModal] = useState(false)
// get the content of the selected modal because this is what will be changing if there are updates
const selectedMenu = menus[selectedModal]
// when the content changes, or the selection changes
useEffect(() => {
// there's no selected modal, we're in the clear
if (!selectedModal) return
// generate a new modal with the content
const Modal = () => {
const closeModal = () => {
setSelectedModal(false)
clearModal()
}
return (
<ModalWrapper
slideFrom="right"
keepOpenOnClick={selectedMenu.keepOpenOnClick}
keepOpenOnSwipe
fullWidth
>
<div className="mb-16">{selectedMenu.menuContent}</div>
<button
className="btn btn-accent btn-circle fixed bottom-4 right-4 z-20"
onClick={closeModal}
>
<CloseIcon />
</button>
</ModalWrapper>
)
}
// set it
setModal(Modal)
}, [selectedMenu, selectedModal, clearModal, setModal])
// clear the selection if the modal was cleared externally
useEffect(() => {
if (modalContent === null) {
setSelectedModal(false)
}
}, [modalContent, setSelectedModal])
return (
<div
className={`
lg:hidden
sticky bottom-0 w-20 -ml-20 self-end
duration-300 transition-all
flex flex-col-reverse gap-2 mb-2
z-20
mobile-menubar
`}
>
{Object.keys(menus)
.sort((a, b) => menus[a].order - menus[b].order)
.map((m) => {
const Icon = m === 'nav' ? MenuAltIcon : menus[m].Icon
return (
<button
key={m}
className={`btn ${m === 'nav' ? 'btn-neutral' : 'btn-primary'} btn-circle mx-4`}
onClick={() => setSelectedModal(m)}
>
<Icon />
</button>
)
})}
{Object.keys(actions)
.sort((a, b) => actions[a].order - actions[b].order)
.map((a) => (
<div key={a}>{actions[a].actionContent}</div>
))}
</div>
)
}

View file

@ -0,0 +1,66 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { MenuItemGroup } from './menu-item.mjs'
import { useTranslation } from 'next-i18next'
/**
* A component for a collapsible sidebar menu in workbench views
* @param {Function} options.updateFunc a function the menu's inputs will use to update their values
* @param {String[]} options.ns namespaces used by this menu
* @param {React.Component} options.Icon the menu's icon
* @param {String} options.name the translation key for the menu's title
* @param {Object} options.config the structure of the menu's options
* @param {Number} options.control the user's control level setting
* @param {Object} options.inputs a map of input components to use, keyed by option name
* @param {Object} options.values a map of value components to use, keyed by option name
* @param {Object} options.currentValues a map of the values of the menu's options
* @param {Object} options.passProps any additional properties to pass the the inputs
* @param {string} language the language to use for the menu
* @param {Object} emojis a map of the emojis to use, keyed by option name
* @param {React.component} Item the component to use for menu items
* @return {[type]} [description]
*/
export const WorkbenchMenu = ({
updateFunc,
ns,
Icon = () => null,
config,
control,
inputs,
values,
currentValues,
passProps = {},
language,
emojis,
Item,
children,
isDesignOptionsGroup,
design,
}) => {
// get translation for the menu
const { t } = useTranslation(ns)
return children ? (
children
) : (
<MenuItemGroup
{...{
collapsible: false,
topLevel: true,
control,
currentValues,
structure: config,
Item,
Icon,
values,
inputs,
passProps,
updateFunc,
emojis,
t,
language,
isDesignOptionsGroup,
design,
}}
/>
)
}

View file

@ -0,0 +1,519 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { useCallback, useMemo, useState, useEffect, useRef } from 'react'
import {
round,
measurementAsMm,
measurementAsUnits,
formatFraction128,
fractionToDecimal,
} from 'shared/utils.mjs'
import debounce from 'lodash.debounce'
import { ButtonFrame } from 'shared/components/inputs.mjs'
/*******************************************************************************************
* This file contains the base components to be used by inputs in menus in the workbench
* For the purposes of our menus, we have two main types:
* Sliders for changing numbers
* Lists for changing everything else
*
* Inputs that deal with more specific use cases should wrap one of the above base inputs
*******************************************************************************************/
/** Regex to validate that an input is a number */
const numberInputMatchers = {
0: /^-?[0-9]*[.,eE]?[0-9]+$/, // match a single decimal separator
1: /^-?[0-9]*(\s?[0-9]+\/|[.,eE])?[0-9]+$/, // match a single decimal separator or fraction
}
/**
* Validate and parse a value that should be a number
* @param {any} val the value to validate
* @param {Boolean} allowFractions should fractions be considered valid input?
* @param {Number} min the minimum allowable value
* @param {Number} max the maximum allowable value
* @return {null|false|Number} null if the value is empty,
* false if the value is invalid,
* or the value parsed to a number if it is valid
*/
const validateVal = (val, allowFractions = true, min = -Infinity, max = Infinity) => {
// if it's empty, we're neutral
if (typeof val === 'undefined' || val === '') return null
// make sure it's a string
val = ('' + val).trim()
// get the appropriate match pattern and check for a match
const matchPattern = numberInputMatchers[Number(allowFractions)]
if (!val.match(matchPattern)) return false
// replace comma with period
const parsedVal = val.replace(',', '.')
// if fractions are allowed, parse for fractions, otherwise use the number as a value
const useVal = allowFractions ? fractionToDecimal(parsedVal) : parsedVal
// check that it's a number and it's in the range
if (isNaN(useVal) || useVal > max || useVal < min) return false
// all checks passed. return the parsed value
return useVal
}
/**
* A number input that accepts comma or period decimal separators.
* Because our use case is almost never going to include thousands, we're using a very simple way of accepting commas:
* The validator checks for the presence of a single comma or period followed by numbers
* The parser replaces a single comma with a period
*
* optionally accepts fractions
* @param {Number} options.val the value of the input
* @param {Function} options.onUpdate a function to handle when the value is updated to a valid value
* @param {Boolean} options.fractions should the input allow fractional input
*/
export const NumberInput = ({
value,
onUpdate,
onMount,
className,
fractions = true,
min = -Infinity,
max = Infinity,
}) => {
const valid = useRef(validateVal(value, fractions, min, max))
// this is the change handler that will be debounced by the debounce handler
// we check validity inside this debounced function because
// we need to call the debounce handler on change regardless of validity
// if we don't, the displayed value won't update
const handleChange = useCallback(
(newVal) => {
// only actually update if the value is valid
if (typeof onUpdate === 'function') {
onUpdate(valid.current, newVal)
}
},
[onUpdate, valid]
)
// get a debounce handler
const { debouncedHandleChange, displayVal } = useDebouncedHandlers({ handleChange, val: value })
// onChange
const onChange = useCallback(
(evt) => {
const newVal = evt.target.value
// set validity so it will display
valid.current = validateVal(newVal, fractions, min, max)
// handle the change
debouncedHandleChange(newVal)
},
[debouncedHandleChange, fractions, min, max, valid]
)
useEffect(() => {
if (typeof onMount === 'function') {
onMount(valid.current)
}
}, [onMount, valid])
return (
<input
type="text"
inputMode="number"
className={`input input-secondary ${className || 'input-sm grow text-base-content'}
${valid.current === false && 'input-error'}
${valid.current && 'input-success'}
`}
value={displayVal}
onChange={onChange}
/>
)
}
/** A component that shows a number input to edit a value */
const EditCount = (props) => {
const { handleChange } = props
const onUpdate = useCallback(
(validVal) => {
if (validVal !== null && validVal !== false) handleChange(validVal)
},
[handleChange]
)
return (
<div className="form-control mb-2 w-full">
<label className="label">
<span className="label-text text-base-content">{props.min}</span>
<span className="label-text font-bold text-base-content">{props.current}</span>
<span className="label-text text-base-content">{props.max}</span>
</label>
<label className="input-group input-group-sm">
<NumberInput value={props.current} onUpdate={onUpdate} min={props.min} max={props.max} />
<span className="text-base-content font-bold">#</span>
</label>
</div>
)
}
/**
* A hook to get the change handler for an input.
* @param {Number|String|Boolean} options.dflt the default value for the input
* @param {Function} options.updateFunc the onChange
* @param {string} options.name the name of the property being changed
* @return the change handler for the input
*/
const useSharedHandlers = ({ dflt, updateFunc, name }) => {
return useCallback(
(newCurrent) => {
if (newCurrent === dflt) newCurrent = undefined
updateFunc([name], newCurrent)
},
[dflt, updateFunc, name]
)
}
/** get the configuration that allows a boolean value to use the list input */
const useBoolConfig = (name, config) => {
return useMemo(
() => ({
list: [false, true],
choiceTitles: {
false: `${name}No`,
true: `${name}Yes`,
},
valueTitles: {
false: 'no',
true: 'yes',
},
...config,
}),
[name, config]
)
}
/** a toggle input for list/boolean values */
export const ListToggle = ({ config, changed, updateFunc, name }) => {
const boolConfig = useBoolConfig(name, config)
const handleChange = useSharedHandlers({ dflt: boolConfig.dflt, updateFunc, name })
const dfltIndex = boolConfig.list.indexOf(boolConfig.dflt)
const doToggle = () =>
handleChange(boolConfig.list[changed ? dfltIndex : Math.abs(dfltIndex - 1)])
const checked = boolConfig.dflt == false ? changed : !changed
return (
<input
type="checkbox"
className={`toggle ${changed ? 'toggle-accent' : 'toggle-secondary'}`}
checked={checked}
onChange={doToggle}
onClick={(evt) => evt.stopPropagation()}
/>
)
}
/**
* An input for selecting and item from a list
* @param {String} options.name the name of the property this input changes
* @param {Object} options.config configuration for the input
* @param {String|Number} options.current the current value of the input
* @param {Function} options.updateFunc the function called by the event handler to update the value
* @param {Boolean} options.compact include descriptions with the list items?
* @param {Function} options.t translation function
* @param {String} design name of the design
* @param {Boolean} isDesignOption Whether or not it's a design option
*/
export const ListInput = ({
name,
config,
current,
updateFunc,
compact = false,
t,
changed,
design,
isDesignOption = false,
}) => {
const handleChange = useSharedHandlers({
dflt: config.dflt,
updateFunc,
name,
})
return config.list.map((entry) => {
const titleKey = config.choiceTitles
? config.choiceTitles[entry]
: isDesignOption
? `${design}:${name}.${entry}`
: `${name}.o.${entry}`
const title = config.titleMethod ? config.titleMethod(entry, t) : t(`${titleKey}.t`)
const desc = config.valueMethod ? config.valueMethod(entry, t) : t(`${titleKey}.d`)
const sideBySide = config.sideBySide || desc.length + title.length < 42
return (
<ButtonFrame
dense={config.dense || false}
key={entry}
active={
changed
? Array.isArray(current)
? current.includes(entry)
: current === entry
: entry === config.dflt
}
onClick={() => handleChange(entry)}
>
<div
className={`w-full flex items-start ${
sideBySide ? 'flex-row justify-between gap-2' : 'flex-col'
}`}
>
<div className="font-bold text-lg shrink-0">{title}</div>
{compact ? null : <div className="text-base font-normal">{desc}</div>}
</div>
</ButtonFrame>
)
})
}
/** A boolean version of {@see ListInput} that sets up the necessary configuration */
export const BoolInput = (props) => {
const { name, config } = props
const boolConfig = useBoolConfig(name, config)
return <ListInput {...props} config={boolConfig} />
}
export const useDebouncedHandlers = ({ handleChange = () => {}, val }) => {
// hold onto what we're showing as the value so that the input doesn't look unresponsive
const [displayVal, setDisplayVal] = useState(val)
// the debounce function needs to be it's own memoized value so we can flush it on unmount
const debouncer = useMemo(
() => debounce(handleChange, 300, { leading: true, trailing: true }),
[handleChange]
)
// this is the change handler
const debouncedHandleChange = useCallback(
(newVal) => {
// always set the display
setDisplayVal(newVal)
// debounce the actual update
debouncer(newVal)
},
[setDisplayVal, debouncer]
)
// immediately call the debounced function on unmount so we don't miss an update
useEffect(() => debouncer.flush, [debouncer])
// set the display val to the current value when it gets changed
useEffect(() => {
setDisplayVal(val)
}, [val])
return { debouncedHandleChange, displayVal }
}
/**
* An input component that uses a slider to change a number value
* @param {String} options.name the name of the property being changed by the input
* @param {Object} options.config configuration for the input
* @param {Number} options.current the current value of the input
* @param {Function} options.updateFunc the function called by the event handler to update the value
* @param {Function} options.t translation function
* @param {Boolean} options.override open the text input to allow override of the slider?
* @param {String} options.suffix a suffix to append to value labels
* @param {Function} options.valFormatter a function that accepts a value and formats it for display as a label
* @param {Function} options.setReset a setter for the reset function on the parent component
*/
export const SliderInput = ({
name,
config,
current,
updateFunc,
t,
override,
suffix = '',
valFormatter = (val) => val,
setReset,
children,
changed,
}) => {
const { max, min } = config
const handleChange = useSharedHandlers({
current,
dflt: config.dflt,
updateFunc,
name,
setReset,
})
const { debouncedHandleChange, displayVal } = useDebouncedHandlers({
handleChange,
val: changed ? current : config.dflt,
})
return (
<>
<div className="flex flex-row justify-between">
{override ? (
<EditCount
{...{
current: displayVal,
handleChange,
min,
max,
t,
}}
/>
) : (
<>
<span className="opacity-50">
<span dangerouslySetInnerHTML={{ __html: valFormatter(min) + suffix }} />
</span>
<span
className={`font-bold ${
displayVal === config.dflt ? 'text-secondary' : 'text-accent'
}`}
>
<span dangerouslySetInnerHTML={{ __html: valFormatter(displayVal) + suffix }} />
</span>
<span className="opacity-50">
<span dangerouslySetInnerHTML={{ __html: valFormatter(max) + suffix }} />
</span>
</>
)}
</div>
<input
type="range"
{...{ min, max, value: displayVal, step: config.step || 0.1 }}
onChange={(evt) => debouncedHandleChange(evt.target.value)}
className={`
range range-sm mt-1
${changed ? 'range-accent' : 'range-secondary'}
`}
/>
{children}
</>
)
}
/**
* round a value to the correct number of decimal places to display all supplied digits after multiplication
* this is a workaround for floating point errors
* examples:
* roundPct(0.72, 100) === 72
* roundPct(7.5, 0.01) === 0.075
* roundPct(7.50, 0.01) === 0.0750
* @param {Number} num the number to be operated on
* @param {Number} factor the number to multiply by
* @return {Number} the given num multiplied by the factor, rounded appropriately
*/
const roundPct = (num, factor) => {
// stringify
const str = '' + num
// get the index of the decimal point in the number
const decimalIndex = str.indexOf('.')
// get the number of places the factor moves the decimal point
const factorPlaces = factor > 0 ? Math.ceil(Math.log10(factor)) : Math.floor(Math.log10(factor))
// the number of places needed is the number of digits that exist after the decimal minus the number of places the decimal point is being moved
const numPlaces = Math.max(0, str.length - (decimalIndex + factorPlaces))
return round(num * factor, numPlaces)
}
/** A {@see SliderInput} to handle percentage values */
export const PctInput = ({ current, changed, updateFunc, config, ...rest }) => {
const factor = 100
let pctCurrent = changed ? roundPct(current, factor) : current
const pctUpdateFunc = useCallback(
(path, newVal) =>
updateFunc(path, newVal === undefined ? undefined : roundPct(newVal, 1 / factor)),
[updateFunc]
)
return (
<SliderInput
{...{
...rest,
config: { ...config, dflt: roundPct(config.dflt, factor) },
current: pctCurrent,
updateFunc: pctUpdateFunc,
suffix: '%',
valFormatter: round,
changed,
}}
/>
)
}
/** A {@see SliderInput} to handle degree values */
export const DegInput = (props) => {
const { updateFunc } = props
const degUpdateFunc = useCallback(
(path, newVal) => {
updateFunc(path, newVal === undefined ? undefined : Number(newVal))
},
[updateFunc]
)
return <SliderInput {...props} suffix="°" valFormatter={round} updateFunc={degUpdateFunc} />
}
export const MmInput = (props) => {
const { units, updateFunc, current, config } = props
const mmUpdateFunc = useCallback(
(path, newCurrent) => {
const calcCurrent =
typeof newCurrent === 'undefined' ? undefined : measurementAsMm(newCurrent, units)
updateFunc(path, calcCurrent)
},
[updateFunc, units]
)
// add a default step that's appropriate to the unit. can be overwritten by config
const defaultStep = units === 'imperial' ? 0.125 : 0.1
return (
<SliderInput
{...{
...props,
config: {
step: defaultStep,
...config,
dflt: measurementAsUnits(config.dflt, units),
},
current: current === undefined ? undefined : measurementAsUnits(current, units),
updateFunc: mmUpdateFunc,
valFormatter: (val) => (units === 'imperial' ? formatFraction128(val, null) : val),
suffix: units === 'imperial' ? '"' : 'cm',
}}
/>
)
}
/** A placeholder for an input to handle constant values */
export const ConstantInput = ({
type = 'number',
name,
current,
updateFunc,
//t,
changed,
config,
}) => (
<>
<input
type={type}
className={`
input input-bordered w-full text-base-content
input-${changed ? 'secondary' : 'accent'}
`}
value={changed ? current : config.dflt}
onChange={(evt) => updateFunc([name], evt.target.value)}
/>
</>
)

View file

@ -0,0 +1,278 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { ResetIcon, EditIcon } from 'shared/components/icons.mjs'
import { useState, useMemo } from 'react'
import { SubAccordion } from 'shared/components/accordion.mjs'
import { FormControl } from 'shared/components/inputs.mjs'
import { BoxIcon as GroupIcon, OptionsIcon } from 'shared/components/icons.mjs'
import { optionType } from 'shared/utils.mjs'
/**
* Check to see if a value is different from its default
* @param {Number|String|Boolean} current the current value
* @param {Object} config configuration containing a dflt key
* @return {Boolean} was the value changed?
*/
export const wasChanged = (current, config) => {
if (typeof current === 'undefined') return false
if (current == config.dflt) return false
return true
}
/**
* A generic component to present the title of a menu item
* @param {String} options.name the name of the item, to act as its translation key
* @param {Function} options.t the translation function
* @param {String|React.Component} options.current a the current value, or a Value component to display it
* @param {Boolean} options.open is the menu item open?
* @param {String} options.emoji the emoji icon of the menu item
*/
export const ItemTitle = ({ name, t, current = null, open = false, emoji = '', Icon = false }) => (
<div className={`flex flex-row gap-1 items-center w-full ${open ? '' : 'justify-between'}`}>
<span className="font-medium capitalize flex flex-row gap-2">
{Icon ? <Icon /> : <span role="img">{emoji}</span>}
{t([`${name}.t`, name])}
</span>
<span className="font-bold">{current}</span>
</div>
)
/** @type {String} class to apply to buttons on open menu items */
const iconButtonClass = 'btn btn-xs btn-ghost px-0 text-accent'
/**
* A generic component for handling a menu item.
* Wraps the given input in a {@see Collapse} with the appropriate buttons
* @param {String} options.name the name of the item, for using as a key
* @param {Object} options.config the configuration for the input
* @param {Sting|Boolean|Number} options.current the current value of the item
* @param {Function} options.updateFunc the function that will be called by event handlers to update the value
* @param {Function} options.t the translation function
* @param {Object} options.passProps props to pass to the Input component
* @param {Boolean} changed has the value changed from default?
* @param {React.Component} Input the input component this menu item will use
* @param {React.Component} Value a value display component this menu item will use
* @param {Boolean} allowOverride all a text input to be used to override the given input component
* @param {Number} control the user-defined control level
*/
export const MenuItem = ({
name,
config,
current,
updateFunc,
t,
passProps = {},
changed,
Input = () => {},
allowOverride = false,
control = Infinity,
docs,
design,
}) => {
// state for knowing whether the override input should be shown
const [override, setOverride] = useState(false)
// generate properties to pass to the Input
const drillProps = useMemo(
() => ({
name,
config,
control,
current,
updateFunc,
t,
changed,
override,
design,
...passProps,
}),
[name, config, current, updateFunc, t, changed, override, passProps, control]
)
// don't render if this item is more advanced than the user has chosen to see
if (config.control && config.control > control) return null
// get buttons for open and closed states
const buttons = []
if (allowOverride)
buttons.push(
<button
key="edit"
className={iconButtonClass}
onClick={(evt) => {
evt.stopPropagation()
setOverride(!override)
}}
>
<EditIcon
className={`w-6 h-6 ${
override ? 'bg-secondary text-secondary-content rounded' : 'text-secondary'
}`}
/>
</button>
)
const ResetButton = ({ disabled = false }) => (
<button
className={`${iconButtonClass} disabled:bg-opacity-0`}
disabled={disabled}
onClick={(evt) => {
evt.stopPropagation()
updateFunc([name])
}}
>
<ResetIcon />
</button>
)
buttons.push(<ResetButton open disabled={!changed} key="clear" />)
return (
<FormControl
label={<span className="text-base font-normal">{t([`${name}.d`, name])}</span>}
id={config.name}
labelBR={<div className="flex flex-row items-center gap-2">{buttons}</div>}
labelBL={
<span
className={`text-base font-medium -mt-2 block ${changed ? 'text-accent' : 'opacity-50'}`}
>
{t(`workbench:youUse${changed ? 'Default' : 'Custom'}Value`)}
</span>
}
docs={docs}
>
<Input {...drillProps} />
</FormControl>
)
}
/**
* A component for recursively displaying groups of menu items.
* Accepts any object where menu item configurations are keyed by name
* Items that are group headings are expected to have an isGroup: true property
* @param {Boolean} options.collapsible Should this group be collapsible (use false for the top level of a menu)
* @param {Number} options.control the user-defined control level
* @param {String} options.name the name of the group or item
* @param {Object} options.currentValues a map of current values for items in the group, keyed by name
* @param {Object} structure the configuration for the group.
* @param {React.Component} Icon the icon to display next to closed groups
* @param {React.Component} Item the component to use for menu items
* @param {Object} values a map of Value display components to be used by menu items in the group
* @param {Object} inputs a map of Input components to be used by menu items in the group
* @param {Object} passProps properties to pass to Inputs within menu items
* @param {Object} emojis a map of emojis to use as icons for groups or items
* @param {Function} updateFunc the function called by change handlers on inputs within menu items
* @param {Boolean} topLevel is this group the top level group? false for nested
* @param {Function} t translation function
*/
export const MenuItemGroup = ({
collapsible = true,
control,
//name,
currentValues = {},
structure,
Icon,
Item = MenuItem,
values = {},
inputs = {},
passProps = {},
emojis = {},
updateFunc,
topLevel = false,
t,
language,
isDesignOptionsGroup = false,
docs = false,
design,
}) => {
// map the entries in the structure
const content = Object.entries(structure).map(([itemName, item]) => {
// if it's the isGroup property, or it is false, it shouldn't be shown
if (itemName === 'isGroup' || item === false) return null
if (!item) return null
if (item.control && control && item.control > control) return null
const ItemIcon = item.icon
? item.icon
: item.isGroup
? GroupIcon
: Icon
? Icon
: () => <span role="img">fixme-icon</span>
const Value = item.isGroup
? () => (
<div className="flex flex-row gap-2 items-center font-medium">
{Object.keys(item).filter((i) => i !== 'isGroup').length}
<OptionsIcon className="w-5 h-5" />
</div>
)
: isDesignOptionsGroup
? values[optionType(item)]
: values[itemName]
? values[itemName]
: () => <span>¯\_()_/¯</span>
return [
<div className="flex flex-row items-center justify-between w-full" key="a">
<div className="flex flex-row items-center gap-4 w-full">
<ItemIcon />
<span className="font-medium">{t([`${itemName}.t`, `workbench:${itemName}`])}</span>
</div>
<div className="font-bold">
<Value
current={currentValues[itemName]}
config={item}
t={t}
changed={wasChanged(currentValues[itemName], item)}
design={design}
/>
</div>
</div>,
item.isGroup ? (
<MenuItemGroup
key={itemName}
{...{
collapsible: true,
// it's the top level if the previous level was top but not wrapped
topLevel: topLevel && !collapsible,
control,
name: itemName,
currentValues,
structure: item,
Icon,
Item,
values,
inputs,
passProps,
emojis,
updateFunc,
t,
language,
isDesignOptionsGroup,
design,
}}
/>
) : (
<Item
key={itemName}
{...{
name: itemName,
current: currentValues[itemName],
config: item,
control,
changed: wasChanged(currentValues[itemName], item),
Value: values[itemName],
Input: inputs[itemName],
t,
updateFunc,
passProps,
language,
docs,
design,
}}
/>
),
itemName,
]
})
return <SubAccordion items={content.filter((item) => item !== null)} />
}

View file

@ -0,0 +1,43 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { useMemo } from 'react'
import { WrenchIcon } from 'shared/components/icons.mjs'
import { useMobileMenu } from 'shared/context/mobile-menubar-context.mjs'
const defaultClasses = `w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-full overflow-scroll`
/**
* a wrapper that displays its contents normally on larger screens
* and adds its contents as a menu on the {@link MobileMenubar} for smaller screens
* @param {ReactChildren} children
* @param {String} wrapperClass - classes to add to the wrapper for larger screens
* @param {ReactComponent} Icon - Icon for the menu button on smaller screens
* @param {Boolean} [keepOpenOnClick=true] - should the modal for this menu stay open when clicked?
* @param {String} [type='settings'] - the type of menu this is. will be used to key the menu in the MobileMenubar
* @param {Number} [order=-1] - the order of this menu in the MobileMenubar
*/
export const MenuWrapper = ({
children,
wrapperClass = defaultClasses,
Icon = WrenchIcon,
keepOpenOnClick = true,
type = 'settings',
order = -1,
}) => {
const menuProps = useMemo(
() => ({
Icon,
menuContent: children,
keepOpenOnClick,
order,
}),
[Icon, children, keepOpenOnClick, order]
)
useMobileMenu(type, menuProps)
return (
<>
<div className={`hidden lg:block ${wrapperClass}`}>{children}</div>
</>
)
}

View file

@ -0,0 +1,63 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { formatMm } from 'shared/utils.mjs'
import { BoolYesIcon, BoolNoIcon } from 'shared/components/icons.mjs'
/*********************************************************************************************************
* This file contains the base components to be used for displaying values in menu titles in the workbench
* Values that deal with more specific use cases should wrap one of the below components
*********************************************************************************************************/
/** The basis of it all. Handles the changed/unchanged styling for the wrapped value */
export const HighlightedValue = ({ changed, children }) => (
<span className={changed ? 'text-accent' : ''}> {children} </span>
)
/**
* A wrapper for displaying the correct value based on whether or not the value has changed
* @param {Number|String|Boolean} options.current the current value, if it has been changed
* @param {Number|String|Boolean} options.dflt the default value
* @param {Boolean} options.changed has the value been changed?
*/
export const PlainValue = ({ current, dflt, changed }) => (
<HighlightedValue changed={changed}> {changed ? current : dflt} </HighlightedValue>
)
/**
* Displays the correct, translated value for a list
* @param {String|Boolean} options.current the current value, if it has been changed
* @param {Function} options.t a translation function
* @param {Object} options.config the item config
* @param {Boolean} options.changed has the value been changed?
*/
export const ListValue = ({ current, t, config, changed }) => {
// get the values
const val = changed ? current : config.dflt
// key will be based on a few factors
let key
// are valueTitles configured?
if (config.valueTitles) key = config.valueTitles[val]
// if not, is the value a string
else if (typeof val === 'string') key = val
// otherwise stringify booleans
else if (val) key = <BoolYesIcon />
else key = <BoolNoIcon />
const translated = config.doNotTranslate || typeof key !== 'string' ? key : t(key)
return <HighlightedValue changed={changed}>{translated}</HighlightedValue>
}
/** Displays the corrent, translated value for a boolean */
export const BoolValue = ListValue
/** Displays a formated mm value based on the current units */
export const MmValue = ({ current, config, units, changed }) => (
<HighlightedValue changed={changed}>
<span
dangerouslySetInnerHTML={{
__html: formatMm(changed ? current : config.dflt, units),
}}
/>
</HighlightedValue>
)

View file

@ -0,0 +1,47 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { RocketIcon, ControlIcon, KioskIcon } from 'shared/components/icons.mjs'
export const loadSettingsConfig = () => {
const uiSettings = {
control: {
control: 1, // Show when control > 0
emoji: '🖥️',
list: [1, 2, 3, 4, 5],
choiceTitles: {},
icon: ControlIcon,
},
kiosk: {
control: 4, // Show when control > 3
list: [0, 1],
choiceTitles: {
0: 'ui-settings:websiteMode',
1: 'ui-settings:kioskMode',
},
//valueTitles: {
// react: 'ui-settings:regular',
// svg: 'ui-settings:kiosk',
//},
dflt: 0,
icon: KioskIcon,
},
renderer: {
control: 4, // Show when control > 3
list: ['react', 'svg'],
choiceTitles: {
react: 'ui-settings:renderWithReact',
svg: 'ui-settings:renderWithCore',
},
valueTitles: {
react: 'React',
svg: 'SVG',
},
dflt: 'react',
icon: RocketIcon,
},
}
uiSettings.control.list.forEach(
(i) => (uiSettings.control.choiceTitles[i] = 'account:control' + i)
)
return uiSettings
}

View file

@ -0,0 +1,43 @@
uiSettings.t: UI Präferenzen
uiSettings.d: Diese Einstellungen steuern die UI-Aspekte (Benutzeroberfläche) unserer Online-Umgebung für den Entwurf von Mustern.
renderer.t: Rendering Engine
renderer.d: Legt fest, wie das Muster auf dem Bildschirm gerendert (gezeichnet) wird
renderWithReact.t: Rendern mit den React-Komponenten von FreeSewing
renderWithReact.d: Rendere als SVG durch unsere React-Komponenten. Ermöglicht Interaktivität und ist für den Bildschirm optimiert. Verwende sie, wenn du dir nicht sicher bist, was du wählen sollst.
renderWithCore.t: Rendering mit der Core-Bibliothek von Freesewing
renderWithCore.d: Rendere direkt aus Core in SVG. Erlaubt keine Interaktivität und ist für den Druck optimiert. Verwende dies, wenn du wissen willst, wie es nach dem Export aussehen wird.
control.t: Benutzererfahrung
control.d: Welches Benutzererlebnis bevorzugst du? Bitte beachte, dass dies eine Kontoeinstellung ist, die sich auf die gesamte Website auswirkt.
inspect.t: Überprüfe
inspect.d: Wenn du diese Funktion aktivierst, kannst du das Muster aufschlüsseln und Informationen über seine verschiedenen Teile, Pfade und Punkte abrufen.
inspectNo.t: Deaktiviere den Inspektor
inspectNo.d: Das ist die Standardeinstellung, der Musterinspektor ist deaktiviert und das Muster wird wie gewohnt angezeigt.
inspectYes.t: Aktiviere den Inspektor
inspectYes.d: Wenn der Musterinspektor aktiviert und die React-Rendering-Engine ausgewählt ist, fügen wir dem Muster Interaktivität hinzu, damit du die verschiedenen Elemente, aus denen das Muster besteht, untersuchen kannst.
no: No
yes: Yes
draft: Entwurf
test: Test
print: Layout drucken
cut: Layout schneiden
save: Speichern
export: Exportieren
edit: bearbeiten
draft.t: Zeichne dein Schnittmuster
draft.d: Einführung des FreeSewing Muster-Editors, mit dem du dein Muster nach Herzenslust verändern kannst
test.t: Teste dein Schnittmuster
test.d: Sieh dir an, wie sich dein Muster an Änderungen der Optionen oder Maße anpasst
print.t: Layout drucken
print.d: Ermöglicht es dir, deine Musterteile so anzuordnen, dass du dein Muster auf so wenig Seiten wie möglich drucken kannst
cut.t: Layout schneiden
cut.d: Damit kannst du deine Schnittmusterteile so anordnen, dass du genau bestimmen kannst, wie viel Stoff du für die Herstellung brauchst.
save.t: Speichere dein Muster
save.d: Speichere das aktuelle Muster in deinem FreeSewing-Konto
export.t: Exportiere dein Muster
export.d: Ermöglicht es dir, dieses Muster in eine Vielzahl von Formaten zu exportieren
logs.t: Musterprotokolle
logs.d: Ermöglicht es dir, die Musterprotokolle zu durchsuchen, um zu sehen, was genau beim Entwerfen dieses Musters passiert ist
edit.t: Bearbeiten Sie die Mustereinstellungen von Hand
edit.d: So kannst du die Mustereinstellungen von Hand bearbeiten und hast die volle Kontrolle darüber, wie dein Muster gezeichnet wird.
view: Siehe
view.d: Dies sind die verschiedenen Ansichten, aus denen du wählen kannst. Dazu gehören die Ansichten, die über die Navigationsleiste oder das Menü verfügbar sind, sowie einige zusätzliche Ansichten

View file

@ -0,0 +1,43 @@
uiSettings.t: UI Preferences
uiSettings.d: These preferences control the UI (User Interface) aspects of our online pattern drafting environment.
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.
control.t: User Experience
control.d: Which user experience do you prefer? Please note that this is an account setting, so it will impact the entire website.
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.
no: No
yes: Yes
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
logs.d: Allows you to browse the pattern logs to see what exactly happened while drafting this pattern
edit.t: Hand-edit the pattern settings
edit.d: This allows you to hand-edit the pattern settings, giving you full control over how your pattern will be drafted
view: View
view.d: These are the various views you can pick from. Includes those views available via the navigation bar or menu, and some additional ones

View file

@ -0,0 +1,43 @@
uiSettings.t: Preferencias de IU
uiSettings.d: Estas preferencias controlan los aspectos de la IU (Interfaz de Usuario) de nuestro entorno de elaboración de patrones en línea.
renderer.t: Motor de renderizado
renderer.d: Controla cómo se representa (dibuja) el patrón en la pantalla
renderWithReact.t: Renderiza con los componentes React de FreeSewing
renderWithReact.d: Renderiza como SVG a través de nuestros componentes React. Permite la interactividad y está optimizado para la pantalla. Utilízalo si no estás seguro de qué elegir.
renderWithCore.t: Renderiza con la biblioteca Core de Freesewing
renderWithCore.d: Renderiza directamente a SVG desde Core. No permite interactividad y está optimizado para impresión. Utilízalo si quieres saber qué aspecto tendrá cuando se exporte.
control.t: Experiencia del usuario
control.d: '¿Qué experiencia de usuario prefieres? Ten en cuenta que se trata de una configuración de cuenta, por lo que afectará a todo el sitio web.'
inspect.t: Inspecciona
inspect.d: Si activas esta opción, podrás profundizar en el patrón y obtener información sobre sus distintas partes, rutas y puntos.
inspectNo.t: Desactivar el inspector
inspectNo.d: Este es el valor por defecto, el inspector de patrones está desactivado y el patrón se muestra como de costumbre.
inspectYes.t: Activar el inspector
inspectYes.d: Con el inspector de patrones activado y el motor de renderizado React seleccionado, añadiremos interactividad al patrón para que puedas inspeccionar los distintos elementos que lo componen.
no: No
yes: Yes
draft: Boceto
test: Prueba
print: Diseño de impresión
cut: Disposición del corte
save: Guardar
export: Exportar
edit: Edita
draft.t: Traza tu patrón
draft.d: Lanza el editor de patrones insignia de FreeSewing, donde puedes retocar tu patrón a tu antojo
test.t: Prueba tu patrón
test.d: Comprueba cómo se adapta tu patrón a los cambios de opciones o medidas
print.t: Diseño de impresión
print.d: Te permite organizar las piezas de tu patrón para que puedas imprimirlo en el menor número de páginas posible
cut.t: Disposición de corte
cut.d: Te permite organizar las piezas de tu patrón para que puedas determinar exactamente cuánta tela necesitas para confeccionarlo.
save.t: Guarda tu patrón
save.d: Guarda el patrón actual en tu cuenta de FreeSewing
export.t: Exporta tu patrón
export.d: Te permite exportar este patrón a varios formatos
logs.t: Registros de patrones
logs.d: Te permite examinar los registros del patrón para ver qué ocurrió exactamente mientras se redactaba este patrón
edit.t: Edita a mano los ajustes del patrón
edit.d: Esto te permite editar a mano los ajustes del patrón, dándote un control total sobre cómo se redactará tu patrón.
view: Ver
view.d: Estas son las distintas vistas que puedes elegir. Incluye las vistas disponibles a través de la barra de navegación o el menú, y algunas adicionales

View file

@ -0,0 +1,43 @@
uiSettings.t: Préférences de l'interface utilisateur
uiSettings.d: Ces préférences contrôlent les aspects de l'interface utilisateur de notre environnement de création de patrons en ligne.
renderer.t: Moteur de Rendu
renderer.d: Contrôle comment le patron est restitué (dessiné) à l'écran
renderWithReact.t: Rendu avec les composants React de FreeSewing
renderWithReact.d: Rendu en tant que SVG par l'intermédiaire de nos composants React. Permet l'interactivité et est optimisé pour l'écran. Utilise ceci si tu n'es pas sûr de ce que tu dois choisir.
renderWithCore.t: Rendu avec la bibliothèque Core de Freesewing
renderWithCore.d: Rendu directement en SVG à partir de Core. Ne permet aucune interactivité et est optimisé pour l'impression. Utilise-le si tu veux savoir à quoi il ressemblera une fois exporté.
control.t: Expérience de l'utilisateur
control.d: Quelle expérience utilisateur préfères-tu ? Note qu'il s'agit d'un paramètre de compte, il aura donc un impact sur l'ensemble du site Web.
inspect.t: Inspecter
inspect.d: L'activation de cette fonction te permettra d'approfondir le modèle et d'obtenir des informations sur ses différentes parties, ses chemins et ses points.
inspectNo.t: Désactive l'inspecteur
inspectNo.d: C'est la valeur par défaut, l'inspecteur de motifs est désactivé et le motif s'affiche comme d'habitude.
inspectYes.t: Active l'inspecteur
inspectYes.d: Avec l'inspecteur de motifs activé et le moteur de rendu React sélectionné, nous allons ajouter de l'interactivité au motif pour te permettre d'inspecter les différents éléments qui le composent.
no: No
yes: Yes
draft: Ébauche
test: Test
print: Mise en page d'impression
cut: Disposition des coupes
save: Sauvegarder
export: Exporter
edit: Éditer
draft.t: Dessiner votre patron
draft.d: Lance l'éditeur de patrons phare de FreeSewing, où tu peux modifier tes patrons selon tes désirs.
test.t: Tester votre patron
test.d: Vois comment ton modèle s'adapte aux changements d'options, ou de mesures.
print.t: Mise en page d'impression
print.d: Permet de disposer les pièces de ton patron de façon à ce que tu puisses imprimer ton patron sur le moins de pages possible.
cut.t: Plan de coupe
cut.d: Permet de disposer les pièces de ton patron de façon à déterminer exactement la quantité de tissu dont tu as besoin pour le réaliser.
save.t: Sauvegarde ton modèle
save.d: Sauvegarde le modèle actuel sur ton compte FreeSewing.
export.t: Exporte ton modèle
export.d: Permet d'exporter ce motif dans différents formats.
logs.t: Fiches d'information sur les modèles
logs.d: Permet de parcourir les journaux des patrons pour voir ce qui s'est passé exactement lors de la réalisation de ce patron.
edit.t: Modifie à la main les paramètres du modèle
edit.d: Cela te permet d'éditer à la main les paramètres du modèle, ce qui te donne un contrôle total sur la façon dont ton modèle sera dessiné
view: Voir
view.d: Voici les différentes vues que tu peux choisir. Comprend les vues disponibles via la barre de navigation ou le menu, ainsi que quelques vues supplémentaires.

View file

@ -0,0 +1,54 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
//Dependencies
import { loadSettingsConfig } from './config.mjs'
// Components
import { WorkbenchMenu } from '../shared/index.mjs'
import { MenuItem } from '../shared/menu-item.mjs'
import { DesktopIcon } from 'shared/components/icons.mjs'
import { inputs } from './inputs.mjs'
import { values } from './values.mjs'
import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs'
export const ns = ['ui-settings']
const UiSetting = ({ name, control, ...rest }) => (
<MenuItem
{...rest}
name={name}
allowToggle={!['control', 'view'].includes(name) && control > 3}
control={control}
docs={
<DynamicMdx
language={rest.language}
slug={
name === 'control'
? 'docs/site/account/control'
: `docs/site/draft/ui-settings/${name.toLowerCase()}`
}
/>
}
/>
)
export const UiSettings = ({ update, ui, control, language, view, setView }) => {
const settingsConfig = loadSettingsConfig()
return (
<WorkbenchMenu
{...{
config: settingsConfig,
control,
currentValues: ui,
Icon: DesktopIcon,
inputs,
Item: UiSetting,
values,
language,
name: 'uiSettings',
ns,
updateFunc: update.ui,
passProps: { view, setView },
}}
/>
)
}

View file

@ -0,0 +1,23 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { ListInput } from '../shared/inputs'
import { useControlState } from 'shared/components/account/control.mjs'
export const ControlSettingInput = (props) => {
const { selection, update } = useControlState()
props.config.dflt = selection
return (
<ListInput
{...props}
updateFunc={(path, newVal) => update(newVal)}
current={selection}
compact={selection < 2}
/>
)
}
export const inputs = {
control: ControlSettingInput,
kiosk: ListInput,
renderer: ListInput,
}

View file

@ -1,17 +1,19 @@
import { Chevron } from 'shared/components/navigation/primary.mjs'
import { ClearIcon, FilterIcon, SearchIcon } from 'shared/components/icons.mjs'
import { Ul, Li, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
import { XrayPath } from './path.mjs'
import { XrayPoint } from './point.mjs'
import { useTranslation } from 'next-i18next'
//import { Chevron } from 'shared/components/navigation/primary.mjs'
//import { ClearIcon, FilterIcon, SearchIcon } from 'shared/components/icons.mjs'
//import { XrayPath } from './path.mjs'
//import { XrayPoint } from './point.mjs'
//import { useTranslation } from 'next-i18next'
const types = {
paths: XrayPath,
points: XrayPoint,
}
//const types = {
// paths: XrayPath,
// points: XrayPoint,
//}
export const XrayList = (props) => {
const { t } = useTranslation(['app', 'parts'])
export const XrayList = () => {
//const { t } = useTranslation(['app', 'parts'])
return null
// FIXME
/*
const title = t(`parts:${props.partName}`) + ` (${props.partName})`
@ -159,4 +161,5 @@ export const XrayList = (props) => {
</Details>
</Li>
)
*/
}

View file

@ -0,0 +1,43 @@
uiSettings.t: UI-voorkeuren
uiSettings.d: Deze voorkeuren bepalen de UI (User Interface) aspecten van onze online omgeving voor het maken van patronen.
renderer.t: Render-engine
renderer.d: Bepaalt hoe het patroon wordt weergegeven (getekend) op het scherm
renderWithReact.t: Renderen met de React-componenten van FreeSewing
renderWithReact.d: Renderen als SVG via onze React-componenten. Maakt interactiviteit mogelijk en is geoptimaliseerd voor het scherm. Gebruik deze als je niet zeker weet wat je moet kiezen.
renderWithCore.t: Renderen met de Core-bibliotheek van Freesewing
renderWithCore.d: Render rechtstreeks naar SVG vanuit Core. Laat geen interactiviteit toe en is geoptimaliseerd voor afdrukken. Gebruik dit als je wilt weten hoe het eruit zal zien als het geëxporteerd wordt.
control.t: Gebruikerservaring
control.d: Welke gebruikerservaring heeft jouw voorkeur? Houd er rekening mee dat dit een accountinstelling is, dus het heeft invloed op de hele website.
inspect.t: Inspecteer
inspect.d: Als je dit inschakelt, kun je dieper in het patroon duiken en informatie opvragen over de verschillende onderdelen, paden en punten.
inspectNo.t: De inspecteur uitschakelen
inspectNo.d: Dit is de standaardinstelling, de patrooninspecteur is uitgeschakeld en het patroon wordt zoals gebruikelijk weergegeven.
inspectYes.t: De inspecteur inschakelen
inspectYes.d: Met de pattern inspector ingeschakeld en de React rendering engine geselecteerd, voegen we interactiviteit toe aan het patroon zodat je de verschillende elementen waaruit het patroon bestaat kunt inspecteren.
no: No
yes: Yes
draft: Patroontekening
test: Test
print: Print lay-out
cut: Lay-out snijden
save: Opslaan
export: Exporteren
edit: Bewerk
draft.t: Teken je patroon
draft.d: Lanceert de FreeSewing patroonbewerker, waar je je patroon naar hartenlust kunt aanpassen
test.t: Test je patroon
test.d: Kijk hoe je patroon zich aanpast aan veranderingen in opties of afmetingen
print.t: Lay-out afdrukken
print.d: Hiermee kun je je patroondelen ordenen zodat je je patroon op zo min mogelijk pagina's kunt afdrukken
cut.t: Lay-out snijden
cut.d: Hiermee kun je je patroondelen ordenen zodat je precies kunt bepalen hoeveel stof je nodig hebt om het te maken.
save.t: Je patroon opslaan
save.d: Sla het huidige patroon op in je FreeSewing account
export.t: Je patroon exporteren
export.d: Hiermee kun je dit patroon exporteren naar verschillende formaten
logs.t: Patroon logboeken
logs.d: Hiermee kun je door de patroonlogboeken bladeren om te zien wat er precies is gebeurd tijdens het maken van dit patroon
edit.t: De patrooninstellingen met de hand bewerken
edit.d: Hiermee kun je de patrooninstellingen met de hand bewerken, zodat je volledige controle hebt over hoe je patroon wordt opgesteld.
view: Bekijk
view.d: Dit zijn de verschillende weergaven waaruit je kunt kiezen. Inclusief de weergaven die beschikbaar zijn via de navigatiebalk of het menu, en enkele extra weergaven

View file

@ -0,0 +1,43 @@
uiSettings.t: Налаштування інтерфейсу
uiSettings.d: Ці налаштування керують аспектами інтерфейсу користувача (UI) нашого онлайн-середовища для створення викрійок.
renderer.t: Рушій рендерингу
renderer.d: Керує відтворення викрійки на екрані
renderWithReact.t: Рендер за допомогою React-компонентів FreeSewing
renderWithReact.d: Відтворення у форматі SVG за допомогою наших React-компонентів. Дозволяє інтерактивність та оптимізований для екрану. Використовуйте його, якщо ви не впевнені, що вибрати.
renderWithCore.t: Рендер за допомогою бібліотеки Freesewing Core
renderWithCore.d: Рендер безпосередньо в SVG з Core. Не містить інтерактивності та оптимізований для друку. Використовуйте його, якщо хочете знати, як виглядатиме зображення після експорту.
control.t: Користувацький досвід
control.d: Якому користувацькому досвіду ви надаєте перевагу? Зверніть увагу, що це налаштування облікового запису, тому воно вплине на весь веб-сайт.
inspect.t: Оглянути
inspect.d: Увімкнувши цю опцію, ви зможете заглибитися в деталь і отримати інформацію про різні її частини, шляхи і точки.
inspectNo.t: Відключити інспектора
inspectNo.d: За замовчуванням інспектор шаблонів вимкнено, і шаблон відображається у звичайному режимі.
inspectYes.t: Увімкніть інспектора
inspectYes.d: Увімкнувши інспектор шаблонів і вибравши рушій рендерингу React, ми додамо до шаблону інтерактивність, щоб ви могли оглядати різні елементи, з яких складається шаблон.
no: No
yes: Yes
draft: Чернетка
test: Тест
print: Макет для друку
cut: Макет розкрою
save: Зберегти
export: Експортувати
edit: Редагувати
draft.t: Створіть Вашу викрійку
draft.d: Запускає флагманський редактор викрійок FreeSewing, де ви можете налаштувати свою викрійку за власним бажанням
test.t: Протестувати Вашу викрійку
test.d: Подивіться, як ваш лекало адаптується до змін у параметрах або вимірах
print.t: Макет для друку
print.d: Дозволяє розташувати частини викрійки так, щоб надрукувати її на якомога меншій кількості сторінок
cut.t: Схема розкрою
cut.d: Дозволяє розташувати частини викрійки так, щоб точно визначити, скільки тканини потрібно для її виготовлення.
save.t: Збережіть свій шаблон
save.d: Збережіть поточну викрійку у своєму обліковому записі FreeSewing
export.t: Експортуйте свій шаблон
export.d: Дозволяє експортувати цей шаблон у різні формати
logs.t: Журнали шаблонів
logs.d: Дозволяє переглянути журнали шаблонів, щоб побачити, що саме відбувалося під час створення цього шаблону
edit.t: Відредагуйте налаштування шаблону вручну
edit.d: Це дає змогу вручну редагувати налаштування шаблону, що дає вам повний контроль над тим, як буде створено ваш візерунок
view: Вигляд
view.d: Це різні подання, з яких ви можете вибирати. Включає ті подання, які доступні через панель навігації або меню, а також деякі додаткові

View file

@ -0,0 +1,9 @@
// __SDEFILE__ - This file is a dependency for the stand-alone environment
import { Difficulty } from 'shared/components/designs/difficulty.mjs'
import { ListValue } from '../shared/values.mjs'
export const values = {
control: ({ control }) => <Difficulty score={control} color="primary" />,
kiosk: ListValue,
renderer: ListValue,
}

Some files were not shown because too many files have changed in this diff Show more