Frontend archives -> teams migration (#429)
This commit is contained in:
parent
d33d5f7700
commit
5daf550cb8
@ -233,11 +233,18 @@ export class AccountSettings extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`<div class="grid gap-4">
|
return html`<div class="grid gap-4">
|
||||||
<h1 class="text-xl font-semibold">${msg("Account settings")}</h1>
|
<h1 class="text-xl font-semibold">${msg("Account Settings")}</h1>
|
||||||
|
|
||||||
${successMessage}
|
${successMessage}
|
||||||
|
|
||||||
<section class="p-4 md:p-8 border rounded-lg grid gap-6">
|
<section class="p-4 md:p-8 border rounded-lg grid gap-6">
|
||||||
|
<div>
|
||||||
|
<div class="mb-1 text-gray-500">${msg("Name")}</div>
|
||||||
|
<div class="inline-flex items-center">
|
||||||
|
<span class="mr-3">${this.userInfo?.name}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-1 text-gray-500">${msg("Email")}</div>
|
<div class="mb-1 text-gray-500">${msg("Email")}</div>
|
||||||
<div class="inline-flex items-center">
|
<div class="inline-flex items-center">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
import { render } from "lit";
|
import { render } from "lit";
|
||||||
import { property, state, query } from "lit/decorators.js";
|
import { property, state, query } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { when } from "lit/directives/when.js";
|
||||||
import { msg, localized } from "@lit/localize";
|
import { msg, localized } from "@lit/localize";
|
||||||
import type { SlDialog } from "@shoelace-style/shoelace";
|
import type { SlDialog } from "@shoelace-style/shoelace";
|
||||||
import "tailwindcss/tailwind.css";
|
import "tailwindcss/tailwind.css";
|
||||||
@ -15,6 +15,7 @@ import type { LoggedInEvent } from "./utils/AuthService";
|
|||||||
import type { ViewState } from "./utils/APIRouter";
|
import type { ViewState } from "./utils/APIRouter";
|
||||||
import type { CurrentUser } from "./types/user";
|
import type { CurrentUser } from "./types/user";
|
||||||
import type { AuthStorageEventData } from "./utils/AuthService";
|
import type { AuthStorageEventData } from "./utils/AuthService";
|
||||||
|
import type { Archive, ArchiveData } from "./utils/archives";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import { ROUTES, DASHBOARD_ROUTE } from "./routes";
|
import { ROUTES, DASHBOARD_ROUTE } from "./routes";
|
||||||
import "./shoelace";
|
import "./shoelace";
|
||||||
@ -30,6 +31,14 @@ type DialogContent = {
|
|||||||
noHeader?: boolean;
|
noHeader?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type APIUser = {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
is_verified: boolean;
|
||||||
|
is_superuser: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @event navigate
|
* @event navigate
|
||||||
* @event notify
|
* @event notify
|
||||||
@ -64,10 +73,19 @@ export class App extends LiteElement {
|
|||||||
@state()
|
@state()
|
||||||
private isRegistrationEnabled?: boolean;
|
private isRegistrationEnabled?: boolean;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private teams?: ArchiveData[];
|
||||||
|
|
||||||
|
// Store selected team ID for when navigating from
|
||||||
|
// pages without associated team (e.g. user account)
|
||||||
|
@state()
|
||||||
|
private selectedTeamId?: string;
|
||||||
|
|
||||||
async connectedCallback() {
|
async connectedCallback() {
|
||||||
const authState = await AuthService.initSessionStorage();
|
const authState = await AuthService.initSessionStorage();
|
||||||
if (authState) {
|
if (authState) {
|
||||||
this.authService.saveLogin(authState);
|
this.authService.saveLogin(authState);
|
||||||
|
this.updateUserInfo();
|
||||||
}
|
}
|
||||||
this.syncViewState();
|
this.syncViewState();
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@ -77,6 +95,19 @@ export class App extends LiteElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.startSyncBrowserTabs();
|
this.startSyncBrowserTabs();
|
||||||
|
this.fetchAppSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
willUpdate(changedProperties: Map<string, any>) {
|
||||||
|
if (changedProperties.has("userInfo") && this.userInfo) {
|
||||||
|
this.selectedTeamId = this.userInfo.defaultTeamId;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
changedProperties.get("viewState") &&
|
||||||
|
this.viewState.route === "archive"
|
||||||
|
) {
|
||||||
|
this.selectedTeamId = this.viewState.params.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private syncViewState() {
|
private syncViewState() {
|
||||||
@ -95,11 +126,7 @@ export class App extends LiteElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated() {
|
private async fetchAppSettings() {
|
||||||
if (this.authService.authState) {
|
|
||||||
this.updateUserInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = await this.getAppSettings();
|
const settings = await this.getAppSettings();
|
||||||
|
|
||||||
if (settings) {
|
if (settings) {
|
||||||
@ -111,22 +138,48 @@ export class App extends LiteElement {
|
|||||||
|
|
||||||
private async updateUserInfo() {
|
private async updateUserInfo() {
|
||||||
try {
|
try {
|
||||||
const data = await this.getUserInfo();
|
const [userInfoResp, archivesResp] = await Promise.allSettled([
|
||||||
|
this.getUserInfo(),
|
||||||
|
// TODO see if we can add API endpoint to retrieve first archive
|
||||||
|
this.getArchives(),
|
||||||
|
]);
|
||||||
|
|
||||||
this.userInfo = {
|
const userInfoSuccess = userInfoResp.status === "fulfilled";
|
||||||
id: data.id,
|
let defaultTeamId: CurrentUser["defaultTeamId"];
|
||||||
email: data.email,
|
|
||||||
name: data.name,
|
if (archivesResp.status === "fulfilled") {
|
||||||
isVerified: data.is_verified,
|
const { archives } = archivesResp.value;
|
||||||
isAdmin: data.is_superuser,
|
this.teams = archives;
|
||||||
};
|
if (userInfoSuccess) {
|
||||||
|
const userInfo = userInfoResp.value;
|
||||||
|
if (archives.length && !userInfo?.is_superuser) {
|
||||||
|
defaultTeamId = archives[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw archivesResp.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userInfoSuccess) {
|
||||||
|
const { value } = userInfoResp;
|
||||||
|
this.userInfo = {
|
||||||
|
id: value.id,
|
||||||
|
email: value.email,
|
||||||
|
name: value.name,
|
||||||
|
isVerified: value.is_verified,
|
||||||
|
isAdmin: value.is_superuser,
|
||||||
|
defaultTeamId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw userInfoResp.reason;
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err?.message === "Unauthorized") {
|
if (err?.message === "Unauthorized") {
|
||||||
console.debug(
|
console.debug(
|
||||||
"Unauthorized with authState:",
|
"Unauthorized with authState:",
|
||||||
this.authService.authState
|
this.authService.authState
|
||||||
);
|
);
|
||||||
this.authService.logout();
|
this.clearUser();
|
||||||
this.navigate(ROUTES.login);
|
this.navigate(ROUTES.login);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,8 +258,12 @@ export class App extends LiteElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNavBar() {
|
private renderNavBar() {
|
||||||
const isAdmin = this.userInfo?.isAdmin;
|
const isAdmin = this.userInfo?.isAdmin;
|
||||||
|
let homeHref = "/";
|
||||||
|
if (!isAdmin && this.selectedTeamId) {
|
||||||
|
homeHref = `/archives/${this.selectedTeamId}/crawls`;
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="border-b">
|
<div class="border-b">
|
||||||
@ -214,7 +271,7 @@ export class App extends LiteElement {
|
|||||||
class="max-w-screen-lg mx-auto pl-3 box-border h-12 flex items-center justify-between"
|
class="max-w-screen-lg mx-auto pl-3 box-border h-12 flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<a href="/" @click="${this.navLink}"
|
<a href="${homeHref}" @click="${this.navLink}"
|
||||||
><h1 class="text-sm hover:text-neutral-400 font-medium">
|
><h1 class="text-sm hover:text-neutral-400 font-medium">
|
||||||
${msg("Browsertrix Cloud")}
|
${msg("Browsertrix Cloud")}
|
||||||
</h1></a
|
</h1></a
|
||||||
@ -226,12 +283,6 @@ export class App extends LiteElement {
|
|||||||
<div
|
<div
|
||||||
class="text-xs md:text-sm grid grid-flow-col gap-3 md:gap-5 items-center"
|
class="text-xs md:text-sm grid grid-flow-col gap-3 md:gap-5 items-center"
|
||||||
>
|
>
|
||||||
<a
|
|
||||||
class="text-neutral-500 hover:text-neutral-400 font-medium"
|
|
||||||
href="/archives"
|
|
||||||
@click=${this.navLink}
|
|
||||||
>${msg("All Archives")}</a
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
class="text-neutral-500 hover:text-neutral-400 font-medium"
|
class="text-neutral-500 hover:text-neutral-400 font-medium"
|
||||||
href="/crawls"
|
href="/crawls"
|
||||||
@ -243,58 +294,40 @@ export class App extends LiteElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<div class="grid grid-flow-col gap-3 md:gap-5 items-center">
|
<div class="grid grid-flow-col auto-cols-max gap-3 items-center">
|
||||||
${this.authService.authState
|
${this.authService.authState
|
||||||
? html` <sl-dropdown placement="bottom-end">
|
? html` ${this.renderTeams()}
|
||||||
<sl-icon-button
|
<sl-dropdown placement="bottom-end">
|
||||||
slot="trigger"
|
<sl-icon-button
|
||||||
name="person-circle"
|
slot="trigger"
|
||||||
style="font-size: 1.5rem;"
|
name="person-circle"
|
||||||
></sl-icon-button>
|
style="font-size: 1.5rem;"
|
||||||
|
></sl-icon-button>
|
||||||
|
|
||||||
<sl-menu class="w-60 min-w-min max-w-full">
|
<sl-menu class="w-60 min-w-min max-w-full">
|
||||||
<div class="px-7 py-2">
|
<div class="px-7 py-2">${this.renderMenuUserInfo()}</div>
|
||||||
${isAdmin
|
<sl-divider></sl-divider>
|
||||||
? html`
|
<sl-menu-item
|
||||||
<div class="mb-2">
|
@click=${() => this.navigate(ROUTES.accountSettings)}
|
||||||
<sl-tag
|
>
|
||||||
class="uppercase"
|
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||||
variant="primary"
|
${msg("Account Settings")}
|
||||||
size="small"
|
</sl-menu-item>
|
||||||
>${msg("admin")}</sl-tag
|
${this.userInfo?.isAdmin
|
||||||
>
|
? html` <sl-menu-item
|
||||||
</div>
|
@click=${() => this.navigate(ROUTES.usersInvite)}
|
||||||
`
|
>
|
||||||
|
<sl-icon slot="prefix" name="person-plus"></sl-icon>
|
||||||
|
${msg("Invite Users")}
|
||||||
|
</sl-menu-item>`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="font-medium text-neutral-700">
|
<sl-divider></sl-divider>
|
||||||
${this.userInfo?.name}
|
<sl-menu-item @click="${this.onLogOut}">
|
||||||
</div>
|
<sl-icon slot="prefix" name="box-arrow-right"></sl-icon>
|
||||||
<div class="text-sm text-neutral-500">
|
${msg("Log Out")}
|
||||||
${this.userInfo?.email}
|
</sl-menu-item>
|
||||||
</div>
|
</sl-menu>
|
||||||
</div>
|
</sl-dropdown>`
|
||||||
<sl-divider></sl-divider>
|
|
||||||
<sl-menu-item
|
|
||||||
@click=${() => this.navigate(ROUTES.accountSettings)}
|
|
||||||
>
|
|
||||||
<sl-icon slot="prefix" name="gear"></sl-icon>
|
|
||||||
${msg("Your account")}
|
|
||||||
</sl-menu-item>
|
|
||||||
${this.userInfo?.isAdmin
|
|
||||||
? html` <sl-menu-item
|
|
||||||
@click=${() => this.navigate(ROUTES.usersInvite)}
|
|
||||||
>
|
|
||||||
<sl-icon slot="prefix" name="person-plus"></sl-icon>
|
|
||||||
${msg("Invite Users")}
|
|
||||||
</sl-menu-item>`
|
|
||||||
: ""}
|
|
||||||
<sl-divider></sl-divider>
|
|
||||||
<sl-menu-item @click="${this.onLogOut}">
|
|
||||||
<sl-icon slot="prefix" name="box-arrow-right"></sl-icon>
|
|
||||||
${msg("Log Out")}
|
|
||||||
</sl-menu-item>
|
|
||||||
</sl-menu>
|
|
||||||
</sl-dropdown>`
|
|
||||||
: html`
|
: html`
|
||||||
<a href="/log-in"> ${msg("Log In")} </a>
|
<a href="/log-in"> ${msg("Log In")} </a>
|
||||||
${this.isRegistrationEnabled
|
${this.isRegistrationEnabled
|
||||||
@ -314,7 +347,91 @@ export class App extends LiteElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFooter() {
|
private renderTeams() {
|
||||||
|
if (!this.teams || this.teams.length < 2 || !this.userInfo) return;
|
||||||
|
|
||||||
|
const selectedOption = this.selectedTeamId
|
||||||
|
? this.teams.find(({ id }) => id === this.selectedTeamId)
|
||||||
|
: { id: "", name: msg("All Teams") };
|
||||||
|
if (!selectedOption) {
|
||||||
|
console.debug(
|
||||||
|
`Could't find team with ID ${this.selectedTeamId}`,
|
||||||
|
this.teams
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<sl-dropdown placement="bottom-end">
|
||||||
|
<sl-button slot="trigger" variant="text" size="small" caret
|
||||||
|
>${selectedOption.name}</sl-button
|
||||||
|
>
|
||||||
|
<sl-menu
|
||||||
|
@sl-select=${(e: CustomEvent) => {
|
||||||
|
const { value } = e.detail.item;
|
||||||
|
this.navigate(`/archives/${value}${value ? "/crawls" : ""}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${when(
|
||||||
|
this.userInfo.isAdmin,
|
||||||
|
() => html`
|
||||||
|
<sl-menu-item value="" ?checked=${!selectedOption.id}
|
||||||
|
>${msg("All Teams")}</sl-menu-item
|
||||||
|
>
|
||||||
|
<sl-divider></sl-divider>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
${this.teams.map(
|
||||||
|
(team) => html`
|
||||||
|
<sl-menu-item
|
||||||
|
value=${team.id}
|
||||||
|
?checked=${team.id === selectedOption.id}
|
||||||
|
>${team.name}</sl-menu-item
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</sl-menu>
|
||||||
|
</sl-dropdown>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderMenuUserInfo() {
|
||||||
|
if (!this.userInfo) return;
|
||||||
|
if (this.userInfo.isAdmin) {
|
||||||
|
return html`
|
||||||
|
<div class="mb-2">
|
||||||
|
<sl-tag class="uppercase" variant="primary" size="small"
|
||||||
|
>${msg("admin")}</sl-tag
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="font-medium text-neutral-700">${this.userInfo?.name}</div>
|
||||||
|
<div class="text-xs text-neutral-500 whitespace-nowrap">
|
||||||
|
${this.userInfo?.email}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.teams?.length === 1) {
|
||||||
|
return html`
|
||||||
|
<div class="font-medium text-neutral-700 my-1">
|
||||||
|
${this.teams![0].name}
|
||||||
|
</div>
|
||||||
|
<div class="text-neutral-500">${this.userInfo?.name}</div>
|
||||||
|
<div class="text-xs text-neutral-500 whitespace-nowrap">
|
||||||
|
${this.userInfo?.email}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="font-medium text-neutral-700">${this.userInfo?.name}</div>
|
||||||
|
<div class="text-xs text-neutral-500 whitespace-nowrap">
|
||||||
|
${this.userInfo?.email}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderFooter() {
|
||||||
return html`
|
return html`
|
||||||
<footer
|
<footer
|
||||||
class="w-full max-w-screen-lg mx-auto p-1 md:p-3 box-border flex justify-end"
|
class="w-full max-w-screen-lg mx-auto p-1 md:p-3 box-border flex justify-end"
|
||||||
@ -347,7 +464,7 @@ export class App extends LiteElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage() {
|
private renderPage() {
|
||||||
switch (this.viewState.route) {
|
switch (this.viewState.route) {
|
||||||
case "signUp": {
|
case "signUp": {
|
||||||
if (!this.isAppSettingsLoaded) {
|
if (!this.isAppSettingsLoaded) {
|
||||||
@ -426,7 +543,8 @@ export class App extends LiteElement {
|
|||||||
@navigate=${this.onNavigateTo}
|
@navigate=${this.onNavigateTo}
|
||||||
@logged-in=${this.onLoggedIn}
|
@logged-in=${this.onLoggedIn}
|
||||||
.authState=${this.authService.authState}
|
.authState=${this.authService.authState}
|
||||||
.userInfo="${this.userInfo}"
|
.userInfo=${this.userInfo}
|
||||||
|
.teamId=${this.selectedTeamId}
|
||||||
></btrix-home>`;
|
></btrix-home>`;
|
||||||
|
|
||||||
case "archives":
|
case "archives":
|
||||||
@ -518,7 +636,7 @@ export class App extends LiteElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSpinner() {
|
private renderSpinner() {
|
||||||
return html`
|
return html`
|
||||||
<div class="w-full flex items-center justify-center text-3xl">
|
<div class="w-full flex items-center justify-center text-3xl">
|
||||||
<sl-spinner></sl-spinner>
|
<sl-spinner></sl-spinner>
|
||||||
@ -526,13 +644,13 @@ export class App extends LiteElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNotFoundPage() {
|
private renderNotFoundPage() {
|
||||||
return html`<btrix-not-found
|
return html`<btrix-not-found
|
||||||
class="w-full md:bg-neutral-50 flex items-center justify-center"
|
class="w-full md:bg-neutral-50 flex items-center justify-center"
|
||||||
></btrix-not-found>`;
|
></btrix-not-found>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFindCrawl() {
|
private renderFindCrawl() {
|
||||||
return html`
|
return html`
|
||||||
<sl-dropdown
|
<sl-dropdown
|
||||||
@sl-after-show=${(e: any) => {
|
@sl-after-show=${(e: any) => {
|
||||||
@ -585,9 +703,7 @@ export class App extends LiteElement {
|
|||||||
const detail = event.detail || {};
|
const detail = event.detail || {};
|
||||||
const redirect = detail.redirect !== false;
|
const redirect = detail.redirect !== false;
|
||||||
|
|
||||||
this.authService.logout();
|
this.clearUser();
|
||||||
this.authService = new AuthService();
|
|
||||||
this.userInfo = undefined;
|
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
this.navigate("/log-in");
|
this.navigate("/log-in");
|
||||||
@ -615,8 +731,7 @@ export class App extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onNeedLogin() {
|
onNeedLogin() {
|
||||||
this.authService.logout();
|
this.clearUser();
|
||||||
|
|
||||||
this.navigate(ROUTES.login);
|
this.navigate(ROUTES.login);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,10 +791,21 @@ export class App extends LiteElement {
|
|||||||
alert.toast();
|
alert.toast();
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserInfo() {
|
getUserInfo(): Promise<APIUser> {
|
||||||
return this.apiFetch("/users/me", this.authService.authState!);
|
return this.apiFetch("/users/me", this.authService.authState!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private clearUser() {
|
||||||
|
this.authService.logout();
|
||||||
|
this.authService = new AuthService();
|
||||||
|
this.userInfo = undefined;
|
||||||
|
this.selectedTeamId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getArchives(): Promise<{ archives: ArchiveData[] }> {
|
||||||
|
return this.apiFetch("/archives", this.authService.authState!);
|
||||||
|
}
|
||||||
|
|
||||||
private showDialog(content: DialogContent) {
|
private showDialog(content: DialogContent) {
|
||||||
this.globalDialogContent = content;
|
this.globalDialogContent = content;
|
||||||
this.globalDialog.show();
|
this.globalDialog.show();
|
||||||
@ -730,7 +856,7 @@ export class App extends LiteElement {
|
|||||||
this.updateUserInfo();
|
this.updateUserInfo();
|
||||||
this.syncViewState();
|
this.syncViewState();
|
||||||
} else {
|
} else {
|
||||||
this.authService.logout();
|
this.clearUser();
|
||||||
this.navigate(ROUTES.login);
|
this.navigate(ROUTES.login);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,8 +100,12 @@ export class CrawlsList extends LiteElement {
|
|||||||
)(crawls) as CrawlSearchResult[];
|
)(crawls) as CrawlSearchResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: Map<string, any>) {
|
protected willUpdate(changedProperties: Map<string, any>) {
|
||||||
if (changedProperties.has("shouldFetch")) {
|
if (
|
||||||
|
changedProperties.has("shouldFetch") ||
|
||||||
|
changedProperties.get("crawlsBaseUrl") ||
|
||||||
|
changedProperties.get("crawlsAPIBaseUrl")
|
||||||
|
) {
|
||||||
if (this.shouldFetch) {
|
if (this.shouldFetch) {
|
||||||
if (!this.crawlsBaseUrl) {
|
if (!this.crawlsBaseUrl) {
|
||||||
throw new Error("Crawls base URL not defined");
|
throw new Error("Crawls base URL not defined");
|
||||||
|
|||||||
@ -71,29 +71,26 @@ export class Archive extends LiteElement {
|
|||||||
@state()
|
@state()
|
||||||
private successfullyInvitedEmail?: string;
|
private successfullyInvitedEmail?: string;
|
||||||
|
|
||||||
async firstUpdated() {
|
async willUpdate(changedProperties: Map<string, any>) {
|
||||||
if (!this.archiveId) return;
|
if (changedProperties.has("archiveId") && this.archiveId) {
|
||||||
|
try {
|
||||||
|
const archive = await this.getArchive(this.archiveId);
|
||||||
|
|
||||||
try {
|
if (!archive) {
|
||||||
const archive = await this.getArchive(this.archiveId);
|
this.navTo("/archives");
|
||||||
|
} else {
|
||||||
|
this.archive = archive;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.archive = null;
|
||||||
|
|
||||||
if (!archive) {
|
this.notify({
|
||||||
this.navTo("/archives");
|
message: msg("Sorry, couldn't retrieve archive at this time."),
|
||||||
} else {
|
variant: "danger",
|
||||||
this.archive = archive;
|
icon: "exclamation-octagon",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
this.archive = null;
|
|
||||||
|
|
||||||
this.notify({
|
|
||||||
message: msg("Sorry, couldn't retrieve archive at this time."),
|
|
||||||
variant: "danger",
|
|
||||||
icon: "exclamation-octagon",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async updated(changedProperties: any) {
|
|
||||||
if (changedProperties.has("isAddingMember") && this.isAddingMember) {
|
if (changedProperties.has("isAddingMember") && this.isAddingMember) {
|
||||||
this.successfullyInvitedEmail = undefined;
|
this.successfullyInvitedEmail = undefined;
|
||||||
}
|
}
|
||||||
@ -145,19 +142,6 @@ export class Archive extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`<article>
|
return html`<article>
|
||||||
<header class="w-full max-w-screen-lg mx-auto px-3 box-border py-4">
|
|
||||||
<nav class="text-sm text-neutral-400">
|
|
||||||
<a
|
|
||||||
class="font-medium hover:underline"
|
|
||||||
href="/archives"
|
|
||||||
@click="${this.navLink}"
|
|
||||||
>${msg("Archives")}</a
|
|
||||||
>
|
|
||||||
<span class="font-mono">/</span>
|
|
||||||
<span>${this.archive.name}</span>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="w-full max-w-screen-lg mx-auto px-3 box-border">
|
<div class="w-full max-w-screen-lg mx-auto px-3 box-border">
|
||||||
<nav class="-ml-3 flex items-end overflow-x-auto">
|
<nav class="-ml-3 flex items-end overflow-x-auto">
|
||||||
${this.renderNavTab({ tabName: "crawls", label: msg("Crawls") })}
|
${this.renderNavTab({ tabName: "crawls", label: msg("Crawls") })}
|
||||||
@ -229,7 +213,6 @@ export class Archive extends LiteElement {
|
|||||||
|
|
||||||
return html`<btrix-crawls-list
|
return html`<btrix-crawls-list
|
||||||
.authState=${this.authState!}
|
.authState=${this.authState!}
|
||||||
archiveId=${this.archiveId!}
|
|
||||||
crawlsBaseUrl=${crawlsBaseUrl}
|
crawlsBaseUrl=${crawlsBaseUrl}
|
||||||
?shouldFetch=${this.archiveTab === "crawls"}
|
?shouldFetch=${this.archiveTab === "crawls"}
|
||||||
></btrix-crawls-list>`;
|
></btrix-crawls-list>`;
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export class Archives extends LiteElement {
|
|||||||
<header
|
<header
|
||||||
class="w-full max-w-screen-lg mx-auto px-3 py-4 box-border md:py-8"
|
class="w-full max-w-screen-lg mx-auto px-3 py-4 box-border md:py-8"
|
||||||
>
|
>
|
||||||
<h1 class="text-xl font-medium">${msg("Archives")}</h1>
|
<h1 class="text-xl font-medium">${msg("Teams")}</h1>
|
||||||
</header>
|
</header>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,24 +14,34 @@ export class Home extends LiteElement {
|
|||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
userInfo?: CurrentUser;
|
userInfo?: CurrentUser;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
teamId?: string;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isInviteComplete?: boolean;
|
private isInviteComplete?: boolean;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private archiveList?: ArchiveData[];
|
private archiveList?: ArchiveData[];
|
||||||
|
|
||||||
async firstUpdated() {
|
|
||||||
this.archiveList = await this.getArchives();
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
if (this.authState) {
|
if (this.authState) {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
if (this.userInfo && !this.teamId) {
|
||||||
|
this.fetchArchives();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navTo("/log-in");
|
this.navTo("/log-in");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
willUpdate(changedProperties: Map<string, any>) {
|
||||||
|
if (changedProperties.has("teamId") && this.teamId) {
|
||||||
|
this.navTo(`/archives/${this.teamId}/crawls`);
|
||||||
|
} else if (changedProperties.has("authState") && this.authState) {
|
||||||
|
this.fetchArchives();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.userInfo || !this.archiveList) {
|
if (!this.userInfo || !this.archiveList) {
|
||||||
return html`
|
return html`
|
||||||
@ -50,7 +60,7 @@ export class Home extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.userInfo.isAdmin === false) {
|
if (this.userInfo.isAdmin === false) {
|
||||||
title = msg("Archives");
|
title = msg("Teams");
|
||||||
content = this.renderLoggedInNonAdmin();
|
content = this.renderLoggedInNonAdmin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +116,7 @@ export class Home extends LiteElement {
|
|||||||
<div class="grid grid-cols-3 gap-8">
|
<div class="grid grid-cols-3 gap-8">
|
||||||
<div class="col-span-3 md:col-span-2">
|
<div class="col-span-3 md:col-span-2">
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-lg font-medium mb-3 mt-2">
|
<h2 class="text-lg font-medium mb-3 mt-2">${msg("All Teams")}</h2>
|
||||||
${msg("All Archives")}
|
|
||||||
</h2>
|
|
||||||
<btrix-archives-list
|
<btrix-archives-list
|
||||||
.userInfo=${this.userInfo}
|
.userInfo=${this.userInfo}
|
||||||
.archiveList=${this.archiveList}
|
.archiveList=${this.archiveList}
|
||||||
@ -171,6 +179,10 @@ export class Home extends LiteElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async fetchArchives() {
|
||||||
|
this.archiveList = await this.getArchives();
|
||||||
|
}
|
||||||
|
|
||||||
private async getArchives(): Promise<ArchiveData[]> {
|
private async getArchives(): Promise<ArchiveData[]> {
|
||||||
const data = await this.apiFetch("/archives", this.authState!);
|
const data = await this.apiFetch("/archives", this.authState!);
|
||||||
|
|
||||||
|
|||||||
@ -118,6 +118,11 @@ const theme = css`
|
|||||||
box-shadow: var(--sl-shadow-small);
|
box-shadow: var(--sl-shadow-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prevent horizontal scrollbar */
|
||||||
|
sl-select::part(menu) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* Decrease control spacing on small select */
|
/* Decrease control spacing on small select */
|
||||||
sl-select[size="small"]::part(control) {
|
sl-select[size="small"]::part(control) {
|
||||||
--sl-input-spacing-small: var(--sl-spacing-x-small);
|
--sl-input-spacing-small: var(--sl-spacing-x-small);
|
||||||
|
|||||||
@ -4,4 +4,5 @@ export type CurrentUser = {
|
|||||||
name: string;
|
name: string;
|
||||||
isVerified: boolean;
|
isVerified: boolean;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
defaultTeamId?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user