|
|
|
@ -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 {
|
|
|
|
|
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 {
|
|
|
|
|
} from "../element/Hyperlink";
|
|
|
|
|
import { isLinearElement } from "../element/typeChecks";
|
|
|
|
|
|
|
|
|
|
const hasEmojiSupport = supportsEmoji();
|
|
|
|
|
export const DEFAULT_SPACING = 2;
|
|
|
|
|
|
|
|
|
|
const strokeRectWithRotation = (
|
|
|
|
@ -159,7 +154,6 @@ const strokeGrid = (
|
|
|
|
|
|
|
|
|
|
const renderSingleLinearPoint = (
|
|
|
|
|
context: CanvasRenderingContext2D,
|
|
|
|
|
appState: AppState,
|
|
|
|
|
renderConfig: RenderConfig,
|
|
|
|
|
point: Point,
|
|
|
|
|
radius: number,
|
|
|
|
@ -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 = (
|
|
|
|
|
if (appState.editingLinearElement) {
|
|
|
|
|
renderSingleLinearPoint(
|
|
|
|
|
context,
|
|
|
|
|
appState,
|
|
|
|
|
renderConfig,
|
|
|
|
|
segmentMidPoint,
|
|
|
|
|
radius,
|
|
|
|
@ -248,7 +234,6 @@ const renderLinearPointHandles = (
|
|
|
|
|
highlightPoint(segmentMidPoint, context, renderConfig);
|
|
|
|
|
renderSingleLinearPoint(
|
|
|
|
|
context,
|
|
|
|
|
appState,
|
|
|
|
|
renderConfig,
|
|
|
|
|
segmentMidPoint,
|
|
|
|
|
radius,
|
|
|
|
@ -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 = ({
|
|
|
|
|
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 = ({
|
|
|
|
|
x -= appState.offsetLeft;
|
|
|
|
|
y -= appState.offsetTop;
|
|
|
|
|
|
|
|
|
|
const width = 9;
|
|
|
|
|
const width = 11;
|
|
|
|
|
const height = 14;
|
|
|
|
|
|
|
|
|
|
const isOutOfBounds =
|
|
|
|
@ -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 = ({
|
|
|
|
|
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 = (
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// render elements
|
|
|
|
|
elements.forEach((element, index) => {
|
|
|
|
|
elements.forEach((element) => {
|
|
|
|
|
if (!element.isDeleted) {
|
|
|
|
|
try {
|
|
|
|
|
renderElementToSvg(
|
|
|
|
|