mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(onboarding): add fluent/legacy mode selection flow
Expand onboarding wizard with new interactive steps to guide users through UI architecture selection: - Add animated typing intro component with AI-style streaming effect - Add UI mode selection step (Fluent vs Legacy interface styles) - Add Fluent placement configuration (Sideleaves vs Inline layout) - Refactor onboarding flow from 5 to 7 steps with conditional branching - Apply selected architecture preferences to plugin settings - Add visual mode classes and enhanced styling support The new flow allows users to customize their Task Genius experience during initial setup while maintaining backward compatibility with existing configurations.
This commit is contained in:
parent
68247f03be
commit
3fcf2f9705
8 changed files with 1746 additions and 800 deletions
112
src/components/features/onboarding/IntroTyping.ts
Normal file
112
src/components/features/onboarding/IntroTyping.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { t } from "@/translations/helper";
|
||||
|
||||
export class IntroTyping {
|
||||
private timers: number[] = [];
|
||||
|
||||
cleanup() {
|
||||
this.timers.forEach((id) => window.clearTimeout(id));
|
||||
this.timers = [];
|
||||
}
|
||||
|
||||
render(container: HTMLElement) {
|
||||
container.empty();
|
||||
const wrap = container.createDiv({cls: "intro-typing"});
|
||||
|
||||
const line1 = wrap.createEl("h1", {cls: "intro-line intro-line-1"});
|
||||
const line2 = wrap.createEl("h2", {cls: "intro-line intro-line-2"});
|
||||
const line3 = wrap.createEl("p", {cls: "intro-line intro-line-3"});
|
||||
const line4 = wrap.createEl("p", {cls: "intro-line intro-line-4"});
|
||||
|
||||
const seq = [
|
||||
{
|
||||
el: line1,
|
||||
text: t("Hi,"),
|
||||
speed: 35
|
||||
},
|
||||
{
|
||||
el: line2,
|
||||
text: t("Thank you for using Task Genius"),
|
||||
speed: 25
|
||||
},
|
||||
{
|
||||
el: line3,
|
||||
text: t("In the following steps, you will gradually set up Task Genius to get a more suitable environment for you"),
|
||||
speed: 20
|
||||
},
|
||||
{
|
||||
el: line4,
|
||||
text: t("In the current version, Task Genius provides a brand new visual and interactive experience: Fluent; while also providing the option to return to the previous interface. Which one do you prefer?"),
|
||||
speed: 20
|
||||
},
|
||||
];
|
||||
|
||||
this.aiStreamSequence(seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI-style streaming text effect with smooth character fade-in
|
||||
*/
|
||||
private aiStreamSequence(seq: { el: HTMLElement; text: string; speed: number }[]) {
|
||||
const streamLine = (idx: number) => {
|
||||
if (idx >= seq.length) return;
|
||||
const {el, text, speed} = seq[idx];
|
||||
|
||||
// Clear and prepare element
|
||||
el.empty();
|
||||
el.addClass("is-streaming");
|
||||
|
||||
// Create individual character spans for smooth animation
|
||||
const chars: HTMLElement[] = [];
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i];
|
||||
const span = el.createSpan({
|
||||
cls: "intro-char",
|
||||
text: char,
|
||||
});
|
||||
// Special handling for spaces to preserve layout
|
||||
if (char === " ") {
|
||||
span.addClass("intro-char-space");
|
||||
}
|
||||
chars.push(span);
|
||||
}
|
||||
|
||||
// Create cursor element that will follow the characters
|
||||
const cursor = el.createSpan({
|
||||
cls: "intro-cursor",
|
||||
text: "▊",
|
||||
});
|
||||
|
||||
// Animate characters with AI-style streaming effect
|
||||
let charIndex = 0;
|
||||
const animateChar = () => {
|
||||
if (charIndex < chars.length) {
|
||||
const char = chars[charIndex];
|
||||
// Add small random variation for more natural feel
|
||||
const jitter = Math.random() * speed * 0.3;
|
||||
char.addClass("intro-char-visible");
|
||||
|
||||
// Move cursor after the just-revealed character
|
||||
char.after(cursor);
|
||||
|
||||
charIndex++;
|
||||
const id = window.setTimeout(animateChar, speed + jitter);
|
||||
this.timers.push(id);
|
||||
} else {
|
||||
// Line complete, remove cursor and move to next
|
||||
cursor.remove();
|
||||
el.removeClass("is-streaming");
|
||||
el.addClass("stream-complete");
|
||||
// Small delay before next line
|
||||
const id = window.setTimeout(() => streamLine(idx + 1), 300);
|
||||
this.timers.push(id);
|
||||
}
|
||||
};
|
||||
|
||||
// Start animation
|
||||
animateChar();
|
||||
};
|
||||
|
||||
streamLine(0);
|
||||
}
|
||||
}
|
||||
|
||||
66
src/components/features/onboarding/ModeSelection.ts
Normal file
66
src/components/features/onboarding/ModeSelection.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { t } from "@/translations/helper";
|
||||
|
||||
export type UIMode = 'fluent' | 'legacy';
|
||||
|
||||
export class ModeSelection {
|
||||
render(
|
||||
container: HTMLElement,
|
||||
current: UIMode | null,
|
||||
onSelect: (mode: UIMode) => void
|
||||
) {
|
||||
container.empty();
|
||||
|
||||
const section = container.createDiv({ cls: "mode-selection" });
|
||||
section.createEl("h2", { text: t("选择界面风格"), cls: "section-title" });
|
||||
section.createEl("p", {
|
||||
text: t("在下方选择你更喜欢的视觉与交互风格:全新的 Fluent 或 传统的 Legacy"),
|
||||
cls: "section-desc",
|
||||
});
|
||||
|
||||
const options = section.createDiv({ cls: "mode-options" });
|
||||
|
||||
const card = (
|
||||
mode: UIMode,
|
||||
title: string,
|
||||
desc: string,
|
||||
icon: string
|
||||
) => {
|
||||
const el = options.createDiv({ cls: `mode-card tg-v2-card mode-${mode}` });
|
||||
const header = el.createDiv({ cls: "tg-v2-card-header" });
|
||||
const titleEl = header.createDiv({ cls: "tg-v2-card-title" });
|
||||
titleEl.setText(title);
|
||||
const body = el.createDiv({ cls: "mode-card-body" });
|
||||
const preview = body.createDiv({ cls: "mode-card-preview" });
|
||||
preview.setText(" "); // 预览区域占位,后续可替换为图片
|
||||
const description = body.createDiv({ cls: "mode-card-desc" });
|
||||
description.setText(desc);
|
||||
|
||||
if (current === mode) el.addClass("is-selected");
|
||||
el.addEventListener("click", () => onSelect(mode));
|
||||
el.setAttr("tabindex", "0");
|
||||
el.addClass("clickable-icon");
|
||||
return el;
|
||||
};
|
||||
|
||||
card(
|
||||
"fluent",
|
||||
t("Fluent(现代与灵动)"),
|
||||
t("全新的信息架构、导航与交互,配合更优雅的样式与动画。"),
|
||||
"sparkles"
|
||||
);
|
||||
card(
|
||||
"legacy",
|
||||
t("Legacy(经典与稳定)"),
|
||||
t("延续过往界面与交互风格,保持熟悉的使用体验。"),
|
||||
"layout"
|
||||
);
|
||||
|
||||
// 小提示
|
||||
const tips = section.createDiv({ cls: "mode-tips" });
|
||||
tips.createEl("p", {
|
||||
text: t("你可以在设置中随时切换这些选项。我们会尽量保持迁移的平滑与安全。"),
|
||||
cls: "text-muted",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10,13 +10,19 @@ import { UserLevelSelector } from "./UserLevelSelector";
|
|||
import { ConfigPreview } from "./ConfigPreview";
|
||||
import { TaskCreationGuide } from "./TaskCreationGuide";
|
||||
import { OnboardingComplete } from "./OnboardingComplete";
|
||||
import { IntroTyping } from "./IntroTyping";
|
||||
import { ModeSelection } from "./ModeSelection";
|
||||
import { FluentPlacement } from "./FluentPlacement";
|
||||
import "@/experimental/v2/styles/v2-enhanced.css";
|
||||
|
||||
export enum OnboardingStep {
|
||||
WELCOME = 0,
|
||||
USER_LEVEL_SELECT = 1,
|
||||
CONFIG_PREVIEW = 2,
|
||||
TASK_CREATION_GUIDE = 3,
|
||||
COMPLETE = 4,
|
||||
INTRO = 0,
|
||||
MODE_SELECT = 1,
|
||||
FLUENT_PLACEMENT = 2,
|
||||
USER_LEVEL_SELECT = 3,
|
||||
CONFIG_PREVIEW = 4,
|
||||
TASK_CREATION_GUIDE = 5,
|
||||
COMPLETE = 6,
|
||||
}
|
||||
|
||||
export interface OnboardingState {
|
||||
|
|
@ -24,6 +30,8 @@ export interface OnboardingState {
|
|||
selectedConfig?: OnboardingConfig;
|
||||
skipTaskGuide: boolean;
|
||||
isCompleting: boolean;
|
||||
uiMode: 'fluent' | 'legacy';
|
||||
useSideLeaves: boolean; // Applies when uiMode === 'fluent'
|
||||
}
|
||||
|
||||
export class OnboardingModal extends Modal {
|
||||
|
|
@ -33,6 +41,9 @@ export class OnboardingModal extends Modal {
|
|||
private state: OnboardingState;
|
||||
|
||||
// Step components
|
||||
private introTyping: IntroTyping;
|
||||
private modeSelection: ModeSelection;
|
||||
private fluentPlacement: FluentPlacement;
|
||||
private userLevelSelector: UserLevelSelector;
|
||||
private configPreview: ConfigPreview;
|
||||
private taskCreationGuide: TaskCreationGuide;
|
||||
|
|
@ -58,12 +69,17 @@ export class OnboardingModal extends Modal {
|
|||
|
||||
// Initialize state
|
||||
this.state = {
|
||||
currentStep: OnboardingStep.WELCOME,
|
||||
currentStep: OnboardingStep.INTRO,
|
||||
skipTaskGuide: false,
|
||||
isCompleting: false,
|
||||
uiMode: 'fluent',
|
||||
useSideLeaves: true,
|
||||
};
|
||||
|
||||
// Initialize components
|
||||
this.introTyping = new IntroTyping();
|
||||
this.modeSelection = new ModeSelection();
|
||||
this.fluentPlacement = new FluentPlacement();
|
||||
this.userLevelSelector = new UserLevelSelector(this.configManager);
|
||||
this.configPreview = new ConfigPreview(this.configManager);
|
||||
this.taskCreationGuide = new TaskCreationGuide(this.plugin);
|
||||
|
|
@ -74,7 +90,7 @@ export class OnboardingModal extends Modal {
|
|||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
|
||||
this.createModalStructure();
|
||||
|
|
@ -82,7 +98,7 @@ export class OnboardingModal extends Modal {
|
|||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
|
||||
|
|
@ -90,8 +106,8 @@ export class OnboardingModal extends Modal {
|
|||
* Create the basic modal structure
|
||||
*/
|
||||
private createModalStructure() {
|
||||
const { contentEl } = this;
|
||||
|
||||
const {contentEl} = this;
|
||||
|
||||
// Header section
|
||||
this.headerEl = contentEl.createDiv("onboarding-header");
|
||||
|
||||
|
|
@ -101,6 +117,30 @@ export class OnboardingModal extends Modal {
|
|||
// Footer with navigation buttons
|
||||
this.footerEl = contentEl.createDiv("onboarding-footer");
|
||||
this.createFooterButtons();
|
||||
|
||||
// Apply initial UI mode class
|
||||
this.applyUIModeClass();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render top header bar (minimal title-only; choices are in content)
|
||||
*/
|
||||
private renderHeaderBar() {
|
||||
this.applyUIModeClass();
|
||||
// const bar = this.headerEl.createDiv({cls: "onboarding-topbar"});
|
||||
// const left = bar.createDiv({cls: "onboarding-topbar-left"});
|
||||
// left.createEl("span", {text: t("Task Genius"), cls: "onboarding-topbar-title"});
|
||||
// bar.createDiv({cls: "onboarding-topbar-right"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle UI mode classes on the modal element
|
||||
*/
|
||||
private applyUIModeClass() {
|
||||
const root = this.modalEl;
|
||||
root.toggleClass("mod-fluent", this.state.uiMode === 'fluent');
|
||||
root.toggleClass("mod-legacy", this.state.uiMode === 'legacy');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -113,17 +153,20 @@ export class OnboardingModal extends Modal {
|
|||
this.skipButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Skip onboarding"))
|
||||
.onClick(() => this.handleSkip());
|
||||
this.skipButton.buttonEl.addClass("clickable-icon");
|
||||
|
||||
// Back button
|
||||
this.backButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Back"))
|
||||
.onClick(() => this.handleBack());
|
||||
this.backButton.buttonEl.addClass("clickable-icon");
|
||||
|
||||
// Next button
|
||||
this.nextButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Next"))
|
||||
.setCta()
|
||||
.onClick(() => this.handleNext());
|
||||
this.nextButton.buttonEl.addClass("clickable-icon");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,12 +177,21 @@ export class OnboardingModal extends Modal {
|
|||
this.headerEl.empty();
|
||||
this.onboardingContentEl.empty();
|
||||
|
||||
// Render topbar
|
||||
this.renderHeaderBar();
|
||||
|
||||
// Update button visibility
|
||||
this.updateButtonStates();
|
||||
|
||||
switch (this.state.currentStep) {
|
||||
case OnboardingStep.WELCOME:
|
||||
this.displayWelcomeStep();
|
||||
case OnboardingStep.INTRO:
|
||||
this.displayIntroTypingStep();
|
||||
break;
|
||||
case OnboardingStep.MODE_SELECT:
|
||||
this.displayModeSelectionStep();
|
||||
break;
|
||||
case OnboardingStep.FLUENT_PLACEMENT:
|
||||
this.displayFluentPlacementStep();
|
||||
break;
|
||||
case OnboardingStep.USER_LEVEL_SELECT:
|
||||
this.displayUserLevelSelectStep();
|
||||
|
|
@ -161,7 +213,7 @@ export class OnboardingModal extends Modal {
|
|||
*/
|
||||
private displayWelcomeStep() {
|
||||
// Header
|
||||
this.headerEl.createEl("h1", { text: t("Welcome to Task Genius") });
|
||||
this.headerEl.createEl("h1", {text: t("Welcome to Task Genius")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t(
|
||||
"Transform your task management with advanced progress tracking and workflow automation"
|
||||
|
|
@ -214,8 +266,8 @@ export class OnboardingModal extends Modal {
|
|||
const iconEl = featureEl.createDiv("feature-icon");
|
||||
setIcon(iconEl, feature.icon);
|
||||
const featureContent = featureEl.createDiv("feature-content");
|
||||
featureContent.createEl("h3", { text: feature.title });
|
||||
featureContent.createEl("p", { text: feature.description });
|
||||
featureContent.createEl("h3", {text: feature.title});
|
||||
featureContent.createEl("p", {text: feature.description});
|
||||
});
|
||||
|
||||
// Setup note
|
||||
|
|
@ -228,12 +280,50 @@ export class OnboardingModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Intro typing step
|
||||
*/
|
||||
private displayIntroTypingStep() {
|
||||
// this.headerEl.createEl("h1", {text: t("Welcome")});
|
||||
this.introTyping.render(this.onboardingContentEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Mode selection (Fluent vs Legacy)
|
||||
*/
|
||||
private displayModeSelectionStep() {
|
||||
this.headerEl.createEl("h1", {text: t("Choose Your Interface Style")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t("Select your preferred visual and interaction style: modern Fluent or traditional Legacy"),
|
||||
cls: "onboarding-subtitle"
|
||||
});
|
||||
this.modeSelection.render(this.onboardingContentEl, this.state.uiMode as any, (mode) => {
|
||||
this.state.uiMode = mode;
|
||||
this.updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Fluent placement selection
|
||||
*/
|
||||
private displayFluentPlacementStep() {
|
||||
this.headerEl.createEl("h1", {text: t("Fluent Layout")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t("Choose how to display Fluent: use Sideleaves for enhanced multi-column collaboration, or Inline for an immersive single-page experience."),
|
||||
cls: "onboarding-subtitle"
|
||||
});
|
||||
this.fluentPlacement.render(this.onboardingContentEl, this.state.useSideLeaves ? "sideleaves" : "inline", (p) => {
|
||||
this.state.useSideLeaves = p === "sideleaves";
|
||||
this.updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display user level selection step
|
||||
*/
|
||||
private displayUserLevelSelectStep() {
|
||||
// Header
|
||||
this.headerEl.createEl("h1", { text: t("Choose Your Usage Mode") });
|
||||
this.headerEl.createEl("h1", {text: t("Choose Your Usage Mode")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t(
|
||||
"Select the configuration that best matches your task management experience"
|
||||
|
|
@ -259,7 +349,7 @@ export class OnboardingModal extends Modal {
|
|||
}
|
||||
|
||||
// Header
|
||||
this.headerEl.createEl("h1", { text: t("Configuration Preview") });
|
||||
this.headerEl.createEl("h1", {text: t("Configuration Preview")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t(
|
||||
"Review the settings that will be applied for your selected mode"
|
||||
|
|
@ -292,7 +382,7 @@ export class OnboardingModal extends Modal {
|
|||
*/
|
||||
private displayTaskCreationGuideStep() {
|
||||
// Header
|
||||
this.headerEl.createEl("h1", { text: t("Create Your First Task") });
|
||||
this.headerEl.createEl("h1", {text: t("Create Your First Task")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t("Learn how to create and format tasks in Task Genius"),
|
||||
cls: "onboarding-subtitle",
|
||||
|
|
@ -309,7 +399,7 @@ export class OnboardingModal extends Modal {
|
|||
if (!this.state.selectedConfig) return;
|
||||
|
||||
// Header
|
||||
this.headerEl.createEl("h1", { text: t("Setup Complete!") });
|
||||
this.headerEl.createEl("h1", {text: t("Setup Complete!")});
|
||||
this.headerEl.createEl("p", {
|
||||
text: t("Task Genius is now configured and ready to use"),
|
||||
cls: "onboarding-subtitle",
|
||||
|
|
@ -328,13 +418,13 @@ export class OnboardingModal extends Modal {
|
|||
private updateButtonStates() {
|
||||
const step = this.state.currentStep;
|
||||
|
||||
// Skip button - only show on welcome
|
||||
// Skip button - only show on intro
|
||||
this.skipButton.buttonEl.style.display =
|
||||
step === OnboardingStep.WELCOME ? "inline-block" : "none";
|
||||
step === OnboardingStep.INTRO ? "inline-block" : "none";
|
||||
|
||||
// Back button - hide on first step
|
||||
this.backButton.buttonEl.style.display =
|
||||
step === OnboardingStep.WELCOME ? "none" : "inline-block";
|
||||
step === OnboardingStep.INTRO ? "none" : "inline-block";
|
||||
|
||||
// Next button
|
||||
const isLastStep = step === OnboardingStep.COMPLETE;
|
||||
|
|
@ -362,7 +452,7 @@ export class OnboardingModal extends Modal {
|
|||
* Handle back navigation
|
||||
*/
|
||||
private handleBack() {
|
||||
if (this.state.currentStep > OnboardingStep.WELCOME) {
|
||||
if (this.state.currentStep > OnboardingStep.INTRO) {
|
||||
this.state.currentStep--;
|
||||
|
||||
// Skip task guide if it was skipped
|
||||
|
|
@ -394,15 +484,26 @@ export class OnboardingModal extends Modal {
|
|||
return;
|
||||
}
|
||||
|
||||
// Move to next step
|
||||
this.state.currentStep++;
|
||||
// Branching for intro/mode/placement
|
||||
if (step === OnboardingStep.INTRO) {
|
||||
this.state.currentStep = OnboardingStep.MODE_SELECT;
|
||||
} else if (step === OnboardingStep.MODE_SELECT) {
|
||||
this.state.currentStep = this.state.uiMode === 'fluent'
|
||||
? OnboardingStep.FLUENT_PLACEMENT
|
||||
: OnboardingStep.USER_LEVEL_SELECT;
|
||||
} else if (step === OnboardingStep.FLUENT_PLACEMENT) {
|
||||
this.state.currentStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
} else {
|
||||
this.state.currentStep++;
|
||||
}
|
||||
|
||||
// Apply configuration when moving to preview
|
||||
// Apply architecture selection and configuration when moving to preview
|
||||
if (
|
||||
this.state.currentStep === OnboardingStep.CONFIG_PREVIEW &&
|
||||
this.state.selectedConfig
|
||||
) {
|
||||
try {
|
||||
await this.applyArchitectureSelections();
|
||||
await this.configManager.applyConfiguration(
|
||||
this.state.selectedConfig.mode
|
||||
);
|
||||
|
|
@ -435,6 +536,34 @@ export class OnboardingModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Legacy vs Fluent selection and sideleaves preference to settings
|
||||
*/
|
||||
private async applyArchitectureSelections() {
|
||||
const isFluent = this.state.uiMode === 'fluent';
|
||||
if (!this.plugin.settings.experimental) {
|
||||
(this.plugin.settings as any).experimental = {enableV2: false, showV2Ribbon: false};
|
||||
}
|
||||
this.plugin.settings.experimental!.enableV2 = isFluent;
|
||||
// Prepare v2 config and set placement option when Fluent is chosen
|
||||
if (!this.plugin.settings.experimental!.v2Config) {
|
||||
(this.plugin.settings.experimental as any).v2Config = {
|
||||
enableWorkspaces: true,
|
||||
defaultWorkspace: 'default',
|
||||
showTopNavigation: true,
|
||||
showNewSidebar: true,
|
||||
allowViewSwitching: true,
|
||||
persistViewMode: true,
|
||||
maxOtherViewsBeforeOverflow: 5,
|
||||
};
|
||||
}
|
||||
if (isFluent) {
|
||||
(this.plugin.settings.experimental as any).v2Config.useWorkspaceSideLeaves = !!this.state.useSideLeaves;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete onboarding process
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ItemView, WorkspaceLeaf, setIcon, ButtonComponent } from "obsidian";
|
||||
import { ItemView, WorkspaceLeaf, setIcon, ButtonComponent, Setting } from "obsidian";
|
||||
import type TaskProgressBarPlugin from "@/index";
|
||||
import { t } from "@/translations/helper";
|
||||
import {
|
||||
|
|
@ -11,16 +11,22 @@ import { UserLevelSelector } from "./UserLevelSelector";
|
|||
import { ConfigPreview } from "./ConfigPreview";
|
||||
import { TaskCreationGuide } from "./TaskCreationGuide";
|
||||
import { OnboardingComplete } from "./OnboardingComplete";
|
||||
import { IntroTyping } from "./IntroTyping";
|
||||
import { ModeSelection } from "./ModeSelection";
|
||||
import { FluentPlacement } from "./FluentPlacement";
|
||||
import "@/experimental/v2/styles/v2-enhanced.css";
|
||||
|
||||
export const ONBOARDING_VIEW_TYPE = "task-genius-onboarding";
|
||||
|
||||
export enum OnboardingStep {
|
||||
SETTINGS_CHECK = 0, // New: Check if user wants onboarding
|
||||
WELCOME = 1,
|
||||
USER_LEVEL_SELECT = 2,
|
||||
CONFIG_PREVIEW = 3,
|
||||
TASK_CREATION_GUIDE = 4,
|
||||
COMPLETE = 5,
|
||||
SETTINGS_CHECK = 0,
|
||||
INTRO = 1,
|
||||
MODE_SELECT = 2,
|
||||
FLUENT_PLACEMENT = 3,
|
||||
USER_LEVEL_SELECT = 4,
|
||||
CONFIG_PREVIEW = 5,
|
||||
TASK_CREATION_GUIDE = 6,
|
||||
COMPLETE = 7,
|
||||
}
|
||||
|
||||
export interface OnboardingState {
|
||||
|
|
@ -30,6 +36,8 @@ export interface OnboardingState {
|
|||
isCompleting: boolean;
|
||||
userHasChanges: boolean;
|
||||
changesSummary: string[];
|
||||
uiMode: 'fluent' | 'legacy';
|
||||
useSideLeaves: boolean; // Applies when uiMode === 'fluent'
|
||||
}
|
||||
|
||||
export class OnboardingView extends ItemView {
|
||||
|
|
@ -40,6 +48,9 @@ export class OnboardingView extends ItemView {
|
|||
private state: OnboardingState;
|
||||
|
||||
// Step components
|
||||
private introTyping: IntroTyping;
|
||||
private modeSelection: ModeSelection;
|
||||
private fluentPlacement: FluentPlacement;
|
||||
private userLevelSelector: UserLevelSelector;
|
||||
private configPreview: ConfigPreview;
|
||||
private taskCreationGuide: TaskCreationGuide;
|
||||
|
|
@ -60,16 +71,21 @@ export class OnboardingView extends ItemView {
|
|||
this.settingsDetector = new SettingsChangeDetector(plugin);
|
||||
this.onComplete = onComplete;
|
||||
|
||||
// Initialize state
|
||||
// Initialize state - always start with INTRO
|
||||
this.state = {
|
||||
currentStep: OnboardingStep.SETTINGS_CHECK,
|
||||
currentStep: OnboardingStep.INTRO,
|
||||
skipTaskGuide: false,
|
||||
isCompleting: false,
|
||||
userHasChanges: this.settingsDetector.hasUserMadeChanges(),
|
||||
changesSummary: this.settingsDetector.getChangesSummary(),
|
||||
uiMode: 'fluent',
|
||||
useSideLeaves: true,
|
||||
};
|
||||
|
||||
// Initialize components
|
||||
this.introTyping = new IntroTyping();
|
||||
this.modeSelection = new ModeSelection();
|
||||
this.fluentPlacement = new FluentPlacement();
|
||||
this.userLevelSelector = new UserLevelSelector(this.configManager);
|
||||
this.configPreview = new ConfigPreview(this.configManager);
|
||||
this.taskCreationGuide = new TaskCreationGuide(this.plugin);
|
||||
|
|
@ -81,7 +97,7 @@ export class OnboardingView extends ItemView {
|
|||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return t("Task Genius Setup");
|
||||
return t("Task Genius Onboarding");
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
|
|
@ -102,7 +118,7 @@ export class OnboardingView extends ItemView {
|
|||
* Create the basic view structure
|
||||
*/
|
||||
private createViewStructure() {
|
||||
const container = this.contentEl;
|
||||
const container = this.containerEl;
|
||||
container.empty();
|
||||
container.addClass("onboarding-view");
|
||||
|
||||
|
|
@ -112,9 +128,38 @@ export class OnboardingView extends ItemView {
|
|||
// Main content section
|
||||
this.onboardingContentEl = container.createDiv("onboarding-content");
|
||||
|
||||
|
||||
// Footer with navigation buttons
|
||||
this.footerEl = container.createDiv("onboarding-footer");
|
||||
this.createFooterButtons();
|
||||
|
||||
// Apply initial UI mode class
|
||||
this.applyUIModeClass();
|
||||
|
||||
container.createEl("div", {
|
||||
cls: "onboarding-shadow"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render top header bar (minimal title-only; choices are in content)
|
||||
*/
|
||||
private renderHeaderBar() {
|
||||
this.applyUIModeClass();
|
||||
// const bar = this.onboardingHeaderEl.createDiv({cls: "onboarding-topbar"});
|
||||
// const left = bar.createDiv({cls: "onboarding-topbar-left"});
|
||||
// left.createEl("span", {text: t("Task Genius"), cls: "onboarding-topbar-title"});
|
||||
// bar.createDiv({cls: "onboarding-topbar-right"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle UI mode classes on the root container
|
||||
*/
|
||||
private applyUIModeClass() {
|
||||
const root = this.contentEl;
|
||||
root.toggleClass("mod-fluent", this.state.uiMode === 'fluent');
|
||||
root.toggleClass("mod-legacy", this.state.uiMode === 'legacy');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -127,17 +172,20 @@ export class OnboardingView extends ItemView {
|
|||
this.skipButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Skip setup"))
|
||||
.onClick(() => this.handleSkip());
|
||||
this.skipButton.buttonEl.addClass("clickable-icon");
|
||||
|
||||
// Back button
|
||||
this.backButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Back"))
|
||||
.onClick(() => this.handleBack());
|
||||
this.backButton.buttonEl.addClass("clickable-icon");
|
||||
|
||||
// Next button
|
||||
this.nextButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Next"))
|
||||
.setCta()
|
||||
.onClick(() => this.handleNext());
|
||||
this.nextButton.buttonEl.addClass("clickable-icon");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -148,6 +196,9 @@ export class OnboardingView extends ItemView {
|
|||
this.onboardingHeaderEl.empty();
|
||||
this.onboardingContentEl.empty();
|
||||
|
||||
// Render topbar
|
||||
this.renderHeaderBar();
|
||||
|
||||
// Update button visibility
|
||||
this.updateButtonStates();
|
||||
|
||||
|
|
@ -155,8 +206,14 @@ export class OnboardingView extends ItemView {
|
|||
case OnboardingStep.SETTINGS_CHECK:
|
||||
this.displaySettingsCheckStep();
|
||||
break;
|
||||
case OnboardingStep.WELCOME:
|
||||
this.displayWelcomeStep();
|
||||
case OnboardingStep.INTRO:
|
||||
this.displayIntroTypingStep();
|
||||
break;
|
||||
case OnboardingStep.MODE_SELECT:
|
||||
this.displayModeSelectionStep();
|
||||
break;
|
||||
case OnboardingStep.FLUENT_PLACEMENT:
|
||||
this.displayFluentPlacementStep();
|
||||
break;
|
||||
case OnboardingStep.USER_LEVEL_SELECT:
|
||||
this.displayUserLevelSelectStep();
|
||||
|
|
@ -174,61 +231,51 @@ export class OnboardingView extends ItemView {
|
|||
}
|
||||
|
||||
/**
|
||||
* Display settings check step (new async approach)
|
||||
* Display settings check step - ask if user wants to continue wizard
|
||||
*/
|
||||
private displaySettingsCheckStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Task Genius Setup") });
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Task Genius Setup")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t("We noticed you've already configured Task Genius"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
// Content
|
||||
const content = this.onboardingContentEl;
|
||||
const checkSection = content.createDiv("settings-check-section");
|
||||
|
||||
if (this.state.userHasChanges) {
|
||||
// User has made changes - ask if they want onboarding
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t("We noticed you've already configured Task Genius"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
// Show detected changes
|
||||
checkSection.createEl("h3", {text: t("Your current configuration includes:")});
|
||||
const changesList = checkSection.createEl("ul", {cls: "changes-summary-list"});
|
||||
|
||||
const checkSection = content.createDiv("settings-check-section");
|
||||
this.state.changesSummary.forEach(change => {
|
||||
const item = changesList.createEl("li");
|
||||
const checkIcon = item.createSpan("change-check");
|
||||
setIcon(checkIcon, "check");
|
||||
item.createSpan("change-text").setText(change);
|
||||
});
|
||||
|
||||
// Show detected changes
|
||||
checkSection.createEl("h3", { text: t("Your current configuration includes:") });
|
||||
const changesList = checkSection.createEl("ul", { cls: "changes-summary-list" });
|
||||
|
||||
this.state.changesSummary.forEach(change => {
|
||||
const item = changesList.createEl("li");
|
||||
const checkIcon = item.createSpan("change-check");
|
||||
setIcon(checkIcon, "check");
|
||||
item.createSpan("change-text").setText(change);
|
||||
});
|
||||
// Ask if they want onboarding
|
||||
const questionSection = content.createDiv("onboarding-question");
|
||||
questionSection.createEl("h3", {text: t("Would you like to continue with the setup wizard?")});
|
||||
|
||||
// Ask if they want onboarding
|
||||
const questionSection = content.createDiv("onboarding-question");
|
||||
questionSection.createEl("h3", { text: t("Would you like to run the setup wizard anyway?") });
|
||||
|
||||
const optionsContainer = questionSection.createDiv("question-options");
|
||||
|
||||
const yesButton = optionsContainer.createEl("button", {
|
||||
text: t("Yes, show me the setup wizard"),
|
||||
cls: "mod-cta question-button",
|
||||
});
|
||||
yesButton.addEventListener("click", () => {
|
||||
this.state.currentStep = OnboardingStep.WELCOME;
|
||||
this.displayCurrentStep();
|
||||
});
|
||||
const optionsContainer = questionSection.createDiv("question-options");
|
||||
|
||||
const noButton = optionsContainer.createEl("button", {
|
||||
text: t("No, I'm happy with my current setup"),
|
||||
cls: "question-button",
|
||||
});
|
||||
noButton.addEventListener("click", () => this.handleSkip());
|
||||
|
||||
} else {
|
||||
// User hasn't made changes - proceed with normal onboarding
|
||||
this.state.currentStep = OnboardingStep.WELCOME;
|
||||
const yesButton = optionsContainer.createEl("button", {
|
||||
text: t("Yes, show me the setup wizard"),
|
||||
cls: "mod-cta question-button clickable-icon",
|
||||
});
|
||||
yesButton.addEventListener("click", () => {
|
||||
this.state.currentStep = OnboardingStep.MODE_SELECT;
|
||||
this.displayCurrentStep();
|
||||
}
|
||||
});
|
||||
|
||||
const noButton = optionsContainer.createEl("button", {
|
||||
text: t("No, I'm happy with my current setup"),
|
||||
cls: "question-button clickable-icon",
|
||||
});
|
||||
noButton.addEventListener("click", () => this.handleSkip());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -236,7 +283,7 @@ export class OnboardingView extends ItemView {
|
|||
*/
|
||||
private displayWelcomeStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Welcome to Task Genius") });
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Welcome to Task Genius")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t(
|
||||
"Transform your task management with advanced progress tracking and workflow automation"
|
||||
|
|
@ -287,8 +334,8 @@ export class OnboardingView extends ItemView {
|
|||
const iconEl = featureEl.createDiv("feature-icon");
|
||||
setIcon(iconEl, feature.icon);
|
||||
const featureContent = featureEl.createDiv("feature-content");
|
||||
featureContent.createEl("h3", { text: feature.title });
|
||||
featureContent.createEl("p", { text: feature.description });
|
||||
featureContent.createEl("h3", {text: feature.title});
|
||||
featureContent.createEl("p", {text: feature.description});
|
||||
});
|
||||
|
||||
// Setup note
|
||||
|
|
@ -301,12 +348,56 @@ export class OnboardingView extends ItemView {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Intro typing step
|
||||
*/
|
||||
private displayIntroTypingStep() {
|
||||
// Header minimal
|
||||
// this.onboardingHeaderEl.createEl("h1", {text: t("Welcome")});
|
||||
// Content typing animation
|
||||
this.introTyping.render(this.onboardingContentEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Mode selection (Fluent vs Legacy) with preview cards
|
||||
*/
|
||||
private displayModeSelectionStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Choose Your Interface Style")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t("Select your preferred visual and interaction style: modern Fluent or traditional Legacy"),
|
||||
cls: "onboarding-subtitle"
|
||||
});
|
||||
// Content
|
||||
this.modeSelection.render(this.onboardingContentEl, this.state.uiMode as any, (mode) => {
|
||||
this.state.uiMode = mode;
|
||||
this.updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Fluent placement selection (Sideleaves vs Inline)
|
||||
*/
|
||||
private displayFluentPlacementStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Fluent Layout")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t("Choose how to display Fluent: use Sideleaves for enhanced multi-column collaboration, or Inline for an immersive single-page experience."),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
// Content
|
||||
this.fluentPlacement.render(this.onboardingContentEl, this.state.useSideLeaves ? "sideleaves" : "inline", (p) => {
|
||||
this.state.useSideLeaves = p === "sideleaves";
|
||||
this.updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display user level selection step
|
||||
*/
|
||||
private displayUserLevelSelectStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Choose Your Usage Mode") });
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Choose Your Usage Mode")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t(
|
||||
"Select the configuration that best matches your task management experience"
|
||||
|
|
@ -332,7 +423,7 @@ export class OnboardingView extends ItemView {
|
|||
}
|
||||
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Configuration Preview") });
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Configuration Preview")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t(
|
||||
"Review the settings that will be applied for your selected mode"
|
||||
|
|
@ -345,6 +436,19 @@ export class OnboardingView extends ItemView {
|
|||
this.onboardingContentEl,
|
||||
this.state.selectedConfig
|
||||
);
|
||||
|
||||
// Task guide option
|
||||
const optionsSection =
|
||||
this.onboardingContentEl.createDiv("config-options");
|
||||
|
||||
new Setting(optionsSection)
|
||||
.setName(t("Include task creation guide"))
|
||||
.setDesc(t("Show a quick tutorial on creating your first task"))
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(!this.state.skipTaskGuide).onChange((value) => {
|
||||
this.state.skipTaskGuide = !value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -352,7 +456,7 @@ export class OnboardingView extends ItemView {
|
|||
*/
|
||||
private displayTaskCreationGuideStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Create Your First Task") });
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Create Your First Task")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t("Learn how to create and format tasks in Task Genius"),
|
||||
cls: "onboarding-subtitle",
|
||||
|
|
@ -369,7 +473,7 @@ export class OnboardingView extends ItemView {
|
|||
if (!this.state.selectedConfig) return;
|
||||
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Setup Complete!") });
|
||||
this.onboardingHeaderEl.createEl("h1", {text: t("Setup Complete!")});
|
||||
this.onboardingHeaderEl.createEl("p", {
|
||||
text: t("Task Genius is now configured and ready to use"),
|
||||
cls: "onboarding-subtitle",
|
||||
|
|
@ -388,19 +492,19 @@ export class OnboardingView extends ItemView {
|
|||
private updateButtonStates() {
|
||||
const step = this.state.currentStep;
|
||||
|
||||
// Skip button - show on settings check and welcome
|
||||
// Skip button - show on settings check and intro
|
||||
this.skipButton.buttonEl.style.display =
|
||||
step === OnboardingStep.SETTINGS_CHECK || step === OnboardingStep.WELCOME
|
||||
step === OnboardingStep.SETTINGS_CHECK || step === OnboardingStep.INTRO
|
||||
? "inline-block" : "none";
|
||||
|
||||
// Back button - hide on first two steps
|
||||
// Back button - hide on intro, show on settings check and after
|
||||
this.backButton.buttonEl.style.display =
|
||||
step <= OnboardingStep.WELCOME ? "none" : "inline-block";
|
||||
step === OnboardingStep.INTRO ? "none" : "inline-block";
|
||||
|
||||
// Next button text and state
|
||||
const isLastStep = step === OnboardingStep.COMPLETE;
|
||||
const isSettingsCheck = step === OnboardingStep.SETTINGS_CHECK;
|
||||
|
||||
|
||||
if (isSettingsCheck) {
|
||||
this.nextButton.buttonEl.style.display = "none"; // Hide on settings check
|
||||
} else {
|
||||
|
|
@ -431,15 +535,27 @@ export class OnboardingView extends ItemView {
|
|||
* Handle back navigation
|
||||
*/
|
||||
private handleBack() {
|
||||
if (this.state.currentStep > OnboardingStep.SETTINGS_CHECK) {
|
||||
this.state.currentStep--;
|
||||
|
||||
// Skip task guide if it was skipped
|
||||
if (
|
||||
this.state.currentStep === OnboardingStep.TASK_CREATION_GUIDE &&
|
||||
this.state.skipTaskGuide
|
||||
) {
|
||||
if (this.state.currentStep > OnboardingStep.INTRO) {
|
||||
// Handle back from settings check - go to intro
|
||||
if (this.state.currentStep === OnboardingStep.SETTINGS_CHECK) {
|
||||
this.state.currentStep = OnboardingStep.INTRO;
|
||||
}
|
||||
// Handle back from mode select - check if came from settings check
|
||||
else if (this.state.currentStep === OnboardingStep.MODE_SELECT) {
|
||||
// If user has changes, go back to settings check, otherwise to intro
|
||||
this.state.currentStep = this.state.userHasChanges
|
||||
? OnboardingStep.SETTINGS_CHECK
|
||||
: OnboardingStep.INTRO;
|
||||
} else {
|
||||
this.state.currentStep--;
|
||||
|
||||
// Skip task guide if it was skipped
|
||||
if (
|
||||
this.state.currentStep === OnboardingStep.TASK_CREATION_GUIDE &&
|
||||
this.state.skipTaskGuide
|
||||
) {
|
||||
this.state.currentStep--;
|
||||
}
|
||||
}
|
||||
|
||||
this.displayCurrentStep();
|
||||
|
|
@ -463,15 +579,30 @@ export class OnboardingView extends ItemView {
|
|||
return;
|
||||
}
|
||||
|
||||
// Move to next step
|
||||
this.state.currentStep++;
|
||||
// Custom flow for new intro/mode/placement steps
|
||||
if (step === OnboardingStep.INTRO) {
|
||||
// After intro, check if user has changes to decide next step
|
||||
this.state.currentStep = this.state.userHasChanges
|
||||
? OnboardingStep.SETTINGS_CHECK
|
||||
: OnboardingStep.MODE_SELECT;
|
||||
} else if (step === OnboardingStep.MODE_SELECT) {
|
||||
this.state.currentStep = this.state.uiMode === 'fluent'
|
||||
? OnboardingStep.FLUENT_PLACEMENT
|
||||
: OnboardingStep.USER_LEVEL_SELECT;
|
||||
} else if (step === OnboardingStep.FLUENT_PLACEMENT) {
|
||||
this.state.currentStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
} else {
|
||||
// Default increment for the remaining steps
|
||||
this.state.currentStep++;
|
||||
}
|
||||
|
||||
// Apply configuration when moving to preview
|
||||
// Apply architecture selection and configuration when moving to preview
|
||||
if (
|
||||
this.state.currentStep === OnboardingStep.CONFIG_PREVIEW &&
|
||||
this.state.selectedConfig
|
||||
) {
|
||||
try {
|
||||
await this.applyArchitectureSelections();
|
||||
await this.configManager.applyConfiguration(
|
||||
this.state.selectedConfig.mode
|
||||
);
|
||||
|
|
@ -504,6 +635,34 @@ export class OnboardingView extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Legacy vs Fluent selection and sideleaves preference to settings
|
||||
*/
|
||||
private async applyArchitectureSelections() {
|
||||
const isFluent = this.state.uiMode === 'fluent';
|
||||
if (!this.plugin.settings.experimental) {
|
||||
(this.plugin.settings as any).experimental = {enableV2: false, showV2Ribbon: false};
|
||||
}
|
||||
this.plugin.settings.experimental!.enableV2 = isFluent;
|
||||
// Prepare v2 config and set placement option when Fluent is chosen
|
||||
if (!this.plugin.settings.experimental!.v2Config) {
|
||||
(this.plugin.settings.experimental as any).v2Config = {
|
||||
enableWorkspaces: true,
|
||||
defaultWorkspace: 'default',
|
||||
showTopNavigation: true,
|
||||
showNewSidebar: true,
|
||||
allowViewSwitching: true,
|
||||
persistViewMode: true,
|
||||
maxOtherViewsBeforeOverflow: 5,
|
||||
};
|
||||
}
|
||||
if (isFluent) {
|
||||
(this.plugin.settings.experimental as any).v2Config.useWorkspaceSideLeaves = !!this.state.useSideLeaves;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete onboarding process
|
||||
*/
|
||||
|
|
@ -535,4 +694,4 @@ export class OnboardingView extends ItemView {
|
|||
private close() {
|
||||
this.leaf.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { t } from "@/translations/helper";
|
|||
|
||||
export class V2Integration {
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private revealingSideLeaves = false;
|
||||
|
||||
constructor(plugin: TaskProgressBarPlugin) {
|
||||
this.plugin = plugin;
|
||||
|
|
@ -64,25 +65,35 @@ export class V2Integration {
|
|||
// When any of the V2 views becomes active, reveal the other side leaves without focusing them
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.workspace.on("active-leaf-change", async (leaf) => {
|
||||
if (this.revealingSideLeaves) return;
|
||||
const useSideLeaves = !!(this.plugin.settings.experimental as any)?.v2Config?.useWorkspaceSideLeaves;
|
||||
if (!useSideLeaves || !leaf?.view?.getViewType) return;
|
||||
const vt = leaf.view.getViewType();
|
||||
const watched = new Set<string>([
|
||||
TASK_VIEW_V2_TYPE,
|
||||
TG_LEFT_SIDEBAR_VIEW_TYPE,
|
||||
TG_RIGHT_DETAIL_VIEW_TYPE,
|
||||
]);
|
||||
if (!watched.has(vt)) return;
|
||||
const ws = this.plugin.app.workspace as Workspace & any;
|
||||
this.revealingSideLeaves = true;
|
||||
try {
|
||||
const useSideLeaves = !!(this.plugin.settings.experimental as any)?.v2Config?.useWorkspaceSideLeaves;
|
||||
if (!useSideLeaves) return;
|
||||
if (!leaf?.view?.getViewType) return;
|
||||
const vt = leaf.view.getViewType();
|
||||
const watched = new Set<string>([
|
||||
TASK_VIEW_V2_TYPE,
|
||||
TG_LEFT_SIDEBAR_VIEW_TYPE,
|
||||
TG_RIGHT_DETAIL_VIEW_TYPE,
|
||||
]);
|
||||
if (!watched.has(vt)) return;
|
||||
const ws = this.plugin.app.workspace as Workspace & any;
|
||||
// Ensure side leaves exist and are visible, but do not focus
|
||||
await ws.ensureSideLeaf(TG_LEFT_SIDEBAR_VIEW_TYPE, "left", { active: false });
|
||||
await ws.ensureSideLeaf(TG_RIGHT_DETAIL_VIEW_TYPE, "right", { active: false });
|
||||
// Expand sidebars if they are collapsed, without changing focus
|
||||
// Ensure side leaves exist
|
||||
const leftLeaf = await ws.ensureSideLeaf(TG_LEFT_SIDEBAR_VIEW_TYPE, "left", { active: false });
|
||||
const rightLeaf = await ws.ensureSideLeaf(TG_RIGHT_DETAIL_VIEW_TYPE, "right", { active: false });
|
||||
// Bring them to front within their splits (without keeping focus)
|
||||
if (leftLeaf) ws.revealLeaf(leftLeaf);
|
||||
if (rightLeaf) ws.revealLeaf(rightLeaf);
|
||||
// Expand sidebars if they are collapsed
|
||||
if (ws.leftSplit?.collapsed && typeof ws.leftSplit.expand === "function") ws.leftSplit.expand();
|
||||
if (ws.rightSplit?.collapsed && typeof ws.rightSplit.expand === "function") ws.rightSplit.expand();
|
||||
} catch (e) {}
|
||||
// Restore focus to the currently active (incoming) leaf
|
||||
if (ws.setActiveLeaf && leaf) ws.setActiveLeaf(leaf, { focus: true });
|
||||
} catch (_) {
|
||||
// noop
|
||||
} finally {
|
||||
this.revealingSideLeaves = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -863,3 +863,27 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.onboarding-shadow {
|
||||
position: absolute;
|
||||
background: hsl(var(--color-accent-hsl), 0);
|
||||
border-radius: 24px;
|
||||
rotate: 35deg;
|
||||
width: 260px;
|
||||
top: 200px;
|
||||
height: 400px;
|
||||
filter: blur(150px);
|
||||
animation: shadow-slide infinite 4s linear alternate;
|
||||
}
|
||||
|
||||
@keyframes shadow-slide {
|
||||
0% {
|
||||
background: hsl(var(--color-accent-hsl), 0.2);
|
||||
right: 360px
|
||||
}
|
||||
to {
|
||||
background: hsl(var(--color-accent-hsl), 0.8);
|
||||
right: 160px
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1093,3 +1093,133 @@
|
|||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================== */
|
||||
/* AI-Style Intro Typing Animation */
|
||||
/* ============================== */
|
||||
|
||||
.intro-typing {
|
||||
padding: var(--size-4-6) var(--size-4-4);
|
||||
text-align: left;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.intro-line {
|
||||
margin-bottom: var(--size-4-4);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.intro-line-1 {
|
||||
/* "Hi," - Large responsive greeting */
|
||||
font-size: clamp(3rem, 6vw, 5rem);
|
||||
font-weight: 700;
|
||||
color: var(--text-normal);
|
||||
margin-bottom: var(--size-4-2);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.intro-line-2 {
|
||||
/* "Thank you for using Task Genius" - Secondary greeting */
|
||||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
margin-bottom: var(--size-4-6);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.intro-line-3,
|
||||
.intro-line-4 {
|
||||
/* Description text - Responsive body text */
|
||||
font-size: clamp(1rem, 2vw, 1.4rem);
|
||||
color: var(--text-muted);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--size-4-5);
|
||||
}
|
||||
|
||||
/* AI-style streaming characters */
|
||||
.intro-char {
|
||||
display: inline;
|
||||
opacity: 0;
|
||||
filter: blur(4px);
|
||||
transform: translateY(-2px);
|
||||
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
filter 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.intro-char-visible {
|
||||
opacity: 1;
|
||||
filter: blur(0px);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Space character handling */
|
||||
.intro-char-space {
|
||||
width: 0.25em;
|
||||
}
|
||||
|
||||
/* Streaming cursor that follows characters */
|
||||
.intro-cursor {
|
||||
display: inline-block;
|
||||
margin-left: 1px;
|
||||
opacity: 0.7;
|
||||
animation: cursorBlink 1s infinite;
|
||||
color: var(--interactive-accent);
|
||||
font-weight: normal;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@keyframes cursorBlink {
|
||||
0%, 49% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
50%, 100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Completed stream line */
|
||||
.stream-complete {
|
||||
animation: streamComplete 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes streamComplete {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.95;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enhanced readability on different themes */
|
||||
.theme-dark .intro-line-1 {
|
||||
color: var(--text-normal);
|
||||
text-shadow: 0 0 20px rgba(var(--interactive-accent-rgb), 0.1);
|
||||
}
|
||||
|
||||
.theme-dark .intro-char-visible {
|
||||
/* Subtle glow effect for dark theme */
|
||||
text-shadow: 0 0 8px rgba(var(--interactive-accent-rgb), 0.05);
|
||||
}
|
||||
|
||||
/* Smooth appearance for whole typing container */
|
||||
.intro-typing {
|
||||
animation: fadeInTyping 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInTyping {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1671
styles.css
1671
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue