M12 — the Android client, end to end #2
@@ -11,6 +11,7 @@ import EmptyState from "../components/EmptyState.vue";
|
||||
import FilterBar from "../components/FilterBar.vue";
|
||||
import NoteCard from "../components/NoteCard.vue";
|
||||
import NoteEditor from "../components/NoteEditor.vue";
|
||||
import { isDesktop, sync as syncBridge } from "../desktop/bridge";
|
||||
|
||||
const notes = useNotesStore();
|
||||
const config = useConfigStore();
|
||||
@@ -157,12 +158,45 @@ const retentionNotice = computed(() => {
|
||||
: `Notes here are permanently deleted ${days} days after you trash them. Restore one to keep it.`;
|
||||
});
|
||||
|
||||
/**
|
||||
* Whether this desktop install has been linked to a server. Only consulted for the
|
||||
* first-run copy below — offering to connect a server to someone who already did
|
||||
* would be noise. Never set on the web build, which IS a server's UI.
|
||||
*/
|
||||
const linkedToServer = ref(false);
|
||||
|
||||
/**
|
||||
* The desktop's start state: a fresh install, nothing written yet.
|
||||
*
|
||||
* There is no login and no onboarding gate — the app is usable the instant it
|
||||
* opens, and interrupting that with a welcome modal would tax the one thing the
|
||||
* product is for (dumping a thought in a second). So the empty board carries the
|
||||
* message instead, and retires itself the moment a first note exists. That also
|
||||
* means no "seen it" flag to persist, migrate, or get out of step with reality.
|
||||
*/
|
||||
const startState = computed(
|
||||
() =>
|
||||
isDesktop() &&
|
||||
isMainBoard.value &&
|
||||
!filtered.value &&
|
||||
!linkedToServer.value &&
|
||||
notes.items.length === 0,
|
||||
);
|
||||
|
||||
const emptyState = computed(() => {
|
||||
if (filtered.value) return { title: "No notes match these filters", subtitle: "Try clearing or loosening a facet." };
|
||||
if (currentView.value === "trash") return { title: "Trash is empty", subtitle: "Notes you delete land here first." };
|
||||
if (currentView.value === "archived")
|
||||
return { title: "Nothing archived", subtitle: "Archived notes are tucked away here." };
|
||||
if (currentLabel.value) return { title: "No notes with this label", subtitle: "Tag a note to see it here." };
|
||||
// Says what "no account" actually means for the notes about to be written here.
|
||||
// A new user otherwise has no way to tell whether this thing is storing their
|
||||
// thoughts locally, silently waiting for a login, or quietly sending them off.
|
||||
if (startState.value)
|
||||
return {
|
||||
title: "Your notes live on this device",
|
||||
subtitle: "ThoughtSync works fully offline — no account, nothing to sign in to.",
|
||||
};
|
||||
return { title: "No notes yet", subtitle: "Your captured thoughts will show up here." };
|
||||
});
|
||||
|
||||
@@ -178,6 +212,17 @@ async function reload() {
|
||||
onMounted(() => {
|
||||
void reload();
|
||||
window.addEventListener("keydown", onBoardKey);
|
||||
// Best-effort and never awaited: the board must render at full speed whether or
|
||||
// not this answers. A failure just leaves the start-state copy showing, which is
|
||||
// the correct reading of "we don't know of a server".
|
||||
if (isDesktop()) {
|
||||
void syncBridge
|
||||
.status()
|
||||
.then((s) => {
|
||||
linkedToServer.value = s.linked;
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("keydown", onBoardKey);
|
||||
@@ -234,7 +279,21 @@ async function onDrop(target: Note) {
|
||||
error-title="Couldn't load your notes"
|
||||
@retry="reload"
|
||||
>
|
||||
<EmptyState v-if="notes.items.length === 0" :title="emptyState.title" :subtitle="emptyState.subtitle" />
|
||||
<EmptyState v-if="notes.items.length === 0" :title="emptyState.title" :subtitle="emptyState.subtitle">
|
||||
<!-- v-if on the slot itself, so every OTHER empty state renders without a
|
||||
stray call-to-action box beneath it. -->
|
||||
<template v-if="startState" #default>
|
||||
<RouterLink
|
||||
to="/sync"
|
||||
class="inline-block rounded-md border border-neutral-300 px-3 py-1.5 text-sm text-neutral-600 transition hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-800"
|
||||
>
|
||||
Connect a sync server
|
||||
</RouterLink>
|
||||
<p class="mt-2 text-xs text-neutral-400">
|
||||
Optional — only if you want these notes on your other devices.
|
||||
</p>
|
||||
</template>
|
||||
</EmptyState>
|
||||
<template v-else>
|
||||
<template v-if="isMainBoard">
|
||||
<section v-if="pinnedNotes.length">
|
||||
|
||||
Reference in New Issue
Block a user