added loading indicator for large vaults

This commit is contained in:
Gamosoft 2026-06-30 12:26:32 +02:00
parent 0cd5332a99
commit c21d44a169
13 changed files with 71 additions and 5 deletions

View File

@ -262,6 +262,13 @@ function noteApp() {
alreadyDonated: false, alreadyDonated: false,
autosaveDelayMs: CONFIG.AUTOSAVE_DELAY, // hydrated from /api/config in loadConfig() autosaveDelayMs: CONFIG.AUTOSAVE_DELAY, // hydrated from /api/config in loadConfig()
notes: [], notes: [],
// True while /api/notes is in flight. Drives the "Loading your vault…"
// placeholder + the delayed overlay (notesLoadingShowOverlay).
notesLoading: true,
notesLoadingShowOverlay: false,
_notesLoadingOverlayTimer: null,
currentNote: '', currentNote: '',
currentNoteName: '', currentNoteName: '',
noteContent: '', noteContent: '',
@ -1524,6 +1531,16 @@ function noteApp() {
// Load all notes // Load all notes
async loadNotes() { async loadNotes() {
this.notesLoading = true;
// Show the overlay only if we're still loading after 800ms AND
// the tree is currently empty (first load). Refreshes after a
// save typically resolve in <100ms and never trip this.
clearTimeout(this._notesLoadingOverlayTimer);
this._notesLoadingOverlayTimer = setTimeout(() => {
if (this.notesLoading && this.notes.length === 0 && this.allFolders.length === 0) {
this.notesLoadingShowOverlay = true;
}
}, 800);
try { try {
const response = await fetch('/api/notes'); const response = await fetch('/api/notes');
const data = await response.json(); const data = await response.json();
@ -1534,6 +1551,10 @@ function noteApp() {
await this.loadTags(); // Load tags after notes are loaded await this.loadTags(); // Load tags after notes are loaded
} catch (error) { } catch (error) {
ErrorHandler.handle('load notes', error); ErrorHandler.handle('load notes', error);
} finally {
clearTimeout(this._notesLoadingOverlayTimer);
this.notesLoading = false;
this.notesLoadingShowOverlay = false;
} }
}, },

View File

@ -1398,6 +1398,29 @@
</head> </head>
<body x-data="noteApp()" x-init="init()" style="background-color: var(--bg-primary);" :class="{'zen-mode-active': zenMode}"> <body x-data="noteApp()" x-init="init()" style="background-color: var(--bg-primary);" :class="{'zen-mode-active': zenMode}">
<!-- Vault loading overlay. Centered floating card; the outer div is
pointer-events:none so settings/theme/search/icon-rail stay clickable.
Shown only when /api/notes is still in flight 800ms after boot AND the
tree is currently empty — so small vaults / warm caches never see it. -->
<div x-show="notesLoadingShowOverlay"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 flex items-center justify-center"
style="z-index: 60; pointer-events: none;">
<div class="px-6 py-4 rounded-lg shadow-2xl flex items-center gap-3"
style="background-color: var(--bg-secondary); border: 1px solid var(--border-primary); color: var(--text-primary); pointer-events: auto;">
<svg class="animate-spin h-5 w-5 flex-shrink-0" style="color: var(--accent-primary);" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-sm" x-text="t('sidebar.loading_notes')"></span>
</div>
</div>
<!-- Zen Mode Exit Button --> <!-- Zen Mode Exit Button -->
<button <button
x-show="zenMode" x-show="zenMode"
@ -1800,8 +1823,19 @@
<!-- Root notes and images (no folder) --> <!-- Root notes and images (no folder) -->
<div x-html="renderRootItems()"></div> <div x-html="renderRootItems()"></div>
<!-- Empty state --> <!-- Loading state (initial /api/notes still in flight) -->
<template x-if="notes.length === 0 && allFolders.length === 0"> <template x-if="notesLoading && notes.length === 0 && allFolders.length === 0">
<div class="px-3 py-4 text-sm text-center flex items-center justify-center gap-2" style="color: var(--text-tertiary);">
<svg class="animate-spin h-4 w-4" style="color: var(--accent-primary);" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span x-text="t('sidebar.loading_notes')"></span>
</div>
</template>
<!-- Empty state (load finished, vault genuinely empty) -->
<template x-if="!notesLoading && notes.length === 0 && allFolders.length === 0">
<div class="px-3 py-4 text-sm text-center" style="color: var(--text-tertiary);"> <div class="px-3 py-4 text-sm text-center" style="color: var(--text-tertiary);">
<span x-text="t('sidebar.no_notes_yet')"></span><br> <span x-text="t('sidebar.no_notes_yet')"></span><br>
<span x-text="t('sidebar.create_first')"></span> <span x-text="t('sidebar.create_first')"></span>
@ -2432,8 +2466,8 @@
</button> </button>
</div> </div>
<!-- Welcome Hero - Show when app is empty --> <!-- Welcome Hero - Show when app is empty (but not while still loading) -->
<template x-if="isAppEmpty"> <template x-if="!notesLoading && isAppEmpty">
<div class="flex flex-col items-center justify-center h-full py-16 text-center"> <div class="flex flex-col items-center justify-center h-full py-16 text-center">
<svg class="mx-auto h-24 w-24 mb-4" style="color: var(--text-tertiary); opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="mx-auto h-24 w-24 mb-4" style="color: var(--text-tertiary); opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>

View File

@ -49,6 +49,7 @@
"no_notes": "Du hast noch keine Notizen. Erstelle deine erste!", "no_notes": "Du hast noch keine Notizen. Erstelle deine erste!",
"no_notes_yet": "Noch keine Notizen oder Ordner.", "no_notes_yet": "Noch keine Notizen oder Ordner.",
"create_first": "Erstelle deine erste Notiz oder Ordner!", "create_first": "Erstelle deine erste Notiz oder Ordner!",
"loading_notes": "Notizen werden geladen…",
"drop_to_root": "Hier ablegen für Stammverzeichnis", "drop_to_root": "Hier ablegen für Stammverzeichnis",
"no_favorites": "Noch keine Favoriten", "no_favorites": "Noch keine Favoriten",
"no_results": "Keine Ergebnisse gefunden", "no_results": "Keine Ergebnisse gefunden",

View File

@ -49,6 +49,7 @@
"no_notes": "No notes yet. Create your first note!", "no_notes": "No notes yet. Create your first note!",
"no_notes_yet": "No notes or folders yet.", "no_notes_yet": "No notes or folders yet.",
"create_first": "Create your first note or folder!", "create_first": "Create your first note or folder!",
"loading_notes": "Loading your notes…",
"no_favorites": "No favourites yet", "no_favorites": "No favourites yet",
"no_results": "No results found", "no_results": "No results found",
"expand_all": "Expand all folders", "expand_all": "Expand all folders",

View File

@ -49,6 +49,7 @@
"no_notes": "No notes yet. Create your first note!", "no_notes": "No notes yet. Create your first note!",
"no_notes_yet": "No notes or folders yet.", "no_notes_yet": "No notes or folders yet.",
"create_first": "Create your first note or folder!", "create_first": "Create your first note or folder!",
"loading_notes": "Loading your notes…",
"drop_to_root": "Drop here for root", "drop_to_root": "Drop here for root",
"no_favorites": "No favorites yet", "no_favorites": "No favorites yet",
"no_results": "No results found", "no_results": "No results found",

View File

@ -49,6 +49,7 @@
"no_notes": "Aún no hay notas. ¡Crea tu primera nota!", "no_notes": "Aún no hay notas. ¡Crea tu primera nota!",
"no_notes_yet": "Aún no hay notas ni carpetas.", "no_notes_yet": "Aún no hay notas ni carpetas.",
"create_first": "¡Crea tu primera nota o carpeta!", "create_first": "¡Crea tu primera nota o carpeta!",
"loading_notes": "Cargando tus notas…",
"drop_to_root": "Soltar aquí para raíz", "drop_to_root": "Soltar aquí para raíz",
"no_favorites": "Aún no hay favoritos", "no_favorites": "Aún no hay favoritos",
"no_results": "No se encontraron resultados", "no_results": "No se encontraron resultados",

View File

@ -49,6 +49,7 @@
"no_notes": "Pas encore de notes. Créez votre première note !", "no_notes": "Pas encore de notes. Créez votre première note !",
"no_notes_yet": "Pas encore de notes ni de dossiers.", "no_notes_yet": "Pas encore de notes ni de dossiers.",
"create_first": "Créez votre première note ou dossier !", "create_first": "Créez votre première note ou dossier !",
"loading_notes": "Chargement de vos notes…",
"drop_to_root": "Déposer ici pour la racine", "drop_to_root": "Déposer ici pour la racine",
"no_favorites": "Pas encore de favoris", "no_favorites": "Pas encore de favoris",
"no_results": "Aucun résultat trouvé", "no_results": "Aucun résultat trouvé",

View File

@ -49,6 +49,7 @@
"no_notes": "Még nincsenek jegyzetek. Hozd létre az első jegyzeted!", "no_notes": "Még nincsenek jegyzetek. Hozd létre az első jegyzeted!",
"no_notes_yet": "Még nincsenek jegyzetek vagy mappák.", "no_notes_yet": "Még nincsenek jegyzetek vagy mappák.",
"create_first": "Hozd létre az első jegyzeted vagy mappád!", "create_first": "Hozd létre az első jegyzeted vagy mappád!",
"loading_notes": "Jegyzeteid betöltése…",
"drop_to_root": "Húzd ide a gyökérhez", "drop_to_root": "Húzd ide a gyökérhez",
"no_favorites": "Még nincsenek kedvencek", "no_favorites": "Még nincsenek kedvencek",
"no_results": "Nincs találat", "no_results": "Nincs találat",

View File

@ -49,6 +49,7 @@
"no_notes": "Nessuna nota. Crea la tua prima nota!", "no_notes": "Nessuna nota. Crea la tua prima nota!",
"no_notes_yet": "Nessuna nota o cartella.", "no_notes_yet": "Nessuna nota o cartella.",
"create_first": "Crea la tua prima nota o cartella!", "create_first": "Crea la tua prima nota o cartella!",
"loading_notes": "Caricamento delle tue note…",
"no_favorites": "Nessun preferito", "no_favorites": "Nessun preferito",
"no_results": "Nessun risultato trovato", "no_results": "Nessun risultato trovato",
"expand_all": "Espandi tutte le cartelle", "expand_all": "Espandi tutte le cartelle",

View File

@ -49,6 +49,7 @@
"no_notes": "ノートがありません。最初のノートを作成しましょう!", "no_notes": "ノートがありません。最初のノートを作成しましょう!",
"no_notes_yet": "ノートやフォルダがありません。", "no_notes_yet": "ノートやフォルダがありません。",
"create_first": "最初のノートまたはフォルダを作成しましょう!", "create_first": "最初のノートまたはフォルダを作成しましょう!",
"loading_notes": "ノートを読み込み中…",
"no_favorites": "お気に入りがありません", "no_favorites": "お気に入りがありません",
"no_results": "結果が見つかりません", "no_results": "結果が見つかりません",
"expand_all": "すべて展開", "expand_all": "すべて展開",

View File

@ -49,6 +49,7 @@
"no_notes": "Заметок пока нет. Создайте первую!", "no_notes": "Заметок пока нет. Создайте первую!",
"no_notes_yet": "Нет заметок или папок.", "no_notes_yet": "Нет заметок или папок.",
"create_first": "Создайте первую заметку или папку!", "create_first": "Создайте первую заметку или папку!",
"loading_notes": "Загрузка ваших заметок…",
"no_favorites": "Нет избранного", "no_favorites": "Нет избранного",
"no_results": "Ничего не найдено", "no_results": "Ничего не найдено",
"expand_all": "Развернуть все папки", "expand_all": "Развернуть все папки",

View File

@ -49,6 +49,7 @@
"no_notes": "Ni še zapiskov. Ustvarite svoj prvi zapis!", "no_notes": "Ni še zapiskov. Ustvarite svoj prvi zapis!",
"no_notes_yet": "Ni še zapiskov ali map.", "no_notes_yet": "Ni še zapiskov ali map.",
"create_first": "Ustvarite svoj prvi zapis ali mapo!", "create_first": "Ustvarite svoj prvi zapis ali mapo!",
"loading_notes": "Nalaganje vaših zapiskov…",
"no_favorites": "Ni priljubljenih", "no_favorites": "Ni priljubljenih",
"no_results": "Ni najdenih rezultatov", "no_results": "Ni najdenih rezultatov",
"expand_all": "Razširi vse mape", "expand_all": "Razširi vse mape",

View File

@ -49,6 +49,7 @@
"no_notes": "还没有笔记。创建您的第一篇笔记!", "no_notes": "还没有笔记。创建您的第一篇笔记!",
"no_notes_yet": "还没有笔记或文件夹。", "no_notes_yet": "还没有笔记或文件夹。",
"create_first": "创建您的第一篇笔记或文件夹!", "create_first": "创建您的第一篇笔记或文件夹!",
"loading_notes": "正在加载您的笔记…",
"no_favorites": "还没有收藏夹", "no_favorites": "还没有收藏夹",
"no_results": "未找到结果", "no_results": "未找到结果",
"expand_all": "展开所有文件夹", "expand_all": "展开所有文件夹",