From 79645b64fe497686e24fb1af20254a81d87fd4b9 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 30 Jan 2024 19:46:42 -0800 Subject: [PATCH] Refactor collections and browser profile data-tables (#1505) - Updates browser profile list styles to match other data table styles - Makes entire collection item clickable - Refactors row click area to fix text overflow --- .../src/components/ui/overflow-dropdown.ts | 18 +- .../src/components/ui/table/table-cell.ts | 29 ++- frontend/src/components/ui/table/table-row.ts | 1 + .../components/ui/table/table.stylesheet.css | 24 ++ frontend/src/components/ui/table/table.ts | 13 +- .../archived-items/archived-item-list.ts | 34 +-- .../src/features/archived-items/crawl-list.ts | 33 +-- .../src/pages/org/browser-profiles-list.ts | 207 +++++++++--------- frontend/src/pages/org/collections-list.ts | 30 ++- .../src/{theme.css => theme.stylesheet.css} | 0 frontend/src/theme.ts | 2 +- frontend/webpack.config.js | 9 +- 12 files changed, 204 insertions(+), 196 deletions(-) create mode 100644 frontend/src/components/ui/table/table.stylesheet.css rename frontend/src/{theme.css => theme.stylesheet.css} (100%) diff --git a/frontend/src/components/ui/overflow-dropdown.ts b/frontend/src/components/ui/overflow-dropdown.ts index 92cf60a8..2c413a3d 100644 --- a/frontend/src/components/ui/overflow-dropdown.ts +++ b/frontend/src/components/ui/overflow-dropdown.ts @@ -1,7 +1,12 @@ import { LitElement, html, css } from "lit"; -import { customElement, state, queryAssignedElements } from "lit/decorators.js"; +import { + customElement, + state, + queryAssignedElements, + query, +} from "lit/decorators.js"; import { msg, localized } from "@lit/localize"; -import type { SlMenu } from "@shoelace-style/shoelace"; +import type { SlDropdown, SlMenu } from "@shoelace-style/shoelace"; /** * Dropdown for additional actions. @@ -34,12 +39,15 @@ export class OverflowDropdown extends LitElement { @state() private hasMenuItems?: boolean; + @query("sl-dropdown") + private dropdown?: SlDropdown; + @queryAssignedElements({ selector: "sl-menu", flatten: true }) private menu!: Array; render() { return html` - + `; } + + hide() { + this.dropdown?.hide(); + } } diff --git a/frontend/src/components/ui/table/table-cell.ts b/frontend/src/components/ui/table/table-cell.ts index ca862fbb..514bd09b 100644 --- a/frontend/src/components/ui/table/table-cell.ts +++ b/frontend/src/components/ui/table/table-cell.ts @@ -40,23 +40,22 @@ export class TableCell extends LitElement { role = "cell"; @property({ type: String }) - rowClickTarget?: (typeof ALLOWED_ROW_CLICK_TARGET_TAG)[number] | "" = ""; + rowClickTarget?: (typeof ALLOWED_ROW_CLICK_TARGET_TAG)[number]; render() { - return html`${this.rowClickTarget && - ALLOWED_ROW_CLICK_TARGET_TAG.includes(this.rowClickTarget) - ? html`` - : nothing} `; + private handleSlotChange(e: Event) { + if (!this.rowClickTarget) return; + const elems = (e.target as HTMLSlotElement).assignedElements(); + const rowClickTarget = elems.find( + (el) => el.tagName.toLowerCase() === this.rowClickTarget + ); + + if (!rowClickTarget) return; + + // Styled in table.css + rowClickTarget.classList.add("rowClickTarget"); } } diff --git a/frontend/src/components/ui/table/table-row.ts b/frontend/src/components/ui/table/table-row.ts index e6ed2768..38606c8b 100644 --- a/frontend/src/components/ui/table/table-row.ts +++ b/frontend/src/components/ui/table/table-row.ts @@ -11,6 +11,7 @@ export class TableRow extends LitElement { grid-column: var(--btrix-table-grid-column); display: grid; grid-template-columns: subgrid; + position: relative; } `; diff --git a/frontend/src/components/ui/table/table.stylesheet.css b/frontend/src/components/ui/table/table.stylesheet.css new file mode 100644 index 00000000..dfd1dc23 --- /dev/null +++ b/frontend/src/components/ui/table/table.stylesheet.css @@ -0,0 +1,24 @@ +btrix-table-cell[rowClickTarget] { + display: grid; + grid-template-columns: subgrid; + white-space: nowrap; + overflow: hidden; +} + +.rowClickTarget { + max-width: 100%; +} + +.rowClickTarget::after { + content: ""; + display: block; + position: absolute; + inset: 0; + grid-column: clickable-start / clickable-end; +} + +.rowClickTarget:focus-visible { + outline: var(--sl-focus-ring); + outline-offset: -0.25rem; + border-radius: 0.5rem; +} diff --git a/frontend/src/components/ui/table/table.ts b/frontend/src/components/ui/table/table.ts index 693ff206..9c7fe2bc 100644 --- a/frontend/src/components/ui/table/table.ts +++ b/frontend/src/components/ui/table/table.ts @@ -6,9 +6,18 @@ import { queryAssignedElements, } from "lit/decorators.js"; -import { TailwindElement } from "@/classes/TailwindElement"; +import { theme } from "@/theme"; +import tableCSS from "./table.stylesheet.css"; import { type TableHead } from "./table-head"; +// Add table CSS to theme CSS to make it available throughout the app, +// to both shadow and light dom components. +// TODO Remove once all `LiteElement`s are migrated over to `TailwindElement` +tableCSS.split("}").forEach((rule: string) => { + if (!rule.trim()) return; + theme.insertRule(`${rule}}`); +}); + /** * Low-level component for displaying content as a table. * To style tables, use TailwindCSS utility classes. @@ -46,7 +55,7 @@ import { type TableHead } from "./table-head"; * @cssproperty --btrix-cell-padding-bottom */ @customElement("btrix-table") -export class Table extends TailwindElement { +export class Table extends LitElement { static styles = css` :host { --btrix-cell-gap: 0; diff --git a/frontend/src/features/archived-items/archived-item-list.ts b/frontend/src/features/archived-items/archived-item-list.ts index c5d7a07a..7c654844 100644 --- a/frontend/src/features/archived-items/archived-item-list.ts +++ b/frontend/src/features/archived-items/archived-item-list.ts @@ -6,15 +6,14 @@ import { queryAssignedElements, query, } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { msg, localized, str } from "@lit/localize"; +import { type SlCheckbox } from "@shoelace-style/shoelace"; import { TailwindElement } from "@/classes/TailwindElement"; import type { ArchivedItem } from "@/types/crawler"; import { renderName } from "@/utils/crawler"; import { NavigateController } from "@/controllers/navigate"; -import { type SlCheckbox } from "@shoelace-style/shoelace"; - -const NAME_WIDTH_CSS = css`26rem`; export type CheckboxChangeEventDetail = { checked: boolean; @@ -38,29 +37,8 @@ export class ArchivedItemListItem extends TailwindElement { border-radius: var(--btrix-border-radius-top, 0) var(--btrix-border-radius-to, 0) var(--btrix-border-radius-bottom, 0) var(--btrix-border-radius-bottom, 0); - position: relative; height: 2.5rem; } - - /* - * TODO consolidate data-table variations - * https://github.com/webrecorder/browsertrix-cloud/issues/1504 - */ - btrix-table-cell { - overflow: hidden; - white-space: nowrap; - } - - .clickLabel { - width: ${NAME_WIDTH_CSS}; - display: flex; - gap: var(--btrix-cell-gap, 0); - align-items: center; - height: 100%; - box-sizing: border-box; - padding: var(--btrix-cell-padding-top) var(--btrix-cell-padding-right) - var(--btrix-cell-padding-bottom) var(--btrix-cell-padding-left); - } `; @property({ type: Object }) @@ -87,7 +65,7 @@ export class ArchivedItemListItem extends TailwindElement { if (!this.item) return; const checkboxId = `${this.item.id}-checkbox`; const rowName = html` -
+
${renderName(this.item)}
@@ -122,7 +100,9 @@ export class ArchivedItemListItem extends TailwindElement { ` : nothing} ${this.href ? html` @@ -225,7 +205,7 @@ export class ArchivedItemList extends TailwindElement { btrix-table { grid-template-columns: ${`${ this.hasCheckboxCell ? "min-content" : "" - } [clickable-start] ${NAME_WIDTH_CSS} 12rem 1fr 1fr 1fr [clickable-end] ${ + } [clickable-start] 60ch 12rem 1fr 1fr 30ch [clickable-end] ${ this.hasActionCell ? "min-content" : "" }`.trim()}; } diff --git a/frontend/src/features/archived-items/crawl-list.ts b/frontend/src/features/archived-items/crawl-list.ts index 49e45e32..7d6efa3a 100644 --- a/frontend/src/features/archived-items/crawl-list.ts +++ b/frontend/src/features/archived-items/crawl-list.ts @@ -19,6 +19,7 @@ import { query, queryAssignedElements, } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { msg, localized, str } from "@lit/localize"; import { RelativeDuration } from "@/components/ui/relative-duration"; @@ -28,8 +29,6 @@ import { renderName } from "@/utils/crawler"; import { TailwindElement } from "@/classes/TailwindElement"; import { NavigateController } from "@/controllers/navigate"; -const NAME_WIDTH_CSS = css`16rem`; - /** * @slot menu */ @@ -46,28 +45,6 @@ export class CrawlListItem extends TailwindElement { border-radius: var(--btrix-border-radius-top, 0) var(--btrix-border-radius-to, 0) var(--btrix-border-radius-bottom, 0) var(--btrix-border-radius-bottom, 0); - position: relative; - } - - /* - * TODO consolidate data-table variations - * https://github.com/webrecorder/browsertrix-cloud/issues/1504 - */ - btrix-table-cell { - overflow: hidden; - white-space: nowrap; - } - - .clickLabel { - width: ${NAME_WIDTH_CSS}; - overflow: hidden; - display: flex; - gap: var(--btrix-cell-gap, 0); - align-items: center; - height: 100%; - box-sizing: border-box; - padding: var(--btrix-cell-padding-top) var(--btrix-cell-padding-right) - var(--btrix-cell-padding-bottom) var(--btrix-cell-padding-left); } `; @@ -102,7 +79,7 @@ export class CrawlListItem extends TailwindElement { if (this.workflowId) { const label = html` -
+
${this.safeRender( (crawl) => html` `; idCell = html` - + ${this.href ? html` ${label} @@ -320,7 +299,7 @@ export class CrawlList extends TailwindElement { btrix-table { grid-template-columns: min-content [clickable-start] - ${this.workflowId ? "" : `${NAME_WIDTH_CSS} `}${NAME_WIDTH_CSS} auto + ${this.workflowId ? "" : `auto `}auto auto auto auto auto auto [clickable-end] min-content; } diff --git a/frontend/src/pages/org/browser-profiles-list.ts b/frontend/src/pages/org/browser-profiles-list.ts index 3e3a012a..29563337 100644 --- a/frontend/src/pages/org/browser-profiles-list.ts +++ b/frontend/src/pages/org/browser-profiles-list.ts @@ -8,6 +8,7 @@ import type { Profile } from "./types"; import type { APIPaginatedList } from "@/types/api"; import type { SelectNewDialogEvent } from "./index"; import type { Browser } from "@/types/browser"; +import { nothing } from "lit"; /** * Usage: @@ -54,133 +55,129 @@ export class BrowserProfilesList extends LiteElement {
- - ${this.renderTable()}`; +
${this.renderTable()}
`; } private renderTable() { return html` -
-
- - ${this.browserProfiles - ? this.browserProfiles.length - ? html`
- ${this.browserProfiles.map(this.renderItem.bind(this))} -
` - : html` -
-

- ${msg("No browser profiles yet.")} -

-
- ` - : ""} -
+ `} `; } - private renderItem(data: Profile) { + private renderItem = (data: Profile) => { + const isBackedUp = data.resource && data.resource.replicas.length > 0; return html` -
-
-
-
- ${data.name} - ${when( - data.resource && data.resource.replicas.length > 0, - () => html` - - ` - )} -
-
+ + + + + + + + ${data.name} + +
+
+ ${data.description} ${data.description} ${data.description} + ${data.description} ${data.description} ${data.description} ${data.description}
-
- ${new Date(data.created).toLocaleDateString()} -
-
- ${data.origins.join(", ")} -
-
- ${this.renderMenu(data)} -
-
- - `; - } - - private renderMenu(data: Profile) { - return html` - e.preventDefault()}> - - - + + ${msg("Delete")} + + + `; } diff --git a/frontend/src/pages/org/collections-list.ts b/frontend/src/pages/org/collections-list.ts index 0f90e2ec..b996905a 100644 --- a/frontend/src/pages/org/collections-list.ts +++ b/frontend/src/pages/org/collections-list.ts @@ -15,6 +15,7 @@ import type { Collection, CollectionSearchValues } from "@/types/collection"; import type { CollectionSavedEvent } from "@/features/collections/collection-metadata-dialog"; import noCollectionsImg from "~assets/images/no-collections-found.webp"; import type { SelectNewDialogEvent } from "./index"; +import type { OverflowDropdown } from "@/components/ui/overflow-dropdown"; type Collections = APIPaginatedList; type SearchFields = "name"; @@ -151,7 +152,9 @@ export class CollectionsList extends LiteElement { > ${this.renderControls()}
- ${guard([this.collections], this.renderList)} +
+ ${guard([this.collections], this.renderList)} +
` : this.renderLoading() )} @@ -391,7 +394,7 @@ export class CollectionsList extends LiteElement { if (this.collections?.items.length) { return html` @@ -485,7 +488,9 @@ export class CollectionsList extends LiteElement { private renderItem = (col: Collection) => html` - + ${col?.isPublic ? html` @@ -507,10 +512,10 @@ export class CollectionsList extends LiteElement { `} - + ${col.name} @@ -542,7 +547,7 @@ export class CollectionsList extends LiteElement { minute="2-digit" > - + ${this.isCrawler ? this.renderActions(col) : ""} @@ -552,10 +557,7 @@ export class CollectionsList extends LiteElement { const authToken = this.authState!.headers.Authorization.split(" ")[1]; return html` - - - - + this.manageCollection(col, "editMetadata")} @@ -602,7 +604,11 @@ export class CollectionsList extends LiteElement { class="px-6 py-[0.6rem] flex gap-2 items-center whitespace-nowrap hover:bg-neutral-100" download @click=${(e: MouseEvent) => { - (e.target as HTMLAnchorElement).closest("sl-dropdown")?.hide(); + ( + (e.target as HTMLAnchorElement).closest( + "btrix-overflow-dropdown" + ) as OverflowDropdown + )?.hide(); }} > @@ -617,7 +623,7 @@ export class CollectionsList extends LiteElement { ${msg("Delete Collection")} - + `; }; diff --git a/frontend/src/theme.css b/frontend/src/theme.stylesheet.css similarity index 100% rename from frontend/src/theme.css rename to frontend/src/theme.stylesheet.css diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts index 60c5d2db..8f01e8b1 100644 --- a/frontend/src/theme.ts +++ b/frontend/src/theme.ts @@ -1,4 +1,4 @@ -import themeCSS from "./theme.css"; +import themeCSS from "./theme.stylesheet.css"; // Create a new style sheet from the compiled theme CSS export const theme = new CSSStyleSheet(); diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 062543d1..9a4f266d 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -104,13 +104,14 @@ const main = { exclude: /node_modules/, }, { - // Non-theme styles and assets like fonts and Shoelace + // Global styles and assets, like fonts and Shoelace, + // that get added to document styles test: /\.css$/, include: [ path.resolve(__dirname, "src"), path.resolve(__dirname, "node_modules/@shoelace-style/shoelace"), ], - exclude: [path.resolve(__dirname, "src/theme.css")], + exclude: /\.stylesheet\.css$/, use: [ "style-loader", { loader: "css-loader", options: { importLoaders: 1 } }, @@ -118,8 +119,8 @@ const main = { ], }, { - // Theme CSS loaded as raw string and used as a CSSStyleSheet - test: /theme\.css$/, + // CSS loaded as raw string and used as a CSSStyleSheet + test: /\.stylesheet\.css$/, type: "asset/source", include: [path.resolve(__dirname, "src")], use: ["postcss-loader"],