mirror of
https://github.com/Ellpeck/ObsidianSimpleTimeTracker.git
synced 2024-12-19 03:39:23 +01:00
added the ability to copy the table as markdown or csv
This commit is contained in:
parent
10251de71c
commit
90c6b55ad7
9 changed files with 191 additions and 51 deletions
|
@ -17,7 +17,6 @@ The tracker's information is stored in the code block as JSON data. The names, s
|
||||||
Super Simple Time Tracker is still in its early stages! There are a lot of plans for it, including:
|
Super Simple Time Tracker is still in its early stages! There are a lot of plans for it, including:
|
||||||
- A setting to link segments to corresponding daily notes automatically
|
- A setting to link segments to corresponding daily notes automatically
|
||||||
- A neat interface to edit previous segments' names and time stamps
|
- A neat interface to edit previous segments' names and time stamps
|
||||||
- The ability to copy the table in various formats, including as text, markdown, and csv
|
|
||||||
- A fancier Start and End button
|
- A fancier Start and End button
|
||||||
|
|
||||||
# 🙏 Acknowledgements
|
# 🙏 Acknowledgements
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
|
||||||
.setDesc(createFragment(f => {
|
.setDesc(createFragment(f => {
|
||||||
f.createSpan({ text: "The way that timestamps in time tracker tables should be displayed. Uses " });
|
f.createSpan({ text: "The way that timestamps in time tracker tables should be displayed. Uses " });
|
||||||
f.createEl("a", { text: "moment.js", href: "https://momentjs.com/docs/#/parsing/string-format/" });
|
f.createEl("a", { text: "moment.js", href: "https://momentjs.com/docs/#/parsing/string-format/" });
|
||||||
f.createSpan({ text: " syntax. Clear to reset to default." });
|
f.createSpan({ text: " syntax." });
|
||||||
}))
|
}))
|
||||||
.addText(t => {
|
.addText(t => {
|
||||||
t.setValue(String(this.plugin.settings.timestampFormat));
|
t.setValue(String(this.plugin.settings.timestampFormat));
|
||||||
|
@ -30,6 +30,17 @@ export class SimpleTimeTrackerSettingsTab extends PluginSettingTab {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
new Setting(this.containerEl)
|
||||||
|
.setName("CSV Delimiter")
|
||||||
|
.setDesc("The delimiter character that should be used when copying a tracker table as CSV. For example, some languages use a semicolon instead of a comma.")
|
||||||
|
.addText(t => {
|
||||||
|
t.setValue(String(this.plugin.settings.csvDelimiter));
|
||||||
|
t.onChange(async v => {
|
||||||
|
this.plugin.settings.csvDelimiter = v.length ? v : defaultSettings.csvDelimiter;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.containerEl.createEl("hr");
|
this.containerEl.createEl("hr");
|
||||||
this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" });
|
this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" });
|
||||||
this.containerEl.createEl("a", { href: "https://ellpeck.de/support" })
|
this.containerEl.createEl("a", { href: "https://ellpeck.de/support" })
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
export const defaultSettings: SimpleTimeTrackerSettings = {
|
export const defaultSettings: SimpleTimeTrackerSettings = {
|
||||||
timestampFormat: "YY-MM-DD hh:mm:ss"
|
timestampFormat: "YY-MM-DD hh:mm:ss",
|
||||||
|
csvDelimiter: ","
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface SimpleTimeTrackerSettings {
|
export interface SimpleTimeTrackerSettings {
|
||||||
|
|
||||||
timestampFormat: string;
|
timestampFormat: string;
|
||||||
|
csvDelimiter: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,27 +81,36 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
|
||||||
let total = totalDiv.createEl("span", { cls: "simple-time-tracker-timer-time", text: "0s" });
|
let total = totalDiv.createEl("span", { cls: "simple-time-tracker-timer-time", text: "0s" });
|
||||||
totalDiv.createEl("span", { text: "Total" });
|
totalDiv.createEl("span", { text: "Total" });
|
||||||
|
|
||||||
// add table
|
|
||||||
if (tracker.entries.length > 0) {
|
if (tracker.entries.length > 0) {
|
||||||
|
// add table
|
||||||
let table = element.createEl("table", { cls: "simple-time-tracker-table" });
|
let table = element.createEl("table", { cls: "simple-time-tracker-table" });
|
||||||
table.createEl("tr").append(
|
table.createEl("tr").append(
|
||||||
createEl("th", { text: "Segment" }),
|
createEl("th", { text: "Segment" }),
|
||||||
createEl("th", { text: "Start time" }),
|
createEl("th", { text: "Start time" }),
|
||||||
createEl("th", { text: "End time" }),
|
createEl("th", { text: "End time" }),
|
||||||
createEl("th", { text: "Total" }));
|
createEl("th", { text: "Duration" }));
|
||||||
|
|
||||||
for (let entry of tracker.entries) {
|
for (let entry of tracker.entries) {
|
||||||
let row = table.createEl("tr");
|
let row = table.createEl("tr");
|
||||||
row.createEl("td", { text: entry.name });
|
row.createEl("td", { text: entry.name });
|
||||||
row.createEl("td", { text: moment.unix(entry.startTime).format(settings.timestampFormat) });
|
row.createEl("td", { text: formatTimestamp(entry.startTime, settings) });
|
||||||
if (entry.endTime) {
|
if (entry.endTime) {
|
||||||
row.createEl("td", { text: moment.unix(entry.endTime).format(settings.timestampFormat) });
|
row.createEl("td", { text: formatTimestamp(entry.endTime, settings) });
|
||||||
let duration = moment.unix(entry.endTime).diff(moment.unix(entry.startTime));
|
row.createEl("td", { text: formatDurationBetween(entry.startTime, entry.endTime) });
|
||||||
row.createEl("td", { text: getCountdownDisplay(moment.duration(duration)) });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add copy buttons
|
||||||
|
let buttons = element.createEl("div", { cls: "simple-time-tracker-bottom" });
|
||||||
|
new ButtonComponent(buttons)
|
||||||
|
.setButtonText("Copy as table")
|
||||||
|
.onClick(() => navigator.clipboard.writeText(createMarkdownTable(tracker, settings)));
|
||||||
|
new ButtonComponent(buttons)
|
||||||
|
.setButtonText("Copy as csv")
|
||||||
|
.onClick(() => navigator.clipboard.writeText(createCsv(tracker, settings)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setCountdownValues(tracker, current, total, currentDiv);
|
setCountdownValues(tracker, current, total, currentDiv);
|
||||||
let intervalId = window.setInterval(() => {
|
let intervalId = window.setInterval(() => {
|
||||||
// we delete the interval timer when the element is removed
|
// we delete the interval timer when the element is removed
|
||||||
|
@ -113,7 +122,35 @@ export function displayTracker(tracker: Tracker, element: HTMLElement, getSectio
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCountdownDisplay(duration: moment.Duration): string {
|
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement) {
|
||||||
|
let currEntry = tracker.entries.last();
|
||||||
|
if (currEntry) {
|
||||||
|
if (!currEntry.endTime)
|
||||||
|
current.setText(formatDurationBetween(currEntry.startTime, moment().unix()));
|
||||||
|
total.setText(formatDuration(getTotalDuration(tracker)));
|
||||||
|
}
|
||||||
|
currentDiv.hidden = !currEntry || !!currEntry.endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalDuration(tracker: Tracker): number {
|
||||||
|
let totalDuration = 0;
|
||||||
|
for (let entry of tracker.entries) {
|
||||||
|
let endTime = entry.endTime ? moment.unix(entry.endTime) : moment();
|
||||||
|
totalDuration += endTime.diff(moment.unix(entry.startTime));
|
||||||
|
}
|
||||||
|
return totalDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestamp(timestamp: number, settings: SimpleTimeTrackerSettings): string {
|
||||||
|
return moment.unix(timestamp).format(settings.timestampFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDurationBetween(startTime: number, endTime: number): string {
|
||||||
|
return formatDuration(moment.unix(endTime).diff(moment.unix(startTime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(totalTime: number): string {
|
||||||
|
let duration = moment.duration(totalTime);
|
||||||
let ret = "";
|
let ret = "";
|
||||||
if (duration.hours() > 0)
|
if (duration.hours() > 0)
|
||||||
ret += duration.hours() + "h ";
|
ret += duration.hours() + "h ";
|
||||||
|
@ -123,20 +160,39 @@ function getCountdownDisplay(duration: moment.Duration): string {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCountdownValues(tracker: Tracker, current: HTMLElement, total: HTMLElement, currentDiv: HTMLDivElement) {
|
function createMarkdownTable(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||||
let currEntry = tracker.entries.last();
|
let table = [["Segment", "Start time", "End time", "Duration"]];
|
||||||
if (currEntry) {
|
for (let entry of tracker.entries)
|
||||||
if (!currEntry.endTime) {
|
table.push(createTableRow(entry, settings));
|
||||||
let currDuration = moment().diff(moment.unix(currEntry.startTime));
|
table.push(["**Total**", "", "", `**${formatDuration(getTotalDuration(tracker))}**`]);
|
||||||
current.setText(getCountdownDisplay(moment.duration(currDuration)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalDuration = 0;
|
let ret = "";
|
||||||
for (let entry of tracker.entries) {
|
// calculate the width every column needs to look neat when monospaced
|
||||||
let endTime = entry.endTime ? moment.unix(entry.endTime) : moment();
|
let widths = Array.from(Array(4).keys()).map(i => Math.max(...table.map(a => a[i].length)));
|
||||||
totalDuration += endTime.diff(moment.unix(entry.startTime));
|
for (let r = 0; r < table.length; r++) {
|
||||||
}
|
// add separators after first row
|
||||||
total.setText(getCountdownDisplay(moment.duration(totalDuration)));
|
if (r == 1)
|
||||||
|
ret += Array.from(Array(4).keys()).map(i => "-".repeat(widths[i])).join(" | ") + "\n";
|
||||||
|
|
||||||
|
let row: string[] = [];
|
||||||
|
for (let i = 0; i < 4; i++)
|
||||||
|
row.push(table[r][i].padEnd(widths[i], " "));
|
||||||
|
ret += row.join(" | ") + "\n";
|
||||||
}
|
}
|
||||||
currentDiv.hidden = !currEntry || !!currEntry.endTime;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCsv(tracker: Tracker, settings: SimpleTimeTrackerSettings): string {
|
||||||
|
let ret = "";
|
||||||
|
for (let entry of tracker.entries)
|
||||||
|
ret += createTableRow(entry, settings).join(settings.csvSeparator) + "\n";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTableRow(entry: Entry, settings: SimpleTimeTrackerSettings): string[] {
|
||||||
|
return [
|
||||||
|
entry.name,
|
||||||
|
formatTimestamp(entry.startTime, settings),
|
||||||
|
entry.endTime ? formatTimestamp(entry.endTime, settings) : "",
|
||||||
|
entry.endTime ? formatDurationBetween(entry.startTime, entry.endTime) : ""];
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,12 @@
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple-time-tracker-timers {
|
.simple-time-tracker-bottom button {
|
||||||
|
margin: 10px 5px 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simple-time-tracker-timers,
|
||||||
|
.simple-time-tracker-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"timestampFormat": "YY-MM-DD hh:mm:ss"
|
"timestampFormat": "YY-MM-DD hh:mm:ss",
|
||||||
|
"csvSeparator": ","
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
|
@ -20,7 +20,12 @@
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple-time-tracker-timers {
|
.simple-time-tracker-bottom button {
|
||||||
|
margin: 10px 5px 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simple-time-tracker-timers,
|
||||||
|
.simple-time-tracker-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -2,5 +2,17 @@
|
||||||
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":"Think about project","startTime":1664305777,"endTime":1664308788},{"name":"Create project note","startTime":1664308810,"endTime":1664308815},{"name":"Work on project","startTime":1664308830,"endTime":1664309301}]}
|
{"entries":[{"name":"Think about project","startTime":1664305777,"endTime":1664308788},{"name":"Create project note","startTime":1664308810,"endTime":1664308815},{"name":"Work on project","startTime":1664308830,"endTime":1664309301},{"name":"Segment 4","startTime":1664364444,"endTime":1664364449},{"name":"Segment 5","startTime":1664364495,"endTime":1664364498}]}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Think about project;22-09-27 09:09:37;22-09-27 09:59:48;50m 11s
|
||||||
|
Create project note;22-09-27 10:00:10;22-09-27 10:00:15;5s
|
||||||
|
Work on project;22-09-27 10:00:30;22-09-27 10:08:21;7m 51s
|
||||||
|
Segment 4;22-09-28 01:27:24;22-09-28 01:27:29;5s
|
||||||
|
Segment 5;22-09-28 01:28:15;22-09-28 01:28:18;3s
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue