From e8cc5099150384a2838f517af98b72d93adffdbd Mon Sep 17 00:00:00 2001 From: Carter Date: Thu, 25 Jun 2026 12:15:49 -0600 Subject: [PATCH] less restrictive on receiving websockets --- src/interactions/InteractionsOverview.tsx | 85 ++++++++++++++--------- src/pages/Device.tsx | 19 +++++ 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/interactions/InteractionsOverview.tsx b/src/interactions/InteractionsOverview.tsx index b579660..450c44f 100644 --- a/src/interactions/InteractionsOverview.tsx +++ b/src/interactions/InteractionsOverview.tsx @@ -70,12 +70,6 @@ interface Props { refreshCallback: () => void; } -interface PendingInteraction { - since: string; - seenUnsynced: boolean; - accepted: boolean; -} - export default function InteractionsOverview(props: Props) { const classes = useStyles(); const [{as, user}] = useGlobalState(); @@ -87,12 +81,21 @@ export default function InteractionsOverview(props: Props) { const [interactions, setInteractions] = useState(props.interactions); const [dirtyInteractions, setDirtyInteractions] = useState>(new Map()); const [mappedComponents, setMappedComponents] = useState>(new Map()); - const [pendingInteractions, setPendingInteractions] = useState>(new Map()); + const [acceptedInteractions, setAcceptedInteractions] = useState>(new Set()); const [renderToggle, setRenderToggle] = useState(false); const [selectedInteraction, setSelectedInteraction] = useState( undefined ); + useEffect(() => { + console.log("[interactions:mount] initial interactions:", props.interactions.map(i => ({ + key: i.key(), + synced: i.status.synced, + lastUpdate: i.status.lastUpdate, + lastSynced: i.status.lastSynced, + }))); + }, []); + useEffect(() => { if (components !== prevComponents) { let initMappedComponents: Map = new Map(); @@ -103,25 +106,30 @@ export default function InteractionsOverview(props: Props) { } if (props.interactions !== prevInteractions) { - setPendingInteractions((prevPending) => { - const updatedPending = new Map(prevPending); + console.log("[interactions:effect] props.interactions changed, prevInteractions was", prevInteractions ? "defined" : "undefined"); + props.interactions.forEach((interaction: Interaction) => { + const key = interaction.key(); + const prevInteraction = prevInteractions?.find(p => p.key() === key); + console.log("[interactions:effect]", key, { + synced: interaction.status.synced, + lastUpdate: interaction.status.lastUpdate, + prevSynced: prevInteraction?.status.synced, + prevLastUpdate: prevInteraction?.status.lastUpdate, + }); + }); + setAcceptedInteractions(prev => { + const updated = new Set(prev); props.interactions.forEach((interaction: Interaction) => { - const existing = updatedPending.get(interaction.key()); + const key = interaction.key(); + const prevInteraction = prevInteractions?.find(p => p.key() === key); if (!interaction.status.synced) { - if (!existing || existing.accepted) { - updatedPending.set(interaction.key(), { - since: interaction.status.lastUpdate || moment().toISOString(), - seenUnsynced: true, - accepted: false - }); - } else if (!existing.seenUnsynced) { - updatedPending.set(interaction.key(), { ...existing, seenUnsynced: true }); - } - } else if (existing && existing.seenUnsynced && !existing.accepted) { - updatedPending.set(interaction.key(), { ...existing, accepted: true }); + updated.delete(key); + } else if (prevInteraction && !prevInteraction.status.synced) { + console.log("[interactions:effect] ACCEPTED", key); + updated.add(key); } }); - return updatedPending; + return updated; }); setInteractions(cloneDeep(props.interactions)); } @@ -229,20 +237,30 @@ export default function InteractionsOverview(props: Props) { index: number, settings: pond.IInteractionSettings ) => { - const interactionKey = settings.key ?? ""; - setPendingInteractions((prevPending) => { - const updatedPending = new Map(prevPending); - if (interactionKey) { - updatedPending.set(interactionKey, { since: moment().toISOString(), seenUnsynced: false, accepted: false }); - } - return updatedPending; - }); interactionsAPI .updateInteraction(Number(deviceID), settings, as) .then((_response: any) => { + console.log("[interactions:submit] API success, setting synced=false for index", index); let updatedDirtyInteractions = cloneDeep(dirtyInteractions); updatedDirtyInteractions.set(index, false); setDirtyInteractions(updatedDirtyInteractions); + setInteractions(prev => { + const updated = cloneDeep(prev); + if (updated[index]) { + updated[index].status.synced = false; + updated[index].status.lastUpdate = moment().toISOString(); + console.log("[interactions:submit] local state updated for", updated[index].key()); + } + return updated; + }); + const interactionKey = settings.key ?? ""; + if (interactionKey) { + setAcceptedInteractions(prev => { + const updated = new Set(prev); + updated.delete(interactionKey); + return updated; + }); + } success("Successfully updated the interaction for " + component.name()); }) .catch((_err: any) => { @@ -261,13 +279,12 @@ export default function InteractionsOverview(props: Props) { let key = interaction.key(); let source = mappedComponents.get(componentIDToString(interaction.settings.source)); let sink = mappedComponents.get(componentIDToString(interaction.settings.sink)); - const pending = pendingInteractions.get(interaction.key()); - const isAccepted = Boolean(pending?.accepted); - const isPending = Boolean(pending && !pending.accepted); + const isAccepted = acceptedInteractions.has(interaction.key()); + const isPending = !interaction.status.synced; let statusText = isAccepted ? "Pending change accepted" : isPending - ? "Pending " + moment(pending.since).fromNow() + ? "Pending " + moment(interaction.status.lastUpdate).fromNow() : ""; let schedule = pond.InteractionSchedule.create( interaction.settings.schedule !== null ? interaction.settings.schedule : undefined diff --git a/src/pages/Device.tsx b/src/pages/Device.tsx index 96b06b6..733d621 100644 --- a/src/pages/Device.tsx +++ b/src/pages/Device.tsx @@ -304,11 +304,30 @@ export default function DevicePage() { onMessage: (data) => { if (!data) return; const { key, interaction } = data; + console.log("[ws:interaction] received", key, { + synced: interaction.status.synced, + lastUpdate: interaction.status.lastUpdate, + lastSynced: interaction.status.lastSynced, + }); setInteractions((prev) => { const index = prev.findIndex((existing) => existing.key() === key); if (index < 0) { return [...prev, interaction]; } + const existing = prev[index]; + const existingLastUpdate = getTimestampMillis(existing.status.lastUpdate); + const incomingLastUpdate = getTimestampMillis(interaction.status.lastUpdate); + const incomingLastSynced = getTimestampMillis(interaction.status.lastSynced); + if (existingLastUpdate !== undefined && incomingLastUpdate !== undefined && + existingLastUpdate > incomingLastUpdate) { + return prev; + } + if (!existing.status.synced && interaction.status.synced && + incomingLastUpdate !== undefined && + existingLastUpdate === incomingLastUpdate && + (incomingLastSynced === undefined || incomingLastSynced < incomingLastUpdate)) { + return prev; + } const updated = [...prev]; updated[index] = interaction; return updated;