OLD | NEW |
---|---|
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 var Galore = Galore || {}; | 5 var NOT_RECORDING = "Stopped"; |
6 | 6 var RECORDING = "Recording"; |
7 Galore.controller = { | 7 var PAUSED = "Paused"; |
8 /** @constructor */ | 8 var PLAYING = "Playing"; |
9 create: function() { | 9 |
10 var controller = Object.create(this); | 10 var recordingState = NOT_RECORDING; |
11 controller.api = chrome; | 11 |
12 controller.counter = 0; | 12 // Timestamp when current segment started. |
13 return controller; | 13 var segmentStart; |
14 }, | 14 // Segment duration accumulated before pause button was hit. |
15 | 15 var pausedDuration; |
16 createWindow: function() { | 16 // The array of segments, with delay and action. |
17 chrome.storage.sync.get('settings', this.onSettingsFetched_.bind(this)); | 17 var recordingList; |
18 }, | 18 // When this timer fires, the next segment from recordingList should be played. |
19 | 19 var playingTimer; |
20 /** @private */ | 20 var currentSegmentIndex; |
21 onSettingsFetched_: function(items) { | 21 |
22 var request = new XMLHttpRequest(); | 22 function setRecordingState(newState) { |
23 var settings = items.settings || {}; | 23 controller.view.setRecorderStatusText(newState); |
24 var source = settings.data || '/data/' + this.getDataVersion_(); | 24 recordingState = newState; |
25 request.open('GET', source, true); | 25 } |
26 request.responseType = 'text'; | 26 |
27 request.onload = this.onDataFetched_.bind(this, settings, request); | 27 function loadRecording() { |
28 request.send(); | 28 chrome.storage.local.get("recording", function(items) { |
29 }, | 29 recordingList = JSON.parse(items["recording"] || "[]"); |
30 | 30 }) |
31 /** @private */ | 31 } |
32 onDataFetched_: function(settings, request) { | 32 |
33 var count = 0; | 33 function finalizeRecording() { |
34 var data = JSON.parse(request.response); | 34 chrome.storage.local.set({"recording": JSON.stringify(recordingList)}); |
35 } | |
36 | |
37 function setPreviousSegmentDuration() { | |
38 var now = new Date().getTime(); | |
39 var delay = now - segmentStart; | |
40 segmentStart = now; | |
41 recordingList[currentSegmentIndex++].delay = delay; | |
42 } | |
43 | |
44 function recordCreate(id, options) { | |
45 if (recordingState != RECORDING) | |
46 return; | |
47 setPreviousSegmentDuration(); | |
48 recordingList.push({ type: "create", id: id, options: options }); | |
49 } | |
50 | |
51 function recordDelete(id) { | |
52 if (recordingState != RECORDING) | |
53 return; | |
54 setPreviousSegmentDuration(); | |
55 recordingList.push({ type: "delete", id: id }); | |
56 } | |
57 | |
58 function startPlaying() { | |
59 if (recordingList.length < 2) | |
60 return false; | |
61 | |
62 if (playingTimer) | |
63 clearTimeout(playingTimer); | |
64 | |
65 currentSegmentIndex = 0; | |
66 playingTimer = setTimeout(playNextSegment, | |
67 recordingList[currentSegmentIndex].delay); | |
68 } | |
69 | |
70 function playNextSegment() { | |
71 currentSegmentIndex++; | |
72 var segment = recordingList[currentSegmentIndex]; | |
73 if (!segment) | |
74 stopPlaying(); | |
75 | |
76 if (segment.type == "create") { | |
77 chrome.notifications.create(segment.id, segment.options, function() {}); | |
78 } else { // type == "delete" | |
79 chrome.notifications.clear(segment.id, function() {}); | |
80 } | |
81 playingTimer = setTimeout(playNextSegment, | |
82 recordingList[currentSegmentIndex].delay); | |
83 segmentStart = new Date().getTime(); | |
84 } | |
85 | |
86 function stopPlaying() { | |
87 clearTimeout(playingTimer); | |
88 } | |
89 | |
90 function pausePlaying() { | |
91 clearTimeout(playingTimer); | |
92 pausedDuration = | |
93 recordingList[currentSegmentIndex].delay - (now - segmentStart); | |
94 } | |
95 | |
96 function unpausePlaying() { | |
97 playingTimer = setTimeout(playNextSegment, pausedDuration); | |
98 segmentStart = new Date().getTime(); | |
99 } | |
100 | |
101 function onRecord() { | |
102 if (recordingState == NOT_RECORDING) { | |
103 currentSegmentIndex = 0; | |
104 segmentStart = new Date().getTime(); | |
105 pausedDuration = 0; | |
106 // This item is only needed to keep a duration of the delay between start | |
107 // and first action. | |
108 recordingList = [ { type:"start" } ]; | |
109 } else if (recordingState == PAUSED) { | |
110 segmentStart = new Date().getTime() - pausedDuration; | |
111 pausedDuration = 0; | |
112 } else { | |
113 return; | |
114 } | |
115 setRecordingState(RECORDING); | |
116 } | |
117 | |
118 function onPause() { | |
119 if (recordingState == RECORDING) { | |
120 pausedDuration = new Date().getTime() - segmentStart; | |
121 segmentStart = 0; | |
122 } else if (recordingState == PLAYING) { | |
123 pausePlaying(); | |
124 } else { | |
125 return; | |
126 } | |
127 setRecordingState(PAUSED); | |
128 } | |
129 | |
130 function onStop() { | |
131 if (recordingState == RECORDING) { | |
132 finalizeRecording(); | |
133 } else if (recordingState == PAUSED) { | |
134 segmentStart = new Date().getTime() - pausedDuration; | |
135 finalizeRecording(); | |
136 } else if (recordingState == PLAYING) { | |
137 stopPlaying(); | |
138 } else { | |
139 return; | |
140 } | |
141 setRecordingState(NOT_RECORDING); | |
142 } | |
143 | |
144 function onPlay() { | |
145 if (recordingState == NOT_RECORDING && startPlaying()) { | |
146 setRecordingState = PLAYING; | |
147 } | |
148 } | |
149 | |
150 function createWindow() { | |
151 loadRecording(); | |
152 chrome.storage.local.get('settings', onSettingsFetched); | |
153 } | |
154 | |
155 function onSettingsFetched(items) { | |
156 settings = items.settings || settings; | |
157 var request = new XMLHttpRequest(); | |
158 var source = '/data/data.json'; | |
159 request.open('GET', source, true); | |
160 request.responseType = 'text'; | |
161 request.onload = onDataFetched; | |
162 request.send(); | |
163 } | |
164 | |
165 function onDataFetched() { | |
166 var data = JSON.parse(this.response); | |
167 createAppWindow(function() { | |
168 // Create notification buttons. | |
35 data.forEach(function(section) { | 169 data.forEach(function(section) { |
170 var type = section.notificationType; | |
36 (section.notificationOptions || []).forEach(function(options) { | 171 (section.notificationOptions || []).forEach(function(options) { |
37 ++count; | 172 addNotificationButton(section.sectionName, |
38 this.fetchImages_(options, function() { | 173 options.title, |
39 if (--count == 0) | 174 options.iconUrl, |
40 this.onImagesFetched_(settings, data); | 175 function() { createNotification(type, options) }); |
41 }.bind(this)); | 176 }); |
42 }, this); | |
43 }, this); | |
44 }, | |
45 | |
46 /** @private */ | |
47 onImagesFetched_: function(settings, data) { | |
48 this.settings = settings; | |
49 this.view = Galore.view.create(this.settings, function() { | |
50 // Create buttons. | |
51 data.forEach(function(section) { | |
52 var defaults = section.globals || data[0].globals; | |
53 var type = section.notificationType; | |
54 (section.notificationOptions || []).forEach(function(options) { | |
55 var defaulted = this.getDefaultedOptions_(options, defaults); | |
56 var create = this.createNotification_.bind(this, type, defaulted); | |
57 this.view.addNotificationButton(section.sectionName, | |
58 defaulted.title, | |
59 defaulted.iconUrl, | |
60 create); | |
61 }, this); | |
62 }, this); | |
63 // Set the API entry point and use it to set event listeners. | |
64 this.api = this.getApi_(data); | |
65 if (this.api) | |
66 this.addListeners_(this.api, data[0].events); | |
67 // Display the completed and ready window. | |
68 this.view.showWindow(); | |
69 }.bind(this), this.onSettingsChange_.bind(this)); | |
70 }, | |
71 | |
72 /** @private */ | |
73 fetchImages_: function(options, onFetched) { | |
74 var count = 0; | |
75 var replacements = {}; | |
76 this.mapStrings_(options, function(string) { | |
77 if (string.indexOf("/images/") == 0 || string.search(/https?:\//) == 0) { | |
78 ++count; | |
79 this.fetchImage_(string, function(url) { | |
80 replacements[string] = url; | |
81 if (--count == 0) { | |
82 this.mapStrings_(options, function(string) { | |
83 return replacements[string] || string; | |
84 }); | |
85 onFetched.call(this, options); | |
86 } | |
87 }); | |
88 } | |
89 }); | 177 }); |
90 }, | 178 addListeners(); |
91 | 179 showWindow(); |
92 /** @private */ | 180 }); |
93 fetchImage_: function(url, onFetched) { | 181 } |
94 var request = new XMLHttpRequest(); | 182 |
95 request.open('GET', url, true); | 183 function onSettingsChange(settings) { |
96 request.responseType = 'blob'; | 184 chrome.storage.local.set({settings: settings}); |
97 request.onload = function() { | 185 } |
98 var url = window.URL.createObjectURL(request.response); | 186 |
99 onFetched.call(this, url); | 187 function createNotification(type, options) { |
100 }.bind(this); | 188 var id = getNextId(); |
101 request.send(); | 189 var priority = Number(settings.priority || 0); |
102 }, | 190 if (type == 'webkit') |
103 | 191 createWebKitNotification(options); |
104 /** @private */ | 192 else |
105 onSettingsChange_: function(settings) { | 193 createRichNotification(id, type, priority, options); |
106 this.settings = settings; | 194 } |
107 chrome.storage.sync.set({settings: this.settings}); | 195 |
108 }, | 196 function createWebKitNotification(options) { |
dewittj
2014/06/06 17:54:22
s/webkit/w3c/ or web
Dmitry Titov
2014/06/06 22:57:18
Done.
| |
109 | 197 var iconUrl = options.iconUrl; |
110 /** @private */ | 198 var title = options.title; |
111 createNotification_: function(type, options) { | 199 var message = options.message; |
112 var id = this.getNextId_(); | 200 new Notification(title, { |
113 var priority = Number(this.settings.priority || 0); | 201 body: message, |
114 var expanded = this.getExpandedOptions_(options, id, type, priority); | 202 icon: iconUrl |
dewittj
2014/06/06 17:54:22
add tag?
Dmitry Titov
2014/06/06 22:57:18
Done.
We currently don't seem to catch/log any ev
| |
115 if (type == 'webkit') | 203 }); |
116 this.createWebKitNotification_(expanded); | 204 logCreate('created WebKit', '', 'title: "' + title + '"'); |
117 else | 205 } |
118 this.createRichNotification_(expanded, id, type, priority); | 206 |
119 }, | 207 function createRichNotification(id, type, priority, options) { |
120 | 208 options["type"] = type; |
121 /** @private */ | 209 options["priority"] = priority; |
122 createWebKitNotification_: function(options) { | 210 chrome.notifications.create(id, options, function() { |
123 var iconUrl = options.iconUrl; | 211 var argument1 = 'type: "' + type + '"'; |
124 var title = options.title; | 212 var argument2 = 'priority: ' + priority; |
125 var message = options.message; | 213 var argument3 = 'title: "' + options.title + '"'; |
126 new Notification(title, { | 214 logCreate('created Rich', id, argument1, argument2, argument3); |
127 body: message, | 215 }); |
128 icon: iconUrl | 216 recordCreate(id, options); |
129 }); | 217 } |
130 this.handleEvent_('create', '?', 'title: "' + title + '"'); | 218 |
131 }, | 219 var counter = 0; |
132 | 220 function getNextId() { |
133 /** @private */ | 221 return String(counter++); |
134 createRichNotification_: function(options, id, type, priority) { | 222 } |
135 this.api.create(id, options, function() { | 223 |
136 var argument1 = 'type: "' + type + '"'; | 224 function addListeners() { |
137 var argument2 = 'priority: ' + priority; | 225 chrome.notifications.onClosed.addListener(onClosed); |
138 var argument3 = 'title: "' + options.title + '"'; | 226 chrome.notifications.onClicked.addListener(onClicked); |
139 this.handleEvent_('create', id, argument1, argument2, argument3); | 227 chrome.notifications.onButtonClicked.addListener(onButtonClicked); |
140 }.bind(this)); | 228 } |
141 }, | 229 |
142 | 230 function logCreate(event, id, var_args) { |
143 /** @private */ | 231 logEvent('Notification #' + id + ': ' + event + '(' + |
144 getNextId_: function() { | 232 Array.prototype.slice.call(arguments, 2).join(', ') + ')'); |
145 this.counter += 1; | 233 } |
146 return String(this.counter); | 234 |
147 }, | 235 function onClosed(id) { |
148 | 236 logEvent('Notification #' + id + ': onClosed'); |
149 /** @private */ | 237 recordDelete(id); |
150 getDefaultedOptions_: function(options, defaults) { | 238 } |
151 var defaulted = this.deepCopy_(options); | 239 |
152 Object.keys(defaults || {}).forEach(function (key) { | 240 function onClicked(id) { |
153 defaulted[key] = options[key] || defaults[key]; | 241 logEvent('Notification #' + id + ': onClicked'); |
154 }); | 242 } |
155 return defaulted; | 243 |
156 }, | 244 function onButtonClicked(id, index) { |
157 | 245 logEvent('Notification #' + id + ': onButtonClicked, btn: ' + index); |
158 /** @private */ | 246 } |
159 getExpandedOptions_: function(options, id, type, priority) { | |
160 var expanded = this.deepCopy_(options); | |
161 return this.mapStrings_(expanded, function(string) { | |
162 return this.getExpandedOption_(string, id, type, priority); | |
163 }, this); | |
164 }, | |
165 | |
166 /** @private */ | |
167 getExpandedOption_: function(option, id, type, priority) { | |
168 if (option == '$!') { | |
169 option = priority; // Avoids making priorities into strings. | |
170 } else { | |
171 option = option.replace(/\$#/g, id); | |
172 option = option.replace(/\$\?/g, type); | |
173 option = option.replace(/\$\!/g, priority); | |
174 } | |
175 return option; | |
176 }, | |
177 | |
178 /** @private */ | |
179 deepCopy_: function(value) { | |
180 var copy = value; | |
181 if (Array.isArray(value)) { | |
182 copy = value.map(this.deepCopy_, this); | |
183 } else if (value && typeof value === 'object') { | |
184 copy = {} | |
185 Object.keys(value).forEach(function (key) { | |
186 copy[key] = this.deepCopy_(value[key]); | |
187 }, this); | |
188 } | |
189 return copy; | |
190 }, | |
191 | |
192 /** @private */ | |
193 mapStrings_: function(value, map) { | |
194 var mapped = value; | |
195 if (typeof value === 'string') { | |
196 mapped = map.call(this, value); | |
197 mapped = (typeof mapped !== 'undefined') ? mapped : value; | |
198 } else if (value && typeof value == 'object') { | |
199 Object.keys(value).forEach(function (key) { | |
200 mapped[key] = this.mapStrings_(value[key], map); | |
201 }, this); | |
202 } | |
203 return mapped; | |
204 }, | |
205 | |
206 /** @private */ | |
207 addListeners_: function(api, events) { | |
208 (events || []).forEach(function(event) { | |
209 var listener = this.handleEvent_.bind(this, event); | |
210 if (api[event]) | |
211 api[event].addListener(listener); | |
212 else | |
213 console.log('Event ' + event + ' not defined.'); | |
214 }, this); | |
215 }, | |
216 | |
217 /** @private */ | |
218 handleEvent_: function(event, id, var_args) { | |
219 this.view.logEvent('Notification #' + id + ': ' + event + '(' + | |
220 Array.prototype.slice.call(arguments, 2).join(', ') + | |
221 ')'); | |
222 }, | |
223 | |
224 /** @private */ | |
225 getDataVersion_: function() { | |
226 var version = navigator.appVersion.replace(/^.* Chrome\//, ''); | |
227 return (version > '28.0.1500.70') ? '28.0.1500.70.json' : | |
228 (version > '27.0.1433.1') ? '27.0.1433.1.json' : | |
229 (version > '27.0.1432.2') ? '27.0.1432.2.json' : | |
230 '27.0.0.0.json'; | |
231 }, | |
232 | |
233 /** @private */ | |
234 getApi_: function(data) { | |
235 var path = data[0].api || 'notifications'; | |
236 var api = chrome; | |
237 path.split('.').forEach(function(key) { api = api && api[key]; }); | |
238 if (!api) | |
239 this.view.logError('No API found - chrome.' + path + ' is undefined'); | |
240 return api; | |
241 } | |
242 | |
243 }; | |
OLD | NEW |