Rough out calendar

This commit is contained in:
Jon Staab
2024-09-11 15:21:25 -07:00
parent 648b15a1c4
commit 525a823862
17 changed files with 409 additions and 54 deletions
+3 -3
View File
@@ -48,12 +48,12 @@
"@tiptap/extension-text": "^2.6.6",
"@tiptap/suggestion": "^2.6.4",
"@types/throttle-debounce": "^5.0.2",
"@welshman/app": "^0.0.4",
"@welshman/lib": "^0.0.17",
"@welshman/util": "^0.0.30",
"@welshman/store": "^0.0.5",
"@welshman/net": "^0.0.21",
"@welshman/signer": "^0.0.5",
"@welshman/app": "^0.0.4",
"@welshman/store": "^0.0.5",
"@welshman/util": "^0.0.30",
"daisyui": "^4.12.10",
"fuse.js": "^7.0.0",
"idb": "^8.0.0",
+3 -2
View File
@@ -48,8 +48,9 @@
<div
class="shadow-top-xl relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100 p-2">
<Button
on:click={() => addFile($editor)}
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200">
data-tip="Add an image"
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200 tooltip"
on:click={() => addFile($editor)}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
+107
View File
@@ -0,0 +1,107 @@
<script lang="ts">
import {onMount} from "svelte"
import type {Readable} from "svelte/store"
import {writable} from "svelte/store"
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
import {NProfileExtension, ImageExtension} from "nostr-editor"
import {createEvent, EVENT_DATE, EVENT_TIME} from "@welshman/util"
import {publishThunk, makeThunk} from "@welshman/app"
import {findNodes} from "@lib/tiptap"
import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte"
import {makeMention, makeIMeta} from "@app/commands"
import {getNoteEditorOptions, addFile} from "@app/editor"
import {pushModal} from "@app/modal"
export let url
const next = () => null
const back = () => history.back()
const uploading = writable(false)
const sendMessage = () => {
const json = $editor.getJSON()
const kind = isAllDay ? EVENT_DATE : EVENT_TIME
const mentionTags = findNodes(NProfileExtension.name, json).map(m =>
makeMention(m.attrs!.pubkey, m.attrs!.relays),
)
const imetaTags = findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) =>
makeIMeta(src, {x, ox: x}),
)
const event = createEvent(kind, {
content: $editor.getText(),
tags: [["-"], ...mentionTags, ...imetaTags],
})
publishThunk(makeThunk({event, relays: [url]}))
$editor.chain().clearContent().run()
}
let editor: Readable<Editor>
let isAllDay = false
let file: File
let title = ""
let location = ""
let start = ""
let end = ""
onMount(() => {
editor = createEditor(getNoteEditorOptions({uploading, sendMessage}))
})
</script>
<form class="column gap-4" on:submit|preventDefault={next}>
<div class="py-2">
<h1 class="heading">Create an Event</h1>
<p class="text-center">Invite other group members to events online or in real life.</p>
</div>
<Field>
<p slot="label">Title*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<input bind:value={title} class="grow" type="text" />
</label>
</Field>
<Field>
<p slot="label">Summary</p>
<div
slot="input"
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<Button
data-tip="Add an image"
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200 tooltip"
on:click={() => addFile($editor)}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="gallery-send" />
{/if}
</Button>
<div class="flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
</div>
</Field>
<Field>
<p slot="label">Location (optional)</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="map-point" />
<input bind:value={title} class="grow" type="text" />
</label>
</Field>
<div class="flex flex-row items-center justify-between gap-4">
<Button class="btn btn-link" on:click={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary">
Next
<Icon icon="alt-arrow-right" class="!bg-base-300" />
</Button>
</div>
</form>
+4 -11
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
import Link from "@lib/components/Link.svelte"
import Icon from "@lib/components/Icon.svelte"
import SpaceInviteAccept from "@app/components/SpaceInviteAccept.svelte"
@@ -12,23 +11,17 @@
window.open("https://relay.tools/signup")
pushModal(SpaceInviteAccept)
}
let file: File
let name = ""
let relay = ""
</script>
<form class="column gap-4" on:submit|preventDefault={next}>
<div class="py-2">
<h1 class="heading">Create a Space</h1>
<p class="text-center">
Host your own space, for your community.
</p>
<p class="text-center">Host your own space, for your community.</p>
</div>
<p>
<Link class="text-primary" external href="https://relay.tools">relay.tools</Link> is a third-party service
that allows anyone to run their own relay for use with Flotilla, or any other
nostr-compatible app.
<Link class="text-primary" external href="https://relay.tools">relay.tools</Link> is a third-party
service that allows anyone to run their own relay for use with Flotilla, or any other nostr-compatible
app.
</p>
<p>
Once you've created a relay of your own, come back here to link Flotilla with your new relay.
+63 -2
View File
@@ -37,12 +37,12 @@ export const addFile = (editor: Editor) => editor.chain().selectFiles().run()
export const uploadFiles = (editor: Editor) => editor.chain().uploadFiles().run()
type ChatComposeEditorOptions = {
type EditorOptions = {
uploading: Writable<boolean>
sendMessage: () => void
}
export const getChatEditorOptions = ({uploading, sendMessage}: ChatComposeEditorOptions) => ({
export const getChatEditorOptions = ({uploading, sendMessage}: EditorOptions) => ({
content: "",
autofocus: true,
extensions: [
@@ -146,3 +146,64 @@ export const getChatViewOptions = (content: string) => ({
VideoExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeVideo)})),
],
})
export const getNoteEditorOptions = ({uploading, sendMessage}: EditorOptions) => ({
content: "",
autofocus: true,
extensions: [
Document,
Dropcursor,
Gapcursor,
History,
Paragraph,
Text,
HardBreakExtension,
LinkExtension.extend({
addNodeView: () => SvelteNodeViewRenderer(ChatComposeLink),
}),
Bolt11Extension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeBolt11)}),
),
NProfileExtension.extend({
addNodeView: () => SvelteNodeViewRenderer(ChatComposeMention),
addProseMirrorPlugins() {
return [
createSuggestions({
char: "@",
name: "nprofile",
editor: this.editor,
search: profileSearch,
select: (pubkey: string, props: any) => {
const relays = getPubkeyHints(pubkey)
const nprofile = nprofileEncode({pubkey, relays})
return props.command({pubkey, nprofile, relays})
},
suggestionComponent: ChatSuggestionProfile,
suggestionsComponent: ChatComposeSuggestions,
}),
]
},
}),
NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})),
NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})),
ImageExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeImage)}),
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
VideoExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeVideo)}),
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
FileUploadExtension.configure({
immediateUpload: false,
sign: (event: StampedEvent) => {
uploading.set(true)
return signer.get()!.sign(event)
},
onComplete: () => {
uploading.set(false)
sendMessage()
},
}),
],
})
+33 -19
View File
@@ -1,12 +1,14 @@
import {nip19} from "nostr-tools"
import {get, derived, writable} from "svelte/store"
import {get, derived} from "svelte/store"
import type {Maybe} from "@welshman/lib"
import {setContext, max, groupBy, pushToMapKey, nthEq} from "@welshman/lib"
import {setContext, max, pushToMapKey, nthEq} from "@welshman/lib"
import {
getIdFilters,
RELAYS,
REACTION,
ZAP_RESPONSE,
EVENT_DATE,
EVENT_TIME,
getRelayTagValues,
getTopicTagValues,
isShareableRelayUrl,
@@ -150,27 +152,23 @@ export const makeChatId = (url: string, topic: string) => `${url}'${topic}`
export const splitChatId = (id: string) => id.split("'")
export const chats = derived(
[trackerStore, messages],
([$tracker, $messages]) => {
const messagesByChatId = new Map<string, Message[]>()
export const chats = derived([trackerStore, messages], ([$tracker, $messages]) => {
const messagesByChatId = new Map<string, Message[]>()
for (const message of $messages) {
for (const url of $tracker.getRelays(message.event.id)) {
const chatId = makeChatId(url, message.topic)
for (const message of $messages) {
for (const url of $tracker.getRelays(message.event.id)) {
const chatId = makeChatId(url, message.topic)
pushToMapKey(messagesByChatId, chatId, message)
}
pushToMapKey(messagesByChatId, chatId, message)
}
return Array.from(messagesByChatId.entries())
.map(([id, messages]) => {
const [url, topic] = splitChatId(id)
return {id, url, topic, messages}
})
}
)
return Array.from(messagesByChatId.entries()).map(([id, messages]) => {
const [url, topic] = splitChatId(id)
return {id, url, topic, messages}
})
})
export const {
indexStore: chatsById,
@@ -190,6 +188,22 @@ export const {
},
})
// Calendar vents
export const events = deriveEvents(repository, {filters: [{kinds: [EVENT_DATE, EVENT_TIME]}]})
export const eventsByUrl = derived([trackerStore, events], ([$tracker, $events]) => {
const eventsByUrl = new Map<string, TrustedEvent[]>()
for (const event of $events) {
for (const url of $tracker.getRelays(event.id)) {
pushToMapKey(eventsByUrl, url, event)
}
}
return eventsByUrl
})
// Topics
export const topicsByUrl = derived(chats, $chats => {
+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 10C3 6.22876 3 4.34315 4.17157 3.17157C5.34315 2 7.22876 2 11 2H13C16.7712 2 18.6569 2 19.8284 3.17157C21 4.34315 21 6.22876 21 10V14C21 17.7712 21 19.6569 19.8284 20.8284C18.6569 22 16.7712 22 13 22H11C7.22876 22 5.34315 22 4.17157 20.8284C3 19.6569 3 17.7712 3 14V10Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M8 10H16" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M8 14H13" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 584 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 10.1433C4 5.64588 7.58172 2 12 2C16.4183 2 20 5.64588 20 10.1433C20 14.6055 17.4467 19.8124 13.4629 21.6744C12.5343 22.1085 11.4657 22.1085 10.5371 21.6744C6.55332 19.8124 4 14.6055 4 10.1433Z" stroke="#1C274C" stroke-width="1.5"/>
<circle cx="12" cy="10" r="3" stroke="#1C274C" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.721 21H13.358C15.5854 21 16.6992 21 17.6289 20.4672C18.5586 19.9345 19.1488 18.958 20.3294 17.005L21.0102 15.8787C22.0034 14.2357 22.5 13.4142 22.5 12.5C22.5 11.5858 22.0034 10.7643 21.0102 9.12126L20.3294 7.99501C19.1488 6.04203 18.5586 5.06554 17.6289 4.53277C16.6992 4 15.5854 4 13.358 4H10.721C6.84561 4 4.90789 4 3.70394 5.2448C2.5 6.48959 2.5 8.49306 2.5 12.5C2.5 16.5069 2.5 18.5104 3.70394 19.7552C4.90789 21 6.8456 21 10.721 21Z" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M7.5 7.99512V17" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 703 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5 6.5H17.5M17.5 6.5H20.5M17.5 6.5V9.5M17.5 6.5V3.5" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M2.5 6.5C2.5 4.61438 2.5 3.67157 3.08579 3.08579C3.67157 2.5 4.61438 2.5 6.5 2.5C8.38562 2.5 9.32843 2.5 9.91421 3.08579C10.5 3.67157 10.5 4.61438 10.5 6.5C10.5 8.38562 10.5 9.32843 9.91421 9.91421C9.32843 10.5 8.38562 10.5 6.5 10.5C4.61438 10.5 3.67157 10.5 3.08579 9.91421C2.5 9.32843 2.5 8.38562 2.5 6.5Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M13.5 17.5C13.5 15.6144 13.5 14.6716 14.0858 14.0858C14.6716 13.5 15.6144 13.5 17.5 13.5C19.3856 13.5 20.3284 13.5 20.9142 14.0858C21.5 14.6716 21.5 15.6144 21.5 17.5C21.5 19.3856 21.5 20.3284 20.9142 20.9142C20.3284 21.5 19.3856 21.5 17.5 21.5C15.6144 21.5 14.6716 21.5 14.0858 20.9142C13.5 20.3284 13.5 19.3856 13.5 17.5Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M2.5 17.5C2.5 15.6144 2.5 14.6716 3.08579 14.0858C3.67157 13.5 4.61438 13.5 6.5 13.5C8.38562 13.5 9.32843 13.5 9.91421 14.0858C10.5 14.6716 10.5 15.6144 10.5 17.5C10.5 19.3856 10.5 20.3284 9.91421 20.9142C9.32843 21.5 8.38562 21.5 6.5 21.5C4.61438 21.5 3.67157 21.5 3.08579 20.9142C2.5 20.3284 2.5 19.3856 2.5 17.5Z" stroke="#1C274C" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+8
View File
@@ -11,6 +11,7 @@
import AddSquare from "@assets/icons/Add Square.svg?dataurl"
import ArrowsALogout2 from "@assets/icons/Arrows ALogout 2.svg?dataurl"
import Code2 from "@assets/icons/Code 2.svg?dataurl"
import Document from "@assets/icons/Document.svg?dataurl"
import Earth from "@assets/icons/Earth.svg?dataurl"
import Pen from "@assets/icons/Pen.svg?dataurl"
import HeadphonesRound from "@assets/icons/Headphones Round.svg?dataurl"
@@ -44,6 +45,7 @@
import Login from "@assets/icons/Login.svg?dataurl"
import Login2 from "@assets/icons/Login 2.svg?dataurl"
import Magnifer from "@assets/icons/Magnifer.svg?dataurl"
import MapPoint from "@assets/icons/Map Point.svg?dataurl"
import MenuDots from "@assets/icons/Menu Dots.svg?dataurl"
import NotesMinimalistic from "@assets/icons/Notes Minimalistic.svg?dataurl"
import Pallete2 from "@assets/icons/Pallete 2.svg?dataurl"
@@ -52,6 +54,7 @@
import RemoteControllerMinimalistic from "@assets/icons/Remote Controller Minimalistic.svg?dataurl"
import Reply from "@assets/icons/Reply.svg?dataurl"
import Settings from "@assets/icons/Settings.svg?dataurl"
import TagHorizontal from "@assets/icons/Tag Horizontal.svg?dataurl"
import ShopMinimalistic from "@assets/icons/Shop Minimalistic.svg?dataurl"
import SmileCircle from "@assets/icons/Smile Circle.svg?dataurl"
import SquareShareLine from "@assets/icons/Square Share Line.svg?dataurl"
@@ -60,6 +63,7 @@
import UserCircle from "@assets/icons/User Circle.svg?dataurl"
import UserRounded from "@assets/icons/User Rounded.svg?dataurl"
import Widget from "@assets/icons/Widget.svg?dataurl"
import WidgetAdd from "@assets/icons/Widget Add.svg?dataurl"
import WiFiRouterRound from "@assets/icons/Wi-Fi Router Round.svg?dataurl"
export let icon
@@ -71,6 +75,7 @@
"add-square": AddSquare,
"arrows-a-logout-2": ArrowsALogout2,
"code-2": Code2,
document: Document,
earth: Earth,
pen: Pen,
"headphones-round": HeadphonesRound,
@@ -104,6 +109,7 @@
login: Login,
"login-2": Login2,
magnifer: Magnifer,
'map-point': MapPoint,
"menu-dots": MenuDots,
"notes-minimalistic": NotesMinimalistic,
"pallete-2": Pallete2,
@@ -114,12 +120,14 @@
"shop-minimalistic": ShopMinimalistic,
"smile-circle": SmileCircle,
settings: Settings,
'tag-horizontal': TagHorizontal,
"ufo-3": UFO3,
"square-share-line": SquareShareLine,
"user-heart": UserHeart,
"user-circle": UserCircle,
"user-rounded": UserRounded,
widget: Widget,
'widget-add': WidgetAdd,
"wifi-router-round": WiFiRouterRound,
})
+91
View File
@@ -0,0 +1,91 @@
<script lang="ts">
import {randomId} from "@welshman/lib"
import Icon from "@lib/components/Icon.svelte"
export let file: File | null = null
export let url: string | null = null
const id = randomId()
const onDragEnter = () => {
active = true
}
const onDragOver = () => {
active = true
}
const onDragLeave = () => {
active = false
}
const onDrop = (e: any) => {
active = false
file = e.dataTransfer.files[0]
}
const onChange = (e: any) => {
file = e.target.files[0]
}
const onClear = () => {
initialUrl = null
file = null
url = null
}
let active = false
let initialUrl = url
$: {
if (file) {
const reader = new FileReader()
reader.addEventListener(
"load",
() => {
url = reader.result as string
},
false,
)
reader.readAsDataURL(file)
} else {
url = initialUrl
}
}
</script>
<form>
<input {id} type="file" accept="image/*" on:change={onChange} class="hidden" />
<label
for={id}
aria-label="Drag and drop files here."
style="background-image: url({url});"
class="relative flex h-24 w-24 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 border-dashed border-base-content bg-base-300 bg-cover bg-center transition-all"
class:border-primary={active}
on:dragenter|preventDefault|stopPropagation={onDragEnter}
on:dragover|preventDefault|stopPropagation={onDragOver}
on:dragleave|preventDefault|stopPropagation={onDragLeave}
on:drop|preventDefault|stopPropagation={onDrop}>
<div
class="absolute right-0 top-0 h-5 w-5 overflow-hidden rounded-full bg-primary"
class:bg-error={file}
class:bg-primary={!file}>
{#if file}
<span
role="button"
tabindex="-1"
on:mousedown|stopPropagation={onClear}
on:touchstart|stopPropagation={onClear}>
<Icon icon="close-circle" class="scale-150 !bg-base-300" />
</span>
{:else}
<Icon icon="add-circle" class="scale-150 !bg-base-300" />
{/if}
</div>
{#if !file}
<Icon icon="gallery-send" size={7} />
{/if}
</label>
</form>
+1 -1
View File
@@ -1,4 +1,4 @@
import {sleep} from '@welshman/lib'
import {sleep} from "@welshman/lib"
export const copyToClipboard = (text: string) => {
const {activeElement} = document
+1 -1
View File
@@ -3,7 +3,7 @@
import Masonry from "svelte-bricks"
import {displayRelayUrl} from "@welshman/util"
import {relaySearch} from "@welshman/app"
import {createScroller} from '@lib/html'
import {createScroller} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import {makeSpacePath} from "@app/routes"
import {userMembership, discoverRelays} from "@app/state"
+2 -7
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import {onMount} from 'svelte'
import {onMount} from "svelte"
import {page} from "$app/stores"
import {sort, now} from "@welshman/lib"
import {displayRelayUrl, EVENT_DATE, EVENT_TIME, CLASSIFIED} from "@welshman/util"
@@ -103,15 +103,10 @@
</SecondaryNavItem>
</div>
<div in:fly|local={{delay: getDelay()}}>
<SecondaryNavItem href={makeSpacePath(url, "events")}>
<SecondaryNavItem href={makeSpacePath(url, "calendar")}>
<Icon icon="calendar-minimalistic" /> Calendar
</SecondaryNavItem>
</div>
<div in:fly|local={{delay: getDelay()}}>
<SecondaryNavItem href={makeSpacePath(url, "listings")}>
<Icon icon="shop-minimalistic" /> Market
</SecondaryNavItem>
</div>
{#if rooms.length > 0}
<div transition:slide|local={{delay: getDelay()}}>
<div class="h-2" />
@@ -8,17 +8,16 @@
</script>
<script lang="ts">
import {onMount} from "svelte"
import {page} from "$app/stores"
import {sortBy, now} from "@welshman/lib"
import type {TrustedEvent, Filter} from "@welshman/util"
import {sortBy} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {formatTimestampAsDate} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ChatMessage from "@app/components/ChatMessage.svelte"
import ChatCompose from "@app/components/ChatCompose.svelte"
import {userMembership, decodeNRelay, makeChatId, deriveChat, MESSAGE, REPLY} from "@app/state"
import {userMembership, decodeNRelay, makeChatId, deriveChat} from "@app/state"
import {addRoomMembership, removeRoomMembership} from "@app/commands"
const {nrelay, topic = ""} = $page.params
@@ -67,19 +66,20 @@
<div class="relative flex h-screen flex-col">
<div class="relative z-feature mx-2 rounded-xl pt-4">
<div class="flex min-h-12 justify-between items-center gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
<div
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
<div class="flex items-center gap-2">
<Icon icon="hashtag" />
<strong>{topic || 'General'}</strong>
<strong>{topic || "General"}</strong>
</div>
{#if topic}
{#if membership.includes(topic)}
<Button class="btn btn-sm btn-neutral" on:click={() => removeRoomMembership(url, topic)}>
<Button class="btn btn-neutral btn-sm" on:click={() => removeRoomMembership(url, topic)}>
<Icon icon="arrows-a-logout-2" />
Leave Room
</Button>
{:else}
<Button class="btn btn-sm btn-neutral" on:click={() => addRoomMembership(url, topic)}>
<Button class="btn btn-neutral btn-sm" on:click={() => addRoomMembership(url, topic)}>
<Icon icon="login-2" />
Join Room
</Button>
@@ -0,0 +1,66 @@
<script lang="ts">
import {page} from "$app/stores"
import type {TrustedEvent} from '@welshman/util'
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import EventCreate from "@app/components/EventCreate.svelte"
import {pushModal} from "@app/modal"
import {eventsByUrl, decodeNRelay} from "@app/state"
const url = decodeNRelay($page.params.nrelay)
const createEvent = () => pushModal(EventCreate)
const getDateDisplay = (event: TrustedEvent, reset: boolean) => {
if (reset) {
prevEvent = undefined
}
return "hi"
}
let prevEvent
let loading = true
$: events = $eventsByUrl.get(url) || []
setTimeout(() => {
loading = false
}, 3000)
</script>
<div class="relative flex h-screen flex-col">
<div class="relative z-feature mx-2 rounded-xl pt-4">
<div
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
<div class="flex items-center gap-2">
<Icon icon="calendar-minimalistic" />
<strong>Calendar</strong>
</div>
</div>
</div>
<div class="-mt-2 flex flex-grow flex-col overflow-auto py-2">
{#each events as event, i (event.id)}
{@const dateDisplay = getDateDisplay(event, i === 0)}
{#if dateDisplay}
<div>{dateDisplay}</div>
{/if}
<div>{event.id}</div>
{/each}
<p class="flex h-10 items-center justify-center py-20">
<Spinner {loading}>
{#if loading}
Looking for events...
{:else if events.length === 0}
No events found.
{/if}
</Spinner>
</p>
</div>
<Button class="fixed bottom-4 right-4 tooltip tooltip-left p-1" data-tip="Create an Event" on:click={createEvent}>
<div class="w-12 h-12 flex items-center justify-center btn btn-primary btn-circle">
<Icon icon="widget-add" />
</div>
</Button>
</div>