From 4948f0fa39d5b7a30f8e31d39abae088af8a9005 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Tue, 11 Oct 2011 13:29:05 +0200 Subject: [PATCH] manage names and titles --- pandora/event/models.py | 9 +- pandora/item/models.py | 6 +- pandora/person/managers.py | 143 +++++++++++++++ pandora/person/models.py | 24 ++- pandora/person/views.py | 155 ++++++++++++++++- pandora/settings.py | 1 + pandora/title/__init__.py | 0 pandora/title/managers.py | 143 +++++++++++++++ pandora/title/models.py | 63 +++++++ pandora/title/views.py | 154 +++++++++++++++++ static/js/pandora/ui/menu.js | 4 + static/js/pandora/ui/namesDialog.js | 248 +++++++++++++++++++++++++++ static/js/pandora/ui/titlesDialog.js | 240 ++++++++++++++++++++++++++ static/json/pandora.json | 2 + 14 files changed, 1181 insertions(+), 11 deletions(-) create mode 100644 pandora/person/managers.py create mode 100644 pandora/title/__init__.py create mode 100644 pandora/title/managers.py create mode 100644 pandora/title/models.py create mode 100644 pandora/title/views.py create mode 100644 static/js/pandora/ui/namesDialog.js create mode 100644 static/js/pandora/ui/titlesDialog.js diff --git a/pandora/event/models.py b/pandora/event/models.py index 8db6a967..3c02c186 100644 --- a/pandora/event/models.py +++ b/pandora/event/models.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 from __future__ import division, with_statement +import unicodedata +import string from django.db import models from django.contrib.auth.models import User, Group @@ -10,9 +12,10 @@ import ox from ox.django import fields from annotation.models import Annotation -from item.models import Item, canonicalTitle +from item.models import Item from item import utils from person.models import get_name_sort +from title.models import get_title_sort import managers @@ -63,7 +66,7 @@ class Event(models.Model): if self.user == user or user.is_staff: return True return False - + def get_matches(self): q = Q(value__icontains=" " + self.name)|Q(value__startswith=self.name) for name in self.alternativeNames: @@ -91,7 +94,7 @@ class Event(models.Model): if self.type == 'person': value = get_name_sort(value) else: - value = utils.sort_title(canonicalTitle(value)) + value = get_title_sort(value) self.name_sort = utils.sort_string(value) def save(self, *args, **kwargs): diff --git a/pandora/item/models.py b/pandora/item/models.py index 9f8d1bd0..4b225320 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -23,7 +23,6 @@ from django.contrib.sites.models import Site import ox from ox.django import fields -from ox.normalize import canonicalTitle import ox.web.imdb import ox.image @@ -37,6 +36,7 @@ from annotation.models import Annotation, Layer import archive.models from person.models import get_name_sort +from title.models import get_title_sort def get_item(info, user=None, async=False): @@ -618,8 +618,8 @@ class Item(models.Model): if name not in base_keys: if sort_type == 'title': - value = utils.sort_title(canonicalTitle(self.get(source, u'Untitled'))) - value = utils.sort_string(value) + value = get_title_sort(self.get(source, u'Untitled')) + value = utils.sort_string(value)[:955] set_value(s, name, value) elif sort_type == 'person': value = sortNames(self.get(source, [])) diff --git a/pandora/person/managers.py b/pandora/person/managers.py new file mode 100644 index 00000000..827cd0b8 --- /dev/null +++ b/pandora/person/managers.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from django.db.models import Q, Manager +from ox.django.query import QuerySet +import ox + +def parseCondition(condition, user): + ''' + condition: { + value: "war" + } + or + condition: { + key: "year", + value: "1970-1980, + operator: "!=" + } + ... + ''' + k = condition.get('key', 'name') + k = { + 'user': 'user__username', + }.get(k, k) + if not k: + k = 'name' + v = condition['value'] + op = condition.get('operator') + if not op: + op = '=' + if op.startswith('!'): + op = op[1:] + exclude = True + else: + exclude = False + if op == '-': + q = parseCondition({'key': k, 'value': v[0], 'operator': '>='}, user) \ + & parseCondition({'key': k, 'value': v[1], 'operator': '<'}, user) + if exclude: + return ~q + else: + return q + if k == 'id': + v = ox.from26(v) + if isinstance(v, bool): #featured and public flag + key = k + elif k in ('lat', 'lng', 'area', 'south', 'west', 'north', 'east', 'matches', 'id'): + key = '%s%s' % (k, { + '>': '__gt', + '>=': '__gte', + '<': '__lt', + '<=': '__lte', + }.get(op,'')) + else: + key = '%s%s' % (k, { + '==': '__iexact', + '^': '__istartswith', + '$': '__iendswith', + }.get(op,'__icontains')) + + key = str(key) + if exclude: + q = ~Q(**{key: v}) + else: + q = Q(**{key: v}) + return q + +def parseConditions(conditions, operator, user): + ''' + conditions: [ + { + value: "war" + } + { + key: "year", + value: "1970-1980, + operator: "!=" + }, + { + key: "country", + value: "f", + operator: "^" + } + ], + operator: "&" + ''' + conn = [] + for condition in conditions: + if 'conditions' in condition: + q = parseConditions(condition['conditions'], + condition.get('operator', '&'), user) + if q: + conn.append(q) + pass + else: + conn.append(parseCondition(condition, user)) + if conn: + q = conn[0] + for c in conn[1:]: + if operator == '|': + q = q | c + else: + q = q & c + return q + return None + + +class PersonManager(Manager): + + def get_query_set(self): + return QuerySet(self.model) + + def find(self, data, user): + ''' + query: { + conditions: [ + { + value: "war" + } + { + key: "year", + value: "1970-1980, + operator: "!=" + }, + { + key: "country", + value: "f", + operator: "^" + } + ], + operator: "&" + } + ''' + + #join query with operator + qs = self.get_query_set() + + query = data.get('query', {}) + conditions = parseConditions(query.get('conditions', []), + query.get('operator', '&'), + user) + if conditions: + qs = qs.filter(conditions) + return qs diff --git a/pandora/person/models.py b/pandora/person/models.py index 392d9af0..8a5cf554 100644 --- a/pandora/person/models.py +++ b/pandora/person/models.py @@ -9,6 +9,7 @@ from django.db import models from ox.django import fields import ox +import managers def get_name_sort(name): person, created = Person.objects.get_or_create(name=name) @@ -18,6 +19,7 @@ def get_name_sort(name): class Person(models.Model): name = models.CharField(max_length=200, unique=True) name_sort = models.CharField(max_length=200) + numberofnames = models.IntegerField(default=0) #FIXME: how to deal with aliases aliases = fields.TupleField(default=[]) @@ -25,8 +27,7 @@ class Person(models.Model): imdbId = models.CharField(max_length=7, blank=True) wikipediaId = models.CharField(max_length=1000, blank=True) - class Meta: - ordering = ('name_sort', ) + objects = managers.PersonManager() def __unicode__(self): return self.name @@ -34,6 +35,8 @@ class Person(models.Model): def save(self, *args, **kwargs): if not self.name_sort: self.name_sort = ox.normalize.canonicalName(self.name) + self.name_sort = unicodedata.normalize('NFKD', self.name_sort) + self.numberofnames = len(self.name.split(' ')) super(Person, self).save(*args, **kwargs) def get_or_create(model, name, imdbId=None): @@ -51,5 +54,18 @@ class Person(models.Model): return o get_or_create = classmethod(get_or_create) - def json(self): - return self.name + def get_id(self): + return ox.to26(self.id) + + def json(self, keys=None, user=None): + j = { + 'id': self.get_id(), + 'name': self.name, + 'nameSort': self.name_sort, + 'numberofnames': self.numberofnames, + } + if keys: + for key in j.keys(): + if key not in keys: + del j[key] + return j diff --git a/pandora/person/views.py b/pandora/person/views.py index 60f00ef0..802f3294 100644 --- a/pandora/person/views.py +++ b/pandora/person/views.py @@ -1 +1,154 @@ -# Create your views here. +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + +from django.db.models import Max, Min + +import ox +from ox.utils import json + +from ox.django.decorators import login_required_json, admin_required_json +from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response + +from api.actions import actions +from item import utils + +import models + +@admin_required_json +def editName(request): + ''' + param data { + 'id': nameid, + 'nameSort': ... + } + can contain any of the allowed keys for name + ''' + data = json.loads(request.POST['data']) + person = get_object_or_404_json(models.Person, pk=ox.from26(data['id'])) + response = json_response() + if 'nameSort' in data: + person.name_sort = utils.sort_string(data['nameSort']) + person.save() + response['data'] = person.json() + return render_to_json_response(response) +actions.register(editName, cache=False) + +def parse_query(data, user): + query = {} + query['range'] = [0, 100] + query['sort'] = [{'key':'name', 'operator':'+'}] + for key in ('keys', 'group', 'list', 'range', 'sort', 'query'): + if key in data: + query[key] = data[key] + query['qs'] = models.Person.objects.find(query, user) + #if 'itemQuery' in data: + # item_query = models.Item.objects.find({'query': data['itemQuery']}, user) + # query['qs'] = query['qs'].filter(items__in=item_query) + return query + +def order_query(qs, sort): + order_by = [] + for e in sort: + operator = e['operator'] + if operator != '-': + operator = '' + key = { + 'name': 'name_sort', + 'nameSort': 'name_sort', + }.get(e['key'], e['key']) + order = '%s%s' % (operator, key) + order_by.append(order) + if order_by: + qs = qs.order_by(*order_by, nulls_last=True) + return qs + +def findNames(request): + ''' + param data { + query: { + conditions: [ + { + key: 'user', + value: 'something', + operator: '=' + } + ] + operator: "," + }, + itemQuery: { + //see find request + }, + sort: [{key: 'name', operator: '+'}], + range: [0, 100] + keys: [] + } + + possible query keys: + name, numberofnames + + possible keys: + name, nameSort, numberofnames + + return { + status: { + code: int, + text: string + }, + data: { + items: [ + {name:, user:, featured:, public...} + ] + } + } + param data + {'query': query, 'sort': array, 'range': array} + + query: query object, more on query syntax at + https://wiki.0x2620.org/wiki/pandora/QuerySyntax + sort: array of key, operator dics + [ + { + key: "year", + operator: "-" + }, + { + key: "director", + operator: "" + } + ] + range: result range, array [from, to] + + with keys, items is list of dicts with requested properties: + return {'status': {'code': int, 'text': string}, + 'data': {items: array}} + + ''' + data = json.loads(request.POST['data']) + response = json_response() + + query = parse_query(data, request.user) + qs = order_query(query['qs'], query['sort']) + qs = qs.distinct() + if 'keys' in data: + qs = qs.select_related() + qs = qs[query['range'][0]:query['range'][1]] + response['data']['items'] = [p.json(data['keys'], request.user) for p in qs] + elif 'position' in query: + ids = [i.get_id() for i in qs] + data['conditions'] = data['conditions'] + { + 'value': data['position'], + 'key': query['sort'][0]['key'], + 'operator': '^' + } + query = parse_query(data, request.user) + qs = order_query(query['qs'], query['sort']) + if qs.count() > 0: + response['data']['position'] = utils.get_positions(ids, [qs[0].itemId])[0] + elif 'positions' in data: + ids = [i.get_id() for i in qs] + response['data']['positions'] = utils.get_positions(ids, data['positions']) + else: + response['data']['items'] = qs.count() + return render_to_json_response(response) +actions.register(findNames) diff --git a/pandora/settings.py b/pandora/settings.py index 540a0a10..1274fd2a 100644 --- a/pandora/settings.py +++ b/pandora/settings.py @@ -130,6 +130,7 @@ INSTALLED_APPS = ( 'item', 'itemlist', 'person', + 'title', 'place', 'text', 'edit', diff --git a/pandora/title/__init__.py b/pandora/title/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pandora/title/managers.py b/pandora/title/managers.py new file mode 100644 index 00000000..98afa293 --- /dev/null +++ b/pandora/title/managers.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from django.db.models import Q, Manager +from ox.django.query import QuerySet +import ox + +def parseCondition(condition, user): + ''' + condition: { + value: "war" + } + or + condition: { + key: "year", + value: "1970-1980, + operator: "!=" + } + ... + ''' + k = condition.get('key', 'title') + k = { + 'user': 'user__usertitle', + }.get(k, k) + if not k: + k = 'title' + v = condition['value'] + op = condition.get('operator') + if not op: + op = '=' + if op.startswith('!'): + op = op[1:] + exclude = True + else: + exclude = False + if op == '-': + q = parseCondition({'key': k, 'value': v[0], 'operator': '>='}, user) \ + & parseCondition({'key': k, 'value': v[1], 'operator': '<'}, user) + if exclude: + return ~q + else: + return q + if k == 'id': + v = ox.from26(v) + if isinstance(v, bool): #featured and public flag + key = k + elif k in ('lat', 'lng', 'area', 'south', 'west', 'north', 'east', 'matches', 'id'): + key = '%s%s' % (k, { + '>': '__gt', + '>=': '__gte', + '<': '__lt', + '<=': '__lte', + }.get(op,'')) + else: + key = '%s%s' % (k, { + '==': '__iexact', + '^': '__istartswith', + '$': '__iendswith', + }.get(op,'__icontains')) + + key = str(key) + if exclude: + q = ~Q(**{key: v}) + else: + q = Q(**{key: v}) + return q + +def parseConditions(conditions, operator, user): + ''' + conditions: [ + { + value: "war" + } + { + key: "year", + value: "1970-1980, + operator: "!=" + }, + { + key: "country", + value: "f", + operator: "^" + } + ], + operator: "&" + ''' + conn = [] + for condition in conditions: + if 'conditions' in condition: + q = parseConditions(condition['conditions'], + condition.get('operator', '&'), user) + if q: + conn.append(q) + pass + else: + conn.append(parseCondition(condition, user)) + if conn: + q = conn[0] + for c in conn[1:]: + if operator == '|': + q = q | c + else: + q = q & c + return q + return None + + +class TitleManager(Manager): + + def get_query_set(self): + return QuerySet(self.model) + + def find(self, data, user): + ''' + query: { + conditions: [ + { + value: "war" + } + { + key: "year", + value: "1970-1980, + operator: "!=" + }, + { + key: "country", + value: "f", + operator: "^" + } + ], + operator: "&" + } + ''' + + #join query with operator + qs = self.get_query_set() + + query = data.get('query', {}) + conditions = parseConditions(query.get('conditions', []), + query.get('operator', '&'), + user) + if conditions: + qs = qs.filter(conditions) + return qs diff --git a/pandora/title/models.py b/pandora/title/models.py new file mode 100644 index 00000000..72cbe50a --- /dev/null +++ b/pandora/title/models.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division, with_statement + +import unicodedata + +from django.db import models + +import ox + +import managers + +def get_title_sort(title): + title, created = Title.objects.get_or_create(title=title) + title_sort = unicodedata.normalize('NFKD', title.title_sort) + return title_sort + +class Title(models.Model): + title = models.CharField(max_length=200, unique=True) + title_sort = models.CharField(max_length=200) + + imdbId = models.CharField(max_length=7, blank=True) + + objects = managers.TitleManager() + + def __unicode__(self): + return self.title + + def save(self, *args, **kwargs): + if not self.title_sort: + self.title_sort = ox.normalize.canonicalTitle(self.title) + self.title_sort = unicodedata.normalize('NFKD', self.title_sort) + super(Title, self).save(*args, **kwargs) + + def get_or_create(model, title, imdbId=None): + if imdbId: + q = model.objects.filter(title=title, imdbId=imdbId) + else: + q = model.objects.all().filter(title=title) + if q.count() > 0: + o = q[0] + else: + o = model.objects.create(title=title) + if imdbId: + o.imdbId = imdbId + o.save() + return o + get_or_create = classmethod(get_or_create) + + def get_id(self): + return ox.to26(self.id) + + def json(self, keys=None, user=None): + j = { + 'id': self.get_id(), + 'title': self.title, + 'titleSort': self.title_sort, + } + if keys: + for key in j.keys(): + if key not in keys: + del j[key] + return j diff --git a/pandora/title/views.py b/pandora/title/views.py new file mode 100644 index 00000000..83ad7eb9 --- /dev/null +++ b/pandora/title/views.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division + +from django.db.models import Max, Min + +import ox +from ox.utils import json + +from ox.django.decorators import admin_required_json +from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response + +from api.actions import actions +from item import utils + +import models + +@admin_required_json +def editTitle(request): + ''' + param data { + 'id': titleid, + 'titleSort': ... + } + can contain any of the allowed keys for title + ''' + data = json.loads(request.POST['data']) + title = get_object_or_404_json(models.Title, pk=ox.from26(data['id'])) + response = json_response() + if 'titleSort' in data: + title.title_sort = utils.sort_string(data['titleSort']) + title.save() + response['data'] = title.json() + return render_to_json_response(response) +actions.register(editTitle, cache=False) + +def parse_query(data, user): + query = {} + query['range'] = [0, 100] + query['sort'] = [{'key':'title', 'operator':'+'}] + for key in ('keys', 'group', 'list', 'range', 'sort', 'query'): + if key in data: + query[key] = data[key] + query['qs'] = models.Title.objects.find(query, user) + #if 'itemQuery' in data: + # item_query = models.Item.objects.find({'query': data['itemQuery']}, user) + # query['qs'] = query['qs'].filter(items__in=item_query) + return query + +def order_query(qs, sort): + order_by = [] + for e in sort: + operator = e['operator'] + if operator != '-': + operator = '' + key = { + 'title': 'title_sort', + 'titleSort': 'title_sort', + }.get(e['key'], e['key']) + order = '%s%s' % (operator, key) + order_by.append(order) + if order_by: + qs = qs.order_by(*order_by, nulls_last=True) + return qs + +def findTitles(request): + ''' + param data { + query: { + conditions: [ + { + key: 'user', + value: 'something', + operator: '=' + } + ] + operator: "," + }, + itemQuery: { + //see find request + }, + sort: [{key: 'title', operator: '+'}], + range: [0, 100] + keys: [] + } + + possible query keys: + title, numberoftitles + + possible keys: + title, titleSort, numberoftitles + + return { + status: { + code: int, + text: string + }, + data: { + items: [ + {title:, user:, featured:, public...} + ] + } + } + param data + {'query': query, 'sort': array, 'range': array} + + query: query object, more on query syntax at + https://wiki.0x2620.org/wiki/pandora/QuerySyntax + sort: array of key, operator dics + [ + { + key: "year", + operator: "-" + }, + { + key: "director", + operator: "" + } + ] + range: result range, array [from, to] + + with keys, items is list of dicts with requested properties: + return {'status': {'code': int, 'text': string}, + 'data': {items: array}} + + ''' + data = json.loads(request.POST['data']) + response = json_response() + + query = parse_query(data, request.user) + qs = order_query(query['qs'], query['sort']) + qs = qs.distinct() + if 'keys' in data: + qs = qs.select_related() + qs = qs[query['range'][0]:query['range'][1]] + response['data']['items'] = [p.json(data['keys'], request.user) for p in qs] + elif 'position' in query: + ids = [i.get_id() for i in qs] + data['conditions'] = data['conditions'] + { + 'value': data['position'], + 'key': query['sort'][0]['key'], + 'operator': '^' + } + query = parse_query(data, request.user) + qs = order_query(query['qs'], query['sort']) + if qs.count() > 0: + response['data']['position'] = utils.get_positions(ids, [qs[0].itemId])[0] + elif 'positions' in data: + ids = [i.get_id() for i in qs] + response['data']['positions'] = utils.get_positions(ids, data['positions']) + else: + response['data']['items'] = qs.count() + return render_to_json_response(response) +actions.register(findTitles) diff --git a/static/js/pandora/ui/menu.js b/static/js/pandora/ui/menu.js index 1d44ce96..65feff15 100644 --- a/static/js/pandora/ui/menu.js +++ b/static/js/pandora/ui/menu.js @@ -244,6 +244,10 @@ pandora.ui.mainMenu = function() { } else if (data.id == 'posters') { var id = pandora.user.ui.item || pandora.user.ui.listItem; pandora.$ui.postersDialog = pandora.ui.postersDialog(id).open(); + } else if (data.id == 'names') { + pandora.$ui.namesDialog = pandora.ui.namesDialog().open(); + } else if (data.id == 'titles') { + pandora.$ui.titlesDialog = pandora.ui.titlesDialog().open(); } else if (data.id == 'places') { pandora.$ui.placesDialog = pandora.ui.placesDialog().open(); } else if (data.id == 'events') { diff --git a/static/js/pandora/ui/namesDialog.js b/static/js/pandora/ui/namesDialog.js new file mode 100644 index 00000000..9e32eca1 --- /dev/null +++ b/static/js/pandora/ui/namesDialog.js @@ -0,0 +1,248 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript +pandora.ui.namesDialog = function() { + + var height = Math.round((window.innerHeight - 48) * 0.9), + width = Math.round(window.innerWidth * 0.9), + numberOfNames = 0, + + $status = Ox.Label({ + title: 'Loading...' + }) + .css({float: 'left', margin: '4px'}), + + $findSelect = Ox.Select({ + items: [ + {id: 'all', title: 'Find: All'} + ], + overlap: 'right', + type: 'image' + }) + .bindEvent({ + change: function(data) { + var key = data.selected[0].id, + value = $findInput.value(); + value && updateList(key, value); + $findInput.options({ + placeholder: data.selected[0].title + }); + } + }), + + $findInput = Ox.Input({ + changeOnKeypress: true, + clear: true, + placeholder: 'Find: All', + width: 192 + }) + .bindEvent({ + change: function(data) { + var key = $findSelect.value(), + value = data.value; + updateList(key, value); + } + }), + + $findElement = Ox.FormElementGroup({ + elements: [ + $findSelect, + $findInput + ] + }) + .css({float: 'right', margin: '4px'}), + + $list = Ox.TextList({ + columns: [ + { + id: 'id', + title: 'ID', + unique: true, + visible: false, + }, + { + id: 'name', + operator: '+', + removable: false, + title: 'Name', + visible: true, + width: 250 + }, + { + id: 'nameSort', + operator: '+', + title: 'Sort Name', + visible: true, + width: 250 + }, + { + id: 'numberofnames', + align: 'right', + operator: '-', + title: 'Number of Names', + visible: true, + width: 60 + }, + ], + columnsRemovable: true, + columnsVisible: true, + items: pandora.api.findNames, + keys: [], + max: 1, + scrollbarVisible: true, + sort: [ + {key: 'name', operator: '+'} + ] + }) + .bindEvent({ + init: function(data) { + numberOfNames = data.items; + $status.options({ + title: Ox.formatNumber(numberOfNames) + + ' name' + (numberOfNames == 1 ? '' : 's') + }); + }, + select: function(data) { + var values; + $name.empty(); + if (data.ids.length) { + values = $list.value(data.ids[0]); + $nameLabel.options({ + title: values.name + ' <' + values.nameSort + '>' + }); + $name.append(renderNameForm(values)) + } else { + $nameLabel.options({title: 'No name selected'}); + } + } + }), + + $nameLabel = Ox.Label({ + textAlign: 'center', + title: 'No name selected', + width: 248 + }) + .css({margin: '4px'}), + + $name = Ox.Element({}), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'done', + title: 'Done' + }).bindEvent({ + click: function() { + that.close(); + } + }) + ], + closeButton: true, + content: Ox.SplitPanel({ + elements: [ + { + element: Ox.SplitPanel({ + elements: [ + { + element: Ox.Bar({size: 24}) + .append($status) + .append( + $findElement + ), + size: 24 + }, + { + element: $list + } + ], + orientation: 'vertical' + }) + }, + { + element: Ox.SplitPanel({ + elements: [ + { + element: Ox.Bar({size: 24}) + .append($nameLabel), + size: 24 + }, + { + element: $name + } + ], + orientation: 'vertical' + }), + size: 256 + } + ], + orientation: 'horizontal' + }), + height: height, + maximizeButton: true, + minHeight: 256, + minWidth: 512, + padding: 0, + title: 'Manage Names', + width: width + }); + + function renderNameForm(nameData) { + var $checkbox; + return Ox.Form({ + items: [ + Ox.Input({ + id: 'name', + label: 'Name', + labelWidth: 80, + value: nameData.name, + width: 240 + }) + .bindEvent({ + submit: function(data) { + + } + }), + Ox.Input({ + id: 'nameSort', + label: 'Sort Name', + labelWidth: 80, + value: nameData.nameSort, + width: 240 + }) + .bindEvent({ + submit: function(data) { + + } + }), + ], + width: 240 + }) + .css({margin: '8px'}) + .bindEvent({ + change: function(event) { + var data = {id: nameData.id}, key, value; + data[event.id] = event.data.value; + $list.value(nameData.id, event.id, data[event.id]); + pandora.api.editName(data, function(result) { + Ox.Request.clearCache('findNames'); + }); + } + }); + } + + function updateList(key, value) { + var query = { + conditions: [{key: 'name', value: value, operator: '='}], + operator: '&' + }; + $list.options({ + items: function(data, callback) { + return pandora.api.findNames(Ox.extend(data, { + query: query + }), callback); + } + }); + } + + return that; + +}; + diff --git a/static/js/pandora/ui/titlesDialog.js b/static/js/pandora/ui/titlesDialog.js new file mode 100644 index 00000000..a3d2871b --- /dev/null +++ b/static/js/pandora/ui/titlesDialog.js @@ -0,0 +1,240 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript +pandora.ui.titlesDialog = function() { + + var height = Math.round((window.innerHeight - 48) * 0.9), + width = Math.round(window.innerWidth * 0.9), + numberOfTitles = 0, + + $status = Ox.Label({ + title: 'Loading...' + }) + .css({float: 'left', margin: '4px'}), + + $findSelect = Ox.Select({ + items: [ + {id: 'all', title: 'Find: All'} + ], + overlap: 'right', + type: 'image' + }) + .bindEvent({ + change: function(data) { + var key = data.selected[0].id, + value = $findInput.value(); + value && updateList(key, value); + $findInput.options({ + placeholder: data.selected[0].title + }); + } + }), + + $findInput = Ox.Input({ + changeOnKeypress: true, + clear: true, + placeholder: 'Find: All', + width: 192 + }) + .bindEvent({ + change: function(data) { + var key = $findSelect.value(), + value = data.value; + updateList(key, value); + } + }), + + $findElement = Ox.FormElementGroup({ + elements: [ + $findSelect, + $findInput + ] + }) + .css({float: 'right', margin: '4px'}), + + $list = Ox.TextList({ + columns: [ + { + id: 'id', + title: 'ID', + unique: true, + visible: false, + }, + { + id: 'title', + operator: '+', + removable: false, + title: 'Title', + visible: true, + width: 250 + }, + { + id: 'titleSort', + operator: '+', + title: 'Sort Title', + visible: true, + width: 250 + }, + ], + columnsRemovable: true, + columnsVisible: true, + items: pandora.api.findTitles, + keys: [], + max: 1, + scrollbarVisible: true, + sort: [ + {key: 'title', operator: '+'} + ] + }) + .bindEvent({ + init: function(data) { + numberOfTitles = data.items; + $status.options({ + title: Ox.formatNumber(numberOfTitles) + + ' title' + (numberOfTitles == 1 ? '' : 's') + }); + }, + select: function(data) { + var values; + $title.empty(); + if (data.ids.length) { + values = $list.value(data.ids[0]); + $titleLabel.options({ + title: values.title + ' <' + values.titleSort + '>' + }); + $title.append(renderTitleForm(values)) + } else { + $titleLabel.options({title: 'No title selected'}); + } + } + }), + + $titleLabel = Ox.Label({ + textAlign: 'center', + title: 'No title selected', + width: 248 + }) + .css({margin: '4px'}), + + $title = Ox.Element({}), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'done', + title: 'Done' + }).bindEvent({ + click: function() { + that.close(); + } + }) + ], + closeButton: true, + content: Ox.SplitPanel({ + elements: [ + { + element: Ox.SplitPanel({ + elements: [ + { + element: Ox.Bar({size: 24}) + .append($status) + .append( + $findElement + ), + size: 24 + }, + { + element: $list + } + ], + orientation: 'vertical' + }) + }, + { + element: Ox.SplitPanel({ + elements: [ + { + element: Ox.Bar({size: 24}) + .append($titleLabel), + size: 24 + }, + { + element: $title + } + ], + orientation: 'vertical' + }), + size: 256 + } + ], + orientation: 'horizontal' + }), + height: height, + maximizeButton: true, + minHeight: 256, + minWidth: 512, + padding: 0, + title: 'Manage Titles', + width: width + }); + + function renderTitleForm(titleData) { + var $checkbox; + return Ox.Form({ + items: [ + Ox.Input({ + id: 'title', + label: 'Title', + labelWidth: 80, + value: titleData.title, + width: 240 + }) + .bindEvent({ + submit: function(data) { + + } + }), + Ox.Input({ + id: 'titleSort', + label: 'Sort Title', + labelWidth: 80, + value: titleData.titleSort, + width: 240 + }) + .bindEvent({ + submit: function(data) { + + } + }), + ], + width: 240 + }) + .css({margin: '8px'}) + .bindEvent({ + change: function(event) { + var data = {id: titleData.id}, key, value; + data[event.id] = event.data.value; + $list.value(titleData.id, event.id, data[event.id]); + pandora.api.editTitle(data, function(result) { + Ox.Request.clearCache('findTitles'); + }); + } + }); + } + + function updateList(key, value) { + var query = { + conditions: [{key: 'title', value: value, operator: '='}], + operator: '&' + }; + $list.options({ + items: function(data, callback) { + return pandora.api.findTitles(Ox.extend(data, { + query: query + }), callback); + } + }); + } + + return that; + +}; + diff --git a/static/json/pandora.json b/static/json/pandora.json index 8fa207bb..1314d77a 100644 --- a/static/json/pandora.json +++ b/static/json/pandora.json @@ -15,6 +15,8 @@ "js/pandora/ui/deleteListDialog.js", "js/pandora/ui/editor.js", "js/pandora/ui/eventsDialog.js", + "js/pandora/ui/namesDialog.js", + "js/pandora/ui/titlesDialog.js", "js/pandora/ui/filter.js", "js/pandora/ui/filterDialog.js", "js/pandora/ui/findElement.js",