Refactor avatar components, add space edit form

This commit is contained in:
Jon Staab
2025-11-11 13:39:32 -08:00
parent 183aebf841
commit 8e411daaef
32 changed files with 356 additions and 157 deletions
+4 -12
View File
@@ -2,21 +2,14 @@
import {type Instance} from "tippy.js"
import {hash, formatTimestampAsTime} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {
thunks,
mergeThunks,
pubkey,
deriveProfile,
deriveProfileDisplay,
sendWrapped,
} from "@welshman/app"
import {thunks, mergeThunks, pubkey, deriveProfileDisplay, sendWrapped} from "@welshman/app"
import {isMobile} from "@lib/html"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Tippy from "@lib/components/Tippy.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 ReactionSummary from "@app/components/ReactionSummary.svelte"
import ThunkFailure from "@app/components/ThunkFailure.svelte"
@@ -37,7 +30,6 @@
const {event, replyTo, pubkeys, showPubkey = false}: Props = $props()
const isOwn = event.pubkey === $pubkey
const profile = deriveProfile(event.pubkey)
const profileDisplay = deriveProfileDisplay(event.pubkey)
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
@@ -107,8 +99,8 @@
<div class="flex items-center gap-2">
{#if !isOwn}
<Button onclick={openProfile} class="flex items-center gap-1">
<Avatar
src={$profile?.picture}
<ProfileCircle
pubkey={event.pubkey}
class="border border-solid border-base-content"
size={4} />
<div class="flex items-center gap-2">
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import {removeNil} from "@welshman/lib"
import {removeUndefined} from "@welshman/lib"
import type {ProfilePointer} from "@welshman/content"
import {deriveProfileDisplay} from "@welshman/app"
import Button from "@lib/components/Button.svelte"
@@ -13,7 +13,7 @@
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})
</script>
+2 -2
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import Link from "@lib/components/Link.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 RelayDescription from "@app/components/RelayDescription.svelte"
import {makeSpacePath} from "@app/util/routes"
@@ -15,7 +15,7 @@
<Link replaceState href={path}>
<CardButton class="btn-neutral shadow-md">
{#snippet icon()}
<div><SpaceAvatar {url} /></div>
<RelayIcon {url} size={12} />
{/snippet}
{#snippet title()}
<div class="flex gap-1">
+23 -18
View File
@@ -4,7 +4,15 @@
import {goto} from "$app/navigation"
import {splitAt} from "@welshman/lib"
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 PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
import ChatEnable from "@app/components/ChatEnable.svelte"
@@ -15,13 +23,6 @@
import {pushModal} from "@app/util/modal"
import {makeSpacePath} from "@app/util/routes"
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 = {
children?: Snippet
@@ -61,7 +62,7 @@
<PrimaryNavItemSpace {url} />
{:else}
<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>
<Divider />
{#each primarySpaceUrls as url (url)}
@@ -73,11 +74,11 @@
class="tooltip-right"
onclick={showOtherSpacesMenu}
notification={otherSpaceNotifications}>
<Avatar icon={Widget} class="!h-10 !w-10" />
<ImageIcon alt="Other Spaces" src={Widget} />
</PrimaryNavItem>
{/if}
<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>
{/each}
</div>
@@ -90,17 +91,17 @@
href="/settings/profile"
prefix="/settings"
class="tooltip-right">
<Avatar src={$userProfile?.picture} class="!h-10 !w-10" />
<ImageIcon alt="Settings" src={$userProfile?.picture || UserRounded} class="rounded-full" />
</PrimaryNavItem>
<PrimaryNavItem
title="Messages"
onclick={openChat}
class="tooltip-right"
notification={$notifications.has("/chat")}>
<Avatar icon={Letter} class="!h-10 !w-10" />
<ImageIcon alt="Messages" src={Letter} size={7} />
</PrimaryNavItem>
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
<Avatar icon={Magnifier} class="!h-10 !w-10" />
<ImageIcon alt="Search" src={Magnifier} size={7} />
</PrimaryNavItem>
</div>
</div>
@@ -115,22 +116,26 @@
<div class="content-padding-x content-sizing flex justify-between px-2">
<div class="flex gap-2 sm:gap-6">
<PrimaryNavItem title="Home" href="/home">
<Avatar icon={HomeSmile} class="!h-10 !w-10" />
<ImageIcon alt="Home" src={HomeSmile} size={7} />
</PrimaryNavItem>
<PrimaryNavItem
title="Messages"
onclick={openChat}
notification={$notifications.has("/chat")}>
<Avatar icon={Letter} class="!h-10 !w-10" />
<ImageIcon alt="Messages" src={Letter} size={7} />
</PrimaryNavItem>
{#if PLATFORM_RELAYS.length !== 1}
<PrimaryNavItem title="Spaces" href="/spaces" notification={anySpaceNotifications}>
<Avatar icon={SettingsMinimalistic} class="!h-10 !w-10" />
<ImageIcon alt="Spaces" src={SettingsMinimalistic} size={7} />
</PrimaryNavItem>
{/if}
</div>
<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>
</div>
</div>
@@ -1,7 +1,7 @@
<script lang="ts">
import {displayRelayUrl} from "@welshman/util"
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 {notifications} from "@app/util/notifications"
@@ -15,5 +15,5 @@
title={displayRelayUrl(url)}
class="tooltip-right"
notification={$notifications.has(makeSpacePath(url))}>
<SpaceAvatar {url} />
<RelayIcon {url} size={10} class="rounded-full" />
</PrimaryNavItem>
+5 -11
View File
@@ -1,16 +1,11 @@
<script lang="ts">
import * as nip19 from "nostr-tools/nip19"
import {removeNil} from "@welshman/lib"
import {removeUndefined} from "@welshman/lib"
import {displayPubkey} from "@welshman/util"
import {
deriveHandleForPubkey,
displayHandle,
deriveProfile,
deriveProfileDisplay,
} from "@welshman/app"
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app"
import Icon from "@lib/components/Icon.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 ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/util/modal"
@@ -26,8 +21,7 @@
const {pubkey, url, showPubkey, avatarSize = 10}: Props = $props()
const relays = removeNil([url])
const profile = deriveProfile(pubkey, relays)
const relays = removeUndefined([url])
const profileDisplay = deriveProfileDisplay(pubkey, relays)
const handle = deriveHandleForPubkey(pubkey)
@@ -38,7 +32,7 @@
<div class="flex max-w-full items-start gap-3">
<Button onclick={openProfile} class="py-1">
<Avatar src={$profile?.picture} size={avatarSize} />
<ProfileCircle {pubkey} size={avatarSize} />
</Button>
<div class="flex min-w-0 flex-col">
<div class="flex items-center gap-2">
+14 -7
View File
@@ -1,17 +1,24 @@
<script lang="ts">
import Avatar from "@lib/components/Avatar.svelte"
import {removeNil} from "@welshman/lib"
import cx from "classnames"
import {removeUndefined} from "@welshman/lib"
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 = {
pubkey: string
class?: string
size?: number
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>
<Avatar src={$profile?.picture} icon={UserCircle} {...props} />
<ImageIcon
{size}
class={cx(props.class, "rounded-full")}
src={$profile?.picture || UserRounded}
alt="Profile picture" />
+8 -3
View File
@@ -1,13 +1,18 @@
<script lang="ts">
import ProfileCircle from "@app/components/ProfileCircle.svelte"
const {...props} = $props()
type Props = {
pubkeys: string[]
size?: number
}
const {pubkeys, size = 7}: Props = $props()
</script>
<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">
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {...props} />
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {size} />
</div>
{/each}
</div>
+2 -2
View File
@@ -4,7 +4,7 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Letter from "@assets/icons/letter-opened.svg?dataurl"
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 Button from "@lib/components/Button.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -41,7 +41,7 @@
</Button>
<div class="flex gap-2">
<Link external href={pubkeyLink(pubkey)} class="btn btn-neutral">
<Avatar src="/coracle.png" />
<ImageIcon alt="Open in Coracle" src="/coracle.png" />
Open in Coracle
</Link>
<Button onclick={openChat} class="btn btn-primary">
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import {removeNil} from "@welshman/lib"
import {removeUndefined} from "@welshman/lib"
import {deriveProfile} from "@welshman/app"
import ContentMinimal from "@app/components/ContentMinimal.svelte"
@@ -10,7 +10,7 @@
const {pubkey, url}: Props = $props()
const profile = deriveProfile(pubkey, removeNil([url]))
const profile = deriveProfile(pubkey, removeUndefined([url]))
</script>
{#if $profile}
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import {removeNil} from "@welshman/lib"
import {removeUndefined} from "@welshman/lib"
import {deriveProfileDisplay} from "@welshman/app"
type Props = {
@@ -9,7 +9,7 @@
const {pubkey, url}: Props = $props()
const profileDisplay = deriveProfileDisplay(pubkey, removeNil([url]))
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url]))
</script>
{$profileDisplay}
+2 -2
View File
@@ -5,7 +5,7 @@
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.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 {makeSpacePath} from "@app/util/routes"
import {deriveGroupSelections, getSpaceUrlsFromGroupSelections} from "@app/core/state"
@@ -26,7 +26,7 @@
{#each spaceUrls as url (url)}
<div class="card2 bg-alt flex flex-row items-center gap-2">
<div class="flex-shrink-0">
<SpaceAvatar {url} />
<RelayIcon {url} size={12} />
</div>
<div class="flex flex-grow flex-col">
<RelayName {url} />
+21
View File
@@ -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} />
+5 -1
View File
@@ -1,7 +1,11 @@
<script lang="ts">
import {deriveRelayDisplay} from "@welshman/app"
const {url} = $props()
type Props = {
url: string
}
const {url}: Props = $props()
const display = $derived(deriveRelayDisplay(url))
</script>
+5 -4
View File
@@ -7,7 +7,6 @@
thunks,
pubkey,
mergeThunks,
deriveProfile,
deriveProfileDisplay,
displayProfileByPubkey,
} from "@welshman/app"
@@ -16,12 +15,12 @@
import Reply from "@assets/icons/reply-2.svg?dataurl"
import ReplyAlt from "@assets/icons/reply.svg?dataurl"
import TapTarget from "@lib/components/TapTarget.svelte"
import Avatar from "@lib/components/Avatar.svelte"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import ThunkFailure from "@app/components/ThunkFailure.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import RoomItemZapButton from "@app/components/RoomItemZapButton.svelte"
import RoomItemEmojiButton from "@app/components/RoomItemEmojiButton.svelte"
@@ -56,7 +55,6 @@
const path = getRoomItemPath(url, event)
const shouldProtect = canEnforceNip70(url)
const today = formatTimestampAsDate(now())
const profile = deriveProfile(event.pubkey, [url])
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
@@ -83,7 +81,10 @@
<div class="flex w-full gap-3 overflow-auto">
{#if showPubkey}
<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>
{:else}
<div class="w-8 min-w-8 max-w-8"></div>
-20
View File
@@ -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} />
+23 -1
View File
@@ -2,16 +2,22 @@
import {displayRelayUrl} from "@welshman/util"
import {deriveRelay} from "@welshman/app"
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 BillList from "@assets/icons/bill-list.svg?dataurl"
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Button from "@lib/components/Button.svelte"
import RelayName from "@app/components/RelayName.svelte"
import SpaceEdit from "@app/components/SpaceEdit.svelte"
import SpaceRelayStatus from "@app/components/SpaceRelayStatus.svelte"
import RelayDescription from "@app/components/RelayDescription.svelte"
import ProfileLatest from "@app/components/ProfileLatest.svelte"
import {deriveUserIsSpaceAdmin} from "@app/core/state"
import {pushModal} from "@app/util/modal"
type Props = {
url: string
@@ -20,8 +26,11 @@
const {url}: Props = $props()
const relay = deriveRelay(url)
const owner = $derived($relay?.pubkey)
const userIsAdmin = deriveUserIsSpaceAdmin(url)
const back = () => history.back()
const startEdit = () => pushModal(SpaceEdit, {url, initialValues: $relay})
</script>
<div class="column gap-4">
@@ -78,5 +87,18 @@
</div>
{/if}
</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>
+202
View File
@@ -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>
+1 -1
View File
@@ -21,7 +21,7 @@
</script>
<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)}>
{#if title}
<div class="flex w-full items-center justify-between gap-2">