design improvement and consistency
This commit is contained in:
parent
6ee0809661
commit
f70f4c4518
12 changed files with 135 additions and 110 deletions
|
@ -63,31 +63,6 @@
|
|||
width: 100%;
|
||||
padding: var(--spacing-md);
|
||||
|
||||
> :global(.title) {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
width: calc(100% + 2 * var(--spacing-md));
|
||||
margin: calc(var(--spacing-md) * -1);
|
||||
padding: var(--spacing-md);
|
||||
background: color-mix(in oklab, black 50%, var(--accent));
|
||||
z-index: 99999;
|
||||
|
||||
> :global(.close-button) {
|
||||
position: absolute;
|
||||
top: var(--spacing-md);
|
||||
right: calc(
|
||||
var(--spacing-md) * 2 + 1.5rem
|
||||
);
|
||||
}
|
||||
|
||||
:global(& + *) {
|
||||
margin-top: calc(
|
||||
1.5rem +
|
||||
var(--spacing-md) * 3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
> :global(*:not(:last-child)) {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
bind:open={isAddingFolder}
|
||||
onClose={newFolderDialogClosed}
|
||||
>
|
||||
<div class="title">
|
||||
<div class="section-title">
|
||||
<h3>Create Folder</h3>
|
||||
</div>
|
||||
<p class="text-secondary">Please enter a name for the new folder:</p>
|
||||
|
@ -63,7 +63,7 @@
|
|||
<hr />
|
||||
{/if}
|
||||
{/each}
|
||||
<IconButton icon="eos-icons:plus" onclick={() => isAddingFolder = true} text="Add Folder" />
|
||||
<IconButton icon="pajamas:folder-new" onclick={() => isAddingFolder = true} text="Add Folder" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { activeUser, ndk } from '$lib/stores.svelte';
|
||||
import { activeUser, ndk, pageIcon, pageTitle } from '$lib/stores.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { NDKNip07Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||
|
@ -71,6 +71,8 @@
|
|||
onMount(() => {
|
||||
if ($ndk.activeUser)
|
||||
activeUser.set($ndk.activeUser);
|
||||
$pageTitle = 'Login';
|
||||
$pageIcon = 'line-md:login';
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
import Select from './Select.svelte';
|
||||
import type { Letter } from '$lib/letter';
|
||||
import { slide } from 'svelte/transition';
|
||||
import IconButton from './IconButton.svelte';
|
||||
|
||||
let {
|
||||
letters,
|
||||
foldersList = $bindable<{ id: string; name: string; icon: string }[]>(),
|
||||
folder = $bindable<{ id: string; name: string; icon: string }>()
|
||||
folder = $bindable<{ id: string; name: string; icon: string }>(),
|
||||
moveFolderDialogOpen = $bindable<boolean>()
|
||||
} = $props();
|
||||
|
||||
function formatRange(start, end) {
|
||||
|
@ -83,7 +83,6 @@
|
|||
});
|
||||
|
||||
let selectedLetters = $state<string[]>([]);
|
||||
let moveFolderDialogOpen = $state(false);
|
||||
let moveFolderName = $state('');
|
||||
|
||||
function clickedLetter(letter: Letter) {
|
||||
|
@ -125,7 +124,7 @@
|
|||
</div>
|
||||
{#if letter.stamps}
|
||||
<div class="stamps-count">
|
||||
<Icon icon="emojione-v1:stamped-envelope" />
|
||||
<Icon icon="fxemoji:stampedenvelope" />
|
||||
{letter.stamps} sats
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -140,10 +139,10 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class="letter-meta">
|
||||
<Icon icon="mdi:calendar" />
|
||||
<Icon icon="ph:calendar-duotone" />
|
||||
{getReadableDate(letter.date)}
|
||||
|
||||
<Icon icon="mdi:clock-outline" />
|
||||
<Icon icon="ph:clock-duotone" />
|
||||
{getReadableTime(letter.date)}
|
||||
</div>
|
||||
<div class="letter-preview">{letter.preview}</div>
|
||||
|
@ -156,7 +155,7 @@
|
|||
<Dialog
|
||||
bind:open={moveFolderDialogOpen}
|
||||
>
|
||||
<div class="title">
|
||||
<div class="section-title">
|
||||
<h3>Move Letters to Folder</h3>
|
||||
</div>
|
||||
<p class="text-secondary">Select a folder to move the selected letters to:</p>
|
||||
|
@ -181,7 +180,7 @@
|
|||
</div>
|
||||
{#if letter.stamps}
|
||||
<div class="stamps-count">
|
||||
<Icon icon="emojione-v1:stamped-envelope" />
|
||||
<Icon icon="fxemoji:stampedenvelope" />
|
||||
{letter.stamps} sats
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -201,14 +200,6 @@
|
|||
</div>
|
||||
</Dialog>
|
||||
|
||||
<h2 id="page-title">
|
||||
{folder.name}
|
||||
</h2>
|
||||
|
||||
<div class="actions">
|
||||
<IconButton icon="mdi:folder-move-outline" onclick={() => moveFolderDialogOpen = true} text="Move Letters" />
|
||||
</div>
|
||||
|
||||
<main class="letter-list">
|
||||
{#if $groupByStamps}
|
||||
{#each groupedInbox as [range, letters]}
|
||||
|
@ -317,12 +308,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -33,6 +33,6 @@
|
|||
<div class="notification-message">{@render children?.()}</div>
|
||||
</div>
|
||||
<button class="close-button" onclick={hide}>
|
||||
<Icon icon="mdi:close" />
|
||||
<Icon icon="si:close-circle-duotone" />
|
||||
</button>
|
||||
</div>
|
|
@ -49,16 +49,18 @@
|
|||
--glass-border: color-mix(in srgb, white 12%, transparent);
|
||||
--glass-shadow: rgb(0 0 0 / 0.1);
|
||||
--glass-glow: oklch(67% 0.2 var(--base-hue) / 0.15);
|
||||
--depth-shadow: 0 8px 32px -8px rgb(0 0 0 / 0.3);
|
||||
--depth-shadow: 0 10px 30px -10px hsl(var(--base-hue) 30% 5% / 0.3),
|
||||
0 4px 6px -4px hsl(var(--base-hue) 30% 5% / 0.2);
|
||||
--noise-filter: url("data:image/svg+xml,%3Csvg width='1000' height='1000' viewBox='0 0 1000 1000' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='4' stitchTiles='stitch' seed='2' result='noise'/%3E%3CfeColorMatrix type='saturate' values='0' in='noise' result='desaturatedNoise'/%3E%3CfeBlend in='SourceGraphic' in2='desaturatedNoise' mode='overlay' result='blend'/%3E%3CfeComposite operator='in' in2='SourceGraphic'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E#noiseFilter");
|
||||
--gradient-shine: linear-gradient(
|
||||
135deg,
|
||||
transparent 25%,
|
||||
rgb(255 255 255 / 0.1) 50%,
|
||||
transparent 75%
|
||||
transparent 20%,
|
||||
hsl(var(--base-hue) 100% 100% / 0.1) 40%,
|
||||
transparent 60%
|
||||
);
|
||||
--base-hue: 200;
|
||||
--accent: oklch(67% 0.2 var(--base-hue));
|
||||
--ternary: oklch(25% 0.3 var(--base-hue));
|
||||
--accent-light: oklch(75% 0.2 var(--base-hue));
|
||||
--text-primary: oklch(100% 0 0);
|
||||
--text-secondary: oklch(100% 0 0 / 0.7);
|
||||
|
@ -478,3 +480,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
width: calc(100%);
|
||||
margin: calc(var(--spacing-md) * -1);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--ternary);
|
||||
border: 1px solid var(--accent);
|
||||
z-index: 99999;
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
place-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
|
||||
> .close-button {
|
||||
position: absolute;
|
||||
top: var(--spacing-md);
|
||||
right: calc(
|
||||
var(--spacing-md) * 2 + 1.5rem
|
||||
);
|
||||
}
|
||||
|
||||
& + * {
|
||||
margin-top: calc(
|
||||
1.5rem +
|
||||
var(--spacing-md) * 3
|
||||
);
|
||||
}
|
||||
}
|
|
@ -46,6 +46,9 @@ export const timeFormat = writable(
|
|||
browser ? localStorage.getItem('timeFormat') || 'h:m:s' : 'h:m:s'
|
||||
);
|
||||
|
||||
export let pageTitle = writable('loading');
|
||||
export let pageIcon = writable('');
|
||||
|
||||
if (browser) {
|
||||
baseHue.subscribe((value) => {
|
||||
document.documentElement.style.setProperty('--base-hue', value.toString());
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { activeUser, baseHue, ndk } from '$lib/stores.svelte';
|
||||
import { activeUser, baseHue, ndk, pageIcon, pageTitle } from '$lib/stores.svelte';
|
||||
import LoginWithNostr from '../components/LoginWithNostr.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { NDKNip07Signer } from '@nostr-dev-kit/ndk';
|
||||
import IconButton from '../components/IconButton.svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
onMount(async () => {
|
||||
if (localStorage.getItem('useNip07')) {
|
||||
|
@ -35,8 +38,15 @@
|
|||
</header>
|
||||
|
||||
<div id="content">
|
||||
<div class="section-title">
|
||||
{#if $pageIcon}
|
||||
<Icon width="1.5em" icon={$pageIcon} />
|
||||
{/if}
|
||||
<h3>{$pageTitle}</h3>
|
||||
</div>
|
||||
|
||||
{#if $activeUser}
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
{:else}
|
||||
<LoginWithNostr />
|
||||
{/if}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { ndk } from '$lib/stores.svelte';
|
||||
import { ndk, pageIcon, pageTitle } from '$lib/stores.svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
|
||||
import { decryptSealedMessageIntoReadableType } from '$lib/utils.svelte';
|
||||
|
@ -9,6 +9,7 @@
|
|||
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
|
||||
import IconButton from '../components/IconButton.svelte';
|
||||
import FoldersListSidebar from '../components/FoldersListSidebar.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let sidebarOpen = $state(false);
|
||||
|
||||
|
@ -78,13 +79,25 @@
|
|||
function changeFolder(newFolder: string) {
|
||||
letters = [];
|
||||
activeFolder = newFolder;
|
||||
$pageTitle = folders.find(f => f.id === newFolder)!.name;
|
||||
$pageIcon = folders.find(f => f.id === newFolder)!.icon;
|
||||
decryptMessages($messages);
|
||||
}
|
||||
|
||||
let moveFolderDialogOpen = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
$pageTitle = 'Inbox';
|
||||
$pageIcon = 'solar:inbox-bold-duotone';
|
||||
});
|
||||
</script>
|
||||
|
||||
<IconButton icon="mynaui:sidebar-solid" onclick={() => sidebarOpen = !sidebarOpen} text="Sidebar" />
|
||||
<div class="actions">
|
||||
<IconButton icon="mynaui:sidebar-solid" onclick={() => sidebarOpen = !sidebarOpen} text="Sidebar" />
|
||||
<IconButton icon="mdi:folder-move-outline" onclick={() => moveFolderDialogOpen = true} text="Move Letters" />
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="content" class:sidebar-open={sidebarOpen}>
|
||||
<FoldersListSidebar bind:folders bind:sidebarOpen {changeFolder} />
|
||||
|
||||
<div class="folder-view">
|
||||
|
@ -95,7 +108,8 @@
|
|||
If you have letters, they will appear here, please wait...
|
||||
</p>
|
||||
{:else}
|
||||
<MailboxFolderItems foldersList={folders} folder={folders.find(f => f.id === activeFolder)} {letters} />
|
||||
<MailboxFolderItems bind:moveFolderDialogOpen foldersList={folders}
|
||||
folder={folders.find(f => f.id === activeFolder)} {letters} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,10 +117,21 @@
|
|||
<style>
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
transition: gap 0.3s ease;
|
||||
|
||||
&.sidebar-open {
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.folder-view {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import RecipientChip from '../../components/RecipientChip.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { ndk } from '$lib/stores.svelte';
|
||||
import { ndk, pageIcon, pageTitle } from '$lib/stores.svelte';
|
||||
import Notification from '../../components/Notification.svelte';
|
||||
import { createCarbonCopyLetter, createSealedLetter } from '$lib/utils.svelte';
|
||||
import type { NDKUser } from '@nostr-dev-kit/ndk';
|
||||
|
@ -19,6 +19,11 @@
|
|||
let tokenInfo = $state<TokenInfoWithMailSubscriptionDuration>();
|
||||
let tokenInfoError = $state('');
|
||||
|
||||
$effect(() => {
|
||||
if (isSending) $pageTitle = 'Sending...';
|
||||
else $pageTitle = 'New Letter';
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
try {
|
||||
tokenInfo = new TokenInfoWithMailSubscriptionDuration(stamp);
|
||||
|
@ -111,6 +116,8 @@
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
$pageTitle = 'New Letter';
|
||||
$pageIcon = 'proicons:compose';
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has('replyTo'))
|
||||
replyTo = urlParams.get('replyTo');
|
||||
|
@ -129,19 +136,8 @@
|
|||
</Notification>
|
||||
{/if}
|
||||
|
||||
{#if isSending}
|
||||
{#if !isSending}
|
||||
<div class="compose-container">
|
||||
<div class="compose-header">
|
||||
<div id="page-title">Sending Letter...</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="compose-container">
|
||||
<div class="compose-header">
|
||||
<div id="page-title">New Message</div>
|
||||
<button class="button">Close</button>
|
||||
</div>
|
||||
|
||||
<div class="compose-fields">
|
||||
<div class="field">
|
||||
<label>To:</label>
|
||||
|
@ -152,7 +148,6 @@
|
|||
{/each}
|
||||
<input
|
||||
bind:value={toField}
|
||||
class="input"
|
||||
onkeyup={recepientInput}
|
||||
placeholder="Enter npub or nip05..."
|
||||
type="text"
|
||||
|
@ -175,9 +170,9 @@
|
|||
<p class="error">
|
||||
{tokenInfoError}
|
||||
</p>
|
||||
{:else if tokenInfo}
|
||||
{:else if tokenInfo && stamp.length > 0}
|
||||
<p class="stamp-count">
|
||||
<Icon icon="emojione-v1:stamped-envelope" /> {tokenInfo.amount} sats
|
||||
<Icon icon="fxemoji:stampedenvelope" /> {tokenInfo.amount} sats
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -200,14 +195,6 @@
|
|||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.compose-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: var(--spacing-md);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.compose-fields {
|
||||
display: grid;
|
||||
gap: var(--spacing-sm);
|
||||
|
@ -216,7 +203,12 @@
|
|||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
place-items: end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
padding: var(--spacing-md);
|
||||
|
||||
& .to {
|
||||
display: flex;
|
||||
|
@ -227,6 +219,7 @@
|
|||
}
|
||||
|
||||
& label {
|
||||
font-weight: bolder;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
|
@ -267,20 +260,8 @@
|
|||
}
|
||||
|
||||
@layer components {
|
||||
.input {
|
||||
input {
|
||||
flex-grow: 1;
|
||||
background: transparent;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--input-focus);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.send-button {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</div>
|
||||
{:else if error}
|
||||
<div class="error-state">
|
||||
<Icon icon="eos-icons:close" width="5em" />
|
||||
<Icon icon="si:close-circle-duotone" width="5em" />
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
{:else if letter}
|
||||
|
@ -63,7 +63,11 @@
|
|||
<div class="header-main">
|
||||
<div class="header-info">
|
||||
<div class="subject">
|
||||
<Icon icon="mdi:letter-outline" />
|
||||
{#if letter.emailAddress}
|
||||
<Icon icon="mdi:email-lock" width="32" />
|
||||
{:else}
|
||||
<img src="/nostr-lock.svg" width="32" />
|
||||
{/if}
|
||||
<h1>{letter.subject}</h1>
|
||||
</div>
|
||||
|
||||
|
@ -86,7 +90,7 @@
|
|||
<div class="actions">
|
||||
{#if letter.stamp}
|
||||
<div onclick={() => stampDialogOpen = true} class="stamp-count button">
|
||||
<Icon icon="emojione-v1:stamped-envelope" />
|
||||
<Icon icon="fxemoji:stampedenvelope" />
|
||||
<span>{letter.stamp.amount} sats</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -100,10 +104,10 @@
|
|||
|
||||
<div class="metadata">
|
||||
<div class="timestamp">
|
||||
<Icon icon="mdi:calendar" />
|
||||
<Icon icon="ph:calendar-duotone" />
|
||||
<span>{getReadableDate(letter.date)}</span>
|
||||
<div class="separator" />
|
||||
<Icon icon="mdi:clock-outline" />
|
||||
<Icon icon="ph:clock-duotone" />
|
||||
<span>{getReadableTime(letter.date)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,7 +118,7 @@
|
|||
|
||||
<Dialog bind:open={stampDialogOpen}>
|
||||
{#if letter && letter.stamp}
|
||||
<div class="title">
|
||||
<div class="section-title">
|
||||
<h3>Bitcoin Stamp</h3>
|
||||
<button class="close-button" onclick={() => stampDialogOpen = false}>Close</button>
|
||||
</div>
|
||||
|
@ -127,7 +131,7 @@
|
|||
<hr />
|
||||
|
||||
<div class="stamp-count">
|
||||
<Icon icon="emojione-v1:stamped-envelope" />
|
||||
<Icon icon="fxemoji:stampedenvelope" />
|
||||
<span>{letter.stamp.amount} sats</span>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { activeUser, baseHue, groupByStamps, ndk, sortBy, validSortOptions } from '$lib/stores.svelte';
|
||||
import {
|
||||
activeUser,
|
||||
baseHue,
|
||||
groupByStamps,
|
||||
ndk,
|
||||
pageIcon,
|
||||
pageTitle,
|
||||
sortBy,
|
||||
validSortOptions
|
||||
} from '$lib/stores.svelte';
|
||||
import ColorPicker from '../../components/ColorPicker.svelte';
|
||||
import SettingsLine from '../../components/SettingsLine.svelte';
|
||||
import Checkbox from '../../components/Checkbox.svelte';
|
||||
|
@ -166,13 +175,13 @@
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
$pageTitle = 'Settings';
|
||||
$pageIcon = 'si:settings-cute-line';
|
||||
await reloadAliases();
|
||||
await reloadSubscriptionStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<h2 id="page-title">Settings</h2>
|
||||
|
||||
<SettingsLine title="Your Nostr Public Key">
|
||||
<code>{$activeUser?.npub}</code>
|
||||
</SettingsLine>
|
||||
|
|
Loading…
Add table
Reference in a new issue