Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional status bar format options #150

Merged
merged 12 commits into from
Jul 15, 2024
4 changes: 4 additions & 0 deletions lib/config/DefaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import type { PluginSettings } from "./PluginSettings";
export const DEFAULT_SETTINGS: PluginSettings = {
apiToken: null,
charLimitStatusBar: 40,
statusBarFormat: "m [minute]",
statusBarNoEntryMesssage: "-",
statusBarPrefix: "Timer: ",
statusBarShowProject: false,
updateInRealTime: true,
workspace: { id: "none", name: "None selected" },
};
24 changes: 20 additions & 4 deletions lib/config/PluginSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,31 @@ export interface PluginSettings {
*/
workspace: TogglWorkspace;

/** Has dismissed the update alert for 0.4.0 */
hasDismissedAlert?: boolean;

/** Update the day's total time in real-time in the sidebar */
updateInRealTime?: boolean;

/**
* The max. allowed characters in the title of a timer in
* the status bar.
*/
charLimitStatusBar: number;

/** Has dismissed the update alert for 0.4.0 */
hasDismissedAlert?: boolean;
/**
* The time format for the status bar.
*/
statusBarFormat?: string;

/** Update the day's total time in real-time in the sidebar */
updateInRealTime?: boolean;
/**
* The prefix to show before the time entry in the status bar.
*/
statusBarPrefix?: string;
mcndt marked this conversation as resolved.
Show resolved Hide resolved

/** Whether to show the project in the status bar. */
statusBarShowProject?: boolean;

/** Message shown in the status bar when no time entry is running. */
statusBarNoEntryMesssage?: string;
mcndt marked this conversation as resolved.
Show resolved Hide resolved
}
5 changes: 5 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
export const ACTIVE_TIMER_POLLING_INTERVAL = 6000;

/**
* The interval in ms at which the status bar is updated
*/
export const STATUS_BAR_UPDATE_INTERVAL = 1000;

/**
* The language string used for report code blocks.
*/
Expand Down
4 changes: 2 additions & 2 deletions lib/stores/dailySummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export const DailySummary = derived(
[summaryItems, Projects],
([$summaryItems, $projects]) => {
const summary = {
projects_breakdown: $summaryItems.map(
projects_breakdown: ($summaryItems ?? []).map(
(item): EnrichedWithProject<typeof item> => ({
...item,
$project:
$projects.find((project) => project.id === item.project_id) ?? null,
}),
),
total_seconds: $summaryItems.reduce((a, b) => a + b.tracked_seconds, 0),
total_seconds: ($summaryItems ?? []).reduce((a, b) => a + b.tracked_seconds, 0),
};

return summary;
Expand Down
33 changes: 26 additions & 7 deletions lib/toggl/TogglService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ACTIVE_TIMER_POLLING_INTERVAL } from "lib/constants";
import { ACTIVE_TIMER_POLLING_INTERVAL, STATUS_BAR_UPDATE_INTERVAL } from "lib/constants";
import type {
ClientId,
EnrichedWithClient,
Expand Down Expand Up @@ -30,6 +30,7 @@ import {
import { apiStatusStore, togglService } from "lib/util/stores";
import type MyPlugin from "main";
import moment from "moment";
import "moment-duration-format";
import { Notice } from "obsidian";
import { derived, get } from "svelte/store";

Expand Down Expand Up @@ -71,13 +72,13 @@ export default class TogglService {
private _statusBarItem: HTMLElement;

private _currentTimerInterval: number = null;
private _statusBarInterval: number = null;
private _currentTimeEntry: TimeEntry = null;
private _ApiAvailable = ApiStatus.UNTESTED;

constructor(plugin: MyPlugin) {
this._plugin = plugin;
this._statusBarItem = this._plugin.addStatusBarItem();
this._statusBarItem = this._plugin.addStatusBarItem();
this._statusBarItem.setText("Connecting to Toggl...");
// Store a reference to the manager in a svelte store to avoid passing
// of references around the component trees.
Expand All @@ -91,6 +92,7 @@ export default class TogglService {
*/
public async setToken(token: string) {
window.clearInterval(this._currentTimerInterval);
window.clearInterval(this._statusBarInterval);
if (token != null && token != "") {
try {
this._apiManager = new TogglAPI();
Expand All @@ -108,6 +110,7 @@ export default class TogglService {

// Fetch daily summary data and start polling for current timers.
this.startTimerInterval();
this.startStatusBarInterval();
this._apiManager
.getDailySummary()
.then((response) => setDailySummaryItems(response));
Expand Down Expand Up @@ -184,6 +187,17 @@ export default class TogglService {
this._plugin.registerInterval(this._currentTimerInterval);
}

/**
* Start updating the status bar periodically.
*/
private startStatusBarInterval() {
this.updateStatusBarText();
this._statusBarInterval = window.setInterval(() => {
this.updateStatusBarText();
}, STATUS_BAR_UPDATE_INTERVAL);
this._plugin.registerInterval(this._statusBarInterval);
}

private async updateCurrentTimer() {
if (!this.isApiAvailable) {
return;
Expand Down Expand Up @@ -257,7 +271,6 @@ export default class TogglService {
}

this._currentTimeEntry = curr;
this.updateStatusBarText();
}

/**
Expand All @@ -267,7 +280,7 @@ export default class TogglService {
private updateStatusBarText() {
let timer_msg = null;
if (this._currentTimeEntry == null) {
timer_msg = "-";
timer_msg = this._plugin.settings.statusBarNoEntryMesssage;
} else {
let title: string =
this._currentTimeEntry.description || "No description";
Expand All @@ -278,11 +291,17 @@ export default class TogglService {
)}...`;
}
const duration = this.getTimerDuration(this._currentTimeEntry);
const minutes = Math.floor(duration / 60);
const time_string = `${minutes} minute${minutes != 1 ? "s" : ""}`;
const time_string = moment.duration(duration, 'seconds').format(
this._plugin.settings.statusBarFormat,
{ trim: false, trunc: true },
)
if (this._plugin.settings.statusBarShowProject){
const currentEnhanced = enrichObjectWithProject(this._currentTimeEntry)
title += ` - ${currentEnhanced.$project?.name || "No project"}`
}
timer_msg = `${title} (${time_string})`;
}
this._statusBarItem.setText(`Timer: ${timer_msg}`);
this._statusBarItem.setText(`${this._plugin.settings.statusBarPrefix}${timer_msg}`);
}

/**
Expand Down
99 changes: 99 additions & 0 deletions lib/ui/TogglSettingsTab.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DEFAULT_SETTINGS } from "lib/config/DefaultSettings";
import type MyPlugin from "main";
import {
App,
Expand Down Expand Up @@ -32,6 +33,15 @@ export default class TogglSettingsTab extends PluginSettingTab {
this.addTestConnectionSetting(containerEl);
this.addWorkspaceSetting(containerEl);
this.addUpdateRealTimeSetting(containerEl);

containerEl.createEl("h2", {
text: "Status bar display options",
});
this.addCharLimitStatusBarSetting(containerEl);
this.addStatusBarFormatSetting(containerEl);
this.addStatusBarPrefixSetting(containerEl);
this.addStatusBarProjectSetting(containerEl);
this.addStatusBarNoEntrySetting(containerEl);
}

private addApiTokenSetting(containerEl: HTMLElement) {
Expand Down Expand Up @@ -104,6 +114,95 @@ export default class TogglSettingsTab extends PluginSettingTab {
});
}

private addCharLimitStatusBarSetting(containerEl: HTMLElement) {
new Setting(containerEl)
.setName("Status bar character limit")
.setDesc(
"Set a character limit for the time entry " +
"displayed in the status bar."
)
.addText((text) => {
text.setPlaceholder(String(DEFAULT_SETTINGS.charLimitStatusBar))
text.inputEl.type = "number"
text.setValue(String(this.plugin.settings.charLimitStatusBar))
text.onChange(async (value) => {
this.plugin.settings.charLimitStatusBar = (
value !== "" ? Number(value) : DEFAULT_SETTINGS.charLimitStatusBar
);
await this.plugin.saveSettings();
});
});
}

private addStatusBarFormatSetting(containerEl: HTMLElement) {
new Setting(containerEl)
.setName("Status bar time format")
.setDesc(
"Time format for the status bar. " +
"See https://github.com/jsmreese/moment-duration-format for format options.",
)
.addText((text) =>
text
.setPlaceholder(DEFAULT_SETTINGS.statusBarFormat)
.setValue(this.plugin.settings.statusBarFormat || "")
.onChange(async (value) => {
this.plugin.settings.statusBarFormat = value;
await this.plugin.saveSettings();
}),
);
}

private addStatusBarPrefixSetting(containerEl: HTMLElement) {
new Setting(containerEl)
.setName("Status bar prefix")
.setDesc(
"Prefix before the time entry in the status bar. " +
"Leave blank for no prefix.",
)
.addText((text) =>
text
.setPlaceholder(DEFAULT_SETTINGS.statusBarPrefix)
.setValue(this.plugin.settings.statusBarPrefix || "")
.onChange(async (value) => {
this.plugin.settings.statusBarPrefix = value;
await this.plugin.saveSettings();
}),
);
}

private addStatusBarProjectSetting(containerEl: HTMLElement) {
new Setting(containerEl)
.setName("Show project in status bar")
.setDesc(
"Show the project of the time entry displayed in the status bar."
)
.addToggle((toggle) => {
toggle
.setValue(this.plugin.settings.statusBarShowProject || false)
.onChange(async (value) => {
this.plugin.settings.statusBarShowProject = value;
await this.plugin.saveSettings();
});
});
}

private addStatusBarNoEntrySetting(containerEl: HTMLElement) {
new Setting(containerEl)
.setName("No entry status bar message")
.setDesc(
"Message in the status bar when no time entry is running."
)
.addText((text) =>
text
.setPlaceholder(DEFAULT_SETTINGS.statusBarNoEntryMesssage)
.setValue(this.plugin.settings.statusBarNoEntryMesssage || "")
.onChange(async (value) => {
this.plugin.settings.statusBarNoEntryMesssage = value;
await this.plugin.saveSettings();
}),
);
}

private async fetchWorkspaces() {
// empty the dropdown's list
const selectEl = this.workspaceDropdown.selectEl;
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/components/current_timer/CurrentTimerDisplay.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<span
class="timer-project-name"
style:color={timer.$project?.color ?? "var(--text-muted)"}
>{timer.$project.name}</span
>{timer.$project?.name ?? "No project"}</span
>
<span class="divider-bullet mx-1">•</span>
<span class="timer-duration">{secondsToTimeString(duration)}</span>
Expand Down
21 changes: 11 additions & 10 deletions lib/util/millisecondsToTimeString.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
export default function millisecondsToTimeString(ms: number): string {
const sec = Math.round((ms / 1000) % 60);
const min = Math.floor((ms / 1000 / 60) % 60);
const hr = Math.floor(ms / 1000 / 60 / 60);
import moment from "moment";
import "moment-duration-format";

return `${hr}:${min < 10 ? "0" + min : min}:${sec < 10 ? "0" + sec : sec}`;
export default function millisecondsToTimeString(ms: number): string {
return moment.duration(ms).format(
'h:mm:ss',
{ trim: false, trunc: true },
)
}

export function secondsToTimeString(seconds: number): string {
const sec = Math.round(seconds % 60);
const min = Math.floor((seconds / 60) % 60);
const hr = Math.floor(seconds / 60 / 60);

return `${hr}:${min < 10 ? "0" + min : min}:${sec < 10 ? "0" + sec : sec}`;
return moment.duration(seconds, 'seconds').format(
'h:mm:ss',
{ trim: false, trunc: true },
)
}
Loading