Maybe get dialogs behaving properly

This commit is contained in:
Jon Staab
2024-10-15 15:52:30 -07:00
parent 4decb2f4d9
commit 9c300d40f6
22 changed files with 169 additions and 189 deletions
-4
View File
@@ -140,10 +140,6 @@
@apply shadow-[0_20px_25px_-5px_rgb(0,0,0,0.1)_0_8px_10px_-6px_rgb(0,0,0,0.1)];
}
.modal-box .z-feature {
@apply z-modal-feature;
}
/* tiptap */
.input-editor,
+7 -7
View File
@@ -1,17 +1,17 @@
<script lang="ts">
import {pubkey} from '@welshman/app'
import {pubkey} from "@welshman/app"
import Landing from "@app/components/Landing.svelte"
import Toast from "@app/components/Toast.svelte"
import PrimaryNav from "@app/components/PrimaryNav.svelte"
</script>
<div class="flex h-screen overflow-hidden">
<PrimaryNav>
{#if $pubkey}
{#if $pubkey}
<PrimaryNav>
<slot />
{:else}
<Landing />
{/if}
</PrimaryNav>
</PrimaryNav>
{:else}
<Landing />
{/if}
</div>
<Toast />
+7 -5
View File
@@ -14,7 +14,6 @@
import type {PublishStatusData} from "@welshman/app"
import {REACTION, ZAP_RESPONSE, displayRelayUrl} from "@welshman/util"
import {repository} from "@welshman/app"
import {slideAndFade} from '@lib/transition'
import Icon from "@lib/components/Icon.svelte"
import Avatar from "@lib/components/Avatar.svelte"
import Button from "@lib/components/Button.svelte"
@@ -41,7 +40,10 @@
const rootHints = [rootTag?.[2]].filter(Boolean) as string[]
const rootEvent = rootId ? deriveEvent(rootId, rootHints) : readable(null)
const [colorName, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
const ps = throttled(300, derived(publishStatusData, $m => Object.values($m[event.id] || {})))
const ps = throttled(
300,
derived(publishStatusData, $m => Object.values($m[event.id] || {})),
)
const showInfo = () => pushModal(EventInfo, {event})
@@ -94,7 +96,7 @@
</p>
</div>
{/if}
<div class="flex gap-2 w-full">
<div class="flex w-full gap-2">
{#if showPubkey}
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={10} />
{:else}
@@ -128,7 +130,7 @@
</div>
{#if $reactions.length > 0 || $zaps.length > 0}
<div class="ml-12 text-xs">
{#each groupBy(e => e.content, uniqBy(e => e.pubkey + e.content, $reactions)).entries() as [content, events]}
{#each groupBy( e => e.content, uniqBy(e => e.pubkey + e.content, $reactions), ).entries() as [content, events]}
{@const isOwn = events.some(e => e.pubkey === $pubkey)}
{@const onClick = () => onReactionClick(content, events)}
<button
@@ -147,7 +149,7 @@
</div>
{/if}
<button
class="join absolute top-1 right-1 border border-solid border-neutral text-xs opacity-0 transition-all group-hover:opacity-100"
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all group-hover:opacity-100"
on:click|stopPropagation>
<ChannelMessageEmojiButton {url} {room} {event} />
<Button class="btn join-item btn-xs" on:click={showInfo}>
+1 -1
View File
@@ -43,7 +43,7 @@
}
</script>
<div class="fixed flex max-h-screen w-full flex-col gap-2">
<div class="col-2">
<div class="overflow-auto pt-3">
<ChannelMessage {url} {room} {event} showPubkey />
{#each sortBy(e => e.created_at, $replies) as reply (reply.id)}
+8 -5
View File
@@ -35,7 +35,10 @@
const reactions = deriveEvents(repository, {filters: [{kinds: [REACTION], "#e": [event.id]}]})
const zaps = deriveEvents(repository, {filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}]})
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
const ps = throttled(300, derived(publishStatusData, $m => Object.values($m[event.wrap!.id] || {})))
const ps = throttled(
300,
derived(publishStatusData, $m => Object.values($m[event.wrap!.id] || {})),
)
const showProfile = () => pushDrawer(ProfileDetail, {pubkey: event.pubkey})
@@ -67,7 +70,7 @@
</script>
<div
class="chat flex gap-1 items-center group justify-end"
class="group chat flex items-center justify-end gap-1"
class:chat-start={event.pubkey !== $pubkey}
class:flex-row-reverse={event.pubkey !== $pubkey}
class:chat-end={event.pubkey === $pubkey}>
@@ -85,13 +88,13 @@
popoverIsVisible = false
},
}}>
<Button class="group-hover:opacity-100 opacity-0 transition-all" on:click={togglePopover}>
<Button class="opacity-0 transition-all group-hover:opacity-100" on:click={togglePopover}>
<Icon icon="menu-dots" size={4} />
</Button>
</Tippy>
<div class="flex flex-col">
<div class="chat-bubble mx-1 max-w-sm">
<div class="flex items-start gap-2 w-full">
<div class="flex w-full items-start gap-2">
{#if showPubkey}
<Button on:click={showProfile}>
<Avatar
@@ -129,7 +132,7 @@
</div>
</div>
{#if $reactions.length > 0 || $zaps.length > 0}
<div class="-mt-4 text-xs z-feature relative flex justify-end">
<div class="relative z-feature -mt-4 flex justify-end text-xs">
{#each groupBy( e => e.content, uniqBy(e => e.pubkey + e.content, $reactions), ).entries() as [content, events]}
{@const isOwn = events.some(e => e.pubkey === $pubkey)}
{@const onClick = () => onReactionClick(content, events)}
+4 -4
View File
@@ -1,9 +1,9 @@
<script lang="ts">
import Icon from '@lib/components/Icon.svelte'
import Button from '@lib/components/Button.svelte'
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte"
import EventInfo from '@app/components/EventInfo.svelte'
import {pushModal} from '@app/modal'
import EventInfo from "@app/components/EventInfo.svelte"
import {pushModal} from "@app/modal"
export let event
export let pubkeys
+1 -2
View File
@@ -14,7 +14,6 @@
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
import {getPubkeyHints} from "@app/commands"
import {getEditorOptions, addFile, uploadFiles, getEditorTags} from "@lib/editor"
import {clearModal} from "@app/modal"
import {pushToast} from "@app/toast"
export let url
@@ -54,7 +53,7 @@
})
publishThunk({event, relays: [url]})
clearModal()
history.back()
}
let editor: Readable<Editor>
+2 -4
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import Button from '@lib/components/Button.svelte'
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
export let event
@@ -8,9 +8,7 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">Event Details</div>
<div slot="info">
The full details of this event are shown below.
</div>
<div slot="info">The full details of this event are shown below.</div>
</ModalHeader>
<pre class="overflow-auto"><code>{JSON.stringify(event, null, 2)}</code></pre>
<Button class="btn btn-primary" on:click={() => history.back()}>Got it</Button>
+22 -19
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Dialog from "@lib/components/Dialog.svelte"
import CardButton from "@lib/components/CardButton.svelte"
import LogIn from "@app/components/LogIn.svelte"
import SignUp from "@app/components/SignUp.svelte"
@@ -11,23 +12,25 @@
const signUp = () => pushModal(SignUp)
</script>
<div class="column gap-4">
<div class="py-2">
<h1 class="heading">Welcome to Flotilla!</h1>
<p class="text-center">The chat app built for sovereign communities.</p>
<Dialog>
<div class="column gap-4">
<div class="py-2">
<h1 class="heading">Welcome to Flotilla!</h1>
<p class="text-center">The chat app built for sovereign communities.</p>
</div>
<Button on:click={logIn}>
<CardButton>
<div slot="icon"><Icon icon="login-2" size={7} /></div>
<div slot="title">Log in</div>
<div slot="info">If you've been here before, you know the drill.</div>
</CardButton>
</Button>
<Button on:click={signUp}>
<CardButton>
<div slot="icon"><Icon icon="add-circle" size={7} /></div>
<div slot="title">Create an account</div>
<div slot="info">Just a few questions and you'll be on your way.</div>
</CardButton>
</Button>
</div>
<Button on:click={logIn}>
<CardButton>
<div slot="icon"><Icon icon="login-2" size={7} /></div>
<div slot="title">Log in</div>
<div slot="info">If you've been here before, you know the drill.</div>
</CardButton>
</Button>
<Button on:click={signUp}>
<CardButton>
<div slot="icon"><Icon icon="add-circle" size={7} /></div>
<div slot="title">Create an account</div>
<div slot="info">Just a few questions and you'll be on your way.</div>
</CardButton>
</Button>
</div>
</Dialog>
+2 -2
View File
@@ -9,7 +9,7 @@
import SignUp from "@app/components/SignUp.svelte"
import InfoNostr from "@app/components/InfoNostr.svelte"
import LogInInfoRemoteSigner from "@app/components/LogInInfoRemoteSigner.svelte"
import {pushModal, clearModal} from "@app/modal"
import {pushModal, clearModals} from "@app/modal"
import {pushToast} from "@app/toast"
import {loadUserData} from "@app/commands"
@@ -31,7 +31,7 @@
await loadUserData(session.pubkey, {relays})
pushToast({message: "Successfully logged in!"})
clearModal()
clearModals()
}
const loginWithNip46 = withLoading(async () => {
+23 -45
View File
@@ -1,56 +1,34 @@
<script lang="ts">
import {onMount} from 'svelte'
import type {SvelteComponent} from "svelte"
import {page} from "$app/stores"
import {fly} from "@lib/transition"
import Drawer from "@lib/components/Drawer.svelte"
import {modals} from "@app/modal"
import Toast from "@app/components/Toast.svelte"
import Dialog from "@lib/components/Dialog.svelte"
import {modals, clearModals} from "@app/modal"
let prev: any
let mounted = false
let dialog: HTMLDialogElement
let drawer: SvelteComponent
$: hash = $page.url.hash.slice(1)
$: modal = modals.get(hash)
$: prev = modal || prev
$: {
if (mounted) {
if (modal?.options?.drawer) {
drawer.open()
} else if (modal) {
dialog.showModal()
} else {
drawer.close()
dialog.close()
}
const onKeyDown = (e: any) => {
if (e.code === "Escape" && e.target === document.body) {
clearModals()
}
}
onMount(() => {
mounted = true
})
let modal: any
$: hash = $page.url.hash.slice(1)
$: hashIsValid = Boolean($modals[hash])
$: modal = $modals[hash] || modal
</script>
<dialog bind:this={dialog} class="modal modal-bottom !z-modal sm:modal-middle">
{#if modal && !modal.options?.drawer}
{#key hash}
<div class="bg-alt modal-box overflow-visible overflow-y-auto" transition:fly={{duration: 100}}>
<svelte:component this={modal.component} {...modal.props} />
</div>
{/key}
<Toast />
{/if}
<form method="dialog" class="modal-backdrop">
<button />
</form>
</dialog>
<Drawer bind:this={drawer}>
{#if modal && modal.options?.drawer}
{#key hash}
<svelte:window on:keydown={onKeyDown} />
{#if hashIsValid && modal?.options?.drawer}
<Drawer onClose={clearModals}>
{#key modal.id}
<svelte:component this={modal.component} {...modal.props} />
{/key}
{/if}
</Drawer>
</Drawer>
{:else if hashIsValid && modal}
<Dialog onClose={clearModals}>
{#key modal.id}
<svelte:component this={modal.component} {...modal.props} />
{/key}
</Dialog>
{/if}
+2 -2
View File
@@ -54,9 +54,9 @@
{@const following = getPubkeyTagValues(getListTags($userFollows)).includes(pubkey)}
<div class="divider" />
<button type="button" class="chat chat-start cursor-default" on:click|stopPropagation>
<div class="bg-alt chat-bubble text-left col-4">
<div class="bg-alt col-4 chat-bubble text-left">
<Content hideMedia={!following} {event} />
<Link external href={entityLink(nevent)} class="row-2 justify-end group whitespace-nowrap">
<Link external href={entityLink(nevent)} class="row-2 group justify-end whitespace-nowrap">
<Icon icon="link-round" size={3} />
<p class="text-xs">{formatTimestamp(event.created_at)}</p>
</Link>
+2 -2
View File
@@ -7,7 +7,7 @@
import Spinner from "@lib/components/Spinner.svelte"
import LogIn from "@app/components/LogIn.svelte"
import InfoNostr from "@app/components/LogIn.svelte"
import {pushModal, clearModal} from "@app/modal"
import {pushModal, clearModals} from "@app/modal"
import {pushToast} from "@app/toast"
const login = () => pushModal(LogIn)
@@ -38,7 +38,7 @@
if (await loginBroker.connect("", nip46Perms)) {
addSession({method: "nip46", pubkey, secret, handler})
pushToast({message: "Successfully logged in!"})
clearModal()
clearModals()
} else {
pushToast({
theme: "error",
+2 -2
View File
@@ -5,7 +5,7 @@
import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {clearModal} from "@app/modal"
import {clearModals} from "@app/modal"
import {addSpaceMembership} from "@app/commands"
export let url
@@ -15,7 +15,7 @@
const tryJoin = async () => {
await addSpaceMembership(url)
clearModal()
clearModals()
}
const join = async () => {
+1 -2
View File
@@ -11,7 +11,6 @@
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {getPubkeyHints} from "@app/commands"
import {getEditorOptions, addFile, uploadFiles, getEditorTags} from "@lib/editor"
import {clearModal} from "@app/modal"
export let url
@@ -25,7 +24,7 @@
const event = createEvent(NOTE, {content: $editor.getText(), tags: getEditorTags($editor)})
publishThunk({event, relays: [url]})
clearModal()
history.back()
}
let editor: Readable<Editor>
+16 -9
View File
@@ -1,15 +1,23 @@
import type {ComponentType} from "svelte"
import {randomId, Emitter} from "@welshman/lib"
import {writable} from "svelte/store"
import {randomId, always, assoc, Emitter} from "@welshman/lib"
import {goto} from "$app/navigation"
export const emitter = new Emitter()
export const modals = new Map()
export type ModalOptions = {
drawer?: boolean
}
export type Modal = {
id: string
component: ComponentType
props: Record<string, any>
options: ModalOptions
}
export const emitter = new Emitter()
export const modals = writable<Record<string, Modal>>({})
export const pushModal = (
component: ComponentType,
props: Record<string, any> = {},
@@ -17,7 +25,7 @@ export const pushModal = (
) => {
const id = randomId()
modals.set(id, {component, props, options})
modals.update(assoc(id, {id, component, props, options}))
goto("#" + id)
@@ -30,8 +38,7 @@ export const pushDrawer = (
options: ModalOptions = {},
) => pushModal(component, props, {...options, drawer: true})
export const clearModal = () => {
goto("#")
modals.clear()
export const clearModals = () => {
modals.update(always({}))
emitter.emit("close")
}
+2 -2
View File
@@ -187,7 +187,7 @@ setContext({
return false
}
const roomTags = event.tags.filter(nthEq(0, '~'))
const roomTags = event.tags.filter(nthEq(0, "~"))
if (roomTags.length > 0 && !roomTags.some(nthEq(2, url))) {
return false
@@ -268,7 +268,7 @@ export const {
// Messages
export type ChannelMessage = {
url: string,
url: string
room: string
event: TrustedEvent
}
+18
View File
@@ -0,0 +1,18 @@
<script lang="ts">
import {noop} from '@welshman/lib'
import {fade, fly} from "@lib/transition"
export let onClose: any = noop
</script>
<div class="center fixed inset-0 z-modal">
<button
class="absolute inset-0 cursor-pointer bg-black opacity-50"
transition:fade
on:click={onClose} />
<div
class="card2 bg-alt absolute max-h-[90vh] w-[90vw] overflow-auto text-base-content sm:w-[520px]"
transition:fly={{duration: 300}}>
<slot />
</div>
</div>
+11 -31
View File
@@ -1,37 +1,17 @@
<script lang="ts">
import {randomId} from "@welshman/lib"
import {fade, slide} from "@lib/transition"
const id = randomId()
let input: any
let label: any
export const open = () => {
if (!input.checked) {
label.click()
}
}
export const close = () => {
if (input.checked) {
label.click()
}
}
export const toggle = () => {
label.click()
}
export let onClose
</script>
<div class="drawer drawer-end">
<input {id} type="checkbox" class="drawer-toggle" bind:this={input} on:change />
<div class="drawer-content">
<label for={id} bind:this={label} />
</div>
<div class="drawer-side z-modal">
<label for={id} aria-label="close sidebar" class="drawer-overlay"></label>
<div class="menu h-full w-80 overflow-auto bg-base-200 p-0 text-base-content lg:w-96">
<slot />
</div>
<div class="fixed inset-0 z-modal">
<button
class="absolute inset-0 cursor-pointer bg-black opacity-50"
transition:fade
on:click={onClose} />
<div
class="menu absolute bottom-0 right-0 top-0 w-80 overflow-auto bg-base-200 text-base-content lg:w-96"
transition:slide={{axis: "x", duration: 300}}>
<slot />
</div>
</div>
+1 -1
View File
@@ -1,3 +1,3 @@
<div class="mt-4 flex flex-row items-center justify-between gap-4">
<div class="row-4 mt-4 items-center justify-between">
<slot />
</div>
+37 -35
View File
@@ -1,5 +1,5 @@
// @ts-nocheck
import {cubicOut} from 'svelte/easing'
import {cubicOut} from "svelte/easing"
import type {FlyParams} from "svelte/transition"
import {fly as baseFly} from "svelte/transition"
@@ -9,38 +9,40 @@ export const fly = (node: Element, params?: FlyParams | undefined) =>
baseFly(node, {y: 20, ...params})
// Copy-pasted and tweaked from slide source code
export function slideAndFade(node: any, { delay = 0, duration = 400, easing = cubicOut, axis = 'y' } = {}) {
const style = getComputedStyle(node)
const opacity = +style.opacity
const primary_property = axis === 'y' ? 'height' : 'width'
const primary_property_value = parseFloat(style[primary_property])
const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right']
const capitalized_secondary_properties = secondary_properties.map(
(e: string) => `${e[0].toUpperCase()}${e.slice(1)}`
)
const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`])
const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`])
const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`])
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`])
const border_width_start_value = parseFloat(
style[`border${capitalized_secondary_properties[0]}Width`]
)
const border_width_end_value = parseFloat(
style[`border${capitalized_secondary_properties[1]}Width`]
)
return {
delay,
duration,
easing,
css: (t: number) =>
'overflow: hidden;' +
`opacity: ${t};` +
`${primary_property}: ${t * primary_property_value}px;` +
`padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
`padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
`margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
`margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
`border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
`border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`
}
export function slideAndFade(
node: any,
{delay = 0, duration = 400, easing = cubicOut, axis = "y"} = {},
) {
const style = getComputedStyle(node)
const primary_property = axis === "y" ? "height" : "width"
const primary_property_value = parseFloat(style[primary_property])
const secondary_properties = axis === "y" ? ["top", "bottom"] : ["left", "right"]
const capitalized_secondary_properties = secondary_properties.map(
(e: string) => `${e[0].toUpperCase()}${e.slice(1)}`,
)
const padding_start_value = parseFloat(style[`padding${capitalized_secondary_properties[0]}`])
const padding_end_value = parseFloat(style[`padding${capitalized_secondary_properties[1]}`])
const margin_start_value = parseFloat(style[`margin${capitalized_secondary_properties[0]}`])
const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`])
const border_width_start_value = parseFloat(
style[`border${capitalized_secondary_properties[0]}Width`],
)
const border_width_end_value = parseFloat(
style[`border${capitalized_secondary_properties[1]}Width`],
)
return {
delay,
duration,
easing,
css: (t: number) =>
"overflow: hidden;" +
`opacity: ${t};` +
`${primary_property}: ${t * primary_property_value}px;` +
`padding-${secondary_properties[0]}: ${t * padding_start_value}px;` +
`padding-${secondary_properties[1]}: ${t * padding_end_value}px;` +
`margin-${secondary_properties[0]}: ${t * margin_start_value}px;` +
`margin-${secondary_properties[1]}: ${t * margin_end_value}px;` +
`border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` +
`border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`,
}
}
-5
View File
@@ -2,9 +2,6 @@
import "@src/app.css"
import {onMount} from "svelte"
import {get} from "svelte/store"
import {page} from "$app/stores"
import {goto} from "$app/navigation"
import {browser} from "$app/environment"
import {sleep, take, sortBy, ago, now, HOUR} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {
@@ -26,7 +23,6 @@
db,
initStorage,
repository,
session,
pubkey,
publishStatusData,
plaintext,
@@ -39,7 +35,6 @@
import * as app from "@welshman/app"
import AppContainer from "@app/components/AppContainer.svelte"
import ModalContainer from "@app/components/ModalContainer.svelte"
import {modals, clearModal} from "@app/modal"
import {theme} from "@app/theme"
import {INDEXER_RELAYS} from "@app/state"
import {loadUserData} from "@app/commands"