diff --git a/.clasp.json b/.clasp.json
index 93e6d50..304a7d0 100644
--- a/.clasp.json
+++ b/.clasp.json
@@ -1,7 +1,7 @@
{
+ "scriptId": "1IzIEW1uGP4WWdowrlYyNEwhasdXObby_Lik-xwRXn3uWFUitm19SHAhU",
"rootDir": "dist",
- "scriptId": "1EBKJNiPs_XxntcBHxMKJgfBr7089AHhT3boz7_Lkc6AD0hjsQavkIjxz",
"parentId": [
- "1UAVn7sP1pTutyOM2i95jxQz6sjVr2ZrxhG82uLIdd70"
+ "1NVNWs4Wdj2ib6hhPgq1kzA-BMtydvk_SnxoK1K2yX5o"
]
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 37b4a80..60c26ac 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,14 @@
{
"name": "google-plugin",
- "version": "1.0.0",
+ "version": "1.2.0",
"type": "module",
"scripts": {
"dev": "vite",
"lint": "eslint .",
- "login": "clasp login",
- "setup": "rimraf .clasp.json && mkdirp dist && clasp create --type sheets --title \"My React Project\" --rootDir ./dist && mv ./dist/.clasp.json ./.clasp.json && rimraf dist",
- "open": "clasp open --addon",
- "push": "clasp push",
+ "login": "npx clasp login",
+ "setup": "rimraf .clasp.json && mkdirp dist && npx clasp create --type sheets --title \"My React Project\" --rootDir ./dist && mv ./dist/.clasp.json ./.clasp.json && rimraf dist",
+ "open": "npx clasp open --addon",
+ "push": "npx clasp push",
"setup:https": "mkdirp certs && mkcert -key-file ./certs/key.pem -cert-file ./certs/cert.pem localhost 127.0.0.1",
"build:dev": "tsc && vite build --mode development",
"build:test": "tsc && NODE_ENV=test vite build --mode test",
diff --git a/src/analytics/analytics.ts b/src/analytics/analytics.ts
new file mode 100644
index 0000000..d7d0515
--- /dev/null
+++ b/src/analytics/analytics.ts
@@ -0,0 +1,79 @@
+class Analytics {
+
+ private async getServerFunctions() {
+ try {
+ // Dynamic import to avoid circular dependency issues
+ const { serverFunctions } = await import('../client/utils/serverFunctions');
+ return serverFunctions;
+ } catch (error) {
+ console.warn('Failed to import serverFunctions:', error);
+ return null;
+ }
+ }
+
+ public sendEvent(eventName: string, eventID:string, errorMessage?: string, diagramType?:string, userLoginState: boolean = true) {
+ const analyticsID = getAnalyticsID();
+ const pluginID= "google-docs-plugin";
+ const pluginSource = 'googledocs';
+ const payload = {
+ analyticsID,
+ pluginID,
+ eventName,
+ eventID,
+ userLoginState,
+ pluginSource,
+ errorMessage,
+ diagramType
+ };
+
+ this.getServerFunctions().then(serverFunctions => {
+ if (serverFunctions && serverFunctions.sendAnalyticsEvent) {
+ serverFunctions.sendAnalyticsEvent(payload).catch(error => {
+ console.error('Failed to send analytics event:', error);
+ });
+ } else {
+ console.warn('Analytics service unavailable - serverFunctions not available');
+ }
+ }).catch(error => {
+ console.error('Failed to send analytics event:', error);
+ });
+ }
+
+
+ public trackLogin() {
+ this.sendEvent('Google Docs Plugin Logged In','GOOGLE_DOCS_PLUGIN_LOGIN');
+ }
+
+ public trackLogout() {
+ this.sendEvent('Google Docs Logged Out','GOOGLE_DOCS_PLUGIN_LOGOUT', undefined, undefined, false);
+ }
+
+ public trackBrowseDiagram() {
+ this.sendEvent('Google Docs Browse Diagram','GOOGLE_DOCS_PLUGIN_BROWSE_DIAGRAM');
+ }
+
+ public trackNewDiagram() {
+ this.sendEvent('Google Docs New Diagram', 'GOOGLE_DOCS_PLUGIN_NEW_DIAGRAM');
+ }
+
+ public trackEditDiagram() {
+ this.sendEvent('Google Docs Edit Diagram', 'GOOGLE_DOCS_PLUGIN_EDIT_DIAGRAM');
+ }
+
+ public trackUpdateAllDiagrams() {
+ this.sendEvent('Google Docs Update All Diagrams', 'GOOGLE_DOCS_PLUGIN_UPDATE_ALL_DIAGRAMS');
+ }
+}
+
+function getAnalyticsID() {
+ const STORAGE_KEY = 'MERMAIDCHART_ANALYTICS_ID';
+
+ let id = localStorage.getItem(STORAGE_KEY);
+ if (!id) {
+ id = crypto.randomUUID();
+ localStorage.setItem(STORAGE_KEY, id);
+ }
+ return id;
+}
+
+export default new Analytics();
\ No newline at end of file
diff --git a/src/analytics/httpClient.ts b/src/analytics/httpClient.ts
new file mode 100644
index 0000000..82847d1
--- /dev/null
+++ b/src/analytics/httpClient.ts
@@ -0,0 +1,26 @@
+import axios, { AxiosInstance, AxiosResponse, AxiosError } from 'axios';
+import { prodUrl } from '../config/urls';
+
+const ANALYTICS_BASE_URL = prodUrl;
+
+const httpClient: AxiosInstance = axios.create({
+ baseURL: ANALYTICS_BASE_URL,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ timeout: 10000, // 10 second timeout
+});
+
+httpClient.interceptors.response.use(
+ (response: AxiosResponse) => response,
+ (error: AxiosError) => {
+ if (error.code === 'ERR_NETWORK') {
+ console.warn('Analytics endpoint unreachable - continuing without tracking');
+ return Promise.resolve({ data: null, status: 200, statusText: 'OK', headers: {}, config: error.config });
+ }
+ console.error('HTTP Client error:', error);
+ return Promise.reject(error);
+ }
+);
+
+export default httpClient;
\ No newline at end of file
diff --git a/src/client/components/button.module.css b/src/client/components/button.module.css
index 3557db1..4143a3b 100644
--- a/src/client/components/button.module.css
+++ b/src/client/components/button.module.css
@@ -5,13 +5,14 @@
padding: 0.5rem;
outline: none;
border-radius: 0.375rem;
- border: 1px solid rgb(207, 207, 211);
- background-color: white;
+ border: none;
+ background-color: #E80962;
cursor: pointer;
+ color: white;
}
.button:hover {
- background-color: rgb(0, 66, 235);
+ background-color: #B20E45;
color: white;
transition: all 0.2s;
}
diff --git a/src/client/components/toast.tsx b/src/client/components/toast.tsx
new file mode 100644
index 0000000..f062cb4
--- /dev/null
+++ b/src/client/components/toast.tsx
@@ -0,0 +1,30 @@
+import { Snackbar, Alert, AlertColor } from '@mui/material';
+
+interface ToastProps {
+ open: boolean;
+ message: string;
+ severity: AlertColor;
+ onClose: () => void;
+ autoHideDuration?: number;
+}
+
+const Toast = ({
+ open,
+ message,
+ severity,
+ onClose,
+ autoHideDuration = 4000,
+}: ToastProps) => (
+
+
+ {message}
+
+
+);
+
+export default Toast;
diff --git a/src/client/create-diagram-dialog/components/create-diagram-dialog.tsx b/src/client/create-diagram-dialog/components/create-diagram-dialog.tsx
index 9e42d87..e8dee98 100644
--- a/src/client/create-diagram-dialog/components/create-diagram-dialog.tsx
+++ b/src/client/create-diagram-dialog/components/create-diagram-dialog.tsx
@@ -1,13 +1,18 @@
import { useEffect, useState } from 'react';
-import { buildUrl, handleDialogClose } from '../../utils/helpers';
-import { serverFunctions } from '../../utils/serverFunctions';
+import {
+ buildUrl,
+ handleDialogClose,
+ compressBase64Image,
+} from '../../utils/helpers';
+
import useAuth from '../../hooks/useAuth';
-import { CircularProgress, Container, Typography } from '@mui/material';
+import { CircularProgress, Container, Typography, Box } from '@mui/material';
import { showAlertDialog } from '../../utils/alert';
const CreateDiagramDialog = () => {
const { authState, authStatus } = useAuth();
const [diagramsUrl, setDiagramsUrl] = useState('');
+ const [iframeLoading, setIframeLoading] = useState(true);
useEffect(() => {
if (!authState?.authorized) return;
@@ -31,14 +36,23 @@ const CreateDiagramDialog = () => {
});
try {
- await serverFunctions.insertBase64ImageWithMetadata(
- data.diagramImage,
- metadata.toString()
- );
+ const compressedImage = await compressBase64Image(data.diagramImage);
+
+ // Pass data to sidebar via BroadcastChannel and close immediately
+ const channel = new BroadcastChannel('diagram_channel');
+ channel.postMessage({
+ type: 'pendingInsertion',
+ payload: {
+ image: compressedImage,
+ metadata: metadata.toString(),
+ operation: 'insert',
+ },
+ });
+ channel.close();
handleDialogClose();
} catch (error) {
- console.error('Error inserting image with metadata', error);
- showAlertDialog('Error inserting image, please try again');
+ console.error('Error preparing diagram insertion', error);
+ showAlertDialog('Error preparing diagram, please try again');
}
}
};
@@ -50,18 +64,21 @@ const CreateDiagramDialog = () => {
};
}, []);
+ const handleIframeLoad = () => {
+ setIframeLoading(false);
+ };
+
if (authStatus === 'idle' || authStatus === 'loading') {
return (
-
+
);
}
@@ -77,28 +94,65 @@ const CreateDiagramDialog = () => {
height: '96.5vh',
}}
>
-
+
Error
-
+
Something went wrong. Please try again later.
);
}
- return (
-
-
-
+ >
+
+
+ );
+ }
+
+ return (
+ <>
+ {iframeLoading && (
+
+
+
+ )}
+
+
+
+ >
);
};
diff --git a/src/client/create-diagram-dialog/index.html b/src/client/create-diagram-dialog/index.html
index 20bd2dd..bc2245b 100644
--- a/src/client/create-diagram-dialog/index.html
+++ b/src/client/create-diagram-dialog/index.html
@@ -26,6 +26,39 @@
diff --git a/src/client/edit-diagram-dialog/components/edit-diagram-dialog.tsx b/src/client/edit-diagram-dialog/components/edit-diagram-dialog.tsx
index 0dec079..511772a 100644
--- a/src/client/edit-diagram-dialog/components/edit-diagram-dialog.tsx
+++ b/src/client/edit-diagram-dialog/components/edit-diagram-dialog.tsx
@@ -1,12 +1,18 @@
import { useEffect, useState } from 'react';
import { serverFunctions } from '../../utils/serverFunctions';
-import { buildUrl, handleDialogClose } from '../../utils/helpers';
+import {
+ buildUrl,
+ handleDialogClose,
+ compressBase64Image,
+} from '../../utils/helpers';
import useAuth from '../../hooks/useAuth';
import { showAlertDialog } from '../../utils/alert';
+import { CircularProgress, Container, Typography, Box } from '@mui/material';
const EditDiagramDialog = () => {
const { authState, authStatus } = useAuth();
const [diagramsUrl, setDiagramsUrl] = useState('');
+ const [iframeLoading, setIframeLoading] = useState(true);
useEffect(() => {
if (!authState?.authorized) return;
@@ -23,7 +29,7 @@ const EditDiagramDialog = () => {
const minor = params.get('minor');
if (projectID && documentID && major && minor) {
const iframeUrl = buildUrl(
- `/app/projects/${projectID}/diagrams/${documentID}/version/v.${major}.${minor}/edit`,
+ `/app/projects/${projectID}/diagrams/${documentID}/version/v${major}.${minor}/edit?pluginSource=googledocs`,
authState.token
);
setDiagramsUrl(iframeUrl);
@@ -49,14 +55,23 @@ const EditDiagramDialog = () => {
minor: data.minor,
});
try {
- await serverFunctions.replaceSelectedImageWithBase64AndSize(
- data.diagramImage,
- metadata.toString()
- );
+ const compressedImage = await compressBase64Image(data.diagramImage);
+
+ // Pass data to sidebar via BroadcastChannel and close immediately
+ const channel = new BroadcastChannel('diagram_channel');
+ channel.postMessage({
+ type: 'pendingInsertion',
+ payload: {
+ image: compressedImage,
+ metadata: metadata.toString(),
+ operation: 'replace',
+ },
+ });
+ channel.close();
handleDialogClose();
} catch (error) {
- console.error('Error updating image with metadata', error);
- showAlertDialog('Error updating image, please try again');
+ console.error('Error preparing diagram update', error);
+ showAlertDialog('Error preparing diagram update, please try again');
}
}
};
@@ -68,22 +83,95 @@ const EditDiagramDialog = () => {
};
}, []);
- if (authStatus !== 'success' || !diagramsUrl) {
- return null;
+ const handleIframeLoad = () => {
+ setIframeLoading(false);
+ };
+
+ if (authStatus === 'idle' || authStatus === 'loading') {
+ return (
+
+
+
+ );
}
- return (
-
-
-
+ >
+
+ Error
+
+
+ Something went wrong. Please try again later.
+
+
+ );
+ }
+
+ if (!diagramsUrl) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+ {iframeLoading && (
+
+
+
+ )}
+
+
+
+ >
);
};
diff --git a/src/client/edit-diagram-dialog/index.html b/src/client/edit-diagram-dialog/index.html
index 20bd2dd..95af56e 100644
--- a/src/client/edit-diagram-dialog/index.html
+++ b/src/client/edit-diagram-dialog/index.html
@@ -24,10 +24,50 @@
src="https://unpkg.com/@types/react@18.2.66/index.d.ts"
>
-
-
-
+
+
+