Fix multiple choice poll selections not saving, bring back base64 profile image fallback but compressed
This commit is contained in:
parent
e575be921d
commit
27252f543a
4 changed files with 77 additions and 22 deletions
|
|
@ -7,7 +7,6 @@
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import {uploadFile} from "@app/uploads"
|
import {uploadFile} from "@app/uploads"
|
||||||
import {pushToast} from "@app/toast"
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
file?: File | undefined
|
file?: File | undefined
|
||||||
|
|
@ -18,6 +17,10 @@
|
||||||
|
|
||||||
const id = randomId()
|
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 = () => {
|
const onDragEnter = () => {
|
||||||
active = true
|
active = true
|
||||||
}
|
}
|
||||||
|
|
@ -33,11 +36,11 @@
|
||||||
const onDrop = async (e: any) => {
|
const onDrop = async (e: any) => {
|
||||||
active = false
|
active = false
|
||||||
|
|
||||||
file = await compressFile(e.dataTransfer.files[0])
|
file = await compressFile(e.dataTransfer.files[0], compressOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = async (e: any) => {
|
const onChange = async (e: any) => {
|
||||||
file = await compressFile(e.target.files[0])
|
file = await compressFile(e.target.files[0], compressOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClear = () => {
|
const onClear = () => {
|
||||||
|
|
@ -55,15 +58,24 @@
|
||||||
if (file) {
|
if (file) {
|
||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
const {error, result} = await uploadFile(file)
|
const {result} = await uploadFile(file)
|
||||||
|
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
if (result?.url) {
|
if (result?.url) {
|
||||||
url = result.url
|
url = result.url
|
||||||
} else {
|
} else {
|
||||||
file = undefined
|
const reader = new FileReader()
|
||||||
pushToast({theme: "error", message: error || "Failed to upload image."})
|
|
||||||
|
reader.addEventListener(
|
||||||
|
"load",
|
||||||
|
() => {
|
||||||
|
url = reader.result as string
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
url = initialUrl
|
url = initialUrl
|
||||||
|
|
@ -73,7 +85,12 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form>
|
<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
|
<label
|
||||||
for={id}
|
for={id}
|
||||||
aria-label="Drag and drop files here."
|
aria-label="Drag and drop files here."
|
||||||
|
|
@ -85,7 +102,7 @@
|
||||||
ondragleave={stopPropagation(preventDefault(onDragLeave))}
|
ondragleave={stopPropagation(preventDefault(onDragLeave))}
|
||||||
ondrop={stopPropagation(preventDefault(onDrop))}>
|
ondrop={stopPropagation(preventDefault(onDrop))}>
|
||||||
<button
|
<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"
|
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)'};">
|
style="background: {url ? 'var(--error)' : 'var(--primary)'};">
|
||||||
{#if url}
|
{#if url}
|
||||||
|
|
|
||||||
47
src/app/components/PollActions.svelte
Normal file
47
src/app/components/PollActions.svelte
Normal 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>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import {getTagValue} from "@welshman/util"
|
import {getTagValue} from "@welshman/util"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.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 RoomLink from "@app/components/RoomLink.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import {makePollPath} from "@app/routes"
|
import {makePollPath} from "@app/routes"
|
||||||
|
|
@ -29,6 +29,6 @@
|
||||||
in <RoomLink {url} {h} />
|
in <RoomLink {url} {h} />
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
<CommentActions segment="polls" showActivity {url} {event} />
|
<PollActions showActivity {url} {event} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
import {pubkey, publishThunk, abortThunk} from "@welshman/app"
|
import {pubkey, publishThunk, abortThunk} from "@welshman/app"
|
||||||
import {formatTimestampRelative} from "@welshman/lib"
|
import {formatTimestampRelative} from "@welshman/lib"
|
||||||
import {deriveEvents} from "@app/repository"
|
import {deriveEvents} from "@app/repository"
|
||||||
import {pushToast} from "@app/toast"
|
|
||||||
import {makePollResponse} from "@app/polls"
|
import {makePollResponse} from "@app/polls"
|
||||||
import PollOption from "@app/components/PollOption.svelte"
|
import PollOption from "@app/components/PollOption.svelte"
|
||||||
import {
|
import {
|
||||||
|
|
@ -30,7 +29,6 @@
|
||||||
const options = getPollOptions(event)
|
const options = getPollOptions(event)
|
||||||
const closed = isPollClosed(event)
|
const closed = isPollClosed(event)
|
||||||
const endsAt = getPollEndsAt(event)
|
const endsAt = getPollEndsAt(event)
|
||||||
const publishDelay = pollType === "multiplechoice" ? 10_000 : undefined
|
|
||||||
|
|
||||||
const getOwnResponse = (responses: TrustedEvent[]) => {
|
const getOwnResponse = (responses: TrustedEvent[]) => {
|
||||||
let latest: TrustedEvent | undefined
|
let latest: TrustedEvent | undefined
|
||||||
|
|
@ -61,19 +59,12 @@
|
||||||
activeThunk = publishThunk({
|
activeThunk = publishThunk({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
event: makePollResponse({event, selectedIds: selection}),
|
event: makePollResponse({event, selectedIds: selection}),
|
||||||
delay: publishDelay,
|
delay: pollType === "multiplechoice" ? 1000 : undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishCurrentSelection = () => {
|
const publishCurrentSelection = () =>
|
||||||
const selection = pollType === "singlechoice" ? selectedIds.slice(0, 1) : selectedIds
|
publishSelection(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 results = $derived(getPollResults(event, $responses))
|
const results = $derived(getPollResults(event, $responses))
|
||||||
const ownResponse = $derived(getOwnResponse($responses))
|
const ownResponse = $derived(getOwnResponse($responses))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue