refactor(views/table): split column & rows into separate file

This commit is contained in:
Elian Doran 2025-07-04 21:18:52 +03:00
parent 28a755306a
commit af296a1e4e
No known key found for this signature in database
4 changed files with 186 additions and 195 deletions

View File

@ -0,0 +1,110 @@
import { RelationEditor } from "./relation_editor.js";
import { NoteFormatter, NoteTitleFormatter } from "./formatters.js";
import { applyHeaderMenu } from "./header-menu.js";
import type { ColumnDefinition } from "tabulator-tables";
import { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
type ColumnType = LabelType | "relation";
export interface PromotedAttributeInformation {
name: string;
title?: string;
type?: ColumnType;
}
const labelTypeMappings: Record<ColumnType, Partial<ColumnDefinition>> = {
text: {
editor: "input"
},
boolean: {
formatter: "tickCross",
editor: "tickCross"
},
date: {
editor: "date",
},
datetime: {
editor: "datetime"
},
number: {
editor: "number"
},
time: {
editor: "input"
},
url: {
formatter: "link",
editor: "input"
},
relation: {
editor: RelationEditor,
formatter: NoteFormatter
}
};
export function buildColumnDefinitions(info: PromotedAttributeInformation[], existingColumnData?: ColumnDefinition[]) {
const columnDefs: ColumnDefinition[] = [
{
title: "#",
formatter: "rownum",
headerSort: false,
hozAlign: "center",
resizable: false,
frozen: true
},
{
field: "noteId",
title: "Note ID",
visible: false
},
{
field: "title",
title: "Title",
editor: "input",
formatter: NoteTitleFormatter,
width: 400
}
];
const seenFields = new Set<string>();
for (const { name, title, type } of info) {
const prefix = (type === "relation" ? "relations" : "labels");
const field = `${prefix}.${name}`;
if (seenFields.has(field)) {
continue;
}
columnDefs.push({
field,
title: title ?? name,
editor: "input",
...labelTypeMappings[type ?? "text"],
});
seenFields.add(field);
}
applyHeaderMenu(columnDefs);
if (existingColumnData) {
restoreExistingData(columnDefs, existingColumnData);
}
return columnDefs;
}
function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[]) {
const byField = new Map<string, ColumnDefinition>;
for (const def of oldDefs) {
byField.set(def.field ?? "", def);
}
for (const newDef of newDefs) {
const oldDef = byField.get(newDef.field ?? "");
if (!oldDef) {
continue;
}
newDef.width = oldDef.width;
newDef.visible = oldDef.visible;
}
}

View File

@ -1,194 +0,0 @@
import FNote from "../../../entities/fnote.js";
import type { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
import type { ColumnDefinition } from "tabulator-tables";
import { RelationEditor } from "./relation_editor.js";
import { NoteFormatter, NoteTitleFormatter } from "./formatters.js";
import { applyHeaderMenu } from "./header-menu.js";
export type TableData = {
iconClass: string;
noteId: string;
title: string;
labels: Record<string, boolean | string | null>;
relations: Record<string, boolean | string | null>;
branchId: string;
};
type ColumnType = LabelType | "relation";
export interface PromotedAttributeInformation {
name: string;
title?: string;
type?: ColumnType;
}
const labelTypeMappings: Record<ColumnType, Partial<ColumnDefinition>> = {
text: {
editor: "input"
},
boolean: {
formatter: "tickCross",
editor: "tickCross"
},
date: {
editor: "date",
},
datetime: {
editor: "datetime"
},
number: {
editor: "number"
},
time: {
editor: "input"
},
url: {
formatter: "link",
editor: "input"
},
relation: {
editor: RelationEditor,
formatter: NoteFormatter
}
};
type GridLabelType = 'text' | 'number' | 'boolean' | 'date' | 'dateString' | 'object';
export async function buildData(parentNote: FNote, info: PromotedAttributeInformation[], notes: FNote[]) {
const columnDefs = buildColumnDefinitions(info);
const rowData = await buildRowDefinitions(parentNote, notes, info);
return {
rowData,
columnDefs
}
}
export function buildColumnDefinitions(info: PromotedAttributeInformation[], existingColumnData?: ColumnDefinition[]) {
const columnDefs: ColumnDefinition[] = [
{
title: "#",
formatter: "rownum",
headerSort: false,
hozAlign: "center",
resizable: false,
frozen: true
},
{
field: "noteId",
title: "Note ID",
visible: false
},
{
field: "title",
title: "Title",
editor: "input",
formatter: NoteTitleFormatter,
width: 400
}
];
const seenFields = new Set<string>();
for (const { name, title, type } of info) {
const prefix = (type === "relation" ? "relations" : "labels");
const field = `${prefix}.${name}`;
if (seenFields.has(field)) {
continue;
}
columnDefs.push({
field,
title: title ?? name,
editor: "input",
...labelTypeMappings[type ?? "text"],
});
seenFields.add(field);
}
applyHeaderMenu(columnDefs);
if (existingColumnData) {
restoreExistingData(columnDefs, existingColumnData);
}
return columnDefs;
}
function restoreExistingData(newDefs: ColumnDefinition[], oldDefs: ColumnDefinition[]) {
const byField = new Map<string, ColumnDefinition>;
for (const def of oldDefs) {
byField.set(def.field, def);
}
for (const newDef of newDefs) {
const oldDef = byField.get(newDef.field);
if (!oldDef) {
continue;
}
newDef.width = oldDef.width;
newDef.visible = oldDef.visible;
}
}
export async function buildRowDefinitions(parentNote: FNote, notes: FNote[], infos: PromotedAttributeInformation[]) {
const definitions: TableData[] = [];
for (const branch of parentNote.getChildBranches()) {
const note = await branch.getNote();
if (!note) {
continue; // Skip if the note is not found
}
const labels: typeof definitions[0]["labels"] = {};
const relations: typeof definitions[0]["relations"] = {};
for (const { name, type } of infos) {
if (type === "relation") {
relations[name] = note.getRelationValue(name);
} else if (type === "boolean") {
labels[name] = note.hasLabel(name);
} else {
labels[name] = note.getLabelValue(name);
}
}
definitions.push({
iconClass: note.getIcon(),
noteId: note.noteId,
title: note.title,
labels,
relations,
branchId: branch.branchId
});
}
return definitions;
}
export default function getPromotedAttributeInformation(parentNote: FNote) {
const info: PromotedAttributeInformation[] = [];
for (const promotedAttribute of parentNote.getPromotedDefinitionAttributes()) {
const def = promotedAttribute.getDefinition();
if (def.multiplicity !== "single") {
console.warn("Multiple values are not supported for now");
continue;
}
const [ labelType, name ] = promotedAttribute.name.split(":", 2);
if (promotedAttribute.type !== "label") {
console.warn("Relations are not supported for now");
continue;
}
let type: LabelType | "relation" = def.labelType || "text";
if (labelType === "relation") {
type = "relation";
}
info.push({
name,
title: def.promotedAlias,
type
});
}
console.log("Promoted attribute information", info);
return info;
}

View File

@ -1,7 +1,6 @@
import froca from "../../../services/froca.js"; import froca from "../../../services/froca.js";
import ViewMode, { type ViewModeArgs } from "../view_mode.js"; import ViewMode, { type ViewModeArgs } from "../view_mode.js";
import attributes, { setAttribute, setLabel } from "../../../services/attributes.js"; import attributes, { setAttribute, setLabel } from "../../../services/attributes.js";
import getPromotedAttributeInformation, { buildColumnDefinitions, buildData, buildRowDefinitions, TableData } from "./data.js";
import server from "../../../services/server.js"; import server from "../../../services/server.js";
import SpacedUpdate from "../../../services/spaced_update.js"; import SpacedUpdate from "../../../services/spaced_update.js";
import type { CommandListenerData, EventData } from "../../../components/app_context.js"; import type { CommandListenerData, EventData } from "../../../components/app_context.js";
@ -11,6 +10,8 @@ import {Tabulator, SortModule, FormatModule, InteractionModule, EditModule, Resi
import "tabulator-tables/dist/css/tabulator_bootstrap5.min.css"; import "tabulator-tables/dist/css/tabulator_bootstrap5.min.css";
import { canReorderRows, configureReorderingRows } from "./dragging.js"; import { canReorderRows, configureReorderingRows } from "./dragging.js";
import buildFooter from "./footer.js"; import buildFooter from "./footer.js";
import getPromotedAttributeInformation, { buildRowDefinitions } from "./rows.js";
import { buildColumnDefinitions } from "./columns.js";
const TPL = /*html*/` const TPL = /*html*/`
<div class="table-view"> <div class="table-view">

View File

@ -0,0 +1,74 @@
import FNote from "../../../entities/fnote.js";
import type { LabelType } from "../../../services/promoted_attribute_definition_parser.js";
import type { PromotedAttributeInformation } from "./columns.js";
export type TableData = {
iconClass: string;
noteId: string;
title: string;
labels: Record<string, boolean | string | null>;
relations: Record<string, boolean | string | null>;
branchId: string;
};
export async function buildRowDefinitions(parentNote: FNote, notes: FNote[], infos: PromotedAttributeInformation[]) {
const definitions: TableData[] = [];
for (const branch of parentNote.getChildBranches()) {
const note = await branch.getNote();
if (!note) {
continue; // Skip if the note is not found
}
const labels: typeof definitions[0]["labels"] = {};
const relations: typeof definitions[0]["relations"] = {};
for (const { name, type } of infos) {
if (type === "relation") {
relations[name] = note.getRelationValue(name);
} else if (type === "boolean") {
labels[name] = note.hasLabel(name);
} else {
labels[name] = note.getLabelValue(name);
}
}
definitions.push({
iconClass: note.getIcon(),
noteId: note.noteId,
title: note.title,
labels,
relations,
branchId: branch.branchId
});
}
return definitions;
}
export default function getPromotedAttributeInformation(parentNote: FNote) {
const info: PromotedAttributeInformation[] = [];
for (const promotedAttribute of parentNote.getPromotedDefinitionAttributes()) {
const def = promotedAttribute.getDefinition();
if (def.multiplicity !== "single") {
console.warn("Multiple values are not supported for now");
continue;
}
const [ labelType, name ] = promotedAttribute.name.split(":", 2);
if (promotedAttribute.type !== "label") {
console.warn("Relations are not supported for now");
continue;
}
let type: LabelType | "relation" = def.labelType || "text";
if (labelType === "relation") {
type = "relation";
}
info.push({
name,
title: def.promotedAlias,
type
});
}
console.log("Promoted attribute information", info);
return info;
}