|
|
|
|
@ -30,7 +30,7 @@ import {
@@ -30,7 +30,7 @@ import {
|
|
|
|
|
import { getSelectedElements } from "../scene/selection"; |
|
|
|
|
|
|
|
|
|
import { renderElement, renderElementToSvg } from "./renderElement"; |
|
|
|
|
import { getClientColors } from "../clients"; |
|
|
|
|
import { getClientColor } from "../clients"; |
|
|
|
|
import { LinearElementEditor } from "../element/linearElementEditor"; |
|
|
|
|
import { |
|
|
|
|
isSelectedViaGroup, |
|
|
|
|
@ -48,11 +48,7 @@ import {
@@ -48,11 +48,7 @@ import {
|
|
|
|
|
TransformHandles, |
|
|
|
|
TransformHandleType, |
|
|
|
|
} from "../element/transformHandles"; |
|
|
|
|
import { |
|
|
|
|
viewportCoordsToSceneCoords, |
|
|
|
|
supportsEmoji, |
|
|
|
|
throttleRAF, |
|
|
|
|
} from "../utils"; |
|
|
|
|
import { viewportCoordsToSceneCoords, throttleRAF } from "../utils"; |
|
|
|
|
import { UserIdleState } from "../types"; |
|
|
|
|
import { THEME_FILTER } from "../constants"; |
|
|
|
|
import { |
|
|
|
|
@ -61,7 +57,6 @@ import {
@@ -61,7 +57,6 @@ import {
|
|
|
|
|
} from "../element/Hyperlink"; |
|
|
|
|
import { isLinearElement } from "../element/typeChecks"; |
|
|
|
|
|
|
|
|
|
const hasEmojiSupport = supportsEmoji(); |
|
|
|
|
export const DEFAULT_SPACING = 2; |
|
|
|
|
|
|
|
|
|
const strokeRectWithRotation = ( |
|
|
|
|
@ -159,7 +154,6 @@ const strokeGrid = (
@@ -159,7 +154,6 @@ const strokeGrid = (
|
|
|
|
|
|
|
|
|
|
const renderSingleLinearPoint = ( |
|
|
|
|
context: CanvasRenderingContext2D, |
|
|
|
|
appState: AppState, |
|
|
|
|
renderConfig: RenderConfig, |
|
|
|
|
point: Point, |
|
|
|
|
radius: number, |
|
|
|
|
@ -206,14 +200,7 @@ const renderLinearPointHandles = (
@@ -206,14 +200,7 @@ const renderLinearPointHandles = (
|
|
|
|
|
const isSelected = |
|
|
|
|
!!appState.editingLinearElement?.selectedPointsIndices?.includes(idx); |
|
|
|
|
|
|
|
|
|
renderSingleLinearPoint( |
|
|
|
|
context, |
|
|
|
|
appState, |
|
|
|
|
renderConfig, |
|
|
|
|
point, |
|
|
|
|
radius, |
|
|
|
|
isSelected, |
|
|
|
|
); |
|
|
|
|
renderSingleLinearPoint(context, renderConfig, point, radius, isSelected); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
//Rendering segment mid points
|
|
|
|
|
@ -237,7 +224,6 @@ const renderLinearPointHandles = (
@@ -237,7 +224,6 @@ const renderLinearPointHandles = (
|
|
|
|
|
if (appState.editingLinearElement) { |
|
|
|
|
renderSingleLinearPoint( |
|
|
|
|
context, |
|
|
|
|
appState, |
|
|
|
|
renderConfig, |
|
|
|
|
segmentMidPoint, |
|
|
|
|
radius, |
|
|
|
|
@ -248,7 +234,6 @@ const renderLinearPointHandles = (
@@ -248,7 +234,6 @@ const renderLinearPointHandles = (
|
|
|
|
|
highlightPoint(segmentMidPoint, context, renderConfig); |
|
|
|
|
renderSingleLinearPoint( |
|
|
|
|
context, |
|
|
|
|
appState, |
|
|
|
|
renderConfig, |
|
|
|
|
segmentMidPoint, |
|
|
|
|
radius, |
|
|
|
|
@ -258,7 +243,6 @@ const renderLinearPointHandles = (
@@ -258,7 +243,6 @@ const renderLinearPointHandles = (
|
|
|
|
|
} else if (appState.editingLinearElement || points.length === 2) { |
|
|
|
|
renderSingleLinearPoint( |
|
|
|
|
context, |
|
|
|
|
appState, |
|
|
|
|
renderConfig, |
|
|
|
|
segmentMidPoint, |
|
|
|
|
POINT_HANDLE_SIZE / 2, |
|
|
|
|
@ -527,7 +511,7 @@ export const _renderScene = ({
@@ -527,7 +511,7 @@ export const _renderScene = ({
|
|
|
|
|
selectionColors.push( |
|
|
|
|
...renderConfig.remoteSelectedElementIds[element.id].map( |
|
|
|
|
(socketId) => { |
|
|
|
|
const { background } = getClientColors(socketId, appState); |
|
|
|
|
const background = getClientColor(socketId); |
|
|
|
|
return background; |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
@ -647,7 +631,7 @@ export const _renderScene = ({
@@ -647,7 +631,7 @@ export const _renderScene = ({
|
|
|
|
|
x -= appState.offsetLeft; |
|
|
|
|
y -= appState.offsetTop; |
|
|
|
|
|
|
|
|
|
const width = 9; |
|
|
|
|
const width = 11; |
|
|
|
|
const height = 14; |
|
|
|
|
|
|
|
|
|
const isOutOfBounds = |
|
|
|
|
@ -661,15 +645,20 @@ export const _renderScene = ({
@@ -661,15 +645,20 @@ export const _renderScene = ({
|
|
|
|
|
y = Math.max(y, 0); |
|
|
|
|
y = Math.min(y, normalizedCanvasHeight - height); |
|
|
|
|
|
|
|
|
|
const { background, stroke } = getClientColors(clientId, appState); |
|
|
|
|
const background = getClientColor(clientId); |
|
|
|
|
|
|
|
|
|
context.save(); |
|
|
|
|
context.strokeStyle = stroke; |
|
|
|
|
context.strokeStyle = background; |
|
|
|
|
context.fillStyle = background; |
|
|
|
|
|
|
|
|
|
const userState = renderConfig.remotePointerUserStates[clientId]; |
|
|
|
|
if (isOutOfBounds || userState === UserIdleState.AWAY) { |
|
|
|
|
context.globalAlpha = 0.48; |
|
|
|
|
const isInactive = |
|
|
|
|
isOutOfBounds || |
|
|
|
|
userState === UserIdleState.IDLE || |
|
|
|
|
userState === UserIdleState.AWAY; |
|
|
|
|
|
|
|
|
|
if (isInactive) { |
|
|
|
|
context.globalAlpha = 0.3; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
@ -686,73 +675,91 @@ export const _renderScene = ({
@@ -686,73 +675,91 @@ export const _renderScene = ({
|
|
|
|
|
context.beginPath(); |
|
|
|
|
context.arc(x, y, 15, 0, 2 * Math.PI, false); |
|
|
|
|
context.lineWidth = 1; |
|
|
|
|
context.strokeStyle = stroke; |
|
|
|
|
context.strokeStyle = background; |
|
|
|
|
context.stroke(); |
|
|
|
|
context.closePath(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Background (white outline) for arrow
|
|
|
|
|
context.fillStyle = oc.white; |
|
|
|
|
context.strokeStyle = oc.white; |
|
|
|
|
context.lineWidth = 6; |
|
|
|
|
context.lineJoin = "round"; |
|
|
|
|
context.beginPath(); |
|
|
|
|
context.moveTo(x, y); |
|
|
|
|
context.lineTo(x + 1, y + 14); |
|
|
|
|
context.lineTo(x + 0, y + 14); |
|
|
|
|
context.lineTo(x + 4, y + 9); |
|
|
|
|
context.lineTo(x + 9, y + 10); |
|
|
|
|
context.lineTo(x, y); |
|
|
|
|
context.fill(); |
|
|
|
|
context.lineTo(x + 11, y + 8); |
|
|
|
|
context.closePath(); |
|
|
|
|
context.stroke(); |
|
|
|
|
context.fill(); |
|
|
|
|
|
|
|
|
|
const username = renderConfig.remotePointerUsernames[clientId]; |
|
|
|
|
|
|
|
|
|
let idleState = ""; |
|
|
|
|
if (userState === UserIdleState.AWAY) { |
|
|
|
|
idleState = hasEmojiSupport ? "⚫️" : ` (${UserIdleState.AWAY})`; |
|
|
|
|
} else if (userState === UserIdleState.IDLE) { |
|
|
|
|
idleState = hasEmojiSupport ? "💤" : ` (${UserIdleState.IDLE})`; |
|
|
|
|
// Arrow
|
|
|
|
|
context.fillStyle = background; |
|
|
|
|
context.strokeStyle = background; |
|
|
|
|
context.lineWidth = 2; |
|
|
|
|
context.lineJoin = "round"; |
|
|
|
|
context.beginPath(); |
|
|
|
|
if (isInactive) { |
|
|
|
|
context.moveTo(x - 1, y - 1); |
|
|
|
|
context.lineTo(x - 1, y + 15); |
|
|
|
|
context.lineTo(x + 5, y + 10); |
|
|
|
|
context.lineTo(x + 12, y + 9); |
|
|
|
|
context.closePath(); |
|
|
|
|
context.fill(); |
|
|
|
|
} else { |
|
|
|
|
context.moveTo(x, y); |
|
|
|
|
context.lineTo(x + 0, y + 14); |
|
|
|
|
context.lineTo(x + 4, y + 9); |
|
|
|
|
context.lineTo(x + 11, y + 8); |
|
|
|
|
context.closePath(); |
|
|
|
|
context.fill(); |
|
|
|
|
context.stroke(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const usernameAndIdleState = `${username || ""}${ |
|
|
|
|
idleState ? ` ${idleState}` : "" |
|
|
|
|
}`;
|
|
|
|
|
const username = renderConfig.remotePointerUsernames[clientId] || ""; |
|
|
|
|
|
|
|
|
|
if (!isOutOfBounds && usernameAndIdleState) { |
|
|
|
|
const offsetX = x + width; |
|
|
|
|
const offsetY = y + height; |
|
|
|
|
const paddingHorizontal = 4; |
|
|
|
|
const paddingVertical = 4; |
|
|
|
|
const measure = context.measureText(usernameAndIdleState); |
|
|
|
|
if (!isOutOfBounds && username) { |
|
|
|
|
context.font = "600 12px sans-serif"; // font has to be set before context.measureText()
|
|
|
|
|
|
|
|
|
|
const offsetX = x + width / 2; |
|
|
|
|
const offsetY = y + height + 2; |
|
|
|
|
const paddingHorizontal = 5; |
|
|
|
|
const paddingVertical = 3; |
|
|
|
|
const measure = context.measureText(username); |
|
|
|
|
const measureHeight = |
|
|
|
|
measure.actualBoundingBoxDescent + measure.actualBoundingBoxAscent; |
|
|
|
|
const finalHeight = Math.max(measureHeight, 12); |
|
|
|
|
|
|
|
|
|
const boxX = offsetX - 1; |
|
|
|
|
const boxY = offsetY - 1; |
|
|
|
|
const boxWidth = measure.width + 2 * paddingHorizontal + 2; |
|
|
|
|
const boxHeight = measureHeight + 2 * paddingVertical + 2; |
|
|
|
|
const boxWidth = measure.width + 2 + paddingHorizontal * 2 + 2; |
|
|
|
|
const boxHeight = finalHeight + 2 + paddingVertical * 2 + 2; |
|
|
|
|
if (context.roundRect) { |
|
|
|
|
context.beginPath(); |
|
|
|
|
context.roundRect( |
|
|
|
|
boxX, |
|
|
|
|
boxY, |
|
|
|
|
boxWidth, |
|
|
|
|
boxHeight, |
|
|
|
|
4 / renderConfig.zoom.value, |
|
|
|
|
); |
|
|
|
|
context.roundRect(boxX, boxY, boxWidth, boxHeight, 8); |
|
|
|
|
context.fillStyle = background; |
|
|
|
|
context.fill(); |
|
|
|
|
context.fillStyle = stroke; |
|
|
|
|
context.strokeStyle = oc.white; |
|
|
|
|
context.stroke(); |
|
|
|
|
} else { |
|
|
|
|
// Border
|
|
|
|
|
context.fillStyle = stroke; |
|
|
|
|
context.fillStyle = oc.white; |
|
|
|
|
context.fillRect(boxX, boxY, boxWidth, boxHeight); |
|
|
|
|
// Background
|
|
|
|
|
context.fillStyle = background; |
|
|
|
|
context.fillRect(offsetX, offsetY, boxWidth - 2, boxHeight - 2); |
|
|
|
|
} |
|
|
|
|
context.fillStyle = oc.white; |
|
|
|
|
context.fillStyle = oc.black; |
|
|
|
|
|
|
|
|
|
context.fillText( |
|
|
|
|
usernameAndIdleState, |
|
|
|
|
offsetX + paddingHorizontal, |
|
|
|
|
offsetY + paddingVertical + measure.actualBoundingBoxAscent, |
|
|
|
|
username, |
|
|
|
|
offsetX + paddingHorizontal + 1, |
|
|
|
|
offsetY + |
|
|
|
|
paddingVertical + |
|
|
|
|
measure.actualBoundingBoxAscent + |
|
|
|
|
Math.floor((finalHeight - measureHeight) / 2) + |
|
|
|
|
1, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -1145,7 +1152,7 @@ export const renderSceneToSvg = (
@@ -1145,7 +1152,7 @@ export const renderSceneToSvg = (
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
// render elements
|
|
|
|
|
elements.forEach((element, index) => { |
|
|
|
|
elements.forEach((element) => { |
|
|
|
|
if (!element.isDeleted) { |
|
|
|
|
try { |
|
|
|
|
renderElementToSvg( |
|
|
|
|
|