Gitlab CSE Unil

Commit 46db4303 authored by Julien Furrer's avatar Julien Furrer
Browse files

Big packed commit with a lot of mixed good stuff

Missing intermediate commits with good comments....
parent b7fc9b91
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('adim', '0004_auto_20150424_1209'),
]
operations = [
migrations.AddField(
model_name='anobj',
name='env',
field=models.CharField(blank=True, max_length=64, choices=[('', 'Standard'), ('cimaf', 'CIMAF')]),
),
]
......@@ -28,6 +28,10 @@ RANDOM_NODE = random.randrange(0, 1 << 48L) | 0x010000000000L
# Destination path for uploaded images, relative to MEDIA_ROOT
AO_IMAGES_PATH = 'ao_images'
AO_ENVIRON = (
('', 'Standard'),
('cimaf', 'CIMAF'),
)
class AOType(models.Model):
name = models.CharField(max_length=128)
......@@ -106,6 +110,8 @@ class AnObj(models.Model):
members = models.ManyToManyField(User, verbose_name=_("members"), through='AnObjMembership',
related_name='shared_anobjs', blank=True)
env = models.CharField(max_length=64, choices=AO_ENVIRON, blank=True)
image = models.ImageField(upload_to=get_image_path, verbose_name=_("image"), blank=True, null=True)
image_url = models.CharField(max_length=512, verbose_name=_("image url"), blank=True, null=True, default="")
......
......@@ -102,7 +102,7 @@ class AnObjSerializer(BaseAnObjSerializer):
class Meta:
model = AnObj
fields = ('id', 'uuid', 'name', 'owners', 'owner', 'owner_name', 'annotations', 'members',
'sharing_mode', 'sharing_opts', 'locked', 'allow_public_publishing')
'sharing_mode', 'sharing_opts', 'locked', 'allow_public_publishing', 'env')
class SharedAnObjSerializer(BaseAnObjSerializer):
......
......@@ -52,6 +52,10 @@ function($){
},
// If not defined or empty, will be built from the tools button found on the page.
// Otherwise use the value given. If an annotation type is not found in `activeTools` it wont be displayed
activeTools: [],
userEngine: {
limit: 7,
remoteUrl: "http://path/to/suggestion/url/?q=%QUERY",
......@@ -82,6 +86,15 @@ function($){
}, window[adim_config_varname] || {}, true);
if (!config.activeTools || config.activeTools.length == 0)
try {
config.activeTools = $("#draw-tool-tb [data-tool-name]").map(function(){
return $(this).attr('data-tool-name')
});
} catch (e) {
config.activeTools = null;
}
if (config.csrfToken) {
$.ajaxSetup({
beforeSend: function (xhr) {
......
/**
* Created by jfurrer on 22.06.15.
*/
define([
"jquery",
"underscore",
"paper",
"signals",
"adim/config",
"adim/view",
"adim/attributes",
"adim/io",
"adim/tools",
"adim/ui"
],
function($, _, paper, Signal, config, view, attributes, io, tools, ui){
/**
* Signal binding specific to CIMAF environment
* @private
*/
function _initSignalRouting(){
console.info("_initSignalRouting from env/cimaf");
var userLayer;
/**
* Count the number of intersections for each CMS Area.
* Only intersection with concordant stria are relevant.
*
* @param [annotation] {object} The annotation responsible for the update. If it is a 'cmsarea' it means
* this area was created/edited, so we don't need to update the count for other area.
*/
function updateAreaIntersectionCount(annotation) {
// If annotation is not area or concordant stria, there is no need for computation
if (annotation && ['cmsarea', 'concordantstr'].indexOf(annotation.data.type) < 0)
return;
if (!userLayer) {
userLayer = view.getOrCreateUserLayer(config.user.id, config.user.full_name)
}
if (!userLayer) return;
var zones = userLayer.children.filter(function(a) { return a.data.type && a.data.type === 'cmsarea'; });
var striae = userLayer.children.filter(function(a) { return a.data.type && a.data.type === 'concordantstr'; });
// If the current selection is an area, keep it's current nbConcordances
var items = paper.project.getSelectedItems();
var selectedArea = (items.length === 1 && items[0].data.type === 'cmsarea') ? items[0] : null;
var selectedAreaCount = selectedArea ? selectedArea.data.prop.nbConcordances : null;
var zone, nbInter, i, ii, nbStria = striae.length;
for (i = 0; zone = zones[i]; i++) {
nbInter = 0;
for (ii=0; ii<nbStria; ii++) {
nbInter += Math.round(zone.getIntersections(striae[ii]).length / 2);
}
zone.data.prop.nbConcordances = nbInter;
}
if (selectedAreaCount !== null && selectedArea.data.prop.nbConcordances !== selectedAreaCount) {
ui.displayProperties(selectedArea);
}
}
tools.events.annotationAdded.add(function(annotation) {
updateAreaIntersectionCount(annotation);
}, this, 100);
tools.events.annotationChanged.add(function(annotations) {
updateAreaIntersectionCount(annotations.length===1 ? annotations[0] : void(0));
}, this, 100);
// This should be called before the annotations are rendered, so we bind it to the imageLoaded event
// before the image loading is started. To do so we set a higher priority (100)
// than the handler defined in main. @TODO: get the cut position and cut margin from config
io.events.annotableLoaded.add(function(annotableData){
var concordantstrTool = tools.tools['concordantstr'];
var discordantstrTool = tools.tools['discordantstr'];
var cmsareaTool = tools.tools['cmsarea'];
view.events.imageLoaded.add(function(p){
var cutPos = p.raster.scaling.x * p.raster.width / 2 ;
var cutMargin = 25;
concordantstrTool.listeners.onCutPositionChanged(cutPos);
discordantstrTool.listeners.onCutPositionChanged(cutPos);
cmsareaTool.listeners.onCutPositionChanged(cutPos);
cmsareaTool.listeners.onSetAreaWidth(cutMargin*2);
});
}, 100);
}
function _initUI(params) {
/**
* UI initialization for CIMF. Called after ui.init by main
*/
// Init Type Display Selector
$(".adim-display-type-selector").find("button").on('click', function(event) {
var $but = $(this);
var activate = !$but.hasClass("active");
var typeName = $but.data('typeName');
if (event.metaKey) {
if (typeName !== 'image') {
view.selectItemsByType(typeName);
}
} else {
$but.toggleClass("active", activate);
if (typeName === 'image') {
attributes.setImageOpacity(activate ? 100 : 0, true);
//_opacitySlider.slider('setValue', activate ? 100 : 0);
} else {
view.toggleItemsByType($but.data('typeName'), activate);
ui.enableTool(typeName, activate);
}
}
});
}
return {
name: 'cimaf',
initSignalRouting: _initSignalRouting,
initUI: _initUI
}
});
\ No newline at end of file
/**
* This module is a proxy for the various environement defined
* It could be bypassed if the environment is directly defined in
* the main app by overriding the "env" path. See su_app.js
*/
define([
"underscore",
"adim/config",
// below are environments modules
"env/cimaf/env"
],
function(_, config){
var _envs = _.filter(
Array.prototype.slice.call(arguments, 2),
{name: config.annotable.env}
);
function _proxy(fname) {
return function() {
_.invoke(_envs, fname, arguments);
}
}
return {
name: 'default'
, initSignalRouting: _proxy('initSignalRouting')
, initUI: _proxy('initUI')
}
});
\ No newline at end of file
// Customized version of https://github.com/javiertoledo/bootstrap-rating-input
// based on an old old version
(function ($) {
$.fn.rating = function (cmd) {
var element;
var classActive = 'label label-warning rating-on'; //'glyphicon glyphicon-star';
var classInactive = 'label label-default rating-off'; //'glyphicon glyphicon-star-empty';
// A private function to highlight a star corresponding to a given value
function _paintValue(ratingInput, value) {
var selectedStar = $(ratingInput).find('[data-value=' + value + ']');
selectedStar.removeClass(classInactive).addClass(classActive);
selectedStar.prevAll('[data-value]').removeClass(classInactive).addClass(classActive);
selectedStar.nextAll('[data-value]').removeClass(classActive).addClass(classInactive);
}
// A private function to remove the selected rating
function _clearValue(ratingInput) {
var self = $(ratingInput);
self.find('[data-value]').removeClass(classActive).addClass(classInactive);
self.find('.rating-clear').hide();
var input = self.find('input');
input.val(input.data('empty-value')).trigger('change');
}
function buildWidgets(elems) {
// Iterate and transform all selected inputs
for (element = elems.length - 1; element >= 0; element--) {
var el, i,
originalInput = $(elems[element]),
max = originalInput.data('max') || 5,
min = originalInput.data('min') || 0,
clearable = originalInput.data('clearable') || null,
displayValue = originalInput.data('display') || null,
stars = '';
// HTML element construction
for (i = min; i <= max; i++) {
// Create <max> empty stars
stars += [
'<span class="', classInactive, '" data-value="', i, '">',
displayValue ? i : "",
'</span>'
].join('');
}
// Add a clear link if clearable option is set
if (clearable) {
stars += [
' <a class="rating-clear" style="display:none;" href="javascript:void">',
'<span class="glyphicon glyphicon-remove"></span> ',
clearable,
'</a>'].join('');
}
// Clone the original input to preserve any additional data bindings using attributes.
var newInput = originalInput.clone()
.attr('type', 'hidden')
.data('max', max)
.data('min', min);
// Rating widget is wrapped inside a div
el = [
'<div class="rating-input">',
stars,
'</div>'].join('');
// Replace original inputs HTML with the new one
originalInput.replaceWith($(el).append(newInput));
enableWidgets(newInput);
}
}
function enableWidgets(elemInput) {
elemInput.removeAttr("disabled");
// Give live to the newly generated widgets
elemInput.closest('.rating-input')
.removeClass("disabled")
// Highlight stars on hovering
.on('mouseenter', '[data-value]', function () {
var self = $(this);
_paintValue(self.closest('.rating-input'), self.data('value'));
})
// View current value while mouse is out
.on('mouseleave', '[data-value]', function () {
var self = $(this),
input = self.siblings('input'),
val = input.val(),
min = input.data('min'),
max = input.data('max');
if (val >= min && val <= max) {
_paintValue(self.closest('.rating-input'), val);
} else {
_clearValue(self.closest('.rating-input'));
}
})
// Set the selected value to the hidden field
.on('click', '[data-value]', function (e) {
var self = $(this);
var val = self.data('value');
self.siblings('input').val(val).trigger('change');
self.siblings('.rating-clear').show();
e.preventDefault();
return false;
})
// Remove value on clear
.on('click', '.rating-clear', function (e) {
_clearValue($(this).closest('.rating-input'));
e.preventDefault();
return false;
})
// Initialize view with default value
.each(function () {
var input = $(this).find('input'),
val = input.val(),
min = input.data('min'),
max = input.data('max');
if (val !== "" && +val >= min && +val <= max) {
_paintValue(this, val);
$(this).find('.rating-clear').show();
}
else {
_clearValue(this);
}
});
}
function disableWidgets(elemInput) {
elemInput.attr("disabled", "disabled");
var ratingInput = elemInput.closest('.rating-input');
ratingInput
.addClass("disabled")
.off('mouseenter', '[data-value]')
.off('mouseleave', '[data-value]')
.off('click', '[data-value]')
.off('click', '.rating-clear')
;
_clearValue(ratingInput);
}
function setValue(elemInput, val) {
var value = parseInt(val, 10);
if (!isNaN(value)) {
var ratingInput = elemInput.closest('.rating-input');
elemInput.val(val);
_paintValue(ratingInput, value);
}
}
if (cmd) {
if (cmd === 'disable') {
this.each(function(i, n){
disableWidgets($(n));
});
} else if (cmd === 'enable') {
this.each(function(i, n){
enableWidgets($(n));
});
} else if (cmd === 'setValue' && arguments.length > 1) {
var val = arguments[1];
this.each(function(i, n){
setValue($(n), val);
});
}
} else {
buildWidgets(this);
}
};
// Auto apply conversion of number fields with class 'rating' into rating-fields
$(function () {
if ($('input.rating[type=number]').length > 0) {
$('input.rating[type=number]').rating();
}
});
}(jQuery));
......@@ -33,14 +33,32 @@ define([
"adim/users",
"helper/theme",
"helper/fileUploader",
"helper/exporter"
"helper/exporter",
"env/env"
],
function ($, paper, Signal, config, view, io, tools, attributes, ui, Users, theme, fileUploader, exporter) {
function ($, paper, Signal, config, view, io, tools, attributes, ui, Users, theme, fileUploader, exporter, environ) {
var _events = {};
var CANVAS_EL_ID = 'my-canvas';
console.timeEnd("main app loading");
function initSignalRouting() {
// ----- Initialise les tools avec les données de l'annotable -----
io.events.annotableLoaded.add(function(annotableData){
// Add known users to the Users module
Users.addUsers([{id: annotableData.owner, username: annotableData.owner_name}]);
// Load the image
console.time("loadImage");
view.loadImage({url: config.annotable.image},
function(){
console.timeEnd("loadImage");
console.time("loadAnnotations");
view.loadAnnotations(annotableData.annotations, config.user.id);
console.timeEnd("loadAnnotations");
}
);
});
tools.events.annotationAdded.add(function(annotation) {
io.saveAnnotations(annotation);
});
......@@ -49,6 +67,8 @@ define([
io.saveAnnotations(annotations);
});
// ------ Attributes -----
attributes.events.annotationChanged.add(function(annotations, forceAll) {
if (forceAll) {
io.saveUserAnnotations();
......@@ -57,6 +77,8 @@ define([
}
});
// ----- View -----
view.events.annotationPropertyChanged.add(function(annotation){
io.saveAnnotations([annotation]);
});
......@@ -65,6 +87,8 @@ define([
io.removeAnnotation(annotId);
});
// ----- UI -----
ui.events.saveButClick.add(function(){
io.saveUserAnnotations();
});
......@@ -73,6 +97,7 @@ define([
//io.setAutoSave(autoSave);
});
// ----- IO -----
/**
* When an annotation is created, it has a temporary id until it is saved
* to the DB. After saving, we use the temporary id to retrieve the Annotation
......@@ -101,7 +126,6 @@ define([
}
});
io.events.annotationSaved.add(function(data){
if (config.autoSaveThumbnail) {
sendThumbnail();
......@@ -115,7 +139,11 @@ define([
});
}
initSignalRouting();
if (_.isFunction(environ.initSignalRouting))
environ.initSignalRouting();
var sendThumbnail = (function() {
var throttleDelay = 5 * 1000, // Waiting time before executing job
......@@ -176,6 +204,8 @@ define([
attributes.init();
ui.init({ro:config.annotable.locked});
if (_.isFunction(environ.initUI))
environ.initUI({ro:config.annotable.locked});
io.loadData(config.api.annotables + config.annotable.id + '/'); // + '?mode=' + config.mode);
......@@ -293,59 +323,9 @@ define([
}
});
// FIXME: A supprimer
//// Stries
//var concordantstrTool = tools.tools['concordantstr'];
//var discordantstrTool = tools.tools['discordantstr'];
//var cmsareaTool = tools.tools['cmsarea'];
// ----- Initialise les tools avec les données de l'annotable -----
io.events.annotableLoaded.add(function(annotableData){
// Add known users to the Users module
Users.addUsers([{id: annotableData.owner, username: annotableData.owner_name}]);
//Users.addUsers(annotableData.members); // members are only loaded if needed. this statement should be removed
// Load the image
console.time("loadImage");
view.loadImage({url: config.annotable.image},
function(){
console.timeEnd("loadImage");
console.time("loadAnnotations");
view.loadAnnotations(annotableData.annotations, config.user.id);
console.timeEnd("loadAnnotations");
}
);
});
window.ADIM = { tools: tools };
console.timeEnd("main dom ready");
});
// The ``adim_global_varname`` var in not used in the code.
// The global ADIM var is neither used directly (window.ADIM or just ADIM)
// The idea of passing an ADIM object to a callback defined in config was interesting
// but is currently not used. The whole stuff will be commented for now.
//var ADIM = {
// events: {
// imageLoaded: view.events.imageLoaded
// },
// view: {
// _view: view,
// resize: view.resize,
// showLayer: view.showLayer,
// hideLayer: view.hideLayer,
// toggleLayer: view.toggleLayer
// },
// io: io,
// ui: ui,
// tools: tools,
// config: config
//};