fix: Fix debounced inputs (#2191)

Fixes https://github.com/webrecorder/browsertrix/issues/2190

## Changes

- Fixes not being able to add live exclusion
- Fixes not being able to delete last exclusion
- Fixes collection list search
- Refactors exclusion form event name
This commit is contained in:
sua yoo 2024-12-02 17:04:40 -08:00 committed by GitHub
parent 99115473e5
commit 2c2b8227d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 47 additions and 33 deletions

View File

@ -89,7 +89,17 @@ export class ExclusionEditor extends LiteElement {
? html`<btrix-queue-exclusion-table ? html`<btrix-queue-exclusion-table
?removable=${this.isActiveCrawl} ?removable=${this.isActiveCrawl}
.exclusions=${this.config.exclude || []} .exclusions=${this.config.exclude || []}
@btrix-remove=${this.deleteExclusion} @btrix-change=${async (e: ExclusionRemoveEvent) => {
await this.updateComplete;
const { index, regex } = e.detail;
if (this.config?.exclude && index === 0 && !regex) {
void this.deleteExclusion({
regex: this.config.exclude[index],
});
}
}}
@btrix-remove=${(e: ExclusionRemoveEvent) =>
void this.deleteExclusion({ regex: e.detail.regex })}
> >
</btrix-queue-exclusion-table>` </btrix-queue-exclusion-table>`
: html` : html`
@ -102,8 +112,8 @@ export class ExclusionEditor extends LiteElement {
<btrix-queue-exclusion-form <btrix-queue-exclusion-form
?isSubmitting=${this.isSubmitting} ?isSubmitting=${this.isSubmitting}
fieldErrorMessage=${this.exclusionFieldErrorMessage} fieldErrorMessage=${this.exclusionFieldErrorMessage}
@on-change=${this.handleRegexChange} @btrix-change=${this.handleRegexChange}
@on-add=${this.handleAddRegex} @btrix-add=${this.handleAddRegex}
> >
</btrix-queue-exclusion-form> </btrix-queue-exclusion-form>
</div>` </div>`
@ -138,9 +148,7 @@ export class ExclusionEditor extends LiteElement {
} }
} }
private async deleteExclusion(e: ExclusionRemoveEvent) { private async deleteExclusion({ regex }: { regex: string }) {
const { regex } = e.detail;
try { try {
const params = new URLSearchParams({ regex }); const params = new URLSearchParams({ regex });
const data = await this.apiFetch<{ success: boolean }>( const data = await this.apiFetch<{ success: boolean }>(

View File

@ -1,7 +1,7 @@
import { localized, msg } from "@lit/localize"; import { localized, msg } from "@lit/localize";
import { type SlInput, type SlSelect } from "@shoelace-style/shoelace"; import { type SlInput, type SlSelect } from "@shoelace-style/shoelace";
import { type PropertyValues } from "lit"; import { type PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, query, state } from "lit/decorators.js";
import debounce from "lodash/fp/debounce"; import debounce from "lodash/fp/debounce";
import type { UnderlyingFunction } from "@/types/utils"; import type { UnderlyingFunction } from "@/types/utils";
@ -31,13 +31,13 @@ const MIN_LENGTH = 2;
* Usage example: * Usage example:
* ```ts * ```ts
* <btrix-queue-exclusion-form * <btrix-queue-exclusion-form
* @on-change=${this.handleExclusionChange} * @btrix-change=${this.handleExclusionChange}
* @on-add=${this.handleExclusionAdd} * @btrix-add=${this.handleExclusionAdd}
* ></btrix-queue-exclusion-form> * ></btrix-queue-exclusion-form>
* ``` * ```
* *
* @event on-change ExclusionChangeEvent * @fires btrix-change ExclusionChangeEvent
* @event on-add ExclusionAddEvent * @fires btrix-add ExclusionAddEvent
*/ */
@localized() @localized()
@customElement("btrix-queue-exclusion-form") @customElement("btrix-queue-exclusion-form")
@ -52,18 +52,21 @@ export class QueueExclusionForm extends LiteElement {
private selectValue: Exclusion["type"] = "text"; private selectValue: Exclusion["type"] = "text";
@state() @state()
private inputValue = ""; private regex = "";
@state() @state()
private isRegexInvalid = false; private isRegexInvalid = false;
@query("sl-input")
private readonly input?: SlInput | null;
async willUpdate( async willUpdate(
changedProperties: PropertyValues<this> & Map<string, unknown>, changedProperties: PropertyValues<this> & Map<string, unknown>,
) { ) {
if ( if (
changedProperties.get("selectValue") || changedProperties.get("selectValue") ||
(changedProperties.has("inputValue") && (changedProperties.has("regex") &&
changedProperties.get("inputValue") !== undefined) changedProperties.get("regex") !== undefined)
) { ) {
this.fieldErrorMessage = ""; this.fieldErrorMessage = "";
this.checkInputValidity(); this.checkInputValidity();
@ -107,7 +110,6 @@ export class QueueExclusionForm extends LiteElement {
placeholder=${this.selectValue === "text" placeholder=${this.selectValue === "text"
? "/skip-this-page" ? "/skip-this-page"
: "example.com/skip.*"} : "example.com/skip.*"}
.value=${this.inputValue}
?disabled=${this.isSubmitting} ?disabled=${this.isSubmitting}
@keydown=${this.onKeyDown} @keydown=${this.onKeyDown}
@sl-input=${this.onInput as UnderlyingFunction< @sl-input=${this.onInput as UnderlyingFunction<
@ -151,7 +153,7 @@ export class QueueExclusionForm extends LiteElement {
<btrix-button <btrix-button
variant="neutral" variant="neutral"
raised raised
?disabled=${!this.inputValue || ?disabled=${!this.regex ||
this.isRegexInvalid || this.isRegexInvalid ||
this.isSubmitting} this.isSubmitting}
?loading=${this.isSubmitting} ?loading=${this.isSubmitting}
@ -166,8 +168,8 @@ export class QueueExclusionForm extends LiteElement {
`; `;
} }
private readonly onInput = debounce(200)((e: Event) => { private readonly onInput = debounce(200)(() => {
this.inputValue = (e.target as SlInput).value; this.regex = this.input?.value || "";
}); });
private onButtonClick() { private onButtonClick() {
@ -184,12 +186,12 @@ export class QueueExclusionForm extends LiteElement {
private checkInputValidity(): void { private checkInputValidity(): void {
let isValid = true; let isValid = true;
if (!this.inputValue || this.inputValue.length < MIN_LENGTH) { if (!this.regex || this.regex.length < MIN_LENGTH) {
isValid = false; isValid = false;
} else if (this.selectValue === "regex") { } else if (this.selectValue === "regex") {
try { try {
// Check if valid regex // Check if valid regex
new RegExp(this.inputValue); new RegExp(this.regex);
} catch (err) { } catch (err) {
this.fieldErrorMessage = (err as Error).message; this.fieldErrorMessage = (err as Error).message;
isValid = false; isValid = false;
@ -202,12 +204,10 @@ export class QueueExclusionForm extends LiteElement {
private async dispatchChangeEvent() { private async dispatchChangeEvent() {
await this.updateComplete; await this.updateComplete;
this.dispatchEvent( this.dispatchEvent(
new CustomEvent("on-change", { new CustomEvent("btrix-change", {
detail: { detail: {
value: value:
this.selectValue === "text" this.selectValue === "text" ? regexEscape(this.regex) : this.regex,
? regexEscape(this.inputValue)
: this.inputValue,
valid: !this.isRegexInvalid, valid: !this.isRegexInvalid,
}, },
}) as ExclusionChangeEvent, }) as ExclusionChangeEvent,
@ -217,19 +217,23 @@ export class QueueExclusionForm extends LiteElement {
private async handleAdd() { private async handleAdd() {
this.onInput.flush(); this.onInput.flush();
await this.updateComplete; await this.updateComplete;
if (!this.inputValue) return; if (!this.regex) return;
let regex = this.inputValue; if (this.input) {
this.input.value = "";
}
let regex = this.regex;
if (this.selectValue === "text") { if (this.selectValue === "text") {
regex = regexEscape(this.inputValue); regex = regexEscape(this.regex);
} }
this.dispatchEvent( this.dispatchEvent(
new CustomEvent("on-add", { new CustomEvent("btrix-add", {
detail: { detail: {
regex, regex,
onSuccess: () => { onSuccess: () => {
this.inputValue = ""; this.regex = "";
}, },
}, },
}) as ExclusionAddEvent, }) as ExclusionAddEvent,

View File

@ -2,7 +2,7 @@ import { localized, msg } from "@lit/localize";
import type { SlInput, SlMenuItem } from "@shoelace-style/shoelace"; import type { SlInput, SlMenuItem } from "@shoelace-style/shoelace";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { html, type PropertyValues } from "lit"; import { html, type PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, query, state } from "lit/decorators.js";
import { guard } from "lit/directives/guard.js"; import { guard } from "lit/directives/guard.js";
import { when } from "lit/directives/when.js"; import { when } from "lit/directives/when.js";
import debounce from "lodash/fp/debounce"; import debounce from "lodash/fp/debounce";
@ -91,6 +91,9 @@ export class CollectionsList extends BtrixElement {
@state() @state()
private fetchErrorStatusCode?: number; private fetchErrorStatusCode?: number;
@query("sl-input")
private readonly input?: SlInput | null;
// For fuzzy search: // For fuzzy search:
private readonly fuse = new Fuse<{ key: "name"; value: string }>([], { private readonly fuse = new Fuse<{ key: "name"; value: string }>([], {
keys: ["value"], keys: ["value"],
@ -339,7 +342,6 @@ export class CollectionsList extends BtrixElement {
size="small" size="small"
placeholder=${msg("Search by Name")} placeholder=${msg("Search by Name")}
clearable clearable
value=${this.searchByValue}
@sl-clear=${() => { @sl-clear=${() => {
this.searchResultsOpen = false; this.searchResultsOpen = false;
this.onSearchInput.cancel(); this.onSearchInput.cancel();
@ -627,8 +629,8 @@ export class CollectionsList extends BtrixElement {
</div> </div>
`; `;
private readonly onSearchInput = debounce(150)((e: Event) => { private readonly onSearchInput = debounce(150)(() => {
this.searchByValue = (e.target as SlInput).value.trim(); this.searchByValue = this.input?.value.trim() || "";
if (!this.searchResultsOpen && this.hasSearchStr) { if (!this.searchResultsOpen && this.hasSearchStr) {
this.searchResultsOpen = true; this.searchResultsOpen = true;