1
0
Fork 0

cutting layout view

This commit is contained in:
Enoch Riese 2023-06-06 11:17:14 -05:00
parent 62638902e8
commit 15c4201906
31 changed files with 585 additions and 741 deletions

View file

@ -1,16 +0,0 @@
canvas: Canvas
cut: Cut
cuttingLayout: Suggested Cutting Layout
fabric: Main Fabric
fabricSize: "{length} of {width} wide material"
heavyCanvas: Heavy Canvas
interfacing: Interfacing
lining: Lining
lmhCanvas: Light to Medium Hair Canvas
mirrored: mirrored
onFoldLower: on the fold
onFoldAndBias: folded on the bias
onBias: on the bias
plastic: Plastic
ribbing: Ribbing
edgeOfFabric: Edge of Fabric

View file

@ -13,7 +13,7 @@ export const PartInner = forwardRef(
path={part.paths[pathName]} path={part.paths[pathName]}
topLeft={part.topLeft} topLeft={part.topLeft}
bottomRight={part.bottomRight} bottomRight={part.bottomRight}
units={settings.units} units={settings[0].units}
{...{ stackName, partName, pathName, part, settings, components, t }} {...{ stackName, partName, pathName, part, settings, components, t }}
/> />
))} ))}

View file

@ -2,11 +2,11 @@ import Worker from 'web-worker'
import fileSaver from 'file-saver' import fileSaver from 'file-saver'
import { themePlugin } from '@freesewing/plugin-theme' import { themePlugin } from '@freesewing/plugin-theme'
import { pluginI18n } from '@freesewing/plugin-i18n' import { pluginI18n } from '@freesewing/plugin-i18n'
import { pagesPlugin, fabricPlugin } from 'shared/plugins/plugin-layout-part.mjs' import { pagesPlugin, materialPlugin } from 'shared/plugins/plugin-layout-part.mjs'
import { pluginAnnotations } from '@freesewing/plugin-annotations' import { pluginAnnotations } from '@freesewing/plugin-annotations'
import { cutLayoutPlugin } from '../layout/cut/plugin-cut-layout.mjs' import { cutLayoutPlugin } from 'shared/plugins/plugin-cut-layout.mjs'
import { fabricSettingsOrDefault } from '../layout/cut/index.mjs' import { materialSettingsOrDefault } from 'shared/components/workbench/views/cut/hooks.mjs'
import { useFabricLength } from '../layout/cut/settings.mjs' import { useMaterialLength } from 'shared/components/workbench/views/cut/hooks.mjs'
import { capitalize, formatMm } from 'shared/utils.mjs' import { capitalize, formatMm } from 'shared/utils.mjs'
import { import {
defaultPrintSettings, defaultPrintSettings,
@ -14,7 +14,7 @@ import {
} from 'shared/components/workbench/views/print/config.mjs' } from 'shared/components/workbench/views/print/config.mjs'
import get from 'lodash.get' import get from 'lodash.get'
export const ns = ['plugin', 'common'] export const ns = ['cut', 'plugin', 'common']
export const exportTypes = { export const exportTypes = {
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'], exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'],
exportForEditing: ['svg', 'pdf'], exportForEditing: ['svg', 'pdf'],
@ -48,39 +48,39 @@ const themedPattern = (Design, settings, overwrite, format, t) => {
* @param {Object} settings the settings * @param {Object} settings the settings
* @param {string} format the export format this pattern will be prepared for * @param {string} format the export format this pattern will be prepared for
* @param {function} t the i18n function * @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, settings, format, t, ui) => { const generateCutLayouts = (pattern, Design, settings, format, t, ui) => {
// get the fabrics from the already drafted base pattern // get the materials from the already drafted base pattern
const fabrics = pattern.setStores[pattern.activeSet].cutlist.getCutFabrics( const materials = pattern.setStores[pattern.activeSet].cutlist.getCutFabrics(
pattern.settings[0] pattern.settings[0]
) || ['fabric'] ) || ['material']
if (!fabrics.length) return if (!materials.length) return
const isImperial = settings.units === 'imperial' const isImperial = settings.units === 'imperial'
const cutLayouts = {} const cutLayouts = {}
// each fabric // each material
fabrics.forEach((f) => { materials.forEach((f) => {
// get the settings and layout for that fabric // get the settings and layout for that material
const fabricSettings = fabricSettingsOrDefault(settings.units, ui, f) const materialSettings = materialSettingsOrDefault(settings.units, ui, f)
const fabricLayout = get(ui, ['layouts', 'cut', f], true) const materialLayout = get(ui, ['layouts', 'cut', f], true)
// make a new pattern // make a new pattern
const fabricPattern = themedPattern(Design, settings, { layout: fabricLayout }, format, t) const materialPattern = themedPattern(Design, settings, { layout: materialLayout }, format, t)
// add cut layout plugin and fabric plugin // add cut layout plugin and material plugin
.use(cutLayoutPlugin(f, fabricSettings.grainDirection)) .use(cutLayoutPlugin(f, materialSettings.grainDirection))
.use(fabricPlugin({ ...fabricSettings, printStyle: true, setPatternSize: 'width' })) .use(materialPlugin({ ...materialSettings, printStyle: true, setPatternSize: 'width' }))
// draft and render // draft and render
fabricPattern.draft() materialPattern.draft()
const svg = fabricPattern.render() const svg = materialPattern.render()
// include translations // include translations
cutLayouts[f] = { cutLayouts[f] = {
svg, svg,
title: t('plugin:' + f), title: t('cut:' + f),
dimensions: t('plugin:fabricSize', { dimensions: t('cut:materialSize', {
width: formatMm(fabricSettings.sheetWidth, settings.units, 'notags'), width: formatMm(materialSettings.sheetWidth, settings.units, 'notags'),
length: useFabricLength(isImperial, fabricPattern.height, 'notags'), length: useMaterialLength(isImperial, materialPattern.height, 'notags'),
interpolation: { escapeValue: false }, interpolation: { escapeValue: false },
}), }),
} }
@ -160,7 +160,6 @@ export const handleExport = async ({
} }
try { try {
throw new Error('bad bad bad')
// add pages to pdf exports // add pages to pdf exports
if (format !== 'svg') { if (format !== 'svg') {
pattern.use( pattern.use(
@ -177,7 +176,7 @@ export const handleExport = async ({
design: capitalize(design), design: capitalize(design),
tagline: t('common:sloganCome') + '. ' + t('common:sloganStay'), tagline: t('common:sloganCome') + '. ' + t('common:sloganStay'),
url: window.location.href, url: window.location.href,
cuttingLayout: t('plugin:cuttingLayout'), cuttingLayout: t('cut:cuttingLayout'),
} }
} }

View file

@ -170,8 +170,8 @@ export class PdfMaker {
} }
/** generate the title for a cutting layout page */ /** generate the title for a cutting layout page */
async generateCutLayoutTitle(fabricTitle, fabricDimensions) { async generateCutLayoutTitle(materialTitle, materialDimensions) {
this.addText(this.strings.cuttingLayout, 12, 2).addText(fabricTitle, 28) this.addText(this.strings.cuttingLayout, 12, 2).addText(materialTitle, 28)
this.pdf.lineWidth(1) this.pdf.lineWidth(1)
this.pdf this.pdf
@ -180,16 +180,16 @@ export class PdfMaker {
.stroke() .stroke()
this.lineLevel += 5 this.lineLevel += 5
this.addText(fabricDimensions, 16) this.addText(materialDimensions, 16)
} }
/** generate all cutting layout pages */ /** generate all cutting layout pages */
async generateCutLayoutPages() { async generateCutLayoutPages() {
if (!this.pageSettings.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() this.nextPage()
const { title, dimensions, svg } = this.cutLayouts[fabric] const { title, dimensions, svg } = this.cutLayouts[material]
await this.generateCutLayoutTitle(title, dimensions) await this.generateCutLayoutTitle(title, dimensions)
await this.generateSvgPage(svg) await this.generateSvgPage(svg)
} }

View file

@ -15,8 +15,9 @@ import { ModalSpinner } from 'shared/components/modal/spinner.mjs'
import { DraftView, ns as draftNs } from 'shared/components/workbench/views/draft/index.mjs' import { DraftView, ns as draftNs } from 'shared/components/workbench/views/draft/index.mjs'
import { SaveView, ns as saveNs } from 'shared/components/workbench/views/save/index.mjs' import { SaveView, ns as saveNs } from 'shared/components/workbench/views/save/index.mjs'
import { PrintView, ns as printNs } from 'shared/components/workbench/views/print/index.mjs' import { PrintView, ns as printNs } from 'shared/components/workbench/views/print/index.mjs'
import { CutView, ns as cutNs } from 'shared/components/workbench/views/cut/index.mjs'
export const ns = ['account', 'workbench', ...draftNs, ...saveNs, ...printNs] export const ns = ['account', 'workbench', ...draftNs, ...saveNs, ...printNs, ...cutNs]
const defaultUi = { const defaultUi = {
renderer: 'react', renderer: 'react',
@ -25,6 +26,7 @@ const defaultUi = {
const views = { const views = {
draft: DraftView, draft: DraftView,
print: PrintView, print: PrintView,
cut: CutView,
} }
const draftViews = ['draft', 'test'] const draftViews = ['draft', 'test']

View file

@ -1,125 +0,0 @@
import { useTranslation } from 'next-i18next'
import { CutLayoutSettings } from './settings.mjs'
// import { Draft } from '../draft/index.mjs'
import { fabricPlugin } from 'shared/plugins/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 = (units, ui, fabric) => {
const isImperial = units === 'imperial'
const sheetHeight = measurementAsMm(isImperial ? 36 : 100, units)
const uiSettings = get(ui, ['cut', 'fabric', fabric], {})
const sheetWidth = uiSettings.sheetWidth || measurementAsMm(isImperial ? 54 : 120, units)
const grainDirection = uiSettings.grainDirection === undefined ? 90 : uiSettings.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,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

@ -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

@ -71,7 +71,7 @@ export const loadSettingsConfig = ({
units: { units: {
control: 1, // Show when control > 2 control: 1, // Show when control > 2
list: ['metric', 'imperial'], list: ['metric', 'imperial'],
dflt: units, dflt: 'metric',
choiceTitles: { choiceTitles: {
metric: 'metric', metric: 'metric',
imperial: 'imperial', imperial: 'imperial',

View file

@ -1,7 +1,6 @@
import { useContext } from 'react' import { useContext } from 'react'
import { MenuItemGroup } from './menu-item.mjs' import { MenuItemGroup } from './menu-item.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { HelpIcon } from 'shared/components/icons.mjs'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { ModalContext } from 'shared/context/modal-context.mjs' import { ModalContext } from 'shared/context/modal-context.mjs'

View file

@ -85,9 +85,7 @@ export const ListToggle = ({ config, changed, updateFunc, name }) => {
return ( return (
<input <input
type="checkbox" type="checkbox"
className={`toggle toggle-sm ${ className={`toggle toggle-sm ${changed ? 'toggle-accent' : 'toggle-secondary'}`}
checked && !boolConfig.dflt ? 'toggle-accent' : 'toggle-secondary'
}`}
checked={checked} checked={checked}
onChange={doToggle} onChange={doToggle}
/> />
@ -276,4 +274,25 @@ export const MmInput = (props) => {
} }
/** A placeholder for an input to handle constant values */ /** A placeholder for an input to handle constant values */
export const ConstantInput = () => <p>FIXME: Constant options are not implemented (yet)</p> export const ConstantInput = ({
type = 'number',
name,
current,
updateFunc,
t,
changed,
config,
}) => (
<>
<p>{t(`${name}.d`)}</p>
<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

@ -119,7 +119,7 @@ export const MenuItem = ({
className={open ? openButtonClass : 'btn btn-accent'} className={open ? openButtonClass : 'btn btn-accent'}
onClick={(evt) => { onClick={(evt) => {
evt.stopPropagation() evt.stopPropagation()
updateFunc(name) updateFunc([name])
}} }}
> >
<ClearIcon /> <ClearIcon />

View file

@ -1,20 +1,11 @@
import { useRef } from 'react' import { useRef } from 'react'
import { Stack } from './stack.mjs'
// import { SvgWrapper } from '../../pattern/svg.mjs'
// import { PartInner } from '../../pattern/part.mjs'
import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
import { MovableStack } from './stack.mjs' import { MovableStack } from './stack.mjs'
import get from 'lodash.get'
export const MovablePattern = ({ export const MovablePattern = ({
design,
pattern,
renderProps, renderProps,
patternConfig,
settings,
showButtons = true, showButtons = true,
update, update,
bgProps = {},
fitImmovable = false, fitImmovable = false,
immovable = [], immovable = [],
layoutPath, layoutPath,
@ -68,10 +59,6 @@ export const MovablePattern = ({
} }
} }
const viewBox = layout.topLeft
? `${layout.topLeft.x} ${layout.topLeft.y} ${layout.width} ${layout.height}`
: false
const sortedStacks = {} const sortedStacks = {}
Object.keys(renderProps.stacks) Object.keys(renderProps.stacks)
.sort((a, b) => { .sort((a, b) => {
@ -90,13 +77,13 @@ export const MovablePattern = ({
{...{ {...{
stackName, stackName,
stack, stack,
settings,
components, components,
t, t,
movable: !immovable.includes(stackName), movable: !immovable.includes(stackName),
layout: layout.stacks[stackName], layout: layout.stacks[stackName],
updateLayout, updateLayout,
showButtons, showButtons,
settings,
}} }}
/> />
) )

View file

@ -43,25 +43,23 @@
* I've sort of left it at this because I'm starting to wonder if we should perhaps re-think * 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. * 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 { useRef, useState, useEffect, useCallback } from 'react'
import { generateStackTransform, getTransformedBounds } from '@freesewing/core' import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
import { Stack } from 'pkgs/react-components/src/pattern/stack.mjs'
import { getProps, angle } from 'pkgs/react-components/src/pattern/utils.mjs' import { getProps, angle } from 'pkgs/react-components/src/pattern/utils.mjs'
import { drag } from 'd3-drag' import { drag } from 'd3-drag'
import { select } from 'd3-selection' import { select } from 'd3-selection'
import { Buttons } from './transform-buttons.mjs' import { Buttons } from './transform-buttons.mjs'
import get from 'lodash.get'
export const MovableStack = ({ export const MovableStack = ({
stackName, stackName,
stack, stack,
settings,
components, components,
t, t,
movable = true, movable = true,
layout, layout,
updateLayout, updateLayout,
showButtons, showButtons,
settings,
}) => { }) => {
const stackExists = !movable || typeof layout?.move?.x !== 'undefined' const stackExists = !movable || typeof layout?.move?.x !== 'undefined'
@ -72,100 +70,80 @@ export const MovableStack = ({
// State variable to switch between moving or rotating the part // State variable to switch between moving or rotating the part
const [rotate, setRotate] = useState(false) const [rotate, setRotate] = useState(false)
// update the layout on mount // This is kept as state to avoid re-rendering on drag, which would kill performance
useEffect(() => { // It's a bit of an anti-pattern, but we'll directly manipulate the properties instead of updating the state
// only update if there's a rendered part and it's not the pages or fabric part
if (stackRef.current && movable) {
updateStacklayout(false)
}
}, [stackRef, layout])
// Initialize drag handler
useEffect(() => {
// don't drag the pages
if (!movable || !stackExists) return
handleDrag(select(stackRef.current))
}, [rotate, stackRef, layout])
// // Don't just assume this makes sense
if (!stackExists) return null
if (!movable) return <Stack {...{ stackName, stack, settings, components, t }} />
// 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 // Managing the difference between re-render and direct DOM updates makes this
// whole thing a bit tricky to wrap your head around // whole thing a bit tricky to wrap your head around
let translateX = layout.move.x const stackRotation = layout?.rotate || 0
let translateY = layout.move.y const [liveTransforms] = useState({
let stackRotation = layout.rotate || 0 translateX: layout?.move.x,
let rotation = stackRotation translateY: layout?.move.y,
let flipX = !!layout.flipX rotation: stackRotation,
let flipY = !!layout.flipY flipX: !!layout?.flipX,
flipY: !!layout?.flipY,
})
const center = { const center = stack.topLeft && {
x: stack.topLeft.x + (stack.bottomRight.x - stack.topLeft.x) / 2, x: stack.topLeft.x + (stack.bottomRight.x - stack.topLeft.x) / 2,
y: stack.topLeft.y + (stack.bottomRight.y - stack.topLeft.y) / 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 setTransforms = useCallback(() => {
const getRotation = (event) =>
angle(center, event.subject) - angle(center, { x: event.x, y: event.y })
const setTransforms = () => {
// get the transform attributes // get the transform attributes
const { translateX, translateY, rotation, flipX, flipY } = liveTransforms
const transforms = generateStackTransform(translateX, translateY, rotation, flipX, flipY, stack) const transforms = generateStackTransform(translateX, translateY, rotation, flipX, flipY, stack)
const me = select(stackRef.current) const me = select(stackRef.current)
me.attr('transform', transforms.join(' ')) me.attr('transform', transforms.join(' '))
return transforms return transforms
} }, [liveTransforms, stackRef, stack])
let didDrag = false /** update the layout either locally or in the gist */
const handleDrag = drag() const updateStacklayout = useCallback(
// subject allows us to save data from the start of the event to use throughout event handing (history = true) => {
.subject(function (event) { /** don't mess with what we don't lay out */
return rotate if (!stackRef.current || !movable) return
? // 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) { // set the transforms on the stack in order to calculate from the latest position
let newRotation = getRotation(event) const transforms = setTransforms()
// 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 // apply the transforms to the bounding box to get the new extents of the stack
} else { const { tl, br } = getTransformedBounds(stack, transforms)
translateX = event.x
translateY = event.y
}
// a drag happened, so we should update the layout when we're done // update it on the draft component
didDrag = true updateLayout(
setTransforms() stackName,
}) {
.on('end', function () { move: {
// save to gist if anything actually changed x: liveTransforms.translateX,
if (didDrag) updateStacklayout() y: liveTransforms.translateY,
},
rotate: liveTransforms.rotation % 360,
flipX: liveTransforms.flipX,
flipY: liveTransforms.flipY,
tl,
br,
},
history
)
},
[stackRef, setTransforms, updateLayout, liveTransforms, movable, stack, stackName]
)
didDrag = false // update the layout on mount
}) useEffect(() => {
// only update if there's a rendered part and it's not an imovable part
if (stackRef.current && movable) {
updateStacklayout(false)
}
}, [stackRef, movable, updateStacklayout])
/** reset the part's transforms */ /** reset the part's transforms */
const resetPart = () => { const resetPart = () => {
rotation = 0 liveTransforms.rotation = 0
flipX = 0 liveTransforms.flipX = 0
flipY = 0 liveTransforms.flipY = 0
updateStacklayout() updateStacklayout()
} }
@ -179,58 +157,85 @@ export const MovableStack = ({
setRotate(!rotate) setRotate(!rotate)
} }
/** update the layout either locally or in the gist */
const updateStacklayout = (history = true) => {
/** don't mess with what we don't lay out */
if (!stackRef.current || !movable) return
// set the transforms on the stack in order to calculate from the latest position
const transforms = setTransforms()
// apply the transforms to the bounding box to get the new extents of the stack
const { tl, br } = getTransformedBounds(stack, transforms)
// update it on the draft component
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 */ /** Method to flip (mirror) the part along the X or Y axis */
const flip = (axis) => { const flip = (axis) => {
if (axis === 'x') flipX = !flipX if (axis === 'x') liveTransforms.flipX = !liveTransforms.flipX
else flipY = !flipY else liveTransforms.flipY = !liveTransforms.flipY
updateStacklayout() updateStacklayout()
} }
/** method to rotate 90 degrees */ /** method to rotate 90 degrees */
const rotate90 = (direction = 1) => { const rotate90 = (direction = 1) => {
if (flipX) direction *= -1 if (liveTransforms.flipX) direction *= -1
if (flipY) direction *= -1 if (liveTransforms.flipY) direction *= -1
rotation += 90 * direction liveTransforms.rotation += 90 * direction
updateStacklayout() updateStacklayout()
} }
/** get the delta rotation from the start of the drag event to now */
const getRotation = (event) =>
angle(center, event.subject) - angle(center, { x: event.x, y: event.y })
let didDrag = false
const handleDrag =
movable &&
drag()
// subject allows us to save data from the start of the event to use throughout event handing
.subject(function (event) {
return rotate
? // if we're rotating, the subject is the mouse position
{ x: event.x, y: event.y }
: // if we're moving, the subject is the part's x,y coordinates
{ x: liveTransforms.translateX, y: liveTransforms.translateY }
})
.on('drag', function (event) {
if (!event.dx && !event.dy) return
if (rotate) {
let newRotation = getRotation(event)
// shift key to snap the rotation
if (event.sourceEvent.shiftKey) {
newRotation = Math.ceil(newRotation / 15) * 15
}
// reverse the rotation direction one time per flip. if we're flipped both directions, rotation will be positive again
if (liveTransforms.flipX) newRotation *= -1
if (liveTransforms.flipY) newRotation *= -1
liveTransforms.rotation = stackRotation + newRotation
} else {
liveTransforms.translateX = event.x
liveTransforms.translateY = event.y
}
// a drag happened, so we should update the layout when we're done
didDrag = true
setTransforms()
})
.on('end', function () {
// save to gist if anything actually changed
if (didDrag) updateStacklayout()
didDrag = false
})
// Initialize drag handler
useEffect(() => {
// don't drag the pages
if (!movable || !stackExists) return
handleDrag(select(stackRef.current))
}, [stackRef, movable, stackExists, handleDrag])
// // Don't just assume this makes sense
if (!stackExists) return null
const { Group, Part } = components const { Group, Part } = components
return ( return (
<Group id={`stack-${stackName}`} {...getProps(stack)} ref={stackRef}> <Group id={`stack-${stackName}`} {...getProps(stack)} ref={stackRef}>
<Group id={`stack-inner-${stackName}`} ref={innerRef}> <Group id={`stack-inner-${stackName}`} ref={innerRef}>
{[...stack.parts].map((part, key) => ( {[...stack.parts].map((part, key) => (
<Part {...{ settings, components, t, part, stackName, key }} /> <Part {...{ components, t, part, stackName, settings }} key={key} />
))} ))}
</Group> </Group>
{movable && ( {movable && (
@ -246,9 +251,11 @@ export const MovableStack = ({
/> />
{showButtons ? ( {showButtons ? (
<Buttons <Buttons
transform={`translate(${center.x}, ${center.y}) rotate(${-rotation}) scale(${ transform={`translate(${center.x}, ${
flipX ? -1 : 1 center.y
},${flipY ? -1 : 1})`} }) rotate(${-liveTransforms.rotation}) scale(${liveTransforms.flipX ? -1 : 1},${
liveTransforms.flipY ? -1 : 1
})`}
flip={flip} flip={flip}
rotate={rotate} rotate={rotate}
setRotate={setRotate} setRotate={setRotate}

View file

@ -1,6 +1,5 @@
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { ClearIcon } from 'shared/components/icons.mjs' import { ClearIcon } from 'shared/components/icons.mjs'
import get from 'lodash.get'
const Triangle = ({ transform = 'translate(0,0)', fill = 'currentColor' }) => ( const Triangle = ({ transform = 'translate(0,0)', fill = 'currentColor' }) => (
<path <path
@ -54,20 +53,19 @@ const Button = ({ onClickCb, transform, Icon, children }) => {
) )
} }
export const ShowButtonsToggle = ({ gist, layoutSetType, updateGist }) => { export const ShowButtonsToggle = ({ ui, update }) => {
const { t } = useTranslation('workbench') const { t } = useTranslation('workbench')
const path = ['_state', 'layout', layoutSetType, 'showButtons'] const hideButtons = (evt) => {
const showButtons = get(gist, path, true) update.ui('hideMovableButtons', !evt.target.checked)
const setShowButtons = () => updateGist(path, !showButtons) }
return ( return (
<label htmlFor="showButtons" className="label"> <label className="label cursor-pointer">
<span className="mr-2">{t('showButtons')}</span> <span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
<input <input
type="checkbox" type="checkbox"
className="toggle toggle-primary" className="toggle toggle-primary"
checked={showButtons} checked={!ui.hideMovableButtons}
onChange={setShowButtons} onChange={hideButtons}
/> />
</label> </label>
) )

View file

@ -0,0 +1,26 @@
canvas: Canvas
cut: Cut
cuttingLayout: Suggested Cutting Layout
fabric: Main Fabric
materialSize: "{length} of {width} wide material"
heavyCanvas: Heavy Canvas
interfacing: Interfacing
lining: Lining
lmhCanvas: Light to Medium Hair Canvas
mirrored: mirrored
onFoldLower: on the fold
onFoldAndBias: folded on the bias
onBias: on the bias
plastic: Plastic
ribbing: Ribbing
edgeOfFabric: Edge of Fabric
sheetWidth.t: Material Width
sheetWidth.d: How wide is the material?
grainDirection.t: Grain Direction
grainDirection.d: What direction does the grain run on the material?
horizontal.t: Horizontal
horizontal.d: Grain runs left to right
vertical.t: Vertical
vertical.d: Grain runs top to bottom
cutSettings: Material Settings
cutSettings.d: These settings allow you to specify properties of the material to aid in making a cutting layout

View file

@ -0,0 +1,80 @@
import { measurementAsMm, formatFraction128 } from 'shared/utils.mjs'
import { materialPlugin } from 'shared/plugins/plugin-layout-part.mjs'
import { cutLayoutPlugin } from 'shared/plugins/plugin-cut-layout.mjs'
import { pluginAnnotations } from '@freesewing/plugin-annotations'
import { round } from 'shared/utils'
import get from 'lodash.get'
export const activeMaterialPath = ['cutting', 'activeMaterial']
export const materialSettingsPath = ['cutting', 'materials']
export const useMaterialLength = (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 materialUnit = isImperial ? 36 : 100
// for material, these divisions are granular enough
const rounder = isImperial ? 16 : 10
// we convert the used material height to the right units so we can round it
const inMaterialUnits = height / (materialUnit * 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 * inMaterialUnits) / 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 materialSettingsOrDefault = (units, ui, material) => {
const isImperial = units === 'imperial'
const sheetHeight = measurementAsMm(isImperial ? 36 : 100, units)
const uiSettings = get(ui, [...materialSettingsPath, material], {})
const sheetWidth = uiSettings.sheetWidth || measurementAsMm(isImperial ? 54 : 120, units)
const grainDirection = uiSettings.grainDirection === undefined ? 90 : uiSettings.grainDirection
return { activeMaterial: material, sheetWidth, grainDirection, sheetHeight }
}
export const useMaterialSettings = ({ ui }) => {
const activeMaterial = get(ui, activeMaterialPath, 'fabric')
return {
activeMaterial,
...get(ui, [...materialSettingsPath, activeMaterial]),
}
}
export const useMaterialDraft = ({ settings, ui, Design, materialSettings }) => {
// get the appropriate layout for the view
const layout = get(ui, ['layouts', 'cut', materialSettings.activeMaterial], true)
// hand it separately to the design
const pattern = new Design({ ...settings, layout })
materialSettings = materialSettingsOrDefault(settings.units, ui, materialSettings.activeMaterial)
const layoutSettings = {
sheetWidth: materialSettings.sheetWidth,
sheetHeight: materialSettings.sheetHeight,
}
let renderProps
try {
// add the material plugin to the draft
pattern.use(materialPlugin(layoutSettings))
// add the cutLayout plugin
pattern.use(cutLayoutPlugin(materialSettings.activeMaterial, materialSettings.grainDirection))
// also, pluginAnnotations and pluginFlip are needed
pattern.use(pluginAnnotations)
// draft the pattern
pattern.draft()
renderProps = pattern.getRenderProps()
} catch (err) {
console.log(err)
}
return { pattern, renderProps }
}
export const useMaterialList = (pattern) => {
return pattern.setStores[0].cutlist.getCutFabrics(pattern.settings[0])
}

View file

@ -0,0 +1,121 @@
import { useEffect, useCallback } from 'react'
import { useTranslation } from 'next-i18next'
import { CutMenu, ns as menuNs } from './menu.mjs'
import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs'
import { IconWrapper } from 'shared/components/icons.mjs'
import {
activeMaterialPath,
useMaterialSettings,
useMaterialDraft,
useMaterialList,
useMaterialLength,
} from './hooks'
export const ns = [...menuNs]
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 MaterialCounter = ({ settings, renderProps }) => {
const materialLength = useMaterialLength(settings.units === 'imperial', renderProps.height)
return (
<div className="flex flex-row font-bold items-center text-2xl justify-center">
<SheetIcon className="h-6 w-6 mr-2 inline align-middle" />
<span className="align-middle">{materialLength}</span>
</div>
)
}
export const CutView = ({
design,
patternConfig,
settings,
ui,
update,
language,
account,
DynamicDocs,
Design,
}) => {
const { t } = useTranslation(['workbench', 'plugin'])
const materialSettings = useMaterialSettings({ ui, units: settings.units })
const { pattern, renderProps } = useMaterialDraft({ settings, ui, Design, materialSettings })
const materialList = useMaterialList(pattern)
const setActiveMaterial = useCallback(
(newMaterial) => {
update.ui(activeMaterialPath, newMaterial)
},
[update]
)
useEffect(() => {
if (!materialList.includes(materialSettings.activeMaterial)) setActiveMaterial(materialList[0])
}, [materialSettings, materialList, setActiveMaterial])
return (
<div>
<div className="flex flex-row">
<div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0">
<div className="flex justify-between items-baseline">
<h2 className="capitalize">
{t('layoutThing', { thing: design }) + ' ' + t('forCutting')}
</h2>
<MaterialCounter settings={settings} renderProps={renderProps} />
</div>
<div className="my-4">
{materialList.length > 1 ? (
<div className="tabs">
{materialList.map((title) => (
<button
key={title}
className={`text-xl font-bold capitalize tab tab-bordered grow ${
materialSettings.activeMaterial === title ? 'tab-active' : ''
}`}
onClick={() => setActiveMaterial(title)}
>
{t('plugin:' + title)}
</button>
))}
</div>
) : null}
<MovablePattern
{...{
renderProps,
update,
immovable: ['material'],
layoutPath: ['layouts', 'cut', materialSettings.activeMaterial],
showButtons: !ui.hideMovableButtons,
}}
/>
</div>
</div>
<div className="w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-screen overflow-scroll">
<CutMenu
{...{
design,
pattern,
patternConfig,
settings,
ui,
update,
language,
account,
DynamicDocs,
materialSettings,
}}
/>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,64 @@
import { useTranslation } from 'next-i18next'
import {
DesignOptions,
ns as designMenuNs,
} from 'shared/components/workbench/menus/design-options/index.mjs'
import {
CoreSettings,
ns as coreMenuNs,
} from 'shared/components/workbench/menus/core-settings/index.mjs'
import { CutSettings, ns as cutNs } from './settings.mjs'
import { ShowButtonsToggle } from 'shared/components/workbench/pattern/movable/transform-buttons.mjs'
import { ClearIcon } from 'shared/components/icons.mjs'
export const ns = [...coreMenuNs, ...designMenuNs, ...cutNs, 'workbench']
const CutActions = ({ update, ui, materialSettings }) => {
// get translation for the menu
const { t } = useTranslation(['workbench'])
const resetLayout = () => update.ui(['layouts', 'cut', materialSettings.activeMaterial])
return (
<div className="mt-2 mb-4">
<div className="flex justify-evenly flex-col lg:flex-row">
<ShowButtonsToggle update={update} ui={ui} />
<button className="btn btn-primary btn-outline" onClick={resetLayout}>
<ClearIcon />
<span className="ml-2">{t('reset')}</span>
</button>
</div>
</div>
)
}
export const CutMenu = ({
design,
patternConfig,
settings,
ui,
update,
language,
account,
DynamicDocs,
materialSettings,
}) => {
const control = account.control
const menuProps = {
design,
patternConfig,
settings,
update,
language,
account,
DynamicDocs,
control,
}
return (
<nav className="grow mb-12">
<CutActions update={update} ui={ui} materialSettings={materialSettings} />
<CutSettings {...menuProps} ui={ui} materialSettings={materialSettings} />
<DesignOptions {...menuProps} />
<CoreSettings {...menuProps} />
</nav>
)
}

View file

@ -0,0 +1,70 @@
import { CutIcon } from 'shared/components/icons.mjs'
import { measurementAsMm, measurementAsUnits } from 'shared/utils.mjs'
import { ConstantInput, ListInput } from 'shared/components/workbench/menus/shared/inputs.mjs'
import { MmValue, ListValue } from 'shared/components/workbench/menus/shared/values.mjs'
import { WorkbenchMenu } from 'shared/components/workbench/menus/shared/index.mjs'
import { materialSettingsPath } from './hooks.mjs'
export const ns = ['cut']
const FabricSizeInput = (props) => {
const mmCurrent = measurementAsUnits(props.current, props.units)
const mmUpdateFunc = (path, newVal) =>
props.updateFunc(path, measurementAsMm(newVal, props.units))
return <ConstantInput {...props} current={mmCurrent} updateFunc={mmUpdateFunc} />
}
const loadConfig = (units) => ({
sheetWidth: {
dflt: units === 'imperial' ? 54 : 120,
},
grainDirection: {
list: [0, 90],
dflt: 90,
choiceTitles: {
0: 'horizontal',
90: 'vertical',
},
valueTitles: {
0: 'horizontal',
90: 'vertical',
},
},
})
const inputs = {
sheetWidth: FabricSizeInput,
grainDirection: ListInput,
}
const values = {
sheetWidth: MmValue,
grainDirection: ListValue,
}
export const CutSettings = ({ update, settings, account, materialSettings }) => {
const { activeMaterial } = materialSettings
const config = loadConfig(settings.units)
const passProps = { units: settings.units }
const updateFunc = (path, newVal) =>
update.ui([...materialSettingsPath, activeMaterial, ...path], newVal)
return (
<WorkbenchMenu
{...{
config,
control: account.control,
currentValues: materialSettings,
Icon: CutIcon,
inputs,
values,
name: 'cutSettings',
ns,
passProps,
updateFunc,
}}
/>
)
}

View file

@ -1,50 +1,19 @@
import { WorkbenchMenu } from 'shared/components/workbench/menus/shared/index.mjs'
import { MenuItem } from 'shared/components/workbench/menus/shared/menu-item.mjs'
import { BoolInput } from 'shared/components/workbench/menus/shared/inputs.mjs'
import { ListValue } from 'shared/components/workbench/menus/shared/values.mjs'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { ClearIcon, ExportIcon } from 'shared/components/icons.mjs' import { ClearIcon, ExportIcon } from 'shared/components/icons.mjs'
const handlers = { import { ShowButtonsToggle } from 'shared/components/workbench/pattern/movable/transform-buttons.mjs'
showMovableButtons: (update) => (_path, newVal) => update.ui('showMovableButtons', newVal),
}
export const ns = ['workbench', 'print'] export const ns = ['workbench', 'print']
const config = { export const PrintActions = ({ update, ui, exportIt }) => {
showMovableButtons: {
dflt: true,
},
}
const PrintActionItem = ({ name, config, settings, ui, updateFunc, passProps, ...rest }) => {
switch (name) {
case 'showMovableButtons':
return
}
}
export const PrintActions = ({ update, settings, ui, account, exportIt }) => {
// get translation for the menu // get translation for the menu
const { t } = useTranslation(ns) const { t } = useTranslation(ns)
const hideButtons = (evt) => {
update.ui('hideMovableButtons', !evt.target.checked)
}
const resetLayout = () => update.ui(['layouts', 'print']) const resetLayout = () => update.ui(['layouts', 'print'])
return ( return (
<div className="mt-2 mb-4"> <div className="mt-2 mb-4">
<div className="flex justify-evenly flex-col lg:flex-row"> <div className="flex justify-evenly flex-col lg:flex-row">
<label className="label cursor-pointer"> <ShowButtonsToggle update={update} ui={ui} />
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={!ui.hideMovableButtons}
onChange={hideButtons}
/>
</label>
<button className="btn btn-primary btn-outline" onClick={resetLayout}> <button className="btn btn-primary btn-outline" onClick={resetLayout}>
<ClearIcon /> <ClearIcon />
<span className="ml-2">{t('reset')}</span> <span className="ml-2">{t('reset')}</span>

View file

@ -1,4 +1,4 @@
import { useEffect, useState, useContext } from 'react' import { useContext } from 'react'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs' import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs'
import { import {
@ -9,18 +9,36 @@ import get from 'lodash.get'
import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs' import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs'
import { PrintMenu, ns as menuNs } from './menu.mjs' import { PrintMenu, ns as menuNs } from './menu.mjs'
import { defaultPrintSettings, printSettingsPath } from './config.mjs' import { defaultPrintSettings, printSettingsPath } from './config.mjs'
import { PrintIcon, RightIcon, ClearIcon, ExportIcon } from 'shared/components/icons.mjs' import { PrintIcon, RightIcon } from 'shared/components/icons.mjs'
import { LoadingContext } from 'shared/context/loading-context.mjs' import { LoadingContext } from 'shared/context/loading-context.mjs'
import { useToast } from 'shared/hooks/use-toast.mjs' import { useToast } from 'shared/hooks/use-toast.mjs'
const viewNs = ['print', ...exportNs] const viewNs = ['print', ...exportNs]
export const ns = [...viewNs, ...menuNs] export const ns = [...viewNs, ...menuNs]
const PageCounter = ({ pattern }) => {
const pages = pattern.setStores[0].get('pages', {})
const { cols, rows, count } = pages
return (
<div className="flex flex-row font-bold items-center text-2xl justify-center ">
<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="ml-2">{rows}</span>
</div>
)
}
export const PrintView = ({ export const PrintView = ({
design, design,
pattern, pattern,
patternConfig, patternConfig,
setView,
settings, settings,
ui, ui,
update, update,
@ -50,7 +68,6 @@ export const PrintView = ({
} catch (err) { } catch (err) {
console.log(err) console.log(err)
} }
const bgProps = { fill: 'none' }
const exportIt = () => { const exportIt = () => {
handleExport({ handleExport({
@ -67,29 +84,15 @@ export const PrintView = ({
}) })
} }
let name = design
const pages = pattern.setStores[0].get('pages', {})
const { cols, rows, count } = pages
return ( return (
<div> <div>
<div className="flex flex-row"> <div className="flex flex-row">
<div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0"> <div className="w-2/3 shrink-0 grow lg:p-4 sticky top-0">
<div className="flex justify-between"> <div className="flex justify-between items-baseline">
<h2 className="capitalize"> <h2 className="capitalize">
{t('layoutThing', { thing: name }) + ' ' + t('forPrinting')} {t('layoutThing', { thing: design }) + ' ' + t('forPrinting')}
</h2> </h2>
<div className="flex flex-row font-bold items-center text-2xl justify-center "> <PageCounter pattern={pattern} />
<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="ml-2">{rows}</span>
</div>
</div> </div>
<MovablePattern <MovablePattern
{...{ {...{

View file

@ -13,7 +13,6 @@ export const ns = [...coreMenuNs, ...designMenuNs, ...printMenuNs]
export const PrintMenu = ({ export const PrintMenu = ({
design, design,
pattern,
patternConfig, patternConfig,
settings, settings,
ui, ui,

View file

@ -1,4 +1,3 @@
showMovableButtons: Buttons
printSettings: Print Settings printSettings: Print Settings
printSettings.d: These settings control aspects of the page for PDF export and printing printSettings.d: These settings control aspects of the page for PDF export and printing
size.t: Paper Size size.t: Paper Size
@ -20,11 +19,11 @@ legal.d: 8.5 x 14 in
tabloid.t: Tabloid tabloid.t: Tabloid
tabloid.d: 11 x 17 in tabloid.d: 11 x 17 in
cutlist.t: Include Cutting Layouts cutlist.t: Include Cutting Layouts
cutlist.d: Should images of the suggested cutting layouts for each fabric be included in the exported PDF? cutlist.d: Should images of the suggested cutting layouts for each material be included in the exported PDF?
cutlistNo.t: Do not include cutting layouts cutlistNo.t: Do not include cutting layouts
cutlistNo.d: Exported PDFs will not include any suggested cutting layouts cutlistNo.d: Exported PDFs will not include any suggested cutting layouts
cutlistYes.t: Include cutting layouts cutlistYes.t: Include cutting layouts
cutlistYes.d: Exported PDFs will include a page for each fabric used by the pattern listing how much of the fabric is needed and suggesting a layout for cutting out the appropriate pieces cutlistYes.d: Exported PDFs will include a page for each material used by the pattern listing how much of the material is needed and suggesting a layout for cutting out the appropriate pieces
coverPage.t: Include Cover Page coverPage.t: Include Cover Page
coverPage.d: Should the exported PDF include a cover page? coverPage.d: Should the exported PDF include a cover page?
coverPageYes.t: Include a cover page coverPageYes.t: Include a cover page

View file

@ -26,7 +26,7 @@ export const ns = ['print']
export const PrintSettings = ({ update, settings, ui, account }) => { export const PrintSettings = ({ update, settings, ui, account }) => {
const config = loadPrintConfig(settings.units) const config = loadPrintConfig(settings.units)
const passProps = { units: settings.units } const passProps = { units: settings.units }
const updateFunc = (name, newVal) => update.ui([...printSettingsPath, name], newVal) const updateFunc = (path, newVal) => update.ui([...printSettingsPath, ...path], newVal)
return ( return (
<WorkbenchMenu <WorkbenchMenu

View file

@ -17,6 +17,7 @@ bottomRight: Bottom Right
attributes: Attributes attributes: Attributes
showAllParts: Show all pattern parts showAllParts: Show all pattern parts
showOnlyThisPart: Show only this pattern part showOnlyThisPart: Show only this pattern part
showMovableButtons: Buttons
partInfo: Pattern part info partInfo: Pattern part info
pathInfo: Path info pathInfo: Path info
part: Pattern part part: Pattern part

View file

@ -53,10 +53,10 @@ export const pagesPlugin = ({ size = 'a4', ...settings }) => {
return basePlugin({ ...settings, sheetWidth, sheetHeight }) return basePlugin({ ...settings, sheetWidth, sheetHeight })
} }
export const fabricPlugin = (settings) => { export const materialPlugin = (settings) => {
return basePlugin({ return basePlugin({
...settings, ...settings,
partName: 'fabric', partName: 'material',
responsiveColumns: false, responsiveColumns: false,
}) })
} }

View file

@ -54,7 +54,7 @@ export const formatFraction128 = (fraction, format = 'html') => {
rest = fraction - inches rest = fraction - inches
} }
let fraction128 = Math.round(rest * 128) let fraction128 = Math.round(rest * 128)
if (fraction128 == 0) return formatImperial(negative, 0, false, false, format) if (fraction128 == 0) return formatImperial(negative, inches, false, false, format)
for (let i = 1; i < 7; i++) { for (let i = 1; i < 7; i++) {
const numoFactor = Math.pow(2, 7 - i) const numoFactor = Math.pow(2, 7 - i)