Finish space create form
29
src/app/components/InfoNip29.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import Link from '@lib/components/Link.svelte'
|
||||
import Icon from '@lib/components/Icon.svelte'
|
||||
import {clip} from '@app/toast'
|
||||
</script>
|
||||
|
||||
<div class="column gap-4">
|
||||
<h1 class="heading">What is a relay?</h1>
|
||||
<p>
|
||||
Flotilla hosts spaces on the <Link external href="https://nostr.com/">Nostr protocol</Link>.
|
||||
Nostr uses "relays" to host data, which are special-purpose servers that speak nostr's language.
|
||||
This means that anyone can host their own data, making the web more decentralized and resilient.
|
||||
</p>
|
||||
<p>
|
||||
Only some relays support spaces. You can find a list of suggested relays below,
|
||||
or you can <Link external href="https://coracle.tools">host your own</Link>.
|
||||
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">
|
||||
relay.whatever.com
|
||||
<button on:click={() => clip('relay.whatever.com')}>
|
||||
<Icon icon="copy" />
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-primary" on:click={() => history.back()}>
|
||||
Got it
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,25 +1,55 @@
|
||||
<script lang="ts">
|
||||
import InputProfilePicture from '@lib/components/InputProfilePicture.svelte'
|
||||
import Field from '@lib/components/Field.svelte'
|
||||
import Icon from '@lib/components/Icon.svelte'
|
||||
import InfoNip29 from '@app/components/InfoNip29.svelte'
|
||||
import {pushModal} from '@app/modal'
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const next = () => pushModal()
|
||||
|
||||
const showNip29Info = () => pushModal(InfoNip29)
|
||||
|
||||
let file: File
|
||||
let name = ""
|
||||
let relay = ""
|
||||
</script>
|
||||
|
||||
<div class="column gap-4">
|
||||
<h1 class="heading">Customize your Space</h1>
|
||||
<p class="text-center">
|
||||
Give people a few details to go. You can always change this later.
|
||||
Give people a few details to go on. You can always change this later.
|
||||
</p>
|
||||
<div class="flex justify-center py-2">
|
||||
<InputProfilePicture bind:file />
|
||||
</div>
|
||||
<Field>
|
||||
<p slot="label">Give your space a name:</p>
|
||||
<input slot="input" type="text" class="input input-bordered w-full max-w-xs" />
|
||||
<p slot="label">Space Name</p>
|
||||
<label class="input input-bordered w-full flex items-center gap-2" slot="input">
|
||||
<Icon icon="fire-minimalistic" />
|
||||
<input bind:value={name} class="grow" type="text" />
|
||||
</label>
|
||||
</Field>
|
||||
<Field>
|
||||
<p slot="label">Relay</p>
|
||||
<label class="input input-bordered w-full flex items-center gap-2" slot="input">
|
||||
<Icon icon="remote-controller-minimalistic" />
|
||||
<input bind:value={relay} class="grow" type="text" />
|
||||
</label>
|
||||
<p slot="info">
|
||||
This should be a NIP-29 compatible nostr relay where you'd like to host your space.
|
||||
<button class="text-primary underline cursor-pointer" on:click={showNip29Info}>
|
||||
More information
|
||||
</button>
|
||||
</p>
|
||||
</Field>
|
||||
<div class="flex flex-row justify-between items-center gap-4">
|
||||
<button class="btn btn-link" on:click={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
Go back
|
||||
</button>
|
||||
<button class="btn btn-primary" on:click={back}>
|
||||
<button class="btn btn-primary" on:click={next}>
|
||||
Next
|
||||
<Icon icon="alt-arrow-right" class="!bg-base-300" />
|
||||
</button>
|
||||
|
||||
14
src/app/components/Toast.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import {fly} from "@lib/transition"
|
||||
import {toast} from "@app/toast"
|
||||
</script>
|
||||
|
||||
{#if $toast}
|
||||
{#key $toast.id}
|
||||
<div transition:fly class="toast z-toast">
|
||||
<div role="alert" class="alert flex justify-center">
|
||||
{$toast.message}
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
@@ -1,5 +1,6 @@
|
||||
import {writable} from "svelte/store"
|
||||
import {randomId} from "@welshman/lib"
|
||||
import {copyToClipboard} from '@lib/html'
|
||||
|
||||
export type Toast = {
|
||||
id: string
|
||||
@@ -15,7 +16,7 @@ export const toast = writable<Toast | null>(null)
|
||||
|
||||
export const pushToast = (
|
||||
{message = "", id = randomId()}: Partial<Toast>,
|
||||
options: ToastOptions,
|
||||
options: ToastOptions = {},
|
||||
) => {
|
||||
toast.set({id, message, options})
|
||||
|
||||
@@ -25,3 +26,8 @@ export const pushToast = (
|
||||
}
|
||||
|
||||
export const popToast = (id: string) => toast.update($t => ($t?.id === id ? null : $t))
|
||||
|
||||
export const clip = (value: string) => {
|
||||
copyToClipboard(value)
|
||||
pushToast({message: "Copied to clipboard!"})
|
||||
}
|
||||
|
||||
4
src/assets/icons/Close Circle.svg
Normal 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="12" cy="12" r="10" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M14.4999 9.5L9.49997 14.5M9.49995 9.49998L14.4999 14.5" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 298 B |
4
src/assets/icons/Copy.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 11C6 8.17157 6 6.75736 6.87868 5.87868C7.75736 5 9.17157 5 12 5H15C17.8284 5 19.2426 5 20.1213 5.87868C21 6.75736 21 8.17157 21 11V16C21 18.8284 21 20.2426 20.1213 21.1213C19.2426 22 17.8284 22 15 22H12C9.17157 22 7.75736 22 6.87868 21.1213C6 20.2426 6 18.8284 6 16V11Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M6 19C4.34315 19 3 17.6569 3 16V10C3 6.22876 3 4.34315 4.17157 3.17157C5.34315 2 7.22876 2 11 2H15C16.6569 2 18 3.34315 18 5" stroke="#1C274C" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 597 B |
BIN
src/assets/icons/Fire Minimalistic.png
Normal file
|
After Width: | Height: | Size: 668 B |
3
src/assets/icons/Fire Minimalistic.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 21C16.4183 21 20 17.6439 20 13.504C20 9.76257 17.9654 6.83811 16.562 5.44436C16.3017 5.18584 15.8683 5.30006 15.7212 5.63288C14.9742 7.3229 13.4178 9.75607 11.4286 9.75607C10.1975 9.92086 8.31688 8.86844 9.83483 3.64868C9.97151 3.17868 9.46972 2.80113 9.08645 3.11539C6.9046 4.90436 4 8.51143 4 13.504C4 17.6439 7.58172 21 12 21Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 485 B |
5
src/assets/icons/Gallery Send.svg
Normal 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="M22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M2 12.5001L3.75159 10.9675C4.66286 10.1702 6.03628 10.2159 6.89249 11.0721L11.1822 15.3618C11.8694 16.0491 12.9512 16.1428 13.7464 15.5839L14.0446 15.3744C15.1888 14.5702 16.7369 14.6634 17.7765 15.599L21 18.5001" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M17 2V11M17 2L20 5M17 2L14 5" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 776 B |
5
src/assets/icons/Info Circle.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M12 17V11" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="1" cy="1" r="1" transform="matrix(1 0 0 -1 11 9)" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 332 B |
6
src/assets/icons/Remote Controller Minimalistic.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 9C5 6.19108 5 4.78661 5.67412 3.77772C5.96596 3.34096 6.34096 2.96596 6.77772 2.67412C7.78661 2 9.19108 2 12 2C14.8089 2 16.2134 2 17.2223 2.67412C17.659 2.96596 18.034 3.34096 18.3259 3.77772C19 4.78661 19 6.19108 19 9V15C19 17.8089 19 19.2134 18.3259 20.2223C18.034 20.659 17.659 21.034 17.2223 21.3259C16.2134 22 14.8089 22 12 22C9.19108 22 7.78661 22 6.77772 21.3259C6.34096 21.034 5.96596 20.659 5.67412 20.2223C5 19.2134 5 17.8089 5 15V9Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M15 11C15 12.6569 13.6569 14 12 14C10.3431 14 9 12.6569 9 11C9 9.34315 10.3431 8 12 8C13.6569 8 15 9.34315 15 11Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="5" r="1" fill="#1C274C"/>
|
||||
<circle cx="12" cy="17" r="1" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 854 B |
10
src/assets/icons/Wi-Fi Router Round.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 15C2 12.2386 4.23858 10 7 10H17C19.7614 10 22 12.2386 22 15C22 17.7614 19.7614 20 17 20H7C4.23858 20 2 17.7614 2 15Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M18.3292 22.3354C18.5144 22.7059 18.9649 22.8561 19.3354 22.6708C19.7059 22.4856 19.8561 22.0351 19.6708 21.6646L18.3292 22.3354ZM17.3292 20.3354L18.3292 22.3354L19.6708 21.6646L18.6708 19.6646L17.3292 20.3354Z" fill="#1C274C"/>
|
||||
<path d="M5.67082 22.3354C5.48558 22.7059 5.03507 22.8561 4.66459 22.6708C4.29411 22.4856 4.14394 22.0351 4.32918 21.6646L5.67082 22.3354ZM6.67082 20.3354L5.67082 22.3354L4.32918 21.6646L5.32918 19.6646L6.67082 20.3354Z" fill="#1C274C"/>
|
||||
<path d="M8.5 15C8.5 15.8284 7.82843 16.5 7 16.5C6.17157 16.5 5.5 15.8284 5.5 15C5.5 14.1716 6.17157 13.5 7 13.5C7.82843 13.5 8.5 14.1716 8.5 15Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M12 15H18.5" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M21.5833 5.39702C20.7574 3.40286 18.7924 2 16.4996 2C14.2069 2 12.2419 3.40286 11.416 5.39702" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M19.3806 6.65811C19.0167 5.41107 17.865 4.5 16.5004 4.5C15.1358 4.5 13.984 5.41107 13.6201 6.65811" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M17.5 7C17.5 7.55228 17.0523 8 16.5 8C15.9477 8 15.5 7.55228 15.5 7C15.5 6.44772 15.9477 6 16.5 6C17.0523 6 17.5 6.44772 17.5 7Z" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -13,14 +13,21 @@
|
||||
import AltArrowRight from "@assets/icons/Alt Arrow Right.svg?dataurl"
|
||||
import AltArrowLeft from "@assets/icons/Alt Arrow Left.svg?dataurl"
|
||||
import ClipboardText from "@assets/icons/Clipboard Text.svg?dataurl"
|
||||
import CloseCircle from "@assets/icons/Close Circle.svg?dataurl"
|
||||
import Copy from "@assets/icons/Copy.svg?dataurl"
|
||||
import Compass from "@assets/icons/Compass.svg?dataurl"
|
||||
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 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 Plain from "@assets/icons/Plain.svg?dataurl"
|
||||
import RemoteControllerMinimalistic from "@assets/icons/Remote Controller Minimalistic.svg?dataurl"
|
||||
import Settings from "@assets/icons/Settings.svg?dataurl"
|
||||
import UFO3 from "@assets/icons/UFO 3.svg?dataurl"
|
||||
import UserHeart from "@assets/icons/User Heart.svg?dataurl"
|
||||
import WiFiRouterRound from "@assets/icons/Wi-Fi Router Round.svg?dataurl"
|
||||
|
||||
export let icon
|
||||
export let size = 5
|
||||
@@ -33,14 +40,21 @@
|
||||
"alt-arrow-right": AltArrowRight,
|
||||
"alt-arrow-left": AltArrowLeft,
|
||||
"clipboard-text": ClipboardText,
|
||||
"close-circle": CloseCircle,
|
||||
copy: Copy,
|
||||
compass: Compass,
|
||||
"compass-big": CompassBig,
|
||||
"fire-minimalistic": FireMinimalistic,
|
||||
"gallery-send": GallerySend,
|
||||
"hand-pills": HandPills,
|
||||
"home-smile": HomeSmile,
|
||||
"info-circle": InfoCircle,
|
||||
plain: Plain,
|
||||
'remote-controller-minimalistic': RemoteControllerMinimalistic,
|
||||
settings: Settings,
|
||||
"ufo-3": UFO3,
|
||||
"user-heart": UserHeart,
|
||||
"wifi-router-round": WiFiRouterRound,
|
||||
})
|
||||
|
||||
if (!data) {
|
||||
|
||||
86
src/lib/components/InputProfilePicture.svelte
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import {randomId} from '@welshman/lib'
|
||||
import Icon from '@lib/components/Icon.svelte'
|
||||
|
||||
export let file: File | null = null
|
||||
export let url: string | null = null
|
||||
|
||||
const id = randomId()
|
||||
|
||||
const onDragEnter = () => {
|
||||
active = true
|
||||
}
|
||||
|
||||
const onDragOver = () => {
|
||||
active = true
|
||||
}
|
||||
|
||||
const onDragLeave = () => {
|
||||
active = false
|
||||
}
|
||||
|
||||
const onDrop = (e: any) => {
|
||||
active = false
|
||||
|
||||
file = e.dataTransfer.files[0]
|
||||
}
|
||||
|
||||
const onChange = (e: any) => {
|
||||
file = e.target.files[0]
|
||||
}
|
||||
|
||||
const onClear = () => {
|
||||
initialUrl = null
|
||||
file = null
|
||||
url = null
|
||||
}
|
||||
|
||||
let active = false
|
||||
let initialUrl = url
|
||||
|
||||
$: {
|
||||
if (file) {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.addEventListener("load", () => { url = reader.result as string }, false)
|
||||
reader.readAsDataURL(file)
|
||||
} else {
|
||||
url = initialUrl
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form>
|
||||
<input {id} type="file" accept="image/*" on:change={onChange} class="hidden" />
|
||||
<label
|
||||
for={id}
|
||||
aria-label="Drag and drop files here."
|
||||
style="background-image: url({url});"
|
||||
class="relative flex justify-center items-center w-24 h-24 rounded-full border-2 border-dashed border-base-content transition-all bg-base-300 cursor-pointer bg-cover bg-center shrink-0"
|
||||
class:border-primary={active}
|
||||
on:dragenter|preventDefault|stopPropagation={onDragEnter}
|
||||
on:dragover|preventDefault|stopPropagation={onDragOver}
|
||||
on:dragleave|preventDefault|stopPropagation={onDragLeave}
|
||||
on:drop|preventDefault|stopPropagation={onDrop}>
|
||||
<div
|
||||
class="bg-primary right-0 top-0 absolute rounded-full overflow-hidden"
|
||||
class:bg-error={file}
|
||||
class:bg-primary={!file}>
|
||||
{#if file}
|
||||
<span
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
on:mousedown|stopPropagation={onClear}
|
||||
on:touchstart|stopPropagation={onClear}>
|
||||
<Icon icon="close-circle" class="!bg-base-300 scale-150" />
|
||||
</span>
|
||||
{:else}
|
||||
<Icon icon="add-circle" class="!bg-base-300 scale-150" />
|
||||
{/if}
|
||||
</div>
|
||||
{#if !file}
|
||||
<Icon icon="gallery-send" size={7} />
|
||||
{/if}
|
||||
</label>
|
||||
</form>
|
||||
15
src/lib/components/Link.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import cx from 'classnames'
|
||||
|
||||
export let href
|
||||
export let external = false
|
||||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
{...$$props}
|
||||
class={cx($$props.class, "cursor-pointer underline text-primary")}
|
||||
rel={external ? "noopener noreferer" : ""}
|
||||
target={external ? "_blank" : ""}>
|
||||
<slot />
|
||||
</a>
|
||||
15
src/lib/html.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const copyToClipboard = (text: string) => {
|
||||
const {activeElement} = document
|
||||
const input = document.createElement("textarea")
|
||||
|
||||
input.innerHTML = text
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
|
||||
const result = document.execCommand("copy")
|
||||
|
||||
document.body.removeChild(input)
|
||||
;(activeElement as HTMLElement).focus()
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
import "@src/app.css"
|
||||
import {page} from "$app/stores"
|
||||
import {fly} from "@lib/transition"
|
||||
import {toast} from "@app/toast"
|
||||
import {modals} from "@app/modal"
|
||||
import ModalBox from "@lib/components/ModalBox.svelte"
|
||||
import Toast from "@app/components/Toast.svelte"
|
||||
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
||||
import SecondaryNav from "@app/components/SecondaryNav.svelte"
|
||||
import {modals} from "@app/modal"
|
||||
|
||||
const login = async () => {
|
||||
const nl = await import("nostr-login")
|
||||
@@ -43,23 +43,16 @@
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<dialog bind:this={modal} class="modal modal-bottom sm:modal-middle">
|
||||
<dialog bind:this={modal} class="modal modal-bottom sm:modal-middle !z-modal">
|
||||
{#if $page.state.modal}
|
||||
{#key $page.state.modal}
|
||||
<ModalBox {...modals.get($page.state.modal)} />
|
||||
{/key}
|
||||
<Toast />
|
||||
{/if}
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button />
|
||||
</form>
|
||||
</dialog>
|
||||
{#if $toast}
|
||||
{#key $toast.id}
|
||||
<div transition:fly class="toast">
|
||||
<div class="alert">
|
||||
<span>{$toast.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
<Toast />
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,8 @@ export default {
|
||||
none: 0,
|
||||
"nav-active": 1,
|
||||
"nav-item": 2,
|
||||
"modal": 3,
|
||||
"toast": 4,
|
||||
},
|
||||
},
|
||||
plugins: [daisyui],
|
||||
|
||||