diff --git a/components/TheToolbar.vue b/components/TheToolbar.vue
index 909c5cf..a5cd497 100644
--- a/components/TheToolbar.vue
+++ b/components/TheToolbar.vue
@@ -1,13 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/composables/useTutorial.ts b/composables/useTutorial.ts
new file mode 100644
index 0000000..cd895f1
--- /dev/null
+++ b/composables/useTutorial.ts
@@ -0,0 +1,259 @@
+import { ref, computed } from 'vue';
+import { useEditorState } from '~/composables/useEditorState';
+import { useLocalStorage } from '@vueuse/core';
+
+export interface TutorialStep {
+ id: string;
+ title: string;
+ description: string;
+ initialCode: string;
+}
+
+export interface TutorialCategory {
+ id: string;
+ name: string;
+ description: string;
+ icon: string;
+ steps: TutorialStep[];
+}
+
+export const TUTORIAL_CATEGORIES: TutorialCategory[] = [
+ {
+ id: 'flowchart',
+ name: 'Flowcharts',
+ description: 'Nodes, edges, and shapes to represent processes.',
+ icon: 'GitCommit',
+ steps: [
+ {
+ id: 'flowchart-1',
+ title: 'Flowchart 101: Basics',
+ description: `Welcome to the interactive Mermaid tutorial! 🎓\n\nFlowcharts start with a declaration like \`flowchart TD\` (Top-Down) or \`LR\` (Left-Right).\n\nYou define nodes using an ID, optionally followed by text in brackets, e.g., \`A[Start]\`.\n\nYou connect nodes using arrows like \`-->\`.\n\n**Task**: Add a new line connecting \`Start\` to a new node \`B[End]\` using \`-->\`.`,
+ initialCode: `flowchart LR\n A[Start]\n `,
+ },
+ {
+ id: 'flowchart-2',
+ title: 'Flowchart 102: Shapes',
+ description: `You can change the shape of nodes by using different brackets:\n- \`[Rectangle]\`\n- \`(Rounded Rectangle)\`\n- \`([Stadium])\`\n- \`{Diamond / Decision}\`\n- \`((Circle))\`\n\n**Task**: Add a Decision node. Connect \`A\` to \`C{Is it true?}\`.`,
+ initialCode: `flowchart TD\n A((Start)) --> B[Process]\n \n %% Add your decision node here\n `,
+ },
+ {
+ id: 'flowchart-3',
+ title: 'Flowchart 103: Styling Links',
+ description: `Links can have text labels or different styles:\n- \`-- Text --- >\` or \`-->|Text|\` for labels\n- \`-.->\` for dotted links\n- \`==>\` for thick links\n\n**Task**: Change the link between \`A\` and \`B\` to a dotted link \`-.->\` and add a label to the link to \`C\`.`,
+ initialCode: `flowchart LR\n A[Start] --> B[Option 1]\n A -->|Label| C[Option 2]\n `,
+ }
+ ]
+ },
+ {
+ id: 'sequence',
+ name: 'Sequence Diagrams',
+ description: 'Interactions between processes over time.',
+ icon: 'ArrowRightLeft',
+ steps: [
+ {
+ id: 'sequence-1',
+ title: 'Sequence 101: Participants',
+ description: `Sequence diagrams show interactions over time. Start with \`sequenceDiagram\`.\n\nEntities are defined with \`participant\` or \`actor\`.\n\nSend messages using:\n- \`->>\` (solid line, arrow)\n- \`-->>\` (dotted line, arrow)\n\n**Task**: Make frontend send a \`Login Request\` message to backend: \`Frontend->>Backend: Login Request\`.`,
+ initialCode: `sequenceDiagram\n actor User\n participant Frontend\n participant Backend\n \n User->>Frontend: Click Login\n `,
+ },
+ {
+ id: 'sequence-2',
+ title: 'Sequence 102: Activations',
+ description: `You can show when a participant is active using \`activate\` and \`deactivate\`, or by appending \`+\` / \`-\` to the arrow.\n\nYou can also add notes: \`Note over Actor: text\`.\n\n**Task**: Add a note over the Database stating "Validating credentials".`,
+ initialCode: `sequenceDiagram\n participant Backend\n participant Database\n \n Backend->>+Database: Query User\n Database-->>-Backend: Response\n `,
+ },
+ {
+ id: 'sequence-3',
+ title: 'Sequence 103: Loops & Alt',
+ description: `Use \`loop\` for repetition and \`alt\`/\`else\` for logic.\n\n\`\`\`\nalt is Success\n A-->>B: OK\nelse is Failure\n A-->>B: Error\nend\n\`\`\`\n\n**Task**: Add an \`else\` block handling the failure case sending "Invalid Credentials" back to the user.`,
+ initialCode: `sequenceDiagram\n actor User\n participant Auth\n \n User->>Auth: Login\n alt is Valid\n Auth-->>User: Token\n end\n `,
+ }
+ ]
+ },
+ {
+ id: 'state',
+ name: 'State Diagrams',
+ description: 'State machines and transitions.',
+ icon: 'CircleDot',
+ steps: [
+ {
+ id: 'state-1',
+ title: 'State 101: Basics',
+ description: `State diagrams describe how a system moves from one state to another.\n\nThey start with \`stateDiagram-v2\`.\nThe special syntax \`[*]\` indicates the start and end of the state machine.\n\n**Task**: Add a transition from \`Idle\` to \`Processing\` triggered by a \`Task added\` event: \`Idle --> Processing : Task added\`.`,
+ initialCode: `stateDiagram-v2\n [*] --> Idle\n Processing --> [*]\n `,
+ },
+ {
+ id: 'state-2',
+ title: 'State 102: Nested States',
+ description: `States can contain other states.\nUse \`state StateName {\` to define boundaries.\n\n**Task**: Inside the \`Active\` state, add two internal states: \`Reading\` and \`Writing\`, with a transition between them.`,
+ initialCode: `stateDiagram-v2\n [*] --> Active\n \n state Active {\n [*] --> Reading\n }\n `,
+ }
+ ]
+ },
+ {
+ id: 'class',
+ name: 'Class Diagrams',
+ description: 'Object-oriented structures and relationships.',
+ icon: 'Network',
+ steps: [
+ {
+ id: 'class-1',
+ title: 'Class 101: Basics',
+ description: `Class diagrams define object-oriented structures.\n\nDefine an attribute by listing its type and name (\`+String title\`). Define a method with parentheses (\`+save()\`).\n\n**Task**: Add a \`logout()\` method to the \`User\` class.`,
+ initialCode: `classDiagram\n class User {\n +String username\n +login()\n }\n class Admin {\n +banUser()\n }\n `,
+ },
+ {
+ id: 'class-2',
+ title: 'Class 102: Relationships',
+ description: `Relationships connect classes:\n- \`<|--\` (Inheritance)\n- \`*--\` (Composition)\n- \`o--\` (Aggregation)\n- \`-->\` (Association)\n\n**Task**: Make \`Admin\` inherit from \`User\` by adding \`User <|-- Admin\`.`,
+ initialCode: `classDiagram\n class User\n class Admin\n \n %% Add inheritance here\n `,
+ }
+ ]
+ },
+ {
+ id: 'er',
+ name: 'Entity Relationship',
+ description: 'Database schemas and data models.',
+ icon: 'Database',
+ steps: [
+ {
+ id: 'er-1',
+ title: 'ER 101: Entities & Attributes',
+ description: `ER diagrams start with \`erDiagram\`.\nDefine entities and their attributes inside curly braces.\n\n**Task**: Add an \`email\` attribute of type \`string\` to the \`USER\` entity.`,
+ initialCode: `erDiagram\n USER {\n int id PK\n string name\n }\n `,
+ },
+ {
+ id: 'er-2',
+ title: 'ER 102: Relationships',
+ description: `Define relationships like \`Entity1 ||--o{ Entity2 : "Label"\`.\n- \`||\` exactly one\n- \`}o\` zero or more\n- \`|o\` zero or one\n- \`}|\` one or more\n\n**Task**: Connect \`USER\` to \`POST\` with a one-to-many relationship (one user has zero or more posts): \`USER ||--o{ POST : writes\`.`,
+ initialCode: `erDiagram\n USER {\n int id PK\n }\n POST {\n int id PK\n int user_id FK\n }\n \n %% Add your relationship here\n `,
+ }
+ ]
+ }
+];
+
+// Composable State
+const isTutorialActive = ref(false);
+const activeCategoryIndex = useLocalStorage('graphlet-tutorial-category', -1);
+const activeStepIndexes = useLocalStorage>('graphlet-tutorial-steps', {});
+const completedCategories = useLocalStorage('graphlet-tutorial-completed-categories', []);
+
+export const useTutorial = () => {
+ const { code, title, eyebrow, badges } = useEditorState();
+
+ const activeCategory = computed(() => {
+ if (activeCategoryIndex.value >= 0 && activeCategoryIndex.value < TUTORIAL_CATEGORIES.length) {
+ return TUTORIAL_CATEGORIES[activeCategoryIndex.value];
+ }
+ return null;
+ });
+
+ const currentStepIndex = computed({
+ get: () => {
+ if (!activeCategory.value) return 0;
+ return activeStepIndexes.value[activeCategory.value.id] || 0;
+ },
+ set: (val) => {
+ if (activeCategory.value) {
+ activeStepIndexes.value = {
+ ...activeStepIndexes.value,
+ [activeCategory.value.id]: val
+ };
+ }
+ }
+ });
+
+ const currentStep = computed(() => {
+ if (!activeCategory.value) return null;
+ return activeCategory.value.steps[currentStepIndex.value];
+ });
+
+ const totalSteps = computed(() => {
+ if (!activeCategory.value) return 0;
+ return activeCategory.value.steps.length;
+ });
+
+ const progress = computed(() => {
+ if (totalSteps.value === 0) return 0;
+ return ((currentStepIndex.value + 1) / totalSteps.value) * 100;
+ });
+
+ const startTutorial = () => {
+ isTutorialActive.value = true;
+ if (activeCategoryIndex.value !== -1) {
+ applyStepCode(currentStepIndex.value);
+ }
+ };
+
+ const stopTutorial = () => {
+ isTutorialActive.value = false;
+ };
+
+ const selectCategory = (index: number) => {
+ activeCategoryIndex.value = index;
+ applyStepCode(currentStepIndex.value);
+ };
+
+ const backToMenu = () => {
+ activeCategoryIndex.value = -1;
+ };
+
+ const applyStepCode = (index: number) => {
+ if (!activeCategory.value) return;
+ const step = activeCategory.value.steps[index];
+ if (step) {
+ code.value = step.initialCode;
+ title.value = step.title;
+ eyebrow.value = 'Tutorial Mode - ' + activeCategory.value.name;
+ badges.value = ['Learn', 'Mermaid'];
+ }
+ };
+
+ const nextStep = () => {
+ if (!activeCategory.value) return;
+
+ if (currentStepIndex.value < totalSteps.value - 1) {
+ currentStepIndex.value++;
+ applyStepCode(currentStepIndex.value);
+ } else {
+ if (!completedCategories.value.includes(activeCategory.value.id)) {
+ completedCategories.value.push(activeCategory.value.id);
+ }
+ backToMenu();
+ }
+ };
+
+ const prevStep = () => {
+ if (currentStepIndex.value > 0) {
+ currentStepIndex.value--;
+ applyStepCode(currentStepIndex.value);
+ }
+ };
+
+ const resetCategoryProgress = () => {
+ if (!activeCategory.value) return;
+ currentStepIndex.value = 0;
+ completedCategories.value = completedCategories.value.filter(id => id !== activeCategory.value?.id);
+ applyStepCode(0);
+ };
+
+ return {
+ isTutorialActive,
+ activeCategoryIndex,
+ completedCategories,
+ activeCategory,
+ currentStepIndex,
+ currentStep,
+ totalSteps,
+ progress,
+ categories: TUTORIAL_CATEGORIES,
+
+ startTutorial,
+ stopTutorial,
+ selectCategory,
+ backToMenu,
+ nextStep,
+ prevStep,
+ resetCategoryProgress
+ };
+};
diff --git a/pages/index.vue b/pages/index.vue
index 2c0d676..01cc8a6 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -12,6 +12,7 @@ import TheShareModal from '~/components/TheShareModal.vue';
import TheWelcome from '~/components/TheWelcome.vue';
import TheKeyboardShortcuts from '~/components/TheKeyboardShortcuts.vue';
import TheDiagramSidebar from '~/components/TheDiagramSidebar.vue';
+import TheTutorialPanel from '~/components/TheTutorialPanel.vue';
const { width } = useWindowSize();
const { currentTheme, isInfoOpen, isWelcomeOpen, isShortcutsOpen } = useEditorState();
@@ -140,6 +141,7 @@ onUnmounted(() => {
+