-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.js
317 lines (273 loc) · 11.5 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
window.addEventListener('DOMContentLoaded', function () {
'use strict';
var translate = navigator.mozL10n.get;
});
var bookCache = {};
var httpRequestHandler = new HttpRequestHandler();
function stripHTMLTags(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
}
function Book(args) {
this.chapters = args.chapters;
var json = args.json;
this.description = stripHTMLTags(json.description);
this.title = stripHTMLTags(json.title);
this.id = json.id;
this.fullBookURL = json.url_zip_file;
}
function Chapter(args) {
var title_regex = /^<!\[CDATA\[(.*)\]\]>$/;
var title_match = title_regex.exec(args.title);
this.title = stripHTMLTags(title_match[1] || args.title); // if regex doesn't match, fall back to raw string
this.index = args.index; // TODO: Add whenever this method is called, return current chapter or get it if not available
this.url = args.url;
this.position = 0;
}
function UIState(args) {
this.currentBook = args.currentBook;
this.currentChapter = args.currentChapter;
this.bookCache = args.bookCache; // to increase reausability of object - did not hard-code the coupling with our global bookCache
this.setCurrentBookById = function (id) {
this.currentBook = this.bookCache[id];
};
this.setCurrentChapterByIndex = function (index) {
this.currentChapter = this.currentBook.chapters[index];
};
}
var appUIState = new UIState({'bookCache': bookCache});
function ChaptersListPageGenerator(args) {
var args = args || {};
var httpRequestHandler = args.httpRequestHandler;
var selector = args.selector;
this.generatePage = function (book) {
if (book.chapters) {
showLocalChapters(book.chapters);
} else {
getChaptersFromFeed(book.id, function (chapters) {
book.chapters = chapters;
showLocalChapters(book.chapters);
});
}
};
function showLocalChapters(chapters) {
$.each(chapters, function (index, chapter) {
generateChapterListItem(chapter);
});
$(selector).listview('refresh');
};
function generateChapterListItem(chapter) {
var chapterListItem = $('<li chapter-index=' + chapter.index + '><a href="book.html"><h2>' + chapter.title + '</h2></a></li>');
chapterListItem.click(function () {
appUIState.setCurrentChapterByIndex($(this).attr("chapter-index"));
});
$(selector).append(chapterListItem);
};
function getChaptersFromFeed(book_id, callback_func) {
httpRequestHandler.getXML("https://librivox.org/rss/" + encodeURIComponent(book_id), function(xhr) {
var xml = $(xhr.response),
$items = xml.find("item"),
chapters = [];
$items.each(function (index, element) {
var $title = $(element).find("title");
var $enclosure = $(element).find("enclosure");
var chapter = new Chapter({'index': chapters.length, 'title': $title.text(), 'url': $enclosure.attr('url')});
chapters.push(chapter);
});
callback_func(chapters);
});
};
}
var chaptersListGen = new ChaptersListPageGenerator({'httpRequestHandler': httpRequestHandler, 'selector': '#chaptersList'});
$( document ).on( "pagecreate", "#chaptersListPage", function (event) {
var selectedBook = appUIState.currentBook;
if (!selectedBook) { // selectedBook is undefined if you refresh the app from WebIDE on a chapter list page
console.warn("Chapters List: selectedBook was undefined, which freezes the app. Did you refresh from WebIDE?");
return false;
}
chaptersListGen.generatePage(selectedBook);
});
function BookDownloadManager(args) {
var that = this;
var progressBarSelector = args.progressSelector;
function downloadFile(url, finished_callback) {
var sdcard = navigator.getDeviceStorage("sdcard"); // TODO research other storage options - music?
var req_progress_callback = function(event) {
if(event.lengthComputable){
var percentage = (event.loaded / event.total) * 100;
$(progressBarSelector).css('width', percentage + '%');
}
}
httpRequestHandler.getBlob(
url,
function (xhr) { finished_callback(xhr.response); },
{'progress_callback': req_progress_callback}
);
}
this.downloadBook = function(book_obj) {
console.log('downloadBook message recieved');
downloadFile(book_obj.url, function (response) {
that.write(response, that.getBookFilePath(book_obj.id));
});
}
this.downloadChapter = function(book_id, chapter_obj) {
console.log('downloadChapter message recieved with ' + book_id + ' and ' + chapter_obj.index);
downloadFile(chapter_obj.url, function (response) {
that.write(response, that.getChapterFilePath(book_id, chapter_obj.index));
});
}
this.write = function (blob, path) { // should be moved to different object
var sdcard = navigator.getDeviceStorage('sdcard');
var request = sdcard.addNamed(blob, path);
var temp_that;
request.onsuccess = function () {
temp_that = this;
console.log('wrote: ' + this.result);
}
}
this.getBookFilePath = function (book_id) {
console.error('getBookFilePath doesn\'t work yet.');
return 'librifox/' + book_id + '/full.mp3'; // will not work until we set up fullbook unzip.
}
this.getChapterFilePath = function (book_id, chapter_index) {
return 'librifox/' + book_id + '/' + chapter_index + '.mp3';
}
}
function BookPlayerPageGenerator(args) {
var args = args || {};
var dlFullBook = args.selectors.dlFullBook;
var dlChapter = args.selectors.dlChapter;
var audioSource = args.selectors.audioSource;
var bookDownloadManager = args.bookDownloadManager;
this.generatePage = function(page_args) {
var page_args = page_args || {}; // how should null args be handled? Is it better to do this, or to fail loudly?
var book_obj = page_args.book;
var chapter_obj = page_args.chapter;
$(dlFullBook).click(function () {
bookDownloadManager.downloadBook(book_obj); // doesn't work
});
$(dlChapter).click(function(){
bookDownloadManager.downloadChapter(book_obj.id, chapter_obj);
});
$(audioSource).prop("src", chapter_obj.url);
$(audioSource).on("timeupdate", function () {
appUIState.currentChapter.position = this.currentTime;
});
}
}
var bookDownloadManager = new BookDownloadManager({'progressSelector': ".progressBarSlider"});
var bookPlayerPageGenerator = new BookPlayerPageGenerator(
{'selectors':
{
'dlFullBook': '#downloadFullBook',
'dlChapter': '#downloadPart',
'audioSource': '#audioSource',
},
'bookDownloadManager': bookDownloadManager
}
);
$( document ).on( "pagecreate", "#homeBook", function (event) {
navigator.getDeviceStorage("sdcard").addNamed(new Blob(['test file'], {type: 'text/plain'}), 'librifox/test.txt'); // temp
navigator.getDeviceStorage("sdcard").addNamed(new Blob(['test file'], {type: 'text/plain'}), 'librifox/folder1/testalalalalala.txt'); // temp
if (!appUIState.currentBook) { // selectedBook is undefined if you refresh the app from WebIDE on a chapter list page
console.warn("Chapters List: selectedBook was undefined, which freezes the app. Did you refresh from WebIDE?");
return false;
}
bookPlayerPageGenerator.generatePage(
{ book: appUIState.currentBook,
chapter: appUIState.currentChapter}); // is this formatting style better or worse than the regular way?
});
$( document ).on( "pagecreate", "#homeFileManager", function () { // TODO work only in LibriFox directory
var sdcard = navigator.getDeviceStorage('sdcard');
var request = sdcard.enumerate();
request.onsuccess = function () {
if (this.result) {
fileListItem = $('<li><a data-icon="delete">' + this.result.name + '</a></li>');
fileListItem.click(function () {
// play book... change page and set audio src to the file
});
$("#downloadedFiles").append(fileListItem);
this.continue();
}
$("#downloadedFiles").listview('refresh');
};
request.onerror = function () {
$("#noAvailableDownloads").show();
}
});
function SearchResltsPageGenerator(args) {
var args = args || {};
var httpRequestHandler = args.httpRequestHandler;
var selector = args.selector; // #booksList
this.generatePage = function (search_string) {
clearResultsElement();
getSearchJSON(search_string, function (books) {
if (books) {
books.forEach(function(book_entry) {
var book = new Book({'json': book_entry});
bookCache[book.id] = book; // this ends up storing id 3 times (as key, in book object, and in book object json)
bookListItem = $('<li book-id="' + book.id + '"><a href="chapters.html"><h2>'
+ book.title + '</h2><p>' + book.description + '</p></a></li>');
bookListItem.click(function(){
appUIState.setCurrentBookById($(this).attr("book-id"));
});
$(selector).append(bookListItem);
});
$(selector).listview('refresh');
} else {
$(selector).append('<p>No books found, try simplifying your search.<br/>' +
'The LibriVox search API is not very good, so we apologize for the inconvenience.</p>');
}
});
}
function getSearchJSON(search_string, callback_func) {
httpRequestHandler.getJSON(generateBookUrl(search_string), function (xhr) {
callback_func(xhr.response.books);
});
}
function generateBookUrl (search_string) { // this should be private, but I want to test it :(
return "https://librivox.org/api/feed/audiobooks/title/^" + encodeURIComponent(search_string) + "?&format=json";
}
function clearResultsElement() { $(selector).empty(); }
}
var searchResultsPageGenerator =
new SearchResltsPageGenerator({'httpRequestHandler': httpRequestHandler, 'selector':'#booksList'});
$("#newSearch").submit(function(event){
$("#booksList").empty();
var input = $("#bookSearch").val();
searchResultsPageGenerator.generatePage(input);
return false;
});
function HttpRequestHandler() {
var that = this;
this.getDataFromUrl = function (url, type, load_callback, other_args) // NEEDS MORE MAGIC STRINGS
{
other_args = other_args || {};
var xhr = new XMLHttpRequest({ mozSystem: true });
if (xhr.overrideMimeType && type == 'json') {
xhr.overrideMimeType('application/json');
}
other_args.error_callback = other_args.error_callback || function(e) {
console.log("error loading json from url " + url);
console.log(e);
}
other_args.timeout_callback = other_args.timeout_callback || function(e) {
console.log("timeout loading json from url " + url);
console.log(e);
}
xhr.addEventListener('load', function(e) {
load_callback(xhr,e);
});
xhr.addEventListener('error', other_args.error_callback);
xhr.addEventListener('timeout', other_args.timeout_callback);
xhr.addEventListener('progress', other_args.progress_callback);
// xhr.upload.addEventListener("load", transferComplete, false);
// xhr.upload.addEventListener("error", transferFailed, false);
// xhr.upload.addEventListener("abort", transferCanceled, false);
xhr.open('GET', url);
if (type != 'default') { xhr.responseType = type; }
xhr.send();
}
this.getJSON = function (url, load_callback, other_args) { that.getDataFromUrl(url, 'json', load_callback, other_args); };
this.getXML = function (url, load_callback, other_args) { that.getDataFromUrl(url, 'default', load_callback, other_args); };
this.getBlob = function (url, load_callback, other_args) { that.getDataFromUrl(url, 'blob', load_callback, other_args); };
}