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 board custom card order #788

Merged
merged 14 commits into from
Mar 13, 2024
4 changes: 4 additions & 0 deletions src/customViewApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { ProjectDefinition, ViewId } from "./settings/settings";

export interface DataQueryResult {
data: DataFrame;
hasSort: boolean;
hasFilter: boolean;
}

/**
Expand All @@ -18,6 +20,8 @@ export interface ProjectViewProps<T = Record<string, any>> {
viewApi: ViewApi;
readonly: boolean;
getRecordColor: (record: DataRecord) => string | null;
sortRecords: (records: ReadonlyArray<DataRecord>) => DataRecord[];
getRecord: (id: string) => DataRecord | undefined;
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/lib/datasources/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ export function parseRecords(
return records;
}

/**
* Copies a data record and merges values.
*
* @param {Readonly<DataRecord>} record - the original data record
* @param {Readonly<DataRecord["values"]>} values - the values to merge into the original record
* @return {DataRecord} a new data record with the merged values
*/
export function copyRecordWithValues(
record: Readonly<DataRecord>,
values: Readonly<DataRecord["values"]>
): DataRecord {
return { ...record, values: { ...record.values, ...values } };
}

export function detectFields(records: DataRecord[]): DataField[] {
const valuesByField: Record<string, Optional<DataValue>[]> = {};

Expand Down
13 changes: 13 additions & 0 deletions src/lib/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { get } from "svelte/store";

import { app } from "src/lib/stores/obsidian";
import type { ProjectDefinition, ViewDefinition } from "src/settings/settings";
import { getContext, setContext } from "svelte";
import type { DataField } from "./dataframe/dataframe";

/**
Expand Down Expand Up @@ -88,3 +89,15 @@ export function getNameFromPath(path: string) {
const end: number = path.lastIndexOf(".");
return path.substring(start, end);
}

export type Context<T> = Readonly<{
get: () => T;
set: (value: T) => void;
}>;
export function makeContext<T>(): Context<T> {
const key = Symbol();
return {
get: () => getContext(key),
set: (value: T) => setContext(key, value),
};
}
4 changes: 4 additions & 0 deletions src/lib/stores/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@
"column-width": {
"name": "Column width",
"description": "Width of each column in pixels."
},
"order-sync-field": {
"name": "Sync card order with field",
"description": "Field to store the position of cards in the board."
}
},
"note": {
Expand Down
6 changes: 5 additions & 1 deletion src/lib/stores/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@
"column-width": {
"name": "列宽度",
"description": "调整每列的像素宽度。"
},
"order-sync-field": {
"name": "卡片顺序同步字段",
"description": "该字段将用于存储卡片在看板列中的位置顺序信息。"
}
},
"note": {
Expand Down Expand Up @@ -447,4 +451,4 @@
}
}
}
}
}
34 changes: 32 additions & 2 deletions src/ui/app/View.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import type {
ProjectDefinition,
ProjectId,
SortDefinition,
ViewDefinition,
ViewId,
} from "src/settings/settings";
import { applyFilter, matchesCondition } from "./filterFunctions";

import { useView } from "./useView";
import { applySort } from "./viewSort";
import { applySort, sortRecords } from "./viewSort";

/**
* Specify the project.
Expand Down Expand Up @@ -74,9 +75,21 @@
$: viewFilter = view.filter ?? { conditions: [] };
$: filteredFrame = applyFilter(frame, viewFilter);

$: viewSort = view.sort ?? { criteria: [] };
$: viewSort =
view.sort.criteria.length > 0
? view.sort
: ({
criteria: [{ field: "path", order: "asc", enabled: true }],
} satisfies SortDefinition);

$: sortedFrame = applySort(filteredFrame, viewSort);

let recordCache: Record<string, DataRecord | undefined>;
$: {
frame;
recordCache = {};
}

function getRecordColor(record: DataRecord): string | null {
const colorFilter = view.colors ?? { conditions: [] };
for (const cond of colorFilter.conditions) {
Expand All @@ -88,6 +101,19 @@
}
return null;
}

const applyViewSortToRecords = (
records: ReadonlyArray<DataRecord>
): Array<DataRecord> => {
return sortRecords([...records], viewSort);
};

const getRecord = (id: string) => {
return (
recordCache[id] ??
(recordCache[id] = frame.records.find((record) => record.id === id))
);
};
</script>

<!--
Expand All @@ -100,13 +126,17 @@
view,
dataProps: {
data: sortedFrame,
hasSort: view.sort.criteria.filter((c) => c.enabled).length > 0,
hasFilter: view.filter.conditions.filter((c) => c.enabled).length > 0,
},
viewApi: api,
project,
readonly,
config: view.config,
onConfigChange: handleConfigChange,
getRecordColor: getRecordColor,
sortRecords: applyViewSortToRecords,
getRecord,
}}
/>

Expand Down
2 changes: 1 addition & 1 deletion src/ui/app/onboarding/demoProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export async function createDemoProject(vault: Vault) {

const boardConfig: BoardConfig = {
groupByField: "status",
priorityField: "weight",
orderSyncField: "weight",
};

const calendarConfig: CalendarConfig = {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/app/toolbar/viewOptions/color/ColorOptions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
outline: "none",
borderRadius: "5px",
background: "hsla(var(--interactive-accent-hsl), 0.3)",
transition: "all 150ms easy-in-out",
transition: "all 150ms ease-in-out",
},
}}
on:consider={handleDndConsider}
Expand Down
91 changes: 43 additions & 48 deletions src/ui/app/useView.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { get } from "svelte/store";

import type { DataQueryResult } from "src/customViewApi";
import type {
DataQueryResult,
ProjectView,
ProjectViewProps,
} from "src/customViewApi";
import type { DataRecord } from "src/lib/dataframe/dataframe";
import { customViews } from "src/lib/stores/customViews";
import type { ViewApi } from "src/lib/viewApi";
import type { DataRecord } from "src/lib/dataframe/dataframe";
import type { ProjectDefinition, ViewDefinition } from "src/settings/settings";

export interface ViewProps {
Expand All @@ -15,65 +19,56 @@ export interface ViewProps {
readonly: boolean;
project: ProjectDefinition;
getRecordColor: (record: DataRecord) => string | null;
sortRecords: ProjectViewProps["sortRecords"];
getRecord: ProjectViewProps["getRecord"];
}

export function useView(node: HTMLElement, props: ViewProps) {
// Keep track of previous view id to determine if view should be invalidated.
let viewId = props.view.id;

let viewId: string;
const projectId = props.project.id;
let projectView: ProjectView<Record<string, any>> | undefined;

let projectView = get(customViews)[props.view.type];
const update = (newprops: ViewProps) => {
// User switched to a different view.
const dirty =
newprops.view.id !== viewId || newprops.project.id !== projectId;

if (projectView) {
// Component just mounted, so treat all properties as dirty.
projectView.onOpen({
viewId: props.view.id,
project: props.project,
contentEl: node,
config: props.config,
saveConfig: props.onConfigChange,
viewApi: props.viewApi,
readonly: props.readonly,
getRecordColor: props.getRecordColor,
});
projectView.onData(props.dataProps);
}
if (dirty) {
// Clean up previous view.
projectView?.onClose();

return {
update(newprops: ViewProps) {
// User switched to a different view.
const dirty =
newprops.view.id !== viewId || newprops.project.id !== projectId;
node.empty();

if (dirty) {
// Clean up previous view.
projectView?.onClose();
// Look up the next view.
projectView = get(customViews)[newprops.view.type];

node.empty();
if (projectView) {
projectView.onOpen({
contentEl: node,
viewId: newprops.view.id,
project: newprops.project,
viewApi: newprops.viewApi,
readonly: newprops.readonly,
config: newprops.config,
saveConfig: newprops.onConfigChange,
getRecordColor: newprops.getRecordColor,
sortRecords: newprops.sortRecords,
getRecord: newprops.getRecord,
});
projectView.onData(newprops.dataProps);
}

// Look up the next view.
projectView = get(customViews)[newprops.view.type];
viewId = newprops.view.id;
} else {
projectView?.onData(newprops.dataProps);
}
};

if (projectView) {
projectView.onOpen({
contentEl: node,
viewId: newprops.view.id,
project: newprops.project,
viewApi: newprops.viewApi,
readonly: newprops.readonly,
config: newprops.config,
saveConfig: newprops.onConfigChange,
getRecordColor: newprops.getRecordColor,
});
projectView.onData(newprops.dataProps);
}
update(props);

viewId = newprops.view.id;
} else {
projectView?.onData(newprops.dataProps);
}
},
return {
update,
destroy() {
projectView?.onClose();
},
Expand Down
38 changes: 22 additions & 16 deletions src/ui/app/viewSort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ import type { DataRecord } from "../../lib/dataframe/dataframe";

export function applySort(frame: DataFrame, sort: SortDefinition): DataFrame {
return produce(frame, (draft) => {
draft.records = draft.records.sort((a, b): number => {
let res = 0;

const enabledCriteria = sort.criteria.filter((c) => c.enabled);

for (let i = 0; i < enabledCriteria.length; i++) {
const criteria = enabledCriteria[i];

// @ts-ignore
res = sortCriteria(a, b, criteria);
sortRecords(draft.records, sort);
});
}

if (res !== 0) {
break;
}
/**
* Sorts records in place. This method mutates the array
* and returns a reference to the same array.
*
* @param {DataRecord[]} records - the records to be sorted
* @param {SortDefinition} sort - the definition for sorting the records
*/
export function sortRecords(records: DataRecord[], sort: SortDefinition) {
return records.sort((a, b): number => {
let res = 0;

const enabledCriteria = sort.criteria.filter((c) => c.enabled);
for (const criteria of enabledCriteria) {
res = sortCriteria(a, b, criteria);
if (res !== 0) {
break;
}

return res;
});
}

return res;
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/ui/views/Board/BoardOptionsProvider.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
includedFields={config.includeFields ?? []}
onIncludedFieldsChange={handleIncludedFieldsChange}
onSettings={() => {
new BoardSettingsModal($app, config, (value) => {
new BoardSettingsModal($app, config, fields, (value) => {
onConfigChange(value);
}).open();
}}
Expand Down
Loading
Loading