From ccb9b7e5fb5bbddcce42a3144687177923fe8b7d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 25 Jun 2025 16:18:34 +0300 Subject: [PATCH] feat(book/table): store hidden columns --- .../src/widgets/view_widgets/calendar_view.ts | 6 +-- .../widgets/view_widgets/list_or_grid_view.ts | 11 ++--- .../src/widgets/view_widgets/table_view.ts | 7 +-- .../view_widgets/table_view/renderer.ts | 23 +++++++--- .../view_widgets/table_view/storage.ts | 6 +++ .../src/widgets/view_widgets/view_mode.ts | 22 +++++++++- .../widgets/view_widgets/view_mode_storage.ts | 43 +++++++++++++++++++ 7 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 apps/client/src/widgets/view_widgets/table_view/storage.ts create mode 100644 apps/client/src/widgets/view_widgets/view_mode_storage.ts diff --git a/apps/client/src/widgets/view_widgets/calendar_view.ts b/apps/client/src/widgets/view_widgets/calendar_view.ts index 51758fca3..4d6a32913 100644 --- a/apps/client/src/widgets/view_widgets/calendar_view.ts +++ b/apps/client/src/widgets/view_widgets/calendar_view.ts @@ -109,24 +109,22 @@ const CALENDAR_VIEWS = [ "listMonth" ] -export default class CalendarView extends ViewMode { +export default class CalendarView extends ViewMode<{}> { private $root: JQuery; private $calendarContainer: JQuery; private noteIds: string[]; - private parentNote: FNote; private calendar?: Calendar; private isCalendarRoot: boolean; private lastView?: string; private debouncedSaveView?: DebouncedFunction<() => void>; constructor(args: ViewModeArgs) { - super(args); + super(args, "calendar"); this.$root = $(TPL); this.$calendarContainer = this.$root.find(".calendar-container"); this.noteIds = args.noteIds; - this.parentNote = args.parentNote; this.isCalendarRoot = false; args.$parent.append(this.$root); } diff --git a/apps/client/src/widgets/view_widgets/list_or_grid_view.ts b/apps/client/src/widgets/view_widgets/list_or_grid_view.ts index 54b83b971..c8236b053 100644 --- a/apps/client/src/widgets/view_widgets/list_or_grid_view.ts +++ b/apps/client/src/widgets/view_widgets/list_or_grid_view.ts @@ -6,6 +6,7 @@ import treeService from "../../services/tree.js"; import utils from "../../services/utils.js"; import type FNote from "../../entities/fnote.js"; import ViewMode, { type ViewModeArgs } from "./view_mode.js"; +import type { ViewTypeOptions } from "../../services/note_list_renderer.js"; const TPL = /*html*/`
@@ -157,26 +158,22 @@ const TPL = /*html*/`
`; -class ListOrGridView extends ViewMode { +class ListOrGridView extends ViewMode<{}> { private $noteList: JQuery; - private parentNote: FNote; private noteIds: string[]; private page?: number; private pageSize?: number; - private viewType?: string | null; private showNotePath?: boolean; private highlightRegex?: RegExp | null; /* * We're using noteIds so that it's not necessary to load all notes at once when paging */ - constructor(viewType: string, args: ViewModeArgs) { - super(args); + constructor(viewType: ViewTypeOptions, args: ViewModeArgs) { + super(args, viewType); this.$noteList = $(TPL); - this.viewType = viewType; - this.parentNote = args.parentNote; const includedNoteIds = this.getIncludedNoteIds(); this.noteIds = args.noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden"); diff --git a/apps/client/src/widgets/view_widgets/table_view.ts b/apps/client/src/widgets/view_widgets/table_view.ts index 0505f09c7..05c4c7188 100644 --- a/apps/client/src/widgets/view_widgets/table_view.ts +++ b/apps/client/src/widgets/view_widgets/table_view.ts @@ -1,5 +1,6 @@ import froca from "../../services/froca.js"; import renderTable from "./table_view/renderer.js"; +import type { StateInfo } from "./table_view/storage.js"; import ViewMode, { ViewModeArgs } from "./view_mode"; const TPL = /*html*/` @@ -24,14 +25,14 @@ const TPL = /*html*/` `; -export default class TableView extends ViewMode { +export default class TableView extends ViewMode { private $root: JQuery; private $container: JQuery; private args: ViewModeArgs; constructor(args: ViewModeArgs) { - super(args); + super(args, "table"); this.$root = $(TPL); this.$container = this.$root.find(".table-view-container"); @@ -48,7 +49,7 @@ export default class TableView extends ViewMode { const notes = await froca.getNotes(noteIds); this.$container.empty(); - renderTable(this.$container[0], parentNote, notes); + renderTable(this.$container[0], parentNote, notes, this.viewStorage); return this.$root; } diff --git a/apps/client/src/widgets/view_widgets/table_view/renderer.ts b/apps/client/src/widgets/view_widgets/table_view/renderer.ts index dbf27f13c..18ed838a4 100644 --- a/apps/client/src/widgets/view_widgets/table_view/renderer.ts +++ b/apps/client/src/widgets/view_widgets/table_view/renderer.ts @@ -1,25 +1,35 @@ -import { createGrid, AllCommunityModule, ModuleRegistry, columnDropStyleBordered, GridOptions } from "ag-grid-community"; +import { createGrid, AllCommunityModule, ModuleRegistry, GridOptions } from "ag-grid-community"; import { buildData, type TableData } from "./data.js"; import FNote from "../../../entities/fnote.js"; -import getPromotedAttributeInformation, { PromotedAttributeInformation } from "./parser.js"; +import getPromotedAttributeInformation from "./parser.js"; import { setLabel } from "../../../services/attributes.js"; import applyHeaderCustomization from "./header-customization.js"; +import ViewModeStorage from "../view_mode_storage.js"; +import { type StateInfo } from "./storage.js"; ModuleRegistry.registerModules([ AllCommunityModule ]); -export default function renderTable(el: HTMLElement, parentNote: FNote, notes: FNote[]) { +export default async function renderTable(el: HTMLElement, parentNote: FNote, notes: FNote[], storage: ViewModeStorage) { const info = getPromotedAttributeInformation(parentNote); + const viewStorage = await storage.restore(); + const initialState = viewStorage?.gridState; createGrid(el, { ...buildData(info, notes), - ...setupEditing(info), - onGridReady(event) { + ...setupEditing(), + initialState, + async onGridReady(event) { applyHeaderCustomization(el, event.api); }, + onStateUpdated(event) { + storage.store({ + gridState: event.api.getState() + }); + } }); } -function setupEditing(info: PromotedAttributeInformation[]): GridOptions { +function setupEditing(): GridOptions { return { onCellValueChanged(event) { if (event.type !== "cellValueChanged") { @@ -37,3 +47,4 @@ function setupEditing(info: PromotedAttributeInformation[]): GridOptions; @@ -8,11 +10,18 @@ export interface ViewModeArgs { showNotePath?: boolean; } -export default abstract class ViewMode { +export default abstract class ViewMode { - constructor(args: ViewModeArgs) { + private _viewStorage: ViewModeStorage | null; + protected parentNote: FNote; + protected viewType: ViewTypeOptions; + + constructor(args: ViewModeArgs, viewType: ViewTypeOptions) { + this.parentNote = args.parentNote; + this._viewStorage = null; // note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work args.$parent.empty(); + this.viewType = viewType; } abstract renderList(): Promise | undefined>; @@ -32,4 +41,13 @@ export default abstract class ViewMode { return false; } + get viewStorage() { + if (this._viewStorage) { + return this._viewStorage; + } + + this._viewStorage = new ViewModeStorage(this.parentNote, this.viewType); + return this._viewStorage; + } + } diff --git a/apps/client/src/widgets/view_widgets/view_mode_storage.ts b/apps/client/src/widgets/view_widgets/view_mode_storage.ts new file mode 100644 index 000000000..750e9d9b0 --- /dev/null +++ b/apps/client/src/widgets/view_widgets/view_mode_storage.ts @@ -0,0 +1,43 @@ +import type FNote from "../../entities/fnote"; +import type { ViewTypeOptions } from "../../services/note_list_renderer"; +import server from "../../services/server"; + +const ATTACHMENT_ROLE = "viewConfig"; + +export default class ViewModeStorage { + + private note: FNote; + private attachmentName: string; + + constructor(note: FNote, viewType: ViewTypeOptions) { + this.note = note; + this.attachmentName = viewType + ".json"; + } + + async store(data: T) { + const payload = { + role: ATTACHMENT_ROLE, + title: this.attachmentName, + mime: "application/json", + content: JSON.stringify(data), + position: 0 + }; + await server.post(`notes/${this.note.noteId}/attachments?matchBy=title`, payload); + } + + async restore() { + const existingAttachments = await this.note.getAttachmentsByRole(ATTACHMENT_ROLE); + if (existingAttachments.length === 0) { + return undefined; + } + + const attachment = existingAttachments + .find(a => a.title === this.attachmentName); + if (!attachment) { + return undefined; + } + + const attachmentData = await server.get<{ content: string } | null>(`attachments/${attachment.attachmentId}/blob`); + return JSON.parse(attachmentData?.content ?? "{}"); + } +}