From 44f76c2d61f311df262264a5e0b8deb9accaa94a Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 20 Feb 2025 01:33:55 -0500 Subject: [PATCH 1/2] wip --- backend/btrixcloud/colls.py | 16 ++++--- backend/btrixcloud/models.py | 1 + frontend/src/components/ui/combobox.ts | 26 +++++++++-- frontend/src/components/ui/index.ts | 1 + .../components/ui/menu-item-without-focus.ts | 22 +++++++++ frontend/src/context/popup-boundary.ts | 8 ++++ .../collections/collection-edit-dialog.ts | 6 +++ .../collections/select-collection-page.ts | 46 +++++++++++++------ frontend/src/utils/css.ts | 13 +++--- 9 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 frontend/src/components/ui/menu-item-without-focus.ts create mode 100644 frontend/src/context/popup-boundary.ts diff --git a/backend/btrixcloud/colls.py b/backend/btrixcloud/colls.py index 9ac7592210..433c0c4656 100644 --- a/backend/btrixcloud/colls.py +++ b/backend/btrixcloud/colls.py @@ -341,9 +341,11 @@ async def get_collection_out( result = await self.get_collection_raw(coll_id, public_or_unlisted_only) if resources: - result["resources"], crawl_ids, pages_optimized = ( - await self.get_collection_crawl_resources(coll_id) - ) + ( + result["resources"], + crawl_ids, + pages_optimized, + ) = await self.get_collection_crawl_resources(coll_id) initial_pages, _ = await self.page_ops.list_pages( crawl_ids=crawl_ids, @@ -965,9 +967,11 @@ async def get_collection_all(org: Organization = Depends(org_viewer_dep)): try: all_collections, _ = await colls.list_collections(org, page_size=10_000) for collection in all_collections: - results[collection.name], _, _ = ( - await colls.get_collection_crawl_resources(collection.id) - ) + ( + results[collection.name], + _, + _, + ) = await colls.get_collection_crawl_resources(collection.id) except Exception as exc: # pylint: disable=raise-missing-from raise HTTPException( diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index 241dfd2c21..09e3a66201 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -1376,6 +1376,7 @@ class PageUrlCount(BaseModel): """Model for counting pages by URL""" url: AnyHttpUrl + title: Optional[str] = None count: int = 0 snapshots: List[PageIdTimestamp] = [] diff --git a/frontend/src/components/ui/combobox.ts b/frontend/src/components/ui/combobox.ts index a8de1bfa80..b45dc79b0e 100644 --- a/frontend/src/components/ui/combobox.ts +++ b/frontend/src/components/ui/combobox.ts @@ -1,5 +1,6 @@ +import { consume } from "@lit/context"; import type { SlMenu, SlMenuItem, SlPopup } from "@shoelace-style/shoelace"; -import { css, html, LitElement, type PropertyValues } from "lit"; +import { css, html, type PropertyValues } from "lit"; import { customElement, property, @@ -8,6 +9,8 @@ import { state, } from "lit/decorators.js"; +import { TailwindElement } from "@/classes/TailwindElement"; +import { popupBoundary } from "@/context/popup-boundary"; import { dropdown } from "@/utils/css"; /** @@ -20,7 +23,7 @@ import { dropdown } from "@/utils/css"; * @event request-close */ @customElement("btrix-combobox") -export class Combobox extends LitElement { +export class Combobox extends TailwindElement { static styles = [ dropdown, css` @@ -34,6 +37,13 @@ export class Combobox extends LitElement { @property({ type: Boolean }) open = false; + @property({ type: Boolean }) + loading = false; + + @consume({ context: popupBoundary }) + @state() + autoSizeBoundary?: Element | Element[] | undefined; + @state() isActive = true; @@ -69,22 +79,25 @@ export class Combobox extends LitElement { } render() { + console.log(this.autoSizeBoundary); return html` -
+
diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index a2e674a1ff..53a6cf838f 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -39,3 +39,4 @@ import("./tag-input"); import("./tag"); import("./time-input"); import("./user-language-select"); +import("./menu-item-without-focus"); diff --git a/frontend/src/components/ui/menu-item-without-focus.ts b/frontend/src/components/ui/menu-item-without-focus.ts new file mode 100644 index 0000000000..1984e7ec39 --- /dev/null +++ b/frontend/src/components/ui/menu-item-without-focus.ts @@ -0,0 +1,22 @@ +/** A version of that doesn't steal focus on mouseover */ + +import { SlMenuItem } from "@shoelace-style/shoelace"; +import { customElement } from "lit/decorators.js"; + +@customElement("btrix-menu-item") +// @ts-expect-error this shouldn't be allowed, but idk of an easier way without +// forking the whole component +export class BtrixMenuItem extends SlMenuItem { + private readonly handleMouseOver = (event: MouseEvent) => { + // NOT doing this.focus(); + event.stopPropagation(); + }; + + connectedCallback() { + super.connectedCallback(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + } +} diff --git a/frontend/src/context/popup-boundary.ts b/frontend/src/context/popup-boundary.ts new file mode 100644 index 0000000000..e395e63296 --- /dev/null +++ b/frontend/src/context/popup-boundary.ts @@ -0,0 +1,8 @@ +import { createContext } from "@lit/context"; + +/** + * Boundary for custom instances to use, e.g. when inside a dialog + */ +export const popupBoundary = createContext( + "popup-boundary", +); diff --git a/frontend/src/features/collections/collection-edit-dialog.ts b/frontend/src/features/collections/collection-edit-dialog.ts index fed4f33913..1de4ad23a2 100644 --- a/frontend/src/features/collections/collection-edit-dialog.ts +++ b/frontend/src/features/collections/collection-edit-dialog.ts @@ -1,3 +1,4 @@ +import { provide } from "@lit/context"; import { localized, msg, str } from "@lit/localize"; import { Task, TaskStatus } from "@lit/task"; import { type SlRequestCloseEvent } from "@shoelace-style/shoelace"; @@ -22,6 +23,7 @@ import { type SelectCollectionPage } from "./select-collection-page"; import { BtrixElement } from "@/classes/BtrixElement"; import type { Dialog } from "@/components/ui/dialog"; import { type TabGroupPanel } from "@/components/ui/tab-group/tab-panel"; +import { popupBoundary } from "@/context/popup-boundary"; import { type Collection, type CollectionThumbnailSource, @@ -118,6 +120,9 @@ export class CollectionEdit extends BtrixElement { @query("btrix-collection-snapshot-preview") public readonly thumbnailPreview?: CollectionSnapshotPreview | null; + @provide({ context: popupBoundary }) + private popupBoundary: Element | Element[] | undefined; + protected willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has("collectionId") && this.collectionId) { void this.fetchCollection(this.collectionId); @@ -135,6 +140,7 @@ export class CollectionEdit extends BtrixElement { null; this.selectedSnapshot = this.collection?.thumbnailSource ?? null; } + this.popupBoundary = this.dialog; } readonly checkChanged = checkChanged.bind(this); diff --git a/frontend/src/features/collections/select-collection-page.ts b/frontend/src/features/collections/select-collection-page.ts index f9a4babd2f..b5be981189 100644 --- a/frontend/src/features/collections/select-collection-page.ts +++ b/frontend/src/features/collections/select-collection-page.ts @@ -22,6 +22,8 @@ import type { APIPaginationQuery } from "@/types/api"; import type { Collection } from "@/types/collection"; import type { UnderlyingFunction } from "@/types/utils"; import { tw } from "@/utils/tailwind"; +import { timeoutCache } from "@/utils/timeoutCache"; +import { cached } from "@/utils/weakCache"; type Snapshot = { pageId: string; @@ -31,6 +33,7 @@ type Snapshot = { type Page = { url: string; + title?: string; count: number; snapshots: Snapshot[]; }; @@ -179,17 +182,20 @@ export class SelectCollectionPage extends BtrixElement { } private readonly searchResults = new Task(this, { - task: async ([searchValue], { signal }) => { - const pageUrls = await this.getPageUrls( - { - id: this.collectionId!, - urlPrefix: searchValue, - }, - signal, - ); + task: cached( + async ([searchValue], { signal }) => { + const pageUrls = await this.getPageUrls( + { + id: this.collectionId!, + urlPrefix: searchValue, + }, + signal, + ); - return pageUrls; - }, + return pageUrls; + }, + { cacheConstructor: timeoutCache(300) }, + ), args: () => [this.searchQuery] as const, }); @@ -365,6 +371,7 @@ export class SelectCollectionPage extends BtrixElement { this.renderItems( // Render previous value so that dropdown doesn't shift while typing this.searchResults.value, + true, ), complete: this.renderItems, }); @@ -372,12 +379,13 @@ export class SelectCollectionPage extends BtrixElement { private readonly renderItems = ( results: SelectCollectionPage["searchResults"]["value"], + loading = false, ) => { if (!results) return; const { items } = results; - if (!items.length) { + if (!loading && !items.length) { return html` ${msg("No matching page found.")} @@ -388,7 +396,7 @@ export class SelectCollectionPage extends BtrixElement { return html` ${items.map((item: Page) => { return html` - { if (this.input) { @@ -401,8 +409,18 @@ export class SelectCollectionPage extends BtrixElement { this.selectedSnapshot = this.selectedPage.snapshots[0]; }} - >${item.url} - + >${item.title + ? html`
${item.title}
+
+ ${item.url} +
` + : html`
+ ${msg("No page title")} +
+
+ ${item.url} +
`} + `; })} `; diff --git a/frontend/src/utils/css.ts b/frontend/src/utils/css.ts index 3420c9a820..9a1c201730 100644 --- a/frontend/src/utils/css.ts +++ b/frontend/src/utils/css.ts @@ -102,7 +102,6 @@ export const animatePulse = css` export const dropdown = css` .dropdown { contain: content; - transform-origin: top left; box-shadow: var(--sl-shadow-medium); } @@ -111,34 +110,34 @@ export const dropdown = css` } .animateShow { - animation: dropdownShow 100ms ease forwards; + animation: dropdownShow 150ms cubic-bezier(0, 0, 0.2, 1) forwards; } .animateHide { - animation: dropdownHide 100ms ease forwards; + animation: dropdownHide 150ms cubic-bezier(0.4, 0, 1, 1) forwards; } @keyframes dropdownShow { from { opacity: 0; - transform: scale(0.9); + transform: translateY(-8px); } to { opacity: 1; - transform: scale(1); + transform: translateY(0); } } @keyframes dropdownHide { from { opacity: 1; - transform: scale(1); + transform: translateY(0); } to { opacity: 0; - transform: scale(0.9); + transform: translateY(-8px); } } `; From 0a9863afce0a0a28ef8586e035a55808571f7266 Mon Sep 17 00:00:00 2001 From: emma Date: Thu, 20 Feb 2025 14:54:27 -0500 Subject: [PATCH 2/2] fix mis-sized spinner --- .../features/collections/edit-dialog/presentation-section.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/collections/edit-dialog/presentation-section.ts b/frontend/src/features/collections/edit-dialog/presentation-section.ts index f4d913a00e..63be9383b7 100644 --- a/frontend/src/features/collections/edit-dialog/presentation-section.ts +++ b/frontend/src/features/collections/edit-dialog/presentation-section.ts @@ -100,7 +100,7 @@ export default function renderPresentation(this: CollectionEdit) { ` : this.thumbnailPreview?.blobTask.status === TaskStatus.PENDING && !this.blobIsLoaded - ? html`` + ? html`` : nothing}