Aktraiser
commited on
Commit
·
0eed2ad
1
Parent(s):
7b4a53b
background shape for figjam
Browse files
app.py
CHANGED
@@ -251,127 +251,6 @@ def get_figma_comments(file_id: str = "") -> str:
|
|
251 |
|
252 |
return f"📝 **Commentaires récents :**\n" + "\n\n".join(comment_list)
|
253 |
|
254 |
-
def get_figma_collaborators(file_id: str = "") -> str:
|
255 |
-
"""Liste qui travaille sur un fichier Figma (collaborateurs actifs)"""
|
256 |
-
file_id = file_id or figma_config["file_id"]
|
257 |
-
|
258 |
-
if not file_id:
|
259 |
-
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
260 |
-
|
261 |
-
collaborators = {}
|
262 |
-
|
263 |
-
# 1. Récupérer les commentateurs récents
|
264 |
-
comments_result = make_figma_request(f"files/{file_id}/comments")
|
265 |
-
if "error" not in comments_result:
|
266 |
-
comments = comments_result.get("comments", [])
|
267 |
-
for comment in comments[-20:]: # 20 commentaires les plus récents
|
268 |
-
user_data = comment.get("user", {})
|
269 |
-
if user_data:
|
270 |
-
user_id = user_data.get("id")
|
271 |
-
if user_id:
|
272 |
-
collaborators[user_id] = {
|
273 |
-
"nom": user_data.get("handle", "Anonyme"),
|
274 |
-
"avatar": user_data.get("img_url", ""),
|
275 |
-
"derniere_activite": comment.get("created_at", ""),
|
276 |
-
"type_activite": "💬 Commentaire",
|
277 |
-
"dernier_commentaire": comment.get("message", "")[:100] + "..." if len(comment.get("message", "")) > 100 else comment.get("message", "")
|
278 |
-
}
|
279 |
-
|
280 |
-
# 2. Récupérer les versions récentes pour voir les contributeurs
|
281 |
-
versions_result = make_figma_request(f"files/{file_id}/versions")
|
282 |
-
if "error" not in versions_result:
|
283 |
-
versions = versions_result.get("versions", [])
|
284 |
-
for version in versions[-10:]: # 10 versions les plus récentes
|
285 |
-
user_data = version.get("user", {})
|
286 |
-
if user_data:
|
287 |
-
user_id = user_data.get("id")
|
288 |
-
if user_id:
|
289 |
-
if user_id not in collaborators:
|
290 |
-
collaborators[user_id] = {
|
291 |
-
"nom": user_data.get("handle", "Anonyme"),
|
292 |
-
"avatar": user_data.get("img_url", ""),
|
293 |
-
"derniere_activite": version.get("created_at", ""),
|
294 |
-
"type_activite": "🎨 Modification",
|
295 |
-
"description": version.get("description", "Modification du design")
|
296 |
-
}
|
297 |
-
else:
|
298 |
-
# Mettre à jour avec l'activité la plus récente
|
299 |
-
version_date = version.get("created_at", "")
|
300 |
-
current_date = collaborators[user_id]["derniere_activite"]
|
301 |
-
if version_date > current_date:
|
302 |
-
collaborators[user_id]["derniere_activite"] = version_date
|
303 |
-
collaborators[user_id]["type_activite"] = "🎨 Modification"
|
304 |
-
collaborators[user_id]["description"] = version.get("description", "Modification du design")
|
305 |
-
|
306 |
-
if not collaborators:
|
307 |
-
return "👥 Aucune activité récente trouvée sur ce fichier"
|
308 |
-
|
309 |
-
# Trier par dernière activité
|
310 |
-
sorted_collaborators = sorted(
|
311 |
-
collaborators.items(),
|
312 |
-
key=lambda x: x[1]["derniere_activite"],
|
313 |
-
reverse=True
|
314 |
-
)
|
315 |
-
|
316 |
-
collaborator_list = []
|
317 |
-
for user_id, data in sorted_collaborators:
|
318 |
-
# Formater la date
|
319 |
-
date_str = data["derniere_activite"]
|
320 |
-
if date_str:
|
321 |
-
try:
|
322 |
-
from datetime import datetime
|
323 |
-
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
324 |
-
date_formatted = dt.strftime("%d/%m/%Y %H:%M")
|
325 |
-
except:
|
326 |
-
date_formatted = date_str
|
327 |
-
else:
|
328 |
-
date_formatted = "Date inconnue"
|
329 |
-
|
330 |
-
collaborator_info = f"👤 **{data['nom']}**\n {data['type_activite']} - {date_formatted}"
|
331 |
-
|
332 |
-
# Ajouter des détails selon le type d'activité
|
333 |
-
if "dernier_commentaire" in data:
|
334 |
-
collaborator_info += f"\n 💬 \"{data['dernier_commentaire']}\""
|
335 |
-
elif "description" in data:
|
336 |
-
collaborator_info += f"\n 📝 {data['description']}"
|
337 |
-
|
338 |
-
collaborator_list.append(collaborator_info)
|
339 |
-
|
340 |
-
return f"👥 **Collaborateurs actifs sur ce fichier :**\n\n" + "\n\n".join(collaborator_list)
|
341 |
-
|
342 |
-
def list_figma_recent_files() -> str:
|
343 |
-
"""Liste tous les fichiers Figma récents de l'utilisateur connecté"""
|
344 |
-
result = make_figma_request("files/recent")
|
345 |
-
|
346 |
-
if "error" in result:
|
347 |
-
return f"❌ Erreur : {result['error']}"
|
348 |
-
|
349 |
-
files = result.get("files", [])
|
350 |
-
|
351 |
-
if not files:
|
352 |
-
return "📁 Aucun fichier récent trouvé"
|
353 |
-
|
354 |
-
file_list = []
|
355 |
-
for file_data in files[:20]: # Limiter à 20 fichiers récents
|
356 |
-
name = file_data.get("name", "Sans nom")
|
357 |
-
file_key = file_data.get("key", "")
|
358 |
-
last_modified = file_data.get("last_modified", "")
|
359 |
-
|
360 |
-
# Extraire juste la date de la timestamp
|
361 |
-
if last_modified:
|
362 |
-
try:
|
363 |
-
from datetime import datetime
|
364 |
-
dt = datetime.fromisoformat(last_modified.replace('Z', '+00:00'))
|
365 |
-
date_str = dt.strftime("%d/%m/%Y %H:%M")
|
366 |
-
except:
|
367 |
-
date_str = last_modified
|
368 |
-
else:
|
369 |
-
date_str = "Date inconnue"
|
370 |
-
|
371 |
-
file_list.append(f"📄 **{name}**\n ID: `{file_key}`\n Modifié: {date_str}")
|
372 |
-
|
373 |
-
return f"📁 **Vos fichiers Figma récents :**\n\n" + "\n\n".join(file_list)
|
374 |
-
|
375 |
def get_figma_user_info() -> str:
|
376 |
"""Récupère les informations de l'utilisateur connecté"""
|
377 |
result = make_figma_request("me")
|
@@ -412,27 +291,16 @@ def list_figma_team_projects(team_id: str = "") -> str:
|
|
412 |
|
413 |
return f"📁 **Projets de l'équipe :**\n" + "\n".join(project_list)
|
414 |
|
415 |
-
def
|
416 |
-
"""Crée un post-it dans FigJam"""
|
417 |
if not figma_config["file_id"]:
|
418 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
419 |
|
420 |
try:
|
421 |
x_pos, y_pos = float(x), float(y)
|
|
|
422 |
|
423 |
-
|
424 |
-
colors = {
|
425 |
-
"yellow": "🟡",
|
426 |
-
"blue": "🔵",
|
427 |
-
"green": "🟢",
|
428 |
-
"red": "🔴",
|
429 |
-
"purple": "🟣",
|
430 |
-
"orange": "🟠"
|
431 |
-
}
|
432 |
-
|
433 |
-
color_emoji = colors.get(color.lower(), "🟡")
|
434 |
-
|
435 |
-
comment_text = f"{color_emoji} **Post-it à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Texte: \"{text}\"\n- Couleur: {color}"
|
436 |
|
437 |
comment_data = {
|
438 |
"message": comment_text,
|
@@ -447,13 +315,35 @@ def create_figma_sticky_note(x: str, y: str, text: str, color: str = "yellow", n
|
|
447 |
if "error" in result:
|
448 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
449 |
|
450 |
-
return f"✅
|
451 |
|
452 |
except ValueError:
|
453 |
-
return "❌ Les coordonnées doivent être des nombres"
|
454 |
|
455 |
-
def
|
456 |
-
"""Crée
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
457 |
if not figma_config["file_id"]:
|
458 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
459 |
|
@@ -461,7 +351,19 @@ def create_figma_section(x: str, y: str, width: str, height: str, title: str, co
|
|
461 |
x_pos, y_pos = float(x), float(y)
|
462 |
w, h = float(width), float(height)
|
463 |
|
464 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
465 |
|
466 |
comment_data = {
|
467 |
"message": comment_text,
|
@@ -476,27 +378,27 @@ def create_figma_section(x: str, y: str, width: str, height: str, title: str, co
|
|
476 |
if "error" in result:
|
477 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
478 |
|
479 |
-
return f"✅
|
480 |
|
481 |
except ValueError:
|
482 |
return "❌ Les coordonnées et dimensions doivent être des nombres"
|
483 |
|
484 |
-
def
|
485 |
-
"""Crée un
|
486 |
if not figma_config["file_id"]:
|
487 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
488 |
|
489 |
try:
|
490 |
-
|
491 |
-
|
492 |
|
493 |
-
comment_text = f"
|
494 |
|
495 |
comment_data = {
|
496 |
"message": comment_text,
|
497 |
"client_meta": {
|
498 |
-
"x":
|
499 |
-
"y":
|
500 |
}
|
501 |
}
|
502 |
|
@@ -505,32 +407,23 @@ def create_figma_connector(start_x: str, start_y: str, end_x: str, end_y: str, s
|
|
505 |
if "error" in result:
|
506 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
507 |
|
508 |
-
return f"✅
|
509 |
|
510 |
except ValueError:
|
511 |
-
return "❌
|
512 |
|
513 |
-
def
|
514 |
-
"""Crée
|
515 |
if not figma_config["file_id"]:
|
516 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
517 |
|
518 |
try:
|
519 |
x_pos, y_pos = float(x), float(y)
|
520 |
-
w, h = float(width), float(height)
|
521 |
-
|
522 |
-
shapes = {
|
523 |
-
"circle": "⭕",
|
524 |
-
"triangle": "🔺",
|
525 |
-
"star": "⭐",
|
526 |
-
"diamond": "💎",
|
527 |
-
"heart": "❤️",
|
528 |
-
"arrow": "➡️"
|
529 |
-
}
|
530 |
|
531 |
-
|
|
|
532 |
|
533 |
-
comment_text = f"
|
534 |
|
535 |
comment_data = {
|
536 |
"message": comment_text,
|
@@ -545,20 +438,22 @@ def create_figma_shape(x: str, y: str, width: str, height: str, shape_type: str,
|
|
545 |
if "error" in result:
|
546 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
547 |
|
548 |
-
return f"✅
|
549 |
|
550 |
except ValueError:
|
551 |
-
return "❌ Les coordonnées
|
552 |
|
553 |
-
def
|
554 |
-
"""
|
555 |
if not figma_config["file_id"]:
|
556 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
557 |
|
558 |
try:
|
559 |
-
x_pos, y_pos = float(
|
|
|
|
|
560 |
|
561 |
-
comment_text = f"
|
562 |
|
563 |
comment_data = {
|
564 |
"message": comment_text,
|
@@ -573,26 +468,51 @@ def move_figma_element(element_name: str, new_x: str, new_y: str) -> str:
|
|
573 |
if "error" in result:
|
574 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
575 |
|
576 |
-
return f"✅
|
577 |
|
578 |
except ValueError:
|
579 |
-
return "❌ Les coordonnées doivent être des nombres"
|
580 |
|
581 |
-
def
|
582 |
-
"""
|
583 |
if not figma_config["file_id"]:
|
584 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
585 |
|
586 |
try:
|
587 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
588 |
|
589 |
-
|
|
|
|
|
590 |
|
591 |
comment_data = {
|
592 |
"message": comment_text,
|
593 |
"client_meta": {
|
594 |
-
"x":
|
595 |
-
"y":
|
596 |
}
|
597 |
}
|
598 |
|
@@ -601,26 +521,83 @@ def resize_figma_element(element_name: str, new_width: str, new_height: str) ->
|
|
601 |
if "error" in result:
|
602 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
603 |
|
604 |
-
return f"✅
|
605 |
|
606 |
except ValueError:
|
607 |
-
return "❌ Les
|
608 |
|
609 |
-
def
|
610 |
-
"""Crée
|
611 |
if not figma_config["file_id"]:
|
612 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
613 |
|
614 |
try:
|
615 |
-
|
616 |
|
617 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
618 |
|
619 |
comment_data = {
|
620 |
"message": comment_text,
|
621 |
"client_meta": {
|
622 |
-
"x":
|
623 |
-
"y":
|
624 |
}
|
625 |
}
|
626 |
|
@@ -629,21 +606,29 @@ def create_figma_layout_grid(columns: str, rows: str, gap: str = "20") -> str:
|
|
629 |
if "error" in result:
|
630 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
631 |
|
632 |
-
return f"✅
|
633 |
|
634 |
except ValueError:
|
635 |
-
return "❌ Les
|
636 |
|
637 |
-
def
|
638 |
-
"""Crée
|
639 |
if not figma_config["file_id"]:
|
640 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
641 |
|
642 |
try:
|
643 |
x_pos, y_pos = float(x), float(y)
|
644 |
w, h = float(width), float(height)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
645 |
|
646 |
-
comment_text = f"
|
647 |
|
648 |
comment_data = {
|
649 |
"message": comment_text,
|
@@ -658,7 +643,7 @@ def create_figma_component(x: str, y: str, width: str, height: str, component_na
|
|
658 |
if "error" in result:
|
659 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
660 |
|
661 |
-
return f"✅
|
662 |
|
663 |
except ValueError:
|
664 |
return "❌ Les coordonnées et dimensions doivent être des nombres"
|
@@ -678,12 +663,6 @@ def setup_demo():
|
|
678 |
def test_user():
|
679 |
return get_figma_user_info()
|
680 |
|
681 |
-
def test_recent_files():
|
682 |
-
return list_figma_recent_files()
|
683 |
-
|
684 |
-
def test_collaborators():
|
685 |
-
return get_figma_collaborators()
|
686 |
-
|
687 |
with gr.Blocks(
|
688 |
title="🎨 Figma MCP Server",
|
689 |
theme=gr.themes.Soft(),
|
@@ -738,11 +717,9 @@ def setup_demo():
|
|
738 |
|
739 |
# Actions de test
|
740 |
with gr.Row():
|
741 |
-
test_files_btn = gr.Button("📁 Fichiers Récents")
|
742 |
test_info_btn = gr.Button("📄 Info Fichier")
|
743 |
test_comments_btn = gr.Button("📝 Commentaires")
|
744 |
test_user_btn = gr.Button("👤 Info Utilisateur")
|
745 |
-
test_collaborators_btn = gr.Button("👥 Collaborateurs")
|
746 |
|
747 |
# Connexions des événements
|
748 |
token_btn.click(
|
@@ -757,11 +734,6 @@ def setup_demo():
|
|
757 |
outputs=[status_output]
|
758 |
)
|
759 |
|
760 |
-
test_files_btn.click(
|
761 |
-
test_recent_files,
|
762 |
-
outputs=[status_output]
|
763 |
-
)
|
764 |
-
|
765 |
test_info_btn.click(
|
766 |
test_file_info,
|
767 |
outputs=[status_output]
|
@@ -776,11 +748,6 @@ def setup_demo():
|
|
776 |
test_user,
|
777 |
outputs=[status_output]
|
778 |
)
|
779 |
-
|
780 |
-
test_collaborators_btn.click(
|
781 |
-
test_collaborators,
|
782 |
-
outputs=[status_output]
|
783 |
-
)
|
784 |
|
785 |
gr.Markdown("""
|
786 |
---
|
@@ -791,30 +758,42 @@ def setup_demo():
|
|
791 |
- `configure_figma_file_id(file_id)` - Configure l'ID du fichier
|
792 |
|
793 |
**📁 Navigation :**
|
794 |
-
- `list_figma_recent_files()` - Liste tous vos fichiers récents avec leurs IDs
|
795 |
- `get_figma_file_info()` - Récupère les infos du fichier
|
796 |
- `get_figma_comments()` - Récupère les commentaires
|
797 |
- `get_figma_user_info()` - Info utilisateur connecté
|
798 |
-
- `get_figma_collaborators()` - Récupère les collaborateurs actifs
|
799 |
|
800 |
-
**🎨 Création d'éléments de base :**
|
801 |
- `create_figma_rectangle(x, y, width, height, name, color)` - Crée un rectangle
|
802 |
- `create_figma_frame(x, y, width, height, name)` - Crée un frame
|
803 |
- `create_figma_text(x, y, text, name, font_size)` - Crée un texte
|
804 |
-
- `create_figma_shape(x, y, width, height, shape_type, name, color)` - Crée des formes (circle, triangle, star, diamond, heart, arrow)
|
805 |
-
|
806 |
-
**🟡 FigJam - Post-its et organisation :**
|
807 |
-
- `create_figma_sticky_note(x, y, text, color, name)` - Crée un post-it (couleurs: yellow, blue, green, red, purple, orange)
|
808 |
-
- `create_figma_section(x, y, width, height, title, color)` - Crée une section pour organiser
|
809 |
-
- `create_figma_connector(start_x, start_y, end_x, end_y, style)` - Crée un connecteur entre points
|
810 |
-
|
811 |
-
**🔧 Manipulation d'objets :**
|
812 |
-
- `move_figma_element(element_name, new_x, new_y)` - Déplace un élément
|
813 |
-
- `resize_figma_element(element_name, new_width, new_height)` - Redimensionne un élément
|
814 |
|
815 |
-
|
816 |
-
- `
|
817 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
818 |
""")
|
819 |
|
820 |
return demo
|
|
|
251 |
|
252 |
return f"📝 **Commentaires récents :**\n" + "\n\n".join(comment_list)
|
253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
def get_figma_user_info() -> str:
|
255 |
"""Récupère les informations de l'utilisateur connecté"""
|
256 |
result = make_figma_request("me")
|
|
|
291 |
|
292 |
return f"📁 **Projets de l'équipe :**\n" + "\n".join(project_list)
|
293 |
|
294 |
+
def create_figjam_sticky_note(x: str, y: str, text: str, width: str = "240", height: str = "240") -> str:
|
295 |
+
"""Crée un post-it (sticky note) dans FigJam avec les dimensions officielles"""
|
296 |
if not figma_config["file_id"]:
|
297 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
298 |
|
299 |
try:
|
300 |
x_pos, y_pos = float(x), float(y)
|
301 |
+
w, h = float(width), float(height)
|
302 |
|
303 |
+
comment_text = f"🟡 **Sticky Note à créer (API FigJam) :**\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px (défaut: 240x240)\n- Texte: \"{text}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst sticky = figma.createSticky()\nsticky.x = {x_pos}\nsticky.y = {y_pos}\nsticky.resize({w}, {h})\nawait figma.loadFontAsync(sticky.text.fontName)\nsticky.text.characters = '{text}'\n```"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
|
305 |
comment_data = {
|
306 |
"message": comment_text,
|
|
|
315 |
if "error" in result:
|
316 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
317 |
|
318 |
+
return f"✅ Instructions Sticky Note créées à ({x_pos}, {y_pos}) - Taille: {w}x{h}px"
|
319 |
|
320 |
except ValueError:
|
321 |
+
return "❌ Les coordonnées et dimensions doivent être des nombres"
|
322 |
|
323 |
+
def create_figjam_connector_between_elements(element1_name: str, element2_name: str, style: str = "solid") -> str:
|
324 |
+
"""Crée un connecteur entre deux éléments FigJam (officiel)"""
|
325 |
+
if not figma_config["file_id"]:
|
326 |
+
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
327 |
+
|
328 |
+
comment_text = f"🔗 **Connecteur FigJam à créer (API officielle) :**\n- Entre: {element1_name}\n- Et: {element2_name}\n- Style: {style}\n\n📋 **Code Plugin Figma :**\n```javascript\n// Trouver les éléments par nom\nconst element1 = figma.currentPage.findOne(n => n.name === '{element1_name}')\nconst element2 = figma.currentPage.findOne(n => n.name === '{element2_name}')\n\n// Créer le connecteur\nconst connector = figma.createConnector()\nconnector.connectorStart = {{\n endpointNodeId: element1.id,\n magnet: 'AUTO'\n}}\nconnector.connectorEnd = {{\n endpointNodeId: element2.id,\n magnet: 'AUTO'\n}}\n```"
|
329 |
+
|
330 |
+
comment_data = {
|
331 |
+
"message": comment_text,
|
332 |
+
"client_meta": {
|
333 |
+
"x": 0,
|
334 |
+
"y": 0
|
335 |
+
}
|
336 |
+
}
|
337 |
+
|
338 |
+
result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
|
339 |
+
|
340 |
+
if "error" in result:
|
341 |
+
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
342 |
+
|
343 |
+
return f"✅ Instructions connecteur créées entre {element1_name} et {element2_name}"
|
344 |
+
|
345 |
+
def create_figjam_shape_with_text(x: str, y: str, shape_type: str, text: str, width: str = "208", height: str = "208") -> str:
|
346 |
+
"""Crée une forme avec texte intégré dans FigJam (API officielle)"""
|
347 |
if not figma_config["file_id"]:
|
348 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
349 |
|
|
|
351 |
x_pos, y_pos = float(x), float(y)
|
352 |
w, h = float(width), float(height)
|
353 |
|
354 |
+
# Types de formes supportés par l'API Figma
|
355 |
+
supported_shapes = {
|
356 |
+
"rectangle": "ROUNDED_RECTANGLE",
|
357 |
+
"circle": "ELLIPSE",
|
358 |
+
"triangle": "TRIANGLE",
|
359 |
+
"diamond": "DIAMOND",
|
360 |
+
"star": "STAR",
|
361 |
+
"hexagon": "HEXAGON"
|
362 |
+
}
|
363 |
+
|
364 |
+
figma_shape = supported_shapes.get(shape_type.lower(), "ROUNDED_RECTANGLE")
|
365 |
+
|
366 |
+
comment_text = f"🔶 **Forme avec texte FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Type: {shape_type} → {figma_shape}\n- Taille: {w}x{h}px (défaut: 208x208)\n- Texte: \"{text}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst shape = figma.createShapeWithText()\nshape.x = {x_pos}\nshape.y = {y_pos}\nshape.resize({w}, {h})\nshape.shapeType = '{figma_shape}'\nawait figma.loadFontAsync(shape.text.fontName)\nshape.text.characters = '{text}'\n```"
|
367 |
|
368 |
comment_data = {
|
369 |
"message": comment_text,
|
|
|
378 |
if "error" in result:
|
379 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
380 |
|
381 |
+
return f"✅ Instructions forme {shape_type} avec texte créées à ({x_pos}, {y_pos})"
|
382 |
|
383 |
except ValueError:
|
384 |
return "❌ Les coordonnées et dimensions doivent être des nombres"
|
385 |
|
386 |
+
def create_figjam_table(rows: str, columns: str, x: str = "0", y: str = "0") -> str:
|
387 |
+
"""Crée un tableau dans FigJam (API officielle)"""
|
388 |
if not figma_config["file_id"]:
|
389 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
390 |
|
391 |
try:
|
392 |
+
num_rows, num_cols = int(rows), int(columns)
|
393 |
+
x_pos, y_pos = float(x), float(y)
|
394 |
|
395 |
+
comment_text = f"📊 **Tableau FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Dimensions: {num_rows} lignes × {num_cols} colonnes\n\n📋 **Code Plugin Figma :**\n```javascript\nconst table = figma.createTable({num_rows}, {num_cols})\ntable.x = {x_pos}\ntable.y = {y_pos}\n\n// Exemple: ajouter du texte dans les cellules\nawait figma.loadFontAsync(table.cellAt(0, 0).text.fontName)\ntable.cellAt(0, 0).text.characters = 'Titre 1'\ntable.cellAt(0, 1).text.characters = 'Titre 2'\n```"
|
396 |
|
397 |
comment_data = {
|
398 |
"message": comment_text,
|
399 |
"client_meta": {
|
400 |
+
"x": x_pos,
|
401 |
+
"y": y_pos
|
402 |
}
|
403 |
}
|
404 |
|
|
|
407 |
if "error" in result:
|
408 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
409 |
|
410 |
+
return f"✅ Instructions tableau {num_rows}×{num_cols} créées à ({x_pos}, {y_pos})"
|
411 |
|
412 |
except ValueError:
|
413 |
+
return "❌ Les dimensions et coordonnées doivent être des nombres"
|
414 |
|
415 |
+
def create_figjam_code_block(x: str, y: str, code: str, language: str = "javascript") -> str:
|
416 |
+
"""Crée un bloc de code dans FigJam (API officielle)"""
|
417 |
if not figma_config["file_id"]:
|
418 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
419 |
|
420 |
try:
|
421 |
x_pos, y_pos = float(x), float(y)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
422 |
|
423 |
+
# Échapper les caractères spéciaux pour l'affichage
|
424 |
+
escaped_code = code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
|
425 |
|
426 |
+
comment_text = f"💻 **Bloc de code FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Langage: {language}\n- Code: \"{code[:100]}{'...' if len(code) > 100 else ''}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst codeBlock = figma.createCodeBlock()\ncodeBlock.x = {x_pos}\ncodeBlock.y = {y_pos}\nawait figma.loadFontAsync(codeBlock.codeBlockText.fontName)\ncodeBlock.codeBlockText.characters = \"{escaped_code}\"\n```"
|
427 |
|
428 |
comment_data = {
|
429 |
"message": comment_text,
|
|
|
438 |
if "error" in result:
|
439 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
440 |
|
441 |
+
return f"✅ Instructions bloc de code {language} créées à ({x_pos}, {y_pos})"
|
442 |
|
443 |
except ValueError:
|
444 |
+
return "❌ Les coordonnées doivent être des nombres"
|
445 |
|
446 |
+
def create_figjam_background_shape(x: str, y: str, width: str, height: str, color: str = "#F3F4F6", title: str = "", corner_radius: str = "8") -> str:
|
447 |
+
"""Crée une forme de fond rectangulaire pour organiser le contenu FigJam"""
|
448 |
if not figma_config["file_id"]:
|
449 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
450 |
|
451 |
try:
|
452 |
+
x_pos, y_pos = float(x), float(y)
|
453 |
+
w, h = float(width), float(height)
|
454 |
+
radius = float(corner_radius)
|
455 |
|
456 |
+
comment_text = f"📐 **Forme de fond FigJam (zone de travail) :**\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px\n- Couleur: {color}\n- Titre: \"{title}\"\n- Rayon coins: {radius}px\n\n📋 **Code Plugin Figma :**\n```javascript\n// Créer la forme de fond\nconst background = figma.createRectangle()\nbackground.x = {x_pos}\nbackground.y = {y_pos}\nbackground.resize({w}, {h})\nbackground.cornerRadius = {radius}\nbackground.fills = [{{\n type: 'SOLID',\n color: {{ r: 0.95, g: 0.96, b: 0.97 }} // {color}\n}}]\nbackground.name = 'Zone - {title}'\n\n// Optionnel: ajouter un titre\nif ('{title}') {{\n const titleText = figma.createText()\n titleText.x = {x_pos + 16}\n titleText.y = {y_pos + 16}\n await figma.loadFontAsync(titleText.fontName)\n titleText.characters = '{title}'\n titleText.fontSize = 18\n}}\n```"
|
457 |
|
458 |
comment_data = {
|
459 |
"message": comment_text,
|
|
|
468 |
if "error" in result:
|
469 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
470 |
|
471 |
+
return f"✅ Zone de travail \"{title}\" créée à ({x_pos}, {y_pos}) - {w}x{h}px"
|
472 |
|
473 |
except ValueError:
|
474 |
+
return "❌ Les coordonnées et dimensions doivent être des nombres"
|
475 |
|
476 |
+
def create_figjam_sticker(x: str, y: str, sticker_type: str, size: str = "40") -> str:
|
477 |
+
"""Crée un sticker/emoji dans FigJam pour les réactions et annotations"""
|
478 |
if not figma_config["file_id"]:
|
479 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
480 |
|
481 |
try:
|
482 |
+
x_pos, y_pos = float(x), float(y)
|
483 |
+
sticker_size = float(size)
|
484 |
+
|
485 |
+
# Mapping des stickers populaires
|
486 |
+
stickers = {
|
487 |
+
"thumbs_up": "👍",
|
488 |
+
"thumbs_down": "👎",
|
489 |
+
"heart": "❤️",
|
490 |
+
"star": "⭐",
|
491 |
+
"fire": "🔥",
|
492 |
+
"rocket": "🚀",
|
493 |
+
"bulb": "💡",
|
494 |
+
"warning": "⚠️",
|
495 |
+
"check": "✅",
|
496 |
+
"cross": "❌",
|
497 |
+
"question": "❓",
|
498 |
+
"idea": "💭",
|
499 |
+
"clock": "⏰",
|
500 |
+
"money": "💰",
|
501 |
+
"target": "🎯",
|
502 |
+
"celebrate": "🎉",
|
503 |
+
"thinking": "🤔",
|
504 |
+
"confused": "😕"
|
505 |
+
}
|
506 |
|
507 |
+
emoji = stickers.get(sticker_type.lower(), sticker_type)
|
508 |
+
|
509 |
+
comment_text = f"🎭 **Sticker FigJam :**\n- Position: ({x_pos}, {y_pos})\n- Type: {sticker_type} → {emoji}\n- Taille: {sticker_size}px\n\n📋 **Code Plugin Figma :**\n```javascript\n// Créer un sticker comme texte stylisé\nconst sticker = figma.createText()\nsticker.x = {x_pos}\nsticker.y = {y_pos}\nsticker.resize({sticker_size}, {sticker_size})\nawait figma.loadFontAsync({{ family: 'Inter', style: 'Regular' }})\nsticker.characters = '{emoji}'\nsticker.fontSize = {sticker_size}\nsticker.textAlignHorizontal = 'CENTER'\nsticker.textAlignVertical = 'CENTER'\nsticker.name = 'Sticker - {sticker_type}'\n```"
|
510 |
|
511 |
comment_data = {
|
512 |
"message": comment_text,
|
513 |
"client_meta": {
|
514 |
+
"x": x_pos,
|
515 |
+
"y": y_pos
|
516 |
}
|
517 |
}
|
518 |
|
|
|
521 |
if "error" in result:
|
522 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
523 |
|
524 |
+
return f"✅ Sticker {emoji} créé à ({x_pos}, {y_pos}) - Taille: {sticker_size}px"
|
525 |
|
526 |
except ValueError:
|
527 |
+
return "❌ Les coordonnées et la taille doivent être des nombres"
|
528 |
|
529 |
+
def create_figjam_workshop_template(template_type: str, x: str = "0", y: str = "0") -> str:
|
530 |
+
"""Crée des templates d'atelier FigJam prêts à utiliser"""
|
531 |
if not figma_config["file_id"]:
|
532 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
533 |
|
534 |
try:
|
535 |
+
x_pos, y_pos = float(x), float(y)
|
536 |
|
537 |
+
templates = {
|
538 |
+
"retrospective": {
|
539 |
+
"title": "Rétrospective Sprint",
|
540 |
+
"zones": [
|
541 |
+
{"title": "😊 Ce qui s'est bien passé", "x": 0, "y": 100, "width": 300, "height": 400, "color": "#D1FAE5"},
|
542 |
+
{"title": "😐 Ce qui peut être amélioré", "x": 320, "y": 100, "width": 300, "height": 400, "color": "#FEF3C7"},
|
543 |
+
{"title": "🚀 Actions à prendre", "x": 640, "y": 100, "width": 300, "height": 400, "color": "#DBEAFE"}
|
544 |
+
]
|
545 |
+
},
|
546 |
+
"brainstorm": {
|
547 |
+
"title": "Session Brainstorming",
|
548 |
+
"zones": [
|
549 |
+
{"title": "🎯 Objectif", "x": 0, "y": 100, "width": 960, "height": 120, "color": "#F3E8FF"},
|
550 |
+
{"title": "💡 Idées", "x": 0, "y": 240, "width": 480, "height": 400, "color": "#FEF3C7"},
|
551 |
+
{"title": "⭐ Meilleures idées", "x": 500, "y": 240, "width": 460, "height": 400, "color": "#D1FAE5"}
|
552 |
+
]
|
553 |
+
},
|
554 |
+
"user_journey": {
|
555 |
+
"title": "Parcours Utilisateur",
|
556 |
+
"zones": [
|
557 |
+
{"title": "👤 Persona", "x": 0, "y": 100, "width": 200, "height": 300, "color": "#F3E8FF"},
|
558 |
+
{"title": "🎯 Découverte", "x": 220, "y": 100, "width": 180, "height": 300, "color": "#DBEAFE"},
|
559 |
+
{"title": "🔍 Exploration", "x": 420, "y": 100, "width": 180, "height": 300, "color": "#FEF3C7"},
|
560 |
+
{"title": "✅ Conversion", "x": 620, "y": 100, "width": 180, "height": 300, "color": "#D1FAE5"},
|
561 |
+
{"title": "💝 Fidélisation", "x": 820, "y": 100, "width": 180, "height": 300, "color": "#FECACA"}
|
562 |
+
]
|
563 |
+
}
|
564 |
+
}
|
565 |
+
|
566 |
+
template = templates.get(template_type.lower())
|
567 |
+
|
568 |
+
if not template:
|
569 |
+
available = ", ".join(templates.keys())
|
570 |
+
return f"❌ Template non trouvé. Disponibles: {available}"
|
571 |
+
|
572 |
+
# Générer le code pour créer le template complet
|
573 |
+
zones_code = []
|
574 |
+
for i, zone in enumerate(template["zones"]):
|
575 |
+
zone_x = x_pos + zone["x"]
|
576 |
+
zone_y = y_pos + zone["y"]
|
577 |
+
zones_code.append(f"""
|
578 |
+
// Zone {i+1}: {zone['title']}
|
579 |
+
const zone{i+1} = figma.createRectangle()
|
580 |
+
zone{i+1}.x = {zone_x}
|
581 |
+
zone{i+1}.y = {zone_y}
|
582 |
+
zone{i+1}.resize({zone['width']}, {zone['height']})
|
583 |
+
zone{i+1}.cornerRadius = 8
|
584 |
+
zone{i+1}.fills = [{{ type: 'SOLID', color: {{ r: 0.95, g: 0.96, b: 0.97 }} }}]
|
585 |
+
zone{i+1}.name = '{zone['title']}'
|
586 |
+
|
587 |
+
const title{i+1} = figma.createText()
|
588 |
+
title{i+1}.x = {zone_x + 16}
|
589 |
+
title{i+1}.y = {zone_y + 16}
|
590 |
+
await figma.loadFontAsync(title{i+1}.fontName)
|
591 |
+
title{i+1}.characters = '{zone['title']}'
|
592 |
+
title{i+1}.fontSize = 16""")
|
593 |
+
|
594 |
+
comment_text = f"🏗️ **Template FigJam: {template['title']} :**\n- Position: ({x_pos}, {y_pos})\n- {len(template['zones'])} zones de travail\n\n📋 **Code Plugin Figma :**\n```javascript\n// Template: {template['title']}{''.join(zones_code)}\n```"
|
595 |
|
596 |
comment_data = {
|
597 |
"message": comment_text,
|
598 |
"client_meta": {
|
599 |
+
"x": x_pos,
|
600 |
+
"y": y_pos
|
601 |
}
|
602 |
}
|
603 |
|
|
|
606 |
if "error" in result:
|
607 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
608 |
|
609 |
+
return f"✅ Template \"{template['title']}\" créé avec {len(template['zones'])} zones à ({x_pos}, {y_pos})"
|
610 |
|
611 |
except ValueError:
|
612 |
+
return "❌ Les coordonnées doivent être des nombres"
|
613 |
|
614 |
+
def create_figjam_organized_zone(title: str, x: str, y: str, width: str = "400", height: str = "500", max_stickies: str = "12") -> str:
|
615 |
+
"""Crée une zone organisée avec grille pour post-its dans FigJam"""
|
616 |
if not figma_config["file_id"]:
|
617 |
return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
|
618 |
|
619 |
try:
|
620 |
x_pos, y_pos = float(x), float(y)
|
621 |
w, h = float(width), float(height)
|
622 |
+
max_notes = int(max_stickies)
|
623 |
+
|
624 |
+
# Calculer la grille optimale
|
625 |
+
cols = 3 if max_notes <= 9 else 4
|
626 |
+
rows = (max_notes + cols - 1) // cols # Division avec arrondi vers le haut
|
627 |
+
|
628 |
+
sticky_width = (w - 80) // cols # 80px de marge totale
|
629 |
+
sticky_height = 120 # Hauteur standard d'un post-it
|
630 |
|
631 |
+
comment_text = f"📋 **Zone organisée FigJam :**\n- Titre: \"{title}\"\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px\n- Grille: {cols}×{rows} ({max_notes} post-its max)\n\n📋 **Code Plugin Figma :**\n```javascript\n// Zone de fond\nconst zone = figma.createRectangle()\nzone.x = {x_pos}\nzone.y = {y_pos}\nzone.resize({w}, {h})\nzone.cornerRadius = 12\nzone.fills = [{{ type: 'SOLID', color: {{ r: 0.98, g: 0.98, b: 0.99 }} }}]\nzone.strokes = [{{ type: 'SOLID', color: {{ r: 0.9, g: 0.9, b: 0.92 }} }}]\nzone.strokeWeight = 2\nzone.name = 'Zone - {title}'\n\n// Titre\nconst titleText = figma.createText()\ntitleText.x = {x_pos + 20}\ntitleText.y = {y_pos + 20}\nawait figma.loadFontAsync(titleText.fontName)\ntitleText.characters = '{title}'\ntitleText.fontSize = 20\ntitleText.fontName = {{ family: 'Inter', style: 'Bold' }}\n\n// Grille de post-its exemple\nfor (let row = 0; row < {rows}; row++) {{\n for (let col = 0; col < {cols}; col++) {{\n const index = row * {cols} + col\n if (index >= {max_notes}) break\n \n const sticky = figma.createSticky()\n sticky.x = {x_pos + 20} + col * ({sticky_width} + 10)\n sticky.y = {y_pos + 70} + row * ({sticky_height} + 10)\n sticky.resize({sticky_width}, {sticky_height})\n await figma.loadFontAsync(sticky.text.fontName)\n sticky.text.characters = `Idée ${{index + 1}}`\n sticky.name = `Sticky ${{index + 1}}`\n }}\n}}\n```"
|
632 |
|
633 |
comment_data = {
|
634 |
"message": comment_text,
|
|
|
643 |
if "error" in result:
|
644 |
return f"❌ Erreur lors de la création du commentaire : {result['error']}"
|
645 |
|
646 |
+
return f"✅ Zone organisée \"{title}\" créée avec grille {cols}×{rows} pour {max_notes} post-its"
|
647 |
|
648 |
except ValueError:
|
649 |
return "❌ Les coordonnées et dimensions doivent être des nombres"
|
|
|
663 |
def test_user():
|
664 |
return get_figma_user_info()
|
665 |
|
|
|
|
|
|
|
|
|
|
|
|
|
666 |
with gr.Blocks(
|
667 |
title="🎨 Figma MCP Server",
|
668 |
theme=gr.themes.Soft(),
|
|
|
717 |
|
718 |
# Actions de test
|
719 |
with gr.Row():
|
|
|
720 |
test_info_btn = gr.Button("📄 Info Fichier")
|
721 |
test_comments_btn = gr.Button("📝 Commentaires")
|
722 |
test_user_btn = gr.Button("👤 Info Utilisateur")
|
|
|
723 |
|
724 |
# Connexions des événements
|
725 |
token_btn.click(
|
|
|
734 |
outputs=[status_output]
|
735 |
)
|
736 |
|
|
|
|
|
|
|
|
|
|
|
737 |
test_info_btn.click(
|
738 |
test_file_info,
|
739 |
outputs=[status_output]
|
|
|
748 |
test_user,
|
749 |
outputs=[status_output]
|
750 |
)
|
|
|
|
|
|
|
|
|
|
|
751 |
|
752 |
gr.Markdown("""
|
753 |
---
|
|
|
758 |
- `configure_figma_file_id(file_id)` - Configure l'ID du fichier
|
759 |
|
760 |
**📁 Navigation :**
|
|
|
761 |
- `get_figma_file_info()` - Récupère les infos du fichier
|
762 |
- `get_figma_comments()` - Récupère les commentaires
|
763 |
- `get_figma_user_info()` - Info utilisateur connecté
|
|
|
764 |
|
765 |
+
**🎨 Création d'éléments de base (Figma Design) :**
|
766 |
- `create_figma_rectangle(x, y, width, height, name, color)` - Crée un rectangle
|
767 |
- `create_figma_frame(x, y, width, height, name)` - Crée un frame
|
768 |
- `create_figma_text(x, y, text, name, font_size)` - Crée un texte
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
769 |
|
770 |
+
**🟡 FigJam - Éléments de base :**
|
771 |
+
- `create_figjam_sticky_note(x, y, text, width, height)` - Post-it (défaut: 240×240px)
|
772 |
+
- `create_figjam_connector_between_elements(element1_name, element2_name, style)` - Connecteur entre éléments
|
773 |
+
- `create_figjam_shape_with_text(x, y, shape_type, text, width, height)` - Forme avec texte (défaut: 208×208px)
|
774 |
+
- `create_figjam_table(rows, columns, x, y)` - Tableau interactif
|
775 |
+
- `create_figjam_code_block(x, y, code, language)` - Bloc de code avec coloration syntaxique
|
776 |
+
|
777 |
+
**📐 FigJam - Zones et organisation :**
|
778 |
+
- `create_figjam_background_shape(x, y, width, height, color, title, corner_radius)` - Zone de fond rectangulaire
|
779 |
+
- `create_figjam_organized_zone(title, x, y, width, height, max_stickies)` - Zone avec grille de post-its
|
780 |
+
- `create_figjam_workshop_template(template_type, x, y)` - Templates d'atelier complets
|
781 |
+
|
782 |
+
**🎭 FigJam - Stickers et réactions :**
|
783 |
+
- `create_figjam_sticker(x, y, sticker_type, size)` - Stickers/emoji pour réactions
|
784 |
+
|
785 |
+
**🎯 Types disponibles :**
|
786 |
+
- **Formes:** rectangle, circle, triangle, diamond, star, hexagon
|
787 |
+
- **Stickers:** thumbs_up, thumbs_down, heart, star, fire, rocket, bulb, warning, check, cross, question, idea, clock, money, target, celebrate, thinking, confused
|
788 |
+
- **Templates:** retrospective, brainstorm, user_journey
|
789 |
+
|
790 |
+
**💡 Workflow recommandé :**
|
791 |
+
1. Créer une zone de fond avec `create_figjam_background_shape()`
|
792 |
+
2. Ajouter des post-its avec `create_figjam_sticky_note()`
|
793 |
+
3. Connecter les éléments avec `create_figjam_connector_between_elements()`
|
794 |
+
4. Ajouter des stickers pour les réactions avec `create_figjam_sticker()`
|
795 |
+
|
796 |
+
**✨ Toutes les fonctions génèrent du code JavaScript prêt à utiliser dans un plugin Figma !**
|
797 |
""")
|
798 |
|
799 |
return demo
|