Make error handling for ajax list incrementing more robust
This commit is contained in:
parent
0e780f26b9
commit
60ddcaa08e
0
app/logs/.gitkeep
Normal file → Executable file
0
app/logs/.gitkeep
Normal file → Executable file
@ -1,5 +1,6 @@
|
||||
import _ from './anime-client.js'
|
||||
import { renderSearchResults } from './template-helpers.js'
|
||||
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||
|
||||
const search = (query, isCollection = false) => {
|
||||
// Show the loader
|
||||
@ -70,6 +71,14 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
const displayMessage = (type, message) => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage(type, `${message} ${title}.`);
|
||||
_.scrollToTop();
|
||||
}
|
||||
|
||||
const showError = () => displayMessage('error', 'Failed to update');
|
||||
|
||||
// If the episode count is 0, and incremented,
|
||||
// change status to currently watching
|
||||
if (isNaN(watchedCount) || watchedCount === 0) {
|
||||
@ -89,36 +98,31 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
success: (res) => {
|
||||
try {
|
||||
const resData = JSON.parse(res);
|
||||
|
||||
if (resData.error) {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${title}. `);
|
||||
_.scrollToTop();
|
||||
|
||||
// Do a rough sanity check for weird errors
|
||||
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||
showError();
|
||||
return;
|
||||
}
|
||||
|
||||
// We've completed the series
|
||||
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
|
||||
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
|
||||
_.hide(parentSel);
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('success', `Successfully completed ${title}`);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Completed')
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_.hide('#loading-shadow');
|
||||
|
||||
_.showMessage('success', `Successfully updated ${title}`);
|
||||
// Just a normal update
|
||||
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
||||
_.scrollToTop();
|
||||
},
|
||||
error: () => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${title}. `);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Updated');
|
||||
} catch (_) {
|
||||
showError();
|
||||
}
|
||||
},
|
||||
error: showError,
|
||||
});
|
||||
});
|
103
frontEndSrc/js/fns.js
Normal file
103
frontEndSrc/js/fns.js
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Make sure properties are in an easily splittable format
|
||||
*
|
||||
* @private
|
||||
* @param {String} props
|
||||
* @param {String} [sep='.'] The default separator
|
||||
* @return {String}
|
||||
*/
|
||||
function _normalizeProperty(props, sep = '.') {
|
||||
// Since we split by period, and property lookup
|
||||
// is the same by dot or [], replace bracket lookups
|
||||
// with periods
|
||||
return props.replace(/\[(.*?)]/g, sep + '$1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if a nested object has a given property (or array a given index)
|
||||
* given an object such as a.b.c.d = 5, hasNestedProperty(a, 'b.c.d') will return true.
|
||||
*
|
||||
* @param {Object} object the object to get the property from
|
||||
* @param {String} property the path to the property as a string
|
||||
* @returns {boolean} true when property in object, false otherwise
|
||||
*/
|
||||
export function hasNestedProperty(object, property) {
|
||||
if (object && typeof object === 'object') {
|
||||
if (typeof property === 'string' && property !== '') {
|
||||
property = _normalizeProperty(property);
|
||||
|
||||
let split = property.split('.');
|
||||
return split.reduce((obj, prop, idx, array) => {
|
||||
if (idx === array.length - 1) {
|
||||
return !!(obj && obj.hasOwnProperty(prop));
|
||||
}
|
||||
|
||||
return obj && obj[prop];
|
||||
}, object);
|
||||
} else if (typeof property === 'number') {
|
||||
return property in object;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a deeply nested property in an object
|
||||
*
|
||||
* @param {Object} object the object to get the property
|
||||
* @param {string} property the path to the property as a string
|
||||
* @param {string} [sep='.'] The default separator to split on
|
||||
* @return {*} the value of the property
|
||||
*/
|
||||
export function getNestedProperty(object, property, sep = '.') {
|
||||
if (isType('string', property) && property !== '') {
|
||||
// convert numbers to dot syntax
|
||||
property = _normalizeProperty(property, sep);
|
||||
const levels = property.split(sep);
|
||||
|
||||
try {
|
||||
return levels.reduce((obj, prop) => obj[prop], object);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reliably get the type of the value of a variable
|
||||
*
|
||||
* @param {*} x The variable to get the type of
|
||||
* @return {string} The name of the type
|
||||
*/
|
||||
export function getType(x) {
|
||||
// is it an array?
|
||||
if (Array.isArray(x)) {
|
||||
return 'array';
|
||||
}
|
||||
|
||||
// Use typeof for truthy primitives
|
||||
if (typeof x !== 'object') {
|
||||
return (typeof x).toLowerCase();
|
||||
}
|
||||
|
||||
const type = function () {
|
||||
return Object.prototype.toString.call(this).slice(8, -1);
|
||||
}
|
||||
|
||||
// Otherwise, strip the type out of the '[Object x]' toString value
|
||||
return type.call(x).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the value matches the passed type name
|
||||
*
|
||||
* @param {string} type Javascript type name
|
||||
* @param {*} val The value to type check
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isType(type, val) {
|
||||
return getType(val) === String(type).toLowerCase();
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import _ from './anime-client.js'
|
||||
import { renderSearchResults } from './template-helpers.js'
|
||||
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||
|
||||
const search = (query) => {
|
||||
_.show('.cssload-loader');
|
||||
@ -36,7 +37,7 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
let type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
||||
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
||||
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
||||
let mangaName = _.$('.name', parentSel)[ 0 ].textContent;
|
||||
let title = _.$('.name', parentSel)[ 0 ].textContent;
|
||||
|
||||
if (isNaN(completed)) {
|
||||
completed = 0;
|
||||
@ -45,12 +46,21 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
// Setup the update data
|
||||
let data = {
|
||||
id: parentSel.dataset.kitsuId,
|
||||
anilist_id: parentSel.dataset.anilistId,
|
||||
mal_id: parentSel.dataset.malId,
|
||||
data: {
|
||||
progress: completed
|
||||
}
|
||||
};
|
||||
|
||||
const displayMessage = (type, message) => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage(type, `${message} ${title}.`);
|
||||
_.scrollToTop();
|
||||
}
|
||||
|
||||
const showError = () => displayMessage('error', 'Failed to update');
|
||||
|
||||
// If the episode count is 0, and incremented,
|
||||
// change status to currently reading
|
||||
if (isNaN(completed) || completed === 0) {
|
||||
@ -73,33 +83,32 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
||||
type: 'POST',
|
||||
mimeType: 'application/json',
|
||||
success: (res) => {
|
||||
const resData = JSON.parse(res)
|
||||
if (resData.error) {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${mangaName}. `);
|
||||
_.scrollToTop();
|
||||
try {
|
||||
const resData = JSON.parse(res);
|
||||
|
||||
// Do a rough sanity check for weird errors
|
||||
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||
showError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (String(data.data.status).toUpperCase() === 'COMPLETED') {
|
||||
// We've completed the series
|
||||
if (getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.status') === 'COMPLETED') {
|
||||
_.hide(parentSel);
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('success', `Successfully completed ${mangaName}`);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Completed')
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_.hide('#loading-shadow');
|
||||
|
||||
// Just a normal update
|
||||
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
||||
_.showMessage('success', `Successfully updated ${mangaName}`);
|
||||
_.scrollToTop();
|
||||
},
|
||||
error: () => {
|
||||
_.hide('#loading-shadow');
|
||||
_.showMessage('error', `Failed to update ${mangaName}`);
|
||||
_.scrollToTop();
|
||||
displayMessage('success', 'Updated');
|
||||
|
||||
} catch (_) {
|
||||
showError();
|
||||
}
|
||||
},
|
||||
error: showError,
|
||||
});
|
||||
});
|
@ -15,7 +15,7 @@
|
||||
"cssnano": "^5.0.1",
|
||||
"postcss": "^8.2.6",
|
||||
"postcss-import": "^15.0.0",
|
||||
"postcss-preset-env": "^7.8.2",
|
||||
"postcss-preset-env": "^8.0.1",
|
||||
"watch": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
module.exports = {
|
||||
const { config } = require("@swc/core/spack");
|
||||
|
||||
module.exports = config({
|
||||
entry: {
|
||||
'scripts.min': __dirname + '/js/index.js',
|
||||
'tables.min': __dirname + '/js/base/sort-tables.js',
|
||||
@ -8,12 +10,15 @@ module.exports = {
|
||||
},
|
||||
options: {
|
||||
jsc: {
|
||||
target: 'es3',
|
||||
loose: true,
|
||||
parser: {
|
||||
syntax: "ecmascript",
|
||||
jsx: false,
|
||||
},
|
||||
target: 'es2016',
|
||||
loose: false,
|
||||
},
|
||||
minify: true,
|
||||
module: {
|
||||
type: 'es6'
|
||||
sourceMaps: false,
|
||||
isModule: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
2
public/css/auto.min.css
vendored
2
public/css/auto.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/dark.min.css
vendored
2
public/css/dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/light.min.css
vendored
2
public/css/light.min.css
vendored
File diff suppressed because one or more lines are too long
39
public/js/scripts.min.js
vendored
39
public/js/scripts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/js/tables.min.js
vendored
2
public/js/tables.min.js
vendored
@ -1 +1 @@
|
||||
var LightTableSorter=function(){var th=null;var cellIndex=null;var order="";var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){var arrayA=textA.replace("episodes: ","").replace("-",0).split("/");var arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};var toggle=function(){var c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};var reset=function(){th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName("tbody")[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(function(row){tbody.appendChild(row)})}}};return{init:function(){var ths=document.getElementsByTagName("th");var results=[];for(var i=0,len=ths.length;i<len;i++){var th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}}();LightTableSorter.init();
|
||||
const LightTableSorter=(()=>{let th=null;let cellIndex=null;let order="";const text=row=>row.cells.item(cellIndex).textContent.toLowerCase();const sort=(a,b)=>{let textA=text(a);let textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){let arrayA=textA.replace("episodes: ","").replace("-",0).split("/");let arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};const toggle=()=>{const c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};const reset=()=>{th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};const onClickEvent=e=>{if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;const tbody=th.offsetParent.getElementsByTagName("tbody")[0];let rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(row=>{tbody.appendChild(row)})}}};return{init:()=>{let ths=document.getElementsByTagName("th");let results=[];for(let i=0,len=ths.length;i<len;i++){let th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}})();LightTableSorter.init();
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user