Add new WIP QA Review page (#1500)
Resolves https://github.com/webrecorder/browsertrix-cloud/issues/1493 <!-- Fixes #issue_number --> ### Changes Adds WIP QA page with basic grid layout sections and navigation. ### Manual testing Page can be access by adding `/review/screenshots` or `/review/replay` to a crawl detail page URL. For example: ``` /orgs/suas-dev-sandbox-2/items/crawl/manual-20240124023524-422e41d6-97d/review/screenshots ``` --------- Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
parent
a8e3ff1141
commit
91ff95c8e9
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -46,5 +46,6 @@
|
||||
}
|
||||
],
|
||||
"eslint.workingDirectories": ["./frontend"],
|
||||
"eslint.nodePath": "./frontend/node_modules"
|
||||
"eslint.nodePath": "./frontend/node_modules",
|
||||
"tailwindCSS.experimental.classRegex": ["tw`([^`]*)"]
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { TailwindElement } from "@/classes/TailwindElement";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
import { html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
/**
|
||||
@ -10,48 +12,33 @@ import { customElement, property } from "lit/decorators.js";
|
||||
* ```
|
||||
*/
|
||||
@customElement("btrix-badge")
|
||||
export class Badge extends LitElement {
|
||||
export class Badge extends TailwindElement {
|
||||
@property({ type: String })
|
||||
variant: "success" | "warning" | "danger" | "neutral" = "neutral";
|
||||
variant:
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "neutral"
|
||||
| "primary"
|
||||
| "high-contrast" = "neutral";
|
||||
|
||||
// postcss-lit-disable-next-line
|
||||
static styles = css`
|
||||
:host > span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
line-height: 1.125rem;
|
||||
height: 1.125rem;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
vertical-align: 1px;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--sl-color-success-500);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--sl-color-warning-600);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--sl-color-danger-500);
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
|
||||
.neutral {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
color: var(--sl-color-neutral-600);
|
||||
}
|
||||
`;
|
||||
@property({ type: String, reflect: true })
|
||||
role: string | null = "status";
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span class=${this.variant}>
|
||||
<span
|
||||
class="h-4.5 ${{
|
||||
success: tw`bg-success-500 text-neutral-0`,
|
||||
warning: tw`bg-warning-600 text-neutral-0`,
|
||||
danger: tw`bg-danger-500 text-neutral-0`,
|
||||
neutral: tw`bg-neutral-100 text-neutral-600`,
|
||||
"high-contrast": tw`bg-neutral-600 text-neutral-0`,
|
||||
primary: tw`bg-primary text-neutral-0`,
|
||||
}[
|
||||
this.variant
|
||||
]} inline-flex items-center justify-center rounded-sm px-2 align-[1px] text-xs"
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
/* eslint-disable lit/binding-positions */
|
||||
/* eslint-disable lit/no-invalid-html */
|
||||
import { LitElement, css } from "lit";
|
||||
import { css } from "lit";
|
||||
import { html, literal } from "lit/static-html.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { TailwindElement } from "@/classes/TailwindElement";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
|
||||
/**
|
||||
* Custom styled button
|
||||
@ -15,7 +16,7 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
* ```
|
||||
*/
|
||||
@customElement("btrix-button")
|
||||
export class Button extends LitElement {
|
||||
export class Button extends TailwindElement {
|
||||
@property({ type: String })
|
||||
type: "submit" | "button" = "button";
|
||||
|
||||
@ -40,7 +41,6 @@ export class Button extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
icon = false;
|
||||
|
||||
// postcss-lit-disable-next-line
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
@ -50,89 +50,24 @@ export class Button extends LitElement {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
all: unset;
|
||||
display: flex;
|
||||
gap: var(--sl-spacing-x-small);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--sl-border-radius-small);
|
||||
box-sizing: border-box;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transform: translateY(0px);
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
box-shadow 0.15s,
|
||||
color 0.15s,
|
||||
transform 0.15s;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--sl-color-neutral-100) !important;
|
||||
color: var(--sl-color-neutral-300) !important;
|
||||
}
|
||||
|
||||
.button.icon {
|
||||
min-width: 1.5rem;
|
||||
min-height: 1.5rem;
|
||||
padding: 0 var(--sl-spacing-2x-small);
|
||||
}
|
||||
|
||||
.button:not(.icon) {
|
||||
height: var(--sl-input-height-small);
|
||||
padding: 0 var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.raised {
|
||||
box-shadow: var(--sl-shadow-x-small);
|
||||
}
|
||||
|
||||
:not([aria-disabled]) .raised:not([disabled]):hover {
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--sl-color-blue-50);
|
||||
color: var(--sl-color-blue-600);
|
||||
}
|
||||
|
||||
:not([aria-disabled]) .primary:hover {
|
||||
background-color: var(--sl-color-blue-100);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--sl-color-danger-50);
|
||||
color: var(--sl-color-danger-600);
|
||||
}
|
||||
|
||||
:not([aria-disabled]) .danger:hover {
|
||||
background-color: var(--sl-color-danger-100);
|
||||
}
|
||||
|
||||
.neutral {
|
||||
color: var(--sl-color-neutral-600);
|
||||
}
|
||||
|
||||
.neutral:hover {
|
||||
color: var(--sl-color-blue-500);
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
const tag = this.href ? literal`a` : literal`button`;
|
||||
return html`<${tag}
|
||||
type=${this.type === "submit" ? "submit" : "button"}
|
||||
class=${classMap({
|
||||
button: true,
|
||||
[this.variant]: true,
|
||||
icon: this.icon,
|
||||
raised: this.raised,
|
||||
})}
|
||||
class=${[
|
||||
tw`flex h-6 cursor-pointer items-center justify-center gap-2 rounded-sm text-center font-medium transition-all disabled:cursor-not-allowed disabled:text-neutral-300`,
|
||||
this.icon ? tw`min-h-6 min-w-6 px-1` : tw`h-6 px-2`,
|
||||
this.raised ? tw`shadow-sm` : "",
|
||||
{
|
||||
primary: tw`bg-blue-50 text-blue-600 shadow-blue-800/20 hover:bg-blue-100`,
|
||||
danger: tw`shadow-danger-800/20 bg-danger-50 text-danger-600 hover:bg-danger-100`,
|
||||
neutral: tw`text-neutral-600 hover:text-blue-600`,
|
||||
}[this.variant],
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
?disabled=${this.disabled}
|
||||
href=${ifDefined(this.href)}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import "./alert";
|
||||
import "./badge";
|
||||
import "./navigation";
|
||||
import("./button");
|
||||
import("./code");
|
||||
import("./combobox");
|
||||
|
||||
1
frontend/src/components/ui/navigation/index.ts
Normal file
1
frontend/src/components/ui/navigation/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./navigation-button";
|
||||
99
frontend/src/components/ui/navigation/navigation-button.ts
Normal file
99
frontend/src/components/ui/navigation/navigation-button.ts
Normal file
@ -0,0 +1,99 @@
|
||||
/* eslint-disable lit/binding-positions */
|
||||
/* eslint-disable lit/no-invalid-html */
|
||||
import { type PropertyValueMap, css } from "lit";
|
||||
import { html, literal } from "lit/static-html.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { TailwindElement } from "@/classes/TailwindElement";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
|
||||
/**
|
||||
* Custom styled button
|
||||
*
|
||||
* Usage example:
|
||||
* ```ts
|
||||
* <btrix-navigation-button>Click me</btrix-navigation-button>
|
||||
* ```
|
||||
*/
|
||||
@customElement("btrix-navigation-button")
|
||||
export class Button extends TailwindElement {
|
||||
@property({ type: Boolean })
|
||||
active = false;
|
||||
|
||||
@property({ type: String })
|
||||
type: "submit" | "button" = "button";
|
||||
|
||||
@property({ type: String })
|
||||
label?: string;
|
||||
|
||||
@property({ type: String })
|
||||
href?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
disabled = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
icon = false;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
role: ARIAMixin["role"] = "tab";
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValueMap<this>) {
|
||||
if (changedProperties.has("active")) {
|
||||
this.ariaSelected = this.active ? "true" : null;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
::slotted(sl-icon) {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
const tag = this.href ? literal`a` : literal`button`;
|
||||
return html`<${tag}
|
||||
type=${this.type === "submit" ? "submit" : "button"}
|
||||
class=${[
|
||||
tw`flex w-full cursor-pointer items-center justify-start gap-2 rounded-sm px-2 py-4 font-medium outline-primary-600 transition hover:transition-none focus-visible:outline focus-visible:outline-3 focus-visible:outline-offset-1 disabled:cursor-not-allowed disabled:bg-transparent disabled:opacity-50`,
|
||||
this.icon ? tw`min-h-6 min-w-6` : tw`h-6`,
|
||||
this.active
|
||||
? tw`bg-blue-100 text-blue-600 shadow-sm shadow-blue-900/40 hover:bg-blue-100`
|
||||
: tw`text-neutral-600 hover:bg-blue-50`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
?disabled=${this.disabled}
|
||||
href=${ifDefined(this.href)}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
@click=${this.handleClick}
|
||||
>
|
||||
<slot></slot>
|
||||
</${tag}>`;
|
||||
}
|
||||
|
||||
private handleClick(e: MouseEvent) {
|
||||
if (this.disabled) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.type === "submit") {
|
||||
this.submit();
|
||||
}
|
||||
}
|
||||
|
||||
private submit() {
|
||||
const form = (this.closest("form") || this.closest("form"))!;
|
||||
|
||||
if (form) {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,7 @@ import { property, queryAsync, customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
const DEFAULT_PANEL_ID = "default-panel";
|
||||
// Breakpoint in pixels for 2-column layout
|
||||
const TWO_COL_SCREEN_MIN = 1032;
|
||||
export const TWO_COL_SCREEN_MIN_CSS = css`64.5rem`;
|
||||
|
||||
/**
|
||||
* Tab list
|
||||
@ -92,7 +91,7 @@ export class TabList extends LitElement {
|
||||
grid-gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN}px) {
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
|
||||
.container {
|
||||
grid-template-areas:
|
||||
". header"
|
||||
@ -105,7 +104,7 @@ export class TabList extends LitElement {
|
||||
grid-area: menu;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN}px) {
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
|
||||
.navWrapper {
|
||||
overflow: initial;
|
||||
}
|
||||
@ -140,7 +139,7 @@ export class TabList extends LitElement {
|
||||
margin-left: var(--track-width);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN}px) {
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
|
||||
.tablist {
|
||||
display: block;
|
||||
}
|
||||
@ -165,7 +164,7 @@ export class TabList extends LitElement {
|
||||
background-color: var(--sl-color-blue-500);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN}px) {
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
|
||||
.tablist,
|
||||
.show-indicator .track,
|
||||
.show-indicator .indicator {
|
||||
|
||||
@ -664,6 +664,9 @@ export class App extends LiteElement {
|
||||
// falls through
|
||||
}
|
||||
|
||||
case "components":
|
||||
return html`<btrix-components></btrix-components>`;
|
||||
|
||||
default:
|
||||
return this.renderNotFoundPage();
|
||||
}
|
||||
|
||||
182
frontend/src/pages/org/archived-item-qa.ts
Normal file
182
frontend/src/pages/org/archived-item-qa.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { html, css, nothing, type PropertyValues } from "lit";
|
||||
import { state, property, customElement } from "lit/decorators.js";
|
||||
import { msg, localized } from "@lit/localize";
|
||||
import { choose } from "lit/directives/choose.js";
|
||||
|
||||
import { TailwindElement } from "@/classes/TailwindElement";
|
||||
import { type AuthState } from "@/utils/AuthService";
|
||||
import { TWO_COL_SCREEN_MIN_CSS } from "@/components/ui/tab-list";
|
||||
import { NavigateController } from "@/controllers/navigate";
|
||||
import { APIController } from "@/controllers/api";
|
||||
import { NotifyController } from "@/controllers/notify";
|
||||
import { renderName } from "@/utils/crawler";
|
||||
import { type ArchivedItem } from "@/types/crawler";
|
||||
|
||||
export type QATab = "screenshots" | "replay";
|
||||
|
||||
@localized()
|
||||
@customElement("btrix-archived-item-qa")
|
||||
export class ArchivedItemQA extends TailwindElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
height: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
article {
|
||||
flex-grow: 1;
|
||||
display: grid;
|
||||
grid-gap: 1rem;
|
||||
grid-template:
|
||||
"mainHeader"
|
||||
"main"
|
||||
"pageListHeader"
|
||||
"pageList";
|
||||
grid-template-rows: repeat(4, max-content);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
|
||||
article {
|
||||
grid-template:
|
||||
"mainHeader pageListHeader"
|
||||
"main pageList";
|
||||
grid-template-columns: 1fr 24rem;
|
||||
grid-template-rows: min-content 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.mainHeader {
|
||||
grid-area: mainHeader;
|
||||
}
|
||||
|
||||
.pageListHeader {
|
||||
grid-area: pageListHeader;
|
||||
}
|
||||
|
||||
.main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
.pageList {
|
||||
grid-area: pageList;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ type: Object })
|
||||
authState?: AuthState;
|
||||
|
||||
@property({ type: String })
|
||||
orgId?: string;
|
||||
|
||||
@property({ type: String })
|
||||
itemId?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
isCrawler = false;
|
||||
|
||||
@property({ type: String })
|
||||
tab: QATab = "screenshots";
|
||||
|
||||
@state()
|
||||
private item?: ArchivedItem;
|
||||
|
||||
private readonly api = new APIController(this);
|
||||
private readonly navigate = new NavigateController(this);
|
||||
private readonly notify = new NotifyController(this);
|
||||
|
||||
protected willUpdate(
|
||||
changedProperties: PropertyValues<this> | Map<PropertyKey, unknown>,
|
||||
): void {
|
||||
if (changedProperties.has("itemId") && this.itemId) {
|
||||
void this.fetchArchivedItem();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const crawlBaseUrl = `${this.navigate.orgBasePath}/items/crawl/${this.itemId}`;
|
||||
const itemName = this.item ? renderName(this.item) : nothing;
|
||||
return html`
|
||||
<nav class="mb-7">
|
||||
<a
|
||||
class="text-sm font-medium text-neutral-500 hover:text-neutral-600"
|
||||
href=${`${crawlBaseUrl}`}
|
||||
@click=${this.navigate.link}
|
||||
>
|
||||
<sl-icon
|
||||
name="arrow-left"
|
||||
class="inline-block align-middle"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle">
|
||||
${msg("Back to")} ${itemName}
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<header class="mainHeader outline">
|
||||
<h1>${msg("Review")} — ${itemName}</h1>
|
||||
</header>
|
||||
<section class="main outline">
|
||||
<nav>
|
||||
<btrix-button
|
||||
id="screenshot-tab"
|
||||
href=${`${crawlBaseUrl}/review/screenshots`}
|
||||
variant=${this.tab === "screenshots" ? "primary" : "neutral"}
|
||||
?raised=${this.tab === "screenshots"}
|
||||
@click=${this.navigate.link}
|
||||
>${msg("Screenshots")}</btrix-button
|
||||
>
|
||||
<btrix-button
|
||||
id="replay-tab"
|
||||
href=${`${crawlBaseUrl}/review/replay`}
|
||||
variant=${this.tab === "replay" ? "primary" : "neutral"}
|
||||
?raised=${this.tab === "replay"}
|
||||
@click=${this.navigate.link}
|
||||
>${msg("Replay")}</btrix-button
|
||||
>
|
||||
</nav>
|
||||
<div role="region" aria-labelledby="${this.tab}-tab">
|
||||
${choose(
|
||||
this.tab,
|
||||
[
|
||||
["screenshots", this.renderScreenshots],
|
||||
["replay", this.renderReplay],
|
||||
],
|
||||
() => html`<btrix-not-found></btrix-not-found>`,
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<h2 class="pageListHeader outline">
|
||||
${msg("Pages List")} <sl-button>${msg("Finish Review")}</sl-button>
|
||||
</h2>
|
||||
<section class="pageList outline">[page list]</section>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
private readonly renderScreenshots = () => {
|
||||
return html`[screenshots]`;
|
||||
};
|
||||
|
||||
private readonly renderReplay = () => {
|
||||
return html`[replay]`;
|
||||
};
|
||||
|
||||
private async fetchArchivedItem(): Promise<void> {
|
||||
try {
|
||||
this.item = await this.getArchivedItem();
|
||||
} catch {
|
||||
this.notify.toast({
|
||||
message: msg("Sorry, couldn't retrieve archived item at this time."),
|
||||
variant: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async getArchivedItem(): Promise<ArchivedItem> {
|
||||
const apiPath = `/orgs/${this.orgId}/all-crawls/${this.itemId}`;
|
||||
return this.api.fetch<ArchivedItem>(apiPath, this.authState!);
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ import "./workflows-list";
|
||||
import "./workflows-new";
|
||||
import "./archived-item-detail";
|
||||
import "./archived-items";
|
||||
import "./archived-item-qa";
|
||||
import "./collections-list";
|
||||
import "./collection-detail";
|
||||
import "./browser-profiles-detail";
|
||||
@ -30,6 +31,7 @@ import type {
|
||||
OrgRemoveMemberEvent,
|
||||
} from "./settings";
|
||||
import type { Tab as CollectionTab } from "./collection-detail";
|
||||
import type { QATab } from "./archived-item-qa";
|
||||
import type { SelectJobTypeEvent } from "@/features/crawl-workflows/new-workflow-dialog";
|
||||
import type { QuotaUpdateDetail } from "@/controllers/api";
|
||||
import { type TemplateResult } from "lit";
|
||||
@ -39,27 +41,34 @@ import type { CollectionSavedEvent } from "@/features/collections/collection-met
|
||||
const RESOURCE_NAMES = ["workflow", "collection", "browser-profile", "upload"];
|
||||
type ResourceName = (typeof RESOURCE_NAMES)[number];
|
||||
export type SelectNewDialogEvent = CustomEvent<ResourceName>;
|
||||
export type OrgTab =
|
||||
| "home"
|
||||
| "crawls"
|
||||
| "workflows"
|
||||
| "items"
|
||||
| "browser-profiles"
|
||||
| "collections"
|
||||
| "settings";
|
||||
|
||||
type Params = {
|
||||
export type OrgParams = {
|
||||
home: Record<string, never>;
|
||||
workflows: {
|
||||
workflowId?: string;
|
||||
jobType?: JobType;
|
||||
new?: ResourceName;
|
||||
};
|
||||
items: {
|
||||
itemType?: Crawl["type"];
|
||||
itemId?: string;
|
||||
qaTab?: QATab;
|
||||
workflowId?: string;
|
||||
collectionId?: string;
|
||||
};
|
||||
"browser-profiles": {
|
||||
browserProfileId?: string;
|
||||
browserId?: string;
|
||||
itemId?: string;
|
||||
new?: ResourceName;
|
||||
};
|
||||
collections: {
|
||||
collectionId?: string;
|
||||
collectionTab?: string;
|
||||
itemType?: Crawl["type"];
|
||||
jobType?: JobType;
|
||||
};
|
||||
settings: {
|
||||
settingsTab?: "information" | "members";
|
||||
new?: ResourceName;
|
||||
};
|
||||
};
|
||||
export type OrgTab = keyof OrgParams;
|
||||
|
||||
const defaultTab = "home";
|
||||
|
||||
@ -90,7 +99,7 @@ export class Org extends LiteElement {
|
||||
orgPath!: string;
|
||||
|
||||
@property({ type: Object })
|
||||
params!: Params;
|
||||
params: OrgParams[OrgTab] = {};
|
||||
|
||||
@property({ type: String })
|
||||
orgTab: OrgTab = defaultTab;
|
||||
@ -253,7 +262,7 @@ export class Org extends LiteElement {
|
||||
tabPanelContent = this.renderDashboard();
|
||||
break;
|
||||
case "items":
|
||||
tabPanelContent = this.renderArchive();
|
||||
tabPanelContent = this.renderArchivedItem();
|
||||
break;
|
||||
case "workflows":
|
||||
tabPanelContent = this.renderWorkflows();
|
||||
@ -278,18 +287,23 @@ export class Org extends LiteElement {
|
||||
break;
|
||||
}
|
||||
|
||||
const noMaxWidth =
|
||||
this.orgTab === "items" && (this.params as OrgParams["items"]).qaTab;
|
||||
|
||||
return html`
|
||||
<div class="flex min-h-full flex-col">
|
||||
${this.renderStorageAlert()} ${this.renderExecutionMinutesAlert()}
|
||||
${this.renderOrgNavBar()}
|
||||
<main>
|
||||
<div
|
||||
class="mx-auto box-border w-full max-w-screen-desktop px-3 py-7"
|
||||
<main
|
||||
class="${noMaxWidth
|
||||
? "w-full"
|
||||
: "w-full max-w-screen-desktop"} mx-auto box-border flex flex-1 flex-col p-3 pt-7"
|
||||
aria-labelledby="${this.orgTab}-tab"
|
||||
>
|
||||
${tabPanelContent}
|
||||
</div>
|
||||
</main>
|
||||
${this.renderNewResourceDialogs()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -499,15 +513,28 @@ export class Org extends LiteElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderArchive() {
|
||||
if (this.params.itemId) {
|
||||
private renderArchivedItem() {
|
||||
const params = this.params as OrgParams["items"];
|
||||
|
||||
if (params.itemId) {
|
||||
if (params.qaTab) {
|
||||
return html` <btrix-archived-item-qa
|
||||
class="flex-1"
|
||||
.authState=${this.authState!}
|
||||
orgId=${this.orgId}
|
||||
itemId=${params.itemId}
|
||||
tab=${params.qaTab}
|
||||
?isCrawler=${this.isCrawler}
|
||||
></btrix-archived-item-qa>`;
|
||||
}
|
||||
|
||||
return html` <btrix-archived-item-detail
|
||||
.authState=${this.authState!}
|
||||
orgId=${this.orgId}
|
||||
crawlId=${this.params.itemId}
|
||||
collectionId=${this.params.collectionId || ""}
|
||||
workflowId=${this.params.workflowId || ""}
|
||||
itemType=${this.params.itemType || "crawl"}
|
||||
crawlId=${params.itemId}
|
||||
collectionId=${params.collectionId || ""}
|
||||
workflowId=${params.workflowId || ""}
|
||||
itemType=${params.itemType || "crawl"}
|
||||
?isCrawler=${this.isCrawler}
|
||||
></btrix-archived-item-detail>`;
|
||||
}
|
||||
@ -518,17 +545,17 @@ export class Org extends LiteElement {
|
||||
orgId=${this.orgId}
|
||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||
?isCrawler=${this.isCrawler}
|
||||
itemType=${ifDefined(this.params.itemType || undefined)}
|
||||
itemType=${ifDefined(params.itemType || undefined)}
|
||||
@select-new-dialog=${this.onSelectNewDialog}
|
||||
></btrix-archived-items>`;
|
||||
}
|
||||
|
||||
private renderWorkflows() {
|
||||
const isEditing = Object.prototype.hasOwnProperty.call(this.params, "edit");
|
||||
const params = this.params as OrgParams["workflows"];
|
||||
const isEditing = Object.prototype.hasOwnProperty.call(params, "edit");
|
||||
const isNewResourceTab =
|
||||
Object.prototype.hasOwnProperty.call(this.params, "new") &&
|
||||
this.params.jobType;
|
||||
const workflowId = this.params.workflowId;
|
||||
Object.prototype.hasOwnProperty.call(params, "new") && params.jobType;
|
||||
const workflowId = params.workflowId;
|
||||
|
||||
if (workflowId) {
|
||||
return html`
|
||||
@ -557,7 +584,7 @@ export class Org extends LiteElement {
|
||||
?isCrawler=${this.isCrawler}
|
||||
.initialWorkflow=${workflow}
|
||||
.initialSeeds=${seeds}
|
||||
jobType=${ifDefined(this.params.jobType)}
|
||||
jobType=${ifDefined(params.jobType)}
|
||||
?orgStorageQuotaReached=${this.orgStorageQuotaReached}
|
||||
?orgExecutionMinutesQuotaReached=${this.orgExecutionMinutesQuotaReached}
|
||||
@select-new-dialog=${this.onSelectNewDialog}
|
||||
@ -576,19 +603,21 @@ export class Org extends LiteElement {
|
||||
}
|
||||
|
||||
private renderBrowserProfiles() {
|
||||
if (this.params.browserProfileId) {
|
||||
const params = this.params as OrgParams["browser-profiles"];
|
||||
|
||||
if (params.browserProfileId) {
|
||||
return html`<btrix-browser-profiles-detail
|
||||
.authState=${this.authState!}
|
||||
.orgId=${this.orgId}
|
||||
profileId=${this.params.browserProfileId}
|
||||
profileId=${params.browserProfileId}
|
||||
></btrix-browser-profiles-detail>`;
|
||||
}
|
||||
|
||||
if (this.params.browserId) {
|
||||
if (params.browserId) {
|
||||
return html`<btrix-browser-profiles-new
|
||||
.authState=${this.authState!}
|
||||
.orgId=${this.orgId}
|
||||
.browserId=${this.params.browserId}
|
||||
.browserId=${params.browserId}
|
||||
></btrix-browser-profiles-new>`;
|
||||
}
|
||||
|
||||
@ -600,15 +629,16 @@ export class Org extends LiteElement {
|
||||
}
|
||||
|
||||
private renderCollections() {
|
||||
if (this.params.collectionId) {
|
||||
const params = this.params as OrgParams["collections"];
|
||||
|
||||
if (params.collectionId) {
|
||||
return html`<btrix-collection-detail
|
||||
.authState=${this.authState!}
|
||||
orgId=${this.orgId}
|
||||
userId=${this.userInfo!.id}
|
||||
collectionId=${this.params.collectionId}
|
||||
collectionTab=${(this.params.collectionTab as
|
||||
| CollectionTab
|
||||
| undefined) || "replay"}
|
||||
collectionId=${params.collectionId}
|
||||
collectionTab=${(params.collectionTab as CollectionTab | undefined) ||
|
||||
"replay"}
|
||||
?isCrawler=${this.isCrawler}
|
||||
></btrix-collection-detail>`;
|
||||
}
|
||||
@ -623,7 +653,8 @@ export class Org extends LiteElement {
|
||||
|
||||
private renderOrgSettings() {
|
||||
if (!this.userInfo || !this.org) return;
|
||||
const activePanel = this.params.settingsTab || "information";
|
||||
const params = this.params as OrgParams["settings"];
|
||||
const activePanel = params.settingsTab || "information";
|
||||
const isAddingMember = Object.prototype.hasOwnProperty.call(
|
||||
this.params,
|
||||
"invite",
|
||||
|
||||
@ -14,7 +14,7 @@ export const ROUTES = {
|
||||
"/orgs/:slug",
|
||||
// Org sections:
|
||||
"(/workflows(/crawls)(/crawl/:workflowId))",
|
||||
"(/items(/:itemType(/:itemId)))",
|
||||
"(/items(/:itemType(/:itemId(/review/:qaTab))))",
|
||||
"(/collections(/new)(/view/:collectionId(/:collectionTab)))",
|
||||
"(/browser-profiles(/profile(/browser/:browserId)(/:browserProfileId)))",
|
||||
"(/settings(/:settingsTab))",
|
||||
|
||||
@ -40,11 +40,23 @@ function makeTheme() {
|
||||
// Map color grading:
|
||||
const colorGrades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
|
||||
/**
|
||||
* @param {string} color
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
const makeColorPalette = (color) =>
|
||||
colorGrades.reduce((acc, v) => ({
|
||||
colorGrades.reduce(
|
||||
/**
|
||||
* @param {Record<string, string>} acc
|
||||
* @param {number} v
|
||||
* @returns
|
||||
*/
|
||||
(acc, v) => ({
|
||||
...acc,
|
||||
[v]: `var(--sl-color-${color}-${v})`,
|
||||
}));
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
// https://github.com/tailwindlabs/tailwindcss/blob/52ab3154392ba3d7a05cae643694384e72dc24b2/stubs/defaultConfig.stub.js
|
||||
@ -52,9 +64,9 @@ function makeTheme() {
|
||||
current: "currentColor",
|
||||
...colors.map(makeColorPalette),
|
||||
primary,
|
||||
success: `var(--success)`,
|
||||
warning: `var(--warning)`,
|
||||
danger: `var(--danger)`,
|
||||
success: { ...makeColorPalette("success"), DEFAULT: `var(--success)` },
|
||||
warning: { ...makeColorPalette("warning"), DEFAULT: `var(--warning)` },
|
||||
danger: { ...makeColorPalette("danger"), DEFAULT: `var(--danger)` },
|
||||
neutral: {
|
||||
...makeColorPalette("neutral"),
|
||||
// Shoelace supports additional neutral variables:
|
||||
@ -120,6 +132,12 @@ function makeTheme() {
|
||||
fast: "var(--sl-transition-fast)",
|
||||
"x-fast": "var(--sl-transition-x-fast)",
|
||||
},
|
||||
outlineWidth: {
|
||||
3: "3px",
|
||||
},
|
||||
outlineOffset: {
|
||||
3: "3px",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user