mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 05:49:02 +00:00
add change stream ui
This commit is contained in:
parent
7eededb9c9
commit
7f1dc8a007
4 changed files with 301 additions and 33 deletions
10
main.ts
10
main.ts
|
|
@ -398,7 +398,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
|
||||
if (stream) {
|
||||
this.log.debug(`File belongs to stream: ${stream.name} (${stream.folder})`);
|
||||
const component = new CalendarComponent(leaf, stream, this.app, this.settings.reuseCurrentTab);
|
||||
const component = new CalendarComponent(leaf, stream, this.app, this.settings.reuseCurrentTab, this.settings.streams);
|
||||
const componentId = filePath || crypto.randomUUID();
|
||||
this.calendarComponents.set(componentId, component);
|
||||
this.log.debug('Calendar component created successfully');
|
||||
|
|
@ -496,7 +496,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
});
|
||||
|
||||
// Create the calendar component
|
||||
const component = new CalendarComponent(leaf, stream, this.app, this.settings.reuseCurrentTab);
|
||||
const component = new CalendarComponent(leaf, stream, this.app, this.settings.reuseCurrentTab, this.settings.streams);
|
||||
|
||||
// Set current viewed date
|
||||
const formattedDate = dateString.split('T')[0];
|
||||
|
|
@ -541,6 +541,12 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
public updateAllCalendarComponents() {
|
||||
this.calendarComponents.forEach(component => {
|
||||
component.updateStreamsList(this.settings.streams);
|
||||
});
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
|
||||
|
|
|
|||
27
settings.ts
27
settings.ts
|
|
@ -67,6 +67,7 @@ export class StreamsSettingTab extends PluginSettingTab {
|
|||
};
|
||||
this.plugin.settings.streams.push(newStream);
|
||||
await this.plugin.saveSettings(true);
|
||||
this.plugin.updateAllCalendarComponents();
|
||||
this.display();
|
||||
}));
|
||||
|
||||
|
|
@ -93,10 +94,11 @@ export class StreamsSettingTab extends PluginSettingTab {
|
|||
stream.name = value;
|
||||
})
|
||||
.then(textComponent => {
|
||||
textComponent.inputEl.addEventListener('blur', async () => {
|
||||
this.plugin.addStreamViewCommand(stream);
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
textComponent.inputEl.addEventListener('blur', async () => {
|
||||
this.plugin.addStreamViewCommand(stream);
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.updateAllCalendarComponents();
|
||||
});
|
||||
}));
|
||||
|
||||
new Setting(card)
|
||||
|
|
@ -291,14 +293,15 @@ export class StreamsSettingTab extends PluginSettingTab {
|
|||
.onClick(async () => {
|
||||
this.plugin.log.debug(`Deleting stream ${stream.id} (${stream.name})`);
|
||||
|
||||
this.plugin.settings.streams.splice(index, 1);
|
||||
|
||||
await this.plugin.saveSettings(true);
|
||||
|
||||
this.plugin.removeStreamCommand(stream.id);
|
||||
this.plugin.removeStreamViewCommand(stream.id);
|
||||
|
||||
this.display();
|
||||
this.plugin.settings.streams.splice(index, 1);
|
||||
|
||||
await this.plugin.saveSettings(true);
|
||||
|
||||
this.plugin.removeStreamCommand(stream.id);
|
||||
this.plugin.removeStreamViewCommand(stream.id);
|
||||
this.plugin.updateAllCalendarComponents();
|
||||
|
||||
this.display();
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,13 +31,16 @@ export class CalendarComponent extends Component {
|
|||
private currentViewedDate: string | null = null;
|
||||
private todayButton: HTMLElement;
|
||||
private reuseCurrentTab: boolean;
|
||||
private streamsDropdown: HTMLElement | null = null;
|
||||
private streams: Stream[];
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, stream: Stream, app: App, reuseCurrentTab: boolean = false) {
|
||||
constructor(leaf: WorkspaceLeaf, stream: Stream, app: App, reuseCurrentTab: boolean = false, streams: Stream[] = []) {
|
||||
super();
|
||||
this.log.debug('Creating calendar component for stream:', stream.name);
|
||||
this.selectedStream = stream;
|
||||
this.app = app;
|
||||
this.reuseCurrentTab = reuseCurrentTab;
|
||||
this.streams = streams;
|
||||
|
||||
this.component = document.createElement('div');
|
||||
this.component.addClass('streams-calendar-component');
|
||||
|
|
@ -121,9 +124,7 @@ export class CalendarComponent extends Component {
|
|||
this.toggleExpanded(collapsedView, expandedView);
|
||||
});
|
||||
|
||||
const streamLabel = collapsedView.createDiv('streams-calendar-label');
|
||||
streamLabel.setText(this.selectedStream.name);
|
||||
|
||||
// Create the navigation controls
|
||||
const navControls = collapsedView.createDiv('streams-calendar-nav-controls');
|
||||
|
||||
const prevDayButton = navControls.createDiv('streams-calendar-day-nav prev-day');
|
||||
|
|
@ -148,6 +149,20 @@ export class CalendarComponent extends Component {
|
|||
await this.navigateToAdjacentDay(1);
|
||||
});
|
||||
|
||||
// Create the "Change Stream" section
|
||||
const changeStreamSection = collapsedView.createDiv('streams-calendar-change-stream');
|
||||
const changeStreamText = changeStreamSection.createDiv('streams-calendar-change-stream-text');
|
||||
changeStreamText.setText('Change Stream');
|
||||
changeStreamSection.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggleStreamsDropdown();
|
||||
});
|
||||
|
||||
// Create the streams dropdown
|
||||
this.streamsDropdown = collapsedView.createDiv('streams-calendar-streams-dropdown');
|
||||
this.streamsDropdown.style.display = 'none';
|
||||
this.populateStreamsDropdown();
|
||||
|
||||
const expandedView = this.component.createDiv('streams-calendar-expanded');
|
||||
|
||||
const topNav = expandedView.createDiv('streams-calendar-top-nav');
|
||||
|
|
@ -202,6 +217,11 @@ export class CalendarComponent extends Component {
|
|||
if (this.expanded && !this.component.contains(e.target as Node)) {
|
||||
this.toggleExpanded(collapsedView, expandedView);
|
||||
}
|
||||
|
||||
// Close streams dropdown when clicking outside
|
||||
if (this.streamsDropdown && this.streamsDropdown.style.display !== 'none' && !this.component.contains(e.target as Node)) {
|
||||
this.toggleStreamsDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// Add keyboard event listener for Escape key
|
||||
|
|
@ -406,6 +426,119 @@ export class CalendarComponent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
private toggleStreamsDropdown() {
|
||||
if (this.streamsDropdown) {
|
||||
const isVisible = this.streamsDropdown.style.display !== 'none';
|
||||
this.streamsDropdown.style.display = isVisible ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
private populateStreamsDropdown() {
|
||||
if (!this.streamsDropdown) return;
|
||||
|
||||
this.streamsDropdown.empty();
|
||||
|
||||
// Display streams in the same order as they appear in Obsidian Streams Settings
|
||||
// The streams array passed from main.ts (this.settings.streams) maintains the exact order
|
||||
// from the settings UI, so we iterate through them in that order
|
||||
this.streams.forEach(stream => {
|
||||
const streamItem = this.streamsDropdown!.createDiv('streams-calendar-stream-item');
|
||||
|
||||
// Add a class to indicate if this is the currently selected stream
|
||||
if (stream.id === this.selectedStream.id) {
|
||||
streamItem.addClass('streams-calendar-stream-item-selected');
|
||||
}
|
||||
|
||||
const streamIcon = streamItem.createDiv('streams-calendar-stream-item-icon');
|
||||
setIcon(streamIcon, stream.icon);
|
||||
const streamName = streamItem.createDiv('streams-calendar-stream-item-name');
|
||||
streamName.setText(stream.name);
|
||||
|
||||
// Add a checkmark for the currently selected stream
|
||||
if (stream.id === this.selectedStream.id) {
|
||||
const checkmark = streamItem.createDiv('streams-calendar-stream-item-checkmark');
|
||||
setIcon(checkmark, 'check');
|
||||
}
|
||||
|
||||
streamItem.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.selectStream(stream);
|
||||
this.toggleStreamsDropdown();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private selectStream(stream: Stream) {
|
||||
// Update the selected stream
|
||||
this.selectedStream = stream;
|
||||
|
||||
// Update the calendar component for the new stream
|
||||
if (this.grid) {
|
||||
this.updateCalendarGrid(this.grid);
|
||||
}
|
||||
|
||||
// Close the dropdown
|
||||
if (this.streamsDropdown) {
|
||||
this.streamsDropdown.style.display = 'none';
|
||||
}
|
||||
|
||||
// Navigate to the selected stream's daily note
|
||||
this.navigateToStreamDailyNote(stream);
|
||||
|
||||
// Refresh the dropdown to update visual indicators
|
||||
this.refreshStreamsDropdown();
|
||||
|
||||
this.log.debug(`Switched to stream: ${stream.name}`);
|
||||
}
|
||||
|
||||
private async navigateToStreamDailyNote(stream: Stream) {
|
||||
try {
|
||||
// Use the current viewed date if available, otherwise use today
|
||||
let targetDate: Date;
|
||||
if (this.currentViewedDate) {
|
||||
targetDate = this.parseViewedDate(this.currentViewedDate);
|
||||
} else {
|
||||
targetDate = new Date();
|
||||
}
|
||||
|
||||
const year = targetDate.getFullYear();
|
||||
const month = String(targetDate.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(targetDate.getDate()).padStart(2, '0');
|
||||
const fileName = `${year}-${month}-${day}.md`;
|
||||
|
||||
// Construct the file path
|
||||
const folderPath = stream.folder
|
||||
.split(/[/\\]/)
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
const filePath = folderPath ? `${folderPath}/${fileName}` : fileName;
|
||||
|
||||
// Try to get the existing file
|
||||
let file = this.app.vault.getAbstractFileByPath(filePath);
|
||||
|
||||
// If file doesn't exist, create it
|
||||
if (!file || !(file instanceof TFile)) {
|
||||
file = await this.app.vault.create(filePath, '');
|
||||
}
|
||||
|
||||
if (file instanceof TFile) {
|
||||
// Update the current viewed date to match the new stream
|
||||
this.currentViewedDate = targetDate.toISOString().split('T')[0];
|
||||
|
||||
// Open the file in the current leaf
|
||||
const leaf = this.app.workspace.activeLeaf;
|
||||
if (leaf) {
|
||||
await leaf.openFile(file);
|
||||
}
|
||||
|
||||
// Update the today button to reflect the new date
|
||||
this.updateTodayButton();
|
||||
}
|
||||
} catch (error) {
|
||||
this.log.error('Error navigating to stream daily note:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public setCurrentViewedDate(dateString: string): void {
|
||||
this.log.debug(`Setting currentViewedDate explicitly to: ${dateString}`);
|
||||
this.currentViewedDate = dateString;
|
||||
|
|
@ -422,6 +555,19 @@ export class CalendarComponent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
public updateStreamsList(streams: Stream[]) {
|
||||
this.streams = streams;
|
||||
if (this.streamsDropdown) {
|
||||
this.populateStreamsDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
public refreshStreamsDropdown() {
|
||||
if (this.streamsDropdown) {
|
||||
this.populateStreamsDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a YYYY-MM-DD date string into a Date object
|
||||
*/
|
||||
|
|
|
|||
143
styles.css
143
styles.css
|
|
@ -201,6 +201,132 @@
|
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Calendar label */
|
||||
.streams-calendar-label {
|
||||
font-size: 0.7em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.streams-calendar-nav-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 2px;
|
||||
background-color: var(--background-primary-alt);
|
||||
border-radius: 4px;
|
||||
padding: 2px 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.streams-calendar-change-stream {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.streams-calendar-change-stream:hover {
|
||||
background-color: var(--interactive-hover);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.streams-calendar-change-stream-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.streams-calendar-change-stream-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.streams-calendar-change-stream-text {
|
||||
font-size: 0.7em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Streams dropdown styles */
|
||||
.streams-calendar-streams-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1006;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item:hover {
|
||||
background-color: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-name {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-normal);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-selected {
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-selected .streams-calendar-stream-item-name {
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-selected .streams-calendar-stream-item-icon svg {
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-checkmark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.streams-calendar-stream-item-checkmark svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.streams-calendar-expanded {
|
||||
position: relative;
|
||||
top: 0;
|
||||
|
|
@ -410,11 +536,7 @@
|
|||
background-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.streams-calendar-label {
|
||||
font-size: 0.7em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
/* Removed old calendar label styles */
|
||||
|
||||
.streams-today-icon-border {
|
||||
border-left: 2px solid var(--text-accent);
|
||||
|
|
@ -778,13 +900,4 @@
|
|||
clip: rect(0, 0, 0, 0) !important;
|
||||
}
|
||||
|
||||
.streams-calendar-nav-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 2px;
|
||||
background-color: var(--background-primary-alt);
|
||||
border-radius: 4px;
|
||||
padding: 2px 2px;
|
||||
border: none;
|
||||
}
|
||||
/* Removed old nav controls styles */
|
||||
|
|
|
|||
Loading…
Reference in a new issue