From 21fe18209c3b93d12bb915287c07d05085466e1a Mon Sep 17 00:00:00 2001 From: Ben Floyd Date: Thu, 4 Dec 2025 17:58:24 -0700 Subject: [PATCH] independent pane streams --- file_list.txt | Bin 0 -> 9364 bytes main.ts | 14 ++++---- .../ComponentLifecycleManager.ts | 33 ++++++++++++++---- .../ComponentStateManager.ts | 25 +++++-------- .../calendar-navigation/ComponentUIBuilder.ts | 9 ++--- .../calendar-navigation/StreamSelector.ts | 26 +++++++------- .../StreamsBarComponent.ts | 9 ++++- .../StreamManagementService.ts | 14 ++++---- 8 files changed, 76 insertions(+), 54 deletions(-) create mode 100644 file_list.txt diff --git a/file_list.txt b/file_list.txt new file mode 100644 index 0000000000000000000000000000000000000000..fb8caaaf6e33fc2560c245bfb6082c596b7a7f54 GIT binary patch literal 9364 zcmcIq>rN9v6rSHEK1z89#eyWDM8sd4n9`Poq@|==6nuE~`_6QBX1a6hb{nFmbk6NN zw>fA0_qS0GYNFO^u4d}JdZ!w-P>*Vg&&~F;{k~E!YJ+bV!S6nTPXoG}mct27hq#>L z)Dym2gM!lji1bUfQZ;_&_4R~G&%_O7Y7<{MD>`gtlx_tsQIZWH%lxaH#pEWdItIr~P2DDP>jMXoEPh>v= zow#lGoX__gsB0idd}u)oMdD9QMmVXxH-c^SLJ6|xpk1juShNsy8G948R2QJ1iT{W< zt&Yiml#_Gynmj?o*NBygL_{2oE1X-3ythIhU*Ytu)qkJppxwlbb}|A-u#}O)Y>QcY zy|sggEQN{@P92`WC5;sGagC_ZSz)}S^Cu?Hk4=X|{S+%G8%zQ0s~s^M^}yCTHg=Ol2!~1uUO& zcDJHez>ImvD!~X|!`>(SOM7bkYpQ98ZUy9Z{LMhcthKcwN2xJ-1^*FvQ2I+iH%8eV zzM*efBiIdyZyh*mYtTya$L$5C=eT%K>H;>8gd;sx!-kaJtO<8Gtt*}JbB<~aiaY!; zJIiG;Pd9=-t!)G))(72fG-8_o3c6D3`blIVvvQ&GmEFjX@VZLqinkCBO29 zPO@*B%HC{c#kiNnWTk%Mhn}O=jeH8lX-Rxn>J(VHdBCcrZKKwH?{V!b)peKu55xuQ zOBvqfQka#`5^cKkvzD?`u){EZy8WS_HM?@`9pVNV%dW;-&m(j(H6(st8b*UlNV^B7 zQ*_X&`105i8Ph~Gbr&4Qwp(N-?{KU>-QL?*hOWZ=`$A%!w*nI(!^WlQZePHEx3>%} zX$31@wq0eTY+U+x+e2%Q<$IhAv;2L%hegZWAFjpk59k1#FX$m1Wy4C#cZtk0^gPO%VB~kOc(xKVsN}-3?xfubw zQ(_;Fe+QQraC=ba;M#yIHw*K`%-u(d>Hoxr71+)?e#9M$lq+ZWdw}8Yc3N{gJ&ql` zmxbnxVWo9%mX+GSR_f?E)Po^iSV_Ax3nd*-rjKyE5*||nJ458FSWb_!`IVD_d-Blu zb9u$5<_aA+H(5=$m4nllJ4b7cb$sxfE5ronYu|tIsMPhS%k>bAxSirI@;m-eC)QZE zhT9EVOt1JZfV&g3Conr>>RK0CoZ_6fIYn)>NzIQbdZNUg2fJI$Mn5`Z=%u$Km2A){ zg*!C%I=ctqPFHW{b-m%#8rrAiBy_7-cUI5wIBj*Cx7FBmpuU!;c#aW8#6$1y`6AL+ z%2@6{+&VJq_00HHrewwjGb6rX$f0s3Q{fzS%J*DZzL#?U8rZ50BO#w-IUO(HCq^kZ zI18zFoRSZX3^QZUN^`%=y)C&n!JO6_QNC2!u4d-sVY1ptU>)Ih_YE<^yDa162~u6j z$C5a_40>8e`m$i}o%4eIo|&GneD)sL%{RAB`mbgC#Kry^9iBn^%jB(sS=OzQXWM$D zJ+EtBjo9m3JNMV^3*ueBjV;y6|gWnT~lbZkO)A%W*q%OkY { - 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);