View and edit browser profile (#214)
This commit is contained in:
parent
71eec4d915
commit
db27b6aaaf
@ -172,7 +172,9 @@ export class App extends LiteElement {
|
||||
window.history.pushState(
|
||||
this.viewState,
|
||||
"",
|
||||
`${this.viewState.pathname}${url.hash}${url.search}`
|
||||
`${this.viewState.pathname.replace(url.search, "")}${url.hash}${
|
||||
url.search
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,23 @@ export class BrowserProfilesDetail extends LiteElement {
|
||||
profileId!: string;
|
||||
|
||||
@state()
|
||||
private profile: Partial<Profile> = {
|
||||
id: "2",
|
||||
name: "Twitter Webrecorder",
|
||||
description: "Et netus et malesuada fames.",
|
||||
created: new Date().toUTCString(),
|
||||
origins: ["https://twitter.com", "https://twitter.com/webrecorder_io"],
|
||||
};
|
||||
private profile?: Profile;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showCreateDialog = false;
|
||||
|
||||
@state()
|
||||
private isCreateFormVisible = false;
|
||||
|
||||
@state()
|
||||
private isSubmitting = false;
|
||||
|
||||
/** Profile creation only works in Chromium-based browsers */
|
||||
private isBrowserCompatible = Boolean((window as any).chrome);
|
||||
|
||||
firstUpdated() {
|
||||
this.fetchProfile();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div class="mb-7">
|
||||
@ -48,9 +58,212 @@ export class BrowserProfilesDetail extends LiteElement {
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<h2 class="text-2xl font-medium mb-3 md:h-8">${this.profile.name}</h2>
|
||||
</header>`;
|
||||
<header class="md:flex items-center justify-between mb-3">
|
||||
<h2 class="text-xl md:text-3xl font-bold md:h-9 mb-1">
|
||||
${this.profile?.name ||
|
||||
html`<sl-skeleton class="md:h-9 w-80"></sl-skeleton>`}
|
||||
</h2>
|
||||
<div>
|
||||
${this.profile
|
||||
? html`<sl-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click=${() => (this.showCreateDialog = true)}
|
||||
><sl-icon slot="prefix" name="collection-play-fill"></sl-icon>
|
||||
${msg("Launch Browser Profile")}</sl-button
|
||||
>`
|
||||
: html`<sl-skeleton
|
||||
style="width: 6em; height: 2em;"
|
||||
></sl-skeleton>`}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="rounded border p-4 md:p-8">
|
||||
<dl class="grid grid-cols-2 gap-5">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<dt class="text-sm text-0-600">${msg("Description")}</dt>
|
||||
<dd>
|
||||
${this.profile
|
||||
? this.profile.description ||
|
||||
html`<span class="text-neutral-400">${msg("None")}</span>`
|
||||
: ""}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<dt class="text-sm text-0-600">
|
||||
<span class="inline-block align-middle"
|
||||
>${msg("Created at")}</span
|
||||
>
|
||||
</dt>
|
||||
<dd>
|
||||
${this.profile
|
||||
? html`
|
||||
<sl-format-date
|
||||
date=${`${this.profile.created}Z` /** Z for UTC */}
|
||||
month="2-digit"
|
||||
day="2-digit"
|
||||
year="2-digit"
|
||||
hour="numeric"
|
||||
minute="numeric"
|
||||
time-zone-name="short"
|
||||
></sl-format-date>
|
||||
`
|
||||
: ""}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<dt class="text-sm text-0-600">${msg("Visited URLs")}</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
${this.profile?.origins.map((url) => html`<li>${url}</li>`)}
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<sl-dialog
|
||||
label=${msg(str`View & Edit Browser Profile`)}
|
||||
?open=${this.showCreateDialog}
|
||||
@sl-request-close=${this.hideDialog}
|
||||
@sl-show=${() => (this.isCreateFormVisible = true)}
|
||||
@sl-after-hide=${() => (this.isCreateFormVisible = false)}
|
||||
>
|
||||
<div class="mb-4">
|
||||
${this.isBrowserCompatible
|
||||
? ""
|
||||
: html`
|
||||
<btrix-alert type="warning" class="text-sm">
|
||||
${msg(
|
||||
"Browser profile creation is only supported in Chromium-based browsers (such as Chrome) at this time. Please re-open this page in a compatible browser to proceed."
|
||||
)}
|
||||
</btrix-alert>
|
||||
`}
|
||||
</div>
|
||||
${this.isCreateFormVisible ? this.renderCreateForm() : ""}
|
||||
</sl-dialog> `;
|
||||
}
|
||||
|
||||
private renderCreateForm() {
|
||||
return html`<sl-form @sl-submit=${this.onSubmit}>
|
||||
<div class="grid gap-5">
|
||||
<sl-select
|
||||
name="url"
|
||||
label=${msg("Starting URL")}
|
||||
value=${this.profile?.origins[0] || ""}
|
||||
required
|
||||
hoist
|
||||
?disabled=${!this.isBrowserCompatible}
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
>
|
||||
${this.profile?.origins.map(
|
||||
(origin) => html`
|
||||
<sl-menu-item value=${origin}>${origin}</sl-menu-item>
|
||||
`
|
||||
)}
|
||||
</sl-select>
|
||||
|
||||
<div class="text-right">
|
||||
<sl-button @click=${this.hideDialog}>${msg("Cancel")}</sl-button>
|
||||
<sl-button
|
||||
type="primary"
|
||||
submit
|
||||
?disabled=${!this.isBrowserCompatible || this.isSubmitting}
|
||||
?loading=${this.isSubmitting}
|
||||
>
|
||||
${msg("Start Browser")}
|
||||
</sl-button>
|
||||
</div>
|
||||
</div>
|
||||
</sl-form>`;
|
||||
}
|
||||
|
||||
private hideDialog() {
|
||||
this.showCreateDialog = false;
|
||||
}
|
||||
|
||||
async onSubmit(event: { detail: { formData: FormData } }) {
|
||||
if (!this.profile) return;
|
||||
|
||||
this.isSubmitting = true;
|
||||
|
||||
const { formData } = event.detail;
|
||||
const url = formData.get("url") as string;
|
||||
const params = {
|
||||
url,
|
||||
profileId: this.profile.id,
|
||||
};
|
||||
|
||||
try {
|
||||
const data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/profiles/browser`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
}
|
||||
);
|
||||
|
||||
this.notify({
|
||||
message: msg("Starting up browser."),
|
||||
type: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
|
||||
this.navTo(
|
||||
`/archives/${this.archiveId}/browser-profiles/profile/browser/${
|
||||
data.browserid
|
||||
}?name=${window.encodeURIComponent(
|
||||
this.profile.name
|
||||
)}&description=${window.encodeURIComponent(
|
||||
this.profile.description || ""
|
||||
)}&profileId=${window.encodeURIComponent(this.profile.id)}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.isSubmitting = false;
|
||||
|
||||
this.notify({
|
||||
message: msg("Sorry, couldn't create browser profile at this time."),
|
||||
type: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch browser profile and update internal state
|
||||
*/
|
||||
private async fetchProfile(): Promise<void> {
|
||||
try {
|
||||
const data = await this.getProfile();
|
||||
|
||||
this.profile = data;
|
||||
} catch (e) {
|
||||
this.notify({
|
||||
message: msg("Sorry, couldn't retrieve browser profiles at this time."),
|
||||
type: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async getProfile(): Promise<Profile> {
|
||||
const data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/profiles/${this.profileId}`,
|
||||
this.authState!
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop propgation of sl-select events.
|
||||
* Prevents bug where sl-dialog closes when dropdown closes
|
||||
* https://github.com/shoelace-style/shoelace/issues/170
|
||||
*/
|
||||
private stopProp(e: CustomEvent) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,9 @@ export class BrowserProfilesList extends LiteElement {
|
||||
private renderItem(data: Profile) {
|
||||
return html`
|
||||
<a
|
||||
class="block p-4 leading-none border-t first:border-t-0 transition-colors"
|
||||
class="block p-4 leading-none hover:bg-zinc-50 hover:text-primary border-t first:border-t-0 transition-colors"
|
||||
href=${`/archives/${this.archiveId}/browser-profiles/profile/${data.id}`}
|
||||
@click=${this.navLink}
|
||||
title=${data.name}
|
||||
>
|
||||
<div class="grid grid-cols-7 gap-3 md:gap-5" role="row">
|
||||
@ -191,7 +193,7 @@ export class BrowserProfilesList extends LiteElement {
|
||||
|
||||
<div class="p-3">
|
||||
<sl-select
|
||||
name="baseId"
|
||||
name="profileId"
|
||||
label=${msg("Extend Profile")}
|
||||
help-text=${msg("Extend an existing browser profile.")}
|
||||
clearable
|
||||
@ -234,11 +236,18 @@ export class BrowserProfilesList extends LiteElement {
|
||||
|
||||
const { formData } = event.detail;
|
||||
const url = formData.get("url") as string;
|
||||
const params = {
|
||||
const profileId = formData.get("profileId") as string;
|
||||
const params: {
|
||||
url: string;
|
||||
profileId?: string;
|
||||
} = {
|
||||
url: `${formData.get("urlPrefix")}${url.substring(url.indexOf(",") + 1)}`,
|
||||
baseId: formData.get("baseId"),
|
||||
};
|
||||
|
||||
if (profileId) {
|
||||
params.profileId = profileId;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/profiles/browser`,
|
||||
@ -256,7 +265,11 @@ export class BrowserProfilesList extends LiteElement {
|
||||
});
|
||||
|
||||
this.navTo(
|
||||
`/archives/${this.archiveId}/browser-profiles/profile/browser/${data.browserid}`
|
||||
`/archives/${this.archiveId}/browser-profiles/profile/browser/${
|
||||
data.browserid
|
||||
}?name=${window.encodeURIComponent(
|
||||
"My Profile"
|
||||
)}&description=&profileId=`
|
||||
);
|
||||
} catch (e) {
|
||||
this.isSubmitting = false;
|
||||
|
@ -38,6 +38,15 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
@state()
|
||||
private isFullscreen = false;
|
||||
|
||||
// URL params can be used to pass name and description
|
||||
// base ID determines whether this is an edit/extension
|
||||
@state()
|
||||
private params: Partial<{
|
||||
name: string;
|
||||
description: string;
|
||||
profileId: string | null;
|
||||
}> = {};
|
||||
|
||||
private pollTimerId?: number;
|
||||
|
||||
connectedCallback() {
|
||||
@ -52,12 +61,39 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
this.params = {
|
||||
name: params.get("name") || msg("My Profile"),
|
||||
description: params.get("description") || "",
|
||||
profileId: params.get("profileId") || null,
|
||||
};
|
||||
|
||||
this.fetchBrowser();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="browserProfileInstructions" class="mb-5">
|
||||
<div class="mb-7">
|
||||
<a
|
||||
class="text-neutral-500 hover:text-neutral-600 text-sm font-medium"
|
||||
href=${this.params.profileId
|
||||
? `/archives/${this.archiveId}/browser-profiles/profile/${this.params.profileId}`
|
||||
: `/archives/${this.archiveId}/browser-profiles`}
|
||||
@click=${this.navLink}
|
||||
>
|
||||
<sl-icon
|
||||
name="arrow-left"
|
||||
class="inline-block align-middle"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle"
|
||||
>${this.params.profileId
|
||||
? msg("Back to Profile")
|
||||
: msg("Back to Browser Profiles")}</span
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-5">
|
||||
<p class="text-sm text-neutral-500">
|
||||
${msg(
|
||||
"Interact with the browser to record your browser profile. When you’re finished interacting, name and save the profile."
|
||||
@ -83,19 +119,19 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
`
|
||||
: html`
|
||||
<div class="lg:flex bg-white relative">
|
||||
<div class="grow lg:rounded-l overflow-hidden">
|
||||
<div class="grow lg:rounded-lg overflow-hidden">
|
||||
${this.browserUrl
|
||||
? this.renderBrowser()
|
||||
: html`
|
||||
<div
|
||||
class="aspect-video bg-slate-50 flex items-center justify-center text-4xl"
|
||||
class="aspect-video bg-slate-50 flex items-center justify-center text-4xl pr-72"
|
||||
>
|
||||
<sl-spinner></sl-spinner>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<div
|
||||
class="rounded-b lg:rounded-b-none lg:rounded-r border p-2 shadow-inner bg-white absolute h-full right-0"
|
||||
class="rounded-b lg:rounded-b-none lg:rounded-r border w-72 p-2 shadow-inner bg-white absolute h-full right-0"
|
||||
>
|
||||
${document.fullscreenEnabled
|
||||
? html`
|
||||
@ -130,7 +166,21 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="p-2">${this.renderForm()}</div>
|
||||
<div class="p-2">
|
||||
${this.params.profileId
|
||||
? html`
|
||||
<div class="mb-2">
|
||||
<btrix-alert class="text-sm" type="info"
|
||||
>${msg(
|
||||
html`Viewing
|
||||
<strong>${this.params.name}</strong>`
|
||||
)}</btrix-alert
|
||||
>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this.renderForm()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
@ -148,7 +198,7 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
desc: "Example browser profile name",
|
||||
})}
|
||||
autocomplete="off"
|
||||
value="My Profile"
|
||||
value=${this.params.name || ""}
|
||||
?disabled=${!this.browserUrl}
|
||||
required
|
||||
></sl-input>
|
||||
@ -162,10 +212,21 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
})}
|
||||
rows="2"
|
||||
autocomplete="off"
|
||||
value=${this.params.description || ""}
|
||||
?disabled=${!this.browserUrl}
|
||||
></sl-textarea>
|
||||
|
||||
<div class="text-right">
|
||||
<sl-button
|
||||
type="text"
|
||||
href=${this.params.profileId
|
||||
? `/archives/${this.archiveId}/browser-profiles/profile/${this.params.profileId}`
|
||||
: `/archives/${this.archiveId}/browser-profiles`}
|
||||
@click=${this.navLink}
|
||||
>
|
||||
${msg("Cancel")}
|
||||
</sl-button>
|
||||
|
||||
<sl-button
|
||||
type="primary"
|
||||
submit
|
||||
@ -263,19 +324,35 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
|
||||
const { formData } = event.detail;
|
||||
const params = {
|
||||
browserid: this.browserId,
|
||||
name: formData.get("name"),
|
||||
description: formData.get("description"),
|
||||
};
|
||||
|
||||
try {
|
||||
const data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/profiles/browser/${this.browserId}/commit`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
}
|
||||
);
|
||||
let data: {
|
||||
id: string;
|
||||
};
|
||||
|
||||
if (this.params.profileId) {
|
||||
data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/profiles/${this.params.profileId}`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify(params),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
data = await this.apiFetch(
|
||||
`/archives/${this.archiveId}/profiles`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.notify({
|
||||
message: msg("Successfully created browser profile."),
|
||||
@ -283,11 +360,9 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
icon: "check2-circle",
|
||||
});
|
||||
|
||||
// TODO nav to detail page
|
||||
// this.navTo(
|
||||
// `/archives/${this.archiveId}/browser-profiles/profile/${data.id}`
|
||||
// );
|
||||
this.navTo(`/archives/${this.archiveId}/browser-profiles`);
|
||||
this.navTo(
|
||||
`/archives/${this.archiveId}/browser-profiles/profile/${data.id}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.isSubmitting = false;
|
||||
|
||||
@ -304,12 +379,13 @@ export class BrowserProfilesNew extends LiteElement {
|
||||
|
||||
el.addEventListener("load", () => {
|
||||
// TODO see if we can make this work locally without CORs errors
|
||||
const sidebarWidth = 288;
|
||||
try {
|
||||
//el.style.width = "132%";
|
||||
el.contentWindow?.localStorage.setItem("uiTheme", '"default"');
|
||||
el.contentWindow?.localStorage.setItem(
|
||||
"InspectorView.screencastSplitViewState",
|
||||
'{"vertical":{"size":241}}'
|
||||
`{"vertical":{"size":${sidebarWidth}}}`
|
||||
);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
@ -123,7 +123,7 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
</sl-button>
|
||||
`}
|
||||
`
|
||||
: html`<sl-skeleton class="h-7 w-80"></sl-skeleton>`}
|
||||
: html`<sl-skeleton class="md:h-9 w-80"></sl-skeleton>`}
|
||||
</h2>
|
||||
<div class="text-sm text-neutral-400 md:h-5">
|
||||
<div class="md:inline-block mr-3">${msg("Crawl Template")}</div>
|
||||
@ -591,10 +591,10 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
<div class="text-sm text-0-600">${msg("Browser Profile")}</div>
|
||||
${this.crawlTemplate
|
||||
? html`
|
||||
${this.crawlTemplate.profileid
|
||||
${this.crawlTemplate.profileId
|
||||
? html`<a
|
||||
class="font-medium text-neutral-700 hover:text-neutral-900"
|
||||
href=${`/archives/${this.archiveId}/browser-profiles/profile/${this.crawlTemplate.profileid}`}
|
||||
href=${`/archives/${this.archiveId}/browser-profiles/profile/${this.crawlTemplate.profileId}`}
|
||||
@click=${this.navLink}
|
||||
>
|
||||
<sl-icon
|
||||
|
@ -19,7 +19,7 @@ export type NewCrawlTemplate = {
|
||||
crawlTimeout?: number;
|
||||
scale: number;
|
||||
config: CrawlConfig;
|
||||
profileid: string;
|
||||
profileId: string;
|
||||
};
|
||||
|
||||
const initialValues = {
|
||||
@ -581,7 +581,7 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
runNow: this.isRunNow,
|
||||
crawlTimeout: crawlTimeoutMinutes ? +crawlTimeoutMinutes * 60 : 0,
|
||||
scale: +scale,
|
||||
profileid: formData.get("profileId") as string,
|
||||
profileId: formData.get("profileId") as string,
|
||||
};
|
||||
|
||||
if (this.isConfigCodeView) {
|
||||
|
@ -54,7 +54,7 @@ export type CrawlTemplate = {
|
||||
inactive: boolean;
|
||||
config: CrawlConfig;
|
||||
scale: number;
|
||||
profileid: string | null;
|
||||
profileId: string | null;
|
||||
profileName: string | null;
|
||||
};
|
||||
|
||||
@ -64,7 +64,7 @@ export type Profile = {
|
||||
description: string;
|
||||
created: string;
|
||||
origins: string[];
|
||||
baseId: string;
|
||||
profileId: string;
|
||||
baseProfileName: string;
|
||||
aid: string;
|
||||
resource: {
|
||||
|
@ -16,7 +16,8 @@ export const ROUTES = {
|
||||
archiveAddMember: "/archives/:id/:tab/add-member",
|
||||
archiveCrawl: "/archives/:id/:tab/crawl/:crawlId",
|
||||
browserProfile: "/archives/:id/:tab/profile/:browserProfileId",
|
||||
browser: "/archives/:id/:tab/profile/browser/:browserId",
|
||||
browser:
|
||||
"/archives/:id/:tab/profile/browser/:browserId?name&description&profileId",
|
||||
crawlTemplate: "/archives/:id/:tab/config/:crawlConfigId",
|
||||
crawlTemplateEdit: "/archives/:id/:tab/config/:crawlConfigId?edit",
|
||||
users: "/users",
|
||||
|
Loading…
Reference in New Issue
Block a user