Add event timeline (close #35)

Skylar Ittner 4 years ago
parent e063875712
commit 9a86653fda

@ -0,0 +1,108 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
function appendActivityLog(title, subtitle, content, icon, timestamp) {
if (typeof timestamp == "undefined") {
timestamp = time();
}
let entry = {
title: title,
subtitle: subtitle,
content: content,
icon: icon,
timestamp: timestamp
};
let log = getStorage("activitylog");
if (log == null) {
log = [];
} else {
try {
log = JSON.parse(log);
} catch (ex) {
log = [];
}
}
let pushed = false;
let datestr = formatTimestamp("Y-m-d", timestamp);
for (var i = 0; i < log.length; i++) {
if (formatTimestamp("Y-m-d", log[i].date) == datestr) {
log[i].entries.push(entry);
pushed = true;
break;
}
}
if (!pushed) {
log.push({
date: timestamp,
entries: [
entry
]
});
}
console.log("Added activity log entry", entry);
console.log(log);
setStorage("activitylog", JSON.stringify(log));
// Trim the log soon but don't block for it
setTimeout(trimActivityLog, 100);
}
function clearActivityLog() {
setStorage("activitylog", "[]");
}
function trimActivityLog() {
let log = JSON.parse(getStorage("activitylog"));
log.sort(function (x, y) {
if (x.date < y.date) {
return 1;
} else if (x.date > y.date) {
return -1;
}
return 0;
});
let entries = 0;
let allowed = SETTINGS.activitylog_maxlength;
let newlog = [];
for (var i = 0; i < log.length; i++) {
let logdate = {
date: log[i].date,
entries: []
};
for (var j = 0; j < log[i].entries.length; j++) {
if (entries < allowed) {
logdate.entries.push(log[i].entries[j]);
}
entries++;
}
if (logdate.entries.length > 0) {
newlog.push(logdate);
}
}
if (entries - allowed > 0) {
newlog[newlog.length - 1].entries.push({
title: "Log Trimmed",
subtitle: "",
content: (entries - allowed) + " older " + ((entries - allowed) == 1 ? "entry was" : "entries were") + " removed from the log.",
icon: "fas fa-cut",
timestamp: newlog[newlog.length - 1].date
});
}
console.log(log);
console.log(newlog);
setStorage("activitylog", JSON.stringify(newlog));
}

@ -183,8 +183,11 @@ function confirmDeleteAllPackages() {
"Clear Packages",
function () {
// clear
let count = countPackages();
let remaining = countRemainingPackages();
packages = [];
setStorage("packages", JSON.stringify(packages));
appendActivityLog("Cleared List", count + " " + (count != 1 ? "items" : "item") + " removed.", (remaining > 0 ? remaining + " " + (remaining != 1 ? "were" : "was") + " not delivered." : ""), "fas fa-trash");
loadPackageList();
if (map != null) {
map.updatePackageLayer(packages);

@ -1,4 +1,4 @@
/*
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
@ -49,7 +49,7 @@ function isDeliverable(number, street) {
};
}
function saveNote(id) {
function saveNote(id) {
var exists = false;
var index = -1;
for (var i = 0; i < notes.length; i++) {
@ -94,7 +94,7 @@ function saveNote(id) {
app.dialog.alert("Fill in a route (examples: C123, R001, H050).", "Error");
return false;
}
setStorage("lastrouteid", note.route);
setStorage("zipcode", note.zipcode);
@ -111,6 +111,12 @@ function saveNote(id) {
setStorage("notes", JSON.stringify(notes));
if (exists) {
appendActivityLog("Edited note", note.number + " " + note.street, "", "fas fa-sticky-note");
} else {
appendActivityLog("Added note", note.number + " " + note.street, "", "fas fa-sticky-note");
}
app.toast.show({
text: "<i class='fas fa-check'></i> Note saved!",
position: "bottom",
@ -120,12 +126,15 @@ function saveNote(id) {
}
function deleteNote(id) {
var note = {};
for (var i = 0; i < notes.length; i++) {
if (notes[i].id == id) {
note = notes[i];
notes.splice(i, 1);
}
}
setStorage("notes", JSON.stringify(notes));
appendActivityLog("Deleted note", note.number + " " + note.street, "", "fas fa-sticky-note");
}
$(".view-main").on("click", "#savenotebtn", function () {

@ -144,6 +144,8 @@ function addPackage(address, latitude, longitude, type, callback, deadline) {
}
setStorage("packages", JSON.stringify(packages));
appendActivityLog("Added", SETTINGS.itemtypes[type].name, address, "fas fa-truck-loading");
playSound("ok");
app.toast.show({
@ -174,6 +176,7 @@ function addPackage(address, latitude, longitude, type, callback, deadline) {
*/
function importPackageList(newlist) {
skipped = 0;
let count = 0;
for (latlng in newlist) {
var latitude = newlist[latlng].coords[0];
var longitude = newlist[latlng].coords[1];
@ -195,6 +198,7 @@ function importPackageList(newlist) {
}
if (!added) {
packages[i].items.push(package);
count++;
added = true;
}
break;
@ -206,6 +210,8 @@ function importPackageList(newlist) {
}
}
setStorage("packages", JSON.stringify(packages));
appendActivityLog("Imported List", count + " items added", "", "fas fa-file-download");
if (map != null) {
reloadMap();
}
@ -237,12 +243,9 @@ function mapCalibrate(item, packagesentry) {
locationtype: locationtype
},
success: function () {
app.toast.show({
text: "Calibration recorded. Thank you for improving the map!",
position: "bottom",
destroyOnClose: true,
closeTimeout: 1000 * 3
});
appendActivityLog("Map Calibrated", item.extended.number + " " + item.extended.street,
"Thanks for improving the map accuracy!<br>Old: <a href='geo:" + packagesentry.coords[0] + "," + packagesentry.coords[1] + "'>" + packagesentry.coords[0] + ", " + packagesentry.coords[1] + "</a><br>"
+ "New: <a href='geo:" + latitude + "," + longitude + "'>" + latitude + ", " + longitude + "</a>", "fas fa-map-marked-alt");
},
error: function () {
// try again in five minutes
@ -312,14 +315,15 @@ function markDelivered(id, delivered) {
packages[i].items[j].delivered = delivered;
if (delivered) {
packages[i].items[j].deliverytimestamp = Date.now();
setStorage("packages", JSON.stringify(packages));
packages[i].items[j].deliverytimestamp = time();
appendActivityLog("Delivered", SETTINGS.itemtypes[packages[i].items[j].type].name, packages[i].items[j].address, "far fa-check-circle");
mapCalibrate(packages[i].items[j], packages[i]);
return; // so we don't keep looping over the rest of the packages
} else {
packages[i].items[j].deliverytimestamp = null;
appendActivityLog("Undelivered", SETTINGS.itemtypes[packages[i].items[j].type].name, packages[i].items[j].address, "fas fa-undo");
}
setStorage("packages", JSON.stringify(packages));
return; // so we don't keep looping over the rest of the packages
}
}
}
@ -343,6 +347,9 @@ function deletePackage(id, callback) {
for (var i = 0; i < packages.length; i++) {
for (var j = 0; j < packages[i].items.length; j++) {
if (packages[i].items[j].id == id) {
appendActivityLog("Deleted", SETTINGS.itemtypes[packages[i].items[j].type].name, packages[i].items[j].address, "fas fa-trash");
packages[i].items.splice(j, 1);
if (packages[i].items.length == 0) {

@ -0,0 +1,21 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
function confirmDeleteActivityLog() {
app.dialog.confirm(
"Really delete all log entries?",
"Clear Log",
function () {
// clear
clearActivityLog();
router.refreshPage();
},
function () {
// cancel
}
);
}

@ -101,7 +101,7 @@ function addCodeToScannerList(code) {
} else {
app.dialog.confirm(
"It looks like this item might require a signature or other special procedures. Add it anyways?",
"Signature Item",
"Special Item",
function () {
$("#codelist").append(codeEntryTemplate({
code: code
@ -195,6 +195,8 @@ function saveScanCode(code) {
var events = JSON.parse(getStorage("scanevents"));
events.push(code);
setStorage("scanevents", JSON.stringify(events));
appendActivityLog("Scanned Item", code.event.join(' <i class="fas fa-chevron-right"></i> '), code.code + (code.form3849 == "" ? "" : "<br>Form 3849: " + code.form3849), "fas fa-barcode");
}
$(".view-main").off("click", "#codelist li.codelist-entry");

@ -33,6 +33,7 @@ function uploadList() {
height: 40
});
$("#listidbarcodeli").css("display", "");
appendActivityLog("Shared List", countPackages() + " items sent", "", "fas fa-file-upload");
} else {
app.dialog.alert(resp.message, "Error");
}

@ -1,4 +1,4 @@
/*
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
@ -16,16 +16,233 @@ function uuidv4() {
});
}
function timestampToDateTimeString(timestamp) {
/**
* Take a UNIX timestamp (seconds since Jan 1 1970) and format it.
* (Mostly) compatible with PHP's date() function.
* @param {String} date format string, see https://www.php.net/manual/en/function.date.php
* @param {Integer} timestamp UNIX timestamp
* @return {String}
*/
function formatTimestamp(format, timestamp) {
if (typeof timestamp == "undefined") {
timestamp = time();
}
var date = new Date(timestamp * 1000);
var pm = date.getHours() >= 12;
var hours = date.getHours() > 12 ? date.getHours() - 12 : date.getHours();
hours = (hours == 0 ? 12 : hours);
var minutes = date.getMinutes();
var time = hours + ":" + (minutes < 10 ? "0" + minutes : minutes) + " " + (pm ? "PM" : "AM");
var out = "";
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
for (var i = 0; i < format.length; i++) {
var c = format.charAt(i);
// Handle backslash-escaped characters
if (c == "\\" && i < format.length - 1) {
out += format.charAt(i + 1);
i++;
continue;
}
switch (c) {
case "d":
var d = date.getDate();
if (d < 10) {
out += "0";
}
out += d;
break;
case "D":
out += days[date.getDay()].substring(0, 3);
break;
case "j":
out += date.getDate();
break;
case "l":
out += days[date.getDay()];
break;
case "N":
// TODO
break;
case "S":
// TODO
break;
case "w":
out += date.getDay();
break;
case "z":
// TODO
break;
case "W":
// TODO
break;
case "F":
out += months[date.getMonth()];
break;
case "m":
var m = date.getMonth() + 1;
if (m < 10) {
out += "0";
}
out += m;
break;
case "M":
out += months[date.getMonth()].substring(0, 3);
break;
case "n":
out += date.getMonth() + 1;
break;
case "t":
// TODO
break;
case "L":
// TODO
break;
case "o":
// TODO
break;
case "Y":
out += date.getFullYear();
break;
case "y":
var y = (date.getFullYear() + "");
out += y.substring(y.length - 2);
break;
case "a":
if (date.getHours() < 12) {
out += "am";
} else {
out += "pm";
}
break;
case "A":
if (date.getHours() < 12) {
out += "AM";
} else {
out += "PM";
}
break;
case "B":
// TODO
break;
case "g":
var h = date.getHours() % 12;
if (h == 0) {
h = 12;
}
out += h;
break;
case "G":
out += date.getHours();
break;
case "h":
var h = date.getHours() % 12;
if (h == 0) {
h = 12;
}
if (h < 10) {
out += "0";
}
out += h;
break;
case "H":
var h = date.getHours();
if (h < 10) {
out += "0";
}
out += h;
break;
case "i":
var ii = date.getMinutes();
if (ii < 10) {
out += "0";
}
out += ii;
break;
case "s":
var s = date.getSeconds();
if (s < 10) {
out += "0";
}
out += s;
break;
case "u":
out += date.getMilliseconds() * 1000;
break;
case "v":
out += date.getMilliseconds();
break;
case "e":
// TODO
break;
case "I":
// TODO
break;
case "O":
var off = date.getTimezoneOffset();
var m = off % 60;
var h = (off - m) / 60;
if (off >= 0) {
out += "+";
} else {
out += "-";
}
if (h < 10) {
out += "0";
}
out += h;
if (m < 10) {
out += "0";
}
out += m;
break;
case "P":
var off = date.getTimezoneOffset();
var m = off % 60;
var h = (off - m) / 60;
if (off >= 0) {
out += "+";
} else {
out += "-";
}
if (h < 10) {
out += "0";
}
out += h;
out += ":";
if (m < 10) {
out += "0";
}
out += m;
break;
case "T":
// TODO
break;
case "Z":
out += date.getTimezoneOffset() * 60;
break;
case "c":
out += formatTimestamp(timestamp, "Y-m-d\\TH:i:sP");
break;
case "r":
out += formatTimestamp(timestamp, "D, j M Y G:i:s O");
break;
case "U":
out += Math.round(timestamp);
break;
default:
out += c;
}
}
return out;
}
function timestampToDateTimeString(timestamp) {
return timestampToDateString(timestamp) + " " + timestampToTimeString(timestamp);
}
function timestampToDateString(timestamp) {
var date = new Date(timestamp * 1000);
return date.toLocaleDateString() + " " + time;
return date.toLocaleDateString();
}
function timestampToTimeString(timestamp) {

@ -53,6 +53,7 @@
<script src="assets/js/platform.js"></script>
<script src="assets/js/util.js"></script>
<script src="assets/js/activitylog.js"></script>
<script src="assets/js/audio.js"></script>
<script src="assets/js/autofill.js"></script>
<script src="assets/js/packages.js"></script>
@ -61,7 +62,6 @@
<script src="assets/js/map_leaflet.js"></script>
<script src="assets/js/map_mapbox.js"></script>
<script src="assets/js/map.js"></script>
<script src="assets/js/manage.js"></script>
<script src="assets/js/sync.js"></script>
<script src="assets/js/notes.js"></script>

@ -53,6 +53,14 @@
</div>
</a>
</li>
<li>
<a href="/toolbox/log" class="item-link item-content">
<div class="item-media"><i class="icon fas fa-book"></i></div>
<div class="item-inner">
<div class="item-title">Activity Log</div>
</div>
</a>
</li>
<li>
<a href="/toolbox/sharelist" class="item-link item-content">
<div class="item-media"><i class="icon fas fa-share-alt"></i></div>

@ -0,0 +1,57 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="page" data-name="log">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="left">
<a href="#" class="link icon-only back">
<i class="icon icon-back"></i>
</a>
</div>
<div class="title">Activity Log</div>
<div class="right">
<a class="link text-color-red" onclick="confirmDeleteActivityLog()">
<i class="icon material-icons">delete</i>
</a>
</div>
</div>
</div>
<div class="page-content">
<div class="row justify-content-center">
<div class="col-100 medium-60 large-50 xlarge-40 elevation-tablet margin-top-tablet">
{{#if events}}
<div class="timeline medium-sides">
{{#each events}}
<div class="timeline-item">
<div class="timeline-item-date">{{date}} <small>{{addldate}}</small></div>
<div class="timeline-item-divider"></div>
<div class="timeline-item-content">
{{#each entries}}
<div class="timeline-item-inner">
<div class="timeline-item-time">{{time}}</div>
<div class="timeline-item-title"><i class="{{icon}}"></i> {{title}}</div>
<div class="timeline-item-subtitle">{{subtitle}}</div>
<div class="timeline-item-text">{{content}}</div>
</div>
{{/each}}
</div>
</div>
{{/each}}
</div>
{{else}}
<div class="block text-align-center">
<img style="width: 60%; max-width: 300px; max-height: 40vh;" src="assets/images/history-dashed.svg" class="margin-vertical" />
<div class="margin-top">Log empty! When you mark packages as delivered, add notes, and more, this log will automatically gain entries.</div>
</div>
{{/if}}
</div>
</div>
</div>
<script src="assets/js/toolbox_log.js"></script>
</div>

@ -313,6 +313,77 @@ var routes = [
url: './pages/toolbox/addrlookup.html',
name: 'addrlookup'
},
{
path: '/log',
name: 'log',
async: function (routeTo, routeFrom, resolve, reject) {
var activitylog = getStorage("activitylog");
let events = [];
if (activitylog != null) {
try {
events = JSON.parse(activitylog);
events.sort(function (x, y) {
if (x.date < y.date) {
return 1;
} else if (x.date > y.date) {
return -1;
}
return 0;
});
let year = formatTimestamp("Y");
let yearmonth = formatTimestamp("Y-m");
let day = formatTimestamp("j");
for (var i = 0; i < events.length; i++) {
let evtdate = events[i].date;
if (formatTimestamp("Y-m", evtdate) == yearmonth) {
// same year and month
if (formatTimestamp("J", evtdate) - day < 7) {
// same week
events[i].date = formatTimestamp("l", evtdate);
events[i].addldate = formatTimestamp("M j", evtdate);
} else {
events[i].date = formatTimestamp("D", evtdate);
events[i].addldate = formatTimestamp("M j", evtdate);
}
} else if (formatTimestamp("Y", evtdate) == year) {
// same year
events[i].date = formatTimestamp("M j", evtdate);
events[i].addldate = year;
} else {
// different year
events[i].date = formatTimestamp("M j", evtdate);
events[i].addldate = formatTimestamp("Y", evtdate);
}
events[i].entries.sort(function (x, y) {
if (x.timestamp < y.timestamp) {
return 1;
} else if (x.timestamp > y.timestamp) {
return -1;
}
return 0;
});
for (var j = 0; j < events[i].entries.length; j++) {
events[i].entries[j].time = formatTimestamp("g:i a", events[i].entries[j].timestamp);
}
}
console.log(events);
} catch (ex) {
events = [];
}
}
console.log(events);
if (events == null || (typeof events == "object" && events.length == 0)) {
events = false;
}
resolve({
templateUrl: './pages/toolbox/log.html'
}, {
context: {
events: events
}
});
}
},
{
path: '/sharelist',
url: './pages/toolbox/sharelist.html',

@ -6,6 +6,7 @@
var SETTINGS = {
cacheversion: "v1.5.0_0",
activitylog_maxlength: 500,
maptileurls: {
liberty: {
url: "https://maps.netsyms.net/styles/osm-liberty/{z}/{x}/{y}.png",

Loading…
Cancel
Save