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