Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 29f55a0

Browse files
committed
Merge branch 'footer-menu'
2 parents 80d0435 + 6276930 commit 29f55a0

26 files changed

+586
-242
lines changed

docs/docs/changelog.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 3.0.1
2+
### Shiny new things
3+
- (experimental) new options for the footer including a menu to select those options:
4+
- Percentage of empty cells
5+
- Percentage of cells with a value
6+
- Count of empty cells
7+
- Count of cells with a value
8+
- Count of unique values
9+
- Formula
10+
- Sum of number columns
11+
### No longer broken
12+
- message when dv is not loaded not spammed anymore [ISSUE#642](https://github.com/RafaelGB/obsidian-db-folder/issues/642)
13+
- Correct tooltrip for export button [ISSUE#641](https://github.com/RafaelGB/obsidian-db-folder/issues/641)
114
## 3.0.0
215
### Shiny new things
316
- A new design for the plugin [ISSUE#577](https://github.com/RafaelGB/obsidian-db-folder/issues/577)

manifest-beta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "dbfolder",
33
"name": "DB Folder",
4-
"version": "3.0.0",
4+
"version": "3.0.1",
55
"minAppVersion": "0.16.3",
66
"description": "Folder with the capability to store and retrieve data from a folder like database",
77
"author": "RafaelGB",

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "dbfolder",
33
"name": "DB Folder",
4-
"version": "3.0.0",
4+
"version": "3.0.1",
55
"minAppVersion": "0.16.3",
66
"description": "Folder with the capability to store and retrieve data from a folder like database",
77
"author": "RafaelGB",

package-lock.json

Lines changed: 171 additions & 171 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-dbfolder",
3-
"version": "3.0.0",
3+
"version": "3.0.1",
44
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
55
"main": "main.js",
66
"scripts": {
@@ -23,25 +23,25 @@
2323
"@rollup/plugin-json": "5.0.2",
2424
"@rollup/plugin-node-resolve": "15.0.1",
2525
"@rollup/plugin-replace": "5.0.1",
26-
"@rollup/plugin-typescript": "10.0.0",
26+
"@rollup/plugin-typescript": "10.0.1",
2727
"@rollup/plugin-terser": "0.1.0",
2828
"@testing-library/jest-dom": "5.16.5",
2929
"@testing-library/react": "13.4.0",
30-
"@types/jest": "29.2.3",
30+
"@types/jest": "29.2.4",
3131
"@types/luxon": "3.1.0",
32-
"@types/node": "18.11.9",
33-
"@types/react": "18.0.25",
32+
"@types/node": "18.11.11",
33+
"@types/react": "18.0.26",
3434
"@types/react-csv": "1.1.3",
3535
"@types/react-datepicker": "4.8.0",
3636
"@types/react-dom": "18.0.9",
3737
"@types/react-window": "1.8.5",
38-
"@typescript-eslint/eslint-plugin": "5.45.0",
39-
"@typescript-eslint/parser": "5.45.0",
40-
"eslint": "8.28.0",
38+
"@typescript-eslint/eslint-plugin": "5.45.1",
39+
"@typescript-eslint/parser": "5.45.1",
40+
"eslint": "8.29.0",
4141
"jest": "29.3.1",
4242
"jest-mock-extended": "3.0.1",
4343
"obsidian": "0.16.3",
44-
"rollup": "3.5.0",
44+
"rollup": "3.6.0",
4545
"rollup-plugin-typescript2": "0.34.1",
4646
"ts-jest": "29.0.3",
4747
"tslib": "2.4.1",
@@ -50,7 +50,7 @@
5050
"dependencies": {
5151
"@emotion/styled": "11.10.5",
5252
"@mui/icons-material": "5.10.16",
53-
"@mui/material": "5.10.16",
53+
"@mui/material": "5.10.17",
5454
"@popperjs/core": "2.11.6",
5555
"@tanstack/match-sorter-utils": "8.7.0",
5656
"@tanstack/react-table": "8.7.0",
@@ -64,8 +64,8 @@
6464
"react-csv": "2.2.2",
6565
"react-datepicker": "4.8.0",
6666
"react-dom": "18.2.0",
67-
"react-select": "5.6.1",
67+
"react-select": "5.7.0",
6868
"react-window": "1.8.8",
69-
"zustand": "4.1.4"
69+
"zustand": "4.1.5"
7070
}
7171
}

src/DatabaseView.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ import {
3535
WorkspaceLeaf,
3636
TFile,
3737
Menu,
38+
Notice,
3839
} from "obsidian";
3940
import { createRoot, Root } from "react-dom/client";
4041
import DatabaseInfo from "services/DatabaseInfo";
42+
import { DataviewService } from "services/DataviewService";
4143
import { LOGGER } from "services/Logger";
4244
import { SettingsModal } from "Settings";
4345
import StateManager from "StateManager";
@@ -145,6 +147,7 @@ export class DatabaseView extends TextFileView implements HoverParent {
145147
async initDatabase(): Promise<void> {
146148
try {
147149
LOGGER.info(`=>initDatabase ${this.file.path}`);
150+
this.checkRequiredLibraries();
148151
// Load the database file
149152
this.diskConfig = new DatabaseInfo(this.file);
150153
await this.diskConfig.initDatabaseconfigYaml(
@@ -347,4 +350,15 @@ export class DatabaseView extends TextFileView implements HoverParent {
347350
openFilters() {
348351
this.emitter.emit(EMITTERS_GROUPS.SHORTCUT, EMITTERS_SHORTCUT.OPEN_FILTERS);
349352
}
353+
/****************************************************************
354+
* VIEW VALIDATIONS
355+
* **************************************************************/
356+
private checkRequiredLibraries(): void {
357+
if (!DataviewService.indexIsLoaded) {
358+
new Notice(
359+
`Dataview plugin is not loaded yet. Please wait a few seconds and refresh the page.`,
360+
1000
361+
);
362+
}
363+
}
350364
}

src/automations/Footer.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,92 @@
11
import { Row } from "@tanstack/react-table";
22
import { RowDataType } from "cdm/FolderModel";
3+
import { FooterType } from "helpers/Constants";
34

45
export default class Footer {
56
constructor(public readonly rows: Row<RowDataType>[]) { }
67

8+
public dispatch(footerType: string, key: string): string {
9+
let footerInfo: string;
10+
try {
11+
switch (footerType) {
12+
case FooterType.COUNT_UNIQUE:
13+
footerInfo = this.countUnique(key);
14+
break;
15+
case FooterType.COUNT_EMPTY:
16+
footerInfo = this.countEmpty(key);
17+
break;
18+
case FooterType.PERCENT_EMPTY:
19+
footerInfo = this.percentEmpty(key);
20+
break;
21+
case FooterType.COUNT_FILLED:
22+
footerInfo = this.countFilled(key);
23+
break;
24+
case FooterType.PERCENT_FILLED:
25+
footerInfo = this.percentFilled(key);
26+
break;
27+
case FooterType.SUM:
28+
footerInfo = this.sum(key);
29+
break;
30+
case FooterType.MIN:
31+
footerInfo = this.min(key);
32+
break;
33+
case FooterType.MAX:
34+
footerInfo = this.max(key);
35+
break;
36+
case FooterType.NONE:
37+
default:
38+
footerInfo = "";
39+
}
40+
} catch (e) {
41+
footerInfo = `Error: ${e.message}`;
42+
} finally {
43+
return footerInfo;
44+
}
45+
}
46+
747
public sum(key: string): string {
8-
const total = this.rows.reduce((acc, row) => acc + row.getValue<number>(key), 0);
48+
const total = this.rows.reduce((acc, row) => acc + Number(row.getValue<number>(key)), 0);
949
return `Total: ${total}`;
1050
}
51+
52+
public min(key: string): string {
53+
const min = this.rows.reduce((acc, row) => Math.min(acc, row.getValue<number>(key)), Number.MAX_SAFE_INTEGER);
54+
return `Min: ${min}`;
55+
}
56+
57+
public max(key: string): string {
58+
const max = this.rows.reduce((acc, row) => Math.max(acc, row.getValue<number>(key)), Number.MIN_SAFE_INTEGER);
59+
return `Max: ${max}`;
60+
}
61+
62+
public countUnique(key: string): string {
63+
const uniqueValues = new Set();
64+
this.rows
65+
.filter((row) => row.getValue(key) !== undefined)
66+
.forEach((row) => {
67+
uniqueValues.add(row.getValue(key));
68+
});
69+
return `Unique: ${uniqueValues.size}`;
70+
}
71+
72+
public countEmpty(key: string): string {
73+
const empty = this.rows.filter((row) => !row.getValue(key)).length;
74+
return `Empty: ${empty}`;
75+
}
76+
77+
public percentEmpty(key: string): string {
78+
const empty = this.rows.filter((row) => !row.getValue(key)).length;
79+
return `Empty: ${(empty / this.rows.length * 100).toFixed(2)}%`;
80+
}
81+
82+
public countFilled(key: string): string {
83+
const filled = this.rows.filter((row) => row.getValue(key)).length;
84+
return `Filled: ${filled}`;
85+
}
86+
87+
public percentFilled(key: string): string {
88+
const filled = this.rows.filter((row) => row.getValue(key)).length;
89+
return `Filled: ${(filled / this.rows.length * 100).toFixed(2)}%`;
90+
}
91+
1192
}

src/cdm/FolderModel.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export interface ConfigColumn {
5757
rollup_action?: string;
5858
rollup_key?: string;
5959
persist_rollup?: boolean;
60+
// Footer
61+
footer_type: string;
62+
footer_formula?: string;
6063
/** Extras from yaml */
6164
[key: string]: Literal;
6265
}

src/cdm/TableStateInterface.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SortingState } from "@tanstack/react-table";
1+
import { Row, SortingState } from "@tanstack/react-table";
22
import { ConfigColumn, RowDataType, TableColumn } from "cdm/FolderModel";
33
import { FilterSettings, GlobalSettings, LocalSettings } from "cdm/SettingsModel";
44
import { DatabaseView } from "DatabaseView";
@@ -74,6 +74,7 @@ export interface ColumnsState {
7474
alterColumnLabel: (column: TableColumn, label: string) => Promise<void>;
7575
alterColumnSize: (id: string, width: number) => void;
7676
alterIsHidden: (column: TableColumn, isHidden: boolean) => void;
77+
alterColumnConfig: (column: TableColumn, config: Partial<ConfigColumn>) => void;
7778
}
7879
info: {
7980
getAllColumns: () => TableColumn[];
@@ -100,7 +101,8 @@ export interface AutomationState {
100101
info: {
101102
getFormula: (name: string) => unknown;
102103
runFormula: (input: string, row: RowDataType, dbbConfig: LocalSettings) => Literal;
103-
dispatchRollup: (configColumn: ConfigColumn, relation: Literal, dbbConfig: LocalSettings) => Literal;
104+
dispatchFooter: (column: TableColumn, rows: Row<RowDataType>[]) => Literal;
105+
dispatchRollup: (configColumn: ConfigColumn, relation: Literal, ddbbConfig: LocalSettings) => Literal;
104106
},
105107
actions: {
106108
loadFormulas: (ddbbConfig: LocalSettings) => Promise<void>;

src/components/DefaultFooter.tsx

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,56 @@
11
import Footer from "automations/Footer";
22
import { DatabaseHeaderProps, TableColumn } from "cdm/FolderModel";
3-
import { InputType } from "helpers/Constants";
4-
import { c, getAlignmentClassname } from "helpers/StylesHelper";
5-
import React from "react";
3+
import { COLUMN_ALIGNMENT_OPTIONS, FooterType } from "helpers/Constants";
4+
import { c } from "helpers/StylesHelper";
5+
import React, { MouseEventHandler, useState } from "react";
6+
import { showFooterMenu } from "components/obsidianArq/commands";
7+
import { Literal } from "obsidian-dataview";
68

79
export default function DefaultFooter(headerProps: DatabaseHeaderProps) {
810
/** Properties of footer */
911
const { header, table } = headerProps;
1012
const { tableState } = table.options.meta;
1113
const configInfo = tableState.configState((state) => state.info);
14+
const columnActions = tableState.columns((state) => state.actions);
15+
1216
/** Column values */
13-
const { input, config } = header.column.columnDef as TableColumn;
14-
let footerInfo: string;
15-
switch (input) {
16-
case InputType.NUMBER:
17-
footerInfo = new Footer(table.getRowModel().rows).sum(header.id);
18-
break;
19-
default:
20-
return null;
21-
// Do nothing
17+
const tableColumn = header.column.columnDef as TableColumn;
18+
const formulaInfo = tableState.automations((state) => state.info);
19+
const [footerType, setFooterValue] = useState(tableColumn.config.footer_type);
20+
const { config } = tableColumn;
21+
let footerInfo: Literal = "";
22+
if (config.footer_type === FooterType.FORMULA) {
23+
footerInfo = formulaInfo.dispatchFooter(
24+
tableColumn,
25+
table.getRowModel().rows
26+
);
27+
} else {
28+
footerInfo = new Footer(table.getRowModel().rows).dispatch(
29+
footerType,
30+
header.id
31+
);
2232
}
33+
const handlerFooterOptions: MouseEventHandler<HTMLDivElement> = async (
34+
event: React.MouseEvent
35+
) => {
36+
await showFooterMenu(
37+
event.nativeEvent,
38+
tableColumn,
39+
columnActions,
40+
footerType,
41+
setFooterValue
42+
);
43+
};
2344

2445
return (
25-
<span
26-
key={`foot-th-cell-${header.id}`}
27-
className={c(
28-
getAlignmentClassname(config, configInfo.getLocalSettings())
29-
)}
46+
<div
47+
key={`default-footer-${header.id}`}
48+
onClick={handlerFooterOptions}
49+
style={{
50+
minHeight: "20px",
51+
}}
3052
>
31-
{footerInfo}
32-
</span>
53+
{footerInfo?.toString()}
54+
</div>
3355
);
3456
}

0 commit comments

Comments
 (0)