diff --git a/source/Ox.UI/css/Ox.UI.css b/source/Ox.UI/css/Ox.UI.css index eb61dfbe..763e38c2 100644 --- a/source/Ox.UI/css/Ox.UI.css +++ b/source/Ox.UI/css/Ox.UI.css @@ -627,10 +627,16 @@ OxArrayEditable -------------------------------------------------------------------------------- */ -.OxArrayEditable { - //display: table-cell; +.OxArrayEditable.OxArrayEditableInput { padding: 4px; } +.OxArrayEditable.OxArrayEditableTextarea .OxEditableElement { + padding: 4px; + border-top: 1px solid rgb(128, 128, 128); +} +.OxArrayEditable.OxArrayEditableTextarea .OxEditableElement:first-child { + border-top: 0px +} /* -------------------------------------------------------------------------------- diff --git a/source/Ox.UI/js/Form/Ox.ArrayEditable.js b/source/Ox.UI/js/Form/Ox.ArrayEditable.js index bd25c07a..25869871 100644 --- a/source/Ox.UI/js/Form/Ox.ArrayEditable.js +++ b/source/Ox.UI/js/Form/Ox.ArrayEditable.js @@ -21,7 +21,7 @@ Ox.ArrayEditable = function(options, self) { width: 256 }) .options(options || {}) - .addClass('OxArrayEditable') + .addClass('OxArrayEditable OxArrayEditable' + Ox.toTitleCase(self.options.type)) .css({width: self.options.width - 8 + 'px'}) // 2 x 4 px padding .bindEvent({ anyclick: anyclick, @@ -29,10 +29,10 @@ Ox.ArrayEditable = function(options, self) { key_delete: deleteItem, key_enter: editItem, key_escape: selectNone, - key_down: selectLast, - key_left: selectPrevious, - key_right: selectNext, - key_up: selectFirst + key_down: self.options.type == 'input' ? selectLast : selectNext, + key_left: self.options.type == 'input' ? selectPrevious : selectFirst, + key_right: self.options.type == 'input' ? selectNext : selectLast, + key_up: self.options.type == 'input' ? selectFirst : selectPrevious }); self.$items = []; @@ -122,7 +122,7 @@ Ox.ArrayEditable = function(options, self) { ), type: self.options.type, value: item.value, - width: self.options.type == 'input' ? 0 : self.options.width + width: self.options.type == 'input' ? 0 : self.options.width - 8 }) .addClass(item.id == self.options.selected ? 'OxSelected' : '') .data({position: i}) @@ -160,7 +160,7 @@ Ox.ArrayEditable = function(options, self) { } that.find('.OxSelected').removeClass('OxSelected'); self.selected > -1 && self.$items[self.selected].addClass('OxSelected'); - that.triggerEvent('select', {id: self.options.selected}); + triggerSelectEvent(); } function selectLast() { @@ -197,6 +197,16 @@ Ox.ArrayEditable = function(options, self) { } } + function triggerSelectEvent() { + if (!self.triggered) { + that.triggerEvent('select', {id: self.options.selected}); + self.triggered = true; + setTimeout(function() { + self.triggered = false; + }, 250); + } + }; + self.setOption = function(key, value) { if (key == 'items') { renderItems(); diff --git a/source/Ox.UI/js/Form/Ox.Editable.js b/source/Ox.UI/js/Form/Ox.Editable.js index 5a9243ff..3f4b9471 100644 --- a/source/Ox.UI/js/Form/Ox.Editable.js +++ b/source/Ox.UI/js/Form/Ox.Editable.js @@ -87,7 +87,7 @@ Ox.Editable = function(options, self) { //height = self.options.height || Ox.limit(self.$test.height() + 2, self.minHeight, self.maxHeight); height = self.options.height || Math.max(self.$test.height() + 2, self.minHeight); width = self.options.width || Ox.limit(self.$test.width() + 2, self.minWidth, self.maxWidth); - Ox.Log('Form', self.options.width, self.$test.width(), 'wxh', width, height) + Ox.Log('Form', self.options.width, self.$test.html(), self.$test.width(), self.$test.height(), 'wxh', width, height) if (self.options.type == 'input') { self.$input.options({ width: width @@ -184,8 +184,14 @@ Ox.Editable = function(options, self) { function formatTestValue() { return self.options.type == 'input' ? self.options.value.replace(/ /g, ' ') + : Ox.encodeHTML(self.options.value || ' ') + //.replace(/ /g, ' ') + .replace(/\n$/, '\n ') + .replace(/\n/g, '
') + /* : Ox.parseHTML(self.options.value || ' ') - .replace(/$/, '
 '); + .replace(/(\n|)$/, '
 '); + */ } function formatValue() { diff --git a/source/Ox.UI/js/List/Ox.List.js b/source/Ox.UI/js/List/Ox.List.js index b6983db4..603058d5 100644 --- a/source/Ox.UI/js/List/Ox.List.js +++ b/source/Ox.UI/js/List/Ox.List.js @@ -1282,6 +1282,8 @@ Ox.List = function(options, self) { } function triggerSelectEvent() { + // FIXME: the first select event should fire immediately + // see ArrayEditable getSelectedIds(function(ids) { self.options.selected = ids; setTimeout(function() { diff --git a/source/Ox.UI/js/Video/Ox.AnnotationPanel.js b/source/Ox.UI/js/Video/Ox.AnnotationPanel.js index b0880080..b5b59cbc 100644 --- a/source/Ox.UI/js/Video/Ox.AnnotationPanel.js +++ b/source/Ox.UI/js/Video/Ox.AnnotationPanel.js @@ -85,14 +85,12 @@ Ox.AnnotationPanel = function(options, self) { selected: self.options.selected, sort: self.sort, submitOnBlur: false, - width: self.options.width, + width: self.options.type == 'text' ? self.options.width + 8 : self.options.width, type: self.options.type == 'text' ? 'textarea' : 'input' }) .bindEvent({ add: function(data) { - that.triggerEvent('add', { - value: data.value || '' - }); + that.triggerEvent('add', {value: data.value || ''}); }, blur: function() { that.triggerEvent('blur'); @@ -196,6 +194,10 @@ Ox.AnnotationPanel = function(options, self) { self.$annotations.blurItem(); }; + that.editItem = function(id) { + self.$annotations.editItem(id); + }; + /*@ removeItems removeItems @*/ diff --git a/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js b/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js index 97860c85..d9724d02 100644 --- a/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js +++ b/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js @@ -31,6 +31,7 @@ Ox.BlockVideoTimeline = function(options, self) { mousemove: mousemove }) .bindEvent({ + doubleclick: doubleclick, drag: function(data) { mousedown(data); } @@ -56,6 +57,7 @@ Ox.BlockVideoTimeline = function(options, self) { 'in': self.options['in'], out: self.options.out, results: self.options.results, + state: self.options.state, subtitles: self.options.showSubtitles ? self.options.subtitles : [], timeline: self.options.getImageURL, width: Math.round(self.options.duration), @@ -118,6 +120,22 @@ Ox.BlockVideoTimeline = function(options, self) { .appendTo(self.$lines[i]); } + function doubleclick(e) { + var position; + if ($(e.target).is('.OxInterface')) { + position = getPosition(e); + if ( + self.options.state == 'selected' + && position >= self.options['in'] + && position <= self.options.out + ) { + that.triggerEvent('edit'); + } else if (self.options.state != 'editing') { + that.triggerEvent('select'); + } + } + } + function getLines() { return Math.ceil(self.options.duration / self.options.width); } @@ -138,6 +156,10 @@ Ox.BlockVideoTimeline = function(options, self) { return subtitle; } + function getTooltip(e) { + + } + function mousedown(e) { if ($(e.target).is('.OxInterface')) { self.options.position = getPosition(e); diff --git a/source/Ox.UI/js/Video/Ox.SmallVideoTimeline.js b/source/Ox.UI/js/Video/Ox.SmallVideoTimeline.js index 889d9198..b8f31449 100644 --- a/source/Ox.UI/js/Video/Ox.SmallVideoTimeline.js +++ b/source/Ox.UI/js/Video/Ox.SmallVideoTimeline.js @@ -1,11 +1,17 @@ 'use strict'; +/*@ +Ox.SmallVideoTimeline Small Video Timeline +@*/ + Ox.SmallVideoTimeline = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ - _offset: 0, // hack for cases where all these position: absolute elements have to go into a float: left + // _offset is a hack for cases where all these position: absolute + // elements have to go into a float: left + _offset: 0, disabled: false, duration: 0, find: '', @@ -53,9 +59,6 @@ Ox.SmallVideoTimeline = function(options, self) { width: self.interfaceWidth + 'px', height: '20px', }) - .bind({ - mousedown: mousedown - }) .bindEvent({ drag: function(data) { mousedown(data); @@ -63,7 +66,8 @@ Ox.SmallVideoTimeline = function(options, self) { dragend: function(data) { self.triggered = false; mousedown(data); - } + }, + mousedown: mousedown }) .appendTo(that); diff --git a/source/Ox.UI/js/Video/Ox.SmallVideoTimelineImage.js b/source/Ox.UI/js/Video/Ox.SmallVideoTimelineImage.js index f7048d8f..714f2b43 100644 --- a/source/Ox.UI/js/Video/Ox.SmallVideoTimelineImage.js +++ b/source/Ox.UI/js/Video/Ox.SmallVideoTimelineImage.js @@ -151,7 +151,7 @@ Ox.SmallVideoTimelineImage = function(options, self) { Ox.loop(left, right, function(x) { Ox.loop(top, bottom, function(y) { var alpha = self.options.type == 'editor' - && (y == top || y == bottom - 1) ? 192 : 64, + && (y == top || y == bottom - 1) ? 255 : 128, color = rgb[[2, 3, 6].indexOf(x % 4 + y % 4) > -1 ? 0 : 1], index = x * 4 + y * 4 * width; data[index] = color[0]; diff --git a/source/Ox.UI/js/Video/Ox.VideoEditor.js b/source/Ox.UI/js/Video/Ox.VideoEditor.js index 71de34e4..e2de1bae 100644 --- a/source/Ox.UI/js/Video/Ox.VideoEditor.js +++ b/source/Ox.UI/js/Video/Ox.VideoEditor.js @@ -69,10 +69,10 @@ Ox.VideoEditor = function(options, self) { key_alt_shift_right: function() { }, key_backslash: function() { - select('subtitle'); + selectAnnotation(); }, key_closebracket: function() { - movePositionTo('subtitle', 1); + movePositionTo('annotation', 1); }, key_comma: function() { movePositionTo('cut', -1); @@ -83,6 +83,20 @@ Ox.VideoEditor = function(options, self) { key_down: function() { movePositionBy(self.sizes.timeline[0].width); }, + key_enter: function() { + if (self.editiing) { + submitAnnotation(); + } else if (self.options.selected) { + editAnnotation(); + } + }, + key_escape: function() { + if (self.editing) { + blurAnnotation(); + } else if (self.options.selected) { + deselectAnnotation(); + } + }, key_f: function() { setTimeout(function() { self.$findInput.focusInput(true); @@ -101,7 +115,7 @@ Ox.VideoEditor = function(options, self) { setPoint('out', self.options.position); }, key_openbracket: function() { - movePositionTo('subtitle', -1); + movePositionTo('annotation', -1); }, key_p: playInToOut, key_right: function() { @@ -141,7 +155,7 @@ Ox.VideoEditor = function(options, self) { movePositionBy(-self.options.position); }, key_slash: function() { - select('cut'); + selectCut(); }, key_space: togglePaused, key_up: function() { @@ -149,6 +163,12 @@ Ox.VideoEditor = function(options, self) { } }); + Ox.loop(1, self.options.layers.length + 1, function(i) { + that.bindEvent('key_' + i, function() { + addAnnotation(self.options.layers[i - 1].id); + }); + }); + Ox.extend(self, { $player: [], $timeline: [], @@ -157,7 +177,7 @@ Ox.VideoEditor = function(options, self) { margin: 8, }); - //Ox.print('VIDEO EDITOR OPTIONS', self.options) + Ox.print('VIDEO EDITOR OPTIONS', self.options) self.words = []; Ox.forEach(Ox.count(Ox.words(self.options.subtitles.map(function(subtitle) { @@ -306,8 +326,12 @@ Ox.VideoEditor = function(options, self) { top: self.sizes.timeline[1].top + 'px', }) .bindEvent({ + edit: editAnnotation, position: function(data) { setPosition(data.position); + }, + select: function() { + selectAnnotation(); } }) .appendTo(self.$editor); @@ -335,10 +359,7 @@ Ox.VideoEditor = function(options, self) { ) .bindEvent({ add: function(data) { - data.layer = layer.id; - data['in'] = self.options['in']; - data.out = self.options.out; - that.triggerEvent('addannotation', data); + addAnnotation(layer.id); }, blur: function() { //Ox.print('VIDEO EDITOR BLUR FOCUSED?', self.focused) @@ -346,13 +367,13 @@ Ox.VideoEditor = function(options, self) { // ... } else { self.editing = false; - setState(); + setTimelineState(); this.blurItem(); } }, edit: function() { self.editing = true; - setState(); + setTimelineState(); }, remove: function(data) { that.triggerEvent('removeannotation', { @@ -372,7 +393,7 @@ Ox.VideoEditor = function(options, self) { // ... } }, - submit: editAnnotation + submit: submitAnnotation }) .appendTo(self.$annotations); }); @@ -756,12 +777,31 @@ Ox.VideoEditor = function(options, self) { submitFindInput(self.options.find, true); }, 0); - function editAnnotation(data) { + function addAnnotation(layer) { + that.triggerEvent('addannotation', { + 'in': self.options['in'], + layer: layer, + out: self.options.out, + value: '' + }); + } + + function blurAnnotation() { self.editing = false; - setState(); - data['in'] = self.options['in']; - data.out = self.options.out; - that.triggerEvent('editannotation', data); + setTimelineState(); + getPanel(self.options.selected).blurItem(self.options.selected); + } + + function deselectAnnotation() { + // FIXME: there is selectAnnotation({id: ''}) + self.options.selected = ''; + setTimelineState(); + } + + function editAnnotation() { + self.editing = true; + setTimelineState(); + getPanel(self.options.selected).editItem(self.options.selected); } function find(query) { @@ -778,21 +818,60 @@ Ox.VideoEditor = function(options, self) { return results; } + function getAnnotation() { + // Get annotation at current position + var annotations = []; + self.options.layers.forEach(function(layer) { + layer.items.forEach(function(item) { + if ( + item['in'] <= self.options.position + && item.out >= self.options.position + ) { + annotations.push(item); + } + }); + }); + annotations.sort(function(a, b) { + var aValue = self.options.position - a['in'], + bValue = self.options.position - b['in'], + ret = 0; + if (aValue < bValue) { + ret = -1; + } else if (aValue > bValue) { + ret = 1; + } else if (a.duration < b.duration) { + ret = -1 + } else if (a.duration > b.duration) { + ret = 1; + } else if (a.value < b.value) { + ret = -1 + } else if (a.value > b.value) { + ret = 1; + } + return ret; + }); + return annotations.length ? annotations[0] : {id: ''}; + } + // fixme: why not goToNextPosition()? function getNextPosition(type, direction) { var found = false, position = 0, positions; - if (type == 'cut') { - positions = self.options.cuts; + if (type == 'annotation') { + positions = Ox.sort(Ox.unique(Ox.flatten( + self.options.layers.map(function(layer) { + return layer.items.map(function(item) { + return item['in']; + }); + }) + ))); + } else if (type == 'cut') { + positions = Ox.merge(0, self.options.cuts, self.options.duration); } else if (type == 'result') { positions = self.results.map(function(v) { return v['in']; }); - } else if (type == 'subtitle') { - positions = self.options.subtitles.map(function(v) { - return v['in']; - }); } direction == -1 && positions.reverse(); Ox.forEach(positions, function(v) { @@ -809,23 +888,42 @@ Ox.VideoEditor = function(options, self) { return position; } + function getPanel(annotationId) { + var found = false, panel; + Ox.forEach(self.options.layers, function(layer, i) { + Ox.forEach(layer.items, function(item) { + if (item.id == annotationId) { + panel = self.$annotationPanel[i]; + found = true; + return false; + } + }); + return !found; + }); + return panel; + } + + /* function getPoints(type) { var found = false, points, positions = []; - if (type == 'cut') { + if (type == 'annotation') { + + } else if (type == 'cut') { positions = self.options.cuts; - } else if (type == 'match') { + } else if (type == 'result') { // ... } else if (type == 'subtitle') { + // FIXME: remove? annotation? self.options.subtitles.forEach(function(v, i) { positions.push(v['in']); positions.push(v.out); }); } positions.indexOf(0) == -1 && positions.unshift(0); - positions.indexOf(self.options.duration) == -1 && - positions.push(self.options.duration); + positions.indexOf(self.options.duration) == -1 + && positions.push(self.options.duration); Ox.forEach(positions, function(v, i) { if (v > self.options.position) { points = [positions[i - 1], positions[i]]; @@ -835,6 +933,7 @@ Ox.VideoEditor = function(options, self) { }); return points; } + */ function getSizes(scrollbarIsVisible) { var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE, @@ -932,12 +1031,8 @@ Ox.VideoEditor = function(options, self) { function resizeEditor(data) { var width = data.size - 2 * margin + 100; resizeVideoPlayers(width); - $timelineLarge.options({ - width: width - }); - $timelineSmall.options({ - width: width - }); + $timelineLarge.options({width: width}); + $timelineSmall.options({width: width}); } function resizePlayers() { @@ -955,22 +1050,44 @@ Ox.VideoEditor = function(options, self) { function selectAnnotation(data) { //Ox.print('VE.sA') + if (Ox.isUndefined(data)) { + data = getAnnotation(); + } self.editing = false; - self.options.selected = data.id || ''; - self.options.annotationsRange != 'position' && setPosition(data['in']); - setState(); - that.triggerEvent('select', { - id: self.options.selected - }); + self.options.selected = data.id; + if (self.options.selected && self.options.annotationsRange != 'position') { + setPosition(data['in']); + } if (self.options.selected) { setPoint('in', data['in']); setPoint('out', data.out); + Ox.print('???', JSON.stringify(self.options.selected), getPanel(self.options.selected)) + getPanel(self.options.selected).options({selected: self.options.selected}); } + setTimelineState(); + that.triggerEvent('select', { + id: self.options.selected + }); } - function select(type) { - self.options.points = getPoints(type); - setPoints(); + function selectCut() { + var points = { + 'in': Ox.last(self.options.cuts), + out: self.options.duration + }; + Ox.forEach(self.options.cuts, function(cut, i) { + if (cut > self.options.position) { + points = { + 'in': i ? self.options.cuts[i - 1] : 0, + out: cut - 1 / self.options.fps + }; + return false; + } + }); + self.options.selected = ''; + setTimelineState(); + setPoint('in', points['in']); + setPoint('out', points.out); } function setPoint(point, position, annotation) { @@ -980,7 +1097,7 @@ Ox.VideoEditor = function(options, self) { // should only happen through interaction if (self.options.selected && !self.editing) { self.options.selected = ''; - setState(); + setTimelineState(); } */ self.$player.forEach(function($player) { @@ -1057,7 +1174,7 @@ Ox.VideoEditor = function(options, self) { }); } - function setState() { + function setTimelineState() { self.$timeline[1].options({ state: self.editing ? 'editing' : self.options.selected ? 'selected' @@ -1066,6 +1183,14 @@ Ox.VideoEditor = function(options, self) { //Ox.print('SET STATE', self.$timeline[1].options('state')) } + function submitAnnotation(data) { + self.editing = false; + setTimelineState(); + data['in'] = self.options['in']; + data.out = self.options.out; + that.triggerEvent('editannotation', data); + } + function submitFindInput(value, hasPressedEnter) { self.options.find = value; self.results = find(self.options.find); diff --git a/source/Ox.UI/themes/classic/css/classic.css b/source/Ox.UI/themes/classic/css/classic.css index 38d4af1d..495d9022 100644 --- a/source/Ox.UI/themes/classic/css/classic.css +++ b/source/Ox.UI/themes/classic/css/classic.css @@ -747,6 +747,9 @@ Video background: rgb(160, 224, 160); box-shadow: 0 0 1px rgb(96, 96, 96); } +.OxThemeClassic .OxAnnotationPanel .OxEditableElement textarea { + background: rgb(160, 224, 160); +} /* ================================================================================