Add discover page

This commit is contained in:
Jon Staab
2024-08-14 14:52:00 -07:00
parent 30175c00e0
commit 66bb74fc32
13 changed files with 153 additions and 11 deletions
+18
View File
@@ -18,9 +18,11 @@
"@welshman/store": "^0.0.2",
"@welshman/util": "^0.0.25",
"daisyui": "^4.12.10",
"fuse.js": "^7.0.0",
"idb": "^8.0.0",
"nostr-tools": "^2.7.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"svelte-bricks": "^0.2.1",
"throttle-debounce": "^5.0.2"
},
"devDependencies": {
@@ -2615,6 +2617,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fuse.js": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
"engines": {
"node": ">=10"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -4274,6 +4284,14 @@
"node": ">=16"
}
},
"node_modules/svelte-bricks": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/svelte-bricks/-/svelte-bricks-0.2.1.tgz",
"integrity": "sha512-cc3XK3j5ViPyZ3K183+Sr53B2e8mJaiV3POyoJtjmm1dYc/TBMy7jOUMt8MW/snJzodpACfqwFzokBQbrZ297w==",
"dependencies": {
"svelte": "^4.2.1"
}
},
"node_modules/svelte-check": {
"version": "3.8.5",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.5.tgz",
+2
View File
@@ -44,9 +44,11 @@
"@welshman/store": "^0.0.2",
"@welshman/util": "^0.0.25",
"daisyui": "^4.12.10",
"fuse.js": "^7.0.0",
"idb": "^8.0.0",
"nostr-tools": "^2.7.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"svelte-bricks": "^0.2.1",
"throttle-debounce": "^5.0.2"
}
}
+13 -1
View File
@@ -72,7 +72,7 @@
background-color: var(--stark-content);
}
.card {
.card2 {
padding: 1rem;
background-color: oklch(var(--b2) / 1);
border-radius: var(--rounded-box, 1rem);
@@ -82,6 +82,14 @@
@apply flex flex-col;
}
.center {
@apply flex justify-center items-center;
}
.content {
@apply max-w-2xl w-full p-12 m-auto;
}
.heading {
@apply text-2xl text-stark-content text-center;
}
@@ -90,6 +98,10 @@
@apply text-xl text-stark-content text-center;
}
.superheading {
@apply text-4xl text-stark-content text-center;
}
.link {
@apply text-primary underline cursor-pointer;
}
+3 -3
View File
@@ -20,9 +20,9 @@
If you do decide to join someone else's, make sure to follow their directions for registering
as a user.
</p>
<div class="card flex-row justify-between">
devrelay.highlighter.com
<Button on:click={() => clip('devrelay.highlighter.com')}>
<div class="card2 flex-row justify-between">
groups.fiatjaf.com
<Button on:click={() => clip('groups.fiatjaf.com')}>
<Icon icon="copy" />
</Button>
</div>
+1 -1
View File
@@ -18,7 +18,7 @@
<CardButton icon="add-circle" title="Get started" on:click={startCreate}>
Just a few questions and you'll be on your way.
</CardButton>
<div class="card column gap-4">
<div class="card2 column gap-4">
<h2 class="subheading">Have an invite?</h2>
<Button class="btn btn-primary" on:click={startJoin}>
Join a Space
+22 -3
View File
@@ -1,4 +1,5 @@
import type {Readable} from "svelte/store"
import type {FuseResult} from 'fuse.js'
import {writable, readable, derived} from "svelte/store"
import type {Maybe} from "@welshman/lib"
import {uniq, uniqBy, groupBy, pushToMapKey, nthEq, batcher, postJson, stripProtocol, assoc, indexBy, now} from "@welshman/lib"
@@ -8,7 +9,7 @@ import type {SubscribeRequest} from '@welshman/net'
import {publish, subscribe} from '@welshman/net'
import {decrypt} from '@welshman/signer'
import {deriveEvents, deriveEventsMapped, getter, withGetter} from "@welshman/store"
import {parseJson} from '@lib/util'
import {parseJson, createSearch} from '@lib/util'
import type {Session, Handle, Relay} from '@app/types'
import {INDEXER_RELAYS, DUFFLEPUD_URL, repository, pk, getSession, getSigner, signer} from "@app/base"
@@ -357,6 +358,7 @@ export const getGroupPicture = (e?: CustomEvent) => e?.tags.find(nthEq(0, "pictu
export type Group = {
nom: string,
name?: string,
about?: string,
picture?: string,
event?: CustomEvent
}
@@ -367,10 +369,11 @@ export type PublishedGroup = Omit<Group, "event"> & {
export const readGroup = (event: CustomEvent) => {
const nom = getIdentifier(event)!
const name = event?.tags.find(nthEq(0, "name"))?.[1]
const name = event?.tags.find(nthEq(0, "name"))?.[1] || "[no name]"
const about = event?.tags.find(nthEq(0, "about"))?.[1] || ""
const picture = event?.tags.find(nthEq(0, "picture"))?.[1]
return {nom, name, picture, event}
return {nom, name, about, picture, event}
}
export const groups = deriveEventsMapped<PublishedGroup>({
@@ -401,6 +404,22 @@ export const {
])
})
export const searchGroups = derived(
groups,
$groups =>
createSearch($groups, {
getValue: (group: PublishedGroup) => group.nom,
sortFn: (result: FuseResult<PublishedGroup>) => {
const scale = result.item.picture ? 0.5 : 1
return result.score! * scale
},
fuseOptions: {
keys: ["name", {name: "about", weight: 0.3}],
},
})
)
// Qualified groups
export type QualifiedGroup = {
+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="M16 10.5C16 11.3284 15.5523 12 15 12C14.4477 12 14 11.3284 14 10.5C14 9.67157 14.4477 9 15 9C15.5523 9 16 9.67157 16 10.5Z" fill="#1C274C"/>
<ellipse cx="9" cy="10.5" rx="1" ry="1.5" fill="#1C274C"/>
<path d="M22 19.723V12.3006C22 6.61173 17.5228 2 12 2C6.47715 2 2 6.61173 2 12.3006V19.723C2 21.0453 3.35098 21.9054 4.4992 21.314C5.42726 20.836 6.5328 20.9069 7.39614 21.4998C8.36736 22.1667 9.63264 22.1667 10.6039 21.4998L10.9565 21.2576C11.5884 20.8237 12.4116 20.8237 13.0435 21.2576L13.3961 21.4998C14.3674 22.1667 15.6326 22.1667 16.6039 21.4998C17.4672 20.9069 18.5727 20.836 19.5008 21.314C20.649 21.9054 22 21.0453 22 19.723Z" stroke="#1C274C" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 787 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">
<circle cx="11.5" cy="11.5" r="9.5" stroke="#1C274C" stroke-width="1.5"/>
<path d="M18.5 18.5L22 22" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 265 B

+4
View File
@@ -19,12 +19,14 @@
import CompassBig from "@assets/icons/Compass Big.svg?dataurl"
import FireMinimalistic from "@assets/icons/Fire Minimalistic.svg?dataurl"
import GallerySend from "@assets/icons/Gallery Send.svg?dataurl"
import Ghost from "@assets/icons/Ghost.svg?dataurl"
import HandPills from "@assets/icons/Hand Pills.svg?dataurl"
import HomeSmile from "@assets/icons/Home Smile.svg?dataurl"
import InfoCircle from "@assets/icons/Info Circle.svg?dataurl"
import LinkRound from "@assets/icons/Link Round.svg?dataurl"
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 Pallete2 from "@assets/icons/Pallete 2.svg?dataurl"
import Plain from "@assets/icons/Plain.svg?dataurl"
import RemoteControllerMinimalistic from "@assets/icons/Remote Controller Minimalistic.svg?dataurl"
@@ -52,12 +54,14 @@
"compass-big": CompassBig,
"fire-minimalistic": FireMinimalistic,
"gallery-send": GallerySend,
"ghost": Ghost,
"hand-pills": HandPills,
"home-smile": HomeSmile,
"info-circle": InfoCircle,
"link-round": LinkRound,
"login": Login,
"login-2": Login2,
'magnifer': Magnifer,
'pallete-2': Pallete2,
plain: Plain,
'remote-controller-minimalistic': RemoteControllerMinimalistic,
+32 -1
View File
@@ -1,6 +1,9 @@
import Fuse from "fuse.js"
import type {IFuseOptions, FuseResult} from 'fuse.js'
import {throttle} from 'throttle-debounce'
import {browser} from '$app/environment'
import {writable} from 'svelte/store'
import {sortBy} from '@welshman/lib'
import {browser} from '$app/environment'
export const parseJson = (json: string) => {
if (!json) return null
@@ -29,3 +32,31 @@ export const synced = <T>(key: string, defaultValue: T, delay = 300) => {
return store
}
export type SearchOptions<V, T> = {
getValue: (item: T) => V
fuseOptions?: IFuseOptions<T>
sortFn?: (items: FuseResult<T>) => any
}
export const createSearch = <V, T>(data: T[], opts: SearchOptions<V, T>) => {
const fuse = new Fuse(data, {...opts.fuseOptions, includeScore: true})
const map = new Map<V, T>(data.map(item => [opts.getValue(item), item]))
const search = (term: string) => {
let results = term ? fuse.search(term) : data.map(item => ({item, score: 1}) as FuseResult<T>)
if (opts.sortFn) {
results = sortBy(opts.sortFn, results)
}
return results.map(result => result.item)
}
return {
getValue: opts.getValue,
getOption: (value: V) => map.get(value),
searchOptions: (term: string) => search(term),
searchValues: (term: string) => search(term).map(opts.getValue),
}
}
+1 -1
View File
@@ -65,7 +65,7 @@
<div class="flex h-screen">
<PrimaryNav />
<SecondaryNav />
<div class="flex-grow bg-base-200">
<div class="flex-grow bg-base-200 max-h-screen overflow-auto">
<slot />
</div>
</div>
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="hero min-h-screen bg-base-200">
<div class="hero-content">
<div class="flex max-w-2xl flex-col gap-4">
<div class="column content gap-4">
<h1 class="text-stark-content text-center text-5xl">Welcome to</h1>
<h1 class="text-stark-content mb-4 text-center text-5xl font-bold uppercase">Flotilla</h1>
<div class="grid lg:grid-cols-2 gap-3">
+47
View File
@@ -0,0 +1,47 @@
<script lang="ts">
import {onMount} from 'svelte'
import Masonry from 'svelte-bricks'
import {GROUP_META} from '@welshman/util'
import Icon from '@lib/components/Icon.svelte'
import {load, relays, searchGroups} from '@app/state'
let term = ""
onMount(() => {
load({
relays: $relays.map(r => r.url),
filters: [{kinds: [GROUP_META]}],
})
})
</script>
<div class="content column gap-8">
<div class="column gap-4 pt-20 pb-12">
<h1 class="superheading">Discover Spaces</h1>
<p class="text-center">Find communities all across the nostr network</p>
<label class="input input-bordered w-full flex items-center gap-2">
<Icon icon="magnifer" />
<input bind:value={term} class="grow" type="text" placeholder="Search for spaces..." />
</label>
</div>
<Masonry items={$searchGroups.searchOptions(term)} minColWidth={200} maxColWidth={400} gap={16} idKey="nom" let:item>
<div class="card bg-base-100 shadow-xl">
<div class="avatar center mt-4">
<div class="w-20 rounded-full bg-base-300 border-2 border-solid border-base-300 !flex center">
{#if item?.picture}
<img alt="" src={item.picture} />
{:else}
<Icon icon="ghost" size={7} />
{/if}
</div>
</div>
<div class="card-body">
<h2 class="card-title justify-center">{item.name}</h2>
<p class="text-sm py-4">{item.about}</p>
<div class="card-actions">
<button class="btn btn-primary w-full">Join Space</button>
</div>
</div>
</div>
</Masonry>
</div>