commit f3420832e07846fba797e79531133990b590ae61 Author: j Date: Mon Jun 23 12:15:53 2014 +0200 bak.ma config diff --git a/config.jsonc b/config.jsonc new file mode 100644 index 0000000..380747a --- /dev/null +++ b/config.jsonc @@ -0,0 +1,717 @@ +/* + Pan.do/ra Settings + + You can edit this file. +*/ +{ + "annotations": { + "showUsers": true + }, + "cantPlay": { + "icon": "", + "link": "", + "text": "" + }, + /* + Capabilities are per user level. + They can either be general: + {level: true} means a user of that level has the capability) + or related to items: + {level: x} means a user of that level has the capability + for items of a rights level up to and including x + */ + "capabilities": { + "canAddItems": {"staff": true, "admin": true}, + "canDownloadVideo": {"guest": 0, "member": 0, "staff": 4, "admin": 4}, + "canEditAnnotations": {"staff": true, "admin": true}, + "canEditDocuments": {"staff": true, "admin": true}, + "canEditEvents": {"staff": true, "admin": true}, + "canEditFeaturedEdits": {"staff": true, "admin": true}, + "canEditFeaturedLists": {"staff": true, "admin": true}, + "canEditFeaturedTexts": {"staff": true, "admin": true}, + "canEditMedia": {"staff": true, "admin": true}, + "canEditMetadata": {"staff": true, "admin": true}, + "canEditPlaces": {"staff": true, "admin": true}, + "canEditRightsLevel": {"staff": true, "admin": true}, + "canEditSitePages": {"staff": true, "admin": true}, + "canEditUsers": {"admin": true}, + "canImportAnnotations": {"member": true, "staff": true, "admin": true}, + "canManageDocuments": {"member": true, "staff": true, "admin": true}, + "canManagePlacesAndEvents": {"member": true, "staff": true, "admin": true}, + "canManageTitlesAndNames": {"member": true, "staff": true, "admin": true}, + "canManageUsers": {"staff": true, "admin": true}, + "canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canReadText": {"guest": 0, "member": 0, "staff": 1, "admin": 1}, + "canRemoveItems": {"admin": true}, + "canSeeAccessed": {"staff": true, "admin": true}, + "canSeeDebugMenu": {"staff": true, "admin": true}, + "canSeeExtraItemViews": {"staff": true, "admin": true}, + "canSeeMedia": {"staff": true, "admin": true}, + "canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canSeeSize": {"staff": true, "admin": true}, + "canSeeSoftwareVersion": {"staff": true, "admin": true}, + "canSendMail": {"staff": true, "admin": true} + }, + /* + clipKeys are the properties that clips can be sorted by. + If sortOperator is not specified, it will be + for strings and - for numbers. + */ + "clipKeys": [ + {"id": "text", "title": "Text", "type": "string"}, + {"id": "position", "title": "Position", "type": "float", "sortOperator": "+"}, + {"id": "duration", "title": "Duration", "type": "float"}, + {"id": "hue", "title": "Hue", "type": "float", "sortOperator": "+"}, + {"id": "saturation", "title": "Saturation", "type": "float"}, + {"id": "lightness", "title": "Lightness", "type": "float"}, + {"id": "volume", "title": "Volume", "type": "float"} + ], + /* + clipLayers is the ordered list of public layers that will appear as the + text of clips. Excluding a layer from this list means it will not be + included in find annotations. // FIXME: the last bit is not implemented. + */ + "clipLayers": ["subtitles", "keywords", "notes"], + "flags": false, + "help": [ + {"id": "help", "title": "Help"}, + {"id": "accounts", "title": "Accounts"}, + {"id": "navigation", "title": "Navigation"}, + {"id": "views", "title": "Views"}, + {"id": "timelines", "title": "Timelines"}, + {"id": "clips", "title": "Clips"}, + {"id": "maps", "title": "Maps"}, + {"id": "calendars", "title": "Calendars"}, + {"id": "find", "title": "Find"}, + {"id": "filters", "title": "Filters"}, + {"id": "lists", "title": "Lists"}, + {"id": "player", "title": "Player"}, + {"id": "editor", "title": "Editor"}, + {"id": "documents", "title": "Documents"}, + {"id": "edits", "title": "Edits"}, + {"id": "texts", "title": "Texts"}, + {"id": "embeds", "title": "Embeds"} + ], + /* + An itemKey must have the following properties: + id: The id of the key (as known by the server) + title: The title of the key (as displayed by the client) + type: text, string, float, integer, or array of any of these + and can have any of the following properties: + autocomplete: If true, find element will autocomplete + autocompleteSort: Sort order of autocomplete suggestions + capability: A capability required to see this key + columnRequired: If true, the column can't be removed + columnWidth: Default column width in px + filter: if true, one can filter results by this key + find: If true, this key will appear as a find option + format: {type: "...", args: [...]}, for special formatting + (Ox.formatType(args) will be called) + secondaryId: If true, loading /value will redirect to the item + sort: If true, one can sort results by this key + sortOperator: sort operator (+, -), in case it differs from the + default for the key's type (+ for strings, - for numbers) + sortType: special sort type (title, person) + value: {key: "...", type: "..."}, for keys that are derived + from other keys (like number of actors), or "capability" + */ + "itemKeys": [ + { + "id": "*", + "title": "All", + "type": "text", + "find": true + }, + { + "id": "title", + "title": "Title", + "type": "string", + "autocomplete": true, + "autocompleteSort": [{"key": "timesaccessed", "operator": "-"}], + "columnRequired": true, + "columnWidth": 180, + "find": true, + "sort": true, + "sortType": "title" + }, + { + "id": "location", + "title": "Location", + "type": "string", + "autocomplete": true, + "columnWidth": 180, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "date", + "title": "Date", + "type": "string", + "columnWidth": 120, + //"format": {"type": "date", "args": ["%a, %b %e, %Y"]}, + "filter": true, + "sort": true + }, + { + "id": "category", + "title": "Categories", + "type": ["string"], + "autocomplete": true, + "columnWidth": 180, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "tag", + "title": "Tags", + "type": ["string"], + "autocomplete": true, + "columnWidth": 180, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "name", + "title": "People", + "type": ["string"], + "autocomplete": true, + "filter": true, + "find": true + }, + { + "id": "language", + "title": "Language", + "type": ["string"], + "autocomplete": true, + "columnWidth": 120, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "summary", + "title": "Summary", + "type": "text", + "find": true + }, + { + "id": "notes", + "title": "Notes", + "type": "text", + "capability": "canEditMetadata" + }, + { + "id": "id", + "title": "ID", + "type": "string", + "columnWidth": 90, + "sort": true + }, + { + "id": "annotations", + "title": "Annotations", + "type": "string", // fixme: not the best type for this magic key + "find": true + }, + { + "id": "keywords", + "title": "Keywords", + "type": "layer", + "filter": true, + "find": true + }, + { + "id": "notes", + "title": "Notes", + "type": "layer", + "find": true + }, + { + "id": "subtitles", + "title": "Subtitless", + "type": "layer", + "find": true + }, + { + "id": "numberofannotations", + "title": "Annotations", + "type": "integer", + "columnWidth": 60, + "sort": true + }, + { + "id": "duration", + "title": "Duration", + "type": "time", + "columnWidth": 90, + "format": {"type": "duration", "args": [0, "short"]}, + "sort": true + }, + { + "id": "resolution", + "title": "Resolution", + "type": ["integer"], + "columnWidth": 90, + "format": {"type": "resolution", "args": ["px"]}, + "sort": true + }, + { + "id": "aspectratio", + "title": "Aspect Ratio", + "type": "float", + "columnWidth": 90, + "format": {"type": "unit", "args": [":1", 3]}, + "sort": true + }, + { + "id": "pixels", + "title": "Pixels", + "type": "integer", + "columnWidth": 90, + "format": {"type": "value", "args": ["px"]}, + "sort": true + }, + { + "id": "hue", + "title": "Hue", + "type": "hue", + "columnWidth": 90, + "format": {"type": "color", "args": ["hue"]}, + "sort": true, + "sortOperator": "+" + }, + { + "id": "saturation", + "title": "Saturation", + "type": "float", + "columnWidth": 90, + "format": {"type": "color", "args": ["saturation"]}, + "sort": true + }, + { + "id": "lightness", + "title": "Lightness", + "type": "float", + "columnWidth": 90, + "format": {"type": "color", "args": ["lightness"]}, + "sort": true + }, + { + "id": "volume", + "title": "Volume", + "type": "float", + "columnWidth": 60, + "format": {"type": "color", "args": ["lightness"]}, + "sort": true + }, + { + "id": "numberofcuts", + "title": "Number of Cuts", + "type": "integer", + "columnWidth": 60, + "format": {"type": "number", "args": []}, + "sort": true, + "value": {"key": "cuts", "type": "length"} + }, + { + "id": "cutsperminute", + "title": "Cuts per Minute", + "type": "float", + "columnWidth": 60, + "format": {"type": "number", "args": [3]}, + "sort": true, + "value": {"key": "cuts", "type": "lengthperminute"} + }, + { + "id": "words", + "title": "Number of Words", + "type": "integer", + "columnWidth": 60, + "format": {"type": "number", "args": []}, + "sort": true, + "value": {"layer": "subtitles", "type": "words"} + }, + { + "id": "wordsperminute", + "title": "Words per Minute", + "type": "float", + "columnWidth": 60, + "format": {"type": "number", "args": [3]}, + "sort": true, + "value": {"layer": "subtitles", "type": "wordsperminute"} + }, + { + "id": "size", + "title": "Size", + "type": "integer", + "capability": "canSeeMedia", + "columnWidth": 60, + "format": {"type": "value", "args": ["B"]}, + "sort": true + }, + { + "id": "bitrate", + "title": "Bitrate", + "type": "integer", + "columnWidth": 60, + "format": {"type": "value", "args": ["bps"]}, + "sort": true + }, + { + "id": "numberoffiles", + "title": "Number of Files", + "type": "integer", + "capability": "canSeeMedia", + "columnWidth": 60, + "sort": true, + "value": {"key": "files", "type": "length"} + }, + { + "id": "user", + "title": "User", + "type": "string", + "capability": "canSeeMedia", + "find": true + }, + { + "id": "filename", + "title": "Filename", + "type": ["string"], + "capability": "canSeeMedia", + "find": true + }, + { + "id": "created", + "title": "Date Created", + "type": "date", + "columnWidth": 120, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "modified", + "title": "Last Modified", + "type": "date", + "columnWidth": 90, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "accessed", + "title": "Last Accessed", + "type": "date", + "capability": "canSeeAccessed", + "columnWidth": 90, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "timesaccessed", + "title": "Times Accessed", + "type": "integer", + "capability": "canSeeAccessed", + "columnWidth": 60, + "format": {"type": "number", "args": []}, + "sort": true + }, + { + "id": "rightslevel", + "title": "Rights Level", + "type": "enum", + "columnWidth": 90, + "format": {"type": "ColorLevel", "args": [ + ["Public", "Restricted", "Private"] + ]}, + "sort": true, + "sortOperator": "+", + "values": ["Public", "Restricted", "Private", "Unknown"] + }, + { + "id": "random", + "title": "Random", + "type": "integer", + "sort": true + } + ], + "itemName": { + "singular": "Video", + "plural": "Videos" + }, + "itemRequiresVideo": true, + "itemViews": [ + {"id": "info", "title": "Info"}, + {"id": "player", "title": "Player"}, + {"id": "editor", "title": "Editor"}, + {"id": "timeline", "title": "Timeline"}, + {"id": "clips", "title": "Clips"}, + {"id": "map", "title": "Map"}, + {"id": "calendar", "title": "Calendar"}, + // {"id": "documents", "title": "Documents"}, + {"id": "data", "title": "Data"}, + {"id": "media", "title": "Media"} + ], + "language": "en", + "languages": ["en"], + "layers": [ + { + "id": "keywords", + "title": "Keywords", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "hasEvents": true, + "hasPlaces": true, + "item": "Keyword", + "overlap": true, + "type": "string" + }, + { + "id": "notes", + "title": "Notes", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "hasEvents": true, + "hasPlaces": true, + "item": "Description", + "showInfo": true, + "type": "text" + }, + { + "id": "subtitles", + "title": "Subtitles", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "hasEvents": true, + "hasPlaces": true, + "isSubtitles": true, + "item": "Subtitle", + "showInfo": true, + "type": "text" + } + ], + "listViews": [ + {"id": "list", "title": "as List"}, + {"id": "grid", "title": "as Grid"}, + {"id": "timelines", "title": "with Timelines"}, + {"id": "clips", "title": "with Clips"}, + {"id": "clip", "title": "as Clips"}, + {"id": "map", "title": "on Map"}, + {"id": "calendar", "title": "on Calendar"} + ], + "media": { + "importPosters": false, + "importFrames": false + }, + "menuExtras": [ + "user", + //"locale", + "reload" + ], + "personalLists": [ + {"title": "Favorites"} + ], + "posters": { + "ratio": 0.625 + }, + "rightsLevel": {"member": 2, "staff": 2, "admin": 2}, + "rightsLevels": [ + {"name": "Public", "color": [128, 255, 128]}, + {"name": "Restricted", "color": [255, 192, 128]}, + {"name": "Private", "color": [255, 128, 128]} + ], + "sendReferrer": true, + "site": { + "description": "...", + "email": { + // E-mail address in contact form (to) + "contact": "bak.ma@bak.ma", + "footer": "-- \nbak.ma - http://bak.ma", + "prefix": "bak.ma news -", + // E-mail address uses by the system (from) + "system": "system@bak.ma" + }, + "https": true, + "id": "bakma", + "name": "bak.ma", + "url": "bak.ma", + "videoprefix": "" + }, + "sitePages": [ + {"id": "about", "title": "About"}, + {"id": "news", "title": "News"}, + //{"id": "tour", "title": "Take a Tour"}, + {"id": "faq", "title": "Frequently Asked Questions"}, + //{"id": "terms", "title": "Terms of Service"}, + {"id": "license", "title": "License"}, + {"id": "contact", "title": "Contact"} + ], + "sites": [ + {"name": "Pad.ma", "url": "pad.ma", "https": true}, + {"name": "OxDB", "url": "0xdb.org", "https": true} + ], + "textRightsLevels": [ + {"name": "Public", "color": [128, 255, 128]}, + {"name": "Private", "color": [255, 128, 128]} + ], + "themes": ["oxlight", "oxmedium", "oxdark"], + "timelines": [ + {"id": "antialias", "title": "Anti-Alias"}, + {"id": "slitscan", "title": "Slit-Scan"}, + {"id": "keyframes", "title": "Keyframes"}, + {"id": "audio", "title": "Waveform"} + ], + "totals": [ + {"id": "items"}, + {"id": "files", "admin": true}, + {"id": "duration", "admin": true}, + {"id": "size", "admin": true}, + {"id": "pixels"} + ], + "tv": { + "showLogo": false + }, + "user": { + "level": "guest", + "ui": { + "annotationsCalendarSize": 128, + "annotationsMapSize": 128, + "annotationsRange": "position", + "annotationsSize": 256, + "annotationsSort": "position", + "calendarFind": "", + "calendarSelection": "", + "clipColumns": 2, + "columns": { + "Colors": { + "columns": ["title", "date", "location", "language", "hue", "saturation", "brightness"], + "columnWidth": {} + } + }, + "document": "", + "documents": {}, + "documentSize": 256, + "documentsSelection": {}, + "documentsSort": [{"key": "name", "operator": "+"}], + "documentsView": "grid", + "edit": "", + "edits": {}, + "editSelection": [], + "editSort": [ + {"key": "index", "operator": "+"}, + {"key": "year", "operator": "+"}, + {"key": "director", "operator": "+"}, + {"key": "title", "operator": "+"}, + {"key": "position", "operator": "+"}, + {"key": "duration", "operator": "+"} + ], + "editView": "list", + "embedSize": 256, + "filters": [ + {"id": "date", "sort": [{"key": "name", "operator": "+"}]}, + {"id": "category", "sort": [{"key": "name", "operator": "+"}]}, + {"id": "tag", "sort": [{"key": "items", "operator": "-"}]}, + {"id": "keywords", "sort": [{"key": "items", "operator": "-"}]}, + {"id": "name", "sort": [{"key": "items", "operator": "-"}]} + ], + "filtersSize": 176, + "find": {"conditions": [], "operator": "&"}, + "followPlayer": true, + "help": "", + "icons": "frames", + "infoIconSize": 256, + "item": "", + "itemFind": "", + "itemSort": [{"key": "position", "operator": "+"}], + "itemView": "info", + "listColumns": ["title", "date", "location", "tag", "language", "duration"], + "listColumnWidth": {}, + "listSelection": [], + "listSort": [{"key": "title", "operator": "+"}], + "listView": "grid", + "lists": {}, + "locale": "en", + "mapFind": "", + "mapSelection": "", + "onload": "", + "page": "", + "part": { + "api": "", + "documents": "", + "faq": "", + "help": "", + "news": "", + "preferences": "", + "tv": "" + }, + "section": "items", + "sequenceMode": "shape", + "sequenceSort": [{"key": "title", "operator": "+"}], + "showAdvancedEmbedOptions": false, + "showAnnotations": true, + "showAnnotationsCalendar": true, + "showAnnotationsMap": true, + "showBrowser": true, + "showCalendarControls": true, // fixme: should be false + "showClips": true, + "showDocument": true, + "showFilters": true, + "showIconBrowser": false, + "showInfo": true, + "showLayers": { + "keywords": true, + "notes": true, + "subtitles": true + }, + "showMapControls": false, + "showMapLabels": false, + "showFolder": { + "edits": { + "personal": true, + "favorite": true, + "featured": true, + "volumes": true + }, + "items": { + "personal": true, + "favorite": true, + "featured": true, + "volumes": true + }, + "texts": { + "personal": true, + "favorite": true, + "featured": true + } + }, + "showReflections": true, + "showSidebar": true, + "showSitePosters": false, + "showTimeline": true, + "sidebarSize": 256, + "text": "", + "texts": {}, + "theme": "oxlight", + "updateAdvancedFindResults": false, + "videoLoop": false, + "videoMuted": false, + "videoPoints": {}, + "videoResolution": 240, + "videoScale": "fit", + "videoSize": "large", + "videoSubtitles": true, + "videoTimeline": "antialias", + "videoView": "player", + "videoVolume": 1 + }, + "username": "", + "volumes": [] + }, + "userLevels": ["guest", "member", "staff", "admin"], + "video": { + "torrent": true, + "formats": ["webm", "mp4"], + "previewRatio": 1.3333333333, + //supported resolutions are + //1080, 720, 480, 432, 360, 288, 240, 144, 96 + "resolutions": [96, 240, 480] + } +} diff --git a/install.py b/install.py new file mode 100755 index 0000000..4aae60e --- /dev/null +++ b/install.py @@ -0,0 +1,58 @@ +#!/usr/bin/python + +import os +from os.path import join, abspath, basename, dirname + +name = 'bakma' +base = abspath(dirname(__file__)) +os.chdir(base) + +for root, folders, files in os.walk(join(base, 'static')): + for f in files: + src = join(root, f) + target = src.replace(base, '/srv/pandora') + rel_src = os.path.relpath(src, dirname(target)) + if os.path.exists(target): + os.unlink(target) + os.symlink(rel_src, target) + +''' +overwrite = ( + ('home', 'indiancinema'), + ('infoView', 'indiancinema'), +) + +os.chdir('/srv/pandora/static/js') +for filename, sitename in overwrite: + src = '%s.%s.js' % (filename, sitename) + target = '%s.%s.js' % (filename, name) + if os.path.exists(target): + os.unlink(target) + os.symlink(src, target) +''' + +os.chdir(base) +src = join(base, 'config.jsonc') +target = '/srv/pandora/pandora/config.%s.jsonc' % name +rel_src = os.path.relpath(src, dirname(target)) +if os.path.exists(target): + os.unlink(target) +os.symlink(rel_src, target) +t = '/srv/pandora/pandora/config.jsonc' +if os.path.exists(t): + os.unlink(t) +os.symlink(basename(target), t) + +for root, folders, files in os.walk(join(base, 'scripts')): + for f in files: + src = join(root, f) + target = src.replace(base, '/srv/pandora') + rel_src = os.path.relpath(src, dirname(target)) + if os.path.exists(target): + os.unlink(target) + os.symlink(rel_src, target) + +#todo +#custom python module etc +#local_settings.py? + diff --git a/scripts/poster.py b/scripts/poster.py new file mode 100755 index 0000000..70010d3 --- /dev/null +++ b/scripts/poster.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division +import os + +root_dir = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# using virtualenv's activate_this.py to reorder sys.path +activate_this = os.path.join(root_dir, 'bin', 'activate_this.py') +execfile(activate_this, dict(__file__=activate_this)) + +import Image +import ImageDraw +import json +from optparse import OptionParser +import ox +from ox.image import drawText, wrapText +import sys + +static_root = os.path.join(os.path.dirname(__file__), 'data') + +def render_poster(data, poster): + title = ox.decode_html(data.get('title', '')) + id = data['id'] + frame = data.get('frame') + timeline = data.get('timeline') + + poster_width = 640 + poster_height = 1024 + poster_ratio = poster_width / poster_height + poster_color = (255, 255, 0) + poster_image = Image.new('RGB', (poster_width, poster_height)) + font_file = os.path.join(static_root, 'DejaVuSansCondensedBold.ttf') + font_size = 48 + + # timeline + timeline_height = 64 + timeline_lines = 16 + if timeline: + timeline_image = Image.open(timeline) + timeline_image = timeline_image.resize((10240, timeline_height), Image.ANTIALIAS) + for i in range(timeline_lines): + line_image = timeline_image.crop((i * poster_width, 0, (i + 1) * poster_width, 64)) + poster_image.paste(line_image, (0, i * timeline_height)) + + # id + text = 'Bak.ma/' + id + text_image = Image.new('RGB', (1, 1)) + text_size = drawText(text_image, (0, 0), text, font_file, font_size, poster_color) + text_width = poster_width + text_height = timeline_height + text_left = int((poster_width - text_width) / 2) + text_top = 14 * timeline_height + for y in range(text_top, text_top + text_height): + for x in range(text_left, text_left + text_width): + if y < text_top + 4 or y >= text_top + text_height - 4: + poster_image.putpixel((x, y), poster_color) + else: + pixel = list(poster_image.getpixel((x, y))) + for c in range(3): + pixel[c] = int((pixel[c] + poster_color[c]) / 4) + poster_image.putpixel((x, y), tuple(pixel)) + drawText(poster_image, ((poster_width - text_size[0]) / 2, text_top + (text_height - text_size[1]) / 2), text, font_file, font_size, poster_color) + poster_image.save(poster) + +def main(): + parser = OptionParser() + parser.add_option('-d', '--data', dest='data', help='json file with metadata', default=None) + parser.add_option('-p', '--poster', dest='poster', help='Poster (image file to be written)') + (options, args) = parser.parse_args() + + if None in (options.data, options.poster): + parser.print_help() + sys.exit() + + if options.data == '-': + data = json.load(sys.stdin) + else: + with open(options.data) as f: + data = json.load(f) + render_poster(data, options.poster) + +if __name__ == "__main__": + main() + diff --git a/static/js/home.bakma.js b/static/js/home.bakma.js new file mode 100644 index 0000000..41dee30 --- /dev/null +++ b/static/js/home.bakma.js @@ -0,0 +1,707 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript + +'use strict'; + +pandora.ui.home = function() { + + var self = {}, + + that = $('
') + .addClass('OxScreen') + .css({ + position: 'absolute', + width: '100%', + height: '100%', + opacity: 0, + overflowY: 'auto', + zIndex: 1001 + }), + + $box = $('
') + .css({ + position: 'absolute', + left: 0, + top: '80px', + right: 0, + width: '560px', + margin: '0 auto 0 auto' + }) + .appendTo(that), + + $reflectionImage = $('') + .attr({src: '/static/png/logo.png'}) + .css({ + position: 'absolute', + left: 0, + top: '80px', + right: 0, + bottom: 0, + width: '320px', + height: 'auto', + margin: '0 auto 0 auto', + MozTransform: 'scaleY(-1)', + MsTransform: 'scaleY(-1)', + OTransform: 'scaleY(-1)', + WebkitTransform: 'scaleY(-1)' + }) + .appendTo($box), + + $reflectionGradient = $('
') + .addClass('OxReflection') + .css({ + position: 'absolute', + left: 0, + top: '80px', + right: 0, + width: '320px', + height: '160px', + margin: '0 auto 0 auto', + }) + .appendTo($box), + + $logo = Ox.Element({ + element: '', + tooltip: function() { + return Ox._('Enter {0}', [pandora.site.site.name]); + } + }) + .attr({ + id: 'logo', + src: '/static/png/logo.png' + }) + .css({ + position: 'absolute', + left: 0, + right: 0, + width: '320px', + height: 'auto', + margin: '0 auto 0 auto', + cursor: 'pointer' + }) + .bindEvent({ + anyclick: function() { + $browseButton.triggerEvent('click'); + } + }) + .appendTo($box), + + $findInput = Ox.Input({ + width: 252 + }) + .css({ + position: 'absolute', + left: 0, + top: '104px', + right: '260px', + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .click(function(e) { + // fixme: why? + e.stopPropagation(); + }) + .bindEvent({ + submit: function(data) { + if (data.value) { + $findButton.triggerEvent('click'); + } else { + $browseButton.triggerEvent('click'); + } + } + }) + .appendTo($box), + + $findButton = Ox.Button({ + title: Ox._('Find'), + width: 122 + }) + .css({ + position: 'absolute', + left: '130px', + top: '104px', + right: 0, + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .bindEvent({ + click: function() { + var folder = pandora.getListData().folder, + value = $findInput.value(); + folder && pandora.$ui.folderList[folder].options({selected: []}); + if (pandora.user.ui.section == 'items') { + pandora.$ui.findSelect.value('*'); + pandora.$ui.findInput.value(value); + } + that.fadeOutScreen(); + pandora.UI.set({ + page: '', + find: { + conditions: value === '' + ? [] + : [{key: '*', value: value, operator: '='}], + operator: '&' + }, + section: 'items' + }); + } + }) + .appendTo($box), + + $browseButton = Ox.Button({ + title: Ox._('Browse'), + width: 122 + }) + .css({ + position: 'absolute', + left: '390px', + top: '104px', + right: 0, + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .bindEvent({ + click: function() { + pandora.UI.set({ + page: pandora.user.ui.page == 'home' ? '' : pandora.user.ui.page, + section: 'items' + }); + that.fadeOutScreen(); + } + }) + .appendTo($box), + + $signupButton = Ox.Button({ + title: Ox._('Sign Up'), + width: 122 + }) + .css({ + position: 'absolute', + left: 0, + top: '144px', + right: '390px', + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .bindEvent({ + click: function() { + pandora.UI.set({page: 'signup'}); + that.fadeOutScreen(); + } + }), + + $signinButton = Ox.Button({ + title: Ox._('Sign In'), + width: 122 + }) + .css({ + position: 'absolute', + left: 0, + top: '144px', + right: '130px', + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .bindEvent({ + click: function() { + pandora.UI.set({page :'signin'}); + that.fadeOutScreen(); + } + }), + + $preferencesButton = Ox.Button({ + title: Ox._('Preferences'), + width: 252 + }) + .css({ + position: 'absolute', + left: 0, + top: '144px', + right: '260px', + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .bindEvent({ + click: function() { + pandora.UI.set({page: 'preferences'}); + that.fadeOutScreen(); + } + }), + + $aboutButton = Ox.Button({ + title: Ox._('About {0}', [pandora.site.site.name]), + width: 252 + }) + .css({ + position: 'absolute', + left: '260px', + top: '144px', + right: 0, + bottom: 0, + margin: '0 auto 0 auto', + opacity: 0 + }) + .bindEvent({ + click: function() { + pandora.UI.set({page: 'about'}); + that.fadeOutScreen(); + } + }) + .appendTo($box), + + $features = $('
') + .attr({id: 'lists'}) + .css({ + position: 'absolute', + left: 0, + top: '184px', + right: 0, + bottom: 0, + width: '560px', + margin: '0 auto 0 auto', + opacity: 0 + }) + .appendTo($box); + + if (pandora.user.level == 'guest') { + $signupButton.appendTo($box); + $signinButton.appendTo($box); + } else { + $preferencesButton.appendTo($box); + } + + function showFeatures() { + var $space, + featured = {}, + find = { + query: { + conditions: [{key: 'status', value: 'featured', operator: '=='}], + operator: '&' + }, + keys: ['description', 'modified', 'name', 'user'], + sort: [{key: 'position', operator: '+'}] + }, + items, lists, edits, texts; + pandora.api.findLists(find, function(result) { + lists = result.data.items.length; + items = result.data.items.map(function(item) { + return Ox.extend(item, {type: 'list'}); + }); + pandora.api.findEdits(find, function(result) { + edits = result.data.items.length; + items = items.concat(result.data.items.map(function(item) { + return Ox.extend(item, {type: 'edit'}); + })); + pandora.api.findTexts(find, function(result) { + texts = result.data.items.length; + items = items.concat(result.data.items.map(function(item) { + return Ox.extend(item, {type: 'text'}); + })); + $features.empty(); + show(); + }); + }); + }); + function show() { + var counter = 0, max = 8, mouse = false, position = 0, selected = 0, + color = Ox.Theme() == 'oxlight' ? 'rgb(0, 0, 0)' + : Ox.Theme() == 'oxmedium' ? 'rgb(0, 0, 0)' + : 'rgb(255, 255, 255)', + $label, $icon, $text, + $featuresBox, $featuresContainer, $featuresContent, + $featureBox = [], $featureIcon = [], + $previousButton, $nextButton; + if (items.length) { + $label = Ox.Label({ + textAlign: 'center', + title: '' + Ox._('Featured ' + ( + lists == 1 && edits == 0 && texts == 0 ? 'List' + : lists == 0 && edits == 1 && texts == 0 ? 'Edit' + : lists == 0 && edits == 0 && texts == 1 ? 'Text' + : edits == 0 && texts == 0 ? 'Lists' + : lists == 0 && texts == 0 ? 'Edits' + : lists == 0 && edits == 0 ? 'Texts' + : texts == 0 ? 'Lists and Edits' + : edits == 0 ? 'Lists and Texts' + : lists == 0 ? 'Edits and Texts' + : 'Lists, Edits and Texts' + )) + '', + width: 512 + }) + .css({ + position: 'absolute', + left: 0, + top: 0, + right: 0, + bottom: 0, + margin: '0 auto 0 auto' + }) + .appendTo($features); + $text = Ox.Label({ + width: 386 + }) + .addClass('OxSelectable') + .css({ + position: 'absolute', + left: '24px', + top: '24px', + right: 0, + height: '104px', + borderTopLeftRadius: '32px', + borderBottomLeftRadius: '32px', + padding: '8px 8px 8px 130px', + overflowY: 'auto', + textOverflow: 'ellipsis', + whiteSpace: 'normal' + }) + .html( + getHTML(items[selected]) + ) + .appendTo($features); + pandora.createLinks($text); + $icon = Ox.Element({ + element: '', + tooltip: getTooltip(items[selected]) + }) + .attr({ + src: getImageURL(items[selected]) + }) + .css({ + position: 'absolute', + left: 0, + top: '24px', + right: '390px', + width: '122px', + height: '122px', + borderRadius: '32px', + margin: '0 auto 0 auto', + cursor: 'pointer' + }) + .bindEvent({ + anyclick: function() { + openItem(selected); + } + }) + .appendTo($features); + if (items.length > 1) { + $featuresBox = $('
') + .css({ + position: 'absolute', + left: 0, + top: '150px', + right: 0, + height: '65px', // 4+57+4 + width: '560px', // 16+8+512+8+16 + margin: '0 auto 0 auto' + }) + .appendTo($features); + $featuresContainer = $('
') + .css({ + position: 'absolute', + left: '20px', + right: '20px', + height: '65px', + width: '520px', + overflow: 'hidden' + }) + .appendTo($featuresBox); + $featuresContent = $('
') + .css({ + position: 'absolute', + width: items.length * 65 + 'px', + height: '65px', + marginLeft: items.length < max + ? (max - items.length) * 65 / 2 + 'px' + : 0 + }) + .appendTo($featuresContainer); + if (items.length > max) { + $previousButton = Ox.Button({ + title: 'left', + type: 'image' + }) + .addClass(position > 0 ? 'visible' : '') + .css({ + position: 'absolute', + left: 0, + top: '25px', + opacity: 0 + }) + .hide() + .bindEvent({ + mousedown: function() { + counter = 0; + scrollToPosition(position - 1, true); + }, + mouserepeat: function() { + // fixme: arbitrary + if (counter++ % 5 == 0) { + scrollToPosition(position - 1, false); + } + } + }) + .appendTo($featuresBox); + $nextButton = Ox.Button({ + title: 'right', + type: 'image' + }) + .addClass(position < items.length - 1 ? 'visible' : '') + .css({ + position: 'absolute', + right: 0, + top: '25px', + opacity: 0 + }) + .hide() + .bindEvent({ + mousedown: function() { + counter = 0; + scrollToPosition(position + 1, true); + }, + mouserepeat: function() { + // fixme: arbitrary + if (counter++ % 5 == 0) { + scrollToPosition(position + 1, false); + } + } + }) + .appendTo($featuresBox); + $featuresBox.on({ + mouseenter: function() { + mouse = true; + $('.visible').show().stop().animate({ + opacity: 1 + }, 250); + }, + mouseleave: function() { + mouse = false; + $('.visible').stop().animate({ + opacity: 0 + }, 250, function() { + $(this).hide(); + }); + }, + mousewheel: function(e, delta, deltaX, deltaY) { + // fixme: arbitrary + scrollToPosition(position + Math.round(deltaX * 2), true); + } + }); + } + items.forEach(function(item, i) { + $featureBox[i] = $('
') + .css({ + float: 'left', + width: '57px', + height: '57px', + padding: '2px', + margin: '2px', + borderRadius: '16px', + boxShadow: '0 0 2px ' + (i == selected ? color : 'transparent') + }) + .appendTo($featuresContent); + $featureIcon[i] = Ox.Element({ + element: '', + tooltip: ( + (lists && edits) || (lists && texts) || (edits && texts) + ? Ox._(Ox.toTitleCase(item.type)) + ': ' + : '' + ) + + Ox.encodeHTMLEntities(item.name) + }) + .attr({ + src: getImageURL(item) + }) + .css({ + width: '57px', + height: '57px', + borderRadius: '16px', + cursor: 'pointer' + }) + .bindEvent({ + doubleclick: function() { + openItem(i); + }, + singleclick: function() { + selectItem(i); + } + }) + .appendTo($featureBox[i]); + }); + self.keydown = function(e) { + var focused = Ox.Focus.focused(), + key = Ox.KEYS[e.keyCode]; + if ( + focused === null + || !Ox.UI.elements[focused].hasClass('OxInput') + ) { + if (key == 'left' && selected > 0) { + selectItem(selected - 1); + } else if (key == 'up' && selected > 0) { + selectItem(0); + } else if (key == 'right' && selected < items.length - 1) { + selectItem(selected + 1); + } else if (key == 'down' && selected < items.length - 1) { + selectItem(items.length - 1); + } + } + }; + Ox.$document.on({keydown: self.keydown}); + } + $space = $('
') + .css({ + position: 'absolute', + top: items.length == 0 ? '0px' + : items.length == 1 ? '150px' + : '215px', + width: '560px', + height: '80px' + }) + .appendTo($features); + $features.animate({opacity: 1}, 250); + } + + function getHTML(item) { + return '' + + ( + (lists && edits) || (lists && texts) || (edits && texts) + ? Ox._(Ox.toTitleCase(item.type)) + ': ' + : '' + ) + + Ox.encodeHTMLEntities(item.name) + '

' + + item.description; + } + + function getImageURL(item) { + return '/' + item.type + '/' + item.user + + ':' + encodeURIComponent(item.name) + '/icon256.jpg?' + item.modified; + } + + function getTooltip(item) { + return Ox._('View {0}', [Ox._(Ox.toTitleCase(item.type))]) + } + + function openItem(i) { + that.fadeOutScreen(); + pandora.UI.set(Ox.extend({ + section: items[i].type == 'list' ? 'items' : items[i].type + 's', + page: '' + }, items[i].type == 'list' ? { + find: { + conditions: [{ + key: 'list', + value: items[i].user + ':' + + items[i].name, + operator: '==' + }], + operator: '&' + } + } : items[i].type == 'edit' ? { + edit: items[i].user + ':' + items[i].name + } : { + text: items[i].user + ':' + items[i].name + })); + } + + function scrollToPosition(i, animate) { + if (i >= 0 && i <= items.length - max && i != position) { + position = i; + $featuresContent.stop().animate({ + left: (position * -65) + 'px' + }, animate ? 250 : 0, function() { + if (position == 0) { + $previousButton.removeClass('visible').stop().animate({ + opacity: 0 + }, 250, function() { + $previousButton.hide(); + }); + } else { + $previousButton.addClass('visible'); + } + if (position == items.length - max) { + $nextButton.removeClass('visible').stop().animate({ + opacity: 0 + }, 250, function() { + $nextButton.hide(); + }); + } else { + $nextButton.addClass('visible'); + } + if (mouse) { + $featuresBox.trigger('mouseenter'); + } + }); + } + } + + function selectItem(i) { + if (i >= 0 && i <= items.length - 1 && i != selected) { + $featureBox[selected].css({ + boxShadow: 'none' + }); + selected = i; + $featureBox[selected].css({ + boxShadow: '0 0 2px ' + color + }); + if (selected < position) { + scrollToPosition(selected, true); + } else if (selected > position + max - 1) { + scrollToPosition(selected - max + 1, true); + } + $icon.attr({ + src: getImageURL(items[selected]) + }).options({ + tooltip: getTooltip(items[selected]) + }); + $text.html( + getHTML(items[selected]) + ); + } + } + + } + } + + that.fadeInScreen = function() { + that.appendTo(Ox.UI.$body).animate({opacity: 1}, 500, function() { + that.find('*').animate({opacity: 1}, 250, function() { + $findInput.focusInput(true); + showFeatures(); + }); + }); + return that; + }; + + that.fadeOutScreen = function() { + $('.OxTooltip').remove(); + that.animate({opacity: 0}, 500, function() { + that.remove(); + }); + pandora.$ui.tv && pandora.$ui.tv.unmute().find('.OxControls.OxVolume').hide(); + self.keydown && Ox.$document.off({keydown: self.keydown}); + return that; + }; + + that.showScreen = function(callback) { + var $elements = that.find('*'), count = 0; + $box.css({top: window.innerHeight / 2 - 80 + 'px'}); + that.css({opacity: 1}).appendTo(Ox.UI.$body); + $findInput.focusInput(true); + $box.animate({top: '80px'}, 500, function() { + $elements.animate({opacity: 1}, 250, function() { + if (++count == $elements.length) { + showFeatures(); + callback && callback(); + } + }); + }); + return that; + }; + + return that; + +}; diff --git a/static/js/infoView.bakma.js b/static/js/infoView.bakma.js new file mode 100644 index 0000000..aabf5d2 --- /dev/null +++ b/static/js/infoView.bakma.js @@ -0,0 +1,796 @@ +'use strict'; + +pandora.ui.infoView = function(data) { + + var ui = pandora.user.ui, + canEdit = pandora.site.capabilities.canEditMetadata[pandora.user.level] || data.editable, + canRemove = pandora.site.capabilities.canRemoveItems[pandora.user.level] || data.editable, + css = { + marginTop: '4px', + textAlign: 'justify' + }, + descriptions = [], + html, + iconRatio = ui.icons == 'posters' ? 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, + margin = 16, + nameKeys = ['director', 'cinematographer', 'featuring'], + listKeys = nameKeys.concat(['language', 'tag', 'category', 'groups']), + statisticsWidth = 128, + + $bar = Ox.Bar({size: 16}) + .bindEvent({ + doubleclick: function(e) { + if ($(e.target).is('.OxBar')) { + $info.animate({scrollTop: 0}, 250); + } + } + }), + + $options = Ox.MenuButton({ + items: [ + { + id: 'delete', + title: Ox._('Delete {0}...', [pandora.site.itemName.singular]), + disabled: !canRemove + } + ], + style: 'square', + title: 'set', + tooltip: Ox._('Options'), + type: 'image', + }) + .css({ + float: 'left', + borderColor: 'rgba(0, 0, 0, 0)', + background: 'rgba(0, 0, 0, 0)' + }) + .bindEvent({ + click: function(data_) { + if (data_.id == 'delete') { + pandora.$ui.deleteItemDialog = pandora.ui.deleteItemDialog(data).open(); + } + } + }) + .appendTo($bar), + + $edit = Ox.MenuButton({ + items: [ + { + id: 'insert', + title: Ox._('Insert HTML...'), + disabled: true + } + ], + style: 'square', + title: 'edit', + tooltip: Ox._('Edit'), + type: 'image', + }) + .css({ + float: 'right', + borderColor: 'rgba(0, 0, 0, 0)', + background: 'rgba(0, 0, 0, 0)' + }) + .bindEvent({ + click: function(data) { + // ... + } + }) + .appendTo($bar), + + $info = Ox.Element().css({overflowY: 'auto'}), + + that = Ox.SplitPanel({ + elements: [ + {element: $bar, size: 16}, + {element: $info} + ], + orientation: 'vertical' + }), + + $left = Ox.Element() + .css({ + position: 'absolute' + }) + .appendTo($info), + + $icon = Ox.Element({ + element: '', + tooltip: 'Switch to ' + Ox.getObjectById( + pandora.site.itemViews, + ui.videoView + ).title + ' View' + }) + .attr({ + src: pandora.getMediaURL('/' + data.id + '/' + ( + ui.icons == 'posters' ? 'poster' : 'icon' + ) + '512.jpg?' + data.modified) + }) + .css({ + position: 'absolute', + left: margin + iconLeft + 'px', + top: margin + 'px', + width: iconWidth + 'px', + height: iconHeight + 'px', + borderRadius: borderRadius + 'px', + cursor: 'pointer' + }) + .bindEvent({ + anyclick: function() { + pandora.UI.set({itemView: ui.videoView}); + } + }) + .appendTo($left), + + $reflection = $('
') + .addClass('OxReflection') + .css({ + position: 'absolute', + left: margin + 'px', + top: margin + iconHeight + 'px', + width: iconSize + 'px', + height: Math.round(iconSize / 2) + 'px', + overflow: 'hidden' + }) + .appendTo($left), + + $reflectionIcon = $('') + .attr({ + src: pandora.getMediaURL('/' + data.id + '/' + ( + ui.icons == 'posters' ? 'poster' : 'icon' + ) + '512.jpg?' + data.modified) + }) + .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: Math.round(iconSize / 2) + 'px' + }) + .appendTo($reflection), + + $data = $('
') + .addClass('OxTextPage') + .css({ + position: 'absolute', + left: margin + 'px', + top: margin + iconHeight + margin + 'px', + width: iconSize + 'px', + }) + .appendTo($left), + + $text = Ox.Element() + .addClass('OxTextPage') + .css({ + position: 'absolute', + left: margin + (iconSize == 256 ? 256 : iconWidth) + margin + 'px', + top: margin + 'px', + right: margin + statisticsWidth + margin + 'px', + }) + .appendTo($info), + + $statistics = $('
') + .css({ + position: 'absolute', + width: statisticsWidth + 'px', + top: margin + 'px', + right: margin + 'px' + }) + .appendTo($info), + + $capabilities; + + [$options, $edit].forEach(function($element) { + $element.find('input').css({ + borderWidth: 0, + borderRadius: 0, + padding: '3px' + }); + }); + + if (!canEdit) { + pandora.createLinks($info); + } + + // Source & Project -------------------------------------------------------- +/* + ['source', 'project'].forEach(function(key) { + if (canEdit || data[key]) { + var $div = $('
') + .addClass('OxSelectable') + .css(css) + .css({margin: 0}) + .appendTo($data); + $('') + .html( + formatKey({ + category: 'categories', + }[key] || key).replace('', ' ') + ) + .appendTo($div); + Ox.EditableContent({ + clickLink: pandora.clickLink, + format: function(value) { + return formatValue(key, value); + }, + placeholder: formatLight(Ox._('unknown')), + editable: canEdit, + tooltip: canEdit ? pandora.getEditTooltip() : '', + value: listKeys.indexOf(key) >= 0 + ? (data[key] || []).join(', ') + : data[key] || '' + }) + .bindEvent({ + submit: function(event) { + editMetadata(key, event.value); + } + }) + .appendTo($div); + if (canEdit || data[key + 'description']) { + $('
') + .append( + descriptions[key] = Ox.EditableContent({ + clickLink: pandora.clickLink, + editable: canEdit, + format: function(value) { + return value.replace( + /') + .css({ + marginTop: '-2px', + marginBottom: '12px' + }) + .append( + Ox.EditableContent({ + editable: canEdit, + placeholder: formatLight(Ox._('No Title')), + tooltip: canEdit ? pandora.getEditTooltip() : '', + value: data.title + }) + .css({ + fontWeight: 'bold', + fontSize: '13px' + }) + .bindEvent({ + submit: function(event) { + editMetadata('title', event.value); + } + }) + ) + .appendTo($text); + + // Groups ------------------------------------------------------------------ + + renderGroup(['location', 'date', 'language']); + + renderGroup(['category', 'name']); + + renderGroup(['tag']); + + // Summary ------------------------------------------------------------- + + if (canEdit || data.summary) { + $('
') + .css({ + marginTop: '12px', + marginBottom: '12px' + }) + .append( + Ox.EditableContent({ + clickLink: pandora.clickLink, + collapseToEnd: false, + editable: canEdit, + format: function(value) { + return value.replace( + /').css({width: '1px', height: 0, clear: 'both'}).appendTo($text); + } + + // License ----------------------------------------------------------------- + + //renderGroup(['license']); + + + $('
') + .addClass('OxSelectable') + .css(css) + .css({height: '16px'}) + .appendTo($text); + + // Duration, Aspect Ratio -------------------------------------------------- + + ['duration', 'aspectratio'].forEach(function(key) { + var itemKey = Ox.getObjectById(pandora.site.itemKeys, key), + value = data[key] || 0; + $('
') + .css({marginBottom: '4px'}) + .append(formatKey(itemKey.title, 'statistics')) + .append( + Ox.Theme.formatColor(null, 'gradient') + .css({textAlign: 'right'}) + .html( + Ox['format' + Ox.toTitleCase(itemKey.format.type)] + .apply(null, [value].concat(itemKey.format.args)) + ) + ) + .appendTo($statistics); + }); + + // Hue, Saturation, Lightness, Volume -------------------------------------- + + ['hue', 'saturation', 'lightness', 'volume'].forEach(function(key) { + $('
') + .css({marginBottom: '4px'}) + .append(formatKey(key, 'statistics')) + .append( + Ox.Theme.formatColor( + data[key] || 0, key == 'volume' ? 'lightness' : key + ).css({textAlign: 'right'}) + ) + .appendTo($statistics); + }); + + // Cuts per Minute --------------------------------------------------------- + + $('
') + .css({marginBottom: '4px'}) + .append(formatKey('cuts per minute', 'statistics')) + .append( + Ox.Theme.formatColor(null, 'gradient') + .css({textAlign: 'right'}) + .html(Ox.formatNumber(data['cutsperminute'] || 0, 3)) + ) + .appendTo($statistics); + + // Rights Level ------------------------------------------------------------ + + var $rightsLevel = $('
'); + $('
') + .css({marginBottom: '4px'}) + .append(formatKey(Ox._('Rights Level'), 'statistics')) + .append($rightsLevel) + .appendTo($statistics); + renderRightsLevel(); + + // User and Groups --------------------------------------------------------- + + ['user', 'groups'].forEach(function(key) { + (canEdit || data[key] && data[key].length) && $('
') + .css({marginBottom: '4px'}) + .append(formatKey(key, 'statistics')) + .append( + $('
') + .css({margin: '2px 0 0 -1px'}) // fixme: weird + .append( + Ox.Editable({ + placeholder: key == 'groups' ? formatLight(Ox._('No Groups')) : '', + editable: canEdit, + tooltip: canEdit ? pandora.getEditTooltip() : '', + value: key == 'user' ? data[key] : data[key].join(', ') + }) + .bindEvent({ + submit: function(event) { + editMetadata(key, event.value); + } + }) + ) + ) + .appendTo($statistics); + }); + + // Created and Modified ---------------------------------------------------- + + ['created', 'modified'].forEach(function(key) { + $('
') + .css({marginBottom: '4px'}) + .append(formatKey(key, 'statistics')) + .append( + $('
').html(Ox.formatDate(data[key], '%B %e, %Y')) + ) + .appendTo($statistics); + }); + + // Notes -------------------------------------------------------------------- + + if (canEdit) { + $('
') + .css({marginBottom: '4px'}) + .append( + formatKey('Notes', 'statistics').options({ + tooltip: Ox._('Only {0} can see and edit these comments', [ + Object.keys(pandora.site.capabilities.canEditMetadata).map(function(level, i) { + return ( + i == 0 ? '' + : i < Ox.len(pandora.site.capabilities.canEditMetadata) - 1 ? ', ' + : ' ' + Ox._('and') + ' ' + ) + Ox.toTitleCase(level) + }).join('')]) + }) + ) + .append( + Ox.EditableContent({ + height: 128, + placeholder: formatLight(Ox._('No notes')), + tooltip: pandora.getEditTooltip(), + type: 'textarea', + value: data.notes || '', + width: 128 + }) + .bindEvent({ + submit: function(event) { + editMetadata('notes', event.value); + } + }) + ) + .appendTo($statistics); + } + + $('
').css({height: '16px'}).appendTo($statistics); + + function editMetadata(key, value) { + if (value != data[key]) { + var edit = {id: data.id}; + if (key == 'title') { + edit[key] = value; + } else if (listKeys.indexOf(key) >= 0) { + edit[key] = value ? value.split(', ') : []; + } else { + edit[key] = value ? value : null; + } + pandora.api.edit(edit, function(result) { + data[key] = result.data[key]; + descriptions[key] && descriptions[key].options({ + value: result.data[key + 'description'] + }); + Ox.Request.clearCache(); // fixme: too much? can change filter/list etc + if (result.data.id != data.id) { + pandora.UI.set({item: result.data.id}); + pandora.$ui.browser.value(data.id, 'id', result.data.id); + } + pandora.updateItemContext(); + pandora.$ui.browser.value(result.data.id, key, result.data[key]); + pandora.$ui.itemTitle + .options({ + title: '' + result.data.title + + (Ox.len(result.data.director) + ? ' (' + result.data.director.join(', ') + ')' + : '') + + (result.data.year ? ' ' + result.data.year : '') + '' + }); + //pandora.$ui.contentPanel.replaceElement(0, pandora.$ui.browser = pandora.ui.browser()); + }); + } + } + + function formatKey(key, mode) { + var item = Ox.getObjectById(pandora.site.itemKeys, key); + key = Ox._(item ? item.title : key); + mode = mode || 'text'; + return mode == 'text' + ? '' + Ox.toTitleCase(key) + ': ' + : mode == 'description' + ? Ox.toTitleCase(key) + : Ox.Element() + .css({marginBottom: '4px', fontWeight: 'bold'}) + .html(Ox.toTitleCase(key) + .replace(' Per ', ' per ')); + } + + function formatLight(str) { + return '' + str + ''; + } + + function formatLink(key, value, linkValue) { + linkValue = linkValue || value; + return (Ox.isArray(value) ? value : [value]).map(function(value) { + return key + ? '' + value + '' + : value; + }).join(', '); + } + + function formatValue(key, value) { + var ret; + if (key == 'date') { + ret = value ? Ox.formatDate(value, + ['', '%Y', '%B %Y', '%B %e, %Y'][value.split('-').length] + ) : ''; + } else if (listKeys.indexOf(key) > -1) { + ret = value.split(', '); + } else { + ret = value; + } + if (nameKeys.indexOf(key) > -1) { + key = 'name'; + } + return formatLink(key, ret, key == 'date' && value); + } + + function getRightsLevelElement(rightsLevel) { + return Ox.Theme.formatColorLevel( + rightsLevel, + pandora.site.rightsLevels.map(function(rightsLevel) { + return rightsLevel.name; + }) + ); + } + + function getValue(key, value) { + return !value ? '' + : Ox.contains(listKeys, key) ? value.join(', ') + : value; + } + + function renderCapabilities(rightsLevel) { + var capabilities = [].concat( + 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._(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: Ox._('{0} ' + + (hasCapability ? 'can' : 'can\'t') + ' ' + + Ox.toSlashes(capability.name) + .split('/').slice(1).join(' ') + .toLowerCase() + .replace('see item', 'see the item') + .replace('play video', 'play the full video') + .replace('download video', 'download the video'), + [canEdit ? Ox._(Ox.toTitleCase(userLevel)) : Ox._('You')]), + 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: Ox._('Help'), + tooltip: Ox._('About Rights'), + type: 'image' + }) + .css({marginLeft: '52px'}) + .bindEvent({ + click: function() { + pandora.UI.set({page: 'rights'}); + } + }) + .appendTo($line); + } + }); + } + + function renderGroup(keys) { + var $element; + if (canEdit || keys.filter(function(key) { + return data[key]; + }).length) { + $element = $('
') + .addClass('OxSelectable') + .css(css); + keys.forEach(function(key, i) { + if (canEdit || data[key]) { + if ($element.children().length) { + $('').html('; ').appendTo($element); + } + $('').html(formatKey(key)).appendTo($element); + Ox.EditableContent({ + clickLink: pandora.clickLink, + format: function(value) { + return formatValue(key, value); + }, + placeholder: formatLight(Ox._('unknown')), + tooltip: canEdit ? pandora.getEditTooltip() : '', + value: getValue(key, data[key]) + }) + .bindEvent({ + submit: function(data) { + editMetadata(key, data.value); + } + }) + .appendTo($element); + } + }); + $element.appendTo($text); + } + } + + 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: Ox._(rightsLevel.name)}; + }), + width: 128, + value: data.rightslevel + }) + .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); + $data.animate({ + top: margin + iconHeight + margin + 'px', + width: iconSize + 'px', + }, 250); + $text.animate({ + left: margin + (iconSize == 256 ? 256 : iconWidth) + margin + 'px', + }, 250); + pandora.UI.set({infoIconSize: iconSize}); + } + + that.reload = function() { + var src = pandora.getMediaURL('/' + data.id + '/' + ( + ui.icons == 'posters' ? 'poster' : 'icon' + ) + '512.jpg?' + Ox.uid()); + $icon.attr({src: src}); + $reflectionIcon.attr({src: src}); + iconSize = iconSize == 256 ? 512 : 256; + iconRatio = ui.icons == 'posters' + ? (ui.showSitePosters ? pandora.site.posters.ratio : data.posterRatio) : 1; + toggleIconSize(); + }; + + that.bindEvent({ + pandora_icons: that.reload, + pandora_showsiteposters: function() { + ui.icons == 'posters' && that.reload(); + } + }); + + return that; + +}; diff --git a/static/png/icon.bakma.png b/static/png/icon.bakma.png new file mode 100644 index 0000000..09e2502 Binary files /dev/null and b/static/png/icon.bakma.png differ diff --git a/static/png/logo.bakma.png b/static/png/logo.bakma.png new file mode 100644 index 0000000..dee6007 Binary files /dev/null and b/static/png/logo.bakma.png differ