cutting layout view
This commit is contained in:
parent
62638902e8
commit
15c4201906
31 changed files with 585 additions and 741 deletions
|
@ -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
|
|
@ -13,7 +13,7 @@ export const PartInner = forwardRef(
|
|||
path={part.paths[pathName]}
|
||||
topLeft={part.topLeft}
|
||||
bottomRight={part.bottomRight}
|
||||
units={settings.units}
|
||||
units={settings[0].units}
|
||||
{...{ stackName, partName, pathName, part, settings, components, t }}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -2,11 +2,11 @@ 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 'shared/plugins/plugin-layout-part.mjs'
|
||||
import { pagesPlugin, materialPlugin } from 'shared/plugins/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 { 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,
|
||||
|
@ -14,7 +14,7 @@ import {
|
|||
} from 'shared/components/workbench/views/print/config.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
export const ns = ['plugin', 'common']
|
||||
export const ns = ['cut', 'plugin', 'common']
|
||||
export const exportTypes = {
|
||||
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'],
|
||||
exportForEditing: ['svg', 'pdf'],
|
||||
|
@ -48,39 +48,39 @@ const themedPattern = (Design, settings, overwrite, format, t) => {
|
|||
* @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, settings, format, t, ui) => {
|
||||
// get the fabrics from the already drafted base pattern
|
||||
const fabrics = pattern.setStores[pattern.activeSet].cutlist.getCutFabrics(
|
||||
// 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
|
||||
) || ['material']
|
||||
if (!materials.length) return
|
||||
|
||||
const isImperial = settings.units === 'imperial'
|
||||
const cutLayouts = {}
|
||||
// each fabric
|
||||
fabrics.forEach((f) => {
|
||||
// get the settings and layout for that fabric
|
||||
const fabricSettings = fabricSettingsOrDefault(settings.units, ui, f)
|
||||
const fabricLayout = get(ui, ['layouts', 'cut', 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, settings, { 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, settings.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 },
|
||||
}),
|
||||
}
|
||||
|
@ -160,7 +160,6 @@ export const handleExport = async ({
|
|||
}
|
||||
|
||||
try {
|
||||
throw new Error('bad bad bad')
|
||||
// add pages to pdf exports
|
||||
if (format !== 'svg') {
|
||||
pattern.use(
|
||||
|
@ -177,7 +176,7 @@ export const handleExport = async ({
|
|||
design: capitalize(design),
|
||||
tagline: t('common:sloganCome') + '. ' + t('common:sloganStay'),
|
||||
url: window.location.href,
|
||||
cuttingLayout: t('plugin:cuttingLayout'),
|
||||
cuttingLayout: t('cut:cuttingLayout'),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -170,8 +170,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 +180,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.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)
|
||||
}
|
||||
|
|
|
@ -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 { 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 { 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 = {
|
||||
renderer: 'react',
|
||||
|
@ -25,6 +26,7 @@ const defaultUi = {
|
|||
const views = {
|
||||
draft: DraftView,
|
||||
print: PrintView,
|
||||
cut: CutView,
|
||||
}
|
||||
|
||||
const draftViews = ['draft', 'test']
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -71,7 +71,7 @@ export const loadSettingsConfig = ({
|
|||
units: {
|
||||
control: 1, // Show when control > 2
|
||||
list: ['metric', 'imperial'],
|
||||
dflt: units,
|
||||
dflt: 'metric',
|
||||
choiceTitles: {
|
||||
metric: 'metric',
|
||||
imperial: 'imperial',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useContext } from 'react'
|
||||
import { MenuItemGroup } from './menu-item.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { HelpIcon } from 'shared/components/icons.mjs'
|
||||
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
|
||||
|
|
|
@ -85,9 +85,7 @@ export const ListToggle = ({ config, changed, updateFunc, name }) => {
|
|||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
className={`toggle toggle-sm ${
|
||||
checked && !boolConfig.dflt ? 'toggle-accent' : 'toggle-secondary'
|
||||
}`}
|
||||
className={`toggle toggle-sm ${changed ? 'toggle-accent' : 'toggle-secondary'}`}
|
||||
checked={checked}
|
||||
onChange={doToggle}
|
||||
/>
|
||||
|
@ -276,4 +274,25 @@ export const MmInput = (props) => {
|
|||
}
|
||||
|
||||
/** 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)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -119,7 +119,7 @@ export const MenuItem = ({
|
|||
className={open ? openButtonClass : 'btn btn-accent'}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation()
|
||||
updateFunc(name)
|
||||
updateFunc([name])
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
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 { MovableStack } from './stack.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
export const MovablePattern = ({
|
||||
design,
|
||||
pattern,
|
||||
renderProps,
|
||||
patternConfig,
|
||||
settings,
|
||||
showButtons = true,
|
||||
update,
|
||||
bgProps = {},
|
||||
fitImmovable = false,
|
||||
immovable = [],
|
||||
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 = {}
|
||||
Object.keys(renderProps.stacks)
|
||||
.sort((a, b) => {
|
||||
|
@ -90,13 +77,13 @@ export const MovablePattern = ({
|
|||
{...{
|
||||
stackName,
|
||||
stack,
|
||||
settings,
|
||||
components,
|
||||
t,
|
||||
movable: !immovable.includes(stackName),
|
||||
layout: layout.stacks[stackName],
|
||||
updateLayout,
|
||||
showButtons,
|
||||
settings,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -43,25 +43,23 @@
|
|||
* 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 { useRef, useState, useEffect, useCallback } from 'react'
|
||||
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 { drag } from 'd3-drag'
|
||||
import { select } from 'd3-selection'
|
||||
import { Buttons } from './transform-buttons.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
export const MovableStack = ({
|
||||
stackName,
|
||||
stack,
|
||||
settings,
|
||||
components,
|
||||
t,
|
||||
movable = true,
|
||||
layout,
|
||||
updateLayout,
|
||||
showButtons,
|
||||
settings,
|
||||
}) => {
|
||||
const stackExists = !movable || typeof layout?.move?.x !== 'undefined'
|
||||
|
||||
|
@ -72,115 +70,38 @@ export const MovableStack = ({
|
|||
// 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 && 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
|
||||
// This is kept as state to avoid re-rendering on drag, which would kill performance
|
||||
// It's a bit of an anti-pattern, but we'll directly manipulate the properties instead of updating the state
|
||||
// Managing the difference between re-render and direct DOM updates makes this
|
||||
// whole thing a bit tricky to wrap your head around
|
||||
let translateX = layout.move.x
|
||||
let translateY = layout.move.y
|
||||
let stackRotation = layout.rotate || 0
|
||||
let rotation = stackRotation
|
||||
let flipX = !!layout.flipX
|
||||
let flipY = !!layout.flipY
|
||||
const stackRotation = layout?.rotate || 0
|
||||
const [liveTransforms] = useState({
|
||||
translateX: layout?.move.x,
|
||||
translateY: layout?.move.y,
|
||||
rotation: stackRotation,
|
||||
flipX: !!layout?.flipX,
|
||||
flipY: !!layout?.flipY,
|
||||
})
|
||||
|
||||
const center = {
|
||||
const center = stack.topLeft && {
|
||||
x: stack.topLeft.x + (stack.bottomRight.x - stack.topLeft.x) / 2,
|
||||
y: stack.topLeft.y + (stack.bottomRight.y - stack.topLeft.y) / 2,
|
||||
}
|
||||
|
||||
/** 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 = () => {
|
||||
const setTransforms = useCallback(() => {
|
||||
// get the transform attributes
|
||||
const { translateX, translateY, rotation, flipX, flipY } = liveTransforms
|
||||
const transforms = generateStackTransform(translateX, translateY, rotation, flipX, flipY, stack)
|
||||
|
||||
const me = select(stackRef.current)
|
||||
me.attr('transform', transforms.join(' '))
|
||||
|
||||
return transforms
|
||||
}
|
||||
|
||||
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) updateStacklayout()
|
||||
|
||||
didDrag = false
|
||||
})
|
||||
|
||||
/** reset the part's transforms */
|
||||
const resetPart = () => {
|
||||
rotation = 0
|
||||
flipX = 0
|
||||
flipY = 0
|
||||
updateStacklayout()
|
||||
}
|
||||
|
||||
/** toggle between dragging and rotating */
|
||||
const toggleDragRotate = () => {
|
||||
// only respond if the part should be able to drag/rotate
|
||||
if (!stackRef.current || !movable) {
|
||||
return
|
||||
}
|
||||
|
||||
setRotate(!rotate)
|
||||
}
|
||||
}, [liveTransforms, stackRef, stack])
|
||||
|
||||
/** update the layout either locally or in the gist */
|
||||
const updateStacklayout = (history = true) => {
|
||||
const updateStacklayout = useCallback(
|
||||
(history = true) => {
|
||||
/** don't mess with what we don't lay out */
|
||||
if (!stackRef.current || !movable) return
|
||||
|
||||
|
@ -195,42 +116,126 @@ export const MovableStack = ({
|
|||
stackName,
|
||||
{
|
||||
move: {
|
||||
x: translateX,
|
||||
y: translateY,
|
||||
x: liveTransforms.translateX,
|
||||
y: liveTransforms.translateY,
|
||||
},
|
||||
rotate: rotation % 360,
|
||||
flipX,
|
||||
flipY,
|
||||
rotate: liveTransforms.rotation % 360,
|
||||
flipX: liveTransforms.flipX,
|
||||
flipY: liveTransforms.flipY,
|
||||
tl,
|
||||
br,
|
||||
},
|
||||
history
|
||||
)
|
||||
},
|
||||
[stackRef, setTransforms, updateLayout, liveTransforms, movable, stack, stackName]
|
||||
)
|
||||
|
||||
// update the layout on mount
|
||||
useEffect(() => {
|
||||
// only update if there's a rendered part and it's not an imovable part
|
||||
if (stackRef.current && movable) {
|
||||
updateStacklayout(false)
|
||||
}
|
||||
}, [stackRef, movable, updateStacklayout])
|
||||
|
||||
/** reset the part's transforms */
|
||||
const resetPart = () => {
|
||||
liveTransforms.rotation = 0
|
||||
liveTransforms.flipX = 0
|
||||
liveTransforms.flipY = 0
|
||||
updateStacklayout()
|
||||
}
|
||||
|
||||
/** toggle between dragging and rotating */
|
||||
const toggleDragRotate = () => {
|
||||
// only respond if the part should be able to drag/rotate
|
||||
if (!stackRef.current || !movable) {
|
||||
return
|
||||
}
|
||||
|
||||
setRotate(!rotate)
|
||||
}
|
||||
|
||||
/** Method to flip (mirror) the part along the X or Y axis */
|
||||
const flip = (axis) => {
|
||||
if (axis === 'x') flipX = !flipX
|
||||
else flipY = !flipY
|
||||
if (axis === 'x') liveTransforms.flipX = !liveTransforms.flipX
|
||||
else liveTransforms.flipY = !liveTransforms.flipY
|
||||
updateStacklayout()
|
||||
}
|
||||
|
||||
/** method to rotate 90 degrees */
|
||||
const rotate90 = (direction = 1) => {
|
||||
if (flipX) direction *= -1
|
||||
if (flipY) direction *= -1
|
||||
if (liveTransforms.flipX) direction *= -1
|
||||
if (liveTransforms.flipY) direction *= -1
|
||||
|
||||
rotation += 90 * direction
|
||||
liveTransforms.rotation += 90 * direction
|
||||
|
||||
updateStacklayout()
|
||||
}
|
||||
|
||||
/** get the delta rotation from the start of the drag event to now */
|
||||
const getRotation = (event) =>
|
||||
angle(center, event.subject) - angle(center, { x: event.x, y: event.y })
|
||||
|
||||
let didDrag = false
|
||||
const handleDrag =
|
||||
movable &&
|
||||
drag()
|
||||
// subject allows us to save data from the start of the event to use throughout event handing
|
||||
.subject(function (event) {
|
||||
return rotate
|
||||
? // if we're rotating, the subject is the mouse position
|
||||
{ x: event.x, y: event.y }
|
||||
: // if we're moving, the subject is the part's x,y coordinates
|
||||
{ x: liveTransforms.translateX, y: liveTransforms.translateY }
|
||||
})
|
||||
.on('drag', function (event) {
|
||||
if (!event.dx && !event.dy) return
|
||||
|
||||
if (rotate) {
|
||||
let newRotation = getRotation(event)
|
||||
// shift key to snap the rotation
|
||||
if (event.sourceEvent.shiftKey) {
|
||||
newRotation = Math.ceil(newRotation / 15) * 15
|
||||
}
|
||||
// reverse the rotation direction one time per flip. if we're flipped both directions, rotation will be positive again
|
||||
if (liveTransforms.flipX) newRotation *= -1
|
||||
if (liveTransforms.flipY) newRotation *= -1
|
||||
|
||||
liveTransforms.rotation = stackRotation + newRotation
|
||||
} else {
|
||||
liveTransforms.translateX = event.x
|
||||
liveTransforms.translateY = event.y
|
||||
}
|
||||
|
||||
// a drag happened, so we should update the layout when we're done
|
||||
didDrag = true
|
||||
setTransforms()
|
||||
})
|
||||
.on('end', function () {
|
||||
// save to gist if anything actually changed
|
||||
if (didDrag) updateStacklayout()
|
||||
|
||||
didDrag = false
|
||||
})
|
||||
|
||||
// Initialize drag handler
|
||||
useEffect(() => {
|
||||
// don't drag the pages
|
||||
if (!movable || !stackExists) return
|
||||
handleDrag(select(stackRef.current))
|
||||
}, [stackRef, movable, stackExists, handleDrag])
|
||||
|
||||
// // Don't just assume this makes sense
|
||||
if (!stackExists) return null
|
||||
|
||||
const { Group, Part } = components
|
||||
return (
|
||||
<Group id={`stack-${stackName}`} {...getProps(stack)} ref={stackRef}>
|
||||
<Group id={`stack-inner-${stackName}`} ref={innerRef}>
|
||||
{[...stack.parts].map((part, key) => (
|
||||
<Part {...{ settings, components, t, part, stackName, key }} />
|
||||
<Part {...{ components, t, part, stackName, settings }} key={key} />
|
||||
))}
|
||||
</Group>
|
||||
{movable && (
|
||||
|
@ -246,9 +251,11 @@ export const MovableStack = ({
|
|||
/>
|
||||
{showButtons ? (
|
||||
<Buttons
|
||||
transform={`translate(${center.x}, ${center.y}) rotate(${-rotation}) scale(${
|
||||
flipX ? -1 : 1
|
||||
},${flipY ? -1 : 1})`}
|
||||
transform={`translate(${center.x}, ${
|
||||
center.y
|
||||
}) rotate(${-liveTransforms.rotation}) scale(${liveTransforms.flipX ? -1 : 1},${
|
||||
liveTransforms.flipY ? -1 : 1
|
||||
})`}
|
||||
flip={flip}
|
||||
rotate={rotate}
|
||||
setRotate={setRotate}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
import get from 'lodash.get'
|
||||
|
||||
const Triangle = ({ transform = 'translate(0,0)', fill = 'currentColor' }) => (
|
||||
<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 path = ['_state', 'layout', layoutSetType, 'showButtons']
|
||||
const showButtons = get(gist, path, true)
|
||||
const setShowButtons = () => updateGist(path, !showButtons)
|
||||
|
||||
const hideButtons = (evt) => {
|
||||
update.ui('hideMovableButtons', !evt.target.checked)
|
||||
}
|
||||
return (
|
||||
<label htmlFor="showButtons" className="label">
|
||||
<span className="mr-2">{t('showButtons')}</span>
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
checked={showButtons}
|
||||
onChange={setShowButtons}
|
||||
checked={!ui.hideMovableButtons}
|
||||
onChange={hideButtons}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
|
|
26
sites/shared/components/workbench/views/cut/cut.en.yaml
Normal file
26
sites/shared/components/workbench/views/cut/cut.en.yaml
Normal 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
|
80
sites/shared/components/workbench/views/cut/hooks.mjs
Normal file
80
sites/shared/components/workbench/views/cut/hooks.mjs
Normal 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])
|
||||
}
|
121
sites/shared/components/workbench/views/cut/index.mjs
Normal file
121
sites/shared/components/workbench/views/cut/index.mjs
Normal 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>
|
||||
)
|
||||
}
|
64
sites/shared/components/workbench/views/cut/menu.mjs
Normal file
64
sites/shared/components/workbench/views/cut/menu.mjs
Normal 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>
|
||||
)
|
||||
}
|
70
sites/shared/components/workbench/views/cut/settings.mjs
Normal file
70
sites/shared/components/workbench/views/cut/settings.mjs
Normal 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,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -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 { ClearIcon, ExportIcon } from 'shared/components/icons.mjs'
|
||||
const handlers = {
|
||||
showMovableButtons: (update) => (_path, newVal) => update.ui('showMovableButtons', newVal),
|
||||
}
|
||||
import { ShowButtonsToggle } from 'shared/components/workbench/pattern/movable/transform-buttons.mjs'
|
||||
|
||||
export const ns = ['workbench', 'print']
|
||||
|
||||
const config = {
|
||||
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 }) => {
|
||||
export const PrintActions = ({ update, ui, exportIt }) => {
|
||||
// get translation for the menu
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
const hideButtons = (evt) => {
|
||||
update.ui('hideMovableButtons', !evt.target.checked)
|
||||
}
|
||||
|
||||
const resetLayout = () => update.ui(['layouts', 'print'])
|
||||
|
||||
return (
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex justify-evenly flex-col lg:flex-row">
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
checked={!ui.hideMovableButtons}
|
||||
onChange={hideButtons}
|
||||
/>
|
||||
</label>
|
||||
<ShowButtonsToggle update={update} ui={ui} />
|
||||
<button className="btn btn-primary btn-outline" onClick={resetLayout}>
|
||||
<ClearIcon />
|
||||
<span className="ml-2">{t('reset')}</span>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState, useContext } from 'react'
|
||||
import { useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs'
|
||||
import {
|
||||
|
@ -9,18 +9,36 @@ import get from 'lodash.get'
|
|||
import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs'
|
||||
import { PrintMenu, ns as menuNs } from './menu.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 { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
|
||||
const viewNs = ['print', ...exportNs]
|
||||
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 = ({
|
||||
design,
|
||||
pattern,
|
||||
patternConfig,
|
||||
setView,
|
||||
settings,
|
||||
ui,
|
||||
update,
|
||||
|
@ -50,7 +68,6 @@ export const PrintView = ({
|
|||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
const bgProps = { fill: 'none' }
|
||||
|
||||
const exportIt = () => {
|
||||
handleExport({
|
||||
|
@ -67,29 +84,15 @@ export const PrintView = ({
|
|||
})
|
||||
}
|
||||
|
||||
let name = design
|
||||
const pages = pattern.setStores[0].get('pages', {})
|
||||
const { cols, rows, count } = pages
|
||||
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">
|
||||
<div className="flex justify-between items-baseline">
|
||||
<h2 className="capitalize">
|
||||
{t('layoutThing', { thing: name }) + ' ' + t('forPrinting')}
|
||||
{t('layoutThing', { thing: design }) + ' ' + t('forPrinting')}
|
||||
</h2>
|
||||
<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>
|
||||
<PageCounter pattern={pattern} />
|
||||
</div>
|
||||
<MovablePattern
|
||||
{...{
|
||||
|
|
|
@ -13,7 +13,6 @@ export const ns = [...coreMenuNs, ...designMenuNs, ...printMenuNs]
|
|||
|
||||
export const PrintMenu = ({
|
||||
design,
|
||||
pattern,
|
||||
patternConfig,
|
||||
settings,
|
||||
ui,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
showMovableButtons: Buttons
|
||||
printSettings: Print Settings
|
||||
printSettings.d: These settings control aspects of the page for PDF export and printing
|
||||
size.t: Paper Size
|
||||
|
@ -20,11 +19,11 @@ legal.d: 8.5 x 14 in
|
|||
tabloid.t: Tabloid
|
||||
tabloid.d: 11 x 17 in
|
||||
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.d: Exported PDFs will not include any suggested 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.d: Should the exported PDF include a cover page?
|
||||
coverPageYes.t: Include a cover page
|
||||
|
|
|
@ -26,7 +26,7 @@ export const ns = ['print']
|
|||
export const PrintSettings = ({ update, settings, ui, account }) => {
|
||||
const config = loadPrintConfig(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 (
|
||||
<WorkbenchMenu
|
||||
|
|
|
@ -17,6 +17,7 @@ bottomRight: Bottom Right
|
|||
attributes: Attributes
|
||||
showAllParts: Show all pattern parts
|
||||
showOnlyThisPart: Show only this pattern part
|
||||
showMovableButtons: Buttons
|
||||
partInfo: Pattern part info
|
||||
pathInfo: Path info
|
||||
part: Pattern part
|
||||
|
|
|
@ -53,10 +53,10 @@ export const pagesPlugin = ({ size = 'a4', ...settings }) => {
|
|||
return basePlugin({ ...settings, sheetWidth, sheetHeight })
|
||||
}
|
||||
|
||||
export const fabricPlugin = (settings) => {
|
||||
export const materialPlugin = (settings) => {
|
||||
return basePlugin({
|
||||
...settings,
|
||||
partName: 'fabric',
|
||||
partName: 'material',
|
||||
responsiveColumns: false,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export const formatFraction128 = (fraction, format = 'html') => {
|
|||
rest = fraction - inches
|
||||
}
|
||||
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++) {
|
||||
const numoFactor = Math.pow(2, 7 - i)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue