Improve message boxes

- Add icons
- Add audio
- Add padding
- Add "default button" styling logic, where the default or focused button gets a bolder border.
- Tweak some dialogs, favoring localized and accurate renditions, rather than more modern, more specific button labels.
main
Isaiah Odhner 2021-11-26 18:37:58 -05:00
parent 372da0a041
commit 9c88f205b2
24 changed files with 313 additions and 191 deletions

View File

@ -188,7 +188,7 @@ Functionality:
* JS * JS
* Organize things into files better; "functions.js" is like ONE step above saying "code.js" * Organize things into files better; "functions.js" is like ONE step above saying "code.js"
* `$ToolWindow` has a `$Button` facility; `$DialogWindow` overrides it with essentially a better one * `$ToolWindow` has a `$Button` facility; `$DialogWindow` overrides it with essentially a better one; now there's `showMessageBox` too! and `$ToolWindow` is a wrapper for OS-GUI's `$Window`, and should be removed at some point; btw, should `show_error_message` functionality be folded into `showMessageBox`?
* Make code clearer / improve code quality * Make code clearer / improve code quality
* https://codeclimate.com/github/1j01/jspaint * https://codeclimate.com/github/1j01/jspaint

BIN
audio/chord.wav Normal file

Binary file not shown.

BIN
images/error-16x16-8bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

BIN
images/error-32x32-1bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

BIN
images/error-32x32-8bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

BIN
images/info-16x16-8bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

BIN
images/info-32x32-1bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

BIN
images/info-32x32-8bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

BIN
images/nuke-32x32-8bpp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

BIN
images/windows-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

BIN
images/windows-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

View File

@ -396,13 +396,14 @@
<script src="lib/imagetracer_v1.2.5.js"></script> <script src="lib/imagetracer_v1.2.5.js"></script>
<script src="src/app-localization.js"></script> <!-- must not be async/deferred, as it uses document.write(); and must come before other app code which uses localization functions --> <script src="src/app-localization.js"></script> <!-- must not be async/deferred, as it uses document.write(); and must come before other app code which uses localization functions -->
<script src="src/msgbox.js"></script>
<script src="src/functions.js"></script> <script src="src/functions.js"></script>
<script src="src/helpers.js"></script> <script src="src/helpers.js"></script>
<script src="src/storage.js"></script> <script src="src/storage.js"></script>
<script src="src/$Component.js"></script> <script src="src/$Component.js"></script>
<script src="src/$ToolWindow.js"></script> <script src="src/$ToolWindow.js"></script>
<script> <script>
// after show_error_message, $DialogWindow, and localize are defined, // after show_error_message, showMessageBox, and localize are defined,
// set up better global error handling // set up better global error handling
const old_onerror = window.onerror; const old_onerror = window.onerror;
window.onerror = (message, source, lineno, colno, error)=> { window.onerror = (message, source, lineno, colno, error)=> {

View File

@ -1088,24 +1088,27 @@ function loaded_localizations(language, mapping) {
current_language = language; current_language = language;
} }
function set_language(language) { function set_language(language) {
const $w = $DialogWindow().title("Reload Required").addClass("dialogue-window"); showMessageBox({
$w.$main.text("The application needs to reload to change the language."); title: "Reload Required",
$w.$main.css("max-width", "600px"); message: "The application needs to reload to change the language.",
$w.$Button(localize("OK"), () => { buttons: [
$w.close(); { label: localize("OK"), value: "reload", default: true },
are_you_sure(() => { { label: localize("Cancel"), value: "cancel" },
try { ],
localStorage[language_storage_key] = language; windowOptions: {
location.reload(); innerWidth: 450,
} catch(error) { },
show_error_message("Failed to store language preference. Make sure cookies / local storage is enabled in your browser settings.", error); }).then((result) => {
} if (result === "reload") {
}); are_you_sure(() => {
}).focus(); try {
$w.$Button(localize("Cancel"), () => { localStorage[language_storage_key] = language;
$w.close(); location.reload();
} catch (error) {
show_error_message("Failed to store language preference. Make sure cookies / local storage is enabled in your browser settings.", error);
}
});
}
}); });
$w.center();
// load_language(language);
} }
load_language(current_language); load_language(current_language);

View File

@ -152,12 +152,12 @@ window.systemHookDefaults = {
newFileName = newHandle.name; newFileName = newHandle.name;
const newFileExtension = get_file_extension(newFileName); const newFileExtension = get_file_extension(newFileName);
if (!newFileExtension) { if (!newFileExtension) {
show_error_message(`Missing file extension - try adding .${new_format.extensions[0]} to the name.`); show_error_message(`Missing file extension.\n\nTry adding .${new_format.extensions[0]} to the name.`);
return; return;
} }
if (!new_format.extensions.includes(newFileExtension)) { if (!new_format.extensions.includes(newFileExtension)) {
// Closest translation: "Paint cannot save to the same filename with a different file type." // Closest translation: "Paint cannot save to the same filename with a different file type."
show_error_message(`Wrong file extension for selected file type - try adding .${new_format.extensions[0]} to the name.`); show_error_message(`Wrong file extension for selected file type.\n\nTry adding .${new_format.extensions[0]} to the name.`);
return; return;
} }
// const new_format = // const new_format =

View File

@ -829,35 +829,32 @@ try {
// This will be known as the "Y2T bug" // This will be known as the "Y2T bug"
confirmed_overwrite = Date.now() >= 2000000000000; confirmed_overwrite = Date.now() >= 2000000000000;
} }
function confirm_overwrite() { async function confirm_overwrite() {
return new Promise((resolve) => { if (confirmed_overwrite) {
if (confirmed_overwrite) { return;
resolve(); }
return; const { $window, promise } = showMessageBox({
} messageHTML: `
const $w = new $DialogWindow().addClass("dialogue-window");
$w.title(localize("Paint"));
$w.$main.html(`
<p>JS Paint can now save over existing files.</p> <p>JS Paint can now save over existing files.</p>
<p>Do you want to overwrite the file?</p> <p>Do you want to overwrite the file?</p>
<p> <p>
<label><input type='checkbox'/> Don't ask me again</label> <label><input type='checkbox'/> Don't ask me again</label>
</p> </p>
`); `,
$w.$Button(localize("OK"), () => { buttons: [
$w.close(); { label: localize("Yes"), value: "overwrite", default: true },
confirmed_overwrite = $w.$main.find("input[type='checkbox']").prop("checked"); { label: localize("Cancel"), value: "cancel" },
try { ],
localStorage[confirmed_overwrite_key] = confirmed_overwrite;
} catch (error) {
// no localStorage
}
}).focus();
$w.$Button(localize("Cancel"), () => {
$w.close();
});
$w.center();
}); });
const result = await promise;
if (result === "overwrite") {
confirmed_overwrite = $window.$content.find("input[type='checkbox']").prop("checked");
try {
localStorage[confirmed_overwrite_key] = confirmed_overwrite;
} catch (error) {
// no localStorage... @TODO: don't show the checkbox in this case
}
}
} }
@ -908,49 +905,62 @@ function file_save_as(maybe_saved_callback=()=>{}, update_from_saved=true){
} }
function are_you_sure(action, canceled){ function are_you_sure(action, canceled) {
if(saved){ if (saved) {
action(); action();
}else{ } else {
const $w = new $DialogWindow().addClass("dialogue-window"); showMessageBox({
$w.title(localize("Paint")); message: localize("Save changes to %1?", file_name),
$w.$main.text(localize("Save changes to %1?", file_name)); buttons: [
$w.$Button(localize("Save"), () => { {
$w.close(); // label: localize("Save"),
file_save(()=> { label: localize("Yes"),
value: "save",
default: true,
},
{
// label: "Discard",
label: localize("No"),
value: "discard",
},
{
label: localize("Cancel"),
value: "cancel",
},
],
}).then((result) => {
if (result === "save") {
file_save(() => {
action();
}, false);
} else if (result === "discard") {
action(); action();
}, false); } else {
})[0].focus(); canceled?.();
$w.$Button("Discard", () => { }
$w.close();
action();
}); });
$w.$Button(localize("Cancel"), () => {
$w.close();
canceled && canceled();
});
$w.$x.on("click", () => {
canceled && canceled();
});
$w.center();
} }
} }
function please_enter_a_number() { function please_enter_a_number() {
const $w = new $DialogWindow("Invalid Value").addClass("dialogue-window"); showMessageBox({
$w.$main.text(localize("Please enter a number.")); // title: "Invalid Value",
$w.$Button(localize("OK"), () => { message: localize("Please enter a number."),
$w.close(); });
}).focus();
} }
function show_error_message(message, error){ function show_error_message(message, error) {
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window squish"); const { $message } = showMessageBox({
$w.$main.text(message); iconID: "error",
$w.$main.css("max-width", "600px"); message,
// windowOptions: {
// innerWidth: 600,
// },
});
// $message.css("max-width", "600px");
if(error){ if(error){
const $details = $("<details><summary><span>Details</span></summary></details>") const $details = $("<details><summary><span>Details</span></summary></details>")
.appendTo($w.$main); .appendTo($message);
// Chrome includes the error message in the error.stack string, whereas Firefox doesn't. // Chrome includes the error message in the error.stack string, whereas Firefox doesn't.
// Also note that there can be Exception objects that don't have a message (empty string) but a name, // Also note that there can be Exception objects that don't have a message (empty string) but a name,
@ -979,25 +989,21 @@ function show_error_message(message, error){
overflow: "auto", overflow: "auto",
}); });
} }
$w.$Button(localize("OK"), () => {
$w.close();
}).focus();
$w.center();
if (error) { if (error) {
window.console && console.error(message, error); window.console?.error?.(message, error);
} else { } else {
window.console && console.error(message); window.console?.error?.(message);
} }
} }
// @TODO: close are_you_sure windows and these Error windows when switching sessions // @TODO: close are_you_sure windows and these Error windows when switching sessions
// because it can get pretty confusing // because it can get pretty confusing
function show_resource_load_error_message(error){ function show_resource_load_error_message(error){
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window"); const { $window, $message } = showMessageBox({});
const firefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; const firefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
// @TODO: copy & paste vs download & open, more specific guidance // @TODO: copy & paste vs download & open, more specific guidance
if (error.code === "cross-origin-blob-uri") { if (error.code === "cross-origin-blob-uri") {
$w.$main.html(` $message.html(`
<p>Can't load image from address starting with "blob:".</p> <p>Can't load image from address starting with "blob:".</p>
${ ${
firefox ? firefox ?
@ -1006,47 +1012,43 @@ function show_resource_load_error_message(error){
} }
`); `);
} else if (error.code === "html-not-image") { } else if (error.code === "html-not-image") {
$w.$main.html(` $message.html(`
<p>Address points to a web page, not an image file.</p> <p>Address points to a web page, not an image file.</p>
<p>Try copying and pasting an image instead of a URL.</p> <p>Try copying and pasting an image instead of a URL.</p>
`); `);
} else if (error.code === "decoding-failure") { } else if (error.code === "decoding-failure") {
$w.$main.html(` $message.html(`
<p>Address doesn't point to an image file of a supported format.</p> <p>Address doesn't point to an image file of a supported format.</p>
<p>Try copying and pasting an image instead of a URL.</p> <p>Try copying and pasting an image instead of a URL.</p>
`); `);
} else if (error.code === "access-failure") { } else if (error.code === "access-failure") {
if (navigator.onLine) { if (navigator.onLine) {
$w.$main.html(` $message.html(`
<p>Failed to download image.</p> <p>Failed to download image.</p>
<p>Try copying and pasting an image instead of a URL.</p> <p>Try copying and pasting an image instead of a URL.</p>
`); `);
if (error.fails) { if (error.fails) {
$("<ul>").append(error.fails.map(({status, statusText, url})=> $("<ul>").append(error.fails.map(({status, statusText, url})=>
$("<li>").text(url).prepend($("<b>").text(`${status || ""} ${statusText || "Failed"} `)) $("<li>").text(url).prepend($("<b>").text(`${status || ""} ${statusText || "Failed"} `))
)).appendTo($w.$main); )).appendTo($message);
} }
} else { } else {
$w.$main.html(` $message.html(`
<p>Failed to download image.</p> <p>Failed to download image.</p>
<p>You're offline. Connect to the internet and try again.</p> <p>You're offline. Connect to the internet and try again.</p>
<p>Or copy and paste an image instead of a URL, if possible.</p> <p>Or copy and paste an image instead of a URL, if possible.</p>
`); `);
} }
} else { } else {
$w.$main.html(` $message.html(`
<p>Failed to load image from URL.</p> <p>Failed to load image from URL.</p>
<p>Check your browser's devtools for details.</p> <p>Check your browser's devtools for details.</p>
`); `);
} }
$w.$main.css({maxWidth: "500px"}); $message.css({ maxWidth: "500px" });
$w.$Button(localize("OK"), () => { $window.center(); // after adding content
$w.close();
}).focus();
$w.center();
} }
function show_file_format_errors({ as_image_error, as_palette_error }) { function show_file_format_errors({ as_image_error, as_palette_error }) {
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window");
let html = ` let html = `
<p>${localize("Paint cannot open this file.")}</p> <p>${localize("Paint cannot open this file.")}</p>
`; `;
@ -1096,10 +1098,9 @@ function show_file_format_errors({ as_image_error, as_palette_error }) {
</details> </details>
`; `;
} }
$w.$main.html(html); showMessageBox({
$w.$Button(localize("OK"), () => { messageHTML: html,
$w.close(); });
}).focus();
} }
function show_read_image_file_error(error) { function show_read_image_file_error(error) {
// @TODO: similar friendly messages to show_resource_load_error_message, // @TODO: similar friendly messages to show_resource_load_error_message,
@ -1300,34 +1301,50 @@ async function choose_file_to_paste() {
function paste(img_or_canvas){ function paste(img_or_canvas){
if(img_or_canvas.width > main_canvas.width || img_or_canvas.height > main_canvas.height){ if(img_or_canvas.width > main_canvas.width || img_or_canvas.height > main_canvas.height){
const $w = new $DialogWindow().addClass("dialogue-window"); showMessageBox({
$w.title(localize("Paint")); message: localize("The image in the clipboard is larger than the bitmap.") + "\n" +
$w.$main.html(` localize("Would you like the bitmap enlarged?"),
${localize("The image in the clipboard is larger than the bitmap.")}<br> iconID: "question",
${localize("Would you like the bitmap enlarged?")}<br> windowOptions: {
`); icons: {
$w.$Button("Enlarge", () => { 16: "images/windows-16x16.png",
$w.close(); 32: "images/windows-32x32.png",
// The resize gets its own undoable, as in mspaint },
resize_canvas_and_save_dimensions( },
Math.max(main_canvas.width, img_or_canvas.width), buttons: [
Math.max(main_canvas.height, img_or_canvas.height),
{ {
name: "Enlarge Canvas For Paste", // label: "Enlarge",
icon: get_help_folder_icon("p_stretch_both.png"), label: localize("Yes"),
} value: "enlarge",
); default: true,
do_the_paste(); },
$canvas_area.trigger("resize"); {
})[0].focus(); // label: "Crop",
$w.$Button("Crop", () => { label: localize("No"),
$w.close(); value: "crop",
do_the_paste(); },
{
label: localize("Cancel"),
value: "cancel",
},
],
}).then((result) => {
if (result === "enlarge") {
// The resize gets its own undoable, as in mspaint
resize_canvas_and_save_dimensions(
Math.max(main_canvas.width, img_or_canvas.width),
Math.max(main_canvas.height, img_or_canvas.height),
{
name: "Enlarge Canvas For Paste",
icon: get_help_folder_icon("p_stretch_both.png"),
}
);
do_the_paste();
$canvas_area.trigger("resize");
} else if (result === "crop") {
do_the_paste();
}
}); });
$w.$Button(localize("Cancel"), () => {
$w.close();
});
$w.center();
}else{ }else{
do_the_paste(); do_the_paste();
} }
@ -1647,6 +1664,7 @@ function undo(){
return true; return true;
} }
// @TODO: use Clippy.js instead for potentially annoying tips
let $document_history_prompt_window; let $document_history_prompt_window;
function redo(){ function redo(){
if(redos.length<1){ if(redos.length<1){
@ -1654,13 +1672,11 @@ function redo(){
$document_history_prompt_window.close(); $document_history_prompt_window.close();
} }
if (!$document_history_window || $document_history_window.closed) { if (!$document_history_window || $document_history_window.closed) {
const $w = $document_history_prompt_window = new $DialogWindow(); $document_history_prompt_window = showMessageBox({
$w.title("Redo"); title: "Redo",
$w.$main.html("To view all branches of the history tree, click <b>Edit > History</b>.").css({padding: 10}); messageHTML: `To view all branches of the history tree, click <b>Edit > History</b>.`,
// $w.$Button("Show History", show_document_history).css({margin: 10}).focus(); iconID: "info",
// $w.$Button(localize("Cancel"), ()=> { $w.close(); }).css({margin: 10}); }).$window;
$w.$Button(localize("OK"), ()=> { $w.close(); }).css({margin: 10}).focus();
$w.center();
} }
return false; return false;
} }
@ -3196,18 +3212,16 @@ function sanity_check_blob(blob, okay_callback, magic_number_bytes, magic_wanted
if (magic_found === magic_wanted) { if (magic_found === magic_wanted) {
okay_callback(); okay_callback();
} else { } else {
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window"); showMessageBox({
// hackily combining messages that are already localized // hackily combining messages that are already localized, in ways they were not meant to be used.
// you have to do some deduction to understand this message // you may have to do some deduction to understand this message.
$w.$main.html(` // messageHTML: `
<p>${localize("Unexpected file format.")}</p> // <p>${localize("Unexpected file format.")}</p>
<p>${localize("An unsupported operation was attempted.")}</p> // <p>${localize("An unsupported operation was attempted.")}</p>
`); // `,
$w.$main.css({maxWidth: "500px"}); message: "Your browser does not support writing images in this file format.",
$w.$Button(localize("OK"), () => { iconID: "error",
$w.close(); });
}).focus();
$w.center();
} }
}, (error)=> { }, (error)=> {
show_error_message(localize("An unknown error has occurred."), error); show_error_message(localize("An unknown error has occurred."), error);

View File

@ -255,17 +255,19 @@ function open_help_viewer(options){
}, (/* error */)=> { }, (/* error */)=> {
// access to error message is not allowed either, basically // access to error message is not allowed either, basically
if (location.protocol === "file:") { if (location.protocol === "file:") {
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window"); showMessageBox({
$w.$main.html(` // <p>${localize("Failed to launch help.")}</p>
<p>${localize("Failed to launch help.")}</p> // but it's already launched at this point
<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> // what's a good tutorial for starting a web server?
`); // https://gist.github.com/willurd/5720255 - impressive list, but not a tutorial
$w.$main.css({maxWidth: "500px"}); // https://attacomsian.com/blog/local-web-server - OK, good enough
$w.$Button(localize("OK"), () => { messageHTML: `
$w.close(); <p>Help is not available when running from the <code>file:</code> protocol.</p>
}).focus(); <p>To use this feature, <a href="https://attacomsian.com/blog/local-web-server">start a web server</a>.</p>
$w.center(); `,
iconID: "error",
});
} else { } else {
show_error_message(`${localize("Failed to launch help.")} ${localize("Access to %1 was denied.", options.contentsFile)}`); show_error_message(`${localize("Failed to launch help.")} ${localize("Access to %1 was denied.", options.contentsFile)}`);
} }

View File

@ -3,7 +3,7 @@ let $storage_manager;
let $quota_exceeded_window; let $quota_exceeded_window;
let ignoring_quota_exceeded = false; let ignoring_quota_exceeded = false;
function storage_quota_exceeded(){ async function storage_quota_exceeded(){
if($quota_exceeded_window){ if($quota_exceeded_window){
$quota_exceeded_window.close(); $quota_exceeded_window.close();
$quota_exceeded_window = null; $quota_exceeded_window = null;
@ -11,27 +11,27 @@ function storage_quota_exceeded(){
if(ignoring_quota_exceeded){ if(ignoring_quota_exceeded){
return; return;
} }
const $w = $DialogWindow().title("Storage Error").addClass("dialogue-window squish"); const { promise, $window } = showMessageBox({
$w.$main.html( title: "Storage Error",
"<p>JS Paint stores images as you work on them so that if you " + messageHTML: `
"close your browser or tab or reload the page " + <p>JS Paint stores images as you work on them so that if you close your browser or tab or reload the page your images are usually safe.</p>
"your images are usually safe.</p>" + <p>However, it has run out of space to do so.</p>
"<p>However, it has run out of space to do so.</p>" + <p>You can still save the current image with <b>File > Save</b>. You should save frequently, or free up enough space to keep the image safe.</p>
"<p>You can still save the current image with <b>File > Save</b>. " + `,
"You should save frequently, or free up enough space to keep the image safe.</p>" buttons: [
); { label: "Manage Storage", value: "manage", default: true },
$w.$Button("Manage Storage", () => { { label: "Ignore", value: "ignore" },
$w.close(); ],
iconID: "warning",
});
$quota_exceeded_window = $window;
const result = await promise;
if (result === "ignore") {
ignoring_quota_exceeded = true;
} else if (result === "manage") {
ignoring_quota_exceeded = false; ignoring_quota_exceeded = false;
manage_storage(); manage_storage();
}).focus(); }
$w.$Button("Ignore", () => {
$w.close();
ignoring_quota_exceeded = true;
});
$w.$content.width(500);
$w.center();
$quota_exceeded_window = $w;
} }
function manage_storage(){ function manage_storage(){

105
src/msgbox.js Normal file
View File

@ -0,0 +1,105 @@
// Prefer a function injected from outside an iframe,
// which will make dialogs that can go outside the iframe,
// for 98.js.org integration.
// Note that this API must be kept in sync with the version in 98.js.org.
// Note `defaultMessageBoxTitle` handling in make_iframe_window
// Any other default parameters need to be handled there (as it works now)
window.defaultMessageBoxTitle = localize("Paint");
var chord_audio = new Audio("audio/chord.wav");
window.showMessageBox = window.showMessageBox || (({
title = window.defaultMessageBoxTitle ?? "Alert",
message,
messageHTML,
buttons = [{ label: "OK", value: "ok", default: true }],
iconID = "warning", // "error", "warning", "info", or "nuke" for deleting files/folders
windowOptions = {}, // for controlling width, etc.
}) => {
let $window, $message;
const promise = new Promise((resolve, reject) => {
$window = make_window_supporting_scale(Object.assign({
title,
resizable: false,
innerWidth: 400,
maximizeButton: false,
minimizeButton: false,
}, windowOptions));
// $window.addClass("dialogue-window");
$message =
$("<div>").css({
textAlign: "left",
fontFamily: "MS Sans Serif, Arial, sans-serif",
fontSize: "14px",
marginTop: "22px",
flex: 1,
minWidth: 0, // Fixes hidden overflow, see https://css-tricks.com/flexbox-truncated-text/
whiteSpace: "normal", // overriding .window:not(.squish)
});
if (messageHTML) {
$message.html(messageHTML);
} else if (message) { // both are optional because you may populate later with dynamic content
$message.text(message).css({
whiteSpace: "pre-wrap",
wordWrap: "break-word",
});
}
$("<div>").append(
$("<img width='32' height='32'>").attr("src", `images/${iconID}-32x32-8bpp.png`).css({
margin: "16px",
display: "block",
}),
$message
).css({
display: "flex",
flexDirection: "row",
}).appendTo($window.$content);
$window.$content.css({
textAlign: "center",
});
for (const button of buttons) {
const $button = $window.$Button(button.label, () => {
button.action?.(); // API may be required for using user gesture requiring APIs
resolve(button.value);
$window.close(); // actually happens automatically
});
if (button.default) {
$button.addClass("default");
$button.focus();
setTimeout(() => $button.focus(), 0); // @TODO: why is this needed? does it have to do with the iframe window handling?
}
$button.css({
minWidth: 75,
height: 23,
margin: "16px 2px",
});
}
$window.on("focusin", "button", (event) => {
$(event.currentTarget).addClass("default");
});
$window.on("focusout", "button", (event) => {
$(event.currentTarget).removeClass("default");
});
$window.on("closed", () => {
resolve("closed"); // or "cancel"? do you need to distinguish?
});
$window.center();
});
promise.$window = $window;
promise.$message = $message;
promise.promise = promise; // for easy destructuring
try {
chord_audio.play();
} catch (error) {
console.log(`Failed to play ${chord_audio.src}: `, error);
}
return promise;
});
window.alert = (message) => {
showMessageBox({ message });
};

View File

@ -233,20 +233,17 @@
} }
start() { start() {
// @TODO: how do you actually detect if it's failing??? // @TODO: how do you actually detect if it's failing???
const $w = $DialogWindow().title(localize("Paint")).addClass("dialogue-window"); showMessageBox({
$w.$main.html("<p>The document may not load. Changes may not save.</p>" + messageHTML: `
"<p>Multiuser sessions are public. There is no security.</p>" <p>The document may not load. Changes may not save.</p>
// "<p>The document may not load. Changes may not save. If it does save, it's public. There is no security.</p>"// + <p>Multiuser sessions are public. There is no security.</p>
// "<p>I haven't found a way to detect Firebase quota limits being exceeded, " + `
// "so for now I'm showing this message regardless of whether it's working.</p>" + });
// "<p>If you're interested in using multiuser mode, please thumbs-up " + // "<p>The document may not load. Changes may not save. If it does save, it's public. There is no security.</p>"// +
// "<a href='https://github.com/1j01/jspaint/issues/68'>this issue</a> to show interest, and/or subscribe for updates.</p>" // "<p>I haven't found a way to detect Firebase quota limits being exceeded, " +
); // "so for now I'm showing this message regardless of whether it's working.</p>" +
$w.$main.css({ maxWidth: "500px" }); // "<p>If you're interested in using multiuser mode, please thumbs-up " +
$w.$Button(localize("OK"), () => { // "<a href='https://github.com/1j01/jspaint/issues/68'>this issue</a> to show interest, and/or subscribe for updates.</p>"
$w.close();
}).focus();
$w.center();
// Wrap the Firebase API because they don't // Wrap the Firebase API because they don't
// provide a great way to clean up event listeners // provide a great way to clean up event listeners