mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Phase 2, part 4. Baseline 50 -> 15.
Migrated: all eight calculators, the rest of inventory and collections, the
Map/Zim/Docs cluster, Videos, Files, Search, Archive, DashboardPage,
DatesSelector, HelpModal, Events, Tags, Flasher's neighbours, admin Settings
and Status, Download, and App.
Also the three shared modules everything imports: api.js and customHooks.js
swap the Semantic toast library for the library's shim (same call shape), and
useForm.js renders the library's Message. useForm accepts both the old
`{content, header, positive|negative}` shape and the new `{kind, title}`,
because the pages building those objects migrate separately.
Two library gaps closed, both found by call sites needing them:
ActionInput is a forwardRef now. Its ref reaches the input rather than the
wrapper, because callers use it to focus or select the field -- a ref to the
surrounding div would do neither, which is why one call site had resorted to
hand-copying the component's markup.
Header takes a `color` token name. Three separate call sites had reached for
an inline style to colour a heading that carries meaning ("3 items to add"
against "2 to remove"), which is how a hex ends up in markup.
Two files needed repair after being interrupted mid-edit: MapViewer had a
Segment left in its error branch, and VideoPlayer had lost the useContext
import its own code still used. Neither jest nor tsc noticed; only
`npm run build` did, which is the whole reason that check exists.
Fifteen files remain: Archive, BulkTagModal, Channels, Docs, FileBrowser,
FilePreview, Flasher, Nav, Playlists, Theme.tsx, Upload, VideoPlayer, Zim,
ControllerPage, admin/Downloads. Theme.tsx is deliberately last -- the
unmigrated files still import its Semantic-backed wrappers, so it can only go
once they have.
204 lines
6 KiB
JavaScript
204 lines
6 KiB
JavaScript
import React, {useEffect} from 'react';
|
|
import {useRecurringTimeout} from "./hooks/customHooks";
|
|
import {ApiDownError, getEvents} from "./api";
|
|
import {toast} from "./components/ui";
|
|
|
|
const apiEventName = 'apiEvent';
|
|
|
|
function eventToast(title, description, type = 'info', time = 5000, onClick = null) {
|
|
toast({type: type, title: title, description: description, time: time, onClick: onClick});
|
|
}
|
|
|
|
function handleEvents(events) {
|
|
const newestEvents = {};
|
|
|
|
events.forEach(e => {
|
|
const {event, message, subject, dt, url} = e;
|
|
document.dispatchEvent(new CustomEvent(apiEventName, {detail: e}));
|
|
|
|
console.debug(`Got event: ${event}`);
|
|
|
|
if (event === 'directory_refresh') {
|
|
eventToast(
|
|
'Refresh started',
|
|
message,
|
|
'info',
|
|
);
|
|
}
|
|
|
|
if (subject && newestEvents[subject] && newestEvents[subject] > dt) {
|
|
console.debug(`Already handled newer event of "${subject}".`);
|
|
return;
|
|
} else if (event === 'global_after_refresh_completed' || event === '') {
|
|
let message_ = message || 'Files have been refreshed.';
|
|
eventToast('Refresh completed', message_, 'success');
|
|
}
|
|
|
|
if (event === 'shutdown') {
|
|
eventToast('Shutdown', message, 'warning');
|
|
}
|
|
|
|
if (event === 'shutdown_failed') {
|
|
eventToast('Shutdown Failed', message, 'error');
|
|
}
|
|
|
|
if (event === 'downloads_disabled') {
|
|
eventToast('Downloads Disabled', message);
|
|
}
|
|
|
|
if (event === 'map_import_complete') {
|
|
eventToast('Map import completed', message, 'success');
|
|
}
|
|
|
|
if (event === 'map_import_failed') {
|
|
eventToast('Map import failed', message, 'error');
|
|
}
|
|
|
|
if (event === 'map_search_complete') {
|
|
eventToast('Map Search Index', message, 'success');
|
|
}
|
|
|
|
if (event === 'map_search_failed') {
|
|
eventToast('Map Search Index Failed', message, 'error');
|
|
}
|
|
|
|
if (event === 'user_notify_message') {
|
|
console.log(message, url);
|
|
eventToast(
|
|
message,
|
|
'Click here to view the shared page',
|
|
'info',
|
|
10_000,
|
|
() => window.open(url, '_self'));
|
|
}
|
|
|
|
if (event === 'deleted') {
|
|
eventToast('Successful Delete', message, 'warning');
|
|
}
|
|
|
|
if (event === 'created') {
|
|
eventToast('Successful Creation', message, 'success');
|
|
}
|
|
|
|
if (event === 'tagged') {
|
|
eventToast('Successful tag', message, 'success');
|
|
}
|
|
|
|
if (event === 'file_move_completed') {
|
|
eventToast('Successful Move', message, 'success');
|
|
}
|
|
|
|
if (event === 'file_move_failed') {
|
|
eventToast('Move Failed', message, 'error');
|
|
}
|
|
|
|
if (event === 'config_import_failed') {
|
|
eventToast('Config Import Failed', message, 'error');
|
|
}
|
|
|
|
if (event === 'config_save_failed') {
|
|
eventToast('Config Save Failed', message, 'error');
|
|
}
|
|
|
|
if (event === 'upload_archive') {
|
|
eventToast(
|
|
'Archive Uploaded',
|
|
message,
|
|
'success',
|
|
5000,
|
|
() => window.open(url, '_self'));
|
|
}
|
|
|
|
if (event === 'upload_archive_failed') {
|
|
eventToast('Archive Upload Failed!', message, 'error', 5000);
|
|
}
|
|
|
|
if (event === 'screenshot_generated') {
|
|
eventToast(
|
|
'Screenshot Generated',
|
|
message,
|
|
'success',
|
|
5000,
|
|
() => window.open(url, '_self'));
|
|
}
|
|
|
|
if (event === 'screenshot_generation_failed') {
|
|
eventToast('Screenshot Generation Failed!', message, 'error', 5000);
|
|
}
|
|
|
|
if (event === 'upgrade_started') {
|
|
eventToast('Upgrade Started', message, 'info', 10000);
|
|
// Redirect to Controller UI for upgrade status after a short delay
|
|
setTimeout(() => {
|
|
window.location.href = '/controller/?upgrade=true';
|
|
}, 1000);
|
|
}
|
|
|
|
if (subject) {
|
|
newestEvents[subject] = dt;
|
|
}
|
|
})
|
|
}
|
|
|
|
export function useEventsInterval() {
|
|
const [events, setEvents] = React.useState(null);
|
|
|
|
// `now` is the last time we successfully fetched events.
|
|
const now = React.useRef(localStorage.getItem('events_now'));
|
|
const setNow = (newNow) => {
|
|
now.current = newNow;
|
|
localStorage.setItem('events_now', now.current);
|
|
}
|
|
|
|
const fetchEvents = async () => {
|
|
if (window.apiDown) { // apiDown is set in useStatus
|
|
return;
|
|
}
|
|
try {
|
|
const response = await getEvents(now.current);
|
|
setNow(response['now']);
|
|
setEvents(response['events']);
|
|
} catch (e) {
|
|
setEvents(null);
|
|
if (e instanceof ApiDownError) {
|
|
// API is down, do not log this error.
|
|
return;
|
|
}
|
|
// Ignore SyntaxError because they happen when the API is down.
|
|
if (!(e instanceof SyntaxError)) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
useRecurringTimeout(fetchEvents, 1000 * 5);
|
|
|
|
useEffect(() => {
|
|
if (events && events.length >= 1) {
|
|
handleEvents(events);
|
|
}
|
|
}, [JSON.stringify(events)]);
|
|
|
|
return {events, fetchEvents}
|
|
}
|
|
|
|
export function useSubscribeEvents(listener) {
|
|
// Subscribe to all API sent events.
|
|
document.addEventListener(apiEventName, listener);
|
|
const unsubscribe = () => document.removeEventListener(apiEventName, listener);
|
|
|
|
useEffect(() => {
|
|
return unsubscribe;
|
|
}, []);
|
|
}
|
|
|
|
export function useSubscribeEventName(name, listener) {
|
|
// Subscribe to only API sent events that match `name`.
|
|
const localListener = (event) => {
|
|
if (event && event.detail['event'] === name) {
|
|
listener(event);
|
|
}
|
|
}
|
|
|
|
useSubscribeEvents(localListener);
|
|
}
|