Updates style panel
This commit is contained in:
parent
04efb6f880
commit
39b943248f
22 changed files with 421 additions and 408 deletions
|
@ -80,11 +80,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToBack,
|
type: MoveType.ToBack,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '1', '2', '4'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['3', '1', '2', '4'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings to back', () => {
|
it('moves two adjacent siblings to back', () => {
|
||||||
|
@ -92,11 +88,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToBack,
|
type: MoveType.ToBack,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '4', '3', '1'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['2', '4', '3', '1'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings to back', () => {
|
it('moves two non-adjacent siblings to back', () => {
|
||||||
|
@ -104,11 +96,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToBack,
|
type: MoveType.ToBack,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '1', '2', '3'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['4', '1', '2', '3'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape backward', () => {
|
it('moves a shape backward', () => {
|
||||||
|
@ -116,11 +104,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Backward,
|
type: MoveType.Backward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '1', '3', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['4', '1', '3', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape at first index backward', () => {
|
it('moves a shape at first index backward', () => {
|
||||||
|
@ -128,11 +112,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Backward,
|
type: MoveType.Backward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '1', '3', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['4', '1', '3', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings backward', () => {
|
it('moves two adjacent siblings backward', () => {
|
||||||
|
@ -140,23 +120,15 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Backward,
|
type: MoveType.Backward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '3', '2', '1'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['4', '3', '2', '1'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings backward', () => {
|
it('moves two non-adjacent siblings backward', () => {
|
||||||
tt.clickShape('3').clickShape('1', { shiftKey: true }).send('MOVED', {
|
tt.clickShape('3')
|
||||||
type: MoveType.Backward,
|
.clickShape('1', { shiftKey: true })
|
||||||
})
|
.send('MOVED', { type: MoveType.Backward })
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '4', '1', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['3', '4', '1', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings backward at zero index', () => {
|
it('moves two adjacent siblings backward at zero index', () => {
|
||||||
|
@ -164,11 +136,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Backward,
|
type: MoveType.Backward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '4', '1', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['3', '4', '1', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape forward', () => {
|
it('moves a shape forward', () => {
|
||||||
|
@ -176,11 +144,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Forward,
|
type: MoveType.Forward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '1', '4', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['3', '1', '4', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape forward at the top index', () => {
|
it('moves a shape forward at the top index', () => {
|
||||||
|
@ -188,11 +152,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Forward,
|
type: MoveType.Forward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '1', '4', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['3', '1', '4', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings forward', () => {
|
it('moves two adjacent siblings forward', () => {
|
||||||
|
@ -205,11 +165,7 @@ describe('shapes with children', () => {
|
||||||
|
|
||||||
expect(tt.idsAreSelected(['1', '4'])).toBe(true)
|
expect(tt.idsAreSelected(['1', '4'])).toBe(true)
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '2', '1', '4'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['3', '2', '1', '4'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings forward', () => {
|
it('moves two non-adjacent siblings forward', () => {
|
||||||
|
@ -220,11 +176,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.Forward,
|
type: MoveType.Forward,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '3', '4', '1'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['2', '3', '4', '1'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings forward at top index', () => {
|
it('moves two adjacent siblings forward at top index', () => {
|
||||||
|
@ -234,11 +186,7 @@ describe('shapes with children', () => {
|
||||||
.send('MOVED', {
|
.send('MOVED', {
|
||||||
type: MoveType.Forward,
|
type: MoveType.Forward,
|
||||||
})
|
})
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '4', '3', '1'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['2', '4', '3', '1'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves a shape to front', () => {
|
it('moves a shape to front', () => {
|
||||||
|
@ -246,11 +194,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToFront,
|
type: MoveType.ToFront,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '3', '1', '2'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['4', '3', '1', '2'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two adjacent siblings to front', () => {
|
it('moves two adjacent siblings to front', () => {
|
||||||
|
@ -261,11 +205,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToFront,
|
type: MoveType.ToFront,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '2', '3', '1'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['4', '2', '3', '1'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves two non-adjacent siblings to front', () => {
|
it('moves two non-adjacent siblings to front', () => {
|
||||||
|
@ -276,11 +216,7 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToFront,
|
type: MoveType.ToFront,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '1', '4', '3'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['2', '1', '4', '3'])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('moves siblings already at front to front', () => {
|
it('moves siblings already at front to front', () => {
|
||||||
|
@ -291,10 +227,6 @@ describe('shapes with children', () => {
|
||||||
type: MoveType.ToFront,
|
type: MoveType.ToFront,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(
|
expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '1', '4', '3'])
|
||||||
Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
|
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)
|
|
||||||
.map((shape) => shape.id)
|
|
||||||
).toStrictEqual(['2', '1', '4', '3'])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,32 +1,33 @@
|
||||||
|
import { DashStyle } from 'types'
|
||||||
import { getPerfectDashProps } from 'utils'
|
import { getPerfectDashProps } from 'utils'
|
||||||
|
|
||||||
describe('ellipse dash props', () => {
|
describe('ellipse dash props', () => {
|
||||||
it('renders dashed props on a circle correctly', () => {
|
it('renders dashed props on a circle correctly', () => {
|
||||||
expect(getPerfectDashProps(100, 4, 'dashed')).toMatchSnapshot(
|
expect(getPerfectDashProps(100, 4, DashStyle.Dashed)).toMatchSnapshot(
|
||||||
'small dashed circle dash props'
|
'small dashed circle dash props'
|
||||||
)
|
)
|
||||||
expect(getPerfectDashProps(100, 4, 'dashed')).toMatchSnapshot(
|
expect(getPerfectDashProps(100, 4, DashStyle.Dashed)).toMatchSnapshot(
|
||||||
'small dashed ellipse dash props'
|
'small dashed ellipse dash props'
|
||||||
)
|
)
|
||||||
expect(getPerfectDashProps(200, 8, 'dashed')).toMatchSnapshot(
|
expect(getPerfectDashProps(200, 8, DashStyle.Dashed)).toMatchSnapshot(
|
||||||
'large dashed circle dash props'
|
'large dashed circle dash props'
|
||||||
)
|
)
|
||||||
expect(getPerfectDashProps(200, 8, 'dashed')).toMatchSnapshot(
|
expect(getPerfectDashProps(200, 8, DashStyle.Dashed)).toMatchSnapshot(
|
||||||
'large dashed ellipse dash props'
|
'large dashed ellipse dash props'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders dotted props on a circle correctly', () => {
|
it('renders dotted props on a circle correctly', () => {
|
||||||
expect(getPerfectDashProps(100, 4, 'dotted')).toMatchSnapshot(
|
expect(getPerfectDashProps(100, 4, DashStyle.Dotted)).toMatchSnapshot(
|
||||||
'small dotted circle dash props'
|
'small dotted circle dash props'
|
||||||
)
|
)
|
||||||
expect(getPerfectDashProps(100, 4, 'dotted')).toMatchSnapshot(
|
expect(getPerfectDashProps(100, 4, DashStyle.Dotted)).toMatchSnapshot(
|
||||||
'small dotted ellipse dash props'
|
'small dotted ellipse dash props'
|
||||||
)
|
)
|
||||||
expect(getPerfectDashProps(200, 8, 'dotted')).toMatchSnapshot(
|
expect(getPerfectDashProps(200, 8, DashStyle.Dotted)).toMatchSnapshot(
|
||||||
'large dotted circle dash props'
|
'large dotted circle dash props'
|
||||||
)
|
)
|
||||||
expect(getPerfectDashProps(200, 8, 'dotted')).toMatchSnapshot(
|
expect(getPerfectDashProps(200, 8, DashStyle.Dotted)).toMatchSnapshot(
|
||||||
'large dotted ellipse dash props'
|
'large dotted ellipse dash props'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -91,6 +91,23 @@ class TestState {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sorted ids of the page's children.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
*```ts
|
||||||
|
* tt.getSortedPageShapes()
|
||||||
|
*```
|
||||||
|
*/
|
||||||
|
getSortedPageShapeIds(): string[] {
|
||||||
|
return Object.values(
|
||||||
|
this.data.document.pages[this.data.currentParentId].shapes
|
||||||
|
)
|
||||||
|
.sort((a, b) => a.childIndex - b.childIndex)
|
||||||
|
.map((shape) => shape.id)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get whether the provided ids are the current selected ids. If the `strict` argument is `true`, then the result will be false if the state has selected ids in addition to those provided.
|
* Get whether the provided ids are the current selected ids. If the `strict` argument is `true`, then the result will be false if the state has selected ids in addition to those provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -49,6 +49,7 @@ enum SizeStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DashStyle {
|
enum DashStyle {
|
||||||
|
Draw = 'Draw',
|
||||||
Solid = 'Solid',
|
Solid = 'Solid',
|
||||||
Dashed = 'Dashed',
|
Dashed = 'Dashed',
|
||||||
Dotted = 'Dotted',
|
Dotted = 'Dotted',
|
||||||
|
@ -399,6 +400,9 @@ type PropsOfType<T extends Record<string, unknown>> = {
|
||||||
type Mutable<T extends Shape> = { -readonly [K in keyof T]: T[K] }
|
type Mutable<T extends Shape> = { -readonly [K in keyof T]: T[K] }
|
||||||
|
|
||||||
interface ShapeUtility<K extends Shape> {
|
interface ShapeUtility<K extends Shape> {
|
||||||
|
// Default properties when creating a new shape
|
||||||
|
defaultProps: K
|
||||||
|
|
||||||
// A cache for the computed bounds of this kind of shape.
|
// A cache for the computed bounds of this kind of shape.
|
||||||
boundsCache: WeakMap<K, Bounds>
|
boundsCache: WeakMap<K, Bounds>
|
||||||
|
|
||||||
|
@ -424,7 +428,7 @@ interface ShapeUtility<K extends Shape> {
|
||||||
isShy: boolean
|
isShy: boolean
|
||||||
|
|
||||||
// Create a new shape.
|
// Create a new shape.
|
||||||
create(props: Partial<K>): K
|
create(this: ShapeUtility<K>, props: Partial<K>): K
|
||||||
|
|
||||||
// Update a shape's styles
|
// Update a shape's styles
|
||||||
applyStyles(
|
applyStyles(
|
||||||
|
@ -612,6 +616,7 @@ enum SizeStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DashStyle {
|
enum DashStyle {
|
||||||
|
Draw = 'Draw',
|
||||||
Solid = 'Solid',
|
Solid = 'Solid',
|
||||||
Dashed = 'Dashed',
|
Dashed = 'Dashed',
|
||||||
Dotted = 'Dotted',
|
Dotted = 'Dotted',
|
||||||
|
@ -962,6 +967,9 @@ type PropsOfType<T extends Record<string, unknown>> = {
|
||||||
type Mutable<T extends Shape> = { -readonly [K in keyof T]: T[K] }
|
type Mutable<T extends Shape> = { -readonly [K in keyof T]: T[K] }
|
||||||
|
|
||||||
interface ShapeUtility<K extends Shape> {
|
interface ShapeUtility<K extends Shape> {
|
||||||
|
// Default properties when creating a new shape
|
||||||
|
defaultProps: K
|
||||||
|
|
||||||
// A cache for the computed bounds of this kind of shape.
|
// A cache for the computed bounds of this kind of shape.
|
||||||
boundsCache: WeakMap<K, Bounds>
|
boundsCache: WeakMap<K, Bounds>
|
||||||
|
|
||||||
|
@ -987,7 +995,7 @@ interface ShapeUtility<K extends Shape> {
|
||||||
isShy: boolean
|
isShy: boolean
|
||||||
|
|
||||||
// Create a new shape.
|
// Create a new shape.
|
||||||
create(props: Partial<K>): K
|
create(this: ShapeUtility<K>, props: Partial<K>): K
|
||||||
|
|
||||||
// Update a shape's styles
|
// Update a shape's styles
|
||||||
applyStyles(
|
applyStyles(
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,10 +10,9 @@ import {
|
||||||
StretchHorizontallyIcon,
|
StretchHorizontallyIcon,
|
||||||
StretchVerticallyIcon,
|
StretchVerticallyIcon,
|
||||||
} from '@radix-ui/react-icons'
|
} from '@radix-ui/react-icons'
|
||||||
import { breakpoints, IconButton } from 'components/shared'
|
import { breakpoints, ButtonsRow, IconButton } from 'components/shared'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import state from 'state'
|
import state from 'state'
|
||||||
import styled from 'styles'
|
|
||||||
import { AlignType, DistributeType, StretchType } from 'types'
|
import { AlignType, DistributeType, StretchType } from 'types'
|
||||||
|
|
||||||
function alignTop() {
|
function alignTop() {
|
||||||
|
@ -64,7 +63,8 @@ function AlignDistribute({
|
||||||
hasThreeOrMore: boolean
|
hasThreeOrMore: boolean
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<>
|
||||||
|
<ButtonsRow>
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={breakpoints}
|
bp={breakpoints}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -105,6 +105,8 @@ function AlignDistribute({
|
||||||
>
|
>
|
||||||
<SpaceEvenlyHorizontallyIcon />
|
<SpaceEvenlyHorizontallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</ButtonsRow>
|
||||||
|
<ButtonsRow>
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={breakpoints}
|
bp={breakpoints}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -145,17 +147,9 @@ function AlignDistribute({
|
||||||
>
|
>
|
||||||
<SpaceEvenlyVerticallyIcon />
|
<SpaceEvenlyVerticallyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Container>
|
</ButtonsRow>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(AlignDistribute)
|
export default memo(AlignDistribute)
|
||||||
|
|
||||||
const Container = styled('div', {
|
|
||||||
display: 'grid',
|
|
||||||
padding: 4,
|
|
||||||
gridTemplateColumns: 'repeat(5, auto)',
|
|
||||||
[`& ${IconButton} > svg`]: {
|
|
||||||
stroke: 'transparent',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
DashDashedIcon,
|
DashDashedIcon,
|
||||||
DashDottedIcon,
|
DashDottedIcon,
|
||||||
DashSolidIcon,
|
DashSolidIcon,
|
||||||
|
DashDrawIcon,
|
||||||
} from '../shared'
|
} from '../shared'
|
||||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||||
import { DashStyle } from 'types'
|
import { DashStyle } from 'types'
|
||||||
|
@ -15,6 +16,7 @@ function handleChange(dash: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dashes = {
|
const dashes = {
|
||||||
|
[DashStyle.Draw]: <DashDrawIcon />,
|
||||||
[DashStyle.Solid]: <DashSolidIcon />,
|
[DashStyle.Solid]: <DashSolidIcon />,
|
||||||
[DashStyle.Dashed]: <DashDashedIcon />,
|
[DashStyle.Dashed]: <DashDashedIcon />,
|
||||||
[DashStyle.Dotted]: <DashDottedIcon />,
|
[DashStyle.Dotted]: <DashDottedIcon />,
|
||||||
|
|
|
@ -2,9 +2,9 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { breakpoints, IconButton } from 'components/shared'
|
import { breakpoints, IconButton } from 'components/shared'
|
||||||
import Tooltip from 'components/tooltip'
|
import Tooltip from 'components/tooltip'
|
||||||
import { strokes } from 'state/shape-styles'
|
import { strokes } from 'state/shape-styles'
|
||||||
import { Square } from 'react-feather'
|
|
||||||
import { useSelector } from 'state'
|
import { useSelector } from 'state'
|
||||||
import ColorContent from './color-content'
|
import ColorContent from './color-content'
|
||||||
|
import { BoxIcon } from '../shared'
|
||||||
|
|
||||||
export default function QuickColorSelect(): JSX.Element {
|
export default function QuickColorSelect(): JSX.Element {
|
||||||
const color = useSelector((s) => s.values.selectedStyle.color)
|
const color = useSelector((s) => s.values.selectedStyle.color)
|
||||||
|
@ -13,7 +13,7 @@ export default function QuickColorSelect(): JSX.Element {
|
||||||
<DropdownMenu.Root dir="ltr">
|
<DropdownMenu.Root dir="ltr">
|
||||||
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
|
<DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
|
||||||
<Tooltip label="Color">
|
<Tooltip label="Color">
|
||||||
<Square fill={strokes[color]} stroke={strokes[color]} />
|
<BoxIcon fill={strokes[color]} stroke={strokes[color]} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<ColorContent />
|
<ColorContent />
|
||||||
|
|
|
@ -7,12 +7,14 @@ import { DashStyle } from 'types'
|
||||||
import {
|
import {
|
||||||
DropdownContent,
|
DropdownContent,
|
||||||
Item,
|
Item,
|
||||||
|
DashDrawIcon,
|
||||||
DashDottedIcon,
|
DashDottedIcon,
|
||||||
DashSolidIcon,
|
DashSolidIcon,
|
||||||
DashDashedIcon,
|
DashDashedIcon,
|
||||||
} from '../shared'
|
} from '../shared'
|
||||||
|
|
||||||
const dashes = {
|
const dashes = {
|
||||||
|
[DashStyle.Draw]: <DashDrawIcon />,
|
||||||
[DashStyle.Solid]: <DashSolidIcon />,
|
[DashStyle.Solid]: <DashSolidIcon />,
|
||||||
[DashStyle.Dashed]: <DashDashedIcon />,
|
[DashStyle.Dashed]: <DashDashedIcon />,
|
||||||
[DashStyle.Dotted]: <DashDottedIcon />,
|
[DashStyle.Dotted]: <DashDottedIcon />,
|
||||||
|
|
43
components/style-panel/quick-fill-select.tsx
Normal file
43
components/style-panel/quick-fill-select.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import * as Checkbox from '@radix-ui/react-checkbox'
|
||||||
|
import tld from 'utils/tld'
|
||||||
|
import {
|
||||||
|
breakpoints,
|
||||||
|
BoxIcon,
|
||||||
|
IsFilledFillIcon,
|
||||||
|
IconButton,
|
||||||
|
IconWrapper,
|
||||||
|
} from '../shared'
|
||||||
|
import state, { useSelector } from 'state'
|
||||||
|
import { getShapeUtils } from 'state/shape-utils'
|
||||||
|
|
||||||
|
function handleIsFilledChange(isFilled: boolean) {
|
||||||
|
state.send('CHANGED_STYLE', { isFilled })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IsFilledPicker(): JSX.Element {
|
||||||
|
const isFilled = useSelector((s) => s.values.selectedStyle.isFilled)
|
||||||
|
const canFill = useSelector((s) => {
|
||||||
|
const selectedShapes = tld.getSelectedShapes(s.data)
|
||||||
|
|
||||||
|
return (
|
||||||
|
selectedShapes.length === 0 ||
|
||||||
|
selectedShapes.every((shape) => getShapeUtils(shape).canStyleFill)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox.Root
|
||||||
|
dir="ltr"
|
||||||
|
as={IconButton}
|
||||||
|
bp={breakpoints}
|
||||||
|
checked={isFilled}
|
||||||
|
disabled={!canFill}
|
||||||
|
onCheckedChange={handleIsFilledChange}
|
||||||
|
>
|
||||||
|
<IconWrapper>
|
||||||
|
<BoxIcon />
|
||||||
|
<Checkbox.Indicator as={IsFilledFillIcon} />
|
||||||
|
</IconWrapper>
|
||||||
|
</Checkbox.Root>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,19 +1,16 @@
|
||||||
import tld from 'utils/tld'
|
import tld from 'utils/tld'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { IconButton, breakpoints } from 'components/shared'
|
import { IconButton, ButtonsRow, breakpoints } from 'components/shared'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import styled from 'styles'
|
import { MoveType, ShapeType } from 'types'
|
||||||
import { MoveType } from 'types'
|
|
||||||
import { Trash2 } from 'react-feather'
|
import { Trash2 } from 'react-feather'
|
||||||
import Tooltip from 'components/tooltip'
|
import Tooltip from 'components/tooltip'
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
AspectRatioIcon,
|
AspectRatioIcon,
|
||||||
BoxIcon,
|
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
EyeClosedIcon,
|
GroupIcon,
|
||||||
EyeOpenIcon,
|
|
||||||
LockClosedIcon,
|
LockClosedIcon,
|
||||||
LockOpen1Icon,
|
LockOpen1Icon,
|
||||||
PinBottomIcon,
|
PinBottomIcon,
|
||||||
|
@ -29,8 +26,12 @@ function handleDuplicate() {
|
||||||
state.send('DUPLICATED')
|
state.send('DUPLICATED')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleHide() {
|
function handleGroup() {
|
||||||
state.send('TOGGLED_SHAPE_HIDE')
|
state.send('GROUPED')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUngroup() {
|
||||||
|
state.send('UNGROUPED')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLock() {
|
function handleLock() {
|
||||||
|
@ -74,15 +75,24 @@ function ShapesFunctions() {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const isAllHidden = useSelector((s) => {
|
const isAllGrouped = useSelector((s) => {
|
||||||
const page = tld.getPage(s.data)
|
const selectedShapes = tld.getSelectedShapes(s.data)
|
||||||
return s.values.selectedIds.every((id) => page.shapes[id].isHidden)
|
return selectedShapes.every(
|
||||||
|
(shape) =>
|
||||||
|
shape.type === ShapeType.Group ||
|
||||||
|
(shape.parentId === selectedShapes[0].parentId &&
|
||||||
|
selectedShapes[0].parentId !== s.data.currentPageId)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasSelection = useSelector((s) => {
|
const hasSelection = useSelector((s) => {
|
||||||
return tld.getSelectedIds(s.data).size > 0
|
return tld.getSelectedIds(s.data).size > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasMultipleSelection = useSelector((s) => {
|
||||||
|
return tld.getSelectedIds(s.data).size > 1
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonsRow>
|
<ButtonsRow>
|
||||||
|
@ -107,17 +117,6 @@ function ShapesFunctions() {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
|
||||||
bp={breakpoints}
|
|
||||||
disabled={!hasSelection}
|
|
||||||
size="small"
|
|
||||||
onClick={handleHide}
|
|
||||||
>
|
|
||||||
<Tooltip label="Toogle Hidden">
|
|
||||||
{isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
|
|
||||||
</Tooltip>
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={breakpoints}
|
bp={breakpoints}
|
||||||
disabled={!hasSelection}
|
disabled={!hasSelection}
|
||||||
|
@ -125,7 +124,7 @@ function ShapesFunctions() {
|
||||||
onClick={handleLock}
|
onClick={handleLock}
|
||||||
>
|
>
|
||||||
<Tooltip label="Toogle Locked">
|
<Tooltip label="Toogle Locked">
|
||||||
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
|
{isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon opacity={0.4} />}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
|
@ -136,7 +135,18 @@ function ShapesFunctions() {
|
||||||
onClick={handleAspectLock}
|
onClick={handleAspectLock}
|
||||||
>
|
>
|
||||||
<Tooltip label="Toogle Aspect Ratio Lock">
|
<Tooltip label="Toogle Aspect Ratio Lock">
|
||||||
{isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
|
<AspectRatioIcon opacity={isAllAspectLocked ? 1 : 0.4} />
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
bp={breakpoints}
|
||||||
|
disabled={!isAllGrouped && !hasMultipleSelection}
|
||||||
|
size="small"
|
||||||
|
onClick={isAllGrouped ? handleUngroup : handleGroup}
|
||||||
|
>
|
||||||
|
<Tooltip label="Group">
|
||||||
|
<GroupIcon opacity={isAllGrouped ? 1 : 0.4} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ButtonsRow>
|
</ButtonsRow>
|
||||||
|
@ -201,16 +211,3 @@ function ShapesFunctions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(ShapesFunctions)
|
export default memo(ShapesFunctions)
|
||||||
|
|
||||||
const ButtonsRow = styled('div', {
|
|
||||||
position: 'relative',
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
outline: 'none',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
padding: 4,
|
|
||||||
})
|
|
||||||
|
|
|
@ -2,18 +2,16 @@ import styled from 'styles'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import * as Panel from 'components/panel'
|
import * as Panel from 'components/panel'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { IconButton } from 'components/shared'
|
import { IconButton, ButtonsRow } from 'components/shared'
|
||||||
import { ChevronDown, X } from 'react-feather'
|
import { ChevronDown, X } from 'react-feather'
|
||||||
import ShapesFunctions from './shapes-functions'
|
import ShapesFunctions from './shapes-functions'
|
||||||
import AlignDistribute from './align-distribute'
|
import AlignDistribute from './align-distribute'
|
||||||
import SizePicker from './size-picker'
|
|
||||||
import DashPicker from './dash-picker'
|
|
||||||
import QuickColorSelect from './quick-color-select'
|
import QuickColorSelect from './quick-color-select'
|
||||||
import ColorPicker from './color-picker'
|
|
||||||
import IsFilledPicker from './is-filled-picker'
|
|
||||||
import QuickSizeSelect from './quick-size-select'
|
import QuickSizeSelect from './quick-size-select'
|
||||||
import QuickdashSelect from './quick-dash-select'
|
import QuickDashSelect from './quick-dash-select'
|
||||||
|
import QuickFillSelect from './quick-fill-select'
|
||||||
import Tooltip from 'components/tooltip'
|
import Tooltip from 'components/tooltip'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
|
||||||
const breakpoints = { '@initial': 'mobile', '@sm': 'small' } as any
|
const breakpoints = { '@initial': 'mobile', '@sm': 'small' } as any
|
||||||
|
|
||||||
|
@ -26,116 +24,70 @@ export default function StylePanel(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StylePanelRoot dir="ltr" ref={rContainer} isOpen={isOpen}>
|
<StylePanelRoot dir="ltr" ref={rContainer} isOpen={isOpen}>
|
||||||
{isOpen ? (
|
<ButtonsRow>
|
||||||
<SelectedShapeStyles />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<QuickColorSelect />
|
<QuickColorSelect />
|
||||||
<QuickSizeSelect />
|
<QuickSizeSelect />
|
||||||
<QuickdashSelect />
|
<QuickDashSelect />
|
||||||
|
<QuickFillSelect />
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={breakpoints}
|
bp={breakpoints}
|
||||||
title="Style"
|
title="Style"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleStylePanelOpen}
|
onClick={handleStylePanelOpen}
|
||||||
>
|
>
|
||||||
<Tooltip label="More">
|
<Tooltip label="More">{isOpen ? <X /> : <ChevronDown />}</Tooltip>
|
||||||
<ChevronDown />
|
|
||||||
</Tooltip>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
</ButtonsRow>
|
||||||
)}
|
{isOpen && <SelectedShapeContent />}
|
||||||
</StylePanelRoot>
|
</StylePanelRoot>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This panel is going to be hard to keep cool, as we're selecting computed
|
function SelectedShapeContent(): JSX.Element {
|
||||||
// information, based on the user's current selection. We might have to keep
|
|
||||||
// track of this data manually within our state.
|
|
||||||
|
|
||||||
function SelectedShapeStyles(): JSX.Element {
|
|
||||||
const selectedShapesCount = useSelector((s) => s.values.selectedIds.length)
|
const selectedShapesCount = useSelector((s) => s.values.selectedIds.length)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel.Layout>
|
<>
|
||||||
<Panel.Header side="right">
|
<hr />
|
||||||
<h3>Style</h3>
|
|
||||||
<IconButton
|
|
||||||
bp={breakpoints}
|
|
||||||
size="small"
|
|
||||||
onClick={handleStylePanelOpen}
|
|
||||||
>
|
|
||||||
<X />
|
|
||||||
</IconButton>
|
|
||||||
</Panel.Header>
|
|
||||||
<Content>
|
|
||||||
<ColorPicker />
|
|
||||||
<IsFilledPicker />
|
|
||||||
<Row>
|
|
||||||
<label htmlFor="size">Size</label>
|
|
||||||
<SizePicker />
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<label htmlFor="dash">Dash</label>
|
|
||||||
<DashPicker />
|
|
||||||
</Row>
|
|
||||||
<ShapesFunctions />
|
<ShapesFunctions />
|
||||||
<AlignDistribute
|
<AlignDistribute
|
||||||
hasTwoOrMore={selectedShapesCount > 1}
|
hasTwoOrMore={selectedShapesCount > 1}
|
||||||
hasThreeOrMore={selectedShapesCount > 2}
|
hasThreeOrMore={selectedShapesCount > 2}
|
||||||
/>
|
/>
|
||||||
</Content>
|
</>
|
||||||
</Panel.Layout>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const StylePanelRoot = styled(Panel.Root, {
|
const StylePanelRoot = styled(motion(Panel.Root), {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 184,
|
width: 'fit-content',
|
||||||
maxWidth: 184,
|
maxWidth: 'fit-content',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
border: '1px solid $panel',
|
border: '1px solid $panel',
|
||||||
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
|
padding: 2,
|
||||||
|
|
||||||
|
'& hr': {
|
||||||
|
marginTop: 2,
|
||||||
|
marginBottom: 2,
|
||||||
|
marginLeft: '-2px',
|
||||||
|
border: 'none',
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: '$brushFill',
|
||||||
|
width: 'calc(100% + 4px)',
|
||||||
|
},
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
isOpen: {
|
isOpen: {
|
||||||
true: {},
|
true: {},
|
||||||
false: {
|
false: {
|
||||||
padding: 2,
|
|
||||||
width: 'fit-content',
|
width: 'fit-content',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const Content = styled(Panel.Content, {
|
|
||||||
padding: 8,
|
|
||||||
})
|
|
||||||
|
|
||||||
const Row = styled('div', {
|
|
||||||
position: 'relative',
|
|
||||||
display: 'flex',
|
|
||||||
width: '100%',
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: '4px 2px 4px 12px',
|
|
||||||
|
|
||||||
'& label': {
|
|
||||||
fontFamily: '$ui',
|
|
||||||
fontWeight: 400,
|
|
||||||
fontSize: '$1',
|
|
||||||
margin: 0,
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
'& > svg': {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -89,7 +89,10 @@ class Clipboard {
|
||||||
|
|
||||||
// Take a snapshot of the element
|
// Take a snapshot of the element
|
||||||
const s = new XMLSerializer()
|
const s = new XMLSerializer()
|
||||||
const svgString = s.serializeToString(svg)
|
const svgString = s
|
||||||
|
.serializeToString(svg)
|
||||||
|
.replaceAll(' ', '')
|
||||||
|
.replaceAll(/((\s|")[0-9]*\.[0-9]{2})([0-9]*)(\b|"|\))/g, '$1')
|
||||||
|
|
||||||
// Copy to clipboard!
|
// Copy to clipboard!
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Command from './command'
|
import Command from './command'
|
||||||
import history from '../history'
|
import history from '../history'
|
||||||
import { Data, GroupShape, ShapeType } from 'types'
|
import { Data, GroupShape, ShapeType } from 'types'
|
||||||
import { deepClone, getCommonBounds } from 'utils'
|
import { deepClone, getCommonBounds, uniqueId } from 'utils'
|
||||||
import tld from 'utils/tld'
|
import tld from 'utils/tld'
|
||||||
import { createShape, getShapeUtils } from 'state/shape-utils'
|
import { createShape, getShapeUtils } from 'state/shape-utils'
|
||||||
import commands from '.'
|
import commands from '.'
|
||||||
|
@ -23,6 +23,7 @@ export default function groupCommand(data: Data): void {
|
||||||
// Do we need to ungroup the selected shapes shapes, rather than group them?
|
// Do we need to ungroup the selected shapes shapes, rather than group them?
|
||||||
if (isAllSameParent && initialShapes[0]?.parentId !== currentPageId) {
|
if (isAllSameParent && initialShapes[0]?.parentId !== currentPageId) {
|
||||||
const parent = tld.getShape(data, initialShapes[0]?.parentId) as GroupShape
|
const parent = tld.getShape(data, initialShapes[0]?.parentId) as GroupShape
|
||||||
|
|
||||||
if (parent.children.length === initialShapes.length) {
|
if (parent.children.length === initialShapes.length) {
|
||||||
commands.ungroup(data)
|
commands.ungroup(data)
|
||||||
return
|
return
|
||||||
|
@ -62,6 +63,7 @@ export default function groupCommand(data: Data): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const newGroupShape = createShape(ShapeType.Group, {
|
const newGroupShape = createShape(ShapeType.Group, {
|
||||||
|
id: uniqueId(),
|
||||||
parentId: newGroupParentId,
|
parentId: newGroupParentId,
|
||||||
point: [commonBounds.minX, commonBounds.minY],
|
point: [commonBounds.minX, commonBounds.minY],
|
||||||
size: [commonBounds.width, commonBounds.height],
|
size: [commonBounds.width, commonBounds.height],
|
||||||
|
|
|
@ -37,12 +37,6 @@ const strokeWidths = {
|
||||||
[SizeStyle.Large]: 8,
|
[SizeStyle.Large]: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
const dashArrays = {
|
|
||||||
[DashStyle.Solid]: () => [1],
|
|
||||||
[DashStyle.Dashed]: (sw: number) => [sw * 2, sw * 4],
|
|
||||||
[DashStyle.Dotted]: (sw: number) => [0, sw * 3],
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontSizes = {
|
const fontSizes = {
|
||||||
[SizeStyle.Small]: 24,
|
[SizeStyle.Small]: 24,
|
||||||
[SizeStyle.Medium]: 48,
|
[SizeStyle.Medium]: 48,
|
||||||
|
@ -54,13 +48,6 @@ export function getStrokeWidth(size: SizeStyle): number {
|
||||||
return strokeWidths[size]
|
return strokeWidths[size]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStrokeDashArray(
|
|
||||||
dash: DashStyle,
|
|
||||||
strokeWidth: number
|
|
||||||
): number[] {
|
|
||||||
return dashArrays[dash](strokeWidth)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFontSize(size: SizeStyle): number {
|
export function getFontSize(size: SizeStyle): number {
|
||||||
return fontSizes[size]
|
return fontSizes[size]
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,27 +127,22 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
|
|
||||||
if (isStraightLine) {
|
if (isStraightLine) {
|
||||||
const straight_sw =
|
const straight_sw =
|
||||||
strokeWidth * (style.dash === DashStyle.Solid && bend === 0 ? 1 : 1.618)
|
strokeWidth *
|
||||||
|
(style.dash === DashStyle.Draw && bend === 0 ? 0.9 : 1.618)
|
||||||
|
|
||||||
if (shape.style.dash === DashStyle.Solid && !pathCache.has(shape)) {
|
if (shape.style.dash === DashStyle.Draw && !pathCache.has(shape)) {
|
||||||
renderFreehandArrowShaft(shape)
|
renderFreehandArrowShaft(shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
const path =
|
const path =
|
||||||
shape.style.dash === DashStyle.Solid
|
shape.style.dash === DashStyle.Draw
|
||||||
? pathCache.get(shape)
|
? pathCache.get(shape)
|
||||||
: 'M' + start.point + 'L' + end.point
|
: 'M' + start.point + 'L' + end.point
|
||||||
|
|
||||||
const { strokeDasharray, strokeDashoffset } =
|
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||||
shape.style.dash === DashStyle.Solid
|
|
||||||
? {
|
|
||||||
strokeDasharray: 'none',
|
|
||||||
strokeDashoffset: '0',
|
|
||||||
}
|
|
||||||
: getPerfectDashProps(
|
|
||||||
arrowDist,
|
arrowDist,
|
||||||
sw,
|
sw,
|
||||||
shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed',
|
shape.style.dash,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -182,13 +177,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
|
|
||||||
const path = getArrowArcPath(start, end, circle, bend)
|
const path = getArrowArcPath(start, end, circle, bend)
|
||||||
|
|
||||||
const { strokeDasharray, strokeDashoffset } =
|
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||||
shape.style.dash === DashStyle.Solid
|
|
||||||
? {
|
|
||||||
strokeDasharray: 'none',
|
|
||||||
strokeDashoffset: '0',
|
|
||||||
}
|
|
||||||
: getPerfectDashProps(
|
|
||||||
getArcLength(
|
getArcLength(
|
||||||
[circle[0], circle[1]],
|
[circle[0], circle[1]],
|
||||||
circle[2],
|
circle[2],
|
||||||
|
@ -196,7 +185,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
||||||
end.point
|
end.point
|
||||||
) - 1,
|
) - 1,
|
||||||
sw,
|
sw,
|
||||||
shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed',
|
shape.style.dash,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -520,26 +509,23 @@ function renderFreehandArrowShaft(shape: ArrowShape) {
|
||||||
|
|
||||||
const strokeWidth = +getShapeStyle(style).strokeWidth * 2
|
const strokeWidth = +getShapeStyle(style).strokeWidth * 2
|
||||||
|
|
||||||
const m = vec.add(
|
const st = Math.abs(getRandom())
|
||||||
vec.lrp(start.point, end.point, 0.25 + Math.abs(getRandom()) / 2),
|
|
||||||
[getRandom() * strokeWidth, getRandom() * strokeWidth]
|
|
||||||
)
|
|
||||||
|
|
||||||
const stroke = getStroke(
|
const stroke = getStroke(
|
||||||
[
|
[
|
||||||
...vec.pointsBetween(start.point, m),
|
start.point,
|
||||||
...vec.pointsBetween(m, end.point),
|
...vec.pointsBetween(start.point, end.point),
|
||||||
end.point,
|
end.point,
|
||||||
end.point,
|
end.point,
|
||||||
end.point,
|
end.point,
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
size: strokeWidth * 0.82,
|
size: strokeWidth / 2,
|
||||||
thinning: 0.6,
|
thinning: 0.5 + getRandom() * 0.3,
|
||||||
easing: (t) => t * t * t * t,
|
easing: (t) => t * t,
|
||||||
end: { taper: 4 + getRandom() * 4 },
|
end: { taper: 1 },
|
||||||
start: { taper: 4 + getRandom() * 4 },
|
start: { taper: 1 + 32 * (st * st * st) },
|
||||||
simulatePressure: false,
|
simulatePressure: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -570,10 +556,13 @@ function getArrowHeadPoints(shape: ArrowShape, point: number[], angle = 0) {
|
||||||
const getRandom = rng(shape.id)
|
const getRandom = rng(shape.id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
left: vec.add(point, vec.rot(v, Math.PI / 6 + (Math.PI / 8) * getRandom())),
|
left: vec.add(
|
||||||
|
point,
|
||||||
|
vec.rot(v, Math.PI / 6 + (Math.PI / 12) * getRandom())
|
||||||
|
),
|
||||||
right: vec.add(
|
right: vec.add(
|
||||||
point,
|
point,
|
||||||
vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
|
vec.rot(v, -(Math.PI / 6) + (Math.PI / 12) * getRandom())
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
||||||
const rx = Math.max(0, radiusX - strokeWidth / 2)
|
const rx = Math.max(0, radiusX - strokeWidth / 2)
|
||||||
const ry = Math.max(0, radiusY - strokeWidth / 2)
|
const ry = Math.max(0, radiusY - strokeWidth / 2)
|
||||||
|
|
||||||
if (style.dash === DashStyle.Solid) {
|
if (style.dash === DashStyle.Draw) {
|
||||||
if (!pathCache.has(shape)) {
|
if (!pathCache.has(shape)) {
|
||||||
renderPath(shape)
|
renderPath(shape)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
|
||||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||||
perimeter,
|
perimeter,
|
||||||
strokeWidth * 1.618,
|
strokeWidth * 1.618,
|
||||||
shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed',
|
shape.style.dash,
|
||||||
4
|
4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
||||||
const styles = getShapeStyle(style)
|
const styles = getShapeStyle(style)
|
||||||
const strokeWidth = +styles.strokeWidth
|
const strokeWidth = +styles.strokeWidth
|
||||||
|
|
||||||
if (style.dash === DashStyle.Solid) {
|
if (style.dash === DashStyle.Draw) {
|
||||||
if (!pathCache.has(shape.size)) {
|
if (!pathCache.has(shape.size)) {
|
||||||
renderPath(shape)
|
renderPath(shape)
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
||||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||||
length,
|
length,
|
||||||
sw,
|
sw,
|
||||||
shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed'
|
shape.style.dash
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -751,7 +751,9 @@ const state = createState({
|
||||||
{
|
{
|
||||||
if: 'isToolLocked',
|
if: 'isToolLocked',
|
||||||
to: 'dot.creating',
|
to: 'dot.creating',
|
||||||
else: { to: 'selecting' },
|
else: {
|
||||||
|
to: 'selecting',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
CANCELLED: {
|
CANCELLED: {
|
||||||
|
@ -1152,20 +1154,6 @@ const state = createState({
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// Networked Room
|
// Networked Room
|
||||||
setRtStatus(data, payload: { id: string; status: string }) {
|
|
||||||
const { status } = payload
|
|
||||||
|
|
||||||
if (!data.room) {
|
|
||||||
data.room = {
|
|
||||||
id: null,
|
|
||||||
status: '',
|
|
||||||
peers: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.room.peers = {}
|
|
||||||
data.room.status = status
|
|
||||||
},
|
|
||||||
addRtShape(data, payload: { pageId: string; shape: Shape }) {
|
addRtShape(data, payload: { pageId: string; shape: Shape }) {
|
||||||
const { pageId, shape } = payload
|
const { pageId, shape } = payload
|
||||||
// What if the page is in storage?
|
// What if the page is in storage?
|
||||||
|
@ -1264,6 +1252,7 @@ const state = createState({
|
||||||
})
|
})
|
||||||
|
|
||||||
const siblings = tld.getChildren(data, shape.parentId)
|
const siblings = tld.getChildren(data, shape.parentId)
|
||||||
|
|
||||||
const childIndex = siblings.length
|
const childIndex = siblings.length
|
||||||
? siblings[siblings.length - 1].childIndex + 1
|
? siblings[siblings.length - 1].childIndex + 1
|
||||||
: 1
|
: 1
|
||||||
|
|
1
types.ts
1
types.ts
|
@ -108,6 +108,7 @@ export enum SizeStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DashStyle {
|
export enum DashStyle {
|
||||||
|
Draw = 'Draw',
|
||||||
Solid = 'Solid',
|
Solid = 'Solid',
|
||||||
Dashed = 'Dashed',
|
Dashed = 'Dashed',
|
||||||
Dotted = 'Dotted',
|
Dotted = 'Dotted',
|
||||||
|
|
|
@ -317,6 +317,7 @@ export default class StateUtils {
|
||||||
|
|
||||||
static getTopParentId(data: Data, id: string): string {
|
static getTopParentId(data: Data, id: string): string {
|
||||||
const shape = this.getPage(data).shapes[id]
|
const shape = this.getPage(data).shapes[id]
|
||||||
|
|
||||||
return shape.parentId === data.currentPageId ||
|
return shape.parentId === data.currentPageId ||
|
||||||
shape.parentId === data.currentParentId
|
shape.parentId === data.currentParentId
|
||||||
? id
|
? id
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Bounds, Edge, Corner, BezierCurveSegment } from 'types'
|
import { Bounds, Edge, Corner, BezierCurveSegment, DashStyle } from 'types'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import vec from './vec'
|
import vec from './vec'
|
||||||
import _isMobile from 'ismobilejs'
|
import _isMobile from 'ismobilejs'
|
||||||
|
@ -421,7 +421,7 @@ export function getArcLength(
|
||||||
export function getPerfectDashProps(
|
export function getPerfectDashProps(
|
||||||
length: number,
|
length: number,
|
||||||
strokeWidth: number,
|
strokeWidth: number,
|
||||||
style: 'dashed' | 'dotted' = 'dashed',
|
style: DashStyle,
|
||||||
snap = 1
|
snap = 1
|
||||||
): {
|
): {
|
||||||
strokeDasharray: string
|
strokeDasharray: string
|
||||||
|
@ -431,7 +431,12 @@ export function getPerfectDashProps(
|
||||||
let strokeDashoffset: string
|
let strokeDashoffset: string
|
||||||
let ratio: number
|
let ratio: number
|
||||||
|
|
||||||
if (style === 'dashed') {
|
if (style === DashStyle.Solid || style === DashStyle.Draw) {
|
||||||
|
return {
|
||||||
|
strokeDasharray: 'none',
|
||||||
|
strokeDashoffset: 'none',
|
||||||
|
}
|
||||||
|
} else if (style === DashStyle.Dashed) {
|
||||||
dashLength = strokeWidth * 2
|
dashLength = strokeWidth * 2
|
||||||
ratio = 1
|
ratio = 1
|
||||||
strokeDashoffset = (dashLength / 2).toString()
|
strokeDashoffset = (dashLength / 2).toString()
|
||||||
|
@ -1726,7 +1731,7 @@ export function getSvgPathFromStroke(stroke: number[][]): string {
|
||||||
)
|
)
|
||||||
|
|
||||||
d.push('Z')
|
d.push('Z')
|
||||||
return d.join(' ')
|
return d.join(' ').replaceAll(/(\s[0-9]*\.[0-9]{2})([0-9]*)\b/g, '$1')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function debounce<T extends (...args: unknown[]) => unknown>(
|
export function debounce<T extends (...args: unknown[]) => unknown>(
|
||||||
|
|
Loading…
Reference in a new issue