1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  */
 27 
 28 /**
 29  * @class
 30  * Manages an undo stack (ZmActionStack) and the "Undo" toast messages
 31  * Optimally there should only be one object of this class, globally reachable
 32  */
 33 ZmActionController = function() {
 34 
 35 	this._actionStack = new ZmActionStack(1);
 36 	this._statusTransitions = ZmActionController._substituteTransitions(appCtxt.getSkinHint("toast", "transitions") || ZmToast.DEFAULT_TRANSITIONS);
 37 	this._outsideListener = new AjxListener(this, ZmActionController.prototype._outsideMouseDownListener);
 38 	appCtxt.getKeyboardMgr().addListener(DwtEvent.ONKEYDOWN, this._outsideListener);
 39 };
 40 
 41 ZmActionController.prototype.toString =
 42 function() {
 43 	return "ZmActionController";
 44 };
 45 
 46 /**
 47  * Logs a raw action, dismissing any current undo message, interpreting the params and creates a ZmAction object that is pushed onto the stack and returned
 48  * See also @link ZmActionStack.prototype.logAction
 49  * 
 50  * @param {String}	op			operation to perform. Currently supported are "move", "trash", "spam" and "!spam"
 51  * @param {Hash}	[attrs] 	attributes for the operation. Pretty much the same as what the backend expects, e.g. "l" for the destination folderId of a move
 52 
 53  * @param {String}	[items] 	array of items to perform the action for. Valid types are specified in ZmActionStack.validTypes. Only one of [items],[item],[ids] or [id] should be specified; the first one found is used, ignoring the rest.
 54  * @param {String}	[item] 		item to perform the action for, if there is only one item. Accomplishes the same as putting the item in an array and giving it as [items]
 55  * @param {String}	[ids] 		array of ids of items to perform the action for.
 56  * @param {String}	[id] 		id of item to perform the action for, if there is only one. Accomplishes the same as putting the id in an array and giving it as [ids].
 57  */
 58 ZmActionController.prototype.actionPerformed = function(params) {
 59 	var logElement = this._actionStack.logAction(params);
 60 	if (logElement) {
 61 		this.dismiss();
 62 		logElement.onComplete(new AjxCallback(this, this._handleActionComplete));
 63 	}
 64 	return logElement;
 65 };
 66 
 67 ZmActionController.prototype._handleActionComplete = function(action) {
 68 	this._active = true;
 69 };
 70 
 71 /**
 72  * Returns the HTML code for a link that will undo the specified action
 73  * @param	{ZmAction}	actionElement	undoable action to be called when the link is pressed
 74  * @param	{String}	[text]			optional custom text for the link body. Defaults to ZmMsg.undo
 75  */
 76 ZmActionController.prototype.getUndoLink = function(actionElement, text) {
 77 	if (actionElement instanceof ZmAction) {
 78 		var undoId = ZmActionController._registerCallback(new AjxCallback(this, this.undoCurrent));
 79 		return ["<a onclick='ZmActionController.callRegisteredCallback(",undoId,")' href='javascript:;' class='undo'>", text || ZmMsg.undo, "</a>"].join("");
 80 	} else return ""; // We can't risk displaying "null" or "undefined" wherever this function is called from, and an empty string is just as falsy
 81 };
 82 
 83 ZmActionController.prototype.getStatusTransitions = function() {
 84 	return this._statusTransitions;
 85 };
 86 
 87 /**
 88  * Undoes the specified action
 89  * @param	{ZmAction}	action	the action to undo
 90  */
 91 ZmActionController.prototype.undo = function(action) {
 92 	if (this._active) {
 93 		this.dismiss();
 94 		action.undo();
 95 	}
 96 };
 97 
 98 ZmActionController.prototype.undoCurrent = function() {
 99 	if (this._actionStack.canUndo()) {
100 		if (this._actionStack.actionIsComplete()) {
101 			if (this._active) {
102 				this.dismiss();
103 			}
104 			this._actionStack.undo();
105 		} else {
106 			this._actionStack.onComplete(new AjxCallback(this,function(action) {
107 				// We must call this.undo on a timeout to allow the status message to transition in before dismissing
108 				setTimeout(AjxCallback.simpleClosure(this.undo, this, action), 0);
109 			}));
110 		}
111 	}
112 };
113 
114 ZmActionController.prototype.onPopup = function() {
115 	var omem = appCtxt.getOutsideMouseEventMgr();
116 	var omemParams = {
117 		id:					"ZmActionController",
118 		obj:				this,
119 		elementId:			ZmId.TOAST,
120 		outsideListener:	this._outsideListener
121 	}
122 	omem.startListening(omemParams);
123 };
124 
125 /**
126  * Dismisses the popped up toast
127  */
128 ZmActionController.prototype.dismiss = function() {
129 	appCtxt.dismissStatusMsg(true);
130 	var omem = appCtxt.getOutsideMouseEventMgr();
131 	omem.stopListening("ZmActionController");
132 	this._active = false;
133 };
134 
135 /**
136  * Substitute "pause" for "hold" and make sure there's only one of them
137  * @param	{Array}		transitions		Array of ZmToast transitions to copy from. Values are copied to the returned array
138  */
139 ZmActionController._substituteTransitions = function(transitions) {
140 	var newTransitions = [];
141 	var seenOne = false;
142 	for (var i=0; i<transitions.length; i++) {
143 		var transition = transitions[i];
144 		if (transition) {
145 			if (transition.type == ZmToast.PAUSE.type) {
146 				if (!seenOne) {
147 					seenOne = true;
148 					newTransitions.push(ZmToast.HOLD);
149 				}
150 			} else {
151 				newTransitions.push(transition);
152 			}
153 		}
154 	}
155 	return newTransitions;
156 };
157 
158 
159 
160 ZmActionController._registeredCallbacks = [];
161 
162 ZmActionController._registerCallback = function(callback) {
163 	var id = ZmActionController._registeredCallbacks.length;
164 	ZmActionController._registeredCallbacks[id] = callback;
165 	return id;
166 };
167 
168 ZmActionController.callRegisteredCallback = function(id) {
169 	var callback = ZmActionController._registeredCallbacks[id];
170 	if (callback) {
171 		if (callback instanceof AjxCallback) {
172 			callback.run();
173 		}
174 		else if (AjxUtil.isFunction(callback)) {
175 			callback();
176 		}
177 	}
178 };
179 
180 ZmActionController.prototype._outsideMouseDownListener =
181 function(ev) {
182 	this.dismiss();
183 };
184