independent pane streams

This commit is contained in:
Ben Floyd 2025-12-04 17:58:24 -07:00
parent 328376520d
commit 21fe18209c
8 changed files with 76 additions and 54 deletions

BIN
file_list.txt Normal file

Binary file not shown.

14
main.ts
View file

@ -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<void> {
await serviceRegistry.streamManagement?.setActiveStream(streamId, force);
async setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise<void> {
await serviceRegistry.streamManagement?.setActiveStream(streamId, force, suppressEvent);
}
getActiveStream(): Stream | null {

View file

@ -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}`);
}
}
}
}

View file

@ -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 {

View file

@ -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();

View file

@ -8,7 +8,7 @@ interface PluginInterface {
settings: {
activeStreamId?: string;
};
setActiveStream(streamId: string, force?: boolean): Promise<void>;
setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise<void>;
}
/**
@ -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<void> {
// 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) {

View file

@ -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();

View file

@ -55,9 +55,9 @@ export class StreamManagementService extends SettingsAwareSliceService {
/**
* Set the active stream
*/
public setActiveStream = withAsyncErrorHandling(async (streamId: string | undefined, force = false): Promise<void> => {
public setActiveStream = withAsyncErrorHandling(async (streamId: string | undefined, force = false, suppressEvent = false): Promise<void> => {
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);