Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .clasp.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"scriptId": "1IzIEW1uGP4WWdowrlYyNEwhasdXObby_Lik-xwRXn3uWFUitm19SHAhU",
"rootDir": "dist",
"scriptId": "1EBKJNiPs_XxntcBHxMKJgfBr7089AHhT3boz7_Lkc6AD0hjsQavkIjxz",
"parentId": [
"1UAVn7sP1pTutyOM2i95jxQz6sjVr2ZrxhG82uLIdd70"
"1NVNWs4Wdj2ib6hhPgq1kzA-BMtydvk_SnxoK1K2yX5o"
]
}
}
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "google-plugin",
"version": "1.0.0",
"version": "1.1.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",
Expand Down
63 changes: 63 additions & 0 deletions src/analytics/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import httpClient from './httpClient';
class Analytics {

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
};

httpClient.post('/rest-api/plugins/pulse', payload).catch(error => {
if (error.code !== 'ERR_NETWORK') {
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();
26 changes: 26 additions & 0 deletions src/analytics/httpClient.ts
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 4 additions & 3 deletions src/client/components/button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { useEffect, useState } from 'react';
import { buildUrl, handleDialogClose } from '../../utils/helpers';
import {
buildUrl,
handleDialogClose,
compressBase64Image,
} from '../../utils/helpers';
import { serverFunctions } from '../../utils/serverFunctions';
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';
import LoadingOverlay from '../../components/loading-overlay';

const CreateDiagramDialog = () => {
const { authState, authStatus } = useAuth();
const [diagramsUrl, setDiagramsUrl] = useState('');
const [isInserting, setIsInserting] = useState(false);
const [iframeLoading, setIframeLoading] = useState(true);

useEffect(() => {
if (!authState?.authorized) return;
Expand All @@ -22,6 +29,13 @@ const CreateDiagramDialog = () => {
const handleMessage = async (e: MessageEvent) => {
const action = e.data.action;
if (action === 'save') {
if (isInserting) {
console.log('Already inserting diagram, ignoring duplicate click');
return;
}

setIsInserting(true);

const data = e.data.data;
const metadata = new URLSearchParams({
projectID: data.projectID,
Expand All @@ -31,14 +45,17 @@ const CreateDiagramDialog = () => {
});

try {
const compressedImage = await compressBase64Image(data.diagramImage);

await serverFunctions.insertBase64ImageWithMetadata(
data.diagramImage,
compressedImage,
metadata.toString()
);
handleDialogClose();
} catch (error) {
console.error('Error inserting image with metadata', error);
showAlertDialog('Error inserting image, please try again');
setIsInserting(false);
}
}
};
Expand All @@ -48,20 +65,23 @@ const CreateDiagramDialog = () => {
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);
}, [isInserting]);

const handleIframeLoad = () => {
setIframeLoading(false);
};

if (authStatus === 'idle' || authStatus === 'loading') {
return (
<Container
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '96.5vh',
}}
>
<CircularProgress />
<CircularProgress size={40} />
</Container>
);
}
Expand All @@ -77,28 +97,67 @@ const CreateDiagramDialog = () => {
height: '96.5vh',
}}
>
<Typography variant="h5" gutterBottom my={2} textAlign="center">
<Typography variant="h6" gutterBottom textAlign="center">
Error
</Typography>
<Typography paragraph textAlign="center">
<Typography variant="body2" textAlign="center">
Something went wrong. Please try again later.
</Typography>
</Container>
);
}

return (
<div style={{ padding: '3px', overflowX: 'hidden', height: '100%' }}>
<iframe
src={diagramsUrl}
title="diagrams"
style={{
border: 'none',
width: '100%',
if (!diagramsUrl) {
return (
<Container
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '96.5vh',
}}
/>
</div>
>
<CircularProgress size={40} />
</Container>
);
}

return (
<>
{isInserting && <LoadingOverlay />}
{iframeLoading && (
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
zIndex: 1000,
}}
>
<CircularProgress size={40} />
</Box>
)}
<div style={{ padding: '3px', overflowX: 'hidden', height: '100%' }}>
<iframe
src={diagramsUrl}
title="diagrams"
style={{
border: 'none',
width: '100%',
height: '96.5vh',
opacity: isInserting ? 0.5 : 1,
pointerEvents: isInserting ? 'none' : 'auto',
}}
onLoad={handleIframeLoad}
/>
</div>
</>
);
};

Expand Down
Loading
Loading