Add cypress tests

This commit is contained in:
Jonathan Staab 2023-10-12 16:16:38 -07:00
parent 744f42a65f
commit bc88d341d4
29 changed files with 1022 additions and 246 deletions

7
cypress.config.ts Normal file
View File

@ -0,0 +1,7 @@
import {defineConfig} from "cypress"
export default defineConfig({
e2e: {
baseUrl: "http://localhost:5173",
},
})

21
cypress/e2e/feed.cy.ts Normal file
View File

@ -0,0 +1,21 @@
describe("feed interaction", () => {
beforeEach(() => {
cy.visit("/")
})
it("clicking a card works", () => {
cy.get(".cy-note-click-target:first").click()
cy.get(".modal .fa-heart")
cy.get(".modal .cy-modal-close").click()
cy.get(".modal").should("not.exist")
})
it("feed controls works", () => {
cy.get(".fa-sliders").click()
cy.get(".modal .cy-chip:first .fa-times").click()
cy.get(".modal").contains("Apply Filters").click()
cy.contains("Kinds 30023,")
cy.get(".card", {timeout: 30000})
})
})

12
cypress/e2e/login.cy.ts Normal file
View File

@ -0,0 +1,12 @@
describe("authenticated usage", () => {
beforeEach(() => {
cy.login()
})
it("works", () => {
cy.visit("/")
cy.get("svg.logo").click()
cy.get(".card").contains("Profile").click()
cy.get(".cy-person-name").contains("test account 12938740")
})
})

10
cypress/e2e/search.cy.ts Normal file
View File

@ -0,0 +1,10 @@
describe("search", () => {
it("works", () => {
cy.visit("/")
cy.get(".cy-top-nav .fa-search").click()
cy.get(".cy-top-nav input").type("hodlbod")
cy.get(".modal").contains("hodlbod").click()
cy.get(".modal").contains("following")
cy.get(".modal").contains("followers")
})
})

23
cypress/e2e/signup.cy.ts Normal file
View File

@ -0,0 +1,23 @@
describe("signup", () => {
beforeEach(() => {
cy.visit("/")
})
it("works", () => {
cy.get(".cy-top-nav").contains("Log In").click()
cy.get(".modal").contains("Sign Up").click()
cy.get(".modal").contains("Let's go!").click()
cy.get(".modal [name=name]").type("9sd2j3e0sd")
cy.get(".modal").contains("Continue").click()
cy.get(".modal").contains("Got it").click()
cy.get(".modal").contains("Continue").click()
cy.get(".modal").contains("Continue").click()
cy.wait(1000)
cy.get(".modal").contains("Skip and see your feed").click()
cy.get(".modal").should("not.exist")
cy.contains("From follows")
cy.get("svg.logo").click()
cy.get(".card").contains("Profile").click()
cy.get(".cy-person-name").contains("9sd2j3e0sd")
})
})

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -0,0 +1,29 @@
const nsec = "nsec1er8narhat3xjypf46pksxlr6k4jrmv2e9psazjk0adynly0l4ltsepvmy5"
Cypress.Commands.add("login", () => {
cy.session(
nsec,
() => {
cy.visit("/login/privkey")
cy.get("input[type=password]").type(nsec, {log: false})
cy.get(".cy-login-submit").click()
cy.get(".modal", {timeout: 10_000}).should("not.exist")
cy.contains("Don't have an account?").should("not.exist")
},
{
validate: () => {
let pubkey
cy.window()
.then(w => {
pubkey = w.pubkey.get()
})
.then(() => {
expect(pubkey).to.equal(
"c853d879b7376dab1cdcd4faf235a05f680aae42ba620abdd95d619542a5a379"
)
})
},
}
)
})

20
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands"
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -23,6 +23,7 @@
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0",
"autoprefixer": "^10.4.13",
"cypress": "^13.3.1",
"eslint": "^8.33.0",
"eslint-plugin-svelte3": "^4.0.0",
"postcss": "^8.4.19",
@ -55,6 +56,7 @@
"insane": "^2.6.2",
"lru-cache": "^7.18.3",
"marked": "^5.1.0",
"normalize-url": "^8.0.0",
"nostr-tools": "^1.12.1",
"npm-run-all": "^4.1.5",
"paravel": "^0.3.7",

View File

@ -2,7 +2,7 @@
import "@fortawesome/fontawesome-free/css/fontawesome.css"
import "@fortawesome/fontawesome-free/css/solid.css"
import {nip19} from "nostr-tools"
import {nip19, getPublicKey, generatePrivateKey} from "nostr-tools"
import {pluck} from "ramda"
import {seconds, Fetch} from "hurdak"
import {tryFetch, hexToBech32, bech32ToHex, now} from "src/util/misc"
@ -223,7 +223,15 @@
// Globals
Object.assign(window, {...engine, nip19, bech32ToHex, hexToBech32, router})
Object.assign(window, {
...engine,
nip19,
bech32ToHex,
hexToBech32,
router,
getPublicKey,
generatePrivateKey,
})
// Theme

View File

@ -156,8 +156,8 @@
}} />
<div
class="fixed top-0 z-10 flex h-16 w-full items-center justify-between border-b
border-gray-6 bg-gray-7 px-2 text-gray-2">
class="cy-top-nav fixed top-0 z-10 flex h-16 w-full items-center justify-between
border-b border-gray-6 bg-gray-7 px-2 text-gray-2">
<div>
<div class="app-logo flex cursor-pointer items-center gap-2" on:click={toggleMenu}>
<!-- <img alt="App Logo" src={logoUrl} class="w-10" /> -->
@ -183,7 +183,7 @@
<div
class={cx(
"search-input pointer-events-none fixed top-0 z-10 w-full px-2 text-gray-1",
"flex h-16 items-center justify-end gap-4",
"cy-top-nav flex h-16 items-center justify-end gap-4",
{
"pr-16": $session,
"pr-28": !$session,

View File

@ -223,7 +223,7 @@
</div>
{#if muted}
<p class="border-l-2 border-solid border-gray-6 pl-4 text-gray-1">
You have muted this note.
You have hidden this note.
<Anchor
theme="anchor"
on:click={() => {
@ -233,10 +233,12 @@
{:else}
<NoteContent {anchorId} note={event} {showEntire} />
{/if}
<div class="cy-note-click-target h-px" />
<NoteActions
note={event}
bind:this={actions}
{removeFromContext}
{showMuted}
{replies}
{likes}
{zaps}

View File

@ -37,6 +37,7 @@
export let note: Event
export let reply
export let showMuted
export let showEntire
export let removeFromContext
export let replies
@ -102,7 +103,7 @@
let showDetails = false
let actions = []
$: disableActions = !$canSign || $muted
$: disableActions = !$canSign || ($muted && !showMuted)
$: like = like || find(propEq("pubkey", $session?.pubkey), likes)
$: allLikes = like ? likes.filter(n => n.id !== like?.id).concat(like) : likes
$: $likesCount = allLikes.length

View File

@ -79,7 +79,7 @@
{:else if quote}
{#if muted}
<p class="mb-1 py-24 text-center text-gray-5">
You have muted this note.
You have hidden this note.
<Anchor class="underline" on:click={unmute}>Show</Anchor>
</p>
{:else}

View File

@ -30,7 +30,7 @@
</script>
<div class={cx("flex items-center gap-1", $$props.class)}>
<span>{displayPerson($person)}</span>
<span class="cy-person-name">{displayPerson($person)}</span>
<div class="flex items-center gap-1 font-normal">
{#if $following}
<span class="px-2 py-1 text-xs">

View File

@ -15,13 +15,13 @@
loginWithExtension(await ext.getPublicKey())
boot()
} else {
router.at("login/privkey").open()
router.at("login/privkey").replaceModal()
}
})
const signUp = () => router.at("onboarding").open()
const signUp = () => router.at("onboarding").replaceModal()
const advancedLogIn = () => router.at("login/advanced").open()
const advancedLogIn = () => router.at("login/advanced").replaceModal()
document.title = "Log In"
</script>

View File

@ -131,13 +131,15 @@
<Spinner />
{:else if Object.values(currentRelays).length > 0}
<p>Currently searching:</p>
{#each Object.values(currentRelays) as relay}
<div class="h-12">
{#if relay}
<RelayCard hideActions relay={{...relay, description: null}} />
{/if}
</div>
{/each}
<Content gap="gap-4" size="inherit">
{#each Object.values(currentRelays) as relay}
<div class="h-12">
{#if relay}
<RelayCard hideActions relay={{...relay, description: null}} />
{/if}
</div>
{/each}
</Content>
{/if}
</Content>

View File

@ -33,7 +33,7 @@
<i slot="before" class="fa fa-key" />
</Input>
</div>
<Anchor theme="button" on:click={logIn}>Log In</Anchor>
<Anchor theme="button" class="cy-login-submit" on:click={logIn}>Log In</Anchor>
</div>
{#if !Capacitor.isNativePlatform()}
<p class="rounded border-2 border-solid border-warning bg-gray-8 px-6 py-3">

View File

@ -1,8 +1,9 @@
<script lang="ts">
import {onMount} from "svelte"
import {generatePrivateKey} from "nostr-tools"
import {closure} from "hurdak"
import {closure, sleep} from "hurdak"
import {fly} from "src/util/transition"
import Content from "src/partials/Content.svelte"
import OnboardingIntro from "src/app/views/OnboardingIntro.svelte"
import OnboardingProfile from "src/app/views/OnboardingProfile.svelte"
import OnboardingKey from "src/app/views/OnboardingKey.svelte"
@ -62,6 +63,13 @@
})
const signup = async noteContent => {
// Go to our home page
router.at("notes").push()
// Make things async since the `key` change in App.svelte prevents the modal
// animation from completing, and it gets stuck. This is a svelte bug
await sleep(10)
loginWithPrivateKey(privkey)
// Do this first so we know where to publish everything else
@ -78,12 +86,6 @@
// Start our notifications listener
listenForNotifications()
// Close all modals
router.clearModals()
// Go to our home page
router.at("notes").push()
}
onMount(() => {
@ -94,18 +96,20 @@
{#key stage}
<div in:fly={{y: 20}}>
{#if stage === "intro"}
<OnboardingIntro {setStage} />
{:else if stage === "profile"}
<OnboardingProfile {setStage} {profile} />
{:else if stage === "key"}
<OnboardingKey {setStage} {privkey} />
{:else if stage === "relays"}
<OnboardingRelays {setStage} bind:relays />
{:else if stage === "follows"}
<OnboardingFollows {setStage} bind:petnames />
{:else if stage === "note"}
<OnboardingNote {setStage} {signup} />
{/if}
<Content size="lg">
{#if stage === "intro"}
<OnboardingIntro {setStage} />
{:else if stage === "profile"}
<OnboardingProfile {setStage} {profile} />
{:else if stage === "key"}
<OnboardingKey {setStage} {privkey} />
{:else if stage === "relays"}
<OnboardingRelays {setStage} bind:relays />
{:else if stage === "follows"}
<OnboardingFollows {setStage} bind:petnames />
{:else if stage === "note"}
<OnboardingNote {setStage} {signup} />
{/if}
</Content>
</div>
{/key}

View File

@ -3,7 +3,6 @@
import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
import PersonSummary from "src/app/shared/PersonSummary.svelte"
import type {Person} from "src/engine"
import {env, mention, loadPeople, searchPeople} from "src/engine"
@ -29,58 +28,54 @@
$: results = reject((p: Person) => pubkeys.includes(p.pubkey), $searchPeople(q))
</script>
<Content>
<Content class="text-center">
<Heading>Find Your People</Heading>
<p>
To get you started, weve added some interesting people to your follow list. You can update
your follows list at any time.
</p>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>
</Content>
<div class="flex items-center gap-2">
<i class="fa fa-user-astronaut fa-lg" />
<h2 class="staatliches text-2xl">Your follows</h2>
<Heading class="text-center">Find Your People</Heading>
<p>
To get you started, weve added some interesting people to your follow list. You can update your
follows list at any time.
</p>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>
<div class="flex items-center gap-2">
<i class="fa fa-user-astronaut fa-lg" />
<h2 class="staatliches text-2xl">Your follows</h2>
</div>
{#if pubkeys.length === 0}
<div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No follows selected</span>
</div>
{#if pubkeys.length === 0}
<div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No follows selected</span>
</div>
{:else}
{#each pubkeys as pubkey (pubkey)}
<PersonSummary {pubkey}>
<div slot="actions" class="flex items-start justify-end">
<Anchor
theme="button"
class="flex items-center gap-2"
on:click={() => removeFollow(pubkey)}>
<i class="fa fa-user-slash" /> Unfollow
</Anchor>
</div>
</PersonSummary>
{/each}
{/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other people</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each results.slice(0, 50) as profile (profile.pubkey)}
<PersonSummary pubkey={profile.pubkey}>
{:else}
{#each pubkeys as pubkey (pubkey)}
<PersonSummary {pubkey}>
<div slot="actions" class="flex items-start justify-end">
<Anchor
theme="button-accent"
theme="button"
class="flex items-center gap-2"
on:click={() => addFollow(profile.pubkey)}>
<i class="fa fa-user-plus" /> Follow
on:click={() => removeFollow(pubkey)}>
<i class="fa fa-user-slash" /> Unfollow
</Anchor>
</div>
</PersonSummary>
{/each}
</Content>
{/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other people</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each results.slice(0, 50) as profile (profile.pubkey)}
<PersonSummary pubkey={profile.pubkey}>
<div slot="actions" class="flex items-start justify-end">
<Anchor
theme="button-accent"
class="flex items-center gap-2"
on:click={() => addFollow(profile.pubkey)}>
<i class="fa fa-user-plus" /> Follow
</Anchor>
</div>
</PersonSummary>
{/each}

View File

@ -1,7 +1,6 @@
<script lang="ts">
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
export let setStage
@ -10,16 +9,14 @@
const next = () => setStage("profile")
</script>
<Content size="lg" class="text-center">
<Heading>Create an Account</Heading>
<p>
New to Nostr? Click <Anchor class="underline" external href={tutorialUrl}>here</Anchor> or watch
the video below for a crash course on what it is, and how to use it.
</p>
<video controls src={videoUrl} class="object-contain object-center" />
<p>
When youre ready to dive in, click below and well guide you through the process of creating an
account.
</p>
<Anchor theme="button-accent" on:click={next}>Let's go!</Anchor>
</Content>
<Heading class="text-center">Create an Account</Heading>
<p>
New to Nostr? Click <Anchor class="underline" external href={tutorialUrl}>here</Anchor> or watch the
video below for a crash course on what it is, and how to use it.
</p>
<video controls src={videoUrl} class="object-contain object-center" />
<p>
When youre ready to dive in, click below and well guide you through the process of creating an
account.
</p>
<Anchor theme="button-accent" class="text-center" on:click={next}>Let's go!</Anchor>

View File

@ -5,7 +5,6 @@
import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
import {env} from "src/engine"
export let privkey
@ -21,19 +20,17 @@
}
</script>
<Content size="lg" class="text-center">
<Heading>Generate a Key</Heading>
<p>
Your private key is your password, and gives you total control over your Nostr account. We've
generated a fresh one for you below store it somewhere safe!
</p>
<Input disabled placeholder={"•".repeat(53)} wrapperClass="flex-grow">
<i slot="before" class="fa fa-lock" />
<button slot="after" class="fa fa-copy cursor-pointer" on:click={copyKey} />
</Input>
<p>If you don't want to save your keys now, you can find them later in {appName}'s settings.</p>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Got it</Anchor>
</div>
</Content>
<Heading class="text-center">Generate a Key</Heading>
<p>
Your private key is your password, and gives you total control over your Nostr account. We've
generated a fresh one for you below store it somewhere safe!
</p>
<Input disabled placeholder={"•".repeat(53)} wrapperClass="flex-grow">
<i slot="before" class="fa fa-lock" />
<button slot="after" class="fa fa-copy cursor-pointer" on:click={copyKey} />
</Input>
<p>If you don't want to save your keys now, you can find them later in {appName}'s settings.</p>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Got it</Anchor>
</div>

View File

@ -3,7 +3,6 @@
import Compose from "src/app/shared/Compose.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
export let signup
export let setStage
@ -19,20 +18,18 @@
})
</script>
<Content size="lg">
<Heading class="text-center">Welcome to Nostr</Heading>
<p class="text-center">
Your're all set! If have any questions, just ask! People around these parts are always ready to
lend a hand.
</p>
<div class="border-l-2 border-solid border-gray-6 pl-4">
<Compose bind:this={compose} onSubmit={next} />
</div>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Say Hello!</Anchor>
</div>
<Anchor class="text-center" on:click={skip}>
Skip and see your feed <i class="fa fa-arrow-right" />
</Anchor>
</Content>
<Heading class="text-center">Welcome to Nostr</Heading>
<p>
Your're all set! If have any questions, just ask! People around these parts are always ready to
lend a hand.
</p>
<div class="border-l-2 border-solid border-gray-6 pl-4">
<Compose bind:this={compose} onSubmit={next} />
</div>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Say Hello!</Anchor>
</div>
<Anchor class="text-center" on:click={skip}>
Skip and see your feed <i class="fa fa-arrow-right" />
</Anchor>

View File

@ -4,7 +4,6 @@
import ImageInput from "src/partials/ImageInput.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte"
import Content from "src/partials/Content.svelte"
export let profile
export let setStage
@ -13,35 +12,28 @@
const next = () => setStage("key")
</script>
<Content size="lg">
<Heading class="text-center">Introduce Yourself</Heading>
<p class="text-center">
Give other people something to go on. Remember that "privacy is the power to selectively reveal
oneself to the world".
</p>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-1">
<strong>Your Name</strong>
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={profile.name}>
<i slot="before" class="fa-solid fa-user-astronaut" />
</Input>
</div>
<div class="flex flex-col gap-1">
<strong>About You</strong>
<Textarea name="about" bind:value={profile.about} />
</div>
<div class="flex flex-col gap-1">
<strong>Profile Picture</strong>
<ImageInput
bind:value={profile.picture}
icon="image-portrait"
maxWidth={480}
maxHeight={480} />
<p class="text-sm text-gray-1">Please be mindful of others and only use small images.</p>
</div>
<Heading class="text-center">Introduce Yourself</Heading>
<p>
Give other people something to go on. Remember that "privacy is the power to selectively reveal
oneself to the world".
</p>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-1">
<strong>Your Name</strong>
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={profile.name}>
<i slot="before" class="fa-solid fa-user-astronaut" />
</Input>
</div>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
<div class="flex flex-col gap-1">
<strong>About You</strong>
<Textarea name="about" bind:value={profile.about} />
</div>
</Content>
<div class="flex flex-col gap-1">
<strong>Profile Picture</strong>
<ImageInput bind:value={profile.picture} icon="image-portrait" maxWidth={480} maxHeight={480} />
</div>
</div>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>

View File

@ -34,52 +34,50 @@
)
</script>
<Content>
<div class="text-center">
<Heading>Get Connected</Heading>
<p>
Nostr is a protocol, not a platform. This means that <i>you</i> choose where to store your data.
Select your preferred storage relays below, or click "continue" to use some reasonable defaults.
You can change your selection any time.
</p>
<Heading class="text-center">Get Connected</Heading>
<p>
Nostr is a protocol, not a platform. This means that <i>you</i> choose where to store your data.
</p>
<p>
Select your preferred storage relays below, or click "continue" to use some reasonable defaults.
You can change your selection any time.
</p>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>
<div class="flex items-center gap-2">
<i class="fa fa-server fa-lg" />
<h2 class="staatliches text-2xl">Your relays</h2>
</div>
{#if relays.length === 0}
<div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No relays connected</span>
</div>
<div class="flex gap-2">
<Anchor theme="button" on:click={prev}><i class="fa fa-arrow-left" /></Anchor>
<Anchor theme="button-accent" class="flex-grow text-center" on:click={next}>Continue</Anchor>
</div>
<div class="flex items-center gap-2">
<i class="fa fa-server fa-lg" />
<h2 class="staatliches text-2xl">Your relays</h2>
</div>
{#if relays.length === 0}
<div class="mt-8 flex items-center justify-center gap-2 text-center">
<i class="fa fa-triangle-exclamation" />
<span>No relays connected</span>
</div>
{:else}
<div class="grid grid-cols-1 gap-4">
{#each relays as relay (relay.url)}
<RelayCard {relay}>
<div slot="actions">
{#if relays.length > 1}
<button
class="flex items-center gap-3 text-gray-1"
on:click={() => removeRelay(relay)}>
<i class="fa fa-right-from-bracket" /> Leave
</button>
{/if}
</div>
</RelayCard>
{/each}
</div>
{/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other relays</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
{:else}
<Content gap="gap-4" size="inherit">
{#each relays as relay (relay.url)}
<RelayCard {relay}>
<div slot="actions">
{#if relays.length > 1}
<button class="flex items-center gap-3 text-gray-1" on:click={() => removeRelay(relay)}>
<i class="fa fa-right-from-bracket" /> Leave
</button>
{/if}
</div>
</RelayCard>
{/each}
</Content>
{/if}
<div class="flex items-center gap-2">
<i class="fa fa-earth-asia fa-lg" />
<h2 class="staatliches text-2xl">Other relays</h2>
</div>
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Type to search">
<i slot="before" class="fa-solid fa-search" />
</Input>
<Content gap="gap-4" size="inherit">
{#each (search(q) || []).slice(0, 50) as relay (relay.url)}
<RelayCard {relay}>
<div slot="actions">
@ -89,8 +87,8 @@
</div>
</RelayCard>
{/each}
<small class="text-center">
Showing {Math.min($knownRelays.length - relays.length, 50)}
of {$knownRelays.length - relays.length} known relays
</small>
</Content>
<small class="text-center">
Showing {Math.min($knownRelays.length - relays.length, 50)}
of {$knownRelays.length - relays.length} known relays
</small>

View File

@ -4,10 +4,14 @@
export let theme = "dark"
export let onRemove = null
const className = cx($$props.class, "inline-block rounded-full border border-solid py-1 px-2", {
"border-gray-1": theme === "dark",
"border-gray-8": theme === "light",
})
const className = cx(
$$props.class,
"inline-block rounded-full border border-solid py-1 px-2 cy-chip",
{
"border-gray-1": theme === "dark",
"border-gray-8": theme === "light",
}
)
</script>
<div class={className}>

View File

@ -66,7 +66,7 @@
<div
class="pointer-events-auto flex h-10 w-10 cursor-pointer items-center justify-center rounded-full
border border-solid border-accent-light bg-accent text-white transition-colors hover:bg-accent-light">
<i class="fa fa-times fa-lg" />
<i class="fa fa-times fa-lg cy-modal-close" />
</div>
{#if $modals.length > 1 && index > 0}
<div

View File

@ -3,6 +3,7 @@
"include": ["src/**/*"],
"compilerOptions": {
"baseUrl": ".",
"module": "esnext",
"paths": {
"src/*": ["src/*"]
},

697
yarn.lock

File diff suppressed because it is too large Load Diff