Fix truncation for long workflow / item names (#1773)
Fixes #1771 ### Changes - Previously these would either get cut off or overflow, now they get truncated! - Titles also have a tooltip now ### Screenshots (after, see issue for before) <img width="780" alt="Screenshot 2024-05-02 at 1 25 00 PM" src="https://github.com/webrecorder/browsertrix/assets/5672810/337f90e4-c3b2-4adf-b63e-fd4fdf85d593"> <img width="1184" alt="Screenshot 2024-05-02 at 1 58 21 PM" src="https://github.com/webrecorder/browsertrix/assets/5672810/a0e5111a-ffd4-4d03-8833-cc1831d28e60"> **Before** <img width="655" alt="Screenshot 2024-05-02 at 1 59 26 PM" src="https://github.com/webrecorder/browsertrix/assets/5672810/a69d3359-3932-4183-9563-2d49b4082990"> **After** <img width="489" alt="Screenshot 2024-05-02 at 2 46 49 PM" src="https://github.com/webrecorder/browsertrix/assets/5672810/c1ead7ae-0c26-44e6-916e-df7a398f97df"> ### Caveats - Doesn't replace the in-page dialog. Added a TODO note. --------- Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
parent
93c35ee2ee
commit
cf1592a809
76
frontend/src/components/detail-page-title.ts
Normal file
76
frontend/src/components/detail-page-title.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { localized } from "@lit/localize";
|
||||||
|
import { css, html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import { TailwindElement } from "@/classes/TailwindElement";
|
||||||
|
import { type ArchivedItem, type Workflow } from "@/types/crawler";
|
||||||
|
import { formatNumber } from "@/utils/localization";
|
||||||
|
import { pluralOf } from "@/utils/pluralize";
|
||||||
|
|
||||||
|
enum TitleSource {
|
||||||
|
Name,
|
||||||
|
ID,
|
||||||
|
FirstSeed,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item = Pick<
|
||||||
|
ArchivedItem & Workflow,
|
||||||
|
"name" | "firstSeed" | "seedCount" | "id"
|
||||||
|
>;
|
||||||
|
|
||||||
|
@localized()
|
||||||
|
@customElement("btrix-detail-page-title")
|
||||||
|
export class DetailPageTitle extends TailwindElement {
|
||||||
|
@property({ type: Object })
|
||||||
|
item: Item | undefined;
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-tooltip::part(body) {
|
||||||
|
word-break: break-all;
|
||||||
|
max-width: min(var(--max-width), calc(100vw - 0.5rem));
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
private primaryTitle(item: Item): {
|
||||||
|
title: string;
|
||||||
|
source: TitleSource;
|
||||||
|
} {
|
||||||
|
if (item.name) return { title: item.name, source: TitleSource.Name };
|
||||||
|
if (!item.firstSeed || !item.seedCount)
|
||||||
|
return { title: item.id, source: TitleSource.ID };
|
||||||
|
return { title: item.firstSeed, source: TitleSource.FirstSeed };
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderTitle(item: Item) {
|
||||||
|
const { title, source } = this.primaryTitle(item);
|
||||||
|
|
||||||
|
if (source !== TitleSource.FirstSeed)
|
||||||
|
return html`<span class="truncate">${title}</span>`;
|
||||||
|
|
||||||
|
const remainder = item.seedCount - 1;
|
||||||
|
|
||||||
|
return html`<span class="truncate">${item.firstSeed}</span>${remainder
|
||||||
|
? html` <span class="whitespace-nowrap text-neutral-500"
|
||||||
|
>+${formatNumber(remainder)} ${pluralOf("URLs", remainder)}</span
|
||||||
|
>`
|
||||||
|
: nothing}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.item)
|
||||||
|
return html`<sl-skeleton class="inline-block h-8 w-60"></sl-skeleton>`;
|
||||||
|
|
||||||
|
return html`<sl-tooltip
|
||||||
|
content="${this.primaryTitle(this.item).title}"
|
||||||
|
hoist
|
||||||
|
>
|
||||||
|
<h1 class="flex min-w-32 text-xl font-semibold leading-8">
|
||||||
|
${this.renderTitle(this.item)}
|
||||||
|
</h1>
|
||||||
|
</sl-tooltip>`;
|
||||||
|
}
|
||||||
|
}
|
@ -5,3 +5,4 @@ import("./orgs-list");
|
|||||||
import("./not-found");
|
import("./not-found");
|
||||||
import("./screencast");
|
import("./screencast");
|
||||||
import("./beta-badges");
|
import("./beta-badges");
|
||||||
|
import("./detail-page-title");
|
||||||
|
@ -139,6 +139,7 @@ export class CrawlListItem extends TailwindElement {
|
|||||||
<btrix-crawl-status
|
<btrix-crawl-status
|
||||||
state=${workflow.state}
|
state=${workflow.state}
|
||||||
hideLabel
|
hideLabel
|
||||||
|
hoist
|
||||||
></btrix-crawl-status>
|
></btrix-crawl-status>
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
@ -420,22 +420,27 @@ export class ArchivedItemDetail extends TailwindElement {
|
|||||||
if (!this.crawl)
|
if (!this.crawl)
|
||||||
return html`<sl-skeleton class="inline-block h-8 w-60"></sl-skeleton>`;
|
return html`<sl-skeleton class="inline-block h-8 w-60"></sl-skeleton>`;
|
||||||
|
|
||||||
if (this.crawl.name) return this.crawl.name;
|
if (this.crawl.name)
|
||||||
|
return html`<span class="truncate">${this.crawl.name}</span>`;
|
||||||
if (!this.crawl.firstSeed || !this.crawl.seedCount) return this.crawl.id;
|
if (!this.crawl.firstSeed || !this.crawl.seedCount) return this.crawl.id;
|
||||||
const remainder = this.crawl.seedCount - 1;
|
const remainder = this.crawl.seedCount - 1;
|
||||||
let crawlName: TemplateResult = html`<span class="break-words"
|
let crawlName: TemplateResult = html`<span class="truncate"
|
||||||
>${this.crawl.firstSeed}</span
|
>${this.crawl.firstSeed}</span
|
||||||
>`;
|
>`;
|
||||||
if (remainder) {
|
if (remainder) {
|
||||||
if (remainder === 1) {
|
if (remainder === 1) {
|
||||||
crawlName = msg(
|
crawlName = msg(
|
||||||
html`<span class="break-words">${this.crawl.firstSeed}</span>
|
html`<span class="truncate">${this.crawl.firstSeed}</span>
|
||||||
<span class="text-neutral-500">+${remainder} URL</span>`,
|
<span class="whitespace-nowrap text-neutral-500"
|
||||||
|
>+${remainder} URL</span
|
||||||
|
>`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
crawlName = msg(
|
crawlName = msg(
|
||||||
html`<span class="break-words">${this.crawl.firstSeed}</span>
|
html`<span class="truncate">${this.crawl.firstSeed}</span>
|
||||||
<span class="text-neutral-500">+${remainder} URLs</span>`,
|
<span class="whitespace-nowrap text-neutral-500"
|
||||||
|
>+${remainder} URLs</span
|
||||||
|
>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -534,17 +539,9 @@ export class ArchivedItemDetail extends TailwindElement {
|
|||||||
|
|
||||||
private renderHeader() {
|
private renderHeader() {
|
||||||
return html`
|
return html`
|
||||||
<header class="mb-3 flex flex-wrap items-center gap-2 border-b pb-3">
|
<header class="mb-3 flex flex-wrap gap-2 border-b pb-3">
|
||||||
<h1
|
<btrix-detail-page-title .item=${this.crawl}></btrix-detail-page-title>
|
||||||
class="grid min-w-0 flex-auto truncate text-xl font-semibold leading-7"
|
<div class="ml-auto flex flex-wrap justify-end gap-2">
|
||||||
>
|
|
||||||
${this.renderName()}
|
|
||||||
</h1>
|
|
||||||
<div
|
|
||||||
class="${this.isActive
|
|
||||||
? "justify-between"
|
|
||||||
: "justify-end ml-auto"} grid grid-flow-col gap-2"
|
|
||||||
>
|
|
||||||
${this.isActive
|
${this.isActive
|
||||||
? html`
|
? html`
|
||||||
<sl-button-group>
|
<sl-button-group>
|
||||||
@ -1296,13 +1293,10 @@ ${this.crawl?.description}
|
|||||||
return !formEl.querySelector("[data-invalid]");
|
return !formEl.querySelector("[data-invalid]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO replace with in-page dialog
|
||||||
private async deleteCrawl() {
|
private async deleteCrawl() {
|
||||||
if (
|
if (
|
||||||
!window.confirm(
|
!window.confirm(msg(str`Are you sure you want to delete this crawl?`))
|
||||||
msg(
|
|
||||||
str`Are you sure you want to delete crawl of ${this.renderName()}?`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -282,22 +282,20 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
<div class="grid grid-cols-1 gap-7">
|
<div class="grid grid-cols-1 gap-7">
|
||||||
${this.renderHeader()}
|
${this.renderHeader()}
|
||||||
|
|
||||||
<header class="col-span-1 items-end justify-between md:flex">
|
<header class="col-span-1 flex flex-wrap gap-2">
|
||||||
<h2>
|
<btrix-detail-page-title
|
||||||
<span
|
.item=${this.workflow}
|
||||||
class="inline-block align-middle text-xl font-semibold leading-10 md:mr-2"
|
></btrix-detail-page-title>
|
||||||
>${this.renderName()}</span
|
${when(
|
||||||
>
|
this.workflow?.inactive,
|
||||||
${when(
|
() => html`
|
||||||
this.workflow?.inactive,
|
<btrix-badge class="inline-block align-middle" variant="warning"
|
||||||
() => html`
|
>${msg("Inactive")}</btrix-badge
|
||||||
<btrix-badge class="inline-block align-middle" variant="warning"
|
>
|
||||||
>${msg("Inactive")}</btrix-badge
|
`,
|
||||||
>
|
)}
|
||||||
`,
|
|
||||||
)}
|
<div class="flex-0 ml-auto flex flex-wrap justify-end gap-2">
|
||||||
</h2>
|
|
||||||
<div class="flex-0 flex justify-end">
|
|
||||||
${when(
|
${when(
|
||||||
this.isCrawler && this.workflow && !this.workflow.inactive,
|
this.isCrawler && this.workflow && !this.workflow.inactive,
|
||||||
this.renderActions,
|
this.renderActions,
|
||||||
@ -559,7 +557,9 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
${this.renderHeader(this.workflow!.id)}
|
${this.renderHeader(this.workflow!.id)}
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h2 class="text-xl font-semibold leading-10">${this.renderName()}</h2>
|
<h2 class="break-all text-xl font-semibold leading-10">
|
||||||
|
${this.renderName()}
|
||||||
|
</h2>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
${when(
|
${when(
|
||||||
@ -593,7 +593,7 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
${when(
|
${when(
|
||||||
this.workflow.isCrawlRunning,
|
this.workflow.isCrawlRunning,
|
||||||
() => html`
|
() => html`
|
||||||
<sl-button-group class="mr-2">
|
<sl-button-group>
|
||||||
<sl-button
|
<sl-button
|
||||||
size="small"
|
size="small"
|
||||||
@click=${() => (this.openDialogName = "stop")}
|
@click=${() => (this.openDialogName = "stop")}
|
||||||
@ -629,7 +629,6 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
<sl-button
|
<sl-button
|
||||||
size="small"
|
size="small"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
class="mr-2"
|
|
||||||
?disabled=${this.orgStorageQuotaReached ||
|
?disabled=${this.orgStorageQuotaReached ||
|
||||||
this.orgExecutionMinutesQuotaReached}
|
this.orgExecutionMinutesQuotaReached}
|
||||||
@click=${() => void this.runNow()}
|
@click=${() => void this.runNow()}
|
||||||
@ -798,22 +797,28 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderName() {
|
private renderName() {
|
||||||
if (!this.workflow) return "";
|
if (!this.workflow)
|
||||||
if (this.workflow.name) return this.workflow.name;
|
return html`<sl-skeleton class="inline-block h-8 w-60"></sl-skeleton>`;
|
||||||
|
if (this.workflow.name)
|
||||||
|
return html`<span class="truncate">${this.workflow.name}</span>`;
|
||||||
const { seedCount, firstSeed } = this.workflow;
|
const { seedCount, firstSeed } = this.workflow;
|
||||||
if (seedCount === 1) {
|
if (seedCount === 1) {
|
||||||
return firstSeed;
|
return html`<span class="truncate">${firstSeed}</span>`;
|
||||||
}
|
}
|
||||||
const remainderCount = seedCount - 1;
|
const remainderCount = seedCount - 1;
|
||||||
if (remainderCount === 1) {
|
if (remainderCount === 1) {
|
||||||
return msg(
|
return msg(
|
||||||
html`${firstSeed}
|
html` <span class="truncate">${firstSeed}</span>
|
||||||
<span class="text-neutral-500">+${remainderCount} URL</span>`,
|
<span class="whitespace-nowrap text-neutral-500"
|
||||||
|
>+${remainderCount} URL</span
|
||||||
|
>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return msg(
|
return msg(
|
||||||
html`${firstSeed}
|
html` <span class="truncate">${firstSeed}</span>
|
||||||
<span class="text-neutral-500">+${remainderCount} URLs</span>`,
|
<span class="whitespace-nowrap text-neutral-500"
|
||||||
|
>+${remainderCount} URLs</span
|
||||||
|
>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1050,7 +1055,6 @@ export class WorkflowDetail extends LiteElement {
|
|||||||
this.workflow?.lastCrawlId,
|
this.workflow?.lastCrawlId,
|
||||||
() => html`
|
() => html`
|
||||||
<sl-button
|
<sl-button
|
||||||
class="mr-2"
|
|
||||||
href=${`${this.orgBasePath}/items/crawl/${
|
href=${`${this.orgBasePath}/items/crawl/${
|
||||||
this.workflow!.lastCrawlId
|
this.workflow!.lastCrawlId
|
||||||
}#replay`}
|
}#replay`}
|
||||||
|
@ -56,6 +56,32 @@ const plurals = {
|
|||||||
id: "comments.plural.other",
|
id: "comments.plural.other",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
URLs: {
|
||||||
|
zero: msg("URLs", {
|
||||||
|
desc: 'plural form of "URLs" for zero URLs',
|
||||||
|
id: "URLs.plural.zero",
|
||||||
|
}),
|
||||||
|
one: msg("URL", {
|
||||||
|
desc: 'singular form for "URL"',
|
||||||
|
id: "URLs.plural.one",
|
||||||
|
}),
|
||||||
|
two: msg("URLs", {
|
||||||
|
desc: 'plural form of "URLs" for two URLs',
|
||||||
|
id: "URLs.plural.two",
|
||||||
|
}),
|
||||||
|
few: msg("URLs", {
|
||||||
|
desc: 'plural form of "URLs" for few URLs',
|
||||||
|
id: "URLs.plural.few",
|
||||||
|
}),
|
||||||
|
many: msg("URLs", {
|
||||||
|
desc: 'plural form of "URLs" for many URLs',
|
||||||
|
id: "URLs.plural.many",
|
||||||
|
}),
|
||||||
|
other: msg("URLs", {
|
||||||
|
desc: 'plural form of "URLs" for multiple/other URLs',
|
||||||
|
id: "URLs.plural.other",
|
||||||
|
}),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pluralOf = (word: keyof typeof plurals, count: number) => {
|
export const pluralOf = (word: keyof typeof plurals, count: number) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user