diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts
index f112e380..0a4cd157 100644
--- a/frontend/src/components/ui/index.ts
+++ b/frontend/src/components/ui/index.ts
@@ -14,6 +14,7 @@ import("./desc-list");
import("./details");
import("./dialog");
import("./file-list");
+import("./inline-input");
import("./input");
import("./language-select");
import("./locale-picker");
diff --git a/frontend/src/components/ui/inline-input.ts b/frontend/src/components/ui/inline-input.ts
new file mode 100644
index 00000000..140558e5
--- /dev/null
+++ b/frontend/src/components/ui/inline-input.ts
@@ -0,0 +1,25 @@
+import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
+import inputStyles from "@shoelace-style/shoelace/dist/components/input/input.styles.js";
+import { css } from "lit";
+import { customElement } from "lit/decorators.js";
+
+/**
+ * Input to use inline with text.
+ */
+@customElement("btrix-inline-input")
+export class InlineInput extends SlInput {
+ static styles = [
+ inputStyles,
+ css`
+ :host {
+ --sl-input-height-small: var(--sl-font-size-x-large);
+ --sl-input-color: var(--sl-color-neutral-500);
+ }
+
+ .input--small .input__control {
+ text-align: center;
+ padding: 0 0.5ch;
+ }
+ `,
+ ] as typeof SlInput.styles;
+}
diff --git a/frontend/src/components/ui/pagination.ts b/frontend/src/components/ui/pagination.ts
index 5ab37a1b..e8a2cf35 100644
--- a/frontend/src/components/ui/pagination.ts
+++ b/frontend/src/components/ui/pagination.ts
@@ -33,17 +33,12 @@ export class Pagination extends LitElement {
static styles = [
srOnly,
css`
- :host {
- --sl-input-height-small: var(--sl-font-size-x-large);
- --sl-input-color: var(--sl-color-neutral-500);
- }
-
ul {
align-items: center;
list-style: none;
margin: 0;
padding: 0;
- color: var(--sl-input-color);
+ color: var(--sl-color-neutral-500);
}
ul.compact {
@@ -64,11 +59,6 @@ export class Pagination extends LitElement {
cursor: pointer;
}
- sl-input::part(input) {
- text-align: center;
- padding: 0 0.5ch;
- }
-
.currentPage {
display: flex;
align-items: center;
@@ -211,7 +201,7 @@ export class Pagination extends LitElement {
return html`
`;
}
diff --git a/frontend/src/components/utils/observable.ts b/frontend/src/components/utils/observable.ts
index b37ef342..5706e616 100644
--- a/frontend/src/components/utils/observable.ts
+++ b/frontend/src/components/utils/observable.ts
@@ -32,6 +32,7 @@ export class Observable extends LitElement {
disconnectedCallback(): void {
this.observer?.disconnect();
+ super.disconnectedCallback();
}
firstUpdated() {
diff --git a/frontend/src/features/archived-items/crawl-queue.ts b/frontend/src/features/archived-items/crawl-queue.ts
index b226c025..6cab108c 100644
--- a/frontend/src/features/archived-items/crawl-queue.ts
+++ b/frontend/src/features/archived-items/crawl-queue.ts
@@ -1,4 +1,9 @@
import { localized, msg, str } from "@lit/localize";
+import type {
+ SlChangeEvent,
+ SlInput,
+ SlInputEvent,
+} from "@shoelace-style/shoelace";
import type { PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { when } from "lit/directives/when.js";
@@ -60,6 +65,9 @@ export class CrawlQueue extends LiteElement {
@state()
private isLoading = false;
+ @state()
+ private pageOffset = 0;
+
@state()
private pageSize = 50;
@@ -82,7 +90,10 @@ export class CrawlQueue extends LiteElement {
changedProperties.has("orgId") ||
changedProperties.has("crawlId") ||
changedProperties.has("pageSize") ||
- changedProperties.has("regex")
+ changedProperties.has("regex") ||
+ (changedProperties.has("pageOffset") &&
+ // Prevents double-fetch when offset is programmatically changed according to queue total
+ !changedProperties.has("queue"))
) {
void this.fetchOnUpdate();
}
@@ -90,13 +101,66 @@ export class CrawlQueue extends LiteElement {
render() {
return html`
- ${msg("Queued URLs")} ${this.renderBadge()}
+
+ ${this.renderOffsetControl()} ${this.renderBadge()}
+
${this.renderContent()}
`;
}
+ private renderOffsetControl() {
+ if (!this.queue) {
+ return msg("Queued URLs");
+ }
+ if (this.pageOffset === 0 && this.queue.total <= this.pageSize) {
+ return msg(
+ str`Queued URLs from 1 to ${this.queue.total.toLocaleString()}`,
+ );
+ }
+
+ const offsetValue = this.pageOffset + 1;
+ const countMax = Math.min(
+ this.pageOffset + this.pageSize,
+ this.queue.total,
+ );
+ const getInputWidth = (v: number | string) =>
+ `${Math.max(v.toString().length, 3) + 2}ch`;
+
+ return html`
+
+ ${msg(html`
+ Queued URLs from
+ {
+ const input = e.target as SlInput;
+
+ input.style.width = getInputWidth(input.value);
+ }}
+ @sl-change=${async (e: SlChangeEvent) => {
+ const input = e.target as SlInput;
+ const int = +input.value.replace(/\D/g, "");
+
+ await this.updateComplete;
+
+ const value = Math.max(1, Math.min(int, this.queue!.total - 1));
+
+ input.value = value.toString();
+ this.pageOffset = value - 1;
+ }}
+ >
+ to ${countMax.toLocaleString()} of
+ ${this.queue.total.toLocaleString()}
+ `)}
+
+ `;
+ }
+
private renderContent() {
if (!this.queue?.total) {
if (this.isLoading) {
@@ -119,9 +183,9 @@ export class CrawlQueue extends LiteElement {
const isExcluded = !isMatch && this.isExcluded(url);
return html`
- ${idx + 1}.
+
+ ${(idx + this.pageOffset + 1).toLocaleString()}.
+
${when(
- this.queue.total === this.queue.results.length,
+ this.queue.total <= this.pageOffset + this.pageSize,
() =>
html`
${msg("End of queue")}
@@ -165,14 +229,6 @@ export class CrawlQueue extends LiteElement {
if (!this.queue) return "";
return html`
-
- ${this.queue.total
- ? this.queue.total > 1
- ? msg(str`${this.queue.total.toLocaleString()} URLs`)
- : msg(str`1 URL`)
- : msg("No queue")}
-
-
${this.matchedTotal
? html`
@@ -230,10 +286,13 @@ export class CrawlQueue extends LiteElement {
}
private async getQueue(): Promise {
- const offset = "0";
const count = this.pageSize.toString();
const regex = this.regex;
- const params = new URLSearchParams({ offset, count, regex });
+ const params = new URLSearchParams({
+ offset: this.pageOffset.toString(),
+ count,
+ regex,
+ });
const data: ResponseData = await this.apiFetch(
`/orgs/${this.orgId}/crawls/${this.crawlId}/queue?${params.toString()}`,
this.authState!,
diff --git a/frontend/src/pages/org/workflow-detail.ts b/frontend/src/pages/org/workflow-detail.ts
index b4b842a2..7d6a4d17 100644
--- a/frontend/src/pages/org/workflow-detail.ts
+++ b/frontend/src/pages/org/workflow-detail.ts
@@ -1220,7 +1220,7 @@ export class WorkflowDetail extends LiteElement {
return html`
- ${msg("Crawl URLs")}
+ ${msg("Upcoming Pages")}