feat: multi webpage support #25
This commit is contained in:
+696
@@ -0,0 +1,696 @@
|
||||
const path = require("path");
|
||||
const hardware = require("./hardware");
|
||||
const integration = require("./integration");
|
||||
const { app, screen, nativeTheme, ipcMain, BaseWindow, WebContentsView } = require("electron");
|
||||
|
||||
global.WEBVIEW = global.WEBVIEW || {
|
||||
initialized: false,
|
||||
status: null,
|
||||
locked: false,
|
||||
pointer: {
|
||||
position: {},
|
||||
time: new Date(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the webview with the provided arguments.
|
||||
*
|
||||
* @returns {bool} Returns true if the initialization was successful.
|
||||
*/
|
||||
const init = async () => {
|
||||
if (ARGS.web_url.length === 0) {
|
||||
console.error("Please provide the '--web-url' parameter");
|
||||
return app.quit();
|
||||
}
|
||||
if (ARGS.web_url.some((url) => !/^https?:\/\//.test(url))) {
|
||||
console.error("Please provide the '--web-url' parameter with http(s)");
|
||||
return app.quit();
|
||||
}
|
||||
const fullscreen = ARGS.app_debug !== "true";
|
||||
const widget = ARGS.web_widget ? ARGS.web_widget === "true" : true;
|
||||
const zoom = !isNaN(parseFloat(ARGS.web_zoom)) ? parseFloat(ARGS.web_zoom) : 1.25;
|
||||
const theme = ["light", "dark"].includes(ARGS.web_theme) ? ARGS.web_theme : "dark";
|
||||
const urls = [loaderHtml(40, 1.0, theme)].concat(ARGS.web_url);
|
||||
|
||||
// Init global properties
|
||||
WEBVIEW.viewUrls = urls;
|
||||
WEBVIEW.viewZoom = zoom;
|
||||
WEBVIEW.viewTheme = theme;
|
||||
WEBVIEW.widgetTheme = theme;
|
||||
WEBVIEW.widgetEnabled = widget;
|
||||
WEBVIEW.navigationTheme = theme;
|
||||
WEBVIEW.navigationEnabled = widget;
|
||||
nativeTheme.themeSource = WEBVIEW.viewTheme;
|
||||
Object.defineProperty(WEBVIEW, "viewActive", {
|
||||
get() {
|
||||
return this._viewActive || 0;
|
||||
},
|
||||
set(v) {
|
||||
this._viewActive = Math.max(0, Math.min(v, WEBVIEW.viewUrls.length - 1));
|
||||
updateView();
|
||||
},
|
||||
});
|
||||
|
||||
// Init global root window
|
||||
WEBVIEW.window = new BaseWindow({
|
||||
title: "TouchKio",
|
||||
icon: path.join(__dirname, "..", "img", "icon.png"),
|
||||
});
|
||||
WEBVIEW.window.setMenuBarVisibility(false);
|
||||
WEBVIEW.window.setFullScreen(fullscreen);
|
||||
if (!fullscreen) {
|
||||
WEBVIEW.window.maximize();
|
||||
}
|
||||
|
||||
// Init global webview
|
||||
WEBVIEW.views = [];
|
||||
urls.forEach((url, i) => {
|
||||
const view = new WebContentsView({
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
});
|
||||
view.setVisible(i === 0);
|
||||
view.setBackgroundColor("#FFFFFFFF");
|
||||
WEBVIEW.window.contentView.addChildView(view);
|
||||
view.webContents.loadURL(url);
|
||||
WEBVIEW.views.push(view);
|
||||
});
|
||||
|
||||
// Init global widget
|
||||
WEBVIEW.widget = new WebContentsView({
|
||||
transparent: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
WEBVIEW.widget.setBackgroundColor("#00000000");
|
||||
WEBVIEW.window.contentView.addChildView(WEBVIEW.widget);
|
||||
WEBVIEW.widget.webContents.loadFile(path.join(app.getAppPath(), "html", "widget.html"));
|
||||
|
||||
// Init global navigation
|
||||
WEBVIEW.navigation = new WebContentsView({
|
||||
transparent: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
WEBVIEW.navigation.setBackgroundColor("#00000000");
|
||||
WEBVIEW.window.contentView.addChildView(WEBVIEW.navigation);
|
||||
WEBVIEW.navigation.webContents.loadFile(path.join(app.getAppPath(), "html", "navigation.html"));
|
||||
|
||||
// Register events
|
||||
windowEvents();
|
||||
widgetEvents();
|
||||
navigationEvents();
|
||||
domEvents();
|
||||
appEvents();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the shared webview properties.
|
||||
*/
|
||||
const update = async () => {
|
||||
if (!WEBVIEW.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update windows status
|
||||
if (WEBVIEW.window.isFullScreen()) {
|
||||
WEBVIEW.status = "Fullscreen";
|
||||
} else if (WEBVIEW.window.isMinimized()) {
|
||||
WEBVIEW.status = "Minimized";
|
||||
} else if (WEBVIEW.window.isMaximized()) {
|
||||
WEBVIEW.status = "Maximized";
|
||||
} else {
|
||||
WEBVIEW.status = "Framed";
|
||||
}
|
||||
|
||||
// Update widget status
|
||||
updateWidget();
|
||||
|
||||
// Update navigation status
|
||||
updateNavigation();
|
||||
|
||||
// Update integration sensor
|
||||
console.log("Update Kiosk Status:", WEBVIEW.status);
|
||||
integration.update();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the active view.
|
||||
*/
|
||||
const updateView = () => {
|
||||
const title = `TouchKio - ${new URL(WEBVIEW.viewUrls[WEBVIEW.viewActive]).host}`;
|
||||
console.log(`Update View: ${title} (${WEBVIEW.viewActive})`);
|
||||
WEBVIEW.window.setTitle(title);
|
||||
|
||||
// Hide all other webviews and show only the active one
|
||||
WEBVIEW.views.forEach((view, i) => {
|
||||
view.setVisible(i === WEBVIEW.viewActive);
|
||||
});
|
||||
update();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the widget control.
|
||||
*/
|
||||
const updateWidget = () => {
|
||||
// Hide keyboard button
|
||||
WEBVIEW.widget.webContents.send("button-hidden", {
|
||||
id: "keyboard",
|
||||
hidden: !HARDWARE.support.keyboardVisibility,
|
||||
});
|
||||
|
||||
// Hide navigation button
|
||||
WEBVIEW.widget.webContents.send("button-hidden", {
|
||||
id: "navigation",
|
||||
hidden: !WEBVIEW.navigationEnabled,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the navigation control.
|
||||
*/
|
||||
const updateNavigation = () => {
|
||||
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
||||
const currentUrl = view.webContents.getURL();
|
||||
|
||||
// Disable pager button
|
||||
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||
id: "pager",
|
||||
disabled: WEBVIEW.views.length < 3,
|
||||
});
|
||||
|
||||
// Update url text
|
||||
WEBVIEW.navigation.webContents.send("input-text", {
|
||||
id: "url",
|
||||
text: currentUrl.startsWith("data:") ? "" : currentUrl,
|
||||
placeholder: defaultUrl,
|
||||
});
|
||||
|
||||
// Disable history buttons
|
||||
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||
id: "backward",
|
||||
disabled: !view.webContents.navigationHistory.canGoBack(),
|
||||
});
|
||||
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||
id: "forward",
|
||||
disabled: !view.webContents.navigationHistory.canGoForward(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows or hides the webview navigation bar.
|
||||
*/
|
||||
const toggleNavigation = () => {
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const navigation = WEBVIEW.navigation.getBounds();
|
||||
const height = navigation.height > 0 ? 0 : 60;
|
||||
|
||||
// Show or hide navigation based on height
|
||||
WEBVIEW.navigation.setBounds({
|
||||
x: 0,
|
||||
y: window.height - height,
|
||||
width: window.width,
|
||||
height: height,
|
||||
});
|
||||
|
||||
// Resize webview
|
||||
resizeView();
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates backward in the history of the active webview.
|
||||
*/
|
||||
const historyBackward = () => {
|
||||
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||
if (view.webContents.navigationHistory.canGoBack()) {
|
||||
view.webContents.navigationHistory.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates forward in the history of the active webview.
|
||||
*/
|
||||
const historyForward = () => {
|
||||
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||
if (view.webContents.navigationHistory.canGoForward()) {
|
||||
view.webContents.navigationHistory.goForward();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Activates the next webview or cycles back to the first.
|
||||
*/
|
||||
const nextView = () => {
|
||||
WEBVIEW.viewActive = (WEBVIEW.viewActive + 1) % WEBVIEW.views.length || 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reloads the current or default url on the active webview.
|
||||
*/
|
||||
const reloadView = () => {
|
||||
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
||||
const currentUrl = view.webContents.getURL();
|
||||
|
||||
// Reload the default url or refresh the page
|
||||
if (currentUrl != defaultUrl) {
|
||||
view.webContents.loadURL(defaultUrl);
|
||||
} else {
|
||||
view.webContents.reloadIgnoringCache();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes and positions all webviews.
|
||||
*/
|
||||
const resizeView = () => {
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const navigation = WEBVIEW.navigation.getBounds();
|
||||
const widget = { width: 60, height: 200 };
|
||||
|
||||
// Update view size
|
||||
WEBVIEW.views.forEach((view) => {
|
||||
view.setBounds({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: window.width,
|
||||
height: window.height - navigation.height,
|
||||
});
|
||||
view.webContents.setZoomFactor(WEBVIEW.viewZoom);
|
||||
});
|
||||
|
||||
// Update widget size
|
||||
if (WEBVIEW.widgetEnabled) {
|
||||
WEBVIEW.widget.setBounds({
|
||||
x: window.width - 15,
|
||||
y: parseInt(window.height / 2 - widget.height / 2, 10),
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
});
|
||||
WEBVIEW.widget.webContents.send("data-theme", { theme: "hidden" });
|
||||
}
|
||||
|
||||
// Update navigation size
|
||||
if (WEBVIEW.navigationEnabled) {
|
||||
WEBVIEW.navigation.setBounds({
|
||||
x: 0,
|
||||
y: window.height - navigation.height,
|
||||
width: window.width,
|
||||
height: navigation.height,
|
||||
});
|
||||
WEBVIEW.navigation.webContents.send("data-theme", { theme: WEBVIEW.navigationTheme });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register window events and handler.
|
||||
*/
|
||||
const windowEvents = () => {
|
||||
// Handle window resize events
|
||||
WEBVIEW.window.on("ready-to-show", resizeView);
|
||||
WEBVIEW.window.on("resize", resizeView);
|
||||
resizeView();
|
||||
|
||||
// Handle window focus events
|
||||
WEBVIEW.window.on("focus", () => {
|
||||
if (WEBVIEW.locked) {
|
||||
hardware.setDisplayStatus("ON");
|
||||
WEBVIEW.window.blur();
|
||||
}
|
||||
WEBVIEW.locked = false;
|
||||
});
|
||||
HARDWARE.display.notifiers.push(() => {
|
||||
WEBVIEW.locked = hardware.getDisplayStatus() === "OFF";
|
||||
if (WEBVIEW.locked) {
|
||||
WEBVIEW.window.blur();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle window status updates
|
||||
WEBVIEW.window.on("minimize", update);
|
||||
WEBVIEW.window.on("restore", update);
|
||||
WEBVIEW.window.on("maximize", update);
|
||||
WEBVIEW.window.on("unmaximize", update);
|
||||
WEBVIEW.window.on("enter-full-screen", update);
|
||||
WEBVIEW.window.on("leave-full-screen", update);
|
||||
|
||||
// Handle window touch events for activity tracking
|
||||
setInterval(() => {
|
||||
const posOld = WEBVIEW.pointer.position;
|
||||
const posNew = screen.getCursorScreenPoint();
|
||||
if (posOld.x !== posNew.x || posOld.y !== posNew.y) {
|
||||
const now = new Date();
|
||||
const then = WEBVIEW.pointer.time;
|
||||
|
||||
// Update integration sensor
|
||||
WEBVIEW.pointer.time = now;
|
||||
if (Math.abs(now - then) / 1000 > 30) {
|
||||
console.log("Update Last Active");
|
||||
integration.update();
|
||||
}
|
||||
}
|
||||
WEBVIEW.pointer.position = posNew;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register widget events and handler.
|
||||
*/
|
||||
const widgetEvents = () => {
|
||||
if (!WEBVIEW.widgetEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle widget focus events
|
||||
WEBVIEW.widget.webContents.on("focus", () => {
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const widget = WEBVIEW.widget.getBounds();
|
||||
|
||||
// Show widget
|
||||
WEBVIEW.widget.setBounds({
|
||||
x: window.width - 60,
|
||||
y: widget.y,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
});
|
||||
WEBVIEW.widget.webContents.send("data-theme", { theme: WEBVIEW.widgetTheme });
|
||||
});
|
||||
|
||||
// Handle widget blur events
|
||||
WEBVIEW.widget.webContents.on("blur", () => {
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const widget = WEBVIEW.widget.getBounds();
|
||||
|
||||
// Hide widget
|
||||
WEBVIEW.widget.setBounds({
|
||||
x: window.width - 15,
|
||||
y: widget.y,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
});
|
||||
WEBVIEW.widget.webContents.send("data-theme", { theme: "hidden" });
|
||||
});
|
||||
|
||||
// Handle widget button click events
|
||||
ipcMain.on("button-click", (e, button) => {
|
||||
switch (button.id) {
|
||||
case "keyboard":
|
||||
const toggle = hardware.getKeyboardVisibility() === "ON" ? "OFF" : "ON";
|
||||
hardware.setKeyboardVisibility(toggle);
|
||||
switch (toggle) {
|
||||
case "OFF":
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(true);
|
||||
break;
|
||||
case "ON":
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
WEBVIEW.window.maximize();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "fullscreen":
|
||||
if (WEBVIEW.window.isFullScreen()) {
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
} else {
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(true);
|
||||
}
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
break;
|
||||
case "minimize":
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
WEBVIEW.window.minimize();
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
break;
|
||||
case "navigation":
|
||||
toggleNavigation();
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register navigation events and handler.
|
||||
*/
|
||||
const navigationEvents = () => {
|
||||
if (!WEBVIEW.navigationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle navigation blur events
|
||||
let ignoreBlur = false;
|
||||
WEBVIEW.navigation.webContents.on("blur", () => {
|
||||
const visibility = hardware.getKeyboardVisibility();
|
||||
if (visibility === "ON" && !ignoreBlur) {
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(true);
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
WEBVIEW.navigation.webContents.send("input-blur", { id: "url" });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle input focus events
|
||||
ipcMain.on("input-focus", (e, input) => {
|
||||
const visibility = hardware.getKeyboardVisibility();
|
||||
switch (input.id) {
|
||||
case "url":
|
||||
if (visibility === "OFF") {
|
||||
ignoreBlur = true;
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
WEBVIEW.window.maximize();
|
||||
hardware.setKeyboardVisibility("ON", () => {
|
||||
setTimeout(() => {
|
||||
WEBVIEW.navigation.webContents.focus();
|
||||
WEBVIEW.navigation.webContents.send("input-select", { id: "url" });
|
||||
ignoreBlur = false;
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle input enter events
|
||||
ipcMain.on("input-enter", (e, input) => {
|
||||
switch (input.id) {
|
||||
case "url":
|
||||
let url = input.text.trim();
|
||||
if (url && !/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(url)) {
|
||||
url = "https://" + url;
|
||||
}
|
||||
if (!url) {
|
||||
url = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
||||
}
|
||||
WEBVIEW.views[WEBVIEW.viewActive].webContents.loadURL(url);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle navigation button click events
|
||||
ipcMain.on("button-click", (e, button) => {
|
||||
switch (button.id) {
|
||||
case "pager":
|
||||
nextView();
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
break;
|
||||
case "home":
|
||||
reloadView();
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
break;
|
||||
case "backward":
|
||||
historyBackward();
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
break;
|
||||
case "forward":
|
||||
historyForward();
|
||||
hardware.setKeyboardVisibility("OFF");
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register dom events and handler.
|
||||
*/
|
||||
const domEvents = () => {
|
||||
WEBVIEW.views.forEach((view) => {
|
||||
// Enable webview touch emulation
|
||||
view.webContents.debugger.attach("1.1");
|
||||
view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", {
|
||||
configuration: "mobile",
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
// Redirect webview hyperlinks
|
||||
view.webContents.setWindowOpenHandler(({ url }) => {
|
||||
view.webContents.loadURL(url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
// Remove webview scrollbar
|
||||
view.webContents.on("dom-ready", () => {
|
||||
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
|
||||
});
|
||||
|
||||
// Webview fully loaded
|
||||
view.webContents.on("did-finish-load", () => {
|
||||
WEBVIEW.initialized = true;
|
||||
if (WEBVIEW.viewActive === 0) {
|
||||
nextView();
|
||||
}
|
||||
if (ARGS.app_debug === "true") {
|
||||
setTimeout(() => {
|
||||
view.webContents.openDevTools();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
// Webview not loaded
|
||||
view.webContents.on("did-fail-load", (e, code, text, url, mainframe) => {
|
||||
if (mainframe) {
|
||||
if (WEBVIEW.viewActive === 0) {
|
||||
nextView();
|
||||
}
|
||||
view.webContents.loadURL(errorHtml(code, text, url));
|
||||
}
|
||||
});
|
||||
|
||||
// Webview url changed
|
||||
view.webContents.on("did-navigate-in-page", (e, url, mainframe) => {
|
||||
if (mainframe) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
view.webContents.on("did-navigate", () => {
|
||||
update();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register app events and handler.
|
||||
*/
|
||||
const appEvents = () => {
|
||||
// Handle signal and exit events
|
||||
process.on("SIGINT", app.quit);
|
||||
app.on("before-quit", () => {
|
||||
WEBVIEW.status = "Terminated";
|
||||
integration.update();
|
||||
});
|
||||
|
||||
// Handle multiple instances
|
||||
app.on("second-instance", () => {
|
||||
if (WEBVIEW.window.isMinimized()) {
|
||||
WEBVIEW.window.restore();
|
||||
}
|
||||
WEBVIEW.window.focus();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a html template for a spinning loader.
|
||||
*
|
||||
* @param {number} size - The size of the circle.
|
||||
* @param {number} speed - The rotation speed of the circle.
|
||||
* @param {string} theme - The theme used for spinner colors.
|
||||
* @returns {string} A data string with the generated html.
|
||||
*/
|
||||
const loaderHtml = (size, speed, theme) => {
|
||||
const color = {
|
||||
dark: { border: "#2A2A2A", spinner: "#03A9F4", background: "#111111" },
|
||||
light: { border: "#DCDCDC", spinner: "#03A9F4", background: "#FAFAFA" },
|
||||
}[theme];
|
||||
const html = `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${color.background};
|
||||
}
|
||||
.spinner {
|
||||
width: ${size}px;
|
||||
height: ${size}px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid ${color.border};
|
||||
border-top-color: ${color.spinner};
|
||||
animation: spin ${speed}s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="spinner"></div>
|
||||
</body>
|
||||
</html>`;
|
||||
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a html template for an error page.
|
||||
*
|
||||
* @param {number} code - The error code of the response.
|
||||
* @param {string} text - The error text of the response.
|
||||
* @param {string} url - The url of the requested page.
|
||||
* @returns {string} A data string with the generated html.
|
||||
*/
|
||||
const errorHtml = (code, text, url) => {
|
||||
const html = `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
.icon {
|
||||
margin: 0;
|
||||
font-size: 5rem;
|
||||
color: orange;
|
||||
}
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p class="icon">⚠</p>
|
||||
<h1 class="title">Whoopsie!</h1>
|
||||
<p><strong>Loading:</strong> ${url}</p>
|
||||
<p><strong>Error:</strong> ${text} (${code})</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
update,
|
||||
};
|
||||
Reference in New Issue
Block a user