Add more events for sharing and refactor I/O, dialogs (#2443)
This commit is contained in:
parent
c43109a230
commit
66e5b18e4e
25
analytics.md
25
analytics.md
@ -3,21 +3,19 @@
|
||||
| Shape / Selection | shape | selection, rectangle, diamond, etc | `toolbar` or `shortcut` |
|
||||
| Text on double click | shape | text | `double-click` |
|
||||
| Lock selection | shape | lock | `on` or `off` |
|
||||
| Load file | action | load | `MIME type` |
|
||||
| Import from URL | action | import |
|
||||
| Save | action | save |
|
||||
| Save as | action | save as |
|
||||
| Clear canvas | action | clear canvas |
|
||||
| Zoom in | action | zoom | in | `zoom` |
|
||||
| Zoom out | action | zoom | out | `zoom` |
|
||||
| Zoom fit | action | zoom | fit | `zoom` |
|
||||
| Zoom reset | action | zoom | reset | `zoom` |
|
||||
| Export dialog | action | export | dialog |
|
||||
| Export to backend | action | export | backend |
|
||||
| Export as SVG | action | export | `svg` or `clipboard-svg` |
|
||||
| Export to PNG | action | export | `png` or `clipboard-png` |
|
||||
| Scroll back to content | action | scroll to content |
|
||||
| Open shortcut menu | action | keyboard shortcuts |
|
||||
| Load file | io | load | `MIME type` |
|
||||
| Import from URL | io | import |
|
||||
| Save | io | save |
|
||||
| Save as | io | save as |
|
||||
| Export to backend | io | export | backend |
|
||||
| Export as SVG | io | export | `svg` or `clipboard-svg` |
|
||||
| Export to PNG | io | export | `png` or `clipboard-png` |
|
||||
| Canvas color | change | canvas color | `color` |
|
||||
| Background color | change | background color | `color` |
|
||||
| Stroke color | change | stroke color | `color` |
|
||||
@ -42,6 +40,15 @@
|
||||
| Center vertically | align | vertically | `center` |
|
||||
| Distribute horizontally | align | distribute | `horizontally` |
|
||||
| Distribute vertically | align | distribute | `vertically` |
|
||||
| Start session | share | session start |
|
||||
| Join session | share | session join |
|
||||
| Start end | share | session end |
|
||||
| Copy room link | share | copy link |
|
||||
| Go to collaborator | share | go to collaborator |
|
||||
| Change name | share | name |
|
||||
| Shortcuts dialog | dialog | shortcuts |
|
||||
| Collaboration dialog | dialog | collaboration |
|
||||
| Export dialog | dialog | export |
|
||||
| E2EE shield | exit | e2ee shield |
|
||||
| GitHub corner | exit | github |
|
||||
| Excalidraw blog | exit | blog |
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
import { ProjectName } from "../components/ProjectName";
|
||||
import { saveAsJSON, loadFromJSON } from "../data";
|
||||
import { EVENT_CHANGE, EVENT_IO, trackEvent } from "../analytics";
|
||||
import { load, save, saveAs } from "../components/icons";
|
||||
import { ProjectName } from "../components/ProjectName";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
import { loadFromJSON, saveAsJSON } from "../data";
|
||||
import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { register } from "./register";
|
||||
import { KEYS } from "../keys";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
import { EVENT_ACTION, EVENT_CHANGE, trackEvent } from "../analytics";
|
||||
import { register } from "./register";
|
||||
|
||||
export const actionChangeProjectName = register({
|
||||
name: "changeProjectName",
|
||||
@ -90,7 +90,7 @@ export const actionSaveScene = register({
|
||||
perform: async (elements, appState, value) => {
|
||||
try {
|
||||
const { fileHandle } = await saveAsJSON(elements, appState);
|
||||
trackEvent(EVENT_ACTION, "save");
|
||||
trackEvent(EVENT_IO, "save");
|
||||
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
||||
} catch (error) {
|
||||
if (error?.name !== "AbortError") {
|
||||
@ -121,7 +121,7 @@ export const actionSaveAsScene = register({
|
||||
...appState,
|
||||
fileHandle: null,
|
||||
});
|
||||
trackEvent(EVENT_ACTION, "save as");
|
||||
trackEvent(EVENT_IO, "save as");
|
||||
return { commitToHistory: false, appState: { ...appState, fileHandle } };
|
||||
} catch (error) {
|
||||
if (error?.name !== "AbortError") {
|
||||
|
@ -7,7 +7,7 @@ import { register } from "./register";
|
||||
import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { HelpIcon } from "../components/HelpIcon";
|
||||
import { EVENT_ACTION, trackEvent } from "../analytics";
|
||||
import { EVENT_DIALOG, trackEvent } from "../analytics";
|
||||
|
||||
export const actionToggleCanvasMenu = register({
|
||||
name: "toggleCanvasMenu",
|
||||
@ -72,7 +72,7 @@ export const actionFullScreen = register({
|
||||
export const actionShortcuts = register({
|
||||
name: "toggleShortcuts",
|
||||
perform: (_elements, appState) => {
|
||||
trackEvent(EVENT_ACTION, "keyboard shortcuts");
|
||||
trackEvent(EVENT_DIALOG, "shortcuts");
|
||||
return {
|
||||
appState: {
|
||||
...appState,
|
||||
|
@ -4,11 +4,13 @@ import { register } from "./register";
|
||||
import { getClientColors, getClientInitials } from "../clients";
|
||||
import { Collaborator } from "../types";
|
||||
import { centerScrollOn } from "../scene/scroll";
|
||||
import { EVENT_SHARE, trackEvent } from "../analytics";
|
||||
|
||||
export const actionGoToCollaborator = register({
|
||||
name: "goToCollaborator",
|
||||
perform: (_elements, appState, value) => {
|
||||
const point = value as Collaborator["pointer"];
|
||||
trackEvent(EVENT_SHARE, "go to collaborator");
|
||||
if (!point) {
|
||||
return { appState, commitToHistory: false };
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ export const EVENT_CHANGE = "change";
|
||||
export const EVENT_SHAPE = "shape";
|
||||
export const EVENT_LAYER = "layer";
|
||||
export const EVENT_ALIGN = "align";
|
||||
export const EVENT_SHARE = "share";
|
||||
export const EVENT_IO = "io";
|
||||
export const EVENT_DIALOG = "dialog";
|
||||
|
||||
export const trackEvent = window.gtag
|
||||
? (category: string, name: string, label?: string, value?: number) => {
|
||||
|
@ -181,7 +181,7 @@ import {
|
||||
isSavedToFirebase,
|
||||
} from "../data/firebase";
|
||||
import { getNewZoom } from "../scene/zoom";
|
||||
import { EVENT_SHAPE, trackEvent } from "../analytics";
|
||||
import { EVENT_SHAPE, EVENT_SHARE, trackEvent } from "../analytics";
|
||||
|
||||
/**
|
||||
* @param func handler taking at most single parameter (event).
|
||||
@ -657,8 +657,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
// when joining a room we don't want user's local scene data to be merged
|
||||
// into the remote scene
|
||||
this.resetScene();
|
||||
|
||||
this.initializeSocketClient({ showLoadingState: true });
|
||||
trackEvent(EVENT_SHARE, "session join");
|
||||
} else if (scene) {
|
||||
if (scene.appState) {
|
||||
scene.appState = {
|
||||
@ -1262,12 +1262,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
||||
this.scene.replaceAllElements(this.scene.getElements());
|
||||
|
||||
await this.initializeSocketClient({ showLoadingState: false });
|
||||
trackEvent(EVENT_SHARE, "session start");
|
||||
};
|
||||
|
||||
closePortal = () => {
|
||||
this.saveCollabRoomToFirebase();
|
||||
window.history.pushState({}, "Excalidraw", window.location.origin);
|
||||
this.destroySocketClient();
|
||||
trackEvent(EVENT_SHARE, "session end");
|
||||
};
|
||||
|
||||
toggleLock = () => {
|
||||
|
@ -1,24 +1,21 @@
|
||||
import "./ExportDialog.scss";
|
||||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { render, unmountComponentAtNode } from "react-dom";
|
||||
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { clipboard, exportFile, link } from "./icons";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { exportToCanvas, getExportSize } from "../scene/export";
|
||||
import { ActionsManagerInterface } from "../actions/types";
|
||||
import Stack from "./Stack";
|
||||
import { t } from "../i18n";
|
||||
|
||||
import { EVENT_DIALOG, trackEvent } from "../analytics";
|
||||
import { probablySupportsClipboardBlob } from "../clipboard";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { canvasToBlob } from "../data/blob";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
import { CanvasError } from "../errors";
|
||||
import { EVENT_ACTION, trackEvent } from "../analytics";
|
||||
import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
import { exportToCanvas, getExportSize } from "../scene/export";
|
||||
import { AppState } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import "./ExportDialog.scss";
|
||||
import { clipboard, exportFile, link } from "./icons";
|
||||
import Stack from "./Stack";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
|
||||
const scales = [1, 2, 3];
|
||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
||||
@ -252,7 +249,7 @@ export const ExportDialog = ({
|
||||
<>
|
||||
<ToolButton
|
||||
onClick={() => {
|
||||
trackEvent(EVENT_ACTION, "export", "dialog");
|
||||
trackEvent(EVENT_DIALOG, "export");
|
||||
setModalIsShown(true);
|
||||
}}
|
||||
icon={exportFile}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import clsx from "clsx";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { EVENT_DIALOG, EVENT_SHARE, trackEvent } from "../analytics";
|
||||
import { copyTextToSystemClipboard } from "../clipboard";
|
||||
import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { users, clipboard, start, stop } from "./icons";
|
||||
|
||||
import "./RoomDialog.scss";
|
||||
import { copyTextToSystemClipboard } from "../clipboard";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { AppState } from "../types";
|
||||
import { KEYS } from "../keys";
|
||||
import { AppState } from "../types";
|
||||
import { Dialog } from "./Dialog";
|
||||
import { clipboard, start, stop, users } from "./icons";
|
||||
import "./RoomDialog.scss";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
|
||||
const RoomModal = ({
|
||||
activeRoomLink,
|
||||
@ -33,6 +33,7 @@ const RoomModal = ({
|
||||
const copyRoomLink = async () => {
|
||||
try {
|
||||
await copyTextToSystemClipboard(activeRoomLink);
|
||||
trackEvent(EVENT_SHARE, "copy link");
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message);
|
||||
}
|
||||
@ -95,6 +96,7 @@ const RoomModal = ({
|
||||
value={username || ""}
|
||||
className="RoomDialog-username TextInput"
|
||||
onChange={(event) => onUsernameChange(event.target.value)}
|
||||
onBlur={() => trackEvent(EVENT_SHARE, "name")}
|
||||
onKeyPress={(event) =>
|
||||
event.key === KEYS.ENTER && onPressingEnter()
|
||||
}
|
||||
@ -161,7 +163,10 @@ export const RoomDialog = ({
|
||||
className={clsx("RoomDialog-modalButton", {
|
||||
"is-collaborating": isCollaborating,
|
||||
})}
|
||||
onClick={() => setModalIsShown(true)}
|
||||
onClick={() => {
|
||||
trackEvent(EVENT_DIALOG, "collaboration");
|
||||
setModalIsShown(true);
|
||||
}}
|
||||
icon={users}
|
||||
type="button"
|
||||
title={t("buttons.roomDialog")}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { EVENT_IO, trackEvent } from "../analytics";
|
||||
import { cleanAppStateForExport } from "../appState";
|
||||
import { restore } from "./restore";
|
||||
import { t } from "../i18n";
|
||||
import { AppState } from "../types";
|
||||
import { LibraryData, ImportedDataState } from "./types";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { MIME_TYPES } from "../constants";
|
||||
import { CanvasError } from "../errors";
|
||||
import { clearElementsForExport } from "../element";
|
||||
import { EVENT_ACTION, trackEvent } from "../analytics";
|
||||
import { CanvasError } from "../errors";
|
||||
import { t } from "../i18n";
|
||||
import { calculateScrollCenter } from "../scene";
|
||||
import { AppState } from "../types";
|
||||
import { restore } from "./restore";
|
||||
import { ImportedDataState, LibraryData } from "./types";
|
||||
|
||||
export const parseFileContents = async (blob: Blob | File) => {
|
||||
let contents: string;
|
||||
@ -111,7 +111,7 @@ export const loadFromBlob = async (
|
||||
localAppState,
|
||||
);
|
||||
|
||||
trackEvent(EVENT_ACTION, "load", getMimeType(blob));
|
||||
trackEvent(EVENT_IO, "load", getMimeType(blob));
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
|
@ -1,29 +1,25 @@
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
|
||||
import { getDefaultAppState } from "../appState";
|
||||
|
||||
import { AppState } from "../types";
|
||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||
import { fileSave } from "browser-nativefs";
|
||||
|
||||
import { t } from "../i18n";
|
||||
import { EVENT_IO, trackEvent } from "../analytics";
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import {
|
||||
copyCanvasToClipboardAsPng,
|
||||
copyTextToSystemClipboard,
|
||||
} from "../clipboard";
|
||||
import { serializeAsJSON } from "./json";
|
||||
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
import { exportToCanvas, exportToSvg } from "../scene/export";
|
||||
import { ExportType } from "../scene/types";
|
||||
import { AppState } from "../types";
|
||||
import { canvasToBlob } from "./blob";
|
||||
import { serializeAsJSON } from "./json";
|
||||
import { restore } from "./restore";
|
||||
import { ImportedDataState } from "./types";
|
||||
import { canvasToBlob } from "./blob";
|
||||
import { EVENT_ACTION, trackEvent } from "../analytics";
|
||||
|
||||
export { loadFromBlob } from "./blob";
|
||||
export { saveAsJSON, loadFromJSON } from "./json";
|
||||
export { loadFromJSON, saveAsJSON } from "./json";
|
||||
|
||||
const BACKEND_GET = process.env.REACT_APP_BACKEND_V1_GET_URL;
|
||||
|
||||
@ -218,7 +214,7 @@ export const exportToBackend = async (
|
||||
url.hash = `json=${json.id},${exportedKey.k!}`;
|
||||
const urlString = url.toString();
|
||||
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
|
||||
trackEvent(EVENT_ACTION, "export", "backend");
|
||||
trackEvent(EVENT_IO, "export", "backend");
|
||||
} else if (json.error_class === "RequestTooLargeError") {
|
||||
window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
|
||||
} else {
|
||||
@ -265,7 +261,7 @@ const importFromBackend = async (
|
||||
data = await response.json();
|
||||
}
|
||||
|
||||
trackEvent(EVENT_ACTION, "import");
|
||||
trackEvent(EVENT_IO, "import");
|
||||
return {
|
||||
elements: data.elements || null,
|
||||
appState: data.appState || null,
|
||||
@ -322,10 +318,10 @@ export const exportCanvas = async (
|
||||
fileName: `${name}.svg`,
|
||||
extensions: [".svg"],
|
||||
});
|
||||
trackEvent(EVENT_ACTION, "export", "svg");
|
||||
trackEvent(EVENT_IO, "export", "svg");
|
||||
return;
|
||||
} else if (type === "clipboard-svg") {
|
||||
trackEvent(EVENT_ACTION, "export", "clipboard-svg");
|
||||
trackEvent(EVENT_IO, "export", "clipboard-svg");
|
||||
copyTextToSystemClipboard(tempSvg.outerHTML);
|
||||
return;
|
||||
}
|
||||
@ -357,11 +353,11 @@ export const exportCanvas = async (
|
||||
fileName,
|
||||
extensions: [".png"],
|
||||
});
|
||||
trackEvent(EVENT_ACTION, "export", "png");
|
||||
trackEvent(EVENT_IO, "export", "png");
|
||||
} else if (type === "clipboard") {
|
||||
try {
|
||||
await copyCanvasToClipboardAsPng(tempCanvas);
|
||||
trackEvent(EVENT_ACTION, "export", "clipboard-png");
|
||||
trackEvent(EVENT_IO, "export", "clipboard-png");
|
||||
} catch (error) {
|
||||
if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
|
||||
throw error;
|
||||
|
Loading…
x
Reference in New Issue
Block a user