design improvement and consistency

This commit is contained in:
Danny Morabito 2024-11-29 15:53:44 +01:00
parent 6ee0809661
commit f70f4c4518
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
12 changed files with 135 additions and 110 deletions

View file

@ -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);
}

View file

@ -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>

View file

@ -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>

View file

@ -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)}
&nbsp;
<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>

View file

@ -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>

View file

@ -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
);
}
}

View file

@ -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());

View file

@ -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}

View file

@ -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>

View file

@ -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 {

View file

@ -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>

View file

@ -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>