chore: Remove tracking (#2722)

* chore: Remove tracking

* process

* rename

* remove

* prod

* Update public/index.html

Co-authored-by: David Luzar <luzar.david@gmail.com>

* Update public/index.html

* eol

* more

* stats

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Lipis 2021-01-05 20:06:14 +02:00 committed by GitHub
parent 4acdc47ef0
commit 3aa01ad272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 182 additions and 435 deletions

View File

@ -1 +1 @@
REACT_APP_INCLUDE_GTAG=true REACT_APP_GOOGLE_ANALYTICS_ID=UA-387204-13

View File

@ -5,7 +5,6 @@ WORKDIR /opt/node_app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm i --no-optional RUN npm i --no-optional
ARG REACT_APP_INCLUDE_GTAG=false
ARG NODE_ENV=production ARG NODE_ENV=production
COPY . . COPY . .

View File

@ -1,64 +0,0 @@
| Excalidraw | Category | Name | Label | Value |
| ----------------------- | -------- | ---------------------------------- | ------------------------------- | --------- |
| 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` |
| 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` |
| Scroll back to content | action | scroll to content |
| 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` |
| Stroke width | change | stroke | width | `width` |
| Stroke style | change | style | `solid` or `dashed` or `dotted` |
| Stroke sloppiness | change | stroke | sloppiness | `value` |
| Fill | change | fill | `value` |
| Edge | change | edge | `value` |
| Opacity | change | opacity | value | `opacity` |
| Project name | change | title |
| Theme | change | theme | `light` or `dark` |
| Change language | change | language | `language` |
| Send to back | layer | move | `back` |
| Send backward | layer | move | `down` |
| Bring to front | layer | move | `front` |
| Bring forward | layer | move | `up` |
| Align left | align | align | `left` |
| Align right | align | align | `right` |
| Align top | align | align | `top` |
| Align bottom | align | align | `bottom` |
| Center horizontally | align | horizontally | `center` |
| 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 |
| Add to library | library | add |
| Remove from library | library | remove |
| Load library | library | load |
| Save library | library | save |
| Import library | library | import |
| Shortcuts dialog | dialog | shortcuts |
| Collaboration dialog | dialog | collaboration |
| Export dialog | dialog | export |
| Library dialog | dialog | library |
| E2EE shield | exit | e2ee shield |
| GitHub corner | exit | github |
| Excalidraw blog | exit | blog |
| Excalidraw guides | exit | guides |
| File issues | exit | issues |
| First load | load | first load |
| Load from stroage | load | storage | size | `bytes` |

View File

@ -81,8 +81,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build-node": "node ./scripts/build-node.js", "build-node": "node ./scripts/build-node.js",
"build:app:docker": "REACT_APP_INCLUDE_GTAG=false REACT_APP_DISABLE_SENTRY=true react-scripts build", "build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
"build:app": "REACT_APP_INCLUDE_GTAG=true REACT_APP_GIT_SHA=$NOW_GITHUB_COMMIT_SHA react-scripts build", "build:app": "REACT_APP_GIT_SHA=$NOW_GITHUB_COMMIT_SHA react-scripts build",
"build:version": "node ./scripts/build-version.js", "build:version": "node ./scripts/build-version.js",
"build": "npm run build:app && npm run build:version", "build": "npm run build:app && npm run build:version",
"eject": "react-scripts eject", "eject": "react-scripts eject",

View File

@ -86,10 +86,10 @@
<link rel="stylesheet" href="fonts.css" type="text/css" /> <link rel="stylesheet" href="fonts.css" type="text/css" />
<% if (process.env.REACT_APP_INCLUDE_GTAG === 'true') { %> <% if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) { %>
<script <script
async async
src="https://www.googletagmanager.com/gtag/js?id=UA-387204-13" src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_GOOGLE_ANALYTICS_ID%"
></script> ></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
@ -97,7 +97,7 @@
dataLayer.push(arguments); dataLayer.push(arguments);
} }
gtag("js", new Date()); gtag("js", new Date());
gtag("config", "UA-387204-13"); gtag("config", "%REACT_APP_GOOGLE_ANALYTICS_ID%");
</script> </script>
<% } %> <% } %>

View File

@ -3,7 +3,6 @@ import { getSelectedElements } from "../scene";
import { getNonDeletedElements } from "../element"; import { getNonDeletedElements } from "../element";
import { deepCopyElement } from "../element/newElement"; import { deepCopyElement } from "../element/newElement";
import { Library } from "../data/library"; import { Library } from "../data/library";
import { EVENT_LIBRARY, trackEvent } from "../analytics";
export const actionAddToLibrary = register({ export const actionAddToLibrary = register({
name: "addToLibrary", name: "addToLibrary",
@ -16,7 +15,6 @@ export const actionAddToLibrary = register({
Library.loadLibrary().then((items) => { Library.loadLibrary().then((items) => {
Library.saveLibrary([...items, selectedElements.map(deepCopyElement)]); Library.saveLibrary([...items, selectedElements.map(deepCopyElement)]);
}); });
trackEvent(EVENT_LIBRARY, "add");
return false; return false;
}, },
contextMenuOrder: 6, contextMenuOrder: 6,

View File

@ -1,7 +1,5 @@
import React from "react"; import React from "react";
import { KEYS } from "../keys"; import { alignElements, Alignment } from "../align";
import { t } from "../i18n";
import { register } from "./register";
import { import {
AlignBottomIcon, AlignBottomIcon,
AlignLeftIcon, AlignLeftIcon,
@ -10,14 +8,15 @@ import {
CenterHorizontallyIcon, CenterHorizontallyIcon,
CenterVerticallyIcon, CenterVerticallyIcon,
} from "../components/icons"; } from "../components/icons";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { getElementMap, getNonDeletedElements } from "../element";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import { getElementMap, getNonDeletedElements } from "../element";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { KEYS } from "../keys";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { AppState } from "../types"; import { AppState } from "../types";
import { alignElements, Alignment } from "../align";
import { getShortcutKey } from "../utils"; import { getShortcutKey } from "../utils";
import { trackEvent, EVENT_ALIGN } from "../analytics"; import { register } from "./register";
const enableActionGroup = ( const enableActionGroup = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -44,7 +43,6 @@ const alignSelectedElements = (
export const actionAlignTop = register({ export const actionAlignTop = register({
name: "alignTop", name: "alignTop",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "align", "top");
return { return {
appState, appState,
elements: alignSelectedElements(elements, appState, { elements: alignSelectedElements(elements, appState, {
@ -74,7 +72,6 @@ export const actionAlignTop = register({
export const actionAlignBottom = register({ export const actionAlignBottom = register({
name: "alignBottom", name: "alignBottom",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "align", "bottom");
return { return {
appState, appState,
elements: alignSelectedElements(elements, appState, { elements: alignSelectedElements(elements, appState, {
@ -104,7 +101,6 @@ export const actionAlignBottom = register({
export const actionAlignLeft = register({ export const actionAlignLeft = register({
name: "alignLeft", name: "alignLeft",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "align", "left");
return { return {
appState, appState,
elements: alignSelectedElements(elements, appState, { elements: alignSelectedElements(elements, appState, {
@ -134,7 +130,6 @@ export const actionAlignLeft = register({
export const actionAlignRight = register({ export const actionAlignRight = register({
name: "alignRight", name: "alignRight",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "align", "right");
return { return {
appState, appState,
elements: alignSelectedElements(elements, appState, { elements: alignSelectedElements(elements, appState, {
@ -164,7 +159,6 @@ export const actionAlignRight = register({
export const actionAlignVerticallyCentered = register({ export const actionAlignVerticallyCentered = register({
name: "alignVerticallyCentered", name: "alignVerticallyCentered",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "vertically", "center");
return { return {
appState, appState,
elements: alignSelectedElements(elements, appState, { elements: alignSelectedElements(elements, appState, {
@ -190,7 +184,6 @@ export const actionAlignVerticallyCentered = register({
export const actionAlignHorizontallyCentered = register({ export const actionAlignHorizontallyCentered = register({
name: "alignHorizontallyCentered", name: "alignHorizontallyCentered",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "horizontally", "center");
return { return {
appState, appState,
elements: alignSelectedElements(elements, appState, { elements: alignSelectedElements(elements, appState, {

View File

@ -1,7 +1,5 @@
import React from "react"; import React from "react";
import { EVENT_ACTION, EVENT_CHANGE, trackEvent } from "../analytics";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import colors from "../colors";
import { ColorPicker } from "../components/ColorPicker"; import { ColorPicker } from "../components/ColorPicker";
import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons"; import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
@ -21,15 +19,6 @@ import { register } from "./register";
export const actionChangeViewBackgroundColor = register({ export const actionChangeViewBackgroundColor = register({
name: "changeViewBackgroundColor", name: "changeViewBackgroundColor",
perform: (_, appState, value) => { perform: (_, appState, value) => {
if (value !== appState.viewBackgroundColor) {
trackEvent(
EVENT_CHANGE,
"canvas color",
colors.canvasBackground.includes(value)
? `${value} (picker ${colors.canvasBackground.indexOf(value)})`
: value,
);
}
return { return {
appState: { ...appState, viewBackgroundColor: value }, appState: { ...appState, viewBackgroundColor: value },
commitToHistory: true, commitToHistory: true,
@ -52,7 +41,6 @@ export const actionChangeViewBackgroundColor = register({
export const actionClearCanvas = register({ export const actionClearCanvas = register({
name: "clearCanvas", name: "clearCanvas",
perform: (elements, appState: AppState) => { perform: (elements, appState: AppState) => {
trackEvent(EVENT_ACTION, "clear canvas");
return { return {
elements: elements.map((element) => elements: elements.map((element) =>
newElementWith(element, { isDeleted: true }), newElementWith(element, { isDeleted: true }),
@ -98,7 +86,6 @@ export const actionZoomIn = register({
{ left: appState.offsetLeft, top: appState.offsetTop }, { left: appState.offsetLeft, top: appState.offsetTop },
{ x: appState.width / 2, y: appState.height / 2 }, { x: appState.width / 2, y: appState.height / 2 },
); );
trackEvent(EVENT_ACTION, "zoom", "in", zoom.value * 100);
return { return {
appState: { appState: {
...appState, ...appState,
@ -133,7 +120,6 @@ export const actionZoomOut = register({
{ x: appState.width / 2, y: appState.height / 2 }, { x: appState.width / 2, y: appState.height / 2 },
); );
trackEvent(EVENT_ACTION, "zoom", "out", zoom.value * 100);
return { return {
appState: { appState: {
...appState, ...appState,
@ -161,7 +147,6 @@ export const actionZoomOut = register({
export const actionResetZoom = register({ export const actionResetZoom = register({
name: "resetZoom", name: "resetZoom",
perform: (_elements, appState) => { perform: (_elements, appState) => {
trackEvent(EVENT_ACTION, "zoom", "reset", 100);
return { return {
appState: { appState: {
...appState, ...appState,
@ -234,12 +219,10 @@ const zoomToFitElements = (
left: appState.offsetLeft, left: appState.offsetLeft,
top: appState.offsetTop, top: appState.offsetTop,
}); });
const action = zoomToSelection ? "selection" : "fit";
const [x1, y1, x2, y2] = commonBounds; const [x1, y1, x2, y2] = commonBounds;
const centerX = (x1 + x2) / 2; const centerX = (x1 + x2) / 2;
const centerY = (y1 + y2) / 2; const centerY = (y1 + y2) / 2;
trackEvent(EVENT_ACTION, "zoom", action, newZoom.value * 100);
return { return {
appState: { appState: {
...appState, ...appState,

View File

@ -1,19 +1,18 @@
import React from "react"; import React from "react";
import { CODES } from "../keys";
import { t } from "../i18n";
import { register } from "./register";
import { import {
DistributeHorizontallyIcon, DistributeHorizontallyIcon,
DistributeVerticallyIcon, DistributeVerticallyIcon,
} from "../components/icons"; } from "../components/icons";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { getElementMap, getNonDeletedElements } from "../element";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { distributeElements, Distribution } from "../disitrubte"; import { distributeElements, Distribution } from "../disitrubte";
import { getElementMap, getNonDeletedElements } from "../element";
import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { CODES } from "../keys";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { AppState } from "../types";
import { getShortcutKey } from "../utils"; import { getShortcutKey } from "../utils";
import { EVENT_ALIGN, trackEvent } from "../analytics"; import { register } from "./register";
const enableActionGroup = ( const enableActionGroup = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -40,7 +39,6 @@ const distributeSelectedElements = (
export const distributeHorizontally = register({ export const distributeHorizontally = register({
name: "distributeHorizontally", name: "distributeHorizontally",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "distribute", "horizontally");
return { return {
appState, appState,
elements: distributeSelectedElements(elements, appState, { elements: distributeSelectedElements(elements, appState, {
@ -69,7 +67,6 @@ export const distributeHorizontally = register({
export const distributeVertically = register({ export const distributeVertically = register({
name: "distributeVertically", name: "distributeVertically",
perform: (elements, appState) => { perform: (elements, appState) => {
trackEvent(EVENT_ALIGN, "distribute", "vertically");
return { return {
appState, appState,
elements: distributeSelectedElements(elements, appState, { elements: distributeSelectedElements(elements, appState, {

View File

@ -1,22 +1,21 @@
import React from "react"; import React from "react";
import { EVENT_CHANGE, EVENT_IO, trackEvent } from "../analytics"; import { trackEvent } from "../analytics";
import { load, save, saveAs } from "../components/icons"; import { load, questionCircle, save, saveAs } from "../components/icons";
import { ProjectName } from "../components/ProjectName"; import { ProjectName } from "../components/ProjectName";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import "../components/ToolIcon.scss";
import { Tooltip } from "../components/Tooltip"; import { Tooltip } from "../components/Tooltip";
import { questionCircle } from "../components/icons";
import { loadFromJSON, saveAsJSON } from "../data"; import { loadFromJSON, saveAsJSON } from "../data";
import { t } from "../i18n"; import { t } from "../i18n";
import useIsMobile from "../is-mobile"; import useIsMobile from "../is-mobile";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { muteFSAbortError } from "../utils"; import { muteFSAbortError } from "../utils";
import { register } from "./register"; import { register } from "./register";
import "../components/ToolIcon.scss";
export const actionChangeProjectName = register({ export const actionChangeProjectName = register({
name: "changeProjectName", name: "changeProjectName",
perform: (_elements, appState, value) => { perform: (_elements, appState, value) => {
trackEvent(EVENT_CHANGE, "title"); trackEvent("change", "title");
return { appState: { ...appState, name: value }, commitToHistory: false }; return { appState: { ...appState, name: value }, commitToHistory: false };
}, },
PanelComponent: ({ appState, updateData }) => ( PanelComponent: ({ appState, updateData }) => (
@ -100,7 +99,6 @@ export const actionSaveScene = register({
perform: async (elements, appState, value) => { perform: async (elements, appState, value) => {
try { try {
const { fileHandle } = await saveAsJSON(elements, appState); const { fileHandle } = await saveAsJSON(elements, appState);
trackEvent(EVENT_IO, "save");
return { commitToHistory: false, appState: { ...appState, fileHandle } }; return { commitToHistory: false, appState: { ...appState, fileHandle } };
} catch (error) { } catch (error) {
if (error?.name !== "AbortError") { if (error?.name !== "AbortError") {
@ -131,7 +129,6 @@ export const actionSaveAsScene = register({
...appState, ...appState,
fileHandle: null, fileHandle: null,
}); });
trackEvent(EVENT_IO, "save as");
return { commitToHistory: false, appState: { ...appState, fileHandle } }; return { commitToHistory: false, appState: { ...appState, fileHandle } };
} catch (error) { } catch (error) {
if (error?.name !== "AbortError") { if (error?.name !== "AbortError") {

View File

@ -7,7 +7,6 @@ import { register } from "./register";
import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils"; import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils";
import { CODES, KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
import { HelpIcon } from "../components/HelpIcon"; import { HelpIcon } from "../components/HelpIcon";
import { EVENT_DIALOG, trackEvent } from "../analytics";
export const actionToggleCanvasMenu = register({ export const actionToggleCanvasMenu = register({
name: "toggleCanvasMenu", name: "toggleCanvasMenu",
@ -72,7 +71,6 @@ export const actionFullScreen = register({
export const actionShortcuts = register({ export const actionShortcuts = register({
name: "toggleShortcuts", name: "toggleShortcuts",
perform: (_elements, appState) => { perform: (_elements, appState) => {
trackEvent(EVENT_DIALOG, "shortcuts");
return { return {
appState: { appState: {
...appState, ...appState,

View File

@ -1,16 +1,14 @@
import React from "react"; import React from "react";
import { Avatar } from "../components/Avatar";
import { register } from "./register";
import { getClientColors, getClientInitials } from "../clients"; import { getClientColors, getClientInitials } from "../clients";
import { Collaborator } from "../types"; import { Avatar } from "../components/Avatar";
import { centerScrollOn } from "../scene/scroll"; import { centerScrollOn } from "../scene/scroll";
import { EVENT_SHARE, trackEvent } from "../analytics"; import { Collaborator } from "../types";
import { register } from "./register";
export const actionGoToCollaborator = register({ export const actionGoToCollaborator = register({
name: "goToCollaborator", name: "goToCollaborator",
perform: (_elements, appState, value) => { perform: (_elements, appState, value) => {
const point = value as Collaborator["pointer"]; const point = value as Collaborator["pointer"];
trackEvent(EVENT_SHARE, "go to collaborator");
if (!point) { if (!point) {
return { appState, commitToHistory: false }; return { appState, commitToHistory: false };
} }

View File

@ -1,56 +1,53 @@
import React from "react"; import React from "react";
import { getLanguage } from "../i18n"; import { AppState } from "../../src/types";
import {
ExcalidrawElement,
ExcalidrawTextElement,
TextAlign,
FontFamily,
ExcalidrawLinearElement,
Arrowhead,
} from "../element/types";
import {
getCommonAttributeOfSelectedElements,
isSomeElementSelected,
getTargetElements,
canChangeSharpness,
canHaveArrowheads,
} from "../scene";
import { ButtonSelect } from "../components/ButtonSelect";
import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ButtonIconSelect } from "../components/ButtonIconSelect";
import { ButtonSelect } from "../components/ButtonSelect";
import { ColorPicker } from "../components/ColorPicker";
import { IconPicker } from "../components/IconPicker"; import { IconPicker } from "../components/IconPicker";
import { import {
isTextElement,
redrawTextBoundingBox,
getNonDeletedElements,
} from "../element";
import { isLinearElement, isLinearElementType } from "../element/typeChecks";
import { ColorPicker } from "../components/ColorPicker";
import { AppState } from "../../src/types";
import { t } from "../i18n";
import { register } from "./register";
import { newElementWith } from "../element/mutateElement";
import { DEFAULT_FONT_SIZE, DEFAULT_FONT_FAMILY } from "../constants";
import { randomInteger } from "../random";
import {
FillHachureIcon,
FillCrossHatchIcon,
FillSolidIcon,
StrokeWidthIcon,
StrokeStyleSolidIcon,
StrokeStyleDashedIcon,
StrokeStyleDottedIcon,
EdgeSharpIcon,
EdgeRoundIcon,
SloppinessArchitectIcon,
SloppinessArtistIcon,
SloppinessCartoonistIcon,
ArrowheadArrowIcon, ArrowheadArrowIcon,
ArrowheadBarIcon, ArrowheadBarIcon,
ArrowheadDotIcon, ArrowheadDotIcon,
ArrowheadNoneIcon, ArrowheadNoneIcon,
EdgeRoundIcon,
EdgeSharpIcon,
FillCrossHatchIcon,
FillHachureIcon,
FillSolidIcon,
SloppinessArchitectIcon,
SloppinessArtistIcon,
SloppinessCartoonistIcon,
StrokeStyleDashedIcon,
StrokeStyleDottedIcon,
StrokeStyleSolidIcon,
StrokeWidthIcon,
} from "../components/icons"; } from "../components/icons";
import { EVENT_CHANGE, trackEvent } from "../analytics"; import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "../constants";
import colors from "../colors"; import {
getNonDeletedElements,
isTextElement,
redrawTextBoundingBox,
} from "../element";
import { newElementWith } from "../element/mutateElement";
import { isLinearElement, isLinearElementType } from "../element/typeChecks";
import {
Arrowhead,
ExcalidrawElement,
ExcalidrawLinearElement,
ExcalidrawTextElement,
FontFamily,
TextAlign,
} from "../element/types";
import { getLanguage, t } from "../i18n";
import { randomInteger } from "../random";
import {
canChangeSharpness,
canHaveArrowheads,
getCommonAttributeOfSelectedElements,
getTargetElements,
isSomeElementSelected,
} from "../scene";
import { register } from "./register";
const changeProperty = ( const changeProperty = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -92,15 +89,6 @@ const getFormValue = function <T>(
export const actionChangeStrokeColor = register({ export const actionChangeStrokeColor = register({
name: "changeStrokeColor", name: "changeStrokeColor",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
if (value !== appState.currentItemStrokeColor) {
trackEvent(
EVENT_CHANGE,
"stroke color",
colors.elementStroke.includes(value)
? `${value} (picker ${colors.elementStroke.indexOf(value)})`
: value,
);
}
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -132,16 +120,6 @@ export const actionChangeStrokeColor = register({
export const actionChangeBackgroundColor = register({ export const actionChangeBackgroundColor = register({
name: "changeBackgroundColor", name: "changeBackgroundColor",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
if (value !== appState.currentItemBackgroundColor) {
trackEvent(
EVENT_CHANGE,
"background color",
colors.elementBackground.includes(value)
? `${value} (picker ${colors.elementBackground.indexOf(value)})`
: value,
);
}
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -173,7 +151,6 @@ export const actionChangeBackgroundColor = register({
export const actionChangeFillStyle = register({ export const actionChangeFillStyle = register({
name: "changeFillStyle", name: "changeFillStyle",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
trackEvent(EVENT_CHANGE, "fill", value);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -223,7 +200,6 @@ export const actionChangeFillStyle = register({
export const actionChangeStrokeWidth = register({ export const actionChangeStrokeWidth = register({
name: "changeStrokeWidth", name: "changeStrokeWidth",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
trackEvent(EVENT_CHANGE, "stroke", "width", value);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -286,7 +262,6 @@ export const actionChangeStrokeWidth = register({
export const actionChangeSloppiness = register({ export const actionChangeSloppiness = register({
name: "changeSloppiness", name: "changeSloppiness",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
trackEvent(EVENT_CHANGE, "stroke", "sloppiness", value);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -335,7 +310,6 @@ export const actionChangeSloppiness = register({
export const actionChangeStrokeStyle = register({ export const actionChangeStrokeStyle = register({
name: "changeStrokeStyle", name: "changeStrokeStyle",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
trackEvent(EVENT_CHANGE, "style", value);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -383,7 +357,6 @@ export const actionChangeStrokeStyle = register({
export const actionChangeOpacity = register({ export const actionChangeOpacity = register({
name: "changeOpacity", name: "changeOpacity",
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
trackEvent(EVENT_CHANGE, "opacity", "value", value);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -580,7 +553,6 @@ export const actionChangeSharpness = register({
const shouldUpdateForLinearElements = targetElements.length const shouldUpdateForLinearElements = targetElements.length
? targetElements.every(isLinearElement) ? targetElements.every(isLinearElement)
: isLinearElementType(appState.elementType); : isLinearElementType(appState.elementType);
trackEvent(EVENT_CHANGE, "edge", value);
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(elements, appState, (el) =>
newElementWith(el, { newElementWith(el, {
@ -642,12 +614,6 @@ export const actionChangeArrowhead = register({
return { return {
elements: changeProperty(elements, appState, (el) => { elements: changeProperty(elements, appState, (el) => {
if (isLinearElement(el)) { if (isLinearElement(el)) {
trackEvent(
EVENT_CHANGE,
`arrowhead ${value.position}`,
value.type || "none",
);
const { position, type } = value; const { position, type } = value;
if (position === "start") { if (position === "start") {

View File

@ -1,18 +1,7 @@
export const EVENT_ACTION = "action";
export const EVENT_ALIGN = "align";
export const EVENT_CHANGE = "change";
export const EVENT_DIALOG = "dialog";
export const EVENT_EXIT = "exit";
export const EVENT_IO = "io";
export const EVENT_LAYER = "layer";
export const EVENT_LIBRARY = "library";
export const EVENT_LOAD = "load";
export const EVENT_SHAPE = "shape";
export const EVENT_SHARE = "share";
export const EVENT_MAGIC = "magic";
export const trackEvent = export const trackEvent =
typeof window !== "undefined" && window.gtag process.env.REACT_APP_GOOGLE_ANALYTICS_ID &&
typeof window !== "undefined" &&
window.gtag
? (category: string, name: string, label?: string, value?: number) => { ? (category: string, name: string, label?: string, value?: number) => {
window.gtag("event", name, { window.gtag("event", name, {
event_category: category, event_category: category,
@ -23,5 +12,6 @@ export const trackEvent =
: typeof process !== "undefined" && process?.env?.JEST_WORKER_ID : typeof process !== "undefined" && process?.env?.JEST_WORKER_ID
? (category: string, name: string, label?: string, value?: number) => {} ? (category: string, name: string, label?: string, value?: number) => {}
: (category: string, name: string, label?: string, value?: number) => { : (category: string, name: string, label?: string, value?: number) => {
console.info("Track Event", category, name, label, value); // Uncomment the next line to track locally
// console.info("Track Event", category, name, label, value);
}; };

View File

@ -1,4 +1,4 @@
import { EVENT_MAGIC, trackEvent } from "./analytics"; import { trackEvent } from "./analytics";
import colors from "./colors"; import colors from "./colors";
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, ENV } from "./constants"; import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, ENV } from "./constants";
import { newElement, newLinearElement, newTextElement } from "./element"; import { newElement, newLinearElement, newTextElement } from "./element";
@ -473,7 +473,7 @@ export const renderSpreadsheet = (
x: number, x: number,
y: number, y: number,
): ChartElements => { ): ChartElements => {
trackEvent(EVENT_MAGIC, "chart", chartType, spreadsheet.values.length); trackEvent("magic", "chart", chartType, spreadsheet.values.length);
if (chartType === "line") { if (chartType === "line") {
return chartTypeLine(spreadsheet, x, y); return chartTypeLine(spreadsheet, x, y);
} }

View File

@ -1,23 +1,22 @@
import React from "react"; import React from "react";
import { AppState, Zoom } from "../types";
import { ExcalidrawElement } from "../element/types";
import { ActionManager } from "../actions/manager"; import { ActionManager } from "../actions/manager";
import { getNonDeletedElements } from "../element";
import { ExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import useIsMobile from "../is-mobile";
import { import {
hasBackground,
hasStroke,
canChangeSharpness, canChangeSharpness,
hasText,
canHaveArrowheads, canHaveArrowheads,
getTargetElements, getTargetElements,
hasBackground,
hasStroke,
hasText,
} from "../scene"; } from "../scene";
import { t } from "../i18n";
import { SHAPES } from "../shapes"; import { SHAPES } from "../shapes";
import { ToolButton } from "./ToolButton"; import { AppState, Zoom } from "../types";
import { capitalizeString, isTransparent, setCursorForShape } from "../utils"; import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
import Stack from "./Stack"; import Stack from "./Stack";
import useIsMobile from "../is-mobile"; import { ToolButton } from "./ToolButton";
import { getNonDeletedElements } from "../element";
import { trackEvent, EVENT_SHAPE, EVENT_DIALOG } from "../analytics";
export const SelectedShapeActions = ({ export const SelectedShapeActions = ({
appState, appState,
@ -181,7 +180,6 @@ export const ShapesSwitcher = ({
aria-keyshortcuts={shortcut} aria-keyshortcuts={shortcut}
data-testid={value} data-testid={value}
onChange={() => { onChange={() => {
trackEvent(EVENT_SHAPE, value, "toolbar");
setAppState({ setAppState({
elementType: value, elementType: value,
multiElement: null, multiElement: null,
@ -203,9 +201,6 @@ export const ShapesSwitcher = ({
title={`${capitalizeString(t("toolBar.library"))} — 9`} title={`${capitalizeString(t("toolBar.library"))} — 9`}
aria-label={capitalizeString(t("toolBar.library"))} aria-label={capitalizeString(t("toolBar.library"))}
onClick={() => { onClick={() => {
if (!isLibraryOpen) {
trackEvent(EVENT_DIALOG, "library");
}
setAppState({ isLibraryOpen: !isLibraryOpen }); setAppState({ isLibraryOpen: !isLibraryOpen });
}} }}
/> />

View File

@ -8,12 +8,7 @@ import { createRedoAction, createUndoAction } from "../actions/actionHistory";
import { ActionManager } from "../actions/manager"; import { ActionManager } from "../actions/manager";
import { actions } from "../actions/register"; import { actions } from "../actions/register";
import { ActionResult } from "../actions/types"; import { ActionResult } from "../actions/types";
import { import { trackEvent } from "../analytics";
EVENT_DIALOG,
EVENT_LIBRARY,
EVENT_SHAPE,
trackEvent,
} from "../analytics";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import { import {
copyToClipboard, copyToClipboard,
@ -111,7 +106,7 @@ import {
selectGroupsForSelectedElements, selectGroupsForSelectedElements,
} from "../groups"; } from "../groups";
import { createHistory, SceneHistory } from "../history"; import { createHistory, SceneHistory } from "../history";
import { t, getLanguage, setLanguage, languages, defaultLang } from "../i18n"; import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
import { import {
CODES, CODES,
getResizeCenterPointKey, getResizeCenterPointKey,
@ -504,7 +499,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
) )
) { ) {
await Library.importLibrary(blob); await Library.importLibrary(blob);
trackEvent(EVENT_LIBRARY, "import");
this.setState({ this.setState({
isLibraryOpen: true, isLibraryOpen: true,
}); });
@ -1134,7 +1128,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
toggleLock = () => { toggleLock = () => {
this.setState((prevState) => { this.setState((prevState) => {
trackEvent(EVENT_SHAPE, "lock", !prevState.elementLocked ? "on" : "off");
return { return {
elementLocked: !prevState.elementLocked, elementLocked: !prevState.elementLocked,
elementType: prevState.elementLocked elementType: prevState.elementLocked
@ -1158,7 +1151,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
toggleStats = () => { toggleStats = () => {
if (!this.state.showStats) { if (!this.state.showStats) {
trackEvent(EVENT_DIALOG, "stats"); trackEvent("dialog", "stats");
} }
this.setState({ this.setState({
showStats: !this.state.showStats, showStats: !this.state.showStats,
@ -1270,9 +1263,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
} }
if (event.code === CODES.NINE) { if (event.code === CODES.NINE) {
if (!this.state.isLibraryOpen) {
trackEvent(EVENT_DIALOG, "library");
}
this.setState({ isLibraryOpen: !this.state.isLibraryOpen }); this.setState({ isLibraryOpen: !this.state.isLibraryOpen });
} }
@ -1357,7 +1347,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
) { ) {
const shape = findShapeByKey(event.key); const shape = findShapeByKey(event.key);
if (shape) { if (shape) {
trackEvent(EVENT_SHAPE, shape, "shortcut");
this.selectShapeTool(shape); this.selectShapeTool(shape);
} else if (event.key === KEYS.Q) { } else if (event.key === KEYS.Q) {
this.toggleLock(); this.toggleLock();
@ -1741,7 +1730,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
resetCursor(); resetCursor();
if (!event[KEYS.CTRL_OR_CMD]) { if (!event[KEYS.CTRL_OR_CMD]) {
trackEvent(EVENT_SHAPE, "text", "double-click");
this.startTextEditing({ this.startTextEditing({
sceneX, sceneX,
sceneY, sceneY,

View File

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import { ActionManager } from "../actions/manager"; import { ActionManager } from "../actions/manager";
import { EVENT_CHANGE, trackEvent } from "../analytics";
import { AppState } from "../types"; import { AppState } from "../types";
import { DarkModeToggle } from "./DarkModeToggle"; import { DarkModeToggle } from "./DarkModeToggle";
@ -19,8 +18,6 @@ export const BackgroundPickerAndDarkModeToggle = ({
<DarkModeToggle <DarkModeToggle
value={appState.appearance} value={appState.appearance}
onChange={(appearance) => { onChange={(appearance) => {
// TODO: track the theme on the first load too
trackEvent(EVENT_CHANGE, "theme", appearance);
setAppState({ appearance }); setAppState({ appearance });
}} }}
/> />

View File

@ -6,7 +6,6 @@ import useIsMobile from "../is-mobile";
import { users } from "./icons"; import { users } from "./icons";
import "./CollabButton.scss"; import "./CollabButton.scss";
import { EVENT_DIALOG, trackEvent } from "../analytics";
const CollabButton = ({ const CollabButton = ({
isCollaborating, isCollaborating,
@ -23,10 +22,7 @@ const CollabButton = ({
className={clsx("CollabButton", { className={clsx("CollabButton", {
"is-collaborating": isCollaborating, "is-collaborating": isCollaborating,
})} })}
onClick={() => { onClick={onClick}
trackEvent(EVENT_DIALOG, "collaboration");
onClick();
}}
icon={users} icon={users}
type="button" type="button"
title={t("buttons.roomDialog")} title={t("buttons.roomDialog")}

View File

@ -1,7 +1,6 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { render, unmountComponentAtNode } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import { ActionsManagerInterface } from "../actions/types"; import { ActionsManagerInterface } from "../actions/types";
import { EVENT_DIALOG, trackEvent } from "../analytics";
import { probablySupportsClipboardBlob } from "../clipboard"; import { probablySupportsClipboardBlob } from "../clipboard";
import { canvasToBlob } from "../data/blob"; import { canvasToBlob } from "../data/blob";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../element/types";
@ -251,7 +250,6 @@ export const ExportDialog = ({
<> <>
<ToolButton <ToolButton
onClick={() => { onClick={() => {
trackEvent(EVENT_DIALOG, "export");
setModalIsShown(true); setModalIsShown(true);
}} }}
icon={exportFile} icon={exportFile}

View File

@ -1,6 +1,5 @@
import React from "react";
import oc from "open-color"; import oc from "open-color";
import { EVENT_EXIT, trackEvent } from "../analytics"; import React from "react";
// https://github.com/tholman/github-corners // https://github.com/tholman/github-corners
export const GitHubCorner = React.memo( export const GitHubCorner = React.memo(
@ -17,9 +16,6 @@ export const GitHubCorner = React.memo(
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="GitHub repository" aria-label="GitHub repository"
onClick={() => {
trackEvent(EVENT_EXIT, "github");
}}
> >
<path <path
d="M0 0l115 115h15l12 27 108 108V0z" d="M0 0l115 115h15l12 27 108 108V0z"

View File

@ -1,56 +1,46 @@
import clsx from "clsx";
import React, { import React, {
RefObject,
useCallback,
useEffect,
useRef, useRef,
useState, useState,
RefObject,
useEffect,
useCallback,
} from "react"; } from "react";
import { showSelectedShapeActions } from "../element";
import { calculateScrollCenter, getSelectedElements } from "../scene";
import { exportCanvas } from "../data";
import { AppState, LibraryItems, LibraryItem } from "../types";
import { NonDeletedExcalidrawElement } from "../element/types";
import { ActionManager } from "../actions/manager"; import { ActionManager } from "../actions/manager";
import { Island } from "./Island"; import { CLASSES } from "../constants";
import Stack from "./Stack"; import { exportCanvas } from "../data";
import { FixedSideContainer } from "./FixedSideContainer"; import { importLibraryFromJSON, saveLibraryAsJSON } from "../data/json";
import { UserList } from "./UserList"; import { Library } from "../data/library";
import { LockIcon } from "./LockIcon"; import { showSelectedShapeActions } from "../element";
import { ExportDialog, ExportCB } from "./ExportDialog"; import { NonDeletedExcalidrawElement } from "../element/types";
import { Language, t } from "../i18n"; import { Language, t } from "../i18n";
import { HintViewer } from "./HintViewer";
import useIsMobile from "../is-mobile"; import useIsMobile from "../is-mobile";
import { calculateScrollCenter, getSelectedElements } from "../scene";
import { ExportType } from "../scene/types"; import { ExportType } from "../scene/types";
import { MobileMenu } from "./MobileMenu"; import { AppState, LibraryItem, LibraryItems } from "../types";
import { ZoomActions, SelectedShapeActions, ShapesSwitcher } from "./Actions"; import { muteFSAbortError } from "../utils";
import { Section } from "./Section"; import { SelectedShapeActions, ShapesSwitcher, ZoomActions } from "./Actions";
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
import CollabButton from "./CollabButton"; import CollabButton from "./CollabButton";
import { ErrorDialog } from "./ErrorDialog"; import { ErrorDialog } from "./ErrorDialog";
import { ShortcutsDialog } from "./ShortcutsDialog"; import { ExportCB, ExportDialog } from "./ExportDialog";
import { LoadingMessage } from "./LoadingMessage"; import { FixedSideContainer } from "./FixedSideContainer";
import { CLASSES } from "../constants";
import { shield, exportFile, load } from "./icons";
import { GitHubCorner } from "./GitHubCorner"; import { GitHubCorner } from "./GitHubCorner";
import { Tooltip } from "./Tooltip"; import { HintViewer } from "./HintViewer";
import { exportFile, load, shield } from "./icons";
import { Island } from "./Island";
import "./LayerUI.scss"; import "./LayerUI.scss";
import { LibraryUnit } from "./LibraryUnit"; import { LibraryUnit } from "./LibraryUnit";
import { ToolButton } from "./ToolButton"; import { LoadingMessage } from "./LoadingMessage";
import { saveLibraryAsJSON, importLibraryFromJSON } from "../data/json"; import { LockIcon } from "./LockIcon";
import { muteFSAbortError } from "../utils"; import { MobileMenu } from "./MobileMenu";
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
import clsx from "clsx";
import { Library } from "../data/library";
import {
EVENT_ACTION,
EVENT_EXIT,
EVENT_LIBRARY,
trackEvent,
} from "../analytics";
import { PasteChartDialog } from "./PasteChartDialog"; import { PasteChartDialog } from "./PasteChartDialog";
import { Section } from "./Section";
import { ShortcutsDialog } from "./ShortcutsDialog";
import Stack from "./Stack";
import { ToolButton } from "./ToolButton";
import { Tooltip } from "./Tooltip";
import { UserList } from "./UserList";
interface LayerUIProps { interface LayerUIProps {
actionManager: ActionManager; actionManager: ActionManager;
@ -159,13 +149,7 @@ const LibraryMenuItems = ({
}} }}
/> />
<a <a href="https://libraries.excalidraw.com" target="_excalidraw_libraries">
href="https://libraries.excalidraw.com"
target="_excalidraw_libraries"
onClick={() => {
trackEvent(EVENT_EXIT, "libraries");
}}
>
{t("labels.libraries")} {t("labels.libraries")}
</a> </a>
</div>, </div>,
@ -267,7 +251,6 @@ const LibraryMenu = ({
const items = await Library.loadLibrary(); const items = await Library.loadLibrary();
const nextItems = items.filter((_, index) => index !== indexToRemove); const nextItems = items.filter((_, index) => index !== indexToRemove);
Library.saveLibrary(nextItems); Library.saveLibrary(nextItems);
trackEvent(EVENT_LIBRARY, "remove");
setLibraryItems(nextItems); setLibraryItems(nextItems);
}, []); }, []);
@ -276,7 +259,6 @@ const LibraryMenu = ({
const items = await Library.loadLibrary(); const items = await Library.loadLibrary();
const nextItems = [...items, elements]; const nextItems = [...items, elements];
onAddToLibrary(); onAddToLibrary();
trackEvent(EVENT_LIBRARY, "add");
Library.saveLibrary(nextItems); Library.saveLibrary(nextItems);
setLibraryItems(nextItems); setLibraryItems(nextItems);
}, },
@ -328,9 +310,6 @@ const LayerUI = ({
href="https://blog.excalidraw.com/end-to-end-encryption/" href="https://blog.excalidraw.com/end-to-end-encryption/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={() => {
trackEvent(EVENT_EXIT, "e2ee shield");
}}
> >
<Tooltip label={t("encrypted.tooltip")} position="above" long={true}> <Tooltip label={t("encrypted.tooltip")} position="above" long={true}>
{shield} {shield}
@ -567,7 +546,6 @@ const LayerUI = ({
<button <button
className="scroll-back-to-content" className="scroll-back-to-content"
onClick={() => { onClick={() => {
trackEvent(EVENT_ACTION, "scroll to content");
setAppState({ setAppState({
...calculateScrollCenter(elements, appState, canvas), ...calculateScrollCenter(elements, appState, canvas),
}); });

View File

@ -16,7 +16,6 @@ import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
import { LockIcon } from "./LockIcon"; import { LockIcon } from "./LockIcon";
import { UserList } from "./UserList"; import { UserList } from "./UserList";
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle"; import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
import { EVENT_ACTION, trackEvent } from "../analytics";
type MobileMenuProps = { type MobileMenuProps = {
appState: AppState; appState: AppState;
@ -149,7 +148,6 @@ export const MobileMenu = ({
<button <button
className="scroll-back-to-content" className="scroll-back-to-content"
onClick={() => { onClick={() => {
trackEvent(EVENT_ACTION, "scroll to content");
setAppState({ setAppState({
...calculateScrollCenter(elements, appState, canvas), ...calculateScrollCenter(elements, appState, canvas),
}); });

View File

@ -4,7 +4,6 @@ import { isDarwin } from "../keys";
import { Dialog } from "./Dialog"; import { Dialog } from "./Dialog";
import { getShortcutKey } from "../utils"; import { getShortcutKey } from "../utils";
import "./ShortcutsDialog.scss"; import "./ShortcutsDialog.scss";
import { EVENT_EXIT, trackEvent } from "../analytics";
const Columns = (props: { children: React.ReactNode }) => ( const Columns = (props: { children: React.ReactNode }) => (
<div <div
@ -92,9 +91,6 @@ const Footer = () => (
href="https://blog.excalidraw.com" href="https://blog.excalidraw.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={() => {
trackEvent(EVENT_EXIT, "blog");
}}
> >
{t("shortcutsDialog.blog")} {t("shortcutsDialog.blog")}
</a> </a>
@ -102,9 +98,6 @@ const Footer = () => (
href="https://howto.excalidraw.com" href="https://howto.excalidraw.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={() => {
trackEvent(EVENT_EXIT, "guides");
}}
> >
{t("shortcutsDialog.howto")} {t("shortcutsDialog.howto")}
</a> </a>
@ -112,9 +105,6 @@ const Footer = () => (
href="https://github.com/excalidraw/excalidraw/issues" href="https://github.com/excalidraw/excalidraw/issues"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={() => {
trackEvent(EVENT_EXIT, "issues");
}}
> >
{t("shortcutsDialog.github")} {t("shortcutsDialog.github")}
</a> </a>

View File

@ -1,4 +1,3 @@
import { EVENT_IO, trackEvent } from "../analytics";
import { cleanAppStateForExport } from "../appState"; import { cleanAppStateForExport } from "../appState";
import { MIME_TYPES } from "../constants"; import { MIME_TYPES } from "../constants";
import { clearElementsForExport } from "../element"; import { clearElementsForExport } from "../element";
@ -111,7 +110,6 @@ export const loadFromBlob = async (
localAppState, localAppState,
); );
trackEvent(EVENT_IO, "load", getMimeType(blob));
return result; return result;
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);

View File

@ -1,5 +1,4 @@
import { fileSave } from "browser-nativefs"; import { fileSave } from "browser-nativefs";
import { EVENT_IO, trackEvent } from "../analytics";
import { import {
copyCanvasToClipboardAsPng, copyCanvasToClipboardAsPng,
copyTextToSystemClipboard, copyTextToSystemClipboard,
@ -8,8 +7,8 @@ import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n"; import { t } from "../i18n";
import { exportToCanvas, exportToSvg } from "../scene/export"; import { exportToCanvas, exportToSvg } from "../scene/export";
import { ExportType } from "../scene/types"; import { ExportType } from "../scene/types";
import { canvasToBlob } from "./blob";
import { AppState } from "../types"; import { AppState } from "../types";
import { canvasToBlob } from "./blob";
import { serializeAsJSON } from "./json"; import { serializeAsJSON } from "./json";
export { loadFromBlob } from "./blob"; export { loadFromBlob } from "./blob";
@ -60,10 +59,8 @@ export const exportCanvas = async (
fileName: `${name}.svg`, fileName: `${name}.svg`,
extensions: [".svg"], extensions: [".svg"],
}); });
trackEvent(EVENT_IO, "export", "svg");
return; return;
} else if (type === "clipboard-svg") { } else if (type === "clipboard-svg") {
trackEvent(EVENT_IO, "export", "clipboard-svg");
copyTextToSystemClipboard(tempSvg.outerHTML); copyTextToSystemClipboard(tempSvg.outerHTML);
return; return;
} }
@ -95,11 +92,9 @@ export const exportCanvas = async (
fileName, fileName,
extensions: [".png"], extensions: [".png"],
}); });
trackEvent(EVENT_IO, "export", "png");
} else if (type === "clipboard") { } else if (type === "clipboard") {
try { try {
await copyCanvasToClipboardAsPng(tempCanvas); await copyCanvasToClipboardAsPng(tempCanvas);
trackEvent(EVENT_IO, "export", "clipboard-png");
} catch (error) { } catch (error) {
if (error.name === "CANVAS_POSSIBLY_TOO_BIG") { if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
throw error; throw error;

View File

@ -1,13 +1,11 @@
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { cleanAppStateForExport } from "../appState";
import { fileOpen, fileSave } from "browser-nativefs"; import { fileOpen, fileSave } from "browser-nativefs";
import { loadFromBlob } from "./blob"; import { cleanAppStateForExport } from "../appState";
import { Library } from "./library";
import { MIME_TYPES } from "../constants"; import { MIME_TYPES } from "../constants";
import { clearElementsForExport } from "../element"; import { clearElementsForExport } from "../element";
import { EVENT_LIBRARY, trackEvent } from "../analytics"; import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { loadFromBlob } from "./blob";
import { Library } from "./library";
export const serializeAsJSON = ( export const serializeAsJSON = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -84,7 +82,6 @@ export const saveLibraryAsJSON = async () => {
description: "Excalidraw library file", description: "Excalidraw library file",
extensions: [".excalidrawlib"], extensions: [".excalidrawlib"],
}); });
trackEvent(EVENT_LIBRARY, "save");
}; };
export const importLibraryFromJSON = async () => { export const importLibraryFromJSON = async () => {
@ -93,6 +90,5 @@ export const importLibraryFromJSON = async () => {
extensions: [".json", ".excalidrawlib"], extensions: [".json", ".excalidrawlib"],
mimeTypes: ["application/json"], mimeTypes: ["application/json"],
}); });
trackEvent(EVENT_LIBRARY, "load");
Library.importLibrary(blob); Library.importLibrary(blob);
}; };

View File

@ -1,40 +1,36 @@
import React, { PureComponent } from "react";
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import React, { PureComponent } from "react";
import { ExcalidrawImperativeAPI } from "../../components/App";
import { ErrorDialog } from "../../components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../constants"; import { APP_NAME, ENV, EVENT } from "../../constants";
import { ImportedDataState } from "../../data/types";
import {
decryptAESGEM,
SocketUpdateDataSource,
getCollaborationLinkData,
generateCollaborationLink,
SOCKET_SERVER,
} from "../data";
import { isSavedToFirebase, saveToFirebase } from "../data/firebase";
import Portal from "./Portal";
import { AppState, Collaborator, Gesture } from "../../types";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../element/types";
import {
importUsernameFromLocalStorage,
saveUsernameToLocalStorage,
STORAGE_KEYS,
} from "../data/localStorage";
import { resolvablePromise, withBatchedUpdates } from "../../utils";
import { import {
getSceneVersion, getSceneVersion,
getSyncableElements, getSyncableElements,
} from "../../packages/excalidraw/index"; } from "../../packages/excalidraw/index";
import RoomDialog from "./RoomDialog"; import { AppState, Collaborator, Gesture } from "../../types";
import { ErrorDialog } from "../../components/ErrorDialog"; import { resolvablePromise, withBatchedUpdates } from "../../utils";
import { ImportedDataState } from "../../data/types";
import { ExcalidrawImperativeAPI } from "../../components/App";
import { import {
INITIAL_SCENE_UPDATE_TIMEOUT, INITIAL_SCENE_UPDATE_TIMEOUT,
SCENE, SCENE,
SYNC_FULL_SCENE_INTERVAL_MS, SYNC_FULL_SCENE_INTERVAL_MS,
} from "../app_constants"; } from "../app_constants";
import { EVENT_SHARE, trackEvent } from "../../analytics"; import {
decryptAESGEM,
generateCollaborationLink,
getCollaborationLinkData,
SocketUpdateDataSource,
SOCKET_SERVER,
} from "../data";
import { isSavedToFirebase, saveToFirebase } from "../data/firebase";
import {
importUsernameFromLocalStorage,
saveUsernameToLocalStorage,
STORAGE_KEYS,
} from "../data/localStorage";
import Portal from "./Portal";
import RoomDialog from "./RoomDialog";
interface CollabState { interface CollabState {
isCollaborating: boolean; isCollaborating: boolean;
@ -168,7 +164,6 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
elements, elements,
commitToHistory: true, commitToHistory: true,
}); });
trackEvent(EVENT_SHARE, "session start");
return this.initializeSocketClient(); return this.initializeSocketClient();
}; };
@ -176,7 +171,6 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
this.saveCollabRoomToFirebase(); this.saveCollabRoomToFirebase();
window.history.pushState({}, APP_NAME, window.location.origin); window.history.pushState({}, APP_NAME, window.location.origin);
this.destroySocketClient(); this.destroySocketClient();
trackEvent(EVENT_SHARE, "session end");
}; };
private destroySocketClient = () => { private destroySocketClient = () => {

View File

@ -1,12 +1,10 @@
import React, { useRef } from "react"; import React, { useRef } from "react";
import { t } from "../../i18n";
import { Dialog } from "../../components/Dialog";
import { copyTextToSystemClipboard } from "../../clipboard"; import { copyTextToSystemClipboard } from "../../clipboard";
import { ToolButton } from "../../components/ToolButton"; import { Dialog } from "../../components/Dialog";
import { clipboard, start, stop } from "../../components/icons"; import { clipboard, start, stop } from "../../components/icons";
import { ToolButton } from "../../components/ToolButton";
import { t } from "../../i18n";
import "./RoomDialog.scss"; import "./RoomDialog.scss";
import { EVENT_SHARE, trackEvent } from "../../analytics";
const RoomDialog = ({ const RoomDialog = ({
handleClose, handleClose,
@ -30,7 +28,6 @@ const RoomDialog = ({
const copyRoomLink = async () => { const copyRoomLink = async () => {
try { try {
await copyTextToSystemClipboard(activeRoomLink); await copyTextToSystemClipboard(activeRoomLink);
trackEvent(EVENT_SHARE, "copy link");
} catch (error) { } catch (error) {
setErrorMessage(error.message); setErrorMessage(error.message);
} }
@ -95,7 +92,6 @@ const RoomDialog = ({
value={username || ""} value={username || ""}
className="RoomDialog-username TextInput" className="RoomDialog-username TextInput"
onChange={(event) => onUsernameChange(event.target.value)} onChange={(event) => onUsernameChange(event.target.value)}
onBlur={() => trackEvent(EVENT_SHARE, "name")}
onKeyPress={(event) => event.key === "Enter" && handleClose()} onKeyPress={(event) => event.key === "Enter" && handleClose()}
/> />
</div> </div>

View File

@ -1,10 +1,9 @@
import { t } from "../../i18n";
import { ExcalidrawElement } from "../../element/types";
import { AppState } from "../../types";
import { ImportedDataState } from "../../data/types";
import { restore } from "../../data/restore";
import { EVENT_ACTION, EVENT_IO, trackEvent } from "../../analytics";
import { serializeAsJSON } from "../../data/json"; import { serializeAsJSON } from "../../data/json";
import { restore } from "../../data/restore";
import { ImportedDataState } from "../../data/types";
import { ExcalidrawElement } from "../../element/types";
import { t } from "../../i18n";
import { AppState } from "../../types";
const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2); const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);
@ -192,7 +191,6 @@ const importFromBackend = async (
data = await response.json(); data = await response.json();
} }
trackEvent(EVENT_ACTION, "import");
return { return {
elements: data.elements || null, elements: data.elements || null,
appState: data.appState || null, appState: data.appState || null,
@ -276,7 +274,6 @@ export const exportToBackend = async (
url.hash = `json=${json.id},${exportedKey.k!}`; url.hash = `json=${json.id},${exportedKey.k!}`;
const urlString = url.toString(); const urlString = url.toString();
window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString); window.prompt(`🔒${t("alerts.uploadedSecurly")}`, urlString);
trackEvent(EVENT_IO, "export", "backend");
} else if (json.error_class === "RequestTooLargeError") { } else if (json.error_class === "RequestTooLargeError") {
window.alert(t("alerts.couldNotCreateShareableLinkTooBig")); window.alert(t("alerts.couldNotCreateShareableLinkTooBig"));
} else { } else {

View File

@ -1,45 +1,38 @@
import React, {
useState,
useLayoutEffect,
useEffect,
useRef,
useCallback,
} from "react";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import React, {
import Excalidraw, { useCallback,
languages, useEffect,
defaultLang, useLayoutEffect,
} from "../packages/excalidraw/index"; useRef,
useState,
import { } from "react";
getTotalStorageSize, import { getDefaultAppState } from "../appState";
importFromLocalStorage,
saveToLocalStorage,
STORAGE_KEYS,
} from "./data/localStorage";
import { ImportedDataState } from "../data/types";
import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
import { TopErrorBoundary } from "../components/TopErrorBoundary";
import { Language, t } from "../i18n";
import { exportToBackend, loadScene } from "./data";
import { getCollaborationLinkData } from "./data";
import { EVENT } from "../constants";
import { loadFromFirebase } from "./data/firebase";
import { ExcalidrawImperativeAPI } from "../components/App"; import { ExcalidrawImperativeAPI } from "../components/App";
import { debounce, ResolvablePromise, resolvablePromise } from "../utils"; import { ErrorDialog } from "../components/ErrorDialog";
import { AppState, ExcalidrawAPIRefValue } from "../types"; import { TopErrorBoundary } from "../components/TopErrorBoundary";
import { APP_NAME, EVENT, TITLE_TIMEOUT } from "../constants";
import { ImportedDataState } from "../data/types";
import { import {
ExcalidrawElement, ExcalidrawElement,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
} from "../element/types"; } from "../element/types";
import { Language, t } from "../i18n";
import Excalidraw, {
defaultLang,
languages,
} from "../packages/excalidraw/index";
import { AppState, ExcalidrawAPIRefValue } from "../types";
import { debounce, ResolvablePromise, resolvablePromise } from "../utils";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants"; import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants";
import { EVENT_LOAD, EVENT_SHARE, trackEvent } from "../analytics"; import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
import { ErrorDialog } from "../components/ErrorDialog";
import { getDefaultAppState } from "../appState";
import { APP_NAME, TITLE_TIMEOUT } from "../constants";
import { LanguageList } from "./components/LanguageList"; import { LanguageList } from "./components/LanguageList";
import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
import { loadFromFirebase } from "./data/firebase";
import {
importFromLocalStorage,
saveToLocalStorage,
STORAGE_KEYS,
} from "./data/localStorage";
const languageDetector = new LanguageDetector(); const languageDetector = new LanguageDetector();
languageDetector.init({ languageDetector.init({
@ -163,7 +156,6 @@ const initializeScene = async (opts: {
// into the remote scene // into the remote scene
opts.resetScene(); opts.resetScene();
const scenePromise = opts.initializeSocketClient(); const scenePromise = opts.initializeSocketClient();
trackEvent(EVENT_SHARE, "session join");
try { try {
const [, roomId, roomKey] = getCollaborationLinkData( const [, roomId, roomKey] = getCollaborationLinkData(
@ -231,12 +223,6 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
const { collab } = props; const { collab } = props;
useEffect(() => { useEffect(() => {
const storageSize = getTotalStorageSize();
if (storageSize) {
trackEvent(EVENT_LOAD, "storage", "size", storageSize);
} else {
trackEvent(EVENT_LOAD, "first time");
}
excalidrawRef.current!.readyPromise.then((excalidrawApi) => { excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
initializeScene({ initializeScene({
resetScene: excalidrawApi.resetScene, resetScene: excalidrawApi.resetScene,

View File

@ -1,5 +1,3 @@
import { EVENT_CHANGE, trackEvent } from "./analytics";
import fallbackLangData from "./locales/en.json"; import fallbackLangData from "./locales/en.json";
import percentages from "./locales/percentages.json"; import percentages from "./locales/percentages.json";
@ -67,7 +65,6 @@ export const setLanguage = async (lang: Language) => {
currentLangData = await import( currentLangData = await import(
/* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json` /* webpackChunkName: "i18n-[request]" */ `./locales/${currentLang.code}.json`
); );
trackEvent(EVENT_CHANGE, "language", currentLang.code);
}; };
export const setLanguageFirstTime = async (lang: Language) => { export const setLanguageFirstTime = async (lang: Language) => {

View File

@ -1,8 +1,7 @@
import { AppState } from "./types";
import { ExcalidrawElement } from "./element/types"; import { ExcalidrawElement } from "./element/types";
import { getElementsInGroup } from "./groups"; import { getElementsInGroup } from "./groups";
import { findLastIndex, findIndex } from "./utils"; import { AppState } from "./types";
import { trackEvent, EVENT_LAYER } from "./analytics"; import { findIndex, findLastIndex } from "./utils";
/** /**
* Returns indices of elements to move based on selected elements. * Returns indices of elements to move based on selected elements.
@ -176,7 +175,6 @@ const shiftElements = (
]; ];
}); });
trackEvent(EVENT_LAYER, "move", direction === "left" ? "down" : "up");
return elements; return elements;
}; };
@ -234,7 +232,6 @@ const shiftElementsToEnd = (
const leadingElements = elements.slice(0, leadingIndex); const leadingElements = elements.slice(0, leadingIndex);
const trailingElements = elements.slice(trailingIndex + 1); const trailingElements = elements.slice(trailingIndex + 1);
trackEvent(EVENT_LAYER, "move", direction === "left" ? "back" : "front");
return direction === "left" return direction === "left"
? [ ? [
...leadingElements, ...leadingElements,