mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(onboarding): migrate to leaf view and add smart detection
- Replace OnboardingModal with OnboardingView (ItemView-based) - Add SettingsChangeDetector service for intelligent onboarding triggers - Implement settings check step with user confirmation - Enhance configuration safety with smart view merging - Update CSS to support both modal and leaf view layouts - Add "Open Task Genius Setup" command for manual access - Preserve all existing onboarding functionality - Improve UX by preventing unnecessary interruptions for configured users BREAKING CHANGE: Onboarding now opens as a leaf view instead of modal
This commit is contained in:
parent
ba902e373e
commit
2b14a7a847
10 changed files with 1426 additions and 11450 deletions
|
|
@ -1,8 +1,15 @@
|
|||
import { OnboardingConfig } from "../../utils/OnboardingConfigManager";
|
||||
import { OnboardingConfig, OnboardingConfigManager } from "../../utils/OnboardingConfigManager";
|
||||
import { t } from "../../translations/helper";
|
||||
import { setIcon } from "obsidian";
|
||||
import type TaskProgressBarPlugin from "../../index";
|
||||
|
||||
export class ConfigPreview {
|
||||
private configManager: OnboardingConfigManager;
|
||||
|
||||
constructor(configManager: OnboardingConfigManager) {
|
||||
this.configManager = configManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render configuration preview
|
||||
*/
|
||||
|
|
@ -145,6 +152,95 @@ export class ConfigPreview {
|
|||
config.settings.fileParsingConfig.enableWorkerProcessing ? t("Enabled") : t("Disabled")
|
||||
);
|
||||
}
|
||||
|
||||
// Show configuration change preview
|
||||
this.renderConfigurationChanges(containerEl, config);
|
||||
|
||||
// Customization note
|
||||
const note = containerEl.createDiv("customization-note");
|
||||
note.createEl("p", {
|
||||
text: t(
|
||||
"You can adjust any of these settings later in the plugin settings."
|
||||
),
|
||||
cls: "note-text",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render configuration changes preview
|
||||
*/
|
||||
private renderConfigurationChanges(containerEl: HTMLElement, config: OnboardingConfig) {
|
||||
try {
|
||||
const preview = this.configManager.getConfigurationPreview(config.mode);
|
||||
|
||||
// Show change summary section
|
||||
const changesSection = containerEl.createDiv("config-changes-summary");
|
||||
changesSection.createEl("h3", { text: t("Configuration Changes") });
|
||||
|
||||
// User custom views preserved
|
||||
if (preview.userCustomViewsPreserved.length > 0) {
|
||||
const preservedSection = changesSection.createDiv("preserved-views");
|
||||
const preservedHeader = preservedSection.createDiv("preserved-header");
|
||||
const preservedIcon = preservedHeader.createSpan("preserved-icon");
|
||||
setIcon(preservedIcon, "shield-check");
|
||||
preservedHeader.createSpan("preserved-text").setText(
|
||||
t("Your custom views will be preserved") + ` (${preview.userCustomViewsPreserved.length})`
|
||||
);
|
||||
|
||||
const preservedList = preservedSection.createEl("ul", { cls: "preserved-views-list" });
|
||||
preview.userCustomViewsPreserved.forEach(view => {
|
||||
const item = preservedList.createEl("li");
|
||||
const viewIcon = item.createSpan();
|
||||
setIcon(viewIcon, view.icon || "list");
|
||||
item.createSpan().setText(" " + view.name);
|
||||
});
|
||||
}
|
||||
|
||||
// Views to be added
|
||||
if (preview.viewsToAdd.length > 0) {
|
||||
const addedSection = changesSection.createDiv("added-views");
|
||||
const addedIcon = addedSection.createSpan("change-icon");
|
||||
setIcon(addedIcon, "plus-circle");
|
||||
addedSection.createSpan("change-text").setText(
|
||||
t("New views to be added") + ` (${preview.viewsToAdd.length})`
|
||||
);
|
||||
}
|
||||
|
||||
// Views to be updated
|
||||
if (preview.viewsToUpdate.length > 0) {
|
||||
const updatedSection = changesSection.createDiv("updated-views");
|
||||
const updatedIcon = updatedSection.createSpan("change-icon");
|
||||
setIcon(updatedIcon, "refresh-cw");
|
||||
updatedSection.createSpan("change-text").setText(
|
||||
t("Existing views to be updated") + ` (${preview.viewsToUpdate.length})`
|
||||
);
|
||||
}
|
||||
|
||||
// Settings changes
|
||||
if (preview.settingsChanges.length > 0) {
|
||||
const settingsChangesSection = changesSection.createDiv("settings-changes");
|
||||
const settingsIcon = settingsChangesSection.createSpan("change-icon");
|
||||
setIcon(settingsIcon, "settings");
|
||||
settingsChangesSection.createSpan("change-text").setText(t("Feature changes"));
|
||||
|
||||
const changesList = settingsChangesSection.createEl("ul", { cls: "settings-changes-list" });
|
||||
preview.settingsChanges.forEach(change => {
|
||||
const item = changesList.createEl("li");
|
||||
item.setText(change);
|
||||
});
|
||||
}
|
||||
|
||||
// Safety note
|
||||
const safetyNote = changesSection.createDiv("safety-note");
|
||||
const safetyIcon = safetyNote.createSpan("safety-icon");
|
||||
setIcon(safetyIcon, "info");
|
||||
safetyNote.createSpan("safety-text").setText(
|
||||
t("Only template settings will be applied. Your existing custom configurations will be preserved.")
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.warn("Could not generate configuration preview:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export class OnboardingModal extends Modal {
|
|||
|
||||
// Initialize components
|
||||
this.userLevelSelector = new UserLevelSelector(this.configManager);
|
||||
this.configPreview = new ConfigPreview();
|
||||
this.configPreview = new ConfigPreview(this.configManager);
|
||||
this.taskCreationGuide = new TaskCreationGuide(this.plugin);
|
||||
this.onboardingComplete = new OnboardingComplete();
|
||||
|
||||
|
|
|
|||
538
src/components/onboarding/OnboardingView.ts
Normal file
538
src/components/onboarding/OnboardingView.ts
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
import { ItemView, WorkspaceLeaf, setIcon, ButtonComponent } from "obsidian";
|
||||
import type TaskProgressBarPlugin from "../../index";
|
||||
import { t } from "../../translations/helper";
|
||||
import {
|
||||
OnboardingConfigManager,
|
||||
OnboardingConfigMode,
|
||||
OnboardingConfig,
|
||||
} from "../../utils/OnboardingConfigManager";
|
||||
import { SettingsChangeDetector } from "../../utils/SettingsChangeDetector";
|
||||
import { UserLevelSelector } from "./UserLevelSelector";
|
||||
import { ConfigPreview } from "./ConfigPreview";
|
||||
import { TaskCreationGuide } from "./TaskCreationGuide";
|
||||
import { OnboardingComplete } from "./OnboardingComplete";
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
export interface OnboardingState {
|
||||
currentStep: OnboardingStep;
|
||||
selectedConfig?: OnboardingConfig;
|
||||
skipTaskGuide: boolean;
|
||||
isCompleting: boolean;
|
||||
userHasChanges: boolean;
|
||||
changesSummary: string[];
|
||||
}
|
||||
|
||||
export class OnboardingView extends ItemView {
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private configManager: OnboardingConfigManager;
|
||||
private settingsDetector: SettingsChangeDetector;
|
||||
private onComplete: () => void;
|
||||
private state: OnboardingState;
|
||||
|
||||
// Step components
|
||||
private userLevelSelector: UserLevelSelector;
|
||||
private configPreview: ConfigPreview;
|
||||
private taskCreationGuide: TaskCreationGuide;
|
||||
private onboardingComplete: OnboardingComplete;
|
||||
|
||||
// UI Elements
|
||||
private onboardingHeaderEl: HTMLElement;
|
||||
private onboardingContentEl: HTMLElement;
|
||||
private footerEl: HTMLElement;
|
||||
private nextButton: ButtonComponent;
|
||||
private backButton: ButtonComponent;
|
||||
private skipButton: ButtonComponent;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: TaskProgressBarPlugin, onComplete: () => void) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.configManager = new OnboardingConfigManager(plugin);
|
||||
this.settingsDetector = new SettingsChangeDetector(plugin);
|
||||
this.onComplete = onComplete;
|
||||
|
||||
// Initialize state
|
||||
this.state = {
|
||||
currentStep: OnboardingStep.SETTINGS_CHECK,
|
||||
skipTaskGuide: false,
|
||||
isCompleting: false,
|
||||
userHasChanges: this.settingsDetector.hasUserMadeChanges(),
|
||||
changesSummary: this.settingsDetector.getChangesSummary(),
|
||||
};
|
||||
|
||||
// Initialize components
|
||||
this.userLevelSelector = new UserLevelSelector(this.configManager);
|
||||
this.configPreview = new ConfigPreview(this.configManager);
|
||||
this.taskCreationGuide = new TaskCreationGuide(this.plugin);
|
||||
this.onboardingComplete = new OnboardingComplete();
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return ONBOARDING_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return t("Task Genius Setup");
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return "zap";
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.createViewStructure();
|
||||
this.displayCurrentStep();
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Cleanup when view is closed
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the basic view structure
|
||||
*/
|
||||
private createViewStructure() {
|
||||
const container = this.contentEl;
|
||||
container.empty();
|
||||
container.addClass("onboarding-view");
|
||||
|
||||
// Header section
|
||||
this.onboardingHeaderEl = container.createDiv("onboarding-header");
|
||||
|
||||
// Main content section
|
||||
this.onboardingContentEl = container.createDiv("onboarding-content");
|
||||
|
||||
// Footer with navigation buttons
|
||||
this.footerEl = container.createDiv("onboarding-footer");
|
||||
this.createFooterButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create footer navigation buttons
|
||||
*/
|
||||
private createFooterButtons() {
|
||||
const buttonContainer = this.footerEl.createDiv("onboarding-buttons");
|
||||
|
||||
// Skip button (shown on appropriate steps)
|
||||
this.skipButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Skip setup"))
|
||||
.onClick(() => this.handleSkip());
|
||||
|
||||
// Back button
|
||||
this.backButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Back"))
|
||||
.onClick(() => this.handleBack());
|
||||
|
||||
// Next button
|
||||
this.nextButton = new ButtonComponent(buttonContainer)
|
||||
.setButtonText(t("Next"))
|
||||
.setCta()
|
||||
.onClick(() => this.handleNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the current step content
|
||||
*/
|
||||
private displayCurrentStep() {
|
||||
// Clear content
|
||||
this.onboardingHeaderEl.empty();
|
||||
this.onboardingContentEl.empty();
|
||||
|
||||
// Update button visibility
|
||||
this.updateButtonStates();
|
||||
|
||||
switch (this.state.currentStep) {
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
this.displaySettingsCheckStep();
|
||||
break;
|
||||
case OnboardingStep.WELCOME:
|
||||
this.displayWelcomeStep();
|
||||
break;
|
||||
case OnboardingStep.USER_LEVEL_SELECT:
|
||||
this.displayUserLevelSelectStep();
|
||||
break;
|
||||
case OnboardingStep.CONFIG_PREVIEW:
|
||||
this.displayConfigPreviewStep();
|
||||
break;
|
||||
case OnboardingStep.TASK_CREATION_GUIDE:
|
||||
this.displayTaskCreationGuideStep();
|
||||
break;
|
||||
case OnboardingStep.COMPLETE:
|
||||
this.displayCompleteStep();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display settings check step (new async approach)
|
||||
*/
|
||||
private displaySettingsCheckStep() {
|
||||
// Header
|
||||
this.onboardingHeaderEl.createEl("h1", { text: t("Task Genius Setup") });
|
||||
|
||||
// Content
|
||||
const content = this.onboardingContentEl;
|
||||
|
||||
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",
|
||||
});
|
||||
|
||||
const checkSection = content.createDiv("settings-check-section");
|
||||
|
||||
// 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 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 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;
|
||||
this.displayCurrentStep();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display welcome step
|
||||
*/
|
||||
private displayWelcomeStep() {
|
||||
// Header
|
||||
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"
|
||||
),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
// Content - reuse existing welcome step logic from modal
|
||||
const content = this.onboardingContentEl;
|
||||
const welcomeSection = content.createDiv("welcome-section");
|
||||
|
||||
// Plugin features overview
|
||||
const featuresContainer = welcomeSection.createDiv("features-overview");
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: "bar-chart-3",
|
||||
title: t("Progress Tracking"),
|
||||
description: t(
|
||||
"Visual progress bars and completion tracking for all your tasks"
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: "building",
|
||||
title: t("Project Management"),
|
||||
description: t(
|
||||
"Organize tasks by projects with advanced filtering and sorting"
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: "zap",
|
||||
title: t("Workflow Automation"),
|
||||
description: t(
|
||||
"Automate task status changes and improve your productivity"
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: "calendar",
|
||||
title: t("Multiple Views"),
|
||||
description: t(
|
||||
"Kanban boards, calendars, Gantt charts, and more visualization options"
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
features.forEach((feature) => {
|
||||
const featureEl = featuresContainer.createDiv("feature-item");
|
||||
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 });
|
||||
});
|
||||
|
||||
// Setup note
|
||||
const setupNote = content.createDiv("setup-note");
|
||||
setupNote.createEl("p", {
|
||||
text: t(
|
||||
"This quick setup will help you configure Task Genius based on your experience level and needs. You can always change these settings later."
|
||||
),
|
||||
cls: "setup-description",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display user level selection step
|
||||
*/
|
||||
private displayUserLevelSelectStep() {
|
||||
// Header
|
||||
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"
|
||||
),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
// Content
|
||||
this.userLevelSelector.render(this.onboardingContentEl, (config) => {
|
||||
this.state.selectedConfig = config;
|
||||
this.updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display configuration preview step
|
||||
*/
|
||||
private displayConfigPreviewStep() {
|
||||
if (!this.state.selectedConfig) {
|
||||
this.state.currentStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
this.displayCurrentStep();
|
||||
return;
|
||||
}
|
||||
|
||||
// Header
|
||||
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"
|
||||
),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
// Content
|
||||
this.configPreview.render(
|
||||
this.onboardingContentEl,
|
||||
this.state.selectedConfig
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task creation guide step
|
||||
*/
|
||||
private displayTaskCreationGuideStep() {
|
||||
// Header
|
||||
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",
|
||||
});
|
||||
|
||||
// Content
|
||||
this.taskCreationGuide.render(this.onboardingContentEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display completion step
|
||||
*/
|
||||
private displayCompleteStep() {
|
||||
if (!this.state.selectedConfig) return;
|
||||
|
||||
// Header
|
||||
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",
|
||||
});
|
||||
|
||||
// Content
|
||||
this.onboardingComplete.render(
|
||||
this.onboardingContentEl,
|
||||
this.state.selectedConfig
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update button states based on current step
|
||||
*/
|
||||
private updateButtonStates() {
|
||||
const step = this.state.currentStep;
|
||||
|
||||
// Skip button - show on settings check and welcome
|
||||
this.skipButton.buttonEl.style.display =
|
||||
step === OnboardingStep.SETTINGS_CHECK || step === OnboardingStep.WELCOME
|
||||
? "inline-block" : "none";
|
||||
|
||||
// Back button - hide on first two steps
|
||||
this.backButton.buttonEl.style.display =
|
||||
step <= OnboardingStep.WELCOME ? "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 {
|
||||
this.nextButton.buttonEl.style.display = "inline-block";
|
||||
this.nextButton.setButtonText(
|
||||
isLastStep ? t("Start Using Task Genius") : t("Next")
|
||||
);
|
||||
}
|
||||
|
||||
// Enable/disable next based on selection
|
||||
if (step === OnboardingStep.USER_LEVEL_SELECT) {
|
||||
this.nextButton.setDisabled(!this.state.selectedConfig);
|
||||
} else {
|
||||
this.nextButton.setDisabled(this.state.isCompleting);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle skip onboarding
|
||||
*/
|
||||
private async handleSkip() {
|
||||
await this.configManager.skipOnboarding();
|
||||
this.onComplete();
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
) {
|
||||
this.state.currentStep--;
|
||||
}
|
||||
|
||||
this.displayCurrentStep();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle next navigation
|
||||
*/
|
||||
private async handleNext() {
|
||||
const step = this.state.currentStep;
|
||||
|
||||
// Handle completion
|
||||
if (step === OnboardingStep.COMPLETE) {
|
||||
await this.completeOnboarding();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate current step
|
||||
if (!this.validateCurrentStep()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next step
|
||||
this.state.currentStep++;
|
||||
|
||||
// Apply configuration when moving to preview
|
||||
if (
|
||||
this.state.currentStep === OnboardingStep.CONFIG_PREVIEW &&
|
||||
this.state.selectedConfig
|
||||
) {
|
||||
try {
|
||||
await this.configManager.applyConfiguration(
|
||||
this.state.selectedConfig.mode
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to apply configuration:", error);
|
||||
// Continue anyway, user can adjust in settings
|
||||
}
|
||||
}
|
||||
|
||||
// Skip task guide if requested
|
||||
if (
|
||||
this.state.currentStep === OnboardingStep.TASK_CREATION_GUIDE &&
|
||||
this.state.skipTaskGuide
|
||||
) {
|
||||
this.state.currentStep++;
|
||||
}
|
||||
|
||||
this.displayCurrentStep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate current step before proceeding
|
||||
*/
|
||||
private validateCurrentStep(): boolean {
|
||||
switch (this.state.currentStep) {
|
||||
case OnboardingStep.USER_LEVEL_SELECT:
|
||||
return !!this.state.selectedConfig;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete onboarding process
|
||||
*/
|
||||
private async completeOnboarding() {
|
||||
if (!this.state.selectedConfig || this.state.isCompleting) return;
|
||||
|
||||
this.state.isCompleting = true;
|
||||
this.updateButtonStates();
|
||||
|
||||
try {
|
||||
// Mark onboarding as completed
|
||||
await this.configManager.completeOnboarding(
|
||||
this.state.selectedConfig.mode
|
||||
);
|
||||
|
||||
// Close view and trigger callback
|
||||
this.onComplete();
|
||||
this.close();
|
||||
} catch (error) {
|
||||
console.error("Failed to complete onboarding:", error);
|
||||
this.state.isCompleting = false;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the onboarding view
|
||||
*/
|
||||
private close() {
|
||||
this.leaf.detach();
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,9 @@ export class TaskCreationGuide {
|
|||
|
||||
const emojiLegend = emojiFormat.createDiv("format-legend");
|
||||
emojiLegend.createEl("small", {
|
||||
text: "📅 = Due date, 🔺 = High priority, # = Project tag",
|
||||
text: t(
|
||||
"📅 = Due date, 🔺 = High priority, #project/ = Docs project tag"
|
||||
),
|
||||
});
|
||||
|
||||
// Dataview format
|
||||
|
|
@ -74,7 +76,7 @@ export class TaskCreationGuide {
|
|||
|
||||
const mixedLegend = mixedFormat.createDiv("format-legend");
|
||||
mixedLegend.createEl("small", {
|
||||
text: "Combine emoji and dataview syntax as needed",
|
||||
text: t("Combine emoji and dataview syntax as needed"),
|
||||
});
|
||||
|
||||
// Status markers
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { OnboardingConfigManager, OnboardingConfig } from "../../utils/OnboardingConfigManager";
|
||||
import {
|
||||
OnboardingConfigManager,
|
||||
OnboardingConfig,
|
||||
} from "../../utils/OnboardingConfigManager";
|
||||
import { t } from "../../translations/helper";
|
||||
import { setIcon } from "obsidian";
|
||||
|
||||
|
|
@ -14,17 +17,20 @@ export class UserLevelSelector {
|
|||
/**
|
||||
* Render the user level selector
|
||||
*/
|
||||
render(containerEl: HTMLElement, onSelectionChange: (config: OnboardingConfig) => void) {
|
||||
render(
|
||||
containerEl: HTMLElement,
|
||||
onSelectionChange: (config: OnboardingConfig) => void
|
||||
) {
|
||||
this.onSelectionChange = onSelectionChange;
|
||||
containerEl.empty();
|
||||
|
||||
const configs = this.configManager.getOnboardingConfigs();
|
||||
|
||||
|
||||
// Create card container
|
||||
const cardsContainer = containerEl.createDiv("user-level-cards");
|
||||
|
||||
// Create cards for each configuration
|
||||
configs.forEach(config => {
|
||||
configs.forEach((config) => {
|
||||
this.createConfigCard(cardsContainer, config);
|
||||
});
|
||||
}
|
||||
|
|
@ -38,35 +44,35 @@ export class UserLevelSelector {
|
|||
|
||||
// Card header with icon and title
|
||||
const cardHeader = card.createDiv("card-header");
|
||||
|
||||
|
||||
const iconEl = cardHeader.createDiv("card-icon");
|
||||
setIcon(iconEl, this.getConfigIcon(config.mode));
|
||||
|
||||
const titleEl = cardHeader.createEl("h3", {
|
||||
|
||||
const titleEl = cardHeader.createEl("h3", {
|
||||
text: config.name,
|
||||
cls: "card-title"
|
||||
cls: "card-title",
|
||||
});
|
||||
|
||||
// Card description
|
||||
const descEl = card.createEl("p", {
|
||||
const descEl = card.createEl("p", {
|
||||
text: config.description,
|
||||
cls: "card-description"
|
||||
cls: "card-description",
|
||||
});
|
||||
|
||||
// Features list
|
||||
const featuresEl = card.createDiv("card-features");
|
||||
const featuresList = featuresEl.createEl("ul");
|
||||
|
||||
config.features.forEach(feature => {
|
||||
|
||||
config.features.forEach((feature) => {
|
||||
const featureItem = featuresList.createEl("li");
|
||||
featureItem.setText(feature);
|
||||
});
|
||||
|
||||
// Recommendation badge for beginner
|
||||
if (config.mode === 'beginner') {
|
||||
const badge = card.createDiv("recommendation-badge");
|
||||
badge.setText(t("Recommended for new users"));
|
||||
}
|
||||
// if (config.mode === 'beginner') {
|
||||
// const badge = card.createDiv("recommendation-badge");
|
||||
// badge.setText(t("Recommended for new users"));
|
||||
// }
|
||||
|
||||
// Click handler
|
||||
card.addEventListener("click", () => {
|
||||
|
|
@ -88,11 +94,11 @@ export class UserLevelSelector {
|
|||
*/
|
||||
private getConfigIcon(mode: string): string {
|
||||
switch (mode) {
|
||||
case 'beginner':
|
||||
case "beginner":
|
||||
return "edit-3"; // Lucide edit icon
|
||||
case 'advanced':
|
||||
case "advanced":
|
||||
return "settings"; // Lucide settings icon
|
||||
case 'power':
|
||||
case "power":
|
||||
return "zap"; // Lucide lightning bolt icon
|
||||
default:
|
||||
return "clipboard-list"; // Lucide clipboard icon
|
||||
|
|
@ -105,7 +111,9 @@ export class UserLevelSelector {
|
|||
private selectConfig(config: OnboardingConfig) {
|
||||
// Remove previous selection
|
||||
if (this.selectedConfig) {
|
||||
const prevCard = document.querySelector(`[data-mode="${this.selectedConfig.mode}"]`);
|
||||
const prevCard = document.querySelector(
|
||||
`[data-mode="${this.selectedConfig.mode}"]`
|
||||
);
|
||||
prevCard?.removeClass("selected");
|
||||
}
|
||||
|
||||
|
|
@ -124,4 +132,4 @@ export class UserLevelSelector {
|
|||
getSelectedConfig(): OnboardingConfig | null {
|
||||
return this.selectedConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
src/index.ts
61
src/index.ts
|
|
@ -100,7 +100,8 @@ import { IcsManager } from "./utils/ics/IcsManager";
|
|||
import { VersionManager } from "./utils/VersionManager";
|
||||
import { RebuildProgressManager } from "./utils/RebuildProgressManager";
|
||||
import { OnboardingConfigManager } from "./utils/OnboardingConfigManager";
|
||||
import { OnboardingModal } from "./components/onboarding/OnboardingModal";
|
||||
import { SettingsChangeDetector } from "./utils/SettingsChangeDetector";
|
||||
import { OnboardingView, ONBOARDING_VIEW_TYPE } from "./components/onboarding/OnboardingView";
|
||||
|
||||
class TaskProgressBarPopover extends HoverPopover {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
|
|
@ -204,6 +205,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Onboarding manager instance
|
||||
onboardingConfigManager: OnboardingConfigManager;
|
||||
settingsChangeDetector: SettingsChangeDetector;
|
||||
|
||||
// Preloaded tasks:
|
||||
preloadedTasks: Task[] = [];
|
||||
|
|
@ -231,6 +233,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Initialize onboarding config manager
|
||||
this.onboardingConfigManager = new OnboardingConfigManager(this);
|
||||
this.settingsChangeDetector = new SettingsChangeDetector(this);
|
||||
|
||||
// Initialize global suggest manager
|
||||
this.globalSuggestManager = new SuggestManager(this.app, this);
|
||||
|
|
@ -417,6 +420,16 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
(leaf) => new TimelineSidebarView(leaf, this)
|
||||
);
|
||||
|
||||
// Register the Onboarding View
|
||||
this.registerView(
|
||||
ONBOARDING_VIEW_TYPE,
|
||||
(leaf) => new OnboardingView(leaf, this, () => {
|
||||
console.log("Onboarding completed successfully");
|
||||
// Close the onboarding view and refresh views
|
||||
leaf.detach();
|
||||
})
|
||||
);
|
||||
|
||||
// Add a ribbon icon for opening the TaskView
|
||||
this.addRibbonIcon(
|
||||
"task-genius",
|
||||
|
|
@ -442,6 +455,15 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.activateTimelineSidebarView();
|
||||
},
|
||||
});
|
||||
|
||||
// Add a command to open the Onboarding/Setup View
|
||||
this.addCommand({
|
||||
id: "open-task-genius-setup",
|
||||
name: t("Open Task Genius Setup"),
|
||||
callback: () => {
|
||||
this.openOnboardingView();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (this.settings.habit.enableHabits) {
|
||||
|
|
@ -1080,22 +1102,23 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check and show onboarding for first-time users
|
||||
* Check and show onboarding for first-time users or users who request it
|
||||
*/
|
||||
private async checkAndShowOnboarding(): Promise<void> {
|
||||
try {
|
||||
// Check if this is the first install and onboarding hasn't been completed
|
||||
const versionResult = await this.versionManager.checkVersionChange();
|
||||
const shouldShowOnboarding = versionResult.versionInfo.isFirstInstall &&
|
||||
this.onboardingConfigManager.shouldShowOnboarding();
|
||||
const isFirstInstall = versionResult.versionInfo.isFirstInstall;
|
||||
const shouldShowOnboarding = this.onboardingConfigManager.shouldShowOnboarding();
|
||||
|
||||
if (shouldShowOnboarding) {
|
||||
// For existing users with changes, let the view handle the async detection
|
||||
// For new users, show onboarding directly
|
||||
if ((isFirstInstall && shouldShowOnboarding) ||
|
||||
(!isFirstInstall && shouldShowOnboarding && this.settingsChangeDetector.hasUserMadeChanges())) {
|
||||
|
||||
// Small delay to ensure UI is ready
|
||||
setTimeout(() => {
|
||||
new OnboardingModal(this.app, this, () => {
|
||||
console.log("Onboarding completed successfully");
|
||||
// Optional: refresh views or trigger other updates
|
||||
}).open();
|
||||
this.openOnboardingView();
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -1103,6 +1126,26 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the onboarding view in a new leaf
|
||||
*/
|
||||
async openOnboardingView(): Promise<void> {
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if onboarding view is already open
|
||||
const existingLeaf = workspace.getLeavesOfType(ONBOARDING_VIEW_TYPE)[0];
|
||||
|
||||
if (existingLeaf) {
|
||||
workspace.revealLeaf(existingLeaf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new leaf in the main area and open the onboarding view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
await leaf.setViewState({ type: ONBOARDING_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
const savedData = await this.loadData();
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -310,7 +310,7 @@ export class OnboardingConfigManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Apply configuration template to plugin settings
|
||||
* Apply configuration template to plugin settings with safe view merging
|
||||
*/
|
||||
async applyConfiguration(mode: OnboardingConfigMode): Promise<void> {
|
||||
const configs = this.getOnboardingConfigs();
|
||||
|
|
@ -320,8 +320,22 @@ export class OnboardingConfigManager {
|
|||
throw new Error(`Configuration mode ${mode} not found`);
|
||||
}
|
||||
|
||||
// Deep merge the selected configuration with current settings
|
||||
const newSettings = this.deepMerge(this.plugin.settings, selectedConfig.settings);
|
||||
// Preserve user's custom views before applying configuration
|
||||
const currentViews = this.plugin.settings.viewConfiguration || [];
|
||||
const userCustomViews = currentViews.filter(view => view.type === 'custom');
|
||||
const templateViews = selectedConfig.settings.viewConfiguration || [];
|
||||
|
||||
// Smart merge: keep user custom views, update/add template views
|
||||
const mergedViews = this.mergeViewConfigurations(templateViews, userCustomViews);
|
||||
|
||||
// Deep merge the selected configuration with current settings, excluding viewConfiguration
|
||||
const configWithoutViews = { ...selectedConfig.settings };
|
||||
delete configWithoutViews.viewConfiguration;
|
||||
|
||||
const newSettings = this.deepMerge(this.plugin.settings, configWithoutViews);
|
||||
|
||||
// Apply the safely merged view configuration
|
||||
newSettings.viewConfiguration = mergedViews;
|
||||
|
||||
// Update onboarding status
|
||||
if (!newSettings.onboarding) {
|
||||
|
|
@ -333,7 +347,7 @@ export class OnboardingConfigManager {
|
|||
this.plugin.settings = newSettings as TaskProgressBarSettings;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
console.log(`Applied ${mode} configuration template`);
|
||||
console.log(`Applied ${mode} configuration template with ${userCustomViews.length} user custom views preserved`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -402,6 +416,68 @@ export class OnboardingConfigManager {
|
|||
return currentConfig ? currentConfig.name : t('Custom');
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge view configurations safely, preserving user custom views
|
||||
*/
|
||||
private mergeViewConfigurations(templateViews: ViewConfig[], userCustomViews: ViewConfig[]): ViewConfig[] {
|
||||
// Start with template views (these define which default views are enabled for this mode)
|
||||
const mergedViews: ViewConfig[] = [...templateViews];
|
||||
|
||||
// Add all user custom views (these are always preserved)
|
||||
userCustomViews.forEach(userView => {
|
||||
// Ensure no duplicate IDs (shouldn't happen with custom views, but safety first)
|
||||
if (!mergedViews.find(view => view.id === userView.id)) {
|
||||
mergedViews.push(userView);
|
||||
}
|
||||
});
|
||||
|
||||
return mergedViews;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get preview of configuration changes without applying them
|
||||
*/
|
||||
getConfigurationPreview(mode: OnboardingConfigMode): {
|
||||
viewsToAdd: ViewConfig[];
|
||||
viewsToUpdate: ViewConfig[];
|
||||
userCustomViewsPreserved: ViewConfig[];
|
||||
settingsChanges: string[];
|
||||
} {
|
||||
const configs = this.getOnboardingConfigs();
|
||||
const selectedConfig = configs.find(config => config.mode === mode);
|
||||
|
||||
if (!selectedConfig) {
|
||||
throw new Error(`Configuration mode ${mode} not found`);
|
||||
}
|
||||
|
||||
const currentViews = this.plugin.settings.viewConfiguration || [];
|
||||
const userCustomViews = currentViews.filter(view => view.type === 'custom');
|
||||
const templateViews = selectedConfig.settings.viewConfiguration || [];
|
||||
|
||||
const currentViewIds = new Set(currentViews.map(view => view.id));
|
||||
const viewsToAdd = templateViews.filter(view => !currentViewIds.has(view.id));
|
||||
const viewsToUpdate = templateViews.filter(view => currentViewIds.has(view.id));
|
||||
|
||||
// Analyze setting changes (simplified for now)
|
||||
const settingsChanges: string[] = [];
|
||||
if (selectedConfig.settings.enableView !== this.plugin.settings.enableView) {
|
||||
settingsChanges.push(`Views ${selectedConfig.settings.enableView ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
if (selectedConfig.settings.quickCapture?.enableQuickCapture !== this.plugin.settings.quickCapture?.enableQuickCapture) {
|
||||
settingsChanges.push(`Quick Capture ${selectedConfig.settings.quickCapture?.enableQuickCapture ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
if (selectedConfig.settings.workflow?.enableWorkflow !== this.plugin.settings.workflow?.enableWorkflow) {
|
||||
settingsChanges.push(`Workflow ${selectedConfig.settings.workflow?.enableWorkflow ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
|
||||
return {
|
||||
viewsToAdd,
|
||||
viewsToUpdate,
|
||||
userCustomViewsPreserved: userCustomViews,
|
||||
settingsChanges
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge utility function
|
||||
*/
|
||||
|
|
|
|||
217
src/utils/SettingsChangeDetector.ts
Normal file
217
src/utils/SettingsChangeDetector.ts
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
import type TaskProgressBarPlugin from "../index";
|
||||
import { TaskProgressBarSettings, DEFAULT_SETTINGS } from "../common/setting-definition";
|
||||
import { t } from "../translations/helper";
|
||||
|
||||
/**
|
||||
* Service to detect if user has made changes to plugin settings
|
||||
* Used to determine if onboarding should be offered
|
||||
*/
|
||||
export class SettingsChangeDetector {
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
|
||||
constructor(plugin: TaskProgressBarPlugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has made significant changes to settings that would indicate
|
||||
* they have already configured the plugin
|
||||
*/
|
||||
hasUserMadeChanges(): boolean {
|
||||
const current = this.plugin.settings;
|
||||
const defaults = DEFAULT_SETTINGS;
|
||||
|
||||
// Check for significant configuration changes
|
||||
const significantChanges = [
|
||||
// Custom views added
|
||||
this.hasCustomViews(current),
|
||||
|
||||
// Progress bar settings changed
|
||||
this.isProgressBarCustomized(current, defaults),
|
||||
|
||||
// Task status settings changed
|
||||
this.isTaskStatusCustomized(current, defaults),
|
||||
|
||||
// Quick capture configured differently
|
||||
this.isQuickCaptureCustomized(current, defaults),
|
||||
|
||||
// Workflow settings changed
|
||||
this.isWorkflowCustomized(current, defaults),
|
||||
|
||||
// Advanced features enabled
|
||||
this.areAdvancedFeaturesEnabled(current, defaults),
|
||||
|
||||
// File parsing customized
|
||||
this.isFileParsingCustomized(current, defaults),
|
||||
];
|
||||
|
||||
return significantChanges.some(changed => changed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of what changes the user has made
|
||||
*/
|
||||
getChangesSummary(): string[] {
|
||||
const changes: string[] = [];
|
||||
const current = this.plugin.settings;
|
||||
const defaults = DEFAULT_SETTINGS;
|
||||
|
||||
if (this.hasCustomViews(current)) {
|
||||
const customViewCount = current.viewConfiguration?.filter(v => v.type === 'custom').length || 0;
|
||||
changes.push(t("Custom views created") + ` (${customViewCount})`);
|
||||
}
|
||||
|
||||
if (this.isProgressBarCustomized(current, defaults)) {
|
||||
changes.push(t("Progress bar settings modified"));
|
||||
}
|
||||
|
||||
if (this.isTaskStatusCustomized(current, defaults)) {
|
||||
changes.push(t("Task status settings configured"));
|
||||
}
|
||||
|
||||
if (this.isQuickCaptureCustomized(current, defaults)) {
|
||||
changes.push(t("Quick capture configured"));
|
||||
}
|
||||
|
||||
if (this.isWorkflowCustomized(current, defaults)) {
|
||||
changes.push(t("Workflow settings enabled"));
|
||||
}
|
||||
|
||||
if (this.areAdvancedFeaturesEnabled(current, defaults)) {
|
||||
changes.push(t("Advanced features enabled"));
|
||||
}
|
||||
|
||||
if (this.isFileParsingCustomized(current, defaults)) {
|
||||
changes.push(t("File parsing customized"));
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has created custom views
|
||||
*/
|
||||
private hasCustomViews(settings: TaskProgressBarSettings): boolean {
|
||||
return settings.viewConfiguration?.some(view => view.type === 'custom') ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if progress bar settings have been customized
|
||||
*/
|
||||
private isProgressBarCustomized(current: TaskProgressBarSettings, defaults: TaskProgressBarSettings): boolean {
|
||||
return (
|
||||
current.progressBarDisplayMode !== defaults.progressBarDisplayMode ||
|
||||
current.displayMode !== defaults.displayMode ||
|
||||
current.showPercentage !== defaults.showPercentage ||
|
||||
current.customizeProgressRanges !== defaults.customizeProgressRanges ||
|
||||
current.allowCustomProgressGoal !== defaults.allowCustomProgressGoal ||
|
||||
current.hideProgressBarBasedOnConditions !== defaults.hideProgressBarBasedOnConditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if task status settings have been customized
|
||||
*/
|
||||
private isTaskStatusCustomized(current: TaskProgressBarSettings, defaults: TaskProgressBarSettings): boolean {
|
||||
return (
|
||||
current.enableTaskStatusSwitcher !== defaults.enableTaskStatusSwitcher ||
|
||||
current.enableCustomTaskMarks !== defaults.enableCustomTaskMarks ||
|
||||
current.enableCycleCompleteStatus !== defaults.enableCycleCompleteStatus
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if quick capture has been customized
|
||||
*/
|
||||
private isQuickCaptureCustomized(current: TaskProgressBarSettings, defaults: TaskProgressBarSettings): boolean {
|
||||
const currentQC = current.quickCapture || defaults.quickCapture;
|
||||
const defaultQC = defaults.quickCapture;
|
||||
|
||||
return (
|
||||
currentQC.enableQuickCapture !== defaultQC.enableQuickCapture ||
|
||||
currentQC.enableMinimalMode !== defaultQC.enableMinimalMode
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if workflow has been customized
|
||||
*/
|
||||
private isWorkflowCustomized(current: TaskProgressBarSettings, defaults: TaskProgressBarSettings): boolean {
|
||||
const currentWF = current.workflow || defaults.workflow;
|
||||
const defaultWF = defaults.workflow;
|
||||
|
||||
return (
|
||||
currentWF.enableWorkflow !== defaultWF.enableWorkflow ||
|
||||
currentWF.autoAddTimestamp !== defaultWF.autoAddTimestamp ||
|
||||
currentWF.calculateSpentTime !== defaultWF.calculateSpentTime
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if advanced features are enabled
|
||||
*/
|
||||
private areAdvancedFeaturesEnabled(current: TaskProgressBarSettings, defaults: TaskProgressBarSettings): boolean {
|
||||
return (
|
||||
current.rewards?.enableRewards !== defaults.rewards?.enableRewards ||
|
||||
current.habit?.enableHabits !== defaults.habit?.enableHabits ||
|
||||
current.timelineSidebar?.enableTimelineSidebar !== defaults.timelineSidebar?.enableTimelineSidebar ||
|
||||
current.betaTest?.enableBaseView !== defaults.betaTest?.enableBaseView
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file parsing has been customized
|
||||
*/
|
||||
private isFileParsingCustomized(current: TaskProgressBarSettings, defaults: TaskProgressBarSettings): boolean {
|
||||
const currentFP = current.fileParsingConfig || defaults.fileParsingConfig;
|
||||
const defaultFP = defaults.fileParsingConfig;
|
||||
|
||||
return (
|
||||
currentFP.enableWorkerProcessing !== defaultFP.enableWorkerProcessing ||
|
||||
currentFP.enableFileMetadataParsing !== defaultFP.enableFileMetadataParsing ||
|
||||
currentFP.enableTagBasedTaskParsing !== defaultFP.enableTagBasedTaskParsing ||
|
||||
currentFP.enableMtimeOptimization !== defaultFP.enableMtimeOptimization
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a settings snapshot for later comparison
|
||||
*/
|
||||
createSettingsSnapshot(): string {
|
||||
const snapshot = {
|
||||
customViewCount: this.plugin.settings.viewConfiguration?.filter(v => v.type === 'custom').length || 0,
|
||||
progressBarMode: this.plugin.settings.progressBarDisplayMode,
|
||||
taskStatusEnabled: this.plugin.settings.enableTaskStatusSwitcher,
|
||||
quickCaptureEnabled: this.plugin.settings.quickCapture?.enableQuickCapture,
|
||||
workflowEnabled: this.plugin.settings.workflow?.enableWorkflow,
|
||||
rewardsEnabled: this.plugin.settings.rewards?.enableRewards,
|
||||
habitsEnabled: this.plugin.settings.habit?.enableHabits,
|
||||
workerProcessingEnabled: this.plugin.settings.fileParsingConfig?.enableWorkerProcessing,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
return JSON.stringify(snapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare current settings with a snapshot to detect changes
|
||||
*/
|
||||
hasChangedSinceSnapshot(snapshot: string): boolean {
|
||||
try {
|
||||
const oldSnapshot = JSON.parse(snapshot);
|
||||
const currentSnapshot = JSON.parse(this.createSettingsSnapshot());
|
||||
|
||||
// Compare key fields (excluding timestamp)
|
||||
const fieldsToCompare = [
|
||||
'customViewCount', 'progressBarMode', 'taskStatusEnabled',
|
||||
'quickCaptureEnabled', 'workflowEnabled', 'rewardsEnabled',
|
||||
'habitsEnabled', 'workerProcessingEnabled'
|
||||
];
|
||||
|
||||
return fieldsToCompare.some(field => oldSnapshot[field] !== currentSnapshot[field]);
|
||||
} catch (error) {
|
||||
console.warn("Failed to compare settings snapshot:", error);
|
||||
return true; // Assume changes if we can't compare
|
||||
}
|
||||
}
|
||||
}
|
||||
11205
styles.css
11205
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue