less restrictive on receiving websockets

This commit is contained in:
Carter 2026-06-25 12:15:49 -06:00
parent b9a13c5781
commit e8cc509915
2 changed files with 70 additions and 34 deletions

View file

@ -70,12 +70,6 @@ interface Props {
refreshCallback: () => void; refreshCallback: () => void;
} }
interface PendingInteraction {
since: string;
seenUnsynced: boolean;
accepted: boolean;
}
export default function InteractionsOverview(props: Props) { export default function InteractionsOverview(props: Props) {
const classes = useStyles(); const classes = useStyles();
const [{as, user}] = useGlobalState(); const [{as, user}] = useGlobalState();
@ -87,12 +81,21 @@ export default function InteractionsOverview(props: Props) {
const [interactions, setInteractions] = useState<Interaction[]>(props.interactions); const [interactions, setInteractions] = useState<Interaction[]>(props.interactions);
const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map()); const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map());
const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(new Map()); const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(new Map());
const [pendingInteractions, setPendingInteractions] = useState<Map<string, PendingInteraction>>(new Map()); const [acceptedInteractions, setAcceptedInteractions] = useState<Set<string>>(new Set());
const [renderToggle, setRenderToggle] = useState(false); const [renderToggle, setRenderToggle] = useState(false);
const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>( const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>(
undefined 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(() => { useEffect(() => {
if (components !== prevComponents) { if (components !== prevComponents) {
let initMappedComponents: Map<string, Component> = new Map(); let initMappedComponents: Map<string, Component> = new Map();
@ -103,25 +106,30 @@ export default function InteractionsOverview(props: Props) {
} }
if (props.interactions !== prevInteractions) { if (props.interactions !== prevInteractions) {
setPendingInteractions((prevPending) => { console.log("[interactions:effect] props.interactions changed, prevInteractions was", prevInteractions ? "defined" : "undefined");
const updatedPending = new Map(prevPending);
props.interactions.forEach((interaction: Interaction) => { props.interactions.forEach((interaction: Interaction) => {
const existing = updatedPending.get(interaction.key()); 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 key = interaction.key();
const prevInteraction = prevInteractions?.find(p => p.key() === key);
if (!interaction.status.synced) { if (!interaction.status.synced) {
if (!existing || existing.accepted) { updated.delete(key);
updatedPending.set(interaction.key(), { } else if (prevInteraction && !prevInteraction.status.synced) {
since: interaction.status.lastUpdate || moment().toISOString(), console.log("[interactions:effect] ACCEPTED", key);
seenUnsynced: true, updated.add(key);
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 });
} }
}); });
return updatedPending; return updated;
}); });
setInteractions(cloneDeep(props.interactions)); setInteractions(cloneDeep(props.interactions));
} }
@ -229,20 +237,30 @@ export default function InteractionsOverview(props: Props) {
index: number, index: number,
settings: pond.IInteractionSettings 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 interactionsAPI
.updateInteraction(Number(deviceID), settings, as) .updateInteraction(Number(deviceID), settings, as)
.then((_response: any) => { .then((_response: any) => {
console.log("[interactions:submit] API success, setting synced=false for index", index);
let updatedDirtyInteractions = cloneDeep(dirtyInteractions); let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
updatedDirtyInteractions.set(index, false); updatedDirtyInteractions.set(index, false);
setDirtyInteractions(updatedDirtyInteractions); 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()); success("Successfully updated the interaction for " + component.name());
}) })
.catch((_err: any) => { .catch((_err: any) => {
@ -261,13 +279,12 @@ export default function InteractionsOverview(props: Props) {
let key = interaction.key(); let key = interaction.key();
let source = mappedComponents.get(componentIDToString(interaction.settings.source)); let source = mappedComponents.get(componentIDToString(interaction.settings.source));
let sink = mappedComponents.get(componentIDToString(interaction.settings.sink)); let sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
const pending = pendingInteractions.get(interaction.key()); const isAccepted = acceptedInteractions.has(interaction.key());
const isAccepted = Boolean(pending?.accepted); const isPending = !interaction.status.synced;
const isPending = Boolean(pending && !pending.accepted);
let statusText = isAccepted let statusText = isAccepted
? "Pending change accepted" ? "Pending change accepted"
: isPending : isPending
? "Pending " + moment(pending.since).fromNow() ? "Pending " + moment(interaction.status.lastUpdate).fromNow()
: ""; : "";
let schedule = pond.InteractionSchedule.create( let schedule = pond.InteractionSchedule.create(
interaction.settings.schedule !== null ? interaction.settings.schedule : undefined interaction.settings.schedule !== null ? interaction.settings.schedule : undefined

View file

@ -304,11 +304,30 @@ export default function DevicePage() {
onMessage: (data) => { onMessage: (data) => {
if (!data) return; if (!data) return;
const { key, interaction } = data; const { key, interaction } = data;
console.log("[ws:interaction] received", key, {
synced: interaction.status.synced,
lastUpdate: interaction.status.lastUpdate,
lastSynced: interaction.status.lastSynced,
});
setInteractions((prev) => { setInteractions((prev) => {
const index = prev.findIndex((existing) => existing.key() === key); const index = prev.findIndex((existing) => existing.key() === key);
if (index < 0) { if (index < 0) {
return [...prev, interaction]; 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]; const updated = [...prev];
updated[index] = interaction; updated[index] = interaction;
return updated; return updated;