Compare commits
No commits in common. "79da1115aad20d7b4c2adcbccfbad4e1ae364ea9" and "f9628d96aed4bafceac82a5b6e1ba4c492332ca4" have entirely different histories.
79da1115aa
...
f9628d96ae
54 changed files with 249 additions and 729 deletions
3
ctl
3
ctl
|
|
@ -30,6 +30,9 @@ if [ "$action" = "init" ]; then
|
||||||
$SUDO git clone -b $branch https://code.0x2620.org/0x2620/oxjs.git static/oxjs
|
$SUDO git clone -b $branch https://code.0x2620.org/0x2620/oxjs.git static/oxjs
|
||||||
fi
|
fi
|
||||||
$SUDO mkdir -p src
|
$SUDO mkdir -p src
|
||||||
|
if [ ! -d src/oxtimelines ]; then
|
||||||
|
$SUDO git clone -b $branch https://code.0x2620.org/0x2620/oxtimelines.git src/oxtimelines
|
||||||
|
fi
|
||||||
for package in oxtimelines python-ox; do
|
for package in oxtimelines python-ox; do
|
||||||
cd ${BASE}
|
cd ${BASE}
|
||||||
if [ ! -d src/${package} ]; then
|
if [ ! -d src/${package} ]; then
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
import mozilla_django_oidc.auth
|
|
||||||
|
|
||||||
from user.utils import prepare_user
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class OIDCAuthenticationBackend(mozilla_django_oidc.auth.OIDCAuthenticationBackend):
|
|
||||||
def create_user(self, claims):
|
|
||||||
user = super(OIDCAuthenticationBackend, self).create_user(claims)
|
|
||||||
username = None
|
|
||||||
for key in ('preferred_username', 'name'):
|
|
||||||
if claims.get(key):
|
|
||||||
username = claims[key]
|
|
||||||
break
|
|
||||||
n = 1
|
|
||||||
if username and username != user.username:
|
|
||||||
uname = username
|
|
||||||
while User.objects.filter(username=uname).exclude(id=user.id).exists():
|
|
||||||
n += 1
|
|
||||||
uname = '%s (%s)' % (username, n)
|
|
||||||
user.username = uname
|
|
||||||
user.save()
|
|
||||||
prepare_user(user)
|
|
||||||
return user
|
|
||||||
|
|
||||||
def update_user(self, user, claims):
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
def generate_username(email):
|
|
||||||
return unicodedata.normalize('NFKC', email)[:150]
|
|
||||||
|
|
@ -184,7 +184,6 @@ def init(request, data):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
config['site']['oidc'] = bool(getattr(settings, 'OIDC_RP_CLIENT_ID', False))
|
|
||||||
response['data']['site'] = config
|
response['data']['site'] = config
|
||||||
response['data']['user'] = init_user(request.user, request)
|
response['data']['user'] = init_user(request.user, request)
|
||||||
request.session['last_init'] = str(datetime.now())
|
request.session['last_init'] = str(datetime.now())
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,8 @@ info_key_map = {
|
||||||
'display_id': 'id',
|
'display_id': 'id',
|
||||||
}
|
}
|
||||||
|
|
||||||
YT_DLP = ['yt-dlp']
|
|
||||||
if settings.YT_DLP_EXTRA:
|
|
||||||
YT_DLP += settings.YT_DLP_EXTRA
|
|
||||||
|
|
||||||
def get_info(url, referer=None):
|
def get_info(url, referer=None):
|
||||||
cmd = YT_DLP + ['-j', '--all-subs', url]
|
cmd = ['yt-dlp', '-j', '--all-subs', url]
|
||||||
if referer:
|
if referer:
|
||||||
cmd += ['--referer', referer]
|
cmd += ['--referer', referer]
|
||||||
p = subprocess.Popen(cmd,
|
p = subprocess.Popen(cmd,
|
||||||
|
|
@ -97,7 +93,7 @@ def add_subtitles(item, media, tmp):
|
||||||
sub.save()
|
sub.save()
|
||||||
|
|
||||||
def load_formats(url):
|
def load_formats(url):
|
||||||
cmd = YT_DLP + ['-q', url, '-j', '-F']
|
cmd = ['yt-dlp', '-q', url, '-j', '-F']
|
||||||
p = subprocess.Popen(cmd,
|
p = subprocess.Popen(cmd,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE, close_fds=True)
|
stderr=subprocess.PIPE, close_fds=True)
|
||||||
|
|
@ -116,7 +112,7 @@ def download(item_id, url, referer=None):
|
||||||
if isinstance(tmp, bytes):
|
if isinstance(tmp, bytes):
|
||||||
tmp = tmp.decode('utf-8')
|
tmp = tmp.decode('utf-8')
|
||||||
os.chdir(tmp)
|
os.chdir(tmp)
|
||||||
cmd = YT_DLP + ['-q', media['url']]
|
cmd = ['yt-dlp', '-q', media['url']]
|
||||||
if referer:
|
if referer:
|
||||||
cmd += ['--referer', referer]
|
cmd += ['--referer', referer]
|
||||||
elif 'referer' in media:
|
elif 'referer' in media:
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,10 @@ import ox.image
|
||||||
from ox.utils import json
|
from ox.utils import json
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
import pillow_avif
|
|
||||||
from pillow_heif import register_heif_opener
|
|
||||||
|
|
||||||
from .chop import Chop, make_keyframe_index
|
from .chop import Chop, make_keyframe_index
|
||||||
|
|
||||||
|
|
||||||
register_heif_opener()
|
|
||||||
logger = logging.getLogger('pandora.' + __name__)
|
logger = logging.getLogger('pandora.' + __name__)
|
||||||
|
|
||||||
img_extension = 'jpg'
|
img_extension = 'jpg'
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
# Generated by Django 4.2.7 on 2025-01-23 10:48
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
def update_path(apps, schema_editor):
|
|
||||||
File = apps.get_model("archive", "File")
|
|
||||||
for file in File.objects.all():
|
|
||||||
if file.path:
|
|
||||||
parts = file.path.split('/')
|
|
||||||
file.filename = parts.pop()
|
|
||||||
file.folder = '/'.join(parts)
|
|
||||||
file.save()
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('archive', '0007_stream_archive_str_file_id_69a542_idx'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='file',
|
|
||||||
name='filename',
|
|
||||||
field=models.CharField(default='', max_length=2048),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='file',
|
|
||||||
name='folder',
|
|
||||||
field=models.CharField(default='', max_length=2048),
|
|
||||||
),
|
|
||||||
migrations.RunPython(update_path),
|
|
||||||
]
|
|
||||||
|
|
@ -53,9 +53,6 @@ class File(models.Model):
|
||||||
path = models.CharField(max_length=2048, default="") # canoncial path/file
|
path = models.CharField(max_length=2048, default="") # canoncial path/file
|
||||||
sort_path = models.CharField(max_length=2048, default="") # sort name
|
sort_path = models.CharField(max_length=2048, default="") # sort name
|
||||||
|
|
||||||
folder = models.CharField(max_length=2048, default="")
|
|
||||||
filename = models.CharField(max_length=2048, default="")
|
|
||||||
|
|
||||||
type = models.CharField(default="", max_length=255)
|
type = models.CharField(default="", max_length=255)
|
||||||
|
|
||||||
# editable
|
# editable
|
||||||
|
|
@ -197,13 +194,6 @@ class File(models.Model):
|
||||||
data['part'] = str(data['part'])
|
data['part'] = str(data['part'])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def update_path(self):
|
|
||||||
path = self.normalize_path()
|
|
||||||
parts = path.split('/')
|
|
||||||
self.filename = parts.pop()
|
|
||||||
self.folder = '/'.join(parts)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def normalize_path(self):
|
def normalize_path(self):
|
||||||
# FIXME: always use format_path
|
# FIXME: always use format_path
|
||||||
if settings.CONFIG['site']['folderdepth'] == 4:
|
if settings.CONFIG['site']['folderdepth'] == 4:
|
||||||
|
|
@ -267,7 +257,7 @@ class File(models.Model):
|
||||||
update_path = False
|
update_path = False
|
||||||
if self.info:
|
if self.info:
|
||||||
if self.id:
|
if self.id:
|
||||||
self.path = self.update_path()
|
self.path = self.normalize_path()
|
||||||
else:
|
else:
|
||||||
update_path = True
|
update_path = True
|
||||||
if self.item:
|
if self.item:
|
||||||
|
|
@ -300,7 +290,7 @@ class File(models.Model):
|
||||||
self.streams.filter(source=None, available=True).count()
|
self.streams.filter(source=None, available=True).count()
|
||||||
super(File, self).save(*args, **kwargs)
|
super(File, self).save(*args, **kwargs)
|
||||||
if update_path:
|
if update_path:
|
||||||
self.path = self.update_path()
|
self.path = self.normalize_path()
|
||||||
super(File, self).save(*args, **kwargs)
|
super(File, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def get_path(self, name):
|
def get_path(self, name):
|
||||||
|
|
@ -484,9 +474,6 @@ class File(models.Model):
|
||||||
'videoCodec': self.video_codec,
|
'videoCodec': self.video_codec,
|
||||||
'wanted': self.wanted,
|
'wanted': self.wanted,
|
||||||
}
|
}
|
||||||
for key in ('folder', 'filename'):
|
|
||||||
if keys and key in keys:
|
|
||||||
data[key] = getattr(self, key)
|
|
||||||
if error:
|
if error:
|
||||||
data['error'] = error
|
data['error'] = error
|
||||||
for key in self.PATH_INFO:
|
for key in self.PATH_INFO:
|
||||||
|
|
@ -553,7 +540,7 @@ class File(models.Model):
|
||||||
|
|
||||||
def process_stream(self):
|
def process_stream(self):
|
||||||
'''
|
'''
|
||||||
extract derivatives from stream upload
|
extract derivatives from webm upload
|
||||||
'''
|
'''
|
||||||
from . import tasks
|
from . import tasks
|
||||||
return tasks.process_stream.delay(self.id)
|
return tasks.process_stream.delay(self.id)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@
|
||||||
"canExportAnnotations": {"friend": true, "staff": true, "admin": true},
|
"canExportAnnotations": {"friend": true, "staff": true, "admin": true},
|
||||||
"canImportAnnotations": {"staff": true, "admin": true},
|
"canImportAnnotations": {"staff": true, "admin": true},
|
||||||
"canImportItems": {},
|
"canImportItems": {},
|
||||||
"canTranscribeAudio": {},
|
|
||||||
"canManageDocuments": {"staff": true, "admin": true},
|
"canManageDocuments": {"staff": true, "admin": true},
|
||||||
"canManageEntities": {"staff": true, "admin": true},
|
"canManageEntities": {"staff": true, "admin": true},
|
||||||
"canManageHome": {},
|
"canManageHome": {},
|
||||||
|
|
@ -1402,7 +1401,7 @@
|
||||||
240, 288, 360, 432, 480, 720 and 1080.
|
240, 288, 360, 432, 480, 720 and 1080.
|
||||||
*/
|
*/
|
||||||
"video": {
|
"video": {
|
||||||
"formats": ["mp4"],
|
"formats": ["webm", "mp4"],
|
||||||
// fixme: this should be named "ratio" or "defaultRatio",
|
// fixme: this should be named "ratio" or "defaultRatio",
|
||||||
// as it also applies to clip lists (on load)
|
// as it also applies to clip lists (on load)
|
||||||
"previewRatio": 1.7777777778,
|
"previewRatio": 1.7777777778,
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@
|
||||||
"canImportAnnotations": {"researcher": true, "staff": true, "admin": true},
|
"canImportAnnotations": {"researcher": true, "staff": true, "admin": true},
|
||||||
// import needs to handle itemRequiresVideo=false first
|
// import needs to handle itemRequiresVideo=false first
|
||||||
"canImportItems": {},
|
"canImportItems": {},
|
||||||
"canTranscribeAudio": {},
|
|
||||||
"canManageDocuments": {"member": true, "researcher": true, "staff": true, "admin": true},
|
"canManageDocuments": {"member": true, "researcher": true, "staff": true, "admin": true},
|
||||||
"canManageEntities": {"member": true, "researcher": true, "staff": true, "admin": true},
|
"canManageEntities": {"member": true, "researcher": true, "staff": true, "admin": true},
|
||||||
"canManageHome": {"staff": true, "admin": true},
|
"canManageHome": {"staff": true, "admin": true},
|
||||||
|
|
@ -1888,7 +1887,7 @@
|
||||||
240, 288, 360, 432, 480, 720 and 1080.
|
240, 288, 360, 432, 480, 720 and 1080.
|
||||||
*/
|
*/
|
||||||
"video": {
|
"video": {
|
||||||
"formats": ["mp4"],
|
"formats": ["webm", "mp4"],
|
||||||
"previewRatio": 1.375,
|
"previewRatio": 1.375,
|
||||||
"resolutions": [240, 480]
|
"resolutions": [240, 480]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@
|
||||||
"canExportAnnotations": {"member": true, "staff": true, "admin": true},
|
"canExportAnnotations": {"member": true, "staff": true, "admin": true},
|
||||||
"canImportAnnotations": {"member": true, "staff": true, "admin": true},
|
"canImportAnnotations": {"member": true, "staff": true, "admin": true},
|
||||||
"canImportItems": {"member": true, "staff": true, "admin": true},
|
"canImportItems": {"member": true, "staff": true, "admin": true},
|
||||||
"canTranscribeAudio": {"staff": true, "admin": true},
|
|
||||||
"canManageDocuments": {"member": true, "staff": true, "admin": true},
|
"canManageDocuments": {"member": true, "staff": true, "admin": true},
|
||||||
"canManageEntities": {"member": true, "staff": true, "admin": true},
|
"canManageEntities": {"member": true, "staff": true, "admin": true},
|
||||||
"canManageHome": {"staff": true, "admin": true},
|
"canManageHome": {"staff": true, "admin": true},
|
||||||
|
|
@ -1392,7 +1391,7 @@
|
||||||
240, 288, 360, 432, 480, 720 and 1080.
|
240, 288, 360, 432, 480, 720 and 1080.
|
||||||
*/
|
*/
|
||||||
"video": {
|
"video": {
|
||||||
"formats": ["mp4"],
|
"formats": ["webm", "mp4"],
|
||||||
"previewRatio": 1.3333333333,
|
"previewRatio": 1.3333333333,
|
||||||
//supported resolutions are
|
//supported resolutions are
|
||||||
//1080, 720, 480, 432, 360, 288, 240, 144, 96
|
//1080, 720, 480, 432, 360, 288, 240, 144, 96
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
||||||
"canExportAnnotations": {"member": true, "staff": true, "admin": true},
|
"canExportAnnotations": {"member": true, "staff": true, "admin": true},
|
||||||
"canImportAnnotations": {"member": true, "staff": true, "admin": true},
|
"canImportAnnotations": {"member": true, "staff": true, "admin": true},
|
||||||
"canImportItems": {"member": true, "staff": true, "admin": true},
|
"canImportItems": {"member": true, "staff": true, "admin": true},
|
||||||
"canTranscribeAudio": {},
|
|
||||||
"canManageDocuments": {"member": true, "staff": true, "admin": true},
|
"canManageDocuments": {"member": true, "staff": true, "admin": true},
|
||||||
"canManageEntities": {"member": true, "staff": true, "admin": true},
|
"canManageEntities": {"member": true, "staff": true, "admin": true},
|
||||||
"canManageHome": {"staff": true, "admin": true},
|
"canManageHome": {"staff": true, "admin": true},
|
||||||
|
|
@ -1281,8 +1280,8 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
||||||
240, 288, 360, 432, 480, 720 and 1080.
|
240, 288, 360, 432, 480, 720 and 1080.
|
||||||
*/
|
*/
|
||||||
"video": {
|
"video": {
|
||||||
"downloadFormat": "mp4",
|
"downloadFormat": "webm",
|
||||||
"formats": ["mp4"],
|
"formats": ["webm", "mp4"],
|
||||||
"previewRatio": 1.3333333333,
|
"previewRatio": 1.3333333333,
|
||||||
"resolutions": [240, 480]
|
"resolutions": [240, 480]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ from django.conf import settings
|
||||||
|
|
||||||
logger = logging.getLogger('pandora.' + __name__)
|
logger = logging.getLogger('pandora.' + __name__)
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = ('png', 'jpg', 'webp', 'heic', 'heif')
|
|
||||||
|
|
||||||
def extract_text(pdf, page=None):
|
def extract_text(pdf, page=None):
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
|
@ -54,7 +53,7 @@ class FulltextMixin:
|
||||||
if self.file:
|
if self.file:
|
||||||
if self.extension == 'pdf':
|
if self.extension == 'pdf':
|
||||||
return extract_text(self.file.path)
|
return extract_text(self.file.path)
|
||||||
elif self.extension in IMAGE_EXTENSIONS:
|
elif self.extension in ('png', 'jpg'):
|
||||||
return ocr_image(self.file.path)
|
return ocr_image(self.file.path)
|
||||||
elif self.extension == 'html':
|
elif self.extension == 'html':
|
||||||
return self.data.get('text', '')
|
return self.data.get('text', '')
|
||||||
|
|
@ -181,7 +180,7 @@ class FulltextPageMixin(FulltextMixin):
|
||||||
if self.document.file:
|
if self.document.file:
|
||||||
if self.document.extension == 'pdf':
|
if self.document.extension == 'pdf':
|
||||||
return extract_text(self.document.file.path, self.page)
|
return extract_text(self.document.file.path, self.page)
|
||||||
elif self.extension in IMAGE_EXTENSIONS:
|
elif self.extension in ('png', 'jpg'):
|
||||||
return ocr_image(self.document.file.path)
|
return ocr_image(self.document.file.path)
|
||||||
elif self.extension == 'html':
|
elif self.extension == 'html':
|
||||||
# FIXME: is there a nice way to split that into pages
|
# FIXME: is there a nice way to split that into pages
|
||||||
|
|
|
||||||
|
|
@ -521,17 +521,14 @@ class Document(models.Model, FulltextMixin):
|
||||||
return save_chunk(self, self.file, chunk, offset, name, done_cb)
|
return save_chunk(self, self.file, chunk, offset, name, done_cb)
|
||||||
return False, 0
|
return False, 0
|
||||||
|
|
||||||
def thumbnail(self, size=None, page=None, accept=None):
|
def thumbnail(self, size=None, page=None):
|
||||||
if not self.file:
|
if not self.file:
|
||||||
return os.path.join(settings.STATIC_ROOT, 'png/document.png')
|
return os.path.join(settings.STATIC_ROOT, 'png/document.png')
|
||||||
src = self.file.path
|
src = self.file.path
|
||||||
folder = os.path.dirname(src)
|
folder = os.path.dirname(src)
|
||||||
if size:
|
if size:
|
||||||
size = int(size)
|
size = int(size)
|
||||||
ext = 'jpg'
|
path = os.path.join(folder, '%d.jpg' % size)
|
||||||
if accept and 'image/avif' in accept and size > 512:
|
|
||||||
ext = 'avif'
|
|
||||||
path = os.path.join(folder, '%d.%s' % (size, ext))
|
|
||||||
else:
|
else:
|
||||||
path = src
|
path = src
|
||||||
if self.extension == 'pdf':
|
if self.extension == 'pdf':
|
||||||
|
|
@ -564,7 +561,7 @@ class Document(models.Model, FulltextMixin):
|
||||||
path = os.path.join(folder, '%dp%d,%s.jpg' % (size, page, ','.join(map(str, crop))))
|
path = os.path.join(folder, '%dp%d,%s.jpg' % (size, page, ','.join(map(str, crop))))
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
resize_image(src, path, size=size)
|
resize_image(src, path, size=size)
|
||||||
elif self.extension in ('jpg', 'png', 'gif', 'webp', 'heic', 'heif'):
|
elif self.extension in ('jpg', 'png', 'gif'):
|
||||||
if os.path.exists(src):
|
if os.path.exists(src):
|
||||||
if size and page:
|
if size and page:
|
||||||
crop = list(map(int, page.split(',')))
|
crop = list(map(int, page.split(',')))
|
||||||
|
|
@ -577,7 +574,7 @@ class Document(models.Model, FulltextMixin):
|
||||||
img = open_image_rgb(path)
|
img = open_image_rgb(path)
|
||||||
src = path
|
src = path
|
||||||
if size < max(img.size):
|
if size < max(img.size):
|
||||||
path = os.path.join(folder, '%sp%s.%s' % (size, ','.join(map(str, crop)), ext))
|
path = os.path.join(folder, '%sp%s.jpg' % (size, ','.join(map(str, crop))))
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
resize_image(src, path, size=size)
|
resize_image(src, path, size=size)
|
||||||
if os.path.exists(src) and not os.path.exists(path):
|
if os.path.exists(src) and not os.path.exists(path):
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from glob import glob
|
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from glob import glob
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
import ox
|
import ox
|
||||||
|
|
@ -379,24 +378,15 @@ actions.register(sortDocuments, cache=False)
|
||||||
|
|
||||||
def file(request, id, name=None):
|
def file(request, id, name=None):
|
||||||
document = get_document_or_404_json(request, id)
|
document = get_document_or_404_json(request, id)
|
||||||
accept = request.headers.get("Accept")
|
|
||||||
mime_type = mimetypes.guess_type(document.file.path)[0]
|
|
||||||
mime_type = 'image/%s' % document.extension
|
|
||||||
if accept and 'image/' in accept and document.extension in (
|
|
||||||
'webp', 'heif', 'heic', 'avif', 'tiff'
|
|
||||||
) and mime_type not in accept:
|
|
||||||
image_size = max(document.width, document.height)
|
|
||||||
return HttpFileResponse(document.thumbnail(image_size, accept=accept))
|
|
||||||
return HttpFileResponse(document.file.path)
|
return HttpFileResponse(document.file.path)
|
||||||
|
|
||||||
def thumbnail(request, id, size=256, page=None):
|
def thumbnail(request, id, size=256, page=None):
|
||||||
size = int(size)
|
size = int(size)
|
||||||
document = get_document_or_404_json(request, id)
|
document = get_document_or_404_json(request, id)
|
||||||
accept = request.headers.get("Accept")
|
|
||||||
if "q" in request.GET and page:
|
if "q" in request.GET and page:
|
||||||
img = document.highlight_page(page, request.GET["q"], size)
|
img = document.highlight_page(page, request.GET["q"], size)
|
||||||
return HttpResponse(img, content_type="image/jpeg")
|
return HttpResponse(img, content_type="image/jpeg")
|
||||||
return HttpFileResponse(document.thumbnail(size, page=page, accept=accept))
|
return HttpFileResponse(document.thumbnail(size, page=page))
|
||||||
|
|
||||||
|
|
||||||
@login_required_json
|
@login_required_json
|
||||||
|
|
|
||||||
|
|
@ -495,9 +495,6 @@ class Clip(models.Model):
|
||||||
'id': self.get_id(),
|
'id': self.get_id(),
|
||||||
'index': self.index,
|
'index': self.index,
|
||||||
'volume': self.volume,
|
'volume': self.volume,
|
||||||
'hue': self.hue,
|
|
||||||
'saturation': self.saturation,
|
|
||||||
'lightness': self.lightness,
|
|
||||||
}
|
}
|
||||||
if self.annotation:
|
if self.annotation:
|
||||||
data['annotation'] = self.annotation.public_id
|
data['annotation'] = self.annotation.public_id
|
||||||
|
|
|
||||||
|
|
@ -1375,7 +1375,7 @@ class Item(models.Model):
|
||||||
self.poster_height = self.poster.height
|
self.poster_height = self.poster.height
|
||||||
self.poster_width = self.poster.width
|
self.poster_width = self.poster.width
|
||||||
self.clear_poster_cache(self.poster.path)
|
self.clear_poster_cache(self.poster.path)
|
||||||
if self.poster_width and self.cache.get('posterRatio') != self.poster_width / self.poster_height:
|
if self.cache.get('posterRatio') != self.poster_width / self.poster_height:
|
||||||
self.update_cache(poster_width=self.poster_width,
|
self.update_cache(poster_width=self.poster_width,
|
||||||
poster_height=self.poster_height)
|
poster_height=self.poster_height)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1004,9 +1004,7 @@ def download_source(request, id, part=None):
|
||||||
response['Content-Disposition'] = "attachment; filename*=UTF-8''%s" % quote(filename.encode('utf-8'))
|
response['Content-Disposition'] = "attachment; filename*=UTF-8''%s" % quote(filename.encode('utf-8'))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def download(request, id, resolution=None, format=None, part=None):
|
def download(request, id, resolution=None, format='webm', part=None):
|
||||||
if format is None:
|
|
||||||
format = settings.CONFIG['video']['formats'][0]
|
|
||||||
item = get_object_or_404(models.Item, public_id=id)
|
item = get_object_or_404(models.Item, public_id=id)
|
||||||
if not resolution or int(resolution) not in settings.CONFIG['video']['resolutions']:
|
if not resolution or int(resolution) not in settings.CONFIG['video']['resolutions']:
|
||||||
resolution = max(settings.CONFIG['video']['resolutions'])
|
resolution = max(settings.CONFIG['video']['resolutions'])
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,8 @@ def api(request):
|
||||||
return response
|
return response
|
||||||
if request.META.get('CONTENT_TYPE') == 'application/json':
|
if request.META.get('CONTENT_TYPE') == 'application/json':
|
||||||
r = json.loads(request.body.decode('utf-8'))
|
r = json.loads(request.body.decode('utf-8'))
|
||||||
if 'action' not in r:
|
action = r['action']
|
||||||
logger.error("invalid api request: %s", r)
|
data = r.get('data', {})
|
||||||
response = render_to_json_response(json_response(status=400,
|
|
||||||
text='Invalid request'))
|
|
||||||
response['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return response
|
|
||||||
else:
|
|
||||||
action = r['action']
|
|
||||||
data = r.get('data', {})
|
|
||||||
else:
|
else:
|
||||||
action = request.POST['action']
|
action = request.POST['action']
|
||||||
data = json.loads(request.POST.get('data', '{}'))
|
data = json.loads(request.POST.get('data', '{}'))
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ ROOT_URLCONF = 'urls'
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'mozilla_django_oidc',
|
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
|
|
@ -159,27 +158,6 @@ INSTALLED_APPS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'system.User'
|
AUTH_USER_MODEL = 'system.User'
|
||||||
AUTH_PROFILE_MODULE = 'user.UserProfile'
|
|
||||||
AUTH_CHECK_USERNAME = True
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
|
||||||
)
|
|
||||||
|
|
||||||
# OpenID Connect login support
|
|
||||||
LOGIN_REDIRECT_URL = "/grid"
|
|
||||||
LOGOUT_REDIRECT_URL = "/grid"
|
|
||||||
OIDC_USERNAME_ALGO = "app.oidc.generate_username"
|
|
||||||
OIDC_RP_CLIENT_ID = None
|
|
||||||
|
|
||||||
# define those in local_settings to enable OCID based login
|
|
||||||
#OIDC_RP_CLIENT_ID = '<client id>'
|
|
||||||
#OIDC_RP_CLIENT_SECRET = '<client secret>'
|
|
||||||
#OIDC_RP_SIGN_ALGO = "RS256"
|
|
||||||
#OIDC_OP_JWKS_ENDPOINT = "<jwks endpoint>"
|
|
||||||
#OIDC_OP_AUTHORIZATION_ENDPOINT = "<authorization endpoint>"
|
|
||||||
#OIDC_OP_TOKEN_ENDPOINT = "<token endpoint>"
|
|
||||||
#OIDC_OP_USER_ENDPOINT = "<user endpoint>"
|
|
||||||
|
|
||||||
# Log errors into db
|
# Log errors into db
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
|
@ -215,6 +193,8 @@ CACHES = {
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_PROFILE_MODULE = 'user.UserProfile'
|
||||||
|
AUTH_CHECK_USERNAME = True
|
||||||
FFMPEG = 'ffmpeg'
|
FFMPEG = 'ffmpeg'
|
||||||
FFPROBE = 'ffprobe'
|
FFPROBE = 'ffprobe'
|
||||||
USE_VP9 = True
|
USE_VP9 = True
|
||||||
|
|
@ -311,8 +291,6 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 32 * 1024 * 1024
|
||||||
|
|
||||||
EMPTY_CLIPS = True
|
EMPTY_CLIPS = True
|
||||||
|
|
||||||
YT_DLP_EXTRA = []
|
|
||||||
|
|
||||||
#you can ignore things below this line
|
#you can ignore things below this line
|
||||||
#=========================================================================
|
#=========================================================================
|
||||||
LOCAL_APPS = []
|
LOCAL_APPS = []
|
||||||
|
|
@ -343,7 +321,3 @@ except NameError:
|
||||||
|
|
||||||
INSTALLED_APPS = tuple(list(INSTALLED_APPS) + LOCAL_APPS)
|
INSTALLED_APPS = tuple(list(INSTALLED_APPS) + LOCAL_APPS)
|
||||||
|
|
||||||
if OIDC_RP_CLIENT_ID:
|
|
||||||
AUTHENTICATION_BACKENDS = list(AUTHENTICATION_BACKENDS) + [
|
|
||||||
'app.oidc.OIDCAuthenticationBackend'
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,6 @@ class Task(models.Model):
|
||||||
except Item.DoesNotExist:
|
except Item.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.status == 'transcribing':
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.item.files.filter(wanted=True, available=False).count():
|
if self.item.files.filter(wanted=True, available=False).count():
|
||||||
status = 'pending'
|
status = 'pending'
|
||||||
elif self.item.files.filter(uploading=True).count():
|
elif self.item.files.filter(uploading=True).count():
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<meta property="og:site_name" content="{{ settings.SITENAME }}"/>
|
<meta property="og:site_name" content="{{ settings.SITENAME }}"/>
|
||||||
{% compress css file m %}
|
{% compress css file m %}
|
||||||
<link rel="stylesheet" href="{% static 'mobile/css/reset.css' %}">
|
<link rel="stylesheet" href="{% static 'mobile/css/reset.css' %}"></link>
|
||||||
<link rel="stylesheet" href="{% static 'mobile/css/style.css' %}">
|
<link rel="stylesheet" href="{% static 'mobile/css/style.css' %}"></link>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
<meta name="google" value="notranslate"/>
|
<meta name="google" value="notranslate"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import os
|
import os
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from django.urls import path, re_path, include
|
from django.urls import path, re_path
|
||||||
from oxdjango.http import HttpFileResponse
|
from oxdjango.http import HttpFileResponse
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -33,15 +33,9 @@ import urlalias.views
|
||||||
def serve_static_file(path, location, content_type):
|
def serve_static_file(path, location, content_type):
|
||||||
return HttpFileResponse(location, content_type=content_type)
|
return HttpFileResponse(location, content_type=content_type)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
#path('admin/', admin.site.urls),
|
#path('admin/', admin.site.urls),
|
||||||
]
|
|
||||||
if settings.OIDC_RP_CLIENT_ID:
|
|
||||||
urlpatterns += [
|
|
||||||
path('oidc/', include('mozilla_django_oidc.urls')),
|
|
||||||
]
|
|
||||||
urlpatterns += [
|
|
||||||
re_path(r'^api/locale.(?P<lang>.*).json$', translation.views.locale_json),
|
re_path(r'^api/locale.(?P<lang>.*).json$', translation.views.locale_json),
|
||||||
re_path(r'^api/upload/text/?$', text.views.upload),
|
re_path(r'^api/upload/text/?$', text.views.upload),
|
||||||
re_path(r'^api/upload/document/?$', document.views.upload),
|
re_path(r'^api/upload/document/?$', document.views.upload),
|
||||||
|
|
@ -53,7 +47,7 @@ urlpatterns += [
|
||||||
re_path(r'^resetUI$', user.views.reset_ui),
|
re_path(r'^resetUI$', user.views.reset_ui),
|
||||||
re_path(r'^collection/(?P<id>.*?)/icon(?P<size>\d*).jpg$', documentcollection.views.icon),
|
re_path(r'^collection/(?P<id>.*?)/icon(?P<size>\d*).jpg$', documentcollection.views.icon),
|
||||||
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<size>\d*)p(?P<page>[\d,]*).jpg$', document.views.thumbnail),
|
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<size>\d*)p(?P<page>[\d,]*).jpg$', document.views.thumbnail),
|
||||||
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<name>.*?\.[^\d]{3,4})$', document.views.file),
|
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<name>.*?\.[^\d]{3})$', document.views.file),
|
||||||
re_path(r'^documents/(?P<fragment>.*?)$', document.views.document),
|
re_path(r'^documents/(?P<fragment>.*?)$', document.views.document),
|
||||||
re_path(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),
|
re_path(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),
|
||||||
re_path(r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', itemlist.views.icon),
|
re_path(r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', itemlist.views.icon),
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,8 @@ def has_capability(user, capability):
|
||||||
level = 'guest'
|
level = 'guest'
|
||||||
else:
|
else:
|
||||||
level = user.profile.get_level()
|
level = user.profile.get_level()
|
||||||
return settings.CONFIG['capabilities'].get(capability, {}).get(level)
|
return level in settings.CONFIG['capabilities'][capability] \
|
||||||
|
and settings.CONFIG['capabilities'][capability][level]
|
||||||
|
|
||||||
def merge_users(old, new):
|
def merge_users(old, new):
|
||||||
old.annotations.all().update(user=new)
|
old.annotations.all().update(user=new)
|
||||||
|
|
|
||||||
|
|
@ -3,38 +3,6 @@ from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception
|
||||||
import ox
|
import ox
|
||||||
|
|
||||||
|
|
||||||
def prepare_user(user):
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.conf import settings
|
|
||||||
from itemlist.models import List, Position
|
|
||||||
from django.db.models import Max
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
first_user_qs = User.objects.all()
|
|
||||||
if user.id:
|
|
||||||
first_user_qs = first_user_qs.exclude(id=user.id)
|
|
||||||
if first_user_qs.count() == 0:
|
|
||||||
user.is_superuser = True
|
|
||||||
user.is_staff = True
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
for l in settings.CONFIG['personalLists']:
|
|
||||||
list = List(name=l['title'], user=user)
|
|
||||||
for key in ('query', 'public', 'featured'):
|
|
||||||
if key in l:
|
|
||||||
setattr(list, key, l[key])
|
|
||||||
if key == 'query':
|
|
||||||
for c in list.query['conditions']:
|
|
||||||
if c['key'] == 'user':
|
|
||||||
c['value'] = c['value'].format(username=user.username)
|
|
||||||
list.save()
|
|
||||||
pos = Position(list=list, section='personal', user=user)
|
|
||||||
qs = Position.objects.filter(user=user, section='personal')
|
|
||||||
pos.position = (qs.aggregate(Max('position'))['position__max'] or 0) + 1
|
|
||||||
pos.save()
|
|
||||||
|
|
||||||
|
|
||||||
def get_ip(request):
|
def get_ip(request):
|
||||||
if 'HTTP_X_FORWARDED_FOR' in request.META:
|
if 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||||
ip = request.META['HTTP_X_FORWARDED_FOR'].split(',')[0]
|
ip = request.META['HTTP_X_FORWARDED_FOR'].split(',')[0]
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from user.models import Group
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from .decorators import capability_required_json
|
from .decorators import capability_required_json
|
||||||
from .utils import rename_user, prepare_user
|
from .utils import rename_user
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
@ -177,10 +177,28 @@ def signup(request, data):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
|
first_user = User.objects.count() == 0
|
||||||
user = User(username=data['username'], email=data['email'])
|
user = User(username=data['username'], email=data['email'])
|
||||||
user.set_password(data['password'])
|
user.set_password(data['password'])
|
||||||
|
#make first user admin
|
||||||
|
user.is_superuser = first_user
|
||||||
|
user.is_staff = first_user
|
||||||
user.save()
|
user.save()
|
||||||
prepare_user(user)
|
#create default user lists:
|
||||||
|
for l in settings.CONFIG['personalLists']:
|
||||||
|
list = List(name=l['title'], user=user)
|
||||||
|
for key in ('query', 'public', 'featured'):
|
||||||
|
if key in l:
|
||||||
|
setattr(list, key, l[key])
|
||||||
|
if key == 'query':
|
||||||
|
for c in list.query['conditions']:
|
||||||
|
if c['key'] == 'user':
|
||||||
|
c['value'] = c['value'].format(username=user.username)
|
||||||
|
list.save()
|
||||||
|
pos = Position(list=list, section='personal', user=user)
|
||||||
|
qs = Position.objects.filter(user=user, section='personal')
|
||||||
|
pos.position = (qs.aggregate(Max('position'))['position__max'] or 0) + 1
|
||||||
|
pos.save()
|
||||||
if request.session.session_key:
|
if request.session.session_key:
|
||||||
models.SessionData.objects.filter(session_key=request.session.session_key).update(user=user)
|
models.SessionData.objects.filter(session_key=request.session.session_key).update(user=user)
|
||||||
ui = json.loads(request.session.get('ui', 'null'))
|
ui = json.loads(request.session.get('ui', 'null'))
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,3 @@ future
|
||||||
pytz
|
pytz
|
||||||
pypdfium2
|
pypdfium2
|
||||||
Pillow>=10
|
Pillow>=10
|
||||||
pillow-heif
|
|
||||||
pillow-avif-plugin
|
|
||||||
mozilla-django-oidc==4.0.1
|
|
||||||
|
|
|
||||||
|
|
@ -106,13 +106,6 @@ pandora.ui.addFilesDialog = function(options) {
|
||||||
});
|
});
|
||||||
|
|
||||||
var selectItems = [];
|
var selectItems = [];
|
||||||
selectItems.push({
|
|
||||||
id: 'one',
|
|
||||||
title: Ox._(
|
|
||||||
options.items.length > 1 ? 'Create new {0} with multiple parts' : 'Create new {0}',
|
|
||||||
[pandora.site.itemName.singular.toLowerCase()]
|
|
||||||
)
|
|
||||||
});
|
|
||||||
if (pandora.user.ui.item && options.editable) {
|
if (pandora.user.ui.item && options.editable) {
|
||||||
selectItems.push({
|
selectItems.push({
|
||||||
id: 'add',
|
id: 'add',
|
||||||
|
|
@ -131,6 +124,13 @@ pandora.ui.addFilesDialog = function(options) {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
selectItems.push({
|
||||||
|
id: 'one',
|
||||||
|
title: Ox._(
|
||||||
|
options.items.length > 1 ? 'Create new {0} with multiple parts' : 'Create new {0}',
|
||||||
|
[pandora.site.itemName.singular.toLowerCase()]
|
||||||
|
)
|
||||||
|
});
|
||||||
var $select = Ox.Select({
|
var $select = Ox.Select({
|
||||||
items: selectItems,
|
items: selectItems,
|
||||||
width: 256
|
width: 256
|
||||||
|
|
|
||||||
|
|
@ -100,10 +100,6 @@ pandora.ui.appPanel = function() {
|
||||||
pandora.$ui.siteDialog = pandora.ui.siteDialog(page).open();
|
pandora.$ui.siteDialog = pandora.ui.siteDialog(page).open();
|
||||||
}
|
}
|
||||||
} else if (['signup', 'signin'].indexOf(page) > -1) {
|
} else if (['signup', 'signin'].indexOf(page) > -1) {
|
||||||
if (pandora.site.site.oidc) {
|
|
||||||
pandora.oidcLogin()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (pandora.user.level == 'guest') {
|
if (pandora.user.level == 'guest') {
|
||||||
if (pandora.$ui.accountDialog && pandora.$ui.accountDialog.is(':visible')) {
|
if (pandora.$ui.accountDialog && pandora.$ui.accountDialog.is(':visible')) {
|
||||||
pandora.$ui.accountDialog.options(pandora.ui.accountDialogOptions(page));
|
pandora.$ui.accountDialog.options(pandora.ui.accountDialogOptions(page));
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,11 @@ pandora.ui.editor = function(data) {
|
||||||
censoredIcon: pandora.site.cantPlay.icon,
|
censoredIcon: pandora.site.cantPlay.icon,
|
||||||
censoredTooltip: Ox._(pandora.site.cantPlay.text),
|
censoredTooltip: Ox._(pandora.site.cantPlay.text),
|
||||||
clickLink: pandora.clickLink,
|
clickLink: pandora.clickLink,
|
||||||
confirmDeleteDialog: confirmDeleteDialog,
|
|
||||||
cuts: data.cuts || [],
|
cuts: data.cuts || [],
|
||||||
duration: data.duration,
|
duration: data.duration,
|
||||||
enableDownload: pandora.hasCapability('canDownloadVideo') >= data.rightslevel || data.editable,
|
enableDownload: pandora.hasCapability('canDownloadVideo') >= data.rightslevel || data.editable,
|
||||||
enableExport: pandora.hasCapability('canExportAnnotations') || data.editable,
|
enableExport: pandora.hasCapability('canExportAnnotations') || data.editable,
|
||||||
enableImport: pandora.hasCapability('canImportAnnotations') || data.editable,
|
enableImport: pandora.hasCapability('canImportAnnotations') || data.editable,
|
||||||
enableTranscribe: pandora.hasCapability('canTranscribeAudio') && data.editable,
|
|
||||||
enableSetPosterFrame: !pandora.site.media.importFrames && data.editable,
|
enableSetPosterFrame: !pandora.site.media.importFrames && data.editable,
|
||||||
enableSubtitles: ui.videoSubtitles,
|
enableSubtitles: ui.videoSubtitles,
|
||||||
find: ui.itemFind,
|
find: ui.itemFind,
|
||||||
|
|
@ -222,9 +220,7 @@ pandora.ui.editor = function(data) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
that.updateAnnotation(data.id, result.data);
|
that.updateAnnotation(data.id, result.data);
|
||||||
if (result.data.id) {
|
pandora.UI.set('videoPoints.' + ui.item + '.annotation', result.data.id.split('/')[1] || '');
|
||||||
pandora.UI.set('videoPoints.' + ui.item + '.annotation', result.data.id.split('/')[1] || '');
|
|
||||||
}
|
|
||||||
Ox.Request.clearCache();
|
Ox.Request.clearCache();
|
||||||
};
|
};
|
||||||
var edit = {
|
var edit = {
|
||||||
|
|
@ -395,21 +391,7 @@ pandora.ui.editor = function(data) {
|
||||||
pandora.UI.set({videoResolution: data.resolution});
|
pandora.UI.set({videoResolution: data.resolution});
|
||||||
},
|
},
|
||||||
select: function(data) {
|
select: function(data) {
|
||||||
if (Ox.isArray(data.id)) {
|
pandora.UI.set('videoPoints.' + ui.item + '.annotation', data.id.split('/')[1] || '');
|
||||||
var range = data.id.map(id => {
|
|
||||||
return getAnnotationById(id)
|
|
||||||
})
|
|
||||||
data['in'] = Ox.min(range.map(annotation => { return annotation["in"]; }))
|
|
||||||
data['out'] = Ox.max(range.map(annotation => { return annotation["out"]; }))
|
|
||||||
pandora.UI.set('videoPoints.' + ui.item, {
|
|
||||||
annotation: '',
|
|
||||||
'in': data['in'],
|
|
||||||
out: data.out,
|
|
||||||
position: ui.videoPoints[ui.item].position
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
pandora.UI.set('videoPoints.' + ui.item + '.annotation', data.id.split('/')[1] || '');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
showentityinfo: function(data) {
|
showentityinfo: function(data) {
|
||||||
pandora.URL.push('/entities/' + data.id)
|
pandora.URL.push('/entities/' + data.id)
|
||||||
|
|
@ -435,54 +417,6 @@ pandora.ui.editor = function(data) {
|
||||||
togglesize: function(data) {
|
togglesize: function(data) {
|
||||||
pandora.UI.set({videoSize: data.size});
|
pandora.UI.set({videoSize: data.size});
|
||||||
},
|
},
|
||||||
transcribeaudio: function() {
|
|
||||||
const $dialog = pandora.ui.iconDialog({
|
|
||||||
buttons: [
|
|
||||||
Ox.Button({
|
|
||||||
id: 'cancel',
|
|
||||||
title: Ox._('Cancel')
|
|
||||||
})
|
|
||||||
.bindEvent({
|
|
||||||
click: function() {
|
|
||||||
$dialog.close();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Ox.Button({
|
|
||||||
id: 'transcribe',
|
|
||||||
title: Ox._('Transcribe Audio')
|
|
||||||
})
|
|
||||||
.bindEvent({
|
|
||||||
click: function() {
|
|
||||||
$dialog.close();
|
|
||||||
pandora.api.transcribeAudio({
|
|
||||||
item: pandora.user.ui.item
|
|
||||||
}, function(result) {
|
|
||||||
if (result.data.taskId) {
|
|
||||||
pandora.wait(result.data.taskId, function(result) {
|
|
||||||
Ox.Request.clearCache();
|
|
||||||
if (ui.item == data.id && ui.itemView == 'editor') {
|
|
||||||
pandora.$ui.contentPanel.replaceElement(
|
|
||||||
1, pandora.$ui.item = pandora.ui.item()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
pandora.ui.tasksDialog().open();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
content: Ox._(
|
|
||||||
'Are you sure you want to add automated transcription to "{0}"',
|
|
||||||
[data.title]
|
|
||||||
),
|
|
||||||
keys: {enter: 'transcribe', escape: 'cancel'},
|
|
||||||
title: Ox._(
|
|
||||||
'Transcribe {0} {1}', [data.title]
|
|
||||||
)
|
|
||||||
});
|
|
||||||
$dialog.open()
|
|
||||||
},
|
|
||||||
pandora_showannotations: function(data) {
|
pandora_showannotations: function(data) {
|
||||||
that.options({showAnnotations: data.value});
|
that.options({showAnnotations: data.value});
|
||||||
},
|
},
|
||||||
|
|
@ -494,60 +428,6 @@ pandora.ui.editor = function(data) {
|
||||||
|
|
||||||
pandora._dontSelectResult = false;
|
pandora._dontSelectResult = false;
|
||||||
|
|
||||||
function confirmDeleteDialog(options, callback) {
|
|
||||||
const title = Ox.getObjectById(
|
|
||||||
pandora.site.layers,
|
|
||||||
getAnnotationById(options.items[0]).layer
|
|
||||||
).item
|
|
||||||
const subject = options.items.length == 1 ? title : title + 's'
|
|
||||||
const $dialog = pandora.ui.iconDialog({
|
|
||||||
buttons: [
|
|
||||||
Ox.Button({
|
|
||||||
id: 'cancel',
|
|
||||||
title: Ox._('Keep {0}', [Ox._(subject)])
|
|
||||||
})
|
|
||||||
.bindEvent({
|
|
||||||
click: function() {
|
|
||||||
$dialog.close();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Ox.Button({
|
|
||||||
id: 'delete',
|
|
||||||
title: Ox._('Delete {0}', [Ox._(subject)])
|
|
||||||
})
|
|
||||||
.bindEvent({
|
|
||||||
click: function() {
|
|
||||||
$dialog.close();
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
content: Ox._(
|
|
||||||
'Are you sure you want delete {0} {1}?<br><br>All data will be removed.',
|
|
||||||
[options.items.length, Ox._(subject.toLowerCase())]
|
|
||||||
),
|
|
||||||
height: 96,
|
|
||||||
keys: {enter: 'delete', escape: 'cancel'},
|
|
||||||
title: Ox._(
|
|
||||||
'Delete {0}', [Ox._(subject)]
|
|
||||||
)
|
|
||||||
});
|
|
||||||
$dialog.open()
|
|
||||||
return $dialog
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAnnotationById(id) {
|
|
||||||
var annotation
|
|
||||||
data.annotations.forEach(layer => {
|
|
||||||
layer.items.forEach(a => {
|
|
||||||
if (a.id == id) {
|
|
||||||
annotation = a
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return annotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBrowser() {
|
function updateBrowser() {
|
||||||
pandora.$ui.browser.find('img[src*="/' + ui.item + '/"]').each(function() {
|
pandora.$ui.browser.find('img[src*="/' + ui.item + '/"]').each(function() {
|
||||||
$(this).attr({
|
$(this).attr({
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,6 @@ pandora.ui.exportAnnotationsDialog = function(options) {
|
||||||
$button.wrap($('<a>'));
|
$button.wrap($('<a>'));
|
||||||
// On wrap, a reference to the link would *not* be the link in the DOM
|
// On wrap, a reference to the link would *not* be the link in the DOM
|
||||||
$link = $($button.parent());
|
$link = $($button.parent());
|
||||||
$link.on({
|
|
||||||
click: function() {
|
|
||||||
setTimeout(() => {
|
|
||||||
that.close()
|
|
||||||
}, 10)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
updateLink();
|
updateLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -424,35 +424,26 @@ pandora.ui.folders = function(section) {
|
||||||
}).bindEvent({
|
}).bindEvent({
|
||||||
click: function() {
|
click: function() {
|
||||||
var $dialog = pandora.ui.iconDialog({
|
var $dialog = pandora.ui.iconDialog({
|
||||||
buttons: title != Ox._('Featured ' + folderItems) ? [].concat(
|
buttons: title != Ox._('Featured ' + folderItems) ? [
|
||||||
pandora.site.site.oidc ? []
|
Ox.Button({title: Ox._('Sign Up...')}).bindEvent({
|
||||||
: [
|
click: function() {
|
||||||
Ox.Button({title: Ox._('Sign Up...')}).bindEvent({
|
$dialog.close();
|
||||||
click: function() {
|
pandora.$ui.accountDialog = pandora.ui.accountDialog('signup').open();
|
||||||
$dialog.close();
|
}
|
||||||
pandora.$ui.accountDialog = pandora.ui.accountDialog('signup').open();
|
}),
|
||||||
}
|
Ox.Button({title: Ox._('Sign In...')}).bindEvent({
|
||||||
})
|
click: function() {
|
||||||
],
|
$dialog.close();
|
||||||
[
|
pandora.$ui.accountDialog = pandora.ui.accountDialog('signin').open();
|
||||||
Ox.Button({title: Ox._('Sign In...')}).bindEvent({
|
}
|
||||||
click: function() {
|
}),
|
||||||
$dialog.close();
|
{},
|
||||||
if (pandora.site.site.oidc) {
|
Ox.Button({title: Ox._('Not Now')}).bindEvent({
|
||||||
pandora.oidcLogin()
|
click: function() {
|
||||||
} else {
|
$dialog.close();
|
||||||
pandora.$ui.accountDialog = pandora.ui.accountDialog('signin').open();
|
}
|
||||||
}
|
})
|
||||||
}
|
] : [
|
||||||
}),
|
|
||||||
{},
|
|
||||||
Ox.Button({title: Ox._('Not Now')}).bindEvent({
|
|
||||||
click: function() {
|
|
||||||
$dialog.close();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
): [
|
|
||||||
Ox.Button({title: Ox._('Close')}).bindEvent({
|
Ox.Button({title: Ox._('Close')}).bindEvent({
|
||||||
click: function() {
|
click: function() {
|
||||||
$dialog.close();
|
$dialog.close();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
pandora.getItemTitle = function(itemData, includeYear) {
|
pandora.getItemTitle = function(itemData, includeYear) {
|
||||||
var director = itemData.director
|
|
||||||
if (!Ox.isArray(director)) {
|
|
||||||
director = [director]
|
|
||||||
}
|
|
||||||
return (itemData.title || Ox._('Untitled')) + (
|
return (itemData.title || Ox._('Untitled')) + (
|
||||||
Ox.len(itemData.director) || (includeYear && itemData.year)
|
Ox.len(itemData.director) || (includeYear && itemData.year)
|
||||||
? ' (' + (
|
? ' (' + (
|
||||||
|
|
|
||||||
|
|
@ -137,26 +137,24 @@ pandora.ui.helpDialog = function() {
|
||||||
|
|
||||||
that.select = function(id) {
|
that.select = function(id) {
|
||||||
var img, $img;
|
var img, $img;
|
||||||
if ($text) {
|
$text.html(text[id || 'help']).scrollTop(0);
|
||||||
$text.html(text[id || 'help']).scrollTop(0);
|
img = $text.find('img');
|
||||||
img = $text.find('img');
|
if (img) {
|
||||||
if (img) {
|
$img = $(img);
|
||||||
$img = $(img);
|
$img.replaceWith(
|
||||||
$img.replaceWith(
|
$image = Ox.ImageElement(
|
||||||
$image = Ox.ImageElement(
|
Ox.extend(getImageSize(), {src: $img.attr('src')})
|
||||||
Ox.extend(getImageSize(), {src: $img.attr('src')})
|
)
|
||||||
)
|
.css({borderRadius: '8px'})
|
||||||
.css({borderRadius: '8px'})
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
$text.find('td:first-child')
|
|
||||||
.css({
|
|
||||||
height: '16px',
|
|
||||||
paddingRight: '8px',
|
|
||||||
textAlign: 'right',
|
|
||||||
whiteSpace: 'nowrap'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
$text.find('td:first-child')
|
||||||
|
.css({
|
||||||
|
height: '16px',
|
||||||
|
paddingRight: '8px',
|
||||||
|
textAlign: 'right',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
});
|
||||||
return that;
|
return that;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,13 +171,13 @@ pandora.ui.home = function() {
|
||||||
}),
|
}),
|
||||||
$signinButton = Ox.Button({
|
$signinButton = Ox.Button({
|
||||||
title: Ox._('Sign In'),
|
title: Ox._('Sign In'),
|
||||||
width: pandora.site.site.oidc ? 156 : 74
|
width: 74
|
||||||
})
|
})
|
||||||
.css({
|
.css({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 0,
|
left: 0,
|
||||||
top: '112px',
|
top: '112px',
|
||||||
right: pandora.site.site.oidc ? '164px' : '82px',
|
right: '82px',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
@ -248,13 +248,7 @@ pandora.ui.home = function() {
|
||||||
adjustRatio();
|
adjustRatio();
|
||||||
|
|
||||||
if (pandora.user.level == 'guest') {
|
if (pandora.user.level == 'guest') {
|
||||||
if (pandora.site.site.oidc) {
|
$signupButton.appendTo(that);
|
||||||
$signinButton.options({
|
|
||||||
width: 156
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
$signupButton.appendTo(that);
|
|
||||||
}
|
|
||||||
$signinButton.appendTo(that);
|
$signinButton.appendTo(that);
|
||||||
} else {
|
} else {
|
||||||
$preferencesButton.appendTo(that);
|
$preferencesButton.appendTo(that);
|
||||||
|
|
|
||||||
|
|
@ -201,11 +201,10 @@ pandora.ui.importAnnotationsDialog = function(options) {
|
||||||
pandora.$ui.contentPanel.replaceElement(
|
pandora.$ui.contentPanel.replaceElement(
|
||||||
1, pandora.$ui.item = pandora.ui.item()
|
1, pandora.$ui.item = pandora.ui.item()
|
||||||
);
|
);
|
||||||
that.close();
|
|
||||||
} else {
|
} else {
|
||||||
$status.html(Ox._('Import failed.'));
|
$status.html(Ox._('Import failed.'));
|
||||||
enableButtons();
|
|
||||||
}
|
}
|
||||||
|
enableButtons();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$status.html(Ox._('Import failed.'));
|
$status.html(Ox._('Import failed.'));
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ pandora.ui.item = function() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!Ox.isEmpty(set)) {
|
if (!Ox.isEmpty(set)) {
|
||||||
pandora.UI.set('videoPoints.' + item, Ox.extend(videoPoints, set))
|
pandora.UI.set('videoPoints.' + item, Ox.extend(videoPoints[point], set[point]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,9 +63,9 @@ pandora.ui.item = function() {
|
||||||
var tasks = result_.data.items.filter(function(task) { return task.item == item})
|
var tasks = result_.data.items.filter(function(task) { return task.item == item})
|
||||||
if (tasks.length > 0) {
|
if (tasks.length > 0) {
|
||||||
html = Ox._(
|
html = Ox._(
|
||||||
'<i>{0}</i> is currently being processed. '
|
'<i>{0}</i> is currently processed. '
|
||||||
+ '{1} view will be available in a moment.',
|
+ '{1} view will be available in a moment.',
|
||||||
[result.data.title, Ox.toTitleCase(Ox._(pandora.user.ui.itemView))]
|
[result.data.title, Ox._(pandora.user.ui.itemView)]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
note.html(html)
|
note.html(html)
|
||||||
|
|
@ -202,10 +202,6 @@ pandora.ui.item = function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// avoid loop while selecting multiple annotations
|
|
||||||
if (options.selected == '' && Ox.isArray(pandora.$ui[pandora.user.ui.itemView].options('selected'))) {
|
|
||||||
delete options.selected
|
|
||||||
}
|
|
||||||
pandora.$ui[pandora.user.ui.itemView].options(options);
|
pandora.$ui[pandora.user.ui.itemView].options(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ pandora.ui.list = function() {
|
||||||
item: function(data, sort, size) {
|
item: function(data, sort, size) {
|
||||||
size = 128;
|
size = 128;
|
||||||
var clipsQuery = pandora.getClipsQuery(),
|
var clipsQuery = pandora.getClipsQuery(),
|
||||||
isClipsQuery = clipsQuery.conditions.length > 1,
|
isClipsQuery = !!clipsQuery.conditions.length,
|
||||||
ratio = ui.icons == 'posters'
|
ratio = ui.icons == 'posters'
|
||||||
? (ui.showSitePosters ? pandora.site.posters.ratio : data.posterRatio) : 1,
|
? (ui.showSitePosters ? pandora.site.posters.ratio : data.posterRatio) : 1,
|
||||||
url = pandora.getMediaURL('/' + data.id + '/' + (
|
url = pandora.getMediaURL('/' + data.id + '/' + (
|
||||||
|
|
@ -352,7 +352,7 @@ pandora.ui.list = function() {
|
||||||
},
|
},
|
||||||
items: function(data, callback) {
|
items: function(data, callback) {
|
||||||
var clipsQuery = pandora.getClipsQuery(),
|
var clipsQuery = pandora.getClipsQuery(),
|
||||||
isClipsQuery = clipsQuery.conditions.length > 1;
|
isClipsQuery = !!clipsQuery.conditions.length;
|
||||||
pandora.api.find(Ox.extend(data, Ox.extend({
|
pandora.api.find(Ox.extend(data, Ox.extend({
|
||||||
query: ui.find
|
query: ui.find
|
||||||
}, isClipsQuery ? {clips: {
|
}, isClipsQuery ? {clips: {
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,7 @@ pandora.ui.mainMenu = function() {
|
||||||
{ id: 'tasks', title: Ox._('Tasks...'), disabled: isGuest },
|
{ id: 'tasks', title: Ox._('Tasks...'), disabled: isGuest },
|
||||||
{ id: 'archives', title: Ox._('Archives...'), disabled: /*isGuest*/ true },
|
{ id: 'archives', title: Ox._('Archives...'), disabled: /*isGuest*/ true },
|
||||||
{},
|
{},
|
||||||
!pandora.site.site.oidc
|
{ id: 'signup', title: Ox._('Sign Up...'), disabled: !isGuest },
|
||||||
? { id: 'signup', title: Ox._('Sign Up...'), disabled: !isGuest }
|
|
||||||
: [],
|
|
||||||
isGuest ? { id: 'signin', title: Ox._('Sign In...')}
|
isGuest ? { id: 'signin', title: Ox._('Sign In...')}
|
||||||
: { id: 'signout', title: Ox._('Sign Out...')}
|
: { id: 'signout', title: Ox._('Sign Out...')}
|
||||||
] },
|
] },
|
||||||
|
|
|
||||||
|
|
@ -142,22 +142,6 @@ pandora.ui.mediaView = function(options) {
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 360
|
width: 360
|
||||||
},
|
},
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
id: 'folder',
|
|
||||||
operator: '+',
|
|
||||||
title: Ox._('Folder'),
|
|
||||||
visible: false,
|
|
||||||
width: 360
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
id: 'filename',
|
|
||||||
operator: '+',
|
|
||||||
title: Ox._('Filename'),
|
|
||||||
visible: false,
|
|
||||||
width: 360
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
editable: true,
|
editable: true,
|
||||||
id: 'version',
|
id: 'version',
|
||||||
|
|
@ -457,8 +441,6 @@ pandora.ui.mediaView = function(options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).on({
|
|
||||||
keyup: updateForm
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -681,9 +663,7 @@ pandora.ui.mediaView = function(options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.$moveButton.options({
|
self.$moveButton.options({
|
||||||
disabled: self.selected.length == 0 || (
|
disabled: self.selected.length == 0
|
||||||
self.$idInput.value().length + self.$titleInput.value().length
|
|
||||||
) == 0
|
|
||||||
});
|
});
|
||||||
self.$menu[
|
self.$menu[
|
||||||
self.selected.length == 0 ? 'disableItem' : 'enableItem'
|
self.selected.length == 0 ? 'disableItem' : 'enableItem'
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ pandora.ui.metadataDialog = function(data) {
|
||||||
'To update the metadata for this {0}, please enter its IMDb ID.',
|
'To update the metadata for this {0}, please enter its IMDb ID.',
|
||||||
[pandora.site.itemName.singular.toLowerCase()]
|
[pandora.site.itemName.singular.toLowerCase()]
|
||||||
),
|
),
|
||||||
keys: {enter: 'update', escape: 'close'},
|
keyboard: {enter: 'update', escape: 'close'},
|
||||||
title: Ox._('Update Metadata')
|
title: Ox._('Update Metadata')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -354,10 +354,6 @@ appPanel
|
||||||
if (pandora.user.ui.itemView == 'video') {
|
if (pandora.user.ui.itemView == 'video') {
|
||||||
pandora.user.ui.itemView = 'player';
|
pandora.user.ui.itemView = 'player';
|
||||||
}
|
}
|
||||||
// item find accross relodas breaks links to in/out
|
|
||||||
if (pandora.user.ui.itemFind) {
|
|
||||||
pandora.user.ui.itemFind = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Ox.extend(pandora.site, {
|
Ox.extend(pandora.site, {
|
||||||
calendar: data.site.layers.some(function(layer) {
|
calendar: data.site.layers.some(function(layer) {
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,7 @@ pandora.ui.tasksDialog = function(options) {
|
||||||
'processing': 'Processing',
|
'processing': 'Processing',
|
||||||
'canceled': 'Canceled',
|
'canceled': 'Canceled',
|
||||||
'failed': 'Failed',
|
'failed': 'Failed',
|
||||||
'finished': 'Finished',
|
'finished': 'Finished'
|
||||||
'transcribing': 'Transcribing'
|
|
||||||
}[value] || value;
|
}[value] || value;
|
||||||
},
|
},
|
||||||
id: 'status',
|
id: 'status',
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
||||||
existingFiles = [],
|
existingFiles = [],
|
||||||
uploadFiles = [],
|
uploadFiles = [],
|
||||||
|
|
||||||
|
supportedExtensions = ['gif', 'jpg', 'jpeg', 'pdf', 'png'],
|
||||||
|
|
||||||
filename,
|
filename,
|
||||||
|
|
||||||
ids = [],
|
ids = [],
|
||||||
|
|
@ -70,7 +72,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Ox.every(extensions, function(extension) {
|
if (!Ox.every(extensions, function(extension) {
|
||||||
return Ox.contains(pandora.documentExtensions, extension)
|
return Ox.contains(supportedExtensions, extension)
|
||||||
})) {
|
})) {
|
||||||
return errorDialog(
|
return errorDialog(
|
||||||
Ox._('Supported file types are GIF, JPG, PNG and PDF.')
|
Ox._('Supported file types are GIF, JPG, PNG and PDF.')
|
||||||
|
|
|
||||||
|
|
@ -420,28 +420,13 @@ pandora.createLinks = function($element) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
pandora.imageExtensions = [
|
|
||||||
'avif',
|
|
||||||
'gif',
|
|
||||||
'heic',
|
|
||||||
'heif',
|
|
||||||
'jpeg',
|
|
||||||
'jpg',
|
|
||||||
'png',
|
|
||||||
'tiff',
|
|
||||||
'webp'
|
|
||||||
];
|
|
||||||
|
|
||||||
pandora.documentExtensions = [
|
|
||||||
'pdf', /* 'epub', 'txt', */
|
|
||||||
].concat(pandora.imageExtensions);
|
|
||||||
|
|
||||||
pandora.uploadDroppedFiles = function(files) {
|
pandora.uploadDroppedFiles = function(files) {
|
||||||
|
var documentExtensions = ['pdf', /* 'epub', 'txt', */ 'png', 'gif', 'jpg', 'jpeg'];
|
||||||
files = Ox.map(files, function(file) { return file });
|
files = Ox.map(files, function(file) { return file });
|
||||||
|
|
||||||
if (files.every(function(file) {
|
if (files.every(function(file) {
|
||||||
var extension = file.name.split('.').pop().toLowerCase()
|
var extension = file.name.split('.').pop().toLowerCase()
|
||||||
return Ox.contains(pandora.documentExtensions, extension)
|
return Ox.contains(documentExtensions, extension)
|
||||||
})) {
|
})) {
|
||||||
pandora.ui.uploadDocumentDialog({
|
pandora.ui.uploadDocumentDialog({
|
||||||
files: files
|
files: files
|
||||||
|
|
@ -2147,7 +2132,7 @@ pandora.getSpan = function(state, val, callback) {
|
||||||
} else {
|
} else {
|
||||||
state.span = val;
|
state.span = val;
|
||||||
}
|
}
|
||||||
} else if (Ox.contains(pandora.imageExtensions, extension)) {
|
} else if (Ox.contains(['gif', 'jpg', 'png'], extension)) {
|
||||||
values = val.split(',');
|
values = val.split(',');
|
||||||
if (values.length == 4) {
|
if (values.length == 4) {
|
||||||
state.span = values.map(function(number, index) {
|
state.span = values.map(function(number, index) {
|
||||||
|
|
@ -2665,11 +2650,6 @@ pandora.logEvent = function(data, event, element) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pandora.oidcLogin = function() {
|
|
||||||
Ox.LoadingScreen().css({zIndex: 100}).addClass('OxScreen').appendTo(document.body).show().start()
|
|
||||||
document.location.href = '/oidc/authenticate/';
|
|
||||||
};
|
|
||||||
|
|
||||||
pandora.openLicenseDialog = function() {
|
pandora.openLicenseDialog = function() {
|
||||||
if (!Ox.Focus.focusedElementIsInput() && !pandora.hasDialogOrScreen()) {
|
if (!Ox.Focus.focusedElementIsInput() && !pandora.hasDialogOrScreen()) {
|
||||||
pandora.ui.licenseDialog().open().bindEvent({
|
pandora.ui.licenseDialog().open().bindEvent({
|
||||||
|
|
|
||||||
|
|
@ -201,75 +201,3 @@ ol li {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.seekbar {
|
|
||||||
padding: 12px 22px;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.fullscreen .seekbar {
|
|
||||||
padding: 28px 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar-progress {
|
|
||||||
height: 10px;
|
|
||||||
border: solid 1px #B1B1B1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar-progress [role="progressbar"] {
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
background-color: #B1B1B180;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar-progress [role="progressbar"]:after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -10px;
|
|
||||||
background-color: #B1B1B1;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar input[type="range"] {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 2;
|
|
||||||
background: transparent;
|
|
||||||
outline: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar input[type="range"]::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: block;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar input[type="range"]::-moz-range-thumb {
|
|
||||||
display: block;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar input[type="range"]::-moz-range-track {
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.seekbar input[type="range"]::-moz-focus-outer {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ VideoElement <f> VideoElement Object
|
||||||
loop <b|false> loop playback
|
loop <b|false> loop playback
|
||||||
playbackRate <n|1> playback rate
|
playbackRate <n|1> playback rate
|
||||||
position <n|0> start position
|
position <n|0> start position
|
||||||
in <n|0> start offset
|
|
||||||
self <o> Shared private variable
|
self <o> Shared private variable
|
||||||
([options[, self]]) -> <o:Element> VideoElement Object
|
([options[, self]]) -> <o:Element> VideoElement Object
|
||||||
loadedmetadata <!> loadedmetadata
|
loadedmetadata <!> loadedmetadata
|
||||||
|
|
@ -39,7 +38,6 @@ window.VideoElement = function(options) {
|
||||||
muted: false,
|
muted: false,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
position: 0,
|
position: 0,
|
||||||
"in": 0,
|
|
||||||
volume: 1
|
volume: 1
|
||||||
}
|
}
|
||||||
Object.assign(self.options, options);
|
Object.assign(self.options, options);
|
||||||
|
|
@ -168,10 +166,9 @@ window.VideoElement = function(options) {
|
||||||
|
|
||||||
function getCurrentTime() {
|
function getCurrentTime() {
|
||||||
var item = self.items[self.currentItem];
|
var item = self.items[self.currentItem];
|
||||||
var currentTime = self.seeking || self.loading
|
return self.seeking || self.loading
|
||||||
? self.currentTime
|
? self.currentTime
|
||||||
: item ? item.position + self.video.currentTime - item['in'] - self.options["in"] : 0;
|
: item ? item.position + self.video.currentTime - item['in'] : 0;
|
||||||
return currentTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getset(key, value) {
|
function getset(key, value) {
|
||||||
|
|
@ -531,10 +528,6 @@ window.VideoElement = function(options) {
|
||||||
|
|
||||||
function setCurrentTime(time) {
|
function setCurrentTime(time) {
|
||||||
debug('Video', 'sCT', time);
|
debug('Video', 'sCT', time);
|
||||||
if (self.options["in"]) {
|
|
||||||
debug('Video', 'sCT shift time by', self.options["in"], 'to', time);
|
|
||||||
time += self.options["in"]
|
|
||||||
}
|
|
||||||
var currentTime, currentItem;
|
var currentTime, currentItem;
|
||||||
self.items.forEach(function(item, i) {
|
self.items.forEach(function(item, i) {
|
||||||
if (time >= item.position
|
if (time >= item.position
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ window.VideoPlayer = function(options) {
|
||||||
loop: false,
|
loop: false,
|
||||||
muted: false,
|
muted: false,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
"in": 0,
|
|
||||||
position: 0,
|
position: 0,
|
||||||
volume: 1
|
volume: 1
|
||||||
}
|
}
|
||||||
|
|
@ -154,11 +153,9 @@ window.VideoPlayer = function(options) {
|
||||||
${icon.mute}
|
${icon.mute}
|
||||||
</div>
|
</div>
|
||||||
<div class="position">
|
<div class="position">
|
||||||
<div class="seekbar">
|
<div class="bar">
|
||||||
<input type="range" value="0" min='0' max='100' step='.25' />
|
<div class="progress"></div>
|
||||||
<div class="seekbar-progress">
|
|
||||||
<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="38" style="width: 0%;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="time">
|
<div class="time">
|
||||||
|
|
@ -226,31 +223,15 @@ window.VideoPlayer = function(options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var showControls
|
var showControls
|
||||||
function hideControlsLater() {
|
|
||||||
if (showControls) {
|
|
||||||
clearTimeout(showControls)
|
|
||||||
}
|
|
||||||
showControls = setTimeout(() => {
|
|
||||||
if (touching) {
|
|
||||||
hideControlsLater()
|
|
||||||
} else {
|
|
||||||
self.controls.style.opacity = that.paused ? '1' : '0'
|
|
||||||
showControls = null
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
var toggleControls = event => {
|
var toggleControls = event => {
|
||||||
if (event.target.tagName == "INPUT") {
|
|
||||||
if (showControls) {
|
|
||||||
clearTimeout(showControls)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (self.controls.style.opacity == '0') {
|
if (self.controls.style.opacity == '0') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
self.controls.style.opacity = '1'
|
self.controls.style.opacity = '1'
|
||||||
hideControlsLater()
|
showControls = setTimeout(() => {
|
||||||
|
self.controls.style.opacity = that.paused ? '1' : '0'
|
||||||
|
showControls = null
|
||||||
|
}, 3000)
|
||||||
} else {
|
} else {
|
||||||
self.controls.style.opacity = '0'
|
self.controls.style.opacity = '0'
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +241,10 @@ window.VideoPlayer = function(options) {
|
||||||
clearTimeout(showControls)
|
clearTimeout(showControls)
|
||||||
}
|
}
|
||||||
self.controls.style.opacity = '1'
|
self.controls.style.opacity = '1'
|
||||||
hideControlsLater()
|
showControls = setTimeout(() => {
|
||||||
|
self.controls.style.opacity = that.paused ? '1' : '0'
|
||||||
|
showControls = null
|
||||||
|
}, 3000)
|
||||||
})
|
})
|
||||||
self.controls.addEventListener("mouseleave", event => {
|
self.controls.addEventListener("mouseleave", event => {
|
||||||
if (showControls) {
|
if (showControls) {
|
||||||
|
|
@ -269,13 +253,7 @@ window.VideoPlayer = function(options) {
|
||||||
self.controls.style.opacity = that.paused ? '1' : '0'
|
self.controls.style.opacity = that.paused ? '1' : '0'
|
||||||
showControls = null
|
showControls = null
|
||||||
})
|
})
|
||||||
self.controls.addEventListener("touchstart", event => {
|
|
||||||
touching = true
|
|
||||||
})
|
|
||||||
self.controls.addEventListener("touchstart", toggleControls)
|
self.controls.addEventListener("touchstart", toggleControls)
|
||||||
self.controls.addEventListener("touchend", event => {
|
|
||||||
touching = false
|
|
||||||
})
|
|
||||||
self.controls.querySelector('.toggle').addEventListener("click", toggleVideo)
|
self.controls.querySelector('.toggle').addEventListener("click", toggleVideo)
|
||||||
self.controls.querySelector('.volume').addEventListener("click", toggleSound)
|
self.controls.querySelector('.volume').addEventListener("click", toggleSound)
|
||||||
self.controls.querySelector('.fullscreen-btn').addEventListener("click", toggleFullscreen)
|
self.controls.querySelector('.fullscreen-btn').addEventListener("click", toggleFullscreen)
|
||||||
|
|
@ -332,7 +310,6 @@ window.VideoPlayer = function(options) {
|
||||||
that.append(unblock)
|
that.append(unblock)
|
||||||
})
|
})
|
||||||
var loading = true
|
var loading = true
|
||||||
var touching = false
|
|
||||||
that.brightness(0)
|
that.brightness(0)
|
||||||
that.addEventListener("loadedmetadata", event => {
|
that.addEventListener("loadedmetadata", event => {
|
||||||
//
|
//
|
||||||
|
|
@ -354,40 +331,42 @@ window.VideoPlayer = function(options) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var time = that.querySelector('.controls .time div');
|
var time = that.querySelector('.controls .time div'),
|
||||||
const progressbar = that.querySelector('.seekbar div[role="progressbar"]');
|
progress = that.querySelector('.controls .position .progress')
|
||||||
function setProgressPosition(value) {
|
that.querySelector('.controls .position').addEventListener("click", event => {
|
||||||
progressbar.style.width = value + '%';
|
var bar = event.target
|
||||||
progressbar.setAttribute('aria-valuenow', value);
|
while (bar && !bar.classList.contains('bar')) {
|
||||||
|
bar = bar.parentElement
|
||||||
}
|
|
||||||
that.querySelector('.controls .position input').addEventListener('input', event => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
setProgressPosition(event.target.value)
|
|
||||||
var position = event.target.value/100 * self.options.duration
|
|
||||||
displayTime(position)
|
|
||||||
that.currentTime(position)
|
|
||||||
hideControlsLater()
|
|
||||||
})
|
|
||||||
function displayTime(currentTime) {
|
|
||||||
duration = formatDuration(self.options.duration)
|
|
||||||
currentTime = formatDuration(currentTime)
|
|
||||||
while (duration && duration.startsWith('00:')) {
|
|
||||||
duration = duration.slice(3)
|
|
||||||
}
|
}
|
||||||
currentTime = currentTime.slice(currentTime.length - duration.length)
|
if (bar && bar.classList.contains('bar')) {
|
||||||
time.innerText = `${currentTime} / ${duration}`
|
event.preventDefault()
|
||||||
}
|
event.stopPropagation()
|
||||||
|
var rect = bar.getBoundingClientRect()
|
||||||
|
var x = event.clientX - rect.x
|
||||||
|
var percent = x / rect.width
|
||||||
|
var position = percent * self.options.duration
|
||||||
|
if (self.options.position) {
|
||||||
|
position += self.options.position
|
||||||
|
}
|
||||||
|
progress.style.width = (100 * percent) + '%'
|
||||||
|
that.currentTime(position)
|
||||||
|
}
|
||||||
|
})
|
||||||
that.addEventListener("timeupdate", event => {
|
that.addEventListener("timeupdate", event => {
|
||||||
var currentTime = that.currentTime(),
|
var currentTime = that.currentTime(),
|
||||||
duration = self.options.duration
|
duration = self.options.duration
|
||||||
if (self.options.position) {
|
if (self.options.position) {
|
||||||
currentTime -= self.options.position
|
currentTime -= self.options.position
|
||||||
}
|
}
|
||||||
setProgressPosition(100 * currentTime / duration)
|
progress.style.width = (100 * currentTime / duration) + '%'
|
||||||
displayTime(currentTime)
|
duration = formatDuration(duration)
|
||||||
|
currentTime = formatDuration(currentTime)
|
||||||
|
while (duration && duration.startsWith('00:')) {
|
||||||
|
duration = duration.slice(3)
|
||||||
|
}
|
||||||
|
currentTime = currentTime.slice(currentTime.length - duration.length)
|
||||||
|
time.innerText = `${currentTime} / ${duration}`
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
that.addEventListener("play", event => {
|
that.addEventListener("play", event => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,35 @@
|
||||||
|
|
||||||
|
const getSortValue = function(value) {
|
||||||
|
var sortValue = value;
|
||||||
|
function trim(value) {
|
||||||
|
return value.replace(/^\W+(?=\w)/, '');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isEmpty(value)
|
||||||
|
|| isNull(value)
|
||||||
|
|| isUndefined(value)
|
||||||
|
) {
|
||||||
|
sortValue = null;
|
||||||
|
} else if (isString(value)) {
|
||||||
|
// make lowercase and remove leading non-word characters
|
||||||
|
sortValue = trim(value.toLowerCase());
|
||||||
|
// move leading articles to the end
|
||||||
|
// and remove leading non-word characters
|
||||||
|
['a', 'an', 'the'].forEach(function(article) {
|
||||||
|
if (new RegExp('^' + article + ' ').test(sortValue)) {
|
||||||
|
sortValue = trim(sortValue.slice(article.length + 1))
|
||||||
|
+ ', ' + sortValue.slice(0, article.length);
|
||||||
|
return false; // break
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// remove thousand separators and pad numbers
|
||||||
|
sortValue = sortValue.replace(/(\d),(?=(\d{3}))/g, '$1')
|
||||||
|
.replace(/\d+/g, function(match) {
|
||||||
|
return match.padStart(64, '0')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sortValue;
|
||||||
|
};
|
||||||
|
|
||||||
const sortByKey = function(array, by) {
|
const sortByKey = function(array, by) {
|
||||||
return array.sort(function(a, b) {
|
return array.sort(function(a, b) {
|
||||||
|
|
@ -91,7 +122,7 @@ async function loadEdit(id, args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.edit = response['data']
|
data.edit = response['data']
|
||||||
if (['public', 'featured'].indexOf(data.edit.status) == -1) {
|
if (data.edit.status !== 'public') {
|
||||||
return {
|
return {
|
||||||
site: data.site,
|
site: data.site,
|
||||||
error: {
|
error: {
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,6 @@ async function loadData(id, args) {
|
||||||
<span class="icon">${icon.down}</span>
|
<span class="icon">${icon.down}</span>
|
||||||
${layerData.title}
|
${layerData.title}
|
||||||
</h3>`)
|
</h3>`)
|
||||||
data.layers[layer] = sortBy(data.layers[layer], [
|
|
||||||
{key: "in", operator: "+"},
|
|
||||||
{key: "created", operator: "+"}
|
|
||||||
])
|
|
||||||
data.layers[layer].forEach(annotation => {
|
data.layers[layer].forEach(annotation => {
|
||||||
if (pandora.url) {
|
if (pandora.url) {
|
||||||
annotation.value = annotation.value.replace(
|
annotation.value = annotation.value.replace(
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ function parseURL() {
|
||||||
id = id.replace('/editor/', '/').replace('/player/', '/')
|
id = id.replace('/editor/', '/').replace('/player/', '/')
|
||||||
type = "item"
|
type = "item"
|
||||||
}
|
}
|
||||||
//console.log(type, id, args)
|
|
||||||
return [type, id, args]
|
return [type, id, args]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,7 @@ function renderItem(data) {
|
||||||
var video = window.video = VideoPlayer({
|
var video = window.video = VideoPlayer({
|
||||||
items: data.videos,
|
items: data.videos,
|
||||||
poster: data.poster,
|
poster: data.poster,
|
||||||
"in": data["in"] || 0,
|
position: data["in"] || 0,
|
||||||
position: 0,
|
|
||||||
duration: data.duration,
|
duration: data.duration,
|
||||||
aspectratio: data.aspectratio
|
aspectratio: data.aspectratio
|
||||||
})
|
})
|
||||||
|
|
@ -86,10 +85,16 @@ function renderItem(data) {
|
||||||
video.addEventListener("loadedmetadata", event => {
|
video.addEventListener("loadedmetadata", event => {
|
||||||
//
|
//
|
||||||
})
|
})
|
||||||
|
video.addEventListener("timeupdate", event => {
|
||||||
function updateAnnotations(currentTime) {
|
var currentTime = video.currentTime()
|
||||||
|
if (currentTime >= data['out']) {
|
||||||
|
if (!video.paused) {
|
||||||
|
video.pause()
|
||||||
|
}
|
||||||
|
video.currentTime(data['in'])
|
||||||
|
}
|
||||||
div.querySelectorAll('.annotation').forEach(annot => {
|
div.querySelectorAll('.annotation').forEach(annot => {
|
||||||
var now = currentTime + (data["in"] || 0)
|
var now = currentTime
|
||||||
var start = parseFloat(annot.dataset.in)
|
var start = parseFloat(annot.dataset.in)
|
||||||
var end = parseFloat(annot.dataset.out)
|
var end = parseFloat(annot.dataset.out)
|
||||||
if (now >= start && now <= end) {
|
if (now >= start && now <= end) {
|
||||||
|
|
@ -102,18 +107,8 @@ function renderItem(data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
video.addEventListener("timeupdate", event => {
|
|
||||||
var currentTime = video.currentTime()
|
|
||||||
if ((currentTime + (data["in"] || 0)) >= data['out']) {
|
|
||||||
if (!video.paused) {
|
|
||||||
video.pause()
|
|
||||||
}
|
|
||||||
video.currentTime(0)
|
|
||||||
}
|
|
||||||
updateAnnotations(currentTime)
|
|
||||||
})
|
})
|
||||||
updateAnnotations(data["position"] || 0)
|
|
||||||
if (item.next || item.previous) {
|
if (item.next || item.previous) {
|
||||||
var nav = document.createElement('nav')
|
var nav = document.createElement('nav')
|
||||||
nav.classList.add('items')
|
nav.classList.add('items')
|
||||||
|
|
|
||||||
|
|
@ -125,10 +125,7 @@ const clickLink = function(event) {
|
||||||
}
|
}
|
||||||
document.location.hash = '#' + link.slice(1)
|
document.location.hash = '#' + link.slice(1)
|
||||||
} else {
|
} else {
|
||||||
if (link.includes('/download/')) {
|
if (!link.startsWith('/m')) {
|
||||||
document.location.href = link
|
|
||||||
return
|
|
||||||
} else if (!link.startsWith('/m')) {
|
|
||||||
link = '/m' + link
|
link = '/m' + link
|
||||||
}
|
}
|
||||||
history.pushState({}, '', link);
|
history.pushState({}, '', link);
|
||||||
|
|
@ -164,59 +161,3 @@ const getVideoURL = function(id, resolution, part, track, streamId) {
|
||||||
return prefix + '/' + getVideoURLName(id, resolution, part, track, streamId);
|
return prefix + '/' + getVideoURLName(id, resolution, part, track, streamId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSortValue = function(value) {
|
|
||||||
var sortValue = value;
|
|
||||||
function trim(value) {
|
|
||||||
return value.replace(/^\W+(?=\w)/, '');
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isEmpty(value)
|
|
||||||
|| isNull(value)
|
|
||||||
|| isUndefined(value)
|
|
||||||
) {
|
|
||||||
sortValue = null;
|
|
||||||
} else if (isString(value)) {
|
|
||||||
// make lowercase and remove leading non-word characters
|
|
||||||
sortValue = trim(value.toLowerCase());
|
|
||||||
// move leading articles to the end
|
|
||||||
// and remove leading non-word characters
|
|
||||||
['a', 'an', 'the'].forEach(function(article) {
|
|
||||||
if (new RegExp('^' + article + ' ').test(sortValue)) {
|
|
||||||
sortValue = trim(sortValue.slice(article.length + 1))
|
|
||||||
+ ', ' + sortValue.slice(0, article.length);
|
|
||||||
return false; // break
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// remove thousand separators and pad numbers
|
|
||||||
sortValue = sortValue.replace(/(\d),(?=(\d{3}))/g, '$1')
|
|
||||||
.replace(/\d+/g, function(match) {
|
|
||||||
return match.padStart(64, '0')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return sortValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
function sortBy(array, by, map) {
|
|
||||||
return array.sort(function(a, b) {
|
|
||||||
var aValue, bValue, index = 0, key, ret = 0;
|
|
||||||
while (ret == 0 && index < by.length) {
|
|
||||||
key = by[index].key;
|
|
||||||
aValue = getSortValue(
|
|
||||||
map && map[key] ? map[key](a[key], a) : a[key]
|
|
||||||
);
|
|
||||||
bValue = getSortValue(
|
|
||||||
map && map[key] ? map[key](b[key], b) : b[key]
|
|
||||||
);
|
|
||||||
if ((aValue === null) != (bValue === null)) {
|
|
||||||
ret = aValue === null ? 1 : -1;
|
|
||||||
} else if (aValue < bValue) {
|
|
||||||
ret = by[index].operator == '+' ? -1 : 1;
|
|
||||||
} else if (aValue > bValue) {
|
|
||||||
ret = by[index].operator == '+' ? 1 : -1;
|
|
||||||
} else {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -302,10 +302,6 @@ if __name__ == "__main__":
|
||||||
if old <= 6581:
|
if old <= 6581:
|
||||||
run('./bin/pip', 'install', '-U', 'pip')
|
run('./bin/pip', 'install', '-U', 'pip')
|
||||||
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
||||||
if old <= 6659:
|
|
||||||
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
|
||||||
if old <= 6688:
|
|
||||||
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
|
||||||
else:
|
else:
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
branch = get_branch()
|
branch = get_branch()
|
||||||
|
|
@ -326,7 +322,6 @@ if __name__ == "__main__":
|
||||||
current_branch = get_branch(path)
|
current_branch = get_branch(path)
|
||||||
revno = get_version(path)
|
revno = get_version(path)
|
||||||
if repo == 'pandora':
|
if repo == 'pandora':
|
||||||
print("Pandora Version pre update: ", revno)
|
|
||||||
pandora_old_revno = revno
|
pandora_old_revno = revno
|
||||||
current += revno
|
current += revno
|
||||||
if current_branch != branch:
|
if current_branch != branch:
|
||||||
|
|
@ -350,7 +345,6 @@ if __name__ == "__main__":
|
||||||
new += '+'
|
new += '+'
|
||||||
os.chdir(join(base, 'pandora'))
|
os.chdir(join(base, 'pandora'))
|
||||||
if pandora_old_revno != pandora_new_revno:
|
if pandora_old_revno != pandora_new_revno:
|
||||||
print("Pandora Version post update: ", pandora_new_revno)
|
|
||||||
os.chdir(base)
|
os.chdir(base)
|
||||||
run('./update.py', 'postupdate', pandora_old_revno, pandora_new_revno)
|
run('./update.py', 'postupdate', pandora_old_revno, pandora_new_revno)
|
||||||
os.chdir(join(base, 'pandora'))
|
os.chdir(join(base, 'pandora'))
|
||||||
|
|
@ -367,7 +361,7 @@ if __name__ == "__main__":
|
||||||
and row not in ['BEGIN;', 'COMMIT;']
|
and row not in ['BEGIN;', 'COMMIT;']
|
||||||
]
|
]
|
||||||
if diff:
|
if diff:
|
||||||
print('Database has changed, please make a backup and run: sudo pandoractl update db')
|
print('Database has changed, please make a backup and run %s db' % sys.argv[0])
|
||||||
elif branch != 'master':
|
elif branch != 'master':
|
||||||
print('pan.do/ra is at the latest release,\nyou can run "%s switch master" to switch to the development version' % sys.argv[0])
|
print('pan.do/ra is at the latest release,\nyou can run "%s switch master" to switch to the development version' % sys.argv[0])
|
||||||
elif current != new:
|
elif current != new:
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,53 @@ LXC=`grep -q lxc /proc/1/environ && echo 'yes' || echo 'no'`
|
||||||
if [ -e /etc/os-release ]; then
|
if [ -e /etc/os-release ]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
fi
|
fi
|
||||||
|
if [ -z "$UBUNTU_CODENAME" ]; then
|
||||||
|
UBUNTU_CODENAME=bionic
|
||||||
|
fi
|
||||||
|
if [ "$VERSION_CODENAME" = "bullseye" ]; then
|
||||||
|
UBUNTU_CODENAME=focal
|
||||||
|
fi
|
||||||
|
if [ "$VERSION_CODENAME" = "bookworm" ]; then
|
||||||
|
UBUNTU_CODENAME=lunar
|
||||||
|
fi
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
echo "deb http://ppa.launchpad.net/j/pandora/ubuntu ${UBUNTU_CODENAME} main" > /etc/apt/sources.list.d/j-pandora.list
|
||||||
|
|
||||||
apt-get install -y gnupg curl
|
apt-get install -y gnupg
|
||||||
|
|
||||||
distribution=bookworm
|
if [ -e /etc/apt/trusted.gpg.d ]; then
|
||||||
for version in bookworm trixie bionic focal jammy noble; do
|
gpg --dearmor > /etc/apt/trusted.gpg.d/j-pandora.gpg <<EOF
|
||||||
if [ "$VERSION_CODENAME" = $version ]; then
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
distribution=$VERSION_CODENAME
|
Version: GnuPG v1
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
curl -Ls https://code.0x2620.org/api/packages/0x2620/debian/repository.key -o /etc/apt/keyrings/pandora.asc
|
mI0ESXYhEgEEALl9jDTdmgpApPbjN+7b85dC92HisPUp56ifEkKJOBj0X5HhRqxs
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/pandora.asc] https://code.0x2620.org/api/packages/0x2620/debian $distribution main" > /etc/apt/sources.list.d/pandora.list
|
Wjx/zlP4/XJGrHnxJyrdPxjSwAXz7bNdeggkN4JWdusTkr5GOXvggQnng0X7f/rX
|
||||||
|
oJwoEGtYOCODLPs6PC0qjh5yPzJVeiRsKUOZ7YVNnwNwdfS4D8RZvtCrABEBAAG0
|
||||||
|
FExhdW5jaHBhZCBQUEEgZm9yIGpeiLYEEwECACAFAkl2IRICGwMGCwkIBwMCBBUC
|
||||||
|
CAMEFgIDAQIeAQIXgAAKCRAohRM8AZde82FfA/9OB/64/YLaCpizHZ8f6DK3rGgF
|
||||||
|
e6mX3rFK8yOKGGL06316VhDzfzMiZSauUZ0t+lKHR/KZYeSaFwEoUoblTG/s4IIo
|
||||||
|
9aBMHWhVXJW6eifKUmTGqEn2/0UxoWQq2C3F6njMkCaP+ALOD5uzaSYGdjqAUAwS
|
||||||
|
pAAGSEQ4uz6bYSeM4Q==
|
||||||
|
=SM2a
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
apt-key add - <<EOF
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Version: GnuPG v1
|
||||||
|
|
||||||
|
mI0ESXYhEgEEALl9jDTdmgpApPbjN+7b85dC92HisPUp56ifEkKJOBj0X5HhRqxs
|
||||||
|
Wjx/zlP4/XJGrHnxJyrdPxjSwAXz7bNdeggkN4JWdusTkr5GOXvggQnng0X7f/rX
|
||||||
|
oJwoEGtYOCODLPs6PC0qjh5yPzJVeiRsKUOZ7YVNnwNwdfS4D8RZvtCrABEBAAG0
|
||||||
|
FExhdW5jaHBhZCBQUEEgZm9yIGpeiLYEEwECACAFAkl2IRICGwMGCwkIBwMCBBUC
|
||||||
|
CAMEFgIDAQIeAQIXgAAKCRAohRM8AZde82FfA/9OB/64/YLaCpizHZ8f6DK3rGgF
|
||||||
|
e6mX3rFK8yOKGGL06316VhDzfzMiZSauUZ0t+lKHR/KZYeSaFwEoUoblTG/s4IIo
|
||||||
|
9aBMHWhVXJW6eifKUmTGqEn2/0UxoWQq2C3F6njMkCaP+ALOD5uzaSYGdjqAUAwS
|
||||||
|
pAAGSEQ4uz6bYSeM4Q==
|
||||||
|
=SM2a
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99languages
|
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99languages
|
||||||
|
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue