fixes zstacking with text

This commit is contained in:
Steve Ruiz 2021-06-17 22:50:04 +01:00
parent 946fdbab4c
commit d0d24e9c71
7 changed files with 98 additions and 78 deletions

View file

@ -10,7 +10,6 @@ import {
getSelectedShapes,
isMobile,
} from 'utils/utils'
import CenterHandle from './center-handle'
import CornerHandle from './corner-handle'
import EdgeHandle from './edge-handle'
@ -18,8 +17,11 @@ import RotateHandle from './rotate-handle'
export default function Bounds() {
const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
const isSelecting = useSelector((s) => s.isIn('selecting'))
const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
const bounds = useSelector((s) => s.values.selectedBounds)
const selectedIds = useSelector(

View file

@ -33,7 +33,7 @@ export default function Canvas() {
<g ref={rGroup}>
<BoundsBg />
<Page />
<Selected />
{/* <Selected /> */}
<Bounds />
<Handles />
<Brush />

View file

@ -46,8 +46,8 @@ export const ShapeOutline = memo(function ShapeOutline({ id }: { id: string }) {
const center = getShapeUtils(shape).getCenter(shape)
const transform = `
rotate(${shape.rotation * (180 / Math.PI)}, ${center})
translate(${shape.point})
rotate(${shape.rotation * (180 / Math.PI)}, ${center})
translate(${shape.point})
`
return (
@ -68,7 +68,7 @@ const SelectIndicator = styled('path', {
strokeLinejoin: 'round',
stroke: '$selected',
pointerEvents: 'none',
fill: 'none',
fill: 'transparent',
variants: {
isLocked: {

View file

@ -50,7 +50,7 @@ export default function ToolsPanel() {
<IconButton
name="select"
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
onClick={selectSelectTool}
isActive={activeTool === 'select'}
>
@ -114,44 +114,12 @@ export default function ToolsPanel() {
<TextIcon />
</IconButton>
</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>
<Tooltip label="Lock Tool">
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
onClick={selectToolLock}
>
{isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
@ -161,7 +129,7 @@ export default function ToolsPanel() {
<Tooltip label="Unlock Pen">
<IconButton
bp={{ '@initial': 'mobile', '@sm': 'small' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
onClick={selectToolLock}
>
<Pencil2Icon />

View file

@ -70,7 +70,7 @@ export function getFontStyle(
) {
const fontSize = getFontSize(size)
return `${fontSize * scale}px Verveine Regular`
return `${fontSize * scale}px/1.4 Verveine Regular`
}
export function getShapeStyle(

View file

@ -1,34 +1,41 @@
import { uniqueId } from 'utils/utils'
import vec from 'utils/vec'
import { TextShape, ShapeType, FontSize } from 'types'
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 state from 'state'
import { useEffect, useRef } from 'react'
// A div used for measurement
import React from 'react'
if (document.getElementById('__textMeasure')) {
document.getElementById('__textMeasure').remove()
}
// A div used for measurement
const mdiv = document.createElement('pre')
mdiv.id = '__textMeasure'
mdiv.style.whiteSpace = 'pre'
mdiv.style.width = 'auto'
mdiv.style.border = '1px solid red'
mdiv.style.padding = '4px'
mdiv.style.lineHeight = '1'
mdiv.style.margin = '0px'
mdiv.style.opacity = '0'
mdiv.style.position = 'absolute'
mdiv.style.top = '-500px'
mdiv.style.left = '0px'
mdiv.style.zIndex = '9999'
mdiv.tabIndex = -1
mdiv.setAttribute('readonly', 'true')
document.body.appendChild(mdiv)
function normalizeText(text: string) {
return text.replace(/\t/g, ' ').replace(/\r?\n|\r/g, '\n')
}
const text = registerShapeUtils<TextShape>({
isForeignObject: true,
canChangeAspectRatio: false,
@ -66,6 +73,51 @@ const text = registerShapeUtils<TextShape>({
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 (
<foreignObject
id={id}
@ -82,22 +134,17 @@ const text = registerShapeUtils<TextShape>({
font,
color: styles.stroke,
}}
tabIndex={0}
value={text}
autoComplete="false"
autoCapitalize="false"
autoCorrect="false"
onFocus={(e) => {
e.currentTarget.select()
state.send('FOCUSED_EDITING_SHAPE')
}}
onBlur={() => {
state.send('BLURRED_EDITING_SHAPE')
}}
onChange={(e) => {
state.send('EDITED_SHAPE', {
change: { text: e.currentTarget.value },
})
}}
spellCheck="false"
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
onChange={handleChange}
dir="auto"
/>
) : (
<StyledText
@ -115,7 +162,7 @@ const text = registerShapeUtils<TextShape>({
getBounds(shape) {
if (!this.boundsCache.has(shape)) {
mdiv.innerHTML = shape.text || ' ' // + '&nbsp;'
mdiv.innerHTML = shape.text + '&zwj;'
mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
const [minX, minY] = shape.point
@ -180,14 +227,17 @@ const StyledText = styled('div', {
border: 'none',
padding: '4px',
whiteSpace: 'pre',
resize: 'none',
minHeight: 1,
minWidth: 1,
outline: 'none',
outline: 0,
backgroundColor: 'transparent',
overflow: 'hidden',
pointerEvents: 'none',
userSelect: 'none',
backfaceVisibility: 'hidden',
display: 'inline-block',
position: 'relative',
zIndex: 0,
})
const StyledTextArea = styled('textarea', {
@ -199,8 +249,10 @@ const StyledTextArea = styled('textarea', {
resize: 'none',
minHeight: 1,
minWidth: 1,
outline: 'none',
overflow: 'hidden',
outline: 0,
backgroundColor: '$boundsBg',
overflow: 'hidden',
pointerEvents: 'all',
backfaceVisibility: 'hidden',
display: 'inline-block',
})

View file

@ -155,9 +155,18 @@ const state = createState({
CREATED_PAGE: ['clearSelectedIds', 'createPage'],
DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
PANNED_CAMERA: {
do: 'panCamera',
},
PANNED_CAMERA: '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',
states: {
@ -197,17 +206,6 @@ const state = createState({
},
SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
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: {
do: 'zoomCamera',
},