Fix reactive nature of generic resource store
Some checks failed
/ golint (push) Failing after 10s

These changes are meant to make it possible for new events that come in
to immediately render in the components that depend on them.
This commit is contained in:
Eli Ribble 2026-05-21 23:09:11 +00:00
parent 74ef9a8b3a
commit cecb9ef0f0
No known key found for this signature in database
5 changed files with 92 additions and 59 deletions

View file

@ -144,6 +144,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { computedAsync } from "@vueuse/core";
import MapMultipoint from "@/components/MapMultipoint.vue";
import TimeRelative from "@/components/TimeRelative.vue";
@ -151,6 +152,7 @@ import PublicReportCardCompliance from "@/components/PublicReportCardCompliance.
import PublicReportCardNuisance from "@/components/PublicReportCardNuisance.vue";
import PublicReportCardWater from "@/components/PublicReportCardWater.vue";
import { formatAddress } from "@/format";
import { log } from "@/log";
import { useStoreResource } from "@/store/resource";
import {
PublicReport,
@ -169,10 +171,15 @@ const emit = defineEmits<Emits>();
const props = defineProps<Props>();
const storeResource = useStoreResource();
const report = computedAsync(() => {
return storeResource.publicreport.byURI(props.reportURI);
const report = computed(() => {
const result = storeResource.publicreport.byURI.get(props.reportURI);
log.info("geting computed resource", props.reportURI, result);
return result;
});
function openPhotoViewer(index: number) {
emit("viewImage", index);
}
onMounted(async () => {
await storeResource.publicreport.fetchByURI(props.reportURI);
});
</script>

View file

@ -203,6 +203,7 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import ListCardCommunication from "@/components/ListCardCommunication.vue";
import { log } from "@/log";
import { Communication, LogEntry, PublicReport } from "@/type/api";
interface Props {
@ -263,11 +264,15 @@ const activeFilterCount = computed(() => {
// Filtered communications
const filteredCommunications = computed((): Communication[] => {
log.info(
"getting communication column list filteredCommunications",
props.all,
);
if (props.all == null) {
return [];
}
return props.all.filter((comm) => {
const filtered = props.all.filter((comm) => {
// Status filter
const selectedStatuses = Object.entries(statusFilters.value)
.filter(([_, isSelected]) => isSelected)
@ -299,5 +304,10 @@ const filteredCommunications = computed((): Communication[] => {
return true;
});
const sorted = filtered.sort((a: Communication, b: Communication): number => {
return b.created.getTime() - a.created.getTime();
});
log.info("filtered and sorted to", sorted);
return sorted;
});
</script>

View file

@ -1,8 +1,8 @@
import { defineStore } from "pinia";
import { ref, shallowRef } from "vue";
import { ref, shallowRef, shallowReactive } from "vue";
import { log } from "@/log";
import { SSEManager, SSEMessageResource } from "@/SSEManager";
import { useSessionStore } from "@/store/session";
import { apiClient } from "@/client";
import {
@ -23,7 +23,7 @@ function createResourceStore<dto, full extends uriHaver>(
api_base: string,
from_json: jsonConverter<dto, full>,
) {
const _resourceByURI = shallowRef<Map<string, full>>(new Map());
const byURI = shallowReactive<Map<string, full>>(new Map());
const _resourceFetchAll = ref<Promise<full[]> | null>(null);
const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
new Map(),
@ -33,32 +33,43 @@ function createResourceStore<dto, full extends uriHaver>(
SSEManager.subscribe((msg: SSEMessageResource) => {
if (msg.resource.startsWith(resource_name)) {
if (msg.type == "created") {
console.log("New resource", resource_name, msg.resource, msg.uri);
fetchByURI(msg.uri);
} else if (msg.type == "updated") {
console.log("Updated resource", resource_name, msg.resource, msg.uri);
fetchByURI(msg.uri);
}
}
});
async function byAll(): Promise<full[]> {
const cur = _resourceFetchAll.value;
if (cur) {
return cur;
}
return fetchAll();
}
async function byID(id: string): Promise<full> {
const uri = uriFromID(id);
return byURI(uri);
return ensureURI(uri);
}
async function byURI(uri: string): Promise<full> {
let cur = _resourceFetchByURI.value.get(uri);
if (cur) {
return cur;
async function ensureURI(uri: string): Promise<full> {
// Check if we already have the data
const existing = byURI.get(uri);
if (existing) {
return existing;
}
// Check if we're already fetching it
let fetchPromise = _resourceFetchByURI.value.get(uri);
if (fetchPromise) {
return fetchPromise;
}
// Start fetching
fetchPromise = fetchByURI(uri);
_resourceFetchByURI.value.set(uri, fetchPromise);
try {
const result = await fetchPromise;
return result;
} finally {
// Clean up the promise after it resolves
_resourceFetchByURI.value.delete(uri);
}
cur = fetchByURI(uri);
_resourceFetchByURI.value.set(uri, cur);
return cur;
}
async function fetchAll(): Promise<full[]> {
/*
@ -71,9 +82,13 @@ function createResourceStore<dto, full extends uriHaver>(
const url = `/api${api_base}`;
const dtos = (await apiClient.JSONGet(url)) as dto[];
const resources = dtos.map((m: dto) => from_json(m));
//let new_all = new Map<string, full>();
resources.forEach((r: full) => {
_resourceByURI.value.set(r.uri, r);
byURI.set(r.uri, r);
//new_all.set(r.uri, r);
log.info("Set", resource_name, r);
});
//byURI.value = new_all;
return resources;
}
async function fetchByID(id: string): Promise<full> {
@ -89,19 +104,16 @@ function createResourceStore<dto, full extends uriHaver>(
}
const body: dto = await response.json();
const report = from_json(body);
_resourceByURI.value.set(report.uri, report);
//let new_all = new Map<string, full>(byURI.value);
//new_all.set(report.uri, report);
byURI.set(report.uri, report);
//byURI.value = new_all;
return report;
} catch (err) {
console.error("Error loading users:", err);
throw err;
}
}
function getAll(): full[] | null {
if (_resourceFetchAll) {
return Array.from(_resourceByURI.value.values());
}
return null;
}
function hasAll(): boolean {
return !!_resourceFetchAll.value;
}
@ -118,13 +130,12 @@ function createResourceStore<dto, full extends uriHaver>(
return `${api_base}/${id}`;
}
return {
byAll,
byID,
byURI,
ensureURI,
fetchAll,
fetchByID,
fetchByURI,
getAll,
hasAll,
loadingAll,
loadingURI,

View file

@ -23,10 +23,7 @@
</template>
<template #center>
<CommunicationColumnDetail
:loading="
storePublicReport.loading ||
storeResource.communication.loadingURI(selectedCommunication?.uri)
"
:loading="storePublicReport.loading || loadingSelectedCommunication"
:mapBounds="mapBounds || undefined"
:mapMarkers="mapMarkers"
:selectedCommunication="selectedCommunication"
@ -36,10 +33,7 @@
</template>
<template #right>
<CommunicationColumnAction
:isLoading="
storePublicReport.loading ||
storeResource.communication.loadingURI(selectedCommunication?.uri)
"
:isLoading="storePublicReport.loading || loadingSelectedCommunication"
@markInvalid="markInvalid"
@markPendingResponse="markPendingResponse"
@markPossibleIssue="markPossibleIssue"
@ -78,6 +72,7 @@ import ImageViewerModal from "@/components/ImageViewerModal.vue";
import ThreeColumn from "@/components/layout/ThreeColumn.vue";
import ToastNotification from "@/components/ToastNotification.vue";
import { useQueryParam } from "@/composable/use-query-param";
import { log } from "@/log";
import { SSEManager } from "@/SSEManager";
import { useStoreResource } from "@/store/resource";
import { useSessionStore } from "@/store/session";
@ -111,10 +106,13 @@ const currentImages = computed(() => {
}
return selectedReport.value?.images ?? [];
});
const loadingSelectedCommunication = computed<boolean>((): boolean => {
return !!selectedCommunication.value;
});
const mapBounds = computed<LngLatBounds | null>((): LngLatBounds | null => {
let bounds = new Bounds();
const loc = selectedReport.value?.location;
console.log("updating for loc", loc);
log.info("updating for loc", loc);
if (loc && loc.latitude != 0 && loc.longitude != 0) {
bounds.addLocation(loc);
}
@ -174,15 +172,16 @@ const mapMarkers = computed<Marker[]>((): Marker[] => {
}
return markers;
});
const selectedCommunication = computedAsync(
async (): Promise<Communication | undefined> => {
if (selectedId.value == undefined) {
return undefined;
}
const all = await storeResource.communication.byAll();
return all.find((c: Communication) => c.id == selectedId.value);
},
);
const selectedCommunication = computed((): Communication | undefined => {
log.info("get selectedCommunication", selectedId.value);
if (!selectedId.value) {
return undefined;
}
const all = Array.from(storeResource.communication.byURI.values());
const result = all.find((c: Communication) => c.id == selectedId.value);
log.info("selectedCommunication", selectedId.value, result);
return result;
});
const selectedReport = computedAsync(
async (): Promise<PublicReport | undefined> => {
if (
@ -195,14 +194,20 @@ const selectedReport = computedAsync(
return await storePublicReport.byURI(selectedCommunication.value.source);
},
);
const visibleCommunications = computedAsync(
async (): Promise<Communication[]> => {
const all = await storeResource.communication.byAll();
return all.filter((c: Communication) => {
return c.status == "new" || c.status == "opened";
});
},
);
const visibleCommunications = computed((): Communication[] | undefined => {
log.info("get visibleCommunications", storeResource.communication.byURI);
if (!storeResource.communication.byURI) {
return undefined;
}
const all: Communication[] = Array.from(
storeResource.communication.byURI.values(),
);
const result = all.filter((c: Communication) => {
return c.status == "new" || c.status == "opened";
});
log.info("visibleCommunications:", result);
return result;
});
const handleDeselect = (id: string) => {
selectedId.value = undefined;
};
@ -256,7 +261,7 @@ async function sendMessage(message: string) {
if (selectedCommunication.value == null) return;
if (selectedReport.value == null) return;
if (session.urls == null) return;
console.log("Sending message reporter:", message);
log.info("Sending message reporter:", message);
const payload = {
message: message,

View file

@ -28,7 +28,7 @@ import { Contact } from "@/type/api";
const storeResource = useStoreResource();
const contacts = computedAsync(() => {
return storeResource.contact.byAll();
return Array.from(storeResource.contact.byURI.values());
});
const selectedContact = ref<Contact | undefined>(undefined);
const handleSelectionChange = (selection: Contact | undefined) => {