Skip to content
Draft
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.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",
Expand Down
79 changes: 79 additions & 0 deletions src/analytics/analytics.ts
Original file line number Diff line number Diff line change
@@ -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();
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
30 changes: 30 additions & 0 deletions src/client/components/toast.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={onClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
);

export default Toast;
100 changes: 77 additions & 23 deletions src/client/create-diagram-dialog/components/create-diagram-dialog.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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');
}
}
};
Expand All @@ -50,18 +64,21 @@ const CreateDiagramDialog = () => {
};
}, []);

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 +94,65 @@ 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 (
<>
{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: 1,
}}
onLoad={handleIframeLoad}
/>
</div>
</>
);
};

Expand Down
33 changes: 33 additions & 0 deletions src/client/create-diagram-dialog/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,39 @@
</head>
<body>
<section id="index">
<style>
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
font-family: sans-serif;
position: fixed;
top: 0;
left: 0;
width: 100%;
background: white;
z-index: 9999;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<div class="loading-container">
<div class="spinner"></div>
<div>Loading...</div>
</div>
<script type="module" src="./index.jsx"></script>
<!-- bundled js and css will get inlined here during build-->
</section>
Expand Down
Loading
Loading