mirror of
https://github.com/jacobtread/obsidian-timekeep.git
synced 2026-07-22 10:10:27 +00:00
Merge pull request #75 from jacobtread/feat-status-bar-view
feat: status bar items
This commit is contained in:
commit
8ce8b29dd9
9 changed files with 252 additions and 3 deletions
|
|
@ -54,6 +54,11 @@ export class TimesheetRowDurationComponent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onunload(): void {
|
||||
super.onunload();
|
||||
this.#timeEl?.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current time duration value based
|
||||
* on the current time
|
||||
|
|
|
|||
74
src/components/timesheetStatusBarItem.ts
Normal file
74
src/components/timesheetStatusBarItem.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Component } from "obsidian";
|
||||
|
||||
import { TimeEntry } from "@/timekeep/schema";
|
||||
|
||||
import { TimesheetRowDurationComponent } from "./timesheetRowDuration";
|
||||
|
||||
export class TimesheetStatusBarItem extends Component {
|
||||
/** Parent container element */
|
||||
#containerEl: HTMLElement;
|
||||
|
||||
/** The entry this duration belongs to */
|
||||
entry: TimeEntry;
|
||||
|
||||
/** The time span element */
|
||||
#wrapperEl: HTMLSpanElement | undefined;
|
||||
|
||||
/** Currently tracked background interval for content */
|
||||
currentContentInterval: number | undefined;
|
||||
|
||||
/** Component for rendering the real time updating duration */
|
||||
duration: TimesheetRowDurationComponent;
|
||||
|
||||
/** Callback to open the file when the content is clicked */
|
||||
onOpen: VoidFunction;
|
||||
/** Callback to stop the timekeep item */
|
||||
onStop: VoidFunction;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
entry: TimeEntry,
|
||||
onOpen: VoidFunction,
|
||||
onStop: VoidFunction
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#containerEl = containerEl;
|
||||
this.entry = entry;
|
||||
|
||||
this.onOpen = onOpen;
|
||||
this.onStop = onStop;
|
||||
}
|
||||
|
||||
onload(): void {
|
||||
super.onload();
|
||||
|
||||
const entry = this.entry;
|
||||
const wrapperEl = this.#containerEl.createDiv({ cls: "timekeep-status-item" });
|
||||
this.#wrapperEl = wrapperEl;
|
||||
|
||||
// const stopButton = wrapperEl.createEl("button", {
|
||||
// cls: "timekeep-status-item__button",
|
||||
// });
|
||||
// this.registerDomEvent(stopButton, "click", this.onStop);
|
||||
|
||||
const contentEl = wrapperEl.createDiv({
|
||||
cls: "timekeep-status-item__content",
|
||||
title: "Open File",
|
||||
});
|
||||
|
||||
contentEl.createSpan({
|
||||
cls: "timekeep-status-item__name",
|
||||
text: entry.name + ":",
|
||||
});
|
||||
|
||||
this.addChild(new TimesheetRowDurationComponent(contentEl, entry));
|
||||
|
||||
this.registerDomEvent(contentEl, "click", this.onOpen);
|
||||
}
|
||||
|
||||
onunload(): void {
|
||||
super.onunload();
|
||||
this.#wrapperEl?.remove();
|
||||
}
|
||||
}
|
||||
|
|
@ -23,12 +23,14 @@ import { load, replaceTimekeepCodeblock, extractTimekeepCodeblocks } from "@/tim
|
|||
|
||||
import { stopAllTimekeeps } from "./commands/stopAllTimekeeps";
|
||||
import { stopFileTimekeeps } from "./commands/stopFileTimekeeps";
|
||||
import { TimesheetStatusBarItem } from "./components/timesheetStatusBarItem";
|
||||
import { CustomOutputFormat } from "./output";
|
||||
import { TimekeepRegistry } from "./service/registry";
|
||||
import { Timekeep, TimeEntry } from "./timekeep/schema";
|
||||
import { TimekeepLocatorModal } from "./views/timekeep-locator-modal";
|
||||
import { TimekeepMarkdownView } from "./views/timekeep-markdown-view";
|
||||
import { TimekeepMergerModal } from "./views/timekeep-merger-modal";
|
||||
import { TimekeepStatusBarView } from "./views/timekeep-status-bar-view";
|
||||
|
||||
export default class TimekeepPlugin extends Plugin {
|
||||
settingsStore: Store<TimekeepSettings>;
|
||||
|
|
@ -105,6 +107,11 @@ export default class TimekeepPlugin extends Plugin {
|
|||
this.registry = new TimekeepRegistry(this.app.vault);
|
||||
this.registry.concurrencyLimit = settings.registryConcurrencyLimit;
|
||||
this.registry.onload();
|
||||
|
||||
if (settings.statusBarEnabled) {
|
||||
const containerEl = this.addStatusBarItem();
|
||||
new TimekeepStatusBarView(containerEl, this.app, this.registry, this.settingsStore);
|
||||
}
|
||||
});
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
type TimekeepWithPosition,
|
||||
} from "@/timekeep/parser";
|
||||
|
||||
type TimekeepRegistryEntry = {
|
||||
export type TimekeepRegistryEntry = {
|
||||
file: TFile;
|
||||
timekeeps: TimekeepWithPosition[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -419,5 +419,26 @@ export class TimekeepSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
});
|
||||
});
|
||||
|
||||
// Status bar section
|
||||
new Setting(this.containerEl)
|
||||
.setName("Status Bar")
|
||||
.setDesc(
|
||||
"Timekeep can show status bar entries for running timers within your vault. This requires that the registry option above is enabled"
|
||||
)
|
||||
.setHeading();
|
||||
|
||||
new Setting(this.containerEl)
|
||||
.setName("Enabled")
|
||||
.setDesc("Whether to enable status bar entries.")
|
||||
.addToggle((t) => {
|
||||
t.setValue(settings.statusBarEnabled);
|
||||
t.onChange((v) => {
|
||||
this.settingsStore.setState((currentValue) => ({
|
||||
...currentValue,
|
||||
statusBarEnabled: v,
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ export interface TimekeepSettings {
|
|||
|
||||
registryEnabled: boolean;
|
||||
registryConcurrencyLimit: number;
|
||||
|
||||
statusBarEnabled: boolean;
|
||||
}
|
||||
|
||||
export const defaultSettings: TimekeepSettings = {
|
||||
|
|
@ -98,6 +100,8 @@ export const defaultSettings: TimekeepSettings = {
|
|||
|
||||
registryEnabled: true,
|
||||
registryConcurrencyLimit: 15,
|
||||
|
||||
statusBarEnabled: true,
|
||||
};
|
||||
|
||||
export function legacySettingsCompatibility(settings: TimekeepSettings): void {
|
||||
|
|
|
|||
|
|
@ -286,3 +286,16 @@
|
|||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.timekeep-status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.timekeep-status-item__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
95
src/views/timekeep-status-bar-view.ts
Normal file
95
src/views/timekeep-status-bar-view.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { App, MarkdownView } from "obsidian";
|
||||
|
||||
import { TimesheetStatusBarItem } from "@/components/timesheetStatusBarItem";
|
||||
import { TimekeepRegistry, TimekeepRegistryEntry } from "@/service/registry";
|
||||
import { TimekeepSettings } from "@/settings";
|
||||
import { Store } from "@/store";
|
||||
import { getRunningEntry } from "@/timekeep";
|
||||
import { TimekeepWithPosition } from "@/timekeep/parser";
|
||||
import { TimeEntry } from "@/timekeep/schema";
|
||||
|
||||
export class TimekeepStatusBarView {
|
||||
/** Parent container element */
|
||||
#containerEl: HTMLElement;
|
||||
|
||||
/** Access to the app instance */
|
||||
app: App;
|
||||
/** Access to the app registry */
|
||||
registry: TimekeepRegistry;
|
||||
/** Access to the timekeep settings */
|
||||
settings: Store<TimekeepSettings>;
|
||||
|
||||
/** Currently rendered items */
|
||||
items: TimesheetStatusBarItem[] = [];
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
app: App,
|
||||
registry: TimekeepRegistry,
|
||||
settings: Store<TimekeepSettings>
|
||||
) {
|
||||
this.#containerEl = containerEl;
|
||||
|
||||
this.app = app;
|
||||
this.registry = registry;
|
||||
this.settings = settings;
|
||||
|
||||
// Render the initial state
|
||||
this.render(this.registry.entries.getState());
|
||||
|
||||
// Subscribe to changes to re-render
|
||||
this.registry.entries.subscribe(() => {
|
||||
this.render(this.registry.entries.getState());
|
||||
});
|
||||
}
|
||||
|
||||
render(entries: TimekeepRegistryEntry[]) {
|
||||
// Unload the current children
|
||||
for (const item of this.items) {
|
||||
item.unload();
|
||||
}
|
||||
|
||||
// Load the new children
|
||||
for (const entry of entries) {
|
||||
for (const timekeep of entry.timekeeps) {
|
||||
const runningEntry = getRunningEntry(timekeep.timekeep.entries);
|
||||
if (runningEntry === null) continue;
|
||||
|
||||
const item = new TimesheetStatusBarItem(
|
||||
this.#containerEl,
|
||||
runningEntry,
|
||||
() => {
|
||||
void this.onOpen(entry, timekeep);
|
||||
},
|
||||
() => {
|
||||
this.onStop(entry, timekeep, runningEntry);
|
||||
}
|
||||
);
|
||||
|
||||
this.items.push(item);
|
||||
item.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onStop(_entry: TimekeepRegistryEntry, _timekeep: TimekeepWithPosition, _timeEntry: TimeEntry) {
|
||||
// TODO: For future implementation, ...being able to stop entries
|
||||
}
|
||||
|
||||
async onOpen(entry: TimekeepRegistryEntry, timekeep: TimekeepWithPosition) {
|
||||
const leaf = this.app.workspace.getLeaf();
|
||||
await leaf.openFile(entry.file);
|
||||
|
||||
const view = leaf.view;
|
||||
|
||||
if (view instanceof MarkdownView) {
|
||||
const editor = view.editor;
|
||||
|
||||
const line = timekeep.startLine;
|
||||
|
||||
// Focus the line we opened to
|
||||
editor.setCursor({ line: Math.max(line - 1, 0), ch: 0 });
|
||||
editor.scrollIntoView({ from: { line, ch: 0 }, to: { line, ch: 0 } }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue