fixes zstacking with text
This commit is contained in:
parent
946fdbab4c
commit
d0d24e9c71
7 changed files with 98 additions and 78 deletions
|
@ -10,7 +10,6 @@ import {
|
||||||
getSelectedShapes,
|
getSelectedShapes,
|
||||||
isMobile,
|
isMobile,
|
||||||
} from 'utils/utils'
|
} from 'utils/utils'
|
||||||
|
|
||||||
import CenterHandle from './center-handle'
|
import CenterHandle from './center-handle'
|
||||||
import CornerHandle from './corner-handle'
|
import CornerHandle from './corner-handle'
|
||||||
import EdgeHandle from './edge-handle'
|
import EdgeHandle from './edge-handle'
|
||||||
|
@ -18,8 +17,11 @@ import RotateHandle from './rotate-handle'
|
||||||
|
|
||||||
export default function Bounds() {
|
export default function Bounds() {
|
||||||
const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
|
const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
|
||||||
|
|
||||||
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
const isSelecting = useSelector((s) => s.isIn('selecting'))
|
||||||
|
|
||||||
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
|
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
|
||||||
|
|
||||||
const bounds = useSelector((s) => s.values.selectedBounds)
|
const bounds = useSelector((s) => s.values.selectedBounds)
|
||||||
|
|
||||||
const selectedIds = useSelector(
|
const selectedIds = useSelector(
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default function Canvas() {
|
||||||
<g ref={rGroup}>
|
<g ref={rGroup}>
|
||||||
<BoundsBg />
|
<BoundsBg />
|
||||||
<Page />
|
<Page />
|
||||||
<Selected />
|
{/* <Selected /> */}
|
||||||
<Bounds />
|
<Bounds />
|
||||||
<Handles />
|
<Handles />
|
||||||
<Brush />
|
<Brush />
|
||||||
|
|
|
@ -68,7 +68,7 @@ const SelectIndicator = styled('path', {
|
||||||
strokeLinejoin: 'round',
|
strokeLinejoin: 'round',
|
||||||
stroke: '$selected',
|
stroke: '$selected',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
fill: 'none',
|
fill: 'transparent',
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
isLocked: {
|
isLocked: {
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default function ToolsPanel() {
|
||||||
<IconButton
|
<IconButton
|
||||||
name="select"
|
name="select"
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
|
||||||
onClick={selectSelectTool}
|
onClick={selectSelectTool}
|
||||||
isActive={activeTool === 'select'}
|
isActive={activeTool === 'select'}
|
||||||
>
|
>
|
||||||
|
@ -114,44 +114,12 @@ export default function ToolsPanel() {
|
||||||
<TextIcon />
|
<TextIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/* <IconButton
|
|
||||||
name={ShapeType.Circle}
|
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
|
||||||
onClick={selectCircleTool}
|
|
||||||
isActive={activeTool === ShapeType.Circle}
|
|
||||||
>
|
|
||||||
<CircleIcon />
|
|
||||||
</IconButton> */}
|
|
||||||
{/* <IconButton
|
|
||||||
name={ShapeType.Line}
|
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
|
||||||
onClick={selectLineTool}
|
|
||||||
isActive={activeTool === ShapeType.Line}
|
|
||||||
>
|
|
||||||
<DividerHorizontalIcon transform="rotate(-45)" />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
name={ShapeType.Ray}
|
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
|
||||||
onClick={selectRayTool}
|
|
||||||
isActive={activeTool === ShapeType.Ray}
|
|
||||||
>
|
|
||||||
<SewingPinIcon transform="rotate(-135)" />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
name={ShapeType.Dot}
|
|
||||||
size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
|
|
||||||
onClick={selectDotTool}
|
|
||||||
isActive={activeTool === ShapeType.Dot}
|
|
||||||
>
|
|
||||||
<DotIcon />
|
|
||||||
</IconButton> */}
|
|
||||||
</Container>
|
</Container>
|
||||||
<Container>
|
<Container>
|
||||||
<Tooltip label="Lock Tool">
|
<Tooltip label="Lock Tool">
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
|
||||||
onClick={selectToolLock}
|
onClick={selectToolLock}
|
||||||
>
|
>
|
||||||
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
||||||
|
@ -161,7 +129,7 @@ export default function ToolsPanel() {
|
||||||
<Tooltip label="Unlock Pen">
|
<Tooltip label="Unlock Pen">
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
|
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
|
||||||
onClick={selectToolLock}
|
onClick={selectToolLock}
|
||||||
>
|
>
|
||||||
<Pencil2Icon />
|
<Pencil2Icon />
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function getFontStyle(
|
||||||
) {
|
) {
|
||||||
const fontSize = getFontSize(size)
|
const fontSize = getFontSize(size)
|
||||||
|
|
||||||
return `${fontSize * scale}px Verveine Regular`
|
return `${fontSize * scale}px/1.4 Verveine Regular`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShapeStyle(
|
export function getShapeStyle(
|
||||||
|
|
|
@ -1,34 +1,41 @@
|
||||||
import { uniqueId } from 'utils/utils'
|
import { uniqueId } from 'utils/utils'
|
||||||
import vec from 'utils/vec'
|
|
||||||
import { TextShape, ShapeType, FontSize } from 'types'
|
import { TextShape, ShapeType, FontSize } from 'types'
|
||||||
import { registerShapeUtils } from './index'
|
import { registerShapeUtils } from './index'
|
||||||
import { defaultStyle, getFontStyle, getShapeStyle } from 'lib/shape-styles'
|
import {
|
||||||
|
defaultStyle,
|
||||||
|
getFontSize,
|
||||||
|
getFontStyle,
|
||||||
|
getShapeStyle,
|
||||||
|
} from 'lib/shape-styles'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import { useEffect, useRef } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
// A div used for measurement
|
|
||||||
|
|
||||||
if (document.getElementById('__textMeasure')) {
|
if (document.getElementById('__textMeasure')) {
|
||||||
document.getElementById('__textMeasure').remove()
|
document.getElementById('__textMeasure').remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A div used for measurement
|
||||||
const mdiv = document.createElement('pre')
|
const mdiv = document.createElement('pre')
|
||||||
mdiv.id = '__textMeasure'
|
mdiv.id = '__textMeasure'
|
||||||
mdiv.style.whiteSpace = 'pre'
|
mdiv.style.whiteSpace = 'pre'
|
||||||
mdiv.style.width = 'auto'
|
mdiv.style.width = 'auto'
|
||||||
mdiv.style.border = '1px solid red'
|
mdiv.style.border = '1px solid red'
|
||||||
mdiv.style.padding = '4px'
|
mdiv.style.padding = '4px'
|
||||||
mdiv.style.lineHeight = '1'
|
|
||||||
mdiv.style.margin = '0px'
|
mdiv.style.margin = '0px'
|
||||||
mdiv.style.opacity = '0'
|
mdiv.style.opacity = '0'
|
||||||
mdiv.style.position = 'absolute'
|
mdiv.style.position = 'absolute'
|
||||||
mdiv.style.top = '-500px'
|
mdiv.style.top = '-500px'
|
||||||
mdiv.style.left = '0px'
|
mdiv.style.left = '0px'
|
||||||
mdiv.style.zIndex = '9999'
|
mdiv.style.zIndex = '9999'
|
||||||
|
mdiv.tabIndex = -1
|
||||||
mdiv.setAttribute('readonly', 'true')
|
mdiv.setAttribute('readonly', 'true')
|
||||||
document.body.appendChild(mdiv)
|
document.body.appendChild(mdiv)
|
||||||
|
|
||||||
|
function normalizeText(text: string) {
|
||||||
|
return text.replace(/\t/g, ' ').replace(/\r?\n|\r/g, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
const text = registerShapeUtils<TextShape>({
|
const text = registerShapeUtils<TextShape>({
|
||||||
isForeignObject: true,
|
isForeignObject: true,
|
||||||
canChangeAspectRatio: false,
|
canChangeAspectRatio: false,
|
||||||
|
@ -66,6 +73,51 @@ const text = registerShapeUtils<TextShape>({
|
||||||
|
|
||||||
const bounds = this.getBounds(shape)
|
const bounds = this.getBounds(shape)
|
||||||
|
|
||||||
|
function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||||
|
state.send('EDITED_SHAPE', {
|
||||||
|
change: { text: normalizeText(e.currentTarget.value) },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(e: React.KeyboardEvent) {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlur() {
|
||||||
|
state.send('BLURRED_EDITING_SHAPE')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFocus(e: React.FocusEvent<HTMLTextAreaElement>) {
|
||||||
|
e.currentTarget.select()
|
||||||
|
state.send('FOCUSED_EDITING_SHAPE')
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineHeight = getFontSize(shape.fontSize) * shape.scale
|
||||||
|
const gap = lineHeight * 0.4
|
||||||
|
|
||||||
|
if (!isEditing) {
|
||||||
|
return (
|
||||||
|
<g id={id} pointerEvents="none">
|
||||||
|
{text.split('\n').map((str, i) => (
|
||||||
|
<text
|
||||||
|
key={i}
|
||||||
|
x={4}
|
||||||
|
y={4 + gap / 2 + i * (lineHeight + gap)}
|
||||||
|
style={{ font }}
|
||||||
|
width={bounds.width}
|
||||||
|
height={bounds.height}
|
||||||
|
dominant-baseline="hanging"
|
||||||
|
>
|
||||||
|
{str}
|
||||||
|
</text>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<foreignObject
|
<foreignObject
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -82,22 +134,17 @@ const text = registerShapeUtils<TextShape>({
|
||||||
font,
|
font,
|
||||||
color: styles.stroke,
|
color: styles.stroke,
|
||||||
}}
|
}}
|
||||||
|
tabIndex={0}
|
||||||
value={text}
|
value={text}
|
||||||
autoComplete="false"
|
autoComplete="false"
|
||||||
autoCapitalize="false"
|
autoCapitalize="false"
|
||||||
autoCorrect="false"
|
autoCorrect="false"
|
||||||
onFocus={(e) => {
|
spellCheck="false"
|
||||||
e.currentTarget.select()
|
onFocus={handleFocus}
|
||||||
state.send('FOCUSED_EDITING_SHAPE')
|
onBlur={handleBlur}
|
||||||
}}
|
onKeyDown={handleKeyDown}
|
||||||
onBlur={() => {
|
onChange={handleChange}
|
||||||
state.send('BLURRED_EDITING_SHAPE')
|
dir="auto"
|
||||||
}}
|
|
||||||
onChange={(e) => {
|
|
||||||
state.send('EDITED_SHAPE', {
|
|
||||||
change: { text: e.currentTarget.value },
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StyledText
|
<StyledText
|
||||||
|
@ -115,7 +162,7 @@ const text = registerShapeUtils<TextShape>({
|
||||||
|
|
||||||
getBounds(shape) {
|
getBounds(shape) {
|
||||||
if (!this.boundsCache.has(shape)) {
|
if (!this.boundsCache.has(shape)) {
|
||||||
mdiv.innerHTML = shape.text || ' ' // + ' '
|
mdiv.innerHTML = shape.text + '‍'
|
||||||
mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
|
mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
|
||||||
|
|
||||||
const [minX, minY] = shape.point
|
const [minX, minY] = shape.point
|
||||||
|
@ -180,14 +227,17 @@ const StyledText = styled('div', {
|
||||||
border: 'none',
|
border: 'none',
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
whiteSpace: 'pre',
|
whiteSpace: 'pre',
|
||||||
resize: 'none',
|
|
||||||
minHeight: 1,
|
minHeight: 1,
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
outline: 'none',
|
outline: 0,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
display: 'inline-block',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
const StyledTextArea = styled('textarea', {
|
const StyledTextArea = styled('textarea', {
|
||||||
|
@ -199,8 +249,10 @@ const StyledTextArea = styled('textarea', {
|
||||||
resize: 'none',
|
resize: 'none',
|
||||||
minHeight: 1,
|
minHeight: 1,
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
outline: 'none',
|
outline: 0,
|
||||||
overflow: 'hidden',
|
|
||||||
backgroundColor: '$boundsBg',
|
backgroundColor: '$boundsBg',
|
||||||
|
overflow: 'hidden',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
display: 'inline-block',
|
||||||
})
|
})
|
||||||
|
|
|
@ -155,9 +155,18 @@ const state = createState({
|
||||||
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
|
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
|
||||||
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
|
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
|
||||||
LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
|
LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
|
||||||
PANNED_CAMERA: {
|
PANNED_CAMERA: 'panCamera',
|
||||||
do: 'panCamera',
|
SELECTED_SELECT_TOOL: { to: 'selecting' },
|
||||||
},
|
SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
|
||||||
|
SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
|
||||||
|
SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
|
||||||
|
SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
|
||||||
|
SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
|
||||||
|
SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
|
||||||
|
SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
|
||||||
|
SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
|
||||||
|
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
||||||
|
SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
|
||||||
},
|
},
|
||||||
initial: 'selecting',
|
initial: 'selecting',
|
||||||
states: {
|
states: {
|
||||||
|
@ -197,17 +206,6 @@ const state = createState({
|
||||||
},
|
},
|
||||||
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
|
||||||
NUDGED: { do: 'nudgeSelection' },
|
NUDGED: { do: 'nudgeSelection' },
|
||||||
SELECTED_SELECT_TOOL: { to: 'selecting' },
|
|
||||||
SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
|
|
||||||
SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
|
|
||||||
SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
|
|
||||||
SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
|
|
||||||
SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
|
|
||||||
SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
|
|
||||||
SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
|
|
||||||
SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
|
|
||||||
SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
|
|
||||||
SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
|
|
||||||
ZOOMED_CAMERA: {
|
ZOOMED_CAMERA: {
|
||||||
do: 'zoomCamera',
|
do: 'zoomCamera',
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue