Add interactive getting-started tutorial for first-time users
Auto-starts on first visit with a 9-step walkthrough covering header, tabs, settings, search, theme, and language controls. Stores completion in localStorage; restart via ? button in the header. Includes en/ru/zh translations for all tour steps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
openDisplayPicker, closeDisplayPicker, selectDisplay, formatDisplayLabel,
|
||||
} from './features/displays.js';
|
||||
import {
|
||||
startCalibrationTutorial, startDeviceTutorial,
|
||||
startCalibrationTutorial, startDeviceTutorial, startGettingStartedTutorial,
|
||||
closeTutorial, tutorialNext, tutorialPrev,
|
||||
} from './features/tutorials.js';
|
||||
|
||||
@@ -174,6 +174,7 @@ Object.assign(window, {
|
||||
// tutorials
|
||||
startCalibrationTutorial,
|
||||
startDeviceTutorial,
|
||||
startGettingStartedTutorial,
|
||||
closeTutorial,
|
||||
tutorialNext,
|
||||
tutorialPrev,
|
||||
@@ -502,4 +503,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Start global events WebSocket and auto-refresh
|
||||
startEventsWS();
|
||||
startAutoRefresh();
|
||||
|
||||
// Show getting-started tutorial on first visit
|
||||
if (!localStorage.getItem('tour_completed')) {
|
||||
setTimeout(() => startGettingStartedTutorial(), 600);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,6 +19,20 @@ const calibrationTutorialSteps = [
|
||||
{ selector: '#cal-skip-end', textKey: 'calibration.tip.skip_leds_end', position: 'top' }
|
||||
];
|
||||
|
||||
const TOUR_KEY = 'tour_completed';
|
||||
|
||||
const gettingStartedSteps = [
|
||||
{ selector: 'header .header-title', textKey: 'tour.welcome', position: 'bottom' },
|
||||
{ selector: '#tab-btn-dashboard', textKey: 'tour.dashboard', position: 'bottom' },
|
||||
{ selector: '#tab-btn-targets', textKey: 'tour.targets', position: 'bottom' },
|
||||
{ selector: '#tab-btn-streams', textKey: 'tour.sources', position: 'bottom' },
|
||||
{ selector: '#tab-btn-profiles', textKey: 'tour.profiles', position: 'bottom' },
|
||||
{ selector: '[onclick*="openSettingsModal"]', textKey: 'tour.settings', position: 'bottom' },
|
||||
{ selector: '[onclick*="openCommandPalette"]', textKey: 'tour.search', position: 'bottom' },
|
||||
{ selector: '.theme-toggle', textKey: 'tour.theme', position: 'bottom' },
|
||||
{ selector: '#locale-select', textKey: 'tour.language', position: 'bottom' }
|
||||
];
|
||||
|
||||
const deviceTutorialSteps = [
|
||||
{ selector: '.card-subtitle', textKey: 'device.tip.metadata', position: 'bottom' },
|
||||
{ selector: '.brightness-control', textKey: 'device.tip.brightness', position: 'bottom' },
|
||||
@@ -40,7 +54,8 @@ export function startTutorial(config) {
|
||||
mode: config.mode,
|
||||
step: 0,
|
||||
resolveTarget: config.resolveTarget,
|
||||
container: config.container
|
||||
container: config.container,
|
||||
onClose: config.onClose || null
|
||||
});
|
||||
|
||||
overlay.classList.add('active');
|
||||
@@ -86,8 +101,20 @@ export function startDeviceTutorial(deviceId) {
|
||||
});
|
||||
}
|
||||
|
||||
export function startGettingStartedTutorial() {
|
||||
startTutorial({
|
||||
steps: gettingStartedSteps,
|
||||
overlayId: 'getting-started-overlay',
|
||||
mode: 'fixed',
|
||||
container: null,
|
||||
resolveTarget: (step) => document.querySelector(step.selector) || null,
|
||||
onClose: () => localStorage.setItem(TOUR_KEY, '1')
|
||||
});
|
||||
}
|
||||
|
||||
export function closeTutorial() {
|
||||
if (!activeTutorial) return;
|
||||
const onClose = activeTutorial.onClose;
|
||||
activeTutorial.overlay.classList.remove('active');
|
||||
document.querySelectorAll('.tutorial-target').forEach(el => {
|
||||
el.classList.remove('tutorial-target');
|
||||
@@ -95,6 +122,7 @@ export function closeTutorial() {
|
||||
});
|
||||
document.removeEventListener('keydown', handleTutorialKey);
|
||||
setActiveTutorial(null);
|
||||
if (onClose) onClose();
|
||||
}
|
||||
|
||||
export function tutorialNext() {
|
||||
|
||||
@@ -214,6 +214,16 @@
|
||||
"calibration.tip.border_width": "How many pixels from the screen edge to sample for LED colors",
|
||||
"calibration.tip.skip_leds_start": "Skip LEDs at the start of the strip — skipped LEDs stay off",
|
||||
"calibration.tip.skip_leds_end": "Skip LEDs at the end of the strip — skipped LEDs stay off",
|
||||
"tour.welcome": "Welcome to LED Grab! This quick tour will show you around the interface. Use arrow keys or buttons to navigate.",
|
||||
"tour.dashboard": "Dashboard — live overview of running targets, profiles, and device health at a glance.",
|
||||
"tour.targets": "Targets — add WLED devices, configure LED targets with capture settings and calibration.",
|
||||
"tour.sources": "Sources — manage capture templates, picture sources, audio sources, and color strips.",
|
||||
"tour.profiles": "Profiles — group targets and automate switching with time, audio, or value conditions.",
|
||||
"tour.settings": "Settings — backup and restore configuration, manage auto-backups.",
|
||||
"tour.search": "Search — quickly find and navigate to any entity with Ctrl+K.",
|
||||
"tour.theme": "Theme — switch between dark and light mode.",
|
||||
"tour.language": "Language — choose your preferred interface language.",
|
||||
"tour.restart": "Restart tutorial",
|
||||
"calibration.tutorial.start": "Start tutorial",
|
||||
"calibration.overlay_toggle": "Overlay",
|
||||
"calibration.start_position": "Starting Position:",
|
||||
|
||||
@@ -214,6 +214,16 @@
|
||||
"calibration.tip.border_width": "Сколько пикселей от края экрана использовать для цветов LED",
|
||||
"calibration.tip.skip_leds_start": "Пропуск LED в начале ленты — пропущенные LED остаются выключенными",
|
||||
"calibration.tip.skip_leds_end": "Пропуск LED в конце ленты — пропущенные LED остаются выключенными",
|
||||
"tour.welcome": "Добро пожаловать в LED Grab! Этот краткий тур познакомит вас с интерфейсом. Используйте стрелки или кнопки для навигации.",
|
||||
"tour.dashboard": "Дашборд — обзор запущенных целей, профилей и состояния устройств.",
|
||||
"tour.targets": "Цели — добавляйте WLED-устройства, настраивайте LED-цели с захватом и калибровкой.",
|
||||
"tour.sources": "Источники — управление шаблонами захвата, источниками изображений, звука и цветовых полос.",
|
||||
"tour.profiles": "Профили — группируйте цели и автоматизируйте переключение по расписанию, звуку или значениям.",
|
||||
"tour.settings": "Настройки — резервное копирование и восстановление конфигурации.",
|
||||
"tour.search": "Поиск — быстрый поиск и переход к любому объекту по Ctrl+K.",
|
||||
"tour.theme": "Тема — переключение между тёмной и светлой темой.",
|
||||
"tour.language": "Язык — выберите предпочитаемый язык интерфейса.",
|
||||
"tour.restart": "Запустить тур заново",
|
||||
"calibration.tutorial.start": "Начать обучение",
|
||||
"calibration.overlay_toggle": "Оверлей",
|
||||
"calibration.start_position": "Начальная Позиция:",
|
||||
|
||||
@@ -214,6 +214,16 @@
|
||||
"calibration.tip.border_width": "从屏幕边缘采样多少像素来确定 LED 颜色",
|
||||
"calibration.tip.skip_leds_start": "跳过灯带起始端的 LED — 被跳过的 LED 保持关闭",
|
||||
"calibration.tip.skip_leds_end": "跳过灯带末尾端的 LED — 被跳过的 LED 保持关闭",
|
||||
"tour.welcome": "欢迎使用 LED Grab!快速导览将带您了解界面。使用方向键或按钮进行导航。",
|
||||
"tour.dashboard": "仪表盘 — 实时查看运行中的目标、配置文件和设备状态。",
|
||||
"tour.targets": "目标 — 添加 WLED 设备,配置 LED 目标的捕获设置和校准。",
|
||||
"tour.sources": "来源 — 管理捕获模板、图片来源、音频来源和色带。",
|
||||
"tour.profiles": "配置文件 — 将目标分组,并通过时间、音频或数值条件自动切换。",
|
||||
"tour.settings": "设置 — 备份和恢复配置,管理自动备份。",
|
||||
"tour.search": "搜索 — 使用 Ctrl+K 快速查找并导航到任意实体。",
|
||||
"tour.theme": "主题 — 在深色和浅色模式之间切换。",
|
||||
"tour.language": "语言 — 选择您偏好的界面语言。",
|
||||
"tour.restart": "重新开始导览",
|
||||
"calibration.tutorial.start": "开始教程",
|
||||
"calibration.overlay_toggle": "叠加层",
|
||||
"calibration.start_position": "起始位置:",
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
</div>
|
||||
<div class="server-info">
|
||||
<a href="/docs" target="_blank" class="header-link" data-i18n-title="app.api_docs" title="API Docs">API</a>
|
||||
<button class="search-toggle" id="tour-restart-btn" onclick="startGettingStartedTutorial()" data-i18n-title="tour.restart" title="Restart tutorial">
|
||||
?
|
||||
</button>
|
||||
<button class="search-toggle" onclick="openCommandPalette()" data-i18n-title="search.open" title="Search (Ctrl+K)">
|
||||
🔍
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
<!-- Getting Started Tutorial Overlay (viewport-level) -->
|
||||
<div id="getting-started-overlay" class="tutorial-overlay tutorial-overlay-fixed" role="dialog" aria-modal="true">
|
||||
<div class="tutorial-backdrop"></div>
|
||||
<div class="tutorial-ring"></div>
|
||||
<div class="tutorial-tooltip">
|
||||
<div class="tutorial-tooltip-header">
|
||||
<span class="tutorial-step-counter"></span>
|
||||
<button class="tutorial-close-btn" onclick="closeTutorial()" data-i18n-aria-label="aria.close">×</button>
|
||||
</div>
|
||||
<p class="tutorial-tooltip-text"></p>
|
||||
<div class="tutorial-tooltip-nav">
|
||||
<button class="tutorial-prev-btn" onclick="tutorialPrev()" data-i18n-aria-label="aria.previous">←</button>
|
||||
<button class="tutorial-next-btn" onclick="tutorialNext()" data-i18n-aria-label="aria.next">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Tutorial Overlay (viewport-level) -->
|
||||
<div id="device-tutorial-overlay" class="tutorial-overlay tutorial-overlay-fixed" role="dialog" aria-modal="true">
|
||||
<div class="tutorial-backdrop"></div>
|
||||
|
||||
Reference in New Issue
Block a user