Merge lp:~mttronchetti/novacut/container into lp:novacut

Proposed by Matteo Ronchetti
Status: Merged
Merged at revision: 286
Proposed branch: lp:~mttronchetti/novacut/container
Merge into: lp:novacut
Diff against target: 3825 lines (+3806/-0)
4 files modified
ui/bucket2.css (+640/-0)
ui/bucket2.html (+94/-0)
ui/bucket2.js (+2656/-0)
ui/novacut2.js (+416/-0)
To merge this branch: bzr merge lp:~mttronchetti/novacut/container
Reviewer Review Type Date Requested Status
Novacut Dev Pending
Review via email: mp+119593@code.launchpad.net

Description of the change

My work on the container

it is accessible with novacut-gtk --page=bucket2.html#projectId

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'ui/bucket2.css'
--- ui/bucket2.css 1970-01-01 00:00:00 +0000
+++ ui/bucket2.css 2012-08-14 17:10:21 +0000
@@ -0,0 +1,640 @@
1* {
2 font-family: Ubuntu;
3 line-height: 24px;
4 -webkit-user-select: none;
5}
6
7img {
8 -webkit-user-select: none;
9}
10
11.fgrid_row {
12 white-space: nowrap;
13 font-size: 0;
14 line-height: 0;
15}
16
17.fgrid_row > * {
18 font-size: 15px;
19 display: inline-block;
20 margin: 8px;
21 box-sizing: border-box !important;
22 vertical-align: top;
23}
24
25
26.fgrid_cell > * {
27 display: block;
28 width: 100%;
29 box-sizing: border-box !important;
30}
31
32.fgrid_1 {
33 width: 50px;
34}
35
36.fgrid_2 {
37 width: 116px;
38}
39
40.fgrid_3 {
41 width: 182px;
42}
43
44.fgrid_4 {
45 width: 248px;
46}
47
48.fgrid_5 {
49 width: 314px;
50}
51
52.fgrid_6 {
53 width: 380px;
54}
55
56.fgrid_7 {
57 width: 446px;
58}
59
60.fgrid_8 {
61 width: 512px;
62}
63
64.fgrid_9 {
65 width: 578px;
66}
67
68.fgrid_10 {
69 width: 644px;
70}
71
72.fgrid_11 {
73 width: 710px;
74}
75
76.fgrid_12 {
77 width: 776px;
78}
79
80#flyout .version {
81 text-shadow: 0px 0px 10px #e81f3b;
82}
83
84
85#flyout_capture {
86 position: fixed;
87 z-index: 10;
88 top: 0;
89 right: 0;
90 bottom: 0;
91 left: 0;
92 /*
93 background: rgba(0,0,0,0.5);
94 */
95}
96
97
98#flyout {
99 position: absolute;
100 top: 18px;
101 right: 18px;
102 width: 800px;
103 height: 448px;
104 /*
105 background: -webkit-linear-gradient(top, #333, #2a2a2a);
106 */
107 background-color: rgba(15, 15, 15, 0.9);
108 box-shadow: 0px 0px 10px 0px #e81f3b;
109 border-radius: 12px;
110 font-size: 16px;
111 color: #ddd;
112 box-sizing: border-box !important;
113 padding: 4px;
114}
115
116#flyout a {
117 color: #e81f3b;
118 text-decoration: none;
119}
120
121#flyout a:hover {
122 color: #f82f4b;
123 text-decoration: underline;
124}
125
126#logo {
127 position: fixed;
128 z-index: 11;
129 right: 6px;
130 top: 6px;
131 width: 48px;
132 height: 48px;
133 -webkit-transition: -webkit-transform 300ms linear;
134}
135
136#logo.open {
137 -webkit-transform: rotate(-180deg);
138}
139
140#open_clips {
141 z-index: 4;
142 position: fixed;
143 top: 0;
144 right: 50%;
145 width: 80px;
146 color: #ddd;
147 padding-left: 6px;
148 padding-right: 6px;
149 cursor: default;
150 box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.6);
151 text-align: center;
152 border-radius: 0 0 2px 2px;
153 border:1px solid transparent;
154 border-top:none;
155 border-bottom:1px solid #383838;
156 background: -webkit-linear-gradient(top, #444, #333);
157 text-shadow: 0px -1px 0px rgba(0,0,0,0.5);
158}
159
160#clips_outer {
161 z-index: 3;
162 position: fixed;
163 top: -140px;
164 height: 139px;
165 left: 0;
166 right: 0;
167 overflow: hidden;
168 -webkit-transition: top 250ms ease-in-out;
169 box-shadow: 0px 2px 6px 0px rgba(0,0,0,0.6);
170}
171
172
173#clips_outer.open {
174 top: 0px;
175}
176
177.grid_row {
178 font-size: 0;
179 white-space: nowrap;
180 font-size: 0;
181}
182
183#clips_nav {
184 height: 41px;
185}
186
187#clips_nav label, #clips_nav select {
188 margin: 8px;
189}
190
191
192#clips {
193 width: 100%;
194 overflow-y: hidden;
195 overflow-x: scroll;
196 white-space: nowrap;
197 font-size: 0;
198 line-height: 0;
199 height: 97px;
200
201 /*
202 Dear James, you are looking especially tall today, and tough and handsome.
203 Could you please make this scrollbar less horible looking?
204
205 Love,
206 Jason
207 */
208}
209
210::-webkit-scrollbar{
211 width:5px;
212 height:5px;
213 background:#222;
214}
215
216::-webkit-scrollbar-thumb{
217 border-radius:2px;
218 background:#999;
219}
220
221#clips img {
222 width: 160px;
223 height: 90px;
224 display: inline-block;
225 border: 1px solid #333;
226 -webkit-user-select: none;
227}
228
229#clips img.selected {
230 border-color: #e81f3b;
231}
232
233#bucket {
234 position: fixed;
235 top: 0px;
236 bottom: 246px;
237 left: 0;
238 right: 0;
239 /*
240 background: #333;
241 */
242 background: #301438;
243 overflow: hidden;
244}
245
246.seq-preview {
247 position: fixed;
248 top: 0;
249 left: 0;
250 right: 0;
251 bottom: 246px;
252 z-index: 3;
253 background-color: black;
254}
255
256.seq-preview video {
257 position: absolute;
258 top: 0px;
259 left: 0px;
260 width: 100%;
261 height: 100%;
262}
263
264
265#sequence {
266 position: fixed;
267 height: 221px;
268 bottom: 0;
269 left: 0;
270 right: 0;
271 background-color: #eee;
272
273 overflow: hidden;
274 white-space: nowrap;
275 font-size: 0;
276 line-height: 0;
277
278 padding-top: 25px;
279 background-image: url(grip-short.png);
280 /*
281 background-image: url(grip-short.png), -webkit-linear-gradient(top, #222, #333);
282 */
283 background-repeat: repeat-x;
284}
285
286.slice {
287 border-top: 1px solid #666;
288 border-left: 1px solid #555;
289 border-right: 1px solid #444;
290 border-bottom:1px solid #333;
291}
292
293.slice.selected {
294 border-color: #e81f3b;
295 z-index: 1;
296}
297
298.slice, .slice * {
299 -webkit-user-select: none;
300}
301
302#bucket .slice {
303 position: absolute;
304 box-shadow: 0px 2px 5px 1px rgba(0,0,0,0.5);
305
306 -webkit-transition-timing-function: ease;
307 -webkit-transition-duration: 250ms;
308 -webkit-transition-property: left, top;
309}
310
311#sequence .slice {
312 display: inline-block;
313}
314
315
316.frame {
317 position: relative;
318}
319
320.frame div {
321 position: absolute;
322 top: 0;
323 right: 0;
324 padding: 2px 6px;
325
326 cursor: default;
327 text-align: right;
328 font-family: "Ubuntu Mono";
329 font-size: 14px;
330 line-height: 1.6em;
331 font-weight: bold;
332 color: white;
333 text-shadow: 0 0 2px black;
334 /*
335 -webkit-text-stroke: 1px #000;
336 */
337}
338
339.frame img {
340 margin: 0;
341 padding: 0;
342 border: none;
343 display: block;
344 background-color: white;
345}
346
347/*
348Interesting 16:9 thumbnail sizes we might want to use:
349 112 63
350 128 72
351 144 81
352 160 90
353 176 99
354 192 108
355 208 117
356 224 126
357*/
358
359#bucket img {
360 width: 128px;
361 height: 72px;
362}
363
364#sequence img {
365 width: 192px;
366 height: 108px;
367}
368
369
370.indicator {
371 height: 3px;
372 /*background:-webkit-linear-gradient(top, #111, #222);*/
373 background-color: #333;
374 position: relative;
375 pointer-events: none;
376}
377
378.indicator div {
379 position: absolute;
380 top: 1px;
381 bottom: 1px;
382 left: 0%;
383 right: 0%;
384 background:-webkit-linear-gradient(top, #ddd, #fff);
385}
386
387.indicator div:before{
388 position:absolute;
389 top:-1px;
390 bottom:-1px;
391 left:0px;
392 width:1px;
393 background:white;
394 content:"";
395}
396
397.indicator div:after{
398 position:absolute;
399 top:-1px;
400 bottom:-1px;
401 right:0px;
402 width:1px;
403 background:white;
404 content:"";
405}
406
407
408
409.over {
410 margin-left: 194px;
411}
412
413.over-right {
414 margin-right: 194px;
415}
416
417
418.right {
419 position: relative;
420 left: 194px;
421}
422
423.left {
424 position: relative;
425 left: -194px;
426}
427
428.animated {
429 position: relative;
430 -webkit-transition: left 150ms ease;
431}
432
433.grabbed {
434 -webkit-transition-property: none !important;
435 position: fixed !important;
436}
437
438
439#roughcut {
440 position: fixed;
441 top: 139px;
442 bottom: 0;
443 left: 0;
444 right: 0;
445 background-color: black;
446 z-index: 4;
447}
448
449
450
451#roughcut .frames {
452 position: absolute;
453 left: 0;
454 right: 0;
455 bottom: 30px;
456 font-size: 0px;
457 line-height: 0px;
458}
459
460.videoframe {
461 display: inline-block;
462 width: 50%;
463 position: relative;
464}
465
466
467.videoframe video {
468 pointer-events: none;
469 width: 100%;
470 margin: 0;
471 display: block;
472}
473
474.videoframe div {
475 position: absolute;
476 top: 0;
477 right: 0;
478 padding: 2px 6px;
479
480 cursor: default;
481 text-align: right;
482 font-family: "Ubuntu Mono";
483 font-size: 16px;
484 line-height: 1.6em;
485 font-weight: bold;
486 color: white;
487 text-shadow: 0 0 2px black;
488}
489
490
491
492#roughcut .scrubber {
493 position: absolute;
494 bottom: 0;
495 height: 30px;
496 left: 0;
497 right: 0;
498 background-color: #999;
499}
500
501
502#roughcut .scrubber * {
503 pointer-events: none;
504}
505
506#roughcut .bar {
507 position: absolute;
508 top: 4px;
509 bottom: 4px;
510 background-color: #e81f3b;
511}
512
513#roughcut .playhead {
514 position: absolute;
515 top: 0;
516 bottom: 0;
517 width: 2px;
518 background-color: green;
519 /*
520 -webkit-transition: left 150ms linear;
521 */
522}
523
524#player {
525 position: fixed;
526 z-index: 20;
527 background-color: black;
528 top: 0;
529 bottom: 246px;
530 left: 0;
531 right: 0;
532}
533
534#player video {
535 position: absolute;
536 bottom: 0;
537 width: 100%;
538 height: 100%;
539 margin: 0;
540 pointer-events: none;
541}
542
543#shortcuts{
544 position:fixed;
545 background-color: rgba(15, 15, 15, 0.85);
546text-shadow: 2px 2px 3px rgba(255, 0, 0, 0.5);
547 box-shadow: 0px 0px 10px 0px #e81f3b;
548 border-radius: 12px;
549 font-size: 16px;
550 display:none;
551 z-index:999;
552}
553#shortcuts table{
554 padding: 20px;
555}
556#shortcuts td{
557 padding-top :4px;
558 padding-left: 15px;
559 padding-right: 15px;
560}
561
562.cont{
563border-top: 1px solid rgb(102, 102, 102);
564border-left: 1px solid rgb(85, 85, 85);
565border-right: 1px solid rgb(68, 68, 68);
566border-bottom: 1px solid rgb(51, 51, 51);
567}
568
569#bucket .cont{
570position: absolute;
571min-width:128px;
572height:147px;
573}
574
575.cont ,.cont * {
576-webkit-user-select: none;
577}
578
579.cont{
580/*-webkit-animation-duration: 0.2s;
581-webkit-animation-iteration-count: 1;
582-webkit-animation-timing-function: ease;*/
583background:gray;
584/*position: fixed;*/
585}
586
587
588/*#sequence .cont{
589height: 219px !important;
590}*/
591
592/*#sequence .comp div{
593height: 109px;
594}*/
595
596
597.cont .slice{
598position: relative !important;
599float: left;
600}
601
602#sequence .cont{
603display: inline-block;
604height:219px !important;
605}
606
607.top{
608height: 75px;
609}
610
611.bot{
612height: 72px;
613}
614
615#sequence .top{
616height: 111px;
617}
618
619#sequence .bot{
620height: 108px;
621}
622
623.cont.selected{
624border-color: rgb(232, 31, 59);
625z-index: 1;
626}
627
628@-webkit-keyframes enter{
629from{-webkit-transform: translateX(-200px) translateY(-200px) scale(0.3);}
630to{-webkit-transform: translateX(0px) translateY(0px) scale(1);}
631}
632
633.selector{
634border: 1px solid red;
635position:absolute;
636box-shadow: inset 0px 0px 10px rgba(255,0,0,0.5);
637border-radius: 5px;
638background-color: rgba(255, 0, 0, 0.13);
639z-index: 10;
640}
0641
=== added file 'ui/bucket2.html'
--- ui/bucket2.html 1970-01-01 00:00:00 +0000
+++ ui/bucket2.html 2012-08-14 17:10:21 +0000
@@ -0,0 +1,94 @@
1<!DOCTYPE html>
2<html>
3
4<head>
5 <link rel="stylesheet" href="/_apps/userwebkit/base.css" />
6 <link rel="stylesheet" href="/_apps/dmedia/common.css" />
7 <link rel="stylesheet" href="/_apps/dmedia/grid.css" />
8 <link rel="stylesheet" href="bucket2.css" />
9 <script src="/_apps/userwebkit/couch.js"></script>
10 <script src="/_apps/userwebkit/base.js"></script>
11 <script src="novacut2.js"></script>
12 <script src="bucket2.js"></script>
13</head>
14
15<body>
16<div id="open_clips">Clips</div>
17<div id="clips_outer" class="bar">
18 <div id="clips_nav">
19 <label>Dmedia Project: <select id="dmedia_project"></select></lable>
20 </div>
21 <div id="clips"></div>
22</div>
23
24<div id="flyout_capture" class="hide">
25<div id="flyout">
26 <div class="fgrid_row">
27 <a class="fgrid_4" href="projects.html"><strong>Projects</strong></a>
28 <button class="fgrid_3" id="render-btn" onclick="UI.render()">Render</button>
29 </div>
30 <div class="fgrid_row">
31 <a class="fgrid_4" href="projects.html">(Most recent project)</a>
32 </div>
33 <div class="fgrid_row">
34 <a class="fgrid_4" href="projects.html">(2nd most recent project)</a>
35 </div>
36 <div class="fgrid_row">
37 <a class="fgrid_4" href="projects.html">(3rd most recent project)</a>
38 </div>
39
40 <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
41 <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
42 <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
43 <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
44 <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
45 <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
46
47 <div class="fgrid_row">
48 <strong class="fgrid_4 version">Novacut 12.08.0</strong>
49<a onclick="showShort(event);" style="cursor: pointer; margin-right:135px;">Keyboard Shortcuts</a>
50 <a href="http://www.kickstarter.com/projects/novacut/novacut-pro-video-editor/backers" class="fgrid_5">Thanks to our Kickstarter backers!</a>
51 </div>
52</div>
53</div>
54
55<div id="bucket">
56<div id="shortcuts" >
57<table border="0">
58 <tr>
59 <td>D</td>
60 <td>Duplicate slice</td>
61 </tr>
62 <tr>
63 <td>Del</td>
64 <td>Delete slice</td>
65 </tr>
66 <tr>
67 <td>Space bar</td>
68 <td>Play/Pause</td>
69 </tr>
70 <tr>
71 <td>Esc</td>
72 <td>Close</td>
73 </tr>
74 <tr>
75 <td>Mouse wheel</td>
76 <td>Move of one frame</td>
77 </tr>
78 <tr>
79 <td>Shift+Mouse Wheel</td>
80 <td>Move of ten frames</td>
81 </tr>
82</table>
83</div>
84</div>
85<div id="sequence" clas="lower"></div>
86<div id="roughcut" class="hide">
87 <div class="grid_row">
88 <button class="grid_2" id="close_roughcut">Done</button>
89 <button class="grid_2" id="create_slice">New Slice</button>
90 </div>
91</div>
92</body>
93
94</html>
095
=== added file 'ui/bucket2.js'
--- ui/bucket2.js 1970-01-01 00:00:00 +0000
+++ ui/bucket2.js 2012-08-14 17:10:21 +0000
@@ -0,0 +1,2656 @@
1"use strict";
2
3
4var Thumbs = {
5 db: new couch.Database('thumbnails'),
6
7 docs: {},
8
9 has_frame: function(file_id, index) {
10 if (!Thumbs.docs[file_id]) {
11 try {
12 Thumbs.docs[file_id] = Thumbs.db.get_sync(file_id);
13 }
14 catch (e) {
15 return false;
16 }
17 }
18 if (Thumbs.docs[file_id]._attachments[index]) {
19 return true;
20 }
21 return false;
22 },
23
24 q: {},
25
26 active: {},
27
28 need_init: true,
29
30 init: function() {
31 console.assert(Thumbs.need_init);
32 Thumbs.need_init = false;
33 var ids = Object.keys(Thumbs.q);
34 if (ids.length == 0) {
35 Thumbs.frozen = false;
36 return;
37 }
38 Thumbs.db.post(Thumbs.on_docs, {keys: ids}, '_all_docs', {include_docs: true});
39 },
40
41 on_docs: function(req) {
42 try {
43 var rows = req.read().rows;
44 rows.forEach(function(row) {
45 var id = row.key;
46 if (row.doc) {
47 Thumbs.docs[id] = row.doc;
48 }
49 else {
50 Thumbs.docs[id] = {'_id': id, '_attachments': {}};
51 }
52 });
53 }
54 catch (e) {
55 var ids = Object.keys(Thumbs.q);
56 ids.forEach(function(id) {
57 Thumbs.docs[id] = {'_id': id, '_attachments': {}};
58 });
59 }
60 Thumbs.unfreeze();
61 },
62
63 enqueue: function(frame) {
64 if (!Thumbs.q[frame.file_id]) {
65 Thumbs.q[frame.file_id] = {};
66 }
67 Thumbs.q[frame.file_id][frame.key] = frame;
68 },
69
70 frozen: false,
71
72 freeze: function() {
73 Thumbs.frozen = true;
74 },
75
76 unfreeze: function() {
77 console.log('unfreeze');
78 if (this.need_init) {
79 this.init();
80 return;
81 }
82 Thumbs.frozen = false;
83 Thumbs.flush();
84 },
85
86 flush: function() {
87 if (Thumbs.frozen) {
88 return;
89 }
90 var ids = Object.keys(Thumbs.q);
91 if (ids.length == 0) {
92 console.log('no thumbnails in queue');
93 return;
94 }
95 while (ids.length > 0 && Object.keys(Thumbs.active).length <= 4) {
96 var id = ids.shift();
97 if (Thumbs.active[id]) {
98 console.log('already waiting for ' + id);
99 continue;
100 }
101 var frames = Thumbs.q[id];
102 delete Thumbs.q[id];
103
104 var needed = [];
105 var key, frame;
106 for (key in frames) {
107 frame = frames[key];
108 if (Thumbs.has_frame(id, frame.index)) {
109 frame.request_thumbnail.call(frame);
110 }
111 else {
112 needed.push(frame.index);
113 }
114 }
115 if (needed.length > 0) {
116 Thumbs.active[id] = frames;
117 Hub.send('thumbnail', id, needed);
118 }
119 }
120 },
121
122 on_thumbnail_finished: function(file_id) {
123 if (!Thumbs.active[file_id]) {
124 return;
125 }
126 var frames = Thumbs.active[file_id];
127 delete Thumbs.active[file_id];
128 Thumbs.docs[file_id] = Thumbs.db.get_sync(file_id);
129
130 var key, frame;
131 for (key in frames) {
132 frame = frames[key];
133 if (Thumbs.has_frame(file_id, frame.index)) {
134 frame.request_thumbnail.call(frame);
135 }
136 }
137 Thumbs.flush();
138 },
139}
140
141Hub.connect('thumbnail_finished', Thumbs.on_thumbnail_finished);
142
143
144function $halt(event) {
145 event.preventDefault();
146 event.stopPropagation();
147}
148
149
150function $unparent(id) {
151 var child = $(id);
152 if (child && child.parentNode) {
153 child.parentNode.removeChild(child);
154 }
155 return child;
156}
157
158
159function $position(element) {
160 element = $(element);
161 var pos = {
162 left: element.offsetLeft,
163 top: element.offsetTop,
164 width: element.offsetWidth,
165 height: element.offsetHeight,
166 };
167 while (element.offsetParent) {
168 element = element.offsetParent;
169 pos.left += (element.offsetLeft - element.scrollLeft);
170 pos.top += (element.offsetTop - element.scrollTop);
171 }
172 pos.right = pos.left + pos.width;
173 pos.bottom = pos.top + pos.height;
174 return pos;
175}
176
177
178function $hscroll(child, center) {
179 child = $(child);
180 if (!child.parentNode) {
181 return;
182 }
183 var parent = child.parentNode
184 //var mid = child.offsetLeft + (child.offsetWidth - parent.clientWidth) / 2;
185 if (child.offsetLeft < parent.scrollLeft) {
186 parent.scrollLeft = child.offsetLeft;
187 }
188 else if (child.offsetLeft + child.offsetWidth > parent.scrollLeft + parent.clientWidth) {
189 parent.scrollLeft = child.offsetLeft + child.offsetWidth - parent.clientWidth;
190 }
191}
192
193
194
195var DragEvent = function(event, element) {
196 $halt(event);
197 this.x = event.clientX;
198 this.y = event.clientY;
199 this.ox = this.x;
200 this.oy = this.y;
201 this.dx = 0;
202 this.dy = 0;
203
204 if (element) {
205 var pos = $position(element);
206 this.offsetX = this.x - pos.left;
207 this.offsetY = this.y - pos.top;
208 }
209 else {
210 this.offsetX = event.offsetX;
211 this.offsetY = event.offsetY;
212 }
213
214 this.ondragstart = null;
215 this.ondragcancel = null;
216 this.ondrag = null;
217 this.ondrop = null;
218 this.started = false;
219
220 var self = this;
221 var tmp = {};
222 tmp.on_mousemove = function(event) {
223 self.on_mousemove(event);
224 }
225 tmp.on_mouseup = function(event) {
226 window.removeEventListener('mousemove', tmp.on_mousemove);
227 window.removeEventListener('mouseup', tmp.on_mouseup);
228 self.on_mouseup(event);
229 }
230 window.addEventListener('mousemove', tmp.on_mousemove);
231 window.addEventListener('mouseup', tmp.on_mouseup);
232}
233DragEvent.prototype = {
234 update: function(event) {
235 $halt(event);
236 this.event = event;
237 var html = document.body.parentNode;
238 this.x = Math.max(0, Math.min(event.clientX, html.clientWidth));
239 this.y = Math.max(0, Math.min(event.clientY, html.clientHeight));
240// this.x = event.clientX;
241// this.y = event.clientY;
242 this.dx = this.x - this.ox;
243 this.dy = this.y - this.oy;
244 },
245
246 on_mousemove: function(event) {
247 this.update(event);
248 if (!this.started) {
249 if (Math.max(Math.abs(this.dx), Math.abs(this.dy)) > 3) {
250 this.started = true;
251 if (this.ondragstart) {
252 this.ondragstart(this);
253 }
254 }
255 else {
256 return;
257 }
258 }
259 if (this.ondrag) {
260 this.ondrag(this);
261 }
262 },
263
264 on_mouseup: function(event) {
265 if (!this.started) {
266 if (this.ondragcancel) {
267 this.ondragcancel(this);
268 }
269 return;
270 }
271 if (this.ondrop) {
272 this.update(event);
273 this.ondrop(this);
274 }
275 },
276}
277
278
279var Frame = function(file_id, key) {
280 this.file_id = file_id;
281 this.key = key;
282 this.index = null;
283 this.element = $el('div', {'class': 'frame'});
284 this.img = $el('img');
285 this.element.appendChild(this.img);
286 this.info = $el('div');
287 this.element.appendChild(this.info);
288}
289Frame.prototype = {
290 destroy: function() {
291 $unparent(this.info);
292 $unparent(this.img);
293 $unparent(this.element);
294 delete this.info;
295 delete this.img;
296 delete this.element;
297 },
298
299 set_index: function(index) {
300 if (index === this.index) {
301 return;
302 }
303 this.index = index;
304 this.info.textContent = index + 1;
305 Thumbs.enqueue(this);
306 },
307
308 request_thumbnail: function() {
309 this.img.src = Thumbs.db.att_url(this.file_id, this.index.toString());
310 },
311
312}
313
314
315function SliceIndicator() {
316 this.element = $el('div', {'class': 'indicator'});
317 this.bar = $el('div');
318 this.element.appendChild(this.bar);
319}
320SliceIndicator.prototype = {
321 destroy: function() {
322 $unparent(this.bar);
323 $unparent(this.element);
324 delete this.bar;
325 delete this.element;
326 },
327
328 update: function(start, stop, count) {
329 var left = 100 * start / count;
330 var right = 100 - (100 * stop / count);
331 this.bar.style.left = left.toFixed(1) + '%';
332 this.bar.style.right = right.toFixed(1) + '%';
333 },
334}
335
336
337function wheel_delta(event) {
338 var delta = event.wheelDeltaY;
339 if (delta == 0) {
340 return 0;
341 }
342 var scale = (event.shiftKey) ? -10 : -1;
343 return scale * (delta / Math.abs(delta));
344}
345
346function addCont(){
347 var doc = create_cont();
348 console.log(UI.session.save(doc));
349 console.log(doc);
350 var cont = new Cont(UI.session,doc);
351 UI.bucket.appendChild(cont.element);
352 UI.sequence.do_reorder();
353}
354
355//TODO:HERE
356var Cont = function(session,doc){
357 session.subscribe(doc._id, this.on_change, this);
358 UI.conts.push(this);
359 this.doc = doc;
360 this.id = doc._id;
361 this.session=session;
362 this.element = $el('div', {'class': 'cont','id': doc._id});
363 this.list = $el('ul', {'style': 'display = "none"'});
364 this.comp = $el('div', {'class': 'comp'})
365 this.element.appendChild(this.list);
366 this.element.appendChild(this.comp);
367 this.element.onmousedown = $bind(this.on_mousedown, this);
368 this.element.ondblclick = $bind(this.on_dblclick, this);
369 this.element.onclick = $bind(this.on_click, this);
370 this.expanded = true;
371 this.over = null;
372 this.x = 150;
373 this.y = 150;
374 this.width = 192 + 2;
375 this.threshold = this.width * 0.65;
376 this.on_change(doc);
377 this.close();
378}
379
380Cont.prototype = {
381 set x(value) {
382 if (typeof value == 'number') {
383 this.element.style.left = value + 'px';
384 }
385 else {
386 this.element.style.left = null;
387 }
388 },
389
390 set y(value) {
391 if (typeof value == 'number') {
392 this.element.style.top = value + 'px';
393 }
394 else {
395 this.element.style.top = null;
396 }
397 },
398
399 get x() {
400 if(this.element)return parseInt(this.element.style.left);
401 else return -1;
402 },
403
404 get y() {
405 if(this.element)return parseInt(this.element.style.top);
406 else return -1;
407 },
408
409 get inbucket() {
410 return this.element.parentNode.id == 'bucket';
411 },
412
413 get frombucket() {
414 return this.parent.id == 'bucket';
415 },
416
417 save: function(){
418 this.doc.node.src = Array();
419 for(var a in this.list.childNodes){
420 if(this.list.childNodes[a].id)this.doc.node.src.push(this.list.childNodes[a].id);
421 }
422 this.session.save(this.doc,true);
423 },
424
425 refresh: function(){
426 this.comp.innerHTML = '<div class="top">' + this.list.firstChild.firstChild.firstChild.outerHTML + '</div>';
427 this.comp.innerHTML += '<div class="bot">'+ this.list.lastChild.lastChild.firstChild.outerHTML + '</div>';
428 },
429
430 expand: function(){
431 this.expanded = true;
432 this.list.style.display = 'block';
433 this.comp.style.display = 'none';
434 },
435 close: function(){
436 if(!this.expanded)return;
437 this.expanded = false;
438 console.log("close");
439 this.comp.innerHTML = "";
440 var len = this.list.childNodes.length;
441 console.log(len);
442 if(len){
443 this.refresh();
444 }else{
445 if(len == 1){
446 console.log("save frist child");
447 var el = this.list.childNodes[0];
448 el.style.top = this.y + 'px';
449 el.style.left = this.x + 'px';
450 el.style.marginLeft = '0px';
451 el.style.marginRight = '0px';
452 $unparent(el);
453 UI.bucket.appendChild(el);
454 }
455 UI.bucket.removeChild(this.element);
456 UI.sequence.do_reorder();
457 for(var a in UI.conts)if(UI.conts[a].id == this.id)UI.conts.splice(a,1);
458 return;
459 }
460 this.list.style.display = 'none';
461 this.comp.style.display = 'block';
462
463 },
464 reset_margin: function(){
465 for(var a = 0;a<this.list.childNodes.length;a++){
466 this.list.childNodes[a].style.marginLeft = 0 + 'px';
467 this.list.childNodes[a].style.marginRight = 0 + 'px';
468 }
469 },
470 slice_on: function(x){
471 if(this.list.childNodes.length == 0){
472 this.pos = -1;
473 return;
474 }
475 this.reset_margin();
476 this.pos = parseInt(x/130);
477 if(this.pos >= this.list.childNodes.length) this.list.lastChild.style.marginRight = 130 + 'px';
478 else if(this.list.childNodes[this.pos].classList[1] != 'selected') this.list.childNodes[this.pos].style.marginLeft = 130 + 'px';
479 },
480 on_dblclick: function(event) {
481 this.expand();
482 },
483 on_mousedown: function(event) {
484 UI.select(this.doc._id);
485 if (UI.player.active) {
486 UI.player.hold();
487 }
488 this.pos = $position(this.element);
489 this.dnd = new DragEvent(event, this.element);
490 this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
491 this.dnd.ondragstart = $bind(this.on_dragstart, this);
492 this.dnd.ondrag = $bind(this.on_drag, this);
493 this.dnd.ondrop = $bind(this.on_drop, this);
494 },
495
496 on_dragcancel: function(dnd) {
497 console.log('dragcancel');
498 if (UI.player.active) {
499 UI.player.resume();
500 }
501 this.stop_scrolling();
502 if (this.inbucket && UI.bucket.lastChild != this.element) {
503 console.log('moving to end of bucket');
504 $unparent(this.element);
505 UI.bucket.appendChild(this.element);
506 UI.sequence.do_reorder();
507 }
508 },
509
510 on_dragstart: function(dnd) {
511 console.log('dragstart');
512 this.offsetX = this.dnd.offsetX;
513 this.offsetY = this.dnd.offsetY;
514 this.offsetWidth = this.element.offsetWidth;
515 this.offsetHeight = this.element.offsetHeight;
516
517 this.parent = this.element.parentNode;
518 this.x = dnd.x - this.offsetX;
519 if (this.inbucket) {
520 this.y = dnd.y - this.offsetY;
521 }
522 else {
523 this.nextSibling = this.element.nextSibling;
524 if (this.element.nextSibling) {
525 this.over = this.element.nextSibling;
526 this.over.classList.add('over');
527 }
528 else if (this.element.previousSibling) {
529 this.over = this.element.previousSibling;
530 this.over.classList.add('over-right');
531 }
532 this.target = this.element;
533 var seq = this.element.parentNode;
534 var i, child;
535 for (i=0; i<seq.children.length; i++) {
536 child = seq.children[i];
537 if (child == this.element) {
538 this.i = i;
539 this.orig_i = i;
540 }
541 }
542 this.y = UI.sequence.top - 14;
543 }
544 this.element.classList.add('grabbed');
545 },
546
547 on_drag: function(dnd) {
548 var top = UI.sequence.top;
549 var height = this.element.clientHeight;
550 var y = dnd.y - this.offsetY;
551 var f = 0.65;
552 if (this.inbucket) {
553 if (y > top - height * (1 - f)) {
554 this.move_into_sequence(dnd);
555 }
556 }
557 else {
558 if (y < top - height * f) {
559 this.move_into_bucket(dnd);
560 }
561 }
562 if (this.inbucket) {
563 this.on_mousemove_bucket(dnd);
564 }
565 else {
566 this.on_mousemove_sequence(dnd);
567 }
568 this.on_mousemove_bucket(dnd);
569 },
570 on_click: function(dnd){
571 dnd.stopPropagation();
572 },
573
574 on_drop: function(dnd) {
575 this.stop_scrolling();
576 this.element.classList.remove('grabbed');
577 this.clear_over();
578 UI.sequence.reset();
579 if (this.inbucket) {
580 if (UI.player.active) {
581 UI.player.hide();
582 }
583 if (UI.bucket.lastChild != this.element) {
584 $unparent(this.element);
585 UI.bucket.appendChild(this.element);
586 }
587 var pos = $position(UI.bucket);
588 this.x = Math.max(0, dnd.x - this.offsetX - pos.left);
589 this.y = Math.max(0, dnd.y - this.offsetY - pos.top);
590 }
591 else {
592 console.log(this.orig_i + ' => ' + this.i);
593 this.x = null;
594 this.y = null;
595 var seq = $('sequence');
596 if (this.i == this.orig_i) {
597 console.assert(seq.children[this.i] == this.element);
598 }
599 else {
600 if (this.i < this.orig_i) {
601 var ref = seq.children[this.i];
602 }
603 else {
604 var ref = seq.children[this.i].nextSibling;
605 }
606 $unparent(this.element);
607 seq.insertBefore(this.element, ref);
608 }
609 }
610 UI.sequence.do_reorder();
611 if (UI.player.active) {
612 UI.player.resume();
613 }
614 },
615 on_mousemove_bucket: function(dnd) {
616 this.x = dnd.x - this.offsetX;
617 this.y = dnd.y - this.offsetY;
618 },
619 move_into_bucket: function(dnd) {
620 if (UI.player.active) {
621 UI.player.soft_hide();
622 }
623 this.stop_scrolling();
624 $unparent(this.element);
625 $('bucket').appendChild(this.element);
626 if (this.frombucket) {
627 this.clear_over();
628 UI.sequence.reset();
629 }
630 this.update_offset();
631 },
632 on_mousemove_sequence: function(dnd) {
633 var mid_x = dnd.x - this.offsetX + (this.element.offsetWidth / 2);
634 var width = UI.sequence.element.clientWidth;
635 var left = Math.min(dnd.x, mid_x);
636 var right = Math.max(dnd.x, mid_x);
637
638 if (this.scrolling) {
639 if (left > 0 && right < width) {
640 this.stop_scrolling();
641 }
642 else {
643 return;
644 }
645 }
646 else {
647 if (left <= 0) {
648 this.start_scrolling('left');
649 }
650 else if (right >= width) {
651 this.start_scrolling('right');
652 }
653 }
654 this.do_mousemove_sequence();
655 },
656
657 do_mousemove_sequence: function() {
658 var x = this.dnd.x - this.offsetX;
659 var parent = UI.sequence.element;
660 var scroll_x = x + parent.scrollLeft;
661 var ix = this.i * this.width;
662 var dx = scroll_x - ix;
663
664 this.x = x;
665 this.y = UI.sequence.top - 14;
666
667 if (dx < -this.threshold) {
668 this.shift_right();
669 }
670 else if (dx > this.threshold) {
671 this.shift_left();
672 }
673 },
674 move_into_sequence: function(dnd) {
675 if (UI.player.active) {
676 UI.player.soft_show();
677 }
678 if (!this.frombucket) {
679 this.clear_over();
680 UI.sequence.reset();
681 }
682 var seq = UI.sequence.element;
683 if (seq.children.length == 0) {
684 this.i = 0;
685 this.orig_i = 0;
686 this.target = this.element;
687 seq.appendChild(this.element);
688 this.update_offset();
689 return;
690 }
691
692 var x = this.pos.left + dnd.dx;
693
694 var scroll_x = x + seq.scrollLeft;
695
696 var unclamped = Math.round(scroll_x / this.width);
697 this.i = Math.max(0, Math.min(unclamped, seq.children.length));
698 this.orig_i = this.i;
699 if (this.i == seq.children.length) {
700 this.over = seq.children[this.i - 1];
701 this.over.classList.add('over-right');
702 }
703 else {
704 this.over = seq.children[this.i];
705 this.over.classList.add('over');
706 }
707
708 var ref = seq.children[this.i];
709 $unparent(this.element);
710 seq.insertBefore(this.element, ref);
711 if (!ref) {
712 seq.scrollLeft += this.width;
713 }
714 this.target = this.element;
715 this.update_offset();
716 },
717 update_offset: function() {
718 this.offsetX = Math.round(this.dnd.offsetX * this.element.offsetWidth / this.offsetWidth);
719 this.offsetY = Math.round(this.dnd.offsetY * this.element.offsetHeight / this.offsetHeight);
720 },
721 clear_over: function() {
722 if (this.over) {
723 this.over.classList.remove('over');
724 this.over.classList.remove('over-right');
725 this.over = null;
726 }
727 UI.animate(null);
728 },
729
730 start_scrolling: function(direction) {
731 this.direction = direction;
732 this.scrolling = true;
733 this.interval_id = setInterval($bind(this.on_interval, this), 300);
734 },
735 stop_scrolling: function() {
736 this.scrolling = false;
737 clearInterval(this.interval_id);
738 this.interval_id = null;
739 },
740 on_interval: function() {
741 var d = (this.direction == 'left') ? -1 : 1;
742 UI.sequence.element.scrollLeft += (d * this.width);
743 this.do_mousemove_sequence();
744 },
745 shift_right: function() {
746 if (!this.target.previousSibling) {
747 return;
748 }
749 this.i -= 1;
750 if (this.target.classList.contains('left')) {
751 this.target.classList.remove('left');
752 UI.animate(this.target);
753 }
754 else {
755 this.target.previousSibling.classList.add('right');
756 UI.animate(this.target.previousSibling);
757 }
758 this.target = this.target.previousSibling;
759 },
760
761 shift_left: function() {
762 if (!this.target.nextSibling) {
763 return;
764 }
765 this.i += 1;
766 if (this.target.classList.contains('right')) {
767 this.target.classList.remove('right');
768 UI.animate(this.target);
769 }
770 else {
771 this.target.nextSibling.classList.add('left');
772 UI.animate(this.target.nextSibling);
773 }
774 this.target = this.target.nextSibling;
775
776 },
777 destroy: function() {
778 $unparent(this.element);
779 delete this.element;
780 },
781 on_change: function(doc) {
782 if (doc._deleted) {
783 console.log('deleted ' + doc._id);
784 this.destroy();
785 UI.sequence.do_reorder();
786 return;
787 }
788 this.doc = doc;
789 for(var a in doc.node.src){
790 console.log(doc.node.src[a]);
791 var tdoc = UI.session.get_doc(doc.node.src[a]);
792 var slice = new Slice(UI.session, tdoc);
793 slice.start.request_thumbnail();
794 slice.end.request_thumbnail();
795 $unparent(slice.element);
796 this.list.appendChild(slice.element);
797 }
798 },
799}
800
801var Slice = function(session, doc) {
802 session.subscribe(doc._id, this.on_change, this);
803 this.session = session;
804 this.element = $el('div', {'class': 'slice', 'id': doc._id});
805
806 var file_id = doc.node.src;
807
808 this.start = new Frame(file_id, doc._id + '.start');
809 this.element.appendChild(this.start.element);
810
811 this.indicator = new SliceIndicator();
812 this.element.appendChild(this.indicator.element);
813
814 this.end = new Frame(file_id, doc._id + '.end');
815 this.element.appendChild(this.end.element);
816
817 this.start.element.onmousewheel = $bind(this.on_mousewheel_start, this);
818 this.end.element.onmousewheel = $bind(this.on_mousewheel_end, this);
819 this.element.onmousedown = $bind(this.on_mousedown, this);
820 this.element.ondblclick = $bind(this.on_dblclick, this);
821
822 this.frames = session.get_doc(doc.node.src).duration.frames;
823 this.on_change(doc);
824
825 this.i = null;
826 this.over = null;
827 this.width = 192 + 2;
828 this.threshold = this.width * 0.65;
829 this.timeout_id = null;
830
831 this.oncont = false;
832 this.contn = 0;
833 this.id = doc._id;
834 UI.slices.push(this);
835}
836Slice.prototype = {
837 destroy: function() {
838 this.start.destroy();
839 delete this.start;
840 this.end.destroy();
841 delete this.end;
842 this.indicator.destroy();
843 delete this.indicator;
844 $unparent(this.element);
845 delete this.element;
846 },
847
848 set x(value) {
849 if (typeof value == 'number') {
850 this.element.style.left = value + 'px';
851 }
852 else {
853 this.element.style.left = null;
854 }
855 },
856
857 set y(value) {
858 if (typeof value == 'number') {
859 this.element.style.top = value + 'px';
860 }
861 else {
862 this.element.style.top = null;
863 }
864 },
865
866 get x() {
867 if(this.element)return parseInt(this.element.style.left);
868 else return -1;
869 },
870
871 get y() {
872 return parseInt(this.element.style.top);
873 },
874
875 get inbucket() {
876 return this.element.parentNode.id == 'bucket';
877 },
878
879 get frombucket() {
880 return this.parent.id == 'bucket';
881 },
882
883 on_change: function(doc) {
884 if (doc._deleted) {
885 console.log('deleted ' + doc._id);
886 this.destroy();
887 UI.sequence.do_reorder();
888 return;
889 }
890 this.doc = doc;
891 var node = doc.node;
892 this.start.set_index(node.start.frame);
893 this.end.set_index(node.stop.frame - 1);
894 this.indicator.update(node.start.frame, node.stop.frame, this.frames);
895 Thumbs.flush();
896 },
897
898 reset_adjustment_ux: function() {
899 if (this.timeout_id == null) {
900 UI.player.hold();
901 UI.select(this.doc._id);
902 }
903 clearTimeout(this.timeout_id);
904 this.timeout_id = setTimeout($bind(this.on_timeout, this), 750);
905 },
906
907 on_timeout: function() {
908 console.log('timeout');
909 this.timeout_id = null;
910 UI.player.resume();
911 },
912
913 on_mousewheel_start: function(event) {
914 $halt(event);
915 if (UI.player.active) {
916 this.reset_adjustment_ux();
917 }
918 var delta = wheel_delta(event);
919 var start = this.doc.node.start.frame;
920 var stop = this.doc.node.stop.frame;
921 var proposed = Math.max(0, Math.min(start + delta, stop - 1));
922 if (start != proposed) {
923 this.doc.node.start.frame = proposed;
924 this.session.save(this.doc);
925 this.session.delayed_commit();
926 }
927 },
928
929 on_mousewheel_end: function(event) {
930 $halt(event);
931 if (UI.player.active) {
932 this.reset_adjustment_ux();
933 }
934 var delta = wheel_delta(event);
935 var start = this.doc.node.start.frame;
936 var stop = this.doc.node.stop.frame;
937 var proposed = Math.max(start + 1, Math.min(stop + delta, this.frames));
938 if (stop != proposed) {
939 this.doc.node.stop.frame = proposed;
940 this.session.save(this.doc);
941 this.session.delayed_commit();
942 }
943 },
944
945 on_mousedown: function(event) {
946 UI.select(this.doc._id);
947 if (UI.player.active) {
948 UI.player.hold();
949 }
950 this.pos = $position(this.element);
951 this.dnd = new DragEvent(event, this.element);
952 this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
953 this.dnd.ondragstart = $bind(this.on_dragstart, this);
954 this.dnd.ondrag = $bind(this.on_drag, this);
955 this.dnd.ondrop = $bind(this.on_drop, this);
956 },
957
958 on_dblclick: function(event) {
959 $halt(event);
960 if (UI.player.active) {
961 return;
962 }
963 UI.edit_slice(this.doc);
964 },
965
966 on_dragcancel: function(dnd) {
967 console.log('dragcancel');
968 if (UI.player.active) {
969 UI.player.resume();
970 }
971 this.stop_scrolling();
972 if (this.inbucket && UI.bucket.lastChild != this.element) {
973 console.log('moving to end of bucket');
974 $unparent(this.element);
975 UI.bucket.appendChild(this.element);
976 UI.sequence.do_reorder();
977 }
978 },
979
980 on_dragstart: function(dnd) {
981 console.log('dragstart');
982 UI.dragging = true;
983 this.offsetX = this.dnd.offsetX;
984 this.offsetY = this.dnd.offsetY;
985 this.offsetWidth = this.element.offsetWidth;
986 this.offsetHeight = this.element.offsetHeight;
987
988 this.parent = this.element.parentNode;
989 this.x = dnd.x - this.offsetX;
990 if (this.inbucket) {
991 this.y = dnd.y - this.offsetY;
992 }
993 else {
994 this.nextSibling = this.element.nextSibling;
995 if (this.element.nextSibling) {
996 this.over = this.element.nextSibling;
997 this.over.classList.add('over');
998 }
999 else if (this.element.previousSibling) {
1000 this.over = this.element.previousSibling;
1001 this.over.classList.add('over-right');
1002 }
1003 this.target = this.element;
1004 var seq = this.element.parentNode;
1005 var i, child;
1006 for (i=0; i<seq.children.length; i++) {
1007 child = seq.children[i];
1008 if (child == this.element) {
1009 this.i = i;
1010 this.orig_i = i;
1011 }
1012 }
1013 this.y = UI.sequence.top - 14;
1014 }
1015 this.element.classList.add('grabbed');
1016 },
1017
1018 on_drag: function(dnd) {
1019 var top = UI.sequence.top;
1020 var height = this.element.clientHeight;
1021 var y = dnd.y - this.offsetY;
1022 var x = dnd.x - this.offsetX;
1023 var f = 0.65;
1024 //if(this.oncont)UI.conts[oncontn].reset_margin();
1025 this.oncont = false;
1026 for(var a in UI.conts){
1027 var b = UI.conts[a];
1028 if(b.element && b.x < x && b.y < y && b.x+parseInt(b.element.offsetWidth) > x && b.y+parseInt(b.element.offsetHeight) > y){
1029 b.expand();
1030 this.oncont = true;
1031 this.oncontn = a;
1032 }
1033 else b.close();
1034 }
1035
1036 if(this.oncont) UI.conts[this.oncontn].slice_on(x-UI.conts[this.oncontn].x);
1037
1038 if (this.inbucket) {
1039 if (y > top - height * (1 - f)) {
1040 this.move_into_sequence(dnd);
1041 }
1042 }
1043 else {
1044 if (y < top - height * f) {
1045 this.move_into_bucket(dnd);
1046 }
1047 }
1048 if (this.inbucket) {
1049 this.on_mousemove_bucket(dnd);
1050 }
1051 else {
1052 this.on_mousemove_sequence(dnd);
1053 }
1054 },
1055
1056 move_into_sequence: function(dnd) {
1057 if (UI.player.active) {
1058 UI.player.soft_show();
1059 }
1060 if (!this.frombucket) {
1061 this.clear_over();
1062 UI.sequence.reset();
1063 }
1064 var seq = UI.sequence.element;
1065 if (seq.children.length == 0) {
1066 this.i = 0;
1067 this.orig_i = 0;
1068 this.target = this.element;
1069 seq.appendChild(this.element);
1070 this.update_offset();
1071 return;
1072 }
1073
1074 var x = this.pos.left + dnd.dx;
1075
1076 var scroll_x = x + seq.scrollLeft;
1077
1078 var unclamped = Math.round(scroll_x / this.width);
1079 this.i = Math.max(0, Math.min(unclamped, seq.children.length));
1080 this.orig_i = this.i;
1081 if (this.i == seq.children.length) {
1082 this.over = seq.children[this.i - 1];
1083 this.over.classList.add('over-right');
1084 }
1085 else {
1086 this.over = seq.children[this.i];
1087 this.over.classList.add('over');
1088 }
1089
1090 var ref = seq.children[this.i];
1091 $unparent(this.element);
1092 seq.insertBefore(this.element, ref);
1093 if (!ref) {
1094 seq.scrollLeft += this.width;
1095 }
1096 this.target = this.element;
1097 this.update_offset();
1098 },
1099
1100 move_into_bucket: function(dnd) {
1101 if (UI.player.active) {
1102 UI.player.soft_hide();
1103 }
1104 this.stop_scrolling();
1105 $unparent(this.element);
1106 $('bucket').appendChild(this.element);
1107 if (this.frombucket) {
1108 this.clear_over();
1109 UI.sequence.reset();
1110 }
1111 this.update_offset();
1112 },
1113
1114 update_offset: function() {
1115 this.offsetX = Math.round(this.dnd.offsetX * this.element.offsetWidth / this.offsetWidth);
1116 this.offsetY = Math.round(this.dnd.offsetY * this.element.offsetHeight / this.offsetHeight);
1117 },
1118
1119 on_mousemove_bucket: function(dnd) {
1120 this.x = dnd.x - this.offsetX;
1121 this.y = dnd.y - this.offsetY;
1122 },
1123
1124 start_scrolling: function(direction) {
1125 this.direction = direction;
1126 this.scrolling = true;
1127 this.interval_id = setInterval($bind(this.on_interval, this), 300);
1128 },
1129
1130 stop_scrolling: function() {
1131 this.scrolling = false;
1132 clearInterval(this.interval_id);
1133 this.interval_id = null;
1134 },
1135
1136 on_interval: function() {
1137 var d = (this.direction == 'left') ? -1 : 1;
1138 UI.sequence.element.scrollLeft += (d * this.width);
1139 this.do_mousemove_sequence();
1140 },
1141
1142 on_mousemove_sequence: function(dnd) {
1143 var mid_x = dnd.x - this.offsetX + (this.element.offsetWidth / 2);
1144 var width = UI.sequence.element.clientWidth;
1145 var left = Math.min(dnd.x, mid_x);
1146 var right = Math.max(dnd.x, mid_x);
1147
1148 if (this.scrolling) {
1149 if (left > 0 && right < width) {
1150 this.stop_scrolling();
1151 }
1152 else {
1153 return;
1154 }
1155 }
1156 else {
1157 if (left <= 0) {
1158 this.start_scrolling('left');
1159 }
1160 else if (right >= width) {
1161 this.start_scrolling('right');
1162 }
1163 }
1164 this.do_mousemove_sequence();
1165 },
1166
1167 do_mousemove_sequence: function() {
1168 var x = this.dnd.x - this.offsetX;
1169 var parent = UI.sequence.element;
1170 var scroll_x = x + parent.scrollLeft;
1171 var ix = this.i * this.width;
1172 var dx = scroll_x - ix;
1173
1174 this.x = x;
1175 this.y = UI.sequence.top - 14;
1176
1177 if (dx < -this.threshold) {
1178 this.shift_right();
1179 }
1180 else if (dx > this.threshold) {
1181 this.shift_left();
1182 }
1183 },
1184
1185 shift_right: function() {
1186 if (!this.target.previousSibling) {
1187 return;
1188 }
1189 this.i -= 1;
1190 if (this.target.classList.contains('left')) {
1191 this.target.classList.remove('left');
1192 UI.animate(this.target);
1193 }
1194 else {
1195 this.target.previousSibling.classList.add('right');
1196 UI.animate(this.target.previousSibling);
1197 }
1198 this.target = this.target.previousSibling;
1199 },
1200
1201 shift_left: function() {
1202 if (!this.target.nextSibling) {
1203 return;
1204 }
1205 this.i += 1;
1206 if (this.target.classList.contains('right')) {
1207 this.target.classList.remove('right');
1208 UI.animate(this.target);
1209 }
1210 else {
1211 this.target.nextSibling.classList.add('left');
1212 UI.animate(this.target.nextSibling);
1213 }
1214 this.target = this.target.nextSibling;
1215
1216 },
1217
1218 clear_over: function() {
1219 if (this.over) {
1220 this.over.classList.remove('over');
1221 this.over.classList.remove('over-right');
1222 this.over = null;
1223 }
1224 UI.animate(null);
1225 },
1226
1227 on_drop: function(dnd) {
1228 UI.dragging = false;
1229 this.stop_scrolling();
1230 this.element.classList.remove('grabbed');
1231 this.clear_over();
1232 if(this.oncont){
1233 this.x = null;
1234 this.y = null;
1235 var pos = UI.conts[this.contn].pos;
1236 console.log(pos);
1237 $unparent(this.element);
1238 $unselect(UI.selected);
1239 if(pos == -1) UI.conts[this.contn].list.appendChild(this.element);
1240 else UI.conts[this.contn].list.insertBefore(this.element,UI.conts[this.contn].list.childNodes[pos]);
1241 //UI.conts[this.contn].list.appendChild(this.element);
1242 //UI.conts[this.contn].doc.node.src.push(this.id);
1243 //this.session.save( UI.conts[this.contn].doc,true);
1244 UI.conts[this.contn].reset_margin();
1245 UI.conts[this.contn].save();
1246 UI.sequence.do_reorder();
1247 for(var a in UI.slices)if(UI.slices[a].id == this.id)UI.slices.splice(a,1);
1248 return;
1249 }
1250 UI.sequence.reset();
1251 if (this.inbucket) {
1252 if (UI.player.active) {
1253 UI.player.hide();
1254 }
1255 if (UI.bucket.lastChild != this.element) {
1256 $unparent(this.element);
1257 UI.bucket.appendChild(this.element);
1258 }
1259 var pos = $position(UI.bucket);
1260 this.x = Math.max(0, dnd.x - this.offsetX - pos.left);
1261 this.y = Math.max(0, dnd.y - this.offsetY - pos.top);
1262 }
1263 else {
1264 console.log(this.orig_i + ' => ' + this.i);
1265 this.x = null;
1266 this.y = null;
1267 var seq = $('sequence');
1268 if (this.i == this.orig_i) {
1269 console.assert(seq.children[this.i] == this.element);
1270 }
1271 else {
1272 if (this.i < this.orig_i) {
1273 var ref = seq.children[this.i];
1274 }
1275 else {
1276 var ref = seq.children[this.i].nextSibling;
1277 }
1278 $unparent(this.element);
1279 seq.insertBefore(this.element, ref);
1280 }
1281 }
1282 if(UI.conts[this.contn]) UI.conts[this.contn].save();
1283 UI.sequence.do_reorder();
1284 if (UI.player.active) {
1285 UI.player.resume();
1286 }
1287 },
1288}
1289
1290
1291function $compare(one, two) {
1292 if (! (one instanceof Array && two instanceof Array)) {
1293 return false;
1294 }
1295 if (one.length != two.length) {
1296 return false;
1297 }
1298 var i;
1299 for (i in one) {
1300 if (one[i] != two[i]) {
1301 return false;
1302 }
1303 }
1304 return true;
1305}
1306
1307
1308var Sequence = function(session, doc) {
1309 this.element = $('sequence');
1310 this.bucket = $('bucket');
1311 session.subscribe(doc._id, this.on_change, this);
1312 this.session = session;
1313 this.on_change(doc);
1314
1315 this.element.onmousedown = $bind(this.on_mousedown, this);
1316 this.element.onscroll = $bind(this.on_scroll, this);
1317 this.element.onchildselect = $bind(this.on_childselect, this);
1318}
1319Sequence.prototype = {
1320 get top() {
1321 return this.element.offsetTop + 24;
1322 },
1323
1324 on_childselect: function(id) {
1325 console.log('childselect ' + id);
1326 $hscroll($(id));
1327 },
1328
1329 on_change: function(doc) {
1330 console.log('Sequence.on_change()');
1331 this.doc = doc;
1332
1333 Thumbs.freeze();
1334
1335 var i, _id, child, element;
1336 for (i in doc.node.src) {
1337 _id = doc.node.src[i];
1338 child = this.element.children[i];
1339 if (!child || child.id != _id) {
1340 element = UI.get_slice(_id);
1341 if (element) {
1342 this.element.insertBefore(element, child);
1343 }
1344 }
1345 }
1346
1347 if (! doc.doodle instanceof Array) {
1348 UI.sequence.doc.doodle = [];
1349 }
1350
1351 var obj;
1352 for (i in doc.doodle) {
1353 obj = doc.doodle[i];
1354 child = this.bucket.children[i];
1355 if (!child || child.id != obj.id) {
1356 element = UI.get_slice(obj.id);
1357 if (element) {
1358 this.bucket.insertBefore(element, child);
1359 }
1360 child = element;
1361 }
1362 if (child) {
1363 child.style.left = obj.x + 'px';
1364 child.style.top = obj.y + 'px';
1365 }
1366 }
1367
1368 UI.select(doc.selected);
1369
1370 Thumbs.unfreeze();
1371
1372 console.assert(
1373 $compare(this.doc.node.src, this.get_src())
1374 );
1375 },
1376
1377 on_scroll: function(event) {
1378 this.element.style.setProperty('background-position', -this.element.scrollLeft + 'px 0px');
1379 },
1380
1381 on_mousedown: function(event) {
1382 console.log('sequence mousedown');
1383 this.dnd = new DragEvent(event, this.element);
1384 this.dnd.ondrag = $bind(this.on_drag, this);
1385 this.dnd.scrollLeft = this.element.scrollLeft;
1386 },
1387
1388 on_drag: function(dnd) {
1389 this.element.scrollLeft = dnd.scrollLeft - dnd.dx;
1390 },
1391
1392 get_src: function() {
1393 var i;
1394 var src = [];
1395 for (i=0; i<this.element.children.length; i++) {
1396 src.push(this.element.children[i].id);
1397 }
1398 return src;
1399 },
1400
1401 get_doodle: function() {
1402 var i, child;
1403 var doodle = [];
1404 for (i=0; i<this.bucket.children.length; i++) {
1405 var child = this.bucket.children[i];
1406 doodle.push({
1407 id: child.id,
1408 x: parseInt(child.style.left),
1409 y: parseInt(child.style.top),
1410 });
1411 }
1412 return doodle;
1413 },
1414
1415 do_reorder: function() {
1416 console.log('do_reorder');
1417 var src = this.get_src();
1418 var doodle = this.get_doodle();
1419 this.doc.node.src = src;
1420 this.doc.doodle = doodle;
1421 this.session.save(this.doc, true); // no_emit=true
1422 this.session.commit();
1423 },
1424
1425 reset: function() {
1426 console.log('Sequence.reset()');
1427 var i, child;
1428 for (i=0; i<this.element.children.length; i++) {
1429 child = this.element.children[i];
1430 if (!child.classList.contains('grabbed')) {
1431 child.classList.remove('left');
1432 child.classList.remove('right');
1433 }
1434 }
1435 },
1436}
1437
1438var VideoFrame = function(which) {
1439 this.element = $el('div', {'class': 'videoframe ' + which});
1440 this.video = $el('video');
1441 this.element.appendChild(this.video);
1442 this.info = $el('div');
1443 this.element.appendChild(this.info);
1444 this.ready = false;
1445 this.pending = null;
1446 this.video.addEventListener('canplaythrough',
1447 $bind(this.on_canplaythrough, this)
1448 );
1449 this.video.addEventListener('seeked',
1450 $bind(this.on_seeked, this)
1451 );
1452}
1453VideoFrame.prototype = {
1454 set_index: function(index) {
1455 this.info.textContent = index + 1;
1456 this.seek(index);
1457 },
1458
1459 on_canplaythrough: function(event) {
1460 this.ready = true;
1461 this.do_seek();
1462 },
1463
1464 on_seeked: function(event) {
1465 if (this.pending != null) {
1466 this.do_seek();
1467 }
1468 },
1469
1470 set_clip: function(clip) {
1471 this.ready = false;
1472 this.framerate = clip.framerate;
1473 this.frames = clip.duration.frames;
1474 this.pending = null;
1475 this.video.src = 'dmedia:' + clip._id;
1476 },
1477
1478 play: function() {
1479 this.video.play();
1480 },
1481
1482 pause: function() {
1483 this.video.pause();
1484 },
1485
1486 seek: function(index) {
1487 this.pending = frame_to_seconds(index, this.framerate);
1488 if (this.ready && ! this.video.seeking) {
1489 this.do_seek();
1490 }
1491 },
1492
1493 do_seek: function() {
1494 var t = this.pending;
1495 this.pending = null;
1496 this.video.currentTime = t;
1497 },
1498
1499 show: function() {
1500 this.element.classList.remove('hide');
1501 },
1502
1503 hide: function() {
1504 this.video.pause();
1505 this.element.classList.add('hide');
1506 },
1507
1508 get_x: function(width) {
1509 return Math.round(width * this.video.currentTime / this.video.duration);
1510 },
1511
1512 get_frame: function() {
1513 return Math.round(this.frames * this.video.currentTime / this.video.duration);
1514 },
1515}
1516
1517
1518var RoughCut = function(session) {
1519 this.session = session;
1520 this.active = false;
1521
1522 this.element = $('roughcut');
1523 this.frames = $el('div', {'class': 'frames'});
1524 this.element.appendChild(this.frames);
1525
1526 this.done = $('close_roughcut');
1527 this.done.onclick = function() {
1528 this.blur();
1529 UI.hide_roughcut();
1530 }
1531
1532 this.create_button = $('create_slice');
1533 this.create_button.onclick = $bind(this.create_slice, this);
1534
1535 this.startvideo = new VideoFrame('start');
1536 this.frames.appendChild(this.startvideo.element);
1537
1538 this.endvideo = new VideoFrame('end hide');
1539 this.frames.appendChild(this.endvideo.element);
1540
1541 this.scrubber = $el('div', {'class': 'scrubber'});
1542 this.element.appendChild(this.scrubber);
1543
1544 this.bar = $el('div', {'class': 'bar hide'});
1545 this.scrubber.appendChild(this.bar);
1546
1547 this.playhead = $el('div', {'class': 'playhead hide'});
1548 this.scrubber.appendChild(this.playhead);
1549
1550 this.scrubber.onmouseover = $bind(this.on_mouseover, this);
1551 this.scrubber.onmousedown = $bind(this.on_mousedown, this);
1552
1553 this.startvideo.video.addEventListener('timeupdate',
1554 $bind(this.on_timeupdate, this)
1555 );
1556 this.startvideo.video.addEventListener('ended',
1557 $bind(this.on_ended, this)
1558 );
1559
1560 this.startvideo.element.addEventListener('mousewheel',
1561 $bind(this.on_mousewheel_start, this)
1562 );
1563 this.endvideo.element.addEventListener('mousewheel',
1564 $bind(this.on_mousewheel_end, this)
1565 );
1566}
1567RoughCut.prototype = {
1568 on_ended: function() {
1569 if (! this.playing) {
1570 return;
1571 }
1572 var frame = (this.mode == 'edit' && this.inside) ? this.start : 0;
1573 this.startvideo.seek(frame);
1574 this.startvideo.video.play();
1575 this.pframe = frame;
1576 },
1577
1578 on_timeupdate: function() {
1579 if (! this.playing) {
1580 return;
1581 }
1582 if (this.dnd) {
1583 return;
1584 }
1585 var frame = this.startvideo.get_frame();
1586 if (this.mode == 'edit' && this.inside && frame >= this.stop - 1) {
1587 this.startvideo.seek(this.start, true);
1588 frame = this.start;
1589 }
1590 this.pframe = frame;
1591 },
1592
1593 hide: function() {
1594 this.active = false;
1595 this.mode = null;
1596 this.pause();
1597 $hide(this.element);
1598 },
1599
1600 show: function(id) {
1601 console.log('show ' + id);
1602 if (! this.element.classList.contains('hide')) {
1603 this.x = this.x + 130 + 10 + (this.xf * 3);
1604 if (this.x > document.body.clientWidth - 130) {
1605 this.x = 0;
1606 }
1607 }
1608 else {
1609 this.x = 0;
1610 }
1611 this.y = 0;
1612 this.count = 0;
1613 this.active = true;
1614 this.clip = this.session.get_doc(id);
1615 this.frames = this.clip.duration.frames;
1616 this.startvideo.set_clip(this.clip);
1617 this.endvideo.set_clip(this.clip);
1618 $show(this.element);
1619 },
1620
1621 reset: function() {
1622 delete this.dnd;
1623 this._start = 0;
1624 this._stop = this.frames;
1625 this.pframe = null;
1626 this.startvideo.pause();
1627 this.playing = false;
1628 this.scrubber.onmousemove = null;
1629 },
1630
1631 get_x: function(frame) {
1632 return Math.round(this.scrubber.clientWidth * frame / this.frames);
1633 },
1634
1635 get_frame: function(x, key_unit) {
1636 var frame = Math.round(this.frames * x / this.scrubber.clientWidth);
1637 if (key_unit) {
1638 return 1 + Math.round(frame / 15) * 15;
1639 }
1640 return frame;
1641 },
1642
1643 set start(value) {
1644 this._start = Math.max(0, Math.min(value, this._stop - 1));
1645 this.startvideo.set_index(this._start);
1646 },
1647
1648 get start() {
1649 return this._start;
1650 },
1651
1652 set stop(value) {
1653 this._stop = Math.max(this._start + 1, Math.min(value, this.frames));
1654 this.endvideo.set_index(this._stop - 1);
1655 },
1656
1657 get stop() {
1658 return this._stop;
1659 },
1660
1661 get left() {
1662 return this.get_x(this._start);
1663 },
1664
1665 get right() {
1666 return this.get_x(this._stop);
1667 },
1668
1669 set pframe(value) {
1670 if (value == null) {
1671 this._pframe = null;
1672 this.playhead.classList.add('hide');
1673 }
1674 else {
1675 this._pframe = Math.max(0, Math.min(value, this.frames - 1));
1676 this.playhead.classList.remove('hide');
1677 this.playhead.style.left = this.get_x(this._pframe) + 'px';
1678 }
1679 },
1680
1681 get pframe() {
1682 return this._pframe;
1683 },
1684
1685 on_mousewheel_start: function(event) {
1686 $halt(event);
1687 var orig = this._start;
1688 this.start = orig + wheel_delta(event);
1689 if (this.start != orig) {
1690 this.save_to_slice();
1691 this.session.delayed_commit();
1692 if (this.mode == 'create') {
1693 this.bar.style.left = this.left + 'px';
1694 }
1695 else {
1696 this.update_bar();
1697 }
1698 }
1699 },
1700
1701 on_mousewheel_end: function(event) {
1702 $halt(event);
1703 var orig = this._stop;
1704 this.stop = orig + wheel_delta(event);
1705 if (this.stop != orig) {
1706 this.save_to_slice();
1707 this.session.delayed_commit();
1708 this.update_bar();
1709 }
1710 },
1711
1712 playpause: function() {
1713 if (this.playing) {
1714 this.pause();
1715 }
1716 else {
1717 this.play();
1718 }
1719 },
1720
1721 play: function() {
1722 this.playing = true;
1723 this.scrubber.onmousemove = null;
1724 if (this._pframe == null) {
1725 this.pframe = this.start;
1726 }
1727 this.startvideo.seek(this.pframe);
1728 this.startvideo.play();
1729 },
1730
1731 pause: function() {
1732 this.startvideo.pause();
1733 this.startvideo.seek(this.start);
1734 this.playing = false;
1735 if (this.mode == 'create') {
1736 this.bind_mousemove();
1737 }
1738 },
1739
1740 update_bar: function() {
1741 var left = this.left;
1742 var width = Math.max(2, this.right - left);
1743 this.bar.style.left = left + 'px';
1744 this.bar.style.width = width + 'px';
1745 },
1746
1747 sync_from_slice: function() {
1748 this._start = 0;
1749 this._stop = this.frames;
1750 this.start = this.slice.node.start.frame;
1751 this.stop = this.slice.node.stop.frame;
1752 this.update_bar();
1753 },
1754
1755 save_to_slice: function() {
1756 /*
1757 Store start & stop in slice doc, mark doc as dirty.
1758
1759 Note that this *only* marks the slice doc as dirty, does *not* call
1760 session.commit(). This is for cases when you also need to update the
1761 sequence doc, so you can send both in a single CouchDB request.
1762 */
1763 this.slice.node.start.frame = this.start;
1764 this.slice.node.stop.frame = this.stop;
1765 this.session.save(this.slice);
1766 },
1767
1768 create_slice: function() {
1769 console.log('create_slice');
1770 this.create_button.blur();
1771 this.count += 1;
1772 this.mode = 'create';
1773 this.endvideo.hide();
1774 this.reset();
1775 this.start = 0;
1776 this.bar.style.left = this.left + 'px';
1777 this.bar.style.width = '1px';
1778 this.slice = create_slice(this.clip._id, this.frames);
1779 this.bind_mousemove();
1780 },
1781
1782 edit_slice: function(slice) {
1783 console.log('edit_slice ' + slice._id);
1784 this.mode = 'edit';
1785 this.inside = true;
1786 this.slice = slice;
1787 this.endvideo.show();
1788 this.reset();
1789 $show(this.bar);
1790 this.sync_from_slice();
1791 },
1792
1793 bind_mousemove: function() {
1794 this.scrubber.onmousemove = $bind(this.on_mousemove, this);
1795 },
1796
1797 on_mouseover: function(event) {
1798 if (this.mode == 'create' && !this.playing && !this.dnd) {
1799 $show(this.bar);
1800 }
1801 },
1802
1803 on_mousemove: function(event) {
1804 this.start = this.get_frame(event.clientX);
1805 this.bar.style.left = this.left + 'px';
1806 },
1807
1808 on_mousedown: function(event) {
1809 this.scrubber.onmousemove = null;
1810 this.dnd = new DragEvent(event);
1811 this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
1812 this.dnd.ondragstart = $bind(this.on_dragstart, this);
1813
1814 if (this.playing) {
1815 this.startvideo.pause();
1816 return this.scrub_playhead(this.dnd);
1817 }
1818 if (this.mode == 'edit') {
1819 var mid = (this.left + this.right) / 2;
1820 this.point = (event.clientX <= mid) ? 'left' : 'right';
1821 var frame = this.get_frame(event.clientX);
1822 if (this.point == 'left') {
1823 this.start = frame;
1824 }
1825 else {
1826 this.stop = frame + 1;
1827 }
1828 this.update_bar();
1829 }
1830 },
1831
1832 scrub_playhead: function(dnd) {
1833 var frame = this.get_frame(dnd.x);
1834 if (this.start <= frame && frame < this.stop) {
1835 this.inside = true;
1836 }
1837 else {
1838 this.inside = false;
1839 }
1840 this.pframe = frame;
1841 this.startvideo.seek(frame);
1842 },
1843
1844 on_dragcancel: function(dnd) {
1845 delete this.dnd;
1846 if (this.playing) {
1847 this.startvideo.play();
1848 return;
1849 }
1850 if (this.mode == 'create') {
1851 this.bindmousemove();
1852 }
1853 else {
1854 this.save_to_slice();
1855 this.session.delayed_commit();
1856 }
1857 },
1858
1859 on_dragstart: function(dnd) {
1860 this.dnd.ondrag = $bind(this.on_drag, this);
1861 this.dnd.ondrop = $bind(this.on_drop, this);
1862 if (!this.playing && this.mode == 'create') {
1863 this.endvideo.show();
1864 this.orig_start = this.start;
1865 }
1866 },
1867
1868 on_drag: function(dnd) {
1869 var frame = this.get_frame(dnd.x);
1870 if (this.playing) {
1871 return this.scrub_playhead(dnd);
1872 }
1873 if (this.mode == 'create') {
1874 if (frame < this.orig_start) {
1875 this.start = frame;
1876 this.stop = this.orig_start + 1;
1877 }
1878 else {
1879 this.start = this.orig_start;
1880 this.stop = frame + 1;
1881 }
1882 }
1883 else {
1884 if (this.point == 'left') {
1885 this.start = frame;
1886 }
1887 else {
1888 this.stop = frame + 1;
1889 }
1890 }
1891 this.update_bar();
1892 },
1893
1894 xf: 25,
1895
1896 yf: 50,
1897
1898 on_drop: function(dnd) {
1899 delete this.dnd;
1900 if (this.playing) {
1901 this.startvideo.play();
1902 return;
1903 }
1904 if (this.mode == 'create') {
1905 this.save_to_slice();
1906 var x = this.x + (this.count * this.xf);
1907 var y = this.y + (this.count * this.yf);
1908 UI.sequence.doc.doodle.push({id: this.slice._id, x: x, y: y});
1909 this.session.save(UI.sequence.doc);
1910 this.session.delayed_commit();
1911 this.edit_slice(this.slice);
1912 }
1913 else {
1914 this.save_to_slice();
1915 this.session.delayed_commit();
1916 }
1917 },
1918}
1919
1920
1921function Clips() {
1922 this.selected = null;
1923 this.dropdown = $('dmedia_project');
1924 this.dropdown.onchange = $bind(this.on_dropdown_change, this);
1925 this.div = $('clips');
1926 this.container = $('clips_outer');
1927 this.session = UI.session;
1928 this.doc = this.session.get_doc(UI.project_id);
1929 this.session.subscribe(this.doc._id, this.on_change, this);
1930 this.id = null;
1931 this.db = null;
1932 this.load_projects();
1933 this.open = $('open_clips');
1934 this.open.onclick = $bind(this.on_open_click, this);
1935
1936 this.div.onchildselect = $bind(this.on_childselect, this);
1937}
1938Clips.prototype = {
1939 on_childselect: function(id) {
1940 console.log('childselect ' + id);
1941 $hscroll($(id));
1942 if (this.doc.selected_clips[this.id] != id) {
1943 this.doc.selected_clips[this.id] = id;
1944 this.session.save(this.doc, true);
1945 this.session.delayed_commit();
1946 }
1947 },
1948
1949 on_change: function(doc) {
1950 console.log('Clips.on_change');
1951 this.doc = doc;
1952 if (!this.doc.selected_clips) {
1953 this.doc.selected_clips = {};
1954 }
1955 var id = doc.dmedia_project_id;
1956 this.dropdown.value = id;
1957 if (this.load(id)) {
1958 this.div.innerHTML = null;
1959 this.load_clips();
1960 }
1961 },
1962
1963 load: function(id) {
1964 console.log('load ' + id);
1965 if (!id) {
1966 this.id = null;
1967 this.db = null;
1968 return false;
1969 }
1970 if (id == this.id) {
1971 return false;
1972 }
1973 this.id = id;
1974 this.db = dmedia_project_db(id);
1975 return true;
1976 },
1977
1978 load_projects: function() {
1979 var callback = $bind(this.on_projects, this);
1980 dmedia.view(callback, 'project', 'title');
1981 },
1982
1983 on_projects: function(req) {
1984 var rows = req.read().rows;
1985 this.dropdown.innerHTML = null;
1986 this.placeholder = $el('option');
1987 this.dropdown.appendChild(this.placeholder);
1988 rows.forEach(function(row) {
1989 var option = $el('option', {textContent: row.key, value: row.id});
1990 this.dropdown.appendChild(option);
1991 }, this);
1992 },
1993
1994 on_dropdown_change: function(event) {
1995 if (this.placeholder) {
1996 $unparent(this.placeholder);
1997 delete this.placeholder;
1998 }
1999 this.doc.dmedia_project_id = this.dropdown.value;
2000 this.dropdown.blur();
2001 this.session.save(this.doc);
2002 this.session.delayed_commit();
2003 },
2004
2005 load_clips: function() {
2006 var callback = $bind(this.on_clips, this);
2007 this.db.view(callback, 'user', 'video');
2008 },
2009
2010 on_clips: function(req) {
2011 var rows = req.read().rows;
2012 this.div.innerHTML = null;
2013 var self = this;
2014 rows.forEach(function(row) {
2015 var id = row.id;
2016 var img = new Image();
2017 img.id = id;
2018 img.src = this.att_url(id);
2019 img.onmousedown = function(event) {
2020 self.on_mousedown(id, event);
2021 }
2022 img.ondblclick = function(event) {
2023 self.on_dblclick(id, event);
2024 }
2025 this.div.appendChild(img);
2026 }, this);
2027 UI.select(this.doc.selected_clips[this.id]);
2028 },
2029
2030 on_open_click: function(event) {
2031 if (!this.container.classList.toggle('open')) {
2032 var element = $(UI.selected);
2033 if (element && element.parentNode.id == 'clips') {
2034 UI.select(null);
2035 }
2036 }
2037 },
2038
2039 on_mousedown: function(id, event) {
2040 UI.select(id);
2041 this.dnd = new DragEvent(event);
2042 this.dnd.id = id;
2043 this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
2044 this.dnd.ondragstart = $bind(this.on_dragstart, this);
2045 },
2046
2047 on_dragcancel: function(dnd) {
2048 delete this.dnd;
2049 },
2050
2051 on_dragstart: function(dnd) {
2052 this.dnd.ondrag = $bind(this.on_drag, this);
2053 this.dnd.ondrop = $bind(this.on_drop, this);
2054 },
2055
2056 on_drag: function(dnd) {
2057 if (dnd.dy > 50) {
2058 console.log('creating ' + dnd.dy);
2059 dnd.ondrag = null;
2060 UI.copy_clip(dnd.id);
2061 var clip = this.session.get_doc(dnd.id);
2062 var doc = create_slice(clip._id, clip.duration.frames);
2063 this.session.save(doc, true);
2064 var slice = new Slice(UI.session, doc);
2065 slice.x = dnd.x - 64;
2066 slice.y = dnd.y - 36;
2067 UI.bucket.appendChild(slice.element);
2068 slice.on_mousedown(dnd.event);
2069 }
2070 },
2071
2072 on_drop: function(dnd) {
2073 delete this.dnd;
2074 },
2075
2076 on_dblclick: function(id, event) {
2077 UI.copy_clip(id);
2078 UI.create_slice(id);
2079 },
2080
2081 att_url: function(doc_or_id, name) {
2082 if (!this.db) {
2083 return null;
2084 }
2085 return this.db.att_url(doc_or_id, name);
2086 },
2087
2088 att_css_url: function(doc_or_id, name) {
2089 if (!this.db) {
2090 return null;
2091 }
2092 return this.db.att_css_url(doc_or_id, name);
2093 },
2094}
2095
2096
2097var LoveOrb = function() {
2098 this.logo = $el('img', {'id': 'logo', 'src': 'novacut.png'});
2099 document.body.appendChild(this.logo);
2100 this.capture = $('flyout_capture');
2101 this.flyout = $('flyout');
2102 this.logo.onmousedown = $bind(this.on_mousedown, this);
2103 this.logo.onclick = $bind(this.on_click, this);
2104 this.capture.onclick = $bind(this.on_capture_click, this);
2105 this.flyout.onclick = $bind(this.on_flyout_click, this);
2106}
2107LoveOrb.prototype = {
2108 get active() {
2109 return !this.capture.classList.contains('hide');
2110 },
2111
2112 toggle: function() {
2113 if(this.capture.classList.toggle('hide')) {
2114 this.logo.classList.remove('open');
2115 }
2116 else {
2117 this.logo.classList.add('open');
2118 }
2119 },
2120
2121 on_mousedown: function(event) {
2122 // Needed to prevent annoying drag behavior
2123 $halt(event);
2124 },
2125
2126 on_click: function(event) {
2127 $halt(event);
2128 this.toggle();
2129 },
2130
2131 on_capture_click: function(event) {
2132 console.log('capture click');
2133 $halt(event);
2134 this.toggle();
2135 },
2136
2137 on_flyout_click: function(event) {
2138 console.log('flyout click');
2139 event.stopPropagation();
2140 },
2141}
2142
2143function closeAll(){
2144 for(var a in UI.conts)UI.conts[a].close();
2145}
2146
2147
2148function unselectAll(){
2149 for(var a in UI.selected){
2150 var b = UI.selected[a];
2151 $unselect(b);
2152 }
2153 UI.selected = Array();
2154}
2155
2156var selector = {
2157 init: function(){
2158 selector.down = false;
2159 selector.el = "";
2160 selector.xs = 1;
2161 selector.ys = 1;
2162 },
2163 on_mouse_down: function(ev){
2164 closeAll();
2165 unselectAll();
2166 selector.down = true;
2167 selector.el = $el('div', {'class': 'selector','id': 'sel'});
2168 selector.stx = ev.x;
2169 selector.sty = ev.y;
2170 selector.el.style.left = ev.x +'px';
2171 selector.el.style.top = ev.y +'px';
2172 UI.bucket.appendChild(selector.el);
2173 },
2174 on_mouse_up: function(ev){
2175 console.log("mouse up");
2176 if(selector.down && selector.el && selector.el.parentNode){
2177 selector.el.parentNode.removeChild(selector.el);
2178 if(document.getElementById('sel'))document.getElementById('sel').parentNode.removeChild(document.getElementById('sel'));
2179 selector.down = false;
2180 var stx = selector.stx;
2181 var endx = selector.stx+selector.xs*parseInt(selector.el.style.width);
2182 var sty = selector.sty;
2183 var endy = selector.sty+selector.ys*parseInt(selector.el.style.height);
2184 if(stx > endx){
2185 var tmp = stx;
2186 stx = endx;
2187 endx = tmp;
2188 }
2189 if(sty > endy){
2190 var tmp = sty;
2191 sty = endy;
2192 endy = tmp;
2193 }
2194 for(var a in UI.slices){
2195 var b = UI.slices[a];
2196 if(b.x > stx && b.x < endx && b.y > sty && b.y < endy){
2197 UI.selected.push(b.id);
2198 $select(b.id);
2199 }
2200 }
2201 for(var a in UI.conts){
2202 var b = UI.conts[a];
2203 if(b.x > stx && b.x < endx && b.y > sty && b.y < endy){
2204 UI.selected.push(b.id);
2205 $select(b.id);
2206 }
2207 }
2208 }
2209 },
2210 on_mouse_move: function(ev){
2211 if(selector.down){
2212 var w = ev.x - selector.stx;
2213 var h = ev.y - selector.sty;
2214 selector.el.style.width = ev.x - selector.stx +'px';
2215 selector.el.style.height = ev.y - selector.sty +'px';
2216 selector.xs = 1;
2217 selector.ys = 1;
2218 if(w < 0){
2219 selector.el.style.width = -w + 'px';
2220 selector.el.style.left = ev.x + 'px';
2221 selector.xs = -1;
2222 }
2223 if(h < 0){
2224 selector.el.style.height = -h + 'px';
2225 selector.el.style.top = ev.y + 'px';
2226 selector.ys = -1;
2227 }
2228 }
2229 }
2230
2231}
2232
2233function compare(a,b){
2234return a.x-b.x;
2235}
2236
2237function generateAnimation(id,x,y,ex){//generate the animation
2238 var keyframeprefix = "-webkit-";
2239 var keyframes = '@' + keyframeprefix + 'keyframes '+id+' { '+'0% {' + keyframeprefix + 'transform:scale(1)}'+'50% {' + keyframeprefix + 'transform:translatex('+x+'px) translatey('+y+'px)}'+'100% {' + keyframeprefix + 'transform:translatex('+ex+'px) translatey('+y+'px)}'+'}';
2240 keyframes += '#'+id+'{-webkit-animation-duration: 0.5s;-webkit-animation-iteration-count: 1;-webkit-animation-timing-function:linear;}';
2241 var s = document.createElement( 'style' );
2242 s.innerHTML = keyframes;
2243 document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
2244}
2245
2246
2247var UI = {
2248 init: function() {
2249 Hub.connect('edit_hashed', UI.on_edit_hashed);
2250 Hub.connect('job_hashed', UI.on_job_hashed);
2251 Hub.connect('job_rendered', UI.on_job_rendered);
2252
2253 // Figure out what project we're in:
2254 var parts = parse_hash();
2255 UI.project_id = parts[0];
2256 UI.db = novacut_project_db(UI.project_id);
2257
2258 // Bit of UI setup
2259 window.addEventListener('keyup', UI.on_keyup);
2260 UI.bucket = $('bucket');
2261 UI.orb = new LoveOrb();
2262 document.getElementById('shortcuts').style.marginTop = window.innerHeight/2-200+"px";
2263 document.getElementById('shortcuts').style.marginLeft = window.innerWidth/2-255+"px";
2264
2265 //selector
2266 UI.selected = Array();
2267 UI.bucket.addEventListener('mousedown', selector.on_mouse_down, false);
2268 UI.bucket.addEventListener('mousemove', selector.on_mouse_move, false);
2269 document.addEventListener('mouseup', selector.on_mouse_up, false);
2270
2271 // Create and start the CouchDB session
2272 UI.session = new couch.Session(UI.db, UI.on_new_doc);
2273 UI.session.start();
2274
2275 UI.dragging = false;
2276 UI.slices = Array();
2277 UI.conts = Array();
2278 document.body.onclick = $bind(closeAll);
2279 },
2280
2281 animated: null,
2282
2283 animate: function(element) {
2284 if (UI.animated) {
2285 UI.animated.classList.remove('animated');
2286 }
2287 UI.animated = $(element);
2288 if (UI.animated) {
2289 UI.animated.classList.add('animated');
2290 }
2291 },
2292
2293 copy_clip: function(id) {
2294 try {
2295 UI.session.get_doc(id);
2296 }
2297 catch (e) {
2298 console.log('copying ' + id);
2299 var doc = UI.clips.db.get_sync(id, {attachments: true});
2300 delete doc._rev;
2301 UI.session.save(doc, true);
2302 }
2303 },
2304
2305 select: function(id) {
2306 unselectAll();
2307 var element = $select(id);
2308 if (element) {
2309 UI.selected.push(id);
2310 if (element.parentNode && element.parentNode.onchildselect) {
2311 element.parentNode.onchildselect(id);
2312 }
2313 }
2314 else {
2315 UI.selected = Array();
2316 }
2317 if (UI.sequence) {
2318 var doc = UI.sequence.doc;
2319 if (doc.selected != UI.selected) {
2320 doc.selected = UI.selected;
2321 UI.session.save(doc, true); // No local emit
2322 UI.session.delayed_commit();
2323 }
2324 }
2325 },
2326
2327 delete_selected: function() {
2328 for(var a in UI.selected){
2329 var id = UI.selected[a];
2330 var element = $(UI.selected[a]);
2331 if (element) {
2332 if (element.parentNode && element.parentNode.id == 'clips') {
2333 console.log('no delete in clips browser');
2334 return;
2335 }
2336 try {
2337 var doc = UI.session.get_doc(element.id);
2338 UI.previous();
2339 doc._deleted = true;
2340 UI.session.save(doc);
2341 if(doc.node.type == 'cont')for(var a in UI.conts){
2342 if(UI.conts[a].id == id)UI.conts.splice(a,1);
2343 }
2344 if(doc.node.type == 'slice')for(var a in UI.slices){
2345 if(UI.slices[a].id == id)UI.slices.splice(a,1);
2346 }
2347 }
2348 catch (e) {
2349 return;
2350 }
2351 }
2352 }
2353 },
2354
2355 group_selected: function(){
2356 var ndoc = create_cont();
2357 var list = Array();
2358 if(UI.selected.length < 2)return;
2359 for(var a in UI.selected){//get all the slices
2360 var doc = UI.session.get_doc(UI.selected[a]);
2361 if(doc.node.type == 'slice'){
2362 for(var b in UI.slices)if(UI.slices[b].id == UI.selected[a]){
2363 list.push(UI.slices[b]);
2364 UI.slices.splice(b,1);
2365 }
2366 //UI.bucket.removeChild(document.getElementById(UI.selected[a]))
2367 //ndoc.node.src.push(UI.selected[a]);
2368 }
2369 }
2370 if(list.length < 2)return;
2371 list.sort(compare);//sort for x position
2372 var y = 0;
2373 for(var a in list){//add the slices in the doc and compute the average y
2374 ndoc.node.src.push(list[a].id);
2375 y += list[a].y;
2376 }
2377 y /= list.length;
2378 UI.session.save(ndoc);
2379 var ncont = new Cont(UI.session,ndoc);
2380 ncont.x = list[0].x;
2381 ncont.y = y;
2382 for(var a in list){//animate to form a line
2383 generateAnimation(list[a].id,list[0].x+(a*130)-list[a].x,y-list[a].y,list[0].x-list[a].x);
2384 list[a].element.style.webkitAnimationName = list[a].id;
2385 console.log('animate');
2386 list[a].element.addEventListener('webkitAnimationEnd',function(ev){
2387 //console.log('end');
2388 /*this.style.webkitAnimationName = "";
2389 this.style.top = "0px";
2390 this.style.left = "0px";
2391 $unparent(this);
2392 ncont.list.appendChild(this);
2393 ncont.close();*/
2394 UI.bucket.removeChild(this);
2395 UI.sequence.do_reorder();
2396
2397 });
2398 }
2399 //setTimeout(UI.sequence.do_reorder(),530);
2400 //setTimeout(function(){for(var a in list)UI.bucket.removeChild(document.getElementById(list[a].id));},530,list);
2401 UI.bucket.appendChild(ncont.element);
2402 },
2403
2404 duplicate_selected: function() {
2405 for(var a in UI.selected){
2406 var element = $(UI.selected[a]);
2407 var doc = UI.session.get_doc(element.id);
2408 if(doc.node.type == 'slice'){
2409 var ndoc = create_slice(doc.node.src,doc.node.stop.frame);
2410 ndoc.node.start.frame = doc.node.start.frame;
2411 UI.session.save(ndoc);
2412 var slice = new Slice(UI.session, ndoc);
2413 }
2414 if(doc.node.type == 'cont'){
2415 var ndoc = create_cont();
2416 ndoc.node.src = doc.node.src;
2417 UI.session.save(ndoc);
2418 var slice = new Cont(UI.session, ndoc);
2419 }
2420 slice.x = parseInt(element.style.left)-30;
2421 slice.y = parseInt(element.style.top)-30;
2422 UI.bucket.appendChild(slice.element);
2423 }
2424 UI.sequence.do_reorder();
2425 },
2426
2427 first: function() {
2428 var element = $(UI.selected);
2429 if (element && element.parentNode) {
2430 UI.select(element.parentNode.children[0].id);
2431 }
2432 },
2433
2434 previous: function() {
2435 var element = $(UI.selected);
2436 if (element && element.previousSibling) {
2437 UI.select(element.previousSibling.id);
2438 }
2439 },
2440
2441 next: function() {
2442 var element = $(UI.selected);
2443 if (element && element.nextSibling) {
2444 UI.select(element.nextSibling.id);
2445 }
2446 },
2447
2448 last: function() {
2449 var element = $(UI.selected);
2450 if (element && element.parentNode) {
2451 var i = element.parentNode.children.length - 1;
2452 UI.select(element.parentNode.children[i].id);
2453 }
2454 },
2455
2456
2457 get_slice: function(_id) {
2458 var element = $unparent(_id);
2459 if (element) {
2460 console.log(_id);
2461 return element;
2462 }
2463 try {
2464 var doc = UI.session.get_doc(_id);
2465 }
2466 catch (e) {
2467 return null;
2468 }
2469 console.log(_id);
2470 if(doc.node.type == 'slice'){
2471 var slice = new Slice(UI.session, doc);
2472 return slice.element;
2473 }
2474 if(doc.node.type == 'cont'){
2475 var slice = new Cont(UI.session, doc);
2476 return slice.element;
2477 }
2478 },
2479
2480 on_new_doc: function(doc) {
2481 if (doc._id == UI.project_id) {
2482 UI.doc = doc;
2483
2484 // FIXME: create default sequence if needed
2485 if (!UI.doc.root_id) {
2486 console.log('creating default sequence');
2487 var seq = create_sequence();
2488 UI.doc.root_id = seq._id;
2489 UI.session.save(UI.doc, true);
2490 UI.session.save(seq, true);
2491 UI.session.delayed_commit();
2492 }
2493
2494 UI.sequence = new Sequence(UI.session, UI.session.get_doc(UI.doc.root_id));
2495 UI.clips = new Clips(dmedia);
2496 UI.player= new SequencePlayer(UI.session, UI.sequence.doc);
2497 //closeAll();
2498
2499 }
2500 },
2501
2502 _roughcut: null,
2503
2504 get roughcut() {
2505 if (UI._roughcut == null) {
2506 UI._roughcut = new RoughCut(UI.session);
2507 }
2508 return UI._roughcut;
2509 },
2510
2511 create_slice: function(id) {
2512 UI.roughcut.show(id);
2513 UI.roughcut.create_slice();
2514 },
2515
2516 edit_slice: function(doc) {
2517 UI.roughcut.show(doc.node.src);
2518 UI.roughcut.edit_slice(doc);
2519 },
2520
2521 hide_roughcut: function() {
2522 UI.roughcut.hide();
2523 },
2524
2525 // Key bindings
2526 actions: {
2527 // Left arrow
2528 'Left': function(event) {
2529 if (event.shiftKey) {
2530 UI.first();
2531 }
2532 else {
2533 UI.previous();
2534 }
2535 if (UI.player.active) {
2536 UI.player.hold_and_resume();
2537 }
2538 },
2539
2540 // Right arrow
2541 'Right': function(event) {
2542 if (event.shiftKey) {
2543 UI.last();
2544 }
2545 else {
2546 UI.next();
2547 }
2548 if (UI.player.active) {
2549 UI.player.hold_and_resume();
2550 }
2551 },
2552 //G
2553 'U+0047': function(event){
2554 UI.group_selected();
2555 },
2556 //D
2557 'U+0044': function(event) {
2558 UI.duplicate_selected();
2559 },
2560
2561 // The David Fulde key
2562 // aka Backspace aka Big Delete on a mac keyboard
2563 'U+0008': function(event) {
2564 UI.delete_selected();
2565 },
2566
2567 // Delete
2568 'U+007F': function(event) {
2569 UI.delete_selected();
2570 },
2571
2572 // SpaceBar
2573 'U+0020': function(event) {
2574 if (UI.roughcut.active) {
2575 UI.roughcut.playpause();
2576 }
2577 else {
2578 if (UI.player.active) {
2579 UI.player.playpause();
2580 }
2581 else {
2582 UI.player.show();
2583 }
2584 }
2585 },
2586
2587 // Escape
2588 'U+001B': function(event) {
2589 if (UI.player.active) {
2590 UI.player.hide();
2591 }
2592 },
2593 },
2594
2595 on_keyup: function(event) {
2596 console.log('keyup ' + event.keyIdentifier);
2597 if (document.activeElement != document.body) {
2598 console.log('document body not focused');
2599 return;
2600 }
2601 if (UI.orb.active) {
2602 if (event.keyIdentifier == 'U+001B') {
2603 UI.orb.toggle();
2604 }
2605 return;
2606 }
2607 var action = UI.actions[event.keyIdentifier];
2608 if (action) {
2609 $halt(event);
2610 action(event);
2611 }
2612 },
2613
2614 render: function() {
2615 $("render-btn").disabled = true;
2616 $("render-btn").blur();
2617 UI.orb.toggle();
2618 console.log('render');
2619 },
2620
2621 on_edit_hashed: function(project_id, node_id, intrinsic_id) {
2622 console.log(['edit_hashed', project_id, node_id, intrinsic_id].join(' '));
2623 // null for default settings_id:
2624 Hub.send('hash_job', intrinsic_id, null);
2625 },
2626
2627 on_job_hashed: function(intrinsic_id, settings_id, job_id) {
2628 console.log(['job_hashed', intrinsic_id, settings_id, job_id].join(' '));
2629 Hub.send('render_job', job_id);
2630 },
2631
2632 on_job_rendered: function(job_id, file_id, link) {
2633 console.log(['job_rendered', job_id, file_id, link].join(' '));
2634 //UI.player.src = 'dmedia:' + file_id;
2635 //UI.player.play();
2636 $("render-btn").disabled = false;
2637 },
2638}
2639
2640function showShort(event){
2641 console.log('show');
2642 UI.orb.toggle();
2643 document.getElementById("shortcuts").style.display = "block";
2644 event.stopPropagation();
2645 document.body.onclick = $bind(hideShort);
2646}
2647
2648function hideShort(event){
2649 console.log('hide');
2650 document.getElementById("shortcuts").style.display = "none";
2651 event.stopPropagation();
2652}
2653
2654window.addEventListener('load', UI.init);
2655
2656
02657
=== added file 'ui/novacut2.js'
--- ui/novacut2.js 1970-01-01 00:00:00 +0000
+++ ui/novacut2.js 2012-08-14 17:10:21 +0000
@@ -0,0 +1,416 @@
1"use strict";
2
3var novacut = new couch.Database('novacut-0');
4var dmedia = new couch.Database('dmedia-0');
5
6function parse_hash() {
7 return window.location.hash.slice(1).split('/');
8}
9
10function set_title(id, value) {
11 var el = $(id);
12 if (value) {
13 el.textContent = value;
14 }
15 else {
16 el.textContent = '';
17 el.appendChild($el('em', {textContent: 'Untitled'}));
18 }
19 return el;
20}
21
22
23function project_db(base, ver, project_id) {
24 var name = [base, ver, project_id.toLowerCase()].join('-');
25 return new couch.Database(name);
26}
27
28
29function novacut_project_db(project_id) {
30 return project_db('novacut', 0, project_id);
31}
32
33
34function dmedia_project_db(project_id) {
35 return project_db('dmedia', 0, project_id);
36}
37
38
39function frame_to_seconds(frame, framerate) {
40 return frame * framerate.denom / framerate.num;
41}
42
43
44function seconds_to_frame(seconds, framerate) {
45 return Math.round(seconds * framerate.num / framerate.denom);
46}
47
48
49
50function create_node(node) {
51 return {
52 '_id': couch.random_id(),
53 'ver': 0,
54 'type': 'novacut/node',
55 'time': couch.time(),
56 'node': node,
57 }
58}
59
60
61function create_slice(src, frame_count) {
62 var node = {
63 'type': 'slice',
64 'src': src,
65 'start': {'frame': 0},
66 'stop': {'frame': frame_count},
67 'stream': 'both',
68 }
69 return create_node(node);
70}
71
72function create_cont() {
73 var node = {
74 'type': 'cont',
75 'src': [],
76 }
77 return create_node(node);
78}
79
80function create_sequence() {
81 var node = {
82 'type': 'sequence',
83 'src': [],
84 }
85 var doc = create_node(node);
86 doc.doodle = [];
87 return doc;
88}
89
90
91function SlicePlayer() {
92 this.video = document.createElement('video');
93 //this.video.muted = true;
94 this.video.addEventListener('canplaythrough',
95 $bind(this.on_canplaythrough, this)
96 );
97 this.video.addEventListener('seeked',
98 $bind(this.on_seeked, this)
99 );
100 this.video.addEventListener('ended',
101 $bind(this.on_ended, this)
102 );
103
104 this.timeout_id = null;
105
106 this.playing = false;
107 this.ready = false;
108 this.ended = false;
109
110 this.onready = null;
111 this.onended = null;
112}
113SlicePlayer.prototype = {
114 log_event: function(name, point) {
115 var frame = seconds_to_frame(this.video.currentTime, this.clip.framerate);
116 var parts = [name, frame, point, frame == point];
117 console.log(parts.join(' '));
118 },
119
120 on_canplaythrough: function(event) {
121 this.seek(this.slice.node.start.frame);
122 },
123
124 on_seeked: function(event) {
125 //this.log_event('seeked', this.slice.node.start.frame);
126 if (!this.ready) {
127 this.ready = true;
128 if (this.onready) {
129 this.onready(this);
130 }
131 }
132 },
133
134 on_ended: function(event) {
135 this.video.pause();
136 this.clear_timeout();
137 //this.log_event('ended', this.slice.node.stop.frame);
138 this.ended = true;
139 if (this.onended) {
140 this.onended(this);
141 }
142 },
143
144 seek: function(frame) {
145 this.video.currentTime = this.frame_to_seconds(frame);
146 },
147
148 frame_to_seconds: function(frame) {
149 return frame_to_seconds(frame, this.clip.framerate);
150 },
151
152 clear_timeout: function() {
153 if (this.timeout_id != null) {
154 clearTimeout(this.timeout_id);
155 this.timeout_id = null;
156 }
157 },
158
159 set_timeout: function(duration) {
160 this.clear_timeout();
161 var callback = $bind(this.on_ended, this);
162 this.timeout_id = setTimeout(callback, duration);
163 },
164
165 playpause: function() {
166 if (this.playing) {
167 this.pause();
168 }
169 else {
170 this.play();
171 }
172 },
173
174 play: function() {
175 if (this.playing || !this.ready) {
176 return;
177 }
178 this.playing = true;
179 this.video.style.visibility = 'visible';
180 if (this.slice.node.stop.frame != this.clip.duration.frames) {
181 var stop = this.frame_to_seconds(this.slice.node.stop.frame);
182 var duration = 1000 * (stop - this.video.currentTime);
183 this.set_timeout(duration);
184 }
185 this.video.play();
186 },
187
188 pause: function() {
189 if (!this.ready) {
190 return;
191 }
192 this.playing = false;
193 this.video.style.visibility = 'visible';
194 this.clear_timeout();
195 this.video.pause();
196 },
197
198 stop: function() {
199 this.video.style.visibility = 'hidden';
200 this.video.pause();
201 this.clear_timeout();
202 this.ended = false;
203 this.ready = false;
204 this.playing = false;
205 },
206
207 load_slice: function(clip, slice) {
208 this.stop();
209 this.clip = clip;
210 this.slice = slice;
211 this.video.src = 'dmedia:' + this.clip._id;
212 //setTimeout($bind(this.do_load, this), 50);
213 },
214
215 do_load: function() {
216 this.video.src = 'dmedia:' + this.clip._id;
217 },
218}
219
220
221
222function SequencePlayer(session, doc) {
223 this.session = session;
224 this.doc = doc;
225
226 this.element = $el('div', {'id': 'player', 'class': 'hide'});
227
228 this.player1 = new SlicePlayer();
229 this.player1.i = 0;
230 this.player2 = new SlicePlayer();
231 this.player2.i = 1;
232
233 this.players = [this.player1, this.player2];
234 var on_ready = $bind(this.on_ready, this);
235 var on_ended = $bind(this.on_ended, this);
236 this.players.forEach(function(player) {
237 player.onready = on_ready;
238 player.onended = on_ended;
239 this.element.appendChild(player.video);
240 }, this);
241
242 this.ready = false;
243 this.playing = false;
244 this.active = false;
245
246 this.timeout_id == null
247
248 this.element.onclick = $bind(this.playpause, this);
249
250 document.body.appendChild(this.element);
251
252}
253SequencePlayer.prototype = {
254 show: function() {
255 console.log('show');
256 if (this.doc.node.src.length == 0) {
257 return;
258 }
259 $show(this.element);
260 this.active = true;
261 this.playing = true;
262 this.play_from_slice(UI.selected);
263 },
264
265 soft_hide: function() {
266 $hide(this.element);
267 console.assert(!this.playing);
268 },
269
270 soft_show: function() {
271 $show(this.element);
272 console.assert(!this.playing);
273 },
274
275 hide: function() {
276 console.log('hide');
277 $hide(this.element);
278 this.active = false;
279 this.stop();
280 },
281
282 playpause: function() {
283 if (this.playing) {
284 this.pause();
285 }
286 else {
287 this.play();
288 }
289 },
290
291 hold: function() {
292 this.was_playing = this.playing;
293 this.playing = false;
294 this.activate_target(true);
295 },
296
297 hold_and_resume: function() {
298 if (this.timeout_id == null) {
299 console.log('first hold');
300 this.hold();
301 }
302 clearTimeout(this.timeout_id);
303 this.timeout_id = setTimeout($bind(this.on_timeout, this), 500);
304 },
305
306 on_timeout: function() {
307 console.log('timeout');
308 this.timeout_id = null;
309 this.resume();
310 },
311
312 resume: function() {
313 this.playing = this.was_playing;
314 if (UI.selected) {
315 this.play_from_slice(UI.selected);
316 }
317 else {
318 this.activate_target(true);
319 }
320 },
321
322 stop: function() {
323 this.players.forEach(function(player) {
324 player.stop();
325 });
326 this.ready = false;
327 this.target = null;
328 },
329
330 play: function() {
331 this.playing = true;
332 this.activate_target();
333 },
334
335 pause: function() {
336 this.playing = false;
337 this.activate_target();
338 },
339
340 play_from_slice: function(slice_id) {
341 if (this.doc.node.src.length == 0) {
342 return;
343 }
344 this.stop();
345 if (!slice_id) {
346 slice_id = UI.selected;
347 }
348 console.log('play_from_slice ' + slice_id);
349 var index = Math.max(0, this.doc.node.src.indexOf(slice_id));
350 this.load_slice(this.player1, index);
351 this.load_slice(this.player2, index + 1);
352 },
353
354 get_player: function(i) {
355 return this.players[i % this.players.length];
356 },
357
358 next_slice_index: function(player) {
359 return this.doc.node.src.indexOf(player.slice._id) + 1;
360 },
361
362 load_slice: function(player, index) {
363 var src = this.doc.node.src;
364 var slice_id = src[index % src.length];
365 var slice = this.session.get_doc(slice_id);
366 var clip = this.session.get_doc(slice.node.src);
367 player.load_slice(clip, slice);
368 },
369
370 on_ready: function(player) {
371 if (!this.ready) {
372 if (this.player1.ready && this.player2.ready) {
373 console.log('now ready');
374 this.ready = true;
375 this.target = this.players[0];
376 this.activate_target();
377 }
378 }
379 else if (this.target.ended) {
380 this.swap();
381 }
382 },
383
384 activate_target: function(no_select) {
385 if (!this.target) {
386 return;
387 }
388 if (this.playing) {
389 this.target.play();
390 }
391 else {
392 this.target.pause();
393 }
394 if (!no_select) {
395 UI.select(this.target.slice._id, true);
396 }
397 },
398
399 swap: function() {
400 var current = this.target;
401 var next = this.get_player(current.i + 1);
402 if (next.ready) {
403 this.target = next;
404 var index = this.next_slice_index(next);
405 this.activate_target();
406 this.load_slice(current, index);
407 }
408 },
409
410 on_ended: function(player) {
411 this.swap();
412 },
413}
414
415
416

Subscribers

People subscribed via source and target branches

to all changes:
to status/vote changes: