Compare commits

..

2 Commits

Author SHA1 Message Date
Preet Shihn
b20fe944e7 add max shape count 2021-02-05 10:27:06 -08:00
Preet Shihn
1156ef6b96 pixelated images initial experiment 2021-02-04 23:27:35 -08:00
31 changed files with 249 additions and 643 deletions

View File

@@ -11,7 +11,6 @@
<img src="https://badges.crowdin.net/excalidraw/localized.svg">
</a>
</p>
<p>Ask questions or hang out on our <a target="_blank" href="https://discord.gg/UexuTaE">discord.gg/UexuTaE</a>.</p>
</div>
## Try it now
@@ -20,14 +19,6 @@ Go to [excalidraw.com](https://excalidraw.com) to start sketching.
Read the latest news and updates on our [blog](https://blog.excalidraw.com). A good start is to see all the updates of [One Year of Excalidraw](https://blog.excalidraw.com/one-year-of-excalidraw/).
## We accept donations
If you like the project, you can become a sponsor at [Open Collective](https://opencollective.com/excalidraw).
<a href="https://opencollective.com/excalidraw#category-CONTRIBUTE" target="_blank"><img src="https://opencollective.com/excalidraw/tiers/sponsors.svg?avatarHeight=64"/></a>
<a href="https://opencollective.com/excalidraw#category-CONTRIBUTE" target="_blank"><img src="https://opencollective.com/excalidraw/tiers/backers.svg?avatarHeight=32"/></a>
## Documentation
### Shortcuts

View File

@@ -17,6 +17,6 @@ export const actionToggleGridMode = register({
};
},
checked: (appState: AppState) => appState.gridSize !== null,
contextItemLabel: "labels.showGrid",
contextItemLabel: "labels.gridMode",
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
});

View File

@@ -73,8 +73,6 @@ export const getDefaultAppState = (): Omit<
zenModeEnabled: false,
zoom: { value: 1 as NormalizedZoomValue, translation: { x: 0, y: 0 } },
viewModeEnabled: false,
networkSpeed: 0,
networkPing: 0,
};
};
@@ -155,8 +153,6 @@ const APP_STATE_STORAGE_CONF = (<
zenModeEnabled: { browser: true, export: false },
zoom: { browser: true, export: false },
viewModeEnabled: { browser: false, export: false },
networkSpeed: { browser: false, export: false },
networkPing: { browser: false, export: false },
});
const _clearAppStateForStorage = <ExportType extends "export" | "browser">(

View File

@@ -48,10 +48,8 @@ import {
ELEMENT_TRANSLATE_AMOUNT,
ENV,
EVENT,
GRID_SIZE,
LINE_CONFIRM_THRESHOLD,
MIME_TYPES,
NETWORK_TIMEOUT_MS,
POINTER_BUTTON,
TAP_TWICE_TIMEOUT,
TEXT_TO_CENTER_SNAP_THRESHOLD,
@@ -184,7 +182,6 @@ import LayerUI from "./LayerUI";
import { Stats } from "./Stats";
import { Toast } from "./Toast";
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
import { getNetworkSpeed, getNetworkPing } from "../networkStats";
const { history } = createHistory();
@@ -207,9 +204,6 @@ const gesture: Gesture = {
initialScale: null,
};
const shouldEnableNetworkStats = !!(
typeof process !== "undefined" && process.env?.REACT_APP_SOCKET_SERVER_URL
);
export type PointerDownState = Readonly<{
// The first position at which pointerDown happened
origin: Readonly<{ x: number; y: number }>;
@@ -294,8 +288,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
height: window.innerHeight,
};
private scene: Scene;
private networkSpeedIntervalId?: any;
private networkPingIntervalId?: any;
constructor(props: ExcalidrawProps) {
super(props);
const defaultAppState = getDefaultAppState();
@@ -307,8 +299,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
offsetTop,
excalidrawRef,
viewModeEnabled = false,
zenModeEnabled = false,
gridModeEnabled = false,
} = props;
this.state = {
...defaultAppState,
@@ -317,8 +307,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
height,
...this.getCanvasOffsets({ offsetLeft, offsetTop }),
viewModeEnabled,
zenModeEnabled,
gridSize: gridModeEnabled ? GRID_SIZE : null,
};
if (excalidrawRef) {
const readyPromise =
@@ -465,9 +453,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
onExportToBackend={onExportToBackend}
renderCustomFooter={renderFooter}
viewModeEnabled={viewModeEnabled}
showExitZenModeBtn={
typeof this.props?.zenModeEnabled === "undefined" && zenModeEnabled
}
/>
<div className="excalidraw-textEditorContainer" />
{this.state.showStats && (
@@ -476,8 +461,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
setAppState={this.setAppState}
elements={this.scene.getElements()}
onClose={this.toggleStats}
isCollaborating={this.props.isCollaborating}
shouldEnableNetworkStats={shouldEnableNetworkStats}
/>
)}
{this.state.toastMessage !== null && (
@@ -528,21 +511,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
let gridSize = actionResult?.appState?.gridSize || null;
if (typeof this.props.viewModeEnabled !== "undefined") {
viewModeEnabled = this.props.viewModeEnabled;
}
if (typeof this.props.zenModeEnabled !== "undefined") {
zenModeEnabled = this.props.zenModeEnabled;
}
if (typeof this.props.gridModeEnabled !== "undefined") {
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
}
this.setState(
(state) => ({
...actionResult.appState,
@@ -553,8 +526,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
offsetTop: state.offsetTop,
offsetLeft: state.offsetLeft,
viewModeEnabled,
zenModeEnabled,
gridSize,
}),
() => {
if (actionResult.syncHistory) {
@@ -757,7 +728,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
this.removeEventListeners();
this.scene.destroy();
clearTimeout(touchTimeout);
clearTimeout(this.networkSpeedIntervalId);
touchTimeout = 0;
}
@@ -875,43 +845,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
this.addEventListeners();
}
if (prevProps.zenModeEnabled !== this.props.zenModeEnabled) {
this.setState({ zenModeEnabled: !!this.props.zenModeEnabled });
}
if (prevProps.gridModeEnabled !== this.props.gridModeEnabled) {
this.setState({
gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
});
}
if (
shouldEnableNetworkStats &&
(prevState.showStats !== this.state.showStats ||
prevProps.isCollaborating !== this.props.isCollaborating)
) {
const navigator: Navigator & {
connection?: {
addEventListener: Function;
removeEventListener: Function;
};
} = window.navigator;
if (this.state.showStats && this.props.isCollaborating) {
this.calculateNetStats();
navigator?.connection?.addEventListener(
"change",
this.calculateNetStats,
);
} else {
navigator?.connection?.removeEventListener(
"change",
this.calculateNetStats,
);
clearTimeout(this.networkSpeedIntervalId);
}
}
document
.querySelector(".excalidraw")
?.classList.toggle("Appearance_dark", this.state.appearance === "dark");
@@ -1037,42 +970,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
}
private calculateNetStats = () => {
this.checkNetworkPing();
this.checkNetworkSpeed();
};
private checkNetworkPing = async () => {
if (!this.state.showStats || !this.props.isCollaborating) {
clearTimeout(this.networkPingIntervalId);
return;
}
const networkPing = await getNetworkPing();
this.setState({ networkPing });
if (this.networkPingIntervalId) {
clearTimeout(this.networkPingIntervalId);
}
this.networkPingIntervalId = setTimeout(
this.checkNetworkPing,
NETWORK_TIMEOUT_MS,
);
};
private checkNetworkSpeed = async () => {
if (!this.state.showStats || !this.props.isCollaborating) {
clearTimeout(this.networkSpeedIntervalId);
return;
}
const networkSpeed = await getNetworkSpeed();
this.setState({ networkSpeed });
if (this.networkSpeedIntervalId) {
clearTimeout(this.networkSpeedIntervalId);
}
this.networkSpeedIntervalId = setTimeout(
this.checkNetworkSpeed,
NETWORK_TIMEOUT_MS,
);
};
// Copy/paste
private onCut = withBatchedUpdates((event: ClipboardEvent) => {
@@ -3556,11 +3453,40 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
};
private handleCanvasImageDrop = async (
event: React.DragEvent<HTMLCanvasElement>,
file: File,
) => {
try {
const shapes = await (
await import(
/* webpackChunkName: "pixelated-image" */ "../data/pixelated-image"
)
).pixelateImage(file, 20, 1200, event.clientX, event.clientY);
const nextElements = [
...this.scene.getElementsIncludingDeleted(),
...shapes,
];
this.scene.replaceAllElements(nextElements);
} catch (error) {
return this.setState({
isLoading: false,
errorMessage: error.message,
});
}
};
private handleCanvasOnDrop = async (
event: React.DragEvent<HTMLCanvasElement>,
) => {
let imageFile: File | null = null;
try {
const file = event.dataTransfer.files[0];
if (file?.type.indexOf("image/") === 0) {
imageFile = file;
}
if (file?.type === "image/png" || file?.type === "image/svg+xml") {
const { elements, appState } = await loadFromBlob(file, this.state);
this.syncActionResult({
@@ -3572,8 +3498,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
commitToHistory: true,
});
return;
} else if (imageFile) {
return await this.handleCanvasImageDrop(event, imageFile);
}
} catch (error) {
if (imageFile) {
return await this.handleCanvasImageDrop(event, imageFile);
}
return this.setState({
isLoading: false,
errorMessage: error.message,
@@ -3820,10 +3751,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
separator,
actionSelectAll,
separator,
typeof this.props.gridModeEnabled === "undefined" &&
actionToggleGridMode,
typeof this.props.zenModeEnabled === "undefined" &&
actionToggleZenMode,
actionToggleGridMode,
actionToggleZenMode,
typeof this.props.viewModeEnabled === "undefined" &&
actionToggleViewMode,
actionToggleStats,

View File

@@ -224,7 +224,7 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
shortcuts={[getShortcutKey("Alt+Z")]}
/>
<Shortcut
label={t("labels.showGrid")}
label={t("labels.gridMode")}
shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
/>
<Shortcut

View File

@@ -52,7 +52,6 @@ interface LayerUIProps {
onLockToggle: () => void;
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
zenModeEnabled: boolean;
showExitZenModeBtn: boolean;
toggleZenMode: () => void;
langCode: Language["code"];
isCollaborating: boolean;
@@ -297,7 +296,6 @@ const LayerUI = ({
onLockToggle,
onInsertElements,
zenModeEnabled,
showExitZenModeBtn,
toggleZenMode,
isCollaborating,
onExportToBackend,
@@ -515,18 +513,17 @@ const LayerUI = ({
"transition-right": zenModeEnabled,
})}
>
{appState.collaborators.size > 0 &&
Array.from(appState.collaborators)
// Collaborator is either not initialized or is actually the current user.
.filter(([_, client]) => Object.keys(client).length !== 0)
.map(([clientId, client]) => (
<Tooltip
label={client.username || "Unknown user"}
key={clientId}
>
{actionManager.renderAction("goToCollaborator", clientId)}
</Tooltip>
))}
{Array.from(appState.collaborators)
// Collaborator is either not initialized or is actually the current user.
.filter(([_, client]) => Object.keys(client).length !== 0)
.map(([clientId, client]) => (
<Tooltip
label={client.username || "Unknown user"}
key={clientId}
>
{actionManager.renderAction("goToCollaborator", clientId)}
</Tooltip>
))}
</UserList>
</div>
</FixedSideContainer>
@@ -581,7 +578,7 @@ const LayerUI = ({
</div>
<button
className={clsx("disable-zen-mode", {
"disable-zen-mode--visible": showExitZenModeBtn,
"disable-zen-mode--visible": zenModeEnabled,
})}
onClick={toggleZenMode}
>

View File

@@ -152,26 +152,24 @@ export const MobileMenu = ({
<Stack.Col gap={4}>
{renderCanvasActions()}
{renderCustomFooter?.(true)}
{appState.collaborators.size > 0 && (
<fieldset>
<legend>{t("labels.collaborators")}</legend>
<UserList mobile>
{Array.from(appState.collaborators)
// Collaborator is either not initialized or is actually the current user.
.filter(
([_, client]) => Object.keys(client).length !== 0,
)
.map(([clientId, client]) => (
<React.Fragment key={clientId}>
{actionManager.renderAction(
"goToCollaborator",
clientId,
)}
</React.Fragment>
))}
</UserList>
</fieldset>
)}
<fieldset>
<legend>{t("labels.collaborators")}</legend>
<UserList mobile>
{Array.from(appState.collaborators)
// Collaborator is either not initialized or is actually the current user.
.filter(
([_, client]) => Object.keys(client).length !== 0,
)
.map(([clientId, client]) => (
<React.Fragment key={clientId}>
{actionManager.renderAction(
"goToCollaborator",
clientId,
)}
</React.Fragment>
))}
</UserList>
</fieldset>
</Stack.Col>
</div>
</Section>

View File

@@ -11,13 +11,7 @@ import { t } from "../i18n";
import useIsMobile from "../is-mobile";
import { getTargetElements } from "../scene";
import { AppState } from "../types";
import {
debounce,
formatSpeedBits,
formatTime,
getVersion,
nFormatter,
} from "../utils";
import { debounce, getVersion, nFormatter } from "../utils";
import { close } from "./icons";
import { Island } from "./Island";
import "./Stats.scss";
@@ -36,8 +30,6 @@ export const Stats = (props: {
setAppState: React.Component<any, AppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[];
onClose: () => void;
isCollaborating?: boolean;
shouldEnableNetworkStats: boolean;
}) => {
const isMobile = useIsMobile();
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
@@ -178,37 +170,6 @@ export const Stats = (props: {
</td>
</tr>
)}
{props.shouldEnableNetworkStats && props.isCollaborating ? (
<>
<tr>
<th colSpan={2}>{t("stats.collaboration")}</th>
</tr>
<tr>
<td>{t("stats.collaborators")}</td>
<td>{props.appState.collaborators.size}</td>
</tr>
<tr>
<td>{t("stats.ping")}</td>
<td>
{props.appState.networkPing === 0
? "…"
: props.appState.networkPing > 0
? formatTime(props.appState.networkPing)
: t("stats.error")}
</td>
</tr>
<tr>
<td>{t("stats.speed")}</td>
<td>
{props.appState.networkSpeed === 0
? "…"
: props.appState.networkSpeed > 0
? formatSpeedBits(props.appState.networkSpeed)
: t("stats.error")}
</td>
</tr>
</>
) : null}
<tr>
<th colSpan={2}>{t("stats.version")}</th>
</tr>

View File

@@ -8,8 +8,6 @@ export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
export const ELEMENT_TRANSLATE_AMOUNT = 1;
export const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
export const SHIFT_LOCKING_ANGLE = Math.PI / 12;
export const NETWORK_TIMEOUT_MS = 4000;
export const CURSOR_TYPE = {
TEXT: "text",
CROSSHAIR: "crosshair",

View File

@@ -227,8 +227,7 @@
.App-top-bar {
z-index: var(--zIndex-layerUI);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.App-bottom-bar {

106
src/data/pixelated-image.ts Normal file
View File

@@ -0,0 +1,106 @@
import { ExcalidrawGenericElement, NonDeleted } from "../element/types";
import { newElement } from "../element";
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "../constants";
import { randomId } from "../random";
const loadImage = async (url: string): Promise<HTMLImageElement> => {
const image = new Image();
return new Promise<HTMLImageElement>((resolve, reject) => {
image.onload = () => resolve(image);
image.onerror = (err) =>
reject(
new Error(
`Failed to load image: ${err ? err.toString : "unknown error"}`,
),
);
image.onabort = () =>
reject(new Error(`Failed to load image: image load aborted`));
image.src = url;
});
};
const commonProps = {
fillStyle: "solid",
fontFamily: DEFAULT_FONT_FAMILY,
fontSize: DEFAULT_FONT_SIZE,
opacity: 100,
roughness: 1,
strokeColor: "transparent",
strokeSharpness: "sharp",
strokeStyle: "solid",
strokeWidth: 1,
verticalAlign: "middle",
} as const;
export const pixelateImage = async (
blob: Blob,
cellSize: number,
suggestedMaxShapeCount: number,
x: number,
y: number,
) => {
const url = URL.createObjectURL(blob);
try {
const image = await loadImage(url);
// initialize canvas for pixelation
const { width, height } = image;
let canvasWidth = Math.floor(width / cellSize);
let canvasHeight = Math.floor(height / cellSize);
const shapeCount = canvasHeight * canvasWidth;
if (shapeCount > suggestedMaxShapeCount) {
canvasWidth = Math.floor(
canvasWidth * (suggestedMaxShapeCount / shapeCount),
);
canvasHeight = Math.floor(
canvasHeight * (suggestedMaxShapeCount / shapeCount),
);
}
const xOffset = x - (canvasWidth * cellSize) / 2;
const yOffset = y - (canvasHeight * cellSize) / 2;
const canvas =
"OffscreenCanvas" in window
? new OffscreenCanvas(canvasWidth, canvasHeight)
: document.createElement("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// Draw image on canvas
const ctx = canvas.getContext("2d")!;
ctx.drawImage(image, 0, 0, width, height, 0, 0, canvasWidth, canvasHeight);
const imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
const buffer = imageData.data;
const groupId = randomId();
const shapes: NonDeleted<ExcalidrawGenericElement>[] = [];
for (let row = 0; row < canvasHeight; row++) {
for (let col = 0; col < canvasWidth; col++) {
const offset = row * canvasWidth * 4 + col * 4;
const r = buffer[offset];
const g = buffer[offset + 1];
const b = buffer[offset + 2];
const alpha = buffer[offset + 3];
if (alpha) {
const color = `rgba(${r}, ${g}, ${b}, ${alpha})`;
const rectangle = newElement({
backgroundColor: color,
groupIds: [groupId],
...commonProps,
type: "rectangle",
x: xOffset + col * cellSize,
y: yOffset + row * cellSize,
width: cellSize,
height: cellSize,
});
shapes.push(rectangle);
}
}
}
return shapes;
} finally {
URL.revokeObjectURL(url);
}
};

View File

@@ -80,14 +80,7 @@ const initializeScene = async (opts: {
let roomLinkData = getCollaborationLinkData(window.location.href);
const isExternalScene = !!(id || jsonMatch || roomLinkData);
if (isExternalScene) {
if (
// don't prompt if scene is empty
!scene.elements.length ||
// don't prompt for collab scenes because we don't override local storage
roomLinkData ||
// otherwise, prompt whether user wants to override current scene
window.confirm(t("alerts.loadSceneOverridePrompt"))
) {
if (roomLinkData || window.confirm(t("alerts.loadSceneOverridePrompt"))) {
// Backwards compatibility with legacy url format
if (id) {
scene = await loadScene(id, null, initialData);

View File

@@ -235,14 +235,14 @@
"storage": "Speicher",
"title": "Statistiken für Nerds",
"total": "Gesamt",
"version": "Version",
"versionCopy": "Zum Kopieren klicken",
"versionNotAvailable": "Version nicht verfügbar",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Breite"
},
"toast": {
"copyStyles": "Formatierung kopiert.",
"copyToClipboard": "In die Zwischenablage kopiert.",
"copyToClipboard": "",
"copyToClipboardAsPng": "In die Zwischenablage als PNG kopiert."
}
}

View File

@@ -77,7 +77,7 @@
"group": "Group selection",
"ungroup": "Ungroup selection",
"collaborators": "Collaborators",
"showGrid": "Show grid",
"gridMode": "Grid mode",
"addToLibrary": "Add to library",
"removeFromLibrary": "Remove from library",
"libraryLoadingMessage": "Loading library…",
@@ -227,16 +227,11 @@
},
"stats": {
"angle": "Angle",
"collaboration": "Collaboration",
"collaborators": "Collaborators",
"element": "Element",
"elements": "Elements",
"error": "Error",
"height": "Height",
"ping": "Ping",
"scene": "Scene",
"selected": "Selected",
"speed": "Speed",
"storage": "Storage",
"title": "Stats for nerds",
"total": "Total",

View File

@@ -80,7 +80,7 @@
"gridMode": "Modo cuadrícula",
"addToLibrary": "Añadir a la biblioteca",
"removeFromLibrary": "Eliminar de la biblioteca",
"libraryLoadingMessage": "Cargando librería…",
"libraryLoadingMessage": "Cargando biblioteca…",
"libraries": "Explorar bibliotecas",
"loadingScene": "Cargando escena…",
"align": "Alinear",
@@ -235,14 +235,14 @@
"storage": "Almacenamiento",
"title": "Estadísticas para nerds",
"total": "Total",
"version": "Versión",
"versionCopy": "Clic para copiar",
"versionNotAvailable": "Versión no disponible",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Ancho"
},
"toast": {
"copyStyles": "Estilos copiados.",
"copyToClipboard": "Copiado en el portapapeles.",
"copyToClipboard": "",
"copyToClipboardAsPng": "Copiado al portapapeles como PNG."
}
}

View File

@@ -235,14 +235,14 @@
"storage": "Tallennustila",
"title": "Nörttien tilastot",
"total": "Yhteensä",
"version": "Versio",
"versionCopy": "Klikkaa kopioidaksesi",
"versionNotAvailable": "Versio ei saatavilla",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Leveys"
},
"toast": {
"copyStyles": "Tyylit kopioitu.",
"copyToClipboard": "Kopioitu leikepöydälle.",
"copyToClipboard": "",
"copyToClipboardAsPng": "Kopioitu leikepöydälle PNG-tiedostona."
}
}

View File

@@ -235,9 +235,9 @@
"storage": "Aḥraz",
"title": "",
"total": "Aɣrud",
"version": "Alqem",
"versionCopy": "Sit ad tneɣleḍ",
"versionNotAvailable": "Ur inuḥ ulqem",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Tehri"
},
"toast": {

View File

@@ -92,7 +92,7 @@
"centerHorizontally": "수평으로 중앙 정렬",
"distributeHorizontally": "수평으로 분배",
"distributeVertically": "수직으로 분배",
"viewMode": "보기 모드"
"viewMode": ""
},
"buttons": {
"clearReset": "캔버스 초기화",
@@ -136,7 +136,7 @@
"decryptFailed": "데이터를 복호화하지 못했습니다.",
"uploadedSecurly": "업로드는 종단 간 암호화로 보호되므로 Excalidraw 서버 및 타사가 콘텐츠를 읽을 수 없습니다.",
"loadSceneOverridePrompt": "외부 파일을 불러 오면 기존 콘텐츠가 대체됩니다. 계속 진행할까요?",
"collabStopOverridePrompt": "협업 세션을 종료하면 로컬 저장소에 있는 그림이 협업 세션의 그림으로 대체됩니다. 진행하겠습니까?\n\n(로컬 저장소에 있는 그림을 유지하려면 현재 브라우저 탭을 닫아주세요.)",
"collabStopOverridePrompt": "",
"errorLoadingLibrary": "외부 라이브러리를 불러오는 중에 문제가 발생했습니다.",
"confirmAddLibrary": "{{numShapes}}개의 모양이 라이브러리에 추가됩니다. 계속하시겠어요?",
"imageDoesNotContainScene": "이미지에서 불러오기는 현재 지원되지 않습니다.\n\n화면을 불러오려고 하셨나요? 이미지에 화면 정보가 없는 것 같습니다. 내보낼 때 화면을 포함했나요?",
@@ -202,25 +202,25 @@
"title": "오류"
},
"helpDialog": {
"blog": "블로그 읽어보기",
"click": "클릭",
"curvedArrow": "곡선 화살표",
"curvedLine": "곡선",
"documentation": "설명서",
"drag": "드래그",
"editor": "에디터",
"github": "문제 제보하기",
"howto": "가이드 참고하기",
"or": "또는",
"preventBinding": "화살표가 붙지 않게 하기",
"shapes": "도형",
"shortcuts": "키보드 단축키",
"textFinish": "편집 완료 (텍스트)",
"textNewLine": "줄바꿈 (텍스트)",
"title": "도움말",
"view": "보기",
"zoomToFit": "모든 요소가 보이도록 확대/축소",
"zoomToSelection": "선택 영역으로 확대/축소"
"blog": "",
"click": "",
"curvedArrow": "",
"curvedLine": "",
"documentation": "",
"drag": "",
"editor": "",
"github": "",
"howto": "",
"or": "",
"preventBinding": "",
"shapes": "",
"shortcuts": "",
"textFinish": "",
"textNewLine": "",
"title": "",
"view": "",
"zoomToFit": "",
"zoomToSelection": ""
},
"encrypted": {
"tooltip": "그림은 종단 간 암호화되므로 Excalidraw의 서버는 절대로 내용을 알 수 없습니다."
@@ -235,14 +235,14 @@
"storage": "저장공간",
"title": "덕후들을 위한 통계",
"total": "합계",
"version": "버전",
"versionCopy": "복사하려면 클릭",
"versionNotAvailable": "해당 버전 사용 불가능",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "너비"
},
"toast": {
"copyStyles": "스타일 복사.",
"copyToClipboard": "클립보드로 복사.",
"copyToClipboardAsPng": "클립보드로 PNG 이미지 복사."
"copyStyles": "",
"copyToClipboard": "",
"copyToClipboardAsPng": ""
}
}

View File

@@ -235,14 +235,14 @@
"storage": "Opslag",
"title": "Statistieken voor nerds",
"total": "Totaal",
"version": "Versie",
"versionCopy": "Klik om te kopiëren",
"versionNotAvailable": "Versie niet beschikbaar",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Breedte"
},
"toast": {
"copyStyles": "Stijlen gekopieerd.",
"copyToClipboard": "Gekopieerd naar het klembord.",
"copyToClipboard": "",
"copyToClipboardAsPng": "Gekopieerd naar klembord als PNG."
}
}

View File

@@ -2,12 +2,12 @@
"ar-SA": 87,
"bg-BG": 91,
"ca-ES": 87,
"de-DE": 100,
"de-DE": 98,
"el-GR": 99,
"en": 100,
"es-ES": 100,
"es-ES": 98,
"fa-IR": 95,
"fi-FI": 100,
"fi-FI": 98,
"fr-FR": 100,
"he-IL": 87,
"hi-IN": 98,
@@ -15,20 +15,20 @@
"id-ID": 97,
"it-IT": 100,
"ja-JP": 79,
"kab-KAB": 96,
"ko-KR": 100,
"kab-KAB": 94,
"ko-KR": 87,
"my-MM": 81,
"nb-NO": 100,
"nl-NL": 99,
"nl-NL": 97,
"nn-NO": 90,
"pa-IN": 100,
"pl-PL": 88,
"pt-BR": 100,
"pt-PT": 97,
"ro-RO": 100,
"ru-RU": 98,
"ru-RU": 97,
"sk-SK": 100,
"sv-SE": 100,
"sv-SE": 98,
"tr-TR": 87,
"uk-UA": 97,
"zh-CN": 98,

View File

@@ -236,7 +236,7 @@
"title": "Статистика для ботаников",
"total": "Всего",
"version": "",
"versionCopy": "Копировать",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Ширина"
},

View File

@@ -235,14 +235,14 @@
"storage": "Lagring",
"title": "Statistik för nördar",
"total": "Totalt",
"version": "Version",
"versionCopy": "Klicka för att kopiera",
"versionNotAvailable": "Versionen är inte tillgänglig",
"version": "",
"versionCopy": "",
"versionNotAvailable": "",
"width": "Bredd"
},
"toast": {
"copyStyles": "Kopierade stilar.",
"copyToClipboard": "Kopierad till urklipp.",
"copyToClipboard": "",
"copyToClipboardAsPng": "Kopierat till urklipp som PNG."
}
}

View File

@@ -1,64 +0,0 @@
import { getAverage } from "./utils";
const IMAGE_URL = `${process.env.REACT_APP_SOCKET_SERVER_URL}/test256.png`;
const IMAGE_SIZE_BITS = 141978 * 8;
const AVERAGE_MAX = 4;
const speedHistory: number[] = [];
const pushSpeed = (speed: number): void => {
speedHistory.push(speed);
if (speedHistory.length > AVERAGE_MAX) {
speedHistory.shift();
}
};
const getSpeedBits = (
imageSize: number,
startTime: number,
endTime: number,
): number => {
const duration = (endTime - startTime) / 1000;
if (duration > 0) {
return imageSize / duration;
}
return 0;
};
const processImage = (): Promise<number> => {
return new Promise((resolve) => {
const image = new Image();
let endTime: number;
image.onload = () => {
endTime = new Date().getTime();
const speed = getSpeedBits(IMAGE_SIZE_BITS, startTime, endTime);
pushSpeed(speed);
resolve(getAverage(speedHistory));
};
image.onerror = () => {
resolve(-1);
};
const startTime = new Date().getTime();
image.src = `${IMAGE_URL}?t=${startTime}`;
});
};
export const getNetworkSpeed = async (): Promise<number> => {
return await processImage();
};
export const getNetworkPing = async () => {
const startTime = new Date().getTime();
try {
await fetch(process.env.REACT_APP_SOCKET_SERVER_URL, {
mode: "no-cors",
method: "HEAD",
});
const endTime = new Date().getTime();
return endTime - startTime;
} catch (error) {
return -1;
}
};

View File

@@ -18,7 +18,6 @@ Please add the latest change on the top under the correct section.
### Features
- Add `zenModeEnabled` and `gridModeEnabled` prop which enables zen mode and grid mode respectively [#2901](https://github.com/excalidraw/excalidraw/pull/2901). When this prop is used, the zen mode / grid mode will be fully controlled by the host app.
- Add `viewModeEnabled` prop which enabled the view mode [#2840](https://github.com/excalidraw/excalidraw/pull/2840). When this prop is used, the view mode will not show up in context menu is so it is fully controlled by host.
- Expose `getAppState` on `excalidrawRef` [#2834](https://github.com/excalidraw/excalidraw/pull/2834).

View File

@@ -138,9 +138,7 @@ export default function App() {
| [`onExportToBackend`](#onExportToBackend) | Function | | Callback triggered when link button is clicked on export dialog |
| [`langCode`](#langCode) | string | `en` | Language code string |
| [`renderFooter `](#renderFooter) | Function | | Function that renders custom UI footer |
| [`viewModeEnabled`](#viewModeEnabled) | boolean | | This implies if the app is in view mode. |
| [`zenModeEnabled`](#zenModeEnabled) | boolean | | This implies if the zen mode is enabled |
| [`gridModeEnabled`](#gridModeEnabled) | boolean | | This implies if the grid mode is enabled |
| [`viewModeEnabled`](#viewModeEnabled) | boolean | false | This implies if the app is in view mode. |
### `Extra API's`
@@ -336,12 +334,4 @@ A function that renders (returns JSX) custom UI footer. For example, you can use
#### `viewModeEnabled`
This prop indicates whether the app is in `view mode`. When supplied, the value takes precedence over `intialData.appState.viewModeEnabled`, the `view mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
#### `zenModeEnabled`
This prop indicates whether the app is in `zen mode`. When supplied, the value takes precedence over `intialData.appState.zenModeEnabled`, the `zen mode` will be fully controlled by the host app, and users won't be able to toggle it from within the app.
#### `gridModeEnabled`
This prop indicates whether the shows the grid. When supplied, the value takes precedence over `intialData.appState.gridModeEnabled`, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.
This prop indicates if the app is in `view mode`. When this prop is used, the `view mode` will not show up in context menu is so it is fully controlled by host. Also the value of this prop if passed will be used over the value of `intialData.appState.viewModeEnabled`

View File

@@ -27,8 +27,6 @@ const Excalidraw = (props: ExcalidrawProps) => {
renderFooter,
langCode = defaultLang.code,
viewModeEnabled,
zenModeEnabled,
gridModeEnabled,
} = props;
useEffect(() => {
@@ -68,8 +66,6 @@ const Excalidraw = (props: ExcalidrawProps) => {
renderFooter={renderFooter}
langCode={langCode}
viewModeEnabled={viewModeEnabled}
zenModeEnabled={zenModeEnabled}
gridModeEnabled={gridModeEnabled}
/>
</IsMobileProvider>
</InitializeApp>

View File

@@ -47,11 +47,9 @@ import {
TransformHandles,
TransformHandleType,
} from "../element/transformHandles";
import { viewportCoordsToSceneCoords, supportsEmoji } from "../utils";
import { viewportCoordsToSceneCoords } from "../utils";
import { UserIdleState } from "../excalidraw-app/collab/types";
const hasEmojiSupport = supportsEmoji();
const strokeRectWithRotation = (
context: CanvasRenderingContext2D,
x: number,
@@ -451,7 +449,7 @@ export const renderScene = (
const userState = sceneState.remotePointerUserStates[clientId];
if (isOutOfBounds || userState === UserIdleState.AWAY) {
context.globalAlpha = 0.48;
context.globalAlpha = 0.2;
}
if (
@@ -483,24 +481,13 @@ export const renderScene = (
context.stroke();
const username = sceneState.remotePointerUsernames[clientId];
let usernameAndIdleState;
if (hasEmojiSupport) {
usernameAndIdleState = `${username ? `${username} ` : ""}${
userState === UserIdleState.AWAY
? "⚫️"
: userState === UserIdleState.IDLE
? "💤"
: "🟢"
}`;
} else {
usernameAndIdleState = `${username ? `${username}` : ""}${
userState === UserIdleState.AWAY
? ` (${UserIdleState.AWAY})`
: userState === UserIdleState.IDLE
? ` (${UserIdleState.IDLE})`
: ""
}`;
}
const usernameAndIdleState = `${username ? `${username} ` : ""}${
userState === UserIdleState.AWAY
? "⚫️"
: userState === UserIdleState.IDLE
? "💤"
: "🟢"
}`;
if (!isOutOfBounds && usernameAndIdleState) {
const offsetX = x + width;

View File

@@ -40,8 +40,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -502,8 +500,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -970,8 +966,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -1747,8 +1741,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -1952,8 +1944,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -2411,8 +2401,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -2665,8 +2653,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -2830,8 +2816,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -3308,8 +3292,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -3617,8 +3599,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -3822,8 +3802,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -4067,8 +4045,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -4320,8 +4296,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -4704,8 +4678,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -5000,8 +4972,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -5308,8 +5278,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -5517,8 +5485,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -5682,8 +5648,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -6136,8 +6100,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -6455,8 +6417,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -8490,8 +8450,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -8853,8 +8811,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -9109,8 +9065,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -9363,8 +9317,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -9679,8 +9631,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -9844,8 +9794,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -10009,8 +9957,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -10174,8 +10120,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -10369,8 +10313,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -10564,8 +10506,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -10759,8 +10699,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -10954,8 +10892,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -11119,8 +11055,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -11284,8 +11218,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -11479,8 +11411,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -11644,8 +11574,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -11839,8 +11767,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -12556,8 +12482,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -12810,8 +12734,6 @@ Object {
"lastPointerDownWith": "touch",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -12913,8 +12835,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -13014,8 +12934,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -13179,8 +13097,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -13488,8 +13404,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -13797,8 +13711,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -13962,8 +13874,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -14159,8 +14069,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -14409,8 +14317,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -14734,8 +14640,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -15574,8 +15478,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -15883,8 +15785,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -16192,8 +16092,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -16572,8 +16470,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -16740,8 +16636,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -17062,8 +16956,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -17302,8 +17194,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -17558,8 +17448,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -17886,8 +17774,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -17987,8 +17873,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -18152,8 +18036,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -18974,8 +18856,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -19075,8 +18955,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -19830,8 +19708,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -20236,8 +20112,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -20510,8 +20384,6 @@ Object {
"lastPointerDownWith": "touch",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -20613,8 +20485,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -21112,8 +20982,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,
@@ -21213,8 +21081,6 @@ Object {
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"networkPing": 0,
"networkSpeed": 0,
"offsetLeft": 0,
"offsetTop": 0,
"openMenu": null,

View File

@@ -1,89 +0,0 @@
import React from "react";
import { fireEvent, GlobalTestState, render } from "./test-utils";
import Excalidraw from "../packages/excalidraw/index";
import { queryByText } from "@testing-library/react";
import { GRID_SIZE } from "../constants";
const { h } = window;
describe("<Excalidraw/>", () => {
describe("Test zenModeEnabled prop", () => {
it('should show exit zen mode button when zen mode is set and zen mode option in context menu when zenModeEnabled is "undefined"', async () => {
const { container } = await render(<Excalidraw />);
expect(
container.getElementsByClassName("disable-zen-mode--visible").length,
).toBe(0);
expect(h.state.zenModeEnabled).toBe(false);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 1,
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
fireEvent.click(queryByText(contextMenu as HTMLElement, "Zen mode")!);
expect(h.state.zenModeEnabled).toBe(true);
expect(
container.getElementsByClassName("disable-zen-mode--visible").length,
).toBe(1);
});
it("should not show exit zen mode button and zen mode option in context menu when zenModeEnabled is set", async () => {
const { container } = await render(<Excalidraw zenModeEnabled={true} />);
expect(
container.getElementsByClassName("disable-zen-mode--visible").length,
).toBe(0);
expect(h.state.zenModeEnabled).toBe(true);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 1,
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
expect(queryByText(contextMenu as HTMLElement, "Zen mode")).toBe(null);
expect(h.state.zenModeEnabled).toBe(true);
expect(
container.getElementsByClassName("disable-zen-mode--visible").length,
).toBe(0);
});
});
describe("Test gridModeEnabled prop", () => {
it('should show grid mode in context menu when gridModeEnabled is "undefined"', async () => {
const { container } = await render(<Excalidraw />);
expect(h.state.gridSize).toBe(null);
expect(
container.getElementsByClassName("disable-zen-mode--visible").length,
).toBe(0);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 1,
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
fireEvent.click(queryByText(contextMenu as HTMLElement, "Show grid")!);
expect(h.state.gridSize).toBe(GRID_SIZE);
});
it('should not show grid mode in context menu when gridModeEnabled is not "undefined"', async () => {
const { container } = await render(
<Excalidraw gridModeEnabled={false} />,
);
expect(h.state.gridSize).toBe(null);
expect(
container.getElementsByClassName("disable-zen-mode--visible").length,
).toBe(0);
fireEvent.contextMenu(GlobalTestState.canvas, {
button: 2,
clientX: 1,
clientY: 1,
});
const contextMenu = document.querySelector(".context-menu");
expect(queryByText(contextMenu as HTMLElement, "Show grid")).toBe(null);
expect(h.state.gridSize).toBe(null);
});
});
});

View File

@@ -88,8 +88,6 @@ export type AppState = {
appearance: "light" | "dark";
gridSize: number | null;
viewModeEnabled: boolean;
networkSpeed: number;
networkPing: number;
/** top-most selected groups (i.e. does not include nested groups) */
selectedGroupIds: { [groupId: string]: boolean };
@@ -187,8 +185,6 @@ export interface ExcalidrawProps {
renderFooter?: (isMobile: boolean) => JSX.Element;
langCode?: Language["code"];
viewModeEnabled?: boolean;
zenModeEnabled?: boolean;
gridModeEnabled?: boolean;
}
export type SceneData = {

View File

@@ -363,47 +363,9 @@ export const nFormatter = (num: number, digits: number): string => {
);
};
export const formatSpeedBits = (speed: number): string => {
// source: https://en.wikipedia.org/wiki/Data-rate_units#Conversion_table
const suffix = ["bps", "kbps", "Mbps", "Gbps"];
let index = 0;
while (speed > 1000) {
index++;
speed = speed / 1000;
}
return `${speed.toFixed(index > 1 ? 1 : 0)} ${suffix[index]}`;
};
export const getVersion = () => {
return (
document.querySelector<HTMLMetaElement>('meta[name="version"]')?.content ||
DEFAULT_VERSION
);
};
export const formatTime = (mseconds: number): string => {
return mseconds < 1000
? `${mseconds} ms`
: `${(mseconds / 1000).toFixed(1)} s`;
};
export const getAverage = (arr: Array<number>): number => {
return arr.reduce((sum, currentVal) => sum + currentVal) / arr.length;
};
// Adapted from https://github.com/Modernizr/Modernizr/blob/master/feature-detects/emoji.js
export const supportsEmoji = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
return false;
}
const offset = 12;
ctx.fillStyle = "#f00";
ctx.textBaseline = "top";
ctx.font = "32px Arial";
// Modernizr used 🐨, but it is sort of supported on Windows 7.
// Luckily 😀 isn't supported.
ctx.fillText("😀", 0, 0);
return ctx.getImageData(offset, offset, 1, 1).data[0] !== 0;
};