Startzeitpunkt manipulierbar
This commit is contained in:
parent
35659829c9
commit
d63f8cb7b2
@ -85,6 +85,33 @@ export class TimerService {
|
|||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateActiveStart(startedAtEpochMs: number): TrackerState {
|
||||||
|
const active = this.state.activeTimer;
|
||||||
|
if (!active) {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(startedAtEpochMs)) {
|
||||||
|
throw new Error("Invalid start timestamp.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = nowMs();
|
||||||
|
if (startedAtEpochMs > now) {
|
||||||
|
throw new Error("Start must be less than or equal to now.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
activeTimer: {
|
||||||
|
...active,
|
||||||
|
startedAtEpochMs
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.persist();
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
public switchToPage(page: PageRef): TrackerState {
|
public switchToPage(page: PageRef): TrackerState {
|
||||||
if (this.state.activeTimer?.pageUuid === page.uuid) {
|
if (this.state.activeTimer?.pageUuid === page.uuid) {
|
||||||
return this.state;
|
return this.state;
|
||||||
|
|||||||
83
src/ui.ts
83
src/ui.ts
@ -54,6 +54,8 @@ export class TimetrackerUi {
|
|||||||
private readonly listPages: () => Promise<PageRef[]>;
|
private readonly listPages: () => Promise<PageRef[]>;
|
||||||
private intervalId: number | null = null;
|
private intervalId: number | null = null;
|
||||||
private editDraft: EditDraft | null = null;
|
private editDraft: EditDraft | null = null;
|
||||||
|
private activeStartDraft: string | null = null;
|
||||||
|
private isEditingActiveStart = false;
|
||||||
private pageOptions: PageRef[] = [];
|
private pageOptions: PageRef[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -71,8 +73,8 @@ export class TimetrackerUi {
|
|||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.render();
|
this.render();
|
||||||
this.intervalId = window.setInterval(() => {
|
this.intervalId = window.setInterval(() => {
|
||||||
// Keep focus stable while an entry is being edited.
|
// Keep focus stable while any form field is being edited.
|
||||||
if (this.editDraft) {
|
if (this.editDraft || this.isEditingActiveStart) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.render();
|
this.render();
|
||||||
@ -90,7 +92,13 @@ export class TimetrackerUi {
|
|||||||
const app = this.ensureContainer();
|
const app = this.ensureContainer();
|
||||||
const state = this.service.getState();
|
const state = this.service.getState();
|
||||||
const active = state.activeTimer;
|
const active = state.activeTimer;
|
||||||
const activeDuration = active ? Date.now() - active.startedAtEpochMs : 0;
|
const activeDuration = active ? Math.max(0, Date.now() - active.startedAtEpochMs) : 0;
|
||||||
|
|
||||||
|
if (!active && this.activeStartDraft !== null) {
|
||||||
|
this.activeStartDraft = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeStartValue = active ? this.activeStartDraft ?? toIsoLocalInput(active.startedAtEpochMs) : "";
|
||||||
|
|
||||||
const daySummaries = computeDaySummaries(state.entries);
|
const daySummaries = computeDaySummaries(state.entries);
|
||||||
const weekSummaries = computeWeekSummaries(state.entries);
|
const weekSummaries = computeWeekSummaries(state.entries);
|
||||||
@ -139,6 +147,20 @@ export class TimetrackerUi {
|
|||||||
<button id="tt-stop">Stop active</button>
|
<button id="tt-stop">Stop active</button>
|
||||||
<button id="tt-export">Export JSON</button>
|
<button id="tt-export">Export JSON</button>
|
||||||
</div>
|
</div>
|
||||||
|
${active
|
||||||
|
? `<div class="tt-edit-panel">
|
||||||
|
<strong>Active timer</strong>
|
||||||
|
<div class="tt-edit-grid">
|
||||||
|
<label>
|
||||||
|
<span>Start</span>
|
||||||
|
<input id="tt-active-start" type="datetime-local" value="${this.escapeAttribute(activeStartValue)}" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="tt-actions">
|
||||||
|
<button id="tt-active-start-save">Save active start</button>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
${this.editDraft ? this.renderEditPanel() : ""}
|
${this.editDraft ? this.renderEditPanel() : ""}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -250,6 +272,11 @@ export class TimetrackerUi {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target.id === "tt-active-start-save") {
|
||||||
|
this.saveActiveStartDraft();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (target.id === "tt-edit-save") {
|
if (target.id === "tt-edit-save") {
|
||||||
this.saveEditDraft();
|
this.saveEditDraft();
|
||||||
return;
|
return;
|
||||||
@ -274,7 +301,15 @@ export class TimetrackerUi {
|
|||||||
|
|
||||||
document.addEventListener("input", (event) => {
|
document.addEventListener("input", (event) => {
|
||||||
const target = event.target as HTMLInputElement | HTMLTextAreaElement | null;
|
const target = event.target as HTMLInputElement | HTMLTextAreaElement | null;
|
||||||
if (!target || !this.editDraft) {
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.id === "tt-active-start") {
|
||||||
|
this.activeStartDraft = target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.editDraft) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,6 +326,20 @@ export class TimetrackerUi {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("focusin", (event) => {
|
||||||
|
const target = event.target as HTMLElement | null;
|
||||||
|
if (target?.id === "tt-active-start") {
|
||||||
|
this.isEditingActiveStart = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("focusout", (event) => {
|
||||||
|
const target = event.target as HTMLElement | null;
|
||||||
|
if (target?.id === "tt-active-start") {
|
||||||
|
this.isEditingActiveStart = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
if (this.editDraft) {
|
if (this.editDraft) {
|
||||||
@ -377,6 +426,32 @@ export class TimetrackerUi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private saveActiveStartDraft(): void {
|
||||||
|
const active = this.service.getState().activeTimer;
|
||||||
|
if (!active) {
|
||||||
|
this.activeStartDraft = null;
|
||||||
|
this.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawInput = this.activeStartDraft ?? toIsoLocalInput(active.startedAtEpochMs);
|
||||||
|
const startedAtEpochMs = parseLocalInput(rawInput);
|
||||||
|
|
||||||
|
if (!Number.isFinite(startedAtEpochMs)) {
|
||||||
|
logseq.App.showMsg("Invalid date format.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.service.updateActiveStart(startedAtEpochMs);
|
||||||
|
this.activeStartDraft = null;
|
||||||
|
this.render();
|
||||||
|
logseq.App.showMsg("Active start updated.", "success");
|
||||||
|
} catch (error) {
|
||||||
|
logseq.App.showMsg((error as Error).message, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private escapeHtml(value: string): string {
|
private escapeHtml(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user