adds trail share
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
export let title: string = "Confirm Deletion";
|
||||
export let text: string;
|
||||
export let action: string = "delete";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -24,7 +25,7 @@
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button class="btn-danger" type="button" on:click={confirm} name="delete"
|
||||
>{$_("delete")}</button
|
||||
>{$_(action)}</button
|
||||
>
|
||||
</div></Modal
|
||||
>
|
||||
|
||||
@@ -8,14 +8,11 @@
|
||||
} from "$lib/util/format_util";
|
||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
export let list: List;
|
||||
export let active: boolean = false;
|
||||
|
||||
const dropdownItems: DropdownItem[] = [
|
||||
{ text: $_("edit"), value: "edit" },
|
||||
{ text: $_("delete"), value: "delete" },
|
||||
];
|
||||
|
||||
$: cumulativeDistance = list.expand?.trails.reduce(
|
||||
(s, b) => s + b.distance!,
|
||||
0,
|
||||
@@ -30,6 +27,22 @@
|
||||
(s, b) => s + b.duration!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: listIsShared = (list.expand?.list_share_via_list?.length ?? 0) > 0;
|
||||
|
||||
$: allowEdit =
|
||||
list.author == $currentUser?.id ||
|
||||
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
|
||||
|
||||
$: dropdownItems = [
|
||||
...(list.author == $currentUser?.id
|
||||
? [{ text: $_("share"), value: "share" }]
|
||||
: []),
|
||||
...(allowEdit ? [{ text: $_("edit"), value: "edit" }] : []),
|
||||
...(list.author == $currentUser?.id
|
||||
? [{ text: $_("delete"), value: "delete" }]
|
||||
: []),
|
||||
];
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -50,11 +63,18 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="self-start min-w-0 w-full transition-transform">
|
||||
<div class="flex justify-between items-center">
|
||||
<h5 class="text-xl font-semibold overflow-hidden overflow-ellipsis">
|
||||
<div class="flex items-center gap-3">
|
||||
<h5
|
||||
class="text-xl font-semibold overflow-hidden overflow-ellipsis basis-full"
|
||||
>
|
||||
{list.name}
|
||||
</h5>
|
||||
<Dropdown items={dropdownItems} on:change></Dropdown>
|
||||
{#if listIsShared}
|
||||
<ShareInfo type="list" subject={list}></ShareInfo>
|
||||
{/if}
|
||||
{#if dropdownItems.length}
|
||||
<Dropdown items={dropdownItems} on:change></Dropdown>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex mt-1 gap-4 text-sm text-gray-500 whitespace-nowrap">
|
||||
<span
|
||||
@@ -73,12 +93,12 @@
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mb-2"
|
||||
>{list.expand?.trails.length ?? 0}
|
||||
<p class="text-sm text-gray-500 mb-2">
|
||||
{list.expand?.trails.length ?? 0}
|
||||
{$_("trail", {
|
||||
values: { n: list.expand?.trails.length ?? 0 },
|
||||
})}</p
|
||||
>
|
||||
})}
|
||||
</p>
|
||||
<p
|
||||
class="text-gray-500 text-sm mr-8 whitespace-pre-wrap {active
|
||||
? ''
|
||||
|
||||
222
web/src/lib/components/list/list_share_modal.svelte
Normal file
222
web/src/lib/components/list/list_share_modal.svelte
Normal file
@@ -0,0 +1,222 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import type { List } from "$lib/models/list";
|
||||
import { ListShare } from "$lib/models/list_share";
|
||||
import { TrailShare } from "$lib/models/trail_share";
|
||||
import type { User } from "$lib/models/user";
|
||||
import {
|
||||
list_share_create,
|
||||
list_share_delete,
|
||||
list_share_index,
|
||||
list_share_update,
|
||||
shares,
|
||||
} from "$lib/stores/list_share_store";
|
||||
import { show_toast } from "$lib/stores/toast_store";
|
||||
import {
|
||||
trail_share_create,
|
||||
trail_share_delete,
|
||||
trail_share_index,
|
||||
} from "$lib/stores/trail_share_store";
|
||||
import { users_search } from "$lib/stores/user_store";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import Button from "../base/button.svelte";
|
||||
import Search, { type SearchItem } from "../base/search.svelte";
|
||||
import type { SelectItem } from "../base/select.svelte";
|
||||
import Select from "../base/select.svelte";
|
||||
import { useFBO } from "@threlte/extras";
|
||||
import { lists } from "$lib/stores/list_store";
|
||||
|
||||
let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
export function openShareModal() {
|
||||
shares.set([]);
|
||||
fetchShares();
|
||||
if (openModal) {
|
||||
openModal();
|
||||
}
|
||||
}
|
||||
|
||||
export let list: List;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let copyButtonText = $_("copy-link");
|
||||
|
||||
let searchItems: SearchItem[] = [];
|
||||
|
||||
let sharesLoading: boolean = false;
|
||||
|
||||
const permissionSelectItems: SelectItem[] = [
|
||||
{ text: $_("view"), value: "view" },
|
||||
{ text: $_("edit"), value: "edit" },
|
||||
];
|
||||
|
||||
async function updateUsers(q: string) {
|
||||
try {
|
||||
const users: User[] = await users_search(q);
|
||||
searchItems = users.map((u) => ({
|
||||
text: u.username!,
|
||||
value: u,
|
||||
icon: "user",
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
show_toast({
|
||||
type: "error",
|
||||
icon: "close",
|
||||
text: "Error during search",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function copyURLToClipboard() {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
|
||||
copyButtonText = $_("link-copied");
|
||||
setTimeout(() => (copyButtonText = $_("copy-link")), 3000);
|
||||
}
|
||||
|
||||
function close() {
|
||||
searchItems = [];
|
||||
dispatch("save");
|
||||
closeModal!();
|
||||
}
|
||||
|
||||
async function shareTrails(userId: string) {
|
||||
const existingTrailShares = await trail_share_index({ user: userId });
|
||||
const trailIds = existingTrailShares.map((s) => s.trail);
|
||||
for (const trailId of list.trails ?? []) {
|
||||
if (!trailIds.includes(trailId)) {
|
||||
const share = new TrailShare(userId, trailId, "view");
|
||||
await trail_share_create(share);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function shareList(item: SelectItem) {
|
||||
const share = new ListShare(item.value.id, list.id!, "view");
|
||||
await list_share_create(share);
|
||||
await shareTrails(item.value.id);
|
||||
fetchShares();
|
||||
}
|
||||
|
||||
async function updateSharePermission(
|
||||
share: ListShare,
|
||||
permission: "view" | "edit",
|
||||
) {
|
||||
share.permission = permission;
|
||||
await list_share_update(share);
|
||||
}
|
||||
|
||||
async function deleteTrailShares(userId: string) {
|
||||
const existingTrailShares = await trail_share_index({ user: userId });
|
||||
for (const trailId of list.trails ?? []) {
|
||||
const shareToDelete = existingTrailShares.find(
|
||||
(s) => s.trail == trailId,
|
||||
);
|
||||
if (shareToDelete) {
|
||||
await trail_share_delete(shareToDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteShare(share: ListShare) {
|
||||
await list_share_delete(share);
|
||||
// await deleteTrailShares(share.user);
|
||||
fetchShares();
|
||||
}
|
||||
|
||||
async function fetchShares() {
|
||||
sharesLoading = true;
|
||||
const fetchedShares = await list_share_index(list.id!);
|
||||
list.expand = {
|
||||
trails: list.expand?.trails ?? [],
|
||||
list_share_via_list: fetchedShares,
|
||||
};
|
||||
sharesLoading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="share-modal"
|
||||
title={$_("share-this-list")}
|
||||
size="max-w-sm overflow-visible"
|
||||
bind:openModal
|
||||
bind:closeModal
|
||||
>
|
||||
<div slot="content">
|
||||
<p class="p-4 bg-amber-100 rounded-xl mb-4 text-sm text-gray-500">
|
||||
{$_("list-share-warning")}
|
||||
</p>
|
||||
<Search
|
||||
on:update={(e) => updateUsers(e.detail)}
|
||||
on:click={(e) => shareList(e.detail)}
|
||||
placeholder={`${$_("username")}`}
|
||||
items={searchItems}
|
||||
>
|
||||
<img
|
||||
slot="item-header"
|
||||
let:item
|
||||
class="rounded-full w-8 aspect-square mr-2"
|
||||
src={getFileURL(item.value, item.value.avatar) ||
|
||||
`https://api.dicebear.com/7.x/initials/svg?seed=${item.value.username}&backgroundType=gradientLinear`}
|
||||
alt="avatar"
|
||||
/>
|
||||
</Search>
|
||||
<h4 class="font-semibold mt-4">{$_("shared-with")}</h4>
|
||||
|
||||
{#if $shares.length == 0}
|
||||
<p class="text-gray-500 text-center mt-2 text-sm">
|
||||
{$_("list-not-shared")}
|
||||
</p>
|
||||
{:else}
|
||||
{#each $shares as share}
|
||||
{#if share.expand}
|
||||
<div class="flex items-center gap-x-2 p-2">
|
||||
<img
|
||||
class="rounded-full w-8 aspect-square mr-2"
|
||||
src={getFileURL(
|
||||
share.expand.user,
|
||||
share.expand.user.avatar,
|
||||
) ||
|
||||
`https://api.dicebear.com/7.x/initials/svg?seed=${share.expand.user.username}&backgroundType=gradientLinear`}
|
||||
alt="avatar"
|
||||
/>
|
||||
<p>{share.expand.user.username}</p>
|
||||
<span
|
||||
class="basis-full text-sm text-center text-gray-500"
|
||||
>{$_("can")}</span
|
||||
>
|
||||
<div class="shrink-0">
|
||||
<Select
|
||||
bind:value={share.permission}
|
||||
items={permissionSelectItems}
|
||||
on:change={(e) =>
|
||||
updateSharePermission(share, e.detail)}
|
||||
></Select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn-icon text-red-500"
|
||||
on:click={() => deleteShare(share)}
|
||||
><i class="fa fa-trash"></i></button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div slot="footer" class="flex justify-between items-center gap-4">
|
||||
<Button
|
||||
secondary={true}
|
||||
disabled={copyButtonText == $_("link-copied")}
|
||||
on:click={copyURLToClipboard}
|
||||
>
|
||||
<i class="fa fa-link mr-2"></i>
|
||||
{copyButtonText}
|
||||
</Button>
|
||||
<button class="btn-primary" on:click={close}>{$_("close")}</button>
|
||||
</div></Modal
|
||||
>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { List } from "$lib/models/list";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import type { User } from "$lib/models/user";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
@@ -7,31 +8,30 @@
|
||||
import { _ } from "svelte-i18n";
|
||||
import { fly, slide } from "svelte/transition";
|
||||
|
||||
export let trail: Trail;
|
||||
export let subject: Trail | List;
|
||||
export let large: boolean = false;
|
||||
export let type: "trail" | "list"
|
||||
|
||||
const shareData = type == "trail" ? (subject as Trail).expand.trail_share_via_trail : (subject as List).expand?.list_share_via_list
|
||||
|
||||
let showInfo: boolean = false;
|
||||
|
||||
let loading: boolean = false;
|
||||
|
||||
let infoLoaded: boolean = false;
|
||||
|
||||
let trailIsOwned: boolean = trail.author == pb.authStore.model?.id;
|
||||
|
||||
let subjectIsOwned: boolean = subject.author == pb.authStore.model?.id;
|
||||
let author: User;
|
||||
|
||||
async function fetchInfo() {
|
||||
if (!infoLoaded) {
|
||||
loading = true;
|
||||
loading = false;
|
||||
if (trailIsOwned) {
|
||||
for (const share of trail.expand.trail_share_via_trail ?? []) {
|
||||
if (subjectIsOwned) {
|
||||
for (const share of shareData ?? []) {
|
||||
share.expand = {
|
||||
user: await users_show(share.user),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
author = await users_show(trail.author!);
|
||||
author = await users_show(subject.author!);
|
||||
}
|
||||
|
||||
infoLoaded = true;
|
||||
@@ -57,9 +57,9 @@
|
||||
>
|
||||
{#if loading}
|
||||
<div class="spinner spinner-dark"></div>
|
||||
{:else if infoLoaded && trail.expand.trail_share_via_trail}
|
||||
{#if trailIsOwned}
|
||||
{#each trail.expand.trail_share_via_trail as share}
|
||||
{:else if infoLoaded && shareData}
|
||||
{#if subjectIsOwned}
|
||||
{#each shareData as share}
|
||||
{#if share.expand}
|
||||
<li>
|
||||
<div class="flex items-center mr-8">
|
||||
@@ -109,7 +109,7 @@
|
||||
{$_("you-can")}
|
||||
</p>
|
||||
<p class="whitespace-nowrap">
|
||||
{#if trail.expand.trail_share_via_trail[0].permission == "view"}
|
||||
{#if shareData[0].permission == "view"}
|
||||
<i class="fa fa-eye mr-1"></i>
|
||||
{$_("view")}
|
||||
{:else}
|
||||
@@ -7,7 +7,7 @@
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import { _ } from "svelte-i18n";
|
||||
import TrailShareInfo from "./trail_share_info.svelte";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if trail.expand?.trail_share_via_trail?.length}
|
||||
<TrailShareInfo {trail}></TrailShareInfo>
|
||||
<ShareInfo type="trail" subject={trail}></ShareInfo>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
import Textarea from "../base/textarea.svelte";
|
||||
import CommentCard from "../comment/comment_card.svelte";
|
||||
import PhotoGallery from "../photo_gallery.svelte";
|
||||
import TrailShareInfo from "./trail_share_info.svelte";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
export let mode: "overview" | "map" = "map";
|
||||
@@ -190,7 +190,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if trailIsShared}
|
||||
<TrailShareInfo {trail} large={true}></TrailShareInfo>
|
||||
<ShareInfo type="trail" subject={trail} large={true}></ShareInfo>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import { _ } from "svelte-i18n";
|
||||
import TrailShareInfo from "./trail_share_info.svelte";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if trail.expand?.trail_share_via_trail?.length}
|
||||
<TrailShareInfo {trail}></TrailShareInfo>
|
||||
<ShareInfo type="trail" subject={trail}></ShareInfo>
|
||||
{/if}
|
||||
</div>
|
||||
{#if trail.date}
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
async function fetchShares() {
|
||||
sharesLoading = true;
|
||||
await trail_share_index(trail.id!);
|
||||
await trail_share_index({trail: trail.id!});
|
||||
sharesLoading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user