Merge pull request #81 from devonthesofa/feature/experiments-beta-check

[feature] Gate dashboard and grouped status view behind experimental settings
This commit is contained in:
Aleix Soler 2025-11-21 16:04:14 +01:00 committed by GitHub
commit 3cfd46b848
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 149 additions and 17 deletions

View file

@ -322,6 +322,43 @@ export const UISettings: React.FC<Props> = ({ settings, onChange }) => {
/>
</SettingItem>
<h3>Experimental features</h3>
<SettingItem
name="Enable experimental features"
description="Unlock beta views that are still under development."
>
<input
type="checkbox"
checked={settings.enableExperimentalFeatures || false}
onChange={handleChange("enableExperimentalFeatures")}
/>
</SettingItem>
<SettingItem
name="Enable status dashboard"
description="Show the ribbon shortcut to open the status dashboard view."
>
<input
type="checkbox"
disabled={!settings.enableExperimentalFeatures}
checked={settings.enableStatusDashboard || false}
onChange={handleChange("enableStatusDashboard")}
/>
</SettingItem>
<SettingItem
name="Enable grouped status view"
description="Show the ribbon shortcut to open the grouped status view."
>
<input
type="checkbox"
disabled={!settings.enableExperimentalFeatures}
checked={settings.enableGroupedStatusView || false}
onChange={handleChange("enableGroupedStatusView")}
/>
</SettingItem>
<h3>Behavior & Other</h3>
<SettingItem

View file

@ -27,6 +27,9 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = {
fileExplorerLeftBorder: false,
fileExplorerStatusDot: false,
fileExplorerUnderlineFileName: false,
enableExperimentalFeatures: true,
enableStatusDashboard: true,
enableGroupedStatusView: true,
enabledTemplates: DEFAULT_ENABLED_TEMPLATES,
useCustomStatusesOnly: false,
useMultipleStatuses: true,

View file

@ -3,6 +3,7 @@ import { NoteStatusService, BaseNoteStatusService } from "./noteStatusService";
import settingsService from "./settingsService";
import eventBus from "./eventBus";
import { VIEW_TYPE_EXAMPLE } from "../integrations/views/grouped-status-view";
import { isExperimentalFeatureEnabled } from "@/utils/experimentalFeatures";
export class CommandsService {
private plugin: Plugin;
@ -229,15 +230,17 @@ export class CommandsService {
});
this.registeredCommands.add("search-by-status");
// Open Status Pane command
this.plugin.addCommand({
id: "open-status-pane",
name: "Open status pane",
callback: async () => {
await this.openStatusPane();
},
});
this.registeredCommands.add("open-status-pane");
// Open Status Pane command (experimental)
if (this.shouldEnableGroupedView()) {
this.plugin.addCommand({
id: "open-status-pane",
name: "Open status pane",
callback: async () => {
await this.openStatusPane();
},
});
this.registeredCommands.add("open-status-pane");
}
}
private async openStatusPane(): Promise<void> {
@ -356,6 +359,10 @@ export class CommandsService {
this.registeredCommands.clear();
}
private shouldEnableGroupedView(): boolean {
return isExperimentalFeatureEnabled("groupedStatusView");
}
public destroy(): void {
// Remove all registered commands properly
this.removeRegisteredCommands();

View file

@ -32,7 +32,9 @@ export class CommandsIntegration {
if (
key === "quickStatusCommands" ||
key === "useMultipleStatuses" ||
key === "templates"
key === "templates" ||
key === "enableExperimentalFeatures" ||
key === "enableGroupedStatusView"
) {
this.commandsService.destroy();
/// BUG: if removed a command will persist because is not removed, you need the oldStates to send it to be disabled // const oldValue = this.settings[key]; // TODO: Send the old value

View file

@ -19,6 +19,7 @@ import {
} from "./integrations/views/status-dashboard-view";
import { StatusesInfoPopup } from "./integrations/popups/statusesInfoPopupIntegration";
import statusStoreManager from "./core/statusStoreManager";
import { isExperimentalFeatureEnabled } from "@/utils/experimentalFeatures";
export default class NoteStatusPlugin extends Plugin {
private statusBarIntegration: StatusBarIntegration;
@ -27,6 +28,7 @@ export default class NoteStatusPlugin extends Plugin {
private fileExplorerIntegration: FileExplorerIntegration;
private commandsIntegration: CommandsIntegration;
private editorToolbarIntegration: EditorToolbarIntegration;
private experimentalRibbonShortcuts: Map<string, HTMLElement> = new Map();
async onload() {
BaseNoteStatusService.initialize(this.app);
@ -53,13 +55,7 @@ export default class NoteStatusPlugin extends Plugin {
(leaf) => new StatusDashboardView(leaf),
);
this.addRibbonIcon("list-tree", "Open grouped status view", () => {
this.activateView();
});
this.addRibbonIcon("activity", "Open status dashboard", () => {
this.activateDashboard();
});
this.syncExperimentalFeatureShortcuts();
}
async onunload() {
@ -70,6 +66,8 @@ export default class NoteStatusPlugin extends Plugin {
this.commandsIntegration?.destroy();
this.editorToolbarIntegration?.destroy();
this.pluginSettingsIntegration?.destroy();
this.experimentalRibbonShortcuts.forEach((el) => el.remove());
this.experimentalRibbonShortcuts.clear();
// Clean up event subscriptions
eventBus.unsubscribe(
@ -148,6 +146,58 @@ export default class NoteStatusPlugin extends Plugin {
}
}
private syncExperimentalFeatureShortcuts(): void {
const shortcuts = this.getExperimentalShortcuts();
Object.entries(shortcuts).forEach(([key, config]) => {
this.toggleExperimentalRibbon(key, config);
});
}
private getExperimentalShortcuts() {
return {
groupedStatusView: {
feature: "groupedStatusView" as const,
icon: "list-tree",
title: "Open grouped status view",
handler: () => this.activateView(),
},
statusDashboard: {
feature: "statusDashboard" as const,
icon: "activity",
title: "Open status dashboard",
handler: () => this.activateDashboard(),
},
};
}
private toggleExperimentalRibbon(
key: string,
config: {
feature: Parameters<typeof isExperimentalFeatureEnabled>[0];
icon: string;
title: string;
handler: () => void;
},
): void {
const isEnabled = isExperimentalFeatureEnabled(config.feature);
const existingIcon = this.experimentalRibbonShortcuts.get(key);
if (isEnabled && !existingIcon) {
const ribbonEl = this.addRibbonIcon(
config.icon,
config.title,
config.handler,
);
this.experimentalRibbonShortcuts.set(key, ribbonEl);
return;
}
if (!isEnabled && existingIcon) {
existingIcon.remove();
this.experimentalRibbonShortcuts.delete(key);
}
}
private async loadEventBus() {
// Propagate to custom event bus the new active file
this.app.workspace.on(
@ -179,6 +229,13 @@ export default class NoteStatusPlugin extends Plugin {
if (key === "enableStatusOverviewPopup" && value === false) {
StatusesInfoPopup.close();
}
if (
key === "enableExperimentalFeatures" ||
key === "enableStatusDashboard" ||
key === "enableGroupedStatusView"
) {
this.syncExperimentalFeatureShortcuts();
}
},
"main-status-popup-setting-subscriptor",
);

View file

@ -28,6 +28,9 @@ export type PluginSettings = {
fileExplorerLeftBorder: boolean; // Whether to display a colored border on the explorer item
fileExplorerStatusDot: boolean; // Whether to append a small colored dot next to the filename
fileExplorerUnderlineFileName: boolean; // Whether to underline the filename using the status color
enableExperimentalFeatures: boolean; // Gate for experimental features
enableStatusDashboard: boolean; // Toggle for the status dashboard experiment
enableGroupedStatusView: boolean; // Toggle for the grouped status view experiment
enabledTemplates: string[]; // IDs of enabled templates
useCustomStatusesOnly: boolean; // Whether to use only custom statuses or include templates
useMultipleStatuses: boolean; // Whether to allow multiple statuses per note

View file

@ -0,0 +1,23 @@
import settingsService from "@/core/settingsService";
export type ExperimentalFeature = "statusDashboard" | "groupedStatusView";
const FEATURE_SETTING_MAP: Record<
ExperimentalFeature,
keyof typeof settingsService.settings
> = {
statusDashboard: "enableStatusDashboard",
groupedStatusView: "enableGroupedStatusView",
};
export function isExperimentalFeatureEnabled(
feature: ExperimentalFeature,
): boolean {
const settings = settingsService.settings;
if (!settings.enableExperimentalFeatures) {
return false;
}
const flagKey = FEATURE_SETTING_MAP[feature];
return Boolean(settings[flagKey]);
}