no-bug: Small touches

This commit is contained in:
mr. m
2026-03-28 14:28:12 +01:00
parent 5963de1486
commit a56bcf3d9c
5 changed files with 164 additions and 125 deletions

View File

@@ -6,9 +6,12 @@ library-spaces-section-title = Spaces
library-downloads-section-title = Downloads
library-history-section-title = History
library-search-placeholder = Search…
library-history-search-placeholder = Search History
library-downloads-search-placeholder = Search Downloads…
library-search-placeholder =
.placeholder = Search…
library-history-search-placeholder =
.placeholder = Search History…
library-downloads-search-placeholder =
.placeholder = Search Downloads…
library-history-today = Today
library-history-yesterday = Yesterday

View File

@@ -8,7 +8,11 @@ export const ZenCustomizableUI = new (class {
constructor() {}
TYPE_TOOLBAR = "toolbar";
defaultSidebarIcons = ["zen-library-button", "zen-workspaces-button", "zen-create-new-button"];
defaultSidebarIcons = [
"zen-library-button",
"zen-workspaces-button",
"zen-create-new-button",
];
startup(CustomizableUIInternal) {
CustomizableUIInternal.registerArea(

View File

@@ -8,15 +8,16 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
let lazy = {};
let gZenLibraryInstance = null;
ChromeUtils.defineESModuleGetters(lazy, {
ZenLibrarySections: "moz-src:///zen/library/ZenLibrarySections.mjs",
}, { global: "current" });
ChromeUtils.defineESModuleGetters(
lazy,
{
ZenLibrarySections: "moz-src:///zen/library/ZenLibrarySections.mjs",
},
{ global: "current" }
);
ChromeUtils.defineLazyGetter(lazy, "l10n", function () {
return new Localization(
["browser/zen-library.ftl"],
true
);
return new Localization(["browser/zen-library.ftl"], true);
});
ChromeUtils.defineLazyGetter(lazy, "appContentWrapper", function () {
@@ -58,14 +59,17 @@ export class ZenLibrary extends MozLitElement {
this.#resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(() => {
let isRightSide = gZenVerticalTabsManager._prefsRightSide;
let translateX = window.windowUtils.getBoundsWithoutFlushing(this)[
isRightSide ? "left" : "right"
];
let contentPosition = window.windowUtils.getBoundsWithoutFlushing(lazy.appContentWrapper)[
isRightSide ? "right" : "left"
]
let existingTransform = new DOMMatrix(lazy.appContentWrapper.style.transform).m41;
translateX = translateX-contentPosition + existingTransform;
let translateX =
window.windowUtils.getBoundsWithoutFlushing(this)[
isRightSide ? "left" : "right"
];
let contentPosition = window.windowUtils.getBoundsWithoutFlushing(
lazy.appContentWrapper
)[isRightSide ? "right" : "left"];
let existingTransform = new DOMMatrix(
lazy.appContentWrapper.style.transform
).m41;
translateX = translateX - contentPosition + existingTransform;
if (isRightSide) {
translateX = -translateX;
}
@@ -96,7 +100,10 @@ export class ZenLibrary extends MozLitElement {
render() {
return html`
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-library.css" />
<link
rel="stylesheet"
href="chrome://browser/content/zen-styles/zen-library.css"
/>
<vbox id="zen-library-sidebar">
<vbox id="zen-library-sidebar-header"></vbox>
<vbox id="zen-library-sidebar-tabs">
@@ -105,7 +112,7 @@ export class ZenLibrary extends MozLitElement {
<vbox
class="zen-library-tab"
?active=${this.activeTab === Section.id}
@click=${() => this.activeTab = Section.id}
@click=${() => (this.activeTab = Section.id)}
>
<label>${lazy.l10n.formatValueSync(Section.label)}</label>
</vbox>
@@ -114,7 +121,11 @@ export class ZenLibrary extends MozLitElement {
</vbox>
<vbox id="zen-library-sidebar-footer"></vbox>
</vbox>
<vbox id="zen-library-content" flex="1" ?large-content=${lazy.ZenLibrarySections[this.activeTab].largeContent}>
<vbox
id="zen-library-content"
flex="1"
?large-content=${lazy.ZenLibrarySections[this.activeTab].largeContent}
>
${this.#sections.find(section => section.constructor.id === this.activeTab)}
</vbox>
`;
@@ -152,16 +163,19 @@ export class ZenLibrary extends MozLitElement {
}
static toggle() {
window.docShell.treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIAppWindow).rollupAllPopups();
window.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.rollupAllPopups();
let instance = this.getInstance();
instance.toggleAttribute("open");
if (!instance.isOpen) {
gNavToolbox.removeAttribute("zen-library-open");
lazy.appContentWrapper.style.transform = "";
if (!instance._deletionIdleCallbackId) {
instance._deletionIdleCallbackId = requestIdleCallback(() => {
this.clearInstance();
});
instance._deletionIdleCallbackId = requestIdleCallback(() => {
this.clearInstance();
});
}
} else {
if (instance._deletionIdleCallbackId) {

View File

@@ -21,7 +21,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
ChromeUtils.defineLazyGetter(
lazy,
"l10n",
() => new Localization(["browser/zen-library.ftl"], true),
() => new Localization(["browser/zen-library.ftl"], true)
);
const SEARCH_DEBOUNCE_MS = 300;
@@ -52,8 +52,8 @@ class LibrarySection extends MozLitElement {
throw new Error("LibrarySection subclass must define a static `label`.");
}
_l10n(key, fallback) {
return lazy.l10n.formatValueSync(key) || fallback;
_l10n(key) {
return lazy.l10n.formatValueSync(key);
}
}
@@ -66,6 +66,7 @@ class SearchSection extends LibrarySection {
searchTerm: { type: String },
inputValue: { type: String },
isEmpty: { type: Boolean },
isScrolledToTop: { type: Boolean },
};
#canDebug = false;
@@ -79,6 +80,7 @@ class SearchSection extends LibrarySection {
if (this.inputValue === undefined) {
this.inputValue = this.searchTerm;
}
this.isScrolledToTop = true;
this.isEmpty = false;
super.connectedCallback();
@@ -86,7 +88,7 @@ class SearchSection extends LibrarySection {
this,
"#canDebug",
"zen.library.debug",
false,
false
);
}
@@ -131,10 +133,10 @@ class SearchSection extends LibrarySection {
const dayStart = new Date(d).setHours(0, 0, 0, 0);
const diffDays = Math.round((todayStart - dayStart) / 86_400_000);
if (diffDays === 0) {
return this._l10n("library-history-today", "Today");
return this._l10n("library-history-today");
}
if (diffDays === 1) {
return this._l10n("library-history-yesterday", "Yesterday");
return this._l10n("library-history-yesterday");
}
if (diffDays < 7) {
return WEEKDAY_FORMATTER.format(d);
@@ -143,7 +145,7 @@ class SearchSection extends LibrarySection {
}
get _searchPlaceholder() {
return this._l10n("library-search-placeholder", "Search…");
return "library-search-placeholder";
}
_formatTime(date) {
@@ -164,6 +166,7 @@ class SearchSection extends LibrarySection {
rel="stylesheet"
href="chrome://browser/content/zen-styles/zen-library.css"
/>
<link rel="localization" href="browser/zen-library.ftl" />
<div class="search-bar">
<div class="search-urlbar">
<div class="search-urlbar-background"></div>
@@ -179,7 +182,7 @@ class SearchSection extends LibrarySection {
type="text"
inputmode="search"
aria-autocomplete="list"
placeholder=${this._searchPlaceholder}
data-l10n-id=${this._searchPlaceholder}
@input=${this._onSearchInput}
.value=${this.inputValue}
/>
@@ -194,10 +197,22 @@ class SearchSection extends LibrarySection {
</div>
<button class="search-filter-button">
<img src="chrome://browser/skin/zen-icons/search-glass.svg" alt="" />
<span>${this._l10n("library-filter-button", "Filter")}</span>
<label data-l10n-id="library-filter-button"></label>
</button>
</div>
<div class="search-results">${this.renderSearchResults()}</div>
<div
class="search-results"
@scroll=${() => {
const el = this.renderRoot.querySelector(".search-results");
if (!el) {
return;
}
this.isScrolledToTop = el.scrollTop === 0;
}}
?scrolled-to-top=${this.isScrolledToTop}
>
${this.renderSearchResults()}
</div>
`;
}
}
@@ -229,7 +244,7 @@ class ProgressiveSearchSection extends SearchSection {
_maintainProgressiveRender() {
this.#renderedItemCount = Math.max(
this.#renderedItemCount,
Math.min(50, this._allItems.length),
Math.min(50, this._allItems.length)
);
this.isEmpty = this._allItems.length === 0;
this.#scheduleNextBatch();
@@ -243,7 +258,7 @@ class ProgressiveSearchSection extends SearchSection {
this.#renderTask = null;
this.#renderedItemCount = Math.min(
this.#renderedItemCount + 100,
this._allItems.length,
this._allItems.length
);
this.requestUpdate();
this.#scheduleNextBatch();
@@ -286,7 +301,7 @@ class ZenLibraryHistorySection extends ProgressiveSearchSection {
#rebuildTimer = null;
get _searchPlaceholder() {
return this._l10n("library-history-search-placeholder", "Search History…");
return "library-history-search-placeholder";
}
async connectedCallback() {
@@ -324,6 +339,7 @@ class ZenLibraryHistorySection extends ProgressiveSearchSection {
this.#teardownResult();
const NHQO = Ci.nsINavHistoryQueryOptions;
// eslint-disable-next-line no-shadow
const history = lazy.PlacesUtils.history;
const query = history.getNewQuery();
const options = history.getNewQueryOptions();
@@ -383,7 +399,7 @@ class ZenLibraryHistorySection extends ProgressiveSearchSection {
}
this.log(
`#walkAndPopulate — ${this._allItems.length} items, search=${!!this.searchTerm}`,
`#walkAndPopulate — ${this._allItems.length} items, search=${!!this.searchTerm}`
);
} finally {
this.#walking = false;
@@ -458,17 +474,16 @@ class ZenLibraryHistorySection extends ProgressiveSearchSection {
}
if (this.isEmpty) {
return html`
<div class="empty-state">
${this.searchTerm
? this._l10n("library-search-no-results", "No results")
: this._l10n("library-history-empty", "No history found")}
</div>
<div
class="empty-state"
data-l10n-id=${this.searchTerm ? "library-search-no-results" : "library-history-empty"}
></div>
`;
}
const slice = this._getRenderedSlice();
this.log(
`renderSearchResults — ${slice.length}/${this._allItems.length} items`,
`renderSearchResults — ${slice.length}/${this._allItems.length} items`
);
const rows = [];
@@ -479,9 +494,7 @@ class ZenLibraryHistorySection extends ProgressiveSearchSection {
if (dayLabel !== lastLabel) {
lastLabel = dayLabel;
rows.push(html`
<div class="library-date-separator">
<span>${lastLabel}</span>
</div>
<label class="library-date-separator"> ${lastLabel} </label>
`);
}
@@ -499,10 +512,16 @@ class ZenLibraryHistorySection extends ProgressiveSearchSection {
/>
</div>
<div class="library-item-label-container">
<span class="library-item-label">${v.title || v.url}</span>
<span class="library-item-sublabel">${v.url}</span>
<label class="library-item-label">${v.title || v.url}</label>
<label class="library-item-sublabel"
>${
v.url?.replace(/^[\w-]+:\/\//, "").replace(/\/$/, "") || ""
}</label
>
</div>
<span class="library-item-time">${this._formatTime(v.date)}</span>
<label class="library-item-time"
>${this._formatTime(v.date)}</label
>
</div>
</div>
</div>
@@ -527,10 +546,7 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
#batchLoading = false;
get _searchPlaceholder() {
return this._l10n(
"library-downloads-search-placeholder",
"Search Downloads…",
);
return "library-downloads-search-placeholder";
}
connectedCallback() {
@@ -550,7 +566,7 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
#setupDownloadsView() {
this.#downloadsView = {
onDownloadAdded: (dl) => {
onDownloadAdded: dl => {
if (this.#batchLoading) {
this.#allDownloads.push(dl);
return;
@@ -566,11 +582,11 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
onDownloadBatchEnded: async () => {
this.#batchLoading = false;
this.#allDownloads.sort(
(a, b) => this.#downloadTime(b) - this.#downloadTime(a),
(a, b) => this.#downloadTime(b) - this.#downloadTime(a)
);
this.#rebuildItems();
this.log(
`batch complete — ${this.#allDownloads.length} downloads loaded`,
`batch complete — ${this.#allDownloads.length} downloads loaded`
);
await this.updateComplete;
// Guard: library may have closed while the batch was loading.
@@ -583,7 +599,7 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
onDownloadChanged: () => {
this.requestUpdate();
},
onDownloadRemoved: (dl) => {
onDownloadRemoved: dl => {
const idx = this.#allDownloads.indexOf(dl);
if (idx !== -1) {
this.#allDownloads.splice(idx, 1);
@@ -610,7 +626,7 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
_onSearch(term) {
this.log(
`_onSearch — term: "${term}", total: ${this.#allDownloads.length}`,
`_onSearch — term: "${term}", total: ${this.#allDownloads.length}`
);
this.#rebuildItems();
this._resetProgressiveRender();
@@ -622,11 +638,10 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
if (this.isEmpty) {
return html`
<div class="empty-state">
${this.searchTerm
? this._l10n("library-search-no-results", "No results")
: this._l10n("library-downloads-empty", "No downloads found")}
</div>
<div
class="empty-state"
data-l10n-id=${this.searchTerm ? "library-search-no-results" : "library-downloads-empty"}
></div>
`;
}
@@ -634,11 +649,11 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
return html`
${Array.from(groups).map(
([key, downloads]) => html`
<div class="library-date-separator">
<span>${this._dayLabel(new Date(key))}</span>
</div>
${downloads.map((dl) => this.#renderDownloadItem(dl))}
`,
<label class="library-date-separator">
${this._dayLabel(new Date(key))}
</label>
${downloads.map(dl => this.#renderDownloadItem(dl))}
`
)}
`;
}
@@ -660,18 +675,20 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
<div class="library-item-icon-stack">
<img
class="library-item-icon-image"
src="${iconPath}"
src=${iconPath}
alt=""
role="presentation"
/>
</div>
<div class="library-item-label-container">
<span class="library-item-label">${nameStr}</span>
<span class="library-item-sublabel">${this.#getStatusLabel(dl)}</span>
<label class="library-item-label">${nameStr}</label>
<label class="library-item-sublabel"
>${this.#getStatusLabel(dl)}</label
>
</div>
<span class="library-item-time">
<label class="library-item-time">
${this._formatTime(this.#downloadTime(dl))}
</span>
</label>
</div>
</div>
</div>
@@ -686,6 +703,8 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
/**
* Binary insertion into #allDownloads (newest-first).
* O(log N) vs O(N log N) for a full sort on every add.
*
* @param {object} dl - Download object from DownloadsCommon.getData()
*/
#insertSorted(dl) {
const time = this.#downloadTime(dl);
@@ -705,8 +724,8 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
#rebuildItems() {
const term = this.searchTerm?.trim().toLowerCase();
if (term) {
this._allItems = this.#allDownloads.filter((dl) =>
this.#matchesSearchTerm(dl, term),
this._allItems = this.#allDownloads.filter(dl =>
this.#matchesSearchTerm(dl, term)
);
} else {
this._allItems = [...this.#allDownloads];
@@ -720,11 +739,7 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
const displayName = lazy.DownloadsViewUI.getDisplayName(dl);
const nameStr =
typeof displayName === "string" ? displayName.toLowerCase() : "";
const url = (
dl.source.originalUrl ||
dl.source.url ||
""
).toLowerCase();
const url = (dl.source.originalUrl || dl.source.url || "").toLowerCase();
return nameStr.includes(term) || url.includes(term);
}
@@ -751,10 +766,11 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
switch (state) {
case lazy.DownloadsCommon.DOWNLOAD_DOWNLOADING: {
const totalBytes = dl.hasProgress ? dl.totalBytes : -1;
// eslint-disable-next-line no-shadow
const [status] = lazy.DownloadUtils.getDownloadStatus(
dl.currentBytes,
totalBytes,
dl.speed,
dl.speed
);
return status;
}
@@ -774,11 +790,11 @@ class ZenLibraryDownloadsSection extends ProgressiveSearchSection {
const total = dl.hasProgress ? dl.totalBytes : -1;
const transfer = lazy.DownloadUtils.getTransferTotal(
dl.currentBytes,
total,
total
);
return strings.statusSeparatorBeforeNumber(
strings.statePaused,
transfer,
transfer
);
}
case lazy.DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL:

View File

@@ -12,9 +12,9 @@
}
@media (-moz-platform: macos) {
--border-radius-medium: 12px;
--border-radius-medium: 14px;
--tab-border-radius: 8px;
font-size: 1.25rem; /* Slightly larger font on macOS */
font-size: 1rem; /* Slightly larger font on macOS */
}
display: flex;
@@ -39,13 +39,14 @@
height: 100%;
padding: 8px;
background: rgba(255, 255, 255, 0.05);
box-shadow: var(--zen-big-shadow);
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 50px;
border-right: 1px solid rgba(0,0,0,.08);
-moz-window-dragging: drag;
}
#zen-library-content {
display: flex;
width: 25rem;
width: 27rem;
max-width: 80vh;
overflow: hidden;
transition: width 0.15s ease-in-out;
@@ -62,8 +63,8 @@
*/
display: flex;
flex-direction: column;
width: 25rem;
min-width: 25rem;
width: 27rem;
min-width: 27rem;
flex-shrink: 0;
overflow: hidden;
}
@@ -102,10 +103,6 @@
&[active]::before {
background: color-mix(in srgb, currentColor 10%, transparent);
}
&:active:hover::before {
transform: scale(0.95);
}
}
/* Search Bar */
@@ -120,7 +117,7 @@
box-sizing: border-box;
@media (-moz-platform: macos) {
font-size: 1.25rem; /* Slightly larger font on macOS */
font-size: 1rem; /* Slightly larger font on macOS */
}
}
@@ -162,8 +159,8 @@
overflow: hidden;
& .search-icon {
width: 16px;
height: 16px;
width: 18px;
height: 18px;
margin-inline-end: 6px;
padding: 4px;
flex-shrink: 0;
@@ -216,15 +213,15 @@
fill: currentColor;
-moz-context-properties: fill, fill-opacity;
& img {
width: 14px;
height: 14px;
}
&:hover {
background: var(--urlbar-box-hover-bgcolor, light-dark(rgba(0,0,0,0.08), rgba(255,255,255,0.1)));
}
}
&:has(.search-input:not(:placeholder-shown)) .search-clear-button {
opacity: 0.5;
pointer-events: auto;
}
}
.search-filter-button {
@@ -262,7 +259,7 @@
flex: 1;
flex-direction: column;
align-items: stretch;
padding: var(--tab-inline-padding);
padding: 10px;
padding-top: 0 !important;
overflow-y: auto;
scrollbar-width: auto;
@@ -271,6 +268,10 @@
@media (-moz-platform: macos) {
font-size: 1.25rem; /* Slightly larger font on macOS */
}
&:not([scrolled-to-top]) {
box-shadow: inset 0 10px 10px -10px var(--zen-toolbar-element-bg);
}
}
/* unfinished */
@@ -280,8 +281,9 @@
flex-shrink: 0;
gap: 8px;
padding: 4px;
font-size: 1em;
font-weight: 700;
padding-top: 12px;
font-size: .9em;
font-weight: 500;
letter-spacing: 0.05em;
opacity: 0.65;
@@ -297,10 +299,9 @@
.library-item {
--tab-label-mask-size: 1em;
display: flex;
align-items: stretch;
margin: 2px 0 2px 0;
appearance: none;
background-color: transparent;
border-width: 0;
@@ -308,10 +309,6 @@
color: inherit;
color-scheme: unset;
overflow: clip;
&:active {
scale: var(--zen-active-tab-scale);
}
}
.library-item-stack {
@@ -341,8 +338,8 @@
position: relative;
align-items: center;
min-width: 0;
height: 36px;
padding: 0 var(--tab-inline-padding);
padding: 12px 10px;
overflow: clip;
}
.library-item-icon-stack {
@@ -354,11 +351,11 @@
.library-item-icon-image {
flex-shrink: 0;
width: var(--size-item-small);
height: var(--size-item-small);
margin-inline-start: calc(var(--toolbarbutton-inner-padding) / 3) !important;
margin-inline-end: var(--toolbarbutton-inner-padding) !important;
padding: 0 !important;
width: 18px;
height: 18px;
margin-inline-start: calc(var(--toolbarbutton-inner-padding) / 2);
margin-inline-end: calc(var(--toolbarbutton-inner-padding) * 1.5);
padding: 0;
border-radius: 4px;
fill: currentColor;
-moz-context-properties: fill, stroke;
@@ -373,21 +370,21 @@
direction: ltr;
mask-image: linear-gradient(to left, transparent, black var(--tab-label-mask-size));
overflow: hidden;
gap: 4px;
}
.library-item-label {
margin-inline: 0;
line-height: var(--tab-label-line-height);
white-space: nowrap;
line-height: 1;
}
/* finished (i hope so) */
.library-item-sublabel {
margin: 0;
font-size: x-small;
font-size: 11px;
white-space: nowrap;
opacity: 0.65; /* what opacity should i keep? */
transform: translateY(-4px);
opacity: 0.6; /* what opacity should i keep? */
pointer-events: none;
overflow: hidden;
}
@@ -395,12 +392,17 @@
/* unfinished */
.library-item-time {
flex-shrink: 0;
margin-inline-start: calc(var(--toolbarbutton-inner-padding) - 0.5em); /* idk why this calc makes sense to me
margin-inline-start: calc(var(--toolbarbutton-inner-padding) - 0.5em); /* idk why this calc makes sense to me
but it is inconsistant so please fix it if you have an idea */
padding-inline-end: calc(var(--toolbarbutton-inner-padding) / 3);
font-size: x-small;
white-space: nowrap;
opacity: 0.65;
display: none;
.library-item-content:hover & {
display: flex;
}
}
/* unfinished */