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 FilterBar from "../components/FilterBar.vue";
|
||||||
import NoteCard from "../components/NoteCard.vue";
|
import NoteCard from "../components/NoteCard.vue";
|
||||||
import NoteEditor from "../components/NoteEditor.vue";
|
import NoteEditor from "../components/NoteEditor.vue";
|
||||||
|
import { isDesktop, sync as syncBridge } from "../desktop/bridge";
|
||||||
|
|
||||||
const notes = useNotesStore();
|
const notes = useNotesStore();
|
||||||
const config = useConfigStore();
|
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.`;
|
: `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(() => {
|
const emptyState = computed(() => {
|
||||||
if (filtered.value) return { title: "No notes match these filters", subtitle: "Try clearing or loosening a facet." };
|
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 === "trash") return { title: "Trash is empty", subtitle: "Notes you delete land here first." };
|
||||||
if (currentView.value === "archived")
|
if (currentView.value === "archived")
|
||||||
return { title: "Nothing archived", subtitle: "Archived notes are tucked away here." };
|
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." };
|
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." };
|
return { title: "No notes yet", subtitle: "Your captured thoughts will show up here." };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,6 +212,17 @@ async function reload() {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void reload();
|
void reload();
|
||||||
window.addEventListener("keydown", onBoardKey);
|
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(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener("keydown", onBoardKey);
|
window.removeEventListener("keydown", onBoardKey);
|
||||||
@@ -234,7 +279,21 @@ async function onDrop(target: Note) {
|
|||||||
error-title="Couldn't load your notes"
|
error-title="Couldn't load your notes"
|
||||||
@retry="reload"
|
@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-else>
|
||||||
<template v-if="isMainBoard">
|
<template v-if="isMainBoard">
|
||||||
<section v-if="pinnedNotes.length">
|
<section v-if="pinnedNotes.length">
|
||||||
|
|||||||
Reference in New Issue
Block a user