Last active
July 19, 2025 16:15
-
-
Save bdulac/b3d6c5b0bc2ef25b7f89f3d8df47b21f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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;"> | |
<<abstract>><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;"> | |
<<interface>><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;"> | |
<<enumeration>><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