Make error handling for ajax list incrementing more robust
All checks were successful
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good
All checks were successful
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good
This commit is contained in:
parent
ca09de44d3
commit
288cbb3e20
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 _ from './anime-client.js'
|
||||||
import { renderSearchResults } from './template-helpers.js'
|
import { renderSearchResults } from './template-helpers.js'
|
||||||
|
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||||
|
|
||||||
const search = (query, isCollection = false) => {
|
const search = (query, isCollection = false) => {
|
||||||
// Show the loader
|
// 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,
|
// If the episode count is 0, and incremented,
|
||||||
// change status to currently watching
|
// change status to currently watching
|
||||||
if (isNaN(watchedCount) || watchedCount === 0) {
|
if (isNaN(watchedCount) || watchedCount === 0) {
|
||||||
@ -89,36 +98,31 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
try {
|
||||||
const resData = JSON.parse(res);
|
const resData = JSON.parse(res);
|
||||||
|
|
||||||
if (resData.error) {
|
// Do a rough sanity check for weird errors
|
||||||
_.hide('#loading-shadow');
|
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||||
_.showMessage('error', `Failed to update ${title}. `);
|
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||||
_.scrollToTop();
|
showError();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've completed the series
|
// 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(parentSel);
|
||||||
_.hide('#loading-shadow');
|
displayMessage('success', 'Completed')
|
||||||
_.showMessage('success', `Successfully completed ${title}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_.hide('#loading-shadow');
|
// Just a normal update
|
||||||
|
|
||||||
_.showMessage('success', `Successfully updated ${title}`);
|
|
||||||
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
_.$('.completed_number', parentSel)[ 0 ].textContent = ++watchedCount;
|
||||||
_.scrollToTop();
|
displayMessage('success', 'Updated');
|
||||||
},
|
} catch (_) {
|
||||||
error: () => {
|
showError();
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('error', `Failed to update ${title}. `);
|
|
||||||
_.scrollToTop();
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
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 _ from './anime-client.js'
|
||||||
import { renderSearchResults } from './template-helpers.js'
|
import { renderSearchResults } from './template-helpers.js'
|
||||||
|
import { getNestedProperty, hasNestedProperty } from "./fns";
|
||||||
|
|
||||||
const search = (query) => {
|
const search = (query) => {
|
||||||
_.show('.cssload-loader');
|
_.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 type = thisSel.classList.contains('plus-one-chapter') ? 'chapter' : 'volume';
|
||||||
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[ 0 ].textContent, 10) || 0;
|
||||||
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
let total = parseInt(_.$(`.${type}_count`, parentSel)[ 0 ].textContent, 10);
|
||||||
let mangaName = _.$('.name', parentSel)[ 0 ].textContent;
|
let title = _.$('.name', parentSel)[ 0 ].textContent;
|
||||||
|
|
||||||
if (isNaN(completed)) {
|
if (isNaN(completed)) {
|
||||||
completed = 0;
|
completed = 0;
|
||||||
@ -45,12 +46,21 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|||||||
// Setup the update data
|
// Setup the update data
|
||||||
let data = {
|
let data = {
|
||||||
id: parentSel.dataset.kitsuId,
|
id: parentSel.dataset.kitsuId,
|
||||||
|
anilist_id: parentSel.dataset.anilistId,
|
||||||
mal_id: parentSel.dataset.malId,
|
mal_id: parentSel.dataset.malId,
|
||||||
data: {
|
data: {
|
||||||
progress: completed
|
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,
|
// If the episode count is 0, and incremented,
|
||||||
// change status to currently reading
|
// change status to currently reading
|
||||||
if (isNaN(completed) || completed === 0) {
|
if (isNaN(completed) || completed === 0) {
|
||||||
@ -73,33 +83,32 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
|
|||||||
type: 'POST',
|
type: 'POST',
|
||||||
mimeType: 'application/json',
|
mimeType: 'application/json',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const resData = JSON.parse(res)
|
try {
|
||||||
if (resData.error) {
|
const resData = JSON.parse(res);
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('error', `Failed to update ${mangaName}. `);
|
// Do a rough sanity check for weird errors
|
||||||
_.scrollToTop();
|
let updatedProgress = getNestedProperty(resData, 'data.libraryEntry.update.libraryEntry.progress');
|
||||||
|
if (hasNestedProperty(resData, 'error') || updatedProgress !== data.data.progress) {
|
||||||
|
showError();
|
||||||
return;
|
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(parentSel);
|
||||||
_.hide('#loading-shadow');
|
displayMessage('success', 'Completed')
|
||||||
_.showMessage('success', `Successfully completed ${mangaName}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_.hide('#loading-shadow');
|
// Just a normal update
|
||||||
|
|
||||||
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
_.$(`.${type}s_read`, parentSel)[ 0 ].textContent = String(completed);
|
||||||
_.showMessage('success', `Successfully updated ${mangaName}`);
|
displayMessage('success', 'Updated');
|
||||||
_.scrollToTop();
|
|
||||||
},
|
} catch (_) {
|
||||||
error: () => {
|
showError();
|
||||||
_.hide('#loading-shadow');
|
|
||||||
_.showMessage('error', `Failed to update ${mangaName}`);
|
|
||||||
_.scrollToTop();
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
error: showError,
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -15,7 +15,7 @@
|
|||||||
"cssnano": "^5.0.1",
|
"cssnano": "^5.0.1",
|
||||||
"postcss": "^8.2.6",
|
"postcss": "^8.2.6",
|
||||||
"postcss-import": "^15.0.0",
|
"postcss-import": "^15.0.0",
|
||||||
"postcss-preset-env": "^7.8.2",
|
"postcss-preset-env": "^8.0.1",
|
||||||
"watch": "^1.0.2"
|
"watch": "^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
module.exports = {
|
const { config } = require("@swc/core/spack");
|
||||||
|
|
||||||
|
module.exports = config({
|
||||||
entry: {
|
entry: {
|
||||||
'scripts.min': __dirname + '/js/index.js',
|
'scripts.min': __dirname + '/js/index.js',
|
||||||
'tables.min': __dirname + '/js/base/sort-tables.js',
|
'tables.min': __dirname + '/js/base/sort-tables.js',
|
||||||
@ -8,12 +10,15 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
jsc: {
|
jsc: {
|
||||||
target: 'es3',
|
parser: {
|
||||||
loose: true,
|
syntax: "ecmascript",
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
target: 'es2016',
|
||||||
|
loose: false,
|
||||||
},
|
},
|
||||||
minify: true,
|
minify: true,
|
||||||
module: {
|
sourceMaps: false,
|
||||||
type: 'es6'
|
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