Improve feed form

This commit is contained in:
Jon Staab 2024-05-07 14:44:26 -07:00
parent 3a269db834
commit 98b4c9fd9f
33 changed files with 517 additions and 459 deletions

4
.env
View File

@ -1,5 +1,5 @@
VITE_DARK_THEME="accent:#FC560E,neutral-100:#F5F5F5,neutral-200:#E5E5E5,neutral-300:#D4D4D4,neutral-400:#A3A3A3,neutral-500:#737373,neutral-50:#FAFAFA,neutral-600:#525252,neutral-700:#404040,neutral-800:#262626,neutral-900:#171717,neutral-950:#0A0A0A,success:#12D2B0,tinted-100:#F1EAE7,tinted-200:#DED3CF,tinted-400:#B9A69E,tinted-500:#756A65,tinted-600:#5A524F,tinted-700:#3E3A38,tinted-800:#332f2d,warning:#FCAB0E"
VITE_LIGHT_THEME="accent:#FC560E,neutral-950:#FAFAFA,neutral-900:#FAFAFA,neutral-800:#F5F5F5,neutral-700:#D4D4D4,neutral-600:#A3A3A3,neutral-500:#737373,neutral-400:#525252,neutral-300:#404040,neutral-200:#262626,neutral-100:#171717,neutral-50:#0A0A0A,success:#12D2B0,tinted-800:#FFFFFF,tinted-700:#FAFAFA,tinted-600:#F5F5F5,tinted-500:#D4D4D4,tinted-400:#A3A3A3,tinted-200:#737373,tinted-100:#525252,warning:#FCAB0E"
VITE_DARK_THEME="accent:#FC560E,neutral-100:#F5F5F5,neutral-200:#E5E5E5,neutral-300:#D4D4D4,neutral-400:#A3A3A3,neutral-500:#737373,neutral-50:#FAFAFA,neutral-600:#525252,neutral-700:#404040,neutral-800:#262626,neutral-900:#171717,neutral-950:#0A0A0A,success:#12D2B0,tinted-100:#F1EAE7,tinted-200:#DED3CF,tinted-400:#B9A69E,tinted-500:#756A65,tinted-600:#5A524F,tinted-700:#3E3A38,tinted-800:#332f2d,warning:#FCAB0E,danger:#dc0c0c"
VITE_LIGHT_THEME="accent:#FC560E,neutral-950:#FAFAFA,neutral-900:#FAFAFA,neutral-800:#F5F5F5,neutral-700:#D4D4D4,neutral-600:#A3A3A3,neutral-500:#737373,neutral-400:#525252,neutral-300:#404040,neutral-200:#262626,neutral-100:#171717,neutral-50:#0A0A0A,success:#12D2B0,tinted-800:#FFFFFF,tinted-700:#FAFAFA,tinted-600:#F5F5F5,tinted-500:#D4D4D4,tinted-400:#A3A3A3,tinted-200:#737373,tinted-100:#525252,warning:#FCAB0E,danger:#dc0c0c"
VITE_DVM_RELAYS=wss://relay.damus.io,wss://offchain.pub,wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.net,wss://relay.nostr.band,wss://bucket.coracle.social
VITE_SEARCH_RELAYS=wss://relay.nostr.band,wss://nostr.wine,wss://search.nos.today
VITE_DEFAULT_RELAYS=wss://relay.damus.io,wss://nos.lol

View File

@ -145,8 +145,8 @@
})
router.register("/feeds", import("src/app/views/FeedList.svelte"))
router.register("/feeds/create", import("src/app/views/FeedForm.svelte"))
router.register("/feeds/:address", import("src/app/views/FeedForm.svelte"), {
router.register("/feeds/create", import("src/app/views/FeedCreate.svelte"))
router.register("/feeds/:address", import("src/app/views/FeedEdit.svelte"), {
serializers: {
address: asNaddr("address"),
},

View File

@ -14,7 +14,7 @@
import MenuDesktopSecondary from "src/app/MenuDesktopSecondary.svelte"
import {feed, slowConnections} from "src/app/state"
import {router} from "src/app/util/router"
import {readFeed, normalizeFeedDefinition} from "src/domain"
import {readFeed, displayFeed, normalizeFeedDefinition} from "src/domain"
import {
env,
user,
@ -60,13 +60,13 @@
in:fly={{x: -100, duration: 200}}
class="fixed bottom-0 left-72 top-0 w-60 bg-tinted-700 pt-24 transition-colors">
<MenuDesktopItem
class="!h-10 !text-lg"
small
isActive={equals(followsFeed, normalizedFeed)}
on:click={() => loadFeed(followsFeed)}>
Follows
</MenuDesktopItem>
<MenuDesktopItem
class="!h-10 !text-lg"
small
isActive={equals(networkFeed, normalizedFeed)}
on:click={() => loadFeed(networkFeed)}>
Network
@ -74,16 +74,16 @@
{#each $userFeeds as event}
{@const thisFeed = readFeed(event)}
<MenuDesktopItem
class="!h-10 !text-lg"
small
isActive={equals(thisFeed.definition, normalizedFeed)}
on:click={() => loadFeed(thisFeed.definition)}>
{thisFeed.name}
{displayFeed(thisFeed)}
</MenuDesktopItem>
{/each}
{#each $userLists as list}
{@const definition = feedFromTags(Tags.fromEvent(list))}
<MenuDesktopItem
class="!h-10 !text-lg"
small
isActive={equals(definition, normalizedFeed)}
on:click={() => loadFeed(definition)}>
{displayList(list)}

View File

@ -7,10 +7,15 @@
export let path = null
export let isAlt = false
export let isActive = false
export let small = false
$: className = cx("relative staatliches h-12 block transition-all", $$props.class, {
"text-3xl text-accent": isActive,
"text-2xl text-tinted-400 hover:text-tinted-100": !isActive,
$: className = cx("relative staatliches block transition-all", $$props.class, {
"h-12": !small,
"h-10 text-lg": small,
"text-3xl": !small && isActive,
"text-2xl": !small && !isActive,
"text-accent": isActive,
"text-tinted-400 hover:text-tinted-100": !isActive,
"hover:bg-tinted-800": !isActive && !isAlt,
"hover:bg-tinted-700": !isActive && isAlt,
})
@ -22,7 +27,9 @@
{#if isActive}
<div
in:fly|local={{x: 50, duration: 1000, easing: elasticOut}}
class="relative top-4 h-px w-full bg-accent" />
class="relative h-px w-full bg-accent"
class:top-4={!small}
class:top-3={small} />
{/if}
</div>
</Anchor>

View File

@ -63,7 +63,6 @@
onChange={relays => onChange({...dvmItem, relays})}>
<span slot="item" let:item>{displayRelayUrl(item)}</span>
</SearchSelect>
<p slot="info">Select which relays requests to this DVM should be sent to.</p>
</Field>
{#each dvmItem.tags || [] as [type, value], i (i + key)}
<div class="flex items-center justify-between gap-2">

View File

@ -0,0 +1,32 @@
<script type="ts">
import {Kind} from '@welshman/util'
import FlexColumn from "src/partials/FlexColumn.svelte"
import Card from "src/partials/Card.svelte"
import FeedSummary from "src/app/shared/FeedSummary.svelte"
import {readFeed, displayFeed, listAsFeed} from "src/domain"
import {repository, lists} from 'src/engine'
export let address
const feed = address.startsWith(Kind.ListBookmarks)
? listAsFeed(lists.key(address).get())
: readFeed(repository.getEvent(address))
</script>
<Card>
<FlexColumn>
<div class="flex items-center justify-between">
<span class="staatliches flex items-center gap-3 text-xl">
<i class="fa fa-rss" />
<span class:text-neutral-500={!feed.name}>
{displayFeed(feed)}
</span>
</span>
<slot name="controls" />
</div>
{#if feed.description}
<p>{feed.description}</p>
{/if}
<FeedSummary feed={feed.definition} />
</FlexColumn>
</Card>

View File

@ -1,32 +1,21 @@
<script lang="ts">
import {debounce} from "throttle-debounce"
import {Tags, decodeAddress} from "@welshman/util"
import {
isScopeFeed,
isSearchFeed,
makeSearchFeed,
Scope,
getFeedArgs,
feedFromTags,
} from "@welshman/feeds"
import {isScopeFeed, isSearchFeed, makeSearchFeed, Scope, getFeedArgs} from "@welshman/feeds"
import Modal from "src/partials/Modal.svelte"
import Textarea from "src/partials/Textarea.svelte"
import Subheading from "src/partials/Subheading.svelte"
import Field from "src/partials/Field.svelte"
import Input from "src/partials/Input.svelte"
import Select from "src/partials/Select.svelte"
import Popover2 from "src/partials/Popover2.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Menu from "src/partials/Menu.svelte"
import MenuItem from "src/partials/MenuItem.svelte"
import FeedField from "src/app/shared/FeedField.svelte"
import FeedForm from "src/app/shared/FeedForm.svelte"
import {router} from "src/app/util"
import {normalizeFeedDefinition, readFeed, initFeed, editFeed, createFeed} from "src/domain"
import {normalizeFeedDefinition, readFeed, initFeed, listAsFeed, displayFeed} from "src/domain"
import {
hints,
repository,
createAndPublish,
displayList as displayList2,
publishDeletion,
userLists,
userFeeds,
} from "src/engine"
@ -50,30 +39,17 @@
formIsOpen = false
}
const openName = () => {
nameIsOpen = true
}
const closeName = () => {
nameIsOpen = false
}
const toggleReplies = () => {
opts = {...opts, shouldHideReplies: !opts.shouldHideReplies}
}
const getSearch = definition => (getFeedArgs(definition)?.find(isSearchFeed)?.[1] as string) || ""
const onDraftFeedChange = definition => {
feed.definition = definition
}
const setFeedDefinition = definition => {
opts = {...opts, feed: definition}
search = getSearch(definition)
closeListMenu()
closeForm()
closeName()
}
const setSubFeed = subFeed => {
@ -94,23 +70,16 @@
}
const setList = list => {
feed = initFeed({
name: list.title,
list: list.address,
description: list.description,
definition: feedFromTags(Tags.fromEvent(list)),
identifier: decodeAddress(list.address).identifier,
})
feed = listAsFeed(list)
setFeedDefinition(feed.definition)
}
const saveFeed = async () => {
const relays = hints.WriteRelays().getUrls()
const template = feed.event ? editFeed(feed) : createFeed(feed)
const pub = await createAndPublish({...template, relays})
const saveFeed = event => {
if (feed.list) {
publishDeletion([feed.list])
}
setFeed(pub.request.event)
setFeed(event)
}
const onSearchBlur = debounce(500, () => {
@ -124,7 +93,6 @@
})
let formIsOpen = false
let nameIsOpen = false
let listMenuIsOpen = false
let feed = address
? readFeed(repository.getEvent(address))
@ -144,7 +112,10 @@
</Select>
<div class="flex flex-grow items-center justify-end gap-2">
<div class="flex">
<Input class="hidden h-7 bg-neutral-900 xs:block" on:input={onSearchBlur} bind:value={search}>
<Input
class="hidden h-7 rounded-r-none bg-neutral-900 xs:block"
on:input={onSearchBlur}
bind:value={search}>
<div slot="after" class="hidden text-white xs:block">
<i class="fa fa-search" />
</div>
@ -177,7 +148,9 @@
</MenuItem>
<div class="max-h-96 overflow-auto">
{#each $userFeeds as event}
<MenuItem on:click={() => setFeed(event)}>{readFeed(event).name}</MenuItem>
<MenuItem on:click={() => setFeed(event)}>
{displayFeed(readFeed(event))}
</MenuItem>
{/each}
{#each $userLists as list}
<MenuItem on:click={() => setList(list)}>{displayList2(list)}</MenuItem>
@ -193,33 +166,14 @@
{#if formIsOpen}
<Modal onEscape={closeForm}>
{#if event}
<Subheading>Edit {feed.name}</Subheading>
{:else}
<Subheading>Customize your feed</Subheading>
{/if}
<FeedField feed={feed.definition} onChange={onDraftFeedChange} />
<div class="flex justify-between gap-2">
<Anchor button on:click={closeForm}>Discard</Anchor>
<div class="flex gap-2">
<Anchor button on:click={openName}>Save Feed</Anchor>
<Anchor button accent on:click={() => setFeedDefinition(feed.definition)}>Done</Anchor>
<FeedForm hideType {feed} onSave={saveFeed}>
<div slot="controls" class="flex justify-between gap-2" let:save>
<Anchor button on:click={closeForm}>Discard</Anchor>
<div class="flex gap-2">
<Anchor button on:click={save}>Save Feed</Anchor>
<Anchor button accent on:click={() => setFeedDefinition(feed.definition)}>Done</Anchor>
</div>
</div>
</div>
</Modal>
{/if}
{#if nameIsOpen}
<Modal onEscape={closeName}>
<Field label="What would you like to name this feed?">
<Input bind:value={feed.name} />
</Field>
<Field label="How would you describe this feed?">
<Textarea bind:value={feed.description} />
</Field>
<div class="flex justify-between gap-2">
<Anchor button on:click={closeName}>Cancel</Anchor>
<Anchor button accent on:click={saveFeed}>Save</Anchor>
</div>
</FeedForm>
</Modal>
{/if}

View File

@ -1,20 +1,31 @@
<script lang="ts">
import {FeedType, makeIntersectionFeed, hasSubFeeds, getFeedArgs} from "@welshman/feeds"
import {identity, partition} from "@welshman/lib"
import {
FeedType,
makeIntersectionFeed,
hasSubFeeds,
getFeedArgs,
isAuthorFeed,
isScopeFeed,
isTagFeed,
isRelayFeed,
isDVMFeed,
makeDVMFeed,
makeScopeFeed,
makeTagFeed,
makeRelayFeed,
Scope,
} from "@welshman/feeds"
import Icon from "src/partials/Icon.svelte"
import SelectTiles from "src/partials/SelectTiles.svelte"
import Card from "src/partials/Card.svelte"
import Anchor from "src/partials/Anchor.svelte"
import FlexColumn from "src/partials/FlexColumn.svelte"
import Field from "src/partials/Field.svelte"
import FeedFormPeople from "src/app/shared/FeedFormPeople.svelte"
import FeedFormTopics from "src/app/shared/FeedFormTopics.svelte"
import FeedFormRelays from "src/app/shared/FeedFormRelays.svelte"
import FeedFormDVMs from "src/app/shared/FeedFormDVMs.svelte"
import FeedFormAdvanced from "src/app/shared/FeedFormAdvanced.svelte"
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
export let feed
export let onChange = null
export let hideType = false
enum FormType {
Advanced = "advanced",
@ -26,9 +37,25 @@
const normalize = feed => (isNormalized(feed) ? feed : [FeedType.Intersection, feed])
const isPeopleFeed = f => isAuthorFeed(f) || isScopeFeed(f)
const isTopicsFeed = f => isTagFeed(f) && f[1] === "#t"
const isNormalized = feed =>
feed[0] === FeedType.Intersection && getFeedArgs(feed).every(f => !hasSubFeeds(f))
const removeSubFeed = condition => {
feed = [feed[0], ...feed.slice(1).filter(f => !condition(f))]
}
const prependDefaultSubFeed = (condition, subFeed) => {
if (!getFeedArgs(feed).some(condition)) {
feed = feed.toSpliced(1, 0, subFeed)
}
feed = [feed[0], ...partition(condition, feed.slice(1)).flatMap(identity)]
}
const inferFormType = feed => {
for (const subFeed of getFeedArgs(normalize(feed))) {
if ([FeedType.Scope, FeedType.Author].includes(subFeed[0])) {
@ -61,8 +88,30 @@
feed = makeIntersectionFeed()
}
// Remove filters directly related to the previous type
if (formType === FormType.People) {
removeSubFeed(isPeopleFeed)
} else if (formType === FormType.Topics) {
removeSubFeed(isTopicsFeed)
} else if (formType === FormType.Relays) {
removeSubFeed(isRelayFeed)
} else if (formType === FormType.DVMs) {
removeSubFeed(isDVMFeed)
}
formType = newFormType
// Add a default filter depending on the new form type
if (formType === FormType.People) {
prependDefaultSubFeed(isPeopleFeed, makeScopeFeed(Scope.Follows))
} else if (formType === FormType.Topics) {
prependDefaultSubFeed(isTopicsFeed, makeTagFeed("#t"))
} else if (formType === FormType.Relays) {
prependDefaultSubFeed(isRelayFeed, makeRelayFeed())
} else if (formType === FormType.DVMs) {
prependDefaultSubFeed(isDVMFeed, makeDVMFeed({kind: 5300}))
}
onChange?.(feed)
}
@ -71,56 +120,60 @@
onChange?.(feed)
}
let innerWidth = 0
let formType = inferFormType(feed)
$: formTypeOptions = innerWidth < 640
? [FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs]
: [FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs, FormType.Advanced]
$: console.log(JSON.stringify(normalize(feed), null, 2))
</script>
<svelte:window bind:innerWidth />
<FlexColumn>
{#if !hideType}
<Card>
<Field label="Choose a feed type">
<SelectTiles
options={[FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs]}
onChange={onFormTypeChange}
value={formType}>
<div slot="item" class="flex flex-col items-center" let:option let:active>
{#if option === FormType.People}
<Icon
icon="people-nearby"
class="h-12 w-12"
color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">People</span>
{:else if option === FormType.Topics}
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
<i class="fa fa-2xl fa-tags" />
</span>
<span class="staatliches text-2xl">Topics</span>
{:else if option === FormType.Relays}
<Icon icon="server" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">Relays</span>
{:else if option === FormType.DVMs}
<Icon icon="network" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">DVMs</span>
{/if}
</div>
</SelectTiles>
</Field>
<div class="flex justify-end">
<Anchor underline on:click={() => onFormTypeChange(FormType.Advanced)}
>Advanced mode</Anchor>
</div>
</Card>
{/if}
{#if formType === FormType.People}
<FeedFormPeople feed={normalize(feed)} onChange={onFeedChange} />
{:else if formType === FormType.Topics}
<FeedFormTopics feed={normalize(feed)} onChange={onFeedChange} />
{:else if formType === FormType.Relays}
<FeedFormRelays feed={normalize(feed)} onChange={onFeedChange} />
{:else if formType === FormType.DVMs}
<FeedFormDVMs feed={normalize(feed)} onChange={onFeedChange} />
{:else if formType === FormType.Advanced}
<FeedFormAdvanced {feed} onChange={onFeedChange} />
{/if}
<Card class="-mb-8">
<FlexColumn small>
<span class="staatliches text-lg">Choose a feed type</span>
<SelectTiles
class="grid-cols-2 sm:grid-cols-5"
options={formTypeOptions}
onChange={onFormTypeChange}
value={formType}>
<div slot="item" class="flex flex-col items-center" let:option let:active>
{#if option === FormType.People}
<Icon
icon="people-nearby"
class="h-12 w-12"
color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">People</span>
{:else if option === FormType.Topics}
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
<i class="fa fa-2xl fa-tags" />
</span>
<span class="staatliches text-2xl">Topics</span>
{:else if option === FormType.Relays}
<Icon icon="server" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">Relays</span>
{:else if option === FormType.DVMs}
<Icon icon="network" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">DVMs</span>
{:else if option === FormType.Advanced}
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
<i class="fa fa-2xl fa-cogs" />
</span>
<span class="staatliches text-2xl">Advanced</span>
{/if}
</div>
</SelectTiles>
</FlexColumn>
</Card>
<FlexColumn>
{#if formType === FormType.Advanced}
<FeedFormAdvanced {feed} onChange={onFeedChange} />
{:else}
<FeedFormFilters {feed} onChange={onFeedChange} />
{/if}
</FlexColumn>
</FlexColumn>

View File

@ -0,0 +1,96 @@
<script lang="ts">
import Field from "src/partials/Field.svelte"
import {showInfo} from "src/partials/Toast.svelte"
import Subheading from "src/partials/Subheading.svelte"
import Modal from "src/partials/Modal.svelte"
import Card from "src/partials/Card.svelte"
import Input from "src/partials/Input.svelte"
import Textarea from "src/partials/Textarea.svelte"
import Anchor from "src/partials/Anchor.svelte"
import FeedField from "src/app/shared/FeedField.svelte"
import {router} from "src/app/util"
import {initFeed, createFeed, editFeed, displayFeed} from "src/domain"
import {publishDeletionForEvent, hints, createAndPublish} from "src/engine"
export let feed
export let onSave
const switchToCreate = () => {
feed = initFeed({definition: feed.definition})
saveIsOpen = true
}
const openDelete = () => {
deleteIsOpen = true
}
const closeDelete = () => {
deleteIsOpen = false
}
const confirmDelete = () => {
if (feed.event) {
publishDeletionForEvent(feed.event)
}
router.at("feeds").push()
}
const openSave = () => {
saveIsOpen = true
}
const closeSave = () => {
saveIsOpen = false
}
const saveFeed = async () => {
const relays = hints.WriteRelays().getUrls()
const template = feed.event ? editFeed(feed) : createFeed(feed)
const pub = await createAndPublish({...template, relays})
showInfo("Your feed has been saved!")
onSave(pub.request.event)
}
let saveIsOpen = false
let deleteIsOpen = false
$: isEdit = feed.event || feed.list
</script>
<FeedField bind:feed={feed.definition} />
{#if isEdit}
<Card class="flex flex-col sm:flex-row justify-between">
<p>You are currently editing your {displayFeed(feed)} feed.</p>
<Anchor underline on:click={switchToCreate} class="text-neutral-400">
Create a new feed instead
</Anchor>
</Card>
{:else if saveIsOpen}
<Modal onEscape={closeSave}>
<Field label="Feed Name">
<Input bind:value={feed.name} />
</Field>
<Field label="Feed Description">
<Textarea bind:value={feed.description} />
</Field>
<div class="flex justify-between gap-2">
<Anchor button on:click={closeSave}>Cancel</Anchor>
<Anchor button accent on:click={saveFeed}>Save</Anchor>
</div>
</Modal>
{/if}
<slot name="controls" {feed} remove={openDelete} save={isEdit ? saveFeed : openSave} />
{#if deleteIsOpen}
<Modal onEscape={closeDelete}>
<Subheading>Confirm deletion</Subheading>
<p>
Are you sure you want to delete your {displayFeed(feed)} feed?
</p>
<div class="flex justify-between gap-2">
<Anchor button on:click={closeDelete}>Cancel</Anchor>
<Anchor button danger on:click={confirmDelete}>Confirm</Anchor>
</div>
</Modal>
{/if}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import {tryCatch} from "@welshman/lib"
import Card from "src/partials/Card.svelte"
import Field from "src/partials/Field.svelte"
import Textarea from "src/partials/Textarea.svelte"
@ -33,17 +34,19 @@
let json = JSON.stringify(feed, null, 2)
</script>
<Field label="Enter your custom feed below">
<Textarea
class="h-72 whitespace-pre-wrap"
value={json}
on:input={onInput}
on:focus={onFocus}
on:blur={onBlur} />
</Field>
{#if !isValid && !isFocused}
<p>
<i class="fa fa-triangle-exclamation" />
Your feed is currently invalid. Please double check that it is valid JSON.
</p>
{/if}
<Card>
<Field label="Enter your custom feed below">
<Textarea
class="h-72 whitespace-pre-wrap"
value={json}
on:input={onInput}
on:focus={onFocus}
on:blur={onBlur} />
</Field>
{#if !isValid && !isFocused}
<p>
<i class="fa fa-triangle-exclamation" />
Your feed is currently invalid. Please double check that it is valid JSON.
</p>
{/if}
</Card>

View File

@ -1,16 +0,0 @@
<script lang="ts">
import {flatten, partition} from "@welshman/lib"
import {FeedType, getFeedArgs, isDVMFeed} from "@welshman/feeds"
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
export let feed
export let onChange
if (!getFeedArgs(feed).some(isDVMFeed)) {
onChange([...feed, [FeedType.DVM, {kind: 5300}]])
}
$: sorted = [feed[0], ...flatten(partition(isDVMFeed, getFeedArgs(feed)))]
</script>
<FeedFormFilters feed={sorted} {onChange} />

View File

@ -19,6 +19,7 @@
import Card from "src/partials/Card.svelte"
import Menu from "src/partials/Menu.svelte"
import MenuItem from "src/partials/MenuItem.svelte"
import FlexColumn from "src/partials/FlexColumn.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Popover2 from "src/partials/Popover2.svelte"
import FeedFormSectionPeople from "src/app/shared/FeedFormSectionPeople.svelte"
@ -68,23 +69,25 @@
{#each subFeeds as subFeed, i}
{@const change = f => onSubFeedChange(i + 1, f)}
<Card class="relative">
{#if isPeopleFeed(subFeed)}
<FeedFormSectionPeople feed={subFeed} onChange={change} />
{:else if isRelayFeed(subFeed)}
<FeedFormSectionRelays feed={subFeed} onChange={change} />
{:else if isTopicFeed(subFeed)}
<FeedFormSectionTopics feed={subFeed} onChange={change} />
{:else if isMentionFeed(subFeed)}
<FeedFormSectionMentions feed={subFeed} onChange={change} />
{:else if isKindFeed(subFeed)}
<FeedFormSectionKinds feed={subFeed} onChange={change} />
{:else if isCreatedAtFeed(subFeed)}
<FeedFormSectionCreatedAt feed={subFeed} onChange={change} />
{:else if isDVMFeed(subFeed)}
<FeedFormSectionDVM feed={subFeed} onChange={change} />
{:else}
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
{/if}
<FlexColumn small>
{#if isPeopleFeed(subFeed)}
<FeedFormSectionPeople feed={subFeed} onChange={change} />
{:else if isRelayFeed(subFeed)}
<FeedFormSectionRelays feed={subFeed} onChange={change} />
{:else if isTopicFeed(subFeed)}
<FeedFormSectionTopics feed={subFeed} onChange={change} />
{:else if isMentionFeed(subFeed)}
<FeedFormSectionMentions feed={subFeed} onChange={change} />
{:else if isKindFeed(subFeed)}
<FeedFormSectionKinds feed={subFeed} onChange={change} />
{:else if isCreatedAtFeed(subFeed)}
<FeedFormSectionCreatedAt feed={subFeed} onChange={change} />
{:else if isDVMFeed(subFeed)}
<FeedFormSectionDVM feed={subFeed} onChange={change} />
{:else}
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
{/if}
</FlexColumn>
{#if i > 0}
<div
class="absolute right-2 top-2 h-4 w-4 cursor-pointer"

View File

@ -1,18 +0,0 @@
<script lang="ts">
import {partition, identity} from "ramda"
import {FeedType, Scope, getFeedArgs, isAuthorFeed, isScopeFeed} from "@welshman/feeds"
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
export let feed
export let onChange
const isPeopleFeed = f => isAuthorFeed(f) || isScopeFeed(f)
if (!getFeedArgs(feed).some(isPeopleFeed)) {
onChange([...feed, [FeedType.Scope, Scope.Follows]])
}
$: sorted = [feed[0], ...partition(isPeopleFeed, feed.slice(1)).flatMap(identity)]
</script>
<FeedFormFilters feed={sorted} {onChange} />

View File

@ -1,17 +0,0 @@
<script lang="ts">
import {flatten, partition} from "@welshman/lib"
import {makeRelayFeed, getFeedArgs, isRelayFeed} from "@welshman/feeds"
import type {IntersectionFeed} from "@welshman/feeds"
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
export let feed: IntersectionFeed
export let onChange
if (!getFeedArgs(feed).some(isRelayFeed)) {
onChange([...feed, makeRelayFeed()])
}
$: sorted = [feed[0], ...flatten(partition(isRelayFeed, getFeedArgs(feed)))]
</script>
<FeedFormFilters feed={sorted} {onChange} />

View File

@ -2,7 +2,6 @@
import {omit} from "ramda"
import {FeedType} from "@welshman/feeds"
import {createLocalDate, dateToSeconds, formatTimestampAsDate} from "src/util/misc"
import Field from "src/partials/Field.svelte"
import DateInput from "src/partials/DateInput.svelte"
export let feed
@ -25,15 +24,14 @@
}
</script>
<Field label="What time range would you like to consider?">
<div class="grid grid-cols-2 gap-2">
<DateInput
placeholder="Since"
value={feed[1]?.since ? formatTimestampAsDate(feed[1]?.since) : null}
onChange={changeSince} />
<DateInput
placeholder="Until"
value={feed[1]?.until ? formatTimestampAsDate(feed[1]?.until) : null}
onChange={changeUntil} />
</div>
</Field>
<span class="staatliches text-lg">What time range would you like to consider?</span>
<div class="grid grid-cols-2 gap-2">
<DateInput
placeholder="Since"
value={feed[1]?.since ? formatTimestampAsDate(feed[1]?.since) : null}
onChange={changeSince} />
<DateInput
placeholder="Until"
value={feed[1]?.until ? formatTimestampAsDate(feed[1]?.until) : null}
onChange={changeUntil} />
</div>

View File

@ -1,14 +1,12 @@
<script lang="ts">
import {getFeedArgs} from "@welshman/feeds"
import Field from "src/partials/Field.svelte"
import DVMField from "src/app/shared/DVMField.svelte"
export let feed
export let onChange
</script>
<Field label="What options should be sent to the DVM?">
{#each getFeedArgs(feed) as dvmItem, i}
<DVMField {dvmItem} onChange={it => onChange(feed.toSpliced(i + 1, 1, it))} />
{/each}
</Field>
<span class="staatliches text-lg">What options should be sent to the DVM?</span>
{#each getFeedArgs(feed) as dvmItem, i}
<DVMField {dvmItem} onChange={it => onChange(feed.toSpliced(i + 1, 1, it))} />
{/each}

View File

@ -2,7 +2,6 @@
import {pluck} from "ramda"
import {FeedType} from "@welshman/feeds"
import {fuzzy} from "src/util/misc"
import Field from "src/partials/Field.svelte"
import SearchSelect from "src/partials/SearchSelect.svelte"
export let feed
@ -47,8 +46,7 @@
}
</script>
<Field label="What kind of content do you want to see?">
<SearchSelect multiple search={searchKinds} value={feed.slice(1)} onChange={onKindsChange}>
<div slot="item" let:item>{displayKind(item)}</div>
</SearchSelect>
</Field>
<span class="staatliches text-lg">What kind of content do you want to see?</span>
<SearchSelect multiple search={searchKinds} value={feed.slice(1)} onChange={onKindsChange}>
<div slot="item" let:item>{displayKind(item)}</div>
</SearchSelect>

View File

@ -1,6 +1,5 @@
<script lang="ts">
import {FeedType} from "@welshman/feeds"
import Field from "src/partials/Field.svelte"
import Anchor from "src/partials/Anchor.svelte"
import SearchSelect from "src/partials/SearchSelect.svelte"
import PersonBadge from "src/app/shared/PersonBadge.svelte"
@ -11,20 +10,19 @@
export let onChange
</script>
<Field label="Which mentions would you like to see?">
<SearchSelect
multiple
value={feed.slice(2)}
search={$searchPubkeys}
onChange={pubkeys => onChange([FeedType.Tag, "#p", ...pubkeys])}>
<span slot="item" let:item let:context>
{#if context === "value"}
<Anchor modal href={router.at("people").of(item).toString()}>
{displayPubkey(item)}
</Anchor>
{:else}
<PersonBadge inert pubkey={item} />
{/if}
</span>
</SearchSelect>
</Field>
<span class="staatliches text-lg">Which mentions would you like to see?</span>
<SearchSelect
multiple
value={feed.slice(2)}
search={$searchPubkeys}
onChange={pubkeys => onChange([FeedType.Tag, "#p", ...pubkeys])}>
<span slot="item" let:item let:context>
{#if context === "value"}
<Anchor modal href={router.at("people").of(item).toString()}>
{displayPubkey(item)}
</Anchor>
{:else}
<PersonBadge inert pubkey={item} />
{/if}
</span>
</SearchSelect>

View File

@ -2,7 +2,6 @@
import {toTitle} from "hurdak"
import {without} from "ramda"
import {FeedType, Scope, isScopeFeed, isAuthorFeed} from "@welshman/feeds"
import Field from "src/partials/Field.svelte"
import Anchor from "src/partials/Anchor.svelte"
import SelectButton from "src/partials/SelectButton.svelte"
import SearchSelect from "src/partials/SearchSelect.svelte"
@ -26,28 +25,28 @@
$: scopes = isScopeFeed(feed) ? feed.slice(1) : ["custom"]
</script>
<Field label="Which authors would you like to see?">
<SelectButton
<span class="staatliches text-lg">Which authors would you like to see?</span>
<SelectButton
multiple
value={scopes}
displayOption={toTitle}
options={scopeOptions}
onChange={onScopeChange} />
{#if isAuthorFeed(feed)}
<div class="h-px bg-neutral-900" />
<SearchSelect
multiple
value={scopes}
displayOption={toTitle}
options={scopeOptions}
onChange={onScopeChange} />
{#if isAuthorFeed(feed)}
<SearchSelect
multiple
value={feed.slice(1)}
search={$searchPubkeys}
onChange={pubkeys => onChange([FeedType.Author, ...pubkeys])}>
<span slot="item" let:item let:context>
{#if context === "value"}
<Anchor modal href={router.at("people").of(item).toString()}>
{displayPubkey(item)}
</Anchor>
{:else}
<PersonBadge inert pubkey={item} />
{/if}
</span>
</SearchSelect>
{/if}
</Field>
value={feed.slice(1)}
search={$searchPubkeys}
onChange={pubkeys => onChange([FeedType.Author, ...pubkeys])}>
<span slot="item" let:item let:context>
{#if context === "value"}
<Anchor modal href={router.at("people").of(item).toString()}>
{displayPubkey(item)}
</Anchor>
{:else}
<PersonBadge inert pubkey={item} />
{/if}
</span>
</SearchSelect>
{/if}

View File

@ -1,19 +1,17 @@
<script lang="ts">
import {FeedType} from '@welshman/feeds'
import Field from 'src/partials/Field.svelte'
import SearchSelect from 'src/partials/SearchSelect.svelte'
import {searchRelayUrls, displayRelayUrl} from 'src/engine'
import {FeedType} from "@welshman/feeds"
import SearchSelect from "src/partials/SearchSelect.svelte"
import {searchRelayUrls, displayRelayUrl} from "src/engine"
export let feed
export let onChange
</script>
<Field label="Which relays would you like to use?">
<SearchSelect
multiple
value={feed.slice(1)}
search={$searchRelayUrls}
onChange={urls => onChange([FeedType.Relay, ...urls])}>
<span slot="item" let:item>{displayRelayUrl(item)}</span>
</SearchSelect>
</Field>
<span class="staatliches text-lg">Which relays would you like to use?</span>
<SearchSelect
multiple
value={feed.slice(1)}
search={$searchRelayUrls}
onChange={urls => onChange([FeedType.Relay, ...urls])}>
<span slot="item" let:item>{displayRelayUrl(item)}</span>
</SearchSelect>

View File

@ -1,19 +1,17 @@
<script lang="ts">
import {FeedType} from '@welshman/feeds'
import Field from 'src/partials/Field.svelte'
import SearchSelect from 'src/partials/SearchSelect.svelte'
import {searchTopicNames} from 'src/engine'
import {FeedType} from "@welshman/feeds"
import SearchSelect from "src/partials/SearchSelect.svelte"
import {searchTopicNames} from "src/engine"
export let feed
export let onChange
</script>
<Field label="Which topics do you want to see?">
<SearchSelect
multiple
value={feed.slice(2)}
search={$searchTopicNames}
onChange={topics => onChange([FeedType.Tag, "#t", ...topics])}>
<span slot="item" let:item>#{item}</span>
</SearchSelect>
</Field>
<span class="staatliches text-lg">Which topics do you want to see?</span>
<SearchSelect
multiple
value={feed.slice(2)}
search={$searchTopicNames}
onChange={topics => onChange([FeedType.Tag, "#t", ...topics])}>
<span slot="item" let:item>#{item}</span>
</SearchSelect>

View File

@ -1,18 +0,0 @@
<script lang="ts">
import {partition, identity} from "ramda"
import {FeedType, getFeedArgs, isTagFeed} from "@welshman/feeds"
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
export let feed
export let onChange
const isTopicFeed = f => isTagFeed(f) && f[1] === "#t"
if (!getFeedArgs(feed).some(isTopicFeed)) {
onChange([...feed, [FeedType.Tag, "#t"]])
}
$: sorted = [feed[0], ...partition(isTopicFeed, feed.slice(1)).flatMap(identity)]
</script>
<FeedFormFilters feed={sorted} {onChange} />

View File

@ -0,0 +1,18 @@
<script lang="ts">
import {makeIntersectionFeed, makeScopeFeed, Scope} from '@welshman/feeds'
import {initFeed} from 'src/domain'
import Anchor from 'src/partials/Anchor.svelte'
import FeedForm from 'src/app/shared/FeedForm.svelte'
import {router} from 'src/app/util'
const feed = initFeed({definition: makeIntersectionFeed(makeScopeFeed(Scope.Follows))})
const exit = () => router.pop()
</script>
<FeedForm {feed} onSave={exit}>
<div slot="controls" let:save class="flex justify-between">
<Anchor button on:click={exit}>Cancel</Anchor>
<Anchor button accent on:click={save}>Save</Anchor>
</div>
</FeedForm>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import {Kind} from '@welshman/util'
import Anchor from 'src/partials/Anchor.svelte'
import FeedForm from 'src/app/shared/FeedForm.svelte'
import {router} from 'src/app/util'
import {readFeed, listAsFeed} from 'src/domain'
import {repository, lists} from 'src/engine'
export let address
const feed = address.startsWith(Kind.ListBookmarks)
? listAsFeed(lists.key(address).get())
: readFeed(repository.getEvent(address))
const exit = () => router.pop()
</script>
{#if feed}
<FeedForm {feed} onSave={exit}>
<div slot="controls" let:remove let:save class="flex justify-between">
<Anchor button on:click={exit}>Cancel</Anchor>
<div class="flex gap-2">
<Anchor button on:click={remove}>Delete</Anchor>
<Anchor button accent on:click={save}>Save</Anchor>
</div>
</div>
</FeedForm>
{:else}
<p class="text-center">Sorry, we weren't able to find that feed.</p>
{/if}

View File

@ -1,73 +0,0 @@
<script lang="ts">
import Card from "src/partials/Card.svelte"
import Field from "src/partials/Field.svelte"
import Subheading from "src/partials/Subheading.svelte"
import Modal from "src/partials/Modal.svelte"
import Input from "src/partials/Input.svelte"
import Textarea from "src/partials/Textarea.svelte"
import Anchor from "src/partials/Anchor.svelte"
import FeedField from "src/app/shared/FeedField.svelte"
import {router} from "src/app/util"
import {initFeed, readFeed, createFeed, editFeed} from "src/domain"
import {publishDeletionForEvent, repository, hints, createAndPublish} from "src/engine"
export let address = null
const values = address ? readFeed(repository.getEvent(address)) : initFeed()
const abort = () => router.pop()
const openDelete = () => {
deleteIsOpen = true
}
const closeDelete = () => {
deleteIsOpen = false
}
const confirmDelete = () => {
publishDeletionForEvent(event)
router.at("feeds").push()
}
const saveFeed = async () => {
const relays = hints.WriteRelays().getUrls()
const template = values.event ? editFeed(values) : createFeed(values)
await createAndPublish({...template, relays})
}
let deleteIsOpen = false
</script>
<FeedField hideType={address} bind:feed={values.definition} />
<Card>
<Field label="What would you like to name this feed?">
<Input bind:value={values.name} />
</Field>
<Field label="How would you describe this feed?">
<Textarea bind:value={values.description} />
</Field>
</Card>
<div class="flex justify-between gap-2">
<Anchor button on:click={abort}>Discard</Anchor>
<div class="flex justify-between gap-2">
{#if values.event}
<Anchor button danger on:click={openDelete}>Delete</Anchor>
{/if}
<Anchor button accent on:click={saveFeed}>Save</Anchor>
</div>
</div>
{#if deleteIsOpen}
<Modal onEscape={closeDelete}>
<Subheading>Confirm deletion</Subheading>
<p>
Are you sure you want to delete your "{values.name}" feed?
</p>
<div class="flex gap-2">
<Anchor button on:click={closeDelete}>Cancel</Anchor>
<Anchor button accent on:click={confirmDelete}>Confirm</Anchor>
</div>
</Modal>
{/if}

View File

@ -1,13 +1,11 @@
<script type="ts">
import {getAddress} from "@welshman/util"
import {fly} from "src/util/transition"
import Subheading from "src/partials/Subheading.svelte"
import FlexColumn from "src/partials/FlexColumn.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Card from "src/partials/Card.svelte"
import FeedSummary from "src/app/shared/FeedSummary.svelte"
import FeedCard from "src/app/shared/FeedCard.svelte"
import {router} from "src/app/util/router"
import {readFeed} from "src/domain"
import {userFeeds} from "src/engine"
import {userFeeds, userLists} from "src/engine"
const createFeed = () => router.at("feeds/create").open()
@ -22,28 +20,27 @@
</div>
{#each $userFeeds as event (getAddress(event))}
{@const address = getAddress(event)}
{@const {name, description, definition} = readFeed(event)}
<Card>
<FlexColumn>
<div class="flex items-center justify-between">
<span class="staatliches flex items-center gap-3 text-xl">
<i class="fa fa-rss" />
{#if name}
{name}
{:else}
<span class="text-neutral-500">No name</span>
{/if}
</span>
<div in:fly={{y: 20}}>
<FeedCard {address}>
<div slot="controls">
<Anchor on:click={() => editFeed(address)}>
<i class="fa fa-edit" /> Edit
</Anchor>
</div>
{#if description}
<p>{description}</p>
{/if}
<FeedSummary feed={definition} />
</FlexColumn>
</Card>
{:else}
<p class="text-center py-12">You don't have any feeds yet.</p>
</FeedCard>
</div>
{/each}
{#each $userLists as list}
<div in:fly={{y: 20}}>
<FeedCard address={list.address}>
<div slot="controls">
<Anchor on:click={() => editFeed(list.address)}>
<i class="fa fa-edit" /> Edit
</Anchor>
</div>
</FeedCard>
</div>
{/each}
{#if $userFeeds.length === 0 && $userLists.length === 0}
<p class="py-12 text-center">No feeds found.</p>
{/if}

View File

@ -1,7 +1,7 @@
import {fromPairs, randomId} from "@welshman/lib"
import {Kind, Tags} from "@welshman/util"
import {Kind, Tags, decodeAddress} from "@welshman/util"
import type {Rumor} from "@welshman/util"
import {makeIntersectionFeed, hasSubFeeds} from "@welshman/feeds"
import {makeIntersectionFeed, feedFromTags, hasSubFeeds} from "@welshman/feeds"
import type {Feed as IFeed} from "@welshman/feeds"
import {tryJson} from "src/util/misc"
@ -17,6 +17,16 @@ export type Feed = {
export const normalizeFeedDefinition = feed =>
hasSubFeeds(feed) ? feed : makeIntersectionFeed(feed)
// Compatibility with the old way we did custom feeds
export const listAsFeed = list =>
initFeed({
name: list.title,
list: list.address,
description: list.description,
definition: feedFromTags(Tags.fromEvent(list)),
identifier: decodeAddress(list.address).identifier,
})
export const initFeed = (feed: Partial<Feed> = {}): Feed => ({
name: "",
description: "",
@ -25,11 +35,15 @@ export const initFeed = (feed: Partial<Feed> = {}): Feed => ({
...feed,
})
export const readFeed = (event: Rumor): Feed => {
export const readFeed = (event: Rumor) => {
if (!event) {
return null
}
const {d: identifier, name = "", description = "", feed = ""} = fromPairs(event.tags)
const definition = tryJson(() => JSON.parse(feed)) || makeIntersectionFeed()
return {name, identifier, description, definition, event}
return {name, identifier, description, definition, event} as Feed
}
export const createFeed = ({identifier, definition, name, description}: Feed) => ({
@ -50,3 +64,5 @@ export const editFeed = (feed: Feed) => ({
.setTag("feed", JSON.stringify(feed.definition))
.unwrap(),
})
export const displayFeed = (feed: Feed) => (feed.name ? feed.name : "[no name]")

View File

@ -1,7 +1,6 @@
import {max, prop, omit, partition, equals} from "ramda"
import {sleep, pickVals} from "hurdak"
import {Worker, derived} from "@welshman/lib"
import type {Rumor} from "@welshman/util"
import {Worker} from "@welshman/lib"
import {createEvent, Kind, Relay, Repository} from "@welshman/util"
import {
Plex,
@ -14,6 +13,7 @@ import {
subscribe as baseSubscribe,
} from "@welshman/net"
import type {PublishRequest, SubscribeRequest} from "@welshman/net"
import logger from "src/util/logger"
import {LOCAL_RELAY_URL, isGiftWrap, generatePrivateKey} from "src/util/nostr"
import {env, pubkey} from "src/engine/session/state"
import {getSetting} from "src/engine/session/utils"
@ -22,7 +22,7 @@ import type {Event} from "src/engine/events/model"
import {publishes} from "src/engine/events/state"
import {LocalTarget} from "./targets"
export const repository = new Repository()
export const repository = new Repository({throttle: 300})
export const relay = new Relay(repository)
@ -33,20 +33,20 @@ export const projections = new Worker<Event>({
})
projections.addGlobalHandler(event => {
if (event.kind === Kind.Feed) {
const kinds = [
Kind.Delete,
Kind.Feed,
Kind.ListBookmarks,
]
if (kinds.includes(event.kind)) {
repository.publish(event)
}
})
export const allEvents = repository.throttle(300)
export const feeds = repository.filter(() => [{kinds: [Kind.Feed]}])
export const deriveEvents = filter => allEvents.derived(r => Array.from(r.query([filter])))
export const feeds = deriveEvents({kinds: [Kind.Feed]})
export const userFeeds = derived([feeds, pubkey], ([$feeds, $pubkey]) =>
$feeds.filter((e: Rumor) => e.pubkey === pubkey.get()),
)
export const userFeeds = repository.filter(() => [{kinds: [Kind.Feed], authors: [pubkey.get()]}])
export const getExecutor = (urls: string[]) => {
const muxUrl = getSetting("multiplextr_url")
@ -170,6 +170,8 @@ export const loadOne = (request: MySubscribeRequest) =>
export const publish = (request: PublishRequest) => {
const pub = basePublish(request)
logger.info(`Publishing event`, request)
// Make sure the event gets into projections asap
projections.push(request.event)

View File

@ -115,7 +115,7 @@
<div on:click|stopPropagation>
<AltColor
background
class="relative h-full w-full cursor-auto overflow-hidden rounded-t-2xl pb-20 pt-2">
class="relative h-full w-full cursor-auto rounded-t-2xl pb-20 pt-2">
<div class="modal-content-inner m-auto flex max-w-2xl flex-col gap-4 p-2">
<slot />
</div>

View File

@ -2,7 +2,6 @@
import cx from "classnames"
import {onMount} from "svelte"
import {reject, equals, identity} from "ramda"
import Chip from "src/partials/Chip.svelte"
import Input from "src/partials/Input.svelte"
import Popover2 from "src/partials/Popover2.svelte"
import Suggestions from "src/partials/Suggestions.svelte"
@ -120,11 +119,14 @@
{#if multiple}
<div class="text-sm">
{#each value as item}
<Chip class="mb-1 mr-1" onRemove={() => remove(item)}>
<div class="px-3 h-7 rounded-full mr-1 mb-1 bg-neutral-900 text-neutral-400 inline-flex items-center">
<div class="h-7 w-5 cursor-pointer flex items-center" on:click={() => remove(item)}>
<i class="fa fa-times" />
</div>
<slot name="item" context="value" {item}>
{displayItem(item)}
</slot>
</Chip>
</div>
{/each}
</div>
{/if}

View File

@ -4,16 +4,16 @@
export let displayOption = x => x
const getClassName = (i, active) =>
cx("px-4 py-2 transition-all", {
"border-l border-solid border-neutral-100": i > 0,
"bg-accent text-white": active,
const getClassName = active =>
cx("px-4 h-7 transition-all rounded-full mr-2 mb-2 inline-flex items-center", {
"bg-tinted-100 text-accent": active,
"bg-neutral-900 text-neutral-400": !active,
})
</script>
<div class="inline-block">
<SelectList {...$$props} class="inline-flex overflow-hidden rounded-full border border-solid border-neutral-100">
<div slot="item" let:i let:active let:option class={getClassName(i, active)}>
<div class="inline-block -mb-2">
<SelectList {...$$props} class="inline-flex staatliches">
<div slot="item" let:i let:active let:option class={getClassName(active)}>
{displayOption(option)}
</div>
</SelectList>

View File

@ -10,7 +10,7 @@
})
</script>
<SelectList {...$$props} class="grid grid-cols-{$$props.options.length} justify-between gap-4">
<SelectList {...$$props} class={cx("grid justify-between gap-4", $$props.class)}>
<div slot="item" let:i let:active let:option>
<Tile class={getClass(active)}>
<slot name="item" {option} {active} />

View File

@ -6,6 +6,7 @@ const colors = {
transparent: "transparent",
accent: "var(--accent)",
warning: "var(--warning)",
danger: "var(--danger)",
success: "var(--success)",
}