chatgpt added offline items update
This commit is contained in:
@@ -3,6 +3,7 @@ from flask_login import login_required, current_user
|
|||||||
from models import db
|
from models import db
|
||||||
from models import Item, ListOfItems
|
from models import Item, ListOfItems
|
||||||
from sqlalchemy import desc, text
|
from sqlalchemy import desc, text
|
||||||
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
from log import Log
|
from log import Log
|
||||||
from auth import create_cookie
|
from auth import create_cookie
|
||||||
@@ -23,6 +24,28 @@ def require_item_access(itemid):
|
|||||||
abort(403)
|
abort(403)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
def client_update_time():
|
||||||
|
client_updated_at = request.args.get("client_updated_at")
|
||||||
|
if not client_updated_at:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return datetime.fromtimestamp(int(client_updated_at) / 1000)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def item_update_is_current(item):
|
||||||
|
updated_at = client_update_time()
|
||||||
|
if updated_at is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return item.updated_at is None or updated_at >= item.updated_at
|
||||||
|
|
||||||
|
def stamp_item_update(item):
|
||||||
|
updated_at = client_update_time()
|
||||||
|
if updated_at is not None:
|
||||||
|
item.updated_at = updated_at
|
||||||
|
|
||||||
# home screen for items
|
# home screen for items
|
||||||
@items_bp.route("/items/<listid>")
|
@items_bp.route("/items/<listid>")
|
||||||
@login_required
|
@login_required
|
||||||
@@ -42,7 +65,10 @@ def items(listid):
|
|||||||
@login_required
|
@login_required
|
||||||
def item_update(itemid, itemchecked):
|
def item_update(itemid, itemchecked):
|
||||||
i = require_item_access(itemid)
|
i = require_item_access(itemid)
|
||||||
|
if not item_update_is_current(i):
|
||||||
|
return '', 204
|
||||||
i.is_checked = True if itemchecked == 'true' else False
|
i.is_checked = True if itemchecked == 'true' else False
|
||||||
|
stamp_item_update(i)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
@@ -170,6 +196,8 @@ def items_multiappend(listid):
|
|||||||
@login_required
|
@login_required
|
||||||
def item_delete(itemid):
|
def item_delete(itemid):
|
||||||
i = require_item_access(itemid)
|
i = require_item_access(itemid)
|
||||||
|
if not item_update_is_current(i):
|
||||||
|
return redirect(url_for("items.items_append", listid=i.listofitems_id))
|
||||||
listid = i.listofitems_id
|
listid = i.listofitems_id
|
||||||
db.session.delete(i)
|
db.session.delete(i)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -195,6 +223,12 @@ def items_clean(listid):
|
|||||||
@login_required
|
@login_required
|
||||||
def item_addone(itemid):
|
def item_addone(itemid):
|
||||||
i = require_item_access(itemid)
|
i = require_item_access(itemid)
|
||||||
|
if not item_update_is_current(i):
|
||||||
|
resp = make_response(render_template("items_append.html", user=current_user,
|
||||||
|
items=Item.query.filter_by(listofitems_id=i.listofitems_id).order_by(Item.label).all(),
|
||||||
|
logo_url=url_for("items.items", listid=i.listofitems_id)))
|
||||||
|
resp.set_cookie('username', create_cookie(current_user.id))
|
||||||
|
return resp
|
||||||
listid = i.listofitems_id
|
listid = i.listofitems_id
|
||||||
# if it is checked or not on the list yet, set quantity to one
|
# if it is checked or not on the list yet, set quantity to one
|
||||||
if i.is_suggestion or i.is_checked:
|
if i.is_suggestion or i.is_checked:
|
||||||
@@ -208,6 +242,7 @@ def item_addone(itemid):
|
|||||||
# if no unit given, make it 'x'
|
# if no unit given, make it 'x'
|
||||||
if not i.unit:
|
if not i.unit:
|
||||||
i.unit = 'x'
|
i.unit = 'x'
|
||||||
|
stamp_item_update(i)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
resp = make_response(render_template("items_append.html", user=current_user,
|
resp = make_response(render_template("items_append.html", user=current_user,
|
||||||
@@ -221,6 +256,12 @@ def item_addone(itemid):
|
|||||||
@login_required
|
@login_required
|
||||||
def item_addquantity(itemid, quantity):
|
def item_addquantity(itemid, quantity):
|
||||||
i = require_item_access(itemid)
|
i = require_item_access(itemid)
|
||||||
|
if not item_update_is_current(i):
|
||||||
|
resp = make_response(render_template("items_append.html", user=current_user,
|
||||||
|
items=Item.query.filter_by(listofitems_id=i.listofitems_id).order_by(Item.label).all(),
|
||||||
|
logo_url=url_for("items.items", listid=i.listofitems_id)))
|
||||||
|
resp.set_cookie('username', create_cookie(current_user.id))
|
||||||
|
return resp
|
||||||
listid = i.listofitems_id
|
listid = i.listofitems_id
|
||||||
# always add the item to the active todo
|
# always add the item to the active todo
|
||||||
if i.is_suggestion or i.is_checked:
|
if i.is_suggestion or i.is_checked:
|
||||||
@@ -229,6 +270,7 @@ def item_addquantity(itemid, quantity):
|
|||||||
i.is_checked = False
|
i.is_checked = False
|
||||||
else:
|
else:
|
||||||
i.quantity += int(quantity)
|
i.quantity += int(quantity)
|
||||||
|
stamp_item_update(i)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
resp = make_response(render_template("items_append.html", user=current_user,
|
resp = make_response(render_template("items_append.html", user=current_user,
|
||||||
@@ -242,6 +284,9 @@ def item_addquantity(itemid, quantity):
|
|||||||
@login_required
|
@login_required
|
||||||
def item_upitem_update_categorydate(itemid, category):
|
def item_upitem_update_categorydate(itemid, category):
|
||||||
i = require_item_access(itemid)
|
i = require_item_access(itemid)
|
||||||
|
if not item_update_is_current(i):
|
||||||
|
return '', 204
|
||||||
i.category = category
|
i.category = category
|
||||||
|
stamp_item_update(i)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
(function () {
|
||||||
|
const STORAGE_KEY = "lapp.pendingItemUpdates";
|
||||||
|
|
||||||
|
function readQueue() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeQueue(queue) {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(queue));
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueRequest(request) {
|
||||||
|
const queue = readQueue();
|
||||||
|
const queuedRequest = {
|
||||||
|
url: request.url,
|
||||||
|
method: request.method || "GET",
|
||||||
|
coalesceKey: request.coalesceKey || null,
|
||||||
|
clientUpdatedAt: request.clientUpdatedAt || Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (queuedRequest.coalesceKey) {
|
||||||
|
const existingIndex = queue.findIndex(function (item) {
|
||||||
|
return item.coalesceKey === queuedRequest.coalesceKey;
|
||||||
|
});
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
queue[existingIndex] = queuedRequest;
|
||||||
|
} else {
|
||||||
|
queue.push(queuedRequest);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queue.push(queuedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeQueue(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendRequest(request) {
|
||||||
|
const url = new URL(request.url, window.location.origin);
|
||||||
|
if (request.clientUpdatedAt) {
|
||||||
|
url.searchParams.set("client_updated_at", request.clientUpdatedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: request.method || "GET",
|
||||||
|
cache: "no-store",
|
||||||
|
credentials: "same-origin"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.redirected && new URL(response.url).pathname === "/login") {
|
||||||
|
const error = new Error("Login required");
|
||||||
|
error.retryable = true;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = new Error("Request failed: " + response.status);
|
||||||
|
error.retryable = response.status >= 500 || response.status === 408 || response.status === 429;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function queueableItemRequest(url, options) {
|
||||||
|
const request = Object.assign({}, options || {}, {
|
||||||
|
url: url,
|
||||||
|
clientUpdatedAt: Date.now()
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await sendRequest(request);
|
||||||
|
flushItemUpdateQueue();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.retryable !== false) {
|
||||||
|
queueRequest(request);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushItemUpdateQueue() {
|
||||||
|
if (flushItemUpdateQueue.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flushItemUpdateQueue.running = true;
|
||||||
|
|
||||||
|
const queue = readQueue();
|
||||||
|
const remaining = [];
|
||||||
|
|
||||||
|
for (const request of queue) {
|
||||||
|
try {
|
||||||
|
await sendRequest(request);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.retryable !== false) {
|
||||||
|
remaining.push(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeQueue(remaining);
|
||||||
|
flushItemUpdateQueue.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.lappQueueItemRequest = queueableItemRequest;
|
||||||
|
window.lappFlushItemUpdateQueue = flushItemUpdateQueue;
|
||||||
|
|
||||||
|
window.addEventListener("online", flushItemUpdateQueue);
|
||||||
|
document.addEventListener("visibilitychange", function () {
|
||||||
|
if (!document.hidden) {
|
||||||
|
flushItemUpdateQueue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.addEventListener("DOMContentLoaded", flushItemUpdateQueue);
|
||||||
|
})();
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
<link href="/static/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/bootstrap.min.css" rel="stylesheet">
|
||||||
<script src="/static/jquery.min.js"></script>
|
<script src="/static/jquery.min.js"></script>
|
||||||
<script src="/static/bootstrap.min.js"></script>
|
<script src="/static/bootstrap.min.js"></script>
|
||||||
|
<script src="/static/item_update_queue.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -6,17 +6,27 @@
|
|||||||
|
|
||||||
// handle Delete
|
// handle Delete
|
||||||
function handleDelete(button) {
|
function handleDelete(button) {
|
||||||
window.location.href = '/item_delete/'+button.name ;
|
lappQueueItemRequest('/item_delete/'+button.name, {
|
||||||
|
coalesceKey: 'item:' + button.name
|
||||||
|
}).then(function (sent) {
|
||||||
|
if (sent) {
|
||||||
|
window.location.href = '/items_append/{{ listid }}';
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle AddOne
|
// handle AddOne
|
||||||
function handleAddOne(button) {
|
function handleAddOne(button) {
|
||||||
const response = fetch('/item_addone/'+button.name);
|
lappQueueItemRequest('/item_addone/'+button.name, {
|
||||||
|
coalesceKey: 'item:' + button.name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle Add with quantity
|
// handle Add with quantity
|
||||||
function handleAddWithQuantity(button, q) {
|
function handleAddWithQuantity(button, q) {
|
||||||
const response = fetch('/item_addquantity/'+button.name+'/'+q);
|
lappQueueItemRequest('/item_addquantity/'+button.name+'/'+q, {
|
||||||
|
coalesceKey: 'item:' + button.name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//trigger change category button
|
//trigger change category button
|
||||||
@@ -27,7 +37,9 @@
|
|||||||
// selected the category
|
// selected the category
|
||||||
function clickedUpdateCategory4Item(icon) {
|
function clickedUpdateCategory4Item(icon) {
|
||||||
image_to_update.src="{{ iconpath }}" + icon.name + ".svg"
|
image_to_update.src="{{ iconpath }}" + icon.name + ".svg"
|
||||||
const response = fetch('/item_update_category/'+image_to_update.name+'/'+icon.name);
|
lappQueueItemRequest('/item_update_category/'+image_to_update.name+'/'+icon.name, {
|
||||||
|
coalesceKey: 'category:' + image_to_update.name
|
||||||
|
});
|
||||||
$('#staticBackdrop').modal('hide');
|
$('#staticBackdrop').modal('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,9 @@
|
|||||||
|
|
||||||
// handle Checboc clicks
|
// handle Checboc clicks
|
||||||
function handleCBClick(cb) {
|
function handleCBClick(cb) {
|
||||||
const response = fetch('/item_update/'+cb.name+'/'+cb.checked);
|
lappQueueItemRequest('/item_update/'+cb.name+'/'+cb.checked, {
|
||||||
|
coalesceKey: 'checked:' + cb.name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user