Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a task progress indicator to notes in board view #897

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7e5ee31
Implement task progress indication to Board view
H3mul Jun 4, 2024
ef7b2c2
Minor regex optimization
H3mul Jun 4, 2024
184d3dc
Merge branch 'main' into feature-task-progress
Acylation Jun 13, 2024
c23a94f
Satisfy check and apply format
Acylation Jun 13, 2024
37476d4
Use metadataCache
Acylation Jun 13, 2024
597f354
Update boardHelpers.ts
Acylation Jun 13, 2024
89d1c35
Use app store and cancel the app as param
Acylation Jun 13, 2024
f2adc35
Implement task weight via field
H3mul Jun 9, 2024
79ddc42
Implement indicators as a separate component
H3mul Jun 12, 2024
5b68748
Adjust getTaskProgress: remove unnecesary async and fix the 0 task case
H3mul Jun 13, 2024
c48a688
Adjust Indicator component import
H3mul Jun 13, 2024
f47ddfc
Extract task helpers to avoid breaking jest
Acylation Jun 13, 2024
30cdc87
Merge branch 'feature-task-progress' of https://github.com/H3mul/obsi…
Acylation Jun 13, 2024
d12cd65
Formatting and satisfy CI check
Acylation Jun 13, 2024
6366199
Refactor Indicator and styles
Acylation Jun 14, 2024
fafb2c1
Merge branch 'main' into feature-task-progress
Acylation Jun 18, 2024
463ad20
Rename weight to points and add tooltips
H3mul Jul 6, 2024
20dee86
Add story point total in columnHeader
H3mul Jul 6, 2024
28e72ae
Rename point fetch function
H3mul Jul 6, 2024
aa8fb3d
Fix absent points case
H3mul Jul 7, 2024
f71a97c
Fix hiding points if feature isn't used
H3mul Jul 7, 2024
d274b06
Merge branch 'main' into feature-task-progress
Acylation Dec 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
Expand Down
10 changes: 10 additions & 0 deletions src/lib/stores/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@
"check": "Check",
"none": "None"
},
"tooltips": {
"checklist-items": "Checklist items",
"task-points": "Story points",
"checked-total": "Cards checked/total",
"task-points-total": "Story point total"
},
"unprioritized": "Unprioritized",
"include-fields": "Include fields",
"settings": {
Expand All @@ -308,6 +314,10 @@
"order-sync-field": {
"name": "Sync card order with field",
"description": "Field to store the position of cards in the board."
},
"points-field": {
"name": "Card story points field",
"description": "Use the selected field as the card story point value."
}
},
"note": {
Expand Down
18 changes: 18 additions & 0 deletions src/ui/components/Indicator/Indicator.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import { Icon } from "obsidian-svelte";
export let icon: string = "";
export let tooltip: string = "";
</script>

<div class="task-indicator" aria-label={tooltip}>
<Icon name={icon} size="sm" />
<slot />
</div>

<style>
div.task-indicator {
display: flex;
gap: 4px;
align-items: center;
}
</style>
1 change: 1 addition & 0 deletions src/ui/views/Board/BoardView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@
)}
{columnWidth}
checkField={fields.find((field) => field.name === config?.checkField)?.name}
pointsField={fields.find((field) => field.name === config?.pointsField)?.name}
includeFields={fields.filter((field) => includeFields.includes(field.name))}
customHeader={fields.find((field) => field.name === customHeader)}
onRecordClick={handleRecordClick}
Expand Down
2 changes: 2 additions & 0 deletions src/ui/views/Board/components/Board/Board.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
export let onColumnPin: OnColumnPin;
export let validateStatusField: () => string;
export let checkField: string | undefined;
export let pointsField: string | undefined;
export let includeFields: DataField[];
export let customHeader: DataField | undefined;

Expand Down Expand Up @@ -83,6 +84,7 @@
records={column.records}
{onRecordClick}
{checkField}
{pointsField}
{onRecordCheck}
onRecordAdd={() => onRecordAdd(column.id)}
onDrop={(record, records, trigger) => {
Expand Down
7 changes: 7 additions & 0 deletions src/ui/views/Board/components/Board/BoardColumn.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
export let readonly: boolean;
export let richText: boolean;
export let checkField: string | undefined;
export let pointsField: string | undefined;
export let includeFields: DataField[];
export let customHeader: DataField | undefined;
export let pinned: boolean;
Expand All @@ -42,6 +43,9 @@

$: count = records.length;
$: checkedCount = records.filter((r) => r.values[checkField ?? ""]).length;
$: pointsCount = records.map((r) => r.values[pointsField ?? ""] as number)
.filter(Number.isFinite)
.reduce((sum, p) => sum + p, 0);

function onColumnMenu() {
const menu = new Menu();
Expand Down Expand Up @@ -109,10 +113,12 @@
value={name}
{count}
{checkedCount}
{pointsCount}
bind:editing
{richText}
{collapse}
{checkField}
{pointsField}
{onColumnMenu}
{onColumnRename}
{onValidate}
Expand All @@ -125,6 +131,7 @@
{customHeader}
{onRecordClick}
{checkField}
{pointsField}
{onRecordCheck}
{onDrop}
{includeFields}
Expand Down
30 changes: 29 additions & 1 deletion src/ui/views/Board/components/Board/CardList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
// import { Checkbox, InternalLink} from "obsidian-svelte";
import { Checkbox } from "obsidian-svelte";
import InternalLink from "src/ui/components/InternalLink.svelte";

import {
isString,
type DataField,
type DataRecord,
} from "src/lib/dataframe/dataframe";
import { app } from "src/lib/stores/obsidian";
import { settings } from "src/lib/stores/settings";
import { i18n } from "src/lib/stores/i18n";
import { get } from "svelte/store";
import CardMetadata from "src/ui/components/CardMetadata/CardMetadata.svelte";
import ColorItem from "src/ui/components/ColorItem/ColorItem.svelte";
import Indicator from "src/ui/components/Indicator/Indicator.svelte";
import {
getRecordColorContext,
handleHoverLink,
Expand All @@ -24,6 +26,7 @@
} from "svelte-dnd-action";
import { flip } from "svelte/animate";
import { getDisplayName } from "./boardHelpers";
import { getTaskProgress } from "./taskHelpers";
import type {
DropTrigger,
OnRecordClick,
Expand All @@ -37,6 +40,10 @@
export let onDrop: OnRecordDrop;
export let includeFields: DataField[];
export let checkField: string | undefined;
export let pointsField: string | undefined;
const getTaskPoints = (item: DataRecord) =>
pointsField ? (item.values[pointsField] as number) : null;

export let customHeader: DataField | undefined;
export let boardEditing: boolean;

Expand Down Expand Up @@ -88,6 +95,8 @@
>
{#each items as item (item.id)}
{@const color = getRecordColor(item)}
{@const taskPoints = getTaskPoints(item)}
{@const taskProgress = getTaskProgress(item.id)}

<article
class="projects--board--card"
Expand Down Expand Up @@ -139,6 +148,18 @@
{/if}
</div>
<CardMetadata fields={includeFields} record={item} />
<div class="task-indicators">
{#if taskProgress}
<Indicator icon="check-square" tooltip={get(i18n).t("views.board.tooltips.checklist-items")}>
{taskProgress}
</Indicator>
{/if}
{#if taskPoints}
<Indicator icon="weight" tooltip={get(i18n).t("views.board.tooltips.task-points")}>
{taskPoints}
</Indicator>
{/if}
</div>
</ColorItem>
</article>
{/each}
Expand All @@ -151,6 +172,13 @@
align-items: center;
}

div.task-indicators {
display: flex;
gap: 4px;
align-items: center;
gap: 10px;
}

.checkbox-wrapper {
display: flex;
flex-direction: column;
Expand Down
15 changes: 13 additions & 2 deletions src/ui/views/Board/components/Board/ColumnHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<script lang="ts">
import { MarkdownRenderer, Menu } from "obsidian";
import { app, view } from "src/lib/stores/obsidian";
import { i18n } from "src/lib/stores/i18n";
import { get } from "svelte/store";
import { getContext } from "svelte";
import { TextInput, IconButton } from "obsidian-svelte";
import { TextInput, IconButton, Icon } from "obsidian-svelte";
import { Flair } from "src/ui/components/Flair";
import { handleHoverLink } from "src/ui/views/helpers";

export let value: string;
export let count: number;
export let checkedCount: number;
export let checkField: string | undefined;
export let pointsCount: number;
export let pointsField: string | undefined;
export let collapse: boolean = false;
export let richText: boolean = false;
const sourcePath = getContext<string>("sourcePath") ?? "";
Expand Down Expand Up @@ -128,8 +132,15 @@
</span>
{/if}
<div>
{#if pointsField && pointsCount}
<Flair variant="primary" tooltip={get(i18n).t("views.board.tooltips.task-points-total")}>
<Icon name="weight" size="xs" --icon-color="var(--text-color)"/>
{pointsCount}
</Flair>
{/if}
{#if collapse || checkField}
<Flair variant="primary">
<Flair variant="primary" tooltip={get(i18n).t("views.board.tooltips.checked-total")}>
<Icon name="wallet-cards" size="xs" --icon-color="var(--text-color)"/>
{checkField ? `${checkedCount}/${count}` : count}
</Flair>
{/if}
Expand Down
17 changes: 17 additions & 0 deletions src/ui/views/Board/components/Board/taskHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { app } from "src/lib/stores/obsidian";
import { get } from "svelte/store";

export function getTaskProgress(recordId: string): string {
let progress = "";

const totalTasks = get(app)
.metadataCache.getCache(recordId)
?.listItems?.filter((item) => item.task !== undefined);

if (totalTasks?.length) {
const completedTasks = totalTasks?.filter((item) => item.task !== " ");
progress = `${completedTasks.length}/${totalTasks.length}`;
}

return progress;
}
22 changes: 19 additions & 3 deletions src/ui/views/Board/settings/BoardSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
let columnWidthValue = config.columnWidth ?? null;

$: headerField = config.headerField ?? "";

$: orderSyncField = config.orderSyncField ?? "";
$: validOrderSyncFields = getFieldsByType(fields, DataFieldType.Number);
$: pointsField = config.pointsField ?? "";

$: numberFields = getFieldsByType(fields, DataFieldType.Number);

const updateConfig = <T extends keyof BoardConfig>(
key: T,
Expand Down Expand Up @@ -69,7 +70,7 @@
>
<Select
value={orderSyncField ?? ""}
options={validOrderSyncFields.map(fieldToSelectableValue)}
options={numberFields.map(fieldToSelectableValue)}
placeholder={$i18n.t("views.board.fields.none") ?? ""}
allowEmpty
on:change={(event) => {
Expand All @@ -78,5 +79,20 @@
}}
/>
</SettingItem>
<SettingItem
name={$i18n.t("views.board.settings.points-field.name")}
description={$i18n.t("views.board.settings.points-field.description")}
>
<Select
value={pointsField ?? ""}
options={numberFields.map(fieldToSelectableValue)}
placeholder={$i18n.t("views.board.fields.none") ?? ""}
allowEmpty
on:change={(event) => {
pointsField = event.detail;
updateConfig("pointsField", pointsField);
}}
/>
</SettingItem>
</ModalContent>
</ModalLayout>
1 change: 1 addition & 0 deletions src/ui/views/Board/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface BoardConfig {
readonly checkField?: string;
readonly headerField?: string;
readonly orderSyncField?: string;
readonly pointsField?: string;
readonly columnWidth?: number;
readonly columns?: ColumnSettings;
readonly includeFields?: string[];
Expand Down