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
1=== added file 'ui/bucket2.css'
2--- ui/bucket2.css 1970-01-01 00:00:00 +0000
3+++ ui/bucket2.css 2012-08-14 17:10:21 +0000
4@@ -0,0 +1,640 @@
5+* {
6+ font-family: Ubuntu;
7+ line-height: 24px;
8+ -webkit-user-select: none;
9+}
10+
11+img {
12+ -webkit-user-select: none;
13+}
14+
15+.fgrid_row {
16+ white-space: nowrap;
17+ font-size: 0;
18+ line-height: 0;
19+}
20+
21+.fgrid_row > * {
22+ font-size: 15px;
23+ display: inline-block;
24+ margin: 8px;
25+ box-sizing: border-box !important;
26+ vertical-align: top;
27+}
28+
29+
30+.fgrid_cell > * {
31+ display: block;
32+ width: 100%;
33+ box-sizing: border-box !important;
34+}
35+
36+.fgrid_1 {
37+ width: 50px;
38+}
39+
40+.fgrid_2 {
41+ width: 116px;
42+}
43+
44+.fgrid_3 {
45+ width: 182px;
46+}
47+
48+.fgrid_4 {
49+ width: 248px;
50+}
51+
52+.fgrid_5 {
53+ width: 314px;
54+}
55+
56+.fgrid_6 {
57+ width: 380px;
58+}
59+
60+.fgrid_7 {
61+ width: 446px;
62+}
63+
64+.fgrid_8 {
65+ width: 512px;
66+}
67+
68+.fgrid_9 {
69+ width: 578px;
70+}
71+
72+.fgrid_10 {
73+ width: 644px;
74+}
75+
76+.fgrid_11 {
77+ width: 710px;
78+}
79+
80+.fgrid_12 {
81+ width: 776px;
82+}
83+
84+#flyout .version {
85+ text-shadow: 0px 0px 10px #e81f3b;
86+}
87+
88+
89+#flyout_capture {
90+ position: fixed;
91+ z-index: 10;
92+ top: 0;
93+ right: 0;
94+ bottom: 0;
95+ left: 0;
96+ /*
97+ background: rgba(0,0,0,0.5);
98+ */
99+}
100+
101+
102+#flyout {
103+ position: absolute;
104+ top: 18px;
105+ right: 18px;
106+ width: 800px;
107+ height: 448px;
108+ /*
109+ background: -webkit-linear-gradient(top, #333, #2a2a2a);
110+ */
111+ background-color: rgba(15, 15, 15, 0.9);
112+ box-shadow: 0px 0px 10px 0px #e81f3b;
113+ border-radius: 12px;
114+ font-size: 16px;
115+ color: #ddd;
116+ box-sizing: border-box !important;
117+ padding: 4px;
118+}
119+
120+#flyout a {
121+ color: #e81f3b;
122+ text-decoration: none;
123+}
124+
125+#flyout a:hover {
126+ color: #f82f4b;
127+ text-decoration: underline;
128+}
129+
130+#logo {
131+ position: fixed;
132+ z-index: 11;
133+ right: 6px;
134+ top: 6px;
135+ width: 48px;
136+ height: 48px;
137+ -webkit-transition: -webkit-transform 300ms linear;
138+}
139+
140+#logo.open {
141+ -webkit-transform: rotate(-180deg);
142+}
143+
144+#open_clips {
145+ z-index: 4;
146+ position: fixed;
147+ top: 0;
148+ right: 50%;
149+ width: 80px;
150+ color: #ddd;
151+ padding-left: 6px;
152+ padding-right: 6px;
153+ cursor: default;
154+ box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.6);
155+ text-align: center;
156+ border-radius: 0 0 2px 2px;
157+ border:1px solid transparent;
158+ border-top:none;
159+ border-bottom:1px solid #383838;
160+ background: -webkit-linear-gradient(top, #444, #333);
161+ text-shadow: 0px -1px 0px rgba(0,0,0,0.5);
162+}
163+
164+#clips_outer {
165+ z-index: 3;
166+ position: fixed;
167+ top: -140px;
168+ height: 139px;
169+ left: 0;
170+ right: 0;
171+ overflow: hidden;
172+ -webkit-transition: top 250ms ease-in-out;
173+ box-shadow: 0px 2px 6px 0px rgba(0,0,0,0.6);
174+}
175+
176+
177+#clips_outer.open {
178+ top: 0px;
179+}
180+
181+.grid_row {
182+ font-size: 0;
183+ white-space: nowrap;
184+ font-size: 0;
185+}
186+
187+#clips_nav {
188+ height: 41px;
189+}
190+
191+#clips_nav label, #clips_nav select {
192+ margin: 8px;
193+}
194+
195+
196+#clips {
197+ width: 100%;
198+ overflow-y: hidden;
199+ overflow-x: scroll;
200+ white-space: nowrap;
201+ font-size: 0;
202+ line-height: 0;
203+ height: 97px;
204+
205+ /*
206+ Dear James, you are looking especially tall today, and tough and handsome.
207+ Could you please make this scrollbar less horible looking?
208+
209+ Love,
210+ Jason
211+ */
212+}
213+
214+::-webkit-scrollbar{
215+ width:5px;
216+ height:5px;
217+ background:#222;
218+}
219+
220+::-webkit-scrollbar-thumb{
221+ border-radius:2px;
222+ background:#999;
223+}
224+
225+#clips img {
226+ width: 160px;
227+ height: 90px;
228+ display: inline-block;
229+ border: 1px solid #333;
230+ -webkit-user-select: none;
231+}
232+
233+#clips img.selected {
234+ border-color: #e81f3b;
235+}
236+
237+#bucket {
238+ position: fixed;
239+ top: 0px;
240+ bottom: 246px;
241+ left: 0;
242+ right: 0;
243+ /*
244+ background: #333;
245+ */
246+ background: #301438;
247+ overflow: hidden;
248+}
249+
250+.seq-preview {
251+ position: fixed;
252+ top: 0;
253+ left: 0;
254+ right: 0;
255+ bottom: 246px;
256+ z-index: 3;
257+ background-color: black;
258+}
259+
260+.seq-preview video {
261+ position: absolute;
262+ top: 0px;
263+ left: 0px;
264+ width: 100%;
265+ height: 100%;
266+}
267+
268+
269+#sequence {
270+ position: fixed;
271+ height: 221px;
272+ bottom: 0;
273+ left: 0;
274+ right: 0;
275+ background-color: #eee;
276+
277+ overflow: hidden;
278+ white-space: nowrap;
279+ font-size: 0;
280+ line-height: 0;
281+
282+ padding-top: 25px;
283+ background-image: url(grip-short.png);
284+ /*
285+ background-image: url(grip-short.png), -webkit-linear-gradient(top, #222, #333);
286+ */
287+ background-repeat: repeat-x;
288+}
289+
290+.slice {
291+ border-top: 1px solid #666;
292+ border-left: 1px solid #555;
293+ border-right: 1px solid #444;
294+ border-bottom:1px solid #333;
295+}
296+
297+.slice.selected {
298+ border-color: #e81f3b;
299+ z-index: 1;
300+}
301+
302+.slice, .slice * {
303+ -webkit-user-select: none;
304+}
305+
306+#bucket .slice {
307+ position: absolute;
308+ box-shadow: 0px 2px 5px 1px rgba(0,0,0,0.5);
309+
310+ -webkit-transition-timing-function: ease;
311+ -webkit-transition-duration: 250ms;
312+ -webkit-transition-property: left, top;
313+}
314+
315+#sequence .slice {
316+ display: inline-block;
317+}
318+
319+
320+.frame {
321+ position: relative;
322+}
323+
324+.frame div {
325+ position: absolute;
326+ top: 0;
327+ right: 0;
328+ padding: 2px 6px;
329+
330+ cursor: default;
331+ text-align: right;
332+ font-family: "Ubuntu Mono";
333+ font-size: 14px;
334+ line-height: 1.6em;
335+ font-weight: bold;
336+ color: white;
337+ text-shadow: 0 0 2px black;
338+ /*
339+ -webkit-text-stroke: 1px #000;
340+ */
341+}
342+
343+.frame img {
344+ margin: 0;
345+ padding: 0;
346+ border: none;
347+ display: block;
348+ background-color: white;
349+}
350+
351+/*
352+Interesting 16:9 thumbnail sizes we might want to use:
353+ 112 63
354+ 128 72
355+ 144 81
356+ 160 90
357+ 176 99
358+ 192 108
359+ 208 117
360+ 224 126
361+*/
362+
363+#bucket img {
364+ width: 128px;
365+ height: 72px;
366+}
367+
368+#sequence img {
369+ width: 192px;
370+ height: 108px;
371+}
372+
373+
374+.indicator {
375+ height: 3px;
376+ /*background:-webkit-linear-gradient(top, #111, #222);*/
377+ background-color: #333;
378+ position: relative;
379+ pointer-events: none;
380+}
381+
382+.indicator div {
383+ position: absolute;
384+ top: 1px;
385+ bottom: 1px;
386+ left: 0%;
387+ right: 0%;
388+ background:-webkit-linear-gradient(top, #ddd, #fff);
389+}
390+
391+.indicator div:before{
392+ position:absolute;
393+ top:-1px;
394+ bottom:-1px;
395+ left:0px;
396+ width:1px;
397+ background:white;
398+ content:"";
399+}
400+
401+.indicator div:after{
402+ position:absolute;
403+ top:-1px;
404+ bottom:-1px;
405+ right:0px;
406+ width:1px;
407+ background:white;
408+ content:"";
409+}
410+
411+
412+
413+.over {
414+ margin-left: 194px;
415+}
416+
417+.over-right {
418+ margin-right: 194px;
419+}
420+
421+
422+.right {
423+ position: relative;
424+ left: 194px;
425+}
426+
427+.left {
428+ position: relative;
429+ left: -194px;
430+}
431+
432+.animated {
433+ position: relative;
434+ -webkit-transition: left 150ms ease;
435+}
436+
437+.grabbed {
438+ -webkit-transition-property: none !important;
439+ position: fixed !important;
440+}
441+
442+
443+#roughcut {
444+ position: fixed;
445+ top: 139px;
446+ bottom: 0;
447+ left: 0;
448+ right: 0;
449+ background-color: black;
450+ z-index: 4;
451+}
452+
453+
454+
455+#roughcut .frames {
456+ position: absolute;
457+ left: 0;
458+ right: 0;
459+ bottom: 30px;
460+ font-size: 0px;
461+ line-height: 0px;
462+}
463+
464+.videoframe {
465+ display: inline-block;
466+ width: 50%;
467+ position: relative;
468+}
469+
470+
471+.videoframe video {
472+ pointer-events: none;
473+ width: 100%;
474+ margin: 0;
475+ display: block;
476+}
477+
478+.videoframe div {
479+ position: absolute;
480+ top: 0;
481+ right: 0;
482+ padding: 2px 6px;
483+
484+ cursor: default;
485+ text-align: right;
486+ font-family: "Ubuntu Mono";
487+ font-size: 16px;
488+ line-height: 1.6em;
489+ font-weight: bold;
490+ color: white;
491+ text-shadow: 0 0 2px black;
492+}
493+
494+
495+
496+#roughcut .scrubber {
497+ position: absolute;
498+ bottom: 0;
499+ height: 30px;
500+ left: 0;
501+ right: 0;
502+ background-color: #999;
503+}
504+
505+
506+#roughcut .scrubber * {
507+ pointer-events: none;
508+}
509+
510+#roughcut .bar {
511+ position: absolute;
512+ top: 4px;
513+ bottom: 4px;
514+ background-color: #e81f3b;
515+}
516+
517+#roughcut .playhead {
518+ position: absolute;
519+ top: 0;
520+ bottom: 0;
521+ width: 2px;
522+ background-color: green;
523+ /*
524+ -webkit-transition: left 150ms linear;
525+ */
526+}
527+
528+#player {
529+ position: fixed;
530+ z-index: 20;
531+ background-color: black;
532+ top: 0;
533+ bottom: 246px;
534+ left: 0;
535+ right: 0;
536+}
537+
538+#player video {
539+ position: absolute;
540+ bottom: 0;
541+ width: 100%;
542+ height: 100%;
543+ margin: 0;
544+ pointer-events: none;
545+}
546+
547+#shortcuts{
548+ position:fixed;
549+ background-color: rgba(15, 15, 15, 0.85);
550+text-shadow: 2px 2px 3px rgba(255, 0, 0, 0.5);
551+ box-shadow: 0px 0px 10px 0px #e81f3b;
552+ border-radius: 12px;
553+ font-size: 16px;
554+ display:none;
555+ z-index:999;
556+}
557+#shortcuts table{
558+ padding: 20px;
559+}
560+#shortcuts td{
561+ padding-top :4px;
562+ padding-left: 15px;
563+ padding-right: 15px;
564+}
565+
566+.cont{
567+border-top: 1px solid rgb(102, 102, 102);
568+border-left: 1px solid rgb(85, 85, 85);
569+border-right: 1px solid rgb(68, 68, 68);
570+border-bottom: 1px solid rgb(51, 51, 51);
571+}
572+
573+#bucket .cont{
574+position: absolute;
575+min-width:128px;
576+height:147px;
577+}
578+
579+.cont ,.cont * {
580+-webkit-user-select: none;
581+}
582+
583+.cont{
584+/*-webkit-animation-duration: 0.2s;
585+-webkit-animation-iteration-count: 1;
586+-webkit-animation-timing-function: ease;*/
587+background:gray;
588+/*position: fixed;*/
589+}
590+
591+
592+/*#sequence .cont{
593+height: 219px !important;
594+}*/
595+
596+/*#sequence .comp div{
597+height: 109px;
598+}*/
599+
600+
601+.cont .slice{
602+position: relative !important;
603+float: left;
604+}
605+
606+#sequence .cont{
607+display: inline-block;
608+height:219px !important;
609+}
610+
611+.top{
612+height: 75px;
613+}
614+
615+.bot{
616+height: 72px;
617+}
618+
619+#sequence .top{
620+height: 111px;
621+}
622+
623+#sequence .bot{
624+height: 108px;
625+}
626+
627+.cont.selected{
628+border-color: rgb(232, 31, 59);
629+z-index: 1;
630+}
631+
632+@-webkit-keyframes enter{
633+from{-webkit-transform: translateX(-200px) translateY(-200px) scale(0.3);}
634+to{-webkit-transform: translateX(0px) translateY(0px) scale(1);}
635+}
636+
637+.selector{
638+border: 1px solid red;
639+position:absolute;
640+box-shadow: inset 0px 0px 10px rgba(255,0,0,0.5);
641+border-radius: 5px;
642+background-color: rgba(255, 0, 0, 0.13);
643+z-index: 10;
644+}
645
646=== added file 'ui/bucket2.html'
647--- ui/bucket2.html 1970-01-01 00:00:00 +0000
648+++ ui/bucket2.html 2012-08-14 17:10:21 +0000
649@@ -0,0 +1,94 @@
650+<!DOCTYPE html>
651+<html>
652+
653+<head>
654+ <link rel="stylesheet" href="/_apps/userwebkit/base.css" />
655+ <link rel="stylesheet" href="/_apps/dmedia/common.css" />
656+ <link rel="stylesheet" href="/_apps/dmedia/grid.css" />
657+ <link rel="stylesheet" href="bucket2.css" />
658+ <script src="/_apps/userwebkit/couch.js"></script>
659+ <script src="/_apps/userwebkit/base.js"></script>
660+ <script src="novacut2.js"></script>
661+ <script src="bucket2.js"></script>
662+</head>
663+
664+<body>
665+<div id="open_clips">Clips</div>
666+<div id="clips_outer" class="bar">
667+ <div id="clips_nav">
668+ <label>Dmedia Project: <select id="dmedia_project"></select></lable>
669+ </div>
670+ <div id="clips"></div>
671+</div>
672+
673+<div id="flyout_capture" class="hide">
674+<div id="flyout">
675+ <div class="fgrid_row">
676+ <a class="fgrid_4" href="projects.html"><strong>Projects</strong></a>
677+ <button class="fgrid_3" id="render-btn" onclick="UI.render()">Render</button>
678+ </div>
679+ <div class="fgrid_row">
680+ <a class="fgrid_4" href="projects.html">(Most recent project)</a>
681+ </div>
682+ <div class="fgrid_row">
683+ <a class="fgrid_4" href="projects.html">(2nd most recent project)</a>
684+ </div>
685+ <div class="fgrid_row">
686+ <a class="fgrid_4" href="projects.html">(3rd most recent project)</a>
687+ </div>
688+
689+ <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
690+ <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
691+ <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
692+ <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
693+ <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
694+ <div class="fgrid_row"><strong class="fgrid_2">.</strong></div>
695+
696+ <div class="fgrid_row">
697+ <strong class="fgrid_4 version">Novacut 12.08.0</strong>
698+<a onclick="showShort(event);" style="cursor: pointer; margin-right:135px;">Keyboard Shortcuts</a>
699+ <a href="http://www.kickstarter.com/projects/novacut/novacut-pro-video-editor/backers" class="fgrid_5">Thanks to our Kickstarter backers!</a>
700+ </div>
701+</div>
702+</div>
703+
704+<div id="bucket">
705+<div id="shortcuts" >
706+<table border="0">
707+ <tr>
708+ <td>D</td>
709+ <td>Duplicate slice</td>
710+ </tr>
711+ <tr>
712+ <td>Del</td>
713+ <td>Delete slice</td>
714+ </tr>
715+ <tr>
716+ <td>Space bar</td>
717+ <td>Play/Pause</td>
718+ </tr>
719+ <tr>
720+ <td>Esc</td>
721+ <td>Close</td>
722+ </tr>
723+ <tr>
724+ <td>Mouse wheel</td>
725+ <td>Move of one frame</td>
726+ </tr>
727+ <tr>
728+ <td>Shift+Mouse Wheel</td>
729+ <td>Move of ten frames</td>
730+ </tr>
731+</table>
732+</div>
733+</div>
734+<div id="sequence" clas="lower"></div>
735+<div id="roughcut" class="hide">
736+ <div class="grid_row">
737+ <button class="grid_2" id="close_roughcut">Done</button>
738+ <button class="grid_2" id="create_slice">New Slice</button>
739+ </div>
740+</div>
741+</body>
742+
743+</html>
744
745=== added file 'ui/bucket2.js'
746--- ui/bucket2.js 1970-01-01 00:00:00 +0000
747+++ ui/bucket2.js 2012-08-14 17:10:21 +0000
748@@ -0,0 +1,2656 @@
749+"use strict";
750+
751+
752+var Thumbs = {
753+ db: new couch.Database('thumbnails'),
754+
755+ docs: {},
756+
757+ has_frame: function(file_id, index) {
758+ if (!Thumbs.docs[file_id]) {
759+ try {
760+ Thumbs.docs[file_id] = Thumbs.db.get_sync(file_id);
761+ }
762+ catch (e) {
763+ return false;
764+ }
765+ }
766+ if (Thumbs.docs[file_id]._attachments[index]) {
767+ return true;
768+ }
769+ return false;
770+ },
771+
772+ q: {},
773+
774+ active: {},
775+
776+ need_init: true,
777+
778+ init: function() {
779+ console.assert(Thumbs.need_init);
780+ Thumbs.need_init = false;
781+ var ids = Object.keys(Thumbs.q);
782+ if (ids.length == 0) {
783+ Thumbs.frozen = false;
784+ return;
785+ }
786+ Thumbs.db.post(Thumbs.on_docs, {keys: ids}, '_all_docs', {include_docs: true});
787+ },
788+
789+ on_docs: function(req) {
790+ try {
791+ var rows = req.read().rows;
792+ rows.forEach(function(row) {
793+ var id = row.key;
794+ if (row.doc) {
795+ Thumbs.docs[id] = row.doc;
796+ }
797+ else {
798+ Thumbs.docs[id] = {'_id': id, '_attachments': {}};
799+ }
800+ });
801+ }
802+ catch (e) {
803+ var ids = Object.keys(Thumbs.q);
804+ ids.forEach(function(id) {
805+ Thumbs.docs[id] = {'_id': id, '_attachments': {}};
806+ });
807+ }
808+ Thumbs.unfreeze();
809+ },
810+
811+ enqueue: function(frame) {
812+ if (!Thumbs.q[frame.file_id]) {
813+ Thumbs.q[frame.file_id] = {};
814+ }
815+ Thumbs.q[frame.file_id][frame.key] = frame;
816+ },
817+
818+ frozen: false,
819+
820+ freeze: function() {
821+ Thumbs.frozen = true;
822+ },
823+
824+ unfreeze: function() {
825+ console.log('unfreeze');
826+ if (this.need_init) {
827+ this.init();
828+ return;
829+ }
830+ Thumbs.frozen = false;
831+ Thumbs.flush();
832+ },
833+
834+ flush: function() {
835+ if (Thumbs.frozen) {
836+ return;
837+ }
838+ var ids = Object.keys(Thumbs.q);
839+ if (ids.length == 0) {
840+ console.log('no thumbnails in queue');
841+ return;
842+ }
843+ while (ids.length > 0 && Object.keys(Thumbs.active).length <= 4) {
844+ var id = ids.shift();
845+ if (Thumbs.active[id]) {
846+ console.log('already waiting for ' + id);
847+ continue;
848+ }
849+ var frames = Thumbs.q[id];
850+ delete Thumbs.q[id];
851+
852+ var needed = [];
853+ var key, frame;
854+ for (key in frames) {
855+ frame = frames[key];
856+ if (Thumbs.has_frame(id, frame.index)) {
857+ frame.request_thumbnail.call(frame);
858+ }
859+ else {
860+ needed.push(frame.index);
861+ }
862+ }
863+ if (needed.length > 0) {
864+ Thumbs.active[id] = frames;
865+ Hub.send('thumbnail', id, needed);
866+ }
867+ }
868+ },
869+
870+ on_thumbnail_finished: function(file_id) {
871+ if (!Thumbs.active[file_id]) {
872+ return;
873+ }
874+ var frames = Thumbs.active[file_id];
875+ delete Thumbs.active[file_id];
876+ Thumbs.docs[file_id] = Thumbs.db.get_sync(file_id);
877+
878+ var key, frame;
879+ for (key in frames) {
880+ frame = frames[key];
881+ if (Thumbs.has_frame(file_id, frame.index)) {
882+ frame.request_thumbnail.call(frame);
883+ }
884+ }
885+ Thumbs.flush();
886+ },
887+}
888+
889+Hub.connect('thumbnail_finished', Thumbs.on_thumbnail_finished);
890+
891+
892+function $halt(event) {
893+ event.preventDefault();
894+ event.stopPropagation();
895+}
896+
897+
898+function $unparent(id) {
899+ var child = $(id);
900+ if (child && child.parentNode) {
901+ child.parentNode.removeChild(child);
902+ }
903+ return child;
904+}
905+
906+
907+function $position(element) {
908+ element = $(element);
909+ var pos = {
910+ left: element.offsetLeft,
911+ top: element.offsetTop,
912+ width: element.offsetWidth,
913+ height: element.offsetHeight,
914+ };
915+ while (element.offsetParent) {
916+ element = element.offsetParent;
917+ pos.left += (element.offsetLeft - element.scrollLeft);
918+ pos.top += (element.offsetTop - element.scrollTop);
919+ }
920+ pos.right = pos.left + pos.width;
921+ pos.bottom = pos.top + pos.height;
922+ return pos;
923+}
924+
925+
926+function $hscroll(child, center) {
927+ child = $(child);
928+ if (!child.parentNode) {
929+ return;
930+ }
931+ var parent = child.parentNode
932+ //var mid = child.offsetLeft + (child.offsetWidth - parent.clientWidth) / 2;
933+ if (child.offsetLeft < parent.scrollLeft) {
934+ parent.scrollLeft = child.offsetLeft;
935+ }
936+ else if (child.offsetLeft + child.offsetWidth > parent.scrollLeft + parent.clientWidth) {
937+ parent.scrollLeft = child.offsetLeft + child.offsetWidth - parent.clientWidth;
938+ }
939+}
940+
941+
942+
943+var DragEvent = function(event, element) {
944+ $halt(event);
945+ this.x = event.clientX;
946+ this.y = event.clientY;
947+ this.ox = this.x;
948+ this.oy = this.y;
949+ this.dx = 0;
950+ this.dy = 0;
951+
952+ if (element) {
953+ var pos = $position(element);
954+ this.offsetX = this.x - pos.left;
955+ this.offsetY = this.y - pos.top;
956+ }
957+ else {
958+ this.offsetX = event.offsetX;
959+ this.offsetY = event.offsetY;
960+ }
961+
962+ this.ondragstart = null;
963+ this.ondragcancel = null;
964+ this.ondrag = null;
965+ this.ondrop = null;
966+ this.started = false;
967+
968+ var self = this;
969+ var tmp = {};
970+ tmp.on_mousemove = function(event) {
971+ self.on_mousemove(event);
972+ }
973+ tmp.on_mouseup = function(event) {
974+ window.removeEventListener('mousemove', tmp.on_mousemove);
975+ window.removeEventListener('mouseup', tmp.on_mouseup);
976+ self.on_mouseup(event);
977+ }
978+ window.addEventListener('mousemove', tmp.on_mousemove);
979+ window.addEventListener('mouseup', tmp.on_mouseup);
980+}
981+DragEvent.prototype = {
982+ update: function(event) {
983+ $halt(event);
984+ this.event = event;
985+ var html = document.body.parentNode;
986+ this.x = Math.max(0, Math.min(event.clientX, html.clientWidth));
987+ this.y = Math.max(0, Math.min(event.clientY, html.clientHeight));
988+// this.x = event.clientX;
989+// this.y = event.clientY;
990+ this.dx = this.x - this.ox;
991+ this.dy = this.y - this.oy;
992+ },
993+
994+ on_mousemove: function(event) {
995+ this.update(event);
996+ if (!this.started) {
997+ if (Math.max(Math.abs(this.dx), Math.abs(this.dy)) > 3) {
998+ this.started = true;
999+ if (this.ondragstart) {
1000+ this.ondragstart(this);
1001+ }
1002+ }
1003+ else {
1004+ return;
1005+ }
1006+ }
1007+ if (this.ondrag) {
1008+ this.ondrag(this);
1009+ }
1010+ },
1011+
1012+ on_mouseup: function(event) {
1013+ if (!this.started) {
1014+ if (this.ondragcancel) {
1015+ this.ondragcancel(this);
1016+ }
1017+ return;
1018+ }
1019+ if (this.ondrop) {
1020+ this.update(event);
1021+ this.ondrop(this);
1022+ }
1023+ },
1024+}
1025+
1026+
1027+var Frame = function(file_id, key) {
1028+ this.file_id = file_id;
1029+ this.key = key;
1030+ this.index = null;
1031+ this.element = $el('div', {'class': 'frame'});
1032+ this.img = $el('img');
1033+ this.element.appendChild(this.img);
1034+ this.info = $el('div');
1035+ this.element.appendChild(this.info);
1036+}
1037+Frame.prototype = {
1038+ destroy: function() {
1039+ $unparent(this.info);
1040+ $unparent(this.img);
1041+ $unparent(this.element);
1042+ delete this.info;
1043+ delete this.img;
1044+ delete this.element;
1045+ },
1046+
1047+ set_index: function(index) {
1048+ if (index === this.index) {
1049+ return;
1050+ }
1051+ this.index = index;
1052+ this.info.textContent = index + 1;
1053+ Thumbs.enqueue(this);
1054+ },
1055+
1056+ request_thumbnail: function() {
1057+ this.img.src = Thumbs.db.att_url(this.file_id, this.index.toString());
1058+ },
1059+
1060+}
1061+
1062+
1063+function SliceIndicator() {
1064+ this.element = $el('div', {'class': 'indicator'});
1065+ this.bar = $el('div');
1066+ this.element.appendChild(this.bar);
1067+}
1068+SliceIndicator.prototype = {
1069+ destroy: function() {
1070+ $unparent(this.bar);
1071+ $unparent(this.element);
1072+ delete this.bar;
1073+ delete this.element;
1074+ },
1075+
1076+ update: function(start, stop, count) {
1077+ var left = 100 * start / count;
1078+ var right = 100 - (100 * stop / count);
1079+ this.bar.style.left = left.toFixed(1) + '%';
1080+ this.bar.style.right = right.toFixed(1) + '%';
1081+ },
1082+}
1083+
1084+
1085+function wheel_delta(event) {
1086+ var delta = event.wheelDeltaY;
1087+ if (delta == 0) {
1088+ return 0;
1089+ }
1090+ var scale = (event.shiftKey) ? -10 : -1;
1091+ return scale * (delta / Math.abs(delta));
1092+}
1093+
1094+function addCont(){
1095+ var doc = create_cont();
1096+ console.log(UI.session.save(doc));
1097+ console.log(doc);
1098+ var cont = new Cont(UI.session,doc);
1099+ UI.bucket.appendChild(cont.element);
1100+ UI.sequence.do_reorder();
1101+}
1102+
1103+//TODO:HERE
1104+var Cont = function(session,doc){
1105+ session.subscribe(doc._id, this.on_change, this);
1106+ UI.conts.push(this);
1107+ this.doc = doc;
1108+ this.id = doc._id;
1109+ this.session=session;
1110+ this.element = $el('div', {'class': 'cont','id': doc._id});
1111+ this.list = $el('ul', {'style': 'display = "none"'});
1112+ this.comp = $el('div', {'class': 'comp'})
1113+ this.element.appendChild(this.list);
1114+ this.element.appendChild(this.comp);
1115+ this.element.onmousedown = $bind(this.on_mousedown, this);
1116+ this.element.ondblclick = $bind(this.on_dblclick, this);
1117+ this.element.onclick = $bind(this.on_click, this);
1118+ this.expanded = true;
1119+ this.over = null;
1120+ this.x = 150;
1121+ this.y = 150;
1122+ this.width = 192 + 2;
1123+ this.threshold = this.width * 0.65;
1124+ this.on_change(doc);
1125+ this.close();
1126+}
1127+
1128+Cont.prototype = {
1129+ set x(value) {
1130+ if (typeof value == 'number') {
1131+ this.element.style.left = value + 'px';
1132+ }
1133+ else {
1134+ this.element.style.left = null;
1135+ }
1136+ },
1137+
1138+ set y(value) {
1139+ if (typeof value == 'number') {
1140+ this.element.style.top = value + 'px';
1141+ }
1142+ else {
1143+ this.element.style.top = null;
1144+ }
1145+ },
1146+
1147+ get x() {
1148+ if(this.element)return parseInt(this.element.style.left);
1149+ else return -1;
1150+ },
1151+
1152+ get y() {
1153+ if(this.element)return parseInt(this.element.style.top);
1154+ else return -1;
1155+ },
1156+
1157+ get inbucket() {
1158+ return this.element.parentNode.id == 'bucket';
1159+ },
1160+
1161+ get frombucket() {
1162+ return this.parent.id == 'bucket';
1163+ },
1164+
1165+ save: function(){
1166+ this.doc.node.src = Array();
1167+ for(var a in this.list.childNodes){
1168+ if(this.list.childNodes[a].id)this.doc.node.src.push(this.list.childNodes[a].id);
1169+ }
1170+ this.session.save(this.doc,true);
1171+ },
1172+
1173+ refresh: function(){
1174+ this.comp.innerHTML = '<div class="top">' + this.list.firstChild.firstChild.firstChild.outerHTML + '</div>';
1175+ this.comp.innerHTML += '<div class="bot">'+ this.list.lastChild.lastChild.firstChild.outerHTML + '</div>';
1176+ },
1177+
1178+ expand: function(){
1179+ this.expanded = true;
1180+ this.list.style.display = 'block';
1181+ this.comp.style.display = 'none';
1182+ },
1183+ close: function(){
1184+ if(!this.expanded)return;
1185+ this.expanded = false;
1186+ console.log("close");
1187+ this.comp.innerHTML = "";
1188+ var len = this.list.childNodes.length;
1189+ console.log(len);
1190+ if(len){
1191+ this.refresh();
1192+ }else{
1193+ if(len == 1){
1194+ console.log("save frist child");
1195+ var el = this.list.childNodes[0];
1196+ el.style.top = this.y + 'px';
1197+ el.style.left = this.x + 'px';
1198+ el.style.marginLeft = '0px';
1199+ el.style.marginRight = '0px';
1200+ $unparent(el);
1201+ UI.bucket.appendChild(el);
1202+ }
1203+ UI.bucket.removeChild(this.element);
1204+ UI.sequence.do_reorder();
1205+ for(var a in UI.conts)if(UI.conts[a].id == this.id)UI.conts.splice(a,1);
1206+ return;
1207+ }
1208+ this.list.style.display = 'none';
1209+ this.comp.style.display = 'block';
1210+
1211+ },
1212+ reset_margin: function(){
1213+ for(var a = 0;a<this.list.childNodes.length;a++){
1214+ this.list.childNodes[a].style.marginLeft = 0 + 'px';
1215+ this.list.childNodes[a].style.marginRight = 0 + 'px';
1216+ }
1217+ },
1218+ slice_on: function(x){
1219+ if(this.list.childNodes.length == 0){
1220+ this.pos = -1;
1221+ return;
1222+ }
1223+ this.reset_margin();
1224+ this.pos = parseInt(x/130);
1225+ if(this.pos >= this.list.childNodes.length) this.list.lastChild.style.marginRight = 130 + 'px';
1226+ else if(this.list.childNodes[this.pos].classList[1] != 'selected') this.list.childNodes[this.pos].style.marginLeft = 130 + 'px';
1227+ },
1228+ on_dblclick: function(event) {
1229+ this.expand();
1230+ },
1231+ on_mousedown: function(event) {
1232+ UI.select(this.doc._id);
1233+ if (UI.player.active) {
1234+ UI.player.hold();
1235+ }
1236+ this.pos = $position(this.element);
1237+ this.dnd = new DragEvent(event, this.element);
1238+ this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
1239+ this.dnd.ondragstart = $bind(this.on_dragstart, this);
1240+ this.dnd.ondrag = $bind(this.on_drag, this);
1241+ this.dnd.ondrop = $bind(this.on_drop, this);
1242+ },
1243+
1244+ on_dragcancel: function(dnd) {
1245+ console.log('dragcancel');
1246+ if (UI.player.active) {
1247+ UI.player.resume();
1248+ }
1249+ this.stop_scrolling();
1250+ if (this.inbucket && UI.bucket.lastChild != this.element) {
1251+ console.log('moving to end of bucket');
1252+ $unparent(this.element);
1253+ UI.bucket.appendChild(this.element);
1254+ UI.sequence.do_reorder();
1255+ }
1256+ },
1257+
1258+ on_dragstart: function(dnd) {
1259+ console.log('dragstart');
1260+ this.offsetX = this.dnd.offsetX;
1261+ this.offsetY = this.dnd.offsetY;
1262+ this.offsetWidth = this.element.offsetWidth;
1263+ this.offsetHeight = this.element.offsetHeight;
1264+
1265+ this.parent = this.element.parentNode;
1266+ this.x = dnd.x - this.offsetX;
1267+ if (this.inbucket) {
1268+ this.y = dnd.y - this.offsetY;
1269+ }
1270+ else {
1271+ this.nextSibling = this.element.nextSibling;
1272+ if (this.element.nextSibling) {
1273+ this.over = this.element.nextSibling;
1274+ this.over.classList.add('over');
1275+ }
1276+ else if (this.element.previousSibling) {
1277+ this.over = this.element.previousSibling;
1278+ this.over.classList.add('over-right');
1279+ }
1280+ this.target = this.element;
1281+ var seq = this.element.parentNode;
1282+ var i, child;
1283+ for (i=0; i<seq.children.length; i++) {
1284+ child = seq.children[i];
1285+ if (child == this.element) {
1286+ this.i = i;
1287+ this.orig_i = i;
1288+ }
1289+ }
1290+ this.y = UI.sequence.top - 14;
1291+ }
1292+ this.element.classList.add('grabbed');
1293+ },
1294+
1295+ on_drag: function(dnd) {
1296+ var top = UI.sequence.top;
1297+ var height = this.element.clientHeight;
1298+ var y = dnd.y - this.offsetY;
1299+ var f = 0.65;
1300+ if (this.inbucket) {
1301+ if (y > top - height * (1 - f)) {
1302+ this.move_into_sequence(dnd);
1303+ }
1304+ }
1305+ else {
1306+ if (y < top - height * f) {
1307+ this.move_into_bucket(dnd);
1308+ }
1309+ }
1310+ if (this.inbucket) {
1311+ this.on_mousemove_bucket(dnd);
1312+ }
1313+ else {
1314+ this.on_mousemove_sequence(dnd);
1315+ }
1316+ this.on_mousemove_bucket(dnd);
1317+ },
1318+ on_click: function(dnd){
1319+ dnd.stopPropagation();
1320+ },
1321+
1322+ on_drop: function(dnd) {
1323+ this.stop_scrolling();
1324+ this.element.classList.remove('grabbed');
1325+ this.clear_over();
1326+ UI.sequence.reset();
1327+ if (this.inbucket) {
1328+ if (UI.player.active) {
1329+ UI.player.hide();
1330+ }
1331+ if (UI.bucket.lastChild != this.element) {
1332+ $unparent(this.element);
1333+ UI.bucket.appendChild(this.element);
1334+ }
1335+ var pos = $position(UI.bucket);
1336+ this.x = Math.max(0, dnd.x - this.offsetX - pos.left);
1337+ this.y = Math.max(0, dnd.y - this.offsetY - pos.top);
1338+ }
1339+ else {
1340+ console.log(this.orig_i + ' => ' + this.i);
1341+ this.x = null;
1342+ this.y = null;
1343+ var seq = $('sequence');
1344+ if (this.i == this.orig_i) {
1345+ console.assert(seq.children[this.i] == this.element);
1346+ }
1347+ else {
1348+ if (this.i < this.orig_i) {
1349+ var ref = seq.children[this.i];
1350+ }
1351+ else {
1352+ var ref = seq.children[this.i].nextSibling;
1353+ }
1354+ $unparent(this.element);
1355+ seq.insertBefore(this.element, ref);
1356+ }
1357+ }
1358+ UI.sequence.do_reorder();
1359+ if (UI.player.active) {
1360+ UI.player.resume();
1361+ }
1362+ },
1363+ on_mousemove_bucket: function(dnd) {
1364+ this.x = dnd.x - this.offsetX;
1365+ this.y = dnd.y - this.offsetY;
1366+ },
1367+ move_into_bucket: function(dnd) {
1368+ if (UI.player.active) {
1369+ UI.player.soft_hide();
1370+ }
1371+ this.stop_scrolling();
1372+ $unparent(this.element);
1373+ $('bucket').appendChild(this.element);
1374+ if (this.frombucket) {
1375+ this.clear_over();
1376+ UI.sequence.reset();
1377+ }
1378+ this.update_offset();
1379+ },
1380+ on_mousemove_sequence: function(dnd) {
1381+ var mid_x = dnd.x - this.offsetX + (this.element.offsetWidth / 2);
1382+ var width = UI.sequence.element.clientWidth;
1383+ var left = Math.min(dnd.x, mid_x);
1384+ var right = Math.max(dnd.x, mid_x);
1385+
1386+ if (this.scrolling) {
1387+ if (left > 0 && right < width) {
1388+ this.stop_scrolling();
1389+ }
1390+ else {
1391+ return;
1392+ }
1393+ }
1394+ else {
1395+ if (left <= 0) {
1396+ this.start_scrolling('left');
1397+ }
1398+ else if (right >= width) {
1399+ this.start_scrolling('right');
1400+ }
1401+ }
1402+ this.do_mousemove_sequence();
1403+ },
1404+
1405+ do_mousemove_sequence: function() {
1406+ var x = this.dnd.x - this.offsetX;
1407+ var parent = UI.sequence.element;
1408+ var scroll_x = x + parent.scrollLeft;
1409+ var ix = this.i * this.width;
1410+ var dx = scroll_x - ix;
1411+
1412+ this.x = x;
1413+ this.y = UI.sequence.top - 14;
1414+
1415+ if (dx < -this.threshold) {
1416+ this.shift_right();
1417+ }
1418+ else if (dx > this.threshold) {
1419+ this.shift_left();
1420+ }
1421+ },
1422+ move_into_sequence: function(dnd) {
1423+ if (UI.player.active) {
1424+ UI.player.soft_show();
1425+ }
1426+ if (!this.frombucket) {
1427+ this.clear_over();
1428+ UI.sequence.reset();
1429+ }
1430+ var seq = UI.sequence.element;
1431+ if (seq.children.length == 0) {
1432+ this.i = 0;
1433+ this.orig_i = 0;
1434+ this.target = this.element;
1435+ seq.appendChild(this.element);
1436+ this.update_offset();
1437+ return;
1438+ }
1439+
1440+ var x = this.pos.left + dnd.dx;
1441+
1442+ var scroll_x = x + seq.scrollLeft;
1443+
1444+ var unclamped = Math.round(scroll_x / this.width);
1445+ this.i = Math.max(0, Math.min(unclamped, seq.children.length));
1446+ this.orig_i = this.i;
1447+ if (this.i == seq.children.length) {
1448+ this.over = seq.children[this.i - 1];
1449+ this.over.classList.add('over-right');
1450+ }
1451+ else {
1452+ this.over = seq.children[this.i];
1453+ this.over.classList.add('over');
1454+ }
1455+
1456+ var ref = seq.children[this.i];
1457+ $unparent(this.element);
1458+ seq.insertBefore(this.element, ref);
1459+ if (!ref) {
1460+ seq.scrollLeft += this.width;
1461+ }
1462+ this.target = this.element;
1463+ this.update_offset();
1464+ },
1465+ update_offset: function() {
1466+ this.offsetX = Math.round(this.dnd.offsetX * this.element.offsetWidth / this.offsetWidth);
1467+ this.offsetY = Math.round(this.dnd.offsetY * this.element.offsetHeight / this.offsetHeight);
1468+ },
1469+ clear_over: function() {
1470+ if (this.over) {
1471+ this.over.classList.remove('over');
1472+ this.over.classList.remove('over-right');
1473+ this.over = null;
1474+ }
1475+ UI.animate(null);
1476+ },
1477+
1478+ start_scrolling: function(direction) {
1479+ this.direction = direction;
1480+ this.scrolling = true;
1481+ this.interval_id = setInterval($bind(this.on_interval, this), 300);
1482+ },
1483+ stop_scrolling: function() {
1484+ this.scrolling = false;
1485+ clearInterval(this.interval_id);
1486+ this.interval_id = null;
1487+ },
1488+ on_interval: function() {
1489+ var d = (this.direction == 'left') ? -1 : 1;
1490+ UI.sequence.element.scrollLeft += (d * this.width);
1491+ this.do_mousemove_sequence();
1492+ },
1493+ shift_right: function() {
1494+ if (!this.target.previousSibling) {
1495+ return;
1496+ }
1497+ this.i -= 1;
1498+ if (this.target.classList.contains('left')) {
1499+ this.target.classList.remove('left');
1500+ UI.animate(this.target);
1501+ }
1502+ else {
1503+ this.target.previousSibling.classList.add('right');
1504+ UI.animate(this.target.previousSibling);
1505+ }
1506+ this.target = this.target.previousSibling;
1507+ },
1508+
1509+ shift_left: function() {
1510+ if (!this.target.nextSibling) {
1511+ return;
1512+ }
1513+ this.i += 1;
1514+ if (this.target.classList.contains('right')) {
1515+ this.target.classList.remove('right');
1516+ UI.animate(this.target);
1517+ }
1518+ else {
1519+ this.target.nextSibling.classList.add('left');
1520+ UI.animate(this.target.nextSibling);
1521+ }
1522+ this.target = this.target.nextSibling;
1523+
1524+ },
1525+ destroy: function() {
1526+ $unparent(this.element);
1527+ delete this.element;
1528+ },
1529+ on_change: function(doc) {
1530+ if (doc._deleted) {
1531+ console.log('deleted ' + doc._id);
1532+ this.destroy();
1533+ UI.sequence.do_reorder();
1534+ return;
1535+ }
1536+ this.doc = doc;
1537+ for(var a in doc.node.src){
1538+ console.log(doc.node.src[a]);
1539+ var tdoc = UI.session.get_doc(doc.node.src[a]);
1540+ var slice = new Slice(UI.session, tdoc);
1541+ slice.start.request_thumbnail();
1542+ slice.end.request_thumbnail();
1543+ $unparent(slice.element);
1544+ this.list.appendChild(slice.element);
1545+ }
1546+ },
1547+}
1548+
1549+var Slice = function(session, doc) {
1550+ session.subscribe(doc._id, this.on_change, this);
1551+ this.session = session;
1552+ this.element = $el('div', {'class': 'slice', 'id': doc._id});
1553+
1554+ var file_id = doc.node.src;
1555+
1556+ this.start = new Frame(file_id, doc._id + '.start');
1557+ this.element.appendChild(this.start.element);
1558+
1559+ this.indicator = new SliceIndicator();
1560+ this.element.appendChild(this.indicator.element);
1561+
1562+ this.end = new Frame(file_id, doc._id + '.end');
1563+ this.element.appendChild(this.end.element);
1564+
1565+ this.start.element.onmousewheel = $bind(this.on_mousewheel_start, this);
1566+ this.end.element.onmousewheel = $bind(this.on_mousewheel_end, this);
1567+ this.element.onmousedown = $bind(this.on_mousedown, this);
1568+ this.element.ondblclick = $bind(this.on_dblclick, this);
1569+
1570+ this.frames = session.get_doc(doc.node.src).duration.frames;
1571+ this.on_change(doc);
1572+
1573+ this.i = null;
1574+ this.over = null;
1575+ this.width = 192 + 2;
1576+ this.threshold = this.width * 0.65;
1577+ this.timeout_id = null;
1578+
1579+ this.oncont = false;
1580+ this.contn = 0;
1581+ this.id = doc._id;
1582+ UI.slices.push(this);
1583+}
1584+Slice.prototype = {
1585+ destroy: function() {
1586+ this.start.destroy();
1587+ delete this.start;
1588+ this.end.destroy();
1589+ delete this.end;
1590+ this.indicator.destroy();
1591+ delete this.indicator;
1592+ $unparent(this.element);
1593+ delete this.element;
1594+ },
1595+
1596+ set x(value) {
1597+ if (typeof value == 'number') {
1598+ this.element.style.left = value + 'px';
1599+ }
1600+ else {
1601+ this.element.style.left = null;
1602+ }
1603+ },
1604+
1605+ set y(value) {
1606+ if (typeof value == 'number') {
1607+ this.element.style.top = value + 'px';
1608+ }
1609+ else {
1610+ this.element.style.top = null;
1611+ }
1612+ },
1613+
1614+ get x() {
1615+ if(this.element)return parseInt(this.element.style.left);
1616+ else return -1;
1617+ },
1618+
1619+ get y() {
1620+ return parseInt(this.element.style.top);
1621+ },
1622+
1623+ get inbucket() {
1624+ return this.element.parentNode.id == 'bucket';
1625+ },
1626+
1627+ get frombucket() {
1628+ return this.parent.id == 'bucket';
1629+ },
1630+
1631+ on_change: function(doc) {
1632+ if (doc._deleted) {
1633+ console.log('deleted ' + doc._id);
1634+ this.destroy();
1635+ UI.sequence.do_reorder();
1636+ return;
1637+ }
1638+ this.doc = doc;
1639+ var node = doc.node;
1640+ this.start.set_index(node.start.frame);
1641+ this.end.set_index(node.stop.frame - 1);
1642+ this.indicator.update(node.start.frame, node.stop.frame, this.frames);
1643+ Thumbs.flush();
1644+ },
1645+
1646+ reset_adjustment_ux: function() {
1647+ if (this.timeout_id == null) {
1648+ UI.player.hold();
1649+ UI.select(this.doc._id);
1650+ }
1651+ clearTimeout(this.timeout_id);
1652+ this.timeout_id = setTimeout($bind(this.on_timeout, this), 750);
1653+ },
1654+
1655+ on_timeout: function() {
1656+ console.log('timeout');
1657+ this.timeout_id = null;
1658+ UI.player.resume();
1659+ },
1660+
1661+ on_mousewheel_start: function(event) {
1662+ $halt(event);
1663+ if (UI.player.active) {
1664+ this.reset_adjustment_ux();
1665+ }
1666+ var delta = wheel_delta(event);
1667+ var start = this.doc.node.start.frame;
1668+ var stop = this.doc.node.stop.frame;
1669+ var proposed = Math.max(0, Math.min(start + delta, stop - 1));
1670+ if (start != proposed) {
1671+ this.doc.node.start.frame = proposed;
1672+ this.session.save(this.doc);
1673+ this.session.delayed_commit();
1674+ }
1675+ },
1676+
1677+ on_mousewheel_end: function(event) {
1678+ $halt(event);
1679+ if (UI.player.active) {
1680+ this.reset_adjustment_ux();
1681+ }
1682+ var delta = wheel_delta(event);
1683+ var start = this.doc.node.start.frame;
1684+ var stop = this.doc.node.stop.frame;
1685+ var proposed = Math.max(start + 1, Math.min(stop + delta, this.frames));
1686+ if (stop != proposed) {
1687+ this.doc.node.stop.frame = proposed;
1688+ this.session.save(this.doc);
1689+ this.session.delayed_commit();
1690+ }
1691+ },
1692+
1693+ on_mousedown: function(event) {
1694+ UI.select(this.doc._id);
1695+ if (UI.player.active) {
1696+ UI.player.hold();
1697+ }
1698+ this.pos = $position(this.element);
1699+ this.dnd = new DragEvent(event, this.element);
1700+ this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
1701+ this.dnd.ondragstart = $bind(this.on_dragstart, this);
1702+ this.dnd.ondrag = $bind(this.on_drag, this);
1703+ this.dnd.ondrop = $bind(this.on_drop, this);
1704+ },
1705+
1706+ on_dblclick: function(event) {
1707+ $halt(event);
1708+ if (UI.player.active) {
1709+ return;
1710+ }
1711+ UI.edit_slice(this.doc);
1712+ },
1713+
1714+ on_dragcancel: function(dnd) {
1715+ console.log('dragcancel');
1716+ if (UI.player.active) {
1717+ UI.player.resume();
1718+ }
1719+ this.stop_scrolling();
1720+ if (this.inbucket && UI.bucket.lastChild != this.element) {
1721+ console.log('moving to end of bucket');
1722+ $unparent(this.element);
1723+ UI.bucket.appendChild(this.element);
1724+ UI.sequence.do_reorder();
1725+ }
1726+ },
1727+
1728+ on_dragstart: function(dnd) {
1729+ console.log('dragstart');
1730+ UI.dragging = true;
1731+ this.offsetX = this.dnd.offsetX;
1732+ this.offsetY = this.dnd.offsetY;
1733+ this.offsetWidth = this.element.offsetWidth;
1734+ this.offsetHeight = this.element.offsetHeight;
1735+
1736+ this.parent = this.element.parentNode;
1737+ this.x = dnd.x - this.offsetX;
1738+ if (this.inbucket) {
1739+ this.y = dnd.y - this.offsetY;
1740+ }
1741+ else {
1742+ this.nextSibling = this.element.nextSibling;
1743+ if (this.element.nextSibling) {
1744+ this.over = this.element.nextSibling;
1745+ this.over.classList.add('over');
1746+ }
1747+ else if (this.element.previousSibling) {
1748+ this.over = this.element.previousSibling;
1749+ this.over.classList.add('over-right');
1750+ }
1751+ this.target = this.element;
1752+ var seq = this.element.parentNode;
1753+ var i, child;
1754+ for (i=0; i<seq.children.length; i++) {
1755+ child = seq.children[i];
1756+ if (child == this.element) {
1757+ this.i = i;
1758+ this.orig_i = i;
1759+ }
1760+ }
1761+ this.y = UI.sequence.top - 14;
1762+ }
1763+ this.element.classList.add('grabbed');
1764+ },
1765+
1766+ on_drag: function(dnd) {
1767+ var top = UI.sequence.top;
1768+ var height = this.element.clientHeight;
1769+ var y = dnd.y - this.offsetY;
1770+ var x = dnd.x - this.offsetX;
1771+ var f = 0.65;
1772+ //if(this.oncont)UI.conts[oncontn].reset_margin();
1773+ this.oncont = false;
1774+ for(var a in UI.conts){
1775+ var b = UI.conts[a];
1776+ if(b.element && b.x < x && b.y < y && b.x+parseInt(b.element.offsetWidth) > x && b.y+parseInt(b.element.offsetHeight) > y){
1777+ b.expand();
1778+ this.oncont = true;
1779+ this.oncontn = a;
1780+ }
1781+ else b.close();
1782+ }
1783+
1784+ if(this.oncont) UI.conts[this.oncontn].slice_on(x-UI.conts[this.oncontn].x);
1785+
1786+ if (this.inbucket) {
1787+ if (y > top - height * (1 - f)) {
1788+ this.move_into_sequence(dnd);
1789+ }
1790+ }
1791+ else {
1792+ if (y < top - height * f) {
1793+ this.move_into_bucket(dnd);
1794+ }
1795+ }
1796+ if (this.inbucket) {
1797+ this.on_mousemove_bucket(dnd);
1798+ }
1799+ else {
1800+ this.on_mousemove_sequence(dnd);
1801+ }
1802+ },
1803+
1804+ move_into_sequence: function(dnd) {
1805+ if (UI.player.active) {
1806+ UI.player.soft_show();
1807+ }
1808+ if (!this.frombucket) {
1809+ this.clear_over();
1810+ UI.sequence.reset();
1811+ }
1812+ var seq = UI.sequence.element;
1813+ if (seq.children.length == 0) {
1814+ this.i = 0;
1815+ this.orig_i = 0;
1816+ this.target = this.element;
1817+ seq.appendChild(this.element);
1818+ this.update_offset();
1819+ return;
1820+ }
1821+
1822+ var x = this.pos.left + dnd.dx;
1823+
1824+ var scroll_x = x + seq.scrollLeft;
1825+
1826+ var unclamped = Math.round(scroll_x / this.width);
1827+ this.i = Math.max(0, Math.min(unclamped, seq.children.length));
1828+ this.orig_i = this.i;
1829+ if (this.i == seq.children.length) {
1830+ this.over = seq.children[this.i - 1];
1831+ this.over.classList.add('over-right');
1832+ }
1833+ else {
1834+ this.over = seq.children[this.i];
1835+ this.over.classList.add('over');
1836+ }
1837+
1838+ var ref = seq.children[this.i];
1839+ $unparent(this.element);
1840+ seq.insertBefore(this.element, ref);
1841+ if (!ref) {
1842+ seq.scrollLeft += this.width;
1843+ }
1844+ this.target = this.element;
1845+ this.update_offset();
1846+ },
1847+
1848+ move_into_bucket: function(dnd) {
1849+ if (UI.player.active) {
1850+ UI.player.soft_hide();
1851+ }
1852+ this.stop_scrolling();
1853+ $unparent(this.element);
1854+ $('bucket').appendChild(this.element);
1855+ if (this.frombucket) {
1856+ this.clear_over();
1857+ UI.sequence.reset();
1858+ }
1859+ this.update_offset();
1860+ },
1861+
1862+ update_offset: function() {
1863+ this.offsetX = Math.round(this.dnd.offsetX * this.element.offsetWidth / this.offsetWidth);
1864+ this.offsetY = Math.round(this.dnd.offsetY * this.element.offsetHeight / this.offsetHeight);
1865+ },
1866+
1867+ on_mousemove_bucket: function(dnd) {
1868+ this.x = dnd.x - this.offsetX;
1869+ this.y = dnd.y - this.offsetY;
1870+ },
1871+
1872+ start_scrolling: function(direction) {
1873+ this.direction = direction;
1874+ this.scrolling = true;
1875+ this.interval_id = setInterval($bind(this.on_interval, this), 300);
1876+ },
1877+
1878+ stop_scrolling: function() {
1879+ this.scrolling = false;
1880+ clearInterval(this.interval_id);
1881+ this.interval_id = null;
1882+ },
1883+
1884+ on_interval: function() {
1885+ var d = (this.direction == 'left') ? -1 : 1;
1886+ UI.sequence.element.scrollLeft += (d * this.width);
1887+ this.do_mousemove_sequence();
1888+ },
1889+
1890+ on_mousemove_sequence: function(dnd) {
1891+ var mid_x = dnd.x - this.offsetX + (this.element.offsetWidth / 2);
1892+ var width = UI.sequence.element.clientWidth;
1893+ var left = Math.min(dnd.x, mid_x);
1894+ var right = Math.max(dnd.x, mid_x);
1895+
1896+ if (this.scrolling) {
1897+ if (left > 0 && right < width) {
1898+ this.stop_scrolling();
1899+ }
1900+ else {
1901+ return;
1902+ }
1903+ }
1904+ else {
1905+ if (left <= 0) {
1906+ this.start_scrolling('left');
1907+ }
1908+ else if (right >= width) {
1909+ this.start_scrolling('right');
1910+ }
1911+ }
1912+ this.do_mousemove_sequence();
1913+ },
1914+
1915+ do_mousemove_sequence: function() {
1916+ var x = this.dnd.x - this.offsetX;
1917+ var parent = UI.sequence.element;
1918+ var scroll_x = x + parent.scrollLeft;
1919+ var ix = this.i * this.width;
1920+ var dx = scroll_x - ix;
1921+
1922+ this.x = x;
1923+ this.y = UI.sequence.top - 14;
1924+
1925+ if (dx < -this.threshold) {
1926+ this.shift_right();
1927+ }
1928+ else if (dx > this.threshold) {
1929+ this.shift_left();
1930+ }
1931+ },
1932+
1933+ shift_right: function() {
1934+ if (!this.target.previousSibling) {
1935+ return;
1936+ }
1937+ this.i -= 1;
1938+ if (this.target.classList.contains('left')) {
1939+ this.target.classList.remove('left');
1940+ UI.animate(this.target);
1941+ }
1942+ else {
1943+ this.target.previousSibling.classList.add('right');
1944+ UI.animate(this.target.previousSibling);
1945+ }
1946+ this.target = this.target.previousSibling;
1947+ },
1948+
1949+ shift_left: function() {
1950+ if (!this.target.nextSibling) {
1951+ return;
1952+ }
1953+ this.i += 1;
1954+ if (this.target.classList.contains('right')) {
1955+ this.target.classList.remove('right');
1956+ UI.animate(this.target);
1957+ }
1958+ else {
1959+ this.target.nextSibling.classList.add('left');
1960+ UI.animate(this.target.nextSibling);
1961+ }
1962+ this.target = this.target.nextSibling;
1963+
1964+ },
1965+
1966+ clear_over: function() {
1967+ if (this.over) {
1968+ this.over.classList.remove('over');
1969+ this.over.classList.remove('over-right');
1970+ this.over = null;
1971+ }
1972+ UI.animate(null);
1973+ },
1974+
1975+ on_drop: function(dnd) {
1976+ UI.dragging = false;
1977+ this.stop_scrolling();
1978+ this.element.classList.remove('grabbed');
1979+ this.clear_over();
1980+ if(this.oncont){
1981+ this.x = null;
1982+ this.y = null;
1983+ var pos = UI.conts[this.contn].pos;
1984+ console.log(pos);
1985+ $unparent(this.element);
1986+ $unselect(UI.selected);
1987+ if(pos == -1) UI.conts[this.contn].list.appendChild(this.element);
1988+ else UI.conts[this.contn].list.insertBefore(this.element,UI.conts[this.contn].list.childNodes[pos]);
1989+ //UI.conts[this.contn].list.appendChild(this.element);
1990+ //UI.conts[this.contn].doc.node.src.push(this.id);
1991+ //this.session.save( UI.conts[this.contn].doc,true);
1992+ UI.conts[this.contn].reset_margin();
1993+ UI.conts[this.contn].save();
1994+ UI.sequence.do_reorder();
1995+ for(var a in UI.slices)if(UI.slices[a].id == this.id)UI.slices.splice(a,1);
1996+ return;
1997+ }
1998+ UI.sequence.reset();
1999+ if (this.inbucket) {
2000+ if (UI.player.active) {
2001+ UI.player.hide();
2002+ }
2003+ if (UI.bucket.lastChild != this.element) {
2004+ $unparent(this.element);
2005+ UI.bucket.appendChild(this.element);
2006+ }
2007+ var pos = $position(UI.bucket);
2008+ this.x = Math.max(0, dnd.x - this.offsetX - pos.left);
2009+ this.y = Math.max(0, dnd.y - this.offsetY - pos.top);
2010+ }
2011+ else {
2012+ console.log(this.orig_i + ' => ' + this.i);
2013+ this.x = null;
2014+ this.y = null;
2015+ var seq = $('sequence');
2016+ if (this.i == this.orig_i) {
2017+ console.assert(seq.children[this.i] == this.element);
2018+ }
2019+ else {
2020+ if (this.i < this.orig_i) {
2021+ var ref = seq.children[this.i];
2022+ }
2023+ else {
2024+ var ref = seq.children[this.i].nextSibling;
2025+ }
2026+ $unparent(this.element);
2027+ seq.insertBefore(this.element, ref);
2028+ }
2029+ }
2030+ if(UI.conts[this.contn]) UI.conts[this.contn].save();
2031+ UI.sequence.do_reorder();
2032+ if (UI.player.active) {
2033+ UI.player.resume();
2034+ }
2035+ },
2036+}
2037+
2038+
2039+function $compare(one, two) {
2040+ if (! (one instanceof Array && two instanceof Array)) {
2041+ return false;
2042+ }
2043+ if (one.length != two.length) {
2044+ return false;
2045+ }
2046+ var i;
2047+ for (i in one) {
2048+ if (one[i] != two[i]) {
2049+ return false;
2050+ }
2051+ }
2052+ return true;
2053+}
2054+
2055+
2056+var Sequence = function(session, doc) {
2057+ this.element = $('sequence');
2058+ this.bucket = $('bucket');
2059+ session.subscribe(doc._id, this.on_change, this);
2060+ this.session = session;
2061+ this.on_change(doc);
2062+
2063+ this.element.onmousedown = $bind(this.on_mousedown, this);
2064+ this.element.onscroll = $bind(this.on_scroll, this);
2065+ this.element.onchildselect = $bind(this.on_childselect, this);
2066+}
2067+Sequence.prototype = {
2068+ get top() {
2069+ return this.element.offsetTop + 24;
2070+ },
2071+
2072+ on_childselect: function(id) {
2073+ console.log('childselect ' + id);
2074+ $hscroll($(id));
2075+ },
2076+
2077+ on_change: function(doc) {
2078+ console.log('Sequence.on_change()');
2079+ this.doc = doc;
2080+
2081+ Thumbs.freeze();
2082+
2083+ var i, _id, child, element;
2084+ for (i in doc.node.src) {
2085+ _id = doc.node.src[i];
2086+ child = this.element.children[i];
2087+ if (!child || child.id != _id) {
2088+ element = UI.get_slice(_id);
2089+ if (element) {
2090+ this.element.insertBefore(element, child);
2091+ }
2092+ }
2093+ }
2094+
2095+ if (! doc.doodle instanceof Array) {
2096+ UI.sequence.doc.doodle = [];
2097+ }
2098+
2099+ var obj;
2100+ for (i in doc.doodle) {
2101+ obj = doc.doodle[i];
2102+ child = this.bucket.children[i];
2103+ if (!child || child.id != obj.id) {
2104+ element = UI.get_slice(obj.id);
2105+ if (element) {
2106+ this.bucket.insertBefore(element, child);
2107+ }
2108+ child = element;
2109+ }
2110+ if (child) {
2111+ child.style.left = obj.x + 'px';
2112+ child.style.top = obj.y + 'px';
2113+ }
2114+ }
2115+
2116+ UI.select(doc.selected);
2117+
2118+ Thumbs.unfreeze();
2119+
2120+ console.assert(
2121+ $compare(this.doc.node.src, this.get_src())
2122+ );
2123+ },
2124+
2125+ on_scroll: function(event) {
2126+ this.element.style.setProperty('background-position', -this.element.scrollLeft + 'px 0px');
2127+ },
2128+
2129+ on_mousedown: function(event) {
2130+ console.log('sequence mousedown');
2131+ this.dnd = new DragEvent(event, this.element);
2132+ this.dnd.ondrag = $bind(this.on_drag, this);
2133+ this.dnd.scrollLeft = this.element.scrollLeft;
2134+ },
2135+
2136+ on_drag: function(dnd) {
2137+ this.element.scrollLeft = dnd.scrollLeft - dnd.dx;
2138+ },
2139+
2140+ get_src: function() {
2141+ var i;
2142+ var src = [];
2143+ for (i=0; i<this.element.children.length; i++) {
2144+ src.push(this.element.children[i].id);
2145+ }
2146+ return src;
2147+ },
2148+
2149+ get_doodle: function() {
2150+ var i, child;
2151+ var doodle = [];
2152+ for (i=0; i<this.bucket.children.length; i++) {
2153+ var child = this.bucket.children[i];
2154+ doodle.push({
2155+ id: child.id,
2156+ x: parseInt(child.style.left),
2157+ y: parseInt(child.style.top),
2158+ });
2159+ }
2160+ return doodle;
2161+ },
2162+
2163+ do_reorder: function() {
2164+ console.log('do_reorder');
2165+ var src = this.get_src();
2166+ var doodle = this.get_doodle();
2167+ this.doc.node.src = src;
2168+ this.doc.doodle = doodle;
2169+ this.session.save(this.doc, true); // no_emit=true
2170+ this.session.commit();
2171+ },
2172+
2173+ reset: function() {
2174+ console.log('Sequence.reset()');
2175+ var i, child;
2176+ for (i=0; i<this.element.children.length; i++) {
2177+ child = this.element.children[i];
2178+ if (!child.classList.contains('grabbed')) {
2179+ child.classList.remove('left');
2180+ child.classList.remove('right');
2181+ }
2182+ }
2183+ },
2184+}
2185+
2186+var VideoFrame = function(which) {
2187+ this.element = $el('div', {'class': 'videoframe ' + which});
2188+ this.video = $el('video');
2189+ this.element.appendChild(this.video);
2190+ this.info = $el('div');
2191+ this.element.appendChild(this.info);
2192+ this.ready = false;
2193+ this.pending = null;
2194+ this.video.addEventListener('canplaythrough',
2195+ $bind(this.on_canplaythrough, this)
2196+ );
2197+ this.video.addEventListener('seeked',
2198+ $bind(this.on_seeked, this)
2199+ );
2200+}
2201+VideoFrame.prototype = {
2202+ set_index: function(index) {
2203+ this.info.textContent = index + 1;
2204+ this.seek(index);
2205+ },
2206+
2207+ on_canplaythrough: function(event) {
2208+ this.ready = true;
2209+ this.do_seek();
2210+ },
2211+
2212+ on_seeked: function(event) {
2213+ if (this.pending != null) {
2214+ this.do_seek();
2215+ }
2216+ },
2217+
2218+ set_clip: function(clip) {
2219+ this.ready = false;
2220+ this.framerate = clip.framerate;
2221+ this.frames = clip.duration.frames;
2222+ this.pending = null;
2223+ this.video.src = 'dmedia:' + clip._id;
2224+ },
2225+
2226+ play: function() {
2227+ this.video.play();
2228+ },
2229+
2230+ pause: function() {
2231+ this.video.pause();
2232+ },
2233+
2234+ seek: function(index) {
2235+ this.pending = frame_to_seconds(index, this.framerate);
2236+ if (this.ready && ! this.video.seeking) {
2237+ this.do_seek();
2238+ }
2239+ },
2240+
2241+ do_seek: function() {
2242+ var t = this.pending;
2243+ this.pending = null;
2244+ this.video.currentTime = t;
2245+ },
2246+
2247+ show: function() {
2248+ this.element.classList.remove('hide');
2249+ },
2250+
2251+ hide: function() {
2252+ this.video.pause();
2253+ this.element.classList.add('hide');
2254+ },
2255+
2256+ get_x: function(width) {
2257+ return Math.round(width * this.video.currentTime / this.video.duration);
2258+ },
2259+
2260+ get_frame: function() {
2261+ return Math.round(this.frames * this.video.currentTime / this.video.duration);
2262+ },
2263+}
2264+
2265+
2266+var RoughCut = function(session) {
2267+ this.session = session;
2268+ this.active = false;
2269+
2270+ this.element = $('roughcut');
2271+ this.frames = $el('div', {'class': 'frames'});
2272+ this.element.appendChild(this.frames);
2273+
2274+ this.done = $('close_roughcut');
2275+ this.done.onclick = function() {
2276+ this.blur();
2277+ UI.hide_roughcut();
2278+ }
2279+
2280+ this.create_button = $('create_slice');
2281+ this.create_button.onclick = $bind(this.create_slice, this);
2282+
2283+ this.startvideo = new VideoFrame('start');
2284+ this.frames.appendChild(this.startvideo.element);
2285+
2286+ this.endvideo = new VideoFrame('end hide');
2287+ this.frames.appendChild(this.endvideo.element);
2288+
2289+ this.scrubber = $el('div', {'class': 'scrubber'});
2290+ this.element.appendChild(this.scrubber);
2291+
2292+ this.bar = $el('div', {'class': 'bar hide'});
2293+ this.scrubber.appendChild(this.bar);
2294+
2295+ this.playhead = $el('div', {'class': 'playhead hide'});
2296+ this.scrubber.appendChild(this.playhead);
2297+
2298+ this.scrubber.onmouseover = $bind(this.on_mouseover, this);
2299+ this.scrubber.onmousedown = $bind(this.on_mousedown, this);
2300+
2301+ this.startvideo.video.addEventListener('timeupdate',
2302+ $bind(this.on_timeupdate, this)
2303+ );
2304+ this.startvideo.video.addEventListener('ended',
2305+ $bind(this.on_ended, this)
2306+ );
2307+
2308+ this.startvideo.element.addEventListener('mousewheel',
2309+ $bind(this.on_mousewheel_start, this)
2310+ );
2311+ this.endvideo.element.addEventListener('mousewheel',
2312+ $bind(this.on_mousewheel_end, this)
2313+ );
2314+}
2315+RoughCut.prototype = {
2316+ on_ended: function() {
2317+ if (! this.playing) {
2318+ return;
2319+ }
2320+ var frame = (this.mode == 'edit' && this.inside) ? this.start : 0;
2321+ this.startvideo.seek(frame);
2322+ this.startvideo.video.play();
2323+ this.pframe = frame;
2324+ },
2325+
2326+ on_timeupdate: function() {
2327+ if (! this.playing) {
2328+ return;
2329+ }
2330+ if (this.dnd) {
2331+ return;
2332+ }
2333+ var frame = this.startvideo.get_frame();
2334+ if (this.mode == 'edit' && this.inside && frame >= this.stop - 1) {
2335+ this.startvideo.seek(this.start, true);
2336+ frame = this.start;
2337+ }
2338+ this.pframe = frame;
2339+ },
2340+
2341+ hide: function() {
2342+ this.active = false;
2343+ this.mode = null;
2344+ this.pause();
2345+ $hide(this.element);
2346+ },
2347+
2348+ show: function(id) {
2349+ console.log('show ' + id);
2350+ if (! this.element.classList.contains('hide')) {
2351+ this.x = this.x + 130 + 10 + (this.xf * 3);
2352+ if (this.x > document.body.clientWidth - 130) {
2353+ this.x = 0;
2354+ }
2355+ }
2356+ else {
2357+ this.x = 0;
2358+ }
2359+ this.y = 0;
2360+ this.count = 0;
2361+ this.active = true;
2362+ this.clip = this.session.get_doc(id);
2363+ this.frames = this.clip.duration.frames;
2364+ this.startvideo.set_clip(this.clip);
2365+ this.endvideo.set_clip(this.clip);
2366+ $show(this.element);
2367+ },
2368+
2369+ reset: function() {
2370+ delete this.dnd;
2371+ this._start = 0;
2372+ this._stop = this.frames;
2373+ this.pframe = null;
2374+ this.startvideo.pause();
2375+ this.playing = false;
2376+ this.scrubber.onmousemove = null;
2377+ },
2378+
2379+ get_x: function(frame) {
2380+ return Math.round(this.scrubber.clientWidth * frame / this.frames);
2381+ },
2382+
2383+ get_frame: function(x, key_unit) {
2384+ var frame = Math.round(this.frames * x / this.scrubber.clientWidth);
2385+ if (key_unit) {
2386+ return 1 + Math.round(frame / 15) * 15;
2387+ }
2388+ return frame;
2389+ },
2390+
2391+ set start(value) {
2392+ this._start = Math.max(0, Math.min(value, this._stop - 1));
2393+ this.startvideo.set_index(this._start);
2394+ },
2395+
2396+ get start() {
2397+ return this._start;
2398+ },
2399+
2400+ set stop(value) {
2401+ this._stop = Math.max(this._start + 1, Math.min(value, this.frames));
2402+ this.endvideo.set_index(this._stop - 1);
2403+ },
2404+
2405+ get stop() {
2406+ return this._stop;
2407+ },
2408+
2409+ get left() {
2410+ return this.get_x(this._start);
2411+ },
2412+
2413+ get right() {
2414+ return this.get_x(this._stop);
2415+ },
2416+
2417+ set pframe(value) {
2418+ if (value == null) {
2419+ this._pframe = null;
2420+ this.playhead.classList.add('hide');
2421+ }
2422+ else {
2423+ this._pframe = Math.max(0, Math.min(value, this.frames - 1));
2424+ this.playhead.classList.remove('hide');
2425+ this.playhead.style.left = this.get_x(this._pframe) + 'px';
2426+ }
2427+ },
2428+
2429+ get pframe() {
2430+ return this._pframe;
2431+ },
2432+
2433+ on_mousewheel_start: function(event) {
2434+ $halt(event);
2435+ var orig = this._start;
2436+ this.start = orig + wheel_delta(event);
2437+ if (this.start != orig) {
2438+ this.save_to_slice();
2439+ this.session.delayed_commit();
2440+ if (this.mode == 'create') {
2441+ this.bar.style.left = this.left + 'px';
2442+ }
2443+ else {
2444+ this.update_bar();
2445+ }
2446+ }
2447+ },
2448+
2449+ on_mousewheel_end: function(event) {
2450+ $halt(event);
2451+ var orig = this._stop;
2452+ this.stop = orig + wheel_delta(event);
2453+ if (this.stop != orig) {
2454+ this.save_to_slice();
2455+ this.session.delayed_commit();
2456+ this.update_bar();
2457+ }
2458+ },
2459+
2460+ playpause: function() {
2461+ if (this.playing) {
2462+ this.pause();
2463+ }
2464+ else {
2465+ this.play();
2466+ }
2467+ },
2468+
2469+ play: function() {
2470+ this.playing = true;
2471+ this.scrubber.onmousemove = null;
2472+ if (this._pframe == null) {
2473+ this.pframe = this.start;
2474+ }
2475+ this.startvideo.seek(this.pframe);
2476+ this.startvideo.play();
2477+ },
2478+
2479+ pause: function() {
2480+ this.startvideo.pause();
2481+ this.startvideo.seek(this.start);
2482+ this.playing = false;
2483+ if (this.mode == 'create') {
2484+ this.bind_mousemove();
2485+ }
2486+ },
2487+
2488+ update_bar: function() {
2489+ var left = this.left;
2490+ var width = Math.max(2, this.right - left);
2491+ this.bar.style.left = left + 'px';
2492+ this.bar.style.width = width + 'px';
2493+ },
2494+
2495+ sync_from_slice: function() {
2496+ this._start = 0;
2497+ this._stop = this.frames;
2498+ this.start = this.slice.node.start.frame;
2499+ this.stop = this.slice.node.stop.frame;
2500+ this.update_bar();
2501+ },
2502+
2503+ save_to_slice: function() {
2504+ /*
2505+ Store start & stop in slice doc, mark doc as dirty.
2506+
2507+ Note that this *only* marks the slice doc as dirty, does *not* call
2508+ session.commit(). This is for cases when you also need to update the
2509+ sequence doc, so you can send both in a single CouchDB request.
2510+ */
2511+ this.slice.node.start.frame = this.start;
2512+ this.slice.node.stop.frame = this.stop;
2513+ this.session.save(this.slice);
2514+ },
2515+
2516+ create_slice: function() {
2517+ console.log('create_slice');
2518+ this.create_button.blur();
2519+ this.count += 1;
2520+ this.mode = 'create';
2521+ this.endvideo.hide();
2522+ this.reset();
2523+ this.start = 0;
2524+ this.bar.style.left = this.left + 'px';
2525+ this.bar.style.width = '1px';
2526+ this.slice = create_slice(this.clip._id, this.frames);
2527+ this.bind_mousemove();
2528+ },
2529+
2530+ edit_slice: function(slice) {
2531+ console.log('edit_slice ' + slice._id);
2532+ this.mode = 'edit';
2533+ this.inside = true;
2534+ this.slice = slice;
2535+ this.endvideo.show();
2536+ this.reset();
2537+ $show(this.bar);
2538+ this.sync_from_slice();
2539+ },
2540+
2541+ bind_mousemove: function() {
2542+ this.scrubber.onmousemove = $bind(this.on_mousemove, this);
2543+ },
2544+
2545+ on_mouseover: function(event) {
2546+ if (this.mode == 'create' && !this.playing && !this.dnd) {
2547+ $show(this.bar);
2548+ }
2549+ },
2550+
2551+ on_mousemove: function(event) {
2552+ this.start = this.get_frame(event.clientX);
2553+ this.bar.style.left = this.left + 'px';
2554+ },
2555+
2556+ on_mousedown: function(event) {
2557+ this.scrubber.onmousemove = null;
2558+ this.dnd = new DragEvent(event);
2559+ this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
2560+ this.dnd.ondragstart = $bind(this.on_dragstart, this);
2561+
2562+ if (this.playing) {
2563+ this.startvideo.pause();
2564+ return this.scrub_playhead(this.dnd);
2565+ }
2566+ if (this.mode == 'edit') {
2567+ var mid = (this.left + this.right) / 2;
2568+ this.point = (event.clientX <= mid) ? 'left' : 'right';
2569+ var frame = this.get_frame(event.clientX);
2570+ if (this.point == 'left') {
2571+ this.start = frame;
2572+ }
2573+ else {
2574+ this.stop = frame + 1;
2575+ }
2576+ this.update_bar();
2577+ }
2578+ },
2579+
2580+ scrub_playhead: function(dnd) {
2581+ var frame = this.get_frame(dnd.x);
2582+ if (this.start <= frame && frame < this.stop) {
2583+ this.inside = true;
2584+ }
2585+ else {
2586+ this.inside = false;
2587+ }
2588+ this.pframe = frame;
2589+ this.startvideo.seek(frame);
2590+ },
2591+
2592+ on_dragcancel: function(dnd) {
2593+ delete this.dnd;
2594+ if (this.playing) {
2595+ this.startvideo.play();
2596+ return;
2597+ }
2598+ if (this.mode == 'create') {
2599+ this.bindmousemove();
2600+ }
2601+ else {
2602+ this.save_to_slice();
2603+ this.session.delayed_commit();
2604+ }
2605+ },
2606+
2607+ on_dragstart: function(dnd) {
2608+ this.dnd.ondrag = $bind(this.on_drag, this);
2609+ this.dnd.ondrop = $bind(this.on_drop, this);
2610+ if (!this.playing && this.mode == 'create') {
2611+ this.endvideo.show();
2612+ this.orig_start = this.start;
2613+ }
2614+ },
2615+
2616+ on_drag: function(dnd) {
2617+ var frame = this.get_frame(dnd.x);
2618+ if (this.playing) {
2619+ return this.scrub_playhead(dnd);
2620+ }
2621+ if (this.mode == 'create') {
2622+ if (frame < this.orig_start) {
2623+ this.start = frame;
2624+ this.stop = this.orig_start + 1;
2625+ }
2626+ else {
2627+ this.start = this.orig_start;
2628+ this.stop = frame + 1;
2629+ }
2630+ }
2631+ else {
2632+ if (this.point == 'left') {
2633+ this.start = frame;
2634+ }
2635+ else {
2636+ this.stop = frame + 1;
2637+ }
2638+ }
2639+ this.update_bar();
2640+ },
2641+
2642+ xf: 25,
2643+
2644+ yf: 50,
2645+
2646+ on_drop: function(dnd) {
2647+ delete this.dnd;
2648+ if (this.playing) {
2649+ this.startvideo.play();
2650+ return;
2651+ }
2652+ if (this.mode == 'create') {
2653+ this.save_to_slice();
2654+ var x = this.x + (this.count * this.xf);
2655+ var y = this.y + (this.count * this.yf);
2656+ UI.sequence.doc.doodle.push({id: this.slice._id, x: x, y: y});
2657+ this.session.save(UI.sequence.doc);
2658+ this.session.delayed_commit();
2659+ this.edit_slice(this.slice);
2660+ }
2661+ else {
2662+ this.save_to_slice();
2663+ this.session.delayed_commit();
2664+ }
2665+ },
2666+}
2667+
2668+
2669+function Clips() {
2670+ this.selected = null;
2671+ this.dropdown = $('dmedia_project');
2672+ this.dropdown.onchange = $bind(this.on_dropdown_change, this);
2673+ this.div = $('clips');
2674+ this.container = $('clips_outer');
2675+ this.session = UI.session;
2676+ this.doc = this.session.get_doc(UI.project_id);
2677+ this.session.subscribe(this.doc._id, this.on_change, this);
2678+ this.id = null;
2679+ this.db = null;
2680+ this.load_projects();
2681+ this.open = $('open_clips');
2682+ this.open.onclick = $bind(this.on_open_click, this);
2683+
2684+ this.div.onchildselect = $bind(this.on_childselect, this);
2685+}
2686+Clips.prototype = {
2687+ on_childselect: function(id) {
2688+ console.log('childselect ' + id);
2689+ $hscroll($(id));
2690+ if (this.doc.selected_clips[this.id] != id) {
2691+ this.doc.selected_clips[this.id] = id;
2692+ this.session.save(this.doc, true);
2693+ this.session.delayed_commit();
2694+ }
2695+ },
2696+
2697+ on_change: function(doc) {
2698+ console.log('Clips.on_change');
2699+ this.doc = doc;
2700+ if (!this.doc.selected_clips) {
2701+ this.doc.selected_clips = {};
2702+ }
2703+ var id = doc.dmedia_project_id;
2704+ this.dropdown.value = id;
2705+ if (this.load(id)) {
2706+ this.div.innerHTML = null;
2707+ this.load_clips();
2708+ }
2709+ },
2710+
2711+ load: function(id) {
2712+ console.log('load ' + id);
2713+ if (!id) {
2714+ this.id = null;
2715+ this.db = null;
2716+ return false;
2717+ }
2718+ if (id == this.id) {
2719+ return false;
2720+ }
2721+ this.id = id;
2722+ this.db = dmedia_project_db(id);
2723+ return true;
2724+ },
2725+
2726+ load_projects: function() {
2727+ var callback = $bind(this.on_projects, this);
2728+ dmedia.view(callback, 'project', 'title');
2729+ },
2730+
2731+ on_projects: function(req) {
2732+ var rows = req.read().rows;
2733+ this.dropdown.innerHTML = null;
2734+ this.placeholder = $el('option');
2735+ this.dropdown.appendChild(this.placeholder);
2736+ rows.forEach(function(row) {
2737+ var option = $el('option', {textContent: row.key, value: row.id});
2738+ this.dropdown.appendChild(option);
2739+ }, this);
2740+ },
2741+
2742+ on_dropdown_change: function(event) {
2743+ if (this.placeholder) {
2744+ $unparent(this.placeholder);
2745+ delete this.placeholder;
2746+ }
2747+ this.doc.dmedia_project_id = this.dropdown.value;
2748+ this.dropdown.blur();
2749+ this.session.save(this.doc);
2750+ this.session.delayed_commit();
2751+ },
2752+
2753+ load_clips: function() {
2754+ var callback = $bind(this.on_clips, this);
2755+ this.db.view(callback, 'user', 'video');
2756+ },
2757+
2758+ on_clips: function(req) {
2759+ var rows = req.read().rows;
2760+ this.div.innerHTML = null;
2761+ var self = this;
2762+ rows.forEach(function(row) {
2763+ var id = row.id;
2764+ var img = new Image();
2765+ img.id = id;
2766+ img.src = this.att_url(id);
2767+ img.onmousedown = function(event) {
2768+ self.on_mousedown(id, event);
2769+ }
2770+ img.ondblclick = function(event) {
2771+ self.on_dblclick(id, event);
2772+ }
2773+ this.div.appendChild(img);
2774+ }, this);
2775+ UI.select(this.doc.selected_clips[this.id]);
2776+ },
2777+
2778+ on_open_click: function(event) {
2779+ if (!this.container.classList.toggle('open')) {
2780+ var element = $(UI.selected);
2781+ if (element && element.parentNode.id == 'clips') {
2782+ UI.select(null);
2783+ }
2784+ }
2785+ },
2786+
2787+ on_mousedown: function(id, event) {
2788+ UI.select(id);
2789+ this.dnd = new DragEvent(event);
2790+ this.dnd.id = id;
2791+ this.dnd.ondragcancel = $bind(this.on_dragcancel, this);
2792+ this.dnd.ondragstart = $bind(this.on_dragstart, this);
2793+ },
2794+
2795+ on_dragcancel: function(dnd) {
2796+ delete this.dnd;
2797+ },
2798+
2799+ on_dragstart: function(dnd) {
2800+ this.dnd.ondrag = $bind(this.on_drag, this);
2801+ this.dnd.ondrop = $bind(this.on_drop, this);
2802+ },
2803+
2804+ on_drag: function(dnd) {
2805+ if (dnd.dy > 50) {
2806+ console.log('creating ' + dnd.dy);
2807+ dnd.ondrag = null;
2808+ UI.copy_clip(dnd.id);
2809+ var clip = this.session.get_doc(dnd.id);
2810+ var doc = create_slice(clip._id, clip.duration.frames);
2811+ this.session.save(doc, true);
2812+ var slice = new Slice(UI.session, doc);
2813+ slice.x = dnd.x - 64;
2814+ slice.y = dnd.y - 36;
2815+ UI.bucket.appendChild(slice.element);
2816+ slice.on_mousedown(dnd.event);
2817+ }
2818+ },
2819+
2820+ on_drop: function(dnd) {
2821+ delete this.dnd;
2822+ },
2823+
2824+ on_dblclick: function(id, event) {
2825+ UI.copy_clip(id);
2826+ UI.create_slice(id);
2827+ },
2828+
2829+ att_url: function(doc_or_id, name) {
2830+ if (!this.db) {
2831+ return null;
2832+ }
2833+ return this.db.att_url(doc_or_id, name);
2834+ },
2835+
2836+ att_css_url: function(doc_or_id, name) {
2837+ if (!this.db) {
2838+ return null;
2839+ }
2840+ return this.db.att_css_url(doc_or_id, name);
2841+ },
2842+}
2843+
2844+
2845+var LoveOrb = function() {
2846+ this.logo = $el('img', {'id': 'logo', 'src': 'novacut.png'});
2847+ document.body.appendChild(this.logo);
2848+ this.capture = $('flyout_capture');
2849+ this.flyout = $('flyout');
2850+ this.logo.onmousedown = $bind(this.on_mousedown, this);
2851+ this.logo.onclick = $bind(this.on_click, this);
2852+ this.capture.onclick = $bind(this.on_capture_click, this);
2853+ this.flyout.onclick = $bind(this.on_flyout_click, this);
2854+}
2855+LoveOrb.prototype = {
2856+ get active() {
2857+ return !this.capture.classList.contains('hide');
2858+ },
2859+
2860+ toggle: function() {
2861+ if(this.capture.classList.toggle('hide')) {
2862+ this.logo.classList.remove('open');
2863+ }
2864+ else {
2865+ this.logo.classList.add('open');
2866+ }
2867+ },
2868+
2869+ on_mousedown: function(event) {
2870+ // Needed to prevent annoying drag behavior
2871+ $halt(event);
2872+ },
2873+
2874+ on_click: function(event) {
2875+ $halt(event);
2876+ this.toggle();
2877+ },
2878+
2879+ on_capture_click: function(event) {
2880+ console.log('capture click');
2881+ $halt(event);
2882+ this.toggle();
2883+ },
2884+
2885+ on_flyout_click: function(event) {
2886+ console.log('flyout click');
2887+ event.stopPropagation();
2888+ },
2889+}
2890+
2891+function closeAll(){
2892+ for(var a in UI.conts)UI.conts[a].close();
2893+}
2894+
2895+
2896+function unselectAll(){
2897+ for(var a in UI.selected){
2898+ var b = UI.selected[a];
2899+ $unselect(b);
2900+ }
2901+ UI.selected = Array();
2902+}
2903+
2904+var selector = {
2905+ init: function(){
2906+ selector.down = false;
2907+ selector.el = "";
2908+ selector.xs = 1;
2909+ selector.ys = 1;
2910+ },
2911+ on_mouse_down: function(ev){
2912+ closeAll();
2913+ unselectAll();
2914+ selector.down = true;
2915+ selector.el = $el('div', {'class': 'selector','id': 'sel'});
2916+ selector.stx = ev.x;
2917+ selector.sty = ev.y;
2918+ selector.el.style.left = ev.x +'px';
2919+ selector.el.style.top = ev.y +'px';
2920+ UI.bucket.appendChild(selector.el);
2921+ },
2922+ on_mouse_up: function(ev){
2923+ console.log("mouse up");
2924+ if(selector.down && selector.el && selector.el.parentNode){
2925+ selector.el.parentNode.removeChild(selector.el);
2926+ if(document.getElementById('sel'))document.getElementById('sel').parentNode.removeChild(document.getElementById('sel'));
2927+ selector.down = false;
2928+ var stx = selector.stx;
2929+ var endx = selector.stx+selector.xs*parseInt(selector.el.style.width);
2930+ var sty = selector.sty;
2931+ var endy = selector.sty+selector.ys*parseInt(selector.el.style.height);
2932+ if(stx > endx){
2933+ var tmp = stx;
2934+ stx = endx;
2935+ endx = tmp;
2936+ }
2937+ if(sty > endy){
2938+ var tmp = sty;
2939+ sty = endy;
2940+ endy = tmp;
2941+ }
2942+ for(var a in UI.slices){
2943+ var b = UI.slices[a];
2944+ if(b.x > stx && b.x < endx && b.y > sty && b.y < endy){
2945+ UI.selected.push(b.id);
2946+ $select(b.id);
2947+ }
2948+ }
2949+ for(var a in UI.conts){
2950+ var b = UI.conts[a];
2951+ if(b.x > stx && b.x < endx && b.y > sty && b.y < endy){
2952+ UI.selected.push(b.id);
2953+ $select(b.id);
2954+ }
2955+ }
2956+ }
2957+ },
2958+ on_mouse_move: function(ev){
2959+ if(selector.down){
2960+ var w = ev.x - selector.stx;
2961+ var h = ev.y - selector.sty;
2962+ selector.el.style.width = ev.x - selector.stx +'px';
2963+ selector.el.style.height = ev.y - selector.sty +'px';
2964+ selector.xs = 1;
2965+ selector.ys = 1;
2966+ if(w < 0){
2967+ selector.el.style.width = -w + 'px';
2968+ selector.el.style.left = ev.x + 'px';
2969+ selector.xs = -1;
2970+ }
2971+ if(h < 0){
2972+ selector.el.style.height = -h + 'px';
2973+ selector.el.style.top = ev.y + 'px';
2974+ selector.ys = -1;
2975+ }
2976+ }
2977+ }
2978+
2979+}
2980+
2981+function compare(a,b){
2982+return a.x-b.x;
2983+}
2984+
2985+function generateAnimation(id,x,y,ex){//generate the animation
2986+ var keyframeprefix = "-webkit-";
2987+ 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)}'+'}';
2988+ keyframes += '#'+id+'{-webkit-animation-duration: 0.5s;-webkit-animation-iteration-count: 1;-webkit-animation-timing-function:linear;}';
2989+ var s = document.createElement( 'style' );
2990+ s.innerHTML = keyframes;
2991+ document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
2992+}
2993+
2994+
2995+var UI = {
2996+ init: function() {
2997+ Hub.connect('edit_hashed', UI.on_edit_hashed);
2998+ Hub.connect('job_hashed', UI.on_job_hashed);
2999+ Hub.connect('job_rendered', UI.on_job_rendered);
3000+
3001+ // Figure out what project we're in:
3002+ var parts = parse_hash();
3003+ UI.project_id = parts[0];
3004+ UI.db = novacut_project_db(UI.project_id);
3005+
3006+ // Bit of UI setup
3007+ window.addEventListener('keyup', UI.on_keyup);
3008+ UI.bucket = $('bucket');
3009+ UI.orb = new LoveOrb();
3010+ document.getElementById('shortcuts').style.marginTop = window.innerHeight/2-200+"px";
3011+ document.getElementById('shortcuts').style.marginLeft = window.innerWidth/2-255+"px";
3012+
3013+ //selector
3014+ UI.selected = Array();
3015+ UI.bucket.addEventListener('mousedown', selector.on_mouse_down, false);
3016+ UI.bucket.addEventListener('mousemove', selector.on_mouse_move, false);
3017+ document.addEventListener('mouseup', selector.on_mouse_up, false);
3018+
3019+ // Create and start the CouchDB session
3020+ UI.session = new couch.Session(UI.db, UI.on_new_doc);
3021+ UI.session.start();
3022+
3023+ UI.dragging = false;
3024+ UI.slices = Array();
3025+ UI.conts = Array();
3026+ document.body.onclick = $bind(closeAll);
3027+ },
3028+
3029+ animated: null,
3030+
3031+ animate: function(element) {
3032+ if (UI.animated) {
3033+ UI.animated.classList.remove('animated');
3034+ }
3035+ UI.animated = $(element);
3036+ if (UI.animated) {
3037+ UI.animated.classList.add('animated');
3038+ }
3039+ },
3040+
3041+ copy_clip: function(id) {
3042+ try {
3043+ UI.session.get_doc(id);
3044+ }
3045+ catch (e) {
3046+ console.log('copying ' + id);
3047+ var doc = UI.clips.db.get_sync(id, {attachments: true});
3048+ delete doc._rev;
3049+ UI.session.save(doc, true);
3050+ }
3051+ },
3052+
3053+ select: function(id) {
3054+ unselectAll();
3055+ var element = $select(id);
3056+ if (element) {
3057+ UI.selected.push(id);
3058+ if (element.parentNode && element.parentNode.onchildselect) {
3059+ element.parentNode.onchildselect(id);
3060+ }
3061+ }
3062+ else {
3063+ UI.selected = Array();
3064+ }
3065+ if (UI.sequence) {
3066+ var doc = UI.sequence.doc;
3067+ if (doc.selected != UI.selected) {
3068+ doc.selected = UI.selected;
3069+ UI.session.save(doc, true); // No local emit
3070+ UI.session.delayed_commit();
3071+ }
3072+ }
3073+ },
3074+
3075+ delete_selected: function() {
3076+ for(var a in UI.selected){
3077+ var id = UI.selected[a];
3078+ var element = $(UI.selected[a]);
3079+ if (element) {
3080+ if (element.parentNode && element.parentNode.id == 'clips') {
3081+ console.log('no delete in clips browser');
3082+ return;
3083+ }
3084+ try {
3085+ var doc = UI.session.get_doc(element.id);
3086+ UI.previous();
3087+ doc._deleted = true;
3088+ UI.session.save(doc);
3089+ if(doc.node.type == 'cont')for(var a in UI.conts){
3090+ if(UI.conts[a].id == id)UI.conts.splice(a,1);
3091+ }
3092+ if(doc.node.type == 'slice')for(var a in UI.slices){
3093+ if(UI.slices[a].id == id)UI.slices.splice(a,1);
3094+ }
3095+ }
3096+ catch (e) {
3097+ return;
3098+ }
3099+ }
3100+ }
3101+ },
3102+
3103+ group_selected: function(){
3104+ var ndoc = create_cont();
3105+ var list = Array();
3106+ if(UI.selected.length < 2)return;
3107+ for(var a in UI.selected){//get all the slices
3108+ var doc = UI.session.get_doc(UI.selected[a]);
3109+ if(doc.node.type == 'slice'){
3110+ for(var b in UI.slices)if(UI.slices[b].id == UI.selected[a]){
3111+ list.push(UI.slices[b]);
3112+ UI.slices.splice(b,1);
3113+ }
3114+ //UI.bucket.removeChild(document.getElementById(UI.selected[a]))
3115+ //ndoc.node.src.push(UI.selected[a]);
3116+ }
3117+ }
3118+ if(list.length < 2)return;
3119+ list.sort(compare);//sort for x position
3120+ var y = 0;
3121+ for(var a in list){//add the slices in the doc and compute the average y
3122+ ndoc.node.src.push(list[a].id);
3123+ y += list[a].y;
3124+ }
3125+ y /= list.length;
3126+ UI.session.save(ndoc);
3127+ var ncont = new Cont(UI.session,ndoc);
3128+ ncont.x = list[0].x;
3129+ ncont.y = y;
3130+ for(var a in list){//animate to form a line
3131+ generateAnimation(list[a].id,list[0].x+(a*130)-list[a].x,y-list[a].y,list[0].x-list[a].x);
3132+ list[a].element.style.webkitAnimationName = list[a].id;
3133+ console.log('animate');
3134+ list[a].element.addEventListener('webkitAnimationEnd',function(ev){
3135+ //console.log('end');
3136+ /*this.style.webkitAnimationName = "";
3137+ this.style.top = "0px";
3138+ this.style.left = "0px";
3139+ $unparent(this);
3140+ ncont.list.appendChild(this);
3141+ ncont.close();*/
3142+ UI.bucket.removeChild(this);
3143+ UI.sequence.do_reorder();
3144+
3145+ });
3146+ }
3147+ //setTimeout(UI.sequence.do_reorder(),530);
3148+ //setTimeout(function(){for(var a in list)UI.bucket.removeChild(document.getElementById(list[a].id));},530,list);
3149+ UI.bucket.appendChild(ncont.element);
3150+ },
3151+
3152+ duplicate_selected: function() {
3153+ for(var a in UI.selected){
3154+ var element = $(UI.selected[a]);
3155+ var doc = UI.session.get_doc(element.id);
3156+ if(doc.node.type == 'slice'){
3157+ var ndoc = create_slice(doc.node.src,doc.node.stop.frame);
3158+ ndoc.node.start.frame = doc.node.start.frame;
3159+ UI.session.save(ndoc);
3160+ var slice = new Slice(UI.session, ndoc);
3161+ }
3162+ if(doc.node.type == 'cont'){
3163+ var ndoc = create_cont();
3164+ ndoc.node.src = doc.node.src;
3165+ UI.session.save(ndoc);
3166+ var slice = new Cont(UI.session, ndoc);
3167+ }
3168+ slice.x = parseInt(element.style.left)-30;
3169+ slice.y = parseInt(element.style.top)-30;
3170+ UI.bucket.appendChild(slice.element);
3171+ }
3172+ UI.sequence.do_reorder();
3173+ },
3174+
3175+ first: function() {
3176+ var element = $(UI.selected);
3177+ if (element && element.parentNode) {
3178+ UI.select(element.parentNode.children[0].id);
3179+ }
3180+ },
3181+
3182+ previous: function() {
3183+ var element = $(UI.selected);
3184+ if (element && element.previousSibling) {
3185+ UI.select(element.previousSibling.id);
3186+ }
3187+ },
3188+
3189+ next: function() {
3190+ var element = $(UI.selected);
3191+ if (element && element.nextSibling) {
3192+ UI.select(element.nextSibling.id);
3193+ }
3194+ },
3195+
3196+ last: function() {
3197+ var element = $(UI.selected);
3198+ if (element && element.parentNode) {
3199+ var i = element.parentNode.children.length - 1;
3200+ UI.select(element.parentNode.children[i].id);
3201+ }
3202+ },
3203+
3204+
3205+ get_slice: function(_id) {
3206+ var element = $unparent(_id);
3207+ if (element) {
3208+ console.log(_id);
3209+ return element;
3210+ }
3211+ try {
3212+ var doc = UI.session.get_doc(_id);
3213+ }
3214+ catch (e) {
3215+ return null;
3216+ }
3217+ console.log(_id);
3218+ if(doc.node.type == 'slice'){
3219+ var slice = new Slice(UI.session, doc);
3220+ return slice.element;
3221+ }
3222+ if(doc.node.type == 'cont'){
3223+ var slice = new Cont(UI.session, doc);
3224+ return slice.element;
3225+ }
3226+ },
3227+
3228+ on_new_doc: function(doc) {
3229+ if (doc._id == UI.project_id) {
3230+ UI.doc = doc;
3231+
3232+ // FIXME: create default sequence if needed
3233+ if (!UI.doc.root_id) {
3234+ console.log('creating default sequence');
3235+ var seq = create_sequence();
3236+ UI.doc.root_id = seq._id;
3237+ UI.session.save(UI.doc, true);
3238+ UI.session.save(seq, true);
3239+ UI.session.delayed_commit();
3240+ }
3241+
3242+ UI.sequence = new Sequence(UI.session, UI.session.get_doc(UI.doc.root_id));
3243+ UI.clips = new Clips(dmedia);
3244+ UI.player= new SequencePlayer(UI.session, UI.sequence.doc);
3245+ //closeAll();
3246+
3247+ }
3248+ },
3249+
3250+ _roughcut: null,
3251+
3252+ get roughcut() {
3253+ if (UI._roughcut == null) {
3254+ UI._roughcut = new RoughCut(UI.session);
3255+ }
3256+ return UI._roughcut;
3257+ },
3258+
3259+ create_slice: function(id) {
3260+ UI.roughcut.show(id);
3261+ UI.roughcut.create_slice();
3262+ },
3263+
3264+ edit_slice: function(doc) {
3265+ UI.roughcut.show(doc.node.src);
3266+ UI.roughcut.edit_slice(doc);
3267+ },
3268+
3269+ hide_roughcut: function() {
3270+ UI.roughcut.hide();
3271+ },
3272+
3273+ // Key bindings
3274+ actions: {
3275+ // Left arrow
3276+ 'Left': function(event) {
3277+ if (event.shiftKey) {
3278+ UI.first();
3279+ }
3280+ else {
3281+ UI.previous();
3282+ }
3283+ if (UI.player.active) {
3284+ UI.player.hold_and_resume();
3285+ }
3286+ },
3287+
3288+ // Right arrow
3289+ 'Right': function(event) {
3290+ if (event.shiftKey) {
3291+ UI.last();
3292+ }
3293+ else {
3294+ UI.next();
3295+ }
3296+ if (UI.player.active) {
3297+ UI.player.hold_and_resume();
3298+ }
3299+ },
3300+ //G
3301+ 'U+0047': function(event){
3302+ UI.group_selected();
3303+ },
3304+ //D
3305+ 'U+0044': function(event) {
3306+ UI.duplicate_selected();
3307+ },
3308+
3309+ // The David Fulde key
3310+ // aka Backspace aka Big Delete on a mac keyboard
3311+ 'U+0008': function(event) {
3312+ UI.delete_selected();
3313+ },
3314+
3315+ // Delete
3316+ 'U+007F': function(event) {
3317+ UI.delete_selected();
3318+ },
3319+
3320+ // SpaceBar
3321+ 'U+0020': function(event) {
3322+ if (UI.roughcut.active) {
3323+ UI.roughcut.playpause();
3324+ }
3325+ else {
3326+ if (UI.player.active) {
3327+ UI.player.playpause();
3328+ }
3329+ else {
3330+ UI.player.show();
3331+ }
3332+ }
3333+ },
3334+
3335+ // Escape
3336+ 'U+001B': function(event) {
3337+ if (UI.player.active) {
3338+ UI.player.hide();
3339+ }
3340+ },
3341+ },
3342+
3343+ on_keyup: function(event) {
3344+ console.log('keyup ' + event.keyIdentifier);
3345+ if (document.activeElement != document.body) {
3346+ console.log('document body not focused');
3347+ return;
3348+ }
3349+ if (UI.orb.active) {
3350+ if (event.keyIdentifier == 'U+001B') {
3351+ UI.orb.toggle();
3352+ }
3353+ return;
3354+ }
3355+ var action = UI.actions[event.keyIdentifier];
3356+ if (action) {
3357+ $halt(event);
3358+ action(event);
3359+ }
3360+ },
3361+
3362+ render: function() {
3363+ $("render-btn").disabled = true;
3364+ $("render-btn").blur();
3365+ UI.orb.toggle();
3366+ console.log('render');
3367+ },
3368+
3369+ on_edit_hashed: function(project_id, node_id, intrinsic_id) {
3370+ console.log(['edit_hashed', project_id, node_id, intrinsic_id].join(' '));
3371+ // null for default settings_id:
3372+ Hub.send('hash_job', intrinsic_id, null);
3373+ },
3374+
3375+ on_job_hashed: function(intrinsic_id, settings_id, job_id) {
3376+ console.log(['job_hashed', intrinsic_id, settings_id, job_id].join(' '));
3377+ Hub.send('render_job', job_id);
3378+ },
3379+
3380+ on_job_rendered: function(job_id, file_id, link) {
3381+ console.log(['job_rendered', job_id, file_id, link].join(' '));
3382+ //UI.player.src = 'dmedia:' + file_id;
3383+ //UI.player.play();
3384+ $("render-btn").disabled = false;
3385+ },
3386+}
3387+
3388+function showShort(event){
3389+ console.log('show');
3390+ UI.orb.toggle();
3391+ document.getElementById("shortcuts").style.display = "block";
3392+ event.stopPropagation();
3393+ document.body.onclick = $bind(hideShort);
3394+}
3395+
3396+function hideShort(event){
3397+ console.log('hide');
3398+ document.getElementById("shortcuts").style.display = "none";
3399+ event.stopPropagation();
3400+}
3401+
3402+window.addEventListener('load', UI.init);
3403+
3404+
3405
3406=== added file 'ui/novacut2.js'
3407--- ui/novacut2.js 1970-01-01 00:00:00 +0000
3408+++ ui/novacut2.js 2012-08-14 17:10:21 +0000
3409@@ -0,0 +1,416 @@
3410+"use strict";
3411+
3412+var novacut = new couch.Database('novacut-0');
3413+var dmedia = new couch.Database('dmedia-0');
3414+
3415+function parse_hash() {
3416+ return window.location.hash.slice(1).split('/');
3417+}
3418+
3419+function set_title(id, value) {
3420+ var el = $(id);
3421+ if (value) {
3422+ el.textContent = value;
3423+ }
3424+ else {
3425+ el.textContent = '';
3426+ el.appendChild($el('em', {textContent: 'Untitled'}));
3427+ }
3428+ return el;
3429+}
3430+
3431+
3432+function project_db(base, ver, project_id) {
3433+ var name = [base, ver, project_id.toLowerCase()].join('-');
3434+ return new couch.Database(name);
3435+}
3436+
3437+
3438+function novacut_project_db(project_id) {
3439+ return project_db('novacut', 0, project_id);
3440+}
3441+
3442+
3443+function dmedia_project_db(project_id) {
3444+ return project_db('dmedia', 0, project_id);
3445+}
3446+
3447+
3448+function frame_to_seconds(frame, framerate) {
3449+ return frame * framerate.denom / framerate.num;
3450+}
3451+
3452+
3453+function seconds_to_frame(seconds, framerate) {
3454+ return Math.round(seconds * framerate.num / framerate.denom);
3455+}
3456+
3457+
3458+
3459+function create_node(node) {
3460+ return {
3461+ '_id': couch.random_id(),
3462+ 'ver': 0,
3463+ 'type': 'novacut/node',
3464+ 'time': couch.time(),
3465+ 'node': node,
3466+ }
3467+}
3468+
3469+
3470+function create_slice(src, frame_count) {
3471+ var node = {
3472+ 'type': 'slice',
3473+ 'src': src,
3474+ 'start': {'frame': 0},
3475+ 'stop': {'frame': frame_count},
3476+ 'stream': 'both',
3477+ }
3478+ return create_node(node);
3479+}
3480+
3481+function create_cont() {
3482+ var node = {
3483+ 'type': 'cont',
3484+ 'src': [],
3485+ }
3486+ return create_node(node);
3487+}
3488+
3489+function create_sequence() {
3490+ var node = {
3491+ 'type': 'sequence',
3492+ 'src': [],
3493+ }
3494+ var doc = create_node(node);
3495+ doc.doodle = [];
3496+ return doc;
3497+}
3498+
3499+
3500+function SlicePlayer() {
3501+ this.video = document.createElement('video');
3502+ //this.video.muted = true;
3503+ this.video.addEventListener('canplaythrough',
3504+ $bind(this.on_canplaythrough, this)
3505+ );
3506+ this.video.addEventListener('seeked',
3507+ $bind(this.on_seeked, this)
3508+ );
3509+ this.video.addEventListener('ended',
3510+ $bind(this.on_ended, this)
3511+ );
3512+
3513+ this.timeout_id = null;
3514+
3515+ this.playing = false;
3516+ this.ready = false;
3517+ this.ended = false;
3518+
3519+ this.onready = null;
3520+ this.onended = null;
3521+}
3522+SlicePlayer.prototype = {
3523+ log_event: function(name, point) {
3524+ var frame = seconds_to_frame(this.video.currentTime, this.clip.framerate);
3525+ var parts = [name, frame, point, frame == point];
3526+ console.log(parts.join(' '));
3527+ },
3528+
3529+ on_canplaythrough: function(event) {
3530+ this.seek(this.slice.node.start.frame);
3531+ },
3532+
3533+ on_seeked: function(event) {
3534+ //this.log_event('seeked', this.slice.node.start.frame);
3535+ if (!this.ready) {
3536+ this.ready = true;
3537+ if (this.onready) {
3538+ this.onready(this);
3539+ }
3540+ }
3541+ },
3542+
3543+ on_ended: function(event) {
3544+ this.video.pause();
3545+ this.clear_timeout();
3546+ //this.log_event('ended', this.slice.node.stop.frame);
3547+ this.ended = true;
3548+ if (this.onended) {
3549+ this.onended(this);
3550+ }
3551+ },
3552+
3553+ seek: function(frame) {
3554+ this.video.currentTime = this.frame_to_seconds(frame);
3555+ },
3556+
3557+ frame_to_seconds: function(frame) {
3558+ return frame_to_seconds(frame, this.clip.framerate);
3559+ },
3560+
3561+ clear_timeout: function() {
3562+ if (this.timeout_id != null) {
3563+ clearTimeout(this.timeout_id);
3564+ this.timeout_id = null;
3565+ }
3566+ },
3567+
3568+ set_timeout: function(duration) {
3569+ this.clear_timeout();
3570+ var callback = $bind(this.on_ended, this);
3571+ this.timeout_id = setTimeout(callback, duration);
3572+ },
3573+
3574+ playpause: function() {
3575+ if (this.playing) {
3576+ this.pause();
3577+ }
3578+ else {
3579+ this.play();
3580+ }
3581+ },
3582+
3583+ play: function() {
3584+ if (this.playing || !this.ready) {
3585+ return;
3586+ }
3587+ this.playing = true;
3588+ this.video.style.visibility = 'visible';
3589+ if (this.slice.node.stop.frame != this.clip.duration.frames) {
3590+ var stop = this.frame_to_seconds(this.slice.node.stop.frame);
3591+ var duration = 1000 * (stop - this.video.currentTime);
3592+ this.set_timeout(duration);
3593+ }
3594+ this.video.play();
3595+ },
3596+
3597+ pause: function() {
3598+ if (!this.ready) {
3599+ return;
3600+ }
3601+ this.playing = false;
3602+ this.video.style.visibility = 'visible';
3603+ this.clear_timeout();
3604+ this.video.pause();
3605+ },
3606+
3607+ stop: function() {
3608+ this.video.style.visibility = 'hidden';
3609+ this.video.pause();
3610+ this.clear_timeout();
3611+ this.ended = false;
3612+ this.ready = false;
3613+ this.playing = false;
3614+ },
3615+
3616+ load_slice: function(clip, slice) {
3617+ this.stop();
3618+ this.clip = clip;
3619+ this.slice = slice;
3620+ this.video.src = 'dmedia:' + this.clip._id;
3621+ //setTimeout($bind(this.do_load, this), 50);
3622+ },
3623+
3624+ do_load: function() {
3625+ this.video.src = 'dmedia:' + this.clip._id;
3626+ },
3627+}
3628+
3629+
3630+
3631+function SequencePlayer(session, doc) {
3632+ this.session = session;
3633+ this.doc = doc;
3634+
3635+ this.element = $el('div', {'id': 'player', 'class': 'hide'});
3636+
3637+ this.player1 = new SlicePlayer();
3638+ this.player1.i = 0;
3639+ this.player2 = new SlicePlayer();
3640+ this.player2.i = 1;
3641+
3642+ this.players = [this.player1, this.player2];
3643+ var on_ready = $bind(this.on_ready, this);
3644+ var on_ended = $bind(this.on_ended, this);
3645+ this.players.forEach(function(player) {
3646+ player.onready = on_ready;
3647+ player.onended = on_ended;
3648+ this.element.appendChild(player.video);
3649+ }, this);
3650+
3651+ this.ready = false;
3652+ this.playing = false;
3653+ this.active = false;
3654+
3655+ this.timeout_id == null
3656+
3657+ this.element.onclick = $bind(this.playpause, this);
3658+
3659+ document.body.appendChild(this.element);
3660+
3661+}
3662+SequencePlayer.prototype = {
3663+ show: function() {
3664+ console.log('show');
3665+ if (this.doc.node.src.length == 0) {
3666+ return;
3667+ }
3668+ $show(this.element);
3669+ this.active = true;
3670+ this.playing = true;
3671+ this.play_from_slice(UI.selected);
3672+ },
3673+
3674+ soft_hide: function() {
3675+ $hide(this.element);
3676+ console.assert(!this.playing);
3677+ },
3678+
3679+ soft_show: function() {
3680+ $show(this.element);
3681+ console.assert(!this.playing);
3682+ },
3683+
3684+ hide: function() {
3685+ console.log('hide');
3686+ $hide(this.element);
3687+ this.active = false;
3688+ this.stop();
3689+ },
3690+
3691+ playpause: function() {
3692+ if (this.playing) {
3693+ this.pause();
3694+ }
3695+ else {
3696+ this.play();
3697+ }
3698+ },
3699+
3700+ hold: function() {
3701+ this.was_playing = this.playing;
3702+ this.playing = false;
3703+ this.activate_target(true);
3704+ },
3705+
3706+ hold_and_resume: function() {
3707+ if (this.timeout_id == null) {
3708+ console.log('first hold');
3709+ this.hold();
3710+ }
3711+ clearTimeout(this.timeout_id);
3712+ this.timeout_id = setTimeout($bind(this.on_timeout, this), 500);
3713+ },
3714+
3715+ on_timeout: function() {
3716+ console.log('timeout');
3717+ this.timeout_id = null;
3718+ this.resume();
3719+ },
3720+
3721+ resume: function() {
3722+ this.playing = this.was_playing;
3723+ if (UI.selected) {
3724+ this.play_from_slice(UI.selected);
3725+ }
3726+ else {
3727+ this.activate_target(true);
3728+ }
3729+ },
3730+
3731+ stop: function() {
3732+ this.players.forEach(function(player) {
3733+ player.stop();
3734+ });
3735+ this.ready = false;
3736+ this.target = null;
3737+ },
3738+
3739+ play: function() {
3740+ this.playing = true;
3741+ this.activate_target();
3742+ },
3743+
3744+ pause: function() {
3745+ this.playing = false;
3746+ this.activate_target();
3747+ },
3748+
3749+ play_from_slice: function(slice_id) {
3750+ if (this.doc.node.src.length == 0) {
3751+ return;
3752+ }
3753+ this.stop();
3754+ if (!slice_id) {
3755+ slice_id = UI.selected;
3756+ }
3757+ console.log('play_from_slice ' + slice_id);
3758+ var index = Math.max(0, this.doc.node.src.indexOf(slice_id));
3759+ this.load_slice(this.player1, index);
3760+ this.load_slice(this.player2, index + 1);
3761+ },
3762+
3763+ get_player: function(i) {
3764+ return this.players[i % this.players.length];
3765+ },
3766+
3767+ next_slice_index: function(player) {
3768+ return this.doc.node.src.indexOf(player.slice._id) + 1;
3769+ },
3770+
3771+ load_slice: function(player, index) {
3772+ var src = this.doc.node.src;
3773+ var slice_id = src[index % src.length];
3774+ var slice = this.session.get_doc(slice_id);
3775+ var clip = this.session.get_doc(slice.node.src);
3776+ player.load_slice(clip, slice);
3777+ },
3778+
3779+ on_ready: function(player) {
3780+ if (!this.ready) {
3781+ if (this.player1.ready && this.player2.ready) {
3782+ console.log('now ready');
3783+ this.ready = true;
3784+ this.target = this.players[0];
3785+ this.activate_target();
3786+ }
3787+ }
3788+ else if (this.target.ended) {
3789+ this.swap();
3790+ }
3791+ },
3792+
3793+ activate_target: function(no_select) {
3794+ if (!this.target) {
3795+ return;
3796+ }
3797+ if (this.playing) {
3798+ this.target.play();
3799+ }
3800+ else {
3801+ this.target.pause();
3802+ }
3803+ if (!no_select) {
3804+ UI.select(this.target.slice._id, true);
3805+ }
3806+ },
3807+
3808+ swap: function() {
3809+ var current = this.target;
3810+ var next = this.get_player(current.i + 1);
3811+ if (next.ready) {
3812+ this.target = next;
3813+ var index = this.next_slice_index(next);
3814+ this.activate_target();
3815+ this.load_slice(current, index);
3816+ }
3817+ },
3818+
3819+ on_ended: function(player) {
3820+ this.swap();
3821+ },
3822+}
3823+
3824+
3825+

Subscribers

People subscribed via source and target branches

to all changes:
to status/vote changes: