diff --git a/file_list.txt b/file_list.txt new file mode 100644 index 0000000..fb8caaa Binary files /dev/null and b/file_list.txt differ diff --git a/main.ts b/main.ts index b7f355d..665df2f 100644 --- a/main.ts +++ b/main.ts @@ -14,7 +14,7 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI { async onload() { sliceContainer.setPlugin(this); - + await this.loadSettings(); ServiceLoader.registerAllServices(); @@ -26,7 +26,7 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI { // Register all views using the centralized configuration // Note: Views are registered after services to avoid conflicts with CalendarNavigationService this.registerViews(); - + this.log?.info('Streams plugin loaded with vertical slice architecture'); } @@ -38,13 +38,13 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI { async loadSettings() { const loadedData = await this.loadData(); this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedData); - + // Migration: ensure barStyle exists if (!this.settings.barStyle) { this.settings.barStyle = 'default'; await this.saveSettings(); } - + // Migration: ensure encryptThisStream exists for existing streams let needsSave = false; for (const stream of this.settings.streams) { @@ -57,7 +57,7 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI { needsSave = true; } } - + if (needsSave) { await this.saveSettings(); } @@ -93,8 +93,8 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI { // ============================================================================ // Stream Management - async setActiveStream(streamId: string, force?: boolean): Promise { - await serviceRegistry.streamManagement?.setActiveStream(streamId, force); + async setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise { + await serviceRegistry.streamManagement?.setActiveStream(streamId, force, suppressEvent); } getActiveStream(): Stream | null { diff --git a/src/slices/calendar-navigation/ComponentLifecycleManager.ts b/src/slices/calendar-navigation/ComponentLifecycleManager.ts index f3ebc54..69f7f23 100644 --- a/src/slices/calendar-navigation/ComponentLifecycleManager.ts +++ b/src/slices/calendar-navigation/ComponentLifecycleManager.ts @@ -91,18 +91,18 @@ export class ComponentLifecycleManager { if (component && typeof component.updateReuseCurrentTab === 'function') { component.updateReuseCurrentTab(settings.reuseCurrentTab); } - + // Refresh bar style for all existing components if (component && typeof component.refreshBarStyle === 'function') { component.refreshBarStyle(); } - + // Update streams list for all existing components if (component && typeof component.updateStreamsList === 'function' && settings.streams) { component.updateStreamsList(settings.streams); } } - + // Force immediate refresh for mobile devices // Use requestAnimationFrame to ensure DOM updates are processed requestAnimationFrame(() => { @@ -140,6 +140,18 @@ export class ComponentLifecycleManager { return !!existingComponent; } + /** + * Get component for a specific leaf + */ + getComponentForLeaf(leaf: WorkspaceLeaf): StreamsBarComponent | undefined { + for (const component of this.calendarComponents.values()) { + if (component.leaf === leaf) { + return component; + } + } + return undefined; + } + /** * Get stream to use for a component (active stream or default) */ @@ -157,9 +169,18 @@ export class ComponentLifecycleManager { if (apiService) { const stream = apiService.getStreamForFile(filePath); if (stream) { - // This is a stream file, update the stream bar to reflect the file's stream and date - await apiService.updateStreamBarFromFile(filePath); - centralizedLogger.debug(`Updated stream bar for file: ${filePath}`); + // Find component for this leaf + const component = this.getComponentForLeaf(activeLeaf); + if (component) { + // Update component locally + component.updateActiveStream(stream); + + // Update global settings silently to ensure persistence without affecting other panels + // We use serviceRegistry.streamManagement directly because StreamsAPI doesn't expose suppressEvent + await serviceRegistry.streamManagement?.setActiveStream(stream.id, true, true); + + centralizedLogger.debug(`Updated stream bar for file: ${filePath}`); + } } } } diff --git a/src/slices/calendar-navigation/ComponentStateManager.ts b/src/slices/calendar-navigation/ComponentStateManager.ts index 6cad6da..b46b3cd 100644 --- a/src/slices/calendar-navigation/ComponentStateManager.ts +++ b/src/slices/calendar-navigation/ComponentStateManager.ts @@ -38,12 +38,6 @@ export class ComponentStateManager { * Get the display name for the active stream */ getDisplayStreamName(): string { - if (this.plugin?.settings?.activeStreamId) { - const activeStream = this.streams.find(s => s.id === this.plugin!.settings.activeStreamId); - if (activeStream) { - return activeStream.name; - } - } return this.selectedStream.name; } @@ -51,16 +45,13 @@ export class ComponentStateManager { * Get the active stream ID */ getActiveStreamId(): string { - return this.plugin?.settings?.activeStreamId || this.selectedStream.id; + return this.selectedStream.id; } /** * Get the active stream */ getActiveStream(): Stream { - if (this.plugin?.settings?.activeStreamId) { - return this.streams.find(s => s.id === this.plugin!.settings.activeStreamId) || this.selectedStream; - } return this.selectedStream; } @@ -69,13 +60,13 @@ export class ComponentStateManager { */ updateStreamEncryptionIcon(container: HTMLElement): void { const activeStream = this.getActiveStream(); - + // Remove existing encryption icon if it exists const existingIcon = container.querySelector('.streams-bar-encryption-icon'); if (existingIcon) { existingIcon.remove(); } - + // Add encryption icon if stream is encrypted if (activeStream.encryptThisStream) { const encryptionIcon = container.createDiv('streams-bar-encryption-icon'); @@ -92,12 +83,12 @@ export class ComponentStateManager { if (!this.plugin?.settings) { return; } - + const barStyle = this.plugin.settings.barStyle; - + // Remove existing style classes component.removeClass('modern-style'); - + // Apply the appropriate style class if (barStyle === 'modern') { component.addClass('modern-style'); @@ -112,11 +103,11 @@ export class ComponentStateManager { const todayYear = today.getFullYear(); const todayMonth = today.getMonth(); const todayDay = today.getDate(); - + const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth(); const currentDay = currentDate.getDate(); - + if (currentYear === todayYear && currentMonth === todayMonth && currentDay === todayDay) { return 'TODAY'; } else { diff --git a/src/slices/calendar-navigation/ComponentUIBuilder.ts b/src/slices/calendar-navigation/ComponentUIBuilder.ts index 68b5af7..5df0b84 100644 --- a/src/slices/calendar-navigation/ComponentUIBuilder.ts +++ b/src/slices/calendar-navigation/ComponentUIBuilder.ts @@ -40,6 +40,7 @@ export interface ComponentCallbacks { hideStreamsDropdown: () => void; updateTodayButton: () => void; isExpanded: () => boolean; + onStreamSelected: (stream: Stream) => void; } /** @@ -102,10 +103,10 @@ export class ComponentUIBuilder { this.setupExpandedViewScroll(expandedView); const navControls = collapsedView.createDiv('streams-bar-nav-controls'); - const { todayButton, streamSelector, streamsDropdown, changeStreamSection, changeStreamText } = + const { todayButton, streamSelector, streamsDropdown, changeStreamSection, changeStreamText } = this.setupCollapsedView(collapsedView, navControls); - const { prevButton, nextButton, dateDisplay, grid, calendarRenderer, currentMonthView } = + const { prevButton, nextButton, dateDisplay, grid, calendarRenderer, currentMonthView } = this.setupExpandedView(expandedView); const touchGestureHandler = this.setupCalendarHandlers( @@ -193,7 +194,7 @@ export class ComponentUIBuilder { this.app, this.settingsManager as any, this.reuseCurrentTab, - () => {} + this.callbacks.onStreamSelected ); streamSelector.populateStreamsDropdown(); @@ -306,7 +307,7 @@ export class ComponentUIBuilder { this.callbacks.toggleExpanded(); } }, - () => {} + () => { } ); calendarRenderer.updateCalendarGrid(); diff --git a/src/slices/calendar-navigation/StreamSelector.ts b/src/slices/calendar-navigation/StreamSelector.ts index b251db3..2499711 100644 --- a/src/slices/calendar-navigation/StreamSelector.ts +++ b/src/slices/calendar-navigation/StreamSelector.ts @@ -8,7 +8,7 @@ interface PluginInterface { settings: { activeStreamId?: string; }; - setActiveStream(streamId: string, force?: boolean): Promise; + setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise; } /** @@ -54,25 +54,25 @@ export class StreamSelector extends Component { */ populateStreamsDropdown(): void { if (!this.dropdown) return; - + this.dropdown.empty(); - + // Filter out disabled streams const enabledStreams = this.streams.filter(stream => !stream.disabled); - + enabledStreams.forEach(stream => { const streamItem = this.dropdown.createDiv('streams-bar-stream-item'); - + const isSelected = stream.id === this.activeStreamId; if (isSelected) { streamItem.addClass('streams-bar-stream-item-selected'); } - + const streamIcon = streamItem.createDiv('streams-bar-stream-item-icon'); setIcon(streamIcon, stream.icon); const streamName = streamItem.createDiv('streams-bar-stream-item-name'); streamName.setText(stream.name); - + // Add encryption icon if stream is encrypted if (stream.encryptThisStream) { const encryptionIcon = streamItem.createDiv('streams-bar-stream-item-encryption'); @@ -80,18 +80,18 @@ export class StreamSelector extends Component { encryptionIcon.setAttribute('title', 'Encrypted stream'); encryptionIcon.setAttribute('aria-label', 'Encrypted stream'); } - + if (isSelected) { const checkmark = streamItem.createDiv('streams-bar-stream-item-checkmark'); setIcon(checkmark, 'check'); } - + streamItem.addEventListener('click', (e) => { e.stopPropagation(); this.selectStream(stream); }); }); - + // Force a reflow on mobile devices to ensure the dropdown updates immediately if (this.dropdown) { // Trigger a reflow to ensure the changes are visible @@ -105,7 +105,7 @@ export class StreamSelector extends Component { updateStreams(streams: Stream[]): void { this.streams = streams; this.populateStreamsDropdown(); - + // Force immediate DOM update for mobile devices requestAnimationFrame(() => { this.dropdown?.offsetHeight; @@ -126,7 +126,7 @@ export class StreamSelector extends Component { private async selectStream(stream: Stream): Promise { // Update the plugin's active stream - this will trigger the event listener if (this.plugin) { - await this.plugin.setActiveStream(stream.id, true); + await this.plugin.setActiveStream(stream.id, true, true); } // Notify parent component @@ -147,7 +147,7 @@ export class StreamSelector extends Component { try { const state = this.dateStateManager.getState(); const targetDate = state.currentDate; - + const command = new OpenStreamDateCommand(this.app, stream, targetDate, this.reuseCurrentTab); await command.execute(); } catch (error) { diff --git a/src/slices/calendar-navigation/StreamsBarComponent.ts b/src/slices/calendar-navigation/StreamsBarComponent.ts index 15839ec..c3178cb 100644 --- a/src/slices/calendar-navigation/StreamsBarComponent.ts +++ b/src/slices/calendar-navigation/StreamsBarComponent.ts @@ -161,6 +161,9 @@ export class StreamsBarComponent extends Component { }, isExpanded: () => { return this.expanded; + }, + onStreamSelected: (stream: Stream) => { + this.updateActiveStream(stream); } }; } @@ -399,6 +402,10 @@ export class StreamsBarComponent extends Component { return; } + this.updateActiveStream(newActiveStream); + } + + public updateActiveStream(newActiveStream: Stream): void { this.selectedStream = newActiveStream; this.stateManager.updateSelectedStream(newActiveStream); this.dateNavigationService.updateStream(newActiveStream); @@ -419,7 +426,7 @@ export class StreamsBarComponent extends Component { } if (this.streamSelector) { - this.streamSelector.updateActiveStreamId(streamId); + this.streamSelector.updateActiveStreamId(newActiveStream.id); } this.updateTodayButton(); diff --git a/src/slices/stream-management/StreamManagementService.ts b/src/slices/stream-management/StreamManagementService.ts index a5aef94..407bffe 100644 --- a/src/slices/stream-management/StreamManagementService.ts +++ b/src/slices/stream-management/StreamManagementService.ts @@ -55,9 +55,9 @@ export class StreamManagementService extends SettingsAwareSliceService { /** * Set the active stream */ - public setActiveStream = withAsyncErrorHandling(async (streamId: string | undefined, force = false): Promise => { + public setActiveStream = withAsyncErrorHandling(async (streamId: string | undefined, force = false, suppressEvent = false): Promise => { const currentActiveStreamId = this.getSettings().activeStreamId; - + if (currentActiveStreamId === streamId && !force) { return; // No change needed } @@ -65,7 +65,7 @@ export class StreamManagementService extends SettingsAwareSliceService { // Update the settings const plugin = this.getPlugin(); plugin.settings.activeStreamId = streamId; - + // Save settings await plugin.saveSettings(); @@ -81,7 +81,9 @@ export class StreamManagementService extends SettingsAwareSliceService { this.globalIndicator.update(this.getActiveStream()); // Emit event for other services - eventBus.emit(EVENTS.ACTIVE_STREAM_CHANGED, { streamId, previousStreamId: currentActiveStreamId }, 'stream-management'); + if (!suppressEvent) { + eventBus.emit(EVENTS.ACTIVE_STREAM_CHANGED, { streamId, previousStreamId: currentActiveStreamId }, 'stream-management'); + } }, 'stream-management', 'setActiveStream'); /** @@ -96,8 +98,8 @@ export class StreamManagementService extends SettingsAwareSliceService { */ public showStreamSelection = withErrorHandling((): void => { const modal = new StreamSelectionModal( - this.getPlugin().app, - this.getStreams(), + this.getPlugin().app, + this.getStreams(), async (selectedStream) => { if (selectedStream) { await this.setActiveStream(selectedStream.id, true);