Compare commits

...

5 Commits

29 changed files with 226 additions and 148 deletions
+1
View File
@@ -12,6 +12,7 @@ if (execSync('git status --porcelain', { encoding: 'utf8' }).trim() && !force) {
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
pkg.pnpm = pkg.pnpm || {}
pkg.pnpm.overrides = pkg.pnpm.overrides || {}
pkg.pnpm.overrides["@welshman/app"] = "link:../welshman/packages/app"
pkg.pnpm.overrides["@welshman/content"] = "link:../welshman/packages/content"
-6
View File
@@ -262,12 +262,6 @@ app.use(
// SPA fallback for routes that don't match static files
app.get("*", async context => {
const requestUrl = requestUrlFromContext(context)
// If the path has an extension, it's likely a missing static asset, not an SPA route
if (path.extname(requestUrl.pathname)) {
return context.text("Not found", 404)
}
const metadata = await getMetadataForRoute(requestUrl)
const html = metadata ? injectMeta(metadata) : TEMPLATE_HTML
+8 -1
View File
@@ -19,6 +19,7 @@ import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
import {signer} from "@welshman/app"
import {load} from "@welshman/net"
import {getLivekitEndpoint} from "$lib/livekit"
import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util"
import {
@@ -154,6 +155,12 @@ const fetchLivekitToken = async (
return response.json()
}
export const loadVoiceParticipants = (url: string, h: string) =>
load({
relays: [url],
filters: [{kinds: [LIVEKIT_PARTICIPANTS], "#d": [h]}],
})
export const deriveVoiceParticipants = (url: string, h: string) =>
// We use the livekit identity list while in a call, and fall back to the list in kind 39004.
derived(
@@ -173,7 +180,7 @@ export const deriveVoiceParticipants = (url: string, h: string) =>
if (!latestEvent) return []
const participants = removeUndefined(
map(
(tag: string[]) => (tag[1] ? {pubkey: tag[1], identity: tag[1]} : undefined),
(tag: string[]) => (tag[1] ? participantFromLiveKitIdentity(tag[1]) : undefined),
getTags("participant", latestEvent.tags),
),
)
+1 -1
View File
@@ -280,7 +280,7 @@
</div>
</PageBar>
<PageContent class="flex flex-col-reverse gap-2 py-4">
<PageContent class="flex flex-col-reverse gap-2 py-2 !mb-0">
{#if missingRelayLists.length > 0}
<div class="py-12">
<div class="card2 col-2 m-auto max-w-md items-center text-center">
+35 -6
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import cx from "classnames"
import {getProfile, loadProfile} from "@welshman/app"
import {isMobile} from "@lib/html"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
@@ -6,10 +7,20 @@
type Props = {
pubkeys: string[]
size?: number
limit?: number
class?: string
}
const {pubkeys, size = 7}: Props = $props()
const limit = isMobile ? 7 : 10
const {pubkeys, size = 7, limit, class: className}: Props = $props()
const effectiveLimit = $derived(limit ?? (isMobile ? 7 : 10))
const dimensions = $derived(
size <= 5
? {box: "h-5 w-5", overlap: "-mr-2", overflow: "text-[9px]"}
: size <= 6
? {box: "h-6 w-6", overlap: "-mr-2.5", overflow: "text-[10px]"}
: {box: "h-8 w-8", overlap: "-mr-3", overflow: "text-xs"},
)
for (const pubkey of pubkeys) {
loadProfile(pubkey)
@@ -20,13 +31,31 @@
return filtered.length > 0 ? filtered : pubkeys.slice(0, 1)
})
const displayPubkeys = $derived(visiblePubkeys.toSorted().slice(0, effectiveLimit))
const overflowCount = $derived(Math.max(0, pubkeys.length - effectiveLimit))
</script>
<div class="flex pr-3">
{#each visiblePubkeys.toSorted().slice(0, limit) as pubkey (pubkey)}
<div class={cx("flex", size <= 5 ? "pr-2" : "pr-3", className)}>
{#each displayPubkeys as pubkey (pubkey)}
<div
class="z-feature -mr-3 inline-block flex h-8 w-8 items-center justify-center rounded-full bg-base-100">
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {size} />
class={cx(
"z-feature inline-block flex items-center justify-center rounded-full bg-base-100",
dimensions.box,
dimensions.overlap,
)}>
<ProfileCircle class={cx(dimensions.box, "bg-base-300")} {pubkey} {size} />
</div>
{/each}
{#if overflowCount > 0}
<div
class={cx(
"z-feature inline-flex items-center justify-center rounded-full bg-neutral font-medium text-neutral-content",
dimensions.box,
dimensions.overlap,
dimensions.overflow,
)}>
+{overflowCount}
</div>
{/if}
</div>
+1 -1
View File
@@ -88,7 +88,7 @@
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label>
<div class="column -m-6 mt-0 h-[50vh] gap-2 overflow-auto p-6 pt-2" bind:this={element}>
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(normalizeRelayUrl(customUrl))}
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(customUrl)}
<RelayItem url={term}>
<Button
class="btn btn-outline btn-sm flex items-center"
+3 -12
View File
@@ -45,20 +45,11 @@
event: TrustedEvent
replyTo?: (event: TrustedEvent) => void
showPubkey?: boolean
addSpaceBelow?: boolean
canEdit: (event: TrustedEvent) => boolean
onEdit: (event: TrustedEvent) => void
}
const {
url,
event,
replyTo = undefined,
showPubkey = false,
addSpaceBelow = false,
canEdit,
onEdit,
}: Props = $props()
const {url, event, replyTo = undefined, showPubkey = false, canEdit, onEdit}: Props = $props()
const path = getRoomItemPath(url, event)
const shouldProtect = canEnforceNip70(url)
@@ -95,7 +86,7 @@
{onTap}
class={cx(
"group relative flex w-full cursor-default flex-col px-2 py-0.5 text-left hover:bg-base-100/50",
{"mt-1.5": showPubkey, "mb-1.5": addSpaceBelow},
{"mt-1.5": showPubkey},
)}>
<div class="flex w-full gap-3 overflow-auto">
{#if showPubkey}
@@ -127,7 +118,7 @@
<div class:mt-2={showPubkey && event.kind !== MESSAGE}>
<RoomItemContent {url} event={$innerEvent ?? event} />
{#if thunk}
<ThunkFailure showToastOnRetry {thunk} class="mt-2 text-sm" />
<ThunkFailure showToastOnRetry {thunk} class="mt-1 flex justify-end" />
{/if}
</div>
</div>
+26 -21
View File
@@ -2,7 +2,7 @@
import {stopPropagation} from "svelte/legacy"
import {noop} from "@welshman/lib"
import type {AbstractThunk} from "@welshman/app"
import {retryThunk, thunkIsComplete, getFailedThunkUrls} from "@welshman/app"
import {flattenThunks, getFailedThunkUrls, publishThunk, thunkIsComplete} from "@welshman/app"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte"
@@ -16,40 +16,45 @@
class?: string
}
let {thunk, showToastOnRetry, ...restProps}: Props = $props()
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const retry = () => {
thunk = retryThunk(thunk)
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
if (showToastOnRetry) {
pushToast({
timeout: 30_000,
children: {
component: ThunkToast,
props: {thunk},
},
})
const retry = (url: string) => {
for (const child of flattenThunks([thunk])) {
if (!child.options.relays.includes(url)) {
continue
}
const retried = publishThunk({...child.options, relays: [url]})
if (showToastOnRetry) {
pushToast({
timeout: 30_000,
children: {
component: ThunkToast,
props: {thunk: retried},
},
})
}
return
}
}
const failedUrls = $derived(getFailedThunkUrls($thunk))
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
</script>
{#if showFailure}
{@const url = failedUrls[0]}
{@const {status, detail: message} = $thunk.results[url]}
<button
class="flex w-full justify-end px-1 text-xs {restProps.class}"
onclick={stopPropagation(noop)}>
<Tippy
class="flex items-center"
component={ThunkStatusDetail}
props={{url, message, status, retry}}
params={{interactive: true}}>
props={{thunk, retry}}
params={{interactive: true, maxWidth: "none"}}>
{#snippet children()}
<span class="flex cursor-pointer items-center gap-1 text-error">
<Icon icon={Danger} size={3} />
<span class="flex cursor-pointer items-center gap-1 opacity-75">
<Icon icon={Danger} class="text-error" size={3} />
<span>Failed to send!</span>
</span>
{/snippet}
+3 -2
View File
@@ -6,17 +6,18 @@
interface Props {
thunk: AbstractThunk
showToastOnRetry?: boolean
class?: string
}
const {thunk, ...restProps}: Props = $props()
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
const showPending = $derived(!thunkIsComplete($thunk))
</script>
{#if showFailure}
<ThunkFailure class={restProps.class} {thunk} />
<ThunkFailure class={restProps.class} {thunk} {showToastOnRetry} />
{:else if showPending}
<ThunkPending class={restProps.class} {thunk} />
{/if}
+53 -16
View File
@@ -1,32 +1,69 @@
<script lang="ts">
import {stopPropagation} from "svelte/legacy"
import type {AbstractThunk} from "@welshman/app"
import {getFailedThunkUrls, getThunkUrlsWithStatus} from "@welshman/app"
import {PublishStatus} from "@welshman/net"
import {displayRelayUrl} from "@welshman/util"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import {addPeriod} from "@lib/util"
interface Props {
url: string
status: string
message: string
retry: () => void
thunk: AbstractThunk
retry: (url: string) => void
}
let {url, status, message = $bindable(), retry}: Props = $props()
const {thunk, retry}: Props = $props()
$effect(() => {
if (!message && status === PublishStatus.Timeout) {
message = "request timed out"
const successUrls = $derived(getThunkUrlsWithStatus(PublishStatus.Success, $thunk))
const failedUrls = $derived(getFailedThunkUrls($thunk))
const total = $derived(successUrls.length + failedUrls.length)
const isPartial = $derived(successUrls.length > 0 && failedUrls.length > 0)
const title = $derived(
isPartial ? `Partial delivery ${successUrls.length}/${total} relays` : "Failed to send!",
)
const relayMessage = (status: PublishStatus | undefined, detail: string | undefined) => {
if (detail) {
return detail
}
if (!message) {
message = "no details recieved"
if (status === PublishStatus.Timeout) {
return "request timed out"
}
})
return "no details received"
}
</script>
<div class="card2 bg-alt col-2 shadow-lg">
<p>
Failed to publish to {displayRelayUrl(url)}: {addPeriod(message)}
</p>
<Button class="link" onclick={retry}>Retry</Button>
<div class="card2 bg-alt flex min-w-72 max-w-sm flex-col gap-3 px-4 py-3 shadow-lg">
<span class="flex items-center gap-2 text-sm font-medium">
<Icon icon={Danger} class="text-error" size={4} />
{title}
</span>
<div class="divider my-0"></div>
<div class="flex flex-col gap-3">
{#each successUrls as url (url)}
<div class="flex items-start gap-2 text-sm">
<Icon icon={CheckCircle} class="mt-0.5 shrink-0 text-success" size={4} />
<span>{displayRelayUrl(url)}</span>
</div>
{/each}
{#each failedUrls as url (url)}
{@const {detail, status} = $thunk.results[url] || {}}
<div class="grid grid-cols-[1rem_1fr_auto] items-start gap-x-3 gap-y-1 text-sm">
<Icon icon={Danger} class="mt-0.5 text-error" size={4} />
<div class="min-w-0">
<p class="break-all">{displayRelayUrl(url)}</p>
<p class="text-xs opacity-60">{addPeriod(relayMessage(status, detail))}</p>
</div>
<Button class="link shrink-0 px-1" onclick={stopPropagation(() => retry(url))}>
Retry
</Button>
</div>
{/each}
</div>
</div>
+32 -18
View File
@@ -4,6 +4,7 @@
import {loadProfile, displayProfileByPubkey} from "@welshman/app"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import ProfileCircles from "@app/components/ProfileCircles.svelte"
import RoomImage from "@app/components/RoomImage.svelte"
import RoomName from "@app/components/RoomName.svelte"
import {makeRoomPath} from "@app/util/routes"
@@ -20,7 +21,11 @@
voiceState,
type VoiceParticipant,
} from "@app/call/stores"
import {cancelJoinVoiceRoom, deriveVoiceParticipants} from "@app/call/voice"
import {
cancelJoinVoiceRoom,
deriveVoiceParticipants,
loadVoiceParticipants,
} from "@app/call/voice"
interface Props {
url: string
@@ -32,6 +37,7 @@
const {url, h, replaceState = false, notification = false}: Props = $props()
const participants = deriveVoiceParticipants(url, h)
const participantPubkeys = $derived($participants.flatMap(p => (p.pubkey ? [p.pubkey] : [])))
const isActive = $derived(
$voiceState === VoiceState.Connected && $currentVoiceRoom?.id === makeRoomId(url, h),
)
@@ -53,6 +59,10 @@
pushModal(VoiceRoomJoinDialog, {url, h})
}
$effect(() => {
void loadVoiceParticipants(url, h)
})
$effect(() => {
for (const p of $participants) {
if (p.pubkey) loadProfile(p.pubkey)
@@ -75,29 +85,33 @@
{/if}
<RoomName {url} {h} />
</div>
{#if $participants.length > 0}
{#each $participants as p (participantKey(p as VoiceParticipant))}
<div class="flex items-center gap-2 ml-6">
<div
class={cx(
"inline-flex shrink-0 items-center justify-center rounded-full transition-shadow",
isActive && $isParticipantSpeaking(p) && "ring-2 ring-success",
)}>
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
</div>
<span class="ellipsize min-w-0 flex-1 text-xs opacity-70">
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"}
</span>
{#if isActive}
{@const media = $mediaStateByIdentity(p.identity)}
{#if participantPubkeys.length > 0}
{#if isActive}
{#each $participants as p (participantKey(p as VoiceParticipant))}
{@const media = $mediaStateByIdentity(p.identity)}
<div class="flex items-center gap-2 ml-6">
<div
class={cx(
"inline-flex shrink-0 items-center justify-center rounded-full transition-shadow",
$isParticipantSpeaking(p) && "ring-2 ring-success",
)}>
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
</div>
<span class="ellipsize min-w-0 flex-1 text-xs opacity-70">
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"}
</span>
<VoiceParticipantMediaBadges
muted={media.muted}
cameraOn={media.cameraOn}
size={3}
class="shrink-0" />
{/if}
</div>
{/each}
{:else}
<div class="ml-6">
<ProfileCircles pubkeys={participantPubkeys} size={5} limit={3} />
</div>
{/each}
{/if}
{/if}
</div>
</SecondaryNavItem>
+10 -1
View File
@@ -13,8 +13,9 @@
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import ModalTitle from "@lib/components/ModalTitle.svelte"
import {AbortError, TimeoutError} from "$lib/util"
import ProfileCircles from "@app/components/ProfileCircles.svelte"
import {displayRoom} from "@app/core/state"
import {joinVoiceRoom} from "@app/call/voice"
import {deriveVoiceParticipants, joinVoiceRoom, loadVoiceParticipants} from "@app/call/voice"
import {popModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
@@ -26,6 +27,8 @@
const {url, h}: Props = $props()
const spaceLabel = $derived(displayRelayUrl(url))
const participants = deriveVoiceParticipants(url, h)
const participantPubkeys = $derived($participants.flatMap(p => (p.pubkey ? [p.pubkey] : [])))
let audioInputs = $state<MediaDeviceInfo[]>([])
let selectedDeviceId = $state("")
@@ -42,6 +45,7 @@
}
$effect(() => {
void loadVoiceParticipants(url, h)
void loadDevices()
})
@@ -81,6 +85,11 @@
</span>
</ModalSubtitle>
</ModalHeader>
{#if participantPubkeys.length > 0}
<div class="flex justify-center py-2">
<ProfileCircles pubkeys={participantPubkeys} size={5} limit={3} />
</div>
{/if}
<p class="text-sm opacity-80">Select a microphone to join the call:</p>
<div class="flex flex-col gap-4 pt-2">
<div class="flex items-center gap-2">
+1 -1
View File
@@ -755,7 +755,7 @@ export const getSpaceUrlsFromGroupList = (groupList: List | undefined) => {
}
}
return uniq(urls.map(normalizeRelayUrl))
return uniqBy(normalizeRelayUrl, urls)
}
export const getSpaceRoomsFromGroupList = (url: string, groupList: List | undefined) => {
+1 -1
View File
@@ -13,7 +13,7 @@
const className = $derived(
cx(
props.class,
"scroll-container z-feature flex min-h-0 w-full min-w-0 flex-col overflow-y-auto overflow-x-hidden pb-14 md:pb-0",
"scroll-container z-feature flex flex-col min-h-0 w-full min-w-0 overflow-y-auto overflow-x-hidden mb-14 md:mb-0",
),
)
</script>
+1 -1
View File
@@ -254,7 +254,7 @@
</div>
</div>
</PageBar>
<PageContent class="flex flex-col gap-2 p-2 pt-4">
<PageContent class="flex flex-col gap-2 p-2">
<div class="flex flex-col gap-2" bind:this={element}>
{#each PLATFORM_RELAYS as url (url)}
<Button
+2 -11
View File
@@ -350,16 +350,11 @@
event.created_at - previousCreatedAt > int(3, MINUTE) ||
previousKind === ROOM_ADD_MEMBER
if (showPubkey && elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
elements.push({
id: event.id,
type: "note",
value: event,
showPubkey,
addSpaceBelow: false,
})
previousDate = date
@@ -368,9 +363,6 @@
previousCreatedAt = event.created_at
seen.add(event.id)
}
if (elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
}
elements.reverse()
@@ -470,7 +462,7 @@
bind:element
onscroll={onScroll}
class={cx(
"flex-col-reverse pb-0! pt-4",
"flex-col-reverse !mb-0",
showMobileVideoPanel ? "hidden md:flex md:flex-col-reverse" : "flex",
pageContentHiddenDesktopVideoOnly && "md:hidden",
)}>
@@ -501,7 +493,7 @@
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
</p>
{/if}
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
{#each elements as { type, id, value, showPubkey }, i (id)}
{#if type === "new-messages"}
<div
{id}
@@ -523,7 +515,6 @@
{event}
{replyTo}
{showPubkey}
{addSpaceBelow}
canEdit={canEditEvent}
onEdit={onEditEvent} />
{/if}
@@ -125,7 +125,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
<PageContent bind:element class="flex flex-col gap-2 p-2">
{#each items as { event, dateDisplay, isFirstFutureEvent }, i (event.id)}
<div class={"calendar-event-" + event.id}>
{#if isFirstFutureEvent}
@@ -64,7 +64,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-3 p-2 pt-4">
<PageContent class="flex flex-col gap-3 p-2">
{#if $event}
<div class="card2 bg-alt col-3 z-feature">
<div class="flex items-start gap-4">
@@ -109,10 +109,12 @@
</div>
{/if}
{:else}
{#await sleep(5000)}
<Spinner loading>Loading comments...</Spinner>
{:then}
<p>Failed to load comments.</p>
{/await}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading event...</Spinner>
{:then}
<p>Failed to load event.</p>
{/await}
</div>
{/if}
</PageContent>
+3 -12
View File
@@ -227,16 +227,11 @@
event.created_at - previousCreatedAt > int(3, MINUTE) ||
previousKind === RELAY_ADD_MEMBER
if (showPubkey && elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
elements.push({
id: event.id,
type: "note",
value: event,
showPubkey,
addSpaceBelow: false,
})
previousDate = date
@@ -245,9 +240,6 @@
previousCreatedAt = event.created_at
seen.add(event.id)
}
if (elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
}
elements.reverse()
@@ -318,13 +310,13 @@
{/snippet}
</SpaceBar>
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4 pb-0!">
<PageContent bind:element onscroll={onScroll} class="flex-col-reverse !mb-0">
{#if loadingForward}
<p class="py-20 flex justify-center">
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
</p>
{/if}
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
{#each elements as { type, id, value, showPubkey }, i (id)}
{#if type === "new-messages"}
<div
{id}
@@ -347,8 +339,7 @@
{replyTo}
{showPubkey}
canEdit={canEditEvent}
onEdit={onEditEvent}
{addSpaceBelow} />
onEdit={onEditEvent} />
{/if}
{/if}
{/each}
@@ -77,7 +77,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
<PageContent bind:element class="flex flex-col gap-2 p-2">
{#each items as event (event.id)}
<div in:fly>
<ClassifiedItem {url} event={$state.snapshot(event)} />
@@ -61,7 +61,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col p-2 pt-4">
<PageContent class="flex flex-col gap-2 p-2">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -90,7 +90,7 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end p-2">
<div class="flex justify-end">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon={Reply} />
Reply to listing
@@ -98,7 +98,7 @@
</div>
{/if}
{:else}
<div class="py-20 m-auto">
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading listing...</Spinner>
{:then}
+1 -1
View File
@@ -76,7 +76,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
<PageContent bind:element class="flex flex-col gap-2 p-2">
{#each items as event (event.id)}
<div in:fly>
<GoalItem {url} event={$state.snapshot(event)} />
@@ -62,7 +62,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col p-2 pt-4">
<PageContent class="flex flex-col gap-2 p-2">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -91,7 +91,7 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end p-2">
<div class="flex justify-end">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon={Reply} />
Comment on this goal
@@ -99,10 +99,12 @@
</div>
{/if}
{:else}
{#await sleep(5000)}
<Spinner loading>Loading funding goal...</Spinner>
{:then}
<p>Failed to load funding goal.</p>
{/await}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading funding goal...</Spinner>
{:then}
<p>Failed to load funding goal.</p>
{/await}
</div>
{/if}
</PageContent>
+1 -1
View File
@@ -76,7 +76,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
<PageContent bind:element class="flex flex-col gap-2 p-2">
{#each items as event (event.id)}
<div in:fly>
<PollItem {url} event={$state.snapshot(event)} />
@@ -63,7 +63,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-3 p-2 pt-4">
<PageContent class="flex flex-col gap-2 p-2">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -92,15 +92,17 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end p-2">
<div class="flex justify-end">
<Button class="btn btn-primary" onclick={openReply}>Comment on this poll</Button>
</div>
{/if}
{:else}
{#await sleep(5000)}
<Spinner loading>Loading poll...</Spinner>
{:then}
<p>Failed to load poll.</p>
{/await}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading poll...</Spinner>
{:then}
<p>Failed to load poll.</p>
{/await}
</div>
{/if}
</PageContent>
@@ -292,7 +292,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-2 p-2 pt-4" bind:element>
<PageContent class="flex flex-col gap-2 p-2" bind:element>
{#if $recentActivity.length === 0}
<p class="flex flex-col items-center py-20 text-center">No recent activity found!</p>
{:else}
@@ -77,7 +77,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
<PageContent bind:element class="flex flex-col gap-2 p-2">
{#each items as event (event.id)}
<div in:fly>
<ThreadItem {url} event={$state.snapshot(event)} />
@@ -61,7 +61,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col p-2 pt-4">
<PageContent class="flex flex-col gap-2 p-2">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -90,7 +90,7 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end p-2">
<div class="flex justify-end">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon={Reply} />
Reply to thread
@@ -98,10 +98,12 @@
</div>
{/if}
{:else}
{#await sleep(5000)}
<Spinner loading>Loading thread...</Spinner>
{:then}
<p>Failed to load thread.</p>
{/await}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading thread...</Spinner>
{:then}
<p>Failed to load thread.</p>
{/await}
</div>
{/if}
</PageContent>
+1 -1
View File
@@ -10,7 +10,7 @@
</script>
<Page>
<PageContent class="flex flex-col items-center gap-2 p-2 pt-4">
<PageContent class="flex flex-col items-center gap-2 p-2">
<PageHeader>
{#snippet title()}
<div>Choose your Hosting Plan</div>