Prompt user to confirm workflow crawl deletion (#1401)

- Adds confirmation dialog for workflow crawls
- Changes archived item confirmation from default browser dialog to
shoelace dialog
- Increase dialog title size
- Out of scope: Localizes other workflow detail confirmation buttons
- Out of scope: Reword missed "Archive" reference in file uploader
This commit is contained in:
sua yoo 2023-11-22 12:40:49 -08:00 committed by GitHub
parent d64def00c2
commit 006ce5a013
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 42 deletions

View File

@ -1,40 +1,48 @@
import { css } from "lit"; import { css } from "lit";
import SLDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.js"; import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.js";
import dialogStyles from "@shoelace-style/shoelace/dist/components/dialog/dialog.styles.js"; import dialogStyles from "@shoelace-style/shoelace/dist/components/dialog/dialog.styles.js";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
/** /**
* Customized <sl-dialog> * <sl-dialog> with custom CSS
* *
* Usage: see https://shoelace.style/components/dialog * Usage: see https://shoelace.style/components/dialog
*/ */
@customElement("btrix-dialog") @customElement("btrix-dialog")
export class Dialog extends SLDialog { export class Dialog extends SlDialog {
static styles = css` static styles = [
${dialogStyles} .dialog__panel { dialogStyles,
overflow: hidden; css`
} .dialog__panel {
overflow: hidden;
}
.dialog__header { .dialog__header {
background-color: var(--sl-color-neutral-50); background-color: var(--sl-color-neutral-50);
border-bottom: 1px solid var(--sl-color-neutral-100); border-bottom: 1px solid var(--sl-color-neutral-100);
} }
.dialog__title { .dialog__title {
padding-top: var(--sl-spacing-small); padding-top: calc(var(--sl-spacing-small) + 0.2rem);
padding-bottom: var(--sl-spacing-small); padding-bottom: var(--sl-spacing-small);
font-size: var(--sl-font-size-medium); font-size: var(--font-size-base);
font-weight: var(--sl-font-weight-medium); font-weight: var(--sl-font-weight-medium);
} line-height: 1;
}
.dialog__close { .dialog__close {
--header-spacing: var(--sl-spacing-2x-small); --header-spacing: var(--sl-spacing-x-small);
} }
.dialog__footer { .dialog__body {
padding-top: var(--sl-spacing-small); line-height: var(--sl-line-height-normal);
padding-bottom: var(--sl-spacing-small); }
border-top: 1px solid var(--sl-color-neutral-100);
} .dialog__footer {
`; padding-top: var(--sl-spacing-small);
padding-bottom: var(--sl-spacing-small);
border-top: 1px solid var(--sl-color-neutral-100);
}
`,
];
} }

View File

@ -454,7 +454,7 @@ export class FileUploader extends LiteElement {
class="underline hover:no-underline" class="underline hover:no-underline"
href="${this.orgBasePath}/items/upload/${data.id}" href="${this.orgBasePath}/items/upload/${data.id}"
@click="${this.navLink.bind(this)}" @click="${this.navLink.bind(this)}"
>View Archive</a >View Item</a
> `), > `),
variant: "success", variant: "success",
icon: "check2-circle", icon: "check2-circle",

View File

@ -10,7 +10,7 @@ import { CrawlStatus } from "../../components/crawl-status";
import type { PageChangeEvent } from "../../components/pagination"; import type { PageChangeEvent } from "../../components/pagination";
import type { AuthState } from "../../utils/AuthService"; import type { AuthState } from "../../utils/AuthService";
import LiteElement, { html } from "../../utils/LiteElement"; import LiteElement, { html } from "../../utils/LiteElement";
import type { Crawl, CrawlState, Workflow, WorkflowParams } from "./types"; import type { Crawl, CrawlState, Workflow, Upload } from "./types";
import type { APIPaginatedList, APIPaginationQuery } from "../../types/api"; import type { APIPaginatedList, APIPaginationQuery } from "../../types/api";
import { import {
isActive, isActive,
@ -97,11 +97,17 @@ export class CrawlsList extends LiteElement {
private filterBy: Partial<Record<keyof Crawl, any>> = {}; private filterBy: Partial<Record<keyof Crawl, any>> = {};
@state() @state()
private itemToEdit: Crawl | null = null; private itemToEdit: Crawl | Upload | null = null;
@state() @state()
private isEditingItem = false; private isEditingItem = false;
@state()
private itemToDelete: Crawl | Upload | null = null;
@state()
private isDeletingItem = false;
@state() @state()
private isUploadingArchive = false; private isUploadingArchive = false;
@ -439,6 +445,38 @@ export class CrawlsList extends LiteElement {
/* TODO fetch current page or single crawl */ this.fetchArchivedItems /* TODO fetch current page or single crawl */ this.fetchArchivedItems
} }
></btrix-crawl-metadata-editor> ></btrix-crawl-metadata-editor>
<btrix-dialog
label=${msg("Delete Archived Item?")}
?open=${this.isDeletingItem}
@sl-after-hide=${() => (this.isDeletingItem = false)}
>
${msg("This item will be removed from any Collection it is a part of.")}
${when(this.itemToDelete?.type === "crawl", () =>
msg(
"All files and logs associated with this item will also be deleted, and the crawl will no longer be visible in its associated Workflow."
)
)}
<div slot="footer" class="flex justify-between">
<sl-button size="small" autofocus>${msg("Cancel")}</sl-button>
<sl-button
size="small"
variant="danger"
@click=${async () => {
this.isDeletingItem = false;
if (this.itemToDelete) {
await this.deleteItem(this.itemToDelete);
}
}}
>${msg(
str`Delete ${
this.itemToDelete?.type === "upload"
? msg("Upload")
: msg("Crawl")
}`
)}</sl-button
>
</div>
</btrix-dialog>
`; `;
} }
@ -504,7 +542,7 @@ export class CrawlsList extends LiteElement {
<sl-divider></sl-divider> <sl-divider></sl-divider>
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--danger)" style="--sl-color-neutral-700: var(--danger)"
@click=${() => this.deleteItem(item)} @click=${() => this.confirmDeleteItem(item)}
> >
<sl-icon name="trash3" slot="prefix"></sl-icon> <sl-icon name="trash3" slot="prefix"></sl-icon>
${msg("Delete Item")} ${msg("Delete Item")}
@ -658,13 +696,12 @@ export class CrawlsList extends LiteElement {
} }
} }
private async deleteItem(item: Crawl) { private confirmDeleteItem = (item: Crawl | Upload) => {
if ( this.itemToDelete = item;
!window.confirm(msg(str`Are you sure you want to delete ${item.name}?`)) this.isDeletingItem = true;
) { };
return;
}
private async deleteItem(item: Crawl | Upload) {
let apiPath; let apiPath;
switch (this.itemType) { switch (this.itemType) {
@ -691,6 +728,7 @@ export class CrawlsList extends LiteElement {
} }
); );
const { items, ...crawlsData } = this.archivedItems!; const { items, ...crawlsData } = this.archivedItems!;
this.itemToDelete = null;
this.archivedItems = { this.archivedItems = {
...crawlsData, ...crawlsData,
items: items.filter((c) => c.id !== item.id), items: items.filter((c) => c.id !== item.id),
@ -702,6 +740,9 @@ export class CrawlsList extends LiteElement {
}); });
this.fetchArchivedItems(); this.fetchArchivedItems();
} catch (e: any) { } catch (e: any) {
if (this.itemToDelete) {
this.confirmDeleteItem(this.itemToDelete);
}
let message = msg( let message = msg(
str`Sorry, couldn't delete archived item at this time.` str`Sorry, couldn't delete archived item at this time.`
); );

View File

@ -64,7 +64,7 @@ export class WorkflowDetail extends LiteElement {
isCrawler!: boolean; isCrawler!: boolean;
@property({ type: String }) @property({ type: String })
openDialogName?: "scale" | "exclusions" | "cancel" | "stop"; openDialogName?: "scale" | "exclusions" | "cancel" | "stop" | "delete";
@property({ type: String }) @property({ type: String })
initialActivePanel?: Tab; initialActivePanel?: Tab;
@ -107,6 +107,9 @@ export class WorkflowDetail extends LiteElement {
@state() @state()
private isCancelingOrStoppingCrawl: boolean = false; private isCancelingOrStoppingCrawl: boolean = false;
@state()
private crawlToDelete: Crawl | null = null;
@state() @state()
private filterBy: Partial<Record<keyof Crawl, any>> = {}; private filterBy: Partial<Record<keyof Crawl, any>> = {};
@ -321,8 +324,9 @@ export class WorkflowDetail extends LiteElement {
<div slot="footer" class="flex justify-between"> <div slot="footer" class="flex justify-between">
<sl-button <sl-button
size="small" size="small"
autofocus
@click=${() => (this.openDialogName = undefined)} @click=${() => (this.openDialogName = undefined)}
>Keep Crawling</sl-button >${msg("Keep Crawling")}</sl-button
> >
<sl-button <sl-button
size="small" size="small"
@ -332,7 +336,7 @@ export class WorkflowDetail extends LiteElement {
await this.stop(); await this.stop();
this.openDialogName = undefined; this.openDialogName = undefined;
}} }}
>Stop Crawling</sl-button >${msg("Stop Crawling")}</sl-button
> >
</div> </div>
</btrix-dialog> </btrix-dialog>
@ -349,8 +353,9 @@ export class WorkflowDetail extends LiteElement {
<div slot="footer" class="flex justify-between"> <div slot="footer" class="flex justify-between">
<sl-button <sl-button
size="small" size="small"
autofocus
@click=${() => (this.openDialogName = undefined)} @click=${() => (this.openDialogName = undefined)}
>Keep Crawling</sl-button >${msg("Keep Crawling")}</sl-button
> >
<sl-button <sl-button
size="small" size="small"
@ -360,7 +365,37 @@ export class WorkflowDetail extends LiteElement {
await this.cancel(); await this.cancel();
this.openDialogName = undefined; this.openDialogName = undefined;
}} }}
>Cancel & Discard Crawl</sl-button >${msg("Cancel & Discard Crawl")}</sl-button
>
</div>
</btrix-dialog>
<btrix-dialog
label=${msg("Delete Crawl?")}
?open=${this.openDialogName === "delete"}
@sl-request-close=${() => (this.openDialogName = undefined)}
@sl-show=${this.showDialog}
@sl-after-hide=${() => (this.isDialogVisible = false)}
>
${msg(
"All files and logs associated with this crawl will also be deleted, and the crawl will be removed from any Collection it is a part of."
)}
<div slot="footer" class="flex justify-between">
<sl-button
size="small"
autofocus
@click=${() => (this.openDialogName = undefined)}
>${msg("Cancel")}</sl-button
>
<sl-button
size="small"
variant="danger"
@click=${async () => {
this.openDialogName = undefined;
if (this.crawlToDelete) {
await this.deleteCrawl(this.crawlToDelete);
}
}}
>${msg("Delete Crawl")}</sl-button
> >
</div> </div>
</btrix-dialog> </btrix-dialog>
@ -855,7 +890,7 @@ export class WorkflowDetail extends LiteElement {
() => html` <sl-menu slot="menu"> () => html` <sl-menu slot="menu">
<sl-menu-item <sl-menu-item
style="--sl-color-neutral-700: var(--danger)" style="--sl-color-neutral-700: var(--danger)"
@click=${() => this.deleteCrawl(crawl)} @click=${() => this.confirmDeleteCrawl(crawl)}
> >
<sl-icon name="trash3" slot="prefix"></sl-icon> <sl-icon name="trash3" slot="prefix"></sl-icon>
${msg("Delete Crawl")} ${msg("Delete Crawl")}
@ -1644,6 +1679,11 @@ export class WorkflowDetail extends LiteElement {
} }
} }
private confirmDeleteCrawl = (crawl: Crawl) => {
this.crawlToDelete = crawl;
this.openDialogName = "delete";
};
private async deleteCrawl(crawl: Crawl) { private async deleteCrawl(crawl: Crawl) {
try { try {
const data = await this.apiFetch( const data = await this.apiFetch(
@ -1656,6 +1696,7 @@ export class WorkflowDetail extends LiteElement {
}), }),
} }
); );
this.crawlToDelete = null;
this.crawls = { this.crawls = {
...this.crawls!, ...this.crawls!,
items: this.crawls!.items.filter((c) => c.id !== crawl.id), items: this.crawls!.items.filter((c) => c.id !== crawl.id),
@ -1667,6 +1708,10 @@ export class WorkflowDetail extends LiteElement {
}); });
this.fetchCrawls(); this.fetchCrawls();
} catch (e: any) { } catch (e: any) {
if (this.crawlToDelete) {
this.confirmDeleteCrawl(this.crawlToDelete);
}
let message = msg( let message = msg(
str`Sorry, couldn't delete archived item at this time.` str`Sorry, couldn't delete archived item at this time.`
); );

View File

@ -23,6 +23,7 @@ const theme = css`
/* Custom font variables */ /* Custom font variables */
--font-monostyle-family: var(--sl-font-mono); --font-monostyle-family: var(--sl-font-mono);
--font-monostyle-variation: "MONO" 0.51, "CASL" 0, "slnt" 0, "CRSV" 0; --font-monostyle-variation: "MONO" 0.51, "CASL" 0, "slnt" 0, "CRSV" 0;
--font-size-base: 1rem;
/* /*
* Shoelace Theme Tokens * Shoelace Theme Tokens