Add prettier. Format.

This commit is contained in:
2026-07-22 02:12:05 +00:00
parent d28b09dc21
commit 18baa96848
45 changed files with 4520 additions and 2613 deletions

View File

@@ -1,103 +1,207 @@
import { computed, reactive, readonly, ref } from 'vue'
import { computed, reactive, readonly, ref } from "vue";
export interface Person {
id: string
name: string
handle: string
color: string
online: boolean
bio: string
id: string;
name: string;
handle: string;
color: string;
online: boolean;
bio: string;
}
export interface Message {
id: string
personId: string
body: string
sentAt: number
mine: boolean
status: 'sent' | 'delivered' | 'failed'
id: string;
personId: string;
body: string;
sentAt: number;
mine: boolean;
status: "sent" | "delivered" | "failed";
}
export interface Conversation {
id: string
personId: string
unread: number
pinned?: boolean
messages: Message[]
id: string;
personId: string;
unread: number;
pinned?: boolean;
messages: Message[];
}
const people: Person[] = [
{ id: 'maya', name: 'Maya Chen', handle: '@mayac', color: '#ff7a8a', online: true, bio: 'Product designer · Sydney to everywhere.' },
{ id: 'noah', name: 'Noah Williams', handle: '@noahw', color: '#4f9cff', online: true, bio: 'Film, tiny cameras, and very long walks.' },
{ id: 'sofia', name: 'Sofia Rossi', handle: '@sofiar', color: '#9d67ff', online: false, bio: 'Making typefaces and better pasta.' },
{ id: 'liam', name: 'Liam Park', handle: '@liamp', color: '#35c7a0', online: true, bio: 'Engineer. Climber. Questionable DJ.' },
{ id: 'amara', name: 'Amara Okafor', handle: '@amarao', color: '#f4ad42', online: false, bio: 'Architecture and cities after dark.' },
]
{
id: "maya",
name: "Maya Chen",
handle: "@mayac",
color: "#ff7a8a",
online: true,
bio: "Product designer · Sydney to everywhere.",
},
{
id: "noah",
name: "Noah Williams",
handle: "@noahw",
color: "#4f9cff",
online: true,
bio: "Film, tiny cameras, and very long walks.",
},
{
id: "sofia",
name: "Sofia Rossi",
handle: "@sofiar",
color: "#9d67ff",
online: false,
bio: "Making typefaces and better pasta.",
},
{
id: "liam",
name: "Liam Park",
handle: "@liamp",
color: "#35c7a0",
online: true,
bio: "Engineer. Climber. Questionable DJ.",
},
{
id: "amara",
name: "Amara Okafor",
handle: "@amarao",
color: "#f4ad42",
online: false,
bio: "Architecture and cities after dark.",
},
];
const timestamp = Date.now()
const timestamp = Date.now();
const seedConversations: Conversation[] = [
{
id: 'maya', personId: 'maya', unread: 2, pinned: true,
id: "maya",
personId: "maya",
unread: 2,
pinned: true,
messages: [
{ id: 'm1', personId: 'maya', body: 'That transition feels ridiculously smooth ✨', sentAt: timestamp - 840_000, mine: false, status: 'delivered' },
{ id: 'm2', personId: 'maya', body: 'Try holding it halfway, then let go slowly.', sentAt: timestamp - 780_000, mine: false, status: 'delivered' },
{
id: "m1",
personId: "maya",
body: "That transition feels ridiculously smooth ✨",
sentAt: timestamp - 840_000,
mine: false,
status: "delivered",
},
{
id: "m2",
personId: "maya",
body: "Try holding it halfway, then let go slowly.",
sentAt: timestamp - 780_000,
mine: false,
status: "delivered",
},
],
},
{
id: 'noah', personId: 'noah', unread: 0,
id: "noah",
personId: "noah",
unread: 0,
messages: [
{ id: 'n1', personId: 'noah', body: 'Uploaded the photos from yesterday.', sentAt: timestamp - 4_200_000, mine: false, status: 'delivered' },
{ id: 'n2', personId: 'noah', body: 'The grain is perfect.', sentAt: timestamp - 4_000_000, mine: true, status: 'delivered' },
{
id: "n1",
personId: "noah",
body: "Uploaded the photos from yesterday.",
sentAt: timestamp - 4_200_000,
mine: false,
status: "delivered",
},
{
id: "n2",
personId: "noah",
body: "The grain is perfect.",
sentAt: timestamp - 4_000_000,
mine: true,
status: "delivered",
},
],
},
{
id: 'sofia', personId: 'sofia', unread: 1,
messages: [{ id: 's1', personId: 'sofia', body: 'Coffee at the new place tomorrow?', sentAt: timestamp - 18_000_000, mine: false, status: 'delivered' }],
id: "sofia",
personId: "sofia",
unread: 1,
messages: [
{
id: "s1",
personId: "sofia",
body: "Coffee at the new place tomorrow?",
sentAt: timestamp - 18_000_000,
mine: false,
status: "delivered",
},
],
},
{
id: 'liam', personId: 'liam', unread: 0,
messages: [{ id: 'l1', personId: 'liam', body: 'The build is green. Ship it.', sentAt: timestamp - 86_000_000, mine: false, status: 'delivered' }],
id: "liam",
personId: "liam",
unread: 0,
messages: [
{
id: "l1",
personId: "liam",
body: "The build is green. Ship it.",
sentAt: timestamp - 86_000_000,
mine: false,
status: "delivered",
},
],
},
{
id: 'amara', personId: 'amara', unread: 0,
messages: [{ id: 'a1', personId: 'amara', body: 'This city never really goes quiet.', sentAt: timestamp - 172_000_000, mine: false, status: 'delivered' }],
id: "amara",
personId: "amara",
unread: 0,
messages: [
{
id: "a1",
personId: "amara",
body: "This city never really goes quiet.",
sentAt: timestamp - 172_000_000,
mine: false,
status: "delivered",
},
],
},
]
];
const conversations = ref<Conversation[]>(structuredClone(seedConversations))
const ready = ref(false)
const offline = ref(!navigator.onLine)
const settings = reactive({ simulatedLatency: 180, simulateFailures: false })
const conversations = ref<Conversation[]>(structuredClone(seedConversations));
const ready = ref(false);
const offline = ref(!navigator.onLine);
const settings = reactive({ simulatedLatency: 180, simulateFailures: false });
function openDatabase() {
return new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open('native-vue-messenger', 1)
request.onupgradeneeded = () => request.result.createObjectStore('state')
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
const request = indexedDB.open("native-vue-messenger", 1);
request.onupgradeneeded = () => request.result.createObjectStore("state");
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function readStored() {
const database = await openDatabase()
const database = await openDatabase();
return await new Promise<Conversation[] | undefined>((resolve, reject) => {
const transaction = database.transaction('state', 'readonly')
const request = transaction.objectStore('state').get('conversations')
request.onsuccess = () => resolve(request.result as Conversation[] | undefined)
request.onerror = () => reject(request.error)
}).finally(() => database.close())
const transaction = database.transaction("state", "readonly");
const request = transaction.objectStore("state").get("conversations");
request.onsuccess = () =>
resolve(request.result as Conversation[] | undefined);
request.onerror = () => reject(request.error);
}).finally(() => database.close());
}
async function persist() {
try {
const database = await openDatabase()
const database = await openDatabase();
await new Promise<void>((resolve, reject) => {
const transaction = database.transaction('state', 'readwrite')
transaction.objectStore('state').put(structuredClone(conversations.value), 'conversations')
transaction.oncomplete = () => resolve()
transaction.onerror = () => reject(transaction.error)
})
database.close()
const transaction = database.transaction("state", "readwrite");
transaction
.objectStore("state")
.put(structuredClone(conversations.value), "conversations");
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
});
database.close();
} catch {
// Private browsing and locked-down webviews can reject IndexedDB.
}
@@ -105,48 +209,58 @@ async function persist() {
async function initialize() {
try {
const stored = await readStored()
if (stored?.length) conversations.value = stored
else await persist()
const stored = await readStored();
if (stored?.length) conversations.value = stored;
else await persist();
} finally {
ready.value = true
ready.value = true;
}
}
window.addEventListener('online', () => { offline.value = false })
window.addEventListener('offline', () => { offline.value = true })
void initialize()
window.addEventListener("online", () => {
offline.value = false;
});
window.addEventListener("offline", () => {
offline.value = true;
});
void initialize();
export function useDemoStore() {
const conversationFor = (id: string) => computed(() => conversations.value.find((conversation) => conversation.id === id))
const personFor = (id: string) => people.find((person) => person.id === id)
const conversationFor = (id: string) =>
computed(() =>
conversations.value.find((conversation) => conversation.id === id),
);
const personFor = (id: string) => people.find((person) => person.id === id);
const markRead = (id: string) => {
const conversation = conversations.value.find((item) => item.id === id)
if (conversation) conversation.unread = 0
void persist()
}
const conversation = conversations.value.find((item) => item.id === id);
if (conversation) conversation.unread = 0;
void persist();
};
const sendMessage = async (id: string, body: string) => {
const conversation = conversations.value.find((item) => item.id === id)
if (!conversation || !body.trim()) return false
const conversation = conversations.value.find((item) => item.id === id);
if (!conversation || !body.trim()) return false;
const message: Message = {
id: crypto.randomUUID(),
personId: id,
body: body.trim(),
sentAt: Date.now(),
mine: true,
status: 'sent',
}
conversation.messages.push(message)
await persist()
await new Promise((resolve) => window.setTimeout(resolve, settings.simulatedLatency))
message.status = settings.simulateFailures || offline.value ? 'failed' : 'delivered'
await persist()
return message.status === 'delivered'
}
status: "sent",
};
conversation.messages.push(message);
await persist();
await new Promise((resolve) =>
window.setTimeout(resolve, settings.simulatedLatency),
);
message.status =
settings.simulateFailures || offline.value ? "failed" : "delivered";
await persist();
return message.status === "delivered";
};
const reset = async () => {
conversations.value = structuredClone(seedConversations)
await persist()
}
conversations.value = structuredClone(seedConversations);
await persist();
};
return {
people: readonly(ref(people)),
conversations: readonly(conversations),
@@ -158,14 +272,14 @@ export function useDemoStore() {
markRead,
sendMessage,
reset,
}
};
}
export function relativeTime(value: number) {
const minutes = Math.floor((Date.now() - value) / 60_000)
if (minutes < 1) return 'now'
if (minutes < 60) return `${minutes}m`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}h`
return `${Math.floor(hours / 24)}d`
const minutes = Math.floor((Date.now() - value) / 60_000);
if (minutes < 1) return "now";
if (minutes < 60) return `${minutes}m`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h`;
return `${Math.floor(hours / 24)}d`;
}