- 前端页面标题、登录/注册页、左侧栏、聊天头部、教程欢迎语全部改为 Astrion - CLI 欢迎面板与状态栏改为 Astrion CLI - 后端启动日志与 setup 向导改为 Astrion - package.json 与 README 标题同步更新 - 主 system prompt 增加 Astrion 自我介绍,视觉模型 prompt 文件名从 qwenvl 改为 vl - 项目记忆与 AGENTS.md/CLAUDE.md 新增产品名说明
750 lines
21 KiB
TypeScript
750 lines
21 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import { useModelStore } from './model';
|
|
|
|
export type TutorialPlacement = 'auto' | 'top' | 'right' | 'bottom' | 'left' | 'center';
|
|
export type TutorialStepMode = 'info' | 'must_click';
|
|
export type TutorialCondition =
|
|
| 'model_supports_media'
|
|
| 'is_app_shell'
|
|
| 'is_mobile_viewport'
|
|
| 'not_mobile_viewport';
|
|
|
|
export interface TutorialStep {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
target: string | null;
|
|
mode: TutorialStepMode;
|
|
autoClick?: boolean;
|
|
autoOutsideClick?: boolean;
|
|
placement?: TutorialPlacement;
|
|
condition?: TutorialCondition;
|
|
}
|
|
|
|
const STORAGE_COMPLETED_KEY = 'agents_tutorial_completed_v1';
|
|
const STORAGE_VERSION_KEY = 'agents_tutorial_version';
|
|
const TUTORIAL_VERSION = 'v1';
|
|
|
|
const DEFAULT_STEPS: TutorialStep[] = [
|
|
{
|
|
id: 'welcome',
|
|
title: '欢迎使用 Astrion',
|
|
description: '我们将用 2-3 分钟带你快速了解核心功能。',
|
|
target: null,
|
|
mode: 'info',
|
|
placement: 'center'
|
|
},
|
|
{
|
|
id: 'sidebar-conversations-open',
|
|
title: '展开对话记录',
|
|
description: '下一步会自动点击展开对话记录。',
|
|
target: '[data-tutorial="conversation-menu"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'sidebar-conversations-close',
|
|
title: '折叠对话记录',
|
|
description: '下一步会自动点击折叠按钮收起对话记录。',
|
|
target: '[data-tutorial="conversation-collapse"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'sidebar-new-chat',
|
|
title: '新建对话',
|
|
description: '下一步会自动新建并进入一个对话。',
|
|
target: '[data-tutorial="quick-new-conversation"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'sidebar-workspace-toggle',
|
|
title: '工作区折叠',
|
|
description: '显示/隐藏左侧工作区面板。',
|
|
target: '[data-tutorial="workspace-toggle"]',
|
|
mode: 'info',
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'sidebar-monitor-toggle',
|
|
title: '虚拟显示器',
|
|
description: '切换到虚拟显示器模式。',
|
|
target: '[data-tutorial="monitor-toggle"]',
|
|
mode: 'info',
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'workspace-panel-switch',
|
|
title: '工作区面板切换',
|
|
description: '下一步会自动展开面板切换菜单。',
|
|
target: '[data-tutorial="panel-menu-toggle"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'workspace-panel-options',
|
|
title: '四合一面板',
|
|
description: '这里可以切换文件 / 待办 / 子智能体 / 后台指令。',
|
|
target: '[data-tutorial="panel-menu"]',
|
|
mode: 'info',
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'workspace-mode-indicator',
|
|
title: '思考模式',
|
|
description: '点击切换快速 / 思考 / 深度思考。',
|
|
target: '[data-tutorial="run-mode-indicator"]',
|
|
mode: 'info',
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'workspace-connection-indicator',
|
|
title: '连接状态指示灯',
|
|
description: '绿色表示连接正常;红色表示与后端断开连接。',
|
|
target: '[data-tutorial="connection-indicator"]',
|
|
mode: 'info',
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'header-model-selector',
|
|
title: '模型与模式选择',
|
|
description: '下一步会自动展开模型与运行模式弹窗。',
|
|
target: '[data-tutorial="header-model-selector"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'header-model-options',
|
|
title: '模型列表',
|
|
description: '这里是可用模型列表。',
|
|
target: '[data-tutorial="header-model-options"]',
|
|
mode: 'info',
|
|
placement: 'bottom',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'header-runmode-options',
|
|
title: '运行模式列表',
|
|
description: '这里可切换快速 / 思考 / 深度思考。',
|
|
target: '[data-tutorial="header-runmode-options"]',
|
|
mode: 'info',
|
|
placement: 'bottom',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'header-menu-close',
|
|
title: '关闭模型菜单',
|
|
description: '下一步会自动点击空白区域关闭菜单。',
|
|
target: '.model-mode-dropdown',
|
|
mode: 'info',
|
|
autoOutsideClick: true,
|
|
placement: 'bottom',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-open-conversation',
|
|
title: '打开菜单',
|
|
description: '下一步会自动打开左上角菜单。',
|
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-conversation',
|
|
title: '对话记录',
|
|
description: '下一步会自动进入对话记录。',
|
|
target: '[data-tutorial="mobile-menu-conversation"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-conversation-close',
|
|
title: '关闭对话记录',
|
|
description: '下一步会自动关闭对话记录面板。',
|
|
target: '[data-tutorial="conversation-collapse"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-open-workspace',
|
|
title: '再次打开菜单',
|
|
description: '下一步会自动打开菜单。',
|
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-workspace',
|
|
title: '工作文件',
|
|
description: '下一步会自动进入工作文件。',
|
|
target: '[data-tutorial="mobile-menu-workspace"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-workspace-panel-switch',
|
|
title: '工作区面板切换',
|
|
description: '下一步会自动展开切换弹窗。',
|
|
target: '[data-tutorial="panel-menu-toggle"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-workspace-panel-options',
|
|
title: '切换弹窗选项',
|
|
description: '这里可切换文件 / 待办 / 子智能体 / 后台指令。',
|
|
target: '[data-tutorial="panel-menu"]',
|
|
mode: 'info',
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-workspace-close',
|
|
title: '关闭工作文件',
|
|
description: '下一步会自动关闭工作文件面板。',
|
|
target: '[data-tutorial="mobile-workspace-close"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-open-newchat',
|
|
title: '再次打开菜单',
|
|
description: '下一步会自动打开菜单。',
|
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-new-chat',
|
|
title: '新建对话',
|
|
description: '下一步会自动新建对话。',
|
|
target: '[data-tutorial="mobile-menu-new-chat"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-model-selector-open',
|
|
title: '模型与思考模式',
|
|
description: '下一步会自动展开手机端模型/思考模式选择。',
|
|
target: '[data-tutorial="header-model-selector-mobile"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-model-options',
|
|
title: '模型列表',
|
|
description: '这里是手机端可选模型。',
|
|
target: '[data-tutorial="header-model-options"]',
|
|
mode: 'info',
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-runmode-options',
|
|
title: '思考模式列表',
|
|
description: '这里可切换快速 / 思考 / 深度思考。',
|
|
target: '[data-tutorial="header-runmode-options"]',
|
|
mode: 'info',
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-model-selector-close',
|
|
title: '关闭选择菜单',
|
|
description: '下一步会自动点击空白区域关闭菜单。',
|
|
target: '.model-mode-dropdown',
|
|
mode: 'info',
|
|
autoOutsideClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'open-quick-menu',
|
|
title: '打开 + 菜单',
|
|
description: '将自动为你展开快捷菜单。',
|
|
target: '[data-tutorial="quick-menu-open"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'top'
|
|
},
|
|
{
|
|
id: 'quick-upload',
|
|
title: '上传文件',
|
|
description: '上传本地文件到当前工作区。',
|
|
target: '[data-tutorial="quick-upload"]',
|
|
mode: 'info',
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'quick-review',
|
|
title: '对话回顾',
|
|
description: '回顾并压缩上下文。',
|
|
target: '[data-tutorial="quick-review"]',
|
|
mode: 'info',
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'quick-send-image',
|
|
title: '发送图片',
|
|
description: '当前模型支持时,可发送图片给 AI 分析。',
|
|
target: '[data-tutorial="quick-send-image"]',
|
|
mode: 'info',
|
|
placement: 'left',
|
|
condition: 'model_supports_media'
|
|
},
|
|
{
|
|
id: 'quick-send-video',
|
|
title: '发送视频',
|
|
description: '当前模型支持时,可发送视频给 AI 分析。',
|
|
target: '[data-tutorial="quick-send-video"]',
|
|
mode: 'info',
|
|
placement: 'left',
|
|
condition: 'model_supports_media'
|
|
},
|
|
{
|
|
id: 'quick-tool-disable',
|
|
title: '工具禁用',
|
|
description: '临时禁用工具分类,控制模型行为范围。',
|
|
target: '[data-tutorial="quick-tool-menu"]',
|
|
mode: 'info',
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'quick-settings',
|
|
title: '设置菜单',
|
|
description: '这里可快速访问常用功能。',
|
|
target: '[data-tutorial="quick-settings-menu"]',
|
|
mode: 'info',
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'open-settings-submenu',
|
|
title: '打开设置子菜单',
|
|
description: '下一步会自动展开设置子菜单。',
|
|
target: '[data-tutorial="quick-settings-menu"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'open-token-panel',
|
|
title: '打开用量统计',
|
|
description: '下一步会自动打开用量统计面板。',
|
|
target: '[data-tutorial="settings-token-panel"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'token-panel',
|
|
title: 'Token 统计面板',
|
|
description: '这里可以查看上下文、额度和资源统计。',
|
|
target: '[data-tutorial="token-drawer"]',
|
|
mode: 'info',
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'close-token-panel',
|
|
title: '关闭统计面板',
|
|
description: '下一步会自动关闭统计面板。',
|
|
target: '[data-tutorial="token-close"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'left'
|
|
},
|
|
{
|
|
id: 'open-personal-space',
|
|
title: '打开个人空间',
|
|
description: '下一步会自动打开个人空间。',
|
|
target: '[data-tutorial="open-personal-space"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'not_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-open-personal',
|
|
title: '打开菜单',
|
|
description: '下一步会自动打开菜单。',
|
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'mobile-menu-personal',
|
|
title: '进入个人空间',
|
|
description: '下一步会自动进入个人空间。',
|
|
target: '[data-tutorial="mobile-menu-personal"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom',
|
|
condition: 'is_mobile_viewport'
|
|
},
|
|
{
|
|
id: 'personal-overview',
|
|
title: '个人空间总览',
|
|
description: '这里是个性化设置与高级配置中心。',
|
|
target: '[data-tutorial="personal-card"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-preferences',
|
|
title: '个性化设置',
|
|
description: '下一步自动切换到“个性化设置”。',
|
|
target: '[data-tutorial="personal-tab-preferences"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-preferences',
|
|
title: '个性化设置内容',
|
|
description: '可配置昵称、语气、职业和必备信息。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-model',
|
|
title: '模型偏好',
|
|
description: '下一步自动切换到“模型偏好”。',
|
|
target: '[data-tutorial="personal-tab-model"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-model',
|
|
title: '模型偏好内容',
|
|
description: '可设置默认模型、默认运行模式和思考频率。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-usage',
|
|
title: '用量统计',
|
|
description: '下一步自动切换到“用量统计”。',
|
|
target: '[data-tutorial="personal-tab-usage"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-usage',
|
|
title: '用量统计内容',
|
|
description: '可查看累计 Token、对话数和工具调用数据。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-app-update',
|
|
title: '软件更新',
|
|
description: '移动端将自动切换到“软件更新”。',
|
|
target: '[data-tutorial="personal-tab-app-update"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right',
|
|
condition: 'is_app_shell'
|
|
},
|
|
{
|
|
id: 'page-app-update',
|
|
title: '软件更新内容',
|
|
description: '这里会展示最新版本和更新说明。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right',
|
|
condition: 'is_app_shell'
|
|
},
|
|
{
|
|
id: 'tab-behavior',
|
|
title: '智能体行为',
|
|
description: '下一步自动切换到“智能体行为”。',
|
|
target: '[data-tutorial="personal-tab-behavior"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-behavior',
|
|
title: '智能体行为内容',
|
|
description: '可配置工具显示、压缩策略等高级选项。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-skills',
|
|
title: 'Skills',
|
|
description: '下一步自动切换到“Skills”。',
|
|
target: '[data-tutorial="personal-tab-skills"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-skills',
|
|
title: 'Skills 内容',
|
|
description: '可启用/禁用技能并同步到工作区。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-image',
|
|
title: '图片压缩',
|
|
description: '下一步自动切换到“图片压缩”。',
|
|
target: '[data-tutorial="personal-tab-image"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-image',
|
|
title: '图片压缩内容',
|
|
description: '可选择原图、1080p、720p、540p 压缩策略。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'tab-theme',
|
|
title: '主题切换',
|
|
description: '下一步自动切换到“主题切换”。',
|
|
target: '[data-tutorial="personal-tab-theme"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'page-theme',
|
|
title: '主题切换内容',
|
|
description: '可在经典、明亮、夜间主题间切换。',
|
|
target: '[data-tutorial="personal-content-shell"]',
|
|
mode: 'info',
|
|
placement: 'right'
|
|
},
|
|
{
|
|
id: 'close-personal-space',
|
|
title: '关闭个人空间',
|
|
description: '下一步自动关闭个人空间并结束导览。',
|
|
target: '[data-tutorial="personal-close"]',
|
|
mode: 'info',
|
|
autoClick: true,
|
|
placement: 'bottom'
|
|
},
|
|
{
|
|
id: 'done',
|
|
title: '教程完成!',
|
|
description: '恭喜你完成新手教程。可随时在个人空间「新手教程」再次查看。',
|
|
target: null,
|
|
mode: 'info',
|
|
placement: 'center'
|
|
}
|
|
];
|
|
|
|
interface TutorialState {
|
|
running: boolean;
|
|
completed: boolean;
|
|
currentIndex: number;
|
|
steps: TutorialStep[];
|
|
activeSelector: string | null;
|
|
}
|
|
|
|
const detectAppShell = () => {
|
|
if (typeof window === 'undefined') return false;
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.has('app_shell')) return true;
|
|
return Boolean((window as any)?.AndroidThemeBridge);
|
|
};
|
|
|
|
const detectMobileViewport = () => {
|
|
if (typeof window === 'undefined') return false;
|
|
return window.innerWidth <= 768;
|
|
};
|
|
|
|
const shouldIncludeStepByCondition = (step: TutorialStep): boolean => {
|
|
if (!step.condition) return true;
|
|
if (step.condition === 'is_app_shell') {
|
|
return detectAppShell();
|
|
}
|
|
if (step.condition === 'model_supports_media') {
|
|
const modelStore = useModelStore();
|
|
const current = modelStore.currentModel as any;
|
|
return !!(current?.supportsImage || current?.supportsVideo);
|
|
}
|
|
if (step.condition === 'is_mobile_viewport') {
|
|
return detectMobileViewport();
|
|
}
|
|
if (step.condition === 'not_mobile_viewport') {
|
|
return !detectMobileViewport();
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const readCompletedState = () => {
|
|
if (typeof window === 'undefined' || !window.localStorage) {
|
|
return false;
|
|
}
|
|
try {
|
|
const version = window.localStorage.getItem(STORAGE_VERSION_KEY);
|
|
if (version !== TUTORIAL_VERSION) {
|
|
return false;
|
|
}
|
|
return window.localStorage.getItem(STORAGE_COMPLETED_KEY) === '1';
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const persistCompletedState = (completed: boolean) => {
|
|
if (typeof window === 'undefined' || !window.localStorage) {
|
|
return;
|
|
}
|
|
try {
|
|
window.localStorage.setItem(STORAGE_VERSION_KEY, TUTORIAL_VERSION);
|
|
window.localStorage.setItem(STORAGE_COMPLETED_KEY, completed ? '1' : '0');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
};
|
|
|
|
export const useTutorialStore = defineStore('tutorial', {
|
|
state: (): TutorialState => ({
|
|
running: false,
|
|
completed: readCompletedState(),
|
|
currentIndex: 0,
|
|
steps: DEFAULT_STEPS,
|
|
activeSelector: null
|
|
}),
|
|
getters: {
|
|
activeStep(state): TutorialStep | null {
|
|
return state.steps[state.currentIndex] || null;
|
|
},
|
|
totalVisibleSteps(state): number {
|
|
return state.steps.filter((step) => shouldIncludeStepByCondition(step)).length;
|
|
},
|
|
visibleStepIndex(state): number {
|
|
if (!state.running) return 0;
|
|
let count = 0;
|
|
for (let i = 0; i <= state.currentIndex; i += 1) {
|
|
const step = state.steps[i];
|
|
if (step && shouldIncludeStepByCondition(step)) {
|
|
count += 1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
},
|
|
actions: {
|
|
shouldIncludeStep(step: TutorialStep): boolean {
|
|
return shouldIncludeStepByCondition(step);
|
|
},
|
|
startTutorial() {
|
|
this.running = true;
|
|
this.completed = false;
|
|
this.currentIndex = 0;
|
|
this.activeSelector = null;
|
|
persistCompletedState(false);
|
|
this.skipHiddenSteps('forward');
|
|
},
|
|
exitTutorial() {
|
|
this.running = false;
|
|
this.activeSelector = null;
|
|
},
|
|
finishTutorial() {
|
|
this.running = false;
|
|
this.completed = true;
|
|
this.activeSelector = null;
|
|
persistCompletedState(true);
|
|
},
|
|
goNext() {
|
|
if (!this.running) return;
|
|
if (this.currentIndex >= this.steps.length - 1) {
|
|
this.finishTutorial();
|
|
return;
|
|
}
|
|
this.currentIndex += 1;
|
|
this.skipHiddenSteps('forward');
|
|
if (this.currentIndex >= this.steps.length - 1 && this.activeStep?.id === 'done') {
|
|
this.completed = true;
|
|
}
|
|
},
|
|
goPrev() {
|
|
if (!this.running) return;
|
|
if (this.currentIndex <= 0) return;
|
|
this.currentIndex -= 1;
|
|
this.skipHiddenSteps('backward');
|
|
},
|
|
skipCurrentStep() {
|
|
this.goNext();
|
|
},
|
|
markCompleted() {
|
|
this.completed = true;
|
|
persistCompletedState(true);
|
|
},
|
|
skipHiddenSteps(direction: 'forward' | 'backward') {
|
|
if (!this.running) return;
|
|
if (direction === 'forward') {
|
|
while (
|
|
this.currentIndex < this.steps.length &&
|
|
!this.shouldIncludeStep(this.steps[this.currentIndex])
|
|
) {
|
|
this.currentIndex += 1;
|
|
}
|
|
if (this.currentIndex >= this.steps.length) {
|
|
this.finishTutorial();
|
|
return;
|
|
}
|
|
} else {
|
|
while (this.currentIndex > 0 && !this.shouldIncludeStep(this.steps[this.currentIndex])) {
|
|
this.currentIndex -= 1;
|
|
}
|
|
}
|
|
},
|
|
setActiveSelector(selector: string | null) {
|
|
this.activeSelector = selector;
|
|
}
|
|
}
|
|
});
|