Support for Public / Shareable Collections (#1038)
* collections: support toggling collections public/private, viewable via RWP - backend: add 'public' to collection model, support patching to update - backend: add .../collections/<id>/public/replay.json for public access - backend: add CORS handling for public endpoint - frontend: support 'make shareable / make private' dropdown actions on collection detail + collection list views - frontend: show shareable / private icons by collection name on detail + list views - frontend: link to replayweb.page for standalone browsing - frontend: add embed code popup when a collection is shareable - refer to public collections as 'shareable' for now --------- Co-authored-by: Henry Wilkinson <henry@wilkinson.graphics>
This commit is contained in:
parent
62d3399223
commit
362afa47bd
@ -7,7 +7,7 @@ import uuid
|
|||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException, Response
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
|
||||||
from .basecrawls import SUCCESSFUL_STATES
|
from .basecrawls import SUCCESSFUL_STATES
|
||||||
@ -148,10 +148,14 @@ class CollectionOps:
|
|||||||
return await self.get_collection(coll_id, org)
|
return await self.get_collection(coll_id, org)
|
||||||
|
|
||||||
async def get_collection(
|
async def get_collection(
|
||||||
self, coll_id: uuid.UUID, org: Organization, resources=False
|
self, coll_id: uuid.UUID, org: Organization, resources=False, public_only=False
|
||||||
):
|
):
|
||||||
"""Get collection by id"""
|
"""Get collection by id"""
|
||||||
result = await self.collections.find_one({"_id": coll_id})
|
query = {"_id": coll_id}
|
||||||
|
if public_only:
|
||||||
|
query["isPublic"] = True
|
||||||
|
|
||||||
|
result = await self.collections.find_one(query)
|
||||||
if resources:
|
if resources:
|
||||||
result["resources"] = await self.get_collection_crawl_resources(
|
result["resources"] = await self.get_collection_crawl_resources(
|
||||||
coll_id, org
|
coll_id, org
|
||||||
@ -348,6 +352,7 @@ def init_collections_api(app, mdb, crawls, orgs, crawl_manager):
|
|||||||
|
|
||||||
org_crawl_dep = orgs.org_crawl_dep
|
org_crawl_dep = orgs.org_crawl_dep
|
||||||
org_viewer_dep = orgs.org_viewer_dep
|
org_viewer_dep = orgs.org_viewer_dep
|
||||||
|
org_public = orgs.org_public
|
||||||
|
|
||||||
@app.post("/orgs/{oid}/collections", tags=["collections"])
|
@app.post("/orgs/{oid}/collections", tags=["collections"])
|
||||||
async def add_collection(
|
async def add_collection(
|
||||||
@ -428,8 +433,36 @@ def init_collections_api(app, mdb, crawls, orgs, crawl_manager):
|
|||||||
coll = await colls.get_collection(coll_id, org, resources=True)
|
coll = await colls.get_collection(coll_id, org, resources=True)
|
||||||
if not coll:
|
if not coll:
|
||||||
raise HTTPException(status_code=404, detail="collection_not_found")
|
raise HTTPException(status_code=404, detail="collection_not_found")
|
||||||
|
|
||||||
return coll
|
return coll
|
||||||
|
|
||||||
|
@app.get(
|
||||||
|
"/orgs/{oid}/collections/{coll_id}/public/replay.json", tags=["collections"]
|
||||||
|
)
|
||||||
|
async def get_collection_public_replay(
|
||||||
|
response: Response,
|
||||||
|
coll_id: uuid.UUID,
|
||||||
|
org: Organization = Depends(org_public),
|
||||||
|
):
|
||||||
|
coll = await colls.get_collection(
|
||||||
|
coll_id, org, resources=True, public_only=True
|
||||||
|
)
|
||||||
|
if not coll:
|
||||||
|
raise HTTPException(status_code=404, detail="collection_not_found")
|
||||||
|
|
||||||
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
|
return coll
|
||||||
|
|
||||||
|
@app.options(
|
||||||
|
"/orgs/{oid}/collections/{coll_id}/public/replay.json", tags=["collections"]
|
||||||
|
)
|
||||||
|
async def get_replay_preflight(response: Response):
|
||||||
|
response.headers["Access-Control-Allow-Methods"] = "GET, HEAD, OPTIONS"
|
||||||
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
|
return {}
|
||||||
|
|
||||||
@app.patch("/orgs/{oid}/collections/{coll_id}", tags=["collections"])
|
@app.patch("/orgs/{oid}/collections/{coll_id}", tags=["collections"])
|
||||||
async def update_collection(
|
async def update_collection(
|
||||||
coll_id: uuid.UUID,
|
coll_id: uuid.UUID,
|
||||||
|
@ -465,6 +465,8 @@ class Collection(BaseMongoModel):
|
|||||||
# Sorted by count, descending
|
# Sorted by count, descending
|
||||||
tags: Optional[List[str]] = []
|
tags: Optional[List[str]] = []
|
||||||
|
|
||||||
|
isPublic: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
class CollIn(BaseModel):
|
class CollIn(BaseModel):
|
||||||
@ -474,6 +476,8 @@ class CollIn(BaseModel):
|
|||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
crawlIds: Optional[List[str]] = []
|
crawlIds: Optional[List[str]] = []
|
||||||
|
|
||||||
|
isPublic: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
class CollOut(Collection):
|
class CollOut(Collection):
|
||||||
@ -488,6 +492,7 @@ class UpdateColl(BaseModel):
|
|||||||
|
|
||||||
name: Optional[str]
|
name: Optional[str]
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
|
isPublic: Optional[bool]
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
@ -45,6 +45,7 @@ class OrgOps:
|
|||||||
self.org_viewer_dep = None
|
self.org_viewer_dep = None
|
||||||
self.org_crawl_dep = None
|
self.org_crawl_dep = None
|
||||||
self.org_owner_dep = None
|
self.org_owner_dep = None
|
||||||
|
self.org_public = None
|
||||||
|
|
||||||
self.invites = invites
|
self.invites = invites
|
||||||
|
|
||||||
@ -300,6 +301,13 @@ def init_orgs_api(app, mdb, user_manager, invites, user_dep: User):
|
|||||||
|
|
||||||
return org
|
return org
|
||||||
|
|
||||||
|
async def org_public(oid: str):
|
||||||
|
org = await ops.get_org_by_id(uuid.UUID(oid))
|
||||||
|
if not org:
|
||||||
|
raise HTTPException(status_code=404, detail="org_not_found")
|
||||||
|
|
||||||
|
return org
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/orgs/{oid}",
|
prefix="/orgs/{oid}",
|
||||||
dependencies=[Depends(org_dep)],
|
dependencies=[Depends(org_dep)],
|
||||||
@ -310,6 +318,7 @@ def init_orgs_api(app, mdb, user_manager, invites, user_dep: User):
|
|||||||
ops.org_viewer_dep = org_dep
|
ops.org_viewer_dep = org_dep
|
||||||
ops.org_crawl_dep = org_crawl_dep
|
ops.org_crawl_dep = org_crawl_dep
|
||||||
ops.org_owner_dep = org_owner_dep
|
ops.org_owner_dep = org_owner_dep
|
||||||
|
ops.org_public = org_public
|
||||||
|
|
||||||
@app.get("/orgs", tags=["organizations"], response_model=PaginatedResponse)
|
@app.get("/orgs", tags=["organizations"], response_model=PaginatedResponse)
|
||||||
async def get_orgs(
|
async def get_orgs(
|
||||||
|
@ -402,7 +402,6 @@ def init_users_api(app, user_manager):
|
|||||||
}
|
}
|
||||||
for org in user_orgs
|
for org in user_orgs
|
||||||
]
|
]
|
||||||
print(f"user info with orgs: {user_info}", flush=True)
|
|
||||||
return user_info
|
return user_info
|
||||||
|
|
||||||
@users_router.get("/invite/{token}", tags=["invites"])
|
@users_router.get("/invite/{token}", tags=["invites"])
|
||||||
|
@ -51,6 +51,9 @@ export class CollectionDetail extends LiteElement {
|
|||||||
@state()
|
@state()
|
||||||
private isDescriptionExpanded = false;
|
private isDescriptionExpanded = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private showEmbedInfo = false;
|
||||||
|
|
||||||
// Use to cancel requests
|
// Use to cancel requests
|
||||||
private getArchivedItemsController: AbortController | null = null;
|
private getArchivedItemsController: AbortController | null = null;
|
||||||
|
|
||||||
@ -89,12 +92,32 @@ export class CollectionDetail extends LiteElement {
|
|||||||
render() {
|
render() {
|
||||||
return html`${this.renderHeader()}
|
return html`${this.renderHeader()}
|
||||||
<header class="md:flex items-center gap-2 pb-3">
|
<header class="md:flex items-center gap-2 pb-3">
|
||||||
<h1
|
<div class="flex items-center gap-2 w-full mb-2 md:mb-0">
|
||||||
class="flex-1 min-w-0 text-xl font-semibold leading-7 truncate mb-2 md:mb-0"
|
${this.collection?.isPublic
|
||||||
>
|
? html`
|
||||||
|
<sl-tooltip content=${msg("Shareable")}>
|
||||||
|
<sl-icon class="text-lg" name="people-fill"></sl-icon>
|
||||||
|
</sl-tooltip>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<sl-tooltip content=${msg("Private")}>
|
||||||
|
<sl-icon class="text-lg" name="eye-slash-fill"></sl-icon>
|
||||||
|
</sl-tooltip>
|
||||||
|
`}
|
||||||
|
<h1 class="flex-1 min-w-0 text-xl font-semibold leading-7 truncate">
|
||||||
${this.collection?.name ||
|
${this.collection?.name ||
|
||||||
html`<sl-skeleton class="w-96"></sl-skeleton>`}
|
html`<sl-skeleton class="w-96"></sl-skeleton>`}
|
||||||
</h1>
|
</h1>
|
||||||
|
</div>
|
||||||
|
${when(
|
||||||
|
this.collection?.isPublic,
|
||||||
|
() => html`
|
||||||
|
<sl-button size="small" @click=${() => (this.showEmbedInfo = true)}>
|
||||||
|
<sl-icon name="code-slash"></sl-icon>
|
||||||
|
View Embed Code
|
||||||
|
</sl-button>
|
||||||
|
`
|
||||||
|
)}
|
||||||
${when(this.isCrawler, this.renderActions)}
|
${when(this.isCrawler, this.renderActions)}
|
||||||
</header>
|
</header>
|
||||||
<div class="border rounded-lg py-2 mb-3">${this.renderInfoBar()}</div>
|
<div class="border rounded-lg py-2 mb-3">${this.renderInfoBar()}</div>
|
||||||
@ -135,9 +158,85 @@ export class CollectionDetail extends LiteElement {
|
|||||||
>Delete Collection</sl-button
|
>Delete Collection</sl-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</btrix-dialog>`;
|
</btrix-dialog>
|
||||||
|
${this.renderShareInfo()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getPublicReplayURL() {
|
||||||
|
return new URL(
|
||||||
|
`/api/orgs/${this.orgId}/collections/${this.collectionId}/public/replay.json`,
|
||||||
|
window.location.href
|
||||||
|
).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderShareInfo = () => {
|
||||||
|
if (!this.collection?.isPublic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const embedCode = `<replay-web-page source="${this.getPublicReplayURL()}"></replay-web-page>`;
|
||||||
|
const importCode = `importScripts("https://replayweb.page/sw.js");`;
|
||||||
|
|
||||||
|
return html` <btrix-dialog
|
||||||
|
label=${msg(str`Embed Code for “${this.collection?.name}”`)}
|
||||||
|
?open=${this.showEmbedInfo}
|
||||||
|
@sl-request-close=${() => (this.showEmbedInfo = false)}
|
||||||
|
>
|
||||||
|
<div class="text-left">
|
||||||
|
<p class="mb-5">
|
||||||
|
${msg(
|
||||||
|
html`Embed this collection in another site using these
|
||||||
|
<strong class="font-medium">ReplayWeb.page</strong> code snippets.`
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="mb-3">
|
||||||
|
${msg(html`Add the following embed code to your HTML page:`)}
|
||||||
|
</p>
|
||||||
|
<div class="relative">
|
||||||
|
<pre
|
||||||
|
class="whitespace-pre-wrap mb-5 rounded p-4 bg-slate-50 text-slate-600 text-[0.9em]"
|
||||||
|
><code>${embedCode}</code></pre>
|
||||||
|
<div class="absolute top-0 right-0">
|
||||||
|
<btrix-copy-button
|
||||||
|
.getValue=${() => embedCode}
|
||||||
|
content=${msg("Copy Embed Code")}
|
||||||
|
></btrix-copy-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mb-3">
|
||||||
|
${msg(
|
||||||
|
html`Add the following JavaScript to
|
||||||
|
<code class="text-[0.9em]">./replay/sw.js</code>:`
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="relative">
|
||||||
|
<pre
|
||||||
|
class="whitespace-pre-wrap mb-5 rounded p-4 bg-slate-50 text-slate-600 text-[0.9em]"
|
||||||
|
><code>${importCode}</code></pre>
|
||||||
|
<div class="absolute top-0 right-0">
|
||||||
|
<btrix-copy-button
|
||||||
|
.getValue=${() => importCode}
|
||||||
|
content=${msg("Copy JS")}
|
||||||
|
></btrix-copy-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
${msg(
|
||||||
|
html`See
|
||||||
|
<a
|
||||||
|
class="text-primary"
|
||||||
|
href="https://replayweb.page/docs/embedding"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
our embedding guide</a
|
||||||
|
>
|
||||||
|
for more details.`
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</btrix-dialog>`;
|
||||||
|
};
|
||||||
|
|
||||||
private renderHeader = () => html`
|
private renderHeader = () => html`
|
||||||
<nav class="mb-7">
|
<nav class="mb-7">
|
||||||
<a
|
<a
|
||||||
@ -197,6 +296,35 @@ export class CollectionDetail extends LiteElement {
|
|||||||
${msg("Edit Collection")}
|
${msg("Edit Collection")}
|
||||||
</sl-menu-item>
|
</sl-menu-item>
|
||||||
<sl-divider></sl-divider>
|
<sl-divider></sl-divider>
|
||||||
|
${!this.collection?.isPublic
|
||||||
|
? html`
|
||||||
|
<sl-menu-item
|
||||||
|
style="--sl-color-neutral-700: var(--success)"
|
||||||
|
@click=${() => this.onTogglePublic(true)}
|
||||||
|
>
|
||||||
|
<sl-icon name="people-fill" slot="prefix"></sl-icon>
|
||||||
|
${msg("Make Shareable")}
|
||||||
|
</sl-menu-item>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<sl-menu-item style="--sl-color-neutral-700: var(--success)">
|
||||||
|
<sl-icon name="box-arrow-up-right" slot="prefix"></sl-icon>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
slot="prefix"
|
||||||
|
href="https://replayweb.page?source=${this.getPublicReplayURL()}"
|
||||||
|
>
|
||||||
|
Go to Public View
|
||||||
|
</a>
|
||||||
|
</sl-menu-item>
|
||||||
|
<sl-menu-item
|
||||||
|
style="--sl-color-neutral-700: var(--warning)"
|
||||||
|
@click=${() => this.onTogglePublic(false)}
|
||||||
|
>
|
||||||
|
<sl-icon name="eye-slash" slot="prefix"></sl-icon>
|
||||||
|
${msg("Make Private")}
|
||||||
|
</sl-menu-item>
|
||||||
|
`}
|
||||||
<!-- Shoelace doesn't allow "href" on menu items,
|
<!-- Shoelace doesn't allow "href" on menu items,
|
||||||
see https://github.com/shoelace-style/shoelace/issues/1351 -->
|
see https://github.com/shoelace-style/shoelace/issues/1351 -->
|
||||||
<a
|
<a
|
||||||
@ -480,6 +608,21 @@ export class CollectionDetail extends LiteElement {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private async onTogglePublic(isPublic: boolean) {
|
||||||
|
const res = await this.apiFetch(
|
||||||
|
`/orgs/${this.orgId}/collections/${this.collectionId}`,
|
||||||
|
this.authState!,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify({ isPublic }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.updated && this.collection) {
|
||||||
|
this.collection = { ...this.collection, isPublic };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private confirmDelete = () => {
|
private confirmDelete = () => {
|
||||||
this.openDialogName = "delete";
|
this.openDialogName = "delete";
|
||||||
};
|
};
|
||||||
|
@ -83,7 +83,8 @@ export class CollectionEdit extends LiteElement {
|
|||||||
|
|
||||||
private async onSubmit(e: CollectionSubmitEvent) {
|
private async onSubmit(e: CollectionSubmitEvent) {
|
||||||
this.isSubmitting = true;
|
this.isSubmitting = true;
|
||||||
const { name, description, crawlIds, oldCrawlIds } = e.detail.values;
|
const { name, description, crawlIds, oldCrawlIds, isPublic } =
|
||||||
|
e.detail.values;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (oldCrawlIds && oldCrawlIds) {
|
if (oldCrawlIds && oldCrawlIds) {
|
||||||
@ -92,7 +93,11 @@ export class CollectionEdit extends LiteElement {
|
|||||||
oldCrawlIds,
|
oldCrawlIds,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.saveMetadata({ name, description });
|
await this.saveMetadata({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
isPublic: isPublic === "on",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.navTo(`/orgs/${this.orgId}/collections/view/${this.collectionId}`);
|
this.navTo(`/orgs/${this.orgId}/collections/view/${this.collectionId}`);
|
||||||
@ -117,7 +122,11 @@ export class CollectionEdit extends LiteElement {
|
|||||||
this.isSubmitting = false;
|
this.isSubmitting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveMetadata(values: { name: string; description: string | null }) {
|
private saveMetadata(values: {
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
isPublic: boolean;
|
||||||
|
}) {
|
||||||
return this.apiFetch(
|
return this.apiFetch(
|
||||||
`/orgs/${this.orgId}/collections/${this.collectionId}`,
|
`/orgs/${this.orgId}/collections/${this.collectionId}`,
|
||||||
this.authState!,
|
this.authState!,
|
||||||
|
@ -84,6 +84,7 @@ export type CollectionSubmitEvent = CustomEvent<{
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
crawlIds: string[];
|
crawlIds: string[];
|
||||||
oldCrawlIds?: string[];
|
oldCrawlIds?: string[];
|
||||||
|
isPublic: string | null;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@ -512,6 +513,11 @@ export class CollectionEditor extends LiteElement {
|
|||||||
maxlength=${4000}
|
maxlength=${4000}
|
||||||
></btrix-markdown-editor>
|
></btrix-markdown-editor>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<label>
|
||||||
|
<sl-switch name="isPublic" ?checked=${this.metadataValues?.isPublic}
|
||||||
|
>Publicly Accessible</sl-switch
|
||||||
|
>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<footer class="border-t px-6 py-4 flex justify-between">
|
<footer class="border-t px-6 py-4 flex justify-between">
|
||||||
${when(
|
${when(
|
||||||
|
@ -454,6 +454,23 @@ export class CollectionsList extends LiteElement {
|
|||||||
class="block text-primary hover:text-indigo-500"
|
class="block text-primary hover:text-indigo-500"
|
||||||
@click=${this.navLink}
|
@click=${this.navLink}
|
||||||
>
|
>
|
||||||
|
${col?.isPublic
|
||||||
|
? html`
|
||||||
|
<sl-tooltip content=${msg("Shareable")}>
|
||||||
|
<sl-icon
|
||||||
|
style="margin-right: 4px; vertical-align: bottom; font-size: 14px;"
|
||||||
|
name="people-fill"
|
||||||
|
></sl-icon>
|
||||||
|
</sl-tooltip>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<sl-tooltip content=${msg("Private")}>
|
||||||
|
<sl-icon
|
||||||
|
style="margin-right: 4px; vertical-align: bottom; font-size: 14px;"
|
||||||
|
name="eye-slash-fill"
|
||||||
|
></sl-icon>
|
||||||
|
</sl-tooltip>
|
||||||
|
`}
|
||||||
${col.name}
|
${col.name}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -523,6 +540,37 @@ export class CollectionsList extends LiteElement {
|
|||||||
${msg("Edit Collection")}
|
${msg("Edit Collection")}
|
||||||
</sl-menu-item>
|
</sl-menu-item>
|
||||||
<sl-divider></sl-divider>
|
<sl-divider></sl-divider>
|
||||||
|
${!col?.isPublic
|
||||||
|
? html`
|
||||||
|
<sl-menu-item
|
||||||
|
style="--sl-color-neutral-700: var(--success)"
|
||||||
|
@click=${() => this.onTogglePublic(col, true)}
|
||||||
|
>
|
||||||
|
<sl-icon name="people-fill" slot="prefix"></sl-icon>
|
||||||
|
${msg("Make Shareable")}
|
||||||
|
</sl-menu-item>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<sl-menu-item style="--sl-color-neutral-700: var(--success)">
|
||||||
|
<sl-icon name="box-arrow-up-right" slot="prefix"></sl-icon>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
slot="prefix"
|
||||||
|
href="https://replayweb.page?source=${this.getPublicReplayURL(
|
||||||
|
col
|
||||||
|
)}"
|
||||||
|
>
|
||||||
|
Go to Shared View
|
||||||
|
</a>
|
||||||
|
</sl-menu-item>
|
||||||
|
<sl-menu-item
|
||||||
|
style="--sl-color-neutral-700: var(--warning)"
|
||||||
|
@click=${() => this.onTogglePublic(col, false)}
|
||||||
|
>
|
||||||
|
<sl-icon name="eye-slash" slot="prefix"></sl-icon>
|
||||||
|
${msg("Make Private")}
|
||||||
|
</sl-menu-item>
|
||||||
|
`}
|
||||||
<!-- Shoelace doesn't allow "href" on menu items,
|
<!-- Shoelace doesn't allow "href" on menu items,
|
||||||
see https://github.com/shoelace-style/shoelace/issues/1351 -->
|
see https://github.com/shoelace-style/shoelace/issues/1351 -->
|
||||||
<a
|
<a
|
||||||
@ -572,6 +620,26 @@ export class CollectionsList extends LiteElement {
|
|||||||
}
|
}
|
||||||
}) as any;
|
}) as any;
|
||||||
|
|
||||||
|
private async onTogglePublic(coll: Collection, isPublic: boolean) {
|
||||||
|
const res = await this.apiFetch(
|
||||||
|
`/orgs/${this.orgId}/collections/${coll.id}`,
|
||||||
|
this.authState!,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify({ isPublic }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.fetchCollections();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPublicReplayURL(col: Collection) {
|
||||||
|
return new URL(
|
||||||
|
`/api/orgs/${this.orgId}/collections/${col.id}/public/replay.json`,
|
||||||
|
window.location.href
|
||||||
|
).href;
|
||||||
|
}
|
||||||
|
|
||||||
private confirmDelete = (collection: Collection) => {
|
private confirmDelete = (collection: Collection) => {
|
||||||
this.collectionToDelete = collection;
|
this.collectionToDelete = collection;
|
||||||
this.openDialogName = "delete";
|
this.openDialogName = "delete";
|
||||||
|
@ -59,12 +59,18 @@ export class CollectionsNew extends LiteElement {
|
|||||||
console.log("submit", e.detail.values);
|
console.log("submit", e.detail.values);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const { name, description, crawlIds, isPublic } = e.detail.values;
|
||||||
const data = await this.apiFetch(
|
const data = await this.apiFetch(
|
||||||
`/orgs/${this.orgId}/collections`,
|
`/orgs/${this.orgId}/collections`,
|
||||||
this.authState!,
|
this.authState!,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(e.detail.values),
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
crawlIds,
|
||||||
|
public: isPublic === "on",
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ export type Collection = {
|
|||||||
totalSize: number;
|
totalSize: number;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
resources: string[];
|
resources: string[];
|
||||||
|
isPublic: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollectionList = Collection[];
|
export type CollectionList = Collection[];
|
||||||
|
Loading…
Reference in New Issue
Block a user