From 665f5009258adee92643c40f8a65a42b2f23541b Mon Sep 17 00:00:00 2001 From: JustAdumbPrsn <73780892+JustAdumbPrsn@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:28:03 +0530 Subject: [PATCH] feat: Implement Zen Library data backend (#12701) --- src/zen/library/ZenLibrarySections.mjs | 169 +++++++++++++++++++++---- 1 file changed, 142 insertions(+), 27 deletions(-) diff --git a/src/zen/library/ZenLibrarySections.mjs b/src/zen/library/ZenLibrarySections.mjs index 2b6b56099..b33336a86 100644 --- a/src/zen/library/ZenLibrarySections.mjs +++ b/src/zen/library/ZenLibrarySections.mjs @@ -7,32 +7,38 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; let lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + DownloadHistory: "resource://gre/modules/DownloadHistory.sys.mjs", + DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs", + PlacesQuery: "resource://gre/modules/PlacesQuery.sys.mjs", +}); + ChromeUtils.defineLazyGetter(lazy, "l10n", function () { - return new Localization( - ["browser/zen-library.ftl"], - true - ); + return new Localization(["browser/zen-library.ftl"], true); }); class LibrarySection extends MozLitElement { static largeContent = false; static get id() { - throw new Error("Unimplemented"); + throw new Error("LibrarySection subclass must define a static `id`."); } static get label() { - throw new Error("Unimplemented"); + throw new Error("LibrarySection subclass must define a static `label`."); } } class SearchSection extends LibrarySection { static properties = { searchTerm: { type: String }, + isEmpty: { type: Boolean }, }; connectedCallback() { this.searchTerm = ""; + this.isEmpty = false; super.connectedCallback(); } @@ -42,12 +48,15 @@ class SearchSection extends LibrarySection { } renderSearchResults() { - return html`${this.searchTerm}`; + return html``; } render() { return html` - + -
- ${this.renderSearchResults()} -
+
${this.renderSearchResults()}
`; } } +// History section +class ZenLibraryHistorySection extends SearchSection { + static id = "history"; + static label = "library-history-section-title"; + + #placesQuery = null; + #history = null; + + renderSearchResults() { + if (this.isEmpty) { + return html`
+ ${lazy.l10n.formatValueSync("library-history-empty")} +
`; + } + return html`${this.searchTerm}`; + } + + async connectedCallback() { + super.connectedCallback(); + try { + this.#placesQuery = new lazy.PlacesQuery(); + this.#history = await this.#placesQuery.getHistory(); + this.isEmpty = this.#history.size === 0; + this.#placesQuery.observeHistory((newHistory) => { + this.#history = newHistory; + this.isEmpty = this.#history.size === 0; + this.requestUpdate(); + }); + this.requestUpdate(); + } catch (ex) { + console.error("Zen Library: Failed to initialize history section.", ex); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.#placesQuery?.close(); + this.#placesQuery = null; + this.#history = null; + } +} + +// Downloads section +class ZenLibraryDownloadsSection extends SearchSection { + static id = "downloads"; + static label = "library-downloads-section-title"; + + #downloadList = null; + #downloads = []; + #downloadsView = null; + #batchLoading = false; + + renderSearchResults() { + if (this.isEmpty) { + return html`
+ ${lazy.l10n.formatValueSync("library-downloads-empty")} +
`; + } + return html`${this.searchTerm}`; + } + + async connectedCallback() { + super.connectedCallback(); + try { + this.#downloadList = await lazy.DownloadHistory.getList({ + type: lazy.Downloads.PUBLIC, + }); + this.#downloadsView = { + onDownloadAdded: (dl) => { + // During the initial batch replay addView fires onDownloadAdded for + // every existing download oldest-first, so we push to preserve order. + // After init, new downloads are unshifted to the front. + if (this.#batchLoading) { + this.#downloads.push(dl); + } else { + this.#downloads.unshift(dl); + this.isEmpty = false; + this.requestUpdate(); + } + }, + onDownloadBatchEnded: () => { + this.#batchLoading = false; + this.isEmpty = this.#downloads.length === 0; + this.requestUpdate(); + }, + onDownloadChanged: (dl) => { + this.requestUpdate(); + }, + onDownloadRemoved: (dl) => { + this.#downloads = this.#downloads.filter((d) => d !== dl); + this.isEmpty = this.#downloads.length === 0; + this.requestUpdate(); + }, + }; + this.#batchLoading = true; + this.#downloadList.addView(this.#downloadsView); + } catch (ex) { + console.error("Zen Library: Failed to initialize downloads section.", ex); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.#downloadList && this.#downloadsView) { + this.#downloadList.removeView(this.#downloadsView); + } + this.#downloadList = null; + this.#downloadsView = null; + this.#downloads = []; + } +} + +// Spaces section +class ZenLibrarySpacesSection extends LibrarySection { + static largeContent = true; + static id = "spaces"; + static label = "library-spaces-section-title"; +} + export const ZenLibrarySections = { - history: class extends SearchSection { - static id = "history"; - static label = "library-history-section-title"; - }, - downloads: class extends SearchSection { - static id = "downloads"; - static label = "library-downloads-section-title"; - }, - spaces: class extends LibrarySection { - static largeContent = true; - static id = "spaces"; - static label = "library-spaces-section-title"; - }, + history: ZenLibraryHistorySection, + downloads: ZenLibraryDownloadsSection, + spaces: ZenLibrarySpacesSection, }; -for (const section of Object.values(ZenLibrarySections)) { - customElements.define(`zen-library-section-${section.id}`, section) - ; +for (const Section of Object.values(ZenLibrarySections)) { + customElements.define(`zen-library-section-${Section.id}`, Section); }