clean up code and ui
- IconButton component - make FoldersListSidebar a separate component from the index page - make move letter to folder better looking - clean up code on index page
This commit is contained in:
parent
4b528e07a6
commit
6ee0809661
6 changed files with 204 additions and 102 deletions
93
src/components/FoldersListSidebar.svelte
Normal file
93
src/components/FoldersListSidebar.svelte
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Icon from '@iconify/svelte';
|
||||||
|
import IconButton from './IconButton.svelte';
|
||||||
|
import Dialog from './Dialog.svelte';
|
||||||
|
import { createFolder } from '$lib/utils.svelte';
|
||||||
|
|
||||||
|
let { sidebarOpen = $bindable(), folders = $bindable(), changeFolder } = $props<{
|
||||||
|
sidebarOpen: boolean;
|
||||||
|
folders: { id: string; name: string; icon: string }[];
|
||||||
|
changeFolder: (folder: string) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let isAddingFolder = $state(false);
|
||||||
|
let newFolderName = $state('');
|
||||||
|
|
||||||
|
async function addNewFolderPressed() {
|
||||||
|
const newFolder = await createFolder(newFolderName, '');
|
||||||
|
if (!newFolder) return;
|
||||||
|
await newFolder.publish();
|
||||||
|
folders = [...folders, { id: newFolder.id, name: newFolderName, icon: 'eos-icons:plus' }];
|
||||||
|
newFolderName = '';
|
||||||
|
isAddingFolder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newFolderDialogClosed() {
|
||||||
|
isAddingFolder = false;
|
||||||
|
newFolderName = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:open={isAddingFolder}
|
||||||
|
onClose={newFolderDialogClosed}
|
||||||
|
>
|
||||||
|
<div class="title">
|
||||||
|
<h3>Create Folder</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-secondary">Please enter a name for the new folder:</p>
|
||||||
|
|
||||||
|
<form onsubmit={addNewFolderPressed}>
|
||||||
|
<input
|
||||||
|
autofocus
|
||||||
|
bind:value={newFolderName}
|
||||||
|
placeholder="Folder name"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button onclick={newFolderDialogClosed} type="button">Cancel</button>
|
||||||
|
<button type="submit">OK</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<div class="sidebar-container" class:open={sidebarOpen}>
|
||||||
|
<div class="glass sidebar">
|
||||||
|
{#each folders as folder}
|
||||||
|
<button class="folder-item" onclick={() => changeFolder(folder.id)}>
|
||||||
|
<Icon icon={folder.icon} />
|
||||||
|
{folder.name}
|
||||||
|
</button>
|
||||||
|
{#if folder.id === 'trash'}
|
||||||
|
<hr />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
<IconButton icon="eos-icons:plus" onclick={() => isAddingFolder = true} text="Add Folder" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sidebar-container {
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: width 0.3s ease-in-out;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
width: clamp(16rem, 20vw, 20rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
30
src/components/IconButton.svelte
Normal file
30
src/components/IconButton.svelte
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Icon from '@iconify/svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
let { icon, text, href, onclick } = $props<{
|
||||||
|
icon: string;
|
||||||
|
text: string;
|
||||||
|
onclick?: () => void;
|
||||||
|
href?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
if (href)
|
||||||
|
onclick = () => {
|
||||||
|
goto(href);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button class="button" onclick={onclick}>
|
||||||
|
<Icon icon={icon} />
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,6 +7,7 @@
|
||||||
import Select from './Select.svelte';
|
import Select from './Select.svelte';
|
||||||
import type { Letter } from '$lib/letter';
|
import type { Letter } from '$lib/letter';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
import IconButton from './IconButton.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
letters,
|
letters,
|
||||||
|
@ -117,7 +118,10 @@
|
||||||
<a in:slide out:slide class="letter-item" href="/letters/{letter.id}">
|
<a in:slide out:slide class="letter-item" href="/letters/{letter.id}">
|
||||||
<div class="subject-line">
|
<div class="subject-line">
|
||||||
<div class="letter-subject">
|
<div class="letter-subject">
|
||||||
<Icon icon="mdi:letter-outline" /> {letter.subject}
|
{#if !letter.emailAddress}
|
||||||
|
<img src="/nostr-lock.svg" width="32" />
|
||||||
|
{/if}
|
||||||
|
{letter.subject}
|
||||||
</div>
|
</div>
|
||||||
{#if letter.stamps}
|
{#if letter.stamps}
|
||||||
<div class="stamps-count">
|
<div class="stamps-count">
|
||||||
|
@ -152,7 +156,9 @@
|
||||||
<Dialog
|
<Dialog
|
||||||
bind:open={moveFolderDialogOpen}
|
bind:open={moveFolderDialogOpen}
|
||||||
>
|
>
|
||||||
|
<div class="title">
|
||||||
<h3>Move Letters to Folder</h3>
|
<h3>Move Letters to Folder</h3>
|
||||||
|
</div>
|
||||||
<p class="text-secondary">Select a folder to move the selected letters to:</p>
|
<p class="text-secondary">Select a folder to move the selected letters to:</p>
|
||||||
|
|
||||||
<Select bind:value={moveFolderName} label="Select folder" options={
|
<Select bind:value={moveFolderName} label="Select folder" options={
|
||||||
|
@ -200,9 +206,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button onclick={() => moveFolderDialogOpen = true}>
|
<IconButton icon="mdi:folder-move-outline" onclick={() => moveFolderDialogOpen = true} text="Move Letters" />
|
||||||
<Icon icon="mdi:folder-move-outline" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="letter-list">
|
<main class="letter-list">
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import LoginWithNostr from '../components/LoginWithNostr.svelte';
|
import LoginWithNostr from '../components/LoginWithNostr.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { NDKNip07Signer } from '@nostr-dev-kit/ndk';
|
import { NDKNip07Signer } from '@nostr-dev-kit/ndk';
|
||||||
|
import IconButton from '../components/IconButton.svelte';
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (localStorage.getItem('useNip07')) {
|
if (localStorage.getItem('useNip07')) {
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
<header id="header">
|
<header id="header">
|
||||||
<nav class="actions">
|
<nav class="actions">
|
||||||
{#if $activeUser}
|
{#if $activeUser}
|
||||||
<a class="button" href="/compose">Compose</a>
|
<IconButton icon="proicons:compose" text="Compose" href="/compose" />
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
|
|
||||||
<nav class="actions">
|
<nav class="actions">
|
||||||
{#if $activeUser}
|
{#if $activeUser}
|
||||||
<a class="button" href="/settings">Settings</a>
|
<IconButton icon="si:settings-cute-line" text="Settings" href="/settings" />
|
||||||
{/if}
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
import { ndk } from '$lib/stores.svelte';
|
import { ndk } from '$lib/stores.svelte';
|
||||||
import Icon from '@iconify/svelte';
|
import Icon from '@iconify/svelte';
|
||||||
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
|
import MailboxFolderItems from '../components/MailboxFolderItems.svelte';
|
||||||
import { createFolder, decryptSealedMessageIntoReadableType } from '$lib/utils.svelte';
|
import { decryptSealedMessageIntoReadableType } from '$lib/utils.svelte';
|
||||||
import { Letter } from '$lib/letter';
|
import { Letter } from '$lib/letter';
|
||||||
import Dialog from '../components/Dialog.svelte';
|
|
||||||
import { FolderLabel } from '$lib/folderLabel';
|
import { FolderLabel } from '$lib/folderLabel';
|
||||||
import type { NDKEventId } from '@nostr-dev-kit/ndk';
|
import type { NDKEvent, NDKEventId } from '@nostr-dev-kit/ndk';
|
||||||
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
|
import { LetterToFolderMapping } from '$lib/letterToFolderMapping';
|
||||||
|
import IconButton from '../components/IconButton.svelte';
|
||||||
|
import FoldersListSidebar from '../components/FoldersListSidebar.svelte';
|
||||||
|
|
||||||
let sidebarOpen = $state(false);
|
let sidebarOpen = $state(false);
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
{ id: 'trash', name: 'Trash', icon: 'eos-icons:trash' }
|
{ id: 'trash', name: 'Trash', icon: 'eos-icons:trash' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
async function decryptMessages(messages) {
|
async function decryptMessages(messages: NDKEvent[]) {
|
||||||
const currentMessages = messages;
|
const currentMessages = messages;
|
||||||
const decryptedFolders = [
|
const decryptedFolders = [
|
||||||
{ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone' },
|
{ id: 'inbox', name: 'Inbox', icon: 'solar:inbox-bold-duotone' },
|
||||||
|
@ -67,81 +68,24 @@
|
||||||
return !allLettersInFolders.includes(letter.id);
|
return !allLettersInFolders.includes(letter.id);
|
||||||
if (!letterLabels.has(activeFolder))
|
if (!letterLabels.has(activeFolder))
|
||||||
return false;
|
return false;
|
||||||
return letterLabels.get(activeFolder).includes(letter.id);
|
return letterLabels.get(activeFolder)!.includes(letter.id);
|
||||||
});
|
});
|
||||||
folders = decryptedFolders;
|
folders = decryptedFolders;
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.subscribe(m => decryptMessages(m));
|
messages.subscribe((m: NDKEvent) => decryptMessages(m));
|
||||||
|
|
||||||
function changeFolder(newFolder) {
|
function changeFolder(newFolder: string) {
|
||||||
letters = [];
|
letters = [];
|
||||||
activeFolder = newFolder;
|
activeFolder = newFolder;
|
||||||
decryptMessages($messages);
|
decryptMessages($messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addNewFolderPressed() {
|
|
||||||
const newFolder = await createFolder(newFolderName, '');
|
|
||||||
if (!newFolder) return;
|
|
||||||
await newFolder.publish();
|
|
||||||
folders = [...folders, { id: newFolder.id, name: newFolderName, icon: 'eos-icons:plus' }];
|
|
||||||
newFolderName = '';
|
|
||||||
isAddingFolder = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newFolderDialogClosed() {
|
|
||||||
isAddingFolder = false;
|
|
||||||
newFolderName = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let isAddingFolder = $state(false);
|
|
||||||
let newFolderName = $state('');
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="sidebar-button" onclick={() => sidebarOpen = !sidebarOpen}>
|
<IconButton icon="mynaui:sidebar-solid" onclick={() => sidebarOpen = !sidebarOpen} text="Sidebar" />
|
||||||
<Icon icon="mynaui:sidebar-solid" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Dialog
|
|
||||||
bind:open={isAddingFolder}
|
|
||||||
onClose={newFolderDialogClosed}
|
|
||||||
>
|
|
||||||
<div class="title">
|
|
||||||
<h3>Create Folder</h3>
|
|
||||||
</div>
|
|
||||||
<p class="text-secondary">Please enter a name for the new folder:</p>
|
|
||||||
|
|
||||||
<form onsubmit={addNewFolderPressed}>
|
|
||||||
<input
|
|
||||||
autofocus
|
|
||||||
bind:value={newFolderName}
|
|
||||||
placeholder="Folder name"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="button-group">
|
|
||||||
<button onclick={newFolderDialogClosed} type="button">Cancel</button>
|
|
||||||
<button type="submit">OK</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="sidebar-container" class:open={sidebarOpen}>
|
<FoldersListSidebar bind:folders bind:sidebarOpen {changeFolder} />
|
||||||
<div class="glass sidebar">
|
|
||||||
{#each folders as folder}
|
|
||||||
<button class="folder-item" onclick={() => changeFolder(folder.id)}>
|
|
||||||
<Icon icon={folder.icon} />
|
|
||||||
{folder.name}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
<hr />
|
|
||||||
<button class="folder-item" onclick={() => isAddingFolder = true}>
|
|
||||||
<Icon icon="eos-icons:plus" />
|
|
||||||
Add Folder
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="folder-view">
|
<div class="folder-view">
|
||||||
{#if letters.length === 0}
|
{#if letters.length === 0}
|
||||||
|
@ -157,41 +101,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.folder-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
|
||||||
width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: width 0.3s ease-in-out;
|
|
||||||
|
|
||||||
&.open {
|
|
||||||
width: clamp(16rem, 20vw, 20rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-view {
|
.folder-view {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
59
static/nostr-lock.svg
Normal file
59
static/nostr-lock.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.2 KiB |
Loading…
Reference in a new issue