mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
fix: Revert "fix: Offline Shopping List Fixes" (#3835)
This commit is contained in:
parent
7931e383b2
commit
d639d168fa
@ -1,8 +1,7 @@
|
||||
import { computed, ref } from "@nuxtjs/composition-api";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/group";
|
||||
import { RequestResponse } from "~/lib/api/types/non-generated";
|
||||
import { ShoppingListItemOut } from "~/lib/api/types/group";
|
||||
|
||||
const localStorageKey = "shopping-list-queue";
|
||||
const queueTimeout = 5 * 60 * 1000; // 5 minutes
|
||||
@ -24,58 +23,21 @@ interface Storage {
|
||||
export function useShoppingListItemActions(shoppingListId: string) {
|
||||
const api = useUserApi();
|
||||
const storage = useLocalStorage(localStorageKey, {} as Storage, { deep: true });
|
||||
const queue = getQueue();
|
||||
const queue = storage.value[shoppingListId] ||= { create: [], update: [], delete: [], lastUpdate: Date.now()};
|
||||
const queueEmpty = computed(() => !queue.create.length && !queue.update.length && !queue.delete.length);
|
||||
if (queueEmpty.value) {
|
||||
queue.lastUpdate = Date.now();
|
||||
storage.value[shoppingListId].lastUpdate = queue.lastUpdate;
|
||||
}
|
||||
|
||||
const isOffline = ref(false);
|
||||
|
||||
function isValidQueueObject(obj: any): obj is ShoppingListQueue {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasRequiredProps = "create" in obj && "update" in obj && "delete" in obj && "lastUpdate" in obj;
|
||||
if (!hasRequiredProps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arraysValid = Array.isArray(obj.create) && Array.isArray(obj.update) && Array.isArray(obj.delete);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const lastUpdateValid = typeof obj.lastUpdate === "number" && !isNaN(new Date(obj.lastUpdate).getTime());
|
||||
|
||||
return arraysValid && lastUpdateValid;
|
||||
}
|
||||
|
||||
function createEmptyQueue(): ShoppingListQueue {
|
||||
return { create: [], update: [], delete: [], lastUpdate: Date.now() };
|
||||
}
|
||||
|
||||
function getQueue(): ShoppingListQueue {
|
||||
try {
|
||||
const queue = storage.value[shoppingListId];
|
||||
if (!isValidQueueObject(queue)) {
|
||||
console.log("Invalid queue object in local storage; resetting queue.");
|
||||
return createEmptyQueue();
|
||||
} else {
|
||||
return queue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error validating queue object in local storage; resetting queue.", error);
|
||||
return createEmptyQueue();
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromQueue(itemQueue: ShoppingListItemOut[], item: ShoppingListItemOut): boolean {
|
||||
const index = itemQueue.findIndex(i => i.id === item.id);
|
||||
function removeFromQueue(queue: ShoppingListItemOut[], item: ShoppingListItemOut): boolean {
|
||||
const index = queue.findIndex(i => i.id === item.id);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
itemQueue.splice(index, 1);
|
||||
queue.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -88,7 +50,6 @@ export function useShoppingListItemActions(shoppingListId: string) {
|
||||
function createItem(item: ShoppingListItemOut) {
|
||||
removeFromQueue(queue.create, item);
|
||||
queue.create.push(item);
|
||||
storage.value[shoppingListId] = { ...queue };
|
||||
}
|
||||
|
||||
function updateItem(item: ShoppingListItemOut) {
|
||||
@ -96,13 +57,11 @@ export function useShoppingListItemActions(shoppingListId: string) {
|
||||
if (removedFromCreate) {
|
||||
// this item hasn't been created yet, so we don't need to update it
|
||||
queue.create.push(item);
|
||||
storage.value[shoppingListId] = { ...queue };
|
||||
return;
|
||||
}
|
||||
|
||||
removeFromQueue(queue.update, item);
|
||||
queue.update.push(item);
|
||||
storage.value[shoppingListId] = { ...queue };
|
||||
}
|
||||
|
||||
function deleteItem(item: ShoppingListItemOut) {
|
||||
@ -115,7 +74,6 @@ export function useShoppingListItemActions(shoppingListId: string) {
|
||||
removeFromQueue(queue.update, item);
|
||||
removeFromQueue(queue.delete, item);
|
||||
queue.delete.push(item);
|
||||
storage.value[shoppingListId] = { ...queue };
|
||||
}
|
||||
|
||||
function getQueueItems(itemQueueType: ItemQueueType) {
|
||||
@ -132,9 +90,6 @@ export function useShoppingListItemActions(shoppingListId: string) {
|
||||
if (itemQueueType === "delete" || itemQueueType === "all") {
|
||||
queue.delete = itemIds ? queue.delete.filter(item => !itemIds.includes(item.id)) : [];
|
||||
}
|
||||
if (queueEmpty.value) {
|
||||
queue.lastUpdate = Date.now();
|
||||
}
|
||||
|
||||
// Set the storage value explicitly so changes are saved in the browser.
|
||||
storage.value[shoppingListId] = { ...queue };
|
||||
@ -143,60 +98,36 @@ export function useShoppingListItemActions(shoppingListId: string) {
|
||||
/**
|
||||
* Handles the response from the backend and sets the isOffline flag if necessary.
|
||||
*/
|
||||
function handleResponse(response: RequestResponse<any>) {
|
||||
isOffline.value = response?.response?.status === undefined;
|
||||
function handleResponse(response: any) {
|
||||
// TODO: is there a better way of checking for network errors?
|
||||
isOffline.value = response.error?.message?.includes("Network Error") || false;
|
||||
}
|
||||
|
||||
function checkUpdateState(list: ShoppingListOut) {
|
||||
const cutoffDate = new Date(queue.lastUpdate + queueTimeout).toISOString();
|
||||
if (list.updateAt && list.updateAt > cutoffDate) {
|
||||
// If the queue is too far behind the shopping list to reliably do updates, we clear the queue
|
||||
console.log("Out of sync with server; clearing queue");
|
||||
clearQueueItems("all");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the queue items and returns whether the processing was successful.
|
||||
*/
|
||||
async function processQueueItems(
|
||||
action: (items: ShoppingListItemOut[]) => Promise<RequestResponse<any>>,
|
||||
action: (items: ShoppingListItemOut[]) => Promise<any>,
|
||||
itemQueueType: ItemQueueType,
|
||||
): Promise<boolean> {
|
||||
let queueItems: ShoppingListItemOut[];
|
||||
try {
|
||||
queueItems = getQueueItems(itemQueueType);
|
||||
if (!queueItems.length) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error fetching queue items of type ${itemQueueType}:`, error);
|
||||
clearQueueItems(itemQueueType);
|
||||
return false;
|
||||
) {
|
||||
const queueItems = getQueueItems(itemQueueType);
|
||||
if (!queueItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const itemsToProcess = [...queueItems];
|
||||
await action(itemsToProcess)
|
||||
.then((response) => {
|
||||
handleResponse(response);
|
||||
if (!isOffline.value) {
|
||||
clearQueueItems(itemQueueType, itemsToProcess.map(item => item.id));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`Error processing queue items of type ${itemQueueType}:`, error);
|
||||
clearQueueItems(itemQueueType);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
const itemsToProcess = [...queueItems];
|
||||
await action(itemsToProcess)
|
||||
.then((response) => {
|
||||
handleResponse(response);
|
||||
if (!isOffline.value) {
|
||||
clearQueueItems(itemQueueType, itemsToProcess.map(item => item.id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function process() {
|
||||
if(queueEmpty.value) {
|
||||
queue.lastUpdate = Date.now();
|
||||
storage.value[shoppingListId].lastUpdate = queue.lastUpdate;
|
||||
if(
|
||||
!queue.create.length &&
|
||||
!queue.update.length &&
|
||||
!queue.delete.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -204,20 +135,21 @@ export function useShoppingListItemActions(shoppingListId: string) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
checkUpdateState(data);
|
||||
|
||||
// We send each bulk request one at a time, since the backend may merge items
|
||||
// "failures" here refers to an actual error, rather than failing to reach the backend
|
||||
let failures = 0;
|
||||
if (!(await processQueueItems((items) => api.shopping.items.deleteMany(items), "delete"))) failures++;
|
||||
if (!(await processQueueItems((items) => api.shopping.items.updateMany(items), "update"))) failures++;
|
||||
if (!(await processQueueItems((items) => api.shopping.items.createMany(items), "create"))) failures++;
|
||||
const cutoffDate = new Date(queue.lastUpdate + queueTimeout).toISOString();
|
||||
if (data.updateAt && data.updateAt > cutoffDate) {
|
||||
// If the queue is too far behind the shopping list to reliably do updates, we clear the queue
|
||||
clearQueueItems("all");
|
||||
} else {
|
||||
// We send each bulk request one at a time, since the backend may merge items
|
||||
await processQueueItems((items) => api.shopping.items.deleteMany(items), "delete");
|
||||
await processQueueItems((items) => api.shopping.items.updateMany(items), "update");
|
||||
await processQueueItems((items) => api.shopping.items.createMany(items), "create");
|
||||
}
|
||||
|
||||
// If we're online, or the queue is empty, the queue is fully processed, so we're up to date
|
||||
// Otherwise, if all three queue processes failed, we've already reset the queue, so we need to reset the date
|
||||
if (!isOffline.value || queueEmpty.value || failures === 3) {
|
||||
// If we're online, the queue is fully processed, so we're up to date
|
||||
if (!isOffline.value) {
|
||||
queue.lastUpdate = Date.now();
|
||||
storage.value[shoppingListId].lastUpdate = queue.lastUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user