Features implemented: - Visual node/edge editing with property panels - Drag and drop node positioning - Color customization for nodes and edges - Text editing with real-time updates - Bidirectional Mermaid code synchronization - Subgraph/grouping support for layout control - Export functionality to JSON with position data - Layout lock system (experimental) - Edge label editing and styling - Comprehensive Mermaid syntax parser 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
257 lines
8.2 KiB
PHP
257 lines
8.2 KiB
PHP
<?php
|
|
require_once 'config.php';
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?php echo SITE_TITLE; ?></title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
font-family: 'Montserrat', sans-serif;
|
|
line-height: 1.6;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
}
|
|
header {
|
|
background-color: #333;
|
|
color: #fff;
|
|
text-align: center;
|
|
padding: 1rem;
|
|
}
|
|
main {
|
|
flex-grow: 1;
|
|
padding: 2rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.container {
|
|
display: flex;
|
|
gap: 2rem;
|
|
flex-grow: 1;
|
|
}
|
|
.input-area, .output-area {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
textarea {
|
|
width: 100%;
|
|
height: 300px;
|
|
margin-bottom: 1rem;
|
|
font-family: 'Montserrat', sans-serif;
|
|
}
|
|
#mermaidOutput {
|
|
border: 1px solid #ccc;
|
|
padding: 1rem;
|
|
flex-grow: 1;
|
|
overflow: auto;
|
|
}
|
|
.button-container {
|
|
margin-top: 1rem;
|
|
}
|
|
button {
|
|
padding: 0.5rem 1rem;
|
|
background-color: #333;
|
|
color: #fff;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-family: 'Montserrat', sans-serif;
|
|
}
|
|
button:hover {
|
|
background-color: #555;
|
|
}
|
|
footer {
|
|
background-color: #333;
|
|
color: #fff;
|
|
text-align: center;
|
|
padding: 1rem;
|
|
}
|
|
</style>
|
|
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
|
|
|
<!-- auth 1 of 4 -->
|
|
<script src="https://alcdn.msauth.net/browser/2.15.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
|
|
<style>
|
|
#protected-content {
|
|
display: block;
|
|
}
|
|
</style>
|
|
<!-- end auth block -->
|
|
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1><?php echo SITE_TITLE; ?></h1>
|
|
</header>
|
|
<main>
|
|
|
|
<!-- auth 2 of 4 -->
|
|
<div style="text-align: left;">
|
|
<button id="logout-button" onclick="signOut()" style="display:none;">Log Out</button>
|
|
<button id="login-button" onclick="signIn()" style="display:none;">Log In</button>
|
|
</div>
|
|
<!-- end auth block -->
|
|
|
|
<div class="container">
|
|
<div class="input-area">
|
|
<h2>Input Mermaid Diagram Code</h2>
|
|
<textarea id="mermaidInput">graph TD
|
|
A[Christmas] -->|Get money| B(Go shopping)
|
|
B --> C{Let me think}
|
|
C -->|One| D[Android]
|
|
C -->|Two| E[Nuka Cola]
|
|
C -->|Three| F[Cold fusion generator]</textarea>
|
|
<div class="button-container">
|
|
<button id="renderBtn">Render Diagram</button>
|
|
</div>
|
|
</div>
|
|
<div class="output-area" id="protected-content">
|
|
<h2>Rendered Diagram</h2>
|
|
<div id="mermaidOutput"></div>
|
|
<div class="button-container">
|
|
<button id="exportBtn">Download as PNG</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
<footer>
|
|
<p>© <?php echo date('Y'); ?> <?php echo SITE_TITLE; ?></p>
|
|
</footer>
|
|
<script>
|
|
mermaid.initialize({
|
|
startOnLoad: false,
|
|
theme: 'base',
|
|
themeVariables: {
|
|
primaryColor: '#ffc406',
|
|
primaryTextColor: '#000000',
|
|
primaryBorderColor: '#000000',
|
|
lineColor: '#000000',
|
|
secondaryColor: '#B8B9B9',
|
|
tertiaryColor: '#B8B9B9'
|
|
},
|
|
flowchart: {
|
|
useMaxWidth: false,
|
|
htmlLabels: true,
|
|
curve: 'basis'
|
|
},
|
|
|
|
securityLevel: 'loose',
|
|
fontFamily: 'Montserrat, sans-serif',
|
|
fontSize: 14
|
|
});
|
|
|
|
function renderMermaidDiagram() {
|
|
const input = document.getElementById('mermaidInput').value;
|
|
const output = document.getElementById('mermaidOutput');
|
|
|
|
output.innerHTML = ''; // Clear previous content
|
|
|
|
mermaid.render('mermaid-svg', input).then(result => {
|
|
output.innerHTML = result.svg;
|
|
}).catch(error => {
|
|
output.innerHTML = `<p style="color: red;">Error rendering diagram: ${error.message}</p>`;
|
|
});
|
|
}
|
|
|
|
// Initial render
|
|
document.addEventListener('DOMContentLoaded', renderMermaidDiagram);
|
|
|
|
document.getElementById('renderBtn').addEventListener('click', renderMermaidDiagram);
|
|
|
|
document.getElementById('exportBtn').addEventListener('click', function() {
|
|
const svg = document.querySelector('#mermaidOutput svg');
|
|
if (!svg) {
|
|
alert('No diagram to export. Please render a diagram first.');
|
|
return;
|
|
}
|
|
const svgData = new XMLSerializer().serializeToString(svg);
|
|
|
|
// Create a high-resolution canvas
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const scale = 4; // Increase this for higher resolution
|
|
canvas.width = svg.viewBox.baseVal.width * scale;
|
|
canvas.height = svg.viewBox.baseVal.height * scale;
|
|
|
|
const img = new Image();
|
|
img.onload = function() {
|
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
const pngFile = canvas.toDataURL('image/png');
|
|
const downloadLink = document.createElement('a');
|
|
downloadLink.download = 'mermaid_diagram_high_res.png';
|
|
downloadLink.href = pngFile;
|
|
downloadLink.click();
|
|
};
|
|
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
|
|
});
|
|
</script>
|
|
|
|
<!-- auth 4 of 4 NOTE: ensure values for clientID, authority (URL with tenant ID) and redirectUri are correct below -->
|
|
<script>
|
|
const msalConfig = {
|
|
auth: {
|
|
clientId: "9079054c-9620-4757-a256-23413042f1ef",
|
|
authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
|
|
redirectUri: "https://ai-sandbox.oliver.solutions/mermaid"
|
|
},
|
|
cache: {
|
|
cacheLocation: "sessionStorage",
|
|
storeAuthStateInCookie: true,
|
|
}
|
|
};
|
|
|
|
const loginRequest = {
|
|
scopes: ["user.read"]
|
|
};
|
|
|
|
const myMSALObj = new msal.PublicClientApplication(msalConfig);
|
|
|
|
signIn();
|
|
|
|
function signIn() {
|
|
myMSALObj.loginPopup(loginRequest)
|
|
.then(loginResponse => {
|
|
console.log("User logged in:", loginResponse.account.username);
|
|
thisUser = loginResponse.account.username;
|
|
sessionStorage.setItem('accessToken', loginResponse.accessToken);
|
|
showProtectedContent(); // Show protected content after successful login
|
|
//onAuthenticated(); // Special for this app
|
|
}).catch(error => {
|
|
console.error("Error during login:", error);
|
|
});
|
|
}
|
|
|
|
function signOut() {
|
|
// Clear the session storage and (does not) sign out from Microsoft Identity
|
|
sessionStorage.removeItem('accessToken');
|
|
//myMSALObj.logoutPopup();
|
|
console.log("User logged out.");
|
|
document.getElementById('protected-content').style.display = 'none'; // Hide protected content
|
|
document.getElementById('logout-button').style.display = 'none'; // Hide logout button
|
|
document.getElementById('login-button').style.display = 'flex'; // Show login button
|
|
}
|
|
|
|
|
|
function showProtectedContent() {
|
|
// Verify that the access token exists before showing protected content
|
|
const accessToken = sessionStorage.getItem('accessToken');
|
|
if (accessToken) {
|
|
document.getElementById('protected-content').style.display = 'block';
|
|
document.getElementById('logout-button').style.display = 'block'; // Show logout button
|
|
document.getElementById('login-button').style.display = 'none'; // Hide login button
|
|
}
|
|
}
|
|
|
|
// Check if the user is already logged in when the page loads
|
|
window.addEventListener('load', showProtectedContent);
|
|
</script>
|
|
<!-- end auth block -->
|
|
|
|
</body>
|
|
</html>
|