feat: reactive stores
Some checks failed
TrafficCue CI / check (push) Failing after 1m35s
TrafficCue CI / build (push) Failing after 8m42s
TrafficCue CI / build-android (push) Failing after 13m39s

This commit is contained in:
2025-09-27 12:13:44 +02:00
parent d9687e3c8d
commit b712966cf7
2 changed files with 69 additions and 2 deletions

View File

@ -3,6 +3,7 @@
CloudUploadIcon, CloudUploadIcon,
HandIcon, HandIcon,
MapIcon, MapIcon,
PackageMinusIcon,
PackagePlusIcon, PackagePlusIcon,
RefreshCcwIcon, RefreshCcwIcon,
SpeechIcon, SpeechIcon,
@ -16,9 +17,11 @@
import { view } from "../../view.svelte"; import { view } from "../../view.svelte";
import { m } from "$lang/messages"; import { m } from "$lang/messages";
import { setOnboardingState } from "$lib/onboarding.svelte"; import { setOnboardingState } from "$lib/onboarding.svelte";
import { syncStores, updateStore } from "$lib/services/stores"; import { stores, syncStores, updateStore } from "$lib/services/stores.svelte";
const dev = getDeveloperToggle(); const dev = getDeveloperToggle();
const locationStores = stores("location");
</script> </script>
<SidebarHeader>{m["sidebar.developer.header"]()}</SidebarHeader> <SidebarHeader>{m["sidebar.developer.header"]()}</SidebarHeader>
@ -91,6 +94,21 @@
await updateStore({ name, type }, JSON.parse(data)); await updateStore({ name, type }, JSON.parse(data));
}} }}
/> />
<SettingsButton
icon={PackageMinusIcon}
text="Delete Store"
onclick={async () => {
const name = prompt("Store Name?");
if (!name) return;
const type = prompt("Store Type? (route, location, vehicle)");
if (type !== "route" && type !== "location" && type !== "vehicle") {
alert("Invalid type");
return;
}
await updateStore({ name, type }, null);
}}
/>
<span>LOCATION STORES: {JSON.stringify(locationStores.current)}</span>
</section> </section>
<section> <section>

View File

@ -53,6 +53,8 @@ export const db = await openDB<TCDB>("tc", 1, {
} }
}) })
const eventTarget = new EventTarget();
export async function syncStores() { export async function syncStores() {
const changes = await Promise.all(await db.getAll("changes").then(changes => changes.map(async change => { const changes = await Promise.all(await db.getAll("changes").then(changes => changes.map(async change => {
const storeData = await db.get("stores", change.id); const storeData = await db.get("stores", change.id);
@ -82,6 +84,7 @@ export async function syncStores() {
} else { } else {
await tx.objectStore("stores").put(store); await tx.objectStore("stores").put(store);
} }
eventTarget.dispatchEvent(new CustomEvent("store-updated", { detail: store }) );
} }
// Delete all changes // Delete all changes
await tx.objectStore("changes").clear(); await tx.objectStore("changes").clear();
@ -102,12 +105,14 @@ async function createStore(info: StoreInfo, data: object) {
await tx.objectStore("stores").add(store); await tx.objectStore("stores").add(store);
await tx.objectStore("changes").add({ id, operation: "create" }); await tx.objectStore("changes").add({ id, operation: "create" });
await tx.done; await tx.done;
eventTarget.dispatchEvent(new CustomEvent("store-updated", { detail: store }) );
return store; return store;
} }
export async function updateStore(info: StoreInfo, data: object) { export async function updateStore(info: StoreInfo, data: object | null) {
const store = await db.getFromIndex("stores", "by-name-and-type", [info.name, info.type]); const store = await db.getFromIndex("stores", "by-name-and-type", [info.name, info.type]);
if (!store) { if (!store) {
if(data === null) return;
return await createStore(info, data); return await createStore(info, data);
} }
// Update the store data // Update the store data
@ -117,5 +122,49 @@ export async function updateStore(info: StoreInfo, data: object) {
await tx.objectStore("stores").put(store); await tx.objectStore("stores").put(store);
await tx.objectStore("changes").add({ id: store.id, operation: "update" }); await tx.objectStore("changes").add({ id: store.id, operation: "update" });
await tx.done; await tx.done;
eventTarget.dispatchEvent(new CustomEvent("store-updated", { detail: store }) );
return store; return store;
} }
// export async function store<T extends object>(info: StoreInfo) {
// const store = await db.getFromIndex("stores", "by-name-and-type", [info.name, info.type]);
// if (!store) {
// return null;
// }
// const state = $state<T>(JSON.parse(store.data) as T);
// $effect(() => {
// updateStore(info, state);
// })
// eventTarget.addEventListener("store-updated", (event) => {
// const customEvent = event as CustomEvent;
// if(customEvent.detail.id === store.id) {
// const updatedStore = customEvent.detail as Store;
// Object.assign(state, JSON.parse(updatedStore.data));
// }
// });
// return state;
// }
export function stores<T extends object>(type: StoreType) {
const state = $state<T[]>([]);
eventTarget.addEventListener("store-updated", async (event) => {
const customEvent = event as CustomEvent;
const updatedStore = customEvent.detail as Store;
if(updatedStore.type === type) {
const stores = await db.getAllFromIndex("stores", "by-type", type);
state.splice(0, state.length, ...(stores.map(store => JSON.parse(store.data) as T).filter(store => store !== null)));
}
});
(async () => {
const stores = await db.getAllFromIndex("stores", "by-type", type);
state.splice(0, state.length, ...(stores.map(store => JSON.parse(store.data) as T).filter(store => store !== null)));
})();
return {
get current() {
return state;
},
set current(newValue: T[]) {
state.splice(0, state.length, ...newValue);
}
}
}