Fix multiple choice poll selections not saving, bring back base64 profile image fallback but compressed

This commit is contained in:
Jon Staab 2026-07-02 14:39:21 -07:00
parent e575be921d
commit 27252f543a
4 changed files with 77 additions and 22 deletions

View file

@ -7,7 +7,6 @@
import Icon from "@lib/components/Icon.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import {uploadFile} from "@app/uploads"
import {pushToast} from "@app/toast"
interface Props {
file?: File | undefined
@ -18,6 +17,10 @@
const id = randomId()
// Profile pictures are shown small (avatar-sized), so compress hard: this keeps
// the base64 fallback below small enough to embed directly in the profile event.
const compressOptions = {maxWidth: 128, maxHeight: 128, quality: 0.6, mimeType: "image/jpeg"}
const onDragEnter = () => {
active = true
}
@ -33,11 +36,11 @@
const onDrop = async (e: any) => {
active = false
file = await compressFile(e.dataTransfer.files[0])
file = await compressFile(e.dataTransfer.files[0], compressOptions)
}
const onChange = async (e: any) => {
file = await compressFile(e.target.files[0])
file = await compressFile(e.target.files[0], compressOptions)
}
const onClear = () => {
@ -55,15 +58,24 @@
if (file) {
loading = true
const {error, result} = await uploadFile(file)
const {result} = await uploadFile(file)
loading = false
if (result?.url) {
url = result.url
} else {
file = undefined
pushToast({theme: "error", message: error || "Failed to upload image."})
const reader = new FileReader()
reader.addEventListener(
"load",
() => {
url = reader.result as string
},
false,
)
reader.readAsDataURL(file)
}
} else {
url = initialUrl
@ -73,7 +85,12 @@
</script>
<form>
<input {id} type="file" accept="image/*" onchange={onChange} class="hidden" />
<input
{id}
type="file"
accept="image/jpeg,image/png,image/webp"
onchange={onChange}
class="hidden" />
<label
for={id}
aria-label="Drag and drop files here."
@ -85,7 +102,7 @@
ondragleave={stopPropagation(preventDefault(onDragLeave))}
ondrop={stopPropagation(preventDefault(onDrop))}>
<button
onclick={url && stopPropagation(onClear)}
onclick={url ? stopPropagation(onClear) : undefined}
class="absolute right-0 top-0 h-5 w-5 overflow-hidden rounded-full flex items-center justify-center"
style="background: {url ? 'var(--error)' : 'var(--primary)'};">
{#if url}

View file

@ -0,0 +1,47 @@
<script lang="ts">
import type {TrustedEvent, EventContent} from "@welshman/util"
import {getTagValue} from "@welshman/util"
import Link from "@lib/components/Link.svelte"
import RoomName from "@app/components/RoomName.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
import EventActivity from "@app/components/EventActivity.svelte"
import EventActions from "@app/components/EventActions.svelte"
import {publishDelete} from "@app/deletes"
import {publishReaction} from "@app/reactions"
import {canEnforceNip70} from "@app/relays"
import {makePollPath, makeSpacePath} from "@app/routes"
interface Props {
url: string
event: TrustedEvent
showRoom?: boolean
showActivity?: boolean
}
const {url, event, showRoom, showActivity}: Props = $props()
const h = getTagValue("h", event.tags)
const path = makePollPath(url, event.id)
const shouldProtect = canEnforceNip70(url)
const deleteReaction = async (event: TrustedEvent) =>
publishDelete({relays: [url], event, protect: await shouldProtect})
const createReaction = async (template: EventContent) =>
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
</script>
<div class="flex grow flex-wrap justify-end gap-2">
{#if h && showRoom}
<Link href={makeSpacePath(url, h)} class="button button-neutral button-xs rounded-full">
Posted in #<RoomName {h} {url} />
</Link>
{/if}
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tip-left" />
<ThunkStatusOrDeleted {event} />
{#if showActivity}
<EventActivity {url} {path} {event} />
{/if}
<EventActions {url} {event} noun="Poll" />
</div>

View file

@ -3,7 +3,7 @@
import {getTagValue} from "@welshman/util"
import Link from "@lib/components/Link.svelte"
import NoteContent from "@app/components/NoteContent.svelte"
import CommentActions from "@app/components/CommentActions.svelte"
import PollActions from "@app/components/PollActions.svelte"
import RoomLink from "@app/components/RoomLink.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
import {makePollPath} from "@app/routes"
@ -29,6 +29,6 @@
in <RoomLink {url} {h} />
{/if}
</span>
<CommentActions segment="polls" showActivity {url} {event} />
<PollActions showActivity {url} {event} />
</div>
</Link>

View file

@ -5,7 +5,6 @@
import {pubkey, publishThunk, abortThunk} from "@welshman/app"
import {formatTimestampRelative} from "@welshman/lib"
import {deriveEvents} from "@app/repository"
import {pushToast} from "@app/toast"
import {makePollResponse} from "@app/polls"
import PollOption from "@app/components/PollOption.svelte"
import {
@ -30,7 +29,6 @@
const options = getPollOptions(event)
const closed = isPollClosed(event)
const endsAt = getPollEndsAt(event)
const publishDelay = pollType === "multiplechoice" ? 10_000 : undefined
const getOwnResponse = (responses: TrustedEvent[]) => {
let latest: TrustedEvent | undefined
@ -61,19 +59,12 @@
activeThunk = publishThunk({
relays: [url],
event: makePollResponse({event, selectedIds: selection}),
delay: publishDelay,
delay: pollType === "multiplechoice" ? 1000 : undefined,
})
}
const publishCurrentSelection = () => {
const selection = pollType === "singlechoice" ? selectedIds.slice(0, 1) : selectedIds
if (selection.length === 0) {
return pushToast({theme: "error", message: "Please select at least one option."})
}
publishSelection(selection)
}
const publishCurrentSelection = () =>
publishSelection(pollType === "singlechoice" ? selectedIds.slice(0, 1) : selectedIds)
const results = $derived(getPollResults(event, $responses))
const ownResponse = $derived(getOwnResponse($responses))