diff --git a/pandora/item/models.py b/pandora/item/models.py index 22f6d25fa..9f88eff86 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -141,7 +141,7 @@ class Item(models.Model): #while metadata is updated, files are set to rendered=False rendered = models.BooleanField(default=False, db_index=True) #should be set based on user - level = models.IntegerField(default=4, db_index=True) + level = models.IntegerField(db_index=True) itemId = models.CharField(max_length=128, unique=True, blank=True) oxdbId = models.CharField(max_length=42, unique=True, blank=True, null=True) @@ -260,6 +260,8 @@ class Item(models.Model): if not self.id: if self.user: self.level = settings.CONFIG['rightsLevel'][self.user.get_profile().get_level()] + else: + self.level = settings.CONFIG['rightsLevel']['member'] if not self.itemId: self.itemId = str(uuid.uuid1()) super(Item, self).save(*args, **kwargs) @@ -441,6 +443,8 @@ class Item(models.Model): 'rendered': self.rendered, 'rightslevel': self.level } + if self.user: + i['user'] = self.user.username i.update(self.external_data) i.update(self.data) for k in settings.CONFIG['itemKeys']: diff --git a/static/js/pandora.js b/static/js/pandora.js index 5d8291cd2..abe406e9f 100644 --- a/static/js/pandora.js +++ b/static/js/pandora.js @@ -152,6 +152,13 @@ appPanel }); loadPandoraFiles(function() { initPandora(data); + if (localStorage && localStorage.pandoraLocal) { + window.pandora.local = Ox.API({ + 'url': localStorage.pandoraLocal + '/api/' + }, function() { + pandora.site.site.videoprefix = localStorage.pandoraLocal; + }); + } }); } }); diff --git a/static/js/pandora/infoView.js b/static/js/pandora/infoView.js index 83fc06732..5b60d5399 100644 --- a/static/js/pandora/infoView.js +++ b/static/js/pandora/infoView.js @@ -782,7 +782,7 @@ pandora.ui.infoView = function(data) { .data({OxColor: $rightsLevelElement.data('OxColor')}) .bindEvent({ change: function(event) { - var rightsLevel = event.value + var rightsLevel = event.value; $rightsLevelElement = getRightsLevelElement(rightsLevel); $rightsLevelSelect .css({background: $rightsLevelElement.css('background')}) diff --git a/static/js/pandora/infoView.padma.js b/static/js/pandora/infoView.padma.js new file mode 100644 index 000000000..1d1801ba5 --- /dev/null +++ b/static/js/pandora/infoView.padma.js @@ -0,0 +1,623 @@ +'use strict'; + +pandora.ui.infoView = function(data) { + + // fixme: given that currently, the info view doesn't scroll into view nicely + // when collapsing the movies browser, the info view should become a split panel + + var ui = pandora.user.ui, + canEdit = pandora.site.capabilities.canEditMetadata[pandora.user.level], + css = { + marginTop: '4px', + textAlign: 'justify', + MozUserSelect: 'text', + WebkitUserSelect: 'text' + }, + iconRatio = ui.icons == 'posters' + ? (ui.showSitePoster ? 5/8 : data.posterRatio) : 1, + iconSize = ui.infoIconSize, + iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio), + iconHeight = iconRatio < 1 ? iconSize : Math.round(iconSize / iconRatio), + iconLeft = iconSize == 256 ? Math.floor((iconSize - iconWidth) / 2) : 0, + borderRadius = ui.icons == 'posters' ? 0 : iconSize / 8, + isEditable = canEdit, + listWidth = 144 + Ox.UI.SCROLLBAR_SIZE, + margin = 16, + statisticsWidth = 128, + uid = Ox.uid(), + + that = Ox.Element(), + + $list, + + $info = $('
') + .css({ + position: 'absolute', + left: canEdit && !ui.showIconBrowser ? -listWidth + 'px' : 0, + top: 0, + right: 0, + }) + .appendTo(that.$element), + + $data = Ox.Container() + .css({ + position: 'absolute', + left: (canEdit ? listWidth : 0) + 'px', + top: 0, + right: 0, + height: pandora.$ui.contentPanel.size(1) + 'px' + }) + .appendTo($info), + + $icon = Ox.Element({ + element: '', + }) + .attr({ + src: '/' + data.id + '/' + ( + ui.icons == 'posters' + ? (ui.showSitePoster ? 'siteposter' : 'poster') : 'icon' + ) + '512.jpg?' + uid + }) + .css({ + position: 'absolute', + left: margin + iconLeft + 'px', + top: margin + 'px', + width: iconWidth + 'px', + height: iconHeight + 'px', + borderRadius: borderRadius + 'px', + cursor: 'pointer' + }) + .bindEvent({ + singleclick: toggleIconSize + }) + .appendTo($data.$element), + + $reflection = $('
') + .addClass('OxReflection') + .css({ + position: 'absolute', + left: margin + 'px', + top: margin + iconHeight + 'px', + width: iconSize + 'px', + height: iconSize / 2 + 'px', + overflow: 'hidden' + }) + .appendTo($data.$element), + + $reflectionIcon = $('') + .attr({ + src: '/' + data.id + '/' + ( + ui.icons == 'posters' ? 'poster' : 'icon' + ) + '512.jpg?' + uid + }) + .css({ + position: 'absolute', + left: iconLeft + 'px', + width: iconWidth + 'px', + height: iconHeight + 'px', + borderRadius: borderRadius + 'px' + }) + .appendTo($reflection), + + $reflectionGradient = $('
') + .css({ + position: 'absolute', + width: iconSize + 'px', + height: iconSize / 2 + 'px' + }) + .appendTo($reflection), + + $text = Ox.Element({ + tooltip: canEdit && !isEditable ? 'Doubleclick to reload metadata' : '' + }) + .css({ + position: 'absolute', + left: margin + (iconSize == 256 ? 256 : iconWidth) + margin + 'px', + top: margin + 'px', + right: margin + statisticsWidth + margin + 'px', + //background: 'green' + }) + .bindEvent(canEdit && !isEditable ? { + doubleclick: reloadMetadata + } : {}) + .appendTo($data.$element), + + $statistics = $('
') + .css({ + position: 'absolute', + width: statisticsWidth + 'px', + top: margin + 'px', + right: margin + 'px' + }) + .appendTo($data.$element), + + $capabilities, + + $browserImages = []; + + pandora.createLinks($text); + + // Title ------------------------------------------------------------------- + + $('
') + .css({ + marginTop: '-2px' + }) + .append( + Ox.Editable({ + editable: isEditable, + format: function(value) { + return formatTitle(value); + }, + tooltip: isEditable ? 'Doubleclick to edit' : '', + value: data.title + }) + .css({ + display: 'inline-block', + marginBottom: '-3px', + fontWeight: 'bold', + fontSize: '13px', + MozUserSelect: 'text', + WebkitUserSelect: 'text' + }) + .bindEvent({ + submit: function(event) { + editMetadata('title', event.value); + } + }) + .appendTo($text) + ) + .appendTo($text); + + // Director ---------------------------------------------------------------- + + if (data.director || isEditable) { + $('
') + .css({ + marginTop: '2px' + }) + .append( + Ox.Editable({ + clickLink: pandora.clickLink, + editable: isEditable, + format: function(value) { + return formatValue(value.split(', '), 'name'); + }, + placeholder: formatLight('Unknown Director'), + tooltip: isEditable ? 'Doubleclick to edit' : '', + value: data.director ? data.director.join(', ') : 'Unknown Director' + }) + .css({ + display: 'inline-block', + marginBottom: '-3px', + fontWeight: 'bold', + fontSize: '13px', + MozUserSelect: 'text', + WebkitUserSelect: 'text' + }) + .bindEvent({ + submit: function(event) { + editMetadata('director', event.value); + } + }) + ) + .appendTo($text); + } + + // Country, Year, Language, Runtime ---------------------------------------- + + if (isEditable) { + var $div = $('
') + .css(css) + .appendTo($text); + ['country', 'year'].forEach(function(key) { + $('
') + .css({float: 'left'}) + .html(formatKey(key).replace('', ' ')) + .appendTo($div); + Ox.Editable({ + clickLink: pandora.clickLink, + format: function(value) { + return formatValue(value.split(', '), key) + }, + placeholder: formatLight('unknown'), + tooltip: 'Doubleclick to edit', + value: key == 'country' + ? (data[key] ? data[key].join(', ') : ['']) + : data[key] || '' + }) + .css({float: 'left'}) + .bindEvent({ + submit: function(event) { + editMetadata(key, event.value); + } + }) + .appendTo($div); + key == 'country' && $('
').css({float: 'left'}).html('; ').appendTo($div); + }); + } else if (data.country || data.year || data.language || data.runtime) { + var html = []; + ['country', 'year', 'language', 'runtime'].forEach(function(key) { + if (data[key]) { + html.push( + formatKey(key) + + (key == 'runtime' + ? Math.round(data[key] / 60) + ' min' + : formatValue(data[key], key)) + ) + } + }); + $('
').css(css).html(html.join('; ')).appendTo($text); + } + $('
').html('
').appendTo($text); + //Categories + $('
') + .css(css) + .html( + formatKey('categories') + formatValue(data['category'], 'category') + ) + .appendTo($text); + + [ + 'source', + 'collection', + 'category', + 'user', + 'location', + ].forEach(function(key) { + $('
') + .html( + formatKey(key=='user'?'contributor':key) + formatValue(data[key], key) + ) + .appendTo($text); + }); + [ + 'date', + 'modified', + 'accessed', + 'created', + ].forEach(function(key) { + $('
') + .html( + formatKey(key=='user'?'contributor':key) + data[key] + ) + .appendTo($text); + }); + data.description && $('
') + .css(css) + .html( + formatKey('description') + data.description + ) + .appendTo($text); + $('
').css({height: '16px'}).appendTo($text); + + // Hue, Saturation, Lightness, Volume -------------------------------------- + + ['hue', 'saturation', 'lightness', 'volume'].forEach(function(key) { + $('
') + .css({marginBottom: '4px'}) + .append(formatKey(key, true)) + .append( + Ox.Theme.formatColor( + data[key] || 0, key == 'volume' ? 'lightness' : key + ).css({textAlign: 'right'}) + ) + .appendTo($statistics); + }); + + // Rights Level ------------------------------------------------------------ + + var $rightsLevel = $('
'); + $('
') + .css({marginBottom: '4px'}) + .append(formatKey('Rights Level', true)) + .append($rightsLevel) + .appendTo($statistics); + renderRightsLevel(); + + // Notes ------------------------------------------------------------------- + + if (canEdit) { + + $('
') + .css({marginBottom: '4px'}) + .append(formatKey('Notes', true)) + .append( + Ox.Editable({ + height: 128, + placeholder: formatLight('No notes'), + tooltip: 'Doubleclick to edit', + type: 'textarea', + value: data.notes, + width: 128 + }) + .bindEvent({ + submit: function(event) { + pandora.api.edit({ + id: data.id, + notes: event.value + }, function(result) { + // ... + }); + } + }) + ) + .appendTo($statistics); + + } + + $('
').css({height: '16px'}).appendTo($statistics); + + function editMetadata(key, value) { + if (value != data[key]) { + var edit = {id: data.id}; + if (key == 'title') { + Ox.extend(edit, parseTitle(value)); + } else if (key == 'director' || key == 'country') { + edit[key] = value.split(', '); + } else { + edit[key] = value; + } + pandora.api.edit(edit, function(result) { + if (result.data.id != data.id) { + Ox.Request.clearCache(); // fixme: too much + pandora.UI.set({item: result.data.id}); + pandora.$ui.browser.value(data.id, 'id', result.data.id); + } + // FIXME: value function should accept {k: v, ...} + pandora.$ui.browser.value(result.data.id, 'title', result.data.title); + pandora.$ui.browser.value(result.data.id, 'director', result.data.director); + pandora.$ui.browser.value(result.data.id, 'country', result.data.country); + pandora.$ui.browser.value(result.data.id, 'year', result.data.year); + //pandora.$ui.contentPanel.replaceElement(0, pandora.$ui.browser = pandora.ui.browser()); + }); + } + } + + function formatKey(key, isStatistics) { + return isStatistics + ? $('
').css({marginBottom: '4px', fontWeight: 'bold'}).html(Ox.toTitleCase(key)) + : '' + Ox.toTitleCase(key) + ': '; + } + + function formatLight(str) { + return '' + str + ''; + } + + function formatTitle(title) { + var match = /(\(S\d{2}E\d{2}\))/.exec(title); + if (match) { + title = title.replace(match[0], formatLight(match[0])); + } + return title + ( + data.originalTitle && data.originalTitle != title + ? ' ' + formatLight('(' + data.originalTitle + ')') : '' + ); + } + + function formatValue(value, key) { + return (Ox.isArray(value) ? value : [value]).map(function(value) { + return key ? + '' + value + '' + : value; + }).join(', '); + } + + function getRightsLevelElement(rightsLevel) { + return Ox.Theme.formatColorLevel( + rightsLevel, + pandora.site.rightsLevels.map(function(rightsLevel) { + return rightsLevel.name; + }) + ); + } + + function parseTitle(title) { + var data = {title: title}, + match = /(\(S(\d{2})E(\d{2})\))/.exec(title), + split; + if (match) { + data.season = parseInt(match[2], 10); + data.episode = parseInt(match[3], 10); + split = title.split(match[1]); + data.seriesTitle = split[0].trim(); + data.episodeTitle = split[1].trim(); + } + return data; + } + + function reloadMetadata() { + var item = ui.item; + // fixme: maybe there's a better method name for this? + pandora.api.updateExternalData({ + id: ui.item + }, function(result) { + Ox.Request.clearCache(item); + if (ui.item == item && ui.itemView == 'info') { + pandora.$ui.contentPanel.replaceElement( + 1, pandora.$ui.item = pandora.ui.item() + ); + } + }); + } + + function renderCapabilities(rightsLevel) { + var capabilities = Ox.merge( + canEdit ? [{name: 'canSeeItem', symbol: 'Find'}] : [], + [ + {name: 'canPlayClips', symbol: 'PlayInToOut'}, + {name: 'canPlayVideo', symbol: 'Play'}, + {name: 'canDownloadVideo', symbol: 'Download'} + ] + ), + userLevels = canEdit ? pandora.site.userLevels : [pandora.user.level]; + $capabilities.empty(); + userLevels.forEach(function(userLevel, i) { + var $element, + $line = $('
') + .css({ + height: '16px', + marginBottom: '4px' + }) + .appendTo($capabilities); + if (canEdit) { + $element = Ox.Theme.formatColorLevel(i, userLevels.map(function(userLevel) { + return Ox.toTitleCase(userLevel); + }), [0, 240]); + Ox.Label({ + textAlign: 'center', + title: Ox.toTitleCase(userLevel), + width: 60 + }) + .addClass('OxColor OxColorGradient') + .css({ + float: 'left', + height: '12px', + paddingTop: '2px', + background: $element.css('background'), + fontSize: '8px', + color: $element.css('color') + }) + .data({OxColor: $element.data('OxColor')}) + .appendTo($line); + } + capabilities.forEach(function(capability) { + var hasCapability = pandora.site.capabilities[capability.name][userLevel] >= rightsLevel, + $element = Ox.Theme.formatColorLevel(hasCapability, ['', '']); + Ox.Button({ + tooltip: (canEdit ? Ox.toTitleCase(userLevel) : 'You') + ' ' + + (hasCapability ? 'can' : 'can\'t') + ' ' + + Ox.map(Ox.toSlashes(capability.name).split('/'), function(word, i) { + return i == 0 ? null : word.toLowerCase(); + }).join(' '), + title: capability.symbol, + type: 'image' + }) + .addClass('OxColor OxColorGradient') + .css({background: $element.css('background')}) + .css('margin' + (canEdit ? 'Left' : 'Right'), '4px') + .data({OxColor: $element.data('OxColor')}) + .appendTo($line); + }); + if (!canEdit) { + Ox.Button({ + title: 'Help', + tooltip: 'About Rights', + type: 'image' + }) + .css({marginLeft: '52px'}) + .bindEvent({ + click: function() { + pandora.UI.set({page: 'rights'}); + } + }) + .appendTo($line); + } + }); + } + + function renderRightsLevel() { + var $rightsLevelElement = getRightsLevelElement(data.rightslevel), + $rightsLevelSelect; + $rightsLevel.empty(); + if (canEdit) { + $rightsLevelSelect = Ox.Select({ + items: pandora.site.rightsLevels.map(function(rightsLevel, i) { + return {id: i, title: rightsLevel.name, checked: i == data.rightslevel}; + }), + width: 128 + }) + .addClass('OxColor OxColorGradient') + .css({ + marginBottom: '4px', + background: $rightsLevelElement.css('background') + }) + .data({OxColor: $rightsLevelElement.data('OxColor')}) + .bindEvent({ + change: function(event) { + var rightsLevel = event.value; + $rightsLevelElement = getRightsLevelElement(rightsLevel); + $rightsLevelSelect + .css({background: $rightsLevelElement.css('background')}) + .data({OxColor: $rightsLevelElement.data('OxColor')}) + renderCapabilities(rightsLevel); + pandora.api.edit({id: data.id, rightslevel: rightsLevel}, function(result) { + // ... + }); + } + }) + .appendTo($rightsLevel); + } else { + $rightsLevelElement + .css({ + marginBottom: '4px' + }) + .appendTo($rightsLevel); + } + $capabilities = $('
').appendTo($rightsLevel); + renderCapabilities(data.rightslevel); + } + + function toggleIconSize() { + iconSize = iconSize == 256 ? 512 : 256; + iconWidth = iconRatio > 1 ? iconSize : Math.round(iconSize * iconRatio); + iconHeight = iconRatio < 1 ? iconSize : Math.round(iconSize / iconRatio); + iconLeft = iconSize == 256 ? Math.floor((iconSize - iconWidth) / 2) : 0, + borderRadius = ui.icons == 'posters' ? 0 : iconSize / 8; + $icon.animate({ + left: margin + iconLeft + 'px', + width: iconWidth + 'px', + height: iconHeight + 'px', + borderRadius: borderRadius + 'px' + }, 250); + $reflection.animate({ + top: margin + iconHeight + 'px', + width: iconSize + 'px', + height: iconSize / 2 + 'px' + }, 250); + $reflectionIcon.animate({ + left: iconLeft + 'px', + width: iconWidth + 'px', + height: iconHeight + 'px', + borderRadius: borderRadius + 'px' + }, 250); + $reflectionGradient.animate({ + width: iconSize + 'px', + height: iconSize / 2 + 'px' + }, 250); + $text.animate({ + left: margin + (iconSize == 256 ? 256 : iconWidth) + margin + 'px', + }, 250); + pandora.UI.set({infoIconSize: iconSize}); + } + + that.reload = function() { + var src = src = '/' + data.id + '/' + ( + ui.icons == 'posters' + ? (ui.showSitePoster ? 'siteposter' : 'poster') : 'icon' + ) + '512.jpg?' + Ox.uid() + $icon.attr({src: src}); + $reflectionIcon.attr({src: src}); + iconSize = iconSize == 256 ? 512 : 256; + iconRatio = ui.icons == 'posters' + ? (ui.showSitePoster ? 5/8 : data.posterRatio) : 1; + toggleIconSize(); + pandora.user.level == 'admin' && $list.replaceWith($list = renderList()); + }; + + that.resize = function() { + var height = pandora.$ui.contentPanel.size(1); + $list && $list.css({height: height + 'px'}); + $data.css({height: height + 'px'}); + }; + + that.bindEvent({ + pandora_icons: that.reload, + pandora_showsiteposter: function() { + ui.icons == 'posters' && that.reload(); + } + }); + + return that; + +}