forked from coracle/flotilla
Add discover page
This commit is contained in:
Generated
+18
@@ -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",
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user