0f70f325b941ba78c1fde5af2f2fedeeeaf1f581
[quanweb.git] / app / lib.js
1 //QuanLib:  eBook Library
2 //(C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
3
4 g_state = {
5                 cache: [],
6                 count: 0,
7                 mousePos: {     // Last known position of the mouse cursor
8                         x: undefined,
9                         y: undefined
10                 },
11                 first: 0,
12                 ids: [],
13                 last: (-1),
14                 map: {},        // map from book.Id to index into cache[]
15                 pageSize: 48,
16                 tooltip: {
17                         bookId: undefined,
18                         milliSecs: 500,     // time to wait before displaying tooltip
19                         mousePos: {
20                                 x: undefined,
21                                 y: undefined
22                         },
23                         threshold: 10,          // number of pixels that mouse can move before tip is dismissed
24                         timer: undefined
25                 }
26 }
27
28 document.onmousemove = onMouseMove;
29
30 function adjustPos(setting) {
31         var value = parseInt(setting)
32
33         if (g_state.first === value) {
34                 // No change
35                 return;
36         }
37
38         var maxFirst = Math.max(0, g_state.count - g_state.pageSize);
39
40         if (value < 0) {
41                 g_state.first = 0;
42         } else if (value > maxFirst) {
43                 g_state.first = maxFirst;
44         } else {
45                 g_state.first = value;
46         }
47
48         g_state.last = g_state.first + g_state.pageSize - 1;
49         if (g_state.last >= g_state.count) {
50                 g_state.last = g_state.count - 1;
51         }
52
53         document.getElementById('slider').value = setting;
54
55         refreshData();
56 }
57
58 function bookHtml(book) {
59         var result = '<div class="book">'
60                 +   '<table>'
61                 +     '<tr>'
62                 +       '<td><a href="/book/' + book.Id + '">';
63         if (0 == book.CoverId) {
64                 result +=          '(No cover available)'
65         } else {
66                 result +=          '<img class="cover-thumb" src="/download/' + book.CoverId + '"/>'
67         }
68         result     +=       '</a></td>'
69                 +       '<td onclick="displayDetails(' + book.Id + ');" '
70                 +          ' onmouseover="startTooltipTimer(' + book.Id + ');">'
71                 +         '<p><b>' + book.Title + '</b></p>'
72                 +         '<p>'
73                 +           '<i>' + book.AuthorReading + '</i>';
74         if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) {
75                 result +=          '<br/><i>' + book.SeriesName + ' ' + book.Volume + '</i>';
76         }
77         result     +=         '</p>'
78                 +       '</td>'
79                 +     '</tr>'
80                 +   '</table>'
81                 + '</div>';
82         return result;
83 }
84
85 //ce(s):  "clear if empty()"
86 //return s, unless it's undefined, in which case return an empty ("clear") string
87 function ce(s) {
88         if (typeof s !== 'undefined') {
89                 return s;
90         }
91         return '';
92 }
93
94 function constructSearchUrl() {
95         var url = window.location.protocol + '//' + window.location.host + '/search/';
96
97         var firstTime = true;
98         var terms = ['aut', 'tit', 'ser'];
99
100         for (idx in terms) {
101                 var term = terms[idx];
102                 var elem = document.getElementById(term);
103                 if (null === elem) {
104                         console.log('Error:  could not find form element for search term "' + term + '".');
105                         continue;
106                 }
107
108                 var value = elem.value;
109                 if (value.length > 0) {
110                         if (firstTime) {
111                                 url += '?';
112                                 firstTime = false;
113                         }
114                         else {
115                                 url += '&';
116                         }
117                         url += term + '=' + encodeURIComponent('%' + value + '%');
118                 }
119         }
120
121         return url;
122 }
123
124 // Set the book ID for the details pane, and then show it
125 function displayDetails(bookId) {
126         g_state.tooltip.bookId = bookId;
127         showDetails();
128 }
129
130 function hideDetails() {
131         g_state.tooltip.mousePos.x = undefined;
132         g_state.tooltip.mousePos.y = undefined;
133         
134         var elem = document.getElementById('details');
135         elem.innerHTML = '';
136         elem.style.display = 'none';
137 }
138
139 function onMouseMove(event) {
140         if (typeof event === 'undefined') {
141                 return;
142         }
143         
144         var x = event.pageX;
145         var y = event.pageY;
146         
147         if (  x === g_state.mousePos.x
148            && y === g_state.mousePos.y)
149         {
150                 // No change from previous known position. 
151                 // Nothing to see (or do) here, move along.
152                 return;
153         }
154         
155         // Remember current mouse (x,y) position
156         g_state.mousePos.x = x;
157         g_state.mousePos.y = y;
158
159         // Is there an active tooltip?
160         if (typeof g_state.tooltip.mousePos.x === 'undefined') {
161                 // No active tooltip, so nothing further to do
162                 return;
163         }
164         
165         var deltaX = Math.abs(x - g_state.tooltip.mousePos.x);
166         var deltaY = Math.abs(y - g_state.tooltip.mousePos.y);
167         
168         if (  deltaX > g_state.tooltip.threshold
169            || deltaY > g_state.tooltip.threshold )
170         {
171                 hideDetails();
172         }
173 }
174
175 function onNext() {
176         if (g_state.last < (g_state.count - 1)) {
177                 adjustPos(g_state.first + g_state.pageSize);
178         }
179 }
180
181 function onPrev() {
182         if (g_state.first > 0) {
183                 adjustPos(g_state.first - g_state.pageSize);
184         }
185 }
186
187 function onSlide(value) {
188         adjustPos(value);
189 }
190
191 function onSearch() {
192         var url = constructSearchUrl();
193
194         fetch(url, {method:'GET', cache:'default'})
195         .then(response => response.json())
196         .then((jsonValue) => {
197                 // console.log('JSON response:  ', jsonValue);
198                 g_state.ids = jsonValue
199                 g_state.count = g_state.ids.length;
200                 g_state.first = (-1)
201
202                 var elem = document.getElementById('slider');
203                 elem.max = g_state.count;
204
205                 adjustPos(0);
206         })
207         .catch(err => { 
208                 var msg = 'Error fetching JSON from URL:  ' + url + ': ' + err + ':' + err.stack;
209                 console.log(msg);
210                 report(msg);
211         });
212 }
213
214 function refreshData() {
215         var i;
216         var url = '/info/?ids=';
217         g_state.map = {};
218         for (i = g_state.first; i <= g_state.last; ++i) {
219                 if (i > g_state.first) {
220                         url += ',';
221                 }
222                 var id = g_state.ids[i];
223                 url += id;
224                 g_state.map[id] = i - g_state.first;
225         }
226
227         fetch(url, {method:'GET', cache:'default'})
228         .then(response => response.json())
229         .then((jsonValue) => {
230                 console.log('JSON response for info:  ', jsonValue);
231                 g_state.cache = jsonValue;
232                 refreshLayout();
233         })
234         .catch(err => {
235                 var msg = 'Error fetching book details via URL:  ' + url + ': ' + err;
236                 console.log(msg, err.stack);
237                 report(msg);
238         });
239 }
240
241 function refreshLayout() {
242         var i;
243         var html = '';
244         var limit = g_state.last - g_state.first;
245         for (i = 0; i <= limit; ++i) {
246                 var book = g_state.cache[i];
247                 html += bookHtml(book);
248         }
249
250         document.getElementById('books').innerHTML = html;
251         document.getElementById('first').innerHTML = (g_state.first + 1);
252         document.getElementById('last').innerHTML = (g_state.last + 1);
253         document.getElementById('count').innerHTML = g_state.count;
254 }
255
256 function report(message) {
257         document.getElementById('books').innerHTML = message;
258 }
259
260 function showDetails() {
261         var id = g_state.tooltip.bookId;
262         var elem = document.getElementById('details');
263         var index = g_state.map[id];
264         var book = g_state.cache[index];
265         var html = '<div><p><b>' + book.Title + '</b></p>'
266         + '<p><i>' + ce(book.AuthorReading) + '<br/>' + ce(book.Series) + ' ' + ce(book.Volume) + '</i></p></div><div>'
267         + ce(book.Description)
268         + '</div>';
269
270         // Remember the current mouse (x,y).
271         // If we move the mouse too far from this point, that will trigger hiding the tooltip.
272         g_state.tooltip.mousePos.x = g_state.mousePos.x;
273         g_state.tooltip.mousePos.y = g_state.mousePos.y;
274
275         elem.innerHTML = html;
276
277         elem.style.display = 'block';   // show, and calculate size, so that we can query it below
278         
279         var x = g_state.mousePos.x;
280         var y = g_state.mousePos.y;
281         
282         var bcr = elem.getBoundingClientRect();
283         
284         var width = bcr.width;
285         var height = bcr.height;
286         
287         x = Math.max(x - (width / 2), 0);
288         y = Math.max(y - (height / 2), 0);
289         
290         elem.style.left = x + 'px';
291         elem.style.top = y + 'px';
292 }
293
294 function startTooltipTimer(bookId) {
295         if (typeof g_state.tooltip.timer !== 'undefined') {
296                 clearTimeout(g_state.tooltip.timer);
297         }
298         g_state.tooltip.bookId = bookId;
299         g_state.tooltip.timer = setTimeout(showDetails, g_state.tooltip.milliSecs);
300 }
301
302 function stopTooltipTimer() {
303         if (typeof g_state.tooltip.timer === 'undefined') {
304                 return;
305         }
306         
307         clearTimeout(g_state.tooltip.timer);
308         g_state.tooltip.timer = undefined;
309         hideDetails();
310 }
311