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

View File

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

View File

@ -233,6 +233,12 @@ class Item(db.Model):
else: else:
f.value = '%s:' % p.id f.value = '%s:' % p.id
db.session.add(f) 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): def update(self):
users = map(str, list(self.users)) 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"/get/(.*)", ShareHandler, dict(app=app)),
(r".*", NodeHandler, 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']) 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, { state.online = directory.put(settings.sk, {
'host': host, 'host': host,
'port': settings.server['node_port'] '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 socket
import requests
from urlparse import urlparse
def get_public_ipv6(): def get_public_ipv6():
host = ('2a01:4f8:120:3201::3', 25519) host = ('2a01:4f8:120:3201::3', 25519)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -162,7 +162,40 @@ oml.ui.infoView = function() {
} }
function renderMediaButton(data) { 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({ ? Ox.FormElementGroup({
elements: [ elements: [
Ox.Button({ Ox.Button({
@ -192,29 +225,13 @@ oml.ui.infoView = function() {
], ],
float: 'right' float: 'right'
}) })
.css({
marginTop: '8px'
})
: Ox.Button({ : Ox.Button({
title: Ox._( title: Ox._('Read Book'),
data.mediastate == 'available' ? 'Read Book' : 'Download Book'
),
width: 128 width: 128
}) })
.css({
marginTop: '8px'
})
.bindEvent({ .bindEvent({
click: function() { click: function() {
if (data.mediastate == 'available') { oml.UI.set({itemView: 'book'});
oml.UI.set({itemView: 'book'});
} else {
data.mediastate = 'transferring';
that.update(data, $data);
oml.api.download({id: ui.item}, function(result) {
// ...
});
}
} }
}); });
} }
@ -339,10 +356,50 @@ oml.ui.infoView = function() {
} else if ($element == $data) { } 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({ Ox.Button({
title: Ox._('Identify Book...'), title: Ox._('Identify Book...'),
width: 128 width: 128
}) })
.css({marginTop: '16px'})
.bindEvent({ .bindEvent({
click: function() { click: function() {
identify(data); 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); $('<div>').css({height: '16px'}).appendTo($data);
} }

View File

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

View File

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

View File

@ -46,151 +46,8 @@ oml.ui.mainMenu = function() {
} }
] ]
}, },
{ getListMenu(),
id: 'listMenu', getEditMenu(),
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,
}
]
},
{ {
id: 'viewMenu', id: 'viewMenu',
title: Ox._('View'), title: Ox._('View'),
@ -529,6 +386,9 @@ oml.ui.mainMenu = function() {
oml.UI.set({showSidebar: !ui.showSidebar}); oml.UI.set({showSidebar: !ui.showSidebar});
}, },
oml_find: function() { oml_find: function() {
that.replaceMenu('listMenu', getListMenu());
that.replaceMenu('editMenu', getEditMenu());
/*
var action = Ox.startsWith(ui._list, ':') && ui._list != ':' var action = Ox.startsWith(ui._list, ':') && ui._list != ':'
? 'enableItem' : 'disableItem'; ? 'enableItem' : 'disableItem';
that[ that[
@ -537,6 +397,7 @@ oml.ui.mainMenu = function() {
]('duplicatelist'); ]('duplicatelist');
that[action]('editlist'); that[action]('editlist');
that[action]('deletelist'); that[action]('deletelist');
*/
}, },
oml_item: function(data) { oml_item: function(data) {
if (!!data.value != !!data.previousValue) { if (!!data.value != !!data.previousValue) {
@ -544,6 +405,9 @@ oml.ui.mainMenu = function() {
that[data.value ? 'enableItem' : 'disableItem']('showbrowser'); that[data.value ? 'enableItem' : 'disableItem']('showbrowser');
} }
}, },
oml_listselection: function(data) {
that.replaceMenu('editMenu', getEditMenu());
},
oml_showbrowser: function(data) { oml_showbrowser: function(data) {
that.setItemTitle('showbrowser', Ox._((data.value ? 'Hide' : 'Show') + ' Browser')); 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; return that;
}; };

View File

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

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() { (function() {
// Note: getFindState has to run after getListState and getFilterState // Note: getFindState has to run after getListState and getFilterState
@ -140,146 +701,64 @@
}()); }());
oml.addList = function() { oml.getFileInfoColor = function(type, data) {
// addList(isSmart, isFrom) or addList(list) [=dupicate] return type == 'extension' ? (
var args = arguments, data.extension == 'epub' ? [[32, 160, 32], [0, 128, 0], [128, 255, 128]]
isDuplicate = args.length == 1, : data.extension == 'pdf' ? (
isSmart, isFrom, list, listData, data; data.textsize
oml.api.getLists(function(result) { ? [[224, 32, 32], [192, 0, 0], [255, 192, 192]]
var lists = result.data.lists, : [[224, 128, 32], [192, 96, 0], [255, 192, 128]]
listNames = lists[oml.user.id].map(function(list) { )
return list.name; : data.extension == 'txt' ? [[255, 255, 255], [224, 224, 224], [0, 0, 0]]
}), : [[96, 96, 96], [64, 64, 64], [192, 192, 192]]
query; ) : data.mediastate == 'available' ? [[32, 160, 32], [0, 128, 0], [128, 255, 128]]
if (!isDuplicate) { : data.mediastate == 'transferring' ? [[160, 160, 32], [128, 128, 0], [255, 255, 128]]
isSmart = args[0]; : [[224, 32, 32], [192, 0, 0], [255, 192, 192]];
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.clearFilters = function() { oml.getFilterSizes = 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; var ui = oml.user.ui;
oml.ui.confirmDialog({ return Ox.splitInt(
buttons: [ window.innerWidth - ui.showSidebar * ui.sidebarSize - 1,
Ox.Button({ 5
title: Ox._('No, Keep List') );
}), };
Ox.Button({
title: Ox._('Yes, Delete List') oml.getInfoHeight = function() {
}) return Math.min(
], oml.user.ui.sidebarSize,
content: Ox._('Are you sure you want to delete this list?'), window.innerHeight - 20 - 24 - 16 - 1
title: Ox._('Delete List') );
}, function() { };
oml.api.removeList({
id: ui._list oml.getListData = function(list) {
}, function() { var data = {}, ui = oml.user.ui;
oml.UI.set({ if (Ox.isUndefined(list)) {
find: { list = ui._list;
conditions: [{ }
key: 'list', if (ui._lists) {
operator: '==', data = ui._lists[list];
value: ':' }
}], return data;
operator: '&' };
}
}); oml.getListFoldersHeight = function() {
oml.updateLists(); 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) { oml.getPageTitle = function(stateOrURL) {
var page = Ox.getObjectById( var page = Ox.getObjectById(
@ -298,44 +777,47 @@ oml.getSortOperator = function(key) {
) ? '+' : '-'; ) ? '+' : '-';
}; };
oml.getFileTypeColor = function(data) { oml.getUsersAndLists = function(callback) {
return data.extension == 'epub' ? [[0, 128, 0], [128, 255, 128]] var lists = [{
: data.extension == 'pdf' ? ( id: '',
data.textsize ? [[192, 0, 0], [255, 192, 192]] name: Ox._('All Libraries'),
: [[192, 96, 0], [255, 192, 128]] type: 'libraries'
) }],
: data.extension == 'txt' ? [[255, 255, 255], [0, 0, 0]] ui = oml.user.ui,
: [[64, 64, 64], [192, 192, 192]]; username = oml.user.preferences.username,
}; users = [{
id: oml.user.id,
oml.getFilterSizes = function() { nickname: username,
var ui = oml.user.ui; online: oml.user.online
return Ox.splitInt( }];
window.innerWidth - ui.showSidebar * ui.sidebarSize - 1, oml.api.getUsers(function(result) {
5 users = users.concat(
); result.data.users.filter(function(user) {
}; return user.peered;
})
oml.getListFoldersHeight = function() { );
var ui = oml.user.ui; users.forEach(function(user) {
return Object.keys(ui.showFolder).reduce(function(value, id, index) { lists.push({
var items = oml.$ui.folderList[index].options('items').length; id: (user.nickname == username ? '' : user.nickname) + ':',
Ox.print('REDUCE', value, id, index, '...', items) name: Ox._('Library'),
return value + 16 + ui.showFolder[id] * (1 + items) * 16; type: 'library',
}, 16); user: user.nickname
}; });
});
oml.getListFoldersWidth = function() { oml.api.getLists(function(result) {
var ui = oml.user.ui; lists = lists.concat(result.data.lists);
Ox.print('HEIGHT::::', oml.getListFoldersHeight(), 'SCROLLBAR????', oml.$ui.appPanel if (!ui.lists) {
&& oml.getListFoldersHeight() oml.$ui.mainMenu.update();
> window.innerHeight - 20 - 24 - 1 - ui.showInfo * ui.sidebarSize) }
return ui.sidebarSize - ( ui._lists = {};
oml.$ui.appPanel Ox.forEach(lists, function(list) {
&& oml.getListFoldersHeight() ui._lists[list.id] = Ox.extend(list, {
> window.innerHeight - 20 - 24 - 1 - ui.showInfo * ui.sidebarSize editable: list.user == username && list.type == 'static',
? Ox.UI.SCROLLBAR_SIZE : 0 });
); });
callback(users, lists);
});
})
}; };
oml.hasDialogOrScreen = function() { oml.hasDialogOrScreen = function() {
@ -344,6 +826,11 @@ oml.hasDialogOrScreen = function() {
|| !!$('.OxScreen').length; || !!$('.OxScreen').length;
}; };
oml.reloadList = function() {
Ox.print('RELOAD LIST NOT IMPLEMENTED')
// ...
};
oml.resizeFilters = function() { oml.resizeFilters = function() {
// ... // ...
}; };