Skip to content

Commit 7a6b1d7

Browse files
SuaYooemma-sg
andauthored
feat: Filter workflows by tag + update existing filter UI (#2702)
Resolves #2660 ## Changes - Enables filtering workflow list by tag - Displays tags near workflow name in detail view - Adds `<btrix-filter-chip>` component - Migrates "schedule state", "only running", and "only mine" filters - Adds basic documentation to Storybook --------- Co-authored-by: Emma Segal-Grossman <hi@emma.cafe>
1 parent 9cfed7c commit 7a6b1d7

File tree

12 files changed

+820
-112
lines changed

12 files changed

+820
-112
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { localized } from "@lit/localize";
2+
import type { SlDropdown } from "@shoelace-style/shoelace";
3+
import clsx from "clsx";
4+
import { html } from "lit";
5+
import { customElement, property, query } from "lit/decorators.js";
6+
import { ifDefined } from "lit/directives/if-defined.js";
7+
8+
import { TailwindElement } from "@/classes/TailwindElement";
9+
import type { BtrixChangeEvent } from "@/events/btrix-change";
10+
import { tw } from "@/utils/tailwind";
11+
12+
export type BtrixFilterChipChangeEvent = BtrixChangeEvent;
13+
14+
/**
15+
* A filter chip lets users select a content filter. If there's only one option, the chip toggles on and off.
16+
* Otherwise, clicking the chip reveals a dropdown menu of filter options.
17+
*
18+
* Filter chips are meant to be shown as multiple filter options, hence the plus (`+`) icon to indicate adding a filter.
19+
*
20+
* @slot
21+
* @slot dropdown-content
22+
*
23+
* @fires btrix-change
24+
*/
25+
@customElement("btrix-filter-chip")
26+
@localized()
27+
export class FilterChip extends TailwindElement {
28+
@property({ type: Boolean })
29+
checked?: boolean;
30+
31+
@property({ type: Boolean })
32+
selectFromDropdown?: boolean;
33+
34+
@property({ type: Boolean })
35+
stayOpenOnChange?: boolean;
36+
37+
@property({ type: Boolean })
38+
open?: boolean;
39+
40+
@query("sl-dropdown")
41+
private readonly dropdown?: SlDropdown | null;
42+
43+
public hideDropdown() {
44+
void this.dropdown?.hide();
45+
}
46+
47+
public showDropdown() {
48+
void this.dropdown?.show();
49+
}
50+
51+
render() {
52+
if (this.selectFromDropdown) {
53+
return html`
54+
<sl-dropdown
55+
distance="4"
56+
hoist
57+
?stayOpenOnSelect=${this.stayOpenOnChange}
58+
class="group/dropdown"
59+
?open=${this.open}
60+
>
61+
${this.renderButton()}
62+
63+
<slot name="dropdown-content"></slot>
64+
</sl-dropdown>
65+
`;
66+
}
67+
68+
return this.renderButton();
69+
}
70+
71+
private renderButton() {
72+
return html`
73+
<sl-button
74+
slot=${ifDefined(this.selectFromDropdown ? "trigger" : undefined)}
75+
role="checkbox"
76+
aria-checked=${this.checked ? "true" : "false"}
77+
size="small"
78+
?caret=${this.selectFromDropdown}
79+
outline
80+
pill
81+
class=${clsx([
82+
tw`part-[] part-[suffix]:-mr-0.5`,
83+
tw`hover:part-[base]:border-primary-300 hover:part-[base]:bg-transparent hover:part-[base]:text-primary-600`,
84+
tw`aria-checked:part-[base]:border-primary-300 aria-checked:part-[base]:bg-primary-50/80 aria-checked:part-[base]:text-primary-600`,
85+
tw`group-open/dropdown:part-[base]:border-primary-300 group-open/dropdown:part-[caret]:text-primary-600 group-open/dropdown:part-[label]:text-primary-600`,
86+
])}
87+
@click=${this.onClick}
88+
>
89+
<sl-icon
90+
class="size-4 text-base group-open/dropdown:text-primary-600"
91+
slot="prefix"
92+
name=${this.checked ? "check2-circle" : "plus-circle-dotted"}
93+
></sl-icon>
94+
<slot></slot>
95+
</sl-button>
96+
`;
97+
}
98+
99+
private readonly onClick = () => {
100+
if (!this.selectFromDropdown) {
101+
this.toggleChecked();
102+
}
103+
};
104+
105+
private toggleChecked() {
106+
this.checked = !this.checked;
107+
108+
this.dispatchEvent(
109+
new CustomEvent<BtrixFilterChipChangeEvent["detail"]>("btrix-change"),
110+
);
111+
}
112+
}

frontend/src/components/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import("./data-grid");
2020
import("./details");
2121
import("./file-input");
2222
import("./file-list");
23+
import("./filter-chip");
2324
import("./format-date");
2425
import("./inline-input");
2526
import("./language-select");

frontend/src/controllers/localize.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ export class LocalizeController extends SlLocalizeController {
3434
};
3535

3636
readonly bytes = localize.bytes;
37+
38+
readonly list = localize.list;
3739
}

frontend/src/features/crawl-workflows/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ import("./queue-exclusion-form");
77
import("./queue-exclusion-table");
88
import("./workflow-editor");
99
import("./workflow-list");
10+
import("./workflow-schedule-filter");
11+
import("./workflow-tag-filter");
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { localized, msg } from "@lit/localize";
2+
import type { SlSelectEvent } from "@shoelace-style/shoelace";
3+
import { html, nothing, type PropertyValues } from "lit";
4+
import { customElement, property } from "lit/decorators.js";
5+
6+
import { BtrixElement } from "@/classes/BtrixElement";
7+
import type { BtrixChangeEvent } from "@/events/btrix-change";
8+
9+
export type BtrixChangeWorkflowScheduleFilterEvent = BtrixChangeEvent<
10+
undefined | boolean
11+
>;
12+
13+
enum ScheduleType {
14+
Scheduled = "Scheduled",
15+
None = "None",
16+
Any = "Any",
17+
}
18+
19+
/**
20+
* @fires btrix-change
21+
*/
22+
@customElement("btrix-workflow-schedule-filter")
23+
@localized()
24+
export class WorkflowScheduleFilter extends BtrixElement {
25+
@property({ type: Boolean })
26+
schedule?: boolean;
27+
28+
#schedule?: boolean;
29+
30+
protected willUpdate(changedProperties: PropertyValues): void {
31+
if (changedProperties.has("schedule")) {
32+
this.#schedule = this.schedule;
33+
}
34+
}
35+
36+
render() {
37+
const option = (label: string, value: string) => html`
38+
<sl-menu-item value=${value}>${label}</sl-menu-item>
39+
`;
40+
41+
return html`
42+
<btrix-filter-chip
43+
?checked=${this.schedule !== undefined}
44+
selectFromDropdown
45+
@sl-after-hide=${() => {
46+
if (this.#schedule !== this.schedule) {
47+
this.dispatchEvent(
48+
new CustomEvent<BtrixChangeWorkflowScheduleFilterEvent["detail"]>(
49+
"btrix-change",
50+
{
51+
detail: { value: this.#schedule },
52+
},
53+
),
54+
);
55+
}
56+
}}
57+
>
58+
${this.schedule === undefined
59+
? msg("Schedule")
60+
: html`<span
61+
>${this.schedule ? msg("Scheduled") : msg("No Schedule")}</span
62+
>`}
63+
64+
<sl-menu
65+
slot="dropdown-content"
66+
class="pt-0"
67+
@sl-select=${(e: SlSelectEvent) => {
68+
const { item } = e.detail;
69+
70+
switch (item.value as ScheduleType) {
71+
case ScheduleType.Scheduled:
72+
this.#schedule = true;
73+
break;
74+
case ScheduleType.None:
75+
this.#schedule = false;
76+
break;
77+
default:
78+
this.#schedule = undefined;
79+
break;
80+
}
81+
}}
82+
>
83+
<sl-menu-label
84+
class="part-[base]:flex part-[base]:items-center part-[base]:justify-between part-[base]:gap-4 part-[base]:px-3"
85+
>
86+
<div
87+
id="schedule-list-label"
88+
class="leading-[var(--sl-input-height-small)]"
89+
>
90+
${msg("Filter by Schedule Type")}
91+
</div>
92+
${this.schedule !== undefined
93+
? html`<sl-button
94+
variant="text"
95+
size="small"
96+
class="part-[label]:px-0"
97+
@click=${() => {
98+
this.dispatchEvent(
99+
new CustomEvent<BtrixChangeEvent["detail"]>(
100+
"btrix-change",
101+
{
102+
detail: {
103+
value: undefined,
104+
},
105+
},
106+
),
107+
);
108+
}}
109+
>${msg("Clear")}</sl-button
110+
>`
111+
: nothing}
112+
</sl-menu-label>
113+
114+
${option(msg("Scheduled"), ScheduleType.Scheduled)}
115+
${option(msg("No Schedule"), ScheduleType.None)}
116+
</sl-menu>
117+
</btrix-filter-chip>
118+
`;
119+
}
120+
}

0 commit comments

Comments
 (0)