Skip to content

Commit

Permalink
Merge branch 'mcndt:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Marlon154 authored Jan 7, 2025
2 parents 0c62d04 + 0cb44a4 commit c5fef42
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 33 deletions.
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;

/** 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;
}
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
65 changes: 52 additions & 13 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,35 +72,51 @@ 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...");

this._plugin.registerDomEvent(this._statusBarItem, "click", () => {
this.refreshApiConnection(this._plugin.settings.apiToken);
});
// Store a reference to the manager in a svelte store to avoid passing
// of references around the component trees.
togglService.set(this);
apiStatusStore.set(ApiStatus.UNTESTED);
}

private _setApiStatus(status: ApiStatus) {
this._ApiAvailable = status;
apiStatusStore.set(status);
}

/**
* Creates a new toggl client object using the passed API token.
* @param token the API token for the client.
*/
public async setToken(token: string) {
public async refreshApiConnection(token: string) {
this._setApiStatus(ApiStatus.UNTESTED);
this._statusBarItem.setText("Connecting to Toggl...");
if (this._apiManager != null) {
new Notice("Reconnecting to Toggl...");
}

window.clearInterval(this._currentTimerInterval);
window.clearInterval(this._statusBarInterval);
if (token != null && token != "") {
try {
this._apiManager = new TogglAPI();
await this._apiManager.setToken(token);
this._ApiAvailable = ApiStatus.AVAILABLE;
this._setApiStatus(ApiStatus.AVAILABLE);
} catch {
console.error("Cannot connect to toggl API.");
this._statusBarItem.setText("Cannot connect to Toggl API");
this._ApiAvailable = ApiStatus.UNREACHABLE;
this._setApiStatus(ApiStatus.UNREACHABLE);
this.noticeAPINotAvailable();
return;
}
Expand All @@ -108,12 +125,13 @@ 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));
} else {
this._statusBarItem.setText("Open settings to add a Toggl API token.");
this._ApiAvailable = ApiStatus.NO_TOKEN;
this._setApiStatus(ApiStatus.NO_TOKEN);
this.noticeAPINotAvailable();
}
apiStatusStore.set(this._ApiAvailable);
Expand Down Expand Up @@ -184,6 +202,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 All @@ -195,14 +224,14 @@ export default class TogglService {
try {
curr = await this._apiManager.getCurrentTimer();
if (this._ApiAvailable === ApiStatus.DEGRADED) {
this._ApiAvailable = ApiStatus.AVAILABLE;
this._setApiStatus(ApiStatus.AVAILABLE);
}
} catch (err) {
console.error("Error reaching Toggl API");
console.error(err);
if (this._ApiAvailable !== ApiStatus.DEGRADED) {
new Notice("Error updating active Toggl time entry. Retrying...");
this._ApiAvailable = ApiStatus.DEGRADED;
this._setApiStatus(ApiStatus.DEGRADED);
}
return;
}
Expand Down Expand Up @@ -257,17 +286,21 @@ export default class TogglService {
}

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

/**
* Updates the status bar text to reflect the current Toggl
* state (e.g. details of current timer).
*/
private updateStatusBarText() {
if (this._ApiAvailable === ApiStatus.UNTESTED) {
this._statusBarItem.setText("Connecting to Toggl...");
return;
}

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 +311,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
101 changes: 100 additions & 1 deletion 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 All @@ -47,7 +57,7 @@ export default class TogglSettingsTab extends PluginSettingTab {
.setValue(this.plugin.settings.apiToken || "")
.onChange(async (value) => {
this.plugin.settings.apiToken = value;
this.plugin.toggl.setToken(value);
this.plugin.toggl.refreshApiConnection(value);
await this.plugin.saveSettings();
}),
);
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
Loading

0 comments on commit c5fef42

Please sign in to comment.