diff --git a/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx b/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx index d337eb0385d..cd4885c5d9d 100644 --- a/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx @@ -46,12 +46,7 @@ export const contextBarActionTypes = [ ] as const type ContextBarActionType = typeof contextBarActionTypes[number] -const singleShapeActions: ContextBarActionType[] = [ - 'Edit', - 'YoutubeLink', - 'IFrameSource', - 'Links', -] +const singleShapeActions: ContextBarActionType[] = ['Edit', 'YoutubeLink', 'IFrameSource', 'Links'] const contextBarActionMapping = new Map() @@ -68,13 +63,13 @@ export const shapeMapping: Record = { ], youtube: ['YoutubeLink', 'Links'], iframe: ['IFrameSource', 'Links'], - box: ['Swatch', 'NoFill', 'StrokeType', 'Links'], - ellipse: ['Swatch', 'NoFill', 'StrokeType', 'Links'], - polygon: ['Swatch', 'NoFill', 'StrokeType', 'Links'], - line: ['Edit', 'Swatch', 'ArrowMode', 'Links'], + box: ['Edit', 'TextStyle', 'Swatch', 'NoFill', 'StrokeType', 'Links'], + ellipse: ['Edit', 'TextStyle', 'Swatch', 'NoFill', 'StrokeType', 'Links'], + polygon: ['Edit', 'TextStyle', 'Swatch', 'NoFill', 'StrokeType', 'Links'], + line: ['Edit', 'TextStyle', 'Swatch', 'ArrowMode', 'Links'], pencil: ['Swatch', 'Links'], highlighter: ['Swatch', 'Links'], - text: ['Edit', 'Swatch', 'ScaleLevel', 'AutoResizing', 'TextStyle', 'Links'], + text: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'AutoResizing', 'Links'], html: ['ScaleLevel', 'AutoResizing', 'Links'], image: ['Links'], video: ['Links'], @@ -93,6 +88,10 @@ function filterShapeByAction(shapes: Shape[], type: ContextBarA const EditAction = observer(() => { const app = useApp() const shape = filterShapeByAction(app.selectedShapesArray, 'Edit')[0] + const iconName = + ('label' in shape.props && shape.props.label) || ('text' in shape.props && shape.props.text) + ? 'forms' + : 'text' return ( ) }) diff --git a/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx b/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx index 4738d16e7c8..bb4aec3e507 100644 --- a/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/GeometryTools/GeometryTools.tsx @@ -39,7 +39,11 @@ export const GeometryTools = observer(function GeometryTools() { geo.id === activeGeomId)!} /> - + geo.id === app.selectedTool.id)} + className="tl-popover-indicator" + name="chevron-down-left" + /> diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx index bd487a75d45..448b32c3fd3 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx @@ -1,15 +1,22 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { SVGContainer, TLComponentProps, useApp } from '@tldraw/react' -import { TLBoxShape, TLBoxShapeProps, getComputedColor } from '@tldraw/core' +import { SVGContainer, TLComponentProps } from '@tldraw/react' +import { TLBoxShape, TLBoxShapeProps, getComputedColor, getTextLabelSize } from '@tldraw/core' +import Vec from '@tldraw/vec' +import * as React from 'react' import { observer } from 'mobx-react-lite' import { CustomStyleProps, withClampedStyles } from './style-props' import { BindingIndicator } from './BindingIndicator' - +import { TextLabel } from './text/TextLabel' export interface BoxShapeProps extends TLBoxShapeProps, CustomStyleProps { borderRadius: number type: 'box' + label: string + fontWeight: number + italic: boolean } +const font = '18px / 1 var(--ls-font-family)' + export class BoxShape extends TLBoxShape { static id = 'box' @@ -23,62 +30,143 @@ export class BoxShape extends TLBoxShape { stroke: '', fill: '', noFill: false, + fontWeight: 400, + italic: false, strokeType: 'line', strokeWidth: 2, opacity: 1, + label: '', } - ReactComponent = observer(({ events, isErasing, isBinding, isSelected }: TLComponentProps) => { - const { - props: { - size: [w, h], - stroke, - fill, - noFill, - strokeWidth, - strokeType, - borderRadius, - opacity, - }, - } = this + canEdit = true - return ( - - {isBinding && } - - - - ) - }) + ReactComponent = observer( + ({ events, isErasing, isBinding, isSelected, isEditing, onEditingEnd }: TLComponentProps) => { + const { + props: { + size: [w, h], + stroke, + fill, + noFill, + strokeWidth, + strokeType, + borderRadius, + opacity, + label, + italic, + fontWeight, + }, + } = this + + const labelSize = + label || isEditing + ? getTextLabelSize( + label, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) + : [0, 0] + const midPoint = Vec.mul(this.props.size, 0.5) + const scale = Math.max(0.5, Math.min(1, w / labelSize[0], h / labelSize[1])) + const bounds = this.getBounds() + + const offset = React.useMemo(() => { + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) + }, [bounds, scale, midPoint]) + + const handleLabelChange = React.useCallback( + (label: string) => { + this.update?.({ label }) + }, + [label] + ) + + return ( +
+ + + {isBinding && } + + + +
+ ) + } + ) ReactIndicator = observer(() => { const { props: { size: [w, h], borderRadius, + label, + fontWeight, }, } = this - return + + const bounds = this.getBounds() + const labelSize = label + ? getTextLabelSize( + label, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) + : [0, 0] + const scale = Math.max(0.5, Math.min(1, w / labelSize[0], h / labelSize[1])) + const midPoint = Vec.mul(this.props.size, 0.5) + + const offset = React.useMemo(() => { + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) + }, [bounds, scale, midPoint]) + + return ( + + + {label && ( + + )} + + ) }) validateProps = (props: Partial) => { diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx index 8c366ab218a..9d824c57f24 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx @@ -1,14 +1,26 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { TLEllipseShapeProps, TLEllipseShape, getComputedColor } from '@tldraw/core' +import { + TLEllipseShapeProps, + TLEllipseShape, + getComputedColor, + getTextLabelSize, +} from '@tldraw/core' import { SVGContainer, TLComponentProps } from '@tldraw/react' +import Vec from '@tldraw/vec' +import * as React from 'react' import { observer } from 'mobx-react-lite' import { CustomStyleProps, withClampedStyles } from './style-props' - +import { TextLabel } from './text/TextLabel' export interface EllipseShapeProps extends TLEllipseShapeProps, CustomStyleProps { type: 'ellipse' size: number[] + label: string + fontWeight: number + italic: boolean } +const font = '18px / 1 var(--ls-font-family)' + export class EllipseShape extends TLEllipseShape { static id = 'ellipse' @@ -21,50 +33,131 @@ export class EllipseShape extends TLEllipseShape { stroke: '', fill: '', noFill: false, + fontWeight: 400, + italic: false, strokeType: 'line', strokeWidth: 2, opacity: 1, + label: '', } - ReactComponent = observer(({ isSelected, isErasing, events }: TLComponentProps) => { - const { - size: [w, h], - stroke, - fill, - noFill, - strokeWidth, - strokeType, - opacity, - } = this.props - return ( - - - - - ) - }) + canEdit = true + + ReactComponent = observer( + ({ isSelected, isErasing, events, isEditing, onEditingEnd }: TLComponentProps) => { + const { + size: [w, h], + stroke, + fill, + noFill, + strokeWidth, + strokeType, + opacity, + label, + italic, + fontWeight, + } = this.props + + const labelSize = + label || isEditing + ? getTextLabelSize( + label, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) + : [0, 0] + const midPoint = Vec.mul(this.props.size, 0.5) + const scale = Math.max(0.5, Math.min(1, w / labelSize[0], h / labelSize[1])) + const bounds = this.getBounds() + + const offset = React.useMemo(() => { + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) + }, [bounds, scale, midPoint]) + + const handleLabelChange = React.useCallback( + (label: string) => { + this.update?.({ label }) + }, + [label] + ) + + return ( +
+ + + + + +
+ ) + } + ) ReactIndicator = observer(() => { const { size: [w, h], + label, + fontWeight, } = this.props + + const bounds = this.getBounds() + const labelSize = label + ? getTextLabelSize( + label, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) + : [0, 0] + const scale = Math.max(0.5, Math.min(1, w / labelSize[0], h / labelSize[1])) + const midPoint = Vec.mul(this.props.size, 0.5) + + const offset = React.useMemo(() => { + const offset = Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) + return offset + }, [bounds, scale, midPoint]) + return ( - + + + {label && ( + + )} + ) }) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx index 8f897e78a2a..1e13a6a98df 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx @@ -14,6 +14,8 @@ import { TextLabel } from './text/TextLabel' interface LineShapeProps extends CustomStyleProps, TLLineShapeProps { type: 'line' label: string + fontWeight: number + italic: boolean } const font = '18px / 1 var(--ls-font-family)' @@ -33,6 +35,8 @@ export class LineShape extends TLLineShape { stroke: '', fill: '', noFill: true, + fontWeight: 400, + italic: false, strokeType: 'line', strokeWidth: 1, opacity: 1, @@ -51,6 +55,8 @@ export class LineShape extends TLLineShape { handles: { start, end }, opacity, label, + italic, + fontWeight, id, } = this.props const labelSize = label || isEditing ? getTextLabelSize(label, font, 4) : [0, 0] @@ -62,8 +68,7 @@ export class LineShape extends TLLineShape { ) const bounds = this.getBounds() const offset = React.useMemo(() => { - const offset = Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) - return offset + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) }, [bounds, scale, midPoint]) const handleLabelChange = React.useCallback( (label: string) => { @@ -83,6 +88,9 @@ export class LineShape extends TLLineShape { isEditing={isEditing} onChange={handleLabelChange} onBlur={onEditingEnd} + fontStyle={italic ? 'italic' : 'normal'} + fontWeight={fontWeight} + pointerEvents={!!label} /> @@ -111,8 +119,7 @@ export class LineShape extends TLLineShape { Math.min(1, Math.max(dist / (labelSize[1] + 128), dist / (labelSize[0] + 128))) ) const offset = React.useMemo(() => { - const offset = Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) - return offset + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) }, [bounds, scale, midPoint]) return ( diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx index 9e09076b290..c3c1414a17f 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx @@ -400,7 +400,8 @@ export class LogseqPortalShape extends TLBoxShape { if ( boundScreenCenter[0] > screenSize[0] - 400 || boundScreenCenter[1] > screenSize[1] - 240 || - app.viewport.camera.zoom > 1.5 || app.viewport.camera.zoom < 0.5 + app.viewport.camera.zoom > 1.5 || + app.viewport.camera.zoom < 0.5 ) { app.viewport.zoomToBounds({ ...this.bounds, minY: this.bounds.maxY + 25 }) } @@ -445,7 +446,6 @@ export class LogseqPortalShape extends TLBoxShape { }} {...events} > -
{isBinding && }
{ static id = 'polygon' @@ -22,50 +35,148 @@ export class PolygonShape extends TLPolygonShape { isFlippedY: false, stroke: '', fill: '', + fontWeight: 400, + italic: false, noFill: false, strokeType: 'line', strokeWidth: 2, opacity: 1, + label: '', } - ReactComponent = observer(({ events, isErasing, isSelected }: TLComponentProps) => { - const { - offset: [x, y], - props: { stroke, fill, noFill, strokeWidth, opacity, strokeType }, - } = this - const path = this.getVertices(strokeWidth / 2).join() - return ( - - - - { + const { + offset: [x, y], + props: { + stroke, + fill, + noFill, + strokeWidth, + opacity, + strokeType, + label, + italic, + fontWeight, + }, + } = this + + const path = this.getVertices(strokeWidth / 2).join() + + const labelSize = + label || isEditing + ? getTextLabelSize( + label, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) + : [0, 0] + // Using the centroid of the polygon as the label position is preferable in this case + // This shape is an isosceles triangle at the time of writing this comment + const midPoint = [this.props.size[0] / 2, (this.props.size[1] * 2) / 3] + const scale = Math.max( + 0.5, + Math.min( + 1, + this.props.size[0] / (labelSize[0] * 2), + this.props.size[1] / (labelSize[1] * 2) + ) + ) + const bounds = this.getBounds() + + const offset = React.useMemo(() => { + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) + }, [bounds, scale, midPoint]) + + const handleLabelChange = React.useCallback( + (label: string) => { + this.update?.({ label }) + }, + [label] + ) + + return ( +
+ - - - ) - }) + + + + + + +
+ ) + } + ) ReactIndicator = observer(() => { const { offset: [x, y], - props: { strokeWidth }, + props: { label, strokeWidth, fontWeight }, } = this + + const bounds = this.getBounds() + const labelSize = label + ? getTextLabelSize( + label, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) + : [0, 0] + const midPoint = [this.props.size[0] / 2, (this.props.size[1] * 2) / 3] + const scale = Math.max( + 0.5, + Math.min(1, this.props.size[0] / (labelSize[0] * 2), this.props.size[1] / (labelSize[1] * 2)) + ) + + const offset = React.useMemo(() => { + return Vec.sub(midPoint, Vec.toFixed([bounds.width / 2, bounds.height / 2])) + }, [bounds, scale, midPoint]) + return ( - + + + {label && ( + + )} + ) }) diff --git a/tldraw/apps/tldraw-logseq/src/lib/shapes/text/TextLabel.tsx b/tldraw/apps/tldraw-logseq/src/lib/shapes/text/TextLabel.tsx index 3136638f28f..baa1cae6b4d 100644 --- a/tldraw/apps/tldraw-logseq/src/lib/shapes/text/TextLabel.tsx +++ b/tldraw/apps/tldraw-logseq/src/lib/shapes/text/TextLabel.tsx @@ -9,22 +9,28 @@ export interface TextLabelProps { font: string text: string color: string + fontStyle: string + fontWeight: number onBlur?: () => void onChange: (text: string) => void offsetY?: number offsetX?: number scale?: number isEditing?: boolean + pointerEvents?: boolean } export const TextLabel = React.memo(function TextLabel({ font, text, color, + fontStyle, + fontWeight, offsetX = 0, offsetY = 0, scale = 1, isEditing = false, + pointerEvents = false, onBlur, onChange, }: TextLabelProps) { @@ -121,11 +127,15 @@ export const TextLabel = React.memo(function TextLabel({ React.useLayoutEffect(() => { const elm = rInnerWrapper.current if (!elm) return - const size = getTextLabelSize(text, font, 4) + const size = getTextLabelSize( + text, + { fontFamily: 'var(--ls-font-family)', fontSize: 18, lineHeight: 1, fontWeight }, + 4 + ) elm.style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)` elm.style.width = size[0] + 1 + 'px' elm.style.height = size[1] + 1 + 'px' - }, [text, font, offsetY, offsetX, scale]) + }, [text, fontWeight, offsetY, offsetX, scale]) return (
@@ -134,8 +144,10 @@ export const TextLabel = React.memo(function TextLabel({ ref={rInnerWrapper} style={{ font, + fontStyle, + fontWeight, color, - pointerEvents: text ? 'all' : 'none', + pointerEvents: pointerEvents ? 'all' : 'none', userSelect: isEditing ? 'text' : 'none', }} > @@ -145,6 +157,8 @@ export const TextLabel = React.memo(function TextLabel({ style={{ font, color, + fontStyle, + fontWeight, }} className="tl-text-label-textarea" name="text" diff --git a/tldraw/apps/tldraw-logseq/src/styles.css b/tldraw/apps/tldraw-logseq/src/styles.css index fef66dc2edf..ce847e3db1e 100644 --- a/tldraw/apps/tldraw-logseq/src/styles.css +++ b/tldraw/apps/tldraw-logseq/src/styles.css @@ -339,7 +339,6 @@ button.tl-select-input-trigger { &[aria-expanded='true'] { .tl-popover-indicator { transform: rotate(180deg); - color: #000; } } } @@ -351,6 +350,10 @@ button.tl-select-input-trigger { pointer-events: none; left: 0; bottom: -3px; + + &[data-selected='true'] { + color: #000; + } } .floating-panel[data-tool-locked='true'] > .tl-button[data-selected='true']::after { @@ -427,7 +430,7 @@ button.tl-select-input-trigger { .tl-text-label-inner-wrapper { position: absolute; - padding: 4px; + padding: 6px 4px 4px; z-index: 1; min-height: 1px; min-width: 1px; @@ -446,7 +449,7 @@ button.tl-select-input-trigger { z-index: 1; border: none; - padding: 4px; + padding: 6px 4px 4px; resize: none; text-align: inherit; min-height: inherit; @@ -459,7 +462,7 @@ button.tl-select-input-trigger { backface-visibility: hidden; display: inline-block; pointer-events: all; - background: transparent; + background: transparent !important; user-select: text; -webkit-font-smoothing: subpixel-antialiased; white-space: pre-wrap;