wip: Work on layout view in editor
This commit is contained in:
parent
752dd69c50
commit
b58535b972
13 changed files with 638 additions and 172 deletions
|
@ -1,7 +1,7 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { missingMeasurements, flattenFlags } from '../lib/index.mjs'
|
import { missingMeasurements, flattenFlags } from '../lib/index.mjs'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
|
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
|
||||||
// Components
|
// Components
|
||||||
|
@ -21,7 +21,9 @@ import {
|
||||||
MenuIcon,
|
MenuIcon,
|
||||||
OptionsIcon,
|
OptionsIcon,
|
||||||
PaperlessIcon,
|
PaperlessIcon,
|
||||||
|
PrintIcon,
|
||||||
ResetAllIcon,
|
ResetAllIcon,
|
||||||
|
ResetIcon,
|
||||||
RightIcon,
|
RightIcon,
|
||||||
RocketIcon,
|
RocketIcon,
|
||||||
RotateIcon,
|
RotateIcon,
|
||||||
|
@ -38,6 +40,7 @@ import { ButtonFrame } from '@freesewing/react/components/Input'
|
||||||
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
||||||
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
||||||
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
|
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
|
||||||
|
import { LayoutSettingsMenu } from './menus/LayoutMenu.mjs'
|
||||||
import { FlagsAccordionEntries } from './Flag.mjs'
|
import { FlagsAccordionEntries } from './Flag.mjs'
|
||||||
import { UndoStep } from './views/UndosView.mjs'
|
import { UndoStep } from './views/UndosView.mjs'
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ const headerMenuIcons = {
|
||||||
right: RightIcon,
|
right: RightIcon,
|
||||||
settings: SettingsIcon,
|
settings: SettingsIcon,
|
||||||
ui: UiIcon,
|
ui: UiIcon,
|
||||||
|
layout: PrintIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuIcon = (props) => {
|
export const HeaderMenuIcon = (props) => {
|
||||||
|
@ -508,8 +512,106 @@ export const HeaderMenuViewMenu = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HeaderMenuLayoutView = (props) => (
|
||||||
|
<>
|
||||||
|
<HeaderMenuDropdown
|
||||||
|
{...props}
|
||||||
|
id="layoutOptions"
|
||||||
|
tooltip="These options are specific to this design. You can use them to customize your pattern in a variety of ways."
|
||||||
|
toggle={
|
||||||
|
<>
|
||||||
|
<HeaderMenuIcon name="layout" extraClasses="tw-text-secondary" />
|
||||||
|
<span className="tw-hidden lg:tw-inline">Print Settings</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<LayoutSettingsMenu {...props} />
|
||||||
|
</HeaderMenuDropdown>
|
||||||
|
<HeaderMenuLayoutViewIcons {...props} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const HeaderMenuLayoutViewIcons = (props) => {
|
||||||
|
const { pattern, update, state } = props
|
||||||
|
const [tweaks, setTweaks] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/*
|
||||||
|
* When the layout is reset, the UI won't update to changes
|
||||||
|
* unless we apply them on the first change
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
tweaks === 0 &&
|
||||||
|
typeof props.state.ui?.layout === 'object' &&
|
||||||
|
typeof props.state.settings?.layout !== 'object'
|
||||||
|
)
|
||||||
|
applyLayout()
|
||||||
|
setTweaks(tweaks + 1)
|
||||||
|
}, [props.state.ui.layout])
|
||||||
|
|
||||||
|
const applyLayout = () => {
|
||||||
|
setTweaks(-1)
|
||||||
|
update.settings('layout', state.ui.layout)
|
||||||
|
}
|
||||||
|
const resetLayout = () => {
|
||||||
|
setTweaks(-1)
|
||||||
|
update.ui('layout', true)
|
||||||
|
update.settings('layout', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = pattern.setStores[0].get('pages', {})
|
||||||
|
const format = state.ui.print?.pages?.size
|
||||||
|
? state.ui.print.pages.size
|
||||||
|
: state.settings?.units === 'imperial'
|
||||||
|
? 'letter'
|
||||||
|
: 'a4'
|
||||||
|
const { cols, rows, count } = pages
|
||||||
|
const blank = cols * rows - count
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip tip="Number of pages required for the current layout">
|
||||||
|
<span className="tw-px-1 tw-font-bold tw-text-sm tw-block tw-h-8 tw-py-1 tw-opacity-80">
|
||||||
|
<span className="">
|
||||||
|
{count} pages
|
||||||
|
<span className="tw-pl-1 tw-text-xs tw-font-medium">
|
||||||
|
({cols}x{rows}, {blank} blank)
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip tip="Apply this layout to the pattern">
|
||||||
|
<button
|
||||||
|
className="tw-daisy-btn tw-daisy-btn-ghost tw-daisy-btn-sm tw-px-1 disabled:tw-bg-transparent tw-text-secondary"
|
||||||
|
onClick={applyLayout}
|
||||||
|
disabled={tweaks === 0}
|
||||||
|
>
|
||||||
|
Apply Layout
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip tip="Generate a PDF that you can print">
|
||||||
|
<button
|
||||||
|
className="tw-daisy-btn tw-daisy-btn-ghost tw-daisy-btn-sm tw-px-1 disabled:tw-bg-transparent tw-text-secondary"
|
||||||
|
onClick={() => update.view('export')}
|
||||||
|
>
|
||||||
|
<PrintIcon />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip tip="Reset the custom layout">
|
||||||
|
<button
|
||||||
|
className="tw-daisy-btn tw-daisy-btn-ghost tw-daisy-btn-sm tw-px-1 disabled:tw-bg-transparent tw-text-secondary"
|
||||||
|
onClick={resetLayout}
|
||||||
|
>
|
||||||
|
<ResetIcon />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const headerMenus = {
|
const headerMenus = {
|
||||||
draft: HeaderMenuDraftView,
|
draft: HeaderMenuDraftView,
|
||||||
|
layout: HeaderMenuLayoutView,
|
||||||
//HeaderMenuDraftViewDesignOptions,
|
//HeaderMenuDraftViewDesignOptions,
|
||||||
//HeaderMenuDraftViewCoreSettings,
|
//HeaderMenuDraftViewCoreSettings,
|
||||||
//HeaderMenuDraftViewUiPreferences,
|
//HeaderMenuDraftViewUiPreferences,
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
import React, { useRef } from 'react'
|
import React, { useRef, useState, useEffect, useCallback } from 'react'
|
||||||
import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
|
import { ZoomablePattern } from './ZoomablePattern.mjs'
|
||||||
import { MovableStack } from './stack.mjs'
|
import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
|
||||||
|
import { getProps } from '@freesewing/react/components/Pattern'
|
||||||
|
import { FlipIcon, RotateIcon, ResetIcon } from '@freesewing/react/components/Icon'
|
||||||
|
import { drag } from 'd3-drag'
|
||||||
|
import { select } from 'd3-selection'
|
||||||
|
//import { Buttons } from './transform-buttons.mjs'
|
||||||
|
|
||||||
export const MovablePattern = ({
|
export const MovablePattern = ({
|
||||||
renderProps,
|
renderProps,
|
||||||
showButtons = true,
|
state,
|
||||||
update,
|
update,
|
||||||
fitImmovable = false,
|
fitImmovable = false,
|
||||||
immovable = [],
|
immovable = [],
|
||||||
layoutPath,
|
t,
|
||||||
}) => {
|
}) => {
|
||||||
const svgRef = useRef(null)
|
const svgRef = useRef(null)
|
||||||
if (!renderProps) return null
|
if (!renderProps) return null
|
||||||
|
|
||||||
// keep a fresh copy of the layout because we might manipulate it without saving to the gist
|
/* keep a fresh copy of the layout because we might manipulate it without
|
||||||
|
* update the state
|
||||||
|
*/
|
||||||
let layout =
|
let layout =
|
||||||
renderProps.settings[0].layout === true
|
renderProps.settings[0].layout === true
|
||||||
? {
|
? {
|
||||||
|
@ -52,9 +59,15 @@ export const MovablePattern = ({
|
||||||
newLayout.topLeft = topLeft
|
newLayout.topLeft = topLeft
|
||||||
|
|
||||||
if (history) {
|
if (history) {
|
||||||
update.ui(layoutPath, newLayout)
|
update.ui('layout', newLayout)
|
||||||
} else {
|
} else {
|
||||||
// we don't put it in the gist if it shouldn't contribute to history because we need some of the data calculated here for rendering purposes on the initial layout, but we don't want to actually save a layout until the user manipulates it. This is what allows the layout to respond appropriately to settings changes. Once the user has starting playing with the layout, all bets are off
|
/* we don't put it in the gist if it shouldn't contribute to history
|
||||||
|
* because we need some of the data calculated here for rendering
|
||||||
|
* purposes on the initial layout, but we don't want to actually save a
|
||||||
|
* layout until the user manipulates it. This is what allows the layout
|
||||||
|
* to respond appropriately to settings changes. Once the user has
|
||||||
|
* starting playing with the layout, all bets are off
|
||||||
|
*/
|
||||||
layout = newLayout
|
layout = newLayout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,20 +95,22 @@ export const MovablePattern = ({
|
||||||
movable: !immovable.includes(stackName),
|
movable: !immovable.includes(stackName),
|
||||||
layout: layout.stacks[stackName],
|
layout: layout.stacks[stackName],
|
||||||
updateLayout,
|
updateLayout,
|
||||||
showButtons,
|
|
||||||
settings,
|
settings,
|
||||||
|
state,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanZoomPattern
|
<div className="" style={{ height: 'calc(100vh - 12rem)' }}>
|
||||||
|
<ZoomablePattern
|
||||||
{...{
|
{...{
|
||||||
renderProps: sortedRenderProps,
|
renderProps: sortedRenderProps,
|
||||||
components: { Stack },
|
components: { Stack },
|
||||||
}}
|
}}
|
||||||
ref={svgRef}
|
ref={svgRef}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,13 +156,6 @@ export const MovablePattern = ({
|
||||||
* more data and factors in the transforms. We then use our `domToSvg`
|
* more data and factors in the transforms. We then use our `domToSvg`
|
||||||
* function to move the points back into the SVG space.
|
* function to move the points back into the SVG space.
|
||||||
*/
|
*/
|
||||||
//import { useRef, useState, useEffect, useCallback } from 'react'
|
|
||||||
//import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
|
|
||||||
//import { getProps } from 'pkgs/react-components/src/pattern/utils.mjs'
|
|
||||||
//import { angle } from '../utils.mjs'
|
|
||||||
//import { drag } from 'd3-drag'
|
|
||||||
//import { select } from 'd3-selection'
|
|
||||||
//import { Buttons } from './transform-buttons.mjs'
|
|
||||||
|
|
||||||
export const MovableStack = ({
|
export const MovableStack = ({
|
||||||
stackName,
|
stackName,
|
||||||
|
@ -157,8 +165,8 @@ export const MovableStack = ({
|
||||||
movable = true,
|
movable = true,
|
||||||
layout,
|
layout,
|
||||||
updateLayout,
|
updateLayout,
|
||||||
showButtons,
|
|
||||||
settings,
|
settings,
|
||||||
|
state,
|
||||||
}) => {
|
}) => {
|
||||||
const stackExists = !movable || typeof layout?.move?.x !== 'undefined'
|
const stackExists = !movable || typeof layout?.move?.x !== 'undefined'
|
||||||
|
|
||||||
|
@ -198,7 +206,7 @@ export const MovableStack = ({
|
||||||
return transforms
|
return transforms
|
||||||
}, [liveTransforms, stackRef, stack])
|
}, [liveTransforms, stackRef, stack])
|
||||||
|
|
||||||
/** update the layout either locally or in the gist */
|
/** update the layout either locally or in the state */
|
||||||
const updateStacklayout = useCallback(
|
const updateStacklayout = useCallback(
|
||||||
(history = true) => {
|
(history = true) => {
|
||||||
/** don't mess with what we don't lay out */
|
/** don't mess with what we don't lay out */
|
||||||
|
@ -327,9 +335,12 @@ export const MovableStack = ({
|
||||||
}, [stackRef, movable, stackExists, handleDrag])
|
}, [stackRef, movable, stackExists, handleDrag])
|
||||||
|
|
||||||
// // Don't just assume this makes sense
|
// // Don't just assume this makes sense
|
||||||
if (!stackExists) return null
|
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}>
|
||||||
|
@ -348,7 +359,6 @@ export const MovableStack = ({
|
||||||
id={`${stackName}-layout-rect`}
|
id={`${stackName}-layout-rect`}
|
||||||
onClick={toggleDragRotate}
|
onClick={toggleDragRotate}
|
||||||
/>
|
/>
|
||||||
{showButtons ? (
|
|
||||||
<Buttons
|
<Buttons
|
||||||
transform={`translate(${center.x}, ${
|
transform={`translate(${center.x}, ${
|
||||||
center.y
|
center.y
|
||||||
|
@ -361,10 +371,108 @@ export const MovableStack = ({
|
||||||
resetPart={resetPart}
|
resetPart={resetPart}
|
||||||
rotate90={rotate90}
|
rotate90={rotate90}
|
||||||
partName={stackName}
|
partName={stackName}
|
||||||
|
iconSize={state.ui?.layout?.iconSize}
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dx(pointA, pointB) {
|
||||||
|
return pointB.x - pointA.x
|
||||||
|
}
|
||||||
|
function dy(pointA, pointB) {
|
||||||
|
return pointB.y - pointA.y
|
||||||
|
}
|
||||||
|
function rad2deg(radians) {
|
||||||
|
return radians * 57.29577951308232
|
||||||
|
}
|
||||||
|
function angle(pointA, pointB) {
|
||||||
|
let rad = Math.atan2(-1 * dy(pointA, pointB), dx(pointA, pointB))
|
||||||
|
while (rad < 0) rad += 2 * Math.PI
|
||||||
|
|
||||||
|
return rad2deg(rad)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rectSize = 24
|
||||||
|
|
||||||
|
const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
|
||||||
|
const _onClick = (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
onClickCb(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g transform={transform} className="svg-layout-button group">
|
||||||
|
<title>{title}</title>
|
||||||
|
<rect width={rectSize} height={rectSize} className="button" rx="2" ry="2" />
|
||||||
|
<Icon className="group-hover:tw-text-primary-content" />
|
||||||
|
<rect width={rectSize} height={rectSize} onClick={_onClick} className="tw-fill-transparent" />
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowButtonsToggle = ({ ui, update }) => {
|
||||||
|
const hideButtons = (evt) => {
|
||||||
|
update.ui('hideMovableButtons', !evt.target.checked)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<label className="label cursor-pointer">
|
||||||
|
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="toggle toggle-primary"
|
||||||
|
checked={!ui.hideMovableButtons}
|
||||||
|
onChange={hideButtons}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** buttons for manipulating the part */
|
||||||
|
export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize }) => {
|
||||||
|
return (
|
||||||
|
<g transform={transform}>
|
||||||
|
<g style={{ transform: `scale(${iconSize || 0.5}` }}>
|
||||||
|
{rotate ? (
|
||||||
|
<circle cx="0" cy="0" r="50" className="stroke-2xl muted" />
|
||||||
|
) : (
|
||||||
|
<path d="M -50, 0 l 100,0 M 0,-50 l 0,100" className="stroke-2xl muted" />
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClickCb={resetPart}
|
||||||
|
transform={`translate(${rectSize / -2}, ${rectSize / -2})`}
|
||||||
|
Icon={() => <ResetIcon wrapped={0} />}
|
||||||
|
title="Reset part orientation"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClickCb={() => rotate90(-1)}
|
||||||
|
transform={`translate(${rectSize * -2.7}, ${rectSize / -2})`}
|
||||||
|
Icon={() => <RotateIcon wrapped={0} style={{}} />}
|
||||||
|
title="Rotate part clockwise"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClickCb={() => flip('y')}
|
||||||
|
transform={`translate(${rectSize * 0.6}, ${rectSize / -2})`}
|
||||||
|
Icon={() => (
|
||||||
|
<FlipIcon style={{ transform: 'rotate(90deg) translate(0, -24px)' }} wrapped={0} />
|
||||||
|
)}
|
||||||
|
title="Flip part top/bottom"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClickCb={() => flip('x')}
|
||||||
|
transform={`translate(${rectSize * -1.6}, ${rectSize / -2})`}
|
||||||
|
Icon={() => <FlipIcon style={{}} wrapped={0} />}
|
||||||
|
title="Flip part left/right"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClickCb={() => rotate90()}
|
||||||
|
transform={`translate(${rectSize * 1.7}, ${rectSize / -2})`}
|
||||||
|
Icon={() => <RotateIcon transform="scale(-1,1), translate(-24,0)" wrapped={0} />}
|
||||||
|
title="Rotate part counter-clockwise"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Pattern } from '@freesewing/react/components/Pattern'
|
||||||
* A pattern you can pan and zoom
|
* A pattern you can pan and zoom
|
||||||
*/
|
*/
|
||||||
export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref) {
|
export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref) {
|
||||||
const { renderProps, rotate } = props
|
const { renderProps, rotate, components = {} } = props
|
||||||
const { onTransformed, setZoomFunctions } = useContext(ZoomContext)
|
const { onTransformed, setZoomFunctions } = useContext(ZoomContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -26,7 +26,7 @@ export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref
|
||||||
>
|
>
|
||||||
{props.children || (
|
{props.children || (
|
||||||
<Pattern
|
<Pattern
|
||||||
{...{ renderProps }}
|
{...{ renderProps, components }}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={`freesewing pattern tw-w-full ${rotate ? 'tw--rotate-90' : ''}`}
|
className={`freesewing pattern tw-w-full ${rotate ? 'tw--rotate-90' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -192,6 +192,7 @@ export const MenuMmInput = (props) => {
|
||||||
const imperial = units === 'imperial'
|
const imperial = units === 'imperial'
|
||||||
const mmUpdateHandler = useCallback(
|
const mmUpdateHandler = useCallback(
|
||||||
(path, newCurrent) => {
|
(path, newCurrent) => {
|
||||||
|
console.log('mmUpdateHandler', { path, newCurrent })
|
||||||
const calcCurrent =
|
const calcCurrent =
|
||||||
typeof newCurrent === 'undefined' ? undefined : measurementAsMm(newCurrent, units)
|
typeof newCurrent === 'undefined' ? undefined : measurementAsMm(newCurrent, units)
|
||||||
updateHandler(path, calcCurrent)
|
updateHandler(path, calcCurrent)
|
||||||
|
@ -199,7 +200,9 @@ export const MenuMmInput = (props) => {
|
||||||
[updateHandler, units]
|
[updateHandler, units]
|
||||||
)
|
)
|
||||||
|
|
||||||
// add a default step that's appropriate to the unit. can be overwritten by config
|
/*
|
||||||
|
* Set a default step that matches the unit
|
||||||
|
*/
|
||||||
const defaultStep = units === 'imperial' ? 0.125 : 0.1
|
const defaultStep = units === 'imperial' ? 0.125 : 0.1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -209,8 +212,8 @@ export const MenuMmInput = (props) => {
|
||||||
config: {
|
config: {
|
||||||
step: defaultStep,
|
step: defaultStep,
|
||||||
...config,
|
...config,
|
||||||
min: imperial ? config.min / 25.4 : config.min,
|
min: imperial ? config.min / 25.4 : config.min / 10,
|
||||||
max: imperial ? config.max / 25.4 : config.min,
|
max: imperial ? config.max / 25.4 : config.max / 10,
|
||||||
dflt: measurementAsUnits(config.dflt, units),
|
dflt: measurementAsUnits(config.dflt, units),
|
||||||
},
|
},
|
||||||
current: current === undefined ? undefined : measurementAsUnits(current, units),
|
current: current === undefined ? undefined : measurementAsUnits(current, units),
|
||||||
|
@ -391,7 +394,7 @@ export const MenuEditOption = (props) => {
|
||||||
<div className="tw-daisy-form-control tw-mb-2 tw-w-full">
|
<div className="tw-daisy-form-control tw-mb-2 tw-w-full">
|
||||||
<div className="tw-daisy-label tw-font-medium tw-text-accent">
|
<div className="tw-daisy-label tw-font-medium tw-text-accent">
|
||||||
<label className="tw-daisy-label-text">
|
<label className="tw-daisy-label-text">
|
||||||
<em>Enter a custom value</em> {units}
|
<em>Enter a custom value</em>
|
||||||
</label>
|
</label>
|
||||||
{type === 'pct' && typeof config.fromAbs === 'function' ? (
|
{type === 'pct' && typeof config.fromAbs === 'function' ? (
|
||||||
<label className="tw-daisy-label-text">
|
<label className="tw-daisy-label-text">
|
||||||
|
|
134
packages/react/components/Editor/components/menus/LayoutMenu.mjs
Normal file
134
packages/react/components/Editor/components/menus/LayoutMenu.mjs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Dependencies
|
||||||
|
import React from 'react'
|
||||||
|
import { defaultPrintSettings, handleExport } from '../../lib/export/index.mjs'
|
||||||
|
import { tilerPlugin } from '../../lib/export/plugin-tiler.mjs'
|
||||||
|
import { capitalize, get } from '@freesewing/utils'
|
||||||
|
import { draft, menuLayoutSettingsStructure } from '../../lib/index.mjs'
|
||||||
|
// Components
|
||||||
|
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||||
|
import { PatternLayout } from '../PatternLayout.mjs'
|
||||||
|
import { MovablePattern } from '../MovablePattern.mjs'
|
||||||
|
import { Accordion } from '../Accordion.mjs'
|
||||||
|
import {
|
||||||
|
CompareIcon,
|
||||||
|
PageSizeIcon,
|
||||||
|
PatternIcon,
|
||||||
|
PrintIcon,
|
||||||
|
} from '@freesewing/react/components/Icon'
|
||||||
|
import { MenuBoolInput, MenuMmInput, MenuListInput, MenuPctInput } from '../menus/Input.mjs'
|
||||||
|
import { MenuBoolValue, MenuMmValue, MenuListValue, MenuPctOptionValue } from '../menus/Value.mjs'
|
||||||
|
import { MenuItem, MenuItemGroup } from './Container.mjs'
|
||||||
|
import { MenuHighlightValue } from './Value.mjs'
|
||||||
|
|
||||||
|
export const LayoutSettingsMenu = ({ update, state, Design }) => {
|
||||||
|
const structure = menuLayoutSettingsStructure()
|
||||||
|
|
||||||
|
const drillProps = { Design, state, update }
|
||||||
|
const inputs = {
|
||||||
|
size: (props) => <MenuListInput {...drillProps} {...props} />,
|
||||||
|
orientation: (props) => <MenuListInput {...drillProps} {...props} />,
|
||||||
|
margin: (props) => <MenuMmInput {...drillProps} {...props} />,
|
||||||
|
coverPage: (props) => <MenuBoolInput {...drillProps} {...props} />,
|
||||||
|
iconSize: (props) => <MenuPctInput {...drillProps} {...props} />,
|
||||||
|
}
|
||||||
|
const values = {
|
||||||
|
size: ({ current, changed, config }) => (
|
||||||
|
<MenuHighlightValue changed={changed}>
|
||||||
|
{capitalize(current ? current : config.dflt)}
|
||||||
|
</MenuHighlightValue>
|
||||||
|
),
|
||||||
|
orientation: ({ current, changed }) => (
|
||||||
|
<MenuHighlightValue changed={changed}>
|
||||||
|
<PatternIcon
|
||||||
|
className={`tw-w-6 tw-h-6 tw-text-inherit ${current === 'landscape' ? 'tw--rotate-90' : ''}`}
|
||||||
|
/>
|
||||||
|
</MenuHighlightValue>
|
||||||
|
),
|
||||||
|
margin: MenuMmValue,
|
||||||
|
coverPage: MenuBoolValue,
|
||||||
|
iconSize: MenuPctOptionValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItemGroup
|
||||||
|
{...{
|
||||||
|
structure,
|
||||||
|
ux: state.ui?.ux,
|
||||||
|
currentValues: state.ui.layout || {},
|
||||||
|
isFirst: true,
|
||||||
|
name: 'UI Preferences',
|
||||||
|
passProps: {
|
||||||
|
ux: state.ui?.ux,
|
||||||
|
settings: state.settings,
|
||||||
|
patternConfig: Design.patternConfig,
|
||||||
|
},
|
||||||
|
updateHandler: (key, val) => update.ui(['layout', key], val),
|
||||||
|
isDesignOptionsGroup: false,
|
||||||
|
state,
|
||||||
|
Design,
|
||||||
|
inputs,
|
||||||
|
values,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
const PrintActions = ({ state, update }) => (
|
||||||
|
<SubAccordion
|
||||||
|
items={[
|
||||||
|
[
|
||||||
|
<div className="tw-w-full tw-flex tw-flex-row tw-gap2 tw-justify-between" key={1}>
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">
|
||||||
|
<LeftRightIcon />
|
||||||
|
<span>{'workbench:partTransfo'}</span>
|
||||||
|
</div>
|
||||||
|
{state.ui.hideMovableButtons ? <BoolNoIcon /> : <BoolYesIcon />}
|
||||||
|
</div>,
|
||||||
|
<ListInput
|
||||||
|
key={2}
|
||||||
|
update={() => update.state.ui('hideMovableButtons', state.ui.hideMovableButtons ? false : true)}
|
||||||
|
label={
|
||||||
|
<span className="tw-text-base tw-font-normal">{'workbench:partTransfoDesc'}</span>
|
||||||
|
}
|
||||||
|
list={[
|
||||||
|
{
|
||||||
|
val: true,
|
||||||
|
label: 'workbench:partTransfoNo',
|
||||||
|
desc: 'workbench:partTransfoNoDesc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: false,
|
||||||
|
label: 'workbench:partTransfoYes',
|
||||||
|
desc: 'workbench:partTransfoYesDesc',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
current={state.ui.hideMovableButtons ? true : false}
|
||||||
|
/>,
|
||||||
|
'partTransfo',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
<div className="tw-w-full tw-flex tw-flex-row tw-gap2 tw-justify-between" key={1}>
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-2">
|
||||||
|
<ResetIcon />
|
||||||
|
<span>{'workbench:resetPrintLayout'}</span>
|
||||||
|
</div>
|
||||||
|
<WarningIcon />
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
<Fragment key={2}>
|
||||||
|
<p>{'workbench:resetPrintLayoutDesc'}</p>
|
||||||
|
<button
|
||||||
|
className={`${horFlexClasses} tw-btn tw-btn-warning tw-btn-outline tw-w-full`}
|
||||||
|
onClick={() => update.ui(['layouts', 'print'])}
|
||||||
|
>
|
||||||
|
<ResetIcon />
|
||||||
|
<span>{'workbench:resetPrintLayout'}</span>
|
||||||
|
</button>
|
||||||
|
</Fragment>,
|
||||||
|
'reset',
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
*/
|
|
@ -13,15 +13,11 @@ export const UiPreferencesMenu = ({ update, state, Design }) => {
|
||||||
const drillProps = { Design, state, update }
|
const drillProps = { Design, state, update }
|
||||||
const inputs = {
|
const inputs = {
|
||||||
ux: (props) => <MenuUxSettingInput {...drillProps} {...props} />,
|
ux: (props) => <MenuUxSettingInput {...drillProps} {...props} />,
|
||||||
//aside: (props) => <MenuListInput {...drillProps} {...props} />,
|
|
||||||
//kiosk: (props) => <MenuListInput {...drillProps} {...props} />,
|
|
||||||
rotate: (props) => <MenuListInput {...drillProps} {...props} />,
|
rotate: (props) => <MenuListInput {...drillProps} {...props} />,
|
||||||
renderer: (props) => <MenuListInput {...drillProps} {...props} />,
|
renderer: (props) => <MenuListInput {...drillProps} {...props} />,
|
||||||
}
|
}
|
||||||
const values = {
|
const values = {
|
||||||
ux: (props) => <span>{state.ui.ux}/5</span>,
|
ux: (props) => <span>{state.ui.ux}/5</span>,
|
||||||
//aside: MenuListValue,
|
|
||||||
//kiosk: MenuListValue,
|
|
||||||
rotate: MenuListValue,
|
rotate: MenuListValue,
|
||||||
renderer: MenuListValue,
|
renderer: MenuListValue,
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,9 +151,10 @@ export const ExportView = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportPattern(props) {
|
export function exportPattern(props) {
|
||||||
props.setLink(false)
|
if (props.setLink) props.setLink(false)
|
||||||
props.setFormat(props.format)
|
if (props.setFormat) props.setFormat(props.format)
|
||||||
|
|
||||||
handleExport({
|
handleExport({
|
||||||
...props,
|
...props,
|
||||||
onComplete: (e) => (e.data?.link ? props.setLink(e.data.link) : null),
|
onComplete: (e) => (e.data?.link ? props.setLink(e.data.link) : null),
|
||||||
|
|
|
@ -1,142 +1,45 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { defaultPrintSettings, printSettingsPath, handleExport } from '../../lib/export/index.mjs'
|
|
||||||
import { tilerPlugin } from '../../lib/export/plugin-tiler.mjs'
|
|
||||||
import { get } from '@freesewing/utils'
|
|
||||||
import { draft } from '../../lib/index.mjs'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
//import {
|
import { defaultPrintSettings, handleExport } from '../../lib/export/index.mjs'
|
||||||
// handleExport,
|
import { tilerPlugin } from '../../lib/export/plugin-tiler.mjs'
|
||||||
// ns as exportNs,
|
import { capitalize, get } from '@freesewing/utils'
|
||||||
//} from 'shared/components/workbench/exporting/export-handler.mjs'
|
import { draft } from '../../lib/index.mjs'
|
||||||
//import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs'
|
|
||||||
//import get from 'lodash.get'
|
|
||||||
//import { defaultPrintSettings, printSettingsPath } from './config.mjs'
|
|
||||||
//// Hooks
|
|
||||||
//import { useContext } from 'react'
|
|
||||||
//import { useTranslation } from 'next-i18next'
|
|
||||||
//// Context
|
|
||||||
//import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs'
|
|
||||||
// Components
|
// Components
|
||||||
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||||
import { PatternLayout } from '../PatternLayout.mjs'
|
import { PatternLayout } from '../PatternLayout.mjs'
|
||||||
//import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs'
|
import { MovablePattern } from '../MovablePattern.mjs'
|
||||||
//import { PrintMenu, ns as menuNs } from './menu.mjs'
|
import { Accordion } from '../Accordion.mjs'
|
||||||
//import { PatternWithMenu, ns as wrapperNs } from '../pattern-with-menu.mjs'
|
import { CompareIcon, PrintIcon } from '@freesewing/react/components/Icon'
|
||||||
|
import { MenuBoolInput, MenuMmInput, MenuListInput } from '../menus/Input.mjs'
|
||||||
|
import { MenuBoolValue, MenuMmValue, MenuListValue } from '../menus/Value.mjs'
|
||||||
|
|
||||||
export const LayoutView = (props) => {
|
export const LayoutView = (props) => {
|
||||||
// design,
|
|
||||||
// pattern,
|
|
||||||
// patternConfig,
|
|
||||||
// settings,
|
|
||||||
// setSettings,
|
|
||||||
// ui,
|
|
||||||
// update,
|
|
||||||
// language,
|
|
||||||
// account,
|
|
||||||
// Design,
|
|
||||||
//}) => {
|
|
||||||
|
|
||||||
const { config, state, update, Design } = props
|
const { config, state, update, Design } = props
|
||||||
const defaultSettings = defaultPrintSettings(state.settings?.units)
|
const { ui, settings } = state
|
||||||
|
const defaultSettings = defaultPrintSettings(settings?.units)
|
||||||
|
|
||||||
// Settings for the tiler plugin
|
// Settings for the tiler plugin
|
||||||
const pageSettings = {
|
const pageSettings = {
|
||||||
...defaultSettings,
|
...defaultSettings,
|
||||||
...get(state.ui, printSettingsPath, {}),
|
...get(state.ui, 'layout', {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now draft the pattern
|
* Now draft the pattern
|
||||||
*/
|
*/
|
||||||
const { pattern, failure, errors } = draft(Design, state.settings, [tilerPlugin(pageSettings)])
|
const { pattern, failure, errors } = draft(Design, settings, [tilerPlugin(pageSettings)])
|
||||||
if (failure) return <p>Draft failed. FIXME: Handle this gracefully.</p>
|
if (failure) return <p>Draft failed. FIXME: Handle this gracefully.</p>
|
||||||
|
|
||||||
const renderProps = pattern.getRenderProps()
|
|
||||||
|
|
||||||
const exportIt = () => {
|
|
||||||
update.startLoading('layout', { msg: 'Generating PDF' })
|
|
||||||
handleExport({
|
|
||||||
format: pageSettings.size,
|
|
||||||
settings,
|
|
||||||
design,
|
|
||||||
t,
|
|
||||||
Design,
|
|
||||||
ui,
|
|
||||||
startLoading: loading.startLoading,
|
|
||||||
stopLoading: loading.stopLoading,
|
|
||||||
onComplete: () => {
|
|
||||||
setLoadingStatus([true, 'pdfReady', true, true])
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
setLoadingStatus([true, 'pdfFailed', true, true])
|
|
||||||
console.log(err)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = (
|
const output = (
|
||||||
<MovablePattern
|
<MovablePattern
|
||||||
{...{
|
{...{
|
||||||
renderProps,
|
|
||||||
update,
|
update,
|
||||||
|
renderProps: pattern.getRenderProps(),
|
||||||
immovable: ['pages'],
|
immovable: ['pages'],
|
||||||
layoutPath: ['layouts', 'print'],
|
state,
|
||||||
showButtons: !ui.hideMovableButtons,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
//<ZoomablePattern
|
|
||||||
// renderProps={renderProps}
|
|
||||||
// patternLocale="en"
|
|
||||||
// rotate={state.ui.rotate}
|
|
||||||
///>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return <PatternLayout {...{ update, Design, output, state, pattern, config }} />
|
return <PatternLayout {...{ update, Design, output, state, pattern, config }} />
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PatternWithMenu
|
|
||||||
noHeader
|
|
||||||
{...{
|
|
||||||
settings,
|
|
||||||
ui,
|
|
||||||
update,
|
|
||||||
control: account.control,
|
|
||||||
account,
|
|
||||||
design,
|
|
||||||
setSettings,
|
|
||||||
title: (
|
|
||||||
<h2 className="text-center lg:text-left capitalize">{t('workbench:printLayout')}</h2>
|
|
||||||
),
|
|
||||||
pattern: (
|
|
||||||
<MovablePattern
|
|
||||||
{...{
|
|
||||||
renderProps,
|
|
||||||
update,
|
|
||||||
immovable: ['pages'],
|
|
||||||
layoutPath: ['layouts', 'print'],
|
|
||||||
showButtons: !ui.hideMovableButtons,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
menu: (
|
|
||||||
<PrintMenu
|
|
||||||
{...{
|
|
||||||
design,
|
|
||||||
pattern,
|
|
||||||
patternConfig,
|
|
||||||
setSettings,
|
|
||||||
settings,
|
|
||||||
ui,
|
|
||||||
update,
|
|
||||||
language,
|
|
||||||
account,
|
|
||||||
exportIt,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { DraftView } from './DraftView.mjs'
|
||||||
import { SaveView } from './SaveView.mjs'
|
import { SaveView } from './SaveView.mjs'
|
||||||
import { ExportView } from './ExportView.mjs'
|
import { ExportView } from './ExportView.mjs'
|
||||||
import { UndosView } from './UndosView.mjs'
|
import { UndosView } from './UndosView.mjs'
|
||||||
//import { LayoutView } from './LayoutView.mjs'
|
import { LayoutView } from './LayoutView.mjs'
|
||||||
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
import { ErrorIcon } from '@freesewing/react/components/Icon'
|
||||||
import {
|
import {
|
||||||
OptionsIcon,
|
OptionsIcon,
|
||||||
|
@ -58,7 +58,7 @@ export const View = (props) => {
|
||||||
if (view === 'save') return <SaveView {...props} />
|
if (view === 'save') return <SaveView {...props} />
|
||||||
if (view === 'export') return <ExportView {...props} />
|
if (view === 'export') return <ExportView {...props} />
|
||||||
if (view === 'undos') return <UndosView {...props} />
|
if (view === 'undos') return <UndosView {...props} />
|
||||||
//if (view === 'layout') return <LayoutView {...props} />
|
if (view === 'layout') return <LayoutView {...props} />
|
||||||
/*
|
/*
|
||||||
viewComponents: {
|
viewComponents: {
|
||||||
draft: 'DraftView',
|
draft: 'DraftView',
|
||||||
|
@ -109,7 +109,7 @@ export const viewLabels = {
|
||||||
d: 'Shows detailed timing of the pattern being drafted, allowing you to find bottlenecks in performance',
|
d: 'Shows detailed timing of the pattern being drafted, allowing you to find bottlenecks in performance',
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
t: 'Pattern Layout',
|
t: 'Print Layout',
|
||||||
d: 'Organize your pattern parts to minimize paper use',
|
d: 'Organize your pattern parts to minimize paper use',
|
||||||
},
|
},
|
||||||
save: {
|
save: {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
menuCoreSettingsStructure,
|
menuCoreSettingsStructure,
|
||||||
} from './core-settings.mjs'
|
} from './core-settings.mjs'
|
||||||
import { findOption, getOptionStructure, menuDesignOptionsStructure } from './design-options.mjs'
|
import { findOption, getOptionStructure, menuDesignOptionsStructure } from './design-options.mjs'
|
||||||
|
import { menuLayoutSettingsStructure } from './layout-settings.mjs'
|
||||||
import {
|
import {
|
||||||
addUndoStep,
|
addUndoStep,
|
||||||
cloneObject,
|
cloneObject,
|
||||||
|
@ -68,6 +69,8 @@ export {
|
||||||
findOption,
|
findOption,
|
||||||
getOptionStructure,
|
getOptionStructure,
|
||||||
menuDesignOptionsStructure,
|
menuDesignOptionsStructure,
|
||||||
|
// layout-settings.mjs
|
||||||
|
menuLayoutSettingsStructure,
|
||||||
// editor.mjs
|
// editor.mjs
|
||||||
addUndoStep,
|
addUndoStep,
|
||||||
cloneObject,
|
cloneObject,
|
||||||
|
|
122
packages/react/components/Editor/lib/layout-settings.mjs
Normal file
122
packages/react/components/Editor/lib/layout-settings.mjs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { defaultConfig } from '../config/index.mjs'
|
||||||
|
import { linkClasses } from '@freesewing/utils'
|
||||||
|
import {
|
||||||
|
CoverPageIcon,
|
||||||
|
MenuIcon,
|
||||||
|
KioskIcon,
|
||||||
|
RotateIcon,
|
||||||
|
RocketIcon,
|
||||||
|
UxIcon,
|
||||||
|
PageMarginIcon,
|
||||||
|
PageOrientationIcon,
|
||||||
|
PageSizeIcon,
|
||||||
|
PatternIcon,
|
||||||
|
ScaleIcon,
|
||||||
|
} from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
|
const UiDocsLink = ({ item }) => (
|
||||||
|
<a href={`/docs/about/site/draft/#${item.toLowerCase()}`} className={`${linkClasses} tw-px-2`}>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
|
||||||
|
const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'legal', 'tabloid']
|
||||||
|
const defaultPrintSettings = (units) => ({
|
||||||
|
size: units === 'imperial' ? 'letter' : 'a4',
|
||||||
|
orientation: 'portrait',
|
||||||
|
margin: units === 'imperial' ? 12.7 : 10,
|
||||||
|
coverPage: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function menuLayoutSettingsStructure(units) {
|
||||||
|
const defaults = defaultPrintSettings(units)
|
||||||
|
const sizeTitles = {
|
||||||
|
a4: 'A4',
|
||||||
|
a3: 'A3',
|
||||||
|
a2: 'A2',
|
||||||
|
a1: 'A1',
|
||||||
|
a0: 'A0',
|
||||||
|
letter: 'Letter',
|
||||||
|
legal: 'Legal',
|
||||||
|
tabloid: 'Tabloid',
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
size: {
|
||||||
|
dense: true,
|
||||||
|
title: 'Paper Size',
|
||||||
|
about: (
|
||||||
|
<span>
|
||||||
|
This control the pages overlay that helps you see how your pattern spans the pages. This
|
||||||
|
does not limit your export options, you can still export in a variety of paper sizes.
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
ux: 1,
|
||||||
|
list: Object.keys(sizeTitles),
|
||||||
|
choiceTitles: sizeTitles,
|
||||||
|
valueTitles: sizeTitles,
|
||||||
|
dflt: defaults.size,
|
||||||
|
icon: PageSizeIcon,
|
||||||
|
},
|
||||||
|
orientation: {
|
||||||
|
dense: true,
|
||||||
|
title: 'Page Orientation',
|
||||||
|
about: (
|
||||||
|
<span>Landscape or Portrait. Try both to see which yields the least amount of pages.</span>
|
||||||
|
),
|
||||||
|
ux: 1,
|
||||||
|
list: ['portrait', 'landscape'],
|
||||||
|
choiceTitles: {
|
||||||
|
portrait: (
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-4">
|
||||||
|
<PatternIcon className="tw-h-5 tw-w-5" />
|
||||||
|
<span className="tw-grow">Portrait (tall)</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
landscape: (
|
||||||
|
<div className="tw-flex tw-flex-row tw-items-center tw-gap-4">
|
||||||
|
<PatternIcon className="tw-h-5 tw-w-5 tw--rotate-90" />
|
||||||
|
<span className="tw-grow">Landscape (wide)</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
icon: PageOrientationIcon,
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
dense: true,
|
||||||
|
title: 'Page Margin',
|
||||||
|
min: units === 'imperial' ? 2.5 : 5,
|
||||||
|
max: 25,
|
||||||
|
dflt: defaults.margin,
|
||||||
|
icon: PageMarginIcon,
|
||||||
|
ux: 1,
|
||||||
|
},
|
||||||
|
coverPage: {
|
||||||
|
dense: true,
|
||||||
|
ux: 1,
|
||||||
|
icon: CoverPageIcon,
|
||||||
|
title: 'Cover Page',
|
||||||
|
about:
|
||||||
|
'The cover page includes information about the pattern and an overview of how to assemble the pages.',
|
||||||
|
list: [0, 1],
|
||||||
|
choiceTitles: {
|
||||||
|
0: 'Do not include a cover page',
|
||||||
|
1: 'Include a cover page',
|
||||||
|
},
|
||||||
|
dflt: 0,
|
||||||
|
},
|
||||||
|
iconSize: {
|
||||||
|
dense: true,
|
||||||
|
ux: 1,
|
||||||
|
icon: ScaleIcon,
|
||||||
|
title: 'Icon Size',
|
||||||
|
about:
|
||||||
|
'Controls the size of the icons that allow you to rotate/flip individual pattern parts',
|
||||||
|
min: 10,
|
||||||
|
dflt: 0.5,
|
||||||
|
step: 1,
|
||||||
|
max: 200,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { logoPath } from '@freesewing/config'
|
import { logoPath } from '@freesewing/config'
|
||||||
|
|
||||||
|
// Used in several icons
|
||||||
|
const page =
|
||||||
|
'M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Used inside the pattern editor
|
* Used inside the pattern editor
|
||||||
*/
|
*/
|
||||||
|
@ -153,6 +157,16 @@ export const CopyIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Looks like a page with a smiley on it
|
||||||
|
export const CoverPageIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d={page} />
|
||||||
|
<circle cx="9" cy="12" r="1" />
|
||||||
|
<circle cx="14" cy="12" r="1" />
|
||||||
|
<path d="M 9 16 C 11 18 12 18 14 16" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
// Looks like a museum building
|
// Looks like a museum building
|
||||||
export const CuratedMeasurementsSetIcon = (props) => (
|
export const CuratedMeasurementsSetIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
|
@ -268,6 +282,13 @@ export const FlagIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Looks lik a flag
|
||||||
|
export const FlipIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
// Looks like skully
|
// Looks like skully
|
||||||
export const FreeSewingIcon = (props) => (
|
export const FreeSewingIcon = (props) => (
|
||||||
<IconWrapper {...props} stroke={0} fill>
|
<IconWrapper {...props} stroke={0} fill>
|
||||||
|
@ -452,6 +473,36 @@ export const OptionsIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Looks like a page with a margin drawn around
|
||||||
|
export const PageMarginIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path
|
||||||
|
d="M 4.5 2.5 v 19.2 h 15 v -13.3 h-3 v 10.3 h-9 v-13.2 h 6 v-3 z"
|
||||||
|
strokeWidth={0.1}
|
||||||
|
stroke="none"
|
||||||
|
fill="currentColor"
|
||||||
|
fillOpacity="0.666"
|
||||||
|
/>
|
||||||
|
<path d={page} />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Looks like a portrait and landscape page stacked
|
||||||
|
export const PageOrientationIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d={page} transform="scale(-1 1) translate(-21 0)" />
|
||||||
|
<path d="M 16.5 7.75 h 5 v 14 h-5" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Looks like two differently sizes pages stacked
|
||||||
|
export const PageSizeIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d={page} />
|
||||||
|
<path d={page} transform="scale(0.666) translate(3, 11)" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
// Looks like a grid
|
// Looks like a grid
|
||||||
export const PaperlessIcon = (props) => (
|
export const PaperlessIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
|
@ -466,7 +517,7 @@ export const PaperlessIcon = (props) => (
|
||||||
// Looks like a page
|
// Looks like a page
|
||||||
export const PatternIcon = (props) => (
|
export const PatternIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
<path d={page} />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -536,7 +587,12 @@ export const RocketIcon = (props) => (
|
||||||
// Looks like two arrows in a circular layout
|
// Looks like two arrows in a circular layout
|
||||||
export const RotateIcon = (props) => (
|
export const RotateIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M 19.5,12 C 19.5,10.768 19.454,9.547 19.362,8.338 19.21576,6.3582806 17.641719,4.7842398 15.662,4.638 14.504476,4.5506731 13.344609,4.5048098 12.184624,4.5004103 M 19.5,12 l 3,-3 m -3,3 -3,-3 m -12,3 c 0,1.232 0.046,2.453 0.138,3.662 0.1462398,1.979719 1.7202806,3.55376 3.7,3.7 1.295324,0.09777 2.593584,0.143587 3.891661,0.13746 M 4.5,12 l 3,3 m -3,-3 -3,3"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -278,6 +278,44 @@ svg.freesewing.pattern .muted {
|
||||||
opacity: 0.15;
|
opacity: 0.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* layout rectangles */
|
||||||
|
svg.freesewing.pattern .layout-rect {
|
||||||
|
fill: var(--pattern-canvas);
|
||||||
|
fill-opacity: 0.05;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .layout-rect:hover {
|
||||||
|
fill: var(--pattern-lining);
|
||||||
|
fill-opacity: 0.15;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .layout-rect.move:hover {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .layout-rect.rotate:hover {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .svg-layout-button {
|
||||||
|
fill: var(--pattern-note);
|
||||||
|
fill-opacity: 0;
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .svg-layout-button:hover {
|
||||||
|
fill: var(--pattern-note);
|
||||||
|
fill-opacity: 1;
|
||||||
|
stroke: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .svg-layout-button path {
|
||||||
|
stroke-width: calc(var(--pattern-stroke) * 2 * var(--pattern-scale));
|
||||||
|
color: var(--pattern-contrast);
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .svg-layout-button:hover path {
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
svg.freesewing.pattern .svg-layout-button:hover > rect.button {
|
||||||
|
fill: var(--pattern-contrast);
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Developer view */
|
/* Developer view */
|
||||||
g.develop.point {
|
g.develop.point {
|
||||||
circle.center {
|
circle.center {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue