Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Fix #283 #419

Merged
merged 7 commits into from
Mar 9, 2012
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/Dialogs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
*/

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, brackets: false */

/**
* Utilities for creating and managing standard modal dialogs.
*/
define(function (require, exports, module) {
'use strict';

var DIALOG_BTN_CANCEL = "cancel",
DIALOG_BTN_OK = "ok",
DIALOG_BTN_DONTSAVE = "dontsave",
DIALOG_CANCELED = "_canceled";

// TODO: (issue #258) In future, we should templatize the HTML for the dialogs rather than having
// it live directly in the HTML.
var DIALOG_ID_ERROR = "error-dialog",
DIALOG_ID_SAVE_CLOSE = "save-close-dialog",
DIALOG_ID_EXT_CHANGED = "ext-changed-dialog",
DIALOG_ID_EXT_DELETED = "ext-deleted-dialog";

function _dismissDialog(dlg, buttonId) {
dlg.data("buttonId", buttonId);
dlg.modal(true).hide();
}

function _hasButton(dlg, buttonId) {
return dlg.find("[data-button-id='" + buttonId + "']");
}

var _handleKeyDown = function (e) {
var primaryBtn = this.find(".primary"),
buttonId = null,
which = String.fromCharCode(e.which);

if (e.which === 13) {
// Click primary button
if (primaryBtn) {
buttonId = primaryBtn.attr("data-button-id");
}
} else if (e.which === 32) {
// Space bar on focused button
this.find(".dialog-button:focus").click();
} else if (brackets.platform === "mac") {
// CMD+D Don't Save
if (e.metaKey && (which === 'D')) {
if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
buttonId = DIALOG_BTN_DONTSAVE;
}
// FIXME (issue #418) CMD+. Cancel swallowed by native shell
} else if (e.metaKey && (e.which === 190)) {
buttonId = DIALOG_BTN_CANCEL;
}
} else { // if (brackets.platform === "win") {
// 'N' Don't Save
if (which === 'N') {
if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
buttonId = DIALOG_BTN_DONTSAVE;
}
}
}

if (buttonId) {
_dismissDialog(this, buttonId);
} else {
// Stop the event if not handled by this dialog
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to stop the event if it is not targeted for the dialog. This keydown handler is called in the capture phase, so if we stop all events here, we would not be able to have editable text inside a dialog.

e.stopPropagation();
e.preventDefault();
}
};

/**
* General purpose modal dialog. Assumes that:
* -- the root tag of the dialog is marked with a unique class name (passed as dlgClass), as well as the
* classes "template modal hide".
* -- the HTML for the dialog contains elements with "title" and "message" classes, as well as a number
* of elements with "dialog-button" class, each of which has a "data-button-id".
*
* @param {string} dlgClass The class of the dialog node in the HTML.
* @param {string} title The title of the error dialog. Can contain HTML markup.
* @param {string} message The message to display in the error dialog. Can contain HTML markup.
* @return {Deferred} a $.Deferred() that will be resolved with the ID of the clicked button when the dialog
* is dismissed. Never rejected.
*/
function showModalDialog(dlgClass, title, message, callback) {
var result = $.Deferred();

// We clone the HTML rather than using it directly so that if two dialogs of the same
// type happen to show up, they can appear at the same time. (This is an edge case that
// shouldn't happen often, but we can't prevent it from happening since everything is
// asynchronous.)
var dlg = $("." + dlgClass + ".template")
.clone()
.removeClass("template")
.addClass("instance")
.appendTo(document.body);

// Set title and message
$(".dialog-title", dlg).html(title);
$(".dialog-message", dlg).html(message);

var handleKeyDown = _handleKeyDown.bind(dlg);

// Pipe dialog-closing notification back to client code
dlg.one("hidden", function () {
var buttonId = dlg.data("buttonId");
if (!buttonId) { // buttonId will be undefined if closed via Bootstrap's "x" button
buttonId = DIALOG_BTN_CANCEL;
}

// Let call stack return before notifying that dialog has closed; this avoids issue #191
// if the handler we're triggering might show another dialog (as long as there's no
// fade-out animation)
setTimeout(function () {
result.resolve(buttonId);
}, 0);

// Remove the dialog instance from the DOM.
dlg.remove();

// Remove keydown event handler
document.body.removeEventListener("keydown", handleKeyDown, true);
}).one("shown", function () {
// Set focus to the default button
var primaryBtn = dlg.find(".primary");

if (primaryBtn) {
primaryBtn.focus();
}

// Listen for dialog keyboard shortcuts
document.body.addEventListener("keydown", handleKeyDown, true);
});

// Click handler for buttons
dlg.one("click", ".dialog-button", function (e) {
_dismissDialog(dlg, $(this).attr("data-button-id"));
});

// Run the dialog
dlg.modal({
backdrop: "static",
show: true,
keyboard: true
});
return result;
}

/**
* Immediately closes any dialog instances with the given class. The dialog callback for each instance will
* be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
*/
function cancelModalDialogIfOpen(dlgClass) {
$("." + dlgClass + ".instance").each(function (dlg) {
if (dlg.is(":visible")) { // Bootstrap breaks if try to hide dialog that's already hidden
_dismissDialog(dlg, DIALOG_CANCELED);
}
});
}

exports.DIALOG_BTN_CANCEL = DIALOG_BTN_CANCEL;
exports.DIALOG_BTN_OK = DIALOG_BTN_OK;
exports.DIALOG_BTN_DONTSAVE = DIALOG_BTN_DONTSAVE;
exports.DIALOG_CANCELED = DIALOG_CANCELED;

exports.DIALOG_ID_ERROR = DIALOG_ID_ERROR;
exports.DIALOG_ID_SAVE_CLOSE = DIALOG_ID_SAVE_CLOSE;
exports.DIALOG_ID_EXT_CHANGED = DIALOG_ID_EXT_CHANGED;
exports.DIALOG_ID_EXT_DELETED = DIALOG_ID_EXT_DELETED;

exports.showModalDialog = showModalDialog;
exports.cancelModalDialogIfOpen = cancelModalDialogIfOpen;
});
21 changes: 11 additions & 10 deletions src/FileCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ define(function (require, exports, module) {
EditorManager = require("EditorManager"),
FileUtils = require("FileUtils"),
Async = require("Async"),
Dialogs = require("Dialogs"),
Strings = require("strings"),
PreferencesManager = require("PreferencesManager"),
PerfUtils = require("PerfUtils");
Expand Down Expand Up @@ -238,8 +239,8 @@ define(function (require, exports, module) {
}

function showSaveFileError(code, path) {
return brackets.showModalDialog(
brackets.DIALOG_ID_ERROR,
return Dialogs.showModalDialog(
Dialogs.DIALOG_ID_ERROR,
Strings.ERROR_SAVING_FILE_TITLE,
Strings.format(
Strings.ERROR_SAVING_FILE,
Expand Down Expand Up @@ -368,14 +369,14 @@ define(function (require, exports, module) {
if (doc.isDirty) {
var filename = PathUtils.parseUrl(doc.file.fullPath).filename;

brackets.showModalDialog(
brackets.DIALOG_ID_SAVE_CLOSE,
Dialogs.showModalDialog(
Dialogs.DIALOG_ID_SAVE_CLOSE,
Strings.SAVE_CLOSE_TITLE,
Strings.format(Strings.SAVE_CLOSE_MESSAGE, filename)
).done(function (id) {
if (id === brackets.DIALOG_BTN_CANCEL) {
if (id === Dialogs.DIALOG_BTN_CANCEL) {
result.reject();
} else if (id === brackets.DIALOG_BTN_OK) {
} else if (id === Dialogs.DIALOG_BTN_OK) {
doSave(doc)
.done(function () {
doClose(doc);
Expand Down Expand Up @@ -442,14 +443,14 @@ define(function (require, exports, module) {
});
message += "</ul>";

brackets.showModalDialog(
brackets.DIALOG_ID_SAVE_CLOSE,
Dialogs.showModalDialog(
Dialogs.DIALOG_ID_SAVE_CLOSE,
Strings.SAVE_CLOSE_TITLE,
message
).done(function (id) {
if (id === brackets.DIALOG_BTN_CANCEL) {
if (id === Dialogs.DIALOG_BTN_CANCEL) {
result.reject();
} else if (id === brackets.DIALOG_BTN_OK) {
} else if (id === Dialogs.DIALOG_BTN_OK) {
// Save all unsaved files, then if that succeeds, close all
saveAll().done(function () {
result.resolve();
Expand Down
5 changes: 3 additions & 2 deletions src/FileIndexManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ define(function (require, exports, module) {
var NativeFileSystem = require("NativeFileSystem").NativeFileSystem,
PerfUtils = require("PerfUtils"),
ProjectManager = require("ProjectManager"),
Dialogs = require("Dialogs"),
Strings = require("strings");

/**
Expand Down Expand Up @@ -123,8 +124,8 @@ define(function (require, exports, module) {
* Error dialog when max files in index is hit
*/
function _showMaxFilesDialog() {
return brackets.showModalDialog(
brackets.DIALOG_ID_ERROR,
return Dialogs.showModalDialog(
Dialogs.DIALOG_ID_ERROR,
Strings.ERROR_MAX_FILES_TITLE,
Strings.ERROR_MAX_FILES
);
Expand Down
17 changes: 9 additions & 8 deletions src/FileSyncManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ define(function (require, exports, module) {
Commands = require("Commands"),
CommandManager = require("CommandManager"),
Async = require("Async"),
Dialogs = require("Dialogs"),
Strings = require("strings"),
FileUtils = require("FileUtils");

Expand Down Expand Up @@ -151,8 +152,8 @@ define(function (require, exports, module) {
* @return {$.Deferred}
*/
function showReloadError(error, doc) {
return brackets.showModalDialog(
brackets.DIALOG_ID_ERROR,
return Dialogs.showModalDialog(
Dialogs.DIALOG_ID_ERROR,
Strings.ERROR_RELOADING_FILE_TITLE,
Strings.format(
Strings.ERROR_RELOADING_FILE,
Expand Down Expand Up @@ -202,24 +203,24 @@ define(function (require, exports, module) {
// Prompt UI varies depending on whether the file on disk was modified vs. deleted
if (i < editConflicts.length) {
toClose = false;
dialogId = brackets.DIALOG_ID_EXT_CHANGED;
dialogId = Dialogs.DIALOG_ID_EXT_CHANGED;
message = Strings.format(
Strings.EXT_MODIFIED_MESSAGE,
ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath)
);

} else {
toClose = true;
dialogId = brackets.DIALOG_ID_EXT_DELETED;
dialogId = Dialogs.DIALOG_ID_EXT_DELETED;
message = Strings.format(
Strings.EXT_DELETED_MESSAGE,
ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath)
);
}

brackets.showModalDialog(dialogId, Strings.EXT_MODIFIED_TITLE, message)
Dialogs.showModalDialog(dialogId, Strings.EXT_MODIFIED_TITLE, message)
.done(function (id) {
if (id === brackets.DIALOG_BTN_DONTSAVE) {
if (id === Dialogs.DIALOG_BTN_DONTSAVE) {
if (toClose) {
// Discard - close editor
DocumentManager.closeDocument(doc);
Expand Down Expand Up @@ -276,8 +277,8 @@ define(function (require, exports, module) {

// Close dialog if it was open. This will 'unblock' presentConflict(), which bails back
// to us immediately upon seeing _restartPending. We then restart the sync - see below
brackets.cancelModalDialogIfOpen(brackets.DIALOG_ID_EXT_CHANGED);
brackets.cancelModalDialogIfOpen(brackets.DIALOG_ID_EXT_DELETED);
Dialogs.cancelModalDialogIfOpen(Dialogs.DIALOG_ID_EXT_CHANGED);
Dialogs.cancelModalDialogIfOpen(Dialogs.DIALOG_ID_EXT_DELETED);

return;
}
Expand Down
5 changes: 3 additions & 2 deletions src/FileUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ define(function (require, exports, module) {
'use strict';

var NativeFileSystem = require("NativeFileSystem").NativeFileSystem,
Dialogs = require("Dialogs"),
Strings = require("strings");

/**
Expand Down Expand Up @@ -98,8 +99,8 @@ define(function (require, exports, module) {
}

function showFileOpenError(code, path) {
return brackets.showModalDialog(
brackets.DIALOG_ID_ERROR,
return Dialogs.showModalDialog(
Dialogs.DIALOG_ID_ERROR,
Strings.ERROR_OPENING_FILE_TITLE,
Strings.format(
Strings.ERROR_OPENING_FILE,
Expand Down
Loading