Add sticky submit buttons to settings pages

This commit is contained in:
Jon Staab
2026-02-02 09:51:36 -08:00
parent 38c0a9d403
commit 4480132c74
13 changed files with 43 additions and 78 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {onMount, mount, unmount, createRawSnippet} from "svelte" import {onMount, mount, unmount} from "svelte"
import Drawer from "@lib/components/Drawer.svelte" import Drawer from "@lib/components/Drawer.svelte"
import Dialog from "@lib/components/Dialog.svelte" import Dialog from "@lib/components/Dialog.svelte"
import {modal, clearModals} from "@app/util/modal" import {modal, clearModals} from "@app/util/modal"
-3
View File
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames"
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {RoomMeta} from "@welshman/util" import type {RoomMeta} from "@welshman/util"
import {displayRelayUrl, makeRoomMeta} from "@welshman/util" import {displayRelayUrl, makeRoomMeta} from "@welshman/util"
@@ -16,7 +15,6 @@
import Microphone from "@assets/icons/microphone.svg?dataurl" import Microphone from "@assets/icons/microphone.svg?dataurl"
import Bookmark from "@assets/icons/bookmark.svg?dataurl" import Bookmark from "@assets/icons/bookmark.svg?dataurl"
import VolumeLoud from "@assets/icons/volume-loud.svg?dataurl" import VolumeLoud from "@assets/icons/volume-loud.svg?dataurl"
import VolumeCross from "@assets/icons/volume-cross.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 Confirm from "@lib/components/Confirm.svelte" import Confirm from "@lib/components/Confirm.svelte"
@@ -34,7 +32,6 @@
deriveUserIsRoomAdmin, deriveUserIsRoomAdmin,
deriveUserRoomMembershipStatus, deriveUserRoomMembershipStatus,
deriveUserRooms, deriveUserRooms,
userSettingsValues,
deriveIsMuted, deriveIsMuted,
MembershipStatus, MembershipStatus,
} from "@app/core/state" } from "@app/core/state"
+1 -5
View File
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {AuthStatus} from "@welshman/net"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -14,9 +13,8 @@
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal"
import {attemptRelayAccess} from "@app/core/commands" import {attemptRelayAccess} from "@app/core/commands"
import {deriveSocket, parseInviteLink} from "@app/core/state" import {parseInviteLink} from "@app/core/state"
type Props = { type Props = {
url: string url: string
@@ -27,8 +25,6 @@
const back = () => history.back() const back = () => history.back()
const socket = deriveSocket(url)
const join = async () => { const join = async () => {
loading = true loading = true
@@ -1,7 +1,5 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte"
import {dissoc} from "@welshman/lib" import {dissoc} from "@welshman/lib"
import {Pool, AuthStatus} from "@welshman/net"
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {slideAndFade} from "@lib/transition" import {slideAndFade} from "@lib/transition"
@@ -17,9 +15,7 @@
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import RelaySummary from "@app/components/RelaySummary.svelte" import RelaySummary from "@app/components/RelaySummary.svelte"
import SpaceJoin from "@app/components/SpaceJoin.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal"
import {makeSpacePath} from "@app/util/routes" import {makeSpacePath} from "@app/util/routes"
import {relaysMostlyRestricted, parseInviteLink} from "@app/core/state" import {relaysMostlyRestricted, parseInviteLink} from "@app/core/state"
import {attemptRelayAccess, addSpaceMembership, broadcastUserData} from "@app/core/commands" import {attemptRelayAccess, addSpaceMembership, broadcastUserData} from "@app/core/commands"
+2 -9
View File
@@ -1,10 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import {sleep, dissoc} from "@welshman/lib" import {dissoc} from "@welshman/lib"
import {Pool, AuthStatus, SocketStatus} from "@welshman/net"
import {deriveRelay} from "@welshman/app"
import {displayRelayUrl} from "@welshman/util"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
@@ -15,7 +12,6 @@
import Modal from "@lib/components/Modal.svelte" import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte" import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import StatusIndicator from "@lib/components/StatusIndicator.svelte" import StatusIndicator from "@lib/components/StatusIndicator.svelte"
import RelaySummary from "@app/components/RelaySummary.svelte" import RelaySummary from "@app/components/RelaySummary.svelte"
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte" import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
@@ -26,7 +22,7 @@
broadcastUserData, broadcastUserData,
setSpaceNotifications, setSpaceNotifications,
} from "@app/core/commands" } from "@app/core/commands"
import {relaysMostlyRestricted, deriveSpaceMembers, notificationSettings} from "@app/core/state" import {relaysMostlyRestricted, notificationSettings} from "@app/core/state"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {makeSpacePath} from "@app/util/routes" import {makeSpacePath} from "@app/util/routes"
@@ -38,9 +34,6 @@
const {url}: Props = $props() const {url}: Props = $props()
const relay = deriveRelay(url)
const members = deriveSpaceMembers(url)
const back = () => history.back() const back = () => history.back()
const join = async () => { const join = async () => {
-1
View File
@@ -48,7 +48,6 @@
deriveUserCanCreateRoom, deriveUserCanCreateRoom,
deriveUserIsSpaceAdmin, deriveUserIsSpaceAdmin,
deriveEventsForUrl, deriveEventsForUrl,
userSettingsValues,
notificationSettings, notificationSettings,
deriveIsMuted, deriveIsMuted,
} from "@app/core/state" } from "@app/core/state"
+1 -1
View File
@@ -6,7 +6,7 @@
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte" import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
import {makeRoomPath} from "@app/util/routes" import {makeRoomPath} from "@app/util/routes"
import {notifications} from "@app/util/notifications" import {notifications} from "@app/util/notifications"
import {userSettingsValues, deriveIsMuted} from "@app/core/state" import {deriveIsMuted} from "@app/core/state"
interface Props { interface Props {
url: any url: any
-7
View File
@@ -31,8 +31,6 @@ import {
identity, identity,
now, now,
groupBy, groupBy,
nth,
nthEq,
maybe, maybe,
throttle, throttle,
} from "@welshman/lib" } from "@welshman/lib"
@@ -64,7 +62,6 @@ import {
getEventPath, getEventPath,
goToEvent, goToEvent,
} from "@app/util/routes" } from "@app/util/routes"
import type {SpaceNotificationSettings} from "@app/core/state"
import { import {
DM_KINDS, DM_KINDS,
CONTENT_KINDS, CONTENT_KINDS,
@@ -75,15 +72,12 @@ import {
notificationState, notificationState,
chatsById, chatsById,
hasNip29, hasNip29,
getSettings,
userSettingsValues, userSettingsValues,
userGroupList, userGroupList,
getSpaceUrlsFromGroupList, getSpaceUrlsFromGroupList,
getSpaceRoomsFromGroupList, getSpaceRoomsFromGroupList,
makeCommentFilter, makeCommentFilter,
userSpaceUrls, userSpaceUrls,
splitRoomId,
makeRoomId,
isMuted, isMuted,
device, device,
} from "@app/core/state" } from "@app/core/state"
@@ -284,7 +278,6 @@ export const onNotification = call(() => {
if (!unsubscribe) { if (!unsubscribe) {
unsubscribe = on(repository, "update", ({added}) => { unsubscribe = on(repository, "update", ({added}) => {
const $pubkey = pubkey.get() const $pubkey = pubkey.get()
const {alerts} = getSettings()
for (const event of added) { for (const event of added) {
if (event.pubkey == $pubkey) { if (event.pubkey == $pubkey) {
-1
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Component} from "svelte" import type {Component} from "svelte"
import cx from "classnames" import cx from "classnames"
import {onMount} from "svelte"
import {noop} from "@welshman/lib" import {noop} from "@welshman/lib"
import {fade, fly} from "@lib/transition" import {fade, fly} from "@lib/transition"
+4 -17
View File
@@ -1,28 +1,19 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {sleep, equals, remove} from "@welshman/lib" import {sleep} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
import {Capacitor} from "@capacitor/core" import {Capacitor} from "@capacitor/core"
import {Badge} from "@capawesome/capacitor-badge" import {Badge} from "@capawesome/capacitor-badge"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import RoomName from "@app/components/RoomName.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {Push, clearBadges} from "@app/util/notifications" import {Push, clearBadges} from "@app/util/notifications"
import {notificationSettings, userSettingsValues, splitRoomId} from "@app/core/state" import {notificationSettings} from "@app/core/state"
import {publishSettings} from "@app/core/commands"
const reset = () => { const reset = () => {
settings = {...notificationSettings.get()} settings = {...notificationSettings.get()}
} }
const removeMutedRoom = (id: string) => {
muted_rooms = remove(id, muted_rooms)
}
const onsubmit = preventDefault(async () => { const onsubmit = preventDefault(async () => {
loading = true loading = true
@@ -46,10 +37,6 @@
} }
} }
if (!equals(muted_rooms, $userSettingsValues.muted_rooms)) {
publishSettings($state.snapshot({...$userSettingsValues, muted_rooms}))
}
notificationSettings.set(settings) notificationSettings.set(settings)
pushToast({message: "Your settings have been saved!"}) pushToast({message: "Your settings have been saved!"})
@@ -60,7 +47,6 @@
let loading = $state(false) let loading = $state(false)
let settings = $state({...notificationSettings.get()}) let settings = $state({...notificationSettings.get()})
let muted_rooms = $state($userSettingsValues.muted_rooms)
</script> </script>
<form class="content column gap-4" {onsubmit}> <form class="content column gap-4" {onsubmit}>
@@ -105,7 +91,8 @@
<input type="checkbox" class="toggle toggle-primary" bind:checked={settings.messages} /> <input type="checkbox" class="toggle toggle-primary" bind:checked={settings.messages} />
</div> </div>
</div> </div>
<div class="mt-4 flex flex-row items-center justify-between gap-4"> <div
class="card2 bg-alt sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
<Button class="btn btn-neutral" onclick={reset} disabled={loading}>Discard Changes</Button> <Button class="btn btn-neutral" onclick={reset} disabled={loading}>Discard Changes</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Save Changes</Spinner> <Spinner {loading}>Save Changes</Spinner>
+27 -22
View File
@@ -87,6 +87,28 @@
</div> </div>
{/snippet} {/snippet}
</Field> </Field>
</div>
<div class="card2 bg-alt col-4 shadow-md">
<strong class="text-lg">Accessibility</strong>
<Field>
{#snippet label()}
<p>Font size</p>
{/snippet}
{#snippet secondary()}
<p>{Math.round(settings.font_size * 100)}%</p>
{/snippet}
{#snippet input()}
<input
class="range range-primary"
type="range"
min="0.8"
max="1.3"
step="0.05"
bind:value={settings.font_size} />
{/snippet}
</Field>
</div>
<div class="card2 bg-alt col-4 shadow-md">
<strong class="text-lg">Editor Settings</strong> <strong class="text-lg">Editor Settings</strong>
<FieldInline> <FieldInline>
{#snippet label()} {#snippet label()}
@@ -123,27 +145,10 @@
<p>Choose a media server type and url for files you upload to {PLATFORM_NAME}.</p> <p>Choose a media server type and url for files you upload to {PLATFORM_NAME}.</p>
{/snippet} {/snippet}
</Field> </Field>
<strong class="text-lg">Accessibility</strong> </div>
<Field> <div
{#snippet label()} class="card2 bg-alt sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
<p>Font size</p> <Button class="btn btn-neutral" onclick={reset}>Discard Changes</Button>
{/snippet} <Button type="submit" class="btn btn-primary">Save Changes</Button>
{#snippet secondary()}
<p>{Math.round(settings.font_size * 100)}%</p>
{/snippet}
{#snippet input()}
<input
class="range range-primary"
type="range"
min="0.8"
max="1.3"
step="0.05"
bind:value={settings.font_size} />
{/snippet}
</Field>
<div class="mt-4 flex flex-row items-center justify-between gap-4">
<Button class="btn btn-neutral" onclick={reset}>Discard Changes</Button>
<Button type="submit" class="btn btn-primary">Save Changes</Button>
</div>
</div> </div>
</form> </form>
+5 -4
View File
@@ -50,9 +50,10 @@
Allow {PLATFORM_NAME} to collect anonymous usage data. Allow {PLATFORM_NAME} to collect anonymous usage data.
</p> </p>
</div> </div>
<div class="mt-4 flex flex-row items-center justify-between gap-4"> </div>
<Button class="btn btn-neutral" onclick={reset}>Discard Changes</Button> <div
<Button type="submit" class="btn btn-primary">Save Changes</Button> class="card2 bg-alt sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
</div> <Button class="btn btn-neutral" onclick={reset}>Discard Changes</Button>
<Button type="submit" class="btn btn-primary">Save Changes</Button>
</div> </div>
</form> </form>
+2 -3
View File
@@ -1,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import {readable, derived as _derived} from "svelte/store" import {readable} from "svelte/store"
import {onMount, onDestroy} from "svelte" import {onMount, onDestroy} from "svelte"
import {page} from "$app/stores" import {page} from "$app/stores"
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import type {MakeNonOptional} from "@welshman/lib" import type {MakeNonOptional} from "@welshman/lib"
import {now, int, append, remove, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib" import {now, int, formatTimestampAsDate, ago, MINUTE} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {throttled} from "@welshman/store"
import { import {
makeEvent, makeEvent,
makeRoomMeta, makeRoomMeta,