hoist opacity out of props (#1526)
This change hoists opacity out of props and changes it to a number instead of an enum. The change to a number is to make tldraw more flexible for library consumers who might want more expressivity with opacity than our 5 possible values allow. the tldraw editor will now happily respect any opacity between 0 and 1. The limit to our supported values is enforced only in the UI. I think this is limited enough that it's a reasonable tradeoff between in-app simplicity and giving external developers the flexibility they need. There's a new `opacityForNextShape` property on the instance. This works exactly the same way as propsForNextShape does, except... it's just for opacity. With this, there should be no user-facing changes to how opacity works in tldraw. There are also new `opacity`/`setOpacity` APIs in the editor that work with it/selections similar to how props do. @ds300 do you mind reviewing the migrations here? ### Change Type - [x] `major` — Breaking Change ### Test Plan - [x] Unit Tests - [ ] Webdriver tests ### Release Notes [internal only for now]
This commit is contained in:
parent
355ed1de72
commit
f2d8fae6ea
69 changed files with 478 additions and 289 deletions
|
@ -1,9 +1,8 @@
|
|||
import { TLBaseShape, TLOpacityType } from '@tldraw/tldraw'
|
||||
import { TLBaseShape } from '@tldraw/tldraw'
|
||||
|
||||
export type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{
|
||||
opacity: TLOpacityType // necessary for all shapes at the moment, others can be whatever you want!
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
|
|||
// Default props — used for shapes created with the tool
|
||||
override defaultProps(): CardShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 300,
|
||||
h: 300,
|
||||
}
|
||||
|
|
|
@ -178,8 +178,6 @@ export abstract class BaseBoxShapeTool extends StateNode {
|
|||
static initial: string;
|
||||
// (undocumented)
|
||||
abstract shapeType: string;
|
||||
// (undocumented)
|
||||
styles: ("align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "opacity" | "size" | "spline" | "verticalAlign")[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -623,6 +621,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
description: string;
|
||||
}>;
|
||||
get onlySelectedShape(): null | TLShape;
|
||||
// (undocumented)
|
||||
get opacity(): null | number;
|
||||
get openMenus(): string[];
|
||||
packShapes(ids?: TLShapeId[], padding?: number): this;
|
||||
get pages(): TLPage[];
|
||||
|
@ -717,6 +717,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
setHoveredId(id?: null | TLShapeId): this;
|
||||
setInstancePageState(partial: Partial<TLInstancePageState>, ephemeral?: boolean): void;
|
||||
setLocale(locale: string): void;
|
||||
setOpacity(opacity: number, ephemeral?: boolean, squashing?: boolean): this;
|
||||
// (undocumented)
|
||||
setPenMode(isPenMode: boolean): this;
|
||||
// @internal (undocumented)
|
||||
|
@ -915,7 +916,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
fill: "none" | "pattern" | "semi" | "solid";
|
||||
dash: "dashed" | "dotted" | "draw" | "solid";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
|
@ -931,6 +931,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
@ -944,7 +945,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
fill: "none" | "pattern" | "semi" | "solid";
|
||||
dash: "dashed" | "dotted" | "draw" | "solid";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
|
@ -960,6 +960,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
@ -975,6 +976,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | {
|
||||
|
@ -988,6 +990,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
@ -1702,7 +1705,6 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
|
||||
url: string;
|
||||
text: string;
|
||||
};
|
||||
|
@ -1713,6 +1715,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
@ -1725,7 +1728,6 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
|
||||
url: string;
|
||||
text: string;
|
||||
};
|
||||
|
@ -1736,6 +1738,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
@ -1962,6 +1965,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
// (undocumented)
|
||||
path: Computed<string>;
|
||||
// (undocumented)
|
||||
shapeType?: string;
|
||||
// (undocumented)
|
||||
readonly styles: TLStyleType[];
|
||||
// (undocumented)
|
||||
transition(id: string, info: any): this;
|
||||
|
@ -2024,6 +2029,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
props: TLTextShapeProps;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
|
@ -2038,7 +2044,6 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
|
||||
text: string;
|
||||
scale: number;
|
||||
autoSize: boolean;
|
||||
|
@ -2048,6 +2053,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
index: string;
|
||||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
|
|
@ -217,13 +217,6 @@ export const STYLES: TLStyleCollections = {
|
|||
{ id: 'l', type: 'size', icon: 'size-large' },
|
||||
{ id: 'xl', type: 'size', icon: 'size-extra-large' },
|
||||
],
|
||||
opacity: [
|
||||
{ id: '0.1', type: 'opacity', icon: 'color' },
|
||||
{ id: '0.25', type: 'opacity', icon: 'color' },
|
||||
{ id: '0.5', type: 'opacity', icon: 'color' },
|
||||
{ id: '0.75', type: 'opacity', icon: 'color' },
|
||||
{ id: '1', type: 'opacity', icon: 'color' },
|
||||
],
|
||||
font: [
|
||||
{ id: 'draw', type: 'font', icon: 'font-draw' },
|
||||
{ id: 'sans', type: 'font', icon: 'font-sans' },
|
||||
|
|
|
@ -1151,6 +1151,42 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return next
|
||||
}
|
||||
|
||||
@computed get opacity(): number | null {
|
||||
if (this.isIn('select') && this.selectedIds.length > 0) {
|
||||
const shapesToCheck: TLShape[] = []
|
||||
const addShape = (shapeId: TLShapeId) => {
|
||||
const shape = this.getShapeById(shapeId)
|
||||
if (!shape) return
|
||||
// For groups, ignore the opacity of the group shape and instead include
|
||||
// the opacity of the group's children. These are the shapes that would have
|
||||
// their opacity changed if the user called `setOpacity` on the current selection.
|
||||
if (shape.type === 'group') {
|
||||
for (const childId of this.getSortedChildIds(shape.id)) {
|
||||
addShape(childId)
|
||||
}
|
||||
} else {
|
||||
shapesToCheck.push(shape)
|
||||
}
|
||||
}
|
||||
for (const shapeId of this.selectedIds) {
|
||||
addShape(shapeId)
|
||||
}
|
||||
|
||||
let opacity: number | null = null
|
||||
for (const shape of shapesToCheck) {
|
||||
if (opacity === null) {
|
||||
opacity = shape.opacity
|
||||
} else if (opacity !== shape.opacity) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return opacity
|
||||
} else {
|
||||
return this.instanceState.opacityForNextShape
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of all of the shapes on the current page.
|
||||
*
|
||||
|
@ -2242,8 +2278,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const shape = this.getShapeById(id)
|
||||
if (!shape) return
|
||||
|
||||
// todo: move opacity to a property of shape, rather than a property of props
|
||||
let opacity = (+(shape.props as { opacity: string }).opacity ?? 1) * parentOpacity
|
||||
let opacity = shape.opacity * parentOpacity
|
||||
let isShapeErasing = false
|
||||
|
||||
if (!isAncestorErasing && erasingIdsSet?.has(id)) {
|
||||
|
@ -4675,7 +4710,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// We then look up each key in the tab state's props; and if it's there,
|
||||
// we use the value from the tab state's props instead of the default.
|
||||
// Note that props will never include opacity.
|
||||
const { propsForNextShape } = this.instanceState
|
||||
const { propsForNextShape, opacityForNextShape } = this.instanceState
|
||||
for (const key in initialProps) {
|
||||
if (key in propsForNextShape) {
|
||||
if (key === 'url') continue
|
||||
|
@ -4693,6 +4728,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
).create({
|
||||
...partial,
|
||||
index,
|
||||
opacity: partial.opacity ?? opacityForNextShape,
|
||||
parentId: partial.parentId ?? focusLayerId,
|
||||
props: 'props' in partial ? { ...initialProps, ...partial.props } : initialProps,
|
||||
})
|
||||
|
@ -7757,11 +7793,75 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current opacity. This will effect any selected shapes, or the
|
||||
* next-created shape.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.setOpacity(0.5)
|
||||
* editor.setOpacity(0.5, true)
|
||||
* ```
|
||||
*
|
||||
* @param opacity - The opacity to set. Must be a number between 0 and 1
|
||||
* inclusive.
|
||||
* @param ephemeral - Whether the opacity change is ephemeral. Ephemeral
|
||||
* changes don't get added to the undo/redo stack. Defaults to false.
|
||||
* @param squashing - Whether the opacity change will be squashed into the
|
||||
* existing history entry rather than creating a new one. Defaults to false.
|
||||
*/
|
||||
setOpacity(opacity: number, ephemeral = false, squashing = false) {
|
||||
this.history.batch(() => {
|
||||
if (this.isIn('select')) {
|
||||
const {
|
||||
pageState: { selectedIds },
|
||||
} = this
|
||||
|
||||
const shapesToUpdate: TLShape[] = []
|
||||
|
||||
// We can have many deep levels of grouped shape
|
||||
// Making a recursive function to look through all the levels
|
||||
const addShapeById = (id: TLShape['id']) => {
|
||||
const shape = this.getShapeById(id)
|
||||
if (!shape) return
|
||||
if (this.isShapeOfType(shape, GroupShapeUtil)) {
|
||||
const childIds = this.getSortedChildIds(id)
|
||||
for (const childId of childIds) {
|
||||
addShapeById(childId)
|
||||
}
|
||||
} else {
|
||||
shapesToUpdate.push(shape)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedIds.length > 0) {
|
||||
for (const id of selectedIds) {
|
||||
addShapeById(id)
|
||||
}
|
||||
|
||||
this.updateShapes(
|
||||
shapesToUpdate.map((shape) => {
|
||||
return {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
opacity,
|
||||
}
|
||||
}),
|
||||
ephemeral
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.updateInstanceState({ opacityForNextShape: opacity }, ephemeral, squashing)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current props (generally styles).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* editor.setProp('color', 'red')
|
||||
* editor.setProp('color', 'red', true)
|
||||
|
@ -7769,67 +7869,43 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @param key - The key to set.
|
||||
* @param value - The value to set.
|
||||
* @param ephemeral - Whether the style is ephemeral. Defaults to false.
|
||||
* @param ephemeral - Whether the style change is ephemeral. Ephemeral
|
||||
* changes don't get added to the undo/redo stack. Defaults to false.
|
||||
* @param squashing - Whether the style change will be squashed into the
|
||||
* existing history entry rather than creating a new one. Defaults to false.
|
||||
* @public
|
||||
*/
|
||||
setProp(key: TLShapeProp, value: any, ephemeral = false, squashing = false) {
|
||||
const children: (TLShape | undefined)[] = []
|
||||
// We can have many deep levels of grouped shape
|
||||
// Making a recursive function to look through all the levels
|
||||
const getChildProp = (id: TLShape['id']) => {
|
||||
const childIds = this.getSortedChildIds(id)
|
||||
for (const childId of childIds) {
|
||||
const childShape = this.getShapeById(childId)
|
||||
if (childShape?.type === 'group') {
|
||||
getChildProp(childShape.id)
|
||||
}
|
||||
children.push(childShape)
|
||||
}
|
||||
}
|
||||
|
||||
this.history.batch(() => {
|
||||
this.updateInstanceState(
|
||||
{
|
||||
propsForNextShape: setPropsForNextShape(this.instanceState.propsForNextShape, {
|
||||
[key]: value,
|
||||
}),
|
||||
},
|
||||
ephemeral,
|
||||
squashing
|
||||
)
|
||||
|
||||
if (this.isIn('select')) {
|
||||
const {
|
||||
pageState: { selectedIds },
|
||||
} = this
|
||||
|
||||
if (selectedIds.length > 0) {
|
||||
const shapes = compact(
|
||||
selectedIds.map((id) => {
|
||||
const shape = this.getShapeById(id)
|
||||
if (shape?.type === 'group') {
|
||||
const childIds = this.getSortedChildIds(shape.id)
|
||||
for (const childId of childIds) {
|
||||
const childShape = this.getShapeById(childId)
|
||||
if (childShape?.type === 'group') {
|
||||
getChildProp(childShape.id)
|
||||
}
|
||||
children.push(childShape)
|
||||
}
|
||||
return children
|
||||
} else {
|
||||
return shape
|
||||
const shapesToUpdate: TLShape[] = []
|
||||
|
||||
// We can have many deep levels of grouped shape
|
||||
// Making a recursive function to look through all the levels
|
||||
const addShapeById = (id: TLShape['id']) => {
|
||||
const shape = this.getShapeById(id)
|
||||
if (!shape) return
|
||||
if (this.isShapeOfType(shape, GroupShapeUtil)) {
|
||||
const childIds = this.getSortedChildIds(id)
|
||||
for (const childId of childIds) {
|
||||
addShapeById(childId)
|
||||
}
|
||||
})
|
||||
)
|
||||
.flat()
|
||||
.filter(
|
||||
(shape) =>
|
||||
shape!.props[key as keyof TLShape['props']] !== undefined && shape?.type !== 'group'
|
||||
) as TLShape[]
|
||||
} else if (shape!.props[key as keyof TLShape['props']] !== undefined) {
|
||||
shapesToUpdate.push(shape)
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of selectedIds) {
|
||||
addShapeById(id)
|
||||
}
|
||||
|
||||
this.updateShapes(
|
||||
shapes.map((shape) => {
|
||||
shapesToUpdate.map((shape) => {
|
||||
const props = { ...shape.props, [key]: value }
|
||||
if (key === 'color' && 'labelColor' in props) {
|
||||
props.labelColor = 'black'
|
||||
|
@ -7844,10 +7920,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
ephemeral
|
||||
)
|
||||
|
||||
if (key !== 'color' && key !== 'opacity') {
|
||||
if (key !== 'color') {
|
||||
const changes: TLShapePartial[] = []
|
||||
|
||||
for (const shape of shapes) {
|
||||
for (const shape of shapesToUpdate) {
|
||||
const currentShape = this.getShapeById(shape.id)
|
||||
if (!currentShape) continue
|
||||
const util = this.getShapeUtil(currentShape)
|
||||
|
@ -8903,9 +8979,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
index: highestIndex,
|
||||
x,
|
||||
y,
|
||||
props: {
|
||||
opacity: '1',
|
||||
},
|
||||
opacity: 1,
|
||||
props: {},
|
||||
},
|
||||
])
|
||||
this.reparentShapesById(sortedShapeIds, groupId)
|
||||
|
|
|
@ -69,7 +69,6 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
|
||||
override defaultProps(): TLArrowShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
dash: 'draw',
|
||||
size: 'm',
|
||||
fill: 'none',
|
||||
|
|
|
@ -27,7 +27,6 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
|
|||
|
||||
override defaultProps(): TLBookmarkShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
url: '',
|
||||
w: DEFAULT_BOOKMARK_WIDTH,
|
||||
h: DEFAULT_BOOKMARK_HEIGHT,
|
||||
|
|
|
@ -36,7 +36,6 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
fill: 'none',
|
||||
dash: 'draw',
|
||||
size: 'm',
|
||||
opacity: '1',
|
||||
isComplete: false,
|
||||
isClosed: false,
|
||||
isPen: false,
|
||||
|
|
|
@ -42,7 +42,6 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
|
|||
|
||||
override defaultProps(): TLEmbedShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 300,
|
||||
h: 300,
|
||||
url: '',
|
||||
|
|
|
@ -18,7 +18,7 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
|||
override canEdit = () => true
|
||||
|
||||
override defaultProps(): TLFrameShape['props'] {
|
||||
return { opacity: '1', w: 160 * 2, h: 90 * 2, name: '' }
|
||||
return { w: 160 * 2, h: 90 * 2, name: '' }
|
||||
}
|
||||
|
||||
override render(shape: TLFrameShape) {
|
||||
|
@ -56,7 +56,6 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
|||
rect.setAttribute('width', shape.props.w.toString())
|
||||
rect.setAttribute('height', shape.props.h.toString())
|
||||
rect.setAttribute('fill', colors.solid)
|
||||
rect.setAttribute('opacity', shape.props.opacity)
|
||||
rect.setAttribute('stroke', colors.fill.black)
|
||||
rect.setAttribute('stroke-width', '1')
|
||||
rect.setAttribute('rx', '1')
|
||||
|
|
|
@ -55,7 +55,6 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
fill: 'none',
|
||||
dash: 'draw',
|
||||
size: 'm',
|
||||
opacity: '1',
|
||||
font: 'draw',
|
||||
text: '',
|
||||
align: 'middle',
|
||||
|
|
|
@ -16,7 +16,7 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|||
canBind = () => false
|
||||
|
||||
defaultProps(): TLGroupShape['props'] {
|
||||
return { opacity: '1' }
|
||||
return {}
|
||||
}
|
||||
|
||||
getBounds(shape: TLGroupShape): Box2d {
|
||||
|
|
|
@ -27,7 +27,6 @@ export class HighlightShapeUtil extends ShapeUtil<TLHighlightShape> {
|
|||
segments: [],
|
||||
color: 'black',
|
||||
size: 'm',
|
||||
opacity: '1',
|
||||
isComplete: false,
|
||||
isPen: false,
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
|
|||
|
||||
override defaultProps(): TLImageShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 100,
|
||||
h: 100,
|
||||
assetId: null,
|
||||
|
|
|
@ -36,7 +36,6 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
|
||||
override defaultProps(): TLLineShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
dash: 'draw',
|
||||
size: 'm',
|
||||
color: 'black',
|
||||
|
|
|
@ -5,6 +5,7 @@ Object {
|
|||
"id": "shape:line1",
|
||||
"index": "a1",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "page:id50",
|
||||
"props": Object {
|
||||
"color": "black",
|
||||
|
@ -27,7 +28,6 @@ Object {
|
|||
"y": 0,
|
||||
},
|
||||
},
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"spline": "line",
|
||||
},
|
||||
|
|
|
@ -21,7 +21,6 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
|
||||
defaultProps(): TLNoteShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
color: 'black',
|
||||
size: 'm',
|
||||
text: '',
|
||||
|
|
|
@ -26,7 +26,6 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
|
||||
defaultProps(): TLTextShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
color: 'black',
|
||||
size: 'm',
|
||||
w: 8,
|
||||
|
|
|
@ -18,7 +18,6 @@ export class VideoShapeUtil extends BaseBoxShapeUtil<TLVideoShape> {
|
|||
|
||||
override defaultProps(): TLVideoShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 100,
|
||||
h: 100,
|
||||
assetId: null,
|
||||
|
|
|
@ -12,7 +12,6 @@ export class ArrowShapeTool extends StateNode {
|
|||
|
||||
styles = [
|
||||
'color',
|
||||
'opacity',
|
||||
'dash',
|
||||
'size',
|
||||
'arrowheadStart',
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../StateNode'
|
||||
import { Idle } from './children/Idle'
|
||||
import { Pointing } from './children/Pointing'
|
||||
|
@ -10,6 +9,4 @@ export abstract class BaseBoxShapeTool extends StateNode {
|
|||
static children = () => [Idle, Pointing]
|
||||
|
||||
abstract shapeType: string
|
||||
|
||||
styles = ['opacity'] as TLStyleType[]
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ export class DrawShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Drawing]
|
||||
|
||||
styles = ['color', 'opacity', 'dash', 'fill', 'size'] as TLStyleType[]
|
||||
styles = ['color', 'dash', 'fill', 'size'] as TLStyleType[]
|
||||
shapeType = 'draw'
|
||||
|
||||
onExit = () => {
|
||||
const drawingState = this.children!['drawing'] as Drawing
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { BaseBoxShapeTool } from '../BaseBoxShapeTool/BaseBoxShapeTool'
|
||||
|
||||
export class FrameShapeTool extends BaseBoxShapeTool {
|
||||
|
@ -6,6 +5,4 @@ export class FrameShapeTool extends BaseBoxShapeTool {
|
|||
static initial = 'idle'
|
||||
|
||||
shapeType = 'frame'
|
||||
|
||||
styles = ['opacity'] as TLStyleType[]
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ export class GeoShapeTool extends StateNode {
|
|||
|
||||
styles = [
|
||||
'color',
|
||||
'opacity',
|
||||
'dash',
|
||||
'fill',
|
||||
'size',
|
||||
|
@ -20,4 +19,5 @@ export class GeoShapeTool extends StateNode {
|
|||
'align',
|
||||
'verticalAlign',
|
||||
] as TLStyleType[]
|
||||
shapeType = 'geo'
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ export class HighlightShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Drawing]
|
||||
|
||||
styles = ['color', 'opacity', 'size'] as TLStyleType[]
|
||||
styles = ['color', 'size'] as TLStyleType[]
|
||||
shapeType = 'highlight'
|
||||
|
||||
onExit = () => {
|
||||
const drawingState = this.children!['drawing'] as Drawing
|
||||
|
|
|
@ -11,5 +11,5 @@ export class LineShapeTool extends StateNode {
|
|||
|
||||
shapeType = 'line'
|
||||
|
||||
styles = ['color', 'opacity', 'dash', 'size', 'spline'] as TLStyleType[]
|
||||
styles = ['color', 'dash', 'size', 'spline'] as TLStyleType[]
|
||||
}
|
||||
|
|
|
@ -8,5 +8,6 @@ export class NoteShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
styles = ['color', 'opacity', 'size', 'align', 'verticalAlign', 'font'] as TLStyleType[]
|
||||
styles = ['color', 'size', 'align', 'verticalAlign', 'font'] as TLStyleType[]
|
||||
shapeType = 'note'
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export class SelectTool extends StateNode {
|
|||
DraggingHandle,
|
||||
]
|
||||
|
||||
styles = ['color', 'opacity', 'dash', 'fill', 'size'] as TLStyleType[]
|
||||
styles = ['color', 'dash', 'fill', 'size'] as TLStyleType[]
|
||||
|
||||
onExit = () => {
|
||||
if (this.editor.pageState.editingId) {
|
||||
|
|
|
@ -70,6 +70,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
current: Atom<StateNode | undefined>
|
||||
type: TLStateNodeType
|
||||
readonly styles: TLStyleType[] = []
|
||||
shapeType?: string
|
||||
initial?: string
|
||||
children?: Record<string, StateNode>
|
||||
parent: StateNode
|
||||
|
|
|
@ -9,5 +9,6 @@ export class TextShapeTool extends StateNode {
|
|||
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
styles = ['color', 'opacity', 'font', 'align', 'size'] as TLStyleType[]
|
||||
styles = ['color', 'font', 'align', 'size'] as TLStyleType[]
|
||||
shapeType = 'text'
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { PageRecordType, createShapeId } from '@tldraw/tlschema'
|
||||
import { structuredClone } from '@tldraw/utils'
|
||||
import { TestEditor } from './TestEditor'
|
||||
import { TL } from './jsx'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
|
@ -156,6 +157,98 @@ describe('Editor.setProp', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('Editor.opacity', () => {
|
||||
it('should return the current opacity', () => {
|
||||
expect(editor.opacity).toBe(1)
|
||||
editor.setOpacity(0.5)
|
||||
expect(editor.opacity).toBe(0.5)
|
||||
})
|
||||
|
||||
it('should return opacity for a single selected shape', () => {
|
||||
const { A } = editor.createShapesFromJsx(<TL.geo ref="A" opacity={0.3} x={0} y={0} />)
|
||||
editor.setSelectedIds([A])
|
||||
expect(editor.opacity).toBe(0.3)
|
||||
})
|
||||
|
||||
it('should return opacity for multiple selected shapes', () => {
|
||||
const { A, B } = editor.createShapesFromJsx([
|
||||
<TL.geo ref="A" opacity={0.3} x={0} y={0} />,
|
||||
<TL.geo ref="B" opacity={0.3} x={0} y={0} />,
|
||||
])
|
||||
editor.setSelectedIds([A, B])
|
||||
expect(editor.opacity).toBe(0.3)
|
||||
})
|
||||
|
||||
it('should return null when multiple selected shapes have different opacity', () => {
|
||||
const { A, B } = editor.createShapesFromJsx([
|
||||
<TL.geo ref="A" opacity={0.3} x={0} y={0} />,
|
||||
<TL.geo ref="B" opacity={0.5} x={0} y={0} />,
|
||||
])
|
||||
editor.setSelectedIds([A, B])
|
||||
expect(editor.opacity).toBe(null)
|
||||
})
|
||||
|
||||
it('ignores the opacity of groups and returns the opacity of their children', () => {
|
||||
const ids = editor.createShapesFromJsx([
|
||||
<TL.group ref="group" x={0} y={0}>
|
||||
<TL.geo ref="A" opacity={0.3} x={0} y={0} />
|
||||
</TL.group>,
|
||||
])
|
||||
editor.setSelectedIds([ids.group])
|
||||
expect(editor.opacity).toBe(0.3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Editor.setOpacity', () => {
|
||||
it('should set opacity for selected shapes', () => {
|
||||
const ids = editor.createShapesFromJsx([
|
||||
<TL.geo ref="A" opacity={0.3} x={0} y={0} />,
|
||||
<TL.geo ref="B" opacity={0.4} x={0} y={0} />,
|
||||
])
|
||||
|
||||
editor.setSelectedIds([ids.A, ids.B])
|
||||
editor.setOpacity(0.5)
|
||||
|
||||
expect(editor.getShapeById(ids.A)!.opacity).toBe(0.5)
|
||||
expect(editor.getShapeById(ids.B)!.opacity).toBe(0.5)
|
||||
})
|
||||
|
||||
it('should traverse into groups and set opacity in their children', () => {
|
||||
const ids = editor.createShapesFromJsx([
|
||||
<TL.geo ref="boxA" x={0} y={0} />,
|
||||
<TL.group ref="groupA" x={0} y={0}>
|
||||
<TL.geo ref="boxB" x={0} y={0} />
|
||||
<TL.group ref="groupB" x={0} y={0}>
|
||||
<TL.geo ref="boxC" x={0} y={0} />
|
||||
<TL.geo ref="boxD" x={0} y={0} />
|
||||
</TL.group>
|
||||
</TL.group>,
|
||||
])
|
||||
|
||||
editor.setSelectedIds([ids.groupA])
|
||||
editor.setOpacity(0.5)
|
||||
|
||||
// a wasn't selected...
|
||||
expect(editor.getShapeById(ids.boxA)!.opacity).toBe(1)
|
||||
|
||||
// b, c, & d were within a selected group...
|
||||
expect(editor.getShapeById(ids.boxB)!.opacity).toBe(0.5)
|
||||
expect(editor.getShapeById(ids.boxC)!.opacity).toBe(0.5)
|
||||
expect(editor.getShapeById(ids.boxD)!.opacity).toBe(0.5)
|
||||
|
||||
// groups get skipped
|
||||
expect(editor.getShapeById(ids.groupA)!.opacity).toBe(1)
|
||||
expect(editor.getShapeById(ids.groupB)!.opacity).toBe(1)
|
||||
})
|
||||
|
||||
it('stores opacity on opacityForNextShape', () => {
|
||||
editor.setOpacity(0.5)
|
||||
expect(editor.instanceState.opacityForNextShape).toBe(0.5)
|
||||
editor.setOpacity(0.6)
|
||||
expect(editor.instanceState.opacityForNextShape).toBe(0.6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Editor.TickManager', () => {
|
||||
it('Does not produce NaN values when elapsed is 0', () => {
|
||||
// a helper that calls update pointer velocity with a given elapsed time.
|
|
@ -1,5 +1,5 @@
|
|||
import { act, render, screen } from '@testing-library/react'
|
||||
import { TLBaseShape, TLOpacityType, createShapeId } from '@tldraw/tlschema'
|
||||
import { TLBaseShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { TldrawEditor } from '../TldrawEditor'
|
||||
import { Canvas } from '../components/Canvas'
|
||||
import { HTMLContainer } from '../components/HTMLContainer'
|
||||
|
@ -137,7 +137,8 @@ describe('<TldrawEditor />', () => {
|
|||
type: 'geo',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: { geo: 'rectangle', w: 100, h: 100, opacity: '1' },
|
||||
opacity: 1,
|
||||
props: { geo: 'rectangle', w: 100, h: 100 },
|
||||
})
|
||||
|
||||
// Is the shape's component rendering?
|
||||
|
@ -165,7 +166,6 @@ describe('Custom shapes', () => {
|
|||
{
|
||||
w: number
|
||||
h: number
|
||||
opacity: TLOpacityType
|
||||
}
|
||||
>
|
||||
|
||||
|
@ -178,7 +178,6 @@ describe('Custom shapes', () => {
|
|||
|
||||
override defaultProps(): CardShape['props'] {
|
||||
return {
|
||||
opacity: '1',
|
||||
w: 300,
|
||||
h: 300,
|
||||
}
|
||||
|
@ -262,7 +261,8 @@ describe('Custom shapes', () => {
|
|||
type: 'card',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: { w: 100, h: 100, opacity: '1' },
|
||||
opacity: 1,
|
||||
props: { w: 100, h: 100 },
|
||||
})
|
||||
|
||||
// Is the shape's component rendering?
|
||||
|
|
|
@ -6,6 +6,7 @@ Array [
|
|||
"id": "shape:boxA",
|
||||
"index": "a1",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "wahtever",
|
||||
"props": Object {
|
||||
"align": "middle",
|
||||
|
@ -17,7 +18,6 @@ Array [
|
|||
"growY": 0,
|
||||
"h": 100,
|
||||
"labelColor": "black",
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"text": "",
|
||||
"url": "",
|
||||
|
@ -34,6 +34,7 @@ Array [
|
|||
"id": "shape:boxB",
|
||||
"index": "a2",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "wahtever",
|
||||
"props": Object {
|
||||
"align": "middle",
|
||||
|
@ -45,7 +46,6 @@ Array [
|
|||
"growY": 0,
|
||||
"h": 100,
|
||||
"labelColor": "black",
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"text": "",
|
||||
"url": "",
|
||||
|
@ -62,6 +62,7 @@ Array [
|
|||
"id": "shape:boxC",
|
||||
"index": "a3",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "wahtever",
|
||||
"props": Object {
|
||||
"align": "middle",
|
||||
|
@ -73,7 +74,6 @@ Array [
|
|||
"growY": 0,
|
||||
"h": 100,
|
||||
"labelColor": "black",
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"text": "",
|
||||
"url": "",
|
||||
|
@ -95,6 +95,7 @@ Array [
|
|||
"id": "shape:boxA",
|
||||
"index": "a1",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "wahtever",
|
||||
"props": Object {
|
||||
"align": "middle",
|
||||
|
@ -106,7 +107,6 @@ Array [
|
|||
"growY": 0,
|
||||
"h": 100,
|
||||
"labelColor": "black",
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"text": "",
|
||||
"url": "",
|
||||
|
@ -123,6 +123,7 @@ Array [
|
|||
"id": "shape:boxB",
|
||||
"index": "a2",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "wahtever",
|
||||
"props": Object {
|
||||
"align": "middle",
|
||||
|
@ -134,7 +135,6 @@ Array [
|
|||
"growY": 0,
|
||||
"h": 100,
|
||||
"labelColor": "black",
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"text": "",
|
||||
"url": "",
|
||||
|
@ -151,6 +151,7 @@ Array [
|
|||
"id": "shape:boxC",
|
||||
"index": "a3",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "wahtever",
|
||||
"props": Object {
|
||||
"align": "middle",
|
||||
|
@ -162,7 +163,6 @@ Array [
|
|||
"growY": 0,
|
||||
"h": 100,
|
||||
"labelColor": "black",
|
||||
"opacity": "1",
|
||||
"size": "m",
|
||||
"text": "",
|
||||
"url": "",
|
||||
|
|
|
@ -33,9 +33,9 @@ it('Creates shapes with the current style', () => {
|
|||
})
|
||||
|
||||
it('Creates shapes with the current opacity', () => {
|
||||
editor.setProp('opacity', '0.5')
|
||||
editor.setOpacity(0.5)
|
||||
editor.createShapes([{ id: ids.box3, type: 'geo' }])
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.box3)!.props.opacity).toEqual('0.5')
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.box3)!.opacity).toEqual(0.5)
|
||||
})
|
||||
|
||||
it('Creates shapes at the correct index', () => {
|
||||
|
|
|
@ -30,13 +30,13 @@ it('updates shapes', () => {
|
|||
id: ids.box1,
|
||||
rotation: 0,
|
||||
type: 'geo',
|
||||
opacity: 1,
|
||||
props: {
|
||||
h: 100,
|
||||
w: 100,
|
||||
color: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
opacity: '1',
|
||||
size: 'm',
|
||||
},
|
||||
})
|
||||
|
@ -49,13 +49,13 @@ it('updates shapes', () => {
|
|||
id: ids.box1,
|
||||
rotation: 0,
|
||||
type: 'geo',
|
||||
opacity: 1,
|
||||
props: {
|
||||
h: 100,
|
||||
w: 100,
|
||||
color: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
opacity: '1',
|
||||
size: 'm',
|
||||
},
|
||||
})
|
||||
|
@ -68,13 +68,13 @@ it('updates shapes', () => {
|
|||
id: ids.box1,
|
||||
rotation: 0,
|
||||
type: 'geo',
|
||||
opacity: 1,
|
||||
props: {
|
||||
h: 100,
|
||||
w: 100,
|
||||
color: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
opacity: '1',
|
||||
size: 'm',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -20,6 +20,7 @@ type CommonProps = {
|
|||
isLocked?: number
|
||||
ref?: string
|
||||
children?: JSX.Element | JSX.Element[]
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
type ShapeByType<Type extends TLDefaultShape['type']> = Extract<TLDefaultShape, { type: Type }>
|
||||
|
@ -89,7 +90,7 @@ export function shapesFromJsx(shapes: JSX.Element | Array<JSX.Element>) {
|
|||
if (key === 'x' || key === 'y' || key === 'ref' || key === 'id' || key === 'children') {
|
||||
continue
|
||||
}
|
||||
if (key === 'rotation' || key === 'isLocked') {
|
||||
if (key === 'rotation' || key === 'isLocked' || key === 'opacity') {
|
||||
shapePartial[key] = value as any
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ describe('Editor.props', () => {
|
|||
dash: 'draw',
|
||||
fill: 'none',
|
||||
size: 'm',
|
||||
opacity: '1',
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -32,12 +31,6 @@ describe('Editor.props', () => {
|
|||
font: 'draw',
|
||||
geo: 'rectangle',
|
||||
verticalAlign: 'middle',
|
||||
// h: 100,
|
||||
// w: 100,
|
||||
// growY: 0,
|
||||
opacity: '1',
|
||||
// text: '',
|
||||
// url: '',
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -53,12 +46,6 @@ describe('Editor.props', () => {
|
|||
font: 'draw',
|
||||
geo: 'rectangle',
|
||||
verticalAlign: 'middle',
|
||||
// h: 100, // blacklisted
|
||||
// w: 100, // blacklisted
|
||||
// growY: 0, // blacklist
|
||||
opacity: '1',
|
||||
// text: '', // blacklisted
|
||||
// url: '', // blacklisted
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -82,13 +69,7 @@ describe('Editor.props', () => {
|
|||
size: 'm',
|
||||
font: 'draw',
|
||||
geo: 'rectangle',
|
||||
opacity: '1',
|
||||
verticalAlign: 'middle',
|
||||
// h: null, // mixed! but also blacklisted
|
||||
// w: null, // mixed! but also blacklisted
|
||||
// growY: 0, // blacklist
|
||||
// text: '', // blacklisted
|
||||
// url: '', // blacklist
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -111,7 +92,6 @@ describe('Editor.props', () => {
|
|||
align: 'start',
|
||||
text: 'hello world this is a long sentence that should wrap',
|
||||
w: 100,
|
||||
opacity: '0.5',
|
||||
url: 'https://aol.com',
|
||||
verticalAlign: 'start',
|
||||
},
|
||||
|
@ -128,10 +108,7 @@ describe('Editor.props', () => {
|
|||
geo: null,
|
||||
size: null,
|
||||
font: null,
|
||||
opacity: null,
|
||||
verticalAlign: null,
|
||||
// growY: null, // blacklist
|
||||
// url: null, // blacklist
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -405,7 +405,8 @@ describe('When in readonly mode', () => {
|
|||
type: 'embed',
|
||||
x: 100,
|
||||
y: 100,
|
||||
props: { opacity: '1', w: 100, h: 100, url: '', doesResize: false },
|
||||
opacity: 1,
|
||||
props: { w: 100, h: 100, url: '', doesResize: false },
|
||||
},
|
||||
])
|
||||
editor.setReadOnly(true)
|
||||
|
|
|
@ -5,6 +5,7 @@ Object {
|
|||
"id": "shape:lineA",
|
||||
"index": "a3",
|
||||
"isLocked": false,
|
||||
"opacity": 1,
|
||||
"parentId": "shape:boxA",
|
||||
"props": Object {
|
||||
"color": "black",
|
||||
|
@ -13,7 +14,6 @@ Object {
|
|||
"isClosed": false,
|
||||
"isComplete": false,
|
||||
"isPen": false,
|
||||
"opacity": "1",
|
||||
"segments": Array [
|
||||
Object {
|
||||
"points": Array [
|
||||
|
|
|
@ -20,7 +20,6 @@ const imageWidth = 1200
|
|||
const imageHeight = 800
|
||||
|
||||
const imageProps = {
|
||||
opacity: '1',
|
||||
assetId: null,
|
||||
playing: true,
|
||||
url: '',
|
||||
|
|
|
@ -1896,10 +1896,10 @@ describe('Group opacity', () => {
|
|||
it("should set the group's opacity to max even if the selected style panel opacity is lower", () => {
|
||||
editor.createShapes([box(ids.boxA, 0, 0), box(ids.boxB, 20, 0)])
|
||||
editor.select(ids.boxA, ids.boxB)
|
||||
editor.setProp('opacity', '0.5')
|
||||
editor.setOpacity(0.5)
|
||||
editor.groupShapes()
|
||||
const group = editor.getShapeById(onlySelectedId())!
|
||||
assert(editor.isShapeOfType(group, GroupShapeUtil))
|
||||
expect(group.props.opacity).toBe('1')
|
||||
expect(group.opacity).toBe(1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -292,7 +292,6 @@ export async function createShapesFromFiles(
|
|||
props: {
|
||||
w: asset.props!.w,
|
||||
h: asset.props!.h,
|
||||
opacity: '1',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -403,7 +402,6 @@ export function createEmbedShapeAtPoint(
|
|||
h: props.height,
|
||||
doesResize: props.doesResize,
|
||||
url,
|
||||
opacity: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -430,10 +428,10 @@ export async function createBookmarkShapeAtPoint(editor: Editor, url: string, po
|
|||
type: 'bookmark',
|
||||
x: point.x - 150,
|
||||
y: point.y - 160,
|
||||
opacity: 1,
|
||||
props: {
|
||||
assetId: existing.id,
|
||||
url: existing.props.src!,
|
||||
opacity: '1',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -450,9 +448,9 @@ export async function createBookmarkShapeAtPoint(editor: Editor, url: string, po
|
|||
type: 'bookmark',
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
opacity: 1,
|
||||
props: {
|
||||
url: url,
|
||||
opacity: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -480,9 +478,9 @@ export async function createBookmarkShapeAtPoint(editor: Editor, url: string, po
|
|||
{
|
||||
id: shapeId,
|
||||
type: 'bookmark',
|
||||
opacity: 1,
|
||||
props: {
|
||||
assetId: assetId,
|
||||
opacity: '1',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -531,11 +529,11 @@ export async function createAssetShapeAtPoint(
|
|||
type: 'image',
|
||||
x: point.x - width / 2,
|
||||
y: point.y - height / 2,
|
||||
opacity: 1,
|
||||
props: {
|
||||
assetId: asset.id,
|
||||
w: width,
|
||||
h: height,
|
||||
opacity: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -83,6 +83,7 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
|||
parentId: TLParentId;
|
||||
type: Type;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
props: Props;
|
||||
}>;
|
||||
|
||||
|
@ -450,7 +451,7 @@ export const LANGUAGES: readonly [{
|
|||
}];
|
||||
|
||||
// @internal (undocumented)
|
||||
export const opacityValidator: T.Validator<"0.1" | "0.25" | "0.5" | "0.75" | "1">;
|
||||
export const opacityValidator: T.Validator<number>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const pageIdValidator: T.Validator<TLPageId>;
|
||||
|
@ -500,9 +501,6 @@ export const TL_FONT_TYPES: Set<"draw" | "mono" | "sans" | "serif">;
|
|||
// @public (undocumented)
|
||||
export const TL_GEO_TYPES: Set<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_OPACITY_TYPES: Set<"0.1" | "0.25" | "0.5" | "0.75" | "1">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_SIZE_TYPES: Set<"l" | "m" | "s" | "xl">;
|
||||
|
||||
|
@ -510,7 +508,7 @@ export const TL_SIZE_TYPES: Set<"l" | "m" | "s" | "xl">;
|
|||
export const TL_SPLINE_TYPES: Set<"cubic" | "line">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "opacity" | "size" | "spline" | "verticalAlign">;
|
||||
export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "size" | "spline" | "verticalAlign">;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLAlignStyle extends TLBaseStyle {
|
||||
|
@ -552,7 +550,6 @@ export type TLArrowShapeProps = {
|
|||
fill: TLFillType;
|
||||
dash: TLDashType;
|
||||
size: TLSizeType;
|
||||
opacity: TLOpacityType;
|
||||
arrowheadStart: TLArrowheadType;
|
||||
arrowheadEnd: TLArrowheadType;
|
||||
font: TLFontType;
|
||||
|
@ -612,6 +609,8 @@ export interface TLBaseShape<Type extends string, Props extends object> extends
|
|||
// (undocumented)
|
||||
isLocked: boolean;
|
||||
// (undocumented)
|
||||
opacity: TLOpacityType;
|
||||
// (undocumented)
|
||||
parentId: TLParentId;
|
||||
// (undocumented)
|
||||
props: Props;
|
||||
|
@ -816,7 +815,6 @@ export type TLImageShape = TLBaseShape<'image', TLImageShapeProps>;
|
|||
|
||||
// @public (undocumented)
|
||||
export type TLImageShapeProps = {
|
||||
opacity: TLOpacityType;
|
||||
url: string;
|
||||
playing: boolean;
|
||||
w: number;
|
||||
|
@ -848,6 +846,8 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
|||
// (undocumented)
|
||||
isToolLocked: boolean;
|
||||
// (undocumented)
|
||||
opacityForNextShape: TLOpacityType;
|
||||
// (undocumented)
|
||||
propsForNextShape: TLInstancePropsForNextShape;
|
||||
// (undocumented)
|
||||
screenBounds: Box2dModel;
|
||||
|
@ -938,15 +938,7 @@ export type TLNullableShapeProps = {
|
|||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLOpacityStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLOpacityType;
|
||||
// (undocumented)
|
||||
type: 'opacity';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLOpacityType = SetValue<typeof TL_OPACITY_TYPES>;
|
||||
export type TLOpacityType = number;
|
||||
|
||||
// @public
|
||||
export interface TLPage extends BaseRecord<'page', TLPageId> {
|
||||
|
@ -995,7 +987,7 @@ export type TLShapePartial<T extends TLShape = TLShape> = T extends T ? {
|
|||
export type TLShapeProp = keyof TLShapeProps;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLShapeProps = SmooshedUnionObject<TLShape['props']>;
|
||||
export type TLShapeProps = Identity<UnionToIntersection<TLDefaultShape['props']>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLSizeStyle extends TLBaseStyle {
|
||||
|
@ -1052,8 +1044,6 @@ export interface TLStyleCollections {
|
|||
// (undocumented)
|
||||
geo: TLGeoStyle[];
|
||||
// (undocumented)
|
||||
opacity: TLOpacityStyle[];
|
||||
// (undocumented)
|
||||
size: TLSizeStyle[];
|
||||
// (undocumented)
|
||||
spline: TLSplineStyle[];
|
||||
|
@ -1062,7 +1052,7 @@ export interface TLStyleCollections {
|
|||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStyleItem = TLAlignStyle | TLArrowheadEndStyle | TLArrowheadStartStyle | TLColorStyle | TLDashStyle | TLFillStyle | TLFontStyle | TLGeoStyle | TLOpacityStyle | TLSizeStyle | TLSplineStyle | TLVerticalAlignStyle;
|
||||
export type TLStyleItem = TLAlignStyle | TLArrowheadEndStyle | TLArrowheadStartStyle | TLColorStyle | TLDashStyle | TLFillStyle | TLFontStyle | TLGeoStyle | TLSizeStyle | TLSplineStyle | TLVerticalAlignStyle;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStyleProps = Pick<TLShapeProps, TLStyleType>;
|
||||
|
@ -1079,7 +1069,6 @@ export type TLTextShapeProps = {
|
|||
size: TLSizeType;
|
||||
font: TLFontType;
|
||||
align: TLAlignType;
|
||||
opacity: TLOpacityType;
|
||||
w: number;
|
||||
text: string;
|
||||
scale: number;
|
||||
|
|
|
@ -132,7 +132,7 @@ export function createTLSchema(
|
|||
),
|
||||
})
|
||||
),
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false }))
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false, opacity: 1 }))
|
||||
|
||||
return StoreSchema.create<TLRecord, TLStoreProps>(
|
||||
{
|
||||
|
|
|
@ -127,12 +127,7 @@ export {
|
|||
} from './styles/TLFontStyle'
|
||||
export { TL_GEO_TYPES, geoValidator, type TLGeoStyle, type TLGeoType } from './styles/TLGeoStyle'
|
||||
export { iconValidator, type TLIconStyle, type TLIconType } from './styles/TLIconStyle'
|
||||
export {
|
||||
TL_OPACITY_TYPES,
|
||||
opacityValidator,
|
||||
type TLOpacityStyle,
|
||||
type TLOpacityType,
|
||||
} from './styles/TLOpacityStyle'
|
||||
export { opacityValidator, type TLOpacityType } from './styles/TLOpacityStyle'
|
||||
export {
|
||||
TL_SIZE_TYPES,
|
||||
sizeValidator,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { documentMigrations } from './records/TLDocument'
|
|||
import { instanceMigrations, instanceTypeVersions } from './records/TLInstance'
|
||||
import { instancePageStateMigrations, instancePageStateVersions } from './records/TLPageState'
|
||||
import { instancePresenceMigrations, instancePresenceVersions } from './records/TLPresence'
|
||||
import { TLShape, rootShapeMigrations } from './records/TLShape'
|
||||
import { TLShape, rootShapeMigrations, Versions as rootShapeVersions } from './records/TLShape'
|
||||
import { arrowShapeMigrations } from './shapes/TLArrowShape'
|
||||
import { bookmarkShapeMigrations } from './shapes/TLBookmarkShape'
|
||||
import { drawShapeMigrations } from './shapes/TLDrawShape'
|
||||
|
@ -1044,6 +1044,72 @@ describe('Adds NoteShape vertical alignment', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('hoist opacity', () => {
|
||||
test('hoists opacity from a shape to another', () => {
|
||||
const { up, down } = rootShapeMigrations.migrators[rootShapeVersions.HoistOpacity]
|
||||
const before = {
|
||||
type: 'myShape',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: {
|
||||
color: 'red',
|
||||
opacity: '0.5',
|
||||
},
|
||||
}
|
||||
const after = {
|
||||
type: 'myShape',
|
||||
x: 0,
|
||||
y: 0,
|
||||
opacity: 0.5,
|
||||
props: {
|
||||
color: 'red',
|
||||
},
|
||||
}
|
||||
const afterWithNonMatchingOpacity = {
|
||||
type: 'myShape',
|
||||
x: 0,
|
||||
y: 0,
|
||||
opacity: 0.6,
|
||||
props: {
|
||||
color: 'red',
|
||||
},
|
||||
}
|
||||
|
||||
expect(up(before)).toEqual(after)
|
||||
expect(down(after)).toEqual(before)
|
||||
expect(down(afterWithNonMatchingOpacity)).toEqual(before)
|
||||
})
|
||||
|
||||
test('hoists opacity from propsForNextShape', () => {
|
||||
const { up, down } = instanceMigrations.migrators[instanceTypeVersions.HoistOpacity]
|
||||
const before = {
|
||||
isToolLocked: true,
|
||||
propsForNextShape: {
|
||||
color: 'black',
|
||||
opacity: '0.5',
|
||||
},
|
||||
}
|
||||
const after = {
|
||||
isToolLocked: true,
|
||||
opacityForNextShape: 0.5,
|
||||
propsForNextShape: {
|
||||
color: 'black',
|
||||
},
|
||||
}
|
||||
const afterWithNonMatchingOpacity = {
|
||||
isToolLocked: true,
|
||||
opacityForNextShape: 0.6,
|
||||
propsForNextShape: {
|
||||
color: 'black',
|
||||
},
|
||||
}
|
||||
|
||||
expect(up(before)).toEqual(after)
|
||||
expect(down(after)).toEqual(before)
|
||||
expect(down(afterWithNonMatchingOpacity)).toEqual(before)
|
||||
})
|
||||
})
|
||||
|
||||
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
||||
|
||||
for (const migrator of allMigrators) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { fillValidator } from '../styles/TLFillStyle'
|
|||
import { fontValidator } from '../styles/TLFontStyle'
|
||||
import { geoValidator } from '../styles/TLGeoStyle'
|
||||
import { iconValidator } from '../styles/TLIconStyle'
|
||||
import { opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle'
|
||||
import { sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { splineValidator } from '../styles/TLSplineStyle'
|
||||
import { verticalAlignValidator } from '../styles/TLVerticalAlignStyle'
|
||||
|
@ -34,6 +34,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
|||
currentPageId: TLPageId
|
||||
followingUserId: string | null
|
||||
brush: Box2dModel | null
|
||||
opacityForNextShape: TLOpacityType
|
||||
propsForNextShape: TLInstancePropsForNextShape
|
||||
cursor: TLCursor
|
||||
scribble: TLScribble | null
|
||||
|
@ -62,13 +63,13 @@ export const instanceTypeValidator: T.Validator<TLInstance> = T.model(
|
|||
currentPageId: pageIdValidator,
|
||||
followingUserId: T.string.nullable(),
|
||||
brush: T.boxModel.nullable(),
|
||||
opacityForNextShape: opacityValidator,
|
||||
propsForNextShape: T.object({
|
||||
color: colorValidator,
|
||||
labelColor: colorValidator,
|
||||
dash: dashValidator,
|
||||
fill: fillValidator,
|
||||
size: sizeValidator,
|
||||
opacity: opacityValidator,
|
||||
font: fontValidator,
|
||||
align: alignValidator,
|
||||
verticalAlign: verticalAlignValidator,
|
||||
|
@ -104,13 +105,14 @@ const Versions = {
|
|||
AddScribbleDelay: 10,
|
||||
RemoveUserId: 11,
|
||||
AddIsPenModeAndIsGridMode: 12,
|
||||
HoistOpacity: 13,
|
||||
} as const
|
||||
|
||||
export { Versions as instanceTypeVersions }
|
||||
|
||||
/** @public */
|
||||
export const instanceMigrations = defineMigrations({
|
||||
currentVersion: Versions.AddIsPenModeAndIsGridMode,
|
||||
currentVersion: Versions.HoistOpacity,
|
||||
migrators: {
|
||||
[Versions.AddTransparentExportBgs]: {
|
||||
up: (instance: TLInstance) => {
|
||||
|
@ -256,6 +258,29 @@ export const instanceMigrations = defineMigrations({
|
|||
return instance
|
||||
},
|
||||
},
|
||||
[Versions.HoistOpacity]: {
|
||||
up: ({ propsForNextShape: { opacity, ...propsForNextShape }, ...instance }: any) => {
|
||||
return { ...instance, opacityForNextShape: Number(opacity ?? '1'), propsForNextShape }
|
||||
},
|
||||
down: ({ opacityForNextShape: opacity, ...instance }: any) => {
|
||||
return {
|
||||
...instance,
|
||||
propsForNextShape: {
|
||||
...instance.propsForNextShape,
|
||||
opacity:
|
||||
opacity < 0.175
|
||||
? '0.1'
|
||||
: opacity < 0.375
|
||||
? '0.25'
|
||||
: opacity < 0.625
|
||||
? '0.5'
|
||||
: opacity < 0.875
|
||||
? '0.75'
|
||||
: '1',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -267,8 +292,8 @@ export const InstanceRecordType = createRecordType<TLInstance>('instance', {
|
|||
}).withDefaultProperties(
|
||||
(): Omit<TLInstance, 'typeName' | 'id' | 'currentPageId'> => ({
|
||||
followingUserId: null,
|
||||
opacityForNextShape: 1,
|
||||
propsForNextShape: {
|
||||
opacity: '1',
|
||||
color: 'black',
|
||||
labelColor: 'black',
|
||||
dash: 'draw',
|
||||
|
|
|
@ -15,7 +15,6 @@ import { TLLineShape } from '../shapes/TLLineShape'
|
|||
import { TLNoteShape } from '../shapes/TLNoteShape'
|
||||
import { TLTextShape } from '../shapes/TLTextShape'
|
||||
import { TLVideoShape } from '../shapes/TLVideoShape'
|
||||
import { SmooshedUnionObject } from '../util-types'
|
||||
import { TLPageId } from './TLPage'
|
||||
|
||||
/**
|
||||
|
@ -64,8 +63,15 @@ export type TLShapePartial<T extends TLShape = TLShape> = T extends T
|
|||
/** @public */
|
||||
export type TLShapeId = RecordId<TLUnknownShape>
|
||||
|
||||
// evil type shit that will get deleted in the next PR
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
|
||||
? I
|
||||
: never
|
||||
|
||||
type Identity<T> = { [K in keyof T]: T[K] }
|
||||
|
||||
/** @public */
|
||||
export type TLShapeProps = SmooshedUnionObject<TLShape['props']>
|
||||
export type TLShapeProps = Identity<UnionToIntersection<TLDefaultShape['props']>>
|
||||
|
||||
/** @public */
|
||||
export type TLShapeProp = keyof TLShapeProps
|
||||
|
@ -76,13 +82,14 @@ export type TLParentId = TLPageId | TLShapeId
|
|||
/** @public */
|
||||
export type TLNullableShapeProps = { [K in TLShapeProp]?: TLShapeProps[K] | null }
|
||||
|
||||
const Versions = {
|
||||
export const Versions = {
|
||||
AddIsLocked: 1,
|
||||
HoistOpacity: 2,
|
||||
} as const
|
||||
|
||||
/** @internal */
|
||||
export const rootShapeMigrations = defineMigrations({
|
||||
currentVersion: Versions.AddIsLocked,
|
||||
currentVersion: Versions.HoistOpacity,
|
||||
migrators: {
|
||||
[Versions.AddIsLocked]: {
|
||||
up: (record) => {
|
||||
|
@ -98,6 +105,33 @@ export const rootShapeMigrations = defineMigrations({
|
|||
}
|
||||
},
|
||||
},
|
||||
[Versions.HoistOpacity]: {
|
||||
up: ({ props: { opacity, ...props }, ...record }) => {
|
||||
return {
|
||||
...record,
|
||||
opacity: Number(opacity ?? '1'),
|
||||
props,
|
||||
}
|
||||
},
|
||||
down: ({ opacity, ...record }) => {
|
||||
return {
|
||||
...record,
|
||||
props: {
|
||||
...record.props,
|
||||
opacity:
|
||||
opacity < 0.175
|
||||
? '0.1'
|
||||
: opacity < 0.375
|
||||
? '0.25'
|
||||
: opacity < 0.625
|
||||
? '0.5'
|
||||
: opacity < 0.875
|
||||
? '0.75'
|
||||
: '1',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
|||
import { TLDashType, dashValidator } from '../styles/TLDashStyle'
|
||||
import { TLFillType, fillValidator } from '../styles/TLFillStyle'
|
||||
import { TLFontType, fontValidator } from '../styles/TLFontStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { SetValue } from '../util-types'
|
||||
import { TLBaseShape, createShapeValidator, shapeIdValidator } from './TLBaseShape'
|
||||
|
@ -35,7 +34,6 @@ export type TLArrowShapeProps = {
|
|||
fill: TLFillType
|
||||
dash: TLDashType
|
||||
size: TLSizeType
|
||||
opacity: TLOpacityType
|
||||
arrowheadStart: TLArrowheadType
|
||||
arrowheadEnd: TLArrowheadType
|
||||
font: TLFontType
|
||||
|
@ -72,7 +70,6 @@ export const arrowShapeValidator: T.Validator<TLArrowShape> = createShapeValidat
|
|||
fill: fillValidator,
|
||||
dash: dashValidator,
|
||||
size: sizeValidator,
|
||||
opacity: opacityValidator,
|
||||
arrowheadStart: arrowheadValidator,
|
||||
arrowheadEnd: arrowheadValidator,
|
||||
font: fontValidator,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BaseRecord } from '@tldraw/store'
|
|||
import { T } from '@tldraw/validate'
|
||||
import { idValidator } from '../misc/id-validator'
|
||||
import { TLParentId, TLShapeId } from '../records/TLShape'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
|
||||
/** @public */
|
||||
export interface TLBaseShape<Type extends string, Props extends object>
|
||||
|
@ -13,6 +14,7 @@ export interface TLBaseShape<Type extends string, Props extends object>
|
|||
index: string
|
||||
parentId: TLParentId
|
||||
isLocked: boolean
|
||||
opacity: TLOpacityType
|
||||
props: Props
|
||||
}
|
||||
|
||||
|
@ -42,6 +44,7 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
|||
parentId: parentIdValidator,
|
||||
type: T.literal(type),
|
||||
isLocked: T.boolean,
|
||||
opacity: opacityValidator,
|
||||
props,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,12 +2,10 @@ import { defineMigrations } from '@tldraw/store'
|
|||
import { T } from '@tldraw/validate'
|
||||
import { assetIdValidator } from '../assets/TLBaseAsset'
|
||||
import { TLAssetId } from '../records/TLAsset'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
||||
/** @public */
|
||||
export type TLBookmarkShapeProps = {
|
||||
opacity: TLOpacityType
|
||||
w: number
|
||||
h: number
|
||||
assetId: TLAssetId | null
|
||||
|
@ -21,7 +19,6 @@ export type TLBookmarkShape = TLBaseShape<'bookmark', TLBookmarkShapeProps>
|
|||
export const bookmarkShapeValidator: T.Validator<TLBookmarkShape> = createShapeValidator(
|
||||
'bookmark',
|
||||
T.object({
|
||||
opacity: opacityValidator,
|
||||
w: T.nonZeroNumber,
|
||||
h: T.nonZeroNumber,
|
||||
assetId: assetIdValidator.nullable(),
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Vec2dModel } from '../misc/geometry-types'
|
|||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLDashType, dashValidator } from '../styles/TLDashStyle'
|
||||
import { TLFillType, fillValidator } from '../styles/TLFillStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { SetValue } from '../util-types'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
@ -30,7 +29,6 @@ export type TLDrawShapeProps = {
|
|||
fill: TLFillType
|
||||
dash: TLDashType
|
||||
size: TLSizeType
|
||||
opacity: TLOpacityType
|
||||
segments: TLDrawShapeSegment[]
|
||||
isComplete: boolean
|
||||
isClosed: boolean
|
||||
|
@ -48,7 +46,6 @@ export const drawShapeValidator: T.Validator<TLDrawShape> = createShapeValidator
|
|||
fill: fillValidator,
|
||||
dash: dashValidator,
|
||||
size: sizeValidator,
|
||||
opacity: opacityValidator,
|
||||
segments: T.arrayOf(drawShapeSegmentValidator),
|
||||
isComplete: T.boolean,
|
||||
isClosed: T.boolean,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { defineMigrations } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
||||
// Only allow multiplayer embeds. If we add additional routes later for example '/help' this won't match
|
||||
|
@ -549,7 +548,6 @@ export type TLEmbedShapePermissions = { [K in keyof typeof embedShapePermissionD
|
|||
|
||||
/** @public */
|
||||
export type TLEmbedShapeProps = {
|
||||
opacity: TLOpacityType
|
||||
w: number
|
||||
h: number
|
||||
url: string
|
||||
|
@ -566,7 +564,6 @@ export type TLEmbedShape = TLBaseShape<'embed', TLEmbedShapeProps>
|
|||
export const embedShapeTypeValidator: T.Validator<TLEmbedShape> = createShapeValidator(
|
||||
'embed',
|
||||
T.object({
|
||||
opacity: opacityValidator,
|
||||
w: T.nonZeroNumber,
|
||||
h: T.nonZeroNumber,
|
||||
url: T.string,
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { defineMigrations } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle'
|
||||
import { createShapeValidator, TLBaseShape } from './TLBaseShape'
|
||||
|
||||
type TLFrameShapeProps = {
|
||||
opacity: TLOpacityType
|
||||
w: number
|
||||
h: number
|
||||
name: string
|
||||
|
@ -17,7 +15,6 @@ export type TLFrameShape = TLBaseShape<'frame', TLFrameShapeProps>
|
|||
export const frameShapeValidator: T.Validator<TLFrameShape> = createShapeValidator(
|
||||
'frame',
|
||||
T.object({
|
||||
opacity: opacityValidator,
|
||||
w: T.nonZeroNumber,
|
||||
h: T.nonZeroNumber,
|
||||
name: T.string,
|
||||
|
|
|
@ -6,7 +6,6 @@ import { TLDashType, dashValidator } from '../styles/TLDashStyle'
|
|||
import { TLFillType, fillValidator } from '../styles/TLFillStyle'
|
||||
import { TLFontType, fontValidator } from '../styles/TLFontStyle'
|
||||
import { TLGeoType, geoValidator } from '../styles/TLGeoStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { TLVerticalAlignType, verticalAlignValidator } from '../styles/TLVerticalAlignStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
@ -19,7 +18,6 @@ export type TLGeoShapeProps = {
|
|||
fill: TLFillType
|
||||
dash: TLDashType
|
||||
size: TLSizeType
|
||||
opacity: TLOpacityType
|
||||
font: TLFontType
|
||||
align: TLAlignType
|
||||
verticalAlign: TLVerticalAlignType
|
||||
|
@ -43,7 +41,6 @@ export const geoShapeValidator: T.Validator<TLGeoShape> = createShapeValidator(
|
|||
fill: fillValidator,
|
||||
dash: dashValidator,
|
||||
size: sizeValidator,
|
||||
opacity: opacityValidator,
|
||||
font: fontValidator,
|
||||
align: alignValidator,
|
||||
verticalAlign: verticalAlignValidator,
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { defineMigrations } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle'
|
||||
import { createShapeValidator, TLBaseShape } from './TLBaseShape'
|
||||
|
||||
/** @public */
|
||||
export type TLGroupShapeProps = {
|
||||
opacity: TLOpacityType
|
||||
}
|
||||
export type TLGroupShapeProps = { [key in never]: undefined }
|
||||
|
||||
/** @public */
|
||||
export type TLGroupShape = TLBaseShape<'group', TLGroupShapeProps>
|
||||
|
@ -14,9 +11,7 @@ export type TLGroupShape = TLBaseShape<'group', TLGroupShapeProps>
|
|||
/** @internal */
|
||||
export const groupShapeValidator: T.Validator<TLGroupShape> = createShapeValidator(
|
||||
'group',
|
||||
T.object({
|
||||
opacity: opacityValidator,
|
||||
})
|
||||
T.object({})
|
||||
)
|
||||
|
||||
/** @internal */
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { defineMigrations } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
import { TLDrawShapeSegment, drawShapeSegmentValidator } from './TLDrawShape'
|
||||
|
@ -10,7 +9,6 @@ import { TLDrawShapeSegment, drawShapeSegmentValidator } from './TLDrawShape'
|
|||
export type TLHighlightShapeProps = {
|
||||
color: TLColorType
|
||||
size: TLSizeType
|
||||
opacity: TLOpacityType
|
||||
segments: TLDrawShapeSegment[]
|
||||
isComplete: boolean
|
||||
isPen: boolean
|
||||
|
@ -25,7 +23,6 @@ export const highlightShapeValidator: T.Validator<TLHighlightShape> = createShap
|
|||
T.object({
|
||||
color: colorValidator,
|
||||
size: sizeValidator,
|
||||
opacity: opacityValidator,
|
||||
segments: T.arrayOf(drawShapeSegmentValidator),
|
||||
isComplete: T.boolean,
|
||||
isPen: T.boolean,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { T } from '@tldraw/validate'
|
|||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLDashType, dashValidator } from '../styles/TLDashStyle'
|
||||
import { TLIconType, iconValidator } from '../styles/TLIconStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
||||
|
@ -13,7 +12,6 @@ export type TLIconShapeProps = {
|
|||
icon: TLIconType
|
||||
dash: TLDashType
|
||||
color: TLColorType
|
||||
opacity: TLOpacityType
|
||||
scale: number
|
||||
}
|
||||
|
||||
|
@ -28,7 +26,6 @@ export const iconShapeValidator: T.Validator<TLIconShape> = createShapeValidator
|
|||
icon: iconValidator,
|
||||
dash: dashValidator,
|
||||
color: colorValidator,
|
||||
opacity: opacityValidator,
|
||||
scale: T.number,
|
||||
})
|
||||
)
|
||||
|
|
|
@ -3,7 +3,6 @@ import { T } from '@tldraw/validate'
|
|||
import { assetIdValidator } from '../assets/TLBaseAsset'
|
||||
import { Vec2dModel } from '../misc/geometry-types'
|
||||
import { TLAssetId } from '../records/TLAsset'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
||||
/** @public */
|
||||
|
@ -14,7 +13,6 @@ export type TLImageCrop = {
|
|||
|
||||
/** @public */
|
||||
export type TLImageShapeProps = {
|
||||
opacity: TLOpacityType
|
||||
url: string
|
||||
playing: boolean
|
||||
w: number
|
||||
|
@ -35,7 +33,6 @@ export const cropValidator = T.object({
|
|||
export const imageShapeValidator: T.Validator<TLImageShape> = createShapeValidator(
|
||||
'image',
|
||||
T.object({
|
||||
opacity: opacityValidator,
|
||||
w: T.nonZeroNumber,
|
||||
h: T.nonZeroNumber,
|
||||
playing: T.boolean,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { T } from '@tldraw/validate'
|
|||
import { TLHandle, handleValidator } from '../misc/TLHandle'
|
||||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLDashType, dashValidator } from '../styles/TLDashStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { TLSplineType, splineValidator } from '../styles/TLSplineStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
@ -13,7 +12,6 @@ export type TLLineShapeProps = {
|
|||
color: TLColorType
|
||||
dash: TLDashType
|
||||
size: TLSizeType
|
||||
opacity: TLOpacityType
|
||||
spline: TLSplineType
|
||||
handles: {
|
||||
[key: string]: TLHandle
|
||||
|
@ -30,7 +28,6 @@ export const lineShapeValidator: T.Validator<TLLineShape> = createShapeValidator
|
|||
color: colorValidator,
|
||||
dash: dashValidator,
|
||||
size: sizeValidator,
|
||||
opacity: opacityValidator,
|
||||
spline: splineValidator,
|
||||
handles: T.dict(T.string, handleValidator),
|
||||
})
|
||||
|
|
|
@ -3,7 +3,6 @@ import { T } from '@tldraw/validate'
|
|||
import { TLAlignType, alignValidator } from '../styles/TLAlignStyle'
|
||||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLFontType, fontValidator } from '../styles/TLFontStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { TLVerticalAlignType, verticalAlignValidator } from '../styles/TLVerticalAlignStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
@ -15,7 +14,6 @@ export type TLNoteShapeProps = {
|
|||
font: TLFontType
|
||||
align: TLAlignType
|
||||
verticalAlign: TLVerticalAlignType
|
||||
opacity: TLOpacityType
|
||||
growY: number
|
||||
url: string
|
||||
text: string
|
||||
|
@ -33,7 +31,6 @@ export const noteShapeValidator: T.Validator<TLNoteShape> = createShapeValidator
|
|||
font: fontValidator,
|
||||
align: alignValidator,
|
||||
verticalAlign: verticalAlignValidator,
|
||||
opacity: opacityValidator,
|
||||
growY: T.positiveNumber,
|
||||
url: T.string,
|
||||
text: T.string,
|
||||
|
|
|
@ -3,7 +3,6 @@ import { T } from '@tldraw/validate'
|
|||
import { TLAlignType, alignValidator } from '../styles/TLAlignStyle'
|
||||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLFontType, fontValidator } from '../styles/TLFontStyle'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
||||
|
@ -13,7 +12,6 @@ export type TLTextShapeProps = {
|
|||
size: TLSizeType
|
||||
font: TLFontType
|
||||
align: TLAlignType
|
||||
opacity: TLOpacityType
|
||||
w: number
|
||||
text: string
|
||||
scale: number
|
||||
|
@ -31,7 +29,6 @@ export const textShapeValidator: T.Validator<TLTextShape> = createShapeValidator
|
|||
size: sizeValidator,
|
||||
font: fontValidator,
|
||||
align: alignValidator,
|
||||
opacity: opacityValidator,
|
||||
w: T.nonZeroNumber,
|
||||
text: T.string,
|
||||
scale: T.nonZeroNumber,
|
||||
|
|
|
@ -2,12 +2,10 @@ import { defineMigrations } from '@tldraw/store'
|
|||
import { T } from '@tldraw/validate'
|
||||
import { assetIdValidator } from '../assets/TLBaseAsset'
|
||||
import { TLAssetId } from '../records/TLAsset'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
|
||||
|
||||
/** @public */
|
||||
export type TLVideoShapeProps = {
|
||||
opacity: TLOpacityType
|
||||
w: number
|
||||
h: number
|
||||
time: number
|
||||
|
@ -23,7 +21,6 @@ export type TLVideoShape = TLBaseShape<'video', TLVideoShapeProps>
|
|||
export const videoShapeValidator: T.Validator<TLVideoShape> = createShapeValidator(
|
||||
'video',
|
||||
T.object({
|
||||
opacity: opacityValidator,
|
||||
w: T.nonZeroNumber,
|
||||
h: T.nonZeroNumber,
|
||||
time: T.number,
|
||||
|
|
|
@ -7,7 +7,6 @@ export const TL_STYLE_TYPES = new Set([
|
|||
'dash',
|
||||
'fill',
|
||||
'size',
|
||||
'opacity',
|
||||
'font',
|
||||
'align',
|
||||
'verticalAlign',
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
import { T } from '@tldraw/validate'
|
||||
import { SetValue } from '../util-types'
|
||||
import { TLBaseStyle } from './TLBaseStyle'
|
||||
|
||||
/** @public */
|
||||
export const TL_OPACITY_TYPES = new Set(['0.1', '0.25', '0.5', '0.75', '1'] as const)
|
||||
|
||||
/** @public */
|
||||
export type TLOpacityType = SetValue<typeof TL_OPACITY_TYPES>
|
||||
|
||||
/** @public */
|
||||
export interface TLOpacityStyle extends TLBaseStyle {
|
||||
id: TLOpacityType
|
||||
type: 'opacity'
|
||||
}
|
||||
export type TLOpacityType = number
|
||||
|
||||
/** @internal */
|
||||
export const opacityValidator = T.setEnum(TL_OPACITY_TYPES)
|
||||
export const opacityValidator = T.number.check((n) => {
|
||||
if (n < 0 || n > 1) {
|
||||
throw new T.ValidationError('Opacity must be between 0 and 1')
|
||||
}
|
||||
})
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
TLDashStyle,
|
||||
TLFillStyle,
|
||||
TLFontStyle,
|
||||
TLOpacityStyle,
|
||||
TLSizeStyle,
|
||||
TLStyleType,
|
||||
} from '..'
|
||||
|
@ -20,7 +19,6 @@ export type TLStyleItem =
|
|||
| TLFillStyle
|
||||
| TLDashStyle
|
||||
| TLSizeStyle
|
||||
| TLOpacityStyle
|
||||
| TLFontStyle
|
||||
| TLAlignStyle
|
||||
| TLVerticalAlignStyle
|
||||
|
@ -36,7 +34,6 @@ export interface TLStyleCollections {
|
|||
fill: TLFillStyle[]
|
||||
dash: TLDashStyle[]
|
||||
size: TLSizeStyle[]
|
||||
opacity: TLOpacityStyle[]
|
||||
font: TLFontStyle[]
|
||||
align: TLAlignStyle[]
|
||||
verticalAlign: TLVerticalAlignStyle[]
|
||||
|
@ -44,7 +41,6 @@ export interface TLStyleCollections {
|
|||
arrowheadStart: TLArrowheadStartStyle[]
|
||||
arrowheadEnd: TLArrowheadEndStyle[]
|
||||
spline: TLSplineStyle[]
|
||||
// icon: TLIconStyle[]
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -1,11 +1,2 @@
|
|||
/** @public */
|
||||
export type SmooshedUnionObject<T> = {
|
||||
[K in T extends infer P ? keyof P : never]: T extends infer P
|
||||
? K extends keyof P
|
||||
? P[K]
|
||||
: never
|
||||
: never
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type SetValue<T extends Set<any>> = T extends Set<infer U> ? U : never
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Editor, TLNullableShapeProps, TLStyleItem, useEditor } from '@tldraw/editor'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import { minBy } from '@tldraw/utils'
|
||||
import { useValue } from 'signia-react'
|
||||
import { useTranslation } from '../../hooks/useTranslation/useTranslation'
|
||||
import { Button } from '../primitives/Button'
|
||||
|
@ -18,6 +19,10 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) {
|
|||
const editor = useEditor()
|
||||
|
||||
const props = useValue('props', () => editor.props, [editor])
|
||||
const opacity = useValue('opacity', () => editor.opacity, [editor])
|
||||
const toolShapeType = useValue('toolShapeType', () => editor.root.current.value?.shapeType, [
|
||||
editor,
|
||||
])
|
||||
|
||||
const handlePointerOut = useCallback(() => {
|
||||
if (!isMobile) {
|
||||
|
@ -25,9 +30,9 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) {
|
|||
}
|
||||
}, [editor, isMobile])
|
||||
|
||||
if (!props) return null
|
||||
if (!props && !toolShapeType) return null
|
||||
|
||||
const { geo, arrowheadEnd, arrowheadStart, spline, font } = props
|
||||
const { geo, arrowheadEnd, arrowheadStart, spline, font } = props ?? {}
|
||||
|
||||
const hideGeo = geo === undefined
|
||||
const hideArrowHeads = arrowheadEnd === undefined && arrowheadStart === undefined
|
||||
|
@ -36,13 +41,13 @@ export const StylePanel = function StylePanel({ isMobile }: StylePanelProps) {
|
|||
|
||||
return (
|
||||
<div className="tlui-style-panel" data-ismobile={isMobile} onPointerLeave={handlePointerOut}>
|
||||
<CommonStylePickerSet props={props} />
|
||||
{!hideText && <TextStylePickerSet props={props} />}
|
||||
<CommonStylePickerSet props={props ?? {}} opacity={opacity} />
|
||||
{!hideText && <TextStylePickerSet props={props ?? {}} />}
|
||||
{!(hideGeo && hideArrowHeads && hideSpline) && (
|
||||
<div className="tlui-style-panel__section" aria-label="style panel styles">
|
||||
<GeoStylePickerSet props={props} />
|
||||
<ArrowheadStylePickerSet props={props} />
|
||||
<SplineStylePickerSet props={props} />
|
||||
<GeoStylePickerSet props={props ?? {}} />
|
||||
<ArrowheadStylePickerSet props={props ?? {}} />
|
||||
<SplineStylePickerSet props={props ?? {}} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -65,7 +70,15 @@ function useStyleChangeCallback() {
|
|||
)
|
||||
}
|
||||
|
||||
function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) {
|
||||
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
|
||||
|
||||
function CommonStylePickerSet({
|
||||
props,
|
||||
opacity,
|
||||
}: {
|
||||
props: TLNullableShapeProps
|
||||
opacity: number | null
|
||||
}) {
|
||||
const editor = useEditor()
|
||||
const msg = useTranslation()
|
||||
|
||||
|
@ -73,14 +86,14 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) {
|
|||
|
||||
const handleOpacityValueChange = React.useCallback(
|
||||
(value: number, ephemeral: boolean) => {
|
||||
const item = styles.opacity[value]
|
||||
editor.setProp(item.type, item.id, ephemeral)
|
||||
const item = tldrawSupportedOpacities[value]
|
||||
editor.setOpacity(item, ephemeral)
|
||||
editor.isChangingStyle = true
|
||||
},
|
||||
[editor]
|
||||
)
|
||||
|
||||
const { color, fill, dash, size, opacity } = props
|
||||
const { color, fill, dash, size } = props
|
||||
|
||||
if (
|
||||
color === undefined &&
|
||||
|
@ -94,7 +107,14 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) {
|
|||
|
||||
const showPickers = fill !== undefined || dash !== undefined || size !== undefined
|
||||
|
||||
const opacityIndex = styles.opacity.findIndex((s) => s.id === opacity)
|
||||
const opacityIndex =
|
||||
opacity === null
|
||||
? -1
|
||||
: tldrawSupportedOpacities.indexOf(
|
||||
minBy(tldrawSupportedOpacities, (supportedOpacity) =>
|
||||
Math.abs(supportedOpacity - opacity)
|
||||
)!
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -112,10 +132,10 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) {
|
|||
{opacity === undefined ? null : (
|
||||
<Slider
|
||||
data-testid="style.opacity"
|
||||
value={opacityIndex >= 0 ? opacityIndex : styles.opacity.length - 1}
|
||||
value={opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
|
||||
label={opacity ? `opacity-style.${opacity}` : 'style-panel.mixed'}
|
||||
onValueChange={handleOpacityValueChange}
|
||||
steps={styles.opacity.length - 1}
|
||||
steps={tldrawSupportedOpacities.length - 1}
|
||||
title={msg('style-panel.opacity')}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -79,6 +79,7 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
y: element.y,
|
||||
rotation: 0,
|
||||
isLocked: element.locked,
|
||||
opacity: getOpacity(element.opacity),
|
||||
} as const
|
||||
|
||||
if (element.angle !== 0) {
|
||||
|
@ -121,7 +122,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
type: 'geo',
|
||||
props: {
|
||||
geo: element.type,
|
||||
opacity: getOpacity(element.opacity),
|
||||
url: element.link ?? '',
|
||||
w: element.width,
|
||||
h: element.height,
|
||||
|
@ -142,7 +142,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
props: {
|
||||
dash: getDash(element),
|
||||
size: strokeWidthsToSizes[element.strokeWidth],
|
||||
opacity: getOpacity(element.opacity),
|
||||
color: colorsToColors[element.strokeColor] ?? 'black',
|
||||
segments: [
|
||||
{
|
||||
|
@ -169,7 +168,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
props: {
|
||||
dash: getDash(element),
|
||||
size: strokeWidthsToSizes[element.strokeWidth],
|
||||
opacity: getOpacity(element.opacity),
|
||||
color: colorsToColors[element.strokeColor] ?? 'black',
|
||||
spline: element.roundness ? 'cubic' : 'line',
|
||||
handles: {
|
||||
|
@ -234,7 +232,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
text,
|
||||
bend: getBend(element, start, end),
|
||||
dash: getDash(element),
|
||||
opacity: getOpacity(element.opacity),
|
||||
size: strokeWidthsToSizes[element.strokeWidth] ?? 'm',
|
||||
color: colorsToColors[element.strokeColor] ?? 'black',
|
||||
start: startTargetId
|
||||
|
@ -277,7 +274,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
size,
|
||||
scale,
|
||||
font: fontFamilyToFontType[element.fontFamily] ?? 'draw',
|
||||
opacity: getOpacity(element.opacity),
|
||||
color: colorsToColors[element.strokeColor] ?? 'black',
|
||||
text: element.text,
|
||||
align: textAlignToAlignTypes[element.textAlign],
|
||||
|
@ -308,7 +304,6 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
...base,
|
||||
type: 'image',
|
||||
props: {
|
||||
opacity: getOpacity(element.opacity),
|
||||
w: element.width,
|
||||
h: element.height,
|
||||
assetId,
|
||||
|
@ -370,16 +365,16 @@ export async function pasteExcalidrawContent(editor: Editor, clipboard: any, poi
|
|||
const getOpacity = (opacity: number): TLOpacityType => {
|
||||
const t = opacity / 100
|
||||
if (t < 0.2) {
|
||||
return '0.1'
|
||||
return 0.1
|
||||
} else if (t < 0.4) {
|
||||
return '0.25'
|
||||
return 0.25
|
||||
} else if (t < 0.6) {
|
||||
return '0.5'
|
||||
return 0.5
|
||||
} else if (t < 0.8) {
|
||||
return '0.75'
|
||||
return 0.75
|
||||
}
|
||||
|
||||
return '1'
|
||||
return 1
|
||||
}
|
||||
|
||||
const strokeWidthsToSizes: Record<number, TLSizeType> = {
|
||||
|
|
Loading…
Reference in a new issue