This commit is contained in:
j 2014-05-12 14:57:47 +02:00
parent 2ee2bc178a
commit 10d2f35b7b
20 changed files with 1334 additions and 629 deletions

View file

@ -20,8 +20,8 @@ class Changelog(db.Model):
editlist name {name: newname}
orderlists [name, name, name]
removelist name
additemtolist listname itemid
removeitemfromlist listname itemid
addlistitems listname [ids]
removelistitems listname [ids]
editusername username
editcontact string
addpeer peerid peername
@ -182,24 +182,17 @@ class Changelog(db.Model):
l.remove()
return True
def action_addlistitem(self, user, timestamp, name, itemid):
from item.models import Item
def action_addlistitems(self, user, timestamp, name, ids):
from user.models import List
l = List.get(user.id, name)
i = Item.get(itemid)
if l and i:
i.lists.append(l)
i.update()
l = List.get_or_create(user.id, name)
l.add_items(ids)
return True
def action_removelistitem(self, user, timestamp, name, itemid):
from item.models import Item
def action_removelistitem(self, user, timestamp, name, ids):
from user.models import List
l = List.get(user.id, name)
i = Item.get(itemid)
if l and i:
i.lists.remove(l)
i.update()
if l:
l.remove_items(ids)
return True
def action_editusername(self, user, timestamp, username):

View file

@ -6,7 +6,6 @@ from flask import json
from oxflask.api import actions
from oxflask.shortcuts import returns_json
from oml import utils
import query
import models
@ -118,6 +117,8 @@ def edit(request):
if item and keys and item.json()['mediastate'] == 'available':
key = keys[0]
print 'update mainid', key, data[key]
if key in ('isbn10', 'isbn13'):
data[key] = utils.normalize_isbn(data[key])
item.update_mainid(key, data[key])
response = item.json()
else:

View file

@ -233,6 +233,12 @@ class Item(db.Model):
else:
f.value = '%s:' % p.id
db.session.add(f)
for l in self.lists:
f = Find()
f.item_id = self.id
f.key = 'list'
f.value = l.find_id
db.session.add(f)
def update(self):
users = map(str, list(self.users))

104
oml/localnodes.py Normal file
View file

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import socket
import thread
import json
import struct
from threading import Thread
from settings import preferences, server, USER_ID
from node.utils import get_public_ipv6
def can_connect(data):
try:
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.settimeout(1)
s.connect((data['host'], data['port']))
s.close()
return True
except:
pass
return False
class LocalNodes(Thread):
_active = True
_nodes = {}
_BROADCAST = "ff02::1"
_PORT = 9851
TTL = 2
def __init__(self, app):
self._app = app
Thread.__init__(self)
self.daemon = True
self.start()
self.host = get_public_ipv6()
self.send()
def send(self):
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
ttl = struct.pack('@i', self.TTL)
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl)
message = json.dumps({
'id': USER_ID,
'username': preferences.get('username', 'anonymous'),
'host': self.host,
'port': server['node_port'],
})
s.sendto(message + '\0', (self._BROADCAST, self._PORT))
def receive(self):
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', self._PORT))
group_bin = socket.inet_pton(socket.AF_INET6, self._BROADCAST) + '\0'*4
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group_bin)
while self._active:
data, addr = s.recvfrom(1024)
while data[-1] == '\0':
data = data[:-1] # Strip trailing \0's
data = self.validate(data)
if data:
if data['id'] not in self._nodes:
thread.start_new_thread(self.new_node, (data, ))
else:
print 'UPDATE NODE', data
self._nodes[data['id']] = data
def validate(self, data):
try:
data = json.loads(data)
except:
return None
for key in ['id', 'username', 'host', 'port']:
if key not in data:
return None
return data
def get(self, user_id):
if user_id in self._nodes:
if can_connect(self._nodes[user_id]):
return self._nodes[user_id]
def new_node(self, data):
print 'NEW NODE', data
if can_connect(data):
self._nodes[data['id']] = data
with self._app.app_context():
import user.models
u = user.models.User.get_or_create(data['id'])
u.info['username'] = data['username']
u.info['local'] = data
u.save()
self.send()
def run(self):
self.receive()
def join(self):
self._active = False
return Thread.join(self)

View file

@ -107,11 +107,8 @@ def start(app):
(r"/get/(.*)", ShareHandler, dict(app=app)),
(r".*", NodeHandler, dict(app=app)),
])
#tr = WSGIContainer(node_app)
#http_server= HTTPServer(tr)
http_server.listen(settings.server['node_port'], settings.server['node_address'])
host = utils.get_public_ipv4()
host = utils.get_public_ipv6()
state.online = directory.put(settings.sk, {
'host': host,
'port': settings.server['node_port']

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import socket
import requests
from urlparse import urlparse
def get_public_ipv6():
host = ('2a01:4f8:120:3201::3', 25519)

View file

@ -19,6 +19,7 @@ from changelog import Changelog
import directory
from websocket import trigger_event
from localnodes import LocalNodes
ENCODING='base64'
@ -26,8 +27,9 @@ class Node(object):
online = False
download_speed = 0
def __init__(self, app, user):
self._app = app
def __init__(self, nodes, user):
self._nodes = nodes
self._app = nodes._app
self.user_id = user.id
key = str(user.id)
self.vk = ed25519.VerifyingKey(key, encoding=ENCODING)
@ -35,10 +37,15 @@ class Node(object):
@property
def url(self):
if ':' in self.host:
url = 'http://[%s]:%s' % (self.host, self.port)
local = self.get_local()
if local:
url = 'http://[%s]:%s' % (local['host'], local['port'])
print 'using local peer discovery to access node', url
else:
url = 'http://%s:%s' % (self.host, self.port)
if ':' in self.host:
url = 'http://[%s]:%s' % (self.host, self.port)
else:
url = 'http://%s:%s' % (self.host, self.port)
return url
def resolve_host(self):
@ -51,6 +58,11 @@ class Node(object):
self.host = None
self.port = 9851
def get_local(self):
if self._nodes and self._nodes._local:
return self._nodes._local.get(self.user_id)
return None
def request(self, action, *args):
if not self.host:
self.resolve_host()
@ -211,6 +223,7 @@ class Nodes(Thread):
self._app = app
self._q = Queue()
self._running = True
self._local = LocalNodes(app)
Thread.__init__(self)
self.daemon = True
self.start()
@ -238,7 +251,7 @@ class Nodes(Thread):
def _add_node(self, user_id):
if user_id not in self._nodes:
from user.models import User
self._nodes[user_id] = Node(self._app, User.get_or_create(user_id))
self._nodes[user_id] = Node(self, User.get_or_create(user_id))
else:
self._nodes[user_id].online = True
trigger_event('status', {

View file

@ -144,6 +144,8 @@ class Parser(object):
l = self._list.query.filter_by(user_id=p.id, name=name).first()
else:
l = None
if l:
v = l.find_id
if l and l._query:
data = l._query
q = self.parse_conditions(data.get('conditions', []),

View file

@ -38,7 +38,7 @@ server_defaults = {
'port': 9842,
'address': '127.0.0.1',
'node_port': 9851,
'node_address': '::',
'node_address': '',
'extract_text': True,
'directory_service': 'http://[2a01:4f8:120:3201::3]:25519',
'lookup_service': 'http://data.openmedialibrary.com',

View file

@ -84,9 +84,9 @@ actions.register(getUsers)
@returns_json
def getLists(request):
lists = {}
lists = []
for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)):
lists[u.id] = u.lists_json()
lists += u.lists_json()
return {
'lists': lists
}
@ -132,28 +132,24 @@ def editList(request):
actions.register(editList, cache=False)
@returns_json
def addListItem(request):
def addListItems(request):
data = json.loads(request.form['data']) if 'data' in request.form else {}
l = models.List.get_or_create(data['id'])
i = Item.get(data['item'])
if l and i:
l.items.append(i)
models.db.session.add(l)
i.update()
l = models.List.get_or_create(data['list'])
if l:
l.add_items(data['items'])
return l.json()
return {}
actions.register(addListItem, cache=False)
actions.register(addListItems, cache=False)
@returns_json
def removeListItem(request):
def removeListItems(request):
data = json.loads(request.form['data']) if 'data' in request.form else {}
l = models.List.get(data['id'])
i = Item.get(data['item'])
if l and i:
l.items.remove(i)
models.db.session.add(l)
i.update()
l = models.List.get(data['list'])
if l:
l.remove_items(data['items'])
return l.json()
return {}
actions.register(removeListItem, cache=False)
actions.register(removeListItems, cache=False)
@returns_json
def sortLists(request):

View file

@ -59,7 +59,7 @@ class User(db.Model):
return j
def check_online(self):
return state.nodes.check_online(self.id)
return state.nodes and state.nodes.check_online(self.id)
def lists_json(self):
return [l.json() for l in self.lists.order_by('position')]
@ -158,25 +158,39 @@ class List(db.Model):
from item.models import Item
for item_id in items:
i = Item.get(item_id)
self.items.add(i)
self.items.append(i)
i.update()
db.session.add(self)
db.session.commit()
for item_id in items:
i = Item.get(item_id)
i.update_lists()
db.session.commit()
if self.user_id == settings.USER_ID:
Changelog.record(self.user, 'addlistitems', self.name, items)
def remove_items(self, items):
from item.models import Item
for item_id in items:
i = Item.get(item_id)
self.items.remove(i)
i.update()
db.session.add(self)
db.session.commit()
for item_id in items:
i = Item.get(item_id)
i.update_lists()
db.session.commit()
if self.user_id == settings.USER_ID:
Changelog.record(self.user, 'removelistitems', self.name, items)
def remove(self):
if not self._query:
for i in self.items:
self.items.remove(i)
if not self._query:
print 'record change: removelist', self.user, self.name
Changelog.record(self.user, 'removelist', self.name)
if self.user_id == settings.USER_ID:
Changelog.record(self.user, 'removelist', self.name)
db.session.delete(self)
db.session.commit()
@ -184,10 +198,21 @@ class List(db.Model):
def public_id(self):
id = ''
if self.user_id != settings.USER_ID:
id += self.user_id
id = '%s:%s' % (id, self.name)
id += self.user.nickname
id = u'%s:%s' % (id, self.name)
return id
@property
def find_id(self):
id = ''
if self.user_id != settings.USER_ID:
id += self.user_id
id = u'%s:%s' % (id, self.id)
return id
def __repr__(self):
return self.public_id.encode('utf-8')
def items_count(self):
from item.models import Item
if self._query:
@ -199,6 +224,7 @@ class List(db.Model):
def json(self):
r = {
'id': self.public_id,
'user': self.user.nickname if self.user_id != settings.USER_ID else settings.preferences['username'],
'name': self.name,
'index': self.position,
'items': self.items_count(),

View file

@ -9,8 +9,8 @@ oml.ui.browser = function() {
defaultRatio: oml.config.coverRatio,
draggable: true,
item: function(data, sort, size) {
var color = oml.getFileTypeColor(data).map(function(rgb) {
return rgb.concat(0.8)
var color = oml.getFileInfoColor(ui.fileInfo, data).map(function(rgb) {
return rgb.concat(0.8);
}),
ratio = data.coverRatio || oml.config.coverRatio,
width = Math.round(ratio >= 1 ? size : size * ratio),
@ -28,12 +28,14 @@ oml.ui.browser = function() {
height: Math.round(size / 12.8) + 'px',
borderWidth: Math.round(size / 64) + 'px 0',
borderStyle: 'solid',
borderColor: 'rgba(' + color[1].join(', ') + ')',
borderColor: 'rgba(' + color[2].join(', ') + ')',
margin: Math.round(size / 18) + 'px ' + Math.round(width / 3) + 'px',
fontSize: Math.round(size / 16) + 'px',
textAlign: 'center',
color: 'rgba(' + color[1].join(', ') + ')',
backgroundColor: 'rgba(' + color[0].join(', ') + ')',
color: 'rgba(' + color[2].join(', ') + ')',
backgroundImage: '-webkit-linear-gradient(top, ' + color.slice(0, 2).map(function(rgba) {
return 'rgba(' + rgba.join(', ') + ')';
}).join(', ') + ')',
WebkitTransform: 'rotate(45deg)'
})
.html(
@ -55,8 +57,8 @@ oml.ui.browser = function() {
}), callback);
},
keys: [
'author', 'coverRatio', 'extension',
'id', 'size', 'textsize', 'title'
'author', 'coverRatio', 'extension', 'id',
'mediastate', 'size', 'textsize', 'title'
],
max: 1,
min: 1,
@ -101,6 +103,8 @@ oml.ui.browser = function() {
}
});
oml.enableDragAndDrop(that);
return that;
};

View file

@ -58,200 +58,187 @@ oml.ui.folders = function() {
oml.$ui.libraryList = [];
oml.$ui.folderList = [];
oml.api.getUsers(function(result) {
oml.getUsersAndLists(function(users, lists) {
var peers = result.data.users.filter(function(user) {
return user.peered;
});
Ox.print('GOT USERS AND LISTS', users, lists);
oml.api.getLists(function(result) {
users.forEach(function(user, index) {
Ox.print('GOT LISTS', result.data);
var $content,
items = lists.filter(function(list) {
return list.user == user.nickname
&& list.type != 'library';
}),
libraryId = (!index ? '' : user.nickname) + ':'
var users = [
{
id: oml.user.id,
nickname: oml.user.preferences.username,
online: oml.user.online
}
].concat(peers),
userIndex[user.nickname] = index;
lists = result.data.lists;
users.forEach(function(user, index) {
var $content,
libraryId = (!index ? '' : user.nickname) + ':';
userIndex[user.nickname] = index;
oml.$ui.folder[index] = Ox.CollapsePanel({
collapsed: false,
extras: [
oml.ui.statusIcon(
!oml.user.online ? 'unknown'
: user.online ? 'connected'
: 'disconnected'
),
{},
Ox.Button({
style: 'symbol',
title: 'info',
tooltip: Ox._(!index ? 'Preferences' : 'Profile'),
type: 'image'
})
.bindEvent({
click: function() {
if (!index) {
oml.UI.set({
page: 'preferences',
'part.preferences': 'account'
});
} else {
oml.UI.set({page: 'users'})
}
oml.$ui.folder[index] = Ox.CollapsePanel({
collapsed: false,
extras: [
oml.ui.statusIcon(
!oml.user.online ? 'unknown'
: user.online ? 'connected'
: 'disconnected'
),
{},
Ox.Button({
style: 'symbol',
title: 'info',
tooltip: Ox._(!index ? 'Preferences' : 'Profile'),
type: 'image'
})
.bindEvent({
click: function() {
if (!index) {
oml.UI.set({
page: 'preferences',
'part.preferences': 'account'
});
} else {
oml.UI.set({page: 'users'})
}
})
],
title: Ox.encodeHTMLEntities(user.nickname)
})
.css({
width: ui.sidebarSize
}
})
],
title: Ox.encodeHTMLEntities(user.nickname)
})
.css({
width: ui.sidebarSize
})
.bindEvent({
toggle: function(data) {
oml.UI.set('showFolder.' + user.nickname, !data.collapsed);
}
})
.bindEvent(
'oml_showfolder.' + user.nickname.toLowerCase(),
function(data) {
oml.$ui.folder[index].options({collapsed: !data.value});
}
)
.appendTo(that);
$content = oml.$ui.folder[index].$content
.css({
height: (1 + items.length) * 16 + 'px'
});
$lists.push(
oml.$ui.libraryList[index] = oml.ui.folderList({
items: [
{
id: libraryId,
name: Ox._('Library'),
type: 'library',
items: -1
}
]
})
.bindEvent({
toggle: function(data) {
oml.UI.set('showFolder.' + user.nickname, !data.collapsed);
add: function() {
!index && oml.addList();
},
load: function() {
oml.api.find({
query: getFind(libraryId)
}, function(result) {
oml.$ui.libraryList[index].value(
libraryId, 'items', result.data.items
);
});
},
open: function() {
oml.$ui.listDialog = oml.ui.listDialog().open();
},
select: function(data) {
oml.UI.set({find: getFind(data.ids[0])});
},
selectnext: function() {
oml.UI.set({find: getFind(lists[user.id][0].id)});
},
selectprevious: function() {
var userId = !index ? null : users[index - 1].id,
set = {
find: getFind(
!index
? ''
: Ox.last(lists[userId]).id
)
};
if (userId) {
Ox.extend(set, 'showFolder.' + userId, true);
}
oml.UI.set(set);
}
})
.bindEvent(
'oml_showfolder.' + user.nickname.toLowerCase(),
function(data) {
oml.$ui.folder[index].options({collapsed: !data.value});
}
)
.appendTo(that);
.appendTo($content)
);
$content = oml.$ui.folder[index].$content
.css({
height: (1 + lists[user.id].length) * 16 + 'px'
});
$lists.push(
oml.$ui.libraryList[index] = oml.ui.folderList({
items: [
{
id: libraryId,
name: Ox._('Library'),
type: 'library',
items: -1
}
]
})
.bindEvent({
add: function() {
!index && oml.addList();
},
load: function() {
oml.api.find({
query: getFind(libraryId)
}, function(result) {
oml.$ui.libraryList[index].value(
libraryId, 'items', result.data.items
);
});
},
open: function() {
oml.$ui.listDialog = oml.ui.listDialog().open();
},
select: function(data) {
oml.UI.set({find: getFind(data.ids[0])});
},
selectnext: function() {
oml.UI.set({find: getFind(lists[user.id][0].id)});
},
selectprevious: function() {
var userId = !index ? null : users[index - 1].id,
set = {
find: getFind(
!index
? ''
: Ox.last(lists[userId]).id
)
};
if (userId) {
Ox.extend(set, 'showFolder.' + userId, true);
}
oml.UI.set(set);
}
})
.appendTo($content)
);
$lists.push(
oml.$ui.folderList[index] = oml.ui.folderList({
draggable: !!index,
items: lists[user.id],
sortable: true
})
.bindEvent({
add: function() {
!index && oml.addList();
},
'delete': function() {
!index && oml.deleteList();
},
key_control_d: function() {
oml.addList(ui._list);
},
load: function() {
$lists.push(
oml.$ui.folderList[index] = oml.ui.folderList({
draggable: !!index,
items: items,
sortable: true
})
.bindEvent({
add: function() {
!index && oml.addList();
},
'delete': function() {
!index && oml.deleteList();
},
key_control_d: function() {
oml.addList(ui._list);
},
load: function() {
// ...
},
move: function(data) {
lists[user.id] = data.ids.map(function(listId) {
return Ox.getObjectById(lists[user.id], listId);
});
oml.api.sortLists({
ids: data.ids,
user: user.id
}, function(result) {
// ...
},
move: function(data) {
lists[user.id] = data.ids.map(function(listId) {
return Ox.getObjectById(lists[user.id], listId);
});
oml.api.sortLists({
ids: data.ids,
user: user.id
}, function(result) {
// ...
});
},
open: function() {
oml.ui.listDialog().open();
},
select: function(data) {
oml.UI.set({find: getFind(data.ids[0])});
},
selectnext: function() {
if (index < users.length - 1) {
oml.UI.set(Ox.extend(
{find: getFind(users[index + 1].nickname + ':')},
'showFolder.' + users[index + 1].nickname,
true
));
}
},
selectprevious: function() {
oml.UI.set({find: getFind(libraryId)});
});
},
open: function() {
oml.ui.listDialog().open();
},
select: function(data) {
oml.UI.set({find: getFind(data.ids[0])});
},
selectnext: function() {
if (index < users.length - 1) {
oml.UI.set(Ox.extend(
{find: getFind(users[index + 1].nickname + ':')},
'showFolder.' + users[index + 1].nickname,
true
));
}
})
.bindEvent(function(data, event) {
if (!index) {
Ox.print('LIST EVENT', event, data);
}
})
.css({height: lists[user.id].length * 16 + 'px'})
.appendTo($content)
);
},
selectprevious: function() {
oml.UI.set({find: getFind(libraryId)});
}
})
.bindEvent(function(data, event) {
if (!index) {
Ox.print('LIST EVENT', event, data);
}
})
.css({height: items.length * 16 + 'px'})
.appendTo($content)
);
oml.$ui.folderList[index].$body.css({top: '16px'});
});
selectList();
oml.$ui.folderList[index].$body.css({top: '16px'});
});
selectList();
});
function getFind(list) {

View file

@ -8,8 +8,8 @@ oml.ui.gridView = function() {
defaultRatio: oml.config.coverRatio,
draggable: true,
item: function(data, sort, size) {
var color = oml.getFileTypeColor(data).map(function(rgb) {
return rgb.concat(0.8)
var color = oml.getFileInfoColor(ui.fileInfo, data).map(function(rgb) {
return rgb.concat(0.8);
}),
ratio = data.coverRatio || oml.config.coverRatio,
width = Math.round(ratio >= 1 ? size : size * ratio),
@ -20,6 +20,9 @@ oml.ui.gridView = function() {
? (data.author || '') : data[sortKey]
);
size = size || 128;
Ox.print('WTF', '-webkit-linear-gradient(top, ' + color.slice(2).map(function(rgba) {
return 'rgba(' + rgba.join(', ') + ')';
}).join(', ') + ')');
return {
extra: ui.showFileInfo ? $('<div>')
.css({
@ -27,12 +30,14 @@ oml.ui.gridView = function() {
height: Math.round(size / 12.8) + 'px',
borderWidth: Math.round(size / 64) + 'px 0',
borderStyle: 'solid',
borderColor: 'rgba(' + color[1].join(', ') + ')',
borderColor: 'rgba(' + color[2].join(', ') + ')',
margin: Math.round(size / 18) + 'px ' + Math.round(width / 3) + 'px',
fontSize: Math.round(size / 16) + 'px',
textAlign: 'center',
color: 'rgba(' + color[1].join(', ') + ')',
backgroundColor: 'rgba(' + color[0].join(', ') + ')',
color: 'rgba(' + color[2].join(', ') + ')',
backgroundImage: '-webkit-linear-gradient(top, ' + color.slice(0, 2).map(function(rgba) {
return 'rgba(' + rgba.join(', ') + ')';
}).join(', ') + ')',
WebkitTransform: 'rotate(45deg)'
})
.html(
@ -54,8 +59,8 @@ oml.ui.gridView = function() {
}), callback);
},
keys: [
'author', 'coverRatio', 'extension',
'id', 'size', 'textsize', 'title'
'author', 'coverRatio', 'extension', 'id',
'mediastate', 'size', 'textsize', 'title'
],
selected: ui.listSelection,
size: 128,

View file

@ -162,7 +162,40 @@ oml.ui.infoView = function() {
}
function renderMediaButton(data) {
return data.mediastate == 'transferring'
return data.mediastate == 'unavailable'
? Ox.FormElementGroup({
elements: [
Ox.Button({
title: Ox._('Download Book'),
width: 112
})
.bindEvent({
click: function() {
data.mediastate = 'transferring';
that.update(data, $data);
oml.api.download({id: ui.item}, function(result) {
// ...
});
}
}),
Ox.MenuButton({
items: [
{id: '', title: Ox._('Library')}
],
overlap: 'left',
title: 'list',
tooltip: Ox._('Download Book to a List'),
type: 'image'
})
.bindEvent({
click: function() {
// ...
}
})
],
float: 'right'
})
: data.mediastate == 'transferring'
? Ox.FormElementGroup({
elements: [
Ox.Button({
@ -192,29 +225,13 @@ oml.ui.infoView = function() {
],
float: 'right'
})
.css({
marginTop: '8px'
})
: Ox.Button({
title: Ox._(
data.mediastate == 'available' ? 'Read Book' : 'Download Book'
),
title: Ox._('Read Book'),
width: 128
})
.css({
marginTop: '8px'
})
.bindEvent({
click: function() {
if (data.mediastate == 'available') {
oml.UI.set({itemView: 'book'});
} else {
data.mediastate = 'transferring';
that.update(data, $data);
oml.api.download({id: ui.item}, function(result) {
// ...
});
}
oml.UI.set({itemView: 'book'});
}
});
}
@ -339,10 +356,50 @@ oml.ui.infoView = function() {
} else if ($element == $data) {
$mediaButton = renderMediaButton(data)
.appendTo($data);
$('<div>')
.addClass('OxSelectable')
.css({
marginTop: '8px',
})
.text(
[
data.extension.toUpperCase(),
Ox.formatValue(data.size, 'B')
].join(', ')
)
.appendTo($data);
['accessed', 'modified', 'added', 'created'].forEach(function(id) {
var title;
if (data[id]) {
title = Ox.getObjectById(oml.config.itemKeys, id).title;
$('<div>')
.css({
marginTop: '8px',
fontWeight: 'bold'
})
.text(title)
.appendTo($data);
Ox.EditableContent({
editable: false,
format: function(value) {
return value ? Ox.formatDate(value, '%b %e, %Y') : '';
},
placeholder: Ox._('unknown'),
value: data[id] || ''
})
.appendTo($data);
}
});
Ox.Button({
title: Ox._('Identify Book...'),
width: 128
})
.css({marginTop: '16px'})
.bindEvent({
click: function() {
identify(data);
@ -385,42 +442,6 @@ oml.ui.infoView = function() {
});
$mediaButton = renderMediaButton(data)
.appendTo($data);
$('<div>')
.addClass('OxSelectable')
.css({
marginTop: '8px',
})
.text(
[
data.extension.toUpperCase(),
Ox.formatValue(data.size, 'B')
].join(', ')
)
.appendTo($data);
['accessed', 'modified', 'added', 'created'].forEach(function(id) {
var title = Ox.getObjectById(oml.config.itemKeys, id).title;
$('<div>')
.css({
marginTop: '8px',
fontWeight: 'bold'
})
.text(title)
.appendTo($data);
Ox.EditableContent({
editable: false,
format: function(value) {
return value ? Ox.formatDate(value, '%b %e, %Y') : '';
},
placeholder: Ox._('unknown'),
value: data[id] || ''
})
.appendTo($data);
});
$('<div>').css({height: '16px'}).appendTo($data);
}

View file

@ -29,7 +29,7 @@ oml.ui.leftPanel = function() {
resize: function(data) {
ui.sidebarSize = data.size;
oml.resizeListFolders();
that.size(2, data.size);
that.size(2, oml.getInfoHeight());
if (!ui.showInfo) {
that.css({bottom: -data.size + 'px'});
}

View file

@ -63,6 +63,8 @@ oml.ui.list = function() {
}
});
oml.enableDragAndDrop(that);
return that;
};

View file

@ -46,151 +46,8 @@ oml.ui.mainMenu = function() {
}
]
},
{
id: 'listMenu',
title: Ox._('List'),
items: [
{
id: 'newlist',
title: Ox._('New List'),
keyboard: 'control n'
},
{
id: 'newlistfromselection',
title: Ox._('New List from Selection'),
keyboard: 'shift control n',
disabled: true
},
{
id: 'newsmartlist',
title: Ox._('New Smart List'),
keyboard: 'alt control n'
},
{
id: 'newsmartlistfromresults',
title: Ox._('New Smart List from Results'),
keyboard: 'shift alt control n',
disabled: true
},
{},
{
id: 'duplicatelist',
title: Ox._('Duplicate List'),
keyboard: 'control d',
disabled: true
},
{
id: 'editlist',
title: Ox._('Edit List...'),
keyboard: 'return',
disabled: true
},
{
id: 'deletelist',
title: Ox._('Delete List...'),
keyboard: 'delete',
disabled: true
}
]
},
{
id: 'editMenu',
title: Ox._('Edit'),
items: [
{
id: 'importitems',
title: Ox._('Import Books...')
},
{
id: 'exportitems',
title: Ox._('Export Books...')
},
{},
{
id: 'download',
title: Ox._('Download Items'),
disabled: true,
keyboard: 'control d'
},
{},
{
id: 'selectall',
title: Ox._('Select All'),
keyboard: 'control a'
},
{
id: 'selectnone',
title: Ox._('Select None'),
keyboard: 'shift control a'
},
{
id: 'invertselection',
title: Ox._('Invert Selection'),
keyboard: 'alt control a'
},
{},
{
id: 'cut',
title: Ox._('Cut Items'),
disabled: true,
keyboard: 'control x'
},
{
id: 'cutadd',
title: Ox._('Cut and Add to Clipboard'),
disabled: true,
keyboard: 'shift control x'
},
{
id: 'copy',
title: Ox._('Copy Items'),
disabled: true,
keyboard: 'control c'
},
{
id: 'copyadd',
title: Ox._('Copy and Add to Clipboard'),
disabled: true,
keyboard: 'shift control c'
},
{
id: 'paste',
title: Ox._('Paste Items'),
disabled: true,
keyboard: 'control v'
},
{
id: 'clearclipboard',
title: Ox._('Clear Clipboard'),
disabled: true
},
{},
{
id: 'delete',
title: Ox._('Delete Items from List'),
disabled: true,
keyboard: 'delete'
},
{},
{
id: 'undo',
title: Ox._('Undo'),
disabled: true,
keyboard: 'control z'
},
{
id: 'redo',
title: Ox._('Redo'),
disabled: true,
keyboard: 'shift control z'
},
{
id: 'clearhistory',
title: Ox._('Clear History'),
disabled: true,
}
]
},
getListMenu(),
getEditMenu(),
{
id: 'viewMenu',
title: Ox._('View'),
@ -529,6 +386,9 @@ oml.ui.mainMenu = function() {
oml.UI.set({showSidebar: !ui.showSidebar});
},
oml_find: function() {
that.replaceMenu('listMenu', getListMenu());
that.replaceMenu('editMenu', getEditMenu());
/*
var action = Ox.startsWith(ui._list, ':') && ui._list != ':'
? 'enableItem' : 'disableItem';
that[
@ -537,6 +397,7 @@ oml.ui.mainMenu = function() {
]('duplicatelist');
that[action]('editlist');
that[action]('deletelist');
*/
},
oml_item: function(data) {
if (!!data.value != !!data.previousValue) {
@ -544,6 +405,9 @@ oml.ui.mainMenu = function() {
that[data.value ? 'enableItem' : 'disableItem']('showbrowser');
}
},
oml_listselection: function(data) {
that.replaceMenu('editMenu', getEditMenu());
},
oml_showbrowser: function(data) {
that.setItemTitle('showbrowser', Ox._((data.value ? 'Hide' : 'Show') + ' Browser'));
},
@ -559,10 +423,206 @@ oml.ui.mainMenu = function() {
},
});
function getItemMenu() {
// ...
function getEditMenu() {
var listData = oml.getListData(),
username = oml.user.preferences.username,
selectionItems = ui.listSelection.length,
selectionItemName = (
selectionItems > 1 ? Ox.formatNumber(selectionItems) + ' ' : ''
) + Ox._(clipboardItems == 1 ? 'Book' : 'Books'),
clipboardItems = oml.clipboard.items(),
clipboardType = oml.clipboard.type(),
clipboardItemName = !clipboardItems ? ''
: (
clipboardItems > 1 ? Ox.formatNumber(clipboardItems) + ' ' : ''
) + Ox._(clipboardItems == 1 ? 'Book' : 'Books'),
canSelect = !ui.item,
canCopy = canSelect && selectionItems,
canCut = canCopy && listData.editable,
canPaste = listData.editable && clipboardItems,
canAdd = canCopy && clipboardItems && clipboardItemType == ui.section,
canDownload = listData.user != username && selectionItems,
historyItems = oml.history.items(),
undoText = oml.history.undoText(),
redoText = oml.history.redoText();
return {
id: 'editMenu',
title: Ox._('Edit'),
items: [
{
id: 'importitems',
title: Ox._('Import Books...')
},
{
id: 'exportitems',
title: Ox._('Export Books...')
},
{},
{
id: 'download',
title: Ox._('Download {0}', [selectionItemName]),
disabled: !canDownload,
keyboard: 'control d'
},
{},
{
id: 'selectall',
title: Ox._('Select All'),
disabled: !canSelect,
keyboard: 'control a'
},
{
id: 'selectnone',
title: Ox._('Select None'),
disabled: !canSelect,
keyboard: 'shift control a'
},
{
id: 'invertselection',
title: Ox._('Invert Selection'),
disabled: !canSelect,
keyboard: 'alt control a'
},
{},
{
id: 'cut',
title: Ox._('Cut {0}', [selectionItemName]),
disabled: !canCut,
keyboard: 'control x'
},
{
id: 'cutadd',
title: Ox._('Cut and Add to Clipboard'),
disabled: !canCut || !canAdd,
keyboard: 'shift control x'
},
{
id: 'copy',
title: Ox._('Copy {0}', [selectionItemName]),
disabled: !canCopy,
keyboard: 'control c'
},
{
id: 'copyadd',
title: Ox._('Copy and Add to Clipboard'),
disabled: !canCopy || !canAdd,
keyboard: 'shift control c'
},
{
id: 'paste',
title: !clipboardItems ? Ox._('Paste') : Ox._('Paste {0}', [clipboardItemName]),
disabled: !canPaste,
keyboard: 'control v'
},
{
id: 'clearclipboard',
title: Ox._('Clear Clipboard'),
disabled: !clipboardItems
},
{},
{
id: 'delete',
title: Ox._('Delete {0} from List', [selectionItemName]),
disabled: !canCut,
keyboard: 'delete'
},
{},
{
id: 'undo',
title: undoText ? Ox._('Undo {0}', [undoText]) : Ox._('Undo'),
disabled: !undoText,
keyboard: 'control z'
},
{
id: 'redo',
title: redoText ? Ox._('Redo {0}', [redoText]) : Ox._('Redo'),
disabled: !redoText,
keyboard: 'shift control z'
},
{
id: 'clearhistory',
title: Ox._('Clear History'),
disabled: !historyItems,
}
]
};
}
function getListMenu() {
var isLibraries = !ui._list,
isLibrary = Ox.endsWith(ui._list, ':'),
isList = !isLibraries && !isLibrary,
isOwnList = ui._list[0] == ':';
return {
id: 'listMenu',
title: Ox._('List'),
items: [
{
id: 'libraries',
title: Ox._('All Libraries'),
keyboard: 'shift control w'
},
{
id: 'library',
title: Ox._('This Library'),
disabled: isLibraries,
keyboard: isLibrary ? 'control w' : ''
},
{
id: 'list',
title: Ox._('This List'),
disabled: isLibrary,
keyboard: isLibrary ? '' : 'control w'
},
{},
{
id: 'newlist',
title: Ox._('New List'),
keyboard: 'control n'
},
{
id: 'newlistfromselection',
title: Ox._('New List from Selection'),
keyboard: 'shift control n',
disabled: !ui.listSelection.length
},
{
id: 'newsmartlist',
title: Ox._('New Smart List'),
keyboard: 'alt control n'
},
{
id: 'newsmartlistfromresults',
title: Ox._('New Smart List from Results'),
keyboard: 'shift alt control n'
},
{},
{
id: 'duplicatelist',
title: Ox._('Duplicate List'),
disabled: !isList
},
{
id: 'editlist',
title: Ox._('Edit List...'),
keyboard: 'return',
disabled: !isOwnList
},
{
id: 'deletelist',
title: Ox._('Delete List...'),
keyboard: 'delete',
disabled: !isOwnList
}
]
};
}
that.update = function() {
return that.updateMenu('listMenu', getListMenu())
.updateMenu('editMenu', getEditMenu());
};
return that;
};

View file

@ -453,12 +453,12 @@ oml.ui.usersDialog = function() {
})
.bindEvent({
select: function(data) {
$lists.forEach(function($element) {
if ($element != $list) {
$element.options({selected: []});
}
});
renderUser(Ox.getObjectById(users, data.ids[0]));
if (data.ids.length) {
selectItem($list);
renderUser(Ox.getObjectById(users, data.ids[0]));
} else {
renderUser();
}
}
});

View file

@ -1,3 +1,564 @@
oml.addList = function() {
// addList(isSmart, isFrom) or addList(list) [=dupicate]
var args = arguments,
isDuplicate = args.length == 1,
isSmart, isFrom, list, listData, data;
oml.api.getLists(function(result) {
var lists = result.data.lists,
listNames = lists[oml.user.id].map(function(list) {
return list.name;
}),
query;
if (!isDuplicate) {
isSmart = args[0];
isFrom = args[1];
data = {
name: oml.validateName(Ox._('Untitled'), listNames),
type: !isSmart ? 'static' : 'smart'
};
if (isFrom) {
if (!isSmart) {
data.items = ui.listSelection;
} else {
data.query = ui.find;
}
}
addList();
} else {
list = args[0];
listData = Ox.getObjectById(Ox.flatten(Ox.values(lists)), list);
data = Ox.extend({
name: oml.validateName(listData.name, listNames),
type: listData.type
}, listData.query ? {
query: listData.query
} : {});
if (!data.query) {
var query = {
conditions: [{key: 'list', operator: '==', value: list}],
operator: '&'
};
oml.api.find({query: query}, function(result) {
if (result.data.items) {
oml.api.find({
query: query,
keys: ['id'],
sort: [{key: 'id', operator: '+'}],
range: [0, result.data.items]
}, function(result) {
data.items = result.data.items.map(function(item) {
return item.id;
});
addList();
});
} else {
addList();
}
});
} else {
addList();
}
}
});
function addList() {
oml.api.addList(data, function(result) {
var list = result.data.id,
$folderList = oml.$ui.folderList[0];
oml.$ui.folder[0].options({collapsed: false}); // FIXME: SET UI!
// FIXME: DOESN'T WORK
$folderList
.bindEventOnce({
load: function() {
$folderList
.gainFocus()
.options({selected: [list]});
oml.UI.set({
find: {
conditions: [{
key: 'list',
operator: '==',
value: list
}],
operator: '&'
}
});
oml.$ui.listDialog = oml.ui.listDialog().open();
}
});
oml.updateLists();
});
}
};
oml.clearFilters = function() {
var ui = oml.user.ui,
find = Ox.clone(ui.find, true),
indices = ui._filterState.map(function(filterState) {
return filterState.index;
}).filter(function(index) {
return index > -1;
});
find.conditions = find.conditions.filter(function(condition, index) {
return !Ox.contains(indices, index);
});
oml.UI.set({find: find});
};
oml.deleteList = function() {
var ui = oml.user.ui;
oml.ui.confirmDialog({
buttons: [
Ox.Button({
title: Ox._('No, Keep List')
}),
Ox.Button({
title: Ox._('Yes, Delete List')
})
],
content: Ox._('Are you sure you want to delete this list?'),
title: Ox._('Delete List')
}, function() {
oml.api.removeList({
id: ui._list
}, function() {
oml.UI.set({
find: {
conditions: [{
key: 'list',
operator: '==',
value: ':'
}],
operator: '&'
}
});
oml.updateLists();
});
});
};
(function() {
oml.doHistory = function(action, items, targets, callback) {
items = Ox.makeArray(items);
targets = Ox.makeArray(targets);
if (action == 'copy' || action == 'paste') {
addItems(items, targets[0], addToHistory);
} else if (action == 'cut' || action == 'delete') {
removeItems(items, targets[0], addToHistory);
} else if (action == 'move') {
removeItems(items, targets[0], function() {
addItems(items, targets[1], addToHistory);
});
}
function addToHistory(result, addedItems) {
var actions = {
copy: 'Copying',
cut: 'Cutting',
'delete': 'Deleting',
move: 'Moving',
paste: 'Pasting'
},
length = items.length,
text = Ox._(actions[action]) + ' ' + (
length == 1 ? 'Book' : 'Books'
);
oml.history.add({
action: action,
items: action == 'cut' || action == 'delete' ? [items]
: action == 'copy' || action == 'paste' ? [addedItems]
: [items, addedItems], // move
positions: [],
targets: targets,
text: text
});
callback(result);
}
};
oml.redoHistory = function(callback) {
var object = oml.history.redo();
if (object) {
if (object.action == 'copy' || object.action == 'paste') {
addItems(object.items[0], object.targets[0], done);
} else if (object.action == 'cut' || object.action == 'delete') {
removeItems(object.items[0], object.targets[0], done);
} else if (object.action == 'move') {
removeItems(object.items[0], object.targets[0], function() {
addItems(object.items[1], object.targets[1], done);
});
}
}
function done() {
doneHistory(object, callback);
}
};
oml.undoHistory = function(callback) {
var object = oml.history.undo();
if (object) {
if (object.action == 'copy' || object.action == 'paste') {
removeItems(object.items[0], object.targets[0], done);
} else if (object.action == 'cut' || object.action == 'delete') {
addItems(object.items[0], object.targets[0], done);
} else if (object.action == 'move') {
removeItems(object.items[1], object.targets[1], function() {
addItems(object.items[0], object.targets[0], done);
});
}
}
function done() {
doneHistory(object, callback);
}
};
function addItems(items, target, callback) {
oml.api.find({
query: {
conditions: [{
key: 'list',
operator: '==',
value: target
}],
operator: '&'
},
positions: items
}, function(result) {
var existingItems = Object.keys(result.data.positions),
addedItems = items.filter(function(item) {
return !Ox.contains(existingItems, item);
});
if (addedItems.length) {
oml.api.addListItems({
items: addedItems,
list: target
}, function(result) {
callback(result, addedItems);
});
} else {
callback(null, []);
}
});
}
function doneHistory(object, callback) {
var list, listData, ui = oml.user.ui;
Ox.Request.clearCache('find');
object.targets.filter(function(list) {
return list != ui._list;
}).forEach(function(list) {
listData = oml.getListData(list);
oml.api.find({
query: {
conditions: [{
key: 'list',
operator: '==',
value: list
}],
operator: '&'
}
}, function(result) {
oml.$ui.folderList[listData.folder].value(
list, 'items', result.data.items
);
});
});
if (Ox.contains(object.targets, ui._list)) {
// FIXME: Why is this timeout needed?
setTimeout(oml.reloadList, 250);
}
callback && callback();
}
function removeItems(items, target, callback) {
oml.api.removeListItems({
items: items,
list: target
}, callback);
}
}());
oml.enableDragAndDrop = function($list, canMove) {
var $tooltip = Ox.Tooltip({
animate: false
}),
drag = {},
scrollInterval,
ui = oml.user.ui,
username = oml.user.preferences.username;
$list.bindEvent({
draganddropstart: function(data) {
Ox.print('DND START', data);
var $lists = oml.$ui.libraryList.concat(oml.$ui.folderList);
drag.action = 'copy';
drag.ids = $list.options('selected');
drag.item = drag.ids.length == 1
? $list.value(drag.ids[0], 'title')
: drag.ids.length;
drag.source = oml.getListData();
drag.targets = {};
$lists.forEach(function($list) {
$list.addClass('OxDroppable').find('.OxItem').each(function() {
var $item = $(this),
id = $item.data('id'),
data = oml.getListData(id);
drag.targets[id] = Ox.extend(data, {
editable: data.editable || (
data.type == 'library'
&& drag.source.user != username
&& data.user == 'username'
),
selected: data.id == ui._list
}, data);
if (!drag.targets[id].selected && drag.targets[id].editable) {
$item.addClass('OxDroppable');
}
});
});
$tooltip.options({title: getTitle()}).show(data.event);
Ox.UI.$window.on({
keydown: keydown,
keyup: keyup
});
},
draganddrop: function(data) {
var event = data.event;
$tooltip.options({
title: getTitle(event)
}).show(event);
if (scrollInterval && !isAtListsTop(event) && !isAtListsBottom(event)) {
clearInterval(scrollInterval);
scrollInterval = 0;
}
},
draganddroppause: function(data) {
var event = data.event, scroll, title,
ui = oml.user.ui,
$parent, $grandparent, $panel;
if (!ui.showSidebar) {
if (event.clientX < 16 && event.clientY >= 44
&& event.clientY < window.innerHeight - 16
) {
oml.$ui.mainPanel.toggle(0);
}
} else {
$parent = $(event.target).parent();
$grandparent = $parent.parent();
$panel = $parent.is('.OxCollapsePanel') ? $parent
: $grandparent.is('.OxCollapsePanel') ? $grandparent
: null;
if ($panel) {
title = $panel.children('.OxBar').children('.OxTitle')
.html().split(' ')[0].toLowerCase();
if (!ui.showFolder[title]) {
Ox.UI.elements[$panel.data('oxid')].options({
collapsed: false
});
}
}
if (!scrollInterval) {
scroll = isAtListsTop(event) ? -16
: isAtListsBottom(event) ? 16
: 0
if (scroll) {
scrollInterval = setInterval(function() {
oml.$ui.folders.scrollTop(
oml.$ui.folders.scrollTop() + scroll
);
}, 100);
}
}
}
},
draganddropenter: function(data) {
var $parent = $(data.event.target).parent(),
$item = $parent.is('.OxItem') ? $parent : $parent.parent(),
$list = $item.parent().parent().parent().parent();
if ($list.is('.OxDroppable')) {
$item.addClass('OxDrop');
drag.target = drag.targets[$item.data('id')];
} else {
drag.target = null;
}
},
draganddropleave: function(data) {
var $parent = $(data.event.target).parent(),
$item = $parent.is('.OxItem') ? $parent : $parent.parent();
if ($item.is('.OxDroppable')) {
$item.removeClass('OxDrop');
drag.target = null;
}
},
draganddropend: function(data) {
var targets;
Ox.UI.$window.off({
keydown: keydown,
keyup: keyup
});
if (
drag.target && drag.target.editable && !drag.target.selected
&& (drag.action == 'copy' || drag.source.editable)
) {
var targets = drag.action == 'copy' ? drag.target.id
: [oml.user.ui._list, drag.target.id];
oml.doHistory(drag.action, data.ids, targets, function() {
Ox.Request.clearCache('find');
oml.api.find({
query: {
conditions: [{
key: 'list',
operator: '==',
value: drag.target.id
}],
operator: '&'
}
}, function(result) {
oml.$ui.folderList[drag.target.folder].value(
drag.target.id, 'items', result.data.items
);
cleanup(250);
});
drag.action == 'move' && oml.reloadList();
});
} else {
cleanup()
}
}
});
function cleanup(ms) {
ms = ms || 0;
drag = {};
clearInterval(scrollInterval);
scrollInterval = 0;
setTimeout(function() {
$('.OxDroppable').removeClass('OxDroppable');
$('.OxDrop').removeClass('OxDrop');
$tooltip.hide();
}, ms);
}
function getTitle() {
var image, text,
actionText = drag.action == 'copy' ? (
drag.source.user == username ? 'copy' : 'download'
) : 'move',
itemText = Ox.isString(drag.item)
? '"' + Ox.encodeHTMLEntities(Ox.truncate(drag.item, 32)) + '"'
: Ox._('{0} books', [drag.item]),
targetText;
if (drag.action == 'move') {
if (drag.source.user != username) {
text = Ox._('You can only remove books<br>from your own lists.');
} else if (drag.source.type == 'library') {
text = Ox._('You cannot move books<br>out of your library.');
} else if (drag.source.type == 'smart') {
text = Ox._('You cannot move books<br>out of a smart list.');
}
} else if (drag.target) {
targetText = drag.target.type == 'libraries' ? Ox._('a library')
: drag.target.type == 'library' ? Ox._('your library')
: Ox._('the list "{0}"', [Ox.encodeHTMLEntities(Ox.truncate(drag.target.name, 32))]);
if (
(
drag.target.type == 'library'
&& drag.source.user == username
&& drag.target.user == username
)
|| drag.target.selected
) {
text = Ox._('{0}<br>is already in {1}.', [
Ox._(itemText[0] == '"' ? '' : 'These ') + itemText,
targetText
]);
} else if (drag.target.user != username) {
text = Ox._(
'You can only {0} books<br>to your own {1}.',
[actionText, drag.target.type == 'library' ? 'library' : 'lists']
);
} else if (drag.target.type == 'smart') {
text = Ox._('You cannot {0} books<br>to a smart list.', [actionText]);
}
}
if (text) {
image = 'symbolClose'
} else {
image = drag.action == 'copy' ? (
drag.source.user == username ? 'symbolAdd' : 'symbolDownload'
) : 'symbolRemove',
text = Ox._(Ox.toTitleCase(actionText)) + ' ' + (
Ox.isString(drag.item)
? '"' + Ox.encodeHTMLEntities(Ox.truncate(drag.item, 32)) + '"'
: drag.item + ' ' + 'books'
) + '<br>' + (
drag.target && drag.target.editable && !drag.target.selected
? Ox._('to {0}.', [targetText])
: drag.source.user == username
? Ox._('to {0} list.', [ui._list == ':' ? 'a' : 'another'])
: Ox._('to your library or to one of your lists.')
);
}
return $('<div>')
.append(
$('<div>')
.css({
float: 'left',
width: '16px',
height: '16px',
padding: '1px',
border: '3px solid rgb(' + Ox.Theme.getThemeData().symbolDefaultColor.join(', ') + ')',
borderRadius: '12px',
margin: '3px 2px 2px 2px'
})
.append(
$('<img>')
.attr({src: Ox.UI.getImageURL(image)})
.css({width: '16px', height: '16px'})
)
)
.append(
$('<div>')
.css({
float: 'left',
margin: '1px 2px 2px 2px',
fontSize: '11px',
whiteSpace: 'nowrap'
})
.html(text)
);
}
function isAtListsTop(e) {
return ui.showSidebar
&& e.clientX < ui.sidebarSize
&& e.clientY >= 44 && e.clientY < 60;
}
function isAtListsBottom(e) {
var listsBottom = window.innerHeight - oml.getInfoHeight();
return ui.showSidebar
&& e.clientX < ui.sidebarSize
&& e.clientY >= listsBottom - 16 && e.clientY < listsBottom;
}
function keydown(e) {
if (e.metaKey) {
drag.action = 'move';
$tooltip.options({title: getTitle()}).show();
}
}
function keyup(e) {
if (drag.action == 'move') {
drag.action = 'copy';
$tooltip.options({title: getTitle()}).show();
}
}
};
(function() {
// Note: getFindState has to run after getListState and getFilterState
@ -140,146 +701,64 @@
}());
oml.addList = function() {
// addList(isSmart, isFrom) or addList(list) [=dupicate]
var args = arguments,
isDuplicate = args.length == 1,
isSmart, isFrom, list, listData, data;
oml.api.getLists(function(result) {
var lists = result.data.lists,
listNames = lists[oml.user.id].map(function(list) {
return list.name;
}),
query;
if (!isDuplicate) {
isSmart = args[0];
isFrom = args[1];
data = {
name: oml.validateName(Ox._('Untitled'), listNames),
type: !isSmart ? 'static' : 'smart'
};
if (isFrom) {
if (!isSmart) {
data.items = ui.listSelection;
} else {
data.query = ui.find;
}
}
addList();
} else {
list = args[0];
listData = Ox.getObjectById(Ox.flatten(Ox.values(lists)), list);
Ox.print('LISTDATA,', listData)
data = Ox.extend({
name: oml.validateName(listData.name, listNames),
type: listData.type
}, listData.query ? {
query: listData.query
} : {});
if (!data.query) {
var query = {
conditions: [{key: 'list', operator: '==', value: list}],
operator: '&'
};
oml.api.find({query: query}, function(result) {
if (result.data.items) {
oml.api.find({
query: query,
keys: ['id'],
sort: [{key: 'id', operator: '+'}],
range: [0, result.data.items]
}, function(result) {
data.items = result.data.items.map(function(item) {
return item.id;
});
addList();
});
} else {
addList();
}
});
} else {
addList();
}
}
});
function addList() {
Ox.print('DATA, ', data);
oml.api.addList(data, function(result) {
Ox.print('LIST ADDED', result.data);
var list = result.data.id,
$folderList = oml.$ui.folderList[0];
oml.$ui.folder[0].options({collapsed: false}); // FIXME: SET UI!
// FIXME: DOESN'T WORK
$folderList
.bindEventOnce({
load: function() {
$folderList
.gainFocus()
.options({selected: [list]});
oml.UI.set({
find: {
conditions: [{
key: 'list',
operator: '==',
value: list
}],
operator: '&'
}
});
oml.$ui.listDialog = oml.ui.listDialog().open();
}
});
oml.updateLists();
});
}
oml.getFileInfoColor = function(type, data) {
return type == 'extension' ? (
data.extension == 'epub' ? [[32, 160, 32], [0, 128, 0], [128, 255, 128]]
: data.extension == 'pdf' ? (
data.textsize
? [[224, 32, 32], [192, 0, 0], [255, 192, 192]]
: [[224, 128, 32], [192, 96, 0], [255, 192, 128]]
)
: data.extension == 'txt' ? [[255, 255, 255], [224, 224, 224], [0, 0, 0]]
: [[96, 96, 96], [64, 64, 64], [192, 192, 192]]
) : data.mediastate == 'available' ? [[32, 160, 32], [0, 128, 0], [128, 255, 128]]
: data.mediastate == 'transferring' ? [[160, 160, 32], [128, 128, 0], [255, 255, 128]]
: [[224, 32, 32], [192, 0, 0], [255, 192, 192]];
};
oml.clearFilters = function() {
var ui = oml.user.ui,
find = Ox.clone(ui.find, true),
indices = ui._filterState.map(function(filterState) {
return filterState.index;
}).filter(function(index) {
return index > -1;
});
find.conditions = find.conditions.filter(function(condition, index) {
return !Ox.contains(indices, index);
});
oml.UI.set({find: find});
};
oml.deleteList = function() {
oml.getFilterSizes = function() {
var ui = oml.user.ui;
oml.ui.confirmDialog({
buttons: [
Ox.Button({
title: Ox._('No, Keep List')
}),
Ox.Button({
title: Ox._('Yes, Delete List')
})
],
content: Ox._('Are you sure you want to delete this list?'),
title: Ox._('Delete List')
}, function() {
oml.api.removeList({
id: ui._list
}, function() {
oml.UI.set({
find: {
conditions: [{
key: 'list',
operator: '==',
value: ':'
}],
operator: '&'
}
});
oml.updateLists();
});
});
}
return Ox.splitInt(
window.innerWidth - ui.showSidebar * ui.sidebarSize - 1,
5
);
};
oml.getInfoHeight = function() {
return Math.min(
oml.user.ui.sidebarSize,
window.innerHeight - 20 - 24 - 16 - 1
);
};
oml.getListData = function(list) {
var data = {}, ui = oml.user.ui;
if (Ox.isUndefined(list)) {
list = ui._list;
}
if (ui._lists) {
data = ui._lists[list];
}
return data;
};
oml.getListFoldersHeight = function() {
var ui = oml.user.ui;
return Object.keys(ui.showFolder).reduce(function(value, id, index) {
var items = oml.$ui.folderList[index].options('items').length;
return value + 16 + ui.showFolder[id] * (1 + items) * 16;
}, 16);
};
oml.getListFoldersWidth = function() {
var ui = oml.user.ui;
return ui.sidebarSize - (
oml.$ui.appPanel
&& oml.getListFoldersHeight()
> window.innerHeight - 20 - 24 - 1 - ui.showInfo * oml.getInfoHeight()
? Ox.UI.SCROLLBAR_SIZE : 0
);
};
oml.getPageTitle = function(stateOrURL) {
var page = Ox.getObjectById(
@ -298,44 +777,47 @@ oml.getSortOperator = function(key) {
) ? '+' : '-';
};
oml.getFileTypeColor = function(data) {
return data.extension == 'epub' ? [[0, 128, 0], [128, 255, 128]]
: data.extension == 'pdf' ? (
data.textsize ? [[192, 0, 0], [255, 192, 192]]
: [[192, 96, 0], [255, 192, 128]]
)
: data.extension == 'txt' ? [[255, 255, 255], [0, 0, 0]]
: [[64, 64, 64], [192, 192, 192]];
};
oml.getFilterSizes = function() {
var ui = oml.user.ui;
return Ox.splitInt(
window.innerWidth - ui.showSidebar * ui.sidebarSize - 1,
5
);
};
oml.getListFoldersHeight = function() {
var ui = oml.user.ui;
return Object.keys(ui.showFolder).reduce(function(value, id, index) {
var items = oml.$ui.folderList[index].options('items').length;
Ox.print('REDUCE', value, id, index, '...', items)
return value + 16 + ui.showFolder[id] * (1 + items) * 16;
}, 16);
};
oml.getListFoldersWidth = function() {
var ui = oml.user.ui;
Ox.print('HEIGHT::::', oml.getListFoldersHeight(), 'SCROLLBAR????', oml.$ui.appPanel
&& oml.getListFoldersHeight()
> window.innerHeight - 20 - 24 - 1 - ui.showInfo * ui.sidebarSize)
return ui.sidebarSize - (
oml.$ui.appPanel
&& oml.getListFoldersHeight()
> window.innerHeight - 20 - 24 - 1 - ui.showInfo * ui.sidebarSize
? Ox.UI.SCROLLBAR_SIZE : 0
);
oml.getUsersAndLists = function(callback) {
var lists = [{
id: '',
name: Ox._('All Libraries'),
type: 'libraries'
}],
ui = oml.user.ui,
username = oml.user.preferences.username,
users = [{
id: oml.user.id,
nickname: username,
online: oml.user.online
}];
oml.api.getUsers(function(result) {
users = users.concat(
result.data.users.filter(function(user) {
return user.peered;
})
);
users.forEach(function(user) {
lists.push({
id: (user.nickname == username ? '' : user.nickname) + ':',
name: Ox._('Library'),
type: 'library',
user: user.nickname
});
});
oml.api.getLists(function(result) {
lists = lists.concat(result.data.lists);
if (!ui.lists) {
oml.$ui.mainMenu.update();
}
ui._lists = {};
Ox.forEach(lists, function(list) {
ui._lists[list.id] = Ox.extend(list, {
editable: list.user == username && list.type == 'static',
});
});
callback(users, lists);
});
})
};
oml.hasDialogOrScreen = function() {
@ -344,6 +826,11 @@ oml.hasDialogOrScreen = function() {
|| !!$('.OxScreen').length;
};
oml.reloadList = function() {
Ox.print('RELOAD LIST NOT IMPLEMENTED')
// ...
};
oml.resizeFilters = function() {
// ...
};