1
1
// JSON::Presenter
2
2
3
- window . onload = ( ) => {
4
- const createCORSRequest = ( url ) => {
5
- var xhr = new XMLHttpRequest ( ) ;
6
- if ( `withCredentials` in xhr ) {
7
-
8
- // Check if the XMLHttpRequest object has a "withCredentials" property.
9
- // "withCredentials" only exists on XMLHTTPRequest2 objects.
10
- xhr . open ( `GET` , url , true ) ;
11
-
12
- } else if ( typeof XDomainRequest != `undefined` ) {
13
-
14
- // Otherwise, check if XDomainRequest.
15
- // XDomainRequest only exists in IE, and is IE's way of making CORS requests.
16
- xhr = new XDomainRequest ( ) ;
17
- xhr . open ( `GET` , url ) ;
18
-
19
- } else {
20
-
21
- // Otherwise, CORS is not supported by the browser.
22
- xhr = null ;
23
-
24
- }
25
- return xhr ;
26
- } ;
27
-
28
- const container = document . getElementById ( `jp-container` ) ;
29
-
30
- const scriptElement = document . getElementById ( `jp-script` ) ;
31
- const request = createCORSRequest ( scriptElement . innerText ) ;
32
- if ( ! request ) {
33
- throw Error ( `Unable to access the JSON script` ) ;
34
- }
3
+ const JSON_Presenter = ( container , script ) => {
35
4
36
- request . onload = ( ) => {
37
- if ( 200 <= request . status && request . status < 400 ) {
38
- JSON_Presenter . present ( container , request . responseText ) ;
39
- } else {
40
- throw Error ( `Unable to access the JSON script` ) ;
41
- }
42
- } ;
43
-
44
- request . onerror = ( ) => {
45
- throw Error ( `Unable to access the JSON script` ) ;
46
- } ;
47
-
48
- request . send ( ) ;
49
- } ;
50
-
51
- const JSON_Presenter = {
52
-
53
- present : ( container , text ) => {
54
- const containerStyles = [
55
- `border` ,
56
- `background`
57
- ] ;
58
- const defaults = [
59
- `fontFace` ,
60
- `fontWeight` ,
61
- `fontStyle` ,
62
- `textAlign` ,
63
- `fontColor` ,
64
- `blockLeft` ,
65
- `blockTop` ,
66
- `blockWidth` ,
67
- `blockHeight` ,
68
- `blockBackground` ,
69
- `blockPadding`
70
- ] ;
71
-
72
- const script = JSON . parse ( text ) ;
73
- document . title = script . title ;
74
- const height = Math . round ( parseFloat ( container . offsetWidth ) * script . aspectH / script . aspectW ) ;
75
- container . style [ `height` ] = `${ Math . round ( height ) } px` ;
76
- container . style [ `position` ] = `relative` ;
77
- script . element = container ;
78
- for ( const item of containerStyles ) {
79
- JSON_Presenter . doStyle ( container , script . container , item ) ;
80
- }
81
- container . style [ `background-size` ] = `cover` ;
82
- JSON_Presenter . doBlocks ( container , script . blocks , script . defaults ) ;
83
- JSON_Presenter . doStep ( script , 0 ) ;
84
- } ,
5
+ let speed = `normal` ;
6
+ let stepno = - 1 ;
7
+ let step ;
8
+ let mode = `manual` ;
85
9
86
- // Process a style property
87
- doStyle : ( element , spec , property ) => {
88
- if ( typeof spec [ property ] !== 'undefined' ) {
89
- element . style [ property ] = spec [ property ] ;
90
- }
91
- } ,
10
+ const containerStyles = [
11
+ `border` ,
12
+ `background`
13
+ ] ;
14
+ const defaults = [
15
+ `fontFace` ,
16
+ `fontWeight` ,
17
+ `fontStyle` ,
18
+ `textAlign` ,
19
+ `fontColor` ,
20
+ `blockLeft` ,
21
+ `blockTop` ,
22
+ `blockWidth` ,
23
+ `blockHeight` ,
24
+ `blockBackground`
25
+ ] ;
92
26
93
- // Create all the blocks
94
- doBlocks : ( container , blocks , defaults ) => {
27
+ // Initialize all the blocks
28
+ const initBlocks = ( container , blocks , defaults ) => {
95
29
for ( const name in blocks ) {
96
30
const block = blocks [ name ] ;
97
31
const properties = { } ;
@@ -100,315 +34,571 @@ const JSON_Presenter = {
100
34
properties [ name ] = defaults [ name ] ;
101
35
}
102
36
// Override with local values
103
- for ( const name in block . spec ) {
104
- properties [ name ] = block . spec [ name ] ;
37
+ for ( const name in block ) {
38
+ properties [ name ] = block [ name ] ;
105
39
}
106
40
block . properties = properties ;
107
41
block . container = container ;
108
42
}
109
- } ,
43
+ } ;
110
44
111
- // Run a step
112
- doStep : ( script , stepno ) => {
113
- const goto = ( script , stepno ) => {
114
- if ( stepno < script . steps . length ) {
115
- JSON_Presenter . doStep ( script , stepno ) ;
45
+ // Preload all the images
46
+ const preloadImages = ( content ) => {
47
+ for ( const item in content ) {
48
+ if ( item . type == `image` ) {
49
+ item . img = document . createElement ( `div` ) ;
50
+ item . img . style [ `background` ] = `url("${ item . url } ")` ;
116
51
}
117
- } ;
52
+ }
53
+ } ;
118
54
119
- // Create an element.
120
- const createElement = ( block ) => {
121
- const container = block . container ;
122
- w = Math . round ( container . getBoundingClientRect ( ) . width ) ;
123
- h = Math . round ( container . getBoundingClientRect ( ) . height ) ;
124
- const properties = block . properties ;
125
- const element = document . createElement ( `div` ) ;
126
- block . element = element ;
127
- element . style [ `position` ] = `absolute` ;
128
- element . style [ `display` ] = `none` ;
129
- element . style [ `left` ] = properties . blockLeft * w / 1000 ;
130
- element . style [ `top` ] = properties . blockTop * h / 1000 ;
131
- element . style [ `width` ] = `${ properties . blockWidth * w / 1000 } px` ;
132
- element . style [ `height` ] = `${ properties . blockHeight * h / 1000 } px` ;
133
- element . style [ `background` ] = properties . blockBackground ;
134
- element . style [ `border` ] = properties . blockBorder ;
135
- container . appendChild ( element ) ;
136
- const paddingLeft = `${ properties . blockPaddingLeft * w / 1000 } px` ;
137
- const paddingTop = `${ properties . blockPaddingTop * h / 1000 } px` ;
138
- const inner = document . createElement ( `div` ) ;
139
- inner . style [ `position` ] = `absolute` ;
140
- inner . style [ `left` ] = paddingLeft ;
141
- inner . style [ `top` ] = paddingTop ;
142
- inner . style [ `width` ] = `calc(100% - ${ paddingLeft } - ${ paddingLeft } )` ;
143
- element . appendChild ( inner ) ;
144
- element . inner = inner ;
145
- const text = document . createElement ( `div` ) ;
146
- text . style [ `font-face` ] = properties . fontFace ;
147
- text . style [ `font-size` ] = `${ properties . fontSize * h / 1000 } px` ;
148
- text . style [ `font-weight` ] = properties . fontWeight ;
149
- text . style [ `font-style` ] = properties . fontStyle ;
150
- text . style [ `color` ] = properties . fontColor ;
151
- text . style [ `text-align` ] = properties . textAlign ;
152
- inner . appendChild ( text ) ;
153
- inner . text = text ;
154
- } ;
55
+ const doPause = ( ) => {
56
+ setTimeout ( ( ) => {
57
+ doStep ( ) ;
58
+ } , speed === `normal` ? step . duration * 1000 : 0 ) ;
59
+ } ;
155
60
156
- // Set the content of blocks
157
- const doSetContent = ( script , step ) => {
158
- for ( const item of step . blocks ) {
159
- const block = script . blocks [ item . block ] ;
160
- switch ( block . type ) {
161
- case `text` :
162
- let content = script . content [ item . content ] ;
163
- if ( Array . isArray ( content ) ) {
164
- content = content . join ( `<br><br>` ) ;
165
- }
166
- content = content . split ( `\n` ) . join ( `<br>` ) ;
167
- block . element . inner . text . innerHTML = content . split ( `\n` ) . join ( `<br>` ) ;
168
- break ;
169
- case `image` :
61
+ const release = ( ) => {
62
+ document . removeEventListener ( `click` , release ) ;
63
+ document . onkeydown = null ;
64
+ doStep ( ) ;
65
+ } ;
66
+
67
+ const doHold = ( ) => {
68
+ if ( mode === `manual` ) {
69
+ document . addEventListener ( `click` , release ) ;
70
+ document . onkeydown = function ( event ) {
71
+ document . onkeydown = null ;
72
+ switch ( event . code ) {
73
+ case `Space` :
74
+ case `ArrowRight` :
75
+ release ( ) ;
76
+ break ;
77
+ case `ArrowLeft` :
78
+ break ;
79
+ case `Enter` :
80
+ mode = `auto` ;
81
+ doStep ( ) ;
82
+ break ;
83
+ default :
170
84
break ;
171
85
}
172
- }
173
- } ;
174
-
175
- // Create an element
176
- const doCreate = ( script , step ) => {
177
- if ( Array . isArray ( step . blocks ) ) {
178
- for ( const block of step . blocks )
179
- {
180
- createElement ( script . blocks [ block ] ) ;
86
+ return true ;
87
+ } ;
88
+ } else {
89
+ document . onkeydown = function ( event ) {
90
+ document . onkeydown = null ;
91
+ switch ( event . code ) {
92
+ case `Enter` :
93
+ mode = `manual` ;
94
+ break ;
181
95
}
182
- } else {
183
- createElement ( script . blocks [ step . blocks ] ) ;
184
- }
185
- } ;
96
+ return true ;
97
+ } ;
98
+ setTimeout ( ( ) => {
99
+ doStep ( ) ;
100
+ } , speed === `normal` ? step . duration * 1000 : 0 ) ;
101
+ }
102
+ } ;
186
103
187
- // Process a single fade step
188
- const doFadeStep = ( element , steps , n , upDown , onFinish ) => {
189
- if ( upDown ) {
190
- element . style [ `opacity` ] = parseFloat ( n ) / steps ;
191
- } else {
192
- element . style [ `opacity` ] = 1.0 - parseFloat ( n ) / steps ;
193
- }
194
- if ( n < steps ) {
195
- setTimeout ( function ( ) {
196
- doFadeStep ( element , steps , n + 1 , upDown , onFinish ) ;
197
- } , 40 ) ;
198
- } else {
199
- element . style [ `opacity` ] = upDown ? 1.0 : 0.0 ;
200
- if ( ! upDown ) {
201
- element . style [ `display` ] = `none` ;
104
+ // Create a text block.
105
+ const createTextBlock = ( block ) => {
106
+ const container = block . container ;
107
+ if ( block . element ) {
108
+ container . removeChild ( block . element ) ;
109
+ }
110
+ const w = container . getBoundingClientRect ( ) . width / 1000 ;
111
+ const h = container . getBoundingClientRect ( ) . height / 1000 ;
112
+ const properties = block . properties ;
113
+ const element = document . createElement ( `div` ) ;
114
+ block . element = element ;
115
+ element . style [ `position` ] = `absolute` ;
116
+ element . style [ `opacity` ] = `0.0` ;
117
+ element . style [ `left` ] = properties . blockLeft * w ;
118
+ element . style [ `top` ] = properties . blockTop * h ;
119
+ element . style [ `width` ] = `${ properties . blockWidth * w } px` ;
120
+ element . style [ `height` ] = `${ properties . blockHeight * h } px` ;
121
+ element . style [ `background` ] = properties . blockBackground ;
122
+ element . style [ `border` ] = properties . blockBorder ;
123
+ container . appendChild ( element ) ;
124
+ const marginLeft = properties . textMarginLeft * w ;
125
+ const marginTop = properties . textMarginTop * h ;
126
+ const inner = document . createElement ( `div` ) ;
127
+ inner . style [ `position` ] = `absolute` ;
128
+ inner . style [ `left` ] = marginLeft ;
129
+ inner . style [ `top` ] = marginTop ;
130
+ inner . style [ `width` ] = `calc(100% - ${ marginLeft } px - ${ marginLeft } px)` ;
131
+ element . appendChild ( inner ) ;
132
+ element . inner = inner ;
133
+ const text = document . createElement ( `div` ) ;
134
+ text . style [ `font-family` ] = properties . fontFamily ;
135
+ text . style [ `font-size` ] = `${ properties . fontSize * h } px` ;
136
+ text . style [ `font-weight` ] = properties . fontWeight ;
137
+ text . style [ `font-style` ] = properties . fontStyle ;
138
+ text . style [ `color` ] = properties . fontColor ;
139
+ text . style [ `text-align` ] = properties . textAlign ;
140
+ inner . appendChild ( text ) ;
141
+ inner . text = text ;
142
+ } ;
143
+
144
+ // Create an image block.
145
+ const createImageBlock = ( block ) => {
146
+ const container = block . container ;
147
+ if ( block . element ) {
148
+ container . removeChild ( block . element ) ;
149
+ }
150
+ w = container . getBoundingClientRect ( ) . width / 1000 ;
151
+ h = container . getBoundingClientRect ( ) . height / 1000 ;
152
+ const properties = block . properties ;
153
+ const element = document . createElement ( `div` ) ;
154
+ block . element = element ;
155
+ element . style [ `position` ] = `absolute` ;
156
+ element . style [ `opacity` ] = `0.0` ;
157
+ element . style [ `left` ] = properties . blockLeft * w ;
158
+ element . style [ `top` ] = properties . blockTop * h ;
159
+ element . style [ `width` ] = `${ properties . blockWidth * w } px` ;
160
+ element . style [ `height` ] = `${ properties . blockHeight * h } px` ;
161
+ element . style [ `background` ] = properties . blockBackground ;
162
+ element . style [ `border` ] = properties . blockBorder ;
163
+ element . style [ `border-radius` ] = properties . blockBorderRadius ;
164
+ container . appendChild ( element ) ;
165
+ } ;
166
+
167
+ // Set the content of a block
168
+ const setContent = ( spec ) => {
169
+ const block = script . blocks [ spec . block ] ;
170
+ const contentSpec = script . content [ spec . content ] ;
171
+ if ( ! block ) {
172
+ throw Error ( `Block '${ block } ' cannot be found` ) ;
173
+ }
174
+ switch ( contentSpec . type ) {
175
+ case `text` :
176
+ if ( ! block . element ) {
177
+ createTextBlock ( block ) ;
202
178
}
203
- onFinish ( ) ;
179
+ let content = contentSpec . content ;
180
+ if ( Array . isArray ( content ) ) {
181
+ content = content . join ( `<br><br>` ) ;
182
+ }
183
+ block . element . inner . text . innerHTML = content . split ( `\n` ) . join ( `<br>` ) ;
184
+ break ;
185
+ case `image` :
186
+ if ( ! block . element ) {
187
+ createImageBlock ( block ) ;
188
+ }
189
+ block . element . style [ `background` ] = `url("${ contentSpec . url } ")` ;
190
+ block . element . style [ `background-size` ] = `cover` ;
191
+ break ;
192
+ }
193
+ } ;
194
+
195
+ // Set the content of a block
196
+ const doSetContent = ( ) => {
197
+ if ( step . blocks ) {
198
+ for ( const spec of step . blocks )
199
+ {
200
+ setContent ( spec ) ;
204
201
}
205
- } ;
202
+ } else {
203
+ setContent ( step ) ;
204
+ }
205
+ doStep ( ) ;
206
+ } ;
206
207
207
- // Handle a fade up or down
208
- const doFade = ( script , step , stepno , upDown ) => {
209
- const steps = Math . round ( parseFloat ( step . duration ) * 25 ) ;
210
- if ( Array . isArray ( step . blocks ) ) {
211
- let blocks = step . blocks . length ;
212
- for ( const block of step . blocks )
213
- {
214
- const element = script . blocks [ block ] . element ;
215
- element . style [ `opacity` ] = upDown ? 0.0 : 1.0 ;
216
- if ( upDown ) {
217
- element . style [ `display` ] = `block` ;
208
+ // Show or hide a block
209
+ const doShowHide = ( showHide ) => {
210
+ if ( Array . isArray ( step . blocks ) ) {
211
+ for ( const block of step . blocks )
212
+ {
213
+ script . blocks [ block ] . element . style [ `opacity` ] = showHide ? `1.0` : `0.0` ;
214
+ }
215
+ } else {
216
+ script . blocks [ step . blocks ] . element . style [ `opacity` ] = showHide ? `1.0` : `0.0` ;
217
+ }
218
+ doStep ( ) ;
219
+ } ;
220
+
221
+ // Fade up or down
222
+ const doFade = ( upDown ) => {
223
+ const animSteps = Math . round ( step . duration * 25 ) ;
224
+ const stepBlocks = step . blocks ;
225
+ const continueFlag = step . continue ;
226
+ let animStep = 0 ;
227
+ const interval = setInterval ( ( ) => {
228
+ if ( animStep < animSteps ) {
229
+ const ratio = 0.5 - Math . cos ( Math . PI * animStep / animSteps ) / 2 ;
230
+ if ( Array . isArray ( stepBlocks ) ) {
231
+ let blocks = stepBlocks . length ;
232
+ for ( const block of stepBlocks )
233
+ {
234
+ const element = script . blocks [ block ] . element ;
235
+ element . style [ `opacity` ] = upDown ? ratio : 1.0 - ratio ;
236
+ }
237
+ } else {
238
+ const block = script . blocks [ stepBlocks ] ;
239
+ if ( ! block . element ) {
240
+ clearInterval ( interval ) ;
241
+ throw Error ( `I can't fade up a block with no content` ) ;
218
242
}
219
- doFadeStep ( element , steps , 0 , upDown , ( ) => {
220
- blocks -- ;
221
- if ( blocks === 0 && step . wait ) {
222
- goto ( script , stepno + 1 ) ;
223
- }
224
- } ) ;
243
+ block . element . style [ `opacity` ] = upDown ? ratio : 1.0 - ratio ;
225
244
}
245
+ animStep ++ ;
226
246
} else {
227
- const element = script . blocks [ step . blocks ] . element ;
228
- element . style [ `opacity` ] = upDown ? 0.0 : 1.0 ;
229
- if ( upDown ) {
230
- element . style [ `display` ] = `block` ;
247
+ clearInterval ( interval ) ;
248
+ if ( ! continueFlag ) {
249
+ doStep ( ) ;
231
250
}
232
- doFadeStep ( element , steps , 0 , upDown , ( ) => {
233
- if ( step . wait ) {
234
- goto ( script , stepno + 1 ) ;
235
- }
236
- } ) ;
237
251
}
238
- if ( ! step . wait ) {
239
- goto ( script , stepno + 1 ) ;
252
+ } , speed === `normal` ? 40 : 0 ) ;
253
+ if ( continueFlag ) {
254
+ doStep ( ) ;
240
255
}
241
- } ;
256
+ } ;
242
257
243
- // Show or hide an element
244
- const doShowHide = ( script , step , showHide ) => {
245
- if ( Array . isArray ( step . blocks ) ) {
246
- for ( const block of step . blocks )
247
- {
248
- script . blocks [ block ] . element . style [ `display` ] = showHide ? `block` : `none` ;
258
+ // Handle a crossfade
259
+ const doCrossfade = ( ) => {
260
+ const block = script . blocks [ step . block ] ;
261
+ const content = script . content [ step . target ] ;
262
+ const continueFlag = step . continue ;
263
+ let element ;
264
+ let newText ;
265
+ switch ( content . type ) {
266
+ case `text` :
267
+ element = document . createElement ( `div` ) ;
268
+ element . style [ `position` ] = `absolute` ;
269
+ element . style [ `opacity` ] = `0.0` ;
270
+ element . style [ `left` ] = block . element . style [ `left` ] ;
271
+ element . style [ `top` ] = block . element . style [ `top` ] ;
272
+ element . style [ `width` ] = block . element . style [ `width` ] ;
273
+ element . style [ `height` ] = block . element . style [ `height` ] ;
274
+ element . style [ `background` ] = block . element . style [ `background` ]
275
+ element . style [ `border` ] = block . element . style [ `border` ]
276
+ element . style [ `border-radius` ] = block . element . style [ `border-radius` ]
277
+ block . container . appendChild ( element ) ;
278
+ const inner = document . createElement ( `div` ) ;
279
+ inner . style [ `position` ] = `absolute` ;
280
+ inner . style [ `left` ] = block . element . inner . style [ `left` ] ;
281
+ inner . style [ `top` ] = block . element . inner . style [ `top` ] ;
282
+ inner . style [ `width` ] = block . element . inner . style [ `width` ] ;
283
+ element . appendChild ( inner ) ;
284
+ const text = document . createElement ( `div` ) ;
285
+ text . style [ `font-family` ] = block . element . inner . text . style [ `font-family` ] ;
286
+ text . style [ `font-size` ] = block . element . inner . text . style [ `font-size` ] ;
287
+ text . style [ `font-weight` ] = block . element . inner . text . style [ `font-weight` ] ;
288
+ text . style [ `font-style` ] = block . element . inner . text . style [ `font-style` ] ;
289
+ text . style [ `color` ] = block . element . inner . text . style [ `color` ] ;
290
+ text . style [ `text-align` ] = block . element . inner . text . style [ `text-align` ] ;
291
+ inner . appendChild ( text ) ;
292
+ newText = content . content ;
293
+ if ( Array . isArray ( newText ) ) {
294
+ newText = newText . join ( `<br><br>` ) ;
249
295
}
250
- } else {
251
- script . blocks [ step . blocks ] . element . style [ `display` ] = showHide ? `block` : `none` ;
252
- }
253
- } ;
296
+ newText = newText . split ( `\n` ) . join ( `<br>` ) ;
297
+ text . innerHTML = newText ;
298
+ break ;
299
+ case `image` :
300
+ element = document . createElement ( `div` ) ;
301
+ element . style [ `position` ] = `absolute` ;
302
+ element . style [ `opacity` ] = `0.0` ;
303
+ element . style [ `left` ] = block . element . style [ `left` ] ;
304
+ element . style [ `top` ] = block . element . style [ `top` ] ;
305
+ element . style [ `width` ] = block . element . style [ `width` ] ;
306
+ element . style [ `height` ] = block . element . style [ `height` ] ;
307
+ element . style [ `background` ] = block . element . style [ `background` ] ;
308
+ element . style [ `border` ] = block . element . style [ `border` ] ;
309
+ element . style [ `border-radius` ] = block . element . style [ `border-radius` ] ;
310
+ block . container . appendChild ( element ) ;
311
+ element . style [ `background` ] = `url("${ content . url } ")` ;
312
+ element . style [ `background-size` ] = `cover` ;
313
+ break ;
314
+ default :
315
+ throw Error ( `Unknown content type: '${ content . type } '` ) ;
316
+ }
254
317
255
- // Process a single transform step
256
- const doTransformStep = ( block , target , nSteps , n , transform , onFinish ) => {
257
- transform ( block , target , nSteps , n ) ;
258
- if ( n < nSteps ) {
259
- setTimeout ( function ( ) {
260
- doTransformStep ( block , target , nSteps , n + 1 , transform , onFinish ) ;
261
- } , 40 ) ;
318
+ const animSteps = Math . round ( step . duration * 25 ) ;
319
+ let animStep = 0 ;
320
+ const interval = setInterval ( ( ) => {
321
+ if ( animStep < animSteps ) {
322
+ const ratio = 0.5 - Math . cos ( Math . PI * animStep / animSteps ) / 2 ;
323
+ block . element . style [ `opacity` ] = 1.0 - ratio ;
324
+ element . style [ `opacity` ] = ratio ;
325
+ animStep ++ ;
262
326
} else {
263
- onFinish ( ) ;
327
+ clearInterval ( interval ) ;
328
+ switch ( content . type ) {
329
+ case `text` :
330
+ block . element . inner . text . innerHTML = newText ;
331
+ break ;
332
+ case `image` :
333
+ block . element . style [ `background` ] = `url("${ content . url } ")` ;
334
+ block . element . style [ `background-size` ] = `cover` ;
335
+ break ;
336
+ }
337
+ block . element . style [ `opacity` ] = 1.0 ;
338
+ element . parentNode . removeChild ( element ) ;
339
+ if ( ! continueFlag ) {
340
+ doStep ( ) ;
341
+ }
264
342
}
265
- } ;
343
+ } , speed === `normal` ? 80 : 0 ) ;
344
+ if ( continueFlag ) {
345
+ doStep ( ) ;
346
+ }
347
+ } ;
266
348
267
- // Transform block
268
- const transformBlock = ( block , target , nSteps , n ) => {
269
- const boundingRect = script . element . getBoundingClientRect ( ) ;
270
- w = Math . round ( boundingRect . width ) ;
271
- h = Math . round ( boundingRect . height ) ;
272
- const left = block . properties . blockLeft * w / 1000 ;
273
- const top = block . properties . blockTop * h / 1000 ;
274
- const width = block . properties . blockWidth * w / 1000 ;
275
- const height = block . properties . blockHeight * h / 1000 ;
276
- const endLeft = target . properties . blockLeft * w / 1000 ;
277
- const endTop = target . properties . blockTop * h / 1000 ;
278
- const endWidth = target . properties . blockWidth * w / 1000 ;
279
- const endHeight = target . properties . blockHeight * h / 1000 ;
280
- block . element . style [ `left` ] =
281
- left + Math . round ( ( endLeft - left ) * n / nSteps ) ;
282
- block . element . style [ `top` ] =
283
- top + Math . round ( ( endTop - top ) * n / nSteps ) ;
284
- block . element . style [ `width` ] =
285
- `${ width + Math . round ( ( endWidth - width ) * n / nSteps ) } px` ;
286
- block . element . style [ `height` ] =
287
- `${ height + Math . round ( ( endHeight - height ) * n / nSteps ) } px` ;
288
- } ;
349
+ // Compute a block size
350
+ const setComputedBlockSize = ( block , target , ratio ) => {
351
+ const boundingRect = block . container . getBoundingClientRect ( ) ;
352
+ w = Math . round ( boundingRect . width ) ;
353
+ h = Math . round ( boundingRect . height ) ;
354
+ const width = block . properties . blockWidth * w / 1000 ;
355
+ const height = block . properties . blockHeight * h / 1000 ;
356
+ const endWidth = target . properties . blockWidth * w / 1000 ;
357
+ const endHeight = target . properties . blockHeight * h / 1000 ;
358
+ block . element . style [ `width` ] =
359
+ `${ width + ( endWidth - width ) * ratio } px` ;
360
+ block . element . style [ `height` ] =
361
+ `${ height + ( endHeight - height ) * ratio } px` ;
362
+ } ;
289
363
290
- const doTransformBlock = ( script , step , stepno ) => {
291
- const block = script . blocks [ step . block ] ;
292
- const target = script . blocks [ step . target ] ;
293
- const nSteps = Math . round ( parseFloat ( step . duration ) * 25 ) ;
294
- doTransformStep ( block , target , nSteps , 0 , transformBlock , function ( ) {
295
- if ( step . wait ) {
296
- goto ( script , stepno + 1 ) ;
297
- }
298
- } ) ;
299
- if ( ! step . wait ) {
300
- goto ( script , stepno + 1 ) ;
301
- }
302
- } ;
364
+ // Compute a block position
365
+ const setComputedBlockPosition = ( block , target , ratio ) => {
366
+ const boundingRect = block . container . getBoundingClientRect ( ) ;
367
+ w = Math . round ( boundingRect . width ) ;
368
+ h = Math . round ( boundingRect . height ) ;
369
+ const left = block . properties . blockLeft * w / 1000 ;
370
+ const top = block . properties . blockTop * h / 1000 ;
371
+ const endLeft = target . properties . blockLeft * w / 1000 ;
372
+ const endTop = target . properties . blockTop * h / 1000 ;
373
+ block . element . style [ `left` ] =
374
+ left + ( endLeft - left ) * ratio ;
375
+ block . element . style [ `top` ] =
376
+ top + ( endTop - top ) * ratio ;
377
+ } ;
303
378
304
- // Transform font color
305
- const setComputedColor = ( block , target , nSteps , n ) => {
306
- const color = block . spec . fontColor ;
307
- const endColor = target . spec . fontColor ;
308
- const rStart = parseInt ( color . slice ( 1 , 3 ) , 16 ) ;
309
- const gStart = parseInt ( color . slice ( 3 , 5 ) , 16 ) ;
310
- const bStart = parseInt ( color . slice ( 5 , 7 ) , 16 ) ;
311
- const rFinish = parseInt ( endColor . slice ( 1 , 3 ) , 16 ) ;
312
- const gFinish = parseInt ( endColor . slice ( 3 , 5 ) , 16 ) ;
313
- const bFinish = parseInt ( endColor . slice ( 5 , 7 ) , 16 ) ;
314
- const red = rStart + Math . round ( ( rFinish - rStart ) * n / nSteps ) ;
315
- const green = gStart + Math . round ( ( gFinish - gStart ) * n / nSteps ) ;
316
- const blue = bStart + Math . round ( ( bFinish - bStart ) * n / nSteps ) ;
317
- const r = ( "0" + red . toString ( 16 ) ) . slice ( - 2 ) ;
318
- const g = ( "0" + green . toString ( 16 ) ) . slice ( - 2 ) ;
319
- const b = ( "0" + blue . toString ( 16 ) ) . slice ( - 2 ) ;
320
- block . element . inner . text . style [ `color` ] = `#${ r } ${ g } ${ b } ` ;
321
- } ;
379
+ // Compute a font size
380
+ const setComputedFontSize = ( block , target , ratio ) => {
381
+ h = Math . round ( block . container . getBoundingClientRect ( ) . height ) ;
382
+ const size = block . properties . fontSize * h / 1000 ;
383
+ const endSize = target . properties . fontSize * h / 1000 ;
384
+ block . element . inner . text . style [ `font-size` ] =
385
+ `${ size + Math . round ( ( endSize - size ) * ratio ) } px` ;
386
+ } ;
322
387
323
- const doTransformFontColor = ( script , step , stepno ) => {
324
- const block = script . blocks [ step . block ] ;
325
- const target = script . blocks [ step . target ] ;
326
- const nSteps = Math . round ( parseFloat ( step . duration ) * 25 ) ;
327
- doTransformStep ( block , target , nSteps , 0 , setComputedColor , function ( ) {
328
- if ( step . wait ) {
329
- goto ( script , stepno + 1 ) ;
330
- }
331
- } ) ;
332
- if ( ! step . wait ) {
333
- goto ( script , stepno + 1 ) ;
334
- }
335
- } ;
388
+ // Compute a font color
389
+ const setComputedFontColor = ( block , target , ratio ) => {
390
+ const color = block . fontColor ;
391
+ const endColor = target . fontColor ;
392
+ const rStart = parseInt ( color . slice ( 1 , 3 ) , 16 ) ;
393
+ const gStart = parseInt ( color . slice ( 3 , 5 ) , 16 ) ;
394
+ const bStart = parseInt ( color . slice ( 5 , 7 ) , 16 ) ;
395
+ const rFinish = parseInt ( endColor . slice ( 1 , 3 ) , 16 ) ;
396
+ const gFinish = parseInt ( endColor . slice ( 3 , 5 ) , 16 ) ;
397
+ const bFinish = parseInt ( endColor . slice ( 5 , 7 ) , 16 ) ;
398
+ const red = rStart + Math . round ( ( rFinish - rStart ) * ratio ) ;
399
+ const green = gStart + Math . round ( ( gFinish - gStart ) * ratio ) ;
400
+ const blue = bStart + Math . round ( ( bFinish - bStart ) * ratio ) ;
401
+ const r = ( "0" + red . toString ( 16 ) ) . slice ( - 2 ) ;
402
+ const g = ( "0" + green . toString ( 16 ) ) . slice ( - 2 ) ;
403
+ const b = ( "0" + blue . toString ( 16 ) ) . slice ( - 2 ) ;
404
+ block . element . inner . text . style [ `color` ] = `#${ r } ${ g } ${ b } ` ;
405
+ } ;
336
406
337
- // Transform font size
338
- const setComputedFontSize = ( block , target , nSteps , n ) => {
339
- h = Math . round ( script . element . getBoundingClientRect ( ) . height ) ;
340
- const size = block . properties . fontSize * h / 1000 ;
341
- const endSize = target . properties . fontSize * h / 1000 ;
342
- block . element . inner . text . style [ `font-size` ] =
343
- `${ size + Math . round ( ( endSize - size ) * n / nSteps ) } px` ;
344
- } ;
407
+ // Handle a single step of a transition
408
+ const doTransitionStep = ( type , block , target , ratio ) => {
409
+ switch ( type ) {
410
+ case `block size` :
411
+ setComputedBlockSize ( block , target , ratio ) ;
412
+ break ;
413
+ case `block position` :
414
+ setComputedBlockPosition ( block , target , ratio ) ;
415
+ break ;
416
+ case `font size` :
417
+ setComputedFontSize ( block , target , ratio ) ;
418
+ break ;
419
+ case `font color` :
420
+ setComputedFontColor ( block , target , ratio ) ;
421
+ break ;
422
+ default :
423
+ throw Error ( `Unknown transition type: '${ type } '` ) ;
424
+ }
425
+ } ;
345
426
346
- const doTransformFontSize = ( script , step , stepno ) => {
347
- const block = script . blocks [ step . block ] ;
348
- const target = script . blocks [ step . target ] ;
349
- const nSteps = Math . round ( parseFloat ( step . duration ) * 25 ) ;
350
- doTransformStep ( block , target , nSteps , 0 , setComputedFontSize , function ( ) {
351
- if ( step . wait ) {
352
- goto ( script , stepno + 1 ) ;
427
+ // Handle a transition
428
+ const doTransition = ( ) => {
429
+ const animSteps = Math . round ( step . duration * 25 ) ;
430
+ let animStep = 0 ;
431
+ const stepType = step . type ;
432
+ const continueFlag = step . continue ;
433
+ const block = script . blocks [ step . block ] ;
434
+ const target = script . blocks [ step . target ] ;
435
+ const interval = setInterval ( ( ) => {
436
+ if ( animStep < animSteps ) {
437
+ const ratio = 0.5 - Math . cos ( Math . PI * animStep / animSteps ) / 2 ;
438
+ if ( Array . isArray ( stepType ) ) {
439
+ for ( const type of stepType ) {
440
+ doTransitionStep ( type , block , target , ratio ) ;
441
+ }
442
+ } else {
443
+ doTransitionStep ( type , block , target , ratio ) ;
444
+ }
445
+ animStep ++ ;
446
+ } else {
447
+ clearInterval ( interval ) ;
448
+ if ( ! continueFlag ) {
449
+ doStep ( ) ;
353
450
}
354
- } ) ;
355
- if ( ! step . wait ) {
356
- goto ( script , stepno + 1 ) ;
357
451
}
358
- } ;
452
+ } , speed === `normal` ? 40 : 0 ) ;
453
+ if ( continueFlag ) {
454
+ doStep ( ) ;
455
+ }
456
+ } ;
359
457
360
- // Transform an element
361
- const doTransform = ( script , step , stepno ) => {
362
- switch ( step . type ) {
363
- case `block` :
364
- doTransformBlock ( script , step , stepno ) ;
458
+ // Process a single step
459
+ const doStep = ( ) => {
460
+ if ( stepno < script . steps . length ) {
461
+ step = script . steps [ stepno ++ ] ;
462
+ while ( ! step . action ) {
463
+ if ( step . speed ) {
464
+ speed = step . speed ;
465
+ }
466
+ else throw Error ( `Unknown syntax: '${ JSON . stringify ( step , 0 , 2 ) } '` ) ;
467
+ step = script . steps [ stepno ++ ] ;
468
+ }
469
+ if ( step . comment ) {
470
+ console . log ( `Step ${ stepno } : ${ step . comment } ` ) ;
471
+ } else {
472
+ console . log ( `Step ${ stepno } : ${ step . action } ` ) ;
473
+ }
474
+ switch ( step . action ) {
475
+ case `set content` :
476
+ doSetContent ( ) ;
477
+ break ;
478
+ case `show` :
479
+ doShowHide ( true ) ;
480
+ break ;
481
+ case `hide` :
482
+ doShowHide ( false ) ;
365
483
break ;
366
- case `font color ` :
367
- doTransformFontColor ( script , step , stepno ) ;
484
+ case `pause ` :
485
+ doPause ( ) ;
368
486
break ;
369
- case `font size` :
370
- doTransformFontSize ( script , step , stepno ) ;
487
+ case `hold` :
488
+ doHold ( ) ;
489
+ break ;
490
+ case `fade up` :
491
+ doFade ( true ) ;
492
+ break ;
493
+ case `fade down` :
494
+ doFade ( false ) ;
495
+ break ;
496
+ case `crossfade` :
497
+ doCrossfade ( ) ;
498
+ break ;
499
+ case `transition` :
500
+ doTransition ( ) ;
371
501
break ;
372
502
default :
373
- throw Error ( `Unknown transform type : '${ step . type } '` ) ;
503
+ throw Error ( `Unknown action : '${ step . action } '` ) ;
374
504
}
375
- } ;
505
+ }
506
+ else {
507
+ console . log ( `Step ${ stepno } : Finished` ) ;
508
+ }
509
+ } ;
376
510
377
- // Process a single step
378
- const step = script . steps [ stepno ] ;
379
- switch ( step . action ) {
380
- case `set content` :
381
- doSetContent ( script , step ) ;
382
- goto ( script , stepno + 1 ) ;
383
- break ;
384
- case `create` :
385
- doCreate ( script , step ) ;
386
- goto ( script , stepno + 1 ) ;
387
- break ;
388
- case `show` :
389
- doShowHide ( script , step , true ) ;
390
- goto ( script , stepno + 1 ) ;
391
- break ;
392
- case `hide` :
393
- doShowHide ( script , step , false ) ;
394
- goto ( script , stepno + 1 ) ;
395
- break ;
396
- case `hold` :
397
- setTimeout ( function ( ) {
398
- goto ( script , stepno + 1 ) ;
399
- } , step . duration * 1000 ) ;
400
- break ;
401
- case `fade up` :
402
- doFade ( script , step , stepno , true ) ;
403
- break ;
404
- case `fade down` :
405
- doFade ( script , step , stepno , false ) ;
406
- break ;
407
- case `transform` :
408
- doTransform ( script , step , stepno ) ;
511
+ // Initialization
512
+ const init = ( ) => {
513
+ container . innerHTML = `` ;
514
+ document . removeEventListener ( `click` , init ) ;
515
+ document . onkeydown = null ;
516
+ if ( script . title ) {
517
+ document . title = script . title ;
518
+ }
519
+ const height = Math . round ( parseFloat ( container . offsetWidth ) * script . aspectH / script . aspectW ) ;
520
+ container . style [ `height` ] = `${ Math . round ( height ) } px` ;
521
+ container . style [ `position` ] = `relative` ;
522
+ container . style [ `overflow` ] = `hidden` ;
523
+ container . style [ `background-size` ] = `cover` ;
524
+ for ( const property of containerStyles ) {
525
+ if ( typeof script . container [ property ] !== 'undefined' ) {
526
+ container . style [ property ] = script . container [ property ] ;
527
+ }
528
+ }
529
+ initBlocks ( container , script . blocks , script . defaults ) ;
530
+ preloadImages ( script . content ) ;
531
+ stepno = 0 ;
532
+ doStep ( ) ;
533
+ }
534
+
535
+ // Set the default mode
536
+ const modeValue = document . getElementById ( `jp-mode` ) ;
537
+ if ( typeof modeValue !== 'undefined' ) {
538
+ mode = modeValue . innerText ;
539
+ }
540
+ // Wait for a click/tap or a keypress to start
541
+ document . addEventListener ( `click` , init ) ;
542
+ document . onkeydown = function ( event ) {
543
+ document . onkeydown = null ;
544
+ switch ( event . code ) {
545
+ case `Enter` :
546
+ mode = `auto` ;
409
547
break ;
410
548
default :
411
- throw Error ( `Unknown action: '${ step . action } '` ) ;
549
+ mode = `manual` ;
550
+ break ;
412
551
}
552
+ init ( ) ;
553
+ return true ;
554
+ } ;
555
+ } ;
556
+
557
+ window . onload = ( ) => {
558
+ const createCORSRequest = ( url ) => {
559
+ let xhr = new XMLHttpRequest ( ) ;
560
+ if ( `withCredentials` in xhr ) {
561
+
562
+ // Check if the XMLHttpRequest object has a "withCredentials" property.
563
+ // "withCredentials" only exists on XMLHTTPRequest2 objects.
564
+ xhr . open ( `GET` , url , true ) ;
565
+
566
+ } else if ( typeof XDomainRequest != `undefined` ) {
567
+
568
+ // Otherwise, check if XDomainRequest.
569
+ // XDomainRequest only exists in IE, and is IE's way of making CORS requests.
570
+ xhr = new XDomainRequest ( ) ;
571
+ xhr . open ( `GET` , url ) ;
572
+
573
+ } else {
574
+
575
+ // Otherwise, CORS is not supported by the browser.
576
+ xhr = null ;
577
+
578
+ }
579
+ return xhr ;
580
+ } ;
581
+
582
+ const scriptElement = document . getElementById ( `jp-script` ) ;
583
+ if ( scriptElement ) {
584
+ const request = createCORSRequest ( `${ scriptElement . innerText } ?v=${ Math . floor ( Date . now ( ) ) } ` ) ;
585
+ if ( ! request ) {
586
+ throw Error ( `Unable to access the JSON script` ) ;
587
+ }
588
+
589
+ request . onload = ( ) => {
590
+ if ( 200 <= request . status && request . status < 400 ) {
591
+ const script = JSON . parse ( request . responseText ) ;
592
+ JSON_Presenter ( document . getElementById ( `jp-container` ) , script ) ;
593
+ } else {
594
+ throw Error ( `Unable to access the JSON script` ) ;
595
+ }
596
+ } ;
597
+
598
+ request . onerror = ( ) => {
599
+ throw Error ( `Unable to access the JSON script` ) ;
600
+ } ;
601
+
602
+ request . send ( ) ;
413
603
}
414
604
} ;
0 commit comments