diff --git a/package-lock.json b/package-lock.json
index 0c139c4..6dad754 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,9 +17,11 @@
"@saehrimnir/druidjs": "^0.7.3",
"d3": "^7.9.0",
"d3-force": "^3.0.0",
+ "driver.js": "^1.4.0",
"material-icons": "^1.13.14",
"pyodide": "^0.28.0",
"rxjs": "~7.8.0",
+ "shepherd.js": "^15.2.2",
"three": "^0.176.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -1841,6 +1843,31 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
"node_modules/@gar/promise-retry": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz",
@@ -6836,6 +6863,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/delaunator": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
@@ -6956,6 +6992,12 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
+ "node_modules/driver.js": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.4.0.tgz",
+ "integrity": "sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==",
+ "license": "MIT"
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -11375,6 +11417,19 @@
"node": ">=8"
}
},
+ "node_modules/shepherd.js": {
+ "version": "15.2.2",
+ "resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-15.2.2.tgz",
+ "integrity": "sha512-WDJDjZ8tUG2HF93C9mJCXsdGJ01bpZKjonyMABMkU6I4KjSD9pZ0zFKaeSducdFIx9inKHbDmcFE82uJ65HmgA==",
+ "license": "AGPL-3.0",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.5",
+ "deepmerge-ts": "^7.1.5"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
diff --git a/package.json b/package.json
index bc8d348..2306ea6 100644
--- a/package.json
+++ b/package.json
@@ -24,9 +24,11 @@
"@saehrimnir/druidjs": "^0.7.3",
"d3": "^7.9.0",
"d3-force": "^3.0.0",
+ "driver.js": "^1.4.0",
"material-icons": "^1.13.14",
"pyodide": "^0.28.0",
"rxjs": "~7.8.0",
+ "shepherd.js": "^15.2.2",
"three": "^0.176.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -68,4 +70,4 @@
"prettier --write"
]
}
-}
\ No newline at end of file
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 9c03f0d..0dc7772 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { Component, HostListener, OnInit } from '@angular/core';
+import { Component, HostListener, OnInit, AfterViewInit, ViewChild } from '@angular/core';
import { ConfigService } from './services/config.service';
import { GlyphCanvasComponent } from './canvas/glyph-canvas.component';
import { DataLoaderService } from './services/data-loader.service';
@@ -6,6 +6,10 @@ import { checkTextInput } from './shared/helpers/angular-helper';
import { MenuBarComponent } from './menubar/menubar.component';
import { ToastComponent } from './shared/components/toast/toast.component';
import { SidebarComponent } from './sidebar/sidebar.component';
+import { driver } from 'driver.js';
+import 'driver.js/dist/driver.css';
+import Shepherd from 'shepherd.js';
+import 'shepherd.js/dist/css/shepherd.css';
interface GlyphCanvasItem {
id: number;
@@ -20,8 +24,9 @@ interface GlyphCanvasItem {
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
-export class AppComponent implements OnInit {
+export class AppComponent implements OnInit, AfterViewInit {
title = 'Glyphboard Royale';
+ @ViewChild(GlyphCanvasComponent) glyphCanvas!: GlyphCanvasComponent;
grid: GlyphCanvasItem[] = [];
totalCells = 1;
@@ -52,6 +57,53 @@ export class AppComponent implements OnInit {
});
}
+ ngAfterViewInit() {
+ const driverObj = driver({
+ showProgress: true,
+ onDestroyStarted: () => {
+ this.glyphCanvas.tutorialActive = false;
+ driverObj.destroy();
+ },
+ steps: [
+ {
+ element: '[data-tour="data-menu"]',
+ popover: {
+ title: 'Schritt 1: Datensatz auswählen',
+ description: 'Über dieses Menü kann zwischen Datensätzen gewechselt werden.',
+ side: 'bottom',
+ },
+ },
+ {
+ element: '[data-tour="glyph-type"]',
+ popover: {
+ title: 'Schritt 2: Glyph-Typ wechseln',
+ description: 'Hier kann die Darstellungsform der Glyphen geändert werden.',
+ side: 'left',
+ },
+ },
+ {
+ element: '[data-tour="legend"]',
+ popover: {
+ title: 'Schritt 3: Legende verstehen',
+ description: 'Die Legende zeigt, welche Datenattribute über Farben und Formen dargestellt werden.',
+ side: 'left',
+ },
+ },
+ {
+ element: '[data-tour="legend"]',
+ popover: {
+ title: 'Schritt 4: Filter nutzen',
+ description: 'Nutze die Filter, um bestimmte Datenbereiche gezielt hervorzuheben.',
+ side: 'left',
+ },
+ },
+ ],
+ });
+
+ this.glyphCanvas.tutorialActive = true;
+ driverObj.drive();
+ }
+
getNextFreeId(grid: GlyphCanvasItem[]): number {
const usedIds = new Set(grid.map(item => item.id));
diff --git a/src/app/canvas/glyph-canvas.component.html b/src/app/canvas/glyph-canvas.component.html
index 1503dca..f37fe5d 100644
--- a/src/app/canvas/glyph-canvas.component.html
+++ b/src/app/canvas/glyph-canvas.component.html
@@ -29,7 +29,7 @@
>
| undefined;
private mouseDownTime = 0;
diff --git a/src/app/menubar/menubar.component.html b/src/app/menubar/menubar.component.html
index 49b5094..50a3f4f 100644
--- a/src/app/menubar/menubar.component.html
+++ b/src/app/menubar/menubar.component.html
@@ -20,7 +20,7 @@