Compare commits

...

9 commits

Author SHA1 Message Date
Ell
51d1183c0f 0.1.8 2023-09-08 14:06:12 +02:00
Ell
17471e2831 fixed (newly introduced) exception when trying to edit a running timer 2023-09-08 14:02:15 +02:00
Ell
e95f62fb9d store timestamps as iso instead of unix
closes #26
2023-09-08 13:59:50 +02:00
Ell
f766fa7210 merge 2023-09-08 13:26:35 +02:00
Matt Wiseley
a6727bb879 Handle active timer with no end value 2023-07-11 22:17:42 -04:00
Matt Wiseley
3d9e6163e7 Use fixed format for editable timestamps 2023-07-05 09:57:58 -04:00
Matt Wiseley
0612a4bd21 24 hour default time stamp format 2023-07-04 17:51:54 -04:00
Matt Wiseley
5677e02243 Remove unneeded width setting 2023-07-03 12:58:04 -04:00
Matt Wiseley
94631bc293 Editable timestamps and mobile horizontal scroll 2023-07-03 12:23:19 -04:00
10 changed files with 144 additions and 43 deletions

3
.gitignore vendored
View file

@ -12,6 +12,9 @@ node_modules
# They should be uploaded to GitHub releases instead. # They should be uploaded to GitHub releases instead.
/main.js /main.js
# Exclude local settings
data.json
# Exclude sourcemaps # Exclude sourcemaps
*.map *.map

View file

@ -1,7 +1,7 @@
{ {
"id": "simple-time-tracker", "id": "simple-time-tracker",
"name": "Super Simple Time Tracker", "name": "Super Simple Time Tracker",
"version": "0.1.7", "version": "0.1.8",
"minAppVersion": "1.2.8", "minAppVersion": "1.2.8",
"description": "Multi-purpose time trackers for your notes!", "description": "Multi-purpose time trackers for your notes!",
"author": "Ellpeck", "author": "Ellpeck",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "simple-time-tracker", "name": "simple-time-tracker",
"version": "0.1.6", "version": "0.1.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "simple-time-tracker", "name": "simple-time-tracker",
"version": "0.1.6", "version": "0.1.7",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^16.11.6", "@types/node": "^16.11.6",

View file

@ -1,6 +1,6 @@
{ {
"name": "simple-time-tracker", "name": "simple-time-tracker",
"version": "0.1.7", "version": "0.1.8",
"description": "Multi-purpose time trackers for your notes!", "description": "Multi-purpose time trackers for your notes!",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View file

@ -1,5 +1,6 @@
export const defaultSettings: SimpleTimeTrackerSettings = { export const defaultSettings: SimpleTimeTrackerSettings = {
timestampFormat: "YY-MM-DD hh:mm:ss", timestampFormat: "YY-MM-DD HH:mm:ss",
editableTimestampFormat: "YYYY-MM-DD HH:mm:ss",
csvDelimiter: ",", csvDelimiter: ",",
fineGrainedDurations: true fineGrainedDurations: true
}; };
@ -7,6 +8,7 @@ export const defaultSettings: SimpleTimeTrackerSettings = {
export interface SimpleTimeTrackerSettings { export interface SimpleTimeTrackerSettings {
timestampFormat: string; timestampFormat: string;
editableTimestampFormat: string;
csvDelimiter: string; csvDelimiter: string;
fineGrainedDurations: boolean; fineGrainedDurations: boolean;

View file

@ -7,8 +7,8 @@ export interface Tracker {
export interface Entry { export interface Entry {
name: string; name: string;
startTime: number; startTime: string;
endTime: number; endTime: string;
subEntries: Entry[]; subEntries: Entry[];
} }
@ -31,7 +31,9 @@ export async function saveTracker(tracker: Tracker, app: App, fileName: string,
export function loadTracker(json: string): Tracker { export function loadTracker(json: string): Tracker {
if (json) { if (json) {
try { try {
return JSON.parse(json); let ret = JSON.parse(json);
fixLegacyTimestamps(ret.entries);
return ret;
} catch (e) { } catch (e) {
console.log(`Failed to parse Tracker from ${json}`); console.log(`Failed to parse Tracker from ${json}`);
} }
@ -40,6 +42,7 @@ export function loadTracker(json: string): Tracker {
} }
export function displayTracker(tracker: Tracker, element: HTMLElement, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void { export function displayTracker(tracker: Tracker, element: HTMLElement, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings): void {
element.classList.add("simple-time-tracker-container");
// add start/stop controls // add start/stop controls
let running = isRunning(tracker); let running = isRunning(tracker);
let btn = new ButtonComponent(element) let btn = new ButtonComponent(element)
@ -104,7 +107,7 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, file: str
}, 1000); }, 1000);
} }
function startSubEntry(entry: Entry, name: string) { function startSubEntry(entry: Entry, name: string): void {
// if this entry is not split yet, we add its time as a sub-entry instead // if this entry is not split yet, we add its time as a sub-entry instead
if (!entry.subEntries) { if (!entry.subEntries) {
entry.subEntries = [{...entry, name: `Part 1`}]; entry.subEntries = [{...entry, name: `Part 1`}];
@ -114,19 +117,19 @@ function startSubEntry(entry: Entry, name: string) {
if (!name) if (!name)
name = `Part ${entry.subEntries.length + 1}`; name = `Part ${entry.subEntries.length + 1}`;
entry.subEntries.push({name: name, startTime: moment().unix(), endTime: null, subEntries: null}); entry.subEntries.push({name: name, startTime: moment().toISOString(), endTime: null, subEntries: null});
} }
function startNewEntry(tracker: Tracker, name: string): void { function startNewEntry(tracker: Tracker, name: string): void {
if (!name) if (!name)
name = `Segment ${tracker.entries.length + 1}`; name = `Segment ${tracker.entries.length + 1}`;
let entry: Entry = {name: name, startTime: moment().unix(), endTime: null, subEntries: null}; let entry: Entry = {name: name, startTime: moment().toISOString(), endTime: null, subEntries: null};
tracker.entries.push(entry); tracker.entries.push(entry);
} }
function endRunningEntry(tracker: Tracker): void { function endRunningEntry(tracker: Tracker): void {
let entry = getRunningEntry(tracker.entries); let entry = getRunningEntry(tracker.entries);
entry.endTime = moment().unix(); entry.endTime = moment().toISOString();
} }
function removeEntry(entries: Entry[], toRemove: Entry): boolean { function removeEntry(entries: Entry[], toRemove: Entry): boolean {
@ -170,12 +173,12 @@ function getRunningEntry(entries: Entry[]): Entry {
return null; return null;
} }
function getDuration(entry: Entry) { function getDuration(entry: Entry): number {
if (entry.subEntries) { if (entry.subEntries) {
return getTotalDuration(entry.subEntries); return getTotalDuration(entry.subEntries);
} else { } else {
let endTime = entry.endTime ? moment.unix(entry.endTime) : moment(); let endTime = entry.endTime ? moment(entry.endTime) : moment();
return endTime.diff(moment.unix(entry.startTime)); return endTime.diff(moment(entry.startTime));
} }
} }
@ -186,7 +189,7 @@ function getTotalDuration(entries: Entry[]): number {
return ret; return ret;
} }
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement, settings: SimpleTimeTrackerSettings) { function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement, settings: SimpleTimeTrackerSettings): void {
let running = getRunningEntry(tracker.entries); let running = getRunningEntry(tracker.entries);
if (running && !running.endTime) { if (running && !running.endTime) {
current.setText(formatDuration(getDuration(running), settings)); current.setText(formatDuration(getDuration(running), settings));
@ -197,8 +200,16 @@ function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLE
total.setText(formatDuration(getTotalDuration(tracker.entries), settings)); total.setText(formatDuration(getTotalDuration(tracker.entries), settings));
} }
function formatTimestamp(timestamp: number, settings: SimpleTimeTrackerSettings): string { function formatTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
return moment.unix(timestamp).format(settings.timestampFormat); return moment(timestamp).format(settings.timestampFormat);
}
function formatEditableTimestamp(timestamp: string, settings: SimpleTimeTrackerSettings): string {
return moment(timestamp).format(settings.editableTimestampFormat);
}
function unformatEditableTimestamp(formatted: string, settings: SimpleTimeTrackerSettings): string {
return moment(formatted, settings.editableTimestampFormat).toISOString();
} }
function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings): string { function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings): string {
@ -224,6 +235,18 @@ function formatDuration(totalTime: number, settings: SimpleTimeTrackerSettings):
return ret; return ret;
} }
function fixLegacyTimestamps(entries: Entry[]): void {
for (let entry of entries) {
if (!isNaN(+entry.startTime))
entry.startTime = moment.unix(+entry.startTime).toISOString();
if (entry.endTime && !isNaN(+entry.endTime))
entry.endTime = moment.unix(+entry.endTime).toISOString();
if (entry.subEntries)
fixLegacyTimestamps(entry.subEntries);
}
}
function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string { function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
let table = [["Segment", "Start time", "End time", "Duration"]]; let table = [["Segment", "Start time", "End time", "Duration"]];
for (let entry of tracker.entries) for (let entry of tracker.entries)
@ -256,7 +279,7 @@ function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): strin
} }
function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings): string[][] { function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings): string[][] {
let ret: string[][] = [[ let ret = [[
entry.name, entry.name,
entry.startTime ? formatTimestamp(entry.startTime, settings) : "", entry.startTime ? formatTimestamp(entry.startTime, settings) : "",
entry.endTime ? formatTimestamp(entry.endTime, settings) : "", entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
@ -268,17 +291,80 @@ function createTableSection(entry: Entry, settings: SimpleTimeTrackerSettings):
return ret; return ret;
} }
function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, running: boolean, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number) { class EditableField {
cell: HTMLTableCellElement;
label: HTMLSpanElement;
box: TextComponent;
constructor(row: HTMLTableRowElement, indent: number, value: string) {
this.cell = row.createEl("td");
this.label = this.cell.createEl("span", {text: value});
this.label.style.marginLeft = `${indent}em`;
this.box = new TextComponent(this.cell).setValue(value);
this.box.inputEl.classList.add("simple-time-tracker-input");
this.box.inputEl.hide();
}
editing(): boolean {
return this.label.hidden;
}
beginEdit(value: string): void {
this.label.hidden = true;
this.box.setValue(value);
this.box.inputEl.show();
}
endEdit(): string {
const value = this.box.getValue();
this.label.setText(value);
this.box.inputEl.hide();
this.label.hidden = false;
return value;
}
}
class EditableTimestampField extends EditableField {
settings: SimpleTimeTrackerSettings;
constructor(row: HTMLTableRowElement, indent: number, value: string, settings: SimpleTimeTrackerSettings) {
super(row, indent, value ? formatTimestamp(value, settings) : "");
this.settings = settings;
}
beginEdit(value: string): void {
super.beginEdit(value ? formatEditableTimestamp(value, this.settings) : "");
}
endEdit(): string {
const value = this.box.getValue();
let displayValue = value;
if (value) {
const timestamp = unformatEditableTimestamp(value, this.settings);
displayValue = formatTimestamp(timestamp, this.settings);
}
this.label.setText(displayValue);
this.box.inputEl.hide();
this.label.hidden = false;
return value;
}
getTimestamp(): string {
if (this.box.getValue()) {
return unformatEditableTimestamp(this.box.getValue(), this.settings);
} else {
return null;
}
}
}
function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableElement, newSegmentNameBox: TextComponent, running: boolean, file: string, getSectionInfo: () => MarkdownSectionInformation, settings: SimpleTimeTrackerSettings, indent: number): void {
let row = table.createEl("tr"); let row = table.createEl("tr");
let name = row.createEl("td"); let nameField = new EditableField(row, indent, entry.name);
let namePar = name.createEl("span", {text: entry.name}); let startField = new EditableTimestampField(row, indent, (entry.startTime), settings);
namePar.style.marginLeft = `${indent}em`; let endField = new EditableTimestampField(row, indent, (entry.endTime), settings);
let nameBox = new TextComponent(name).setValue(entry.name);
nameBox.inputEl.hidden = true;
row.createEl("td", {text: entry.startTime ? formatTimestamp(entry.startTime, settings) : ""});
row.createEl("td", {text: entry.endTime ? formatTimestamp(entry.endTime, settings) : ""});
row.createEl("td", {text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""}); row.createEl("td", {text: entry.endTime || entry.subEntries ? formatDuration(getDuration(entry), settings) : ""});
let entryButtons = row.createEl("td"); let entryButtons = row.createEl("td");
@ -297,19 +383,18 @@ function addEditableTableRow(tracker: Tracker, entry: Entry, table: HTMLTableEle
.setTooltip("Edit") .setTooltip("Edit")
.setIcon("lucide-pencil") .setIcon("lucide-pencil")
.onClick(async () => { .onClick(async () => {
if (namePar.hidden) { if (nameField.editing()) {
namePar.hidden = false; entry.name = nameField.endEdit();
nameBox.inputEl.hidden = true; startField.endEdit();
editButton.setIcon("lucide-pencil"); entry.startTime = startField.getTimestamp();
if (nameBox.getValue()) { endField.endEdit();
entry.name = nameBox.getValue(); entry.endTime = endField.getTimestamp();
namePar.setText(entry.name);
await saveTracker(tracker, this.app, file, getSectionInfo()); await saveTracker(tracker, this.app, file, getSectionInfo());
} editButton.setIcon("lucide-pencil");
} else { } else {
namePar.hidden = true; nameField.beginEdit(entry.name);
nameBox.inputEl.hidden = false; startField.beginEdit((entry.startTime));
nameBox.setValue(entry.name); endField.beginEdit((entry.endTime));
editButton.setIcon("lucide-check"); editButton.setIcon("lucide-check");
} }
}); });

View file

@ -1,3 +1,7 @@
.simple-time-tracker-container {
overflow-x: scroll;
}
.simple-time-tracker-support { .simple-time-tracker-support {
max-width: 50%; max-width: 50%;
width: 400px; width: 400px;
@ -62,3 +66,8 @@
.simple-time-tracker-table .clickable-icon { .simple-time-tracker-table .clickable-icon {
display: inline; display: inline;
} }
.simple-time-tracker-input {
max-width: 150px;
min-width: 100px;
}

View file

@ -25,5 +25,6 @@
"publish": false, "publish": false,
"sync": false, "sync": false,
"canvas": true, "canvas": true,
"bookmarks": true "bookmarks": true,
"properties": false
} }

View file

@ -2,5 +2,5 @@
These are the notes for my cool project. There's so much left to do! I wish I had a way to track the amount of time I spend on each part of the project. These are the notes for my cool project. There's so much left to do! I wish I had a way to track the amount of time I spend on each part of the project.
```simple-time-tracker ```simple-time-tracker
{"entries":[{"name":"Segment 1","startTime":1666189948,"endTime":1666189951,"subEntries":null},{"name":"Segment 2","startTime":1666189953,"endTime":1666189961,"subEntries":null},{"name":"Segment 3","startTime":null,"endTime":null,"subEntries":[{"name":"Part 1","startTime":1666189962,"endTime":1666189995,"subEntries":null},{"name":"Part 2","startTime":1666190004,"endTime":1666190025,"subEntries":null},{"name":"Part 3","startTime":1666190094,"endTime":1666190101,"subEntries":null}]},{"name":"Segment 4","startTime":1666190088,"endTime":1666190091,"subEntries":null},{"name":"Segment 5","startTime":1684857704,"endTime":1684857708,"subEntries":null},{"name":"Segment 6","startTime":1684857710,"endTime":1684857712,"subEntries":null},{"name":"Segment 7","startTime":1684857729,"endTime":1684857732,"subEntries":null},{"name":"Segment 8","startTime":1684857743,"endTime":1684857748,"subEntries":null}]} {"entries":[{"name":"Segment 1","startTime":"2022-10-19T14:32:28.000Z","endTime":"2022-10-19T14:32:31.000Z","subEntries":null},{"name":"Segment 2","startTime":"2022-10-19T14:32:33.000Z","endTime":"2022-10-19T14:32:41.000Z","subEntries":null},{"name":"Segment 3","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2022-10-19T14:32:42.000Z","endTime":"2022-10-19T14:33:15.000Z","subEntries":null},{"name":"Part 2","startTime":"2022-10-19T14:33:24.000Z","endTime":"2022-10-19T14:33:45.000Z","subEntries":null},{"name":"Part 3","startTime":"2022-10-19T14:34:54.000Z","endTime":"2022-10-19T14:35:01.000Z","subEntries":null}]},{"name":"Segment 4","startTime":"2022-10-19T14:34:48.000Z","endTime":"2022-10-19T14:34:51.000Z","subEntries":null},{"name":"Segment 5","startTime":"2023-05-23T16:01:44.000Z","endTime":"2023-05-23T16:01:48.000Z","subEntries":null},{"name":"Segment 6","startTime":"2023-05-23T16:01:50.000Z","endTime":"2023-05-23T16:01:52.000Z","subEntries":null},{"name":"Segment 7","startTime":"2023-05-23T16:02:09.000Z","endTime":"2023-05-23T16:02:12.000Z","subEntries":null},{"name":"Segment 8","startTime":"1970-01-01T00:00:00.000Z","endTime":null,"subEntries":[{"name":"Part 1","startTime":"2023-05-23T16:02:20.000Z","endTime":"2023-05-23T16:02:28.000Z","subEntries":null},{"name":"Part 2","startTime":"2023-09-08T11:58:11.000Z","endTime":"2023-09-08T11:58:38.000Z","subEntries":null}]},{"name":"Segment 9","startTime":"2023-09-08T12:00:35.000Z","endTime":"2023-09-08T12:00:49.991Z","subEntries":null},{"name":"Segment 10","startTime":"2023-09-08T12:01:45.000Z","endTime":"2023-09-08T12:01:53.711Z","subEntries":null}]}
``` ```

View file

@ -7,5 +7,6 @@
"0.1.4": "0.15.0", "0.1.4": "0.15.0",
"0.1.5": "0.15.0", "0.1.5": "0.15.0",
"0.1.6": "0.15.0", "0.1.6": "0.15.0",
"0.1.7": "1.2.8" "0.1.7": "1.2.8",
"0.1.8": "1.3.0"
} }