diff --git a/.github/workflows/k3d-ci.yaml b/.github/workflows/k3d-ci.yaml index 30466d86a3..6e5ac7185e 100644 --- a/.github/workflows/k3d-ci.yaml +++ b/.github/workflows/k3d-ci.yaml @@ -45,6 +45,13 @@ jobs: needs: paths-filter if: needs.paths-filter.outputs.matches == 'true' steps: + - name: Initial Disk Cleanup + uses: mathio/gha-cleanup@v1 + with: + remove-browsers: true + verbose: true + + - name: Create k3d Cluster uses: AbsaOSS/k3d-action@v2 with: diff --git a/frontend/src/assets/icons/window-gear.svg b/frontend/src/assets/icons/window-gear.svg new file mode 100644 index 0000000000..edd6e8093b --- /dev/null +++ b/frontend/src/assets/icons/window-gear.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/src/components/ui/badge.ts b/frontend/src/components/ui/badge.ts index 16bb776352..e679229211 100644 --- a/frontend/src/components/ui/badge.ts +++ b/frontend/src/components/ui/badge.ts @@ -13,7 +13,9 @@ export type BadgeVariant = | "primary" | "cyan" | "blue" - | "high-contrast"; + | "high-contrast" + | "text" + | "text-neutral"; /** * Show numeric value in a label @@ -64,6 +66,8 @@ export class Badge extends TailwindElement { primary: tw`bg-white text-primary ring-primary`, cyan: tw`bg-cyan-50 text-cyan-600 ring-cyan-600`, blue: tw`bg-blue-50 text-blue-600 ring-blue-600`, + text: tw`text-blue-500 ring-blue-600`, + "text-neutral": tw`text-neutral-500 ring-neutral-600`, }[this.variant], ] : { @@ -75,6 +79,8 @@ export class Badge extends TailwindElement { primary: tw`bg-primary text-neutral-0`, cyan: tw`bg-cyan-50 text-cyan-600`, blue: tw`bg-blue-50 text-blue-600`, + text: tw`text-blue-500`, + "text-neutral": tw`text-neutral-500`, }[this.variant], this.pill ? [ diff --git a/frontend/src/components/ui/code/index.ts b/frontend/src/components/ui/code/index.ts index 081e07b665..2955bcd095 100644 --- a/frontend/src/components/ui/code/index.ts +++ b/frontend/src/components/ui/code/index.ts @@ -3,6 +3,7 @@ import type { LanguageFn } from "highlight.js"; import hljs from "highlight.js/lib/core"; import { css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { html as staticHtml, unsafeStatic } from "lit/static-html.js"; import { TailwindElement } from "@/classes/TailwindElement"; @@ -52,11 +53,11 @@ export class Code extends TailwindElement { } .hljs-path { - color: var(--sl-color-blue-900); + color: var(--sl-color-sky-600); } .hljs-domain { - color: var(--sl-color-blue-600); + color: var(--sl-color-sky-700); } .hljs-string { @@ -71,7 +72,10 @@ export class Code extends TailwindElement { language: Language = Language.XML; @property({ type: Boolean }) - wrap = true; + noWrap = false; + + @property({ type: Boolean }) + truncate = false; async connectedCallback() { const languageFn = (await langaugeFiles[this.language]).default; @@ -94,8 +98,11 @@ export class Code extends TailwindElement { part="base" class=${clsx( tw`font-monospace m-0 text-neutral-600`, - this.wrap ? tw`whitespace-pre-wrap` : tw`whitespace-nowrap`, + this.noWrap ? tw`whitespace-nowrap` : tw`whitespace-pre-wrap`, + this.truncate && tw`truncate`, )} - >${staticHtml`${unsafeStatic(htmlStr)}`}`; + >${staticHtml`${unsafeStatic(htmlStr)}`}`; } } diff --git a/frontend/src/components/ui/config-details.ts b/frontend/src/components/ui/config-details.ts index 63f6e76991..2228e54502 100644 --- a/frontend/src/components/ui/config-details.ts +++ b/frontend/src/components/ui/config-details.ts @@ -1,3 +1,4 @@ +import { consume } from "@lit/context"; import { localized, msg, str } from "@lit/localize"; import ISO6391 from "iso-639-1"; import { html, nothing, type TemplateResult } from "lit"; @@ -6,6 +7,10 @@ import { when } from "lit/directives/when.js"; import capitalize from "lodash/fp/capitalize"; import { BtrixElement } from "@/classes/BtrixElement"; +import { + orgProxiesContext, + type OrgProxiesContext, +} from "@/context/org-proxies"; import { none, notSpecified } from "@/layouts/empty"; import { Behavior, @@ -36,6 +41,9 @@ import { getServerDefaults } from "@/utils/workflow"; @customElement("btrix-config-details") @localized() export class ConfigDetails extends BtrixElement { + @consume({ context: orgProxiesContext, subscribe: true }) + private readonly orgProxies?: OrgProxiesContext; + @property({ type: Object }) crawlConfig?: CrawlConfig; @@ -235,16 +243,16 @@ export class ConfigDetails extends BtrixElement { msg("Browser Profile"), when( crawlConfig?.profileid, - () => - html` html` + ${crawlConfig?.profileName} - `, + + `, () => crawlConfig?.profileName || html``, ), )} + ${crawlConfig?.proxyId + ? this.renderSetting( + msg("Crawler Proxy Server"), + this.orgProxies?.servers.find( + ({ id }) => id === crawlConfig.proxyId, + )?.label || capitalize(crawlConfig.proxyId), + ) + : nothing} ${this.renderSetting( msg("Browser Windows"), crawlConfig?.browserWindows ? `${crawlConfig.browserWindows}` : "", )} ${this.renderSetting( - msg("Crawler Channel (Exact Crawler Version)"), + `${msg("Crawler Channel")} ${crawlConfig?.image ? msg("(Exact Crawler Version)") : ""}`.trim(), capitalize( crawlConfig?.crawlerChannel || CrawlerChannelImage.Default, ) + (crawlConfig?.image ? ` (${crawlConfig.image})` : ""), @@ -284,9 +300,6 @@ export class ConfigDetails extends BtrixElement { ISO6391.getName(seedsConfig.lang), ) : nothing} - ${crawlConfig?.proxyId - ? this.renderSetting(msg("Proxy"), capitalize(crawlConfig.proxyId)) - : nothing} `, })} ${this.renderSection({ diff --git a/frontend/src/components/ui/desc-list.ts b/frontend/src/components/ui/desc-list.ts index a21ab6d8be..03d0ac58c0 100644 --- a/frontend/src/components/ui/desc-list.ts +++ b/frontend/src/components/ui/desc-list.ts @@ -127,6 +127,7 @@ export class DescList extends LitElement { vertical: !this.horizontal, horizontal: this.horizontal, })} + part="base" > `; diff --git a/frontend/src/components/ui/dialog.ts b/frontend/src/components/ui/dialog.ts index 5bd89544b4..d17cfffa29 100644 --- a/frontend/src/components/ui/dialog.ts +++ b/frontend/src/components/ui/dialog.ts @@ -10,6 +10,10 @@ import { * with custom CSS * * Usage: see https://shoelace.style/components/dialog + * + * @attr label + * @attr open + * @attr noHeader */ @customElement("btrix-dialog") export class Dialog extends SlDialog { @@ -27,8 +31,7 @@ export class Dialog extends SlDialog { } .dialog__header { - background-color: var(--sl-color-neutral-50); - border-bottom: 1px solid var(--sl-color-neutral-100); + border-bottom: 1px solid var(--sl-panel-border-color); } .dialog__title { @@ -50,7 +53,7 @@ export class Dialog extends SlDialog { .dialog__footer { padding-top: var(--sl-spacing-small); padding-bottom: var(--sl-spacing-small); - border-top: 1px solid var(--sl-color-neutral-100); + border-top: 1px solid var(--sl-panel-border-color); } `, ]; diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 089b259263..632598bc7c 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -16,6 +16,7 @@ import("./combobox"); import("./config-details"); import("./copy-button"); import("./copy-field"); +import("./tag-container"); import("./data-grid"); import("./details"); import("./file-input"); @@ -41,7 +42,8 @@ import("./select-crawler-proxy"); import("./select-crawler"); import("./syntax-input"); import("./table"); -import("./tag-input"); import("./tag"); +import("./tag-filter"); +import("./tag-input"); import("./time-input"); import("./user-language-select"); diff --git a/frontend/src/components/ui/link.ts b/frontend/src/components/ui/link.ts index c833207030..73f1c46c45 100644 --- a/frontend/src/components/ui/link.ts +++ b/frontend/src/components/ui/link.ts @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -19,13 +19,16 @@ export class Link extends BtrixElement { @property({ type: String }) variant: "primary" | "neutral" = "neutral"; + @property({ type: Boolean }) + hideIcon = false; + render() { if (!this.href) return; return html` - + ${this.hideIcon + ? nothing + : html` + + `} + `; } } diff --git a/frontend/src/components/ui/select-crawler-proxy.ts b/frontend/src/components/ui/select-crawler-proxy.ts index 12362a179b..8c5c5529ce 100644 --- a/frontend/src/components/ui/select-crawler-proxy.ts +++ b/frontend/src/components/ui/select-crawler-proxy.ts @@ -1,6 +1,6 @@ import { localized, msg } from "@lit/localize"; import type { SlSelect } from "@shoelace-style/shoelace"; -import { html } from "lit"; +import { html, type PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -40,15 +40,30 @@ export class SelectCrawlerProxy extends BtrixElement { @property({ type: String }) defaultProxyId: string | null = null; + @property({ type: String }) + profileProxyId?: string | null = null; + @property({ type: Array }) proxyServers: Proxy[] = []; @property({ type: String }) proxyId: string | null = null; + @property({ type: String }) + label?: string; + @property({ type: String }) size?: SlSelect["size"]; + @property({ type: String }) + placeholder?: string; + + @property({ type: String }) + helpText?: string; + + @property({ type: Boolean }) + disabled?: boolean; + @state() private selectedProxy?: Proxy; @@ -59,6 +74,18 @@ export class SelectCrawlerProxy extends BtrixElement { return this.selectedProxy?.id || ""; } + protected willUpdate(changedProperties: PropertyValues): void { + if (changedProperties.has("proxyId")) { + if (this.proxyId) { + this.selectedProxy = this.proxyServers.find( + ({ id }) => id === this.proxyId, + ); + } else if (changedProperties.get("proxyId")) { + this.selectedProxy = undefined; + } + } + } + protected firstUpdated() { void this.initProxies(); } @@ -75,18 +102,21 @@ export class SelectCrawlerProxy extends BtrixElement { return html` + + ${this.proxyServers.map( (server) => html` @@ -102,7 +132,7 @@ export class SelectCrawlerProxy extends BtrixElement { ? html`
${msg("Description:")} - ${this.selectedProxy.description || ""}
@@ -112,12 +142,13 @@ export class SelectCrawlerProxy extends BtrixElement { ? html`
${msg("Description:")} - ${this.defaultProxy.description || ""}
` : ``} +
`; } diff --git a/frontend/src/components/ui/select-crawler.ts b/frontend/src/components/ui/select-crawler.ts index 217b9dacf8..b30d3de05b 100644 --- a/frontend/src/components/ui/select-crawler.ts +++ b/frontend/src/components/ui/select-crawler.ts @@ -1,10 +1,15 @@ +import { consume } from "@lit/context"; import { localized, msg } from "@lit/localize"; import { type SlSelect } from "@shoelace-style/shoelace"; -import { html, type PropertyValues } from "lit"; +import { html, nothing, type PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import capitalize from "lodash/fp/capitalize"; +import { + orgCrawlerChannelsContext, + type OrgCrawlerChannelsContext, +} from "@/context/org-crawler-channels"; import { CrawlerChannelImage, type CrawlerChannel } from "@/pages/org/types"; import LiteElement from "@/utils/LiteElement"; @@ -20,13 +25,11 @@ type SelectCrawlerUpdateDetail = { export type SelectCrawlerUpdateEvent = CustomEvent; -type CrawlerChannelsAPIResponse = { - channels: CrawlerChannel[]; -}; - /** * Crawler channel select dropdown * + * @TODO Convert to form control + * * Usage example: * ```ts * * ``` * - * @event on-change + * @fires on-change */ @customElement("btrix-select-crawler") @localized() export class SelectCrawler extends LiteElement { + @consume({ context: orgCrawlerChannelsContext, subscribe: true }) + private readonly crawlerChannels?: OrgCrawlerChannelsContext; + @property({ type: String }) size?: SlSelect["size"]; @property({ type: String }) - crawlerChannel?: string; + crawlerChannel?: CrawlerChannel["id"]; @state() - private selectedCrawler?: CrawlerChannel; + public value: CrawlerChannel["id"] = ""; - @state() - private crawlerChannels?: CrawlerChannel[]; - - willUpdate(changedProperties: PropertyValues) { + protected willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has("crawlerChannel")) { - void this.updateSelectedCrawlerChannel(); + this.value = this.crawlerChannel || ""; } } - protected firstUpdated() { - void this.updateSelectedCrawlerChannel(); - } - render() { - if (this.crawlerChannels && this.crawlerChannels.length < 2) { - return html``; - } + const selectedCrawler = this.getSelectedChannel(); return html` { - // Refetch to keep list up to date - void this.fetchCrawlerChannels(); - }} @sl-hide=${this.stopProp} @sl-after-hide=${this.stopProp} + ?disabled=${!this.crawlerChannels || this.crawlerChannels.length === 1} > ${this.crawlerChannels?.map( (crawler) => @@ -88,99 +82,53 @@ export class SelectCrawler extends LiteElement { ${capitalize(crawler.id)} `, )} - ${this.selectedCrawler - ? html` -
- ${msg("Version:")} - ${this.selectedCrawler.image} + ${msg("Version:")} + ${selectedCrawler + ? html` + ${selectedCrawler.image} -
- ` - : ``} + ` + : nothing} +
`; } - private onChange(e: Event) { - this.stopProp(e); + private getSelectedChannel() { + const channelId = this.value; - this.selectedCrawler = this.crawlerChannels?.find( - ({ id }) => id === (e.target as SlSelect).value, - ); + if (!this.crawlerChannels || !channelId) return null; - this.dispatchEvent( - new CustomEvent("on-change", { - detail: { - value: this.selectedCrawler?.id, - }, - }), + if (channelId) { + return this.crawlerChannels.find(({ id }) => id === channelId); + } + + return ( + this.crawlerChannels.find( + ({ id }) => id === CrawlerChannelImage.Default, + ) ?? null ); } - private async updateSelectedCrawlerChannel() { - await this.fetchCrawlerChannels(); - await this.updateComplete; - - if (!this.crawlerChannels) return; - - if (this.crawlerChannel && !this.selectedCrawler) { - this.selectedCrawler = this.crawlerChannels.find( - ({ id }) => id === this.crawlerChannel, - ); - } + private onChange(e: Event) { + this.stopProp(e); - if (!this.selectedCrawler) { - this.crawlerChannel = CrawlerChannelImage.Default; - this.dispatchEvent( - new CustomEvent("on-change", { - detail: { - value: CrawlerChannelImage.Default, - }, - }), - ); - this.selectedCrawler = this.crawlerChannels.find( - ({ id }) => id === this.crawlerChannel, - ); - } + const { value } = e.target as SlSelect; - await this.updateComplete; + this.value = value as string; + const selectedCrawler = this.getSelectedChannel(); this.dispatchEvent( - new CustomEvent("on-update", { + new CustomEvent("on-change", { detail: { - show: this.crawlerChannels.length > 1, + value: selectedCrawler?.id, }, }), ); } - /** - * Fetch crawler channels and update internal state - */ - private async fetchCrawlerChannels(): Promise { - try { - const channels = await this.getCrawlerChannels(); - this.crawlerChannels = channels; - } catch (e) { - this.notify({ - message: msg("Sorry, couldn't retrieve crawler channels at this time."), - variant: "danger", - icon: "exclamation-octagon", - id: "crawler-channel-retrieve-error", - }); - } - } - - private async getCrawlerChannels(): Promise { - const data: CrawlerChannelsAPIResponse = - await this.apiFetch( - `/orgs/${this.orgId}/crawlconfigs/crawler-channels`, - ); - - return data.channels; - } - /** * Stop propgation of sl-select events. * Prevents bug where sl-dialog closes when dropdown closes diff --git a/frontend/src/components/ui/table/table-header-cell.ts b/frontend/src/components/ui/table/table-header-cell.ts index d522af2340..0c14ae7687 100644 --- a/frontend/src/components/ui/table/table-header-cell.ts +++ b/frontend/src/components/ui/table/table-header-cell.ts @@ -16,6 +16,9 @@ export class TableHeaderCell extends TableCell { @property({ type: String, reflect: true, noAccessor: true }) role = "columnheader"; + @property({ type: String, reflect: true, noAccessor: true }) + scope?: "row" | "col"; + @property({ type: String, reflect: true }) ariaSort: SortValues = "none"; diff --git a/frontend/src/components/ui/tag-container.ts b/frontend/src/components/ui/tag-container.ts new file mode 100644 index 0000000000..eea43fb312 --- /dev/null +++ b/frontend/src/components/ui/tag-container.ts @@ -0,0 +1,137 @@ +import clsx from "clsx"; +import { css, html, type PropertyValues } from "lit"; +import { + customElement, + property, + query, + queryAll, + state, +} from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import debounce from "lodash/fp/debounce"; + +import { TailwindElement } from "@/classes/TailwindElement"; +import type { Tag } from "@/components/ui/tag"; +import type { UnderlyingFunction } from "@/types/utils"; +import localize from "@/utils/localize"; +import { tw } from "@/utils/tailwind"; + +/** + * Displays all the tags that can be contained to one line. + * Overflowing tags are displayed in a popover. + * + * @cssproperty width + */ +@customElement("btrix-tag-container") +export class TagContainer extends TailwindElement { + static styles = css` + :host { + --width: 100%; + } + `; + + @property({ type: Array }) + tags: string[] = []; + + @query("#container") + private readonly container?: HTMLElement | null; + + @queryAll("btrix-tag") + private readonly tagNodes!: NodeListOf; + + @state() + private displayLimit?: number; + + disconnectedCallback(): void { + this.debouncedCalculate.cancel(); + super.disconnectedCallback(); + } + + protected updated(changedProperties: PropertyValues): void { + if (changedProperties.get("tags")) { + this.debouncedCalculate.cancel(); + this.calculate(); + } + } + + render() { + const maxTags = this.tags.length; + const displayLimit = this.displayLimit; + const remainder = displayLimit && maxTags - displayLimit; + + return html` + } + > +
+
+ ${this.tags.map( + (tag, i) => + html` displayLimit - 1 + ? "true" + : "false", + )} + >${tag}`, + )} +
+ + + +${localize.number(remainder || maxTags)} +
+ ${this.tags + .slice(displayLimit) + .map((tag) => html`${tag}`)} +
+
+
+
+ `; + } + + private readonly calculate = () => { + const tagNodes = Array.from(this.tagNodes); + + if (!tagNodes.length || !this.container) return; + + const containerRect = this.container.getBoundingClientRect(); + const containerTop = containerRect.top; + + // Reset width + this.style.setProperty("--width", "100%"); + const idx = tagNodes.findIndex( + (el) => el.getBoundingClientRect().top > containerTop, + ); + + if (idx === -1) return; + const lastVisible = tagNodes[idx - 1]; + if (lastVisible as unknown) { + const rect = lastVisible.getBoundingClientRect(); + // Decrease width of container to match end of last visible tag + this.style.setProperty( + "--width", + `${rect.left - containerRect.left + rect.width}px`, + ); + } + + this.displayLimit = idx; + }; + + private readonly debouncedCalculate = debounce(50)(this.calculate); +} diff --git a/frontend/src/components/ui/tag-filter/index.ts b/frontend/src/components/ui/tag-filter/index.ts new file mode 100644 index 0000000000..2c16877957 --- /dev/null +++ b/frontend/src/components/ui/tag-filter/index.ts @@ -0,0 +1 @@ +import "./tag-filter"; diff --git a/frontend/src/features/archived-items/archived-item-tag-filter.ts b/frontend/src/components/ui/tag-filter/tag-filter.ts similarity index 88% rename from frontend/src/features/archived-items/archived-item-tag-filter.ts rename to frontend/src/components/ui/tag-filter/tag-filter.ts index dc6bf82146..87c2f25796 100644 --- a/frontend/src/features/archived-items/archived-item-tag-filter.ts +++ b/frontend/src/components/ui/tag-filter/tag-filter.ts @@ -20,37 +20,39 @@ import { repeat } from "lit/directives/repeat.js"; import queryString from "query-string"; import { isFocusable } from "tabbable"; +import type { + BtrixChangeTagFilterEvent, + TagCount, + TagCounts, + TagType, +} from "./types"; + import { BtrixElement } from "@/classes/BtrixElement"; -import type { BtrixChangeEvent } from "@/events/btrix-change"; -import type { ArchivedItem } from "@/types/crawler"; -import { type WorkflowTag, type WorkflowTags } from "@/types/workflow"; import { stopProp } from "@/utils/events"; import { isNotEqual } from "@/utils/is-not-equal"; import { tw } from "@/utils/tailwind"; const MAX_TAGS_IN_LABEL = 5; - -type ChangeArchivedItemTagEventDetails = - | { tags: string[]; type: "and" | "or" } - | undefined; - -export type BtrixChangeArchivedItemTagFilterEvent = - BtrixChangeEvent; +const apiPathForTagType: Record = { + workflow: "crawlconfigs", + "workflow-crawl": "crawls", + "archived-item": "all-crawls", + "archived-item-crawl": "crawls", + upload: "uploads", + profile: "profiles", +}; /** * @fires btrix-change */ -@customElement("btrix-archived-item-tag-filter") +@customElement("btrix-tag-filter") @localized() -export class ArchivedItemTagFilter extends BtrixElement { - @property({ type: Array }) - tags?: string[]; - +export class TagFilter extends BtrixElement { @property({ type: String }) - itemType?: ArchivedItem["type"]; + tagType?: TagType; - @property({ type: Boolean }) - includeNotSuccessful = false; + @property({ type: Array }) + tags?: string[]; @state() private searchString = ""; @@ -61,7 +63,7 @@ export class ArchivedItemTagFilter extends BtrixElement { @queryAll("sl-checkbox") private readonly checkboxes!: NodeListOf; - private readonly fuse = new Fuse([], { + private readonly fuse = new Fuse([], { keys: ["tag"], }); @@ -82,14 +84,22 @@ export class ArchivedItemTagFilter extends BtrixElement { } private readonly orgTagsTask = new Task(this, { - task: async ([itemType], { signal }) => { - const query = queryString.stringify({ - onlySuccessful: !this.includeNotSuccessful, - crawlType: itemType, - }); - - const { tags } = await this.api.fetch( - `/orgs/${this.orgId}/all-crawls/tagCounts?${query}`, + task: async ([tagType], { signal }) => { + if (!tagType) { + console.debug("no tagType"); + return; + } + + let query = ""; + + if (tagType === "workflow-crawl") { + query = queryString.stringify({ + onlySuccessful: false, + }); + } + + const { tags } = await this.api.fetch( + `/orgs/${this.orgId}/${apiPathForTagType[tagType]}/tagCounts${query && `?${query}`}`, { signal }, ); @@ -98,7 +108,7 @@ export class ArchivedItemTagFilter extends BtrixElement { // Match fuse shape return tags.map((item) => ({ item })); }, - args: () => [this.itemType] as const, + args: () => [this.tagType] as const, }); render() { @@ -149,9 +159,8 @@ export class ArchivedItemTagFilter extends BtrixElement { this.checkboxes.forEach((checkbox) => { checkbox.checked = false; }); - + this.selected = new Map(); this.type = "or"; - void this.dispatchChange(); }} >${msg("Clear")} { + if (!tags) return; + let options = tags; if (tags.length && this.searchString) { @@ -267,8 +278,8 @@ export class ArchivedItemTagFilter extends BtrixElement { `; } - private renderList(opts: { item: WorkflowTag }[]) { - const tag = (tag: WorkflowTag) => { + private renderList(opts: { item: TagCount }[]) { + const tag = (tag: TagCount) => { const checked = this.selected.get(tag.tag) === true; return html` @@ -314,9 +325,7 @@ export class ArchivedItemTagFilter extends BtrixElement { .filter(([_tag, selected]) => selected) .map(([tag]) => tag); this.dispatchEvent( - new CustomEvent< - BtrixChangeEvent["detail"] - >("btrix-change", { + new CustomEvent("btrix-change", { detail: { value: selectedTags.length ? { tags: selectedTags, type: this.type } diff --git a/frontend/src/components/ui/tag-filter/types.ts b/frontend/src/components/ui/tag-filter/types.ts new file mode 100644 index 0000000000..5e9933bd46 --- /dev/null +++ b/frontend/src/components/ui/tag-filter/types.ts @@ -0,0 +1,24 @@ +import type { BtrixChangeEvent } from "@/events/btrix-change"; + +export type TagType = + | "workflow" + | "workflow-crawl" + | "archived-item" + | "archived-item-crawl" + | "upload" + | "profile"; + +export type TagCount = { + tag: string; + count: number; +}; + +export type TagCounts = { + tags: TagCount[]; +}; + +export type ChangeTagEventDetails = + | { tags: string[]; type: "and" | "or" } + | undefined; + +export type BtrixChangeTagFilterEvent = BtrixChangeEvent; diff --git a/frontend/src/components/ui/tag-input.ts b/frontend/src/components/ui/tag-input.ts index 45b9da44c0..0260270a04 100644 --- a/frontend/src/components/ui/tag-input.ts +++ b/frontend/src/components/ui/tag-input.ts @@ -16,6 +16,8 @@ import { import { customElement, property, query, state } from "lit/decorators.js"; import debounce from "lodash/fp/debounce"; +import { TAG_MAX_CHARACTERS } from "./tag"; + import type { UnderlyingFunction } from "@/types/utils"; import { type WorkflowTag } from "@/types/workflow"; import { dropdown } from "@/utils/css"; @@ -150,6 +152,10 @@ export class TagInput extends LitElement { @query("sl-popup") private readonly combobox!: SlPopup; + public getTags() { + return this.tags; + } + connectedCallback() { if (this.initialTags) { this.tags = this.initialTags; @@ -230,6 +236,7 @@ export class TagInput extends LitElement { role="combobox" aria-controls="dropdown" aria-expanded="${this.dropdownIsOpen === true}" + maxlength=${TAG_MAX_CHARACTERS} />