From ba0c5a2b7feb3a2065f58c06b900fcecc504ad68 Mon Sep 17 00:00:00 2001 From: ivan-lednev Date: Tue, 17 Oct 2023 22:42:53 +0200 Subject: [PATCH] feat: add option to extend task until next in timeline --- .eslintrc | 4 +-- src/main.ts | 4 ++- src/service/dataview-facade.ts | 10 +++--- src/settings.ts | 4 +++ src/ui/components/task-container.svelte | 2 +- src/ui/hooks/use-tasks-for-day.ts | 10 ++++-- src/ui/settings-tab.ts | 32 +++++++++++++++++++ src/util/get-tasks-for-day.ts | 41 +++++++++++++++++++++++-- src/util/task-utils.ts | 5 --- 9 files changed, 94 insertions(+), 18 deletions(-) diff --git a/.eslintrc b/.eslintrc index f2c335c5a..702306ede 100644 --- a/.eslintrc +++ b/.eslintrc @@ -73,7 +73,7 @@ { "selector": "enum", "format": [ - "StrictPascalCase" + "PascalCase" ] }, { @@ -85,7 +85,7 @@ { "selector": "interface", "format": [ - "StrictPascalCase" + "PascalCase" ], "custom": { "regex": "^I[A-Z]", diff --git a/src/main.ts b/src/main.ts index 6a8027a5d..d4cd426ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -198,7 +198,9 @@ export default class DayPlanner extends Plugin { private updateStatusBar = async (dataviewTasks: DataArray) => { const today = window.moment(); - await this.statusBar.update(getTasksForDay(today, dataviewTasks)); + await this.statusBar.update( + getTasksForDay(today, dataviewTasks, { ...this.settings() }), + ); }; initWeeklyLeaf = async () => { diff --git a/src/service/dataview-facade.ts b/src/service/dataview-facade.ts index 6a6f01b9a..5c65fbcda 100644 --- a/src/service/dataview-facade.ts +++ b/src/service/dataview-facade.ts @@ -1,7 +1,6 @@ import { Moment } from "moment"; import { STask, DateTime } from "obsidian-dataview"; -import { defaultDurationMinutes } from "../constants"; import { createPlanItem } from "../parser/parser"; import { timeRegExp } from "../regexp"; import { PlanItem } from "../types"; @@ -45,6 +44,10 @@ export function sTaskToPlanItem(sTask: STask, day: Moment): PlanItem { }, }); + const durationMinutes = endTime + ? getDiffInMinutes(endTime, startTime) + : undefined; + return { startTime, rawStartTime: "-", @@ -52,10 +55,7 @@ export function sTaskToPlanItem(sTask: STask, day: Moment): PlanItem { listTokens: `${sTask.symbol} [${sTask.status}] `, firstLineText, text, - durationMinutes: getDiffInMinutes( - endTime || startTime.clone().add(defaultDurationMinutes, "minutes"), - startTime, - ), + durationMinutes, startMinutes: getMinutesSinceMidnight(startTime), location: { path: sTask.path, diff --git a/src/settings.ts b/src/settings.ts index c47d7a870..e6ef37b89 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -20,6 +20,8 @@ export interface DayPlannerSettings { timestampFormat: string; dataviewSource: string; showDataviewMigrationWarning: boolean; + extendDurationUntilNext: boolean; + defaultDurationMinutes: number; } export const defaultSettings = { @@ -41,6 +43,8 @@ export const defaultSettings = { timestampFormat: "HH:mm", dataviewSource: "", showDataviewMigrationWarning: true, + extendDurationUntilNext: false, + defaultDurationMinutes: 30, }; export const defaultSettingsForTests = { diff --git a/src/ui/components/task-container.svelte b/src/ui/components/task-container.svelte index 0a499be06..09b7e8373 100644 --- a/src/ui/components/task-container.svelte +++ b/src/ui/components/task-container.svelte @@ -29,7 +29,7 @@ const pointerOffsetY = writable(0); - $: parsedTasks = useTasksForDay({ day, dataviewTasks: $dataviewTasks }); + $: parsedTasks = useTasksForDay({ day, dataviewTasks: $dataviewTasks, settings: $settings }); $: ({ startEdit, displayedTasks, cancelEdit, editStatus, confirmEdit } = useEdit({ diff --git a/src/ui/hooks/use-tasks-for-day.ts b/src/ui/hooks/use-tasks-for-day.ts index a3f4ee20c..36a8afa59 100644 --- a/src/ui/hooks/use-tasks-for-day.ts +++ b/src/ui/hooks/use-tasks-for-day.ts @@ -2,19 +2,25 @@ import { Moment } from "moment"; import { DataArray, STask } from "obsidian-dataview"; import { addPlacing } from "../../overlap/overlap"; +import { DayPlannerSettings } from "../../settings"; import { getTasksForDay } from "../../util/get-tasks-for-day"; interface UseTaskSourceProps { day: Moment; dataviewTasks: DataArray; + settings: DayPlannerSettings; } -export function useTasksForDay({ day, dataviewTasks }: UseTaskSourceProps) { +export function useTasksForDay({ + day, + dataviewTasks, + settings, +}: UseTaskSourceProps) { if (dataviewTasks.length === 0) { return []; } - const tasksForDay = getTasksForDay(day, dataviewTasks); + const tasksForDay = getTasksForDay(day, dataviewTasks, { ...settings }); return addPlacing(tasksForDay); } diff --git a/src/ui/settings-tab.ts b/src/ui/settings-tab.ts index 9953e34e3..1286f22ce 100644 --- a/src/ui/settings-tab.ts +++ b/src/ui/settings-tab.ts @@ -222,6 +222,38 @@ When you open a file, the plugin will search for this heading to detect a day pl }), ); + containerEl.createEl("h2", { text: "Duration" }); + + new Setting(containerEl) + .setName("Stretch task until next one in timeline if it has no end time") + .setDesc( + 'By "no end time" we mean "- [ ] 10:00 Wake up" instead of "- [ ] 10:00 - 11:00 Wake up"', + ) + .addToggle((component) => { + component + .setValue(this.plugin.settings().extendDurationUntilNext) + .onChange((value) => { + this.update({ extendDurationUntilNext: value }); + }); + }); + + new Setting(containerEl) + .setName("Default task duration") + .setDesc( + "Used when you create a task with drag-and-drop & when you don't specify an end time", + ) + .addSlider((slider) => + slider + .setLimits(20, 120, 10) + .setValue(Number(this.plugin.settings().defaultDurationMinutes)) + .setDynamicTooltip() + .onChange((value: number) => { + this.update({ defaultDurationMinutes: value }); + }), + ); + + containerEl.createEl("h2", { text: "Colors" }); + new Setting(containerEl) .setName("Colorful Timeline") .setDesc( diff --git a/src/util/get-tasks-for-day.ts b/src/util/get-tasks-for-day.ts index 78548808d..25acd4601 100644 --- a/src/util/get-tasks-for-day.ts +++ b/src/util/get-tasks-for-day.ts @@ -5,6 +5,7 @@ import { DataArray, STask } from "obsidian-dataview"; import { timeFromStartRegExp } from "../regexp"; import { sTaskToPlanItem } from "../service/dataview-facade"; +import { DayPlannerSettings } from "../settings"; import { PlanItem } from "../types"; function isScheduledForThisDay(task: STask, day: Moment) { @@ -29,14 +30,48 @@ function isScheduledForAnotherDay(task: STask, day: Moment) { return task.scheduled && !isScheduledForThisDay(task, day); } -export function getTasksForDay(day: Moment, dataviewTasks: DataArray) { +type DurationOptions = Pick< + DayPlannerSettings, + "defaultDurationMinutes" | "extendDurationUntilNext" +>; + +function calculateDuration(tasks: PlanItem[], options: DurationOptions) { + return tasks.map((current, i, array) => { + if (current.durationMinutes) { + return current; + } + + const next = array[i + 1]; + const shouldExtendUntilNext = next && options.extendDurationUntilNext; + + if (shouldExtendUntilNext) { + const minutesUntilNext = next.startMinutes - current.startMinutes; + + return { + ...current, + durationMinutes: minutesUntilNext, + }; + } + + return { + ...current, + durationMinutes: options.defaultDurationMinutes, + }; + }); +} + +export function getTasksForDay( + day: Moment, + dataviewTasks: DataArray, + options: DurationOptions, +): PlanItem[] { if (dataviewTasks.length === 0) { return []; } const noteForThisDay = getDailyNote(day, getAllDailyNotes()); - return dataviewTasks + const planItems = dataviewTasks .where( (task: STask) => isTimeSetOnTask(task) && @@ -47,4 +82,6 @@ export function getTasksForDay(day: Moment, dataviewTasks: DataArray) { .map((sTask: STask) => sTaskToPlanItem(sTask, day)) .sort((task: PlanItem) => task.startMinutes) .array(); + + return calculateDuration(planItems, options); } diff --git a/src/util/task-utils.ts b/src/util/task-utils.ts index 0d4a5ed82..3946d1be3 100644 --- a/src/util/task-utils.ts +++ b/src/util/task-utils.ts @@ -1,6 +1,5 @@ import type { Moment } from "moment"; -import { scheduledDateRegExp } from "../regexp"; import type { PlanItem } from "../types"; import { PlacedPlanItem } from "../types"; @@ -31,7 +30,3 @@ export function getRenderKey(task: PlacedPlanItem) { task.isGhost ?? "" }`; } - -export function getDisplayedText(task: PlanItem) { - return task.text.replace(scheduledDateRegExp, ""); -}