Custom fonts for pdf and projector
This commit is contained in:
parent
8042beda60
commit
dfb40684ee
@ -112,6 +112,7 @@ Core:
|
|||||||
- Set default of projector resolution to 1220x915 [#2549].
|
- Set default of projector resolution to 1220x915 [#2549].
|
||||||
- Preparations for the SAML plugin; Fixed caching of main views [#3535].
|
- Preparations for the SAML plugin; Fixed caching of main views [#3535].
|
||||||
- Removed unnecessary OPTIONS request in config [#3541].
|
- Removed unnecessary OPTIONS request in config [#3541].
|
||||||
|
- Added possibility to upload custom fonts for projector and pdf [#3568].
|
||||||
|
|
||||||
Mediafiles:
|
Mediafiles:
|
||||||
- Fixed reloading of PDF on page change [#3274].
|
- Fixed reloading of PDF on page change [#3274].
|
||||||
|
@ -80,9 +80,9 @@ gulp.task('pdf-worker', function () {
|
|||||||
gulp.task('pdf-worker-libs', function () {
|
gulp.task('pdf-worker-libs', function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
path.join('bower_components', 'pdfmake', 'build', 'pdfmake.min.js'),
|
path.join('bower_components', 'pdfmake', 'build', 'pdfmake.min.js'),
|
||||||
path.join('bower_components', 'pdfmake', 'build', 'vfs_fonts.js'),
|
|
||||||
])
|
])
|
||||||
.pipe(concat('pdf-worker-libs.js'))
|
.pipe(gulpif(argv.production, uglify()))
|
||||||
|
.pipe(rename('pdf-worker-libs.js'))
|
||||||
.pipe(gulp.dest(path.join(output_directory, 'js', 'workers')));
|
.pipe(gulp.dest(path.join(output_directory, 'js', 'workers')));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ INPUT_TYPE_MAPPING = {
|
|||||||
'colorpicker': str,
|
'colorpicker': str,
|
||||||
'datetimepicker': int,
|
'datetimepicker': int,
|
||||||
'majorityMethod': str,
|
'majorityMethod': str,
|
||||||
'logo': dict,
|
'static': dict,
|
||||||
'translations': list,
|
'translations': list,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,9 +125,9 @@ class ConfigHandler:
|
|||||||
valuecopy[id] = commentsfield
|
valuecopy[id] = commentsfield
|
||||||
value = valuecopy
|
value = valuecopy
|
||||||
|
|
||||||
if config_variable.input_type == 'logo':
|
if config_variable.input_type == 'static':
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
raise ConfigError(_('logo has to be a dict.'))
|
raise ConfigError(_('This has to be a dict.'))
|
||||||
whitelist = (
|
whitelist = (
|
||||||
'path',
|
'path',
|
||||||
'display_name',
|
'display_name',
|
||||||
|
@ -259,7 +259,7 @@ def get_config_variables():
|
|||||||
default_value={
|
default_value={
|
||||||
'display_name': 'Projector logo',
|
'display_name': 'Projector logo',
|
||||||
'path': ''},
|
'path': ''},
|
||||||
input_type='logo',
|
input_type='static',
|
||||||
weight=301,
|
weight=301,
|
||||||
group='Logo',
|
group='Logo',
|
||||||
hidden=True)
|
hidden=True)
|
||||||
@ -269,7 +269,7 @@ def get_config_variables():
|
|||||||
default_value={
|
default_value={
|
||||||
'display_name': 'Projector header image',
|
'display_name': 'Projector header image',
|
||||||
'path': ''},
|
'path': ''},
|
||||||
input_type='logo',
|
input_type='static',
|
||||||
weight=302,
|
weight=302,
|
||||||
group='Logo',
|
group='Logo',
|
||||||
hidden=True)
|
hidden=True)
|
||||||
@ -279,7 +279,7 @@ def get_config_variables():
|
|||||||
default_value={
|
default_value={
|
||||||
'display_name': 'Web interface header logo',
|
'display_name': 'Web interface header logo',
|
||||||
'path': ''},
|
'path': ''},
|
||||||
input_type='logo',
|
input_type='static',
|
||||||
weight=303,
|
weight=303,
|
||||||
group='Logo',
|
group='Logo',
|
||||||
hidden=True)
|
hidden=True)
|
||||||
@ -290,7 +290,7 @@ def get_config_variables():
|
|||||||
default_value={
|
default_value={
|
||||||
'display_name': 'PDF header logo',
|
'display_name': 'PDF header logo',
|
||||||
'path': ''},
|
'path': ''},
|
||||||
input_type='logo',
|
input_type='static',
|
||||||
weight=310,
|
weight=310,
|
||||||
group='Logo',
|
group='Logo',
|
||||||
hidden=True)
|
hidden=True)
|
||||||
@ -300,7 +300,7 @@ def get_config_variables():
|
|||||||
default_value={
|
default_value={
|
||||||
'display_name': 'PDF footer logo',
|
'display_name': 'PDF footer logo',
|
||||||
'path': ''},
|
'path': ''},
|
||||||
input_type='logo',
|
input_type='static',
|
||||||
weight=311,
|
weight=311,
|
||||||
group='Logo',
|
group='Logo',
|
||||||
hidden=True)
|
hidden=True)
|
||||||
@ -310,11 +310,67 @@ def get_config_variables():
|
|||||||
default_value={
|
default_value={
|
||||||
'display_name': 'PDF ballot paper logo',
|
'display_name': 'PDF ballot paper logo',
|
||||||
'path': ''},
|
'path': ''},
|
||||||
input_type='logo',
|
input_type='static',
|
||||||
weight=312,
|
weight=312,
|
||||||
group='Logo',
|
group='Logo',
|
||||||
hidden=True)
|
hidden=True)
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='fonts_available',
|
||||||
|
default_value=[
|
||||||
|
'font_regular',
|
||||||
|
'font_italic',
|
||||||
|
'font_bold',
|
||||||
|
'font_bold_italic'],
|
||||||
|
weight=320,
|
||||||
|
group='Font',
|
||||||
|
hidden=True)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='font_regular',
|
||||||
|
default_value={
|
||||||
|
'display_name': 'Font regular',
|
||||||
|
'default': 'static/fonts/Roboto-Regular.woff',
|
||||||
|
'path': ''},
|
||||||
|
input_type='static',
|
||||||
|
weight=321,
|
||||||
|
group='Font',
|
||||||
|
hidden=True)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='font_italic',
|
||||||
|
default_value={
|
||||||
|
'display_name': 'Font italic',
|
||||||
|
'default': 'static/fonts/Roboto-Medium.woff',
|
||||||
|
'path': ''},
|
||||||
|
input_type='static',
|
||||||
|
weight=321,
|
||||||
|
group='Font',
|
||||||
|
hidden=True)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='font_bold',
|
||||||
|
default_value={
|
||||||
|
'display_name': 'Font bold',
|
||||||
|
'default': 'static/fonts/Roboto-Condensed-Regular.woff',
|
||||||
|
'path': ''},
|
||||||
|
input_type='static',
|
||||||
|
weight=321,
|
||||||
|
group='Font',
|
||||||
|
hidden=True)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='font_bold_italic',
|
||||||
|
default_value={
|
||||||
|
'display_name': 'Font bold italic',
|
||||||
|
'default': 'static/fonts/Roboto-Condensed-Light.woff',
|
||||||
|
'path': ''},
|
||||||
|
input_type='static',
|
||||||
|
weight=321,
|
||||||
|
group='Font',
|
||||||
|
hidden=True)
|
||||||
|
|
||||||
# Custom translations
|
# Custom translations
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='translations',
|
name='translations',
|
||||||
|
62
openslides/core/migrations/0007_auto_20180130_1400.py
Normal file
62
openslides/core/migrations/0007_auto_20180130_1400.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.8 on 2018-01-30 13:00
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def delete_old_logo_permission(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Deletes the old 'can_manage_logo' permission which is replaced with
|
||||||
|
'can_manage_logos_and_fonts'. If this is a fresh database, no permission
|
||||||
|
will be deleted, in fact the old permission does not exist. Django creates
|
||||||
|
the permission after all migration and the old one is not generated.
|
||||||
|
If this is an old database, the new permission will be created and the old
|
||||||
|
one deleted. Also it will be assigned to the groups, which had the old permission.
|
||||||
|
"""
|
||||||
|
perm = Permission.objects.filter(codename='can_manage_logos')
|
||||||
|
|
||||||
|
if len(perm):
|
||||||
|
perm = perm.get()
|
||||||
|
# Save content_type for manual creation of new permissions.
|
||||||
|
content_type = perm.content_type
|
||||||
|
|
||||||
|
# Save groups. list() is necessary to evaluate the database query right now.
|
||||||
|
groups = list(perm.group_set.all())
|
||||||
|
|
||||||
|
# Delete permission
|
||||||
|
perm.delete()
|
||||||
|
|
||||||
|
# Create new permission
|
||||||
|
perm = Permission.objects.create(
|
||||||
|
codename='can_manage_logos_and_fonts',
|
||||||
|
name='Can manage logos and fonts',
|
||||||
|
content_type=content_type)
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
group.permissions.add(perm)
|
||||||
|
group.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0006_auto_20180123_0903'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='configstore',
|
||||||
|
options={
|
||||||
|
'default_permissions': (),
|
||||||
|
'permissions': (
|
||||||
|
('can_manage_config', 'Can manage configuration'),
|
||||||
|
('can_manage_logos_and_fonts', 'Can manage logos and fonts')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
delete_old_logo_permission
|
||||||
|
),
|
||||||
|
]
|
@ -281,7 +281,7 @@ class ConfigStore(RESTModelMixin, models.Model):
|
|||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
permissions = (
|
permissions = (
|
||||||
('can_manage_config', 'Can manage configuration'),
|
('can_manage_config', 'Can manage configuration'),
|
||||||
('can_manage_logos', 'Can manage logos'))
|
('can_manage_logos_and_fonts', 'Can manage logos and fonts'))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_collection_string(cls):
|
def get_collection_string(cls):
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
/** Fonts **/
|
/** Fonts **/
|
||||||
$font: 'Roboto';
|
/* Note: The font naming has to be consistent to the projector.html */
|
||||||
|
$font: 'OSFont';
|
||||||
$font-src: url('../fonts/Roboto-Regular.woff') format('woff');
|
$font-src: url('../fonts/Roboto-Regular.woff') format('woff');
|
||||||
$font-medium: 'Roboto Medium';
|
$font-medium: 'OSFont Medium';
|
||||||
$font-medium-src: url('../fonts/Roboto-Medium.woff') format('woff');
|
$font-medium-src: url('../fonts/Roboto-Medium.woff') format('woff');
|
||||||
$font-condensed: 'Roboto Condensed';
|
$font-condensed: 'OSFont Condensed';
|
||||||
$font-condensed-src: url('../fonts/Roboto-Condensed-Regular.woff') format('woff');
|
$font-condensed-src: url('../fonts/Roboto-Condensed-Regular.woff') format('woff');
|
||||||
$font-condensed-light: 'Roboto Condensed Light';
|
$font-condensed-light: 'OSFont Condensed Light';
|
||||||
$font-condensed-light-src: url('../fonts/Roboto-Condensed-Light.woff') format('woff');
|
$font-condensed-light-src: url('../fonts/Roboto-Condensed-Light.woff') format('woff');
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
/* General */
|
/* General */
|
||||||
@import "variables";
|
@import "variables";
|
||||||
@import "fonts";
|
|
||||||
@import "helper";
|
@import "helper";
|
||||||
@import "ui-override";
|
@import "ui-override";
|
||||||
|
|
||||||
|
@ -15,4 +15,3 @@
|
|||||||
@import "../../../motions/static/css/motions/site";
|
@import "../../../motions/static/css/motions/site";
|
||||||
@import "../../../users/static/css/users/site";
|
@import "../../../users/static/css/users/site";
|
||||||
@import "../../../mediafiles/static/css/mediafiles/site";
|
@import "../../../mediafiles/static/css/mediafiles/site";
|
||||||
|
|
||||||
|
@ -826,36 +826,78 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
getAll: function () {
|
getAll: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
return _.map(this.getKeys(), function (key) {
|
return _.map(this.getKeys(), function (key) {
|
||||||
return self.getFromKey(key);
|
return self.get(key);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getFromKey: function (key) {
|
get: function (key) {
|
||||||
var config = Config.get(key);
|
var config = Config.get(key);
|
||||||
if (config) {
|
if (config) {
|
||||||
config.value.key = key;
|
config.value.key = key;
|
||||||
return config.value;
|
return config.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isMediafileUsedAsLogo: function (mediafile) {
|
set: function (key, path) {
|
||||||
return _.find(this.getAll(), function (logoPlaceholder) {
|
|
||||||
return logoPlaceholder.path === mediafile.mediafileUrl;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
canMediafileBeUsedAsLogo: function (mediafile) {
|
|
||||||
return mediafile.is_image;
|
|
||||||
},
|
|
||||||
setMediafile: function (key, mediafile) {
|
|
||||||
var config = Config.get(key);
|
var config = Config.get(key);
|
||||||
if (!mediafile || mediafile.canBeUsedAsLogo()) {
|
if (config) {
|
||||||
config.value.path = mediafile ? mediafile.mediafileUrl : '';
|
config.value.path = path;// ? mediafile.mediafileUrl : '';
|
||||||
Config.save(key);
|
Config.save(key);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getLogosForMediafile: function (mediafile) {
|
};
|
||||||
return _.filter(this.getAll(), function (logoPlaceholder) {
|
}
|
||||||
return logoPlaceholder.path === mediafile.mediafileUrl;
|
])
|
||||||
|
|
||||||
|
.factory('Fonts', [
|
||||||
|
'Config',
|
||||||
|
'gettext',
|
||||||
|
function (Config, gettext) {
|
||||||
|
var extensionFormatMap = {
|
||||||
|
'ttf': 'truetype',
|
||||||
|
'woff': 'woff',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getKeys: function () {
|
||||||
|
return Config.get('fonts_available').value;
|
||||||
|
},
|
||||||
|
getAll: function () {
|
||||||
|
var self = this;
|
||||||
|
return _.map(this.getKeys(), function (key) {
|
||||||
|
return self.get(key);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
get: function (key) {
|
||||||
|
var config = Config.get(key);
|
||||||
|
if (config) {
|
||||||
|
config.value.key = key;
|
||||||
|
return config.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getUrl: function (key) {
|
||||||
|
var font = this.get(key);
|
||||||
|
if (font) {
|
||||||
|
var path = font.path;
|
||||||
|
if (!path) {
|
||||||
|
return font.default;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getForCss: function (key) {
|
||||||
|
var url = this.getUrl(key);
|
||||||
|
if (url) {
|
||||||
|
var ext = _.last(url.split('.'));
|
||||||
|
return "url('" + url + "') format('" +
|
||||||
|
extensionFormatMap[ext] + "')";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: function (key, path) {
|
||||||
|
var config = Config.get(key);
|
||||||
|
if (config) {
|
||||||
|
config.value.path = path;
|
||||||
|
Config.save(key);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@ -10,20 +10,18 @@ var document = {
|
|||||||
};
|
};
|
||||||
var window = this;
|
var window = this;
|
||||||
|
|
||||||
// PdfMake and Fonts
|
// PdfMake
|
||||||
importScripts('/static/js/workers/pdf-worker-libs.js');
|
importScripts('/static/js/workers/pdf-worker-libs.js');
|
||||||
|
|
||||||
// Set default font family.
|
// Set default font family.
|
||||||
// To use custom ttf font files you have to replace the vfs_fonts.js file.
|
// "PdfFont" and "OSFont-*" are generic names used here and in core/pdf.js. The
|
||||||
// See https://github.com/pdfmake/pdfmake/wiki/Custom-Fonts---client-side
|
// suffix after "OSFont-" has to be the same as the config value.
|
||||||
// "PdfFont" is used as generic name in core/pdf.js. Adjust the four
|
|
||||||
// font style names only.
|
|
||||||
pdfMake.fonts = {
|
pdfMake.fonts = {
|
||||||
PdfFont: {
|
PdfFont: {
|
||||||
normal: 'Roboto-Regular.ttf',
|
normal: 'OSFont-regular.ttf',
|
||||||
bold: 'Roboto-Medium.ttf',
|
bold: 'OSFont-bold.ttf',
|
||||||
italics: 'Roboto-Italic.ttf',
|
italics: 'OSFont-italic.ttf',
|
||||||
bolditalics: 'Roboto-Italic.ttf'
|
bolditalics: 'OSFont-bold_italic.ttf'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,8 +104,9 @@ var replaceFooter = function (doc) {
|
|||||||
// Create PDF on message and return the base64 decoded document
|
// Create PDF on message and return the base64 decoded document
|
||||||
self.addEventListener('message', function(e) {
|
self.addEventListener('message', function(e) {
|
||||||
var data = JSON.parse(e.data);
|
var data = JSON.parse(e.data);
|
||||||
var doc = data.pdfDocument;
|
pdfMake.vfs = data.vfs; // Set custom fonts.
|
||||||
|
|
||||||
|
var doc = data.pdfDocument;
|
||||||
replaceFooter(doc);
|
replaceFooter(doc);
|
||||||
replacePlaceholder(doc.content);
|
replacePlaceholder(doc.content);
|
||||||
|
|
||||||
|
@ -1061,13 +1061,99 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Creates the virtual filesystem for PdfMake.
|
||||||
|
.factory('PdfVfs', [
|
||||||
|
'$q',
|
||||||
|
'$http',
|
||||||
|
'Fonts',
|
||||||
|
'Config',
|
||||||
|
function ($q, $http, Fonts, Config) {
|
||||||
|
var urlCache = {}; // Caches the get request. Maps urls to base64 data ready to use.
|
||||||
|
|
||||||
|
var loadFont = function (url) {
|
||||||
|
return $q(function (resolve, reject) {
|
||||||
|
// Get font
|
||||||
|
return $http.get(url, {responseType: 'blob'}).then(function (success) {
|
||||||
|
// Convert to base64
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.readAsDataURL(success.data);
|
||||||
|
reader.onloadend = function() {
|
||||||
|
resolve(reader.result.split(',')[1]);
|
||||||
|
};
|
||||||
|
}, function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a map from urls to arrays of font types used by PdfMake.
|
||||||
|
* E.g. if the font "regular" and bold" have the urls "fonts/myFont.ttf",
|
||||||
|
* the map fould be "fonts/myFont.ttf": ["OSFont-regular.ttf", "OSFont-bold.ttf"]
|
||||||
|
*/
|
||||||
|
var getUrlMapping = function () {
|
||||||
|
var urlMap = {};
|
||||||
|
var fonts = ['regular', 'italic', 'bold', 'bold_italic'];
|
||||||
|
_.forEach(fonts, function (font) {
|
||||||
|
var url = Fonts.getUrl('font_' + font);
|
||||||
|
if (!urlMap[url]) {
|
||||||
|
urlMap[url] = [];
|
||||||
|
}
|
||||||
|
urlMap[url].push('OSFont-' + font + '.ttf');
|
||||||
|
});
|
||||||
|
return urlMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the virtual filesystem needed by PdfMake for the fonts. Gets the url
|
||||||
|
* mapping and loads all fonts via get requests or the urlCache.
|
||||||
|
*/
|
||||||
|
var getVfs = function () {
|
||||||
|
return $q(function (resolve, reject) {
|
||||||
|
var vfs = {};
|
||||||
|
var urls = getUrlMapping();
|
||||||
|
var promises = _.chain(urls)
|
||||||
|
.map(function (filenames, url) {
|
||||||
|
if (urlCache[url]) {
|
||||||
|
// Just save the cache data into vfs.
|
||||||
|
_.forEach(filenames, function (filename) {
|
||||||
|
vfs[filename] = urlCache[url];
|
||||||
|
});
|
||||||
|
return false; // No promise here, it was all cached.
|
||||||
|
} else {
|
||||||
|
// Not in the cache, get the font and save the data into vfs.
|
||||||
|
return loadFont(url).then(function (data) {
|
||||||
|
urlCache[url] = data;
|
||||||
|
_.forEach(filenames, function (filename) {
|
||||||
|
vfs[filename] = data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(function (promise) {
|
||||||
|
return promise;
|
||||||
|
})
|
||||||
|
.value();
|
||||||
|
$q.all(promises).then(function () {
|
||||||
|
resolve(vfs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: getVfs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
.factory('PdfCreate', [
|
.factory('PdfCreate', [
|
||||||
'$timeout',
|
'$timeout',
|
||||||
'$q',
|
'$q',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'FileSaver',
|
'FileSaver',
|
||||||
|
'PdfVfs',
|
||||||
'Messaging',
|
'Messaging',
|
||||||
function ($timeout, $q, gettextCatalog, FileSaver, Messaging) {
|
function ($timeout, $q, gettextCatalog, FileSaver, PdfVfs, Messaging) {
|
||||||
var filenameMessageMap = {};
|
var filenameMessageMap = {};
|
||||||
var b64toBlob = function(b64Data) {
|
var b64toBlob = function(b64Data) {
|
||||||
var byteCharacters = atob(b64Data);
|
var byteCharacters = atob(b64Data);
|
||||||
@ -1105,6 +1191,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
return {
|
return {
|
||||||
getBase64FromDocument: function (pdfDocument) {
|
getBase64FromDocument: function (pdfDocument) {
|
||||||
return $q(function (resolve, reject) {
|
return $q(function (resolve, reject) {
|
||||||
|
PdfVfs.get().then(function (vfs) {
|
||||||
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
||||||
pdfWorker.addEventListener('message', function (event) {
|
pdfWorker.addEventListener('message', function (event) {
|
||||||
resolve(event.data);
|
resolve(event.data);
|
||||||
@ -1113,13 +1200,16 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
reject(event);
|
reject(event);
|
||||||
});
|
});
|
||||||
pdfWorker.postMessage(JSON.stringify({
|
pdfWorker.postMessage(JSON.stringify({
|
||||||
pdfDocument: pdfDocument
|
pdfDocument: pdfDocument,
|
||||||
|
vfs: vfs,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// Struckture of pdfDocuments: { filname1: doc, filename2: doc, ...}
|
// Struckture of pdfDocuments: { filname1: doc, filename2: doc, ...}
|
||||||
getBase64FromMultipleDocuments: function (pdfDocuments) {
|
getBase64FromMultipleDocuments: function (pdfDocuments) {
|
||||||
return $q(function (resolve, reject) {
|
return $q(function (resolve, reject) {
|
||||||
|
PdfVfs.get().then(function (vfs) {
|
||||||
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
||||||
var resultCount = 0;
|
var resultCount = 0;
|
||||||
var base64Map = {}; // Maps filename to base64
|
var base64Map = {}; // Maps filename to base64
|
||||||
@ -1137,10 +1227,12 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
_.forEach(pdfDocuments, function (doc, filename) {
|
_.forEach(pdfDocuments, function (doc, filename) {
|
||||||
pdfWorker.postMessage(JSON.stringify({
|
pdfWorker.postMessage(JSON.stringify({
|
||||||
filename: filename,
|
filename: filename,
|
||||||
pdfDocument: doc
|
pdfDocument: doc,
|
||||||
|
vfs: vfs,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
download: function (pdfDocument, filename) {
|
download: function (pdfDocument, filename) {
|
||||||
stateChange('info', filename);
|
stateChange('info', filename);
|
||||||
|
@ -78,12 +78,13 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
.controller('LanguageCtrl', [
|
.controller('LanguageAndFontCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Languages',
|
'Languages',
|
||||||
'Config',
|
'Config',
|
||||||
'ProjectorID',
|
'ProjectorID',
|
||||||
function ($scope, Languages, Config, ProjectorID) {
|
'Fonts',
|
||||||
|
function ($scope, Languages, Config, ProjectorID, Fonts) {
|
||||||
// for the dynamic title
|
// for the dynamic title
|
||||||
$scope.projectorId = ProjectorID();
|
$scope.projectorId = ProjectorID();
|
||||||
|
|
||||||
@ -98,6 +99,18 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
}
|
}
|
||||||
Languages.setCurrentLanguage($scope.selectedLanguage);
|
Languages.setCurrentLanguage($scope.selectedLanguage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$scope.$watch(function () {
|
||||||
|
return Config.lastModified('font_regular') +
|
||||||
|
Config.lastModified('font_italic') +
|
||||||
|
Config.lastModified('font_bold') +
|
||||||
|
Config.lastModified('font_bold_italic');
|
||||||
|
}, function () {
|
||||||
|
$scope.font = Fonts.getForCss('font_regular');
|
||||||
|
$scope.font_medium = Fonts.getForCss('font_italic');
|
||||||
|
$scope.font_condensed = Fonts.getForCss('font_bold');
|
||||||
|
$scope.font_condensed_light = Fonts.getForCss('font_bold_italic');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ selectedLanguage }}" ng-controller="LanguageCtrl" class="no-js">
|
<html lang="{{ selectedLanguage }}" ng-controller="LanguageAndFontCtrl" class="no-js">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ selectedLanguage }}" ng-controller="LanguageCtrl" class="no-js">
|
<html lang="{{ selectedLanguage }}" ng-controller="LanguageAndFontCtrl" class="no-js">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<base href="/">
|
<base href="/">
|
||||||
@ -7,6 +7,34 @@
|
|||||||
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
||||||
<link rel="stylesheet" href="static/css/openslides-projector.css">
|
<link rel="stylesheet" href="static/css/openslides-projector.css">
|
||||||
<link rel="icon" href="/static/img/favicon.png">
|
<link rel="icon" href="/static/img/favicon.png">
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
@font-face {
|
||||||
|
font-family: 'OSFont';
|
||||||
|
src: {{ font }};
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'OSFont Medium';
|
||||||
|
src: {{ font_medium }};
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'OSFont Condensed';
|
||||||
|
src: {{ font_condensed }};
|
||||||
|
font-weight: 100;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'OSFont Condensed Light';
|
||||||
|
src: {{ font_condensed_light }};
|
||||||
|
font-weight: 100;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script src="static/js/openslides-libs.js"></script>
|
<script src="static/js/openslides-libs.js"></script>
|
||||||
<script src="static/js/openslides.js"></script>
|
<script src="static/js/openslides.js"></script>
|
||||||
<script src="static/js/openslides-templates.js"></script>
|
<script src="static/js/openslides-templates.js"></script>
|
||||||
|
@ -600,11 +600,12 @@ class ConfigViewSet(ModelViewSet):
|
|||||||
# enabled.
|
# enabled.
|
||||||
result = self.request.user.is_authenticated() or anonymous_is_enabled()
|
result = self.request.user.is_authenticated() or anonymous_is_enabled()
|
||||||
elif self.action in ('partial_update', 'update'):
|
elif self.action in ('partial_update', 'update'):
|
||||||
# The user needs 'core.can_manage_logos' for all config values
|
# The user needs 'core.can_manage_logos_and_fonts' for all config values
|
||||||
# starting with 'logo'. For all other config values th euser needs
|
# starting with 'logo' and 'font'. For all other config values th euser needs
|
||||||
# the default permissions 'core.can_manage_config'.
|
# the default permissions 'core.can_manage_config'.
|
||||||
if self.kwargs['pk'].startswith('logo'):
|
pk = self.kwargs['pk']
|
||||||
result = has_perm(self.request.user, 'core.can_manage_logos')
|
if pk.startswith('logo') or pk.startswith('font'):
|
||||||
|
result = has_perm(self.request.user, 'core.can_manage_logos_and_fonts')
|
||||||
else:
|
else:
|
||||||
result = has_perm(self.request.user, 'core.can_manage_config')
|
result = has_perm(self.request.user, 'core.can_manage_config')
|
||||||
else:
|
else:
|
||||||
|
@ -24,8 +24,9 @@ angular.module('OpenSlidesApp.mediafiles.list', [
|
|||||||
'Mediafile',
|
'Mediafile',
|
||||||
'MediafileForm',
|
'MediafileForm',
|
||||||
'Logos',
|
'Logos',
|
||||||
|
'Fonts',
|
||||||
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort, osTablePagination,
|
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort, osTablePagination,
|
||||||
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos) {
|
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos, Fonts) {
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Mediafile.lastModified();
|
return Mediafile.lastModified();
|
||||||
}, function () {
|
}, function () {
|
||||||
@ -259,21 +260,9 @@ angular.module('OpenSlidesApp.mediafiles.list', [
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Logos **/
|
/** Logos and fonts **/
|
||||||
$scope.logos = Logos.getAll();
|
$scope.logos = Logos.getAll();
|
||||||
$scope.toggleLogo = function (mediafile, logo) {
|
$scope.fonts = Fonts.getAll();
|
||||||
if (!$scope.hasLogo(mediafile, logo)) {
|
|
||||||
Logos.setMediafile(logo.key, mediafile);
|
|
||||||
} else {
|
|
||||||
Logos.setMediafile(logo.key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$scope.hasLogo = function (mediafile, logo) {
|
|
||||||
var allUrls = _.map(mediafile.getLogos(), function (logo) {
|
|
||||||
return logo.path;
|
|
||||||
});
|
|
||||||
return _.includes(allUrls, logo.path);
|
|
||||||
};
|
|
||||||
$scope.hasProjectorHeaderLogo = function (mediafile) {
|
$scope.hasProjectorHeaderLogo = function (mediafile) {
|
||||||
return _.some(mediafile.getLogos(), function (logo) {
|
return _.some(mediafile.getLogos(), function (logo) {
|
||||||
return logo.key === 'logo_projector_header';
|
return logo.key === 'logo_projector_header';
|
||||||
|
@ -13,7 +13,8 @@ angular.module('OpenSlidesApp.mediafiles.resources', [
|
|||||||
'gettext',
|
'gettext',
|
||||||
'jsDataModel',
|
'jsDataModel',
|
||||||
'Logos',
|
'Logos',
|
||||||
function (DS, gettext, jsDataModel, Logos) {
|
'Fonts',
|
||||||
|
function (DS, gettext, jsDataModel, Logos, Fonts) {
|
||||||
var name = 'mediafiles/mediafile';
|
var name = 'mediafiles/mediafile';
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
@ -50,13 +51,60 @@ angular.module('OpenSlidesApp.mediafiles.resources', [
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
isUsedAsLogo: function () {
|
isUsedAsLogo: function () {
|
||||||
return Logos.isMediafileUsedAsLogo(this);
|
var mediafile = this;
|
||||||
|
return _.find(Logos.getAll(), function (logoPlaceholder) {
|
||||||
|
return logoPlaceholder.path === mediafile.mediafileUrl;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
canBeUsedAsLogo: function () {
|
canBeUsedAsLogo: function () {
|
||||||
return Logos.canMediafileBeUsedAsLogo(this);
|
return this.is_image;
|
||||||
},
|
},
|
||||||
getLogos: function () {
|
getLogos: function () {
|
||||||
return Logos.getLogosForMediafile(this);
|
var mediafile = this;
|
||||||
|
return _.filter(Logos.getAll(), function (logoPlaceholder) {
|
||||||
|
return logoPlaceholder.path === mediafile.mediafileUrl;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasLogo: function (logo) {
|
||||||
|
var allUrls = _.map(this.getLogos(), function (logo) {
|
||||||
|
return logo.path;
|
||||||
|
});
|
||||||
|
return _.includes(allUrls, logo.path);
|
||||||
|
},
|
||||||
|
toggleLogo: function (logo) {
|
||||||
|
if (this.hasLogo(logo)) {
|
||||||
|
Logos.set(logo.key);
|
||||||
|
} else {
|
||||||
|
Logos.set(logo.key, this.mediafileUrl);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isUsedAsFont: function () {
|
||||||
|
var mediafile = this;
|
||||||
|
return _.find(Fonts.getAll(), function (font) {
|
||||||
|
return font.path === mediafile.mediafileUrl;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
canBeUsedAsFont: function () {
|
||||||
|
return this.is_font;
|
||||||
|
},
|
||||||
|
getFonts: function () {
|
||||||
|
var mediafile = this;
|
||||||
|
return _.filter(Fonts.getAll(), function (font) {
|
||||||
|
return font.path === mediafile.mediafileUrl;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hasFont: function (font) {
|
||||||
|
var allUrls = _.map(this.getFonts(), function (font) {
|
||||||
|
return font.path;
|
||||||
|
});
|
||||||
|
return _.includes(allUrls, font.path);
|
||||||
|
},
|
||||||
|
toggleFont: function (font) {
|
||||||
|
if (this.hasFont(font)) {
|
||||||
|
Fonts.set(font.key);
|
||||||
|
} else {
|
||||||
|
Fonts.set(font.key, this.mediafileUrl);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -77,6 +125,11 @@ angular.module('OpenSlidesApp.mediafiles.resources', [
|
|||||||
is_presentable: ['is_pdf', 'is_image', 'is_video', function (is_pdf, is_image, is_video) {
|
is_presentable: ['is_pdf', 'is_image', 'is_video', function (is_pdf, is_image, is_video) {
|
||||||
return (is_pdf && !this.mediafile.encrypted) || is_image || is_video;
|
return (is_pdf && !this.mediafile.encrypted) || is_image || is_video;
|
||||||
}],
|
}],
|
||||||
|
is_font: [function () {
|
||||||
|
var FONT_FILE_EXTENSIONS = ['ttf', 'woff'];
|
||||||
|
var ext = _.last(this.mediafile.name.split('.'));
|
||||||
|
return _.includes(FONT_FILE_EXTENSIONS, ext);
|
||||||
|
}],
|
||||||
mediafileUrl: [function () {
|
mediafileUrl: [function () {
|
||||||
return this.media_url_prefix + this.mediafile.name;
|
return this.media_url_prefix + this.mediafile.name;
|
||||||
}],
|
}],
|
||||||
|
@ -291,8 +291,12 @@
|
|||||||
<div class="icon-column"> <!-- horizontal block -->
|
<div class="icon-column"> <!-- horizontal block -->
|
||||||
<i ng-style="{'visibility': mediafile.hidden ? 'visible' : 'hidden'}" class="fa fa-lock fa-lg"
|
<i ng-style="{'visibility': mediafile.hidden ? 'visible' : 'hidden'}" class="fa fa-lock fa-lg"
|
||||||
title="{{ 'Is hidden' | translate }}"></i>
|
title="{{ 'Is hidden' | translate }}"></i>
|
||||||
<i ng-style="{'visibility': mediafile.isUsedAsLogo() ? 'visible' : 'hidden'}" class="fa fa-picture-o fa-lg spacer-left"
|
<span os-perms="core.can_manage_logos_and_fonts" class="spacer-left">
|
||||||
title="{{ 'Is used as a logo' | translate }}" os-perms="core.can_manage_logos"></i>
|
<i ng-if="mediafile.isUsedAsLogo()" class="fa fa-picture-o fa-lg"
|
||||||
|
title="{{ 'Is used as a logo' | translate }}"></i>
|
||||||
|
<i ng-if="mediafile.isUsedAsFont()" class="fa fa-font fa-lg"
|
||||||
|
title="{{ 'Is used as a font' | translate }}"></i>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="title-column">
|
<div class="title-column">
|
||||||
<div> <!-- vertical block -->
|
<div> <!-- vertical block -->
|
||||||
@ -325,7 +329,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="width: 40%;" class="pull-right optional">
|
<div style="width: 40%;" class="pull-right optional">
|
||||||
<!-- Logo placeholder dropdown for manage user -->
|
<!-- Logo placeholder dropdown for manage user -->
|
||||||
<div os-perms="core.can_manage_logos"
|
<div os-perms="core.can_manage_logos_and_fonts"
|
||||||
ng-mouseover="mediafile.logoHover=true"
|
ng-mouseover="mediafile.logoHover=true"
|
||||||
ng-mouseleave="mediafile.logoHover=false"
|
ng-mouseleave="mediafile.logoHover=false"
|
||||||
ng-show="mediafile.canBeUsedAsLogo()">
|
ng-show="mediafile.canBeUsedAsLogo()">
|
||||||
@ -354,14 +358,49 @@
|
|||||||
</span>
|
</span>
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownLogos{{ mediafile.id }}">
|
<ul class="dropdown-menu" aria-labelledby="dropdownLogos{{ mediafile.id }}">
|
||||||
<li ng-repeat="logo in logos">
|
<li ng-repeat="logo in logos">
|
||||||
<a href ng-click="toggleLogo(mediafile, logo)">
|
<a href ng-click="mediafile.toggleLogo(logo)">
|
||||||
<i class="fa fa-check" ng-if="hasLogo(mediafile, logo)"></i>
|
<i class="fa fa-check" ng-if="mediafile.hasLogo(logo)"></i>
|
||||||
{{ logo.display_name | translate }}
|
{{ logo.display_name | translate }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Font placeholder dropdown for manage user -->
|
||||||
|
<div os-perms="core.can_manage_logos_and_fonts"
|
||||||
|
ng-mouseover="mediafile.fontHover=true"
|
||||||
|
ng-mouseleave="mediafile.fontHover=false"
|
||||||
|
ng-show="mediafile.canBeUsedAsFont()">
|
||||||
|
<span uib-dropdown>
|
||||||
|
<span id="dropdownFont{{ mediafile.id }}" class="pointer nobr" uib-dropdown-toggle>
|
||||||
|
<span uib-tooltip="{{ 'Manage fonts' | translate }}" tooltip-class="nobr">
|
||||||
|
<span ng-if="!mediafile.isUsedAsFont()" ng-show="mediafile.hover">
|
||||||
|
<i class="fa fa-font"></i>
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
</span>
|
||||||
|
<span ng-if="mediafile.isUsedAsFont()">
|
||||||
|
<span ng-repeat="font in mediafile.getFonts()">
|
||||||
|
<i class="fa fa-font spacer-right"
|
||||||
|
ng-style="{'visibility': $first ? 'visible' : 'hidden'}"></i>
|
||||||
|
<small>
|
||||||
|
{{ font.display_name | translate }}<span ng-if="!$last">,</br></span>
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<i class="fa fa-cog fa-lg spacer-left" ng-show="mediafile.fontHover && mediafile.isUsedAsFont()"></i>
|
||||||
|
</span>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="dropdownFonts{{ mediafile.id }}">
|
||||||
|
<li ng-repeat="font in fonts">
|
||||||
|
<a href ng-click="mediafile.toggleFont(font)">
|
||||||
|
<i class="fa fa-check" ng-if="mediafile.hasFont(font)"></i>
|
||||||
|
{{ font.display_name | translate }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
|||||||
'assignments.can_nominate_self',
|
'assignments.can_nominate_self',
|
||||||
'assignments.can_see',
|
'assignments.can_see',
|
||||||
'core.can_manage_config',
|
'core.can_manage_config',
|
||||||
'core.can_manage_logos',
|
'core.can_manage_logos_and_fonts',
|
||||||
'core.can_manage_projector',
|
'core.can_manage_projector',
|
||||||
'core.can_manage_tags',
|
'core.can_manage_tags',
|
||||||
'core.can_manage_chat',
|
'core.can_manage_chat',
|
||||||
@ -116,7 +116,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
|||||||
permission_dict['core.can_see_frontpage'],
|
permission_dict['core.can_see_frontpage'],
|
||||||
permission_dict['core.can_see_projector'],
|
permission_dict['core.can_see_projector'],
|
||||||
permission_dict['core.can_manage_config'],
|
permission_dict['core.can_manage_config'],
|
||||||
permission_dict['core.can_manage_logos'],
|
permission_dict['core.can_manage_logos_and_fonts'],
|
||||||
permission_dict['core.can_manage_projector'],
|
permission_dict['core.can_manage_projector'],
|
||||||
permission_dict['core.can_manage_tags'],
|
permission_dict['core.can_manage_tags'],
|
||||||
permission_dict['core.can_use_chat'],
|
permission_dict['core.can_use_chat'],
|
||||||
|
Loading…
Reference in New Issue
Block a user