Merge branch 'pending_changes' into dev_environment
This commit is contained in:
commit
840c3b0126
5 changed files with 83 additions and 39 deletions
|
|
@ -217,7 +217,8 @@ export default function ComponentSettings(props: Props) {
|
|||
}, [props.component, componentTypeOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.component !== prevComponent || (!isDialogOpen && prevIsDialogOpen)) {
|
||||
if (isDialogOpen && prevIsDialogOpen) return;
|
||||
if (props.component !== prevComponent || isDialogOpen !== prevIsDialogOpen) {
|
||||
init();
|
||||
}
|
||||
}, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]);
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ export default function DeviceSettings(props: Props) {
|
|||
const deviceAPI = useDeviceAPI();
|
||||
const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props;
|
||||
const prevDevice = usePrevious(device);
|
||||
const prevIsDialogOpen = usePrevious(isDialogOpen);
|
||||
const [deviceForm, setDeviceForm] = useState<Device>(deviceFromForm(Device.clone(device)));
|
||||
const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState<boolean>(false);
|
||||
const [sleeps, setSleeps] = useState<boolean>(device.settings.sleepDurationS > 0);
|
||||
|
|
@ -110,7 +111,8 @@ export default function DeviceSettings(props: Props) {
|
|||
const [existingMutation, setExistingMutation] = useState<pond.LinearMutation>();
|
||||
|
||||
useEffect(() => {
|
||||
if (prevDevice !== device) {
|
||||
if (isDialogOpen && prevIsDialogOpen) return;
|
||||
if (prevDevice !== device || isDialogOpen !== prevIsDialogOpen) {
|
||||
setDeviceForm(deviceFromForm(Device.clone(props.device)));
|
||||
if (device.settings.extensionComponents[0]) {
|
||||
setCompExtOne(device.settings.extensionComponents[0]);
|
||||
|
|
@ -130,7 +132,7 @@ export default function DeviceSettings(props: Props) {
|
|||
setComponentsByDevice(cbd);
|
||||
}
|
||||
}
|
||||
}, [device, prevDevice, props.device, components]);
|
||||
}, [device, prevDevice, props.device, components, isDialogOpen, prevIsDialogOpen]);
|
||||
|
||||
const close = () => {
|
||||
closeDialogCallback();
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ export default function InteractionSettings(props: Props) {
|
|||
const prevInitialInteraction = usePrevious(initialInteraction);
|
||||
const prevComponents = usePrevious(components);
|
||||
const prevInitialComponent = usePrevious(initialComponent);
|
||||
const prevIsDialogOpen = usePrevious(isDialogOpen);
|
||||
const classes = useStyles();
|
||||
const [{ user, as }] = useGlobalState();
|
||||
const interactionsAPI = useInteractionsAPI();
|
||||
|
|
@ -232,10 +233,12 @@ export default function InteractionSettings(props: Props) {
|
|||
if (user && user.settings.timezone) {
|
||||
setTimezone(user.settings.timezone);
|
||||
}
|
||||
if (isDialogOpen && prevIsDialogOpen) return;
|
||||
if (
|
||||
prevInitialInteraction !== initialInteraction ||
|
||||
(prevComponents && prevComponents.length !== components.length) ||
|
||||
getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent)
|
||||
getComponentIDString(prevInitialComponent) !== getComponentIDString(initialComponent) ||
|
||||
isDialogOpen !== prevIsDialogOpen
|
||||
) {
|
||||
setDefaultState();
|
||||
}
|
||||
|
|
@ -247,7 +250,9 @@ export default function InteractionSettings(props: Props) {
|
|||
prevInitialComponent,
|
||||
prevInitialInteraction,
|
||||
setDefaultState,
|
||||
user
|
||||
user,
|
||||
isDialogOpen,
|
||||
prevIsDialogOpen
|
||||
]);
|
||||
|
||||
const getAvailableSinks = () => {
|
||||
|
|
|
|||
|
|
@ -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<Interaction[]>(props.interactions);
|
||||
const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(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 [selectedInteraction, setSelectedInteraction] = useState<Interaction | 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(() => {
|
||||
if (components !== prevComponents) {
|
||||
let initMappedComponents: Map<string, Component> = 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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue