489 lines
14 KiB
JavaScript
489 lines
14 KiB
JavaScript
|
|
let $help_window;
|
|
function show_help() {
|
|
if ($help_window) {
|
|
$help_window.focus();
|
|
return;
|
|
}
|
|
$help_window = open_help_viewer({
|
|
title: localize("Paint Help"),
|
|
root: "help",
|
|
contentsFile: "help/mspaint.hhc",
|
|
}).$help_window;
|
|
$help_window.on("close", ()=> {
|
|
$help_window = null;
|
|
});
|
|
}
|
|
|
|
// awkward shim to interface with my own stupid code
|
|
window.TITLEBAR_ICON_SIZE = 16;
|
|
window.$Icon = (name)=> {
|
|
return $(`<img src='images/${name}-16x16.png'>`);
|
|
};
|
|
|
|
// shared code with 98.js.org
|
|
// (copy-pasted for now)
|
|
|
|
function open_help_viewer(options){
|
|
const $help_window = $Window({
|
|
title: options.title || "Help Topics",
|
|
icon: "chm",
|
|
resizable: true,
|
|
})
|
|
$help_window.addClass("help-window");
|
|
|
|
let ignore_one_load = true;
|
|
let back_length = 0;
|
|
let forward_length = 0;
|
|
|
|
const $main = $(E("div")).addClass("main");
|
|
const $toolbar = $(E("div")).addClass("toolbar");
|
|
const add_toolbar_button = (name, sprite_n, action_fn, enabled_fn)=> {
|
|
const $button = $("<button class='lightweight'>")
|
|
.append($("<span>").text(name))
|
|
.appendTo($toolbar)
|
|
.on("click", ()=> {
|
|
action_fn();
|
|
});
|
|
$("<div class='icon'/>")
|
|
.appendTo($button)
|
|
.css({
|
|
backgroundPosition: `${-sprite_n * 55}px 0px`,
|
|
});
|
|
const update_enabled = ()=> {
|
|
$button[0].disabled = enabled_fn && !enabled_fn();
|
|
};
|
|
update_enabled();
|
|
$help_window.on("click", "*", update_enabled);
|
|
$help_window.on("update-buttons", update_enabled);
|
|
return $button;
|
|
};
|
|
const measure_sidebar_width = ()=>
|
|
$contents.outerWidth() +
|
|
parseFloat(getComputedStyle($contents[0]).getPropertyValue("margin-left")) +
|
|
parseFloat(getComputedStyle($contents[0]).getPropertyValue("margin-right")) +
|
|
$resizer.outerWidth();
|
|
const $hide_button = add_toolbar_button("Hide", 0, ()=> {
|
|
const toggling_width = measure_sidebar_width();
|
|
$contents.hide();
|
|
$resizer.hide();
|
|
$hide_button.hide();
|
|
$show_button.show();
|
|
$help_window.width($help_window.width() - toggling_width);
|
|
$help_window.css("left", $help_window.offset().left + toggling_width);
|
|
$help_window.bringTitleBarInBounds();
|
|
});
|
|
const $show_button = add_toolbar_button("Show", 5, ()=> {
|
|
$contents.show();
|
|
$resizer.show();
|
|
$show_button.hide();
|
|
$hide_button.show();
|
|
const toggling_width = measure_sidebar_width();
|
|
$help_window.css("max-width", "unset");
|
|
$help_window.width($help_window.width() + toggling_width);
|
|
$help_window.css("left", $help_window.offset().left - toggling_width);
|
|
// $help_window.applyBounds() would push the window to fit (before trimming it only if needed)
|
|
// Trim the window to fit (especially for if maximized)
|
|
if ($help_window.offset().left < 0) {
|
|
$help_window.width($help_window.width() + $help_window.offset().left);
|
|
$help_window.css("left", 0);
|
|
}
|
|
$help_window.css("max-width", "");
|
|
}).hide();
|
|
add_toolbar_button("Back", 1, ()=> {
|
|
$iframe[0].contentWindow.history.back();
|
|
ignore_one_load = true;
|
|
back_length -= 1;
|
|
forward_length += 1;
|
|
}, ()=> back_length > 0);
|
|
add_toolbar_button("Forward", 2, ()=> {
|
|
$iframe[0].contentWindow.history.forward();
|
|
ignore_one_load = true;
|
|
forward_length -= 1;
|
|
back_length += 1;
|
|
}, ()=> forward_length > 0);
|
|
add_toolbar_button("Options", 3, ()=> {}, ()=> false); // @TODO: hotkey and underline on O
|
|
add_toolbar_button("Web Help", 4, ()=> {
|
|
iframe.src = "help/online_support.htm";
|
|
});
|
|
|
|
const $iframe = $Iframe({src: "help/default.html"}).addClass("inset-deep");
|
|
const iframe = $iframe[0];
|
|
iframe.$window = $help_window; // for focus handling integration
|
|
const $resizer = $(E("div")).addClass("resizer");
|
|
const $contents = $(E("ul")).addClass("contents inset-deep");
|
|
|
|
// @TODO: fix race conditions
|
|
$iframe.on("load", ()=> {
|
|
if (!ignore_one_load) {
|
|
back_length += 1;
|
|
forward_length = 0;
|
|
}
|
|
// iframe.contentWindow.location.href
|
|
ignore_one_load = false;
|
|
$help_window.triggerHandler("update-buttons");
|
|
});
|
|
|
|
$main.append($contents, $resizer, $iframe);
|
|
$help_window.$content.append($toolbar, $main);
|
|
|
|
$help_window.css({width: 800, height: 600});
|
|
|
|
$iframe.attr({name: "help-frame"});
|
|
$iframe.css({
|
|
backgroundColor: "white",
|
|
border: "",
|
|
margin: "1px",
|
|
});
|
|
$contents.css({
|
|
margin: "1px",
|
|
});
|
|
$help_window.center();
|
|
|
|
$main.css({
|
|
position: "relative", // for resizer
|
|
});
|
|
|
|
const resizer_width = 4;
|
|
$resizer.css({
|
|
cursor: "ew-resize",
|
|
width: resizer_width,
|
|
boxSizing: "border-box",
|
|
background: "var(--ButtonFace)",
|
|
borderLeft: "1px solid var(--ButtonShadow)",
|
|
boxShadow: "inset 1px 0 0 var(--ButtonHilight)",
|
|
top: 0,
|
|
bottom: 0,
|
|
zIndex: 1,
|
|
});
|
|
$resizer.on("pointerdown", (e)=> {
|
|
let pointermove, pointerup;
|
|
const getPos = (e)=>
|
|
Math.min($help_window.width() - 100, Math.max(20,
|
|
e.clientX - $help_window.$content.offset().left
|
|
));
|
|
$G.on("pointermove", pointermove = (e)=> {
|
|
$resizer.css({
|
|
position: "absolute",
|
|
left: getPos(e)
|
|
});
|
|
$contents.css({
|
|
marginRight: resizer_width,
|
|
});
|
|
});
|
|
$G.on("pointerup", pointerup = (e)=> {
|
|
$G.off("pointermove", pointermove);
|
|
$G.off("pointerup", pointerup);
|
|
$resizer.css({
|
|
position: "",
|
|
left: ""
|
|
});
|
|
$contents.css({
|
|
flexBasis: getPos(e) - resizer_width,
|
|
marginRight: "",
|
|
});
|
|
});
|
|
});
|
|
|
|
const parse_object_params = $object => {
|
|
// parse an $(<object>) to a plain object of key value pairs
|
|
const object = {};
|
|
for (const param of $object.children("param").get()) {
|
|
object[param.name] = param.value;
|
|
}
|
|
return object;
|
|
};
|
|
|
|
let $last_expanded;
|
|
|
|
const $Item = text => {
|
|
const $item = $(E("div")).addClass("item").text(text.trim());
|
|
$item.on("mousedown", () => {
|
|
$contents.find(".item").removeClass("selected");
|
|
$item.addClass("selected");
|
|
});
|
|
$item.on("click", () => {
|
|
const $li = $item.parent();
|
|
if($li.is(".folder")){
|
|
if($last_expanded){
|
|
$last_expanded.not($li).removeClass("expanded");
|
|
}
|
|
$li.toggleClass("expanded");
|
|
$last_expanded = $li;
|
|
}
|
|
});
|
|
return $item;
|
|
};
|
|
|
|
const $default_item_li = $(E("li")).addClass("page");
|
|
$default_item_li.append($Item("Welcome to Help").on("click", ()=> {
|
|
$iframe.attr({src: "help/default.html"});
|
|
}));
|
|
$contents.append($default_item_li);
|
|
|
|
function renderItem(source_li, $folder_items_ul) {
|
|
const object = parse_object_params($(source_li).children("object"));
|
|
if ($(source_li).find("li").length > 0){
|
|
|
|
const $folder_li = $(E("li")).addClass("folder");
|
|
$folder_li.append($Item(object.Name));
|
|
$contents.append($folder_li);
|
|
|
|
const $folder_items_ul = $(E("ul"));
|
|
$folder_li.append($folder_items_ul);
|
|
|
|
$(source_li).children("ul").children().get().forEach((li)=> {
|
|
renderItem(li, $folder_items_ul);
|
|
});
|
|
} else {
|
|
const $item_li = $(E("li")).addClass("page");
|
|
$item_li.append($Item(object.Name).on("click", ()=> {
|
|
$iframe.attr({src: `${options.root}/${object.Local}`});
|
|
}));
|
|
if ($folder_items_ul) {
|
|
$folder_items_ul.append($item_li);
|
|
} else {
|
|
$contents.append($item_li);
|
|
}
|
|
}
|
|
}
|
|
|
|
fetch(options.contentsFile).then((response)=> {
|
|
response.text().then((hhc)=> {
|
|
$($.parseHTML(hhc)).filter("ul").children().get().forEach((li)=> {
|
|
renderItem(li, null);
|
|
});
|
|
}, (error)=> {
|
|
show_error_message(`${localize("Failed to launch help.")} Failed to read ${options.contentsFile}.`, error);
|
|
});
|
|
}, (/* error */)=> {
|
|
// access to error message is not allowed either, basically
|
|
if (location.protocol === "file:") {
|
|
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window");
|
|
$w.$main.html(`
|
|
<p>${localize("Failed to launch help.")}</p>
|
|
<p>This feature is not available when running from the <code>file:</code> protocol.</p>
|
|
<p>To use this feature, start a web server. If you have Python, you can use <code>python -m SimpleHTTPServer</code></p>
|
|
`);
|
|
$w.$main.css({maxWidth: "500px"});
|
|
$w.$Button(localize("OK"), () => {
|
|
$w.close();
|
|
}).focus();
|
|
$w.center();
|
|
} else {
|
|
show_error_message(`${localize("Failed to launch help.")} ${localize("Access to %1 was denied.", options.contentsFile)}`);
|
|
}
|
|
});
|
|
|
|
// @TODO: keyboard accessability
|
|
// $help_window.on("keydown", (e)=> {
|
|
// switch(e.keyCode){
|
|
// case 37:
|
|
// show_error_message("MOVE IT");
|
|
// break;
|
|
// }
|
|
// });
|
|
// var task = new Task($help_window);
|
|
var task = {};
|
|
task.$help_window = $help_window;
|
|
return task;
|
|
}
|
|
|
|
|
|
var programs_being_loaded = 0;
|
|
|
|
function $Iframe(options){
|
|
var $iframe = $("<iframe allowfullscreen sandbox='allow-same-origin allow-scripts allow-forms allow-pointer-lock allow-modals allow-popups allow-downloads'>");
|
|
var iframe = $iframe[0];
|
|
|
|
var disable_delegate_pointerup = false;
|
|
|
|
$iframe.focus_contents = function(){
|
|
if (!iframe.contentWindow) {
|
|
return;
|
|
}
|
|
if (iframe.contentDocument.hasFocus()) {
|
|
return;
|
|
}
|
|
|
|
disable_delegate_pointerup = true;
|
|
iframe.contentWindow.focus();
|
|
setTimeout(function(){
|
|
iframe.contentWindow.focus();
|
|
disable_delegate_pointerup = false;
|
|
});
|
|
};
|
|
|
|
// Let the iframe to handle mouseup events outside itself
|
|
var delegate_pointerup = function(){
|
|
if (disable_delegate_pointerup) {
|
|
return;
|
|
}
|
|
// This try-catch may only be needed for running Cypress tests.
|
|
try {
|
|
if(iframe.contentWindow && iframe.contentWindow.jQuery){
|
|
iframe.contentWindow.jQuery("body").trigger("pointerup");
|
|
}
|
|
if(iframe.contentWindow){
|
|
const event = new iframe.contentWindow.MouseEvent("mouseup", {button: 0});
|
|
iframe.contentWindow.dispatchEvent(event);
|
|
const event2 = new iframe.contentWindow.MouseEvent("mouseup", {button: 2});
|
|
iframe.contentWindow.dispatchEvent(event2);
|
|
}
|
|
} catch(error) {
|
|
console.log("Failed to access iframe to delegate pointerup; got", error);
|
|
}
|
|
};
|
|
$G.on("mouseup blur", delegate_pointerup);
|
|
$iframe.destroy = ()=> {
|
|
$G.off("mouseup blur", delegate_pointerup);
|
|
};
|
|
|
|
// @TODO: delegate pointermove events too?
|
|
|
|
$("body").addClass("loading-program");
|
|
programs_being_loaded += 1;
|
|
|
|
$iframe.on("load", function(){
|
|
|
|
if(--programs_being_loaded <= 0){
|
|
$("body").removeClass("loading-program");
|
|
}
|
|
|
|
// This try-catch may only be needed for running Cypress tests.
|
|
try {
|
|
if (window.themeCSSProperties) {
|
|
applyTheme(themeCSSProperties, iframe.contentDocument.documentElement);
|
|
}
|
|
|
|
// on Wayback Machine, and iframe's url not saved yet
|
|
if (iframe.contentDocument.querySelector("#error #livewebInfo.available")) {
|
|
var message = document.createElement("div");
|
|
message.style.position = "absolute";
|
|
message.style.left = "0";
|
|
message.style.right = "0";
|
|
message.style.top = "0";
|
|
message.style.bottom = "0";
|
|
message.style.background = "#c0c0c0";
|
|
message.style.color = "#000";
|
|
message.style.padding = "50px";
|
|
iframe.contentDocument.body.appendChild(message);
|
|
message.innerHTML = `<a target="_blank">Save this url in the Wayback Machine</a>`;
|
|
message.querySelector("a").href =
|
|
"https://web.archive.org/save/https://98.js.org/" +
|
|
iframe.src.replace(/.*https:\/\/98.js.org\/?/, "");
|
|
message.querySelector("a").style.color = "blue";
|
|
}
|
|
|
|
var $contentWindow = $(iframe.contentWindow);
|
|
$contentWindow.on("pointerdown click", function(e){
|
|
iframe.$window && iframe.$window.focus();
|
|
|
|
// from close_menus in $MenuBar
|
|
$(".menu-button").trigger("release");
|
|
// Close any rogue floating submenus
|
|
$(".menu-popup").hide();
|
|
});
|
|
// We want to disable pointer events for other iframes, but not this one
|
|
$contentWindow.on("pointerdown", function(e){
|
|
$iframe.css("pointer-events", "all");
|
|
$("body").addClass("dragging");
|
|
});
|
|
$contentWindow.on("pointerup", function(e){
|
|
$("body").removeClass("dragging");
|
|
$iframe.css("pointer-events", "");
|
|
});
|
|
// $("iframe").css("pointer-events", ""); is called elsewhere.
|
|
// Otherwise iframes would get stuck in this interaction mode
|
|
|
|
iframe.contentWindow.close = function(){
|
|
iframe.$window && iframe.$window.close();
|
|
};
|
|
// @TODO: hook into saveAs (a la FileSaver.js) and another function for opening files
|
|
// iframe.contentWindow.saveAs = function(){
|
|
// saveAsDialog();
|
|
// };
|
|
|
|
} catch(error) {
|
|
console.log("Failed to reach into iframe; got", error);
|
|
}
|
|
});
|
|
if (options.src) {
|
|
$iframe.attr({src: options.src});
|
|
}
|
|
$iframe.css({
|
|
minWidth: 0,
|
|
minHeight: 0, // overrides user agent styling apparently, fixes Sound Recorder
|
|
flex: 1,
|
|
border: 0, // overrides user agent styling
|
|
});
|
|
|
|
return $iframe;
|
|
}
|
|
|
|
// function $IframeWindow(options){
|
|
|
|
// var $win = new $Window(options);
|
|
|
|
// var $iframe = $win.$iframe = $Iframe({src: options.src});
|
|
// $win.$content.append($iframe);
|
|
// var iframe = $win.iframe = $iframe[0];
|
|
// // @TODO: should I instead of having iframe.$window, have something like get$Window?
|
|
// // Where all is $window needed?
|
|
// // I know it's used from within the iframe contents as frameElement.$window
|
|
// iframe.$window = $win;
|
|
|
|
// $win.on("close", function(){
|
|
// $iframe.destroy();
|
|
// });
|
|
// $win.onFocus($iframe.focus_contents);
|
|
|
|
// $iframe.on("load", function(){
|
|
// $win.show();
|
|
// $win.focus();
|
|
// // $iframe.focus_contents();
|
|
// });
|
|
|
|
// $win.setInnerDimensions = ({width, height})=> {
|
|
// const width_from_frame = $win.width() - $win.$content.width();
|
|
// const height_from_frame = $win.height() - $win.$content.height();
|
|
// $win.css({
|
|
// width: width + width_from_frame,
|
|
// height: height + height_from_frame + 21,
|
|
// });
|
|
// };
|
|
// $win.setInnerDimensions({
|
|
// width: (options.innerWidth || 640),
|
|
// height: (options.innerHeight || 380),
|
|
// });
|
|
// $win.$content.css({
|
|
// display: "flex",
|
|
// flexDirection: "column",
|
|
// });
|
|
|
|
// // @TODO: cascade windows
|
|
// $win.center();
|
|
// $win.hide();
|
|
|
|
// return $win;
|
|
// }
|
|
|
|
// Fix dragging things (i.e. windows) over iframes (i.e. other windows)
|
|
// (when combined with a bit of css, .dragging iframe { pointer-events: none; })
|
|
// (and a similar thing in $IframeWindow)
|
|
$(window).on("pointerdown", function(e){
|
|
//console.log(e.type);
|
|
$("body").addClass("dragging");
|
|
});
|
|
$(window).on("pointerup dragend blur", function(e){
|
|
//console.log(e.type);
|
|
if(e.type === "blur"){
|
|
if(document.activeElement.tagName.match(/iframe/i)){
|
|
return;
|
|
}
|
|
}
|
|
$("body").removeClass("dragging");
|
|
$("iframe").css("pointer-events", "");
|
|
});
|
|
|