Allow filtering workflows to only running, & add dashboard links (#2607)
Co-authored-by: sua yoo <sua@suayoo.com> Co-authored-by: sua yoo <sua@webrecorder.org>
This commit is contained in:
parent
52da39c2b4
commit
1cfdb97d57
@ -602,6 +602,7 @@ class CrawlConfigOps:
|
||||
description: Optional[str] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
schedule: Optional[bool] = None,
|
||||
isCrawlRunning: Optional[bool] = None,
|
||||
sort_by: str = "lastRun",
|
||||
sort_direction: int = -1,
|
||||
) -> tuple[list[CrawlConfigOut], int]:
|
||||
@ -634,6 +635,9 @@ class CrawlConfigOps:
|
||||
else:
|
||||
match_query["schedule"] = {"$in": ["", None]}
|
||||
|
||||
if isCrawlRunning is not None:
|
||||
match_query["isCrawlRunning"] = isCrawlRunning
|
||||
|
||||
# pylint: disable=duplicate-code
|
||||
aggregate = [
|
||||
{"$match": match_query},
|
||||
@ -1372,6 +1376,7 @@ def init_crawl_config_api(
|
||||
description: Optional[str] = None,
|
||||
tag: Union[List[str], None] = Query(default=None),
|
||||
schedule: Optional[bool] = None,
|
||||
isCrawlRunning: Optional[bool] = None,
|
||||
sortBy: str = "",
|
||||
sortDirection: int = -1,
|
||||
):
|
||||
@ -1394,6 +1399,7 @@ def init_crawl_config_api(
|
||||
description=description,
|
||||
tags=tag,
|
||||
schedule=schedule,
|
||||
isCrawlRunning=isCrawlRunning,
|
||||
page_size=pageSize,
|
||||
page=page,
|
||||
sort_by=sortBy,
|
||||
|
@ -26,7 +26,7 @@ export class Combobox extends LitElement {
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
z-index: 3;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -218,6 +218,15 @@ export class Pagination extends LitElement {
|
||||
this.onPageChange(constrainedPage, { dispatch: false });
|
||||
}
|
||||
|
||||
// if page is out of bounds, clamp it & dispatch an event to re-fetch data
|
||||
if (
|
||||
changedProperties.has("page") &&
|
||||
(this.page > this.pages || this.page < 1)
|
||||
) {
|
||||
const constrainedPage = Math.max(1, Math.min(this.pages, this.page));
|
||||
this.onPageChange(constrainedPage, { dispatch: true });
|
||||
}
|
||||
|
||||
if (changedProperties.get("page") && this._page) {
|
||||
this.inputValue = `${this._page}`;
|
||||
}
|
||||
@ -396,14 +405,11 @@ export class Pagination extends LitElement {
|
||||
}
|
||||
|
||||
private setPage(page: number) {
|
||||
this.searchParams.set((params) => {
|
||||
if (page === 1) {
|
||||
params.delete(this.name);
|
||||
} else {
|
||||
params.set(this.name, page.toString());
|
||||
}
|
||||
return params;
|
||||
});
|
||||
if (page === 1) {
|
||||
this.searchParams.delete(this.name);
|
||||
} else {
|
||||
this.searchParams.set(this.name, page.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private calculatePages() {
|
||||
|
@ -12,7 +12,7 @@ export class SearchParamsController implements ReactiveController {
|
||||
return new URLSearchParams(location.search);
|
||||
}
|
||||
|
||||
public set(
|
||||
public update(
|
||||
update: URLSearchParams | ((prev: URLSearchParams) => URLSearchParams),
|
||||
options: { replace?: boolean; data?: unknown } = { replace: false },
|
||||
) {
|
||||
@ -23,6 +23,63 @@ export class SearchParamsController implements ReactiveController {
|
||||
? update(this.searchParams).toString()
|
||||
: update.toString();
|
||||
|
||||
if (url.toString() === location.toString()) return;
|
||||
|
||||
if (options.replace) {
|
||||
history.replaceState(options.data, "", url);
|
||||
} else {
|
||||
history.pushState(options.data, "", url);
|
||||
}
|
||||
}
|
||||
|
||||
public set(
|
||||
name: string,
|
||||
value: string,
|
||||
options: { replace?: boolean; data?: unknown } = { replace: false },
|
||||
) {
|
||||
this.prevParams = new URLSearchParams(this.searchParams);
|
||||
const url = new URL(location.toString());
|
||||
const newParams = new URLSearchParams(this.searchParams);
|
||||
newParams.set(name, value);
|
||||
url.search = newParams.toString();
|
||||
|
||||
if (url.toString() === location.toString()) return;
|
||||
|
||||
if (options.replace) {
|
||||
history.replaceState(options.data, "", url);
|
||||
} else {
|
||||
history.pushState(options.data, "", url);
|
||||
}
|
||||
}
|
||||
|
||||
public delete(
|
||||
name: string,
|
||||
value: string,
|
||||
options?: { replace?: boolean; data?: unknown },
|
||||
): void;
|
||||
public delete(
|
||||
name: string,
|
||||
options?: { replace?: boolean; data?: unknown },
|
||||
): void;
|
||||
public delete(
|
||||
name: string,
|
||||
valueOrOptions?: string | { replace?: boolean; data?: unknown },
|
||||
options?: { replace?: boolean; data?: unknown },
|
||||
) {
|
||||
this.prevParams = new URLSearchParams(this.searchParams);
|
||||
const url = new URL(location.toString());
|
||||
const newParams = new URLSearchParams(this.searchParams);
|
||||
if (typeof valueOrOptions === "string") {
|
||||
newParams.delete(name, valueOrOptions);
|
||||
} else {
|
||||
newParams.delete(name);
|
||||
options = valueOrOptions;
|
||||
}
|
||||
options ??= { replace: false };
|
||||
url.search = newParams.toString();
|
||||
|
||||
if (url.toString() === location.toString()) return;
|
||||
|
||||
if (options.replace) {
|
||||
history.replaceState(options.data, "", url);
|
||||
} else {
|
||||
|
@ -260,6 +260,9 @@ export class Dashboard extends BtrixElement {
|
||||
name: "gear-wide-connected",
|
||||
class: this.colors.crawls,
|
||||
},
|
||||
button: {
|
||||
url: "/items/crawl",
|
||||
},
|
||||
})}
|
||||
${this.renderStat({
|
||||
value: metrics.uploadCount,
|
||||
@ -269,6 +272,9 @@ export class Dashboard extends BtrixElement {
|
||||
singleLabel: msg("Upload"),
|
||||
pluralLabel: msg("Uploads"),
|
||||
iconProps: { name: "upload", class: this.colors.uploads },
|
||||
button: {
|
||||
url: "/items/upload",
|
||||
},
|
||||
})}
|
||||
${this.renderStat({
|
||||
value: metrics.profileCount,
|
||||
@ -281,6 +287,9 @@ export class Dashboard extends BtrixElement {
|
||||
name: "window-fullscreen",
|
||||
class: this.colors.browserProfiles,
|
||||
},
|
||||
button: {
|
||||
url: "/browser-profiles",
|
||||
},
|
||||
})}
|
||||
<sl-divider
|
||||
style="--spacing:var(--sl-spacing-small)"
|
||||
@ -293,6 +302,9 @@ export class Dashboard extends BtrixElement {
|
||||
singleLabel: msg("Archived Item"),
|
||||
pluralLabel: msg("Archived Items"),
|
||||
iconProps: { name: "file-zip-fill" },
|
||||
button: {
|
||||
url: "/items",
|
||||
},
|
||||
})}
|
||||
</dl>
|
||||
`,
|
||||
@ -316,6 +328,9 @@ export class Dashboard extends BtrixElement {
|
||||
? tw`animate-pulse text-green-600`
|
||||
: tw`text-neutral-600`,
|
||||
},
|
||||
button: {
|
||||
url: "/workflows?isCrawlRunning=true",
|
||||
},
|
||||
})}
|
||||
${this.renderStat({
|
||||
value: metrics.workflowsQueuedCount,
|
||||
@ -365,6 +380,9 @@ export class Dashboard extends BtrixElement {
|
||||
singleLabel: msg("Collection Total"),
|
||||
pluralLabel: msg("Collections Total"),
|
||||
iconProps: { name: "collection-fill" },
|
||||
button: {
|
||||
url: "/collections",
|
||||
},
|
||||
})}
|
||||
${this.renderStat({
|
||||
value: metrics.publicCollectionsCount,
|
||||
@ -919,14 +937,15 @@ export class Dashboard extends BtrixElement {
|
||||
private renderStat(stat: {
|
||||
value: number | string | TemplateResult;
|
||||
secondaryValue?: number | string | TemplateResult;
|
||||
button?: { label?: string | TemplateResult; url: string };
|
||||
singleLabel: string;
|
||||
pluralLabel: string;
|
||||
iconProps: { name: string; library?: string; class?: string };
|
||||
}) {
|
||||
const { value, iconProps } = stat;
|
||||
return html`
|
||||
<div class="mb-2 flex items-center justify-between last:mb-0">
|
||||
<div class="flex items-center">
|
||||
<div class="mb-2 flex items-center gap-2 last:mb-0">
|
||||
<div class="mr-auto flex items-center tabular-nums">
|
||||
<sl-icon
|
||||
class=${clsx(
|
||||
"mr-2 text-base",
|
||||
@ -950,6 +969,18 @@ export class Dashboard extends BtrixElement {
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
${when(
|
||||
stat.button,
|
||||
(button) =>
|
||||
html`<btrix-button size="x-small" href=${`${this.navigate.orgBasePath}${button.url}`} @click=${this.navigate.link}
|
||||
>${
|
||||
button.label ??
|
||||
html`<sl-tooltip content=${msg("View All")} placement="right"
|
||||
><sl-icon name="arrow-right-circle"></sl-icon
|
||||
></sl-tooltip>`
|
||||
}</sl-button
|
||||
>`,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { localized, msg, str } from "@lit/localize";
|
||||
import type {
|
||||
SlChangeEvent,
|
||||
SlCheckbox,
|
||||
SlDialog,
|
||||
SlRadioGroup,
|
||||
SlSelectEvent,
|
||||
} from "@shoelace-style/shoelace";
|
||||
import clsx from "clsx";
|
||||
@ -23,6 +25,7 @@ import { BtrixElement } from "@/classes/BtrixElement";
|
||||
import { parsePage, type PageChangeEvent } from "@/components/ui/pagination";
|
||||
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 { pageHeader } from "@/layouts/pageHeader";
|
||||
import { WorkflowTab } from "@/routes";
|
||||
@ -36,7 +39,12 @@ import { tw } from "@/utils/tailwind";
|
||||
|
||||
type SearchFields = "name" | "firstSeed";
|
||||
type SortField = "lastRun" | "name" | "firstSeed" | "created" | "modified";
|
||||
type SortDirection = "asc" | "desc";
|
||||
const SORT_DIRECTIONS = ["asc", "desc"] as const;
|
||||
type SortDirection = (typeof SORT_DIRECTIONS)[number];
|
||||
type Sort = {
|
||||
field: SortField;
|
||||
direction: SortDirection;
|
||||
};
|
||||
|
||||
const FILTER_BY_CURRENT_USER_STORAGE_KEY =
|
||||
"btrix.filterByCurrentUser.crawlConfigs";
|
||||
@ -72,6 +80,16 @@ const sortableFields: Record<
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_SORT = {
|
||||
field: "lastRun",
|
||||
direction: sortableFields["lastRun"].defaultDirection!,
|
||||
} as const;
|
||||
|
||||
const USED_FILTERS = [
|
||||
"schedule",
|
||||
"isCrawlRunning",
|
||||
] as const satisfies (keyof ListWorkflow)[];
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ```ts
|
||||
@ -102,13 +120,7 @@ export class WorkflowsList extends BtrixElement {
|
||||
private workflowToDelete?: ListWorkflow;
|
||||
|
||||
@state()
|
||||
private orderBy: {
|
||||
field: SortField;
|
||||
direction: SortDirection;
|
||||
} = {
|
||||
field: "lastRun",
|
||||
direction: sortableFields["lastRun"].defaultDirection!,
|
||||
};
|
||||
private orderBy: Sort = DEFAULT_SORT;
|
||||
|
||||
@state()
|
||||
private filterBy: Partial<{ [k in keyof ListWorkflow]: boolean }> = {};
|
||||
@ -132,11 +144,83 @@ export class WorkflowsList extends BtrixElement {
|
||||
);
|
||||
}
|
||||
|
||||
searchParams = new SearchParamsController(this, (params) => {
|
||||
this.updateFiltersFromSearchParams(params);
|
||||
});
|
||||
|
||||
private updateFiltersFromSearchParams(
|
||||
params = this.searchParams.searchParams,
|
||||
) {
|
||||
const filterBy = { ...this.filterBy };
|
||||
// remove filters no longer present in search params
|
||||
for (const key of Object.keys(filterBy)) {
|
||||
if (!params.has(key)) {
|
||||
filterBy[key as keyof typeof filterBy] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// remove current user filter if not present in search params
|
||||
if (!params.has("mine")) {
|
||||
this.filterByCurrentUser = false;
|
||||
}
|
||||
|
||||
// add filters present in search params
|
||||
for (const [key, value] of params) {
|
||||
// Filter by current user
|
||||
if (key === "mine") {
|
||||
this.filterByCurrentUser = value === "true";
|
||||
}
|
||||
|
||||
// Sorting field
|
||||
if (key === "sortBy") {
|
||||
if (value in sortableFields) {
|
||||
this.orderBy = {
|
||||
field: value as SortField,
|
||||
direction:
|
||||
// Use default direction for field if available, otherwise use current direction
|
||||
sortableFields[value as SortField].defaultDirection ||
|
||||
this.orderBy.direction,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (key === "sortDir") {
|
||||
if (SORT_DIRECTIONS.includes(value as SortDirection)) {
|
||||
// Overrides sort direction if specified
|
||||
this.orderBy = { ...this.orderBy, direction: value as SortDirection };
|
||||
}
|
||||
}
|
||||
|
||||
// Ignored params
|
||||
if (["page", "mine", "sortBy", "sortDir"].includes(key)) continue;
|
||||
|
||||
// Convert string bools to filter values
|
||||
if (value === "true") {
|
||||
filterBy[key as keyof typeof filterBy] = true;
|
||||
} else if (value === "false") {
|
||||
filterBy[key as keyof typeof filterBy] = false;
|
||||
} else {
|
||||
filterBy[key as keyof typeof filterBy] = undefined;
|
||||
}
|
||||
}
|
||||
this.filterBy = { ...filterBy };
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.updateFiltersFromSearchParams();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// Apply filterByCurrentUser from session storage, and transparently update url without pushing to history stack
|
||||
// This needs to happen here instead of in the constructor because this only occurs once after the element is connected to the DOM,
|
||||
// and so it overrides the filter state set in `updateFiltersFromSearchParams` but only on first render, not on subsequent navigation.
|
||||
this.filterByCurrentUser =
|
||||
window.sessionStorage.getItem(FILTER_BY_CURRENT_USER_STORAGE_KEY) ===
|
||||
"true";
|
||||
if (this.filterByCurrentUser) {
|
||||
this.searchParams.set("mine", "true", { replace: true });
|
||||
}
|
||||
}
|
||||
|
||||
protected async willUpdate(
|
||||
@ -177,6 +261,56 @@ export class WorkflowsList extends BtrixElement {
|
||||
void this.fetchConfigSearchValues();
|
||||
}
|
||||
|
||||
protected updated(
|
||||
changedProperties: PropertyValues<this> & Map<string, unknown>,
|
||||
) {
|
||||
if (
|
||||
changedProperties.has("filterBy") ||
|
||||
changedProperties.has("filterByCurrentUser") ||
|
||||
changedProperties.has("orderBy")
|
||||
) {
|
||||
this.searchParams.update((params) => {
|
||||
// Reset page
|
||||
params.delete("page");
|
||||
|
||||
const newParams = [
|
||||
// Known filters
|
||||
...USED_FILTERS.map<[string, undefined]>((f) => [f, undefined]),
|
||||
|
||||
// Existing filters
|
||||
...Object.entries(this.filterBy),
|
||||
|
||||
// Filter by current user
|
||||
["mine", this.filterByCurrentUser || undefined],
|
||||
|
||||
// Sorting fields
|
||||
[
|
||||
"sortBy",
|
||||
this.orderBy.field !== DEFAULT_SORT.field
|
||||
? this.orderBy.field
|
||||
: undefined,
|
||||
],
|
||||
[
|
||||
"sortDir",
|
||||
this.orderBy.direction !==
|
||||
sortableFields[this.orderBy.field].defaultDirection
|
||||
? this.orderBy.direction
|
||||
: undefined,
|
||||
],
|
||||
] satisfies [string, boolean | string | undefined][];
|
||||
|
||||
for (const [filter, value] of newParams) {
|
||||
if (value !== undefined) {
|
||||
params.set(filter, value.toString());
|
||||
} else {
|
||||
params.delete(filter);
|
||||
}
|
||||
}
|
||||
return params;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
this.cancelInProgressGetWorkflows();
|
||||
super.disconnectedCallback();
|
||||
@ -389,14 +523,77 @@ export class WorkflowsList extends BtrixElement {
|
||||
|
||||
private renderControls() {
|
||||
return html`
|
||||
<div class="mb-2 flex flex-wrap items-center gap-2 md:gap-4">
|
||||
<div class="grow">${this.renderSearch()}</div>
|
||||
<div class="mb-2 flex flex-wrap items-center justify-end gap-2 md:gap-4">
|
||||
<div class=" grow basis-96">${this.renderSearch()}</div>
|
||||
|
||||
<div class="flex w-full items-center md:w-fit">
|
||||
<div class="text-0-500 mr-2 whitespace-nowrap text-sm">
|
||||
<label class="flex flex-wrap items-center" for="schedule-filter">
|
||||
<span class="mr-2 whitespace-nowrap text-sm text-neutral-500">
|
||||
${msg("Schedule:")}
|
||||
</span>
|
||||
<sl-radio-group
|
||||
size="small"
|
||||
id="schedule-filter"
|
||||
@sl-change=${(e: SlChangeEvent) => {
|
||||
const filter = (e.target as SlRadioGroup).value;
|
||||
switch (filter) {
|
||||
case "all-schedules":
|
||||
this.filterBy = {
|
||||
...this.filterBy,
|
||||
schedule: undefined,
|
||||
};
|
||||
break;
|
||||
case "scheduled":
|
||||
this.filterBy = {
|
||||
...this.filterBy,
|
||||
schedule: true,
|
||||
};
|
||||
break;
|
||||
case "unscheduled":
|
||||
this.filterBy = {
|
||||
...this.filterBy,
|
||||
schedule: false,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}}
|
||||
value=${this.filterBy.schedule === undefined
|
||||
? "all-schedules"
|
||||
: this.filterBy.schedule
|
||||
? "scheduled"
|
||||
: "unscheduled"}
|
||||
>
|
||||
<sl-tooltip content=${msg("All Schedule States")}>
|
||||
<sl-radio-button value="all-schedules" pill>
|
||||
<sl-icon
|
||||
name="asterisk"
|
||||
label=${msg("All Schedule States")}
|
||||
></sl-icon>
|
||||
</sl-radio-button>
|
||||
</sl-tooltip>
|
||||
<sl-radio-button value="unscheduled" pill>
|
||||
<sl-icon
|
||||
name="calendar2-x"
|
||||
slot="prefix"
|
||||
label=${msg("No Schedule")}
|
||||
></sl-icon>
|
||||
${msg("None")}
|
||||
</sl-radio-button>
|
||||
<sl-radio-button value="scheduled" pill>
|
||||
<sl-icon name="calendar2-check" slot="prefix"></sl-icon>
|
||||
${msg("Scheduled")}
|
||||
</sl-radio-button>
|
||||
</sl-radio-group>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center">
|
||||
<label
|
||||
class="mr-2 whitespace-nowrap text-sm text-neutral-500"
|
||||
for="sort-select"
|
||||
>
|
||||
${msg("Sort by:")}
|
||||
</div>
|
||||
</label>
|
||||
<sl-select
|
||||
id="sort-select"
|
||||
class="flex-1 md:min-w-[9.2rem]"
|
||||
size="small"
|
||||
pill
|
||||
@ -417,62 +614,44 @@ export class WorkflowsList extends BtrixElement {
|
||||
`,
|
||||
)}
|
||||
</sl-select>
|
||||
<sl-icon-button
|
||||
name="arrow-down-up"
|
||||
label=${msg("Reverse sort")}
|
||||
@click=${() => {
|
||||
this.orderBy = {
|
||||
...this.orderBy,
|
||||
direction: this.orderBy.direction === "asc" ? "desc" : "asc",
|
||||
};
|
||||
}}
|
||||
></sl-icon-button>
|
||||
<sl-tooltip
|
||||
content=${this.orderBy.direction === "asc"
|
||||
? msg("Sort in descending order")
|
||||
: msg("Sort in ascending order")}
|
||||
>
|
||||
<sl-icon-button
|
||||
name=${this.orderBy.direction === "asc"
|
||||
? "sort-up-alt"
|
||||
: "sort-down"}
|
||||
class="text-base"
|
||||
label=${this.orderBy.direction === "asc"
|
||||
? msg("Sort Descending")
|
||||
: msg("Sort Ascending")}
|
||||
@click=${() => {
|
||||
this.orderBy = {
|
||||
...this.orderBy,
|
||||
direction: this.orderBy.direction === "asc" ? "desc" : "asc",
|
||||
};
|
||||
}}
|
||||
></sl-icon-button>
|
||||
</sl-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label>
|
||||
<span class="mr-1 text-xs text-neutral-500"
|
||||
>${msg("Show Only Running")}</span
|
||||
>
|
||||
<sl-switch
|
||||
@sl-change=${(e: CustomEvent) => {
|
||||
this.filterBy = {
|
||||
...this.filterBy,
|
||||
isCrawlRunning: (e.target as SlCheckbox).checked || undefined,
|
||||
};
|
||||
}}
|
||||
?checked=${this.filterBy.isCrawlRunning === true}
|
||||
></sl-switch>
|
||||
</label>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between">
|
||||
<div class="text-sm">
|
||||
<button
|
||||
class="${this.filterBy.schedule === undefined
|
||||
? "border-b-current text-primary"
|
||||
: "text-neutral-500"} mr-3 inline-block border-b-2 border-transparent font-medium"
|
||||
aria-selected=${this.filterBy.schedule === undefined}
|
||||
@click=${() =>
|
||||
(this.filterBy = {
|
||||
...this.filterBy,
|
||||
schedule: undefined,
|
||||
})}
|
||||
>
|
||||
${msg("All")}
|
||||
</button>
|
||||
<button
|
||||
class="${this.filterBy.schedule === true
|
||||
? "border-b-current text-primary"
|
||||
: "text-neutral-500"} mr-3 inline-block border-b-2 border-transparent font-medium"
|
||||
aria-selected=${this.filterBy.schedule === true}
|
||||
@click=${() =>
|
||||
(this.filterBy = {
|
||||
...this.filterBy,
|
||||
schedule: true,
|
||||
})}
|
||||
>
|
||||
${msg("Scheduled")}
|
||||
</button>
|
||||
<button
|
||||
class="${this.filterBy.schedule === false
|
||||
? "border-b-current text-primary"
|
||||
: "text-neutral-500"} mr-3 inline-block border-b-2 border-transparent font-medium"
|
||||
aria-selected=${this.filterBy.schedule === false}
|
||||
@click=${() =>
|
||||
(this.filterBy = {
|
||||
...this.filterBy,
|
||||
schedule: false,
|
||||
})}
|
||||
>
|
||||
${msg("No schedule")}
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<label>
|
||||
<span class="mr-1 text-xs text-neutral-500"
|
||||
>${msg("Show Only Mine")}</span
|
||||
|
@ -174,7 +174,19 @@
|
||||
}
|
||||
|
||||
sl-radio-button[checked]::part(button) {
|
||||
background-color: theme(colors.primary.500);
|
||||
@apply border-primary-300 bg-primary-50 text-primary-600;
|
||||
}
|
||||
|
||||
sl-radio-button:not([checked]):not(disabled)::part(button):not(:hover) {
|
||||
@apply bg-white text-neutral-600;
|
||||
}
|
||||
|
||||
sl-radio-button:not([checked]):not(disabled):hover::part(button) {
|
||||
@apply bg-primary-400;
|
||||
}
|
||||
|
||||
sl-radio-button::part(label) {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
/* Elevate select and buttons */
|
||||
|
Loading…
Reference in New Issue
Block a user