From 22e72bfe6d7b50e4474302d51ae25e4272dabe7c Mon Sep 17 00:00:00 2001 From: sua yoo Date: Thu, 24 Jul 2025 16:41:01 -0700 Subject: [PATCH 1/6] add example --- frontend/src/stories/design/action-menus.mdx | 9 +++- .../design/examples/ActionMenu.stories.ts | 45 +++++++++++++++++++ frontend/src/theme.stylesheet.css | 13 ++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 frontend/src/stories/design/examples/ActionMenu.stories.ts diff --git a/frontend/src/stories/design/action-menus.mdx b/frontend/src/stories/design/action-menus.mdx index c7603a046b..69fa279c75 100644 --- a/frontend/src/stories/design/action-menus.mdx +++ b/frontend/src/stories/design/action-menus.mdx @@ -1,7 +1,10 @@ -import { Meta } from "@storybook/blocks"; +import { Meta, Canvas } from '@storybook/addon-docs/blocks'; +import { WorkflowActionMenu } from './examples/ActionMenu.stories'; + + # Action Menus While controls are always placed next to the most relevant content area, we @@ -27,3 +30,7 @@ should be ordered as follows: | Actions related to exporting/downloading (eg: download archived item) | | Copy information to clipboard (eg: copy share link)
Copy IDs to clipboard (eg: copy item ID) | | Destructive actions (eg: delete item) | + +## Example + + diff --git a/frontend/src/stories/design/examples/ActionMenu.stories.ts b/frontend/src/stories/design/examples/ActionMenu.stories.ts new file mode 100644 index 0000000000..8a26fe2b07 --- /dev/null +++ b/frontend/src/stories/design/examples/ActionMenu.stories.ts @@ -0,0 +1,45 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; + +const render = () => html` + + + + Run Crawl + + + + + Edit Workflow Settings + + + + Duplicate Workflow + + + + + Copy Tags + + + + Copy Workflow ID + + + + + Delete Workflow + + +`; + +const meta: Meta = { + component: "sl-menu", + tags: ["!dev"], // Hide from story navigation + render, +}; + +export default meta; +type Story = StoryObj; + +export const WorkflowActionMenu: Story = {}; diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 82512fc087..2a765bab7a 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -230,6 +230,19 @@ border: 1px solid var(--sl-panel-border-color); } + sl-menu-item { + @apply part-[base]:text-neutral-700 part-[base]:hover:bg-cyan-50/50 part-[base]:focus-visible:bg-cyan-50/50; + } + + /* Add menu item variants */ + .menu-item-success { + @apply part-[base]:text-success part-[base]:hover:bg-success-50 part-[base]:focus-visible:bg-success-50; + } + + .menu-item-danger { + @apply part-[base]:text-danger part-[base]:hover:bg-danger-50 part-[base]:focus-visible:bg-danger-50; + } + /* Validation styles */ /** * FIXME Use [data-user-invalid] selector exclusion table is migrated From 14c73aed19f05823fd3452a100426721825901d2 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Thu, 24 Jul 2025 18:40:36 -0700 Subject: [PATCH 2/6] move to component --- frontend/package.json | 2 +- .../src/components/ui/overflow-dropdown.ts | 5 +- .../src/features/crawl-workflows/index.ts | 1 + .../btrix-select-action.ts | 9 + .../workflow-action-menu/types.ts | 10 + .../workflow-action-menu.ts | 260 ++++++++++++++++++ frontend/src/pages/org/workflow-detail.ts | 229 +++------------ frontend/src/pages/org/workflows-list.ts | 54 +++- frontend/src/theme.stylesheet.css | 10 +- frontend/yarn.lock | 102 +++---- 10 files changed, 441 insertions(+), 241 deletions(-) create mode 100644 frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts create mode 100644 frontend/src/features/crawl-workflows/workflow-action-menu/types.ts create mode 100644 frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts diff --git a/frontend/package.json b/frontend/package.json index b2c4c95b64..4619e40e95 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -80,7 +80,7 @@ "slugify": "^1.6.6", "style-loader": "^3.3.0", "tabbable": "^6.2.0", - "tailwindcss": "^3.4.1", + "tailwindcss": "^3.4.17", "terser-webpack-plugin": "^5.3.10", "thread-loader": "^4.0.4", "tlds": "^1.259.0", diff --git a/frontend/src/components/ui/overflow-dropdown.ts b/frontend/src/components/ui/overflow-dropdown.ts index 79c5b52646..fe92af283f 100644 --- a/frontend/src/components/ui/overflow-dropdown.ts +++ b/frontend/src/components/ui/overflow-dropdown.ts @@ -40,7 +40,10 @@ export class OverflowDropdown extends TailwindElement { @query("sl-dropdown") private readonly dropdown?: SlDropdown; - @queryAssignedElements({ selector: "sl-menu", flatten: true }) + @queryAssignedElements({ + selector: "sl-menu, btrix-workflow-action-menu", + flatten: true, + }) private readonly menu!: SlMenu[]; render() { diff --git a/frontend/src/features/crawl-workflows/index.ts b/frontend/src/features/crawl-workflows/index.ts index dc074e1560..57a46ba5a5 100644 --- a/frontend/src/features/crawl-workflows/index.ts +++ b/frontend/src/features/crawl-workflows/index.ts @@ -5,6 +5,7 @@ import("./link-selector-table"); import("./new-workflow-dialog"); import("./queue-exclusion-form"); import("./queue-exclusion-table"); +import("./workflow-action-menu/workflow-action-menu"); import("./workflow-editor"); import("./workflow-list"); import("./workflow-schedule-filter"); diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts new file mode 100644 index 0000000000..23475eb187 --- /dev/null +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts @@ -0,0 +1,9 @@ +import type { Action } from "./types"; + +export type BtrixSelectActionEvent = CustomEvent<{ action: Action }>; + +declare global { + interface GlobalEventHandlersEventMap { + "btrix-select-action": BtrixSelectActionEvent; + } +} diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts new file mode 100644 index 0000000000..3078779d91 --- /dev/null +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts @@ -0,0 +1,10 @@ +export enum Action { + Run = "run", + TogglePauseResume = "togglePauseResume", + Stop = "stop", + Cancel = "cancel", + EditBrowserWindows = "editBrowserWindows", + EditExclusions = "editExclusions", + Duplicate = "duplicate", + Delete = "delete", +} diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts new file mode 100644 index 0000000000..0ff0b34c46 --- /dev/null +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts @@ -0,0 +1,260 @@ +import { localized, msg } from "@lit/localize"; +import type { SlSelectEvent } from "@shoelace-style/shoelace"; +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { when } from "lit/directives/when.js"; + +import type { BtrixSelectActionEvent } from "./btrix-select-action"; +import { Action } from "./types"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { ClipboardController } from "@/controllers/clipboard"; +import { WorkflowTab } from "@/routes"; +import type { Crawl, ListWorkflow, Workflow } from "@/types/crawler"; +import { isNotFailed, isSuccessfullyFinished } from "@/utils/crawler"; +import { isArchivingDisabled } from "@/utils/orgs"; + +/** + * @fires btrix-select-action + */ +@customElement("btrix-workflow-action-menu") +@localized() +export class Component extends BtrixElement { + @property({ type: Object }) + workflow?: ListWorkflow | Workflow; + + @property({ type: Object }) + latestCrawl?: Crawl | null; + + @property({ type: Object }) + logTotals?: { errors: number; behaviors: number } | null; + + @property({ type: Boolean }) + hidePauseResume?: boolean; + + @property({ type: Boolean }) + disablePauseResume?: boolean; + + @property({ type: Boolean }) + cancelingRun?: boolean; + + render() { + const workflow = this.workflow; + + if (!workflow) return; + + const canCrawl = this.appState.isCrawler; + const archivingDisabled = isArchivingDisabled(this.org, true); + const paused = workflow.lastCrawlState === "paused"; + const crawling = + workflow.isCrawlRunning && + !workflow.lastCrawlStopping && + !workflow.lastCrawlShouldPause && + workflow.lastCrawlState === "running"; + + return html` { + e.stopPropagation(); + const action = e.detail.item.dataset["action"]; + + if (action) { + this.dispatchEvent( + new CustomEvent( + "btrix-select-action", + { + detail: { action: action as Action }, + }, + ), + ); + } + }} + > + ${when( + canCrawl, + () => + html`${when( + workflow.isCrawlRunning, + () => html` + ${when(!this.hidePauseResume && !this.disablePauseResume, () => + paused + ? html` + + + ${msg("Resume Crawl")} + + ` + : html` + + + ${msg("Pause Crawl")} + + `, + )} + + + + ${msg("Stop Crawl")} + + + + ${msg(html`Cancel & Discard Crawl`)} + + `, + () => html` + + + ${msg("Run Crawl")} + + `, + )} `, + )} + ${when( + canCrawl, + () => + html`${when( + workflow.isCrawlRunning && + !workflow.lastCrawlStopping && + !this.cancelingRun, + () => html` + + + ${msg("Edit Browser Windows")} + + + + ${msg("Edit Exclusions")} + + `, + )} + + this.navigate.to( + `/orgs/${this.appState.orgSlug}/workflows/${workflow.id}?edit`, + )} + > + + ${msg("Edit Workflow Settings")} + + + + ${msg("Duplicate Workflow")} + + `, + )} + ${when( + this.latestCrawl && isNotFailed(this.latestCrawl), + () => html` + + + ${msg("Latest Crawl")} + ${this.renderLatestCrawlMenu(this.latestCrawl!)} + + + `, + )} + ${when( + workflow.tags.length, + () => + html` + ClipboardController.copyToClipboard(workflow.tags.join(", "))} + > + + ${msg("Copy Tags")} + `, + )} + + ClipboardController.copyToClipboard(workflow.id)} + > + + ${msg("Copy Workflow ID")} + + + ${when( + canCrawl && !workflow.crawlCount, + () => html` + + + ${msg("Delete Workflow")} + + `, + )} + `; + } + + private renderLatestCrawlMenu(latestCrawl: Crawl) { + const authToken = this.authState?.headers.Authorization.split(" ")[1]; + const logTotals = this.logTotals; + + return html` + + + + ${msg("Download Item")} + ${latestCrawl.fileSize + ? html` ${this.localize.bytes(latestCrawl.fileSize)}` + : nothing} + + + + + ${msg("Download Log")} + + + ${when( + isSuccessfullyFinished(latestCrawl), + () => html` + + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${latestCrawl.cid}/${WorkflowTab.Crawls}/${latestCrawl.id}`, + )} + > + + ${msg("View Item Details")} + + `, + )} + ClipboardController.copyToClipboard(latestCrawl.id)} + > + + ${msg("Copy Item ID")} + + + `; + } +} diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 4b7948f28b..5d5ab4ccb7 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -23,6 +23,8 @@ import { import { ClipboardController } from "@/controllers/clipboard"; import { CrawlStatus } from "@/features/archived-items/crawl-status"; import { ExclusionEditor } from "@/features/crawl-workflows/exclusion-editor"; +import type { BtrixSelectActionEvent } from "@/features/crawl-workflows/workflow-action-menu/btrix-select-action"; +import { Action } from "@/features/crawl-workflows/workflow-action-menu/types"; import { pageError } from "@/layouts/pageError"; import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import { WorkflowTab } from "@/routes"; @@ -900,7 +902,6 @@ export class WorkflowDetail extends BtrixElement { private readonly renderActions = () => { if (!this.workflow) return; - const workflow = this.workflow; const archivingDisabled = isArchivingDisabled(this.org, true); const cancelStopLoading = this.isCancelingRun; @@ -909,6 +910,7 @@ export class WorkflowDetail extends BtrixElement { const hidePauseResume = !this.lastCrawlId || this.isCancelingRun || + this.workflow.lastCrawlState === "starting" || this.workflow.lastCrawlStopping; const disablePauseResume = this.disablePauseResume || @@ -974,195 +976,50 @@ export class WorkflowDetail extends BtrixElement { ${msg("Actions")} - - ${when( - workflow.isCrawlRunning, - // HACK shoelace doesn't current have a way to override non-hover - // color without resetting the --sl-color-neutral-700 variable - () => html` - ${when(!hidePauseResume && !disablePauseResume, () => - paused - ? html` - void this.pauseResumeTask.run()} - > - - ${msg("Resume Crawl")} - - ` - : html` - void this.pauseResumeTask.run()} - > - - ${msg("Pause Crawl")} - - `, - )} - - (this.openDialogName = "stop")} - ?disabled=${workflow.lastCrawlStopping || this.isCancelingRun} - > - - ${msg("Stop Crawl")} - - (this.openDialogName = "cancel")} - > - - ${msg(html`Cancel & Discard Crawl`)} - - `, - () => html` - void this.runNowTask.run()} - > - - ${msg("Run Crawl")} - - `, - )} - - ${when( - workflow.isCrawlRunning && !workflow.lastCrawlStopping, - () => html` - (this.openDialogName = "scale")}> - - ${msg("Edit Browser Windows")} - - (this.openDialogName = "exclusions")} - ?disabled=${!this.isCrawling} - > - - ${msg("Edit Exclusions")} - - `, - )} - - this.navigate.to( - `/orgs/${this.appState.orgSlug}/workflows/${workflow.id}?edit`, - )} - > - - ${msg("Edit Workflow Settings")} - - void this.duplicateConfig()} - > - - ${msg("Duplicate Workflow")} - - ${when( - workflow.lastCrawlId, - () => html` - - - ${this.tabLabels.latest} ${this.renderLatestCrawlMenu()} - - `, - )} - - - ClipboardController.copyToClipboard(workflow.tags.join(", "))} - ?disabled=${!workflow.tags.length} - > - - ${msg("Copy Tags")} - - ClipboardController.copyToClipboard(workflow.id)} - > - - ${msg("Copy Workflow ID")} - - - ${when( - !workflow.crawlCount, - () => html` - - (this.openDialogName = "delete")} - > - - ${msg("Delete Workflow")} - - `, - )} - + `; }; - private renderLatestCrawlMenu() { - const authToken = this.authState?.headers.Authorization.split(" ")[1]; - const latestCrawl = this.latestCrawlTask.value; - const logTotals = this.logTotalsTask.value; - - return html` - - - - ${msg("Download Item")} - ${latestCrawl?.fileSize - ? html` ${this.localize.bytes(latestCrawl.fileSize)}` - : nothing} - - - - - ${msg("Download Log")} - - - - - ${when( - this.archivedItemId, - (id) => html` - - this.navigate.to( - `${this.basePath}/${WorkflowTab.Crawls}/${id}`, - )} - > - - ${msg("View Item Details")} - - `, - )} - - ClipboardController.copyToClipboard(this.lastCrawlId || "")} - ?disabled=${!this.lastCrawlId} - > - - ${msg("Copy Item ID")} - - - `; - } + private readonly onSelectAction = (e: BtrixSelectActionEvent) => { + switch (e.detail.action) { + case Action.Run: + void this.runNowTask.run(); + break; + case Action.TogglePauseResume: + void this.pauseResumeTask.run(); + break; + case Action.Stop: + this.openDialogName = "stop"; + break; + case Action.Cancel: + this.openDialogName = "cancel"; + break; + case Action.EditBrowserWindows: + this.openDialogName = "scale"; + break; + case Action.EditExclusions: + this.openDialogName = "exclusions"; + break; + case Action.Duplicate: + void this.duplicateConfig(); + break; + case Action.Delete: + this.openDialogName = "delete"; + break; + default: + console.debug("unknown workflow action:", e.detail.action); + break; + } + }; private renderDetails() { const relativeDate = ( diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index eb89203ec0..3f7fd4ddd0 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -24,6 +24,8 @@ import { type SelectEvent } from "@/components/ui/search-combobox"; import { ClipboardController } from "@/controllers/clipboard"; import { SearchParamsController } from "@/controllers/searchParams"; import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog"; +import type { BtrixSelectActionEvent } from "@/features/crawl-workflows/workflow-action-menu/btrix-select-action"; +import { Action } from "@/features/crawl-workflows/workflow-action-menu/types"; import { type BtrixChangeWorkflowProfileFilterEvent } from "@/features/crawl-workflows/workflow-profile-filter"; import type { BtrixChangeWorkflowScheduleFilterEvent } from "@/features/crawl-workflows/workflow-schedule-filter"; import type { BtrixChangeWorkflowTagFilterEvent } from "@/features/crawl-workflows/workflow-tag-filter"; @@ -794,11 +796,59 @@ export class WorkflowsList extends BtrixElement { private readonly renderWorkflowItem = (workflow: ListWorkflow) => html` - ${this.renderMenuItems(workflow)} + { + switch (e.detail.action) { + case Action.Run: + void this.runNow(workflow); + break; + case Action.TogglePauseResume: + // TODO + break; + case Action.Stop: + void this.stop(workflow.lastCrawlId); + break; + case Action.Cancel: + void this.cancel(workflow.lastCrawlId); + break; + case Action.EditBrowserWindows: + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${workflow.id}/${WorkflowTab.LatestCrawl}`, + { + dialog: "scale", + }, + ); + break; + case Action.EditExclusions: + this.navigate.to( + `${this.navigate.orgBasePath}/workflows/${workflow.id}/${WorkflowTab.LatestCrawl}`, + { + dialog: "exclusions", + }, + ); + break; + case Action.Duplicate: + void this.duplicateConfig(workflow); + break; + case Action.Delete: { + this.workflowToDelete = workflow; + await this.updateComplete; + void this.deleteDialog?.show(); + break; + } + default: + console.debug("unknown workflow action:", e.detail.action); + break; + } + }} + > `; - private renderMenuItems(workflow: ListWorkflow) { + private renderMenu(workflow: ListWorkflow) { return html` ${when( workflow.isCrawlRunning && this.appState.isCrawler, diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 2a765bab7a..2d0626b1b4 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -230,17 +230,19 @@ border: 1px solid var(--sl-panel-border-color); } - sl-menu-item { - @apply part-[base]:text-neutral-700 part-[base]:hover:bg-cyan-50/50 part-[base]:focus-visible:bg-cyan-50/50; + /* Adjust menu item hover and focus styles */ + sl-menu-item, + btrix-menu-item-link { + @apply part-[base]:hover:bg-cyan-50/50 part-[base]:hover:text-cyan-700 part-[base]:focus-visible:bg-cyan-50/50; } /* Add menu item variants */ .menu-item-success { - @apply part-[base]:text-success part-[base]:hover:bg-success-50 part-[base]:focus-visible:bg-success-50; + @apply part-[base]:text-success part-[base]:hover:bg-success-50 part-[base]:hover:text-success-700 part-[base]:focus-visible:bg-success-50; } .menu-item-danger { - @apply part-[base]:text-danger part-[base]:hover:bg-danger-50 part-[base]:focus-visible:bg-danger-50; + @apply part-[base]:text-danger part-[base]:hover:bg-danger-50 part-[base]:hover:text-danger-700 part-[base]:focus-visible:bg-danger-50; } /* Validation styles */ diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7f598638fe..2111a96df2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -6003,7 +6003,7 @@ fast-glob@^3.1.1: merge2 "^1.3.0" micromatch "^4.0.8" -fast-glob@^3.2.11, fast-glob@^3.2.2, fast-glob@^3.3.0, fast-glob@^3.3.1, fast-glob@^3.3.2: +fast-glob@^3.2.11, fast-glob@^3.2.2, fast-glob@^3.3.1, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -7288,10 +7288,10 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jiti@^1.19.1: - version "1.21.0" - resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" - integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +jiti@^1.21.6: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== js-levenshtein-esm@^1.2.0: version "1.2.0" @@ -7525,7 +7525,7 @@ lighthouse-logger@^1.0.0: debug "^2.6.9" marky "^1.2.2" -lilconfig@2.1.0, lilconfig@^2.1.0: +lilconfig@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== @@ -7540,6 +7540,11 @@ lilconfig@^3.0.0: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc" integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== +lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -8430,12 +8435,7 @@ nanoid@^3.1.25, nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -nanoid@^3.3.8: +nanoid@^3.3.11, nanoid@^3.3.8: version "3.3.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== @@ -9050,7 +9050,7 @@ postcss-lit@^1.1.1: "@babel/traverse" "^7.16.0" lilconfig "^2.0.6" -postcss-load-config@^4.0.1: +postcss-load-config@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== @@ -9116,14 +9116,14 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-nested@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" - integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== +postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== dependencies: - postcss-selector-parser "^6.0.11" + postcss-selector-parser "^6.1.1" -postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.11" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz" integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== @@ -9131,6 +9131,14 @@ postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selecto cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-selector-parser@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262" @@ -9153,15 +9161,6 @@ postcss@^8.4.19, postcss@^8.4.5: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.23: - version "8.4.33" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" - integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.0.2" - postcss@^8.4.33: version "8.5.3" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" @@ -9171,6 +9170,15 @@ postcss@^8.4.33: picocolors "^1.1.1" source-map-js "^1.2.1" +postcss@^8.4.47: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + postinstall-postinstall@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" @@ -9636,7 +9644,7 @@ resolve@^1.1.7, resolve@^1.17.0, resolve@^1.19.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.20.0: +resolve@^1.20.0, resolve@^1.22.8: version "1.22.10" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== @@ -9645,7 +9653,7 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4: +resolve@^1.22.1, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -10408,7 +10416,7 @@ style-mod@^4.0.0, style-mod@^4.1.0, style-mod@^4.1.2: resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.2.tgz#ca238a1ad4786520f7515a8539d5a63691d7bf67" integrity sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw== -sucrase@^3.32.0: +sucrase@^3.35.0: version "3.35.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== @@ -10469,33 +10477,33 @@ table-layout@^1.0.2: typical "^5.2.0" wordwrapjs "^4.0.0" -tailwindcss@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" - integrity sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA== +tailwindcss@^3.4.17: + version "3.4.17" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.17.tgz#ae8406c0f96696a631c790768ff319d46d5e5a63" + integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" - chokidar "^3.5.3" + chokidar "^3.6.0" didyoumean "^1.2.2" dlv "^1.1.3" - fast-glob "^3.3.0" + fast-glob "^3.3.2" glob-parent "^6.0.2" is-glob "^4.0.3" - jiti "^1.19.1" - lilconfig "^2.1.0" - micromatch "^4.0.5" + jiti "^1.21.6" + lilconfig "^3.1.3" + micromatch "^4.0.8" normalize-path "^3.0.0" object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.23" + picocolors "^1.1.1" + postcss "^8.4.47" postcss-import "^15.1.0" postcss-js "^4.0.1" - postcss-load-config "^4.0.1" - postcss-nested "^6.0.1" - postcss-selector-parser "^6.0.11" - resolve "^1.22.2" - sucrase "^3.32.0" + postcss-load-config "^4.0.2" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" tapable@^1.0.0: version "1.1.3" From c1804ddf9612a5100fb396b131665c8f5de20f97 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Fri, 25 Jul 2025 11:34:19 -0700 Subject: [PATCH 3/6] focus on select --- .../src/components/ui/overflow-dropdown.ts | 7 +++++- frontend/src/events/btrix-select.ts | 7 ++++++ .../btrix-select-action.ts | 9 ------- .../workflow-action-menu/types.ts | 4 +++ .../workflow-action-menu.ts | 25 +++++++------------ frontend/src/pages/org/workflow-detail.ts | 25 +++++++++++++------ frontend/src/pages/org/workflows-list.ts | 12 +++++---- 7 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 frontend/src/events/btrix-select.ts delete mode 100644 frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts diff --git a/frontend/src/components/ui/overflow-dropdown.ts b/frontend/src/components/ui/overflow-dropdown.ts index fe92af283f..bafc589fc3 100644 --- a/frontend/src/components/ui/overflow-dropdown.ts +++ b/frontend/src/components/ui/overflow-dropdown.ts @@ -11,6 +11,7 @@ import { import { ifDefined } from "lit/directives/if-defined.js"; import { TailwindElement } from "@/classes/TailwindElement"; +import type { WorkflowActionMenu } from "@/features/crawl-workflows/workflow-action-menu/workflow-action-menu"; /** * Dropdown for additional actions. @@ -44,7 +45,7 @@ export class OverflowDropdown extends TailwindElement { selector: "sl-menu, btrix-workflow-action-menu", flatten: true, }) - private readonly menu!: SlMenu[]; + private readonly menu!: (SlMenu | WorkflowActionMenu)[]; render() { return html` @@ -52,6 +53,10 @@ export class OverflowDropdown extends TailwindElement { ?disabled=${!this.hasMenuItems} hoist distance=${ifDefined(this.raised ? "4" : undefined)} + @btrix-select=${() => { + void this.dropdown?.hide(); + this.dropdown?.focusOnTrigger(); + }} > = CustomEvent<{ item: T }>; + +declare global { + interface GlobalEventHandlersEventMap { + "btrix-select": BtrixSelectEvent; + } +} diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts deleted file mode 100644 index 23475eb187..0000000000 --- a/frontend/src/features/crawl-workflows/workflow-action-menu/btrix-select-action.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Action } from "./types"; - -export type BtrixSelectActionEvent = CustomEvent<{ action: Action }>; - -declare global { - interface GlobalEventHandlersEventMap { - "btrix-select-action": BtrixSelectActionEvent; - } -} diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts index 3078779d91..0a23b6cec4 100644 --- a/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/types.ts @@ -1,3 +1,5 @@ +import type { BtrixSelectEvent } from "@/events/btrix-select"; + export enum Action { Run = "run", TogglePauseResume = "togglePauseResume", @@ -8,3 +10,5 @@ export enum Action { Duplicate = "duplicate", Delete = "delete", } + +export type BtrixSelectActionEvent = BtrixSelectEvent<{ action: Action }>; diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts index 0ff0b34c46..880c6f1173 100644 --- a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts @@ -4,8 +4,7 @@ import { html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { when } from "lit/directives/when.js"; -import type { BtrixSelectActionEvent } from "./btrix-select-action"; -import { Action } from "./types"; +import { Action, type BtrixSelectActionEvent } from "./types"; import { BtrixElement } from "@/classes/BtrixElement"; import { ClipboardController } from "@/controllers/clipboard"; @@ -14,12 +13,9 @@ import type { Crawl, ListWorkflow, Workflow } from "@/types/crawler"; import { isNotFailed, isSuccessfullyFinished } from "@/utils/crawler"; import { isArchivingDisabled } from "@/utils/orgs"; -/** - * @fires btrix-select-action - */ @customElement("btrix-workflow-action-menu") @localized() -export class Component extends BtrixElement { +export class WorkflowActionMenu extends BtrixElement { @property({ type: Object }) workflow?: ListWorkflow | Workflow; @@ -57,16 +53,13 @@ export class Component extends BtrixElement { e.stopPropagation(); const action = e.detail.item.dataset["action"]; - if (action) { - this.dispatchEvent( - new CustomEvent( - "btrix-select-action", - { - detail: { action: action as Action }, - }, - ), - ); - } + this.dispatchEvent( + new CustomEvent("btrix-select", { + detail: { item: { ...e.detail.item, action: action as Action } }, + bubbles: true, + composed: true, + }), + ); }} > ${when( diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts index 5d5ab4ccb7..a99289267e 100644 --- a/frontend/src/pages/org/workflow-detail.ts +++ b/frontend/src/pages/org/workflow-detail.ts @@ -1,6 +1,6 @@ import { localized, msg, str } from "@lit/localize"; import { Task, TaskStatus } from "@lit/task"; -import type { SlSelect } from "@shoelace-style/shoelace"; +import type { SlDropdown, SlSelect } from "@shoelace-style/shoelace"; import clsx from "clsx"; import { html, nothing, type PropertyValues, type TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; @@ -23,8 +23,10 @@ import { import { ClipboardController } from "@/controllers/clipboard"; import { CrawlStatus } from "@/features/archived-items/crawl-status"; import { ExclusionEditor } from "@/features/crawl-workflows/exclusion-editor"; -import type { BtrixSelectActionEvent } from "@/features/crawl-workflows/workflow-action-menu/btrix-select-action"; -import { Action } from "@/features/crawl-workflows/workflow-action-menu/types"; +import { + Action, + type BtrixSelectActionEvent, +} from "@/features/crawl-workflows/workflow-action-menu/types"; import { pageError } from "@/layouts/pageError"; import { pageNav, type Breadcrumb } from "@/layouts/pageHeader"; import { WorkflowTab } from "@/routes"; @@ -972,7 +974,12 @@ export class WorkflowDetail extends BtrixElement { this.renderRunNowButton, )} - + ${msg("Actions")} @@ -983,14 +990,15 @@ export class WorkflowDetail extends BtrixElement { ?hidePauseResume=${hidePauseResume} ?disablePauseResume=${disablePauseResume} ?cancelingRun=${this.isCancelingRun} - @btrix-select-action=${this.onSelectAction} > `; }; private readonly onSelectAction = (e: BtrixSelectActionEvent) => { - switch (e.detail.action) { + const dropdown = e.currentTarget as SlDropdown; + + switch (e.detail.item.action) { case Action.Run: void this.runNowTask.run(); break; @@ -1016,9 +1024,12 @@ export class WorkflowDetail extends BtrixElement { this.openDialogName = "delete"; break; default: - console.debug("unknown workflow action:", e.detail.action); + console.debug("unknown workflow action:", e.detail.item.action); break; } + + void dropdown.hide(); + dropdown.focusOnTrigger(); }; private renderDetails() { diff --git a/frontend/src/pages/org/workflows-list.ts b/frontend/src/pages/org/workflows-list.ts index 3f7fd4ddd0..857fd69a7c 100644 --- a/frontend/src/pages/org/workflows-list.ts +++ b/frontend/src/pages/org/workflows-list.ts @@ -24,8 +24,10 @@ import { type SelectEvent } from "@/components/ui/search-combobox"; import { ClipboardController } from "@/controllers/clipboard"; import { SearchParamsController } from "@/controllers/searchParams"; import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog"; -import type { BtrixSelectActionEvent } from "@/features/crawl-workflows/workflow-action-menu/btrix-select-action"; -import { Action } from "@/features/crawl-workflows/workflow-action-menu/types"; +import { + Action, + type BtrixSelectActionEvent, +} from "@/features/crawl-workflows/workflow-action-menu/types"; import { type BtrixChangeWorkflowProfileFilterEvent } from "@/features/crawl-workflows/workflow-profile-filter"; import type { BtrixChangeWorkflowScheduleFilterEvent } from "@/features/crawl-workflows/workflow-schedule-filter"; import type { BtrixChangeWorkflowTagFilterEvent } from "@/features/crawl-workflows/workflow-tag-filter"; @@ -800,8 +802,8 @@ export class WorkflowsList extends BtrixElement { slot="menu" .workflow=${workflow} hidePauseResume - @btrix-select-action=${async (e: BtrixSelectActionEvent) => { - switch (e.detail.action) { + @btrix-select=${async (e: BtrixSelectActionEvent) => { + switch (e.detail.item.action) { case Action.Run: void this.runNow(workflow); break; @@ -840,7 +842,7 @@ export class WorkflowsList extends BtrixElement { break; } default: - console.debug("unknown workflow action:", e.detail.action); + console.debug("unknown workflow action:", e.detail.item.action); break; } }} From 324581f5864c78f1abd62376893cf43038f7ff27 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Fri, 25 Jul 2025 12:55:18 -0700 Subject: [PATCH 4/6] document component --- .../api/orgs/[id]/crawlconfigs/[id].js | 77 ++++++++++ .../workflow-action-menu/index.ts | 1 + .../workflow-action-menu.ts | 66 +++++---- .../src/stories/decorators/orgDecorator.ts | 2 + frontend/src/stories/design/action-menus.mdx | 6 +- .../design/examples/ActionMenu.stories.ts | 45 ------ .../WorkflowActionMenu.stories.ts | 132 ++++++++++++++++++ frontend/src/theme.stylesheet.css | 2 +- 8 files changed, 253 insertions(+), 78 deletions(-) create mode 100644 frontend/src/__mocks__/api/orgs/[id]/crawlconfigs/[id].js create mode 100644 frontend/src/features/crawl-workflows/workflow-action-menu/index.ts delete mode 100644 frontend/src/stories/design/examples/ActionMenu.stories.ts create mode 100644 frontend/src/stories/features/crawl-workflows/WorkflowActionMenu.stories.ts diff --git a/frontend/src/__mocks__/api/orgs/[id]/crawlconfigs/[id].js b/frontend/src/__mocks__/api/orgs/[id]/crawlconfigs/[id].js new file mode 100644 index 0000000000..e161780b35 --- /dev/null +++ b/frontend/src/__mocks__/api/orgs/[id]/crawlconfigs/[id].js @@ -0,0 +1,77 @@ +import user from "@/__mocks__/api/users/me"; + +// API v1.18.0 +export default { + name: "", + description: null, + created: "2025-01-23T06:23:32Z", + createdBy: user.id, + modified: "2025-01-28T19:53:15Z", + modifiedBy: user.id, + autoAddCollections: [], + inactive: false, + rev: 2, + crawlAttemptCount: 3, + crawlCount: 3, + crawlSuccessfulCount: 3, + totalSize: 61275, + lastCrawlId: "manual-20250205210838-a5e3cf23-f3c", + lastCrawlStartTime: "2025-02-05T21:08:38Z", + lastStartedBy: user.id, + lastCrawlTime: "2025-02-05T21:09:20Z", + lastCrawlState: "complete", + lastCrawlSize: 61275, + lastRun: "2025-02-05T21:09:20Z", + isCrawlRunning: false, + crawlFilenameTemplate: null, + id: "a5f3cf23-f3ce-4ceb-a232-f2946185370f", + schedule: "", + jobType: "custom", + config: { + seeds: null, + seedFileId: null, + scopeType: "page", + include: null, + exclude: [], + depth: -1, + limit: null, + extraHops: 0, + lang: "en", + blockAds: false, + behaviorTimeout: null, + pageLoadTimeout: null, + pageExtraDelay: null, + postLoadDelay: null, + workers: null, + headless: null, + generateWACZ: null, + combineWARC: null, + useSitemap: false, + failOnFailedSeed: false, + logging: null, + behaviors: "autoscroll,autoplay,autofetch,siteSpecific", + customBehaviors: [], + userAgent: "", + selectLinks: ["a[href]->href"], + clickSelector: "a", + }, + tags: [], + crawlTimeout: 0, + maxCrawlSize: 0, + scale: 1, + browserWindows: 4, + oid: "x_example_org_id_x", + profileid: null, + crawlerChannel: "beta", + proxyId: null, + firstSeed: "https://example.com/", + seedCount: 1, + lastCrawlStopping: false, + lastCrawlShouldPause: false, + lastCrawlPausedAt: null, + lastCrawlPausedExpiry: null, + profileName: null, + createdByName: user.name, + modifiedByName: user.name, + lastStartedByName: user.name, +}; diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/index.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/index.ts new file mode 100644 index 0000000000..be6c1dd2d0 --- /dev/null +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/index.ts @@ -0,0 +1 @@ +export { WorkflowActionMenu } from "./workflow-action-menu"; diff --git a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts index 880c6f1173..87093a59de 100644 --- a/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts +++ b/frontend/src/features/crawl-workflows/workflow-action-menu/workflow-action-menu.ts @@ -68,23 +68,34 @@ export class WorkflowActionMenu extends BtrixElement { html`${when( workflow.isCrawlRunning, () => html` - ${when(!this.hidePauseResume && !this.disablePauseResume, () => - paused - ? html` - - - ${msg("Resume Crawl")} - - ` - : html` - - - ${msg("Pause Crawl")} - - `, + ${when( + !this.hidePauseResume && + !this.disablePauseResume && + !this.cancelingRun, + () => + paused + ? html` + + + ${msg("Resume Crawl")} + + ` + : html` + + + ${msg("Pause Crawl")} + + `, )} `, )} - ${when( - workflow.tags.length, - () => - html` - ClipboardController.copyToClipboard(workflow.tags.join(", "))} - > - - ${msg("Copy Tags")} - `, - )} + + + ClipboardController.copyToClipboard(workflow.tags.join(", "))} + ?disabled=${!workflow.tags.length} + > + + ${msg("Copy Tags")} + ClipboardController.copyToClipboard(workflow.id)} @@ -187,6 +196,7 @@ export class WorkflowActionMenu extends BtrixElement { ${when( canCrawl && !workflow.crawlCount, () => html` + ${msg("Delete Workflow")} diff --git a/frontend/src/stories/decorators/orgDecorator.ts b/frontend/src/stories/decorators/orgDecorator.ts index 4ec4a7294e..b9dbd908cf 100644 --- a/frontend/src/stories/decorators/orgDecorator.ts +++ b/frontend/src/stories/decorators/orgDecorator.ts @@ -8,6 +8,8 @@ import { AppStateService } from "@/utils/state"; const { users, usage, quotas, ...org } = orgMock; +export { orgMock }; + export type StorybookOrgProps = { orgUsers?: boolean; orgUsage?: boolean; diff --git a/frontend/src/stories/design/action-menus.mdx b/frontend/src/stories/design/action-menus.mdx index 69fa279c75..9a2aa57ac6 100644 --- a/frontend/src/stories/design/action-menus.mdx +++ b/frontend/src/stories/design/action-menus.mdx @@ -1,10 +1,8 @@ import { Meta, Canvas } from '@storybook/addon-docs/blocks'; -import { WorkflowActionMenu } from './examples/ActionMenu.stories'; +import { NoRuns } from '../features/crawl-workflows/WorkflowActionMenu.stories'; - - # Action Menus While controls are always placed next to the most relevant content area, we @@ -33,4 +31,4 @@ should be ordered as follows: ## Example - + diff --git a/frontend/src/stories/design/examples/ActionMenu.stories.ts b/frontend/src/stories/design/examples/ActionMenu.stories.ts deleted file mode 100644 index 8a26fe2b07..0000000000 --- a/frontend/src/stories/design/examples/ActionMenu.stories.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/web-components"; -import { html } from "lit"; - -const render = () => html` - - - - Run Crawl - - - - - Edit Workflow Settings - - - - Duplicate Workflow - - - - - Copy Tags - - - - Copy Workflow ID - - - - - Delete Workflow - - -`; - -const meta: Meta = { - component: "sl-menu", - tags: ["!dev"], // Hide from story navigation - render, -}; - -export default meta; -type Story = StoryObj; - -export const WorkflowActionMenu: Story = {}; diff --git a/frontend/src/stories/features/crawl-workflows/WorkflowActionMenu.stories.ts b/frontend/src/stories/features/crawl-workflows/WorkflowActionMenu.stories.ts new file mode 100644 index 0000000000..8452f9797a --- /dev/null +++ b/frontend/src/stories/features/crawl-workflows/WorkflowActionMenu.stories.ts @@ -0,0 +1,132 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import type { DecoratorFunction } from "storybook/internal/types"; + +import { argTypes } from "../excludeContainerProperties"; + +import "@/features/crawl-workflows/workflow-action-menu"; + +import workflowMock from "@/__mocks__/api/orgs/[id]/crawlconfigs/[id]"; +import type { WorkflowActionMenu } from "@/features/crawl-workflows/workflow-action-menu"; +import { orgDecorator, orgMock } from "@/stories/decorators/orgDecorator"; +import type { Workflow } from "@/types/crawler"; +import { AccessCode } from "@/types/org"; +import appState from "@/utils/state"; + +const mockAppState = ({ role }: { role: AccessCode }) => { + return { + ...appState, + org: orgMock, + isCrawler: role >= AccessCode.crawler, + isAdmin: role >= AccessCode.owner, + userOrg: { + default: true, + id: orgMock.id, + name: orgMock.name, + slug: orgMock.slug, + role, + }, + orgId: workflowMock.oid, + }; +}; + +const meta = { + title: "Features/Workflow Action Menu", + component: "btrix-workflow-action-menu", + subcomponents: { + CustomBehaviorsTableRow: "btrix-workflow-action-menu-row", + }, + tags: ["autodocs"], + decorators: [orgDecorator as DecoratorFunction], + render: (args) => html` + + `, + argTypes: { + ...argTypes, + }, + args: { + appState: mockAppState({ role: 20 }), + workflow: workflowMock as unknown as Workflow, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** + * Users with a "crawler" role will see the following options. + */ +export const Crawler: Story = { + args: { + workflow: { + ...meta.args.workflow, + }, + }, +}; + +/** + * A workflow can be deleted if it's never been run. + */ +export const NoRuns: Story = { + args: { + workflow: { + ...meta.args.workflow, + crawlCount: 0, + }, + }, +}; + +/** + * Additional actions are displayed depending on the workflow run state. + */ +export const Running: Story = { + args: { + workflow: { + ...meta.args.workflow, + isCrawlRunning: true, + lastCrawlState: "running", + }, + }, +}; + +export const Paused: Story = { + args: { + workflow: { + ...meta.args.workflow, + isCrawlRunning: true, + lastCrawlState: "paused", + }, + }, +}; + +export const Canceling: Story = { + args: { + workflow: { + ...meta.args.workflow, + isCrawlRunning: true, + lastCrawlState: "running", + }, + cancelingRun: true, + }, +}; + +/** + * Users with a "viewer" role will only see copy-to-clipboard options. + */ +export const Viewer: Story = { + args: { + appState: mockAppState({ role: 10 }), + workflow: { + ...meta.args.workflow, + tags: ["sample-tag-1", "sample-tag-2"], + }, + }, +}; diff --git a/frontend/src/theme.stylesheet.css b/frontend/src/theme.stylesheet.css index 2d0626b1b4..2ad070739a 100644 --- a/frontend/src/theme.stylesheet.css +++ b/frontend/src/theme.stylesheet.css @@ -233,7 +233,7 @@ /* Adjust menu item hover and focus styles */ sl-menu-item, btrix-menu-item-link { - @apply part-[base]:hover:bg-cyan-50/50 part-[base]:hover:text-cyan-700 part-[base]:focus-visible:bg-cyan-50/50; + @apply part-[base]:text-neutral-700 part-[base]:hover:bg-cyan-50/50 part-[base]:hover:text-cyan-700 part-[base]:focus-visible:bg-cyan-50/50; } /* Add menu item variants */ From 8142e3df3bccee7b05933d1bda70fd7bab7c38a2 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Fri, 25 Jul 2025 13:01:05 -0700 Subject: [PATCH 5/6] revert tailwind change --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 4619e40e95..b2c4c95b64 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -80,7 +80,7 @@ "slugify": "^1.6.6", "style-loader": "^3.3.0", "tabbable": "^6.2.0", - "tailwindcss": "^3.4.17", + "tailwindcss": "^3.4.1", "terser-webpack-plugin": "^5.3.10", "thread-loader": "^4.0.4", "tlds": "^1.259.0", From 57e389fee4ffde0c8ee8d7ff089dd30400099c4a Mon Sep 17 00:00:00 2001 From: sua yoo Date: Fri, 25 Jul 2025 13:03:20 -0700 Subject: [PATCH 6/6] update lockfile --- frontend/yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 2111a96df2..38b99be4d3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -10477,7 +10477,7 @@ table-layout@^1.0.2: typical "^5.2.0" wordwrapjs "^4.0.0" -tailwindcss@^3.4.17: +tailwindcss@^3.4.1: version "3.4.17" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.17.tgz#ae8406c0f96696a631c790768ff319d46d5e5a63" integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==