Refactor avatar components, add space edit form
This commit is contained in:
@@ -2,21 +2,14 @@
|
|||||||
import {type Instance} from "tippy.js"
|
import {type Instance} from "tippy.js"
|
||||||
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {
|
import {thunks, mergeThunks, pubkey, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
||||||
thunks,
|
|
||||||
mergeThunks,
|
|
||||||
pubkey,
|
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
|
||||||
sendWrapped,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
@@ -37,7 +30,6 @@
|
|||||||
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props()
|
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props()
|
||||||
|
|
||||||
const isOwn = event.pubkey === $pubkey
|
const isOwn = event.pubkey === $pubkey
|
||||||
const profile = deriveProfile(event.pubkey)
|
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
||||||
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
||||||
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
||||||
@@ -107,8 +99,8 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{#if !isOwn}
|
{#if !isOwn}
|
||||||
<Button onclick={openProfile} class="flex items-center gap-1">
|
<Button onclick={openProfile} class="flex items-center gap-1">
|
||||||
<Avatar
|
<ProfileCircle
|
||||||
src={$profile?.picture}
|
pubkey={event.pubkey}
|
||||||
class="border border-solid border-base-content"
|
class="border border-solid border-base-content"
|
||||||
size={4} />
|
size={4} />
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import type {ProfilePointer} from "@welshman/content"
|
import type {ProfilePointer} from "@welshman/content"
|
||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
const {value, url}: Props = $props()
|
const {value, url}: Props = $props()
|
||||||
|
|
||||||
const display = deriveProfileDisplay(value.pubkey, removeNil([url]))
|
const display = deriveProfileDisplay(value.pubkey, removeUndefined([url]))
|
||||||
|
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import CardButton from "@lib/components/CardButton.svelte"
|
import CardButton from "@lib/components/CardButton.svelte"
|
||||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<Link replaceState href={path}>
|
<Link replaceState href={path}>
|
||||||
<CardButton class="btn-neutral shadow-md">
|
<CardButton class="btn-neutral shadow-md">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><SpaceAvatar {url} /></div>
|
<RelayIcon {url} size={12} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
|
|||||||
@@ -4,7 +4,15 @@
|
|||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {splitAt} from "@welshman/lib"
|
import {splitAt} from "@welshman/lib"
|
||||||
import {userProfile, shouldUnwrap} from "@welshman/app"
|
import {userProfile, shouldUnwrap} from "@welshman/app"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Widget from "@assets/icons/widget.svg?dataurl"
|
||||||
|
import Compass from "@assets/icons/compass.svg?dataurl"
|
||||||
|
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||||
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
|
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
||||||
|
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||||
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import Settings from "@assets/icons/settings.svg?dataurl"
|
||||||
|
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||||
import ChatEnable from "@app/components/ChatEnable.svelte"
|
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||||
@@ -15,13 +23,6 @@
|
|||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {notifications} from "@app/util/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
import Widget from "@assets/icons/widget.svg?dataurl"
|
|
||||||
import Compass from "@assets/icons/compass.svg?dataurl"
|
|
||||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
|
||||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
|
||||||
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
|
||||||
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
|
||||||
import Settings from "@assets/icons/settings.svg?dataurl"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: Snippet
|
children?: Snippet
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
<PrimaryNavItemSpace {url} />
|
<PrimaryNavItemSpace {url} />
|
||||||
{:else}
|
{:else}
|
||||||
<PrimaryNavItem title="Home" href="/home" class="tooltip-right">
|
<PrimaryNavItem title="Home" href="/home" class="tooltip-right">
|
||||||
<Avatar src={PLATFORM_LOGO} class="!h-10 !w-10" />
|
<ImageIcon alt="Home" src={PLATFORM_LOGO} class="rounded-full" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#each primarySpaceUrls as url (url)}
|
{#each primarySpaceUrls as url (url)}
|
||||||
@@ -73,11 +74,11 @@
|
|||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
onclick={showOtherSpacesMenu}
|
onclick={showOtherSpacesMenu}
|
||||||
notification={otherSpaceNotifications}>
|
notification={otherSpaceNotifications}>
|
||||||
<Avatar icon={Widget} class="!h-10 !w-10" />
|
<ImageIcon alt="Other Spaces" src={Widget} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
|
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
|
||||||
<Avatar icon={Compass} class="!h-10 !w-10" />
|
<ImageIcon alt="Add a Space" src={Compass} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -90,17 +91,17 @@
|
|||||||
href="/settings/profile"
|
href="/settings/profile"
|
||||||
prefix="/settings"
|
prefix="/settings"
|
||||||
class="tooltip-right">
|
class="tooltip-right">
|
||||||
<Avatar src={$userProfile?.picture} class="!h-10 !w-10" />
|
<ImageIcon alt="Settings" src={$userProfile?.picture || UserRounded} class="rounded-full" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Messages"
|
title="Messages"
|
||||||
onclick={openChat}
|
onclick={openChat}
|
||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon={Letter} class="!h-10 !w-10" />
|
<ImageIcon alt="Messages" src={Letter} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
||||||
<Avatar icon={Magnifier} class="!h-10 !w-10" />
|
<ImageIcon alt="Search" src={Magnifier} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,22 +116,26 @@
|
|||||||
<div class="content-padding-x content-sizing flex justify-between px-2">
|
<div class="content-padding-x content-sizing flex justify-between px-2">
|
||||||
<div class="flex gap-2 sm:gap-6">
|
<div class="flex gap-2 sm:gap-6">
|
||||||
<PrimaryNavItem title="Home" href="/home">
|
<PrimaryNavItem title="Home" href="/home">
|
||||||
<Avatar icon={HomeSmile} class="!h-10 !w-10" />
|
<ImageIcon alt="Home" src={HomeSmile} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Messages"
|
title="Messages"
|
||||||
onclick={openChat}
|
onclick={openChat}
|
||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon={Letter} class="!h-10 !w-10" />
|
<ImageIcon alt="Messages" src={Letter} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{#if PLATFORM_RELAYS.length !== 1}
|
{#if PLATFORM_RELAYS.length !== 1}
|
||||||
<PrimaryNavItem title="Spaces" href="/spaces" notification={anySpaceNotifications}>
|
<PrimaryNavItem title="Spaces" href="/spaces" notification={anySpaceNotifications}>
|
||||||
<Avatar icon={SettingsMinimalistic} class="!h-10 !w-10" />
|
<ImageIcon alt="Spaces" src={SettingsMinimalistic} size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
||||||
<Avatar icon={Settings} src={$userProfile?.picture} class="!h-10 !w-10" />
|
<ImageIcon
|
||||||
|
alt="Settings"
|
||||||
|
src={$userProfile?.picture || Settings}
|
||||||
|
size={7}
|
||||||
|
class="rounded-full" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||||
import {makeSpacePath, goToSpace} from "@app/util/routes"
|
import {makeSpacePath, goToSpace} from "@app/util/routes"
|
||||||
import {notifications} from "@app/util/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
@@ -15,5 +15,5 @@
|
|||||||
title={displayRelayUrl(url)}
|
title={displayRelayUrl(url)}
|
||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
notification={$notifications.has(makeSpacePath(url))}>
|
notification={$notifications.has(makeSpacePath(url))}>
|
||||||
<SpaceAvatar {url} />
|
<RelayIcon {url} size={10} class="rounded-full" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {displayPubkey} from "@welshman/util"
|
import {displayPubkey} from "@welshman/util"
|
||||||
import {
|
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
||||||
deriveHandleForPubkey,
|
|
||||||
displayHandle,
|
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import WotScore from "@app/components/WotScore.svelte"
|
import WotScore from "@app/components/WotScore.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
@@ -26,8 +21,7 @@
|
|||||||
|
|
||||||
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
|
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
|
||||||
|
|
||||||
const relays = removeNil([url])
|
const relays = removeUndefined([url])
|
||||||
const profile = deriveProfile(pubkey, relays)
|
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey, relays)
|
const profileDisplay = deriveProfileDisplay(pubkey, relays)
|
||||||
const handle = deriveHandleForPubkey(pubkey)
|
const handle = deriveHandleForPubkey(pubkey)
|
||||||
|
|
||||||
@@ -38,7 +32,7 @@
|
|||||||
|
|
||||||
<div class="flex max-w-full items-start gap-3">
|
<div class="flex max-w-full items-start gap-3">
|
||||||
<Button onclick={openProfile} class="py-1">
|
<Button onclick={openProfile} class="py-1">
|
||||||
<Avatar src={$profile?.picture} size={avatarSize} />
|
<ProfileCircle {pubkey} size={avatarSize} />
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex min-w-0 flex-col">
|
<div class="flex min-w-0 flex-col">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import cx from "classnames"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfile} from "@welshman/app"
|
import {deriveProfile} from "@welshman/app"
|
||||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
class?: string
|
||||||
|
size?: number
|
||||||
url?: string
|
url?: string
|
||||||
} & Record<string, any>
|
}
|
||||||
|
|
||||||
const {pubkey, url, ...props}: Props = $props()
|
const {pubkey, url, size = 7, ...props}: Props = $props()
|
||||||
|
|
||||||
const profile = deriveProfile(pubkey, removeNil([url]))
|
const profile = deriveProfile(pubkey, removeUndefined([url]))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Avatar src={$profile?.picture} icon={UserCircle} {...props} />
|
<ImageIcon
|
||||||
|
{size}
|
||||||
|
class={cx(props.class, "rounded-full")}
|
||||||
|
src={$profile?.picture || UserRounded}
|
||||||
|
alt="Profile picture" />
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
|
|
||||||
const {...props} = $props()
|
type Props = {
|
||||||
|
pubkeys: string[]
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const {pubkeys, size = 7}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex pr-3">
|
<div class="flex pr-3">
|
||||||
{#each props.pubkeys.toSorted().slice(0, 15) as pubkey (pubkey)}
|
{#each pubkeys.toSorted().slice(0, 15) as pubkey (pubkey)}
|
||||||
<div class="z-feature -mr-3 inline-block">
|
<div class="z-feature -mr-3 inline-block">
|
||||||
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {...props} />
|
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {size} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Link external href={pubkeyLink(pubkey)} class="btn btn-neutral">
|
<Link external href={pubkeyLink(pubkey)} class="btn btn-neutral">
|
||||||
<Avatar src="/coracle.png" />
|
<ImageIcon alt="Open in Coracle" src="/coracle.png" />
|
||||||
Open in Coracle
|
Open in Coracle
|
||||||
</Link>
|
</Link>
|
||||||
<Button onclick={openChat} class="btn btn-primary">
|
<Button onclick={openChat} class="btn btn-primary">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfile} from "@welshman/app"
|
import {deriveProfile} from "@welshman/app"
|
||||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url}: Props = $props()
|
||||||
|
|
||||||
const profile = deriveProfile(pubkey, removeNil([url]))
|
const profile = deriveProfile(pubkey, removeUndefined([url]))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $profile}
|
{#if $profile}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
const {pubkey, url}: Props = $props()
|
const {pubkey, url}: Props = $props()
|
||||||
|
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
|
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{$profileDisplay}
|
{$profileDisplay}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
import RelayIcon from "@app/components/RelayIcon.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {deriveGroupSelections, getSpaceUrlsFromGroupSelections} from "@app/core/state"
|
import {deriveGroupSelections, getSpaceUrlsFromGroupSelections} from "@app/core/state"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{#each spaceUrls as url (url)}
|
{#each spaceUrls as url (url)}
|
||||||
<div class="card2 bg-alt flex flex-row items-center gap-2">
|
<div class="card2 bg-alt flex flex-row items-center gap-2">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<SpaceAvatar {url} />
|
<RelayIcon {url} size={12} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-grow flex-col">
|
<div class="flex flex-grow flex-col">
|
||||||
<RelayName {url} />
|
<RelayName {url} />
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {deriveRelay} from "@welshman/app"
|
||||||
|
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
||||||
|
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
size?: number
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, size = 7, ...props}: Props = $props()
|
||||||
|
|
||||||
|
const relay = deriveRelay(url)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ImageIcon
|
||||||
|
{size}
|
||||||
|
src={$relay?.icon || RemoteControllerMinimalistic}
|
||||||
|
alt="Relay image"
|
||||||
|
class={props.class} />
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {deriveRelayDisplay} from "@welshman/app"
|
import {deriveRelayDisplay} from "@welshman/app"
|
||||||
|
|
||||||
const {url} = $props()
|
type Props = {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url}: Props = $props()
|
||||||
|
|
||||||
const display = $derived(deriveRelayDisplay(url))
|
const display = $derived(deriveRelayDisplay(url))
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
thunks,
|
thunks,
|
||||||
pubkey,
|
pubkey,
|
||||||
mergeThunks,
|
mergeThunks,
|
||||||
deriveProfile,
|
|
||||||
deriveProfileDisplay,
|
deriveProfileDisplay,
|
||||||
displayProfileByPubkey,
|
displayProfileByPubkey,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
@@ -16,12 +15,12 @@
|
|||||||
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
import ReplyAlt from "@assets/icons/reply.svg?dataurl"
|
import ReplyAlt from "@assets/icons/reply.svg?dataurl"
|
||||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import RoomItemZapButton from "@app/components/RoomItemZapButton.svelte"
|
import RoomItemZapButton from "@app/components/RoomItemZapButton.svelte"
|
||||||
import RoomItemEmojiButton from "@app/components/RoomItemEmojiButton.svelte"
|
import RoomItemEmojiButton from "@app/components/RoomItemEmojiButton.svelte"
|
||||||
@@ -56,7 +55,6 @@
|
|||||||
const path = getRoomItemPath(url, event)
|
const path = getRoomItemPath(url, event)
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
const today = formatTimestampAsDate(now())
|
const today = formatTimestampAsDate(now())
|
||||||
const profile = deriveProfile(event.pubkey, [url])
|
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
||||||
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
|
||||||
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
|
||||||
@@ -83,7 +81,10 @@
|
|||||||
<div class="flex w-full gap-3 overflow-auto">
|
<div class="flex w-full gap-3 overflow-auto">
|
||||||
{#if showPubkey}
|
{#if showPubkey}
|
||||||
<Button onclick={openProfile} class="flex items-start">
|
<Button onclick={openProfile} class="flex items-start">
|
||||||
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={8} />
|
<ProfileCircle
|
||||||
|
pubkey={event.pubkey}
|
||||||
|
class="border border-solid border-base-content"
|
||||||
|
size={8} />
|
||||||
</Button>
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="w-8 min-w-8 max-w-8"></div>
|
<div class="w-8 min-w-8 max-w-8"></div>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {displayRelayUrl} from "@welshman/util"
|
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
|
||||||
import {deriveRelay} from "@welshman/app"
|
|
||||||
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
url?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const {url = ""}: Props = $props()
|
|
||||||
|
|
||||||
const relay = deriveRelay(url)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Avatar
|
|
||||||
icon={RemoteControllerMinimalistic}
|
|
||||||
class="!h-10 !w-10"
|
|
||||||
alt={displayRelayUrl(url)}
|
|
||||||
src={$relay?.icon} />
|
|
||||||
@@ -2,16 +2,22 @@
|
|||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import {deriveRelay} from "@welshman/app"
|
import {deriveRelay} from "@welshman/app"
|
||||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import Pen from "@assets/icons/pen.svg?dataurl"
|
||||||
import ShieldUser from "@assets/icons/shield-user.svg?dataurl"
|
import ShieldUser from "@assets/icons/shield-user.svg?dataurl"
|
||||||
import BillList from "@assets/icons/bill-list.svg?dataurl"
|
import BillList from "@assets/icons/bill-list.svg?dataurl"
|
||||||
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
|
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
|
import SpaceEdit from "@app/components/SpaceEdit.svelte"
|
||||||
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
|
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
|
||||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||||
import ProfileLatest from "@app/components/ProfileLatest.svelte"
|
import ProfileLatest from "@app/components/ProfileLatest.svelte"
|
||||||
|
import {deriveUserIsSpaceAdmin} from "@app/core/state"
|
||||||
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -20,8 +26,11 @@
|
|||||||
const {url}: Props = $props()
|
const {url}: Props = $props()
|
||||||
const relay = deriveRelay(url)
|
const relay = deriveRelay(url)
|
||||||
const owner = $derived($relay?.pubkey)
|
const owner = $derived($relay?.pubkey)
|
||||||
|
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const startEdit = () => pushModal(SpaceEdit, {url, initialValues: $relay})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
@@ -78,5 +87,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
{#if $userIsAdmin}
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon={AltArrowLeft} />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
<Button class="btn btn-primary" onclick={startEdit}>
|
||||||
|
<Icon icon={Pen} />
|
||||||
|
Edit Space
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
{:else}
|
||||||
|
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {uniqBy, prop, append, ifLet} from "@welshman/lib"
|
||||||
|
import type {RelayProfile} from "@welshman/util"
|
||||||
|
import {displayRelayUrl, ManagementMethod} from "@welshman/util"
|
||||||
|
import {manageRelay, relays, fetchRelayProfileDirectly} from "@welshman/app"
|
||||||
|
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
|
||||||
|
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import UploadMinimalistic from "@assets/icons/upload-minimalistic.svg?dataurl"
|
||||||
|
import {preventDefault} from "@lib/html"
|
||||||
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||||
|
import IconPickerButton from "@lib/components/IconPickerButton.svelte"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
import {clearModals} from "@app/util/modal"
|
||||||
|
import {uploadFile} from "@app/core/commands"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
initialValues: RelayProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
const values = $state(initialValues)
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (values.name != initialValues.name) {
|
||||||
|
const res = await manageRelay(url, {
|
||||||
|
method: ManagementMethod.ChangeRelayName,
|
||||||
|
params: [values.name || ""],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
return pushToast({theme: "error", message: res.error})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.description != initialValues.description) {
|
||||||
|
const res = await manageRelay(url, {
|
||||||
|
method: ManagementMethod.ChangeRelayDescription,
|
||||||
|
params: [values.description || ""],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
return pushToast({theme: "error", message: res.error})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageFile) {
|
||||||
|
const {error, result} = await uploadFile(imageFile, {maxWidth: 128, maxHeight: 128})
|
||||||
|
|
||||||
|
console.log(imageFile, result)
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return pushToast({theme: "error", message: error})
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await manageRelay(url, {
|
||||||
|
method: ManagementMethod.ChangeRelayIcon,
|
||||||
|
params: [result.url],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
return pushToast({theme: "error", message: res.error})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force-reload the relay
|
||||||
|
ifLet(await fetchRelayProfileDirectly(url), relay => {
|
||||||
|
relays.update($relays => uniqBy(prop("url"), append(relay, $relays)))
|
||||||
|
})
|
||||||
|
|
||||||
|
pushToast({message: "Your changes have been saved!"})
|
||||||
|
clearModals()
|
||||||
|
}
|
||||||
|
|
||||||
|
const trySubmit = async () => {
|
||||||
|
loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await submit()
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let loading = $state(false)
|
||||||
|
let imageFile = $state<File | undefined>()
|
||||||
|
let imagePreview = $state(initialValues.icon)
|
||||||
|
|
||||||
|
const handleImageUpload = async (event: Event) => {
|
||||||
|
const file = (event.target as HTMLInputElement).files?.[0]
|
||||||
|
|
||||||
|
if (file && file.type.startsWith("image/")) {
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.onload = e => {
|
||||||
|
imageFile = file
|
||||||
|
imagePreview = e.target?.result as string
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleIconSelect = (iconUrl: string) => {
|
||||||
|
imagePreview = iconUrl
|
||||||
|
|
||||||
|
const parts = iconUrl.split(",")
|
||||||
|
const imageData = atob(parts[1])
|
||||||
|
const result = new Uint8Array(imageData.length)
|
||||||
|
|
||||||
|
for (let n = 0; n < imageData.length; n++) {
|
||||||
|
result[n] = imageData.charCodeAt(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageFile = new File([result], `icon.svg`, {type: "image/svg+xml"})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="column gap-4" onsubmit={preventDefault(trySubmit)}>
|
||||||
|
<ModalHeader>
|
||||||
|
{#snippet title()}
|
||||||
|
<div>Edit a Space</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
<span class="text-primary">{displayRelayUrl(url)}</span>
|
||||||
|
{/snippet}
|
||||||
|
</ModalHeader>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Icon</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
{#if imagePreview}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm opacity-75">Selected:</span>
|
||||||
|
<ImageIcon src={imagePreview} alt="Room icon preview" />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<span class="text-sm opacity-75">No icon selected</span>
|
||||||
|
{/if}
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<IconPickerButton onSelect={handleIconSelect} class="btn btn-primary btn-sm">
|
||||||
|
<Icon icon={StickerSmileSquare} size={4} />
|
||||||
|
Select
|
||||||
|
</IconPickerButton>
|
||||||
|
<label class="btn btn-neutral btn-sm cursor-pointer">
|
||||||
|
<Icon icon={UploadMinimalistic} size={4} />
|
||||||
|
Upload
|
||||||
|
<input type="file" accept="image/*" class="hidden" onchange={handleImageUpload} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Name</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
|
{#if imagePreview}
|
||||||
|
<ImageIcon src={imagePreview} alt="Room icon preview" />
|
||||||
|
{:else}
|
||||||
|
<Icon icon={SettingsMinimalistic} />
|
||||||
|
{/if}
|
||||||
|
<input bind:value={values.name} class="grow" type="text" />
|
||||||
|
</label>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Description</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
|
<input bind:value={values.description} class="grow" type="text" />
|
||||||
|
</label>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon={AltArrowLeft} />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
|
<Spinner {loading}>Save Changes</Spinner>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
class="col-2 card2 bg-alt w-full cursor-pointer shadow-md"
|
class="col-2 card2 bg-alt w-full cursor-pointer shadow-xl"
|
||||||
href={makeThreadPath(url, event.id)}>
|
href={makeThreadPath(url, event.id)}>
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="flex w-full items-center justify-between gap-2">
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -660,6 +660,8 @@ export const getBlossomServer = async (options: GetBlossomServerOptions = {}) =>
|
|||||||
export type UploadFileOptions = {
|
export type UploadFileOptions = {
|
||||||
url?: string
|
url?: string
|
||||||
encrypt?: boolean
|
encrypt?: boolean
|
||||||
|
maxWidth?: number
|
||||||
|
maxHeight?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UploadFileResult = {
|
export type UploadFileResult = {
|
||||||
@@ -671,8 +673,8 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
|
|||||||
try {
|
try {
|
||||||
const {name, type} = file
|
const {name, type} = file
|
||||||
|
|
||||||
if (!type.match("image/(webp|gif)")) {
|
if (!type.match("image/(webp|gif|svg)")) {
|
||||||
file = await compressFile(file)
|
file = await compressFile(file, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags: string[][] = []
|
const tags: string[][] = []
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
sortBy,
|
sortBy,
|
||||||
now,
|
now,
|
||||||
on,
|
on,
|
||||||
isNotNil,
|
isDefined,
|
||||||
filterVals,
|
filterVals,
|
||||||
fromPairs,
|
fromPairs,
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
@@ -279,6 +279,6 @@ export const requestRelayClaim = async (url: string) => {
|
|||||||
|
|
||||||
export const requestRelayClaims = async (urls: string[]) =>
|
export const requestRelayClaims = async (urls: string[]) =>
|
||||||
filterVals(
|
filterVals(
|
||||||
isNotNil,
|
isDefined,
|
||||||
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
|
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -730,7 +730,7 @@ export const deriveSpaceMembers = (url: string) =>
|
|||||||
return getTagValues("member", membersEvent.tags)
|
return getTagValues("member", membersEvent.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = new Set()
|
const members = new Set<string>()
|
||||||
|
|
||||||
for (const event of sortBy(e => e.created_at, $events)) {
|
for (const event of sortBy(e => e.created_at, $events)) {
|
||||||
const pubkeys = getPubkeyTagValues(event.tags)
|
const pubkeys = getPubkeyTagValues(event.tags)
|
||||||
@@ -765,7 +765,7 @@ export const deriveRoomMembers = (url: string, h: string) =>
|
|||||||
return getPubkeyTagValues(membersEvent.tags)
|
return getPubkeyTagValues(membersEvent.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = new Set()
|
const members = new Set<string>()
|
||||||
|
|
||||||
for (const event of sortBy(e => -e.created_at, $events)) {
|
for (const event of sortBy(e => -e.created_at, $events)) {
|
||||||
const pubkeys = getPubkeyTagValues(event.tags)
|
const pubkeys = getPubkeyTagValues(event.tags)
|
||||||
@@ -825,7 +825,7 @@ export const deriveUserSpaceMembershipStatus = (url: string) =>
|
|||||||
deriveUserIsSpaceAdmin(url),
|
deriveUserIsSpaceAdmin(url),
|
||||||
],
|
],
|
||||||
([$pubkey, $members, $events, $isAdmin]) => {
|
([$pubkey, $members, $events, $isAdmin]) => {
|
||||||
const isMember = $members.includes($pubkey) || $isAdmin
|
const isMember = $members.includes($pubkey!) || $isAdmin
|
||||||
|
|
||||||
for (const event of $events) {
|
for (const event of $events) {
|
||||||
if (event.pubkey !== $pubkey) {
|
if (event.pubkey !== $pubkey) {
|
||||||
@@ -860,7 +860,7 @@ export const deriveUserRoomMembershipStatus = (url: string, h: string) =>
|
|||||||
deriveUserIsRoomAdmin(url, h),
|
deriveUserIsRoomAdmin(url, h),
|
||||||
],
|
],
|
||||||
([$pubkey, $members, $events, $isAdmin]) => {
|
([$pubkey, $members, $events, $isAdmin]) => {
|
||||||
const isMember = $members.includes($pubkey) || $isAdmin
|
const isMember = $members.includes($pubkey!) || $isAdmin
|
||||||
|
|
||||||
for (const event of $events) {
|
for (const event of $events) {
|
||||||
if (event.pubkey !== $pubkey) {
|
if (event.pubkey !== $pubkey) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type {NodeViewProps} from "@tiptap/core"
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
|
|
||||||
export const makeMentionNodeView =
|
export const makeMentionNodeView =
|
||||||
(url?: string) =>
|
(url?: string) =>
|
||||||
({node}: NodeViewProps) => {
|
({node}: NodeViewProps) => {
|
||||||
const dom = document.createElement("span")
|
const dom = document.createElement("span")
|
||||||
const display = deriveProfileDisplay(node.attrs.pubkey, removeNil([url]))
|
const display = deriveProfileDisplay(node.attrs.pubkey, removeUndefined([url]))
|
||||||
|
|
||||||
dom.classList.add("tiptap-object")
|
dom.classList.add("tiptap-object")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {removeNil} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {displayPubkey} from "@welshman/util"
|
import {displayPubkey} from "@welshman/util"
|
||||||
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
|
||||||
import WotScore from "@app/components/WotScore.svelte"
|
import WotScore from "@app/components/WotScore.svelte"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
const {value, url}: Props = $props()
|
const {value, url}: Props = $props()
|
||||||
|
|
||||||
const pubkey = value
|
const pubkey = value
|
||||||
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
|
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
|
||||||
const handle = deriveHandleForPubkey(pubkey)
|
const handle = deriveHandleForPubkey(pubkey)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
||||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||||
import {
|
import {PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO, SIGNER_RELAYS} from "@app/core/state"
|
||||||
NIP46_PERMS,
|
|
||||||
PLATFORM_URL,
|
|
||||||
PLATFORM_NAME,
|
|
||||||
PLATFORM_LOGO,
|
|
||||||
SIGNER_RELAYS,
|
|
||||||
} from "@app/core/state"
|
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
export class Nip46Controller {
|
export class Nip46Controller {
|
||||||
@@ -25,7 +19,6 @@ export class Nip46Controller {
|
|||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
const url = await this.broker.makeNostrconnectUrl({
|
const url = await this.broker.makeNostrconnectUrl({
|
||||||
perms: NIP46_PERMS,
|
|
||||||
url: PLATFORM_URL,
|
url: PLATFORM_URL,
|
||||||
name: PLATFORM_NAME,
|
name: PLATFORM_NAME,
|
||||||
image: PLATFORM_LOGO,
|
image: PLATFORM_LOGO,
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {onMount} from "svelte"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
|
||||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
|
||||||
|
|
||||||
const {src = "", size = 7, icon = UserRounded, style = "", ...restProps} = $props()
|
|
||||||
|
|
||||||
let element: HTMLElement
|
|
||||||
|
|
||||||
const rem = $derived(size * 4)
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (src) {
|
|
||||||
const image = new Image()
|
|
||||||
|
|
||||||
image.addEventListener("error", () => {
|
|
||||||
element?.querySelector(".hidden")?.classList.remove("hidden")
|
|
||||||
})
|
|
||||||
|
|
||||||
image.src = src
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={element}
|
|
||||||
class="{restProps.class} relative !flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-cover bg-center"
|
|
||||||
style="width: {rem}px; height: {rem}px; min-width: {rem}px; background-image: url({src}); {style}">
|
|
||||||
<Icon {icon} class={src ? "hidden" : ""} size={Math.round(size * 0.8)} />
|
|
||||||
</div>
|
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="btn flex h-[unset] w-full flex-nowrap py-4 text-left {props.class}">
|
<div class="btn flex h-[unset] w-full flex-nowrap py-4 text-left {props.class}">
|
||||||
<div class="flex flex-grow flex-row items-start gap-1 sm:pl-2">
|
<div class="flex flex-grow flex-row items-start gap-4">
|
||||||
<div class="flex h-14 w-12 flex-shrink-0 items-center">
|
<div class="flex h-14 w-12 flex-shrink-0 items-center justify-center">
|
||||||
{@render props.icon?.()}
|
{@render props.icon?.()}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
src: string
|
src: string
|
||||||
alt: string
|
alt: string
|
||||||
size?: number
|
size?: number
|
||||||
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const {src, alt, size = 5}: Props = $props()
|
const {src, alt, size = 5, ...props}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if src.includes("image/svg") || src.endsWith(".svg")}
|
{#if src.includes("image/svg") || src.endsWith(".svg")}
|
||||||
<Icon icon={src} {size} />
|
<Icon icon={src} {size} class={props.class} />
|
||||||
{:else}
|
{:else}
|
||||||
<img {src} {alt} class="h-{size} w-{size} rounded-lg object-cover" />
|
<img {src} {alt} class="h-{size} w-{size} aspect-square object-cover {props.class}" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{#if href}
|
{#if href}
|
||||||
<a {href} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
<a {href} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
||||||
<div
|
<div
|
||||||
class="avatar cursor-pointer rounded-full p-1 {restProps.class} transition-colors hover:bg-base-300"
|
class="avatar cursor-pointer rounded-full p-2 {restProps.class} transition-colors hover:bg-base-300"
|
||||||
class:bg-base-300={active}
|
class:bg-base-300={active}
|
||||||
class:tooltip={title}
|
class:tooltip={title}
|
||||||
data-tip={title}>
|
data-tip={title}>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<Button {onclick} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
<Button {onclick} class="relative z-nav-item flex h-14 w-14 items-center justify-center">
|
||||||
<div
|
<div
|
||||||
class="avatar cursor-pointer rounded-full p-1 {restProps.class} transition-colors hover:bg-base-300"
|
class="avatar cursor-pointer rounded-full p-2 {restProps.class} transition-colors hover:bg-base-300"
|
||||||
class:bg-base-300={active}
|
class:bg-base-300={active}
|
||||||
class:tooltip={title}
|
class:tooltip={title}
|
||||||
data-tip={title}>
|
data-tip={title}>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<Button onclick={addSpace}>
|
<Button onclick={addSpace}>
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon={AddCircle} size={7} /></div>
|
<Icon icon={AddCircle} size={7} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Add a space</div>
|
<div>Add a space</div>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<Link href="/discover">
|
<Link href="/discover">
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon={Compass} size={7} /></div>
|
<Icon icon={Compass} size={7} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Browse the network</div>
|
<div>Browse the network</div>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<Button onclick={openChat}>
|
<Button onclick={openChat}>
|
||||||
<CardButton class="btn-neutral">
|
<CardButton class="btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon={ChatRound} size={7} /></div>
|
<Icon icon={ChatRound} size={7} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Start a conversation</div>
|
<div>Start a conversation</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||||
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
||||||
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
import ProfileDelete from "@app/components/ProfileDelete.svelte"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<div class="flex justify-between gap-2">
|
<div class="flex justify-between gap-2">
|
||||||
<div class="flex max-w-full gap-3">
|
<div class="flex max-w-full gap-3">
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
<Avatar src={$profile?.picture} size={10} />
|
<ProfileCircle pubkey={$pubkey!} size={10} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex min-w-0 flex-col">
|
<div class="flex min-w-0 flex-col">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -85,10 +85,10 @@
|
|||||||
|
|
||||||
if (message && !message.startsWith("duplicate:")) {
|
if (message && !message.startsWith("duplicate:")) {
|
||||||
return pushToast({theme: "error", message})
|
return pushToast({theme: "error", message})
|
||||||
} else {
|
|
||||||
// Restart the feed now that we're a member
|
|
||||||
start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restart the feed now that we're a member
|
||||||
|
start()
|
||||||
} finally {
|
} finally {
|
||||||
joining = false
|
joining = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user