348 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			HTML
		
	
	
	
| <!DOCTYPE html>
 | |
| <html>
 | |
| <head>
 | |
| 	<meta charset="UTF-8">
 | |
| 	<style type="text/css">
 | |
| 		* {
 | |
| 			box-sizing: border-box;
 | |
| 		}
 | |
| 		html {
 | |
| 			width: 100%; height: 100%;
 | |
| 		}
 | |
| 		body {
 | |
| 			position: absolute; top: 0; left: 0; right: 0; bottom: 0;
 | |
| 			margin: 0; padding: 0;
 | |
| 			overflow: hidden;
 | |
| 			color: #eee;
 | |
| 			font-family: sans-serif;
 | |
| 			background: rgba(62, 62, 62, .1);
 | |
| 		}
 | |
| 		.box {
 | |
| 			display: none;
 | |
| 
 | |
| 			position: fixed;
 | |
| 			top: 50%; right: 50%;
 | |
| 			max-height: 100%; max-width: 100%;
 | |
| 			transform: translate(50%, -50%);
 | |
| 
 | |
| 			overflow: auto;
 | |
| 
 | |
| 			padding: 15px;
 | |
| 
 | |
| 			background: #333;
 | |
| 			border-radius: 15px;
 | |
| 			border: 2px solid #999;
 | |
| 
 | |
| 			cursor: default;
 | |
| 			-webkit-user-select: none;
 | |
| 		}
 | |
| 		body.lit .box {
 | |
| 			border-color: #AAA;
 | |
| 		}
 | |
| 		.box h2 {
 | |
| 			margin: 0 0 10px;
 | |
| 		}
 | |
| 		.box button {
 | |
| 			margin: 7px 0px 0px;
 | |
| 			float: right;
 | |
| 			font-size: 125%;
 | |
| 		}
 | |
| 		.box button[data-affirm="0"] {
 | |
| 			float: left;
 | |
| 		}
 | |
| 		.box p {
 | |
| 			margin: 4px 0px;
 | |
| 			white-space: pre-wrap;
 | |
| 		}
 | |
| 		.box input {
 | |
| 			font-size: 110%;
 | |
| 			width: 100%;
 | |
| 			margin: 7px 0px;
 | |
| 			padding: 3px;
 | |
| 		}
 | |
| 
 | |
| 		.menu {
 | |
| 			display: none;
 | |
| 			border: 1px solid black;
 | |
| 			color: black;
 | |
| 			background: gray;
 | |
| 			position: absolute;
 | |
| 			-webkit-user-select: none;
 | |
| 			overflow: auto;
 | |
| 			max-width: 100%;
 | |
| 			max-height: 100%;
 | |
| 		}
 | |
| 		ul.menuList {
 | |
| 			margin: 0; padding: 0;
 | |
| 		}
 | |
| 		ul.menuList > li {
 | |
| 			margin: 1px;
 | |
| 			padding: 6px;
 | |
| 			list-style: none;
 | |
| 			cursor: default;
 | |
| 		}
 | |
| 		ul.menuList > li.sep {
 | |
| 			padding: 0;
 | |
| 			height: 5px;
 | |
| 		}
 | |
| 		ul.menuList > li.disabled {
 | |
| 			color: #444;
 | |
| 		}
 | |
| 		ul.menuList > li:not(.unpickable) {
 | |
| 			cursor: pointer;
 | |
| 		}
 | |
| 		ul.menuList > li:not(.unpickable):hover {
 | |
| 			background: darkturquoise;
 | |
| 		}
 | |
| 
 | |
| 	</style>
 | |
| </head>
 | |
| <body>
 | |
| 	<div class="box" id="alert">
 | |
| 		<h2>JavaScript Alert</h2>
 | |
| 		<p></p>
 | |
| 		<button>OK</button>
 | |
| 	</div>
 | |
| 
 | |
| 	<div class="box" id="confirm">
 | |
| 		<h2>JavaScript Confirmation</h2>
 | |
| 		<p></p>
 | |
| 		<button>OK</button>
 | |
| 		<button data-affirm="0">Cancel</button>
 | |
| 	</div>
 | |
| 
 | |
| 	<div class="box" id="confirmNav">
 | |
| 		<h2>Confirm Page Navigation</h2>
 | |
| 		<div>The page says:</div>
 | |
| 		<p></p>
 | |
| 		<div>Do you want to leave this page?</div>
 | |
| 		<button>Leave</button>
 | |
| 		<button data-affirm="0">Stay</button>
 | |
| 	</div>
 | |
| 
 | |
| 	<div class="box" id="confirmReload">
 | |
| 		<h2>Confirm Page Reload</h2>
 | |
| 		<div>The page says:</div>
 | |
| 		<p></p>
 | |
| 		<div>Do you want to reload this page?</div>
 | |
| 		<button>Reload</button>
 | |
| 		<button data-affirm="0">Stay</button>
 | |
| 	</div>
 | |
| 
 | |
| 	<div class="box" id="prompt">
 | |
| 		<h2>JavaScript Prompt</h2>
 | |
| 		<p></p>
 | |
| 		<input type="text"><br/>
 | |
| 		<button>OK</button>
 | |
| 		<button data-affirm="0">Cancel</button>
 | |
| 	</div>
 | |
| 
 | |
| 	<div class="box" id="auth">
 | |
| 		<h2>Authentication Prompt</h2>
 | |
| 		<p></p>
 | |
| 		<label for="authUser">User name:</label><input id="authUser" type="text"><br/>
 | |
| 		<label for="authPass">Password:</label><input id="authPass" type="password"><br/>
 | |
| 		<button>Log In</button>
 | |
| 		<button data-affirm="0">Cancel</button>
 | |
| 	</div>
 | |
| 
 | |
| 	<div class="menu" id="contextMenu">
 | |
| 		<ul class="menuList"></ul>
 | |
| 	</div>
 | |
| 
 | |
| 	<script type="application/javascript">
 | |
| 	"use strict";
 | |
| 	function $(sel) { return document.querySelector(sel); }
 | |
| 	function $$(sel) { return Array.from(document.querySelectorAll(sel)); }
 | |
| 
 | |
| 	//the embedder will replace these functions:
 | |
| 	function reportDialogResult(affirm, text1, text2) {
 | |
| 		console.log("Would report " + JSON.stringify(arguments));
 | |
| 	}
 | |
| 	function reportContextMenuResult(commandId) { "Would report " + JSON.stringify(arguments); }
 | |
| 
 | |
| 	/**
 | |
| 	 * Makes the border of the dialog flash subtly.
 | |
| 	 * This isn't here so much to look nice as to work around a bug
 | |
| 	 * that sometimes prevents popups from rendering fully.
 | |
| 	 */
 | |
| 	function flashBorder() {
 | |
| 		var speed = 100;
 | |
| 		document.body.className = "";
 | |
| 		setTimeout(() => document.body.className = "lit", 1 * speed);
 | |
| 		setTimeout(() => document.body.className = "", 2 * speed);
 | |
| 		setTimeout(() => document.body.className = "lit", 3 * speed);
 | |
| 		setTimeout(() => document.body.className = "", 4 * speed);
 | |
| 	}
 | |
| 
 | |
| 	reset();
 | |
| 
 | |
| 	var buttons = document.querySelectorAll(".box button");
 | |
| 	for (var i = 0; i < buttons.length; i++) {
 | |
| 		buttons[i].addEventListener("click", function() {
 | |
| 			reportResult(this.getAttribute("data-affirm") !== "0");
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	var bodyClick = null;
 | |
| 	var bodyResize = null;
 | |
| 	var inputFrom = null;
 | |
| 
 | |
| 	document.body.addEventListener("click", ev => {
 | |
| 		if (bodyClick) bodyClick(ev);
 | |
| 	});
 | |
| 	window.addEventListener("resize", ev => {
 | |
| 		if (bodyResize) bodyResize(ev);
 | |
| 	});
 | |
| 
 | |
| 
 | |
| 	/* global reportDialogResult, reportContextMenuResult */
 | |
| 	function reportResult(affirm) {
 | |
| 		if (inputFrom === "prompt") reportDialogResult(affirm, $("#prompt input").value);
 | |
| 		else if (inputFrom === "auth") reportDialogResult(affirm, $("#authUser").value, $("#authPass").value);
 | |
| 		else reportDialogResult(affirm);
 | |
| 		reset();
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	//Things the embedder can call:
 | |
| 	function reset() {
 | |
| 		$$("body > div").forEach(el => el.style.display = "none");
 | |
| 		$("#prompt input").value = "";
 | |
| 		bodyClick = null;
 | |
| 		bodyResize = null;
 | |
| 		inputFrom = null;
 | |
| 	}
 | |
| 
 | |
| 	function showAlert(text) {
 | |
| 		flashBorder();
 | |
| 		$("#alert").style.display = "block";
 | |
| 		$("#alert p").textContent = text;
 | |
| 		inputFrom = null;
 | |
| 	}
 | |
| 
 | |
| 	function showConfirm(text) {
 | |
| 		flashBorder();
 | |
| 		$("#confirm").style.display = "block";
 | |
| 		$("#confirm p").textContent = text;
 | |
| 		inputFrom = null;
 | |
| 	}
 | |
| 
 | |
| 	function showConfirmNav(text) {
 | |
| 		flashBorder();
 | |
| 		$("#confirmNav").style.display = "block";
 | |
| 		$("#confirmNav p").textContent = text;
 | |
| 		inputFrom = null;
 | |
| 	}
 | |
| 
 | |
| 	function showConfirmReload(text) {
 | |
| 		flashBorder();
 | |
| 		$("#confirmReload").style.display = "block";
 | |
| 		$("#confirmReload p").textContent = text;
 | |
| 		inputFrom = null;
 | |
| 	}
 | |
| 
 | |
| 	function showPrompt(text, defaultText) {
 | |
| 		flashBorder();
 | |
| 		$("#prompt").style.display = "block";
 | |
| 		$("#prompt p").textContent = text;
 | |
| 		$("#prompt input").value = defaultText || "";
 | |
| 		$("#prompt input").focus();
 | |
| 		inputFrom = "prompt";
 | |
| 	}
 | |
| 
 | |
| 	function showAuthPrompt(text) {
 | |
| 		flashBorder();
 | |
| 		$("#auth").style.display = "block";
 | |
| 		$("#auth p").textContent = text;
 | |
| 		$("#auth #authUser").value = "";
 | |
| 		$("#auth #authPass").value = "";
 | |
| 		$("#auth #authUser").focus();
 | |
| 		inputFrom = "auth";
 | |
| 	}
 | |
| 
 | |
| 	function buildMenu(menuDef, ul) {
 | |
| 		//Note: I have not yet found any cases where we have checkboxes,
 | |
| 		//radio buttons, or child menus, so they are currently unimplemented.
 | |
| 
 | |
| 		ul.innerHTML = "";
 | |
| 
 | |
| 		menuDef.forEach(item => {
 | |
| 			if (!item.visible) return;
 | |
| 
 | |
| 			var itemEl = document.createElement("li");
 | |
| 			ul.appendChild(itemEl);
 | |
| 
 | |
| 			if (item.type == "separator") {
 | |
| 				itemEl.className = "unpickable sep";
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 
 | |
| 			//todolater: Underline chars, kb nav
 | |
| 			itemEl.textContent = item.label.replace(/&/g, "");
 | |
| 
 | |
| 			if (item.checked) itemEl.classList.add("checked");
 | |
| 
 | |
| 			if (item.commandId && item.enabled) {
 | |
| 				itemEl.addEventListener("click", () => {
 | |
| 					reportContextMenuResult(item.commandId);
 | |
| 					reset();
 | |
| 				});
 | |
| 			} else {
 | |
| 				itemEl.classList.add("unpickable");
 | |
| 			}
 | |
| 
 | |
| 			if (!item.enabled) {
 | |
| 				itemEl.classList.add("unpickable");
 | |
| 				itemEl.classList.add("disabled");
 | |
| 			}
 | |
| 
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	function showContextMenu(defJSON, x, y) {
 | |
| 		var def = JSON.parse(defJSON);
 | |
| 		bodyClick = ev => {
 | |
| 			if (ev.target != document.body) return;
 | |
| 			reportContextMenuResult(-1);
 | |
| 			reset();
 | |
| 		};
 | |
| 
 | |
| 		var menuEl = $('#contextMenu');
 | |
| 
 | |
| 		var ul = $('#contextMenu ul');
 | |
| 		buildMenu(def, ul);
 | |
| 
 | |
| 
 | |
| 		bodyResize = () => {
 | |
| 			//Position the menu.
 | |
| 			menuEl.style.display = "block";
 | |
| 			menuEl.style.left = "";
 | |
| 			menuEl.style.right = "";
 | |
| 			menuEl.style.top = "";
 | |
| 			menuEl.style.bottom = "";
 | |
| 
 | |
| 			//Make sure it's all on screen
 | |
| 			if (x < 0) x = 0;
 | |
| 			if (y < 0) y = 0;
 | |
| 
 | |
| 			var mw = menuEl.offsetWidth;
 | |
| 			var mh = menuEl.offsetHeight;
 | |
| 			var sw = document.body.offsetWidth;
 | |
| 			var sh = document.body.offsetHeight;
 | |
| 
 | |
| 			if (x + mw >= sw) menuEl.style.right = "0";
 | |
| 			else menuEl.style.left = x + "px";
 | |
| 
 | |
| 			if (y + mh >= sh) menuEl.style.bottom = "0";
 | |
| 			else menuEl.style.top = y + "px";
 | |
| 		};
 | |
| 
 | |
| 		bodyResize();
 | |
| 	}
 | |
| 
 | |
| 	</script>
 | |
| </body>
 | |
| </html>
 |