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]}
|
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 }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -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'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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: {
|
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',
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,115 +70,38 @@ 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
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** update the layout either locally or in the gist */
|
/** 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 */
|
/** don't mess with what we don't lay out */
|
||||||
if (!stackRef.current || !movable) return
|
if (!stackRef.current || !movable) return
|
||||||
|
|
||||||
|
@ -195,42 +116,126 @@ export const MovableStack = ({
|
||||||
stackName,
|
stackName,
|
||||||
{
|
{
|
||||||
move: {
|
move: {
|
||||||
x: translateX,
|
x: liveTransforms.translateX,
|
||||||
y: translateY,
|
y: liveTransforms.translateY,
|
||||||
},
|
},
|
||||||
rotate: rotation % 360,
|
rotate: liveTransforms.rotation % 360,
|
||||||
flipX,
|
flipX: liveTransforms.flipX,
|
||||||
flipY,
|
flipY: liveTransforms.flipY,
|
||||||
tl,
|
tl,
|
||||||
br,
|
br,
|
||||||
},
|
},
|
||||||
history
|
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 */
|
/** 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}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
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 { 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>
|
||||||
|
|
|
@ -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
|
||||||
{...{
|
{...{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue