Skip to content

Instantly share code, notes, and snippets.

@bdulac
Last active July 19, 2025 16:15
Show Gist options
  • Save bdulac/b3d6c5b0bc2ef25b7f89f3d8df47b21f to your computer and use it in GitHub Desktop.
Save bdulac/b3d6c5b0bc2ef25b7f89f3d8df47b21f to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Polymeria UML Modeler - Modellnaia Generator</title>
<!-- Import des web components Polymer -->
<script src="https://unpkg.com/@polymer/[email protected]/polymer-legacy.js"></script>
<script src="https://unpkg.com/@webcomponents/[email protected]/webcomponents-loader.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 2em;
}
.content {
display: grid;
grid-template-columns: 200px 1fr 300px;
gap: 20px;
padding: 20px;
min-height: 600px;
}
.toolbox {
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
}
.toolbox h3 {
margin: 0 0 15px 0;
color: #495057;
font-size: 16px;
}
.tool-category {
margin-bottom: 20px;
}
.tool-category h4 {
margin: 0 0 10px 0;
color: #6c757d;
font-size: 14px;
text-transform: uppercase;
font-weight: 600;
}
.tool-btn {
display: block;
width: 100%;
padding: 8px 12px;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
text-align: left;
transition: all 0.2s;
}
.tool-btn:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.tool-btn:active {
background: #667eea;
color: white;
}
.tool-btn.active {
background: #667eea;
color: white;
}
.modeler-area {
border: 1px solid #ddd;
border-radius: 4px;
min-height: 500px;
background: #fafafa;
position: relative;
}
.controls {
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
padding: 20px;
}
.control-group {
margin-bottom: 20px;
}
.control-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #333;
}
.control-group input,
.control-group select,
.control-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.btn {
background: #667eea;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
width: 100%;
margin-bottom: 10px;
}
.btn:hover {
background: #5a6fd8;
}
.btn-secondary {
background: #6c757d;
}
.btn-secondary:hover {
background: #545b62;
}
.status {
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
display: none;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status.info {
background: #cce7ff;
color: #004085;
border: 1px solid #b3d7ff;
}
@media (max-width: 768px) {
.content {
grid-template-columns: 1fr;
}
.toolbox {
order: -1;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎯 UML Modeler & Code Generator</h1>
<p>Créez vos modèles UML avec Polymeria et générez du code Java avec Modellnaia</p>
</div>
<div class="content">
<div class="toolbox">
<h3>🧰 Palette d'outils</h3>
<div class="tool-category">
<h4>Classes</h4>
<button class="tool-btn" onclick="addComponent('class')" title="Ajouter une classe UML">
📋 Classe
</button>
<button class="tool-btn" onclick="addComponent('abstract-class')" title="Ajouter une classe abstraite">
📄 Classe abstraite
</button>
<button class="tool-btn" onclick="addComponent('interface')" title="Ajouter une interface">
🔌 Interface
</button>
<button class="tool-btn" onclick="addComponent('enum')" title="Ajouter une énumération">
📝 Énumération
</button>
</div>
<div class="tool-category">
<h4>Relations</h4>
<button class="tool-btn" onclick="setConnectionMode('association')" title="Créer une association">
↔️ Association
</button>
<button class="tool-btn" onclick="setConnectionMode('inheritance')" title="Héritage entre classes (extends)">
⬆️ Héritage
</button>
<button class="tool-btn" onclick="setConnectionMode('implementation')" title="Implémentation d'interface">
🔌 Implémentation
</button>
<button class="tool-btn" onclick="setConnectionMode('composition')" title="Créer une composition">
◆ Composition
</button>
<button class="tool-btn" onclick="setConnectionMode('aggregation')" title="Créer une agrégation">
◇ Agrégation
</button>
<button class="tool-btn" onclick="setConnectionMode('dependency')" title="Créer une dépendance">
⤴️ Dépendance
</button>
</div>
<div class="tool-category">
<h4>Actions</h4>
<button class="tool-btn" onclick="setSelectionMode()" title="Mode sélection">
👆 Sélectionner
</button>
<button class="tool-btn" onclick="deleteSelected()" title="Supprimer la sélection">
🗑️ Supprimer
</button>
<button class="tool-btn" onclick="alignElements()" title="Aligner les éléments">
📐 Aligner
</button>
</div>
</div>
<div class="modeler-area">
<!-- Zone où seront intégrés les composants Polymeria -->
<div id="uml-modeler" style="width: 100%; height: 100%; position: relative;">
<div id="model-canvas" style="width: 100%; height: 500px; border: 1px solid #e9ecef; background: white; position: relative; overflow: auto;">
<!-- Les composants UML seront ajoutés ici dynamiquement -->
<div id="welcome-message" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #6c757d;">
<h3>🎯 Créez votre modèle UML</h3>
<p>Utilisez la palette d'outils à gauche pour ajouter des éléments</p>
<p style="font-size: 12px; margin-top: 20px;">Cliquez sur un outil puis sur le canvas pour ajouter un élément</p>
</div>
</div>
</div>
</div>
<div class="controls">
<div class="status" id="status-message"></div>
<div class="control-group">
<label for="project-name">Nom du projet :</label>
<input type="text" id="project-name" value="MonProjetUML" placeholder="Nom du projet Java">
</div>
<div class="control-group">
<label for="package-name">Package Java :</label>
<input type="text" id="package-name" value="com.example.model" placeholder="com.example.model">
</div>
<div class="control-group">
<label for="generation-type">Type de génération :</label>
<select id="generation-type">
<option value="entities">Entités JPA</option>
<option value="pojos">POJO simples</option>
<option value="services">Services + Entités</option>
<option value="full">Projet complet</option>
</select>
</div>
<div class="control-group">
<label for="modellnaia-url">URL Modellnaia :</label>
<input type="text" id="modellnaia-url" value="http://localhost:8080" placeholder="http://localhost:8080">
</div>
<button class="btn" onclick="validateModel()">🔍 Valider le modèle</button>
<button class="btn" onclick="generateCode()">⚡ Générer le code Java</button>
<button class="btn btn-secondary" onclick="downloadGenerated()">💾 Télécharger le projet</button>
<div class="control-group">
<label>Actions du modèle :</label>
<button class="btn btn-secondary" onclick="clearModel()">🗑️ Vider</button>
<button class="btn btn-secondary" onclick="exportModel()">📤 Exporter UML</button>
<button class="btn btn-secondary" onclick="importModel()">📥 Importer UML</button>
</div>
</div>
</div>
</div>
<script>
// Configuration globale
const config = {
modellnaiaBaseUrl: 'http://localhost:8080',
polymeriaNs: 'polymeria'
};
// État global de l'application
let currentModel = {
classes: [],
relations: [],
nextId: 1
};
let generatedProjectId = null;
let currentTool = 'select';
let selectedElements = [];
let connectionMode = null;
let tempConnection = null;
let firstSelectedForConnection = null;
let statusTimeout = null;
// Fonction utilitaire pour afficher les messages de statut
function showStatus(message, type = 'info', duration = 5000) {
const statusEl = document.getElementById('status-message');
statusEl.textContent = message;
statusEl.className = `status ${type}`;
statusEl.style.display = 'block';
// Effacer le timeout précédent s'il existe
if (statusTimeout) {
clearTimeout(statusTimeout);
}
// Auto-masquer après la durée spécifiée
statusTimeout = setTimeout(() => {
statusEl.style.display = 'none';
}, duration);
}
// Initialisation des composants Polymeria
function initializePolymeria() {
const canvas = document.getElementById('model-canvas');
// Ajouter les gestionnaires d'événements pour l'interaction
canvas.addEventListener('click', handleCanvasClick);
canvas.addEventListener('contextmenu', handleCanvasRightClick);
// Masquer le message de bienvenue après la première interaction
setTimeout(() => {
const welcome = document.getElementById('welcome-message');
if (welcome && currentModel.classes.length === 0) {
welcome.style.opacity = '0.5';
}
}, 3000);
showStatus('Interface de modélisation prête', 'success');
}
// Ajouter un composant UML au canvas
function addComponent(type) {
currentTool = type;
connectionMode = null; // Réinitialiser le mode connexion
firstSelectedForConnection = null;
document.body.style.cursor = 'crosshair';
showStatus(`Mode ${type} activé - Cliquez sur le canvas pour placer l'élément`, 'info');
// Mettre en évidence le bouton actif
document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
// Gérer les clics sur le canvas
function handleCanvasClick(event) {
// Si on est en mode connexion, gérer la sélection d'éléments
if (connectionMode) {
const clickedElement = event.target.closest('.uml-element');
if (clickedElement) {
const elementId = parseInt(clickedElement.id.replace('element-', ''));
handleConnectionClick(elementId, event);
return;
}
}
if (currentTool === 'select') return;
const rect = event.currentTarget.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
if (currentTool && currentTool !== 'select' && currentTool !== 'connect') {
createUMLElement(currentTool, x, y);
currentTool = 'select';
document.body.style.cursor = 'default';
// Masquer le message de bienvenue
const welcome = document.getElementById('welcome-message');
if (welcome) welcome.style.display = 'none';
}
}
// Créer un élément UML
function createUMLElement(type, x, y) {
const element = {
id: currentModel.nextId++,
type: type,
x: x,
y: y,
width: 150,
height: 100,
name: `${type.charAt(0).toUpperCase() + type.slice(1)}${currentModel.classes.length + 1}`,
attributes: [],
methods: []
};
currentModel.classes.push(element);
renderUMLElement(element);
showStatus(`${type} ajouté au modèle`, 'success');
}
// Rendre un élément UML dans le DOM
function renderUMLElement(element) {
const canvas = document.getElementById('model-canvas');
const div = document.createElement('div');
div.className = 'uml-element';
div.id = `element-${element.id}`;
div.style.cssText = `
position: absolute;
left: ${element.x}px;
top: ${element.y}px;
width: ${element.width}px;
min-height: ${element.height}px;
background: white;
border: 2px solid #495057;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
cursor: move;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
`;
// Contenu de l'élément selon son type
let content = '';
switch(element.type) {
case 'class':
content = `
<div style="padding: 5px; border-bottom: 1px solid #495057; font-weight: bold; text-align: center;">
${element.name}
</div>
<div style="padding: 5px; border-bottom: 1px solid #495057; min-height: 30px;">
<div style="color: #666; font-size: 10px;">Attributs:</div>
${element.attributes.length === 0 ? '<em style="color: #999;">aucun</em>' : element.attributes.join('<br>')}
</div>
<div style="padding: 5px; min-height: 30px;">
<div style="color: #666; font-size: 10px;">Méthodes:</div>
${element.methods.length === 0 ? '<em style="color: #999;">aucune</em>' : element.methods.join('<br>')}
</div>
`;
break;
case 'abstract-class':
content = `
<div style="padding: 5px; border-bottom: 1px solid #495057; font-weight: bold; text-align: center; font-style: italic;">
&lt;&lt;abstract&gt;&gt;<br>${element.name}
</div>
<div style="padding: 5px; border-bottom: 1px solid #495057; min-height: 30px;">
<div style="color: #666; font-size: 10px;">Attributs:</div>
</div>
<div style="padding: 5px; min-height: 30px;">
<div style="color: #666; font-size: 10px;">Méthodes:</div>
</div>
`;
break;
case 'interface':
div.style.borderStyle = 'dashed';
content = `
<div style="padding: 5px; border-bottom: 1px solid #495057; font-weight: bold; text-align: center; font-style: italic;">
&lt;&lt;interface&gt;&gt;<br>${element.name}
</div>
<div style="padding: 5px; min-height: 60px;">
<div style="color: #666; font-size: 10px;">Méthodes:</div>
</div>
`;
break;
case 'enum':
div.style.borderColor = '#28a745';
content = `
<div style="padding: 5px; border-bottom: 1px solid #28a745; font-weight: bold; text-align: center;">
&lt;&lt;enumeration&gt;&gt;<br>${element.name}
</div>
<div style="padding: 5px; min-height: 60px;">
<div style="color: #666; font-size: 10px;">Valeurs:</div>
</div>
`;
break;
}
div.innerHTML = content;
// Ajouter les gestionnaires d'événements
div.addEventListener('click', (e) => handleElementClick(element.id, e));
div.addEventListener('dblclick', (e) => editElement(element.id, e));
makeDraggable(div, element);
canvas.appendChild(div);
}
// Gérer les clics sur les éléments (unifié)
function handleElementClick(elementId, event) {
event.stopPropagation();
if (connectionMode) {
// Mode connexion prioritaire
handleConnectionClick(elementId, event);
} else {
// Mode sélection normale
selectElement(elementId, event);
}
}
// Rendre un élément déplaçable
function makeDraggable(element, data) {
let isDragging = false;
let startX, startY;
element.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX - data.x;
startY = e.clientY - data.y;
element.style.zIndex = '1000';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
data.x = e.clientX - startX;
data.y = e.clientY - startY;
element.style.left = data.x + 'px';
element.style.top = data.y + 'px';
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
element.style.zIndex = 'auto';
}
});
}
// Sélectionner un élément
function selectElement(id, event) {
const element = document.getElementById(`element-${id}`);
if (!element) return;
// Désélectionner tous les autres éléments
document.querySelectorAll('.uml-element').forEach(el => {
el.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
// Sélectionner l'élément cliqué
element.style.boxShadow = '0 0 10px #667eea';
selectedElements = [id];
showStatus(`Élément sélectionné (double-clic pour éditer)`, 'info');
}
// Éditer un élément
function editElement(id, event) {
event.stopPropagation();
const elementData = currentModel.classes.find(c => c.id === id);
if (!elementData) return;
const newName = prompt(`Nom de l'élément:`, elementData.name);
if (newName && newName !== elementData.name) {
elementData.name = newName;
// Re-rendre l'élément
const domElement = document.getElementById(`element-${id}`);
domElement.remove();
renderUMLElement(elementData);
showStatus('Élément modifié', 'success');
}
}
// Gérer le clic droit sur le canvas
function handleCanvasRightClick(event) {
event.preventDefault();
// Ici vous pourriez ajouter un menu contextuel
}
// Mode de connexion pour les relations
function setConnectionMode(type) {
connectionMode = type;
currentTool = 'connect';
firstSelectedForConnection = null;
document.body.style.cursor = 'crosshair';
let message = '';
switch(type) {
case 'inheritance':
message = 'Mode héritage - Cliquez sur la classe enfant puis sur la classe parent';
break;
case 'implementation':
message = 'Mode implémentation - Cliquez sur la classe puis sur l\'interface';
break;
case 'association':
message = 'Mode association / héritage / implémentation - Cliquez sur deux éléments pour les associer';
break;
default:
message = `Mode ${type} - Cliquez sur deux éléments pour les connecter`;
}
showStatus(message, 'info', 10000); // Message plus long pour les relations
// Mettre en évidence le bouton actif
document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
// Gérer les clics pour les connexions
function handleConnectionClick(elementId, event) {
event.stopPropagation();
if (!firstSelectedForConnection) {
// Premier élément sélectionné
firstSelectedForConnection = elementId;
const element = document.getElementById(`element-${elementId}`);
element.style.boxShadow = '0 0 15px #28a745';
let message = '';
switch(connectionMode) {
case 'inheritance':
message = 'Classe enfant sélectionnée - Cliquez maintenant sur la classe parent';
break;
case 'implementation':
message = 'Classe sélectionnée - Cliquez maintenant sur l\'interface à implémenter';
break;
default:
message = 'Premier élément sélectionné - Cliquez sur le second élément';
}
showStatus(message, 'info', 15000); // Message persistant
} else {
// Deuxième élément sélectionné - créer la relation
if (firstSelectedForConnection !== elementId) {
createRelation(firstSelectedForConnection, elementId, connectionMode);
} else {
showStatus('Vous devez sélectionner deux éléments différents', 'error');
}
// Réinitialiser la sélection
document.querySelectorAll('.uml-element').forEach(el => {
el.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
});
firstSelectedForConnection = null;
// Ne pas réinitialiser le mode connexion pour permettre d'ajouter plusieurs relations
// Remettre le message d'origine pour le mode de connexion
let message = '';
switch(connectionMode) {
case 'inheritance':
message = 'Mode héritage - Cliquez sur la classe enfant puis sur la classe parent';
break;
case 'implementation':
message = 'Mode implémentation - Cliquez sur la classe puis sur l\'interface';
break;
case 'association':
message = 'Mode association - Cliquez sur deux éléments pour les associer';
break;
default:
message = `Mode ${connectionMode} - Cliquez sur deux éléments pour les connecter`;
}
showStatus(message, 'info', 10000);
}
}
// Créer une relation entre deux éléments
function createRelation(fromId, toId, type) {
const fromElement = currentModel.classes.find(c => c.id === fromId);
const toElement = currentModel.classes.find(c => c.id === toId);
if (!fromElement || !toElement) {
showStatus('Erreur: éléments non trouvés', 'error');
return;
}
// Vérifier la validité de la relation
if (type === 'implementation' && toElement.type !== 'interface') {
showStatus('Erreur: l\'implémentation nécessite une interface comme cible', 'error');
return;
}
if (type === 'inheritance' && (fromElement.type === 'interface' || toElement.type === 'interface')) {
showStatus('Erreur: l\'héritage ne s\'applique qu\'entre classes', 'error');
return;
}
const relation = {
id: currentModel.nextId++,
type: type,
from: fromId,
to: toId,
fromElement: fromElement,
toElement: toElement
};
currentModel.relations.push(relation);
renderRelation(relation);
let message = '';
switch(type) {
case 'inheritance':
message = `Héritage créé: ${fromElement.name} extends ${toElement.name}`;
break;
case 'implementation':
message = `Implémentation créée: ${fromElement.name} implements ${toElement.name}`;
break;
default:
message = `Relation ${type} créée entre ${fromElement.name} et ${toElement.name}`;
}
showStatus(message, 'success');
}
// Rendre une relation visuellement
function renderRelation(relation) {
const canvas = document.getElementById('model-canvas');
const fromElement = document.getElementById(`element-${relation.from}`);
const toElement = document.getElementById(`element-${relation.to}`);
if (!fromElement || !toElement) return;
// Calculer les positions des centres des éléments
const fromRect = fromElement.getBoundingClientRect();
const toRect = toElement.getBoundingClientRect();
const canvasRect = canvas.getBoundingClientRect();
const fromX = fromElement.offsetLeft + fromElement.offsetWidth / 2;
const fromY = fromElement.offsetTop + fromElement.offsetHeight / 2;
const toX = toElement.offsetLeft + toElement.offsetWidth / 2;
const toY = toElement.offsetTop + toElement.offsetHeight / 2;
// Créer l'élément SVG pour la relation
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.pointerEvents = 'none';
svg.style.zIndex = '1';
svg.id = `relation-${relation.id}`;
// Créer la ligne
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', fromX);
line.setAttribute('y1', fromY);
line.setAttribute('x2', toX);
line.setAttribute('y2', toY);
line.setAttribute('stroke', '#495057');
line.setAttribute('stroke-width', '2');
// Style selon le type de relation
switch(relation.type) {
case 'inheritance':
line.setAttribute('stroke', '#007bff');
line.setAttribute('marker-end', 'url(#inheritance-arrow)');
break;
case 'implementation':
line.setAttribute('stroke', '#28a745');
line.setAttribute('stroke-dasharray', '5,5');
line.setAttribute('marker-end', 'url(#implementation-arrow)');
break;
case 'association':
line.setAttribute('stroke', '#6c757d');
break;
case 'composition':
line.setAttribute('stroke', '#dc3545');
line.setAttribute('marker-end', 'url(#composition-diamond)');
break;
case 'aggregation':
line.setAttribute('stroke', '#fd7e14');
line.setAttribute('marker-end', 'url(#aggregation-diamond)');
break;
case 'dependency':
line.setAttribute('stroke', '#6f42c1');
line.setAttribute('stroke-dasharray', '3,3');
line.setAttribute('marker-end', 'url(#dependency-arrow)');
break;
}
svg.appendChild(line);
canvas.appendChild(svg);
// Ajouter les markers SVG si ce n'est pas déjà fait
addSVGMarkers(canvas);
}
// Ajouter les markers SVG pour les flèches
function addSVGMarkers(canvas) {
let defs = canvas.querySelector('defs');
if (defs) return; // Déjà ajoutés
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute';
svg.style.width = '0';
svg.style.height = '0';
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
// Flèche d'héritage (triangle vide)
const inheritanceArrow = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
inheritanceArrow.setAttribute('id', 'inheritance-arrow');
inheritanceArrow.setAttribute('markerWidth', '10');
inheritanceArrow.setAttribute('markerHeight', '10');
inheritanceArrow.setAttribute('refX', '8');
inheritanceArrow.setAttribute('refY', '3');
inheritanceArrow.setAttribute('orient', 'auto');
inheritanceArrow.innerHTML = '<polygon points="0,0 0,6 6,3" fill="white" stroke="#007bff" stroke-width="2"/>';
defs.appendChild(inheritanceArrow);
// Flèche d'implémentation (triangle vide en pointillés)
const implementationArrow = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
implementationArrow.setAttribute('id', 'implementation-arrow');
implementationArrow.setAttribute('markerWidth', '10');
implementationArrow.setAttribute('markerHeight', '10');
implementationArrow.setAttribute('refX', '8');
implementationArrow.setAttribute('refY', '3');
implementationArrow.setAttribute('orient', 'auto');
implementationArrow.innerHTML = '<polygon points="0,0 0,6 6,3" fill="white" stroke="#28a745" stroke-width="2"/>';
defs.appendChild(implementationArrow);
svg.appendChild(defs);
canvas.appendChild(svg);
}
// Mode sélection
function setSelectionMode() {
currentTool = 'select';
connectionMode = null;
firstSelectedForConnection = null;
document.body.style.cursor = 'default';
document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));
showStatus('Mode sélection activé', 'info');
}
// Supprimer les éléments sélectionnés
function deleteSelected() {
if (selectedElements.length === 0) {
showStatus('Aucun élément sélectionné', 'error');
return;
}
selectedElements.forEach(id => {
// Supprimer du modèle
currentModel.classes = currentModel.classes.filter(c => c.id !== id);
// Supprimer du DOM
const element = document.getElementById(`element-${id}`);
if (element) element.remove();
});
selectedElements = [];
showStatus('Éléments supprimés', 'success');
}
// Aligner les éléments (fonction basique)
function alignElements() {
if (selectedElements.length < 2) {
showStatus('Sélectionnez au moins 2 éléments pour les aligner', 'error');
return;
}
const elements = selectedElements.map(id => currentModel.classes.find(c => c.id === id));
const minY = Math.min(...elements.map(e => e.y));
elements.forEach(element => {
element.y = minY;
const domElement = document.getElementById(`element-${element.id}`);
domElement.style.top = minY + 'px';
});
showStatus('Éléments alignés', 'success');
}
// Validation du modèle UML
async function validateModel() {
if (!currentModel) {
showStatus('Aucun modèle UML à valider', 'error');
return;
}
try {
showStatus('Validation du modèle en cours...', 'info');
const response = await fetch(`${getModellnaiaUrl()}/api/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(currentModel)
});
if (response.ok) {
const result = await response.json();
showStatus(`Modèle validé avec succès : ${result.classCount} classes détectées`, 'success');
} else {
showStatus('Erreur lors de la validation du modèle', 'error');
}
} catch (error) {
showStatus(`Erreur de connexion : ${error.message}`, 'error');
}
}
// Génération du code Java
async function generateCode() {
if (!currentModel) {
showStatus('Aucun modèle UML à traiter', 'error');
return;
}
try {
showStatus('Génération du code Java en cours...', 'info');
const projectName = document.getElementById('project-name').value;
const packageName = document.getElementById('package-name').value;
const generationType = document.getElementById('generation-type').value;
const payload = {
model: currentModel,
projectName: projectName,
packageName: packageName,
generationType: generationType
};
const response = await fetch(`${getModellnaiaUrl()}/api/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
if (response.ok) {
const result = await response.json();
generatedProjectId = result.projectId;
showStatus(`Code généré avec succès ! ID: ${generatedProjectId}`, 'success');
} else {
showStatus('Erreur lors de la génération du code', 'error');
}
} catch (error) {
showStatus(`Erreur de génération : ${error.message}`, 'error');
}
}
// Téléchargement du projet généré
async function downloadGenerated() {
if (!generatedProjectId) {
showStatus('Aucun projet généré à télécharger', 'error');
return;
}
try {
showStatus('Téléchargement en cours...', 'info');
const response = await fetch(`${getModellnaiaUrl()}/api/download/${generatedProjectId}`);
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${document.getElementById('project-name').value}.zip`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
showStatus('Téléchargement terminé !', 'success');
} else {
showStatus('Erreur lors du téléchargement', 'error');
}
} catch (error) {
showStatus(`Erreur de téléchargement : ${error.message}`, 'error');
}
}
// Utilitaires pour le modèle
function clearModel() {
if (confirm('Êtes-vous sûr de vouloir vider le modèle ?')) {
// Supprimer tous les éléments du DOM
const canvas = document.getElementById('model-canvas');
const elements = canvas.querySelectorAll('.uml-element');
const relations = canvas.querySelectorAll('svg[id^="relation-"]');
elements.forEach(el => el.remove());
relations.forEach(el => el.remove());
// Réinitialiser le modèle
currentModel = {
classes: [],
relations: [],
nextId: 1
};
selectedElements = [];
firstSelectedForConnection = null;
// Réafficher le message de bienvenue
const welcome = document.getElementById('welcome-message');
if (welcome) welcome.style.display = 'block';
showStatus('Modèle vidé', 'info');
}
}
function exportModel() {
if (!currentModel) {
showStatus('Aucun modèle à exporter', 'error');
return;
}
const dataStr = JSON.stringify(currentModel, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'model.json';
link.click();
URL.revokeObjectURL(url);
showStatus('Modèle exporté', 'success');
}
function importModel() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const importedModel = JSON.parse(e.target.result);
// Vider le modèle actuel
const canvas = document.getElementById('model-canvas');
canvas.querySelectorAll('.uml-element').forEach(el => el.remove());
// Charger le nouveau modèle
currentModel = importedModel;
// Rendre tous les éléments
currentModel.classes.forEach(element => {
renderUMLElement(element);
});
// Masquer le message de bienvenue
const welcome = document.getElementById('welcome-message');
if (welcome) welcome.style.display = 'none';
showStatus('Modèle importé avec succès', 'success');
} catch (error) {
showStatus('Erreur lors de l\'importation du modèle', 'error');
}
};
reader.readAsText(file);
}
};
input.click();
}
// Utilitaire pour récupérer l'URL de Modellnaia
function getModellnaiaUrl() {
return document.getElementById('modellnaia-url').value || config.modellnaiaBaseUrl;
}
// Initialisation au chargement de la page
document.addEventListener('DOMContentLoaded', function() {
showStatus('Initialisation de l\'application...', 'info');
// Attendre que les web components soient prêts
if (window.WebComponents) {
window.WebComponents.waitFor(() => {
initializePolymeria();
});
} else {
// Fallback si WebComponents n'est pas disponible
setTimeout(initializePolymeria, 1000);
}
});
// Gestion des erreurs globales
window.addEventListener('error', function(event) {
showStatus('Erreur technique détectée', 'error');
console.error('Erreur globale:', event.error);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment