Merge pull request #3568 from FinnStutzenstein/fonts
Custom fonts for pdf and projector
This commit is contained in:
commit
9458ac8161
@ -112,6 +112,7 @@ Core:
|
||||
- Set default of projector resolution to 1220x915 [#2549].
|
||||
- Preparations for the SAML plugin; Fixed caching of main views [#3535].
|
||||
- Removed unnecessary OPTIONS request in config [#3541].
|
||||
- Added possibility to upload custom fonts for projector and pdf [#3568].
|
||||
|
||||
Mediafiles:
|
||||
- Fixed reloading of PDF on page change [#3274].
|
||||
|
@ -80,9 +80,9 @@ gulp.task('pdf-worker', function () {
|
||||
gulp.task('pdf-worker-libs', function () {
|
||||
return gulp.src([
|
||||
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')));
|
||||
});
|
||||
|
||||
|
@ -19,7 +19,7 @@ INPUT_TYPE_MAPPING = {
|
||||
'colorpicker': str,
|
||||
'datetimepicker': int,
|
||||
'majorityMethod': str,
|
||||
'logo': dict,
|
||||
'static': dict,
|
||||
'translations': list,
|
||||
}
|
||||
|
||||
@ -125,9 +125,9 @@ class ConfigHandler:
|
||||
valuecopy[id] = commentsfield
|
||||
value = valuecopy
|
||||
|
||||
if config_variable.input_type == 'logo':
|
||||
if config_variable.input_type == 'static':
|
||||
if not isinstance(value, dict):
|
||||
raise ConfigError(_('logo has to be a dict.'))
|
||||
raise ConfigError(_('This has to be a dict.'))
|
||||
whitelist = (
|
||||
'path',
|
||||
'display_name',
|
||||
|
@ -259,7 +259,7 @@ def get_config_variables():
|
||||
default_value={
|
||||
'display_name': 'Projector logo',
|
||||
'path': ''},
|
||||
input_type='logo',
|
||||
input_type='static',
|
||||
weight=301,
|
||||
group='Logo',
|
||||
hidden=True)
|
||||
@ -269,7 +269,7 @@ def get_config_variables():
|
||||
default_value={
|
||||
'display_name': 'Projector header image',
|
||||
'path': ''},
|
||||
input_type='logo',
|
||||
input_type='static',
|
||||
weight=302,
|
||||
group='Logo',
|
||||
hidden=True)
|
||||
@ -279,7 +279,7 @@ def get_config_variables():
|
||||
default_value={
|
||||
'display_name': 'Web interface header logo',
|
||||
'path': ''},
|
||||
input_type='logo',
|
||||
input_type='static',
|
||||
weight=303,
|
||||
group='Logo',
|
||||
hidden=True)
|
||||
@ -290,7 +290,7 @@ def get_config_variables():
|
||||
default_value={
|
||||
'display_name': 'PDF header logo',
|
||||
'path': ''},
|
||||
input_type='logo',
|
||||
input_type='static',
|
||||
weight=310,
|
||||
group='Logo',
|
||||
hidden=True)
|
||||
@ -300,7 +300,7 @@ def get_config_variables():
|
||||
default_value={
|
||||
'display_name': 'PDF footer logo',
|
||||
'path': ''},
|
||||
input_type='logo',
|
||||
input_type='static',
|
||||
weight=311,
|
||||
group='Logo',
|
||||
hidden=True)
|
||||
@ -310,11 +310,67 @@ def get_config_variables():
|
||||
default_value={
|
||||
'display_name': 'PDF ballot paper logo',
|
||||
'path': ''},
|
||||
input_type='logo',
|
||||
input_type='static',
|
||||
weight=312,
|
||||
group='Logo',
|
||||
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
|
||||
yield ConfigVariable(
|
||||
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 = ()
|
||||
permissions = (
|
||||
('can_manage_config', 'Can manage configuration'),
|
||||
('can_manage_logos', 'Can manage logos'))
|
||||
('can_manage_logos_and_fonts', 'Can manage logos and fonts'))
|
||||
|
||||
@classmethod
|
||||
def get_collection_string(cls):
|
||||
|
@ -3,22 +3,22 @@
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: $font;
|
||||
src: $font-src;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-family: $font;
|
||||
src: $font-src;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: $font-medium;
|
||||
src: $font-medium-src;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-family: $font-medium;
|
||||
src: $font-medium-src;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: $font-condensed;
|
||||
src: $font-condensed-src;
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
font-family: $font-condensed;
|
||||
src: $font-condensed-src;
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: $font-condensed-light;
|
||||
|
@ -1,9 +1,10 @@
|
||||
/** 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-medium: 'Roboto Medium';
|
||||
$font-medium: 'OSFont Medium';
|
||||
$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-light: 'Roboto Condensed Light';
|
||||
$font-condensed-light: 'OSFont Condensed Light';
|
||||
$font-condensed-light-src: url('../fonts/Roboto-Condensed-Light.woff') format('woff');
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
/* General */
|
||||
@import "variables";
|
||||
@import "fonts";
|
||||
@import "helper";
|
||||
@import "ui-override";
|
||||
|
||||
|
@ -15,4 +15,3 @@
|
||||
@import "../../../motions/static/css/motions/site";
|
||||
@import "../../../users/static/css/users/site";
|
||||
@import "../../../mediafiles/static/css/mediafiles/site";
|
||||
|
||||
|
@ -826,36 +826,78 @@ angular.module('OpenSlidesApp.core', [
|
||||
getAll: function () {
|
||||
var self = this;
|
||||
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);
|
||||
if (config) {
|
||||
config.value.key = key;
|
||||
return config.value;
|
||||
}
|
||||
},
|
||||
isMediafileUsedAsLogo: function (mediafile) {
|
||||
return _.find(this.getAll(), function (logoPlaceholder) {
|
||||
return logoPlaceholder.path === mediafile.mediafileUrl;
|
||||
});
|
||||
},
|
||||
canMediafileBeUsedAsLogo: function (mediafile) {
|
||||
return mediafile.is_image;
|
||||
},
|
||||
setMediafile: function (key, mediafile) {
|
||||
set: function (key, path) {
|
||||
var config = Config.get(key);
|
||||
if (!mediafile || mediafile.canBeUsedAsLogo()) {
|
||||
config.value.path = mediafile ? mediafile.mediafileUrl : '';
|
||||
if (config) {
|
||||
config.value.path = path;// ? mediafile.mediafileUrl : '';
|
||||
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;
|
||||
|
||||
// PdfMake and Fonts
|
||||
// PdfMake
|
||||
importScripts('/static/js/workers/pdf-worker-libs.js');
|
||||
|
||||
// Set default font family.
|
||||
// To use custom ttf font files you have to replace the vfs_fonts.js file.
|
||||
// See https://github.com/pdfmake/pdfmake/wiki/Custom-Fonts---client-side
|
||||
// "PdfFont" is used as generic name in core/pdf.js. Adjust the four
|
||||
// font style names only.
|
||||
// "PdfFont" and "OSFont-*" are generic names used here and in core/pdf.js. The
|
||||
// suffix after "OSFont-" has to be the same as the config value.
|
||||
pdfMake.fonts = {
|
||||
PdfFont: {
|
||||
normal: 'Roboto-Regular.ttf',
|
||||
bold: 'Roboto-Medium.ttf',
|
||||
italics: 'Roboto-Italic.ttf',
|
||||
bolditalics: 'Roboto-Italic.ttf'
|
||||
normal: 'OSFont-regular.ttf',
|
||||
bold: 'OSFont-bold.ttf',
|
||||
italics: 'OSFont-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
|
||||
self.addEventListener('message', function(e) {
|
||||
var data = JSON.parse(e.data);
|
||||
var doc = data.pdfDocument;
|
||||
pdfMake.vfs = data.vfs; // Set custom fonts.
|
||||
|
||||
var doc = data.pdfDocument;
|
||||
replaceFooter(doc);
|
||||
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', [
|
||||
'$timeout',
|
||||
'$q',
|
||||
'gettextCatalog',
|
||||
'FileSaver',
|
||||
'PdfVfs',
|
||||
'Messaging',
|
||||
function ($timeout, $q, gettextCatalog, FileSaver, Messaging) {
|
||||
function ($timeout, $q, gettextCatalog, FileSaver, PdfVfs, Messaging) {
|
||||
var filenameMessageMap = {};
|
||||
var b64toBlob = function(b64Data) {
|
||||
var byteCharacters = atob(b64Data);
|
||||
@ -1105,40 +1191,46 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
||||
return {
|
||||
getBase64FromDocument: function (pdfDocument) {
|
||||
return $q(function (resolve, reject) {
|
||||
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
||||
pdfWorker.addEventListener('message', function (event) {
|
||||
resolve(event.data);
|
||||
PdfVfs.get().then(function (vfs) {
|
||||
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
||||
pdfWorker.addEventListener('message', function (event) {
|
||||
resolve(event.data);
|
||||
});
|
||||
pdfWorker.addEventListener('error', function (event) {
|
||||
reject(event);
|
||||
});
|
||||
pdfWorker.postMessage(JSON.stringify({
|
||||
pdfDocument: pdfDocument,
|
||||
vfs: vfs,
|
||||
}));
|
||||
});
|
||||
pdfWorker.addEventListener('error', function (event) {
|
||||
reject(event);
|
||||
});
|
||||
pdfWorker.postMessage(JSON.stringify({
|
||||
pdfDocument: pdfDocument
|
||||
}));
|
||||
});
|
||||
},
|
||||
// Struckture of pdfDocuments: { filname1: doc, filename2: doc, ...}
|
||||
getBase64FromMultipleDocuments: function (pdfDocuments) {
|
||||
return $q(function (resolve, reject) {
|
||||
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
||||
var resultCount = 0;
|
||||
var base64Map = {}; // Maps filename to base64
|
||||
pdfWorker.addEventListener('message', function (event) {
|
||||
resultCount++;
|
||||
var data = JSON.parse(event.data);
|
||||
base64Map[data.filename] = data.base64;
|
||||
if (resultCount === _.keys(pdfDocuments).length) {
|
||||
resolve(base64Map);
|
||||
}
|
||||
});
|
||||
pdfWorker.addEventListener('error', function (event) {
|
||||
reject(event);
|
||||
});
|
||||
_.forEach(pdfDocuments, function (doc, filename) {
|
||||
pdfWorker.postMessage(JSON.stringify({
|
||||
filename: filename,
|
||||
pdfDocument: doc
|
||||
}));
|
||||
PdfVfs.get().then(function (vfs) {
|
||||
var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
|
||||
var resultCount = 0;
|
||||
var base64Map = {}; // Maps filename to base64
|
||||
pdfWorker.addEventListener('message', function (event) {
|
||||
resultCount++;
|
||||
var data = JSON.parse(event.data);
|
||||
base64Map[data.filename] = data.base64;
|
||||
if (resultCount === _.keys(pdfDocuments).length) {
|
||||
resolve(base64Map);
|
||||
}
|
||||
});
|
||||
pdfWorker.addEventListener('error', function (event) {
|
||||
reject(event);
|
||||
});
|
||||
_.forEach(pdfDocuments, function (doc, filename) {
|
||||
pdfWorker.postMessage(JSON.stringify({
|
||||
filename: filename,
|
||||
pdfDocument: doc,
|
||||
vfs: vfs,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -78,12 +78,13 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
}
|
||||
])
|
||||
|
||||
.controller('LanguageCtrl', [
|
||||
.controller('LanguageAndFontCtrl', [
|
||||
'$scope',
|
||||
'Languages',
|
||||
'Config',
|
||||
'ProjectorID',
|
||||
function ($scope, Languages, Config, ProjectorID) {
|
||||
'Fonts',
|
||||
function ($scope, Languages, Config, ProjectorID, Fonts) {
|
||||
// for the dynamic title
|
||||
$scope.projectorId = ProjectorID();
|
||||
|
||||
@ -98,6 +99,18 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
}
|
||||
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>
|
||||
<html lang="{{ selectedLanguage }}" ng-controller="LanguageCtrl" class="no-js">
|
||||
<html lang="{{ selectedLanguage }}" ng-controller="LanguageAndFontCtrl" class="no-js">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<base href="/">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!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 http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<base href="/">
|
||||
@ -7,6 +7,34 @@
|
||||
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
||||
<link rel="stylesheet" href="static/css/openslides-projector.css">
|
||||
<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.js"></script>
|
||||
<script src="static/js/openslides-templates.js"></script>
|
||||
|
@ -600,11 +600,12 @@ class ConfigViewSet(ModelViewSet):
|
||||
# enabled.
|
||||
result = self.request.user.is_authenticated() or anonymous_is_enabled()
|
||||
elif self.action in ('partial_update', 'update'):
|
||||
# The user needs 'core.can_manage_logos' for all config values
|
||||
# starting with 'logo'. For all other config values th euser needs
|
||||
# The user needs 'core.can_manage_logos_and_fonts' for all config values
|
||||
# starting with 'logo' and 'font'. For all other config values th euser needs
|
||||
# the default permissions 'core.can_manage_config'.
|
||||
if self.kwargs['pk'].startswith('logo'):
|
||||
result = has_perm(self.request.user, 'core.can_manage_logos')
|
||||
pk = self.kwargs['pk']
|
||||
if pk.startswith('logo') or pk.startswith('font'):
|
||||
result = has_perm(self.request.user, 'core.can_manage_logos_and_fonts')
|
||||
else:
|
||||
result = has_perm(self.request.user, 'core.can_manage_config')
|
||||
else:
|
||||
|
@ -24,8 +24,9 @@ angular.module('OpenSlidesApp.mediafiles.list', [
|
||||
'Mediafile',
|
||||
'MediafileForm',
|
||||
'Logos',
|
||||
'Fonts',
|
||||
function ($http, $scope, gettext, ngDialog, osTableFilter, osTableSort, osTablePagination,
|
||||
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos) {
|
||||
ProjectionDefault, Projector, User, Mediafile, MediafileForm, Logos, Fonts) {
|
||||
$scope.$watch(function () {
|
||||
return Mediafile.lastModified();
|
||||
}, function () {
|
||||
@ -259,21 +260,9 @@ angular.module('OpenSlidesApp.mediafiles.list', [
|
||||
);
|
||||
};
|
||||
|
||||
/** Logos **/
|
||||
/** Logos and fonts **/
|
||||
$scope.logos = Logos.getAll();
|
||||
$scope.toggleLogo = function (mediafile, logo) {
|
||||
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.fonts = Fonts.getAll();
|
||||
$scope.hasProjectorHeaderLogo = function (mediafile) {
|
||||
return _.some(mediafile.getLogos(), function (logo) {
|
||||
return logo.key === 'logo_projector_header';
|
||||
|
@ -13,7 +13,8 @@ angular.module('OpenSlidesApp.mediafiles.resources', [
|
||||
'gettext',
|
||||
'jsDataModel',
|
||||
'Logos',
|
||||
function (DS, gettext, jsDataModel, Logos) {
|
||||
'Fonts',
|
||||
function (DS, gettext, jsDataModel, Logos, Fonts) {
|
||||
var name = 'mediafiles/mediafile';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
@ -50,13 +51,60 @@ angular.module('OpenSlidesApp.mediafiles.resources', [
|
||||
});
|
||||
},
|
||||
isUsedAsLogo: function () {
|
||||
return Logos.isMediafileUsedAsLogo(this);
|
||||
var mediafile = this;
|
||||
return _.find(Logos.getAll(), function (logoPlaceholder) {
|
||||
return logoPlaceholder.path === mediafile.mediafileUrl;
|
||||
});
|
||||
},
|
||||
canBeUsedAsLogo: function () {
|
||||
return Logos.canMediafileBeUsedAsLogo(this);
|
||||
return this.is_image;
|
||||
},
|
||||
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: {
|
||||
@ -77,6 +125,11 @@ angular.module('OpenSlidesApp.mediafiles.resources', [
|
||||
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;
|
||||
}],
|
||||
is_font: [function () {
|
||||
var FONT_FILE_EXTENSIONS = ['ttf', 'woff'];
|
||||
var ext = _.last(this.mediafile.name.split('.'));
|
||||
return _.includes(FONT_FILE_EXTENSIONS, ext);
|
||||
}],
|
||||
mediafileUrl: [function () {
|
||||
return this.media_url_prefix + this.mediafile.name;
|
||||
}],
|
||||
|
@ -291,8 +291,12 @@
|
||||
<div class="icon-column"> <!-- horizontal block -->
|
||||
<i ng-style="{'visibility': mediafile.hidden ? 'visible' : 'hidden'}" class="fa fa-lock fa-lg"
|
||||
title="{{ 'Is hidden' | translate }}"></i>
|
||||
<i ng-style="{'visibility': mediafile.isUsedAsLogo() ? 'visible' : 'hidden'}" class="fa fa-picture-o fa-lg spacer-left"
|
||||
title="{{ 'Is used as a logo' | translate }}" os-perms="core.can_manage_logos"></i>
|
||||
<span os-perms="core.can_manage_logos_and_fonts" class="spacer-left">
|
||||
<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 class="title-column">
|
||||
<div> <!-- vertical block -->
|
||||
@ -325,7 +329,7 @@
|
||||
</div>
|
||||
<div style="width: 40%;" class="pull-right optional">
|
||||
<!-- 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-mouseleave="mediafile.logoHover=false"
|
||||
ng-show="mediafile.canBeUsedAsLogo()">
|
||||
@ -354,14 +358,49 @@
|
||||
</span>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownLogos{{ mediafile.id }}">
|
||||
<li ng-repeat="logo in logos">
|
||||
<a href ng-click="toggleLogo(mediafile, logo)">
|
||||
<i class="fa fa-check" ng-if="hasLogo(mediafile, logo)"></i>
|
||||
<a href ng-click="mediafile.toggleLogo(logo)">
|
||||
<i class="fa fa-check" ng-if="mediafile.hasLogo(logo)"></i>
|
||||
{{ logo.display_name | translate }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</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>
|
||||
|
||||
|
@ -39,7 +39,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
'assignments.can_nominate_self',
|
||||
'assignments.can_see',
|
||||
'core.can_manage_config',
|
||||
'core.can_manage_logos',
|
||||
'core.can_manage_logos_and_fonts',
|
||||
'core.can_manage_projector',
|
||||
'core.can_manage_tags',
|
||||
'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_projector'],
|
||||
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_tags'],
|
||||
permission_dict['core.can_use_chat'],
|
||||
|
Loading…
Reference in New Issue
Block a user