Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
98c16659c9 | ||
![]() |
5644063fc7 | ||
![]() |
85b8050cc5 | ||
![]() |
60d3bf1718 | ||
![]() |
b8e1b1f3ad | ||
![]() |
57432b9779 | ||
![]() |
156073a407 | ||
![]() |
f676a20332 | ||
![]() |
63af29d345 | ||
![]() |
69a1b74e05 | ||
![]() |
163dbd47d4 | ||
![]() |
08889adfa5 | ||
![]() |
aaf4943fa3 | ||
![]() |
4fd18d1b3e | ||
![]() |
f8cf19cae9 | ||
![]() |
6540c5460e | ||
![]() |
dc95cf3447 | ||
![]() |
1ca985aa4a | ||
![]() |
bc9c8f7ee2 | ||
![]() |
3eacd6f07b | ||
![]() |
afa929b2a2 | ||
![]() |
370c9a643f | ||
![]() |
22c57c8f4a | ||
![]() |
eef9662195 | ||
![]() |
69a7b1f2b5 | ||
![]() |
3b11b1d9d3 | ||
![]() |
b778035854 | ||
![]() |
92c2a42edf | ||
![]() |
7cadc3de52 | ||
![]() |
336b7222de | ||
![]() |
4d0ebf5ac5 | ||
![]() |
949e9ae03a | ||
![]() |
b5151bda5a | ||
![]() |
8157c84d11 |
@ -73,6 +73,8 @@ export const getDefaultAppState = (): Omit<
|
||||
zenModeEnabled: false,
|
||||
zoom: { value: 1 as NormalizedZoomValue, translation: { x: 0, y: 0 } },
|
||||
viewModeEnabled: false,
|
||||
networkSpeed: 0,
|
||||
networkPing: 0,
|
||||
};
|
||||
};
|
||||
|
||||
@ -153,6 +155,8 @@ 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">(
|
||||
|
@ -51,6 +51,7 @@ import {
|
||||
GRID_SIZE,
|
||||
LINE_CONFIRM_THRESHOLD,
|
||||
MIME_TYPES,
|
||||
NETWORK_TIMEOUT_MS,
|
||||
POINTER_BUTTON,
|
||||
TAP_TWICE_TIMEOUT,
|
||||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||
@ -183,6 +184,7 @@ 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();
|
||||
|
||||
@ -205,6 +207,9 @@ 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 }>;
|
||||
@ -289,6 +294,8 @@ 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();
|
||||
@ -469,6 +476,8 @@ 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 && (
|
||||
@ -748,6 +757,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
this.removeEventListeners();
|
||||
this.scene.destroy();
|
||||
clearTimeout(touchTimeout);
|
||||
clearTimeout(this.networkSpeedIntervalId);
|
||||
touchTimeout = 0;
|
||||
}
|
||||
|
||||
@ -874,6 +884,34 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
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");
|
||||
@ -999,6 +1037,42 @@ 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) => {
|
||||
|
@ -11,7 +11,13 @@ import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { getTargetElements } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { debounce, getVersion, nFormatter } from "../utils";
|
||||
import {
|
||||
debounce,
|
||||
formatSpeedBits,
|
||||
formatTime,
|
||||
getVersion,
|
||||
nFormatter,
|
||||
} from "../utils";
|
||||
import { close } from "./icons";
|
||||
import { Island } from "./Island";
|
||||
import "./Stats.scss";
|
||||
@ -30,6 +36,8 @@ 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>({
|
||||
@ -170,6 +178,37 @@ 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>
|
||||
|
@ -8,6 +8,8 @@ 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",
|
||||
|
@ -227,11 +227,16 @@
|
||||
},
|
||||
"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",
|
||||
|
64
src/networkStats.ts
Normal file
64
src/networkStats.ts
Normal file
@ -0,0 +1,64 @@
|
||||
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;
|
||||
}
|
||||
};
|
@ -40,6 +40,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -500,6 +502,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -966,6 +970,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -1741,6 +1747,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -1944,6 +1952,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -2401,6 +2411,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -2653,6 +2665,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -2816,6 +2830,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -3292,6 +3308,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -3599,6 +3617,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -3802,6 +3822,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -4045,6 +4067,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -4296,6 +4320,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -4678,6 +4704,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -4972,6 +5000,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -5278,6 +5308,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -5485,6 +5517,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -5648,6 +5682,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -6100,6 +6136,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -6417,6 +6455,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -8450,6 +8490,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -8811,6 +8853,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -9065,6 +9109,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -9317,6 +9363,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -9631,6 +9679,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -9794,6 +9844,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -9957,6 +10009,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -10120,6 +10174,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -10313,6 +10369,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -10506,6 +10564,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -10699,6 +10759,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -10892,6 +10954,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -11055,6 +11119,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -11218,6 +11284,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -11411,6 +11479,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -11574,6 +11644,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -11767,6 +11839,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -12482,6 +12556,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -12734,6 +12810,8 @@ Object {
|
||||
"lastPointerDownWith": "touch",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -12835,6 +12913,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -12934,6 +13014,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -13097,6 +13179,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -13404,6 +13488,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -13711,6 +13797,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -13874,6 +13962,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -14069,6 +14159,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -14317,6 +14409,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -14640,6 +14734,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -15478,6 +15574,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -15785,6 +15883,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -16092,6 +16192,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -16470,6 +16572,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -16636,6 +16740,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -16956,6 +17062,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -17194,6 +17302,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -17448,6 +17558,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -17774,6 +17886,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -17873,6 +17987,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -18036,6 +18152,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -18856,6 +18974,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -18955,6 +19075,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -19708,6 +19830,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -20112,6 +20236,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -20384,6 +20510,8 @@ Object {
|
||||
"lastPointerDownWith": "touch",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -20485,6 +20613,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -20982,6 +21112,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
@ -21081,6 +21213,8 @@ Object {
|
||||
"lastPointerDownWith": "mouse",
|
||||
"multiElement": null,
|
||||
"name": "Untitled-201933152653",
|
||||
"networkPing": 0,
|
||||
"networkSpeed": 0,
|
||||
"offsetLeft": 0,
|
||||
"offsetTop": 0,
|
||||
"openMenu": null,
|
||||
|
@ -88,6 +88,8 @@ 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 };
|
||||
|
21
src/utils.ts
21
src/utils.ts
@ -363,6 +363,17 @@ 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 ||
|
||||
@ -370,6 +381,16 @@ export const getVersion = () => {
|
||||
);
|
||||
};
|
||||
|
||||
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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user