initial round of text section

This commit is contained in:
j 2013-02-16 01:20:40 +00:00
parent f8ededaf55
commit c8af45e7cf
25 changed files with 2243 additions and 984 deletions

View file

@ -31,6 +31,8 @@
"canEditAnnotations": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditMetadata": {"staff": true, "admin": true},
"canEditPlaces": {"staff": true, "admin": true},
"canEditSitePages": {"staff": true, "admin": true},
@ -712,6 +714,8 @@
"icons": "posters",
"infoIconSize": 256,
"item": "",
"text": "",
"edit": "",
"itemFind": "",
"itemSort": [{"key": "position", "operator": "+"}],
"itemView": "info",
@ -749,6 +753,11 @@
"featured": true,
"volumes": true
}
"texts": {
"personal": true,
"favorite": true,
"featured": true
}
},
"showSidebar": true,
"showSitePosters": false,

View file

@ -29,6 +29,8 @@
"canEditAnnotations": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditMetadata": {"staff": true, "admin": true},
"canEditPlaces": {"staff": true, "admin": true},
"canEditSitePages": {"staff": true, "admin": true},
@ -630,6 +632,8 @@
"icons": "frames",
"infoIconSize": 256,
"item": "",
"text": "",
"edit": "",
"itemFind": "",
"itemSort": [{"key": "position", "operator": "+"}],
"itemView": "info",
@ -670,6 +674,11 @@
"favorite": true,
"featured": true,
"volumes": true
},
"texts": {
"personal": true,
"favorite": true,
"featured": true
}
},
"showSidebar": true,

View file

@ -29,6 +29,8 @@
"canEditAnnotations": {"staff": true, "admin": true},
"canEditEvents": {"staff": true, "admin": true},
"canEditFeaturedLists": {"staff": true, "admin": true},
"canEditFeaturedTexts": {"staff": true, "admin": true},
"canEditFeaturedEdits": {"staff": true, "admin": true},
"canEditMetadata": {"staff": true, "admin": true},
"canEditPlaces": {"staff": true, "admin": true},
"canEditSitePages": {"staff": true, "admin": true},
@ -549,6 +551,8 @@
"icons": "posters",
"infoIconSize": 256,
"item": "",
"text": "",
"edit": "",
"itemFind": "",
"itemSort": [{"key": "position", "operator": "+"}],
"itemView": "info",
@ -589,6 +593,11 @@
"featured": true,
"volumes": true
}
"texts": {
"personal": true,
"favorite": true,
"featured": true
}
},
"showSidebar": true,
"showSitePosters": false,

View file

@ -207,7 +207,9 @@ def addList(request):
return {
status: {'code': int, 'text': string},
data: {
list:
id:
name:
...
}
}
'''

136
pandora/text/managers.py Normal file
View file

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from datetime import datetime
from django.db.models import Q, Manager
import models
def parseCondition(condition, user):
'''
'''
k = condition.get('key', 'name')
k = {
'user': 'user__username',
'position': 'position__position',
'posterFrames': 'poster_frames',
}.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 k == 'id':
v = v.split(":")
if len(v) >= 2:
v = (v[0], ":".join(v[1:]))
q = Q(user__username=v[0], name=v[1])
else:
q = Q(id__in=[])
return q
if k == 'subscribed':
key = 'subscribed_users__username'
v = user.username
elif isinstance(v, bool): #featured and public flag
key = k
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 TextManager(Manager):
def get_query_set(self):
return super(TextManager, self).get_query_set()
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()
conditions = parseConditions(data['query'].get('conditions', []),
data['query'].get('operator', '&'),
user)
if conditions:
qs = qs.filter(conditions)
if user.is_anonymous():
qs = qs.filter(Q(status='public') | Q(status='featured'))
else:
qs = qs.filter(Q(status='public') | Q(status='featured') | Q(user=user))
return qs

View file

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting model 'Image'
db.delete_table('text_image')
# Deleting model 'Attachment'
db.delete_table('text_attachment')
# Adding model 'Position'
db.create_table('text_position', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('text', self.gf('django.db.models.fields.related.ForeignKey')(related_name='position', to=orm['text.Text'])),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='text_position', to=orm['auth.User'])),
('section', self.gf('django.db.models.fields.CharField')(max_length='255')),
('position', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('text', ['Position'])
# Adding unique constraint on 'Position', fields ['user', 'text', 'section']
db.create_unique('text_position', ['user_id', 'text_id', 'section'])
# Deleting field 'Text.public'
db.delete_column('text_text', 'public')
# Deleting field 'Text.slug'
db.delete_column('text_text', 'slug')
# Deleting field 'Text.title'
db.delete_column('text_text', 'title')
# Deleting field 'Text.published'
db.delete_column('text_text', 'published')
# Adding field 'Text.name'
db.add_column('text_text', 'name',
self.gf('django.db.models.fields.CharField')(default=datetime.datetime(2013, 2, 15, 0, 0), max_length=255),
keep_default=False)
# Adding field 'Text.status'
db.add_column('text_text', 'status',
self.gf('django.db.models.fields.CharField')(default='private', max_length=20),
keep_default=False)
# Adding field 'Text.description'
db.add_column('text_text', 'description',
self.gf('django.db.models.fields.TextField')(default=''),
keep_default=False)
# Adding field 'Text.icon'
db.add_column('text_text', 'icon',
self.gf('django.db.models.fields.files.ImageField')(default='', max_length=100, blank=True),
keep_default=False)
# Adding field 'Text.poster_frames'
db.add_column('text_text', 'poster_frames',
self.gf('ox.django.fields.TupleField')(default=[]),
keep_default=False)
# Adding M2M table for field subscribed_users on 'Text'
db.create_table('text_text_subscribed_users', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('text', models.ForeignKey(orm['text.text'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('text_text_subscribed_users', ['text_id', 'user_id'])
# Adding unique constraint on 'Text', fields ['user', 'name']
db.create_unique('text_text', ['user_id', 'name'])
def backwards(self, orm):
# Removing unique constraint on 'Text', fields ['user', 'name']
db.delete_unique('text_text', ['user_id', 'name'])
# Removing unique constraint on 'Position', fields ['user', 'text', 'section']
db.delete_unique('text_position', ['user_id', 'text_id', 'section'])
# Adding model 'Image'
db.create_table('text_image', (
('caption', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
('image', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('text', ['Image'])
# Adding model 'Attachment'
db.create_table('text_attachment', (
('caption', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
))
db.send_create_signal('text', ['Attachment'])
# Deleting model 'Position'
db.delete_table('text_position')
# Adding field 'Text.public'
db.add_column('text_text', 'public',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'Text.slug'
db.add_column('text_text', 'slug',
self.gf('django.db.models.fields.SlugField')(default='', max_length=50),
keep_default=False)
# Adding field 'Text.title'
db.add_column('text_text', 'title',
self.gf('django.db.models.fields.CharField')(max_length=1000, null=True),
keep_default=False)
# Adding field 'Text.published'
db.add_column('text_text', 'published',
self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now),
keep_default=False)
# Deleting field 'Text.name'
db.delete_column('text_text', 'name')
# Deleting field 'Text.status'
db.delete_column('text_text', 'status')
# Deleting field 'Text.description'
db.delete_column('text_text', 'description')
# Deleting field 'Text.icon'
db.delete_column('text_text', 'icon')
# Deleting field 'Text.poster_frames'
db.delete_column('text_text', 'poster_frames')
# Removing M2M table for field subscribed_users on 'Text'
db.delete_table('text_text_subscribed_users')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'text.position': {
'Meta': {'unique_together': "(('user', 'text', 'section'),)", 'object_name': 'Position'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'section': ('django.db.models.fields.CharField', [], {'max_length': "'255'"}),
'text': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'position'", 'to': "orm['text.Text']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'text_position'", 'to': "orm['auth.User']"})
},
'text.text': {
'Meta': {'unique_together': "(('user', 'name'),)", 'object_name': 'Text'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''"}),
'icon': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'poster_frames': ('ox.django.fields.TupleField', [], {'default': '[]'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'private'", 'max_length': '20'}),
'subscribed_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribed_texts'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'text': ('django.db.models.fields.TextField', [], {'default': "''"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'texts'", 'to': "orm['auth.User']"})
}
}
complete_apps = ['text']

View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Text.type'
db.add_column('text_text', 'type',
self.gf('django.db.models.fields.CharField')(default='html', max_length=255),
keep_default=False)
# Adding field 'Text.links'
db.add_column('text_text', 'links',
self.gf('ox.django.fields.DictField')(default={}),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Text.type'
db.delete_column('text_text', 'type')
# Deleting field 'Text.links'
db.delete_column('text_text', 'links')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'text.position': {
'Meta': {'unique_together': "(('user', 'text', 'section'),)", 'object_name': 'Position'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'position': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'section': ('django.db.models.fields.CharField', [], {'max_length': "'255'"}),
'text': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'position'", 'to': "orm['text.Text']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'text_position'", 'to': "orm['auth.User']"})
},
'text.text': {
'Meta': {'unique_together': "(('user', 'name'),)", 'object_name': 'Text'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''"}),
'icon': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'links': ('ox.django.fields.DictField', [], {'default': '{}'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'poster_frames': ('ox.django.fields.TupleField', [], {'default': '[]'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'private'", 'max_length': '20'}),
'subscribed_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subscribed_texts'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'text': ('django.db.models.fields.TextField', [], {'default': "''"}),
'type': ('django.db.models.fields.CharField', [], {'default': "'html'", 'max_length': '255'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'texts'", 'to': "orm['auth.User']"})
}
}
complete_apps = ['text']

View file

@ -2,40 +2,158 @@
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division, with_statement
from datetime import datetime
import os
import subprocess
from glob import glob
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
import ox
from ox.django.fields import DictField, TupleField
from archive import extract
import managers
class Text(models.Model):
class Meta:
unique_together = ("user", "name")
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
published = models.DateTimeField(default=datetime.now, editable=False)
public = models.BooleanField(default=False)
user = models.ForeignKey(User, related_name='texts')
name = models.CharField(max_length=255)
status = models.CharField(max_length=20, default='private')
_status = ['private', 'public', 'featured']
type = models.CharField(max_length=255, default='html')
description = models.TextField(default='')
user = models.ForeignKey(User)
slug = models.SlugField()
title = models.CharField(null=True, max_length=1000)
text = models.TextField(default='')
icon = models.ImageField(default=None, blank=True,
upload_to=lambda i, x: i.path("icon.jpg"))
text = models.TextField(default="")
links = DictField(default={}, editable=True)
poster_frames = TupleField(default=[], editable=False)
subscribed_users = models.ManyToManyField(User, related_name='subscribed_texts')
objects = managers.TextManager()
def save(self, *args, **kwargs):
super(Text, self).save(*args, **kwargs)
def __unicode__(self):
return u"%s <%s>" % (self.title, self.slug)
return self.get_id()
def get_absolute_url(self):
return '/text/%s' % self.slug
def get_id(self):
return u'%s:%s' % (self.user.username, self.name)
def accessible(self, user):
return self.user == user or self.status in ('public', 'featured')
class Image(models.Model):
image = models.ImageField(upload_to='text/image')
caption = models.CharField(max_length=255, default="")
def editable(self, user):
if user.is_anonymous():
return False
if self.user == user or \
user.is_staff or \
user.get_profile().capability('canEditFeaturedTexts') == True:
return True
return False
def get_absolute_url(self):
return self.image.url
def json(self, keys=None, user=None):
if not keys:
keys=['id', 'name', 'user', 'status', 'subscribed', 'posterFrames', 'description', 'text', 'type', 'links']
response = {}
_map = {
'posterFrames': 'poster_frames'
}
for key in keys:
if key == 'id':
response[key] = self.get_id()
elif key == 'user':
response[key] = self.user.username
elif key == 'subscribers':
response[key] = self.subscribed_users.all().count()
elif key == 'subscribed':
if user and not user.is_anonymous():
response[key] = self.subscribed_users.filter(id=user.id).exists()
elif hasattr(self, _map.get(key, key)):
response[key] = getattr(self, _map.get(key,key))
return response
def path(self, name=''):
h = "%07d" % self.id
return os.path.join('texts', h[:2], h[2:4], h[4:6], h[6:], name)
class Attachment(models.Model):
file = models.FileField(upload_to='text/attachment')
caption = models.CharField(max_length=255, default="")
def update_icon(self):
frames = []
if not self.poster_frames:
items = self.get_items(self.user).filter(rendered=True)
if items.count():
poster_frames = []
for i in range(0, items.count(), max(1, int(items.count()/4))):
poster_frames.append({
'item': items[int(i)].itemId,
'position': items[int(i)].poster_frame
})
self.poster_frames = tuple(poster_frames)
self.save()
for i in self.poster_frames:
from item.models import Item
qs = Item.objects.filter(itemId=i['item'])
if qs.count() > 0:
frame = qs[0].frame(i['position'])
if frame:
frames.append(frame)
self.icon.name = self.path('icon.jpg')
icon = self.icon.path
if frames:
while len(frames) < 4:
frames += frames
folder = os.path.dirname(icon)
ox.makedirs(folder)
for f in glob("%s/icon*.jpg" % folder):
os.unlink(f)
cmd = [
settings.LIST_ICON,
'-f', ','.join(frames),
'-o', icon
]
p = subprocess.Popen(cmd)
p.wait()
self.save()
def get_icon(self, size=16):
path = self.path('icon%d.jpg' % size)
path = os.path.join(settings.MEDIA_ROOT, path)
if not os.path.exists(path):
folder = os.path.dirname(path)
ox.makedirs(folder)
if self.icon and os.path.exists(self.icon.path):
source = self.icon.path
max_size = min(self.icon.width, self.icon.height)
else:
source = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
max_size = 256
if size < max_size:
extract.resize_image(source, path, size=size)
else:
path = source
return path
class Position(models.Model):
class Meta:
unique_together = ("user", "text", "section")
text = models.ForeignKey(Text, related_name='position')
user = models.ForeignKey(User, related_name='text_position')
section = models.CharField(max_length='255')
position = models.IntegerField(default=0)
def __unicode__(self):
return u'%s/%s/%s' % (self.section, self.position, self.text)
def get_absolute_url(self):
return self.file.url

View file

@ -1,27 +1,96 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
import os
import re
import ox
from ox.utils import json
from ox.django.decorators import login_required_json
from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response
import models
from ox.django.api import actions
from ox.django.decorators import login_required_json
from ox.django.http import HttpFileResponse
from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response
from django.db.models import Count, Q, Sum, Max
from item import utils
import models
def get_text_or_404_json(id):
id = id.split(':')
username = id[0]
textname = ":".join(id[1:])
return get_object_or_404_json(models.Text, user__username=username, name=textname)
@login_required_json
def addText(request):
'''
param data {
name: value,
}
return {
status: {'code': int, 'text': string},
data: {
id:
name:
...
}
}
'''
data = json.loads(request.POST['data'])
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
name = data['name']
if not name:
name = "Untitled"
num = 1
created = False
while not created:
text, created = models.Text.objects.get_or_create(name=name, user=request.user)
num += 1
name = data['name'] + ' [%d]' % num
text.save()
if text.status == 'featured':
pos, created = models.Position.objects.get_or_create(text=text,
user=request.user, section='featured')
qs = models.Position.objects.filter(section='featured')
else:
pos, created = models.Position.objects.get_or_create(text=text,
user=request.user, section='personal')
qs = models.Position.objects.filter(user=request.user, section='personal')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
response = json_response(status=200, text='created')
response['data'] = text.json()
return render_to_json_response(response)
actions.register(addText, cache=False)
def getText(request):
'''
param data
string id
return page
param data {
id: textid
}
return {
id:
text:
...
}
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Text, pk=itemId)
response['data']['page'] = item.html()
response = json_response()
data = json.loads(request.POST['data'])
public_id = data['id']
if public_id == '':
qs = models.Text.objects.filter(name='')
if qs.count() == 0:
text = models.Text()
text.name = ''
text.text = 'Please put something here'
text.user = request.user
text.save()
else:
text = qs[0]
else:
text = get_text_or_404_json(data['id'])
response['data'] = text.json()
return render_to_json_response(response)
actions.register(getText)
@ -29,22 +98,355 @@ actions.register(getText)
@login_required_json
def editText(request):
'''
param data
string id
return page
param data {
id:
text:
public: boolean
}
return {
id:
text:
...
}
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Text, pk=itemId)
response['data']['page'] = item.html()
response = json_response()
data = json.loads(request.POST['data'])
if data['id']:
public_id = data['id'].split(':')
username = public_id[0]
name = ":".join(public_id[1:])
text, created = models.Text.objects.get_or_create(name=name, user=models.User.objects.get(username=username))
if created:
text.user = request.user
else:
qs = models.Text.objects.filter(name='')
if qs.count() == 0:
if request.user.get_profile().capability('canEditFeaturedTexts'):
text = models.Text(name='', user=request.user)
text.save()
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
else:
text = qs[0]
if text.editable(request.user):
for key in data:
if key == 'status':
value = data[key]
if value not in text._status:
value = text._status[0]
if value == 'private':
for user in text.subscribed_users.all():
text.subscribed_users.remove(user)
qs = models.Position.objects.filter(user=request.user,
section='section', text=text)
if qs.count() > 1:
pos = qs[0]
pos.section = 'personal'
pos.save()
elif value == 'featured':
if request.user.get_profile().capability('canEditFeaturedTexts'):
pos, created = models.Position.objects.get_or_create(text=text, user=request.user,
section='featured')
if created:
qs = models.Position.objects.filter(user=request.user, section='featured')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
models.Position.objects.filter(text=text).exclude(id=pos.id).delete()
else:
value = text.status
elif text.status == 'featured' and value == 'public':
models.Position.objects.filter(text=text).delete()
pos, created = models.Position.objects.get_or_create(text=text,
user=text.user,section='personal')
qs = models.Position.objects.filter(user=text.user,
section='personal')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
for u in text.subscribed_users.all():
pos, created = models.Position.objects.get_or_create(text=text, user=u,
section='public')
qs = models.Position.objects.filter(user=u, section='public')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
text.status = value
elif key == 'name':
data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
name = data['name']
if not name:
name = "Untitled"
num = 1
while models.Text.objects.filter(name=name, user=text.user).exclude(id=text.id).count()>0:
num += 1
name = data['name'] + ' [%d]' % num
text.name = name
elif key == 'description':
text.description = ox.sanitize_html(data['description'])
elif key == 'text':
text.text = ox.sanitize_html(data['text'])
if 'position' in data:
pos, created = models.Position.objects.get_or_create(text=text, user=request.user)
pos.position = data['position']
pos.section = 'featured'
if text.status == 'private':
pos.section = 'personal'
pos.save()
if 'type' in data:
if data['type'] == 'pdf':
text.type = 'pdf'
else:
text.type = 'html'
if 'posterFrames' in data:
text.poster_frames = tuple(data['posterFrames'])
text.update_icon()
text.save()
response['data'] = text.json(user=request.user)
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
actions.register(editText, cache=False)
def findText(request):
def _order_query(qs, sort):
order_by = []
for e in sort:
operator = e['operator']
if operator != '-':
operator = ''
key = {
'subscribed': 'subscribed_users',
'items': 'numberofitems'
}.get(e['key'], e['key'])
order = '%s%s' % (operator, key)
order_by.append(order)
if key == 'subscribers':
qs = qs.annotate(subscribers=Sum('subscribed_users'))
if order_by:
qs = qs.order_by(*order_by)
qs = qs.distinct()
return qs
def parse_query(data, user):
query = {}
query['range'] = [0, 100]
query['sort'] = [{'key':'user', 'operator':'+'}, {'key':'name', 'operator':'+'}]
for key in ('keys', 'group', 'text', 'range', 'position', 'positions', 'sort'):
if key in data:
query[key] = data[key]
print data
query['qs'] = models.Text.objects.find(data, user).exclude(name='')
print query
return query
def findTexts(request):
'''
param data {
query: {
conditions: [
{
key: 'user',
value: 'something',
operator: '='
}
]
operator: ","
},
sort: [{key: 'name', operator: '+'}],
range: [0, 100]
keys: []
}
possible query keys:
name, user, featured, subscribed
possible keys:
name, user, featured, subscribed, query
}
return {status: {code: int, text: string},
data: {
items: [
{name:, user:, featured:, public...}
]
}
}
'''
response = json_response({})
data = json.loads(request.POST['data'])
query = parse_query(data, request.user)
#order
is_section_request = query['sort'] == [{u'operator': u'+', u'key': u'position'}]
def is_featured_condition(x):
return x['key'] == 'status' and \
x['value'] == 'featured' and \
x['operator'] in ('=', '==')
is_featured = len(filter(is_featured_condition, data['query'].get('conditions', []))) > 0
if is_section_request:
qs = query['qs']
if not is_featured and not request.user.is_anonymous():
qs = qs.filter(position__in=models.Position.objects.filter(user=request.user))
qs = qs.order_by('position__position')
else:
qs = _order_query(query['qs'], query['sort'])
response = json_response()
if 'keys' in data:
qs = qs[query['range'][0]:query['range'][1]]
response['data']['items'] = [l.json(data['keys'], request.user) for l in qs]
elif 'position' in data:
#FIXME: actually implement position requests
response['data']['position'] = 0
elif 'positions' in data:
ids = [i.get_id() for i in qs]
response['data']['positions'] = utils.get_positions(ids, query['positions'])
else:
response['data']['items'] = qs.count()
return render_to_json_response(response)
actions.register(findText)
actions.register(findTexts)
@login_required_json
def removeText(request):
'''
param data {
id: testId,
}
return {
status: {'code': int, 'text': string},
data: {
}
}
'''
data = json.loads(request.POST['data'])
text = get_text_or_404_json(data['id'])
response = json_response()
if text.editable(request.user):
text.delete()
else:
response = json_response(status=403, text='not allowed')
return render_to_json_response(response)
actions.register(removeText, cache=False)
@login_required_json
def subscribeToText(request):
'''
param data {
id: testId,
}
return {
status: {'code': int, 'text': string},
data: {
}
}
'''
data = json.loads(request.POST['data'])
text = get_text_or_404_json(data['id'])
user = request.user
if text.status == 'public' and \
text.subscribed_users.filter(username=user.username).count() == 0:
text.subscribed_users.add(user)
pos, created = models.Position.objects.get_or_create(text=text, user=user, section='public')
if created:
qs = models.Position.objects.filter(user=user, section='public')
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
response = json_response()
return render_to_json_response(response)
actions.register(subscribeToText, cache=False)
@login_required_json
def unsubscribeFromText(request):
'''
param data {
id: testId,
user: username(only admins)
}
return {
status: {'code': int, 'text': string},
data: {
}
}
'''
data = json.loads(request.POST['data'])
text = get_text_or_404_json(data['id'])
user = request.user
text.subscribed_users.remove(user)
models.Position.objects.filter(text=text, user=user, section='public').delete()
response = json_response()
return render_to_json_response(response)
actions.register(unsubscribeFromText, cache=False)
@login_required_json
def sortTexts(request):
'''
param data {
section: 'personal',
ids: [1,2,4,3]
}
known sections: 'personal', 'public', 'featured'
featured can only be edited by admins
return {
status: {'code': int, 'text': string},
data: {
}
}
'''
data = json.loads(request.POST['data'])
position = 0
section = data['section']
#ids = list(set(data['ids']))
ids = data['ids']
if section == 'featured' and not request.user.get_profile().capability('canEditFeaturedTexts'):
response = json_response(status=403, text='not allowed')
else:
user = request.user
if section == 'featured':
for i in ids:
l = get_text_or_404_json(i)
qs = models.Position.objects.filter(section=section, text=l)
if qs.count() > 0:
pos = qs[0]
else:
pos = models.Position(text=l, user=user, section=section)
if pos.position != position:
pos.position = position
pos.save()
position += 1
models.Position.objects.filter(section=section, text=l).exclude(id=pos.id).delete()
else:
for i in ids:
l = get_text_or_404_json(i)
pos, created = models.Position.objects.get_or_create(text=l,
user=request.user, section=section)
if pos.position != position:
pos.position = position
pos.save()
position += 1
response = json_response()
return render_to_json_response(response)
actions.register(sortTexts, cache=False)
def icon(request, id, size=16):
if not size:
size = 16
id = id.split(':')
username = id[0]
textname = ":".join(id[1:])
qs = models.Text.objects.filter(user__username=username, name=textname)
if qs.count() == 1 and qs[0].accessible(request.user):
text = qs[0]
icon = text.get_icon(int(size))
else:
icon = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
return HttpFileResponse(icon, content_type='image/jpeg')

View file

@ -28,6 +28,7 @@ urlpatterns = patterns('',
(r'^api/?$', include(ox.django.api.urls)),
(r'^resetUI$', 'user.views.reset_ui'),
(r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'itemlist.views.icon'),
(r'^text/(?P<id>.*?)/icon(?P<size>\d*).jpg$', 'text.views.icon'),
(r'^robots.txt$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'robots.txt'), 'content_type': 'text/plain'}),
(r'^favicon.ico$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'png/icon.16.png'), 'content_type': 'image/x-icon'}),
(r'^opensearch.xml$', 'app.views.opensearch_xml'),

View file

@ -14,6 +14,7 @@ from ox.django.fields import DictField
from ox.utils import json
from itemlist.models import List, Position
import text
import managers
import tasks
@ -261,6 +262,29 @@ def get_ui(user_ui, user=None):
'''
ids.append(id)
return ids
def add_texts(texts, section):
P = text.models.Position
ids = []
for t in texts:
qs = P.objects.filter(section=section)
if section == 'featured':
try:
pos = P.objects.get(text=t, section=section)
created = False
except P.DoesNotExist:
pos = P(text=t, section=section, user=l.user)
pos.save()
created = True
else:
pos, created = P.objects.get_or_create(text=t, user=user, section=section)
qs = qs.filter(user=user)
if created:
pos.position = qs.aggregate(Max('position'))['position__max'] + 1
pos.save()
ids.append(t.get_id())
return ids
ids = ['']
if user:
@ -270,6 +294,11 @@ def get_ui(user_ui, user=None):
for i in ui['lists'].keys():
if i not in ids:
del ui['lists'][i]
tids = ['']
if user:
tids += add_texts(user.texts.exclude(status="featured"), 'personal')
tids += add_texts(user.subscribed_texts.filter(status='public'), 'public')
tids += add_texts(text.models.Text.objects.filter(status='featured'), 'featured')
return ui
def init_user(user, request=None):

View file

@ -54,102 +54,106 @@ pandora.UI = (function() {
self.previousUI = Ox.clone(pandora.user.ui, true);
self.previousUI._list = pandora.getListState(self.previousUI.find);
if ('find' in args) {
// the challenge here is that find may change list,
// and list may then change listSort and listView,
// which we don't want to trigger, since find triggers
// (values we put in add will be changed, but won't trigger)
list = pandora.getListState(args.find);
pandora.user.ui._list = list;
pandora.user.ui._filterState = pandora.getFilterState(args.find);
pandora.user.ui._findState = pandora.getFindState(args.find);
if (pandora.$ui.appPanel && !pandora.stayInItemView) {
// if we're not on page load, and if find isn't a context change
// caused by an edit, then switch from item view to list view
args['item'] = '';
}
if (list != self.previousUI._list) {
Ox.Log('UI', 'FIND HAS CHANGED LIST')
// if find has changed list
Ox.forEach(listSettings, function(listSetting, setting) {
// then for each setting that corresponds to a list setting
if (!pandora.user.ui.lists[list]) {
// either add the default setting
add[setting] = pandora.site.user.ui[setting];
} else {
// or the existing list setting
add[setting] = pandora.user.ui.lists[list][listSetting]
}
});
}
add.itemFind = pandora.getItemFind(args.find);
if (args.section == 'texts') {
trigger['section'] = args['section'];
trigger['text'] = args['text'];
} else {
list = self.previousUI._list;
}
// it is important to check for find first, so that
// if find changes list, list is correct here
item = args.item || pandora.user.ui.item;
listView = add.listView || args.listView;
if (listView) {
if (pandora.isClipView(listView)) {
// when switching to a clip view, clear list selection
// (but don't trigger an additional event)
add.listSelection = [];
} else if (['text', 'position'].indexOf(pandora.user.ui.listSort[0].key) > -1) {
// when switching to a non-clip view, with a sort key
// that only exists in clip view, reset sort to default
args.listSort = pandora.site.user.ui.listSort;
if ('find' in args) {
// the challenge here is that find may change list,
// and list may then change listSort and listView,
// which we don't want to trigger, since find triggers
// (values we put in add will be changed, but won't trigger)
list = pandora.getListState(args.find);
pandora.user.ui._list = list;
pandora.user.ui._filterState = pandora.getFilterState(args.find);
pandora.user.ui._findState = pandora.getFindState(args.find);
if (pandora.$ui.appPanel && !pandora.stayInItemView) {
// if we're not on page load, and if find isn't a context change
// caused by an edit, then switch from item view to list view
args['item'] = '';
}
if (list != self.previousUI._list) {
Ox.Log('UI', 'FIND HAS CHANGED LIST')
// if find has changed list
Ox.forEach(listSettings, function(listSetting, setting) {
// then for each setting that corresponds to a list setting
if (!pandora.user.ui.lists[list]) {
// either add the default setting
add[setting] = pandora.site.user.ui[setting];
} else {
// or the existing list setting
add[setting] = pandora.user.ui.lists[list][listSetting]
}
});
}
add.itemFind = pandora.getItemFind(args.find);
} else {
list = self.previousUI._list;
}
}
// it is important to check for find first, so that
// if find changes list, list is correct here
item = args.item || pandora.user.ui.item;
listView = add.listView || args.listView;
if (!pandora.user.ui.lists[list]) {
add['lists.' + that.encode(list)] = {};
}
Ox.forEach(listSettings, function(listSetting, setting) {
// for each setting that corresponds to a list setting
// set that list setting to
var key = 'lists.' + that.encode(list) + '.' + listSetting;
if (setting in args) {
// the setting passed to UI.set
add[key] = args[setting];
} else if (setting in add) {
// or the setting changed via find
add[key] = add[setting];
} else if (!pandora.user.ui.lists[list]) {
// or the default setting
add[key] = pandora.site.user.ui[setting];
if (listView) {
if (pandora.isClipView(listView)) {
// when switching to a clip view, clear list selection
// (but don't trigger an additional event)
add.listSelection = [];
} else if (['text', 'position'].indexOf(pandora.user.ui.listSort[0].key) > -1) {
// when switching to a non-clip view, with a sort key
// that only exists in clip view, reset sort to default
args.listSort = pandora.site.user.ui.listSort;
}
}
});
if (args.item) {
// when switching to an item, update list selection
add['listSelection'] = [args.item];
add['lists.' + that.encode(list) + '.selection'] = [args.item];
if (
!args.itemView
&& ['timeline', 'player', 'editor'].indexOf(pandora.user.ui.itemView) > -1
&& !pandora.user.ui.videoPoints[item]
&& !args['videoPoints.' + item]
) {
// if the item view doesn't change, remains a video view,
// video points don't exist yet, and won't be set,
// add default video points
add['videoPoints.' + item] = {annotation: '', 'in': 0, out: 0, position: 0};
if (!pandora.user.ui.lists[list]) {
add['lists.' + that.encode(list)] = {};
}
}
Ox.forEach(listSettings, function(listSetting, setting) {
// for each setting that corresponds to a list setting
// set that list setting to
var key = 'lists.' + that.encode(list) + '.' + listSetting;
if (setting in args) {
// the setting passed to UI.set
add[key] = args[setting];
} else if (setting in add) {
// or the setting changed via find
add[key] = add[setting];
} else if (!pandora.user.ui.lists[list]) {
// or the default setting
add[key] = pandora.site.user.ui[setting];
}
});
if (['timeline', 'player', 'editor'].indexOf(args.itemView) > -1) {
// when switching to a video view, add it as default video view
args.videoView = args.itemView;
if (
!pandora.user.ui.videoPoints[item]
&& !args['videoPoints.' + item]
) {
// if video points don't exist yet, and won't be set,
// add default video points
add['videoPoints.' + item] = {annotation: '', 'in': 0, out: 0, position: 0};
if (args.item) {
// when switching to an item, update list selection
add['listSelection'] = [args.item];
add['lists.' + that.encode(list) + '.selection'] = [args.item];
if (
!args.itemView
&& ['timeline', 'player', 'editor'].indexOf(pandora.user.ui.itemView) > -1
&& !pandora.user.ui.videoPoints[item]
&& !args['videoPoints.' + item]
) {
// if the item view doesn't change, remains a video view,
// video points don't exist yet, and won't be set,
// add default video points
add['videoPoints.' + item] = {annotation: '', 'in': 0, out: 0, position: 0};
}
}
if (['timeline', 'player', 'editor'].indexOf(args.itemView) > -1) {
// when switching to a video view, add it as default video view
args.videoView = args.itemView;
if (
!pandora.user.ui.videoPoints[item]
&& !args['videoPoints.' + item]
) {
// if video points don't exist yet, and won't be set,
// add default video points
add['videoPoints.' + item] = {annotation: '', 'in': 0, out: 0, position: 0};
}
}
}
@ -187,7 +191,7 @@ pandora.UI = (function() {
});
});
});
pandora.URL.update(Object.keys(
!pandora.$ui.appPanel ? args : trigger
));

View file

@ -16,17 +16,18 @@ pandora.URL = (function() {
var state = {};
state.type = pandora.site.itemsSection;
state.type = pandora.user.ui.section == 'items' ? pandora.site.itemsSection : pandora.user.ui.section;
state.item = pandora.user.ui[pandora.user.ui.section.slice(0, -1)];
state.item = pandora.user.ui.item;
if (!pandora.user.ui.item) {
state.view = pandora.user.ui.listView;
state.sort = pandora.user.ui.listSort;
state.find = pandora.user.ui.find;
} else {
state.view = pandora.user.ui.itemView;
state.sort = pandora.user.ui.itemSort;
if(pandora.user.ui.section == 'items') {
if (!pandora.user.ui.item) {
state.view = pandora.user.ui.listView;
state.sort = pandora.user.ui.listSort;
state.find = pandora.user.ui.find;
} else {
state.view = pandora.user.ui.itemView;
state.sort = pandora.user.ui.itemSort;
}
}
if (state.view == 'map') {
@ -79,56 +80,58 @@ pandora.URL = (function() {
var set = {
section: state.type == pandora.site.itemsSection ? 'items' : state.type,
item: state.item,
page: ''
};
set[set.section.slice(0, -1)] = state.item;
if (state.view) {
set[!state.item ? 'listView' : 'itemView'] = state.view;
}
if (state.span) {
if (['timeline', 'player', 'editor'].indexOf(state.view) > -1) {
if (Ox.isArray(state.span)) {
set['videoPoints.' + state.item] = {
annotation: '',
'in': state.span[state.span.length - 2] || 0,
out: state.span.length == 1 ? 0 : Math.max(
state.span[state.span.length - 2],
state.span[state.span.length - 1]
),
position: state.span[0]
};
} else {
set['videoPoints.' + state.item + '.annotation'] = state.span;
}
} else if (state.view == 'map') {
// fixme: this doesn't handle map coordinates
if (state.span[0] != '@') {
//pandora.user.ui.mapSelection = state.span;
set['mapSelection'] = state.span;
set['mapFind'] = '';
} else {
//pandora.user.ui.mapFind = state.span.slice(1);
set['mapFind'] = state.span.slice(1);
set['mapSelection'] = '';
}
} else if (state.view == 'calendar') {
// ...
if (set.section == 'items') {
if (state.view) {
set[!state.item ? 'listView' : 'itemView'] = state.view;
}
}
if (state.sort) {
set[!state.item ? 'listSort' : 'itemSort'] = state.sort;
}
if (state.span) {
if (['timeline', 'player', 'editor'].indexOf(state.view) > -1) {
if (Ox.isArray(state.span)) {
set['videoPoints.' + state.item] = {
annotation: '',
'in': state.span[state.span.length - 2] || 0,
out: state.span.length == 1 ? 0 : Math.max(
state.span[state.span.length - 2],
state.span[state.span.length - 1]
),
position: state.span[0]
};
} else {
set['videoPoints.' + state.item + '.annotation'] = state.span;
}
} else if (state.view == 'map') {
// fixme: this doesn't handle map coordinates
if (state.span[0] != '@') {
//pandora.user.ui.mapSelection = state.span;
set['mapSelection'] = state.span;
set['mapFind'] = '';
} else {
//pandora.user.ui.mapFind = state.span.slice(1);
set['mapFind'] = state.span.slice(1);
set['mapSelection'] = '';
}
} else if (state.view == 'calendar') {
// ...
}
}
if (!state.item) {
if (state.find) {
set.find = state.find;
} else if (!pandora.$ui.appPanel) {
// when loading results without find, clear find, so that
// removing a query and reloading works as expected
set.find = pandora.site.user.ui.find;
if (state.sort) {
set[!state.item ? 'listSort' : 'itemSort'] = state.sort;
}
if (!state.item) {
if (state.find) {
set.find = state.find;
} else if (!pandora.$ui.appPanel) {
// when loading results without find, clear find, so that
// removing a query and reloading works as expected
set.find = pandora.site.user.ui.find;
}
}
}
@ -246,6 +249,19 @@ pandora.URL = (function() {
calendar: 'date'
}
};
//Text
views['texts'] = {
list: [],
item: ['text']
}
spanType['texts'] = {
list: [],
item: {}
}
sortKeys['texts'] = {
list: {},
item: {}
}
findKeys = [{id: 'list', type: 'string'}].concat(pandora.site.itemKeys);

View file

@ -12,7 +12,11 @@ pandora.ui.allItems = function() {
.on({
click: function() {
that.gainFocus();
pandora.user.ui._list && pandora.UI.set('find', {conditions: [], operator: '&'});
if (pandora.user.ui.section == 'items') {
pandora.user.ui._list && pandora.UI.set('find', {conditions: [], operator: '&'});
} else {
pandora.UI.set(pandora.user.ui.section.slice(0, -1), '');
}
}
})
.bindEvent({
@ -33,35 +37,38 @@ pandora.ui.allItems = function() {
overflow: 'hidden',
whiteSpace: 'nowrap'
})
.html('All ' + pandora.site.itemName.plural)
.appendTo(that),
$items = $('<div>')
.css({
float: 'left',
width: '42px',
margin: '1px 4px 1px 3px',
textAlign: 'right'
})
.appendTo(that),
$clickButton = Ox.Button({
style: 'symbol',
title: 'click',
type: 'image'
})
.css({opacity: 0.25})
.appendTo(that),
$uploadButton = Ox.Button({
style: 'symbol',
title: 'upload',
type: 'image'
})
.html(pandora.user.ui.section == 'items' ? 'All ' + pandora.site.itemName.plural
: pandora.site.site.name + ' ' + Ox.toTitleCase(pandora.user.ui.section))
.appendTo(that);
pandora.api.find({
query: {conditions: [], operator: '&'}
}, function(result) {
that.update(result.data.items);
});
if (pandora.user.ui.section == 'items') {
var $items = $('<div>')
.css({
float: 'left',
width: '42px',
margin: '1px 4px 1px 3px',
textAlign: 'right'
})
.appendTo(that),
$clickButton = Ox.Button({
style: 'symbol',
title: 'click',
type: 'image'
})
.css({opacity: 0.25})
.appendTo(that),
$uploadButton = Ox.Button({
style: 'symbol',
title: 'upload',
type: 'image'
})
.appendTo(that);
pandora.api.find({
query: {conditions: [], operator: '&'}
}, function(result) {
that.update(result.data.items);
});
}
that.update = function(items) {
$items.html(Ox.formatNumber(items));

View file

@ -3,6 +3,9 @@
'use strict';
pandora.ui.deleteListDialog = function(list) {
var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1);
var listData = pandora.getListData(list),
$folderList = pandora.$ui.folderList[listData.folder],
@ -10,7 +13,7 @@ pandora.ui.deleteListDialog = function(list) {
buttons: [
Ox.Button({
id: 'keep',
title: 'Keep List'
title: 'Keep ' + folderItem
}).bindEvent({
click: function() {
that.close();
@ -18,23 +21,27 @@ pandora.ui.deleteListDialog = function(list) {
}),
Ox.Button({
id: 'delete',
title: 'Delete List'
title: 'Delete ' + folderItem
}).bindEvent({
click: function() {
that.close();
pandora.api.removeList({
pandora.api['remove' + folderItem]({
id: listData.id
}, function(result) {
Ox.Request.clearCache('findLists');
Ox.Request.clearCache('find' + folderItems);
Ox.Request.clearCache(listData.id);
$folderList
.options({selected: []})
.bindEventOnce({
load: function() {
pandora.UI.set('lists.' + listData.id, null);
pandora.UI.set({
find: pandora.site.user.ui.find
});
if (ui.section == 'items') {
pandora.UI.set('lists.' + listData.id, null);
pandora.UI.set({
find: pandora.site.user.ui.find
});
} else {
pandora.UI.set(folderItem.toLowerCase(), '');
}
}
})
.reloadList();
@ -51,7 +58,7 @@ pandora.ui.deleteListDialog = function(list) {
.append(
$('<div>')
.css({position: 'absolute', left: '96px', top: '16px', width: '192px'})
.html('Are you sure you want to delete the list "' + listData.name + '"?')
.html('Are you sure you want to delete the ' + folderItem.toLowerCase() + ' "' + listData.name + '"?')
),
height: 128,
keys: {enter: 'delete', escape: 'keep'},
@ -61,4 +68,4 @@ pandora.ui.deleteListDialog = function(list) {
return that;
}
}

View file

@ -1,7 +1,10 @@
// vim: et:ts=4:sw=4:sts=4:ft=javascript
'use strict';
pandora.ui.folderBrowserBar = function(id) {
var that = Ox.Bar({
var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1),
that = Ox.Bar({
size: 24
});
pandora.$ui.findListElement[id] = Ox.FormElementGroup({
@ -9,7 +12,7 @@ pandora.ui.folderBrowserBar = function(id) {
pandora.$ui.findListSelect[id] = Ox.Select({
items: [
{id: 'user', title: 'Find: User'},
{id: 'name', title: 'Find: List'}
{id: 'name', title: 'Find: ' + folderItem}
],
overlap: 'right',
type: 'image'
@ -56,7 +59,7 @@ pandora.ui.folderBrowserBar = function(id) {
{key: 'status', value: 'private', operator: '!='},
{key: key, value: value, operator: '='}
], operator: '&'};
return pandora.api.findLists(Ox.extend(data, {
return pandora.api['find' + folderItems](Ox.extend(data, {
query: query
}), callback);
}

View file

@ -3,8 +3,11 @@
pandora.ui.folderBrowserList = function(id) {
// fixme: user and name are set to the same width here,
// but resizeFolders will set them to different widths
var columnWidth = (pandora.user.ui.sidebarSize - Ox.UI.SCROLLBAR_SIZE - 96) / 2,
i = Ox.getIndexById(pandora.site.sectionFolders[pandora.user.ui.section], id),
var ui = pandora.user.ui,
columnWidth = (ui.sidebarSize - Ox.UI.SCROLLBAR_SIZE - (ui.section == 'items' ? 96 : 32)) / 2,
i = Ox.getIndexById(pandora.site.sectionFolders[ui.section], id),
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1),
that = Ox.TableList({
columns: [
{
@ -52,7 +55,7 @@ pandora.ui.folderBrowserList = function(id) {
},
id: 'name',
operator: '+',
title: 'List',
title: folderItem,
visible: true,
width: Math.ceil(columnWidth)
},
@ -62,7 +65,7 @@ pandora.ui.folderBrowserList = function(id) {
format: {type: 'number'},
operator: '-',
title: 'Items',
visible: true,
visible: ui.section == 'items',
width: 48
},
{
@ -72,7 +75,7 @@ pandora.ui.folderBrowserList = function(id) {
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL(value == 'static' ? 'symbolClick' : 'symbolFind')
src: Ox.UI.getImageURL(value == 'smart' ? 'symbolFind' : value == 'pdf' ? 'symbolFiles' : value == 'html' ? 'symbolFile' : 'symbolClick')
})
.css({
width: '10px',
@ -89,7 +92,7 @@ pandora.ui.folderBrowserList = function(id) {
? (data.user == pandora.user.username ? 'Edit Query' : 'Show Query')
: (data.user == pandora.user.username ? 'Edit Default View' : 'Default View: ...');
},
visible: true,
visible: ui.section == 'items',
width: 16
},
{
@ -116,7 +119,7 @@ pandora.ui.folderBrowserList = function(id) {
tooltip: function(data) {
var checked = id == 'favorite' ? data.subscribed : data.status == 'featured';
return (checked ? 'Remove from' : 'Add to')
+ ' ' + Ox.toTitleCase(id) + ' Lists';
+ ' ' + Ox.toTitleCase(id) + ' ' + folderItems;
},
visible: true,
width: 16
@ -130,7 +133,7 @@ pandora.ui.folderBrowserList = function(id) {
], operator: '&'} : {conditions: [
{key: 'status', value: 'private', operator: '!='}
], operator: ''};
return pandora.api.findLists(Ox.extend(data, {
return pandora.api['find' + folderItems](Ox.extend(data, {
query: query
}), callback);
},
@ -139,7 +142,7 @@ pandora.ui.folderBrowserList = function(id) {
// not-featured list may be in the user's favorites folder
keys: id == 'featured' ? ['subscribed'] : [],
pageLength: 1000,
selected: pandora.getListData().folder == id ? [pandora.user.ui._list] : [],
selected: pandora.getListData().folder == id ? [ui.section == 'items' ? ui._list : ui[ui.section.slice(0, -1)]] : [],
sort: [{key: 'name', operator: '+'}],
unique: 'id'
})
@ -153,19 +156,19 @@ pandora.ui.folderBrowserList = function(id) {
*/
} else if (data.key == 'subscribed') {
var subscribed = that.value(data.id, 'subscribed');
pandora.api[subscribed ? 'unsubscribeFromList' : 'subscribeToList']({
pandora.api[subscribed ? 'unsubscribeFrom' + folderItem : 'subscribeTo' + folderItem]({
id: data.id
}, function(result) {
that.value(data.id, 'subscribed', !subscribed);
});
} else if (data.key == 'status') {
pandora.api.editList({
pandora.api['edit' + folderItem]({
id: data.id,
status: that.value(data.id, 'status') == 'featured' ? 'public' : 'featured'
}, function(result) {
Ox.Log('', 'result', result)
if (result.data.user == pandora.user.username || result.data.subscribed) {
Ox.Request.clearCache(); // fixme: remove
Ox.Request.clearCache(); // fixme: removen
pandora.$ui.folderList[
result.data.user == pandora.user.username ? 'personal' : 'favorite'
].reloadList();
@ -175,7 +178,7 @@ pandora.ui.folderBrowserList = function(id) {
}
},
init: function(data) {
pandora.site.sectionFolders[pandora.user.ui.section][i].items = data.items;
pandora.site.sectionFolders[ui.section][i].items = data.items;
pandora.$ui.folder[i].$content.css({
height: 40 + data.items * 16 + 'px'
});
@ -185,7 +188,9 @@ pandora.ui.folderBrowserList = function(id) {
pandora.resizeFolders();
},
paste: function(data) {
pandora.$ui.list.triggerEvent('paste', data);
if (ui.section == 'items') {
pandora.$ui.list.triggerEvent('paste', data);
}
},
select: function(data) {
// fixme: duplicated
@ -195,14 +200,18 @@ pandora.ui.folderBrowserList = function(id) {
id != id_ && $list.options('selected', []);
});
}
pandora.UI.set({
find: {
conditions: list ? [
{key: 'list', value: data.ids[0], operator: '=='}
] : [],
operator: '&'
}
});
if (ui.section == 'items') {
pandora.UI.set({
find: {
conditions: list ? [
{key: 'list', value: data.ids[0], operator: '=='}
] : [],
operator: '&'
}
});
} else {
pandora.UI.set(ui.section.slice(0, -1), list);
}
}
});
return that;

View file

@ -1,382 +1,386 @@
// vim: et:ts=4:sw=4:sts=4:ft=javascript
'use strict';
pandora.ui.folderList = function(id) {
var i = Ox.getIndexById(pandora.site.sectionFolders[pandora.user.ui.section], id),
canEditFeaturedLists = pandora.site.capabilities.canEditFeaturedLists[pandora.user.level],
var ui = pandora.user.ui,
i = Ox.getIndexById(pandora.site.sectionFolders[ui.section], id),
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1),
canEditFeatured = pandora.site.capabilities['canEditFeatured' + folderItems][pandora.user.level],
that;
if (pandora.user.ui.section == 'items') {
var columns, items;
if (id != 'volumes') {
columns = [
{
clickable: function(data) {
return data.user == pandora.user.username || (id == 'featured' && canEditFeaturedLists);
},
format: function(value, data) {
return $('<img>').attr({
src: '/list/' + data.id + '/icon.jpg'
}).css({
width: '14px',
height: '14px',
borderRadius: '4px',
margin: '0 0 0 -3px'
});
},
id: 'user',
operator: '+',
tooltip: function(data) {
return data.user == pandora.user.username
|| (id == 'featured' && canEditFeaturedLists)
? 'Edit Icon'
: '';
},
visible: true,
width: 16
var columns, items;
if (id != 'volumes') {
columns = [
{
clickable: function(data) {
return data.user == pandora.user.username || (id == 'featured' && canEditFeatured);
},
{
format: function(value) {
return Ox.encodeHTMLEntities(value.split(':').join(': '));
},
id: 'id',
operator: '+',
visible: id == 'favorite',
// fixme: user and name are set to the same width here,
// but resizeFolders will set them to different widths
width: pandora.user.ui.sidebarWidth - 96
},
{
editable: function(data) {
return data.user == pandora.user.username;
},
format: function(value) {
return Ox.encodeHTMLEntities(value);
},
id: 'name',
input: {
autovalidate: pandora.ui.autovalidateListname
},
operator: '+',
tooltip: id == 'personal' ? 'Edit Title' : '',
unformat: function(value) {
return Ox.decodeHTMLEntities(value);
},
visible: id != 'favorite',
width: pandora.user.ui.sidebarWidth - 96
},
{
align: 'right',
id: 'items',
format: {type: 'number'},
operator: '-',
visible: true,
width: 48
},
{
clickable: function(data) {
return data.type == 'smart' || data.user == pandora.user.username;
},
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL(value == 'static' ? 'symbolClick' : 'symbolFind')
})
.css({
width: '10px',
height: '10px',
padding: '3px',
opacity: data.user == pandora.user.username ? 1 : 0.25
});
},
id: 'type',
operator: '+',
tooltip: function(data) {
return data.type == 'smart'
? (data.user == pandora.user.username ? 'Edit Query' : 'Show Query')
: (data.user == pandora.user.username ? 'Edit Default View' : 'Default View: ...');
},
visible: true,
width: 16
},
{
clickable: id == 'personal',
format: function(value) {
var symbols = {personal: 'Publish', favorite: 'Like', featured: 'Star'};
return $('<img>')
.attr({
src: Ox.UI.getImageURL(
'symbol' + symbols[id]
)
})
.css({
width: '10px',
height: '10px',
padding: '3px',
opacity: value == 'private' ? 0.25 : 1
});
},
id: 'status',
operator: '+',
tooltip: id == 'personal' ? function(data) {
return data.status == 'private' ? 'Make Public' : 'Make Private';
} : null,
visible: true,
width: 16
}
];
items = function(data, callback) {
var query;
if (id == 'personal') {
query = {conditions: [
{key: 'user', value: pandora.user.username, operator: '=='},
{key: 'status', value: 'featured', operator: '!='}
], operator: '&'};
} else if (id == 'favorite') {
query = {conditions: [
{key: 'subscribed', value: true, operator: '='},
{key: 'status', value: 'featured', operator: '!='}
], operator: '&'};
} else if (id == 'featured') {
query = {conditions: [
{key: 'status', value: 'featured', operator: '='} // fixme: '==' performs better
], operator: '&'};
}
return pandora.api.findLists(Ox.extend(data, {
query: query
}), callback);
};
} else {
columns = [
{
format: function() {
return $('<img>').attr({
src: Ox.UI.getImageURL('symbolVolume')
}).css({
width: '10px',
height: '10px',
padding: '3px'
});
},
id: 'user',
operator: '+',
visible: true,
width: 16
},
{
editable: true,
id: 'name',
operator: '+',
tooltip: 'Edit Title',
visible: true,
width: pandora.user.ui.sidebarWidth - 96
},
{
align: 'right',
id: 'items',
format: {type: 'number'},
operator: '-',
visible: true,
width: 48
},
{
clickable: function(data) {
return data.mounted;
},
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL(data.mounted ? 'symbolSync' : 'symbolEdit')
})
.css({
width: '10px',
height: '10px',
padding: '3px'
});
},
id: 'path',
operator: '+',
tooltip: function(data) {
return data.mounted ? 'Scan Volume' : 'Edit Path';
},
visible: true,
width: 16
},
{
clickable: true,
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL('symbolMount')
})
.css({
width: '10px',
height: '10px',
padding: '3px 2px 1px 2px',
opacity: data.mounted ? 1 : 0.25
});
},
id: 'mounted',
operator: '+',
tooltip: function(data) {
return data.mounted ? 'Unmount Volume' : 'Mount Volume';
},
visible: true,
width: 16
}
];
items = function(data, callback) {
var volumes = pandora.user.volumes || [];
if (!data.keys) {
data = {items: volumes.length};
} else {
data = {items: volumes.map(function(volume) {
return Ox.extend({id: volume.name, user: pandora.user.username}, volume);
})};
}
// fixme: ridiculous (we're binding to init too late)
setTimeout(function() {
callback({data: data});
}, 1000);
};
}
that = Ox.TableList({
columns: columns,
items: items,
keys: ['query'],
max: 1,
min: 0,
pageLength: 1000,
//selected: pandora.getListData().folder == id ? [pandora.user.ui._list] : [],
sort: [{key: 'position', operator: '+'}],
sortable: id != 'featured' || canEditFeaturedLists,
unique: id != 'volumes' ? 'id' : 'name'
})
.css({
left: 0,
top: 0,
width: pandora.user.ui.sidebarWidth + 'px'
})
.bindEvent({
add: function(event) {
// fixme: this is duplicated,
// see folder collapse panel menu handler
var i = ['personal', 'favorite', 'featured'].indexOf(id);
if (id == 'personal') {
if (event.keys == '' || event.keys == 'alt') {
pandora.api.addList({
name: 'Untitled',
status: 'private',
type: event.keys == '' ? 'static' : 'smart'
}, function(result) {
var id = result.data.id;
pandora.UI.set({
find: {
conditions: [{key: 'list', value: id, operator: '=='}],
operator: '&'
}
});
Ox.Request.clearCache(); // fixme: remove
that.reloadList().bindEventOnce({
load: function(data) {
that.gainFocus()
.options({selected: [id]})
.editCell(id, 'name');
}
});
format: function(value, data) {
return $('<img>').attr({
src: '/' + folderItem.toLowerCase() + '/' + data.id + '/icon.jpg'
}).css({
width: '14px',
height: '14px',
borderRadius: '4px',
margin: '0 0 0 -3px'
});
}
} else if (id == 'favorite' || (id == 'featured' && canEditFeaturedLists)) {
// this makes the button trigger a change event,
// which is already being handled in folders.js
pandora.$ui.manageListsButton[id].options({value: true});
/*
if (!pandora.site.sectionFolders.items[i].showBrowser) {
pandora.site.sectionFolders.items[i].showBrowser = true;
pandora.$ui.manageListsButton[id].options({selected: true});
pandora.$ui.folderList[id].replaceWith(
pandora.$ui.folderBrowser[id] = pandora.ui.folderBrowser(id)
);
}
*/
}
},
id: 'user',
operator: '+',
tooltip: function(data) {
return data.user == pandora.user.username
|| (id == 'featured' && canEditFeatured)
? 'Edit Icon'
: '';
},
visible: true,
width: 16
},
click: function(data) {
//var $list = pandora.$ui.folderList[id];
if (data.key == 'user') {
pandora.$ui.listDialog = pandora.ui.listDialog('icon').open();
} else if (data.key == 'type') {
if (that.value(data.id, 'type') == 'smart') {
pandora.$ui.listDialog = pandora.ui.listDialog('query').open();
}
} else if (data.key == 'status') {
var status = that.value(data.id, data.key) == 'private' ? 'public' : 'private';
pandora.changeListStatus(data.id, status, function(result) {
that.value(result.data.id, 'status', result.data.status);
});
} else if (data.key == 'path') {
} else if (data.key == 'mounted') {
alert(JSON.stringify(data));
}
{
format: function(value) {
return Ox.encodeHTMLEntities(value.split(':').join(': '));
},
id: 'id',
operator: '+',
visible: id == 'favorite',
// fixme: user and name are set to the same width here,
// but resizeFolders will set them to different widths
width: ui.sidebarWidth - 96
},
'delete': function(data) {
if (id == 'personal') {
pandora.ui.deleteListDialog(data.ids[0]).open();
} else if (id == 'favorite') {
that.options({selected: []});
pandora.api.unsubscribeFromList({
id: data.ids[0]
{
editable: function(data) {
return data.user == pandora.user.username;
},
format: function(value) {
return Ox.encodeHTMLEntities(value);
},
id: 'name',
input: {
autovalidate: pandora.ui.autovalidateListname
},
operator: '+',
tooltip: id == 'personal' ? 'Edit Title' : '',
unformat: function(value) {
return Ox.decodeHTMLEntities(value);
},
visible: id != 'favorite',
width: ui.sidebarWidth - 96
},
{
align: 'right',
id: 'items',
format: {type: 'number'},
operator: '-',
visible: true,
width: 48
},
{
clickable: function(data) {
return data.type == 'smart' || data.user == pandora.user.username;
},
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL(value == 'smart' ? 'symbolFind' : value == 'pdf' ? 'symbolFiles' : value == 'html' ? 'symbolFile' : 'symbolClick')
})
.css({
width: '10px',
height: '10px',
padding: '3px',
opacity: data.user == pandora.user.username ? 1 : 0.25
});
},
id: 'type',
operator: '+',
tooltip: function(data) {
return data.type == 'smart'
? (data.user == pandora.user.username ? 'Edit Query' : 'Show Query')
: (data.user == pandora.user.username ? 'Edit Default View' : 'Default View: ...');
},
visible: true,
width: 16
},
{
clickable: id == 'personal',
format: function(value) {
var symbols = {personal: 'Publish', favorite: 'Like', featured: 'Star'};
return $('<img>')
.attr({
src: Ox.UI.getImageURL(
'symbol' + symbols[id]
)
})
.css({
width: '10px',
height: '10px',
padding: '3px',
opacity: value == 'private' ? 0.25 : 1
});
},
id: 'status',
operator: '+',
tooltip: id == 'personal' ? function(data) {
return data.status == 'private' ? 'Make Public' : 'Make Private';
} : null,
visible: true,
width: 16
}
];
items = function(data, callback) {
var query;
if (id == 'personal') {
query = {conditions: [
{key: 'user', value: pandora.user.username, operator: '=='},
{key: 'status', value: 'featured', operator: '!='}
], operator: '&'};
} else if (id == 'favorite') {
query = {conditions: [
{key: 'subscribed', value: true, operator: '='},
{key: 'status', value: 'featured', operator: '!='}
], operator: '&'};
} else if (id == 'featured') {
query = {conditions: [
{key: 'status', value: 'featured', operator: '='} // fixme: '==' performs better
], operator: '&'};
}
return pandora.api['find' + folderItems](Ox.extend(data, {
query: query
}), callback);
};
} else {
columns = [
{
format: function() {
return $('<img>').attr({
src: Ox.UI.getImageURL('symbolVolume')
}).css({
width: '10px',
height: '10px',
padding: '3px'
});
},
id: 'user',
operator: '+',
visible: true,
width: 16
},
{
editable: true,
id: 'name',
operator: '+',
tooltip: 'Edit Title',
visible: true,
width: ui.sidebarWidth - 96
},
{
align: 'right',
id: 'items',
format: {type: 'number'},
operator: '-',
visible: true,
width: 48
},
{
clickable: function(data) {
return data.mounted;
},
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL(data.mounted ? 'symbolSync' : 'symbolEdit')
})
.css({
width: '10px',
height: '10px',
padding: '3px'
});
},
id: 'path',
operator: '+',
tooltip: function(data) {
return data.mounted ? 'Scan Volume' : 'Edit Path';
},
visible: true,
width: 16
},
{
clickable: true,
format: function(value, data) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL('symbolMount')
})
.css({
width: '10px',
height: '10px',
padding: '3px 2px 1px 2px',
opacity: data.mounted ? 1 : 0.25
});
},
id: 'mounted',
operator: '+',
tooltip: function(data) {
return data.mounted ? 'Unmount Volume' : 'Mount Volume';
},
visible: true,
width: 16
}
];
items = function(data, callback) {
var volumes = pandora.user.volumes || [];
if (!data.keys) {
data = {items: volumes.length};
} else {
data = {items: volumes.map(function(volume) {
return Ox.extend({id: volume.name, user: pandora.user.username}, volume);
})};
}
// fixme: ridiculous (we're binding to init too late)
setTimeout(function() {
callback({data: data});
}, 1000);
};
}
that = Ox.TableList({
columns: columns,
items: items,
keys: ui.section == 'items' ? ['query'] : [],
max: 1,
min: 0,
pageLength: 1000,
//selected: pandora.getListData().folder == id ? [ui._list] : [],
sort: [{key: 'position', operator: '+'}],
sortable: id != 'featured' || canEditFeatured,
unique: id != 'volumes' ? 'id' : 'name'
})
.css({
left: 0,
top: 0,
width: ui.sidebarWidth + 'px'
})
.bindEvent({
add: function(event) {
// fixme: this is duplicated,
// see folder collapse panel menu handler
var i = ['personal', 'favorite', 'featured'].indexOf(id);
if (id == 'personal') {
if (event.keys == '' || event.keys == 'alt') {
pandora.api.addList({
name: 'Untitled',
status: 'private',
type: event.keys == '' ? 'static' : 'smart'
}, function(result) {
var id = result.data.id;
pandora.UI.set({
find: {
conditions: [{key: 'list', value: id, operator: '=='}],
operator: '&'
}
});
Ox.Request.clearCache(); // fixme: remove
that.reloadList();
});
} else if (id == 'featured' && canEditFeaturedLists) {
that.options({selected: []});
pandora.api.editList({
id: data.ids[0],
status: 'public'
}, function(result) {
// fixme: duplicated
if (result.data.user == pandora.user.username || result.data.subscribed) {
Ox.Request.clearCache(); // fixme: remove
pandora.$ui.folderList[
result.data.user == pandora.user.username ? 'personal' : 'favorite'
].reloadList();
}
that.reloadList();
that.reloadList().bindEventOnce({
load: function(data) {
that.gainFocus()
.options({selected: [id]})
.editCell(id, 'name');
}
});
});
}
},
/*
edit: function() {
pandora.ui.listDialog().open();
},
*/
init: function(data) {
pandora.site.sectionFolders[pandora.user.ui.section][i].items = data.items;
pandora.$ui.folder[i].$content.css({
height: data.items * 16 + 'px'
});
pandora.$ui.folderList[id].css({
height: data.items * 16 + 'px'
});
pandora.resizeFolders();
},
move: function(data) {
pandora.api.sortLists({
section: id,
ids: data.ids
});
},
paste: function(data) {
pandora.$ui.list.triggerEvent('paste', data);
},
select: function(data) {
var list = data.ids.length ? data.ids[0] : '';
if (list) {
Ox.forEach(pandora.$ui.folderList, function($list, id_) {
id != id_ && $list.options('selected', []);
});
} else if (id == 'favorite' || (id == 'featured' && canEditFeatured)) {
// this makes the button trigger a change event,
// which is already being handled in folders.js
pandora.$ui.manageListsButton[id].options({value: true});
/*
if (!pandora.site.sectionFolders.items[i].showBrowser) {
pandora.site.sectionFolders.items[i].showBrowser = true;
pandora.$ui.manageListsButton[id].options({selected: true});
pandora.$ui.folderList[id].replaceWith(
pandora.$ui.folderBrowser[id] = pandora.ui.folderBrowser(id)
);
}
*/
}
},
click: function(data) {
//var $list = pandora.$ui.folderList[id];
if (data.key == 'user') {
pandora.$ui.listDialog = pandora.ui.listDialog('icon').open();
} else if (data.key == 'type') {
if (that.value(data.id, 'type') == 'smart') {
pandora.$ui.listDialog = pandora.ui.listDialog('query').open();
}
} else if (data.key == 'status') {
var status = that.value(data.id, data.key) == 'private' ? 'public' : 'private';
pandora.changeFolderItemStatus(data.id, status, function(result) {
that.value(result.data.id, 'status', result.data.status);
});
} else if (data.key == 'path') {
} else if (data.key == 'mounted') {
alert(JSON.stringify(data));
}
},
'delete': function(data) {
if (id == 'personal') {
pandora.ui.deleteListDialog(data.ids[0]).open();
} else if (id == 'favorite') {
that.options({selected: []});
pandora.api['unsubscribeFrom' + folderItem]({
id: data.ids[0]
}, function(result) {
Ox.Request.clearCache(); // fixme: remove
that.reloadList();
});
} else if (id == 'featured' && canEditFeatured) {
that.options({selected: []});
pandora.api['edit' + folderItem]({
id: data.ids[0],
status: 'public'
}, function(result) {
// fixme: duplicated
if (result.data.user == pandora.user.username || result.data.subscribed) {
Ox.Request.clearCache(); // fixme: remove
pandora.$ui.folderList[
result.data.user == pandora.user.username ? 'personal' : 'favorite'
].reloadList();
}
that.reloadList();
});
}
},
/*
edit: function() {
pandora.ui.listDialog().open();
},
*/
init: function(data) {
pandora.site.sectionFolders[ui.section][i].items = data.items;
pandora.$ui.folder[i].$content.css({
height: data.items * 16 + 'px'
});
pandora.$ui.folderList[id].css({
height: data.items * 16 + 'px'
});
pandora.resizeFolders();
},
move: function(data) {
pandora.api['sort' + foldeItem]({
section: id,
ids: data.ids
});
},
paste: function(data) {
pandora.$ui.list.triggerEvent('paste', data);
},
select: function(data) {
var list = data.ids.length ? data.ids[0] : '';
if (list) {
Ox.forEach(pandora.$ui.folderList, function($list, id_) {
id != id_ && $list.options('selected', []);
});
}
if (ui.section == 'items') {
pandora.UI.set({
find: {
conditions: list ? [
@ -385,18 +389,20 @@ pandora.ui.folderList = function(id) {
operator: '&'
}
});
},
submit: function(data) {
var data_ = {id: data.id};
data_[data.key] = data.value;
pandora.api.editList(data_, function(result) {
if (result.data.id != data.id) {
pandora.renameList(data.id, result.data.id, result.data.name, id);
pandora.$ui.info.updateListInfo();
}
});
} else {
pandora.UI.set(ui.section.slice(0, -1), list);
}
});
}
},
submit: function(data) {
var data_ = {id: data.id};
data_[data.key] = data.value;
pandora.api['edit' + folderItem](data_, function(result) {
if (result.data.id != data.id) {
pandora.renameList(data.id, result.data.id, result.data.name, id);
pandora.$ui.info.updateListInfo();
}
});
}
});
return that;
};

View file

@ -7,7 +7,9 @@ pandora.ui.folders = function() {
.css({overflowX: 'hidden', overflowY: 'auto'})
.bindEvent({
resize: pandora.resizeFolders
});
}),
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1);
//var $sections = [];
pandora.$ui.allItems = pandora.ui.allItems().appendTo(that);
@ -18,15 +20,17 @@ pandora.ui.folders = function() {
pandora.$ui.findListSelect = {};
pandora.$ui.findListInput = {};
pandora.$ui.manageListsButton = {};
if (ui.section == 'items') {
pandora.site.sectionFolders.items.forEach(function(folder, i) {
var extras, $select;
if (folder.id == 'personal') {
if (pandora.user.level == 'guest') {
extras = [
infoButton('Personal Lists', 'To create and share your own lists of movies, please sign up or sign in.')
];
} else {
pandora.site.sectionFolders[ui.section].forEach(function(folder, i) {
var extras, $select;
if (folder.id == 'personal') {
if (pandora.user.level == 'guest') {
extras = [
infoButton('Personal ' + folderItems, 'To create and share your own ' + (ui.section == 'items'
? 'lists of ' + pandora.site.itemName.plural.toLowerCase()
: ui.section) + ', please sign up or sign in.')
];
} else {
if (ui.section == 'items') {
extras = [
pandora.$ui.personalListsMenu = Ox.MenuButton({
items: [
@ -78,195 +82,225 @@ pandora.ui.folders = function() {
}
})
];
}
} else if (folder.id == 'favorite') {
if (pandora.user.level == 'guest') {
extras = [infoButton('Favorite Lists', 'To browse and subscribe to shared lists from other users, please sign up or sign in.')];
} else {
extras = [pandora.$ui.manageListsButton['favorite'] = Ox.Button({
selectable: true,
style: 'symbol',
title: 'Edit',
tooltip: 'Manage Favorite Lists',
type: 'image'
})
.bindEvent({
change: function(data) {
var listData;
Ox.Request.clearCache(); // fixme: remove
pandora.site.sectionFolders.items[i].showBrowser = !pandora.site.sectionFolders.items[i].showBrowser;
if (pandora.site.sectionFolders.items[i].showBrowser) {
pandora.$ui.folderList.favorite.replaceWith(
pandora.$ui.folderBrowser.favorite = pandora.ui.folderBrowser('favorite')
);
} else {
listData = pandora.getListData();
if (
pandora.$ui.folderList.favorite.options('selected').length
&& !listData.subscribed
) {
// the selected list in the favorites browser is not in the favorites folder
pandora.$ui.folderList.favorite.options({selected: []});
if (Ox.getObjectById(pandora.site.sectionFolders.items, 'featured').showBrowser) {
// but in the featured browser
pandora.$ui.folderList.featured.options({selected: [listData.id]});
} else {
// and nowhere else
pandora.UI.set({
find: pandora.site.user.ui.find
});
}
extras = [
pandora.$ui.personalListsMenu = Ox.MenuButton({
items: [
{ id: 'new' + folderItem.toLowerCase(), title: 'New ' + folderItem },
{},
{ id: 'delete' + folderItem.toLowerCase(), title: 'Delete Selected ' + folderItem + '...', disabled: !ui[ui.section.slice(0,-1)] }
],
title: 'edit',
tooltip: 'Manage Personal ' + folderItems,
type: 'image'
})
.bindEvent({
click: function(data) {
var $list = pandora.$ui.folderList[folder.id];
// fixme: duplicated
if (data.id == 'new' + folderItem.toLowerCase()) {
pandora['add' + folderItem]();
} else if (data.id == 'delete' + folderItem.toLowerCase()) {
pandora.ui.deleteListDialog().open();
}
pandora.$ui.folderBrowser.favorite.replaceWith(
pandora.$ui.folderList.favorite = pandora.ui.folderList('favorite')
);
}
pandora.resizeFolders();
}
})];
}
} else if (folder.id == 'featured') {
if (pandora.user.level != 'admin') {
extras = [infoButton('Featured Lists', 'Featured lists are selected public lists, picked by the ' + pandora.site.site.name + ' staff.')];
} else {
extras = [pandora.$ui.manageListsButton['featured'] = Ox.Button({
selectable: true,
style: 'symbol',
title: 'Edit',
tooltip: 'Manage Featured Lists',
type: 'image'
})
.bindEvent({
change: function(data) {
var listData;
Ox.Request.clearCache(); // fixme: remove
pandora.site.sectionFolders.items[i].showBrowser = !pandora.site.sectionFolders.items[i].showBrowser;
if (pandora.site.sectionFolders.items[i].showBrowser) {
pandora.$ui.folderList.featured.replaceWith(
pandora.$ui.folderBrowser.featured = pandora.ui.folderBrowser('featured')
);
} else {
listData = pandora.getListData();
Ox.Log('', 'FEATURED', listData)
if (
pandora.$ui.folderList.featured.options('selected').length
&& listData.status != 'featured'
) {
// the selected list in the featured browser is not in the featured folder
pandora.$ui.folderList.featured.options({selected: []});
if (listData.user == pandora.user.username) {
// but in the personal folder
pandora.$ui.folderList.personal.options({selected: [listData.id]});
} else if (
listData.subscribed
|| Ox.getObjectById(pandora.site.sectionFolders.items, 'favorite').showBrowser
) {
// but in the favorites folder or browser
pandora.$ui.folderList.favorite.options({selected: [listData.id]});
} else {
// and nowhere else
pandora.UI.set({
find: pandora.site.user.ui.find
});
}
}
pandora.$ui.folderBrowser.featured.replaceWith(
pandora.$ui.folderList.featured = pandora.ui.folderList('featured')
);
}
pandora.resizeFolders();
}
})];
}
} else if (folder.id == 'volumes') {
if (pandora.user.level == 'guest') {
extras = [infoButton('Local Volumes', 'To import movies from a local disk, please sign up or sign in.')];
} else {
extras = [Ox.MenuButton({
items: [
{ id: 'add', title: 'Add Volume...', disabled: true },
{ id: 'scan', title: 'Scan Selected Volume...', disabled: true },
{ id: 'remove', title: 'Remove Selected Volume...', disabled: true },
{},
{ id: 'import', title: 'Import Movies...', disabled: true }
],
title: 'edit',
tooltip: 'Manage Volumes',
type: 'image'
})
.bindEvent({
click: function(data) {
}
})];
})
.bindEvent('pandora_' + ui.section.slice(0,-1), function(data) {
pandora.$ui.personalListsMenu[
data.value && data.value.length ? 'enableItem' : 'disableItem'
]('delete' + folderItem.toLowerCase());
})
];
}
}
pandora.$ui.folder[i] = Ox.CollapsePanel({
id: folder.id,
collapsed: !ui.showFolder.items[folder.id],
extras: extras,
size: 16,
title: folder.title
} else if (folder.id == 'favorite') {
if (pandora.user.level == 'guest') {
extras = [infoButton('Favorite ' + folderItems,
'To browse and subscribe to shared ' + folderItems.toLowerCase() + ' from other users, please sign up or sign in.')];
} else {
extras = [pandora.$ui.manageListsButton['favorite'] = Ox.Button({
selectable: true,
style: 'symbol',
title: 'Edit',
tooltip: 'Manage Favorite ' + folderItems,
type: 'image'
})
.bindEvent({
// fixme: duplicated
click: function(data) {
var $list = pandora.$ui.folderList[i],
hasFocus, id;
if (data.id == 'new' || data.id == 'newsmart') {
pandora.api.addList({
name: 'Untitled',
status: 'private',
type: data.id == 'new' ? 'static' : 'smart'
}, function(result) {
id = result.data.id;
pandora.URL.set('?find=list:' + id)
Ox.Request.clearCache(); // fixme: remove
$list.reloadList().bindEventOnce({
load: function(data) {
$list.gainFocus()
.options({selected: [id]})
.editCell(id, 'name');
}
});
});
} else if (data.id == 'browse') {
// alert('??')
/*
pandora.$ui.sectionList[1].replaceWith(pandora.$ui.publicLists = pandora.ui.publicLists());
pandora.site.showAllPublicLists = true;
*/
change: function(data) {
var listData;
Ox.Request.clearCache(); // fixme: remove
pandora.site.sectionFolders[ui.section][i].showBrowser = !pandora.site.sectionFolders[ui.section][i].showBrowser;
if (pandora.site.sectionFolders[ui.section][i].showBrowser) {
pandora.$ui.folderList.favorite.replaceWith(
pandora.$ui.folderBrowser.favorite = pandora.ui.folderBrowser('favorite')
);
} else {
listData = pandora.getListData();
if (
pandora.$ui.folderList.favorite.options('selected').length
&& !listData.subscribed
) {
// the selected list in the favorites browser is not in the favorites folder
pandora.$ui.folderList.favorite.options({selected: []});
if (Ox.getObjectById(pandora.site.sectionFolders[ui.section], 'featured').showBrowser) {
// but in the featured browser
pandora.$ui.folderList.featured.options({selected: [listData.id]});
} else {
// and nowhere else
pandora.UI.set({
find: pandora.site.user.ui.find
});
}
}
pandora.$ui.folderBrowser.favorite.replaceWith(
pandora.$ui.folderList.favorite = pandora.ui.folderList('favorite')
);
}
},
toggle: function(data) {
data.collapsed && pandora.$ui.folderList[folder.id].loseFocus();
pandora.UI.set('showFolder.items.' + folder.id, !data.collapsed);
pandora.resizeFolders();
}
});
//$sections.push(pandora.$ui.section[i]);
pandora.$ui.folderList[folder.id] = pandora.ui.folderList(folder.id)
})];
}
} else if (folder.id == 'featured') {
if (pandora.user.level != 'admin') {
extras = [infoButton('Featured ' + folderItems, 'Featured ' + folderItems.toLowerCase() + ' are selected public ' + folderItems.toLowerCase() + ', picked by the ' + pandora.site.site.name + ' staff.')];
} else {
extras = [pandora.$ui.manageListsButton['featured'] = Ox.Button({
selectable: true,
style: 'symbol',
title: 'Edit',
tooltip: 'Manage Featured ' + folderItems,
type: 'image'
})
.bindEvent({
selectafter: function() {
// ...
},
selectbefore: function() {
// ...
}
})
.bindEventOnce({
init: function(data) {
if (++counter == 4) {
pandora.$ui.folder.forEach(function($folder) {
that.append($folder);
});
pandora.resizeFolders();
pandora.selectList();
change: function(data) {
var listData;
Ox.Request.clearCache(); // fixme: remove
pandora.site.sectionFolders[ui.section][i].showBrowser = !pandora.site.sectionFolders[ui.section][i].showBrowser;
if (pandora.site.sectionFolders[ui.section][i].showBrowser) {
pandora.$ui.folderList.featured.replaceWith(
pandora.$ui.folderBrowser.featured = pandora.ui.folderBrowser('featured')
);
} else {
listData = pandora.getListData();
Ox.Log('', 'FEATURED', listData)
if (
pandora.$ui.folderList.featured.options('selected').length
&& listData.status != 'featured'
) {
// the selected list in the featured browser is not in the featured folder
pandora.$ui.folderList.featured.options({selected: []});
if (listData.user == pandora.user.username) {
// but in the personal folder
pandora.$ui.folderList.personal.options({selected: [listData.id]});
} else if (
listData.subscribed
|| Ox.getObjectById(pandora.site.sectionFolders[ui.section], 'favorite').showBrowser
) {
// but in the favorites folder or browser
pandora.$ui.folderList.favorite.options({selected: [listData.id]});
} else {
// and nowhere else
pandora.UI.set({
find: pandora.site.user.ui.find
});
}
}
pandora.$ui.folderBrowser.featured.replaceWith(
pandora.$ui.folderList.featured = pandora.ui.folderList('featured')
);
}
pandora.resizeFolders();
}
})];
}
} else if (folder.id == 'volumes') {
if (pandora.user.level == 'guest') {
extras = [infoButton('Local Volumes', 'To import movies from a local disk, please sign up or sign in.')];
} else {
extras = [Ox.MenuButton({
items: [
{ id: 'add', title: 'Add Volume...', disabled: true },
{ id: 'scan', title: 'Scan Selected Volume...', disabled: true },
{ id: 'remove', title: 'Remove Selected Volume...', disabled: true },
{},
{ id: 'import', title: 'Import Movies...', disabled: true }
],
title: 'edit',
tooltip: 'Manage Volumes',
type: 'image'
})
.appendTo(pandora.$ui.folder[i].$content);
});
}
.bindEvent({
click: function(data) {
}
})];
}
}
pandora.$ui.folder[i] = Ox.CollapsePanel({
id: folder.id,
collapsed: !ui.showFolder.items[folder.id],
extras: extras,
size: 16,
title: folder.title
})
.bindEvent({
// fixme: duplicated
click: function(data) {
var $list = pandora.$ui.folderList[i],
hasFocus, id;
if (data.id == 'new' || data.id == 'newsmart') {
pandora.api.addList({
name: 'Untitled',
status: 'private',
type: data.id == 'new' ? 'static' : 'smart'
}, function(result) {
id = result.data.id;
pandora.URL.set('?find=list:' + id)
Ox.Request.clearCache(); // fixme: remove
$list.reloadList().bindEventOnce({
load: function(data) {
$list.gainFocus()
.options({selected: [id]})
.editCell(id, 'name');
}
});
});
} else if (data.id == 'browse') {
// alert('??')
/*
pandora.$ui.sectionList[1].replaceWith(pandora.$ui.publicLists = pandora.ui.publicLists());
pandora.site.showAllPublicLists = true;
*/
}
},
toggle: function(data) {
data.collapsed && pandora.$ui.folderList[folder.id].loseFocus();
pandora.UI.set('showFolder.items.' + folder.id, !data.collapsed);
pandora.resizeFolders();
}
});
//$sections.push(pandora.$ui.section[i]);
pandora.$ui.folderList[folder.id] = pandora.ui.folderList(folder.id)
.bindEvent({
selectafter: function() {
// ...
},
selectbefore: function() {
// ...
}
})
.bindEventOnce({
init: function(data) {
if (++counter == pandora.site.sectionFolders[ui.section].length) {
pandora.$ui.folder.forEach(function($folder) {
that.append($folder);
});
pandora.resizeFolders();
pandora.selectList();
}
}
})
.appendTo(pandora.$ui.folder[i].$content);
});
function infoButton(title, text) {
return Ox.Button({
style: 'symbol',
@ -340,6 +374,13 @@ pandora.ui.folders = function() {
});
}
*/
},
pandora_text: function() {
if (!pandora.user.ui.text) {
Ox.forEach(pandora.$ui.folderList, function($list, id) {
$list.options('selected', []);
});
}
}
})
return that;

View file

@ -13,7 +13,10 @@ pandora.ui.listDialog = function(section) {
], listData.type == 'smart'
? [{id: 'query', title: 'Query'}]
: []
);
),
ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1);
Ox.getObjectById(tabs, section).selected = true;
pandora.$ui.listDialogTabPanel = Ox.TabPanel({
@ -103,7 +106,7 @@ pandora.ui.listDialog = function(section) {
height: 312,
// keys: {enter: 'save', escape: 'cancel'},
removeOnClose: true,
title: 'List &mdash; ' + Ox.encodeHTMLEntities(listData.name),
title: folderItem + ' &mdash; ' + Ox.encodeHTMLEntities(listData.name),
width: width
});
@ -117,8 +120,11 @@ pandora.ui.listDialog = function(section) {
};
pandora.ui.listGeneralPanel = function(listData) {
var that = Ox.Element();
pandora.api.findLists({
var that = Ox.Element(),
ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1);
pandora.api['find' + folderItems]({
query: {conditions: [{key: 'id', value: listData.id, operator: '=='}]},
keys: ['description', 'subscribers']
}, function(result) {
@ -129,7 +135,7 @@ pandora.ui.listGeneralPanel = function(listData) {
tooltip: 'Doubleclick to edit icon'
})
.attr({
src: '/list/' + listData.id + '/icon256.jpg?' + Ox.uid()
src: '/' + folderItem.toLowerCase() + '/' + listData.id + '/icon256.jpg?' + Ox.uid()
})
.css({
position: 'absolute',
@ -157,7 +163,8 @@ pandora.ui.listGeneralPanel = function(listData) {
submit: editName
})
.appendTo(that),
$itemsInput = Ox.Input({
$itemsInput = ui.section == 'items'
? Ox.Input({
disabled: true,
label: 'Items',
labelWidth: 80,
@ -165,6 +172,21 @@ pandora.ui.listGeneralPanel = function(listData) {
width: 320
})
.css({position: 'absolute', left: '160px', top: '40px'})
.appendTo(that)
: Ox.Select({
items: [
{id: 'html', title: 'HMTL'},
{id: 'pdf', title: 'PDF'}
],
label: 'Type',
labelWidth: 80,
value: listData.type,
width: 320
})
.css({position: 'absolute', left: '160px', top: '40px'})
.bindEvent({
change: editType
})
.appendTo(that),
$statusSelect = listData.status == 'featured'
? Ox.Input({
@ -216,19 +238,19 @@ pandora.ui.listGeneralPanel = function(listData) {
.appendTo(that);
function editDescription(data) {
if (data.value != description) {
pandora.api.editList({
pandora.api['edit' + folderItem]({
id: listData.id,
description: data.value
}, function(result) {
description = result.data.description;
Ox.Request.clearCache('findLists');
Ox.Request.clearCache('find' + folderItems);
pandora.$ui.info.updateListInfo();
});
}
}
function editName(data) {
if (data.value != listData.name) {
pandora.api.editList({
pandora.api['edit' + folderItem]({
id: listData.id,
name: data.value
}, function(result) {
@ -236,10 +258,10 @@ pandora.ui.listGeneralPanel = function(listData) {
pandora.renameList(listData.id, result.data.id, result.data.name);
listData.id = result.data.id;
listData.name = result.data.name;
Ox.Request.clearCache('findLists');
Ox.Request.clearCache('find' + folderItems);
pandora.$ui.info.updateListInfo();
pandora.$ui.listDialog.options({
title: 'List &mdash; ' + Ox.encodeHTMLEntities(listData.name)
title: folderItme + ' &mdash; ' + Ox.encodeHTMLEntities(listData.name)
});
}
});
@ -248,7 +270,7 @@ pandora.ui.listGeneralPanel = function(listData) {
function editStatus(data) {
var status = data.value;
$statusSelect.value(status == 'private' ? 'public' : 'private');
pandora.changeListStatus(listData.id, status, function(result) {
pandora.changeFolderItemStatus(listData.id, status, function(result) {
listData.status = result.data.status;
if (result.data.status == 'private') {
subscribers = 0;
@ -264,6 +286,18 @@ pandora.ui.listGeneralPanel = function(listData) {
);
});
}
function editType(data) {
var type = data.value;
$itemsInput.value(type == 'html' ? 'html' : 'pdf');
pandora.api.editText({
id: listData.id,
type: type
}, function(result) {
Ox.Request.clearCache('getText');
//fixme: reload text and folder list
$itemsInput.value(result.data.type);
});
}
function getDescriptionHeight() {
return listData.status == 'private' ? 184 : 160;
}
@ -282,10 +316,14 @@ pandora.ui.listIconPanel = function(listData) {
var quarter = 0,
quarters = ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1),
$iconPanel = Ox.Element(),
$icon = $('<img>')
.attr({src: '/list/' + listData.id + '/icon256.jpg?' + Ox.uid()})
.attr({src: '/' + folderItem.toLowerCase() + '/' + listData.id + '/icon256.jpg?' + Ox.uid()})
.css({position: 'absolute', borderRadius: '64px', margin: '16px'})
.appendTo($iconPanel),
@ -295,6 +333,10 @@ pandora.ui.listIconPanel = function(listData) {
$list = Ox.Element(),
ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1),
that = Ox.SplitPanel({
elements: [
{
@ -312,7 +354,7 @@ pandora.ui.listIconPanel = function(listData) {
orientation: 'horizontal'
});
pandora.api.findLists({
pandora.api['find' + folderItems]({
query: {
conditions: [{key: 'id', value: listData.id, operator: '=='}],
operator: '&'
@ -364,7 +406,7 @@ pandora.ui.listIconPanel = function(listData) {
items: function(data, callback) {
pandora.api.find(Ox.extend(data, {
query: {
conditions: [{key: 'list', value: listData.id, operator: '=='}],
conditions: ui.section == 'items' ? [{key: 'list', value: listData.id, operator: '=='}] : [],
operator: '&'
}
}), callback);
@ -375,7 +417,7 @@ pandora.ui.listIconPanel = function(listData) {
//orientation: 'vertical',
selected: posterFrame ? [posterFrame.item] : [],
size: 128,
sort: pandora.user.ui.listSort,
sort: ui.section == 'items' ? pandora.user.ui.listSort : pandora.site.user.ui.listSort,
unique: 'id'
})
//.css({width: '144px'})
@ -461,17 +503,17 @@ pandora.ui.listIconPanel = function(listData) {
} else {
posterFrames = Ox.repeat([posterFrame], 4);
}
pandora.api.editList({
pandora.api['edit' + folderItem]({
id: listData.id,
posterFrames: posterFrames
}, function() {
$icon.attr({
src: '/list/' + listData.id + '/icon256.jpg?' + Ox.uid()
src: '/' + folderItem.toLowerCase() + '/' + listData.id + '/icon256.jpg?' + Ox.uid()
});
pandora.$ui.folderList[listData.folder].$element
.find('img[src*="/' + listData.id + '/"]')
.attr({
src: '/list/' + listData.id + '/icon.jpg?' + Ox.uid()
src: '/' + folderItem.toLowerCase() + '/' + listData.id + '/icon.jpg?' + Ox.uid()
});
pandora.$ui.info.updateListInfo();
pandora.clearListIconCache(listData.id);

View file

@ -65,6 +65,12 @@ pandora.ui.mainPanel = function() {
}
}
},
pandora_section: function(data) {
if (data.value != data.previousValue) {
that.replaceElement(0, pandora.$ui.leftPanel = pandora.ui.leftPanel());
that.replaceElement(1, pandora.$ui.rightPanel = pandora.ui.rightPanel());
}
},
pandora_item: function(data) {
if (!data.value || !data.previousValue) {
that.replaceElement(1, pandora.$ui.rightPanel = pandora.ui.rightPanel());

View file

@ -57,6 +57,8 @@ pandora.ui.rightPanel = function() {
}
}
});
} else if (pandora.user.ui.section == 'texts') {
that = pandora.$ui.text = pandora.ui.text();
}
return that;
};

View file

@ -5,7 +5,7 @@ pandora.ui.sectionButtons = function() {
buttons: [
{id: 'items', title: pandora.site.itemName.plural},
{id: 'edits', title: 'Edits', disabled: true},
{id: 'texts', title: 'Texts', disabled: true}
{id: 'texts', title: 'Texts', disabled: pandora.user.level != 'admin'}
],
id: 'sectionButtons',
selectable: true,
@ -17,13 +17,7 @@ pandora.ui.sectionButtons = function() {
.bindEvent({
change: function(data) {
var section = data.value;
if (section == 'items') {
pandora.URL.set(pandora.Query.toString());
} else if (section == 'clips') {
pandora.URL.set('clips');
} else if (section == 'texts') {
pandora.URL.set('texts');
}
pandora.UI.set({section: section});
}
});
return that;

58
static/js/pandora/text.js Normal file
View file

@ -0,0 +1,58 @@
'use strict';
pandora.ui.text = function() {
var ui = pandora.user.ui,
canEdit = pandora.site.capabilities.canEditSitePages[pandora.user.level],
that,
text,
$text;
getText(ui.text);
that = Ox.Element()
.bindEvent({
pandora_text: function(data) {
if (ui.text != text) {
text = ui.text;
getText(ui.text);
}
}
});
function getText(id) {
pandora.api.getText({id: id}, function(result) {
if (result.data.type == 'pdf') {
$text && $text.remove();
$text = Ox.Editable({
clickLink: pandora.clickLink,
editable: false,
type: 'textarea',
value: 'REPLACE ME WITH PDF VIEWER'
})
.appendTo(that);
} else {
var text = result.data ? result.data.text : '';
$text && $text.remove();
$text = Ox.Editable({
clickLink: pandora.clickLink,
editable: canEdit,
tooltip: canEdit ? 'Doubleclick to edit' : '',
type: 'textarea',
placeholder: 'No text',
value: text
})
.bindEvent({
submit: function(data) {
Ox.Request.clearCache('getText');
pandora.api.editText({
id: ui.text,
text: data.value
});
}
})
.appendTo(that);
}
});
}
return that;
}

View file

@ -147,10 +147,31 @@ pandora.addList = function() {
}).reloadList();
}
};
pandora.addText = function() {
var $folderList = pandora.$ui.folderList.personal;
pandora.api.addText({name: 'Untitled'}, function(result) {
reloadFolder(result.data.id);
});
function reloadFolder(newId) {
pandora.$ui.folder[0].options({collapsed: false});
Ox.Request.clearCache('findTexts');
$folderList.bindEventOnce({
load: function(data) {
$folderList.gainFocus()
.options({selected: [newId]})
.editCell(newId, 'name', true);
pandora.UI.set(pandora.user.ui.section.slice(0, -1), newId);
}
}).reloadList();
}
}
pandora.changeListStatus = function(id, status, callback) {
pandora.changeFolderItemStatus = function(id, status, callback) {
var ui = pandora.user.ui,
folderItems = ui.section == 'items' ? 'Lists' : Ox.toTitleCase(ui.section),
folderItem = folderItems.slice(0, -1);
if (status == 'private') {
pandora.api.findLists({
pandora.api['find' + folderItems]({
query: {conditions: [{key: 'id', value: id, operator: '=='}]},
keys: ['name', 'subscribers']
}, function(result) {
@ -168,14 +189,14 @@ pandora.changeListStatus = function(id, status, callback) {
}
}).open();
} else {
changeListStatus();
changeFolderItemStatus();
}
});
} else {
changeListStatus();
changeFolderItemStatus();
}
function changeListStatus() {
pandora.api.editList({
function changeFolderItemStatus() {
pandora.api['edit' + folderItem]({
id: id,
status: status
}, callback);
@ -606,42 +627,46 @@ pandora.getInfoHeight = function(includeHidden) {
return height;
}
pandora.getItemByIdOrTitle = function(str, callback) {
var sortKey = Ox.getObjectById(pandora.site.itemKeys, 'votes')
? 'votes'
: 'timesaccessed';
pandora.api.get({id: str, keys: ['id']}, function(result) {
if (result.status.code == 200) {
callback(result.data.id);
} else {
pandora.api.find({
query: {
conditions: [{key: 'title', value: str, operator: '='}],
operator: '&'
},
sort: [{key: sortKey, operator: ''}],
range: [0, 100],
keys: ['id', 'title', sortKey]
}, function(result) {
var id = '';
if (result.data.items.length) {
var items = Ox.filter(Ox.map(result.data.items, function(item) {
// test if exact match or word match
var sort = new RegExp('^' + str + '$', 'i').test(item.title) ? 2000000
: new RegExp('\\b' + str + '\\b', 'i').test(item.title) ? 1000000 : 0;
return sort ? {id: item.id, sort: sort + (parseInt(item[sortKey]) || 0)} : null;
// fixme: remove the (...|| 0) check once the backend sends correct data
}));
if (items.length) {
id = items.sort(function(a, b) {
return b.sort - a.sort;
})[0].id;
pandora.getItemByIdOrTitle = function(type, str, callback) {
if (type == pandora.site.itemName.plural.toLowerCase()) {
var sortKey = Ox.getObjectById(pandora.site.itemKeys, 'votes')
? 'votes'
: 'timesaccessed';
pandora.api.get({id: str, keys: ['id']}, function(result) {
if (result.status.code == 200) {
callback(result.data.id);
} else {
pandora.api.find({
query: {
conditions: [{key: 'title', value: str, operator: '='}],
operator: '&'
},
sort: [{key: sortKey, operator: ''}],
range: [0, 100],
keys: ['id', 'title', sortKey]
}, function(result) {
var id = '';
if (result.data.items.length) {
var items = Ox.filter(Ox.map(result.data.items, function(item) {
// test if exact match or word match
var sort = new RegExp('^' + str + '$', 'i').test(item.title) ? 2000000
: new RegExp('\\b' + str + '\\b', 'i').test(item.title) ? 1000000 : 0;
return sort ? {id: item.id, sort: sort + (parseInt(item[sortKey]) || 0)} : null;
// fixme: remove the (...|| 0) check once the backend sends correct data
}));
if (items.length) {
id = items.sort(function(a, b) {
return b.sort - a.sort;
})[0].id;
}
}
}
callback(id);
});
}
});
callback(id);
});
}
});
} else {
callback(str);
}
}
pandora.getItemFind = function(find) {
@ -706,7 +731,11 @@ pandora.getItemIdAndPosition = function() {
pandora.getListData = function(list) {
var data = {}, folder;
list = Ox.isUndefined(list) ? pandora.user.ui._list : list;
if (pandora.user.ui.section == 'items') {
list = Ox.isUndefined(list) ? pandora.user.ui._list : list;
} else {
list = Ox.isUndefined(list) ? pandora.user.ui[pandora.user.ui.section.slice(0, -1)] : list;
}
if (list) {
Ox.forEach(pandora.$ui.folderList, function($list, id) {
var ret = true;
@ -726,8 +755,12 @@ pandora.getListData = function(list) {
// FIXME: Is there a `return ret` statement missing here?
});
if (folder) {
data = pandora.$ui.folderList[folder].value(pandora.user.ui._list);
data.editable = data.user == pandora.user.username && data.type == 'static';
data = pandora.$ui.folderList[folder].value(list);
if (pandora.user.ui.section == 'item') {
data.editable = data.user == pandora.user.username && data.type == 'static';
} else {
data.editable = data.user == pandora.user.username;
}
data.folder = folder;
}
}
@ -1076,24 +1109,28 @@ pandora.renameList = function(oldId, newId, newName, folder) {
folder = folder || pandora.getListData(oldId).folder;
pandora.$ui.folderList[folder].value(oldId, 'name', newName);
pandora.$ui.folderList[folder].value(oldId, 'id', newId);
pandora.$ui.toolbar.updateListName(newId);
pandora.UI.set({
find: {
conditions: [{key: 'list', value: newId, operator: '=='}],
operator: '&'
}
}, false);
if (pandora.user.ui.section == 'items') {
pandora.$ui.toolbar.updateListName(newId);
pandora.UI.set({
find: {
conditions: [{key: 'list', value: newId, operator: '=='}],
operator: '&'
}
}, false);
} else {
pandora.UI.set(pandora.user.ui.section.slice(0, -1), newId);
}
};
pandora.resizeFilters = function(width) {
pandora.user.ui.filterSizes = pandora.getFilterSizes();
pandora.$ui.browser
pandora.$ui.browser && pandora.$ui.browser
.size(0, pandora.user.ui.filterSizes[0])
.size(2, pandora.user.ui.filterSizes[4]);
pandora.$ui.filtersInnerPanel
pandora.$ui.filtersInnerPanel && pandora.$ui.filtersInnerPanel
.size(0, pandora.user.ui.filterSizes[1])
.size(2, pandora.user.ui.filterSizes[3]);
pandora.$ui.filters.forEach(function($list, i) {
pandora.$ui.filters && pandora.$ui.filters.forEach(function($list, i) {
$list.resizeColumn('name', pandora.user.ui.filterSizes[i] - 44 - Ox.UI.SCROLLBAR_SIZE);
if (pandora.user.ui.showFlags) {
$list.find('.flagname').css({width: pandora.user.ui.filterSizes[i] - 68 - Ox.UI.SCROLLBAR_SIZE})
@ -1103,27 +1140,24 @@ pandora.resizeFilters = function(width) {
pandora.resizeFolders = function() {
var width = pandora.getFoldersWidth(),
columnWidth = {};
if (pandora.user.ui.section == 'items') {
columnWidth = {user: parseInt((width - 96) * 0.4)};
columnWidth.name = (width - 96) - columnWidth.user;
}
columnWidth = {},
sectionWidth = pandora.user.ui.section == 'items'? 96 : 32;
columnWidth = {user: parseInt((width - (sectionWidth)) * 0.4)};
columnWidth.name = (width - sectionWidth) - columnWidth.user;
Ox.Log('', 'RESIZE FOLDERS', width);
pandora.$ui.allItems.resizeElement(width - 104);
Ox.forEach(pandora.$ui.folderList, function($list, id) {
var pos = Ox.getIndexById(pandora.site.sectionFolders[pandora.user.ui.section], id);
pandora.$ui.folder[pos].css({width: width + 'px'});
$list.css({width: width + 'px'});
if (pandora.user.ui.section == 'items') {
if (pandora.site.sectionFolders[pandora.user.ui.section][pos].showBrowser) {
pandora.$ui.findListInput[id].options({
width: width - 24
});
$list.resizeColumn('user', columnWidth.user)
.resizeColumn('name', columnWidth.name);
} else {
$list.resizeColumn(id == 'favorite' ? 'id' : 'name', width - 96);
}
if (pandora.site.sectionFolders[pandora.user.ui.section][pos].showBrowser) {
pandora.$ui.findListInput[id].options({
width: width - 24
});
$list.resizeColumn('user', columnWidth.user)
.resizeColumn('name', columnWidth.name);
} else {
$list.resizeColumn(id == 'favorite' ? 'id' : 'name', width - 96);
}
if (!pandora.user.ui.showFolder[pandora.user.ui.section][id]) {
pandora.$ui.folder[pos].updatePanel();
@ -1135,88 +1169,105 @@ pandora.resizeWindow = function() {
// FIXME: a lot of this throws errors on load
pandora.$ui.leftPanel && pandora.$ui.leftPanel.size(2, pandora.getInfoHeight(true));
pandora.resizeFolders();
if (!pandora.user.ui.item) {
pandora.resizeFilters(pandora.$ui.rightPanel.width());
if (pandora.user.ui.listView == 'clips') {
var clipsItems = pandora.getClipsItems(),
previousClipsItems = pandora.getClipsItems(pandora.$ui.list.options('width'));
pandora.$ui.list.options({
width: window.innerWidth
- pandora.user.ui.showSidebar * pandora.user.ui.sidebarSize - 1
- Ox.UI.SCROLLBAR_SIZE
});
if (clipsItems != previousClipsItems) {
Ox.Request.clearCache(); // fixme
pandora.$ui.list.reloadList(true);
if (pandora.user.ui.section == 'item') {
if (!pandora.user.ui.item) {
pandora.resizeFilters(pandora.$ui.rightPanel.width());
if (pandora.user.ui.listView == 'clips') {
var clipsItems = pandora.getClipsItems(),
previousClipsItems = pandora.getClipsItems(pandora.$ui.list.options('width'));
pandora.$ui.list.options({
width: window.innerWidth
- pandora.user.ui.showSidebar * pandora.user.ui.sidebarSize - 1
- Ox.UI.SCROLLBAR_SIZE
});
if (clipsItems != previousClipsItems) {
Ox.Request.clearCache(); // fixme
pandora.$ui.list.reloadList(true);
}
} else if (pandora.user.ui.listView == 'timelines') {
pandora.$ui.list.options({
width: window.innerWidth
- pandora.user.ui.showSidebar * pandora.user.ui.sidebarSize - 1
- Ox.UI.SCROLLBAR_SIZE
});
} else if (pandora.user.ui.listView == 'map') {
pandora.$ui.map && pandora.$ui.map.resizeMap();
} else if (pandora.user.ui.listView == 'calendar') {
pandora.$ui.calendar && pandora.$ui.calendar.resizeCalendar();
} else {
pandora.$ui.list && pandora.$ui.list.size();
}
} else if (pandora.user.ui.listView == 'timelines') {
pandora.$ui.list.options({
width: window.innerWidth
- pandora.user.ui.showSidebar * pandora.user.ui.sidebarSize - 1
- Ox.UI.SCROLLBAR_SIZE
});
} else if (pandora.user.ui.listView == 'map') {
pandora.$ui.map && pandora.$ui.map.resizeMap();
} else if (pandora.user.ui.listView == 'calendar') {
pandora.$ui.calendar && pandora.$ui.calendar.resizeCalendar();
} else {
pandora.$ui.list && pandora.$ui.list.size();
}
} else {
pandora.$ui.browser.scrollToSelection();
if (pandora.user.ui.itemView == 'info') {
pandora.$ui.item.resize();
} else if (pandora.user.ui.itemView == 'clips') {
pandora.$ui.clipList.size();
} else if (pandora.user.ui.itemView == 'timeline') {
pandora.$ui.timeline && pandora.$ui.timeline.options({
// fixme: duplicated
height: pandora.$ui.contentPanel.size(1),
width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1
});
} else if (pandora.user.ui.itemView == 'player') {
pandora.$ui.player && pandora.$ui.player.options({
// fixme: duplicated
height: pandora.$ui.contentPanel.size(1),
width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1
});
} else if (pandora.user.ui.itemView == 'editor') {
pandora.$ui.editor && pandora.$ui.editor.options({
// fixme: duplicated
height: pandora.$ui.contentPanel.size(1),
width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1
});
} else if (pandora.user.ui.itemView == 'map') {
pandora.$ui.map.resizeMap();
} else if (pandora.user.ui.itemView == 'calendar') {
pandora.$ui.calendar.resizeCalendar();
pandora.$ui.browser.scrollToSelection();
if (pandora.user.ui.itemView == 'info') {
pandora.$ui.item.resize();
} else if (pandora.user.ui.itemView == 'clips') {
pandora.$ui.clipList.size();
} else if (pandora.user.ui.itemView == 'timeline') {
pandora.$ui.timeline && pandora.$ui.timeline.options({
// fixme: duplicated
height: pandora.$ui.contentPanel.size(1),
width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1
});
} else if (pandora.user.ui.itemView == 'player') {
pandora.$ui.player && pandora.$ui.player.options({
// fixme: duplicated
height: pandora.$ui.contentPanel.size(1),
width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1
});
} else if (pandora.user.ui.itemView == 'editor') {
pandora.$ui.editor && pandora.$ui.editor.options({
// fixme: duplicated
height: pandora.$ui.contentPanel.size(1),
width: pandora.$ui.document.width() - pandora.$ui.mainPanel.size(0) - 1
});
} else if (pandora.user.ui.itemView == 'map') {
pandora.$ui.map.resizeMap();
} else if (pandora.user.ui.itemView == 'calendar') {
pandora.$ui.calendar.resizeCalendar();
}
}
}
};
pandora.selectList = function() {
if (pandora.user.ui._list) {
pandora.api.findLists({
keys: ['status', 'user'],
query: {
conditions: [{key: 'id', value: pandora.user.ui._list, operator: '=='}],
operator: ''
},
range: [0, 1]
}, function(result) {
var folder, list;
if (result.data.items.length) {
list = result.data.items[0];
folder = list.status == 'featured' ? 'featured' : (
list.user == pandora.user.username ? 'personal' : 'favorite'
);
pandora.$ui.folderList[folder]
.options({selected: [pandora.user.ui._list]});
if (!pandora.hasDialogOrScreen() && !pandora.hasFocusedInput()) {
pandora.$ui.folderList[folder].gainFocus();
if (pandora.user.ui.section == 'items') {
if (pandora.user.ui._list) {
pandora.api.findLists({
keys: ['status', 'user'],
query: {
conditions: [{key: 'id', value: pandora.user.ui._list, operator: '=='}],
operator: ''
},
range: [0, 1]
}, function(result) {
var folder, list;
if (result.data.items.length) {
list = result.data.items[0];
folder = list.status == 'featured' ? 'featured' : (
list.user == pandora.user.username ? 'personal' : 'favorite'
);
pandora.$ui.folderList[folder]
.options({selected: [pandora.user.ui._list]});
if (!pandora.hasDialogOrScreen() && !pandora.hasFocusedInput()) {
pandora.$ui.folderList[folder].gainFocus();
}
}
}
});
});
}
} else {
var id = pandora.user.ui[pandora.user.ui.section.slice(0,-1)];
if (id) {
pandora.api.getText({id: id}, function(result) {
var folder;
if (result.data.id) {
folder = result.data.status == 'featured' ? 'featured' : (
result.data.user == pandora.user.username ? 'personal' : 'favorite'
);
pandora.$ui.folderList[folder].options({selected: [id]});
}
});
}
}
};