Experimenting audioContext for Apple (Safari)
[sgc3.git] / SpeakGoodChinese3_Settings.xml
blob62f4940c260c8cb5a603b019418fb5a94932c6fe
1 <?xml version="1.0" encoding="UTF-8"?>
2 <?xml-stylesheet href="xhtml-default.css" type="text/css" media="screen, aural, print" ?>
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
4     "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
5 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" >
6 <head>
7 <link rel="shortcut icon" href="sgc.png" />
8 <link rel="icon" sizes="192x192" href="sgc.png" />
9 <link rel="manifest" href="manifest.json" />
10 <meta name="viewport" content="width=device-width" />
11 <meta name="mobile-web-app-capable" content="yes" />
12 <meta http-equiv="Content-Language" content="en" />
13 <title lang="en" xml:lang="en" dir="ltr">Settings - SpeakGoodChinese</title>
14 <style>
15 body {
16         background-image: url("Background.png");
17         background-color: rgb(250,250,250);
18     background-repeat: no-repeat;
19     background-position: center center;
20     background-attachment: fixed;}
21 h1 {
22         text-align: center;
23         }
24 button {
25                 position: fixed;
26                 overflow: hidden;
27                 text-overflow: clip clip;
28                 width:17%;
29                 height:15%;
30                 cursor:pointer; /*forces the cursor to change to a hand when the button is hovered*/
31                 text-align:center;
32                 font: bold 3vmin "Helvetica";
33                 background-color: rgb(220,220,220);
34                 
35                 -webkit-box-shadow: inset 0px 1px 0px #3e9cbf, 0px 5px 0px 0px #205c73, 0px 10px 5px #999;
36                 -moz-box-shadow: inset 0px 1px 0px #3e9cbf, 0px 5px 0px 0px #205c73, 0px 10px 5px #999;
37                 box-shadow: inset 0px 1px 0px #3e9cbf, 0px 5px 0px 0px #205c73, 0px 10px 5px #999;
38                 
39                 /*give the corners a small curve*/
40                 -moz-border-radius: 7px;
41                 -webkit-border-radius: 7px;
42                 border-radius: 7px;
43                 }
45 </style>
47 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
49 <script type="text/javascript" src="internationalization_tables.js" ></script>   
50 <script type="text/javascript" src="wordlists.js" ></script>   
51 <script type="text/javascript" src="wordlists_plus.js" ></script>   
52 <script type="text/javascript" src="audioProcessing.js" ></script>   
53 <script type="text/javascript" src="toneprot.js" ></script>   
54 <script type="text/javascript" src="jszip.min.js" defer="true" ></script> 
55 </head>
56 <body onfocus="if(localStorage.sgc3_reload == '1'){location.reload();}else{load_SGC3_settings ()}; " onblur="store_SGC3_settings ();" onunload="store_SGC3_settings (); localStorage.removeItem('sgc3_settingsWindow'); if(creditsWindow)creditsWindow.close(); " >
57 <!--
58 SpeakGoodChinese 3
59 Copyright (C) 2016 R.J.J.H. van Son (r.j.j.h.vanson@gmail.com)
61 This program is free software; you can redistribute it and/or
62 modify it under the terms of the GNU General Public License
63 as published by the Free Software Foundation; either version 2
64 of the License, or (at your option) any later version.
66 This program is distributed in the hope that it will be useful,
67 but WITHOUT ANY WARRANTY; without even the implied warranty of
68 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
69 GNU General Public License for more details.
71 You can find a copy of the GNU General Public License at
72 http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
73 -->
74         <div id='headertitle' style='position: center; font-size: 3vmin; '>
75                 <h2 style='text-align: center' id="settingsTitle">
76                 SpeakGoodChinese 3 <br />
77                 Shuō hǎo Zhōngwén <br />
78                 说好中文
79                 </h2>
80         </div>
81         <div style="position: fixed; color: black; bottom: 70%; left: 5%; height: 8%; width: 17%; font: 'Helvetica'; font-size: 3vmin; text-align: center " id="LanguageCaption">---</div>      
82         <select id="Language" style="position: fixed; color: black; bottom: 65%; left: 5%; height: 8%; width: 17%; font: 'Helvetica'; font-size: 3vmin ; background-color: rgb(220,220,220); " onchange="sgc3_settings.language = change_configLanguage(); store_SGC3_settings (); " >
83                 <option value="---" ><span id="LanguageCaption2" title="" >---</span></option>
84         </select> 
86         <a href="" id="HiddenDownloadLink" name="Download" style="display:none" title="Download" ></a>
87         <div style="position: fixed; color: black; bottom: 70%; left: 25%; height: 8%; width: 17%; font: 'Helvetica'; font-size: 3vmin; text-align: center " id="RegisterCaption">---</div>     
88         <select id="Register" style="position: fixed; color: black; bottom: 65%; left: 25%; height: 8%; width: 17%; font: 'Helvetica'; font-size: 3vmin; background-color: rgb(220,220,220); " onchange="sgc3_settings.register = getRegister(); store_SGC3_settings (); " >
89                 <option value="---" ><span id="RegisterCaption2" title="" >---</span></option>
90         </select> 
92         <div title=""><button type="button" style="color: black; bottom: 65%; left: 45%; height: 8%;" id="LocalSynthesis" ><span id="Synthesis_eSpeak" >Synthesis</span></button></div>
93         
94         <div style="background-color: rgb(220,220,220); position: fixed; color: black; bottom: 54%; left: 25%; height: 8%; width: 17%; font-size: 3vmin; text-align: center; " id="LocalStrict" title="">
95                 <span id="Recognition" style="vertical-align: middle; " >---</span><br />
96                 <span id="StrictPost" style="vertical-align: middle; " >---</span>
97                 <select id="Strict"  style="font-size: 2vmin; vertical-align: middle; background-color: rgb(220,220,220);" onchange="sgc3_settings.strict = getStrict(); store_SGC3_settings (); ">
98                         <option value="0">0</option><option value="1">1</option><option value="2">2</option><option value="3">3</option>
99                 </select>
100         </div>
101         
102         <div title=""><div style="background-color: rgb(220,220,220); position: fixed; color: black; bottom: 54%; left: 45%; height: 8%; width: 17%; font-size: 3vmin; text-align: center; " id="LocalRecordingTime" title="">
103                 <span id="RecordingTimeCaption" style="vertical-align: middle; " >---</span><br />
104                 <select id="RecordingTime"  style="font-size: 2vmin; vertical-align: middle; background-color: rgb(220,220,220);" onchange="sgc3_settings.recSecs = getRecordingTime(); store_SGC3_settings (); ">
105                         <option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="10">10</option>
106                 </select>
107                 <span id="RecordingTimePost" style="vertical-align: middle; " >---</span> 
108         </div></div>
109         
110         <div title=""><button type="button" style="color: black; bottom: 65%; left: 65%; height: 8%" id="ShuffleListsButton" ><span id="ShuffleLists" >Synthesis</span></button></div>
111         <div title=""><button type="button" style="color: black; bottom: 55%; left: 65%; height: 8%" id="DisplayNumbersButton" ><span id="DisplayNumbers" >---</span></button></div>
112         <div title=""><button type="button" style="color: black; bottom: 45%; left: 65%; height: 8%" id="DisplayPinyinButton" ><span id="DisplayPinyin" >---</span></button></div>
113         <div title=""><button type="button" style="color: black; bottom: 35%; left: 65%; height: 8%" id="DisplayCharButton" ><span id="DisplayChar" >---</span></button></div>
114         <div title=""><button type="button" style="color: black; bottom: 25%; left: 65%; height: 8%" id="DisplayTransButton" ><span id="DisplayTrans" >---</span></button></div>
115         
116         <div title=""><button type="button" style="color: black; bottom: 62%; left: 85%; height: 14%; width: 10%" id="CreditsButton" onclick="creditsWindow = window.open('SpeakGoodChinese3_Credits.xml', '_blank');" ><span id="Credits" >---</span></button></div>
118         <input type="file" id="HiddenOpenWordlist" name="OpenWordlist" style="display:none" onchange='openWordlist (this.files); this.value="";' title="OpenWord" />
119         <div title=""><button type="button" style="color: black; bottom: 5%; left: 5%; height: 8%;" id="LocalOpenWordlist" ><span id="OpenWordlist" >---</span></button></div>
120         <div style="position: fixed; color: gray; bottom: 13%; left: 20%; height: 5%; width: 27%; font: 'Helvetica'; font-size: 3vmin; text-align: center " id="CurrentWordlist">---</div>      
121         <div title=""><button type="button" style="color: black; bottom: 5%; left: 25%; height: 8%;" id="DeleteWordlistButton" ><span id="DeleteWordlist" >---</span></button></div>
123         <input type="file" id="HiddenImportAudio" name="ImportAudio" style="display:none" onchange='importAudio (this.files); this.value="";' title="OpenWord" />
124         <div title=""><button type="button" style="color: black; bottom: 5%; right: 43%; height: 8%; width: 8% " id="LocalDeleteAudio" ><span id="DeleteAudio" style="font-size: 2vmin;" >Delete</span></button></div>
125         <div title=""><button type="button" style="color: black; bottom: 5%; right: 33%; height: 8%; width: 8% " id="LocalImportAudio" ><span id="ImportAudio" style="font-size: 2vmin;" >Import</span></button></div>
126         <div title=""><button type="button" style="color: black; bottom: 5%; right: 23%; height: 8%; width: 8% " id="ExportAudioButton" ><span id="ExportAudio" style="font-size: 2vmin;" >Export</span></button></div>
128         <div title=""><button type="button" style="color: black; bottom: 40%; right: 5%; height: 8%; width: 10%; vertical-align: top; " id="ListPerfButton" ><span id="ListPerf" style="font-size: 2vmin;" >---</span></button></div>
129         <div title=""><button type="button" style="color: black; bottom: 25%; right: 5%; height: 14%; width: 10%; vertical-align: top; " id="SaveAudioButton" >
130                 <div style="color: gray; font-size: 6vmin; text-align: center; vertical-align: top; " id="SaveAudioLight">&#9679;</div>
131                 <span id="SaveAudio" >---</span></button>
132         </div>
133         
134         <div style="position: fixed; color: black; bottom: 10%; right: 5%; height: 8%; width: 17%; font: 'Helvetica'; font-size: 4vmin; text-align: center " id="AudioNameCaption">---</div>    
135         <select id="AudioName" style="position: fixed; color: black; bottom: 5%; right: 5%; height: 8%; width: 17%; font: 'Helvetica'; font-size: 3vmin; background-color: rgb(220,220,220); " onchange="changeCollection(this.selectedIndex); store_SGC3_settings (); " >
136                 <option value="NewCollection" ><span id="NewCollection" title="" >New</span></option>
137         </select> 
138         
139     <script type="text/javascript">
140         //<![CDATA[
141         // Install the service worker for offline use
142         if ('serviceWorker' in navigator) {
143           navigator.serviceWorker.register('sw.js').then(function(registration) {
144             // Registration was successful
145             console.log('ServiceWorker registration successful with scope: ',    registration.scope);
146           }).catch(function(err) {
147             // registration failed :(
148             console.log('ServiceWorker registration failed: ', err);
149           });
150         }
151         
152         // DOM parameters
153         var mainWindow = localStorage.sgc3_mainWindow;
154         var creditsWindow;
155         
156         var localSynthesis = document.getElementById('LocalSynthesis');
157         var localRecordingTime = document.getElementById('RecordingTime');
158         var localStrict = document.getElementById('Strict');
160         var shuffleListsButton = document.getElementById('ShuffleListsButton');
161         var displayNumbersButton = document.getElementById('DisplayNumbersButton');
162         var displayPinyinButton = document.getElementById('DisplayPinyinButton');
163         var displayCharButton = document.getElementById('DisplayCharButton');
164         var displayTransButton = document.getElementById('DisplayTransButton');
165         var hiddenDownloadLink = document.getElementById('HiddenDownloadLink');
166         var exportAudioButton = document.getElementById('ExportAudioButton');
167         var saveAudioButton = document.getElementById('SaveAudioButton');
168         var listPerfButton = document.getElementById('ListPerfButton');
169         var openWordlistButton = document.getElementById('LocalOpenWordlist');
170         var openAudioButton = document.getElementById('LocalOpenAudio');
171         var deleteAudioButton = document.getElementById('LocalDeleteAudio');
172         var hiddenImportAudio = document.getElementById('HiddenImportAudio');
173         var importAudioButton = document.getElementById('LocalImportAudio');
174         importAudioButton.onclick = function () {
175             document.getElementById('HiddenImportAudio').click();
176         };
177         // Remove collection after confirm
178         deleteAudioButton.onclick = function () {
179                 var confirmText = config_tables[sgc3_settings.language].DeleteWordlistConfirm[1];
180                 var canBeDeleted = confirm(confirmText);
181                 if (canBeDeleted) {
182                         // Note that the currentCollection is reset AFTER the removal
183                         removeCollection (sgc3_settings.currentCollection, function () {
184                                 localStorage.sgc3_currentCollection = JSON.stringify("SGC3");
185                                 load_SGC3_settings ();
186                         });
187                         sgc3_settings.currentCollection = "SGC3";
188                         load_SGC3_settings ();
189                 }
190         };
191         var hiddenOpenWordlist = document.getElementById('HiddenOpenWordlist');
192         openWordlistButton.onclick = function () {
193             document.getElementById('HiddenOpenWordlist').click();
194         };
195         var deleteWordlistButton = document.getElementById('DeleteWordlistButton');
196         var currentWordlistName = document.getElementById('CurrentWordlist');
197         
198         // Global settings
199         localStorage.removeItem("sgc3_reload");
200         var sgc3_settings = {
201                 settingsRead: false,
202                 recSecs: 3,
203                 ttsSpeed: 110,
204                 ttsVariant: "f3",
205                 shuffleLists: true,
206                 register: 249, // Must match Register_249 Id
207                 wordList: "20 basic tone combinations",
208                 language: "",
209                 synthesis_eSpeak: false,
210                 strict: 1,
211                 personalWordlists: [],
212                 displayNumbers: false,
213                 displayPinyin: true,
214                 displayChar: true,
215                 displayTrans: true,
216                 saveAudio: false,
217                 examplesDatabaseName: "SGC3 examples",
218                 audioDatabaseName: "SGC3 audio",
219                 currentCollection: "SGC3",
220                 selectedLessons: [],
221                 selectedTones: [],
222                 deselectedWords: []
223         };
224         
225         // Store settings
226         function store_SGC3_settings () {
227                 sgc3_settings.settingsRead = true;
229                 for (x in sgc3_settings) {
230                         localStorage["sgc3_"+x] = JSON.stringify(sgc3_settings[x]);
231                 };
232         };
233         
234         function load_SGC3_settings () {
235                 for (x in sgc3_settings) {
236                         // For some reason, parsing the language goes wrong
237                         if (localStorage["sgc3_"+x]) {
238                                 sgc3_settings[x] = JSON.parse(localStorage["sgc3_"+x]);
239                         };
240                 };
241                 
242                 set_configLanguage (sgc3_settings.language);
243                 setRegister (sgc3_settings.register);
244                 setRecordingTime (sgc3_settings.recSecs);
245                 setStrict (sgc3_settings.strict);
247                 localRecordingTime.value = sgc3_settings.recSecs;
248                 localStrict.value = sgc3_settings.strict;
249                 sgc3_settings.settingsRead = true;
250                 wordlists = combineWordlistLists(global_wordlists, sgc3_settings.personalWordlists);
251                 get_wordlist (sgc3_settings.wordList);
252                 currentWordlistName.textContent = sgc3_settings.wordList;
253                 currentWordlistName.style.color = "gray";
254                 document.getElementById('SaveAudioLight').style.color = sgc3_settings.saveAudio ? 'blue' : 'gray';
255                 document.getElementById('SaveAudio').style.color = sgc3_settings.saveAudio ? 'blue' : 'black';
256                 deleteWordlistButton.style.color = "gray";
257                 deleteWordlistButton.disabled = true;
258                 if (wordlistExist (sgc3_settings.personalWordlists, sgc3_settings.wordList)) {
259                         currentWordlistName.style.color = "blue";
260                         deleteWordlistButton.style.color = "black";
261                         deleteWordlistButton.disabled = false;
262                 };
263                 examplesDatabaseName = sgc3_settings.examplesDatabaseName;
264                 audioDatabaseName = sgc3_settings.audioDatabaseName;
265                 add_collections_names_to_select ();
266                 
267                 // Reset the language
268                 if (!sgc3_settings.language) {
269                         sgc3_settings.language = (userLanguage) ? userLanguage : "EN";
270                 };
271         };
272         
273         // Initialize to stored settings
274         load_SGC3_settings ();
275         
276         // Set language (make that selectable)
277         if (!sgc3_settings.language) {
278                 sgc3_settings.language = (userLanguage) ? userLanguage : "EN";
279         };
280         set_configLanguage (sgc3_settings.language);
282         // Wordlists    
283         if (sgc3_settings.shuffleLists) {
284                 shuffleListsButton.style.color = "red";
285                 shuffleListsButton.background = "rgb(256,245,245)";
286         };
287         
288         shuffleListsButton.onclick = function () {
289                 if (! sgc3_settings.shuffleLists) {
290                         sgc3_settings.shuffleLists = true;
291                         shuffleListsButton.style.color = "red";
292                         shuffleListsButton.background = "rgb(232,210,210)";
293                 } else {
294                         sgc3_settings.shuffleLists = false;
295                         shuffleListsButton.style.color = "black";
296                         shuffleListsButton.background = "rgb(220,220,220)";
297                 };
298                 store_SGC3_settings ();
299         };
301         
302         if (sgc3_settings.displayNumbers) {
303                 displayNumbersButton.style.color = "red";
304                 displayNumbersButton.background = "rgb(256,245,245)";
305         };
306         
307         displayNumbersButton.onclick = function () {
308                 if (! sgc3_settings.displayNumbers) {
309                         sgc3_settings.displayNumbers = true;
310                         displayNumbersButton.style.color = "red";
311                         displayNumbersButton.background = "rgb(232,210,210)";
312                 } else {
313                         sgc3_settings.displayNumbers = false;
314                         displayNumbersButton.style.color = "black";
315                         displayNumbersButton.background = "rgb(220,220,220)";
316                 };
317                 store_SGC3_settings ();
318         };
320         if (sgc3_settings.displayPinyin) {
321                 displayPinyinButton.style.color = "red";
322                 displayPinyinButton.background = "rgb(256,245,245)";
323         };
324         
325         displayPinyinButton.onclick = function () {
326                 if (! sgc3_settings.displayPinyin) {
327                         sgc3_settings.displayPinyin = true;
328                         displayPinyinButton.style.color = "red";
329                         displayPinyinButton.background = "rgb(232,210,210)";
330                 } else {
331                         sgc3_settings.displayPinyin = false;
332                         displayPinyinButton.style.color = "black";
333                         displayPinyinButton.background = "rgb(220,220,220)";
334                 };
335                 store_SGC3_settings ();
336         };
338         if (sgc3_settings.displayChar) {
339                 displayCharButton.style.color = "red";
340                 displayCharButton.background = "rgb(256,245,245)";
341         };
342         
343         displayCharButton.onclick = function () {
344                 if (! sgc3_settings.displayChar) {
345                         sgc3_settings.displayChar = true;
346                         displayCharButton.style.color = "red";
347                         displayCharButton.background = "rgb(232,210,210)";
348                 } else {
349                         sgc3_settings.displayChar = false;
350                         displayCharButton.style.color = "black";
351                         displayCharButton.background = "rgb(220,220,220)";
352                 };
353                 store_SGC3_settings ();
354         };
356         if (sgc3_settings.displayTrans) {
357                 displayTransButton.style.color = "red";
358                 displayTransButton.background = "rgb(256,245,245)";
359         };
360         
361         displayTransButton.onclick = function () {
362                 if (! sgc3_settings.displayTrans) {
363                         sgc3_settings.displayTrans = true;
364                         displayTransButton.style.color = "red";
365                         displayTransButton.background = "rgb(232,210,210)";
366                 } else {
367                         sgc3_settings.displayTrans = false;
368                         displayTransButton.style.color = "black";
369                         displayTransButton.background = "rgb(220,220,220)";
370                 };
371                 store_SGC3_settings ();
372         };
374         function openWordlist (files) {
375                 var wordlistFile;
376                 for (var i=0; i < files.length; ++i) {
377                         if (files[i].name.match(/\.(tsv|Table|csv|sgc)$/i)) {
378                                 wordlistFile = files[i];
379                                 if (wordlistFile.name.match(/\.sgc$/ig)) {
380                                         importExamples (wordlistFile);
381                                 } else {
382                                         readWordlist (wordlistFile);
383                                 };
384                         };
385                 };
386         };
387                 
388         deleteWordlistButton.onclick = function () {
389                 var wordlistName = sgc3_settings.wordList;
390                 var canBeDeleted = wordlistExist (sgc3_settings.personalWordlists, wordlistName);
391                 var confirmText = config_tables[sgc3_settings.language].DeleteWordlistConfirm[1];
392                 canBeDeleted = confirm(confirmText);
393                 if (canBeDeleted) {
394                         for(var x = sgc3_settings.personalWordlists.length - 1; x >= 0; --x) {
395                                 if (sgc3_settings.personalWordlists[x][0] == wordlistName) {
396                                         sgc3_settings.personalWordlists.splice(x, 1);
397                                         canBeDeleted = true;
398                                 };
399                         };
400                         
401                         // Remove example audio
402                         removeExamples (sgc3_settings.wordList, function () {});
403                         
404                         // rebuild wordlist;
405                         wordlists = combineWordlistLists(global_wordlists, sgc3_settings.personalWordlists);
406                         wordlistNumber = (wordlistNumber < wordlists.length) ? wordlistNumber : 0;
407                         sgc3_settings.wordList = wordlists[wordlistNumber][0];
408                         localStorage.sgc3_currentWord = JSON.stringify(0);
409                         store_SGC3_settings ();
410                         load_SGC3_settings ();
411                 };
412         };
413         
414         // Set TTS
415         if (sgc3_settings.synthesis_eSpeak) {
416                 localSynthesis.style.color = "red";
417                 localSynthesis.background = "rgb(256,245,245)";
418         };
419         
420         localSynthesis.onclick = function () {
421                 if (!sgc3_settings.synthesis_eSpeak) {
422                         sgc3_settings.synthesis_eSpeak = true;
423                         localSynthesis.style.color = "red";
424                         localSynthesis.background = "rgb(232,210,210)";
425                 } else {
426                         sgc3_settings.synthesis_eSpeak = false;
427                         localSynthesis.style.color = "black";
428                         localSynthesis.background = "rgb(220,220,220)";
429                 };
430                 store_SGC3_settings ();
431         };
432         
433         // Record all Audio
434         saveAudioButton.onclick = function () {
435                 sgc3_settings.saveAudio = ! sgc3_settings.saveAudio;
436                 document.getElementById('SaveAudioLight').style.color = sgc3_settings.saveAudio ? 'blue' : 'gray';
437                 document.getElementById('SaveAudio').style.color = sgc3_settings.saveAudio ? 'blue' : 'black';
438                 store_SGC3_settings ();
439                 
440                 // Get performance data
441                 getCurrentMetaData (sgc3_settings.currentCollection, false);
442         };
443         
444         listPerfButton.onclick = function () {          
445                 // Get performance data
446                 getCurrentMetaData (sgc3_settings.currentCollection, function (list){var performanceRecord = objectList2performanceRecord(list); displayPerformanceRecord(performanceRecord);});
447                 
448         };
449         
450         function displayPerformanceRecord (performanceRecord) {
451                 var page = "";
452                 var date = new Date().toLocaleString();
453                 page += "<h1 align=CENTER>"+config_tables[sgc3_settings.language].ListPerf[0]+": "+sgc3_settings.currentCollection+"</h1>\n";
454                 page += "<h4 align=CENTER>"+date+"</h4>\n";
455                 page += "\n";
456                 if (performanceRecord) {
457                         var wordLists = Object.getOwnPropertyNames(performanceRecord);
458                         for (var i=0; i<wordLists.length; ++i) {
459                                 page += "<CENTER><table CELLPADDING='5'><tr><td colspan='9'><h3>"+wordLists[i]+"</h3></td></tr>\n";
460                                 var table = performanceRecord[wordLists[i]];
461                                 var pinyin = Object.getOwnPropertyNames(table).sort();
462         
463                                 // Write out header line
464                                 page += "<tr>";
465                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Pinyin+"</h4></td>";
466                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Character+"</h4></td>";
467                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Grade+"</h4></td>";
468                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Correct+"</h4></td>";
469                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Wrong+"</h4></td>";
470                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].High+"</h4></td>";
471                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Low+"</h4></td>";
472                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Wide+"</h4></td>";
473                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Narrow+"</h4></td>";
474                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Level+"</h4></td>";
475                                 page += "<td><h4>"+evaluation_tables[sgc3_settings.language].Time+"</h4></td>";
476                                 page += "</tr>";
477                                 // Write body of table
478                                 for (var p=0; p<pinyin.length; ++p){
479                                         page += "<td><strong>"+table[pinyin[p]].Mark+"</strong></td>";
480                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Character+"</td>";
481                                         var grade = table[pinyin[p]].Grade == "-1" ? "-" : table[pinyin[p]].Grade
482                                         page += "<td style='text-align: center' ><em>"+grade+"</em></td>";
483                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Correct+"</td>";
484                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Wrong+"</td>";
485                                         page += "<td style='text-align: center' >"+table[pinyin[p]].High+"</td>";
486                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Low+"</td>";
487                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Wide+"</td>";
488                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Narrow+"</td>";
489                                         page += "<td style='text-align: center' >"+table[pinyin[p]].Proficiency+"</td>";
490                                         page += "<td style='text-align: center; font-size: x-small' >"+table[pinyin[p]].Date+"</td>";
491                                         page += "</tr>\n";
492                                 };
493                                 page += "</table></CENTER>\n";
494                         };
495                         page += "\n&nbsp;\n&nbsp;\n<Center><button type='button' onclick='window.print()' style='font: bold 3vmin \"Helvetica\";background-color: rgb(220,220,220);' >"+evaluation_tables[sgc3_settings.language].Print+"</button></Center>\n\n";
496                 };
497                 var windowObjectReference;
498                 var strWindowFeatures = "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
499                 windowObjectReference = window.open("", "", strWindowFeatures);
500                 windowObjectReference.document.write(page);     
501                 windowObjectReference.document.title = config_tables[sgc3_settings.language].ListPerf[0]+": "+sgc3_settings.currentCollection;
502         };
503         
504         function getTTSvoice(){
505                 var selectedVoice;
506                 if (window.speechSynthesis) {
507                         var voices = window.speechSynthesis.getVoices();
508                         if (! voices) voices = window.speechSynthesis.getVoices();
509                         for (x = 0; x < voices.length; ++x) {
510                                 if (voices[x].lang == "zh-CN") { 
511                                         voiceZH_CN = x;
512                                         selectedVoice = voices[x];
513                                 };
514                         };
515                 }
516                 return selectedVoice;
517         }
518         
519         // Recording time
520         function getRecordingTime () {
521                 var index = document.getElementById("RecordingTime").selectedIndex;
522                 var value = document.getElementById("RecordingTime").options[index].value;
523                 if (index < 0) value = 3;
524                 return value;
525         }
526         
527         function setRecordingTime (value) {
528                 for(var x = 0; x < document.getElementById("RecordingTime").options.length; ++ x) {
529                         if (document.getElementById("RecordingTime").options[x].value == value) {
530                                 document.getElementById("RecordingTime").selectedIndex = x;
531                         };
532                 };
533         };
534         
535         function getStrict () {
536                 var index = document.getElementById("Strict").selectedIndex;
537                 var value = document.getElementById("Strict").options[index].value;
538                 if (index < 1) {
539                         value = 0;
540                         document.getElementById("Strict").selectedIndex = 1;
541                 };
542                 return value;
543         }
544         
545         function setStrict (value) {
546                 if (value < 0 || value > 3) value = 0;
547                 for(var x = 0; x < document.getElementById("Strict").options.length; ++ x) {
548                         if (document.getElementById("Strict").options[x].value == value) {
549                                 document.getElementById("Strict").selectedIndex = x;
550                         };
551                 };
552         };
554         // New Collection
555         function changeCollection(index) {
556                 var selector = document.getElementById('AudioName');
557                 var value = selector.options[index].value;
558                 if (index == 0) {
559                         var promptText = config_tables[sgc3_settings.language]["NewCollection"][1];
560                         var collectionName = prompt(promptText, sgc3_settings.currentCollection);
561                         if (collectionName && collectionName.length > 0) sgc3_settings.currentCollection = collectionName;
562                 } else {
563                         sgc3_settings.currentCollection = value;
564                 };
565                 getCurrentMetaData (sgc3_settings.currentCollection, false);
566         };
567                 
568         // Construct a list of Collections
569         function addCollectionsToSelect (records) {
570                 var collectionList = [];
571                 if(sgc3_settings.currentCollection && sgc3_settings.currentCollection.length > 0) collectionList.push(sgc3_settings.currentCollection);
572                 for (var i = 0; i<records.length; ++ i) {
573                         var collection = records[i].collection;
574                         if (collectionList.indexOf(collection) < 0) collectionList.push(collection);
575                 };
576                 collectionList.sort();
577                 
578                 // First, remove old entries
579                 var selector = document.getElementById('AudioName');
580                 var numOptions = selector.options.length
581                 for(var i = numOptions - 1; i > 0; --i) {
582                         selector.remove(i);
583                 };
584                 // Add new entries
585                 var selectedIndex = 1;
586                 for(var i=0; i < collectionList.length; ++i) {
587                         var lastOption = selector.options.length - 1;
588                         var collectionTitle = collectionList[i];
589                         var newOption = selector.options[0].cloneNode(true);
590                         newOption.value = collectionTitle;
591                         newOption.text = collectionTitle;
592                         selector.add(newOption);
593                         if (collectionTitle == sgc3_settings.currentCollection) selectedIndex = selector.options.length - 1;
594                 };
595                 selector.selectedIndex = selectedIndex;
596         };
597         
598         function add_collections_names_to_select () {
599                 // Read localStorage
600                 sgc3_settings.currentCollection = JSON.parse(localStorage.sgc3_currentCollection);
601                 // Get the names of the collections
602                 getAllRecords (undefined, addCollectionsToSelect);
603         };
604         
605         // Export audio
606         exportAudioButton.onclick = function (){
607                 if (hiddenDownloadLink.href != "") URL.revokeObjectURL(hiddenDownloadLink.href);
608                 getAllRecords (sgc3_settings.currentCollection, zipAudio);
609         };
610         
611         var zipAudio = function (records) {
612                 var zip = new JSZip();
613                 // Create a file listing (also to force the whole path into the zip archive)
614                 var text = "";
615                 for (var i = 0; i<records.length; ++ i) {
616                         text += records[i].collection+"/"+records[i].map+"/"+records[i].name+"\t"+records[i].audio.size+"\t"+records[i].date+"\n";
617                 };
618                 zip.file("README.txt", text, {text: true});
619                 
620                 for(var i=0; i <  records.length; ++i) {
621                         if (records[i].collection == sgc3_settings.currentCollection) {
622                                 var filename = records[i].collection+"/"+records[i].map+"/"+records[i].name;
623                                 var blob = records[i].audio;
624                                 zip.file(filename, blob, {binary: true});
625                         };
626                 };
627                 zip.generateAsync({type:"blob"})
628                 .then(function(content) {
629                 hiddenDownloadLink.download = sgc3_settings.currentCollection+".zip";
630                 hiddenDownloadLink.href = URL.createObjectURL(content);
631                 hiddenDownloadLink.click();
632                 });             
633         };      
634         
635         // Import audio
636         function importAudio (files) {
637                 
638                 var zipReader = new FileReader();
639                 zipReader.onload = function(){
640                         var zip = new JSZip();
641                         zip.loadAsync(zipReader.result).then(unZipAudio);
642                 };
643                 zipReader.onerror = function(err) {
644                         console.log("Open zip file",err);
645                 };
646                 zipReader.readAsArrayBuffer(files[0]);
647         };
648         
649         var unZipAudio = function (zip) {
650                 var fileList = zip.folder("\/$").files;
651                 var defaultCollection = Object.getOwnPropertyNames(fileList).find(function(name){return name.match(/^[^\/]+\/$/g)});
652                 defaultCollection = defaultCollection.replace(/\/$/g, "");
653                 
654                 
655                 // Ask whether to override the collection of the import.
656                 var collectionName = defaultCollection;
657                 
658                 /* Check whether collection name already "exists", ie, is in the list */
659                 var selector = document.getElementById('AudioName');
660                 // Note: The selector option list might not have been updated with the currentCollection yet!
661                 var collectionList = [sgc3_settings.currentCollection];
662                 if(selector && selector.options) {
663                         var numOptions = selector.options.length;
664                         for(var i=0; i < numOptions; ++i) {
665                                 collectionList.push(selector.options[i].text);
666                         };
667                 };
668                 while (collectionList.indexOf(collectionName) >= 0) {
669                         var num;
670                         if(collectionName.match(/\[\d+\]$/)) {
671                                 num = collectionName.match(/\[\d+\]$/)[0];
672                                 num = num.replace(/[^0-9]/ig, "");
673                                 ++num;
674                                 collectionName = collectionName.replace(/ \[([0-9]+)\]$/g, " ["+num+"]");
675                         } else {
676                                 collectionName += " [1]";
677                         };
678                 };
679                 
680                 /*
681                  * This is not accepted here anymore. But moving it out of this microtask is a problem.
682                 var promptText = config_tables[sgc3_settings.language]["NewCollection"][1];
683                 var newCollectionName = prompt(promptText, defaultCollection);
684                 if (newCollectionName && newCollectionName.length > 0 && newCollectionName != defaultCollection) collectionName = newCollectionName;
685                 */
686                 
687                 // Set new current collection
688                 sgc3_settings.currentCollection = collectionName;
689                 localStorage.sgc3_currentCollection = JSON.stringify(sgc3_settings.currentCollection);
690                         
691                 zip.forEach(function (relativePath, file){
692                         if(relativePath.match(/\.(wav|flac|mp3|spx|ogg|tsv)$/ig)) {
693                                 var collection = collectionName;
694                                 var map = "";
695                                 var name = "Audio.wav";
696                                 var path = relativePath.split("/");
697                                 name = path[path.length - 1];
698                                 if (path.length > 2) {
699                                         map = path[path.length - 2];
700                                         origCollection = path[path.length - 3];
701                                 } else if (path.length == 2) {
702                                         origCollection = path[path.length - 2];
703                                 };
704                                 // Change the name of the TSV file
705                                 if (origCollection != collection && name == origCollection+".tsv") name = collection+".tsv";
706                                 
707                                 var type = mimeTypes[name.match(/[^\.]+$/g)[0]];
708                                 zip.file(relativePath).async("arraybuffer").then(function (data) {      
709                                         var blob = new Blob([data], {type : type});
710                                         addAudioBlob(collection, map, name, blob);
711                                 }, function(event){console.log("Error", event)});
712                         };
713                 });
714                 
715                 // Update selection list
716                 add_collections_names_to_select ();
717         };
718         
719         // Import examples
720         function importExamples (file) {
722                 var zipReader = new FileReader();
723                 zipReader.onload = function(){
724                         var zip = new JSZip();
725                         zip.loadAsync(zipReader.result).then(unZipExamples);
726                 };
727                 zipReader.onerror = function(err) {
728                         console.log("Open zip file",err);
729                 };
730                 zipReader.readAsArrayBuffer(file);
731         };
732         
733         var unZipExamples = function (zip) {
734                 var fileList = zip.folder("\/$").files;
735                 var collectionName = Object.getOwnPropertyNames(fileList).find(function(name){return name.match(/^[^\/]+\/$/g)});
736                 collectionName = collectionName.replace(/\/$/g, "");
737                 
738                 zip.forEach(function (relativePath, file){
739                         if(!relativePath.match(/\/\./ig) && relativePath.match(/\.(wav|flac|mp3|spx|ogg|tsv|csv|txt|Table)$/ig)) {
740                                 var wordlist = collectionName;
741                                 var map = "";
742                                 var name = "Audio.wav";
743                                 var path = relativePath.split("/");
744                                 name = path[path.length - 1];
745                                 wordlist = path[path.length - 2];
746                                 var type = mimeTypes[name.match(/[^\.]+$/g)[0]];
747                                 zip.file(relativePath).async("arraybuffer").then(function (data) {      
748                                         var blob = new Blob([data], {type : type});
749                                         var itemName = name.replace(/\.(wav|flac|mp3|spx|ogg)$/ig, "");
750                                         itemName = add_missing_neutral_tones (itemName)
751                                         addExamplesBlob(wordlist, itemName, blob);
752                                         // Read wordlist
753                                         if(name.match(/^wordlist\.(tsv|csv|txt|Table)$/ig)) {
754                                                 wordlistURL = URL.createObjectURL(blob);
755                                                 // Set wordlistname
756                                                 readWordlist (wordlistURL, wordlist+(name.match(/\.[^\.]+$/g)[0]));
757                                         };
758                                 }, function(event){console.log("Error", event)});
759                                 
760                         };
761                 });
763         };
764         
765         // Set Register
766         setRegister (sgc3_settings.register);
767         
768         //]]>   
769         </script>
772 </body>
773 </html>