OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 function $(criterion) { | |
6 return document.querySelector(criterion); | |
7 } | |
8 | |
9 var cyclerUI = new (function () { | |
Jeffrey Yasskin
2012/08/09 19:27:04
Do you ever use 'cyclerUI'? I'm not a JS expert, b
clintstaley
2012/08/10 00:33:13
I'm fairly new to it myself, and am just imitating
| |
10 | |
11 /** | |
12 * Note to reviewers: This is a (perhaps excessively?) fancy | |
Jeffrey Yasskin
2012/08/09 19:27:04
Grammar-o: "This is a fancy these functions into .
clintstaley
2012/08/10 00:33:13
Done.
| |
13 * these functions into the cyclerUI object wouldn't | |
14 * group them so nicely, but would also avoid a lot of 'bind' calls. I'm | |
15 * still sorting out JS design, and would value your opinion on this. | |
Jeffrey Yasskin
2012/08/09 19:27:04
I'm not at all an expert on JS design, but it seem
clintstaley
2012/08/10 00:33:13
Yeah, this is what I was worried about, too. If y
| |
16 * | |
17 * Enum for different UI states, showing variously a capture page, | |
18 * a page for choosing a playback, one for showing an already-chosen | |
19 * playback, and one for indicating that no playback choices yet exist. | |
20 * @enum {{set: !function()}} | |
21 * @private | |
Jeffrey Yasskin
2012/08/09 19:27:04
I think being a local variable in the object closu
clintstaley
2012/08/10 00:33:13
It surely does. I was thinking of the @private as
| |
22 */ | |
23 var EnableState_ = { | |
24 capture: { | |
25 set: | |
26 function() { | |
27 if (this.enableState != EnableState_.capture) { | |
28 this.enableState = EnableState_.capture; | |
29 this.enableTab_(this.captureTabLabel, this.captureTab); | |
30 this.captureTab.hidden = false; | |
31 this.playbackTab.hidden = true; | |
32 } | |
33 } | |
34 }, | |
35 choosePlayback: { | |
36 set: | |
37 function() { | |
38 if (this.enableState != EnableState_.choosePlayback) { | |
39 this.enableState = EnableState_.choosePlayback; | |
40 this.enableTab_(this.playbackTabLabel, this.playbackTab); | |
41 this.captureTab.hidden = true; | |
42 this.playbackTab.hidden = false; | |
43 this.noCaptures.hidden = true; | |
44 this.yesCaptures.hidden = false; | |
45 this.playbackDetails.hidden = true; | |
46 } | |
47 } | |
48 }, | |
49 showPlayback: { | |
50 set: | |
51 function() { | |
52 if (this.enableState != EnableState_.showPlayback) { | |
53 this.enableState = EnableState_.showPlayback; | |
54 this.enableTab_(this.playbackTabLabel, this.playbackTab); | |
55 this.captureTab.hidden = true; | |
56 this.playbackTab.hidden = false; | |
57 this.noCaptures.hidden = true; | |
58 this.yesCaptures.hidden = false; | |
59 this.playbackDetails.hidden = false; | |
60 } | |
61 } | |
62 }, | |
63 showNoCaptures: { | |
64 set: | |
65 function() { | |
66 if (this.enableState != EnableState_.showNoCaptures) { | |
67 this.enableState = EnableState_.showNoCaptures; | |
68 this.enableTab_(this.playbackTabLabel, this.playbackTab); | |
69 this.captureTab.hidden = true; | |
70 this.playbackTab.hidden = false; | |
71 this.noCaptures.hidden = false; | |
72 this.yesCaptures.hidden = true; | |
73 } | |
74 } | |
75 } | |
76 }; | |
77 | |
78 // Members for all UI elements subject to programmatic adjustment. | |
79 this.captureTab = $('#capture-tab'); | |
80 this.captureTabLabel = $('#capture-tab-label'); | |
81 this.captureName = $('#capture-name'); | |
82 this.captureURLs = $('#capture-urls'); | |
83 this.doCaptureButton = $('#do-capture'); | |
84 | |
85 this.playbackTab = $('#playback-tab'); | |
86 this.noCaptures = $('#no-captures'); | |
87 this.yesCaptures = $('#yes-captures'); | |
88 | |
89 this.playbackTabLabel = $('#playback-tab-label'); | |
90 this.playbackName = $('#playback-name'); | |
91 this.playbackDetails = $('#playback-details'); | |
92 this.playbackURLs = $('#playback-urls'); | |
93 this.playbackRepeats = $('#playback-repeats'); | |
94 this.playbackExtension = $('#playback-extension'); | |
95 this.doPlaybackButton = $('#do-playback'); | |
96 this.doDeleteButton = $('#do-delete'); | |
97 | |
98 this.popupDialog = $('#popup'); | |
99 this.popupContent = $('#popup-content'); | |
100 this.doPopupDismiss = $('#do-popup-dismiss'); | |
101 | |
102 // currentCaptureName_ is most recently done capture, or most recently | |
103 // selected stored capture. | |
104 this.currentCaptureName_ = null; | |
105 | |
106 /** | |
107 * Enable the tab indciated by |tabLabel| and |tab|, the HTML elements | |
Jeffrey Yasskin
2012/08/09 19:27:04
sp: indciated
clintstaley
2012/08/10 00:33:13
Done.
| |
108 * showing the selection-label for the tab, and the tab itself, respectively. | |
109 * @param {!HTMLDivElement} tabLabel The <div> for the label of the | |
110 * tab to enable. | |
111 * @param {!HTMLDivElement} tab The <div> for the tab itself. | |
112 * @private | |
113 */ | |
114 this.enableTab_ = function(tabLabel, tab) { | |
115 var tabList = document.querySelectorAll('.tab'); | |
116 var tabLabelList = document.querySelectorAll('.tab-label'); | |
117 | |
118 for (var i = 0; i < tabList.length; i++) | |
119 tabList[i].hidden = tabList[i] != tab; | |
120 | |
121 for (var i = 0; i < tabLabelList.length; i++) | |
122 if (tabLabelList[i] == tabLabel) { | |
123 tabLabelList[i].classList.add('select'); | |
124 } else { | |
125 tabLabelList[i].classList.remove('select'); | |
126 } | |
127 } | |
128 | |
129 /** | |
130 * Show an overlay with a message, a dismiss button with configurable | |
131 * label, and an action to call upon dismissal. | |
132 * @param {!string} content The message to display. | |
133 * @param {!string} dismissLabel The label on the dismiss button. | |
134 * @param {function()} action Additional action to take, if any, upon | |
135 * dismissal. | |
136 * @private | |
137 */ | |
138 this.showMessage_ = function(content, dismissLabel, action) { | |
139 this.popupContent.innerText = content; | |
140 this.doPopupDismiss.innerText = dismissLabel; | |
141 this.popupDialog.hidden = false; | |
142 if (action != null) | |
143 doPopupDismiss.addEventListener('click', action); | |
144 } | |
145 | |
146 /** | |
147 * Default action for popup dismissal button, performed in addition to | |
148 * any other actions that may be specified in showMessage_ call. | |
149 * @private | |
150 */ | |
151 this.clearMessage_ = function() { | |
152 this.popupDialog.hidden = true; | |
153 } | |
154 | |
155 /* Mockup of capture saving system. Sets up a simple array of known | |
156 * captures, and adjusts that array. This code will all be replaced | |
157 * in the next CL. | |
158 */ | |
159 this.currentCaptures = ['alpha', 'beta', 'gamma']; | |
160 | |
161 /** | |
162 * Save a capture from a specified cache directory on the browser side, | |
163 * storing it in HTML side FileSystem storage. Update the capture choices | |
164 * HTML select element. | |
165 * @param {!string} name The name of the capture. | |
166 * @param {!Array.<!string>} urlList List of URLs to capture. | |
167 * @param {!string} directory The cache directory name. | |
168 */ | |
169 this.saveCapture = function(name, urlList, directory) { | |
170 console.log('Saving capture ' + name + ' on ' + urlList + | |
171 ' from browserside directory ' + directory); | |
172 this.currentCaptures.push(name); | |
173 } | |
174 | |
175 /** | |
176 * Return a list of currently stored captures in the local FileSystem. | |
177 * @return {Array.<!string>} Names of all the current captures. | |
178 */ | |
179 this.getCaptures = function() { | |
180 return this.currentCaptures; | |
181 } | |
182 | |
183 /** | |
184 * Delete capture |name| from the local FileSystem, and update the | |
Jeffrey Yasskin
2012/08/09 19:27:04
Does this actually delete from the filesystem?
clintstaley
2012/08/10 00:33:13
No, like the comment says above, it's a mockup for
| |
185 * capture choices HTML select element. | |
186 * @param {!string} name The name of the capture to delete. | |
187 */ | |
188 this.deleteCapture = function(name) { | |
189 this.currentCaptures.splice(this.currentCaptures.indexOf(name), 1); | |
190 this.updatePlaybackChoices_(); | |
191 } | |
192 | |
193 /* End capture saving mockup */ | |
194 | |
195 /** | |
196 * Do a capture using current data from the capture tab. Post an error | |
197 * dialog if said data is incorrect or incomplete. Otherwise pass | |
198 * control to the browser side. | |
199 * @private | |
200 */ | |
201 this.doCapture_ = function() { | |
202 var errors = []; | |
203 var captureName = this.captureName.value.trim(); | |
204 var urlList; | |
205 | |
206 urlList = this.captureURLs.value.split('\n'); | |
207 if (captureName.length == 0) | |
208 errors.push('Must give a capture name'); | |
209 if (urlList.length == 0) | |
210 errors.push('Must give at least one URL'); | |
211 | |
212 if (errors.length > 0) { | |
213 this.showMessage_(errors.join('\n'), 'Ok'); | |
214 } else { | |
215 this.doCaptureButton.disabled = true; | |
216 chrome.experimental.record.captureURLs(captureName, urlList, | |
Jeffrey Yasskin
2012/08/09 19:27:04
Can captureURLs throw exceptions that need to be e
clintstaley
2012/08/10 00:33:13
Not across an extension API boundary that I know o
| |
217 this.onCaptureDone.bind(this)); | |
218 } | |
219 } | |
220 | |
221 /** | |
222 * Delete the capture with name |currentCaptureName_|. For this method | |
223 * to be callable, there must be a selected currentCaptureName_. Change | |
224 * the displayed HTML to show no captures if none exist now, or to show | |
225 * none selected (since we just deleted the selected one). | |
226 * @private | |
227 */ | |
228 this.doDelete_ = function() { | |
229 this.deleteCapture(this.currentCaptureName_); | |
230 this.currentCaptureName_ = null; | |
231 this.updatePlaybackChoices_(); | |
232 if (this.getCaptures().length == 0) { | |
233 EnableState_.showNoCaptures.set.bind(this)(); | |
234 } else { | |
235 EnableState_.choosePlayback.set.bind(this)(); | |
236 } | |
237 } | |
238 | |
239 /** | |
240 * Callback for completed (or possibly failed) capture. Post a message | |
241 * box, either with errors or "Success!" message. Save the capture cache | |
242 * directory in the event of success. | |
243 * @param {!Array.<string>} errors List of errors that occured | |
244 * during capture, if any. | |
245 * @param {!string} cacheDirectory Cache directory name for successful | |
246 * capture. | |
247 * @private | |
248 */ | |
249 this.onCaptureDone = function(errors, cacheDirectory) { | |
250 this.doCaptureButton.disabled = false; | |
251 | |
252 if (errors.length > 0) { | |
253 this.showMessage_(errors.join('\n'), 'Ok'); | |
254 } else { | |
255 this.showMessage_('Success!', 'Ok'); | |
256 this.currentCaptureName_ = this.captureName.value.trim(); | |
257 this.saveCapture(this.currentCaptureName_, cacheDirectory); | |
258 this.updatePlaybackChoices_(); | |
259 } | |
260 } | |
261 | |
262 /** | |
263 * Display the playback tab, showing either its "no captures", "choose | |
264 * a capture" or "display chosen capture" form, depending on the state | |
265 * of existing captures and |currentCaptureName_|. | |
266 * @private | |
267 */ | |
268 this.displayPlayback = function() { | |
269 if (this.getCaptures().length == 0) { | |
270 EnableState_.showNoCaptures.set.bind(this)(); | |
271 } else if (this.currentCaptureName_ == null) { | |
272 EnableState_.choosePlayback.set.bind(this)(); | |
273 } else { | |
274 EnableState_.showPlayback.set.bind(this)(); | |
275 } | |
276 } | |
277 | |
278 /** | |
279 * Utility function to refresh the selection list of captures that may | |
280 * be chosen for playback. Show all current captures, and also a | |
281 * "Choose a capture" default text if no capture is currently selected. | |
282 * @private | |
283 */ | |
284 this.updatePlaybackChoices_ = function() { | |
285 var captureNames = this.getCaptures(); | |
286 var options = this.playbackName.options; | |
287 var nextIndex = 0; | |
288 | |
289 options.length = 0; | |
290 | |
291 if (this.currentCaptureName_ == null) { | |
292 options.add(new Option('Choose a capture', null)); | |
293 options.selectedIndex = nextIndex++; | |
294 } | |
295 for (var i = 0; i < captureNames.length; i++) { | |
296 options.add(new Option(captureNames[i], captureNames[i])); | |
297 if (captureNames[i] == this.currentCaptureName_) { | |
298 options.selectedIndex = nextIndex; | |
299 } | |
300 nextIndex++; | |
301 } | |
302 } | |
303 | |
304 /** | |
305 * Event callback for selection of a capture to play back. Save the | |
306 * choice in |currentCaptureName_|. Update the selection list because the | |
307 * default reminder message is no longer needed when a capture is chosen. | |
308 * Change playback tab to show-chosen-capture mode. | |
309 */ | |
310 this.selectPlaybackName = function() { | |
311 this.currentCaptureName_ = this.playbackName.value; | |
312 this.updatePlaybackChoices_(); | |
313 EnableState_.showPlayback.set.bind(this)(); | |
314 } | |
315 | |
316 /** | |
317 * Event callback for pressing the playback button, which button is only | |
318 * enabled if a capture has been chosen for playback. Check for errors | |
319 * on playback page, and either display a message with such errors, or | |
320 * call the extenion api to initiate a playback. | |
321 */ | |
322 this.doPlayback_ = function() { | |
323 var extensionPath = this.playbackExtension.value; | |
324 var repeatCount = parseInt(this.playbackRepeats.value); | |
325 var errors = []; | |
326 | |
327 // Check local errors | |
328 if (isNaN(repeatCount)) { | |
329 errors.push('Enter a number for repeat count'); | |
330 } else if (repeatCount < 1 || repeatCount > 100) { | |
331 errors.push('Repeat count must be between 1 and 100'); | |
332 } | |
333 | |
334 if (errors.length > 0) { | |
335 this.showMessage_(errors.join('\n'), 'Ok'); | |
336 } else { | |
337 this.doPlaybackButton.disabled = true; | |
338 chrome.experimental.record.replayURLs( | |
339 this.currentCaptureName_, | |
340 repeatCount, | |
341 {'extensionPath': extensionPath}, | |
342 this.onPlaybackDone.bind(this)); | |
343 } | |
344 } | |
345 | |
346 /** | |
347 * Extension API calls this back when a playback is done. | |
348 * @param {!number} runtime Number of ms it took to run the playback. | |
349 * @param {!string} stats Statistics from the playback. | |
350 * @param {!Array.<!string>} errors List of error messages, if any, | |
351 * resuting from the playback. | |
352 */ | |
353 this.onPlaybackDone = function(runTime, stats, errors) { | |
354 this.doPlaybackButton.disabled = false; | |
355 | |
356 if (errors.length > 0) { | |
357 this.showMessage_(errors.join('\n'), 'Ok'); | |
358 } else { | |
359 this.showMessage_('Test took ' + runTime + 'mS :\n' + stats, 'Ok'); | |
Jeffrey Yasskin
2012/08/09 19:27:04
I understand this is just the initial version, but
clintstaley
2012/08/10 00:33:13
The design of the C++ side of this is a whole noth
| |
360 } | |
361 } | |
362 | |
363 // Set up listeners on all buttons. | |
364 this.doCaptureButton.addEventListener('click', this.doCapture_.bind(this)); | |
365 this.doPlaybackButton.addEventListener('click', this.doPlayback_.bind(this)); | |
366 this.doDeleteButton.addEventListener('click', this.doDelete_.bind(this)); | |
367 this.doPopupDismiss.addEventListener('click', this.clearMessage_.bind(this)); | |
368 | |
369 // Set up listeners on tab labels. | |
370 this.captureTabLabel.addEventListener('click', | |
371 EnableState_.capture.set.bind(this)); | |
372 this.playbackTabLabel.addEventListener('click', | |
373 this.displayPlayback.bind(this)); | |
374 | |
375 // Set up initial selection list for existing captures, and selection | |
376 // event listener. | |
377 this.updatePlaybackChoices_(); | |
378 this.playbackName.addEventListener('change', | |
379 this.selectPlaybackName.bind(this)); | |
380 | |
381 // Start with capture tab displayed. | |
382 EnableState_.capture.set.bind(this)(); | |
383 })(); | |
OLD | NEW |