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:
parent
4acdc47ef0
commit
3aa01ad272
@ -1 +1 @@
|
|||||||
REACT_APP_INCLUDE_GTAG=true
|
REACT_APP_GOOGLE_ANALYTICS_ID=UA-387204-13
|
||||||
|
@ -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 . .
|
||||||
|
64
analytics.md
64
analytics.md
@ -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` |
|
|
@ -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",
|
||||||
|
@ -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>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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, {
|
||||||
|
@ -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,
|
||||||
|
@ -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, {
|
||||||
|
@ -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") {
|
||||||
|
@ -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,
|
||||||
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
@ -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") {
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -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,
|
||||||
|
@ -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 });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -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")}
|
||||||
|
@ -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}
|
||||||
|
@ -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"
|
||||||
|
@ -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),
|
||||||
});
|
});
|
||||||
|
@ -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),
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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 = () => {
|
||||||
|
@ -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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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) => {
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user