diff --git a/oxcd/server.py b/oxcd/server.py index 4e6295c..d27e829 100644 --- a/oxcd/server.py +++ b/oxcd/server.py @@ -155,6 +155,10 @@ class Server(Resource): return f if request.path == '/api/': return self + if request.path == '/library.xml': + f = File(self.backend.xml) + f.isLeaf = True + return f if request.path.startswith('/track/'): track_id = request.path.split('/')[-1].split('.')[0] track = self.backend.library['Tracks'].get(track_id) diff --git a/oxcd/static/index.html b/oxcd/static/index.html index 8b26615..2968508 100644 --- a/oxcd/static/index.html +++ b/oxcd/static/index.html @@ -5,7 +5,7 @@ 0xcd - + diff --git a/oxcd/static/js/index.js b/oxcd/static/js/index.js new file mode 100644 index 0000000..55e5779 --- /dev/null +++ b/oxcd/static/js/index.js @@ -0,0 +1,276 @@ +'use strict'; + +Ox.load('UI', function() { + var app = { + $ui: {}, + data: {}, + site: { + columns: [ + { + format: function(value) { + return value ? $('').addClass('symbol').attr({ + src: Ox.UI.getImageURL('symbolMute') + }) : ''; + }, + id: 'playing', + operator: '-', + removable: false, + resizable: false, + title: 'Playing', + titleImage: 'mute', + visible: true, + width: 16 + }, + { + format: function(value) { + return value ? '' : $('').addClass('symbol').attr({ + src: Ox.UI.getImageURL('symbolCheck') + }); + }, + id: 'disabled', + operator: '+', + removable: false, + resizable: false, + title: 'Enabled', + titleImage: 'check', + visible: true, + width: 16 + }, + { + format: function(value) { + return app.utils.formatTitle(value); + }, + id: 'name', + operator: '+', + removable: false, + title: 'Name', + visible: true, + width: 192 + }, + { + id: 'artist', + operator: '+', + sort: function(value, data) { + return data.albumArtist ? data.sortAlbumArtist || data.albumArtist + : data.compilation ? '' + : data.sortArtist || data.artist || ''; + }, + title: 'Artist', + visible: true, + width: 192 + }, + { + format: function(value) { + return app.utils.formatTitle(value); + }, + id: 'album', + operator: '+', + title: 'Album', + visible: true, + width: 192 + }, + { + align: 'right', + id: 'year', + operator: '+', + title: 'Year', + visible: true, + width: 64 + }, + { + align: 'right', + format: function(value) { + return app.utils.formatTime(value); + }, + id: 'totalTime', + operator: '-', + title: 'Time', + visible: true, + width: 64 + }, + { + align: 'right', + format: function(value) { + return Ox.formatValue(value, 'B'); + }, + id: 'size', + operator: '-', + title: 'Size', + visible: true, + width: 64, + } + ] + }, + user: { + library: '/library.xml', + sort: [ + {key: 'artist', operator: '+'}, + {key: 'year', operator: '+'}, + {key: 'album', operator: '+'}, + {key: 'discNumber', operator: '+'}, + {key: 'trackNumber', operator: '+'} + ] + }, + ui: {}, + utils: {} + }; + app.load = function() { + app.$ui.appPanel = app.ui.appPanel().appendTo(Ox.$body); + app.utils.parseLibrary(function(data) { + app.user.music = data; + app.loaded = true; + app.$ui.appPanel.replaceElement(1, app.$ui.list = app.ui.list()); + }); + } + app.ui.appPanel = function() { + var $element = Ox.SplitPanel({ + elements: [ + {element: app.$ui.toolbar = app.ui.toolbar(), size: 24}, + {element: app.$ui.list = app.ui.list()}, + {element: app.$ui.statusbar = app.ui.statusbar(), size: 24} + ], + orientation: 'vertical' + }); + return $element; + }; + app.ui.toolbar = function() { + var $element = Ox.Bar({size: 24}); + return $element; + }; + app.ui.list = function() { + var $element = !app.loaded ? Ox.Element() : Ox.TableList({ + columns: app.site.columns, + columnsRemovable: true, + columnsVisible: true, + items: app.user.music.tracks, + keys: ['albumArtist', 'compilation', 'sortAlbumArtist', 'sortArtist'], + /* + query: { + conditions: [ + {key: 'artist', operator: '==', value: 'Mary Lou Lord'}, + {key: 'artist', operator: '==', value: 'Liz Phair'} + ], + operator: '|' + }, + */ + scrollbarVisible: true, + sort: app.user.sort, + sums: ['size', 'totalTime'], + unique: 'trackID' + }) + .bindEvent({ + init: function(data) { + app.$ui.statusbar.update(data); + }, + open: function(data) { + app.utils.play(data.ids[0]); + }, + openpreview: function(data) { + $element.closePreview(); + app.utils.play(data.ids[0]); + }, + select: function(data) { + app.$ui.statusbar.update(data); + } + }); + return $element; + }; + app.ui.statusbar = function() { + var $status = $('
').attr({id: 'status'}), + $element = Ox.Bar({size: 24}) + .append($status); + $element.update = function(data) { + var items; + if (data) { + if (data.items) { + app.data.status = data; + } else if (data.ids && data.ids.length > 1) { + var items = app.user.music.tracks.filter(function(track) { + return Ox.contains(data.ids, track.trackID); + }); + data.selected = data.ids.length; + ['size', 'totalTime'].forEach(function(key) { + data[key] = items.reduce(function(p, c) { + return p + c[key]; + }, 0); + }); + } + } + $status.html( + data ? (data.selected ? data.selected + ' of ' : '') + + Ox.formatNumber(app.data.status.items) + ' item' + + (app.data.status.items == 1 ? '' : 's') + ', ' + + app.utils.formatTime(data.totalTime || app.data.status.totalTime) + ' total time, ' + + Ox.formatValue(data.size || app.data.status.size, 'B') + : 'Loading...' + ); + }; + $element.update(); + return $element; + }; + app.utils.formatTime = function(duration) { + return Ox.formatDuration(duration / 1000).replace(/^00:/, '').replace(/^0/, ''); + }; + app.utils.formatTitle = function(title) { + return title.replace(/\[(.+)\]$/, '($1)'); + }; + app.utils.parseKey = function(key) { + return key[0].toLowerCase() + key.substr(1).replace(/ /g, ''); + }; + app.utils.parseLibrary = function(callback) { + Ox.get(app.user.library, function(xml) { + var data = {playlists: [], tracks: []}, + library = app.utils.parseXML($(xml).children()[0]); + Ox.forEach(library.tracks, function(track) { + if (track.kind == 'MPEG audio file' && !track.podcast) { + app.site.columns.map(function(column) { + return column.id; + }).forEach(function(key) { + if (!track[key]) { + track[key] = ''; + } + }) + data.tracks.push(track); + } + }); + callback(data); + }); + }; + app.utils.parseXML = function(xml) { + var type = xml.nodeName.toLowerCase(), value; + if (type == 'array') { + value = []; + $(xml).children().each(function(k, v) { + value.push(app.utils.parseXML(v)); + }); + } else if (type == 'data') { + value = xml.innerHTML.replace(/(\n|\t)/g, ''); + } else if (type == 'date') { + value = xml.innerHTML; + } else if (type == 'dict') { + value = {}; + $(xml).children('key').each(function(k, v) { + value[app.utils.parseKey(v.innerHTML)] = app.utils.parseXML($(v).next()[0]); + }); + } else if (type == 'false') { + value = false; + } else if (type == 'integer') { + value = parseInt(xml.innerHTML); + } else if (type == 'string') { + value = xml.innerHTML; + } else if (type == 'true') { + value = true; + } + return value; + }; + app.utils.play = function(id) { + if (app.user.playing) { + app.$ui.list.value(app.user.playing, 'playing', false); + } + if (id != app.user.playing) { + app.user.playing = id; + app.$ui.list.value(app.user.playing, 'playing', true); + } + }; + app.load(); +}); diff --git a/oxcd/static/js/site.js b/oxcd/static/js/site.js deleted file mode 100644 index 6cebc6c..0000000 --- a/oxcd/static/js/site.js +++ /dev/null @@ -1,18 +0,0 @@ -Ox.load('UI', function() { - window.oxcd = Ox.App({ - name: 'oxcd', - url: '/api/' - }).bindEvent({ - load: function(data) { - oxcd.api.library(function(result) { - var items = Ox.values(result.data.Tracks), - element = Ox.Element(); - items.forEach(function(item) { - element.append($('
').html(''+item['Track ID'] + ': '+item.Name+'')) - console.log(''+item['Track ID'] + ':'+item.Name+''); - }); - Ox.UI.$body.append(element); - }) - } - }); -});