Просмотр исходного кода

Implement loading spinner for marking as favorite/read, read/unread (#7564)

* Implement loading spinner for marking as favorite

* Ensure that the correct previous icon gets set

* Remove delay

* Improve compatibility with various parsers

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* Support multiple icons (top, bottom)

* Remove preload for now

* Fix CSS, remove !important

* Implement read/unread and alt

* Ensure correct bookmark icon gets set after error

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Inverle 10 месяцев назад
Родитель
Сommit
84d4aeb9e6

+ 1 - 0
app/views/helpers/javascript_vars.phtml

@@ -78,6 +78,7 @@ echo htmlspecialchars(json_encode([
 	'icons' => [
 		'read' => rawurlencode(_i('read')),
 		'unread' => rawurlencode(_i('unread')),
+		'spinner' => '../themes/icons/spinner.svg',
 	],
 	'extensions' => $extData,
 ], JSON_UNESCAPED_UNICODE) ?: '', ENT_NOQUOTES, 'UTF-8');

+ 26 - 0
p/scripts/main.js

@@ -224,6 +224,10 @@ function send_mark_read_queue(queue, asRead, callback) {
 	req.responseType = 'json';
 	req.onerror = function (e) {
 		for (let i = queue.length - 1; i >= 0; i--) {
+			const div = document.getElementById('flux_' + queue[i]);
+			div.querySelectorAll('a.read > .icon').forEach(icon => {
+				icon.outerHTML = div.classList.contains('not_read') ? context.icons.unread : context.icons.read;
+			});
 			delete pending_entries['flux_' + queue[i]];
 		}
 		badAjax(this.status == 403);
@@ -315,6 +319,12 @@ function mark_read(div, only_not_read, asBatch) {
 	}
 	pending_entries[div.id] = true;
 
+	div.querySelectorAll('a.read > .icon').forEach(icon => {
+		icon.src = context.icons.spinner;
+		icon.alt = '⏳';
+		icon.classList.add('spinner');
+	});
+
 	const asRead = div.classList.contains('not_read');
 	const entryId = div.id.replace(/^flux_/, '');
 	if (asRead && asBatch) {
@@ -351,10 +361,26 @@ function mark_favorite(div) {
 	}
 	pending_entries[div.id] = true;
 
+	let originalIcon;
+
+	div.querySelectorAll('a.bookmark > .icon').forEach(icon => {
+		originalIcon = {
+			src: icon.getAttribute('src'),
+			alt: icon.getAttribute('alt')
+		};
+		icon.src = context.icons.spinner;
+		icon.alt = '⏳';
+		icon.classList.add('spinner');
+	});
+
 	const req = new XMLHttpRequest();
 	req.open('POST', url, true);
 	req.responseType = 'json';
 	req.onerror = function (e) {
+		div.querySelectorAll('a.bookmark > .icon').forEach(icon => {
+			icon.src = originalIcon.src;
+			icon.alt = originalIcon.alt;
+		});
 		delete pending_entries[div.id];
 		badAjax(this.status == 403);
 	};

+ 12 - 0
p/themes/Alternative-Dark/adark.css

@@ -274,6 +274,18 @@ th {
 	box-shadow: none;
 }
 
+.spinner {
+	filter: invert(1);
+}
+
+a:hover > .spinner {
+	filter: invert(1) brightness(2);
+}
+
+.flux .item.manage .read:hover .icon.spinner {
+	filter: invert(1) grayscale(0.8) brightness(1.7);
+}
+
 /*=== switches */
 .switch.active {
 	background-color: var(--contrast-background-color);

+ 12 - 0
p/themes/Alternative-Dark/adark.rtl.css

@@ -274,6 +274,18 @@ th {
 	box-shadow: none;
 }
 
+.spinner {
+	filter: invert(1);
+}
+
+a:hover > .spinner {
+	filter: invert(1) brightness(2);
+}
+
+.flux .item.manage .read:hover .icon.spinner {
+	filter: invert(1) grayscale(0.8) brightness(1.7);
+}
+
 /*=== switches */
 .switch.active {
 	background-color: var(--contrast-background-color);

+ 12 - 0
p/themes/Dark/dark.css

@@ -118,6 +118,18 @@ p.help .icon,
 	filter: brightness(.6) contrast(1.2);
 }
 
+.spinner {
+	filter: invert(1) brightness(.6) contrast(1.2);
+}
+
+.bookmark:hover > .spinner {
+	filter: invert(1) brightness(1.1);
+}
+
+a:hover .icon.spinner {
+	filter: invert(1) brightness(1.5);
+}
+
 /*=== Forms */
 legend {
 	border-bottom: 1px solid var(--dark-border-color);

+ 12 - 0
p/themes/Dark/dark.rtl.css

@@ -118,6 +118,18 @@ p.help .icon,
 	filter: brightness(.6) contrast(1.2);
 }
 
+.spinner {
+	filter: invert(1) brightness(.6) contrast(1.2);
+}
+
+.bookmark:hover > .spinner {
+	filter: invert(1) brightness(1.1);
+}
+
+a:hover .icon.spinner {
+	filter: invert(1) brightness(1.5);
+}
+
 /*=== Forms */
 legend {
 	border-bottom: 1px solid var(--dark-border-color);

+ 4 - 0
p/themes/Origine/origine.css

@@ -1323,4 +1323,8 @@ a:hover .icon {
 	:root.darkMode_auto .btn:active .icon {
 		filter: brightness(1.4);
 	}
+
+	:root.darkMode_auto .spinner {
+		filter: invert(1) brightness(2);
+	}
 }

+ 4 - 0
p/themes/Origine/origine.rtl.css

@@ -1323,4 +1323,8 @@ a:hover .icon {
 	:root.darkMode_auto .btn:active .icon {
 		filter: brightness(1.4);
 	}
+
+	:root.darkMode_auto .spinner {
+		filter: invert(1) brightness(2);
+	}
 }

+ 1 - 0
p/themes/icons/spinner.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><style>@keyframes spinner_svv2{to{transform:rotate(360deg)}}</style><path d="M10.14 1.16a11 11 0 0 0-9 8.92A1.59 1.59 0 0 0 2.46 12a1.52 1.52 0 0 0 1.65-1.3 8 8 0 0 1 6.66-6.61A1.42 1.42 0 0 0 12 2.69a1.57 1.57 0 0 0-1.86-1.53Z" style="transform-origin:center;animation:spinner_svv2 .75s infinite linear"/></svg>