1 /**
  2  * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global core, gui, runtime*/
 26 
 27 /**
 28  * @constructor
 29  * @implements {core.Destroyable}
 30  * @param {!ops.EditInfo} editInfo  marker takes ownership
 31  * @param {boolean} initialVisibility Sets the initial edit info marker visibility
 32  */
 33 gui.EditInfoMarker = function EditInfoMarker(editInfo, initialVisibility) {
 34     "use strict";
 35 
 36     var self = this,
 37         /**@type{!Element}*/
 38         editInfoNode,
 39         /**@type{!gui.EditInfoHandle}*/
 40         handle,
 41         /**@type{!HTMLDivElement}*/
 42         marker,
 43         editinfons = 'urn:webodf:names:editinfo',
 44         decayTimer0,
 45         decayTimer1,
 46         decayTimer2,
 47         decayTimeStep = 10000; // 10 seconds
 48 
 49     /**
 50      * Runs and returns a timer that sets the marker's opacity
 51      * to the specified value after the specified delay.
 52      * @param {!number} opacity
 53      * @param {!number} delay
 54      * @return {!number}
 55      */
 56     function applyDecay(opacity, delay) {
 57         return runtime.setTimeout(function () {
 58             marker.style.opacity = opacity;
 59         }, delay);
 60     }
 61 
 62     /**
 63      * Stops the specified timer
 64      * @param {!number} timerId
 65      */
 66     function deleteDecay(timerId) {
 67         runtime.clearTimeout(timerId);
 68     }
 69 
 70     /**
 71      * @param {!string} memberid
 72      */
 73     function setLastAuthor(memberid) {
 74         marker.setAttributeNS(editinfons, 'editinfo:memberid', memberid);
 75     }
 76 
 77     /**
 78      * @param {!string} memberid
 79      * @param {!Date} timestamp
 80      */
 81     this.addEdit = function (memberid, timestamp) {
 82         var age = (Date.now() - timestamp);
 83 
 84         editInfo.addEdit(memberid, timestamp);
 85         handle.setEdits(editInfo.getSortedEdits());
 86         setLastAuthor(memberid);
 87 
 88         // Since a new edit has arrived, stop decaying for the old edits
 89         deleteDecay(decayTimer1);
 90         deleteDecay(decayTimer2);
 91 
 92         // Decide the decay path:
 93         // this decides the initial opacity and subsequent decays to apply
 94         // depending on the age of the edit (for example the edit might have arrived
 95         // here a long time after it was added to the master session. We don't want
 96         // an opaque marker in that case, we would want it to start with a lower opacity
 97         // and decay accordingly further, if possible.
 98         if (age < decayTimeStep) {
 99             decayTimer0 = applyDecay(1, 0);
100             decayTimer1 = applyDecay(0.5, decayTimeStep - age);
101             decayTimer2 = applyDecay(0.2, decayTimeStep * 2 - age);
102         } else if (age >= decayTimeStep && age < decayTimeStep * 2) {
103             decayTimer0 = applyDecay(0.5, 0);
104             decayTimer2 = applyDecay(0.2, decayTimeStep * 2 - age);
105         } else {
106             decayTimer0 = applyDecay(0.2, 0);
107         }
108     };
109     this.getEdits = function () {
110         return editInfo.getEdits();
111     };
112     this.clearEdits = function () {
113         editInfo.clearEdits();
114         handle.setEdits([]);
115         if (marker.hasAttributeNS(editinfons, 'editinfo:memberid')) {
116             marker.removeAttributeNS(editinfons, 'editinfo:memberid');
117         }
118     };
119     this.getEditInfo = function () {
120         return editInfo;
121     };
122 
123     /**
124      * Shows the edit information marker
125      */
126     this.show = function () {
127         marker.style.display = 'block';
128     };
129 
130     /**
131      * Hides the edit information marker
132      */
133     this.hide = function () {
134         self.hideHandle();
135         // edit decays are not cleared as the marker should be properly
136         // faded if it is re-shown
137         marker.style.display = 'none';
138     };
139 
140     this.showHandle = function () {
141         handle.show();
142     };
143 
144     this.hideHandle = function () {
145         handle.hide();
146     };
147 
148     /**
149      * @param {!function(!Error=)} callback, passing an error object in case of error
150      * @return {undefined}
151      */
152     this.destroy = function (callback) {
153         deleteDecay(decayTimer0);
154         deleteDecay(decayTimer1);
155         deleteDecay(decayTimer2);
156         editInfoNode.removeChild(marker);
157         handle.destroy(function (err) {
158             if (err) {
159                 callback(err);
160             } else {
161                 editInfo.destroy(callback);
162             }
163         });
164     };
165 
166     function init() {
167         var dom = editInfo.getOdtDocument().getDOMDocument(),
168             htmlns = dom.documentElement.namespaceURI;
169 
170         marker = /**@type{!HTMLDivElement}*/(dom.createElementNS(htmlns, "div"));
171         marker.setAttribute("class", "editInfoMarker");
172 
173         marker.onmouseover = function () {
174             self.showHandle();
175         };
176         marker.onmouseout = function () {
177             self.hideHandle();
178         };
179 
180         editInfoNode = editInfo.getNode();
181         editInfoNode.appendChild(marker);
182         handle = new gui.EditInfoHandle(editInfoNode);
183         if (!initialVisibility) {
184             self.hide();
185         }
186     }
187 
188     init();
189 };
190