Manage modals with a mega modal model

This commit is contained in:
Jonathan Staab 2023-04-18 12:22:14 -05:00
parent dfb1916b02
commit e43caba359
8 changed files with 85 additions and 75 deletions

View File

@ -19,7 +19,7 @@
import * as db from "src/agent/db" import * as db from "src/agent/db"
import user from "src/agent/user" import user from "src/agent/user"
import {loadAppData} from "src/app/state" import {loadAppData} from "src/app/state"
import {theme, getThemeVariables, modal, openModals} from "src/partials/state" import {theme, getThemeVariables, modal} from "src/partials/state"
import {logUsage} from "src/app/state" import {logUsage} from "src/app/state"
import SideNav from "src/app/SideNav.svelte" import SideNav from "src/app/SideNav.svelte"
import Routes from "src/app/Routes.svelte" import Routes from "src/app/Routes.svelte"
@ -54,16 +54,11 @@
onMount(() => { onMount(() => {
let scrollY let scrollY
// Log modals // Log modals, keep scroll position on body, but don't allow scrolling
const unsubModal = modal.stack.subscribe($stack => { const unsubModal = modal.stack.subscribe($stack => {
if ($stack.length > 0) { if ($stack.length > 0) {
logUsage(btoa(["modal", last($stack).type].join(":"))) logUsage(btoa(["modal", last($stack).type].join(":")))
}
})
// Keep scroll position on body, but don't allow scrolling
const unsubOpenModals = openModals.subscribe(n => {
if (n > 0) {
// This is not idempotent, so don't duplicate it // This is not idempotent, so don't duplicate it
if (document.body.style.position !== "fixed") { if (document.body.style.position !== "fixed") {
scrollY = window.scrollY scrollY = window.scrollY
@ -71,9 +66,11 @@
document.body.style.top = `-${scrollY}px` document.body.style.top = `-${scrollY}px`
document.body.style.position = `fixed` document.body.style.position = `fixed`
} }
} else { } else if (scrollY) {
document.body.setAttribute("style", "") document.body.setAttribute("style", "")
window.scrollTo(0, scrollY) window.scrollTo(0, scrollY)
scrollY = null
} }
}) })
@ -94,7 +91,6 @@
return () => { return () => {
unsubModal() unsubModal()
unsubOpenModals()
unsubHistory() unsubHistory()
} }
}) })

View File

@ -31,52 +31,54 @@
</script> </script>
{#each $stack as m} {#each $stack as m}
<Modal onEscape={m.noEscape || m !== last($stack) ? null : closeModal}> {#if !m.virtual}
{#if m.type === "note/detail"} <Modal virtual={false} onEscape={m.noEscape || m !== last($stack) ? null : closeModal}>
{#key m.note.id} {#if m.type === "note/detail"}
<NoteDetail {...m} invertColors /> {#key m.note.id}
{/key} <NoteDetail {...m} invertColors />
{:else if m.type === "note/create"} {/key}
<NoteCreate pubkey={m.pubkey} nevent={m.nevent} /> {:else if m.type === "note/create"}
{:else if m.type === "relay/add"} <NoteCreate pubkey={m.pubkey} nevent={m.nevent} />
<RelayAdd url={m.url} /> {:else if m.type === "relay/add"}
{:else if m.type === "onboarding"} <RelayAdd url={m.url} />
<Onboarding stage={m.stage} /> {:else if m.type === "onboarding"}
{:else if m.type === "room/edit"} <Onboarding stage={m.stage} />
<ChatEdit {...m} /> {:else if m.type === "room/edit"}
{:else if m.type === "login/privkey"} <ChatEdit {...m} />
<LoginPrivKey /> {:else if m.type === "login/privkey"}
{:else if m.type === "login/pubkey"} <LoginPrivKey />
<LoginPubKey /> {:else if m.type === "login/pubkey"}
{:else if m.type === "login/connect"} <LoginPubKey />
<LoginConnect /> {:else if m.type === "login/connect"}
{:else if m.type === "person/feed"} <LoginConnect />
<PersonFeed pubkey={m.pubkey} /> {:else if m.type === "person/feed"}
{:else if m.type === "person/info"} <PersonFeed pubkey={m.pubkey} />
<PersonProfileInfo person={m.person} /> {:else if m.type === "person/info"}
{:else if m.type === "person/share"} <PersonProfileInfo person={m.person} />
<PersonShare person={m.person} /> {:else if m.type === "person/share"}
{:else if m.type === "person/follows"} <PersonShare person={m.person} />
<PersonList type="follows" pubkey={m.pubkey} /> {:else if m.type === "person/follows"}
{:else if m.type === "person/followers"} <PersonList type="follows" pubkey={m.pubkey} />
<PersonList type="followers" pubkey={m.pubkey} /> {:else if m.type === "person/followers"}
{:else if m.type === "topic/feed"} <PersonList type="followers" pubkey={m.pubkey} />
{#key m.topic} {:else if m.type === "topic/feed"}
<TopicFeed topic={m.topic} /> {#key m.topic}
{/key} <TopicFeed topic={m.topic} />
{:else if m.type === "list/list"} {/key}
<ListList /> {:else if m.type === "list/list"}
{:else if m.type === "list/select"} <ListList />
<ListSelect item={m.item} /> {:else if m.type === "list/select"}
{:else if m.type === "list/edit"} <ListSelect item={m.item} />
<ListEdit list={m.list} /> {:else if m.type === "list/edit"}
{:else if m.type === "message"} <ListEdit list={m.list} />
<Content size="lg"> {:else if m.type === "message"}
<div class="text-center">{m.message}</div> <Content size="lg">
{#if m.spinner} <div class="text-center">{m.message}</div>
<Spinner delay={0} /> {#if m.spinner}
{/if} <Spinner delay={0} />
</Content> {/if}
{/if} </Content>
</Modal> {/if}
</Modal>
{/if}
{/each} {/each}

View File

@ -2,7 +2,6 @@
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {find, last} from "ramda" import {find, last} from "ramda"
import {onMount} from "svelte" import {onMount} from "svelte"
import {get} from "svelte/store"
import {quantify} from "hurdak/lib/hurdak" import {quantify} from "hurdak/lib/hurdak"
import {findRootId, findReplyId, displayPerson} from "src/util/nostr" import {findRootId, findReplyId, displayPerson} from "src/util/nostr"
import {formatTimestamp} from "src/util/misc" import {formatTimestamp} from "src/util/misc"

View File

@ -10,7 +10,7 @@
export let feedRelay export let feedRelay
export let notes export let notes
$: filteredNotes = notes.filter(n => n.seen_on.includes(feedRelay.url)) $: filteredNotes = notes.filter(n => n.seen_on?.includes(feedRelay.url))
</script> </script>
<Content> <Content>

View File

@ -1,8 +1,9 @@
<script> <script>
import {pluck, find} from "ramda" import {pluck, find} from "ramda"
import {Tags, displayPerson, displayRelay} from "src/util/nostr" import {Tags, displayRelay} from "src/util/nostr"
import {modal, toast} from "src/partials/state" import {modal, toast} from "src/partials/state"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import PersonBadge from "src/app/shared/PersonBadge.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
import Button from "src/partials/Button.svelte" import Button from "src/partials/Button.svelte"
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
@ -22,10 +23,14 @@
const search = q => { const search = q => {
if (q.startsWith("#")) { if (q.startsWith("#")) {
return $searchTopics(q).map(({name}) => ["t", name]) return $searchTopics(q)
.slice(0, 5)
.map(({name}) => ["t", name])
} }
return $searchPeople(q).map(({pubkey}) => ["p", pubkey]) return $searchPeople(q)
.slice(0, 5)
.map(({pubkey}) => ["p", pubkey])
} }
const _searchRelays = q => pluck("url", $searchRelays(q)).map(url => ["r", url]) const _searchRelays = q => pluck("url", $searchRelays(q)).map(url => ["r", url])
@ -63,9 +68,11 @@
<MultiSelect {search} bind:value={values.params}> <MultiSelect {search} bind:value={values.params}>
<div slot="item" let:item> <div slot="item" let:item>
{#if item[0] === "p"} {#if item[0] === "p"}
{displayPerson(getPersonWithFallback(item[1]))} <div class="-my-1">
<PersonBadge inert person={getPersonWithFallback(item[1])} />
</div>
{:else} {:else}
#{item[1]} <strong>#{item[1]}</strong>
{/if} {/if}
</div> </div>
</MultiSelect> </MultiSelect>

View File

@ -1,18 +1,26 @@
<script> <script>
import {randomId} from "hurdak/lib/hurdak"
import {onMount, onDestroy} from "svelte" import {onMount, onDestroy} from "svelte"
import {fly, fade} from "svelte/transition" import {fly, fade} from "svelte/transition"
import {openModals} from "src/partials/state" import {modal} from "src/partials/state"
export let virtual = true
export let onEscape = null export let onEscape = null
let root, content let root, content
const id = randomId()
onMount(() => { onMount(() => {
openModals.update(n => n + 1) if (virtual) {
modal.push({id, virtual: true})
}
}) })
onDestroy(() => { onDestroy(() => {
openModals.update(n => n - 1) if (virtual) {
modal.remove(id)
}
}) })
</script> </script>

View File

@ -36,7 +36,7 @@
<div class="mt-2 flex flex-col rounded border border-solid border-gray-6" in:fly={{y: 20}}> <div class="mt-2 flex flex-col rounded border border-solid border-gray-6" in:fly={{y: 20}}>
{#each data as item, i} {#each data as item, i}
<button <button
class="cursor-pointer border-l-2 border-solid border-black py-2 px-4 text-left text-white" class="cursor-pointer border-l-2 border-solid border-black py-2 px-4 text-left text-gray-1 hover:border-accent hover:bg-gray-7"
class:bg-gray-8={index !== i} class:bg-gray-8={index !== i}
class:bg-gray-7={index === i} class:bg-gray-7={index === i}
class:border-accent={index === i} class:border-accent={index === i}

View File

@ -1,4 +1,4 @@
import {prop, fromPairs} from "ramda" import {prop, whereEq, reject, last, fromPairs} from "ramda"
import {uuid, switcher} from "hurdak/lib/hurdak" import {uuid, switcher} from "hurdak/lib/hurdak"
import type {Writable} from "svelte/store" import type {Writable} from "svelte/store"
import {navigate} from "svelte-routing" import {navigate} from "svelte-routing"
@ -44,15 +44,10 @@ toast.show = (type, message, timeout = 5) => {
// Modals // Modals
export const openModals = writable(0)
export const modal = { export const modal = {
stack: new WritableList([]) as WritableList<any>, stack: new WritableList([]) as WritableList<any>,
getCurrent() { getCurrent() {
const $stack = get(modal.stack) return last(get(modal.stack))
const $openModals = get(openModals)
return $stack[$openModals - 1]
}, },
sync($stack, opts = {}) { sync($stack, opts = {}) {
const hash = $stack.length > 0 ? `#m=${$stack.length}` : "" const hash = $stack.length > 0 ? `#m=${$stack.length}` : ""
@ -63,6 +58,9 @@ export const modal = {
return $stack return $stack
}, },
remove(id) {
modal.stack.update($stack => modal.sync(reject(whereEq({id}), $stack)))
},
push(data) { push(data) {
modal.stack.update($stack => modal.sync($stack.concat(data))) modal.stack.update($stack => modal.sync($stack.concat(data)))
}, },