diff --git a/capsule-prototype/css/common.css b/capsule-prototype/css/common.css
index f4cb370e8e61f94ab78196d1428eae580d75985a..959c244a4f1af4d3ae72caeb22aa8b668f71d958 100644
--- a/capsule-prototype/css/common.css
+++ b/capsule-prototype/css/common.css
@@ -23,3 +23,7 @@
 	content: "\2713 ";
 	color: black;
 }
+
+input.warning, *.warning {
+  border: 2px solid red;
+}
diff --git a/capsule-prototype/css/mosaic.css b/capsule-prototype/css/mosaic.css
index 4a8a8ccd8f78604ca9177cc0c78559976b3b0992..78f75b3fa87701f978ae3fbf5b4e68a7d34dd6c3 100644
--- a/capsule-prototype/css/mosaic.css
+++ b/capsule-prototype/css/mosaic.css
@@ -3,6 +3,9 @@
 	--card-height: 150px;
 	--card-width: 150px;
 
+	--card-image-container-width: var(--card-width);
+	--card-image-container-height: calc(var(--card-height) - 50px);
+
 	--filter-width: 80px;
 	--filter-height: 25px;
 
@@ -23,11 +26,11 @@
 	display: flex;
 	flex-direction: row;
 	flex: 1;
-
 	flex-wrap: wrap;
 }
 
 .mosaic_filter_item, .mosaic_label_filter {
+  min-width: min-content;
 	width: var(--filter-width);
 	height: var(--filter-height);
 	margin: var(--base-spacing);
@@ -38,10 +41,7 @@
 	justify-content: left;
 	padding-left: var(--filter-bg-size);
 	align-items: center;
-}
-
-.mosaic_label_filter_enabled {
-  background-color: rgba(255, 255, 255, .5);
+	flex-grow: 1;
 }
 
 .mosaic_category {
@@ -58,6 +58,15 @@
 	flex-direction: column;
 	align-items: center;
 	text-align: center;
+	background: inherit;
+}
+
+.mosaic_button {
+	cursor: pointer;
+}
+
+.mosaic_button:hover {
+	filter: brightness(1.5);
 }
 
 .mosaic_item span {
@@ -65,13 +74,24 @@
 	overflow: hidden;
 	text-overflow: ellipsis;
 	padding: var(--base-spacing);
+	color: #ebecec;
 }
 
-.mosaic_item img {
+.mosaic_item_thumbnail_container {
+	width: var(--card-image-container-width);
+	height: var(--card-image-container-height);
+	display: flex;
+	align-items: center;
+	align-content: center;
+	justify-content: center;
+	padding: var(--base-spacing);
+}
+
+.mosaic_item_thumbnail_container_img {
 	width: 80%;
 	height: auto;
-	max-height: var(--card-height);
-	padding: var(--base-spacing);
+	max-height: var(--card-image-container-height);
+	max-width: var(--card-image-container-width);
 }
 
 .mosaic_filter_item:not(.mosaic_filter_item_all), .mosaic_category > h2 {
@@ -90,6 +110,10 @@
 	background-image: url('images/icn-menu-preview.png');
 }
 
+.mosaic_label_filter_enabled {
+	background-color: rgba(255, 255, 255, .5);
+}
+
 .mosaic_filter_disabled {
 	background-image: url('images/icn-menu-filter.png');
 }
diff --git a/capsule-prototype/index.html b/capsule-prototype/index.html
index 8caea838f292055c6eb52b4b1c8e6fc9cab53aad..7ad8498a28c865808909621e4d94c9849c581c55 100644
--- a/capsule-prototype/index.html
+++ b/capsule-prototype/index.html
@@ -25,9 +25,11 @@
 	<script language="javascript" type='text/javascript' src='../shared/js/libs/sha1.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/libs/fastdom.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/libs/moment-with-langs.min.js'></script>
+	<script language="javascript" type="text/javascript" src='../shared/js/shared.js'></script>
 
 	<script language="javascript" type='text/javascript' src="../shared/php/rekallApp.js.php"></script>
 
+	<script language="javascript" type="text/javascript" src="../shared/js/online-rekall/NotificationEntityChanged.js" ></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/rekall/Utils.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/online-rekall/Rekall.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/rekall/Source.js'></script>
@@ -38,6 +40,8 @@
 	<script language="javascript" type='text/javascript' src='../shared/js/online-rekall/Tags.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/online-rekall/Timeline.js'></script>
 	<script language="javascript" type='text/javascript' src="../shared/js/online-rekall/RekallApplication.js"></script>
+	<script language="javascript" type="text/javascript" src='../shared/js/MosaicPanelWidget.js'></script>
+	<script language="javascript" type="text/javascript" src='../shared/js/PopupPanelWidget.js'></script>
 
 	<script language="javascript" type='text/javascript' src='../shared/js/iannix.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/online-script.js'></script>
@@ -132,61 +136,6 @@
 					<div class="editmode" id="popupEditSupprimer">Delete file</div>
 				</td>
 				<td id="popupRight">
-					<div id="closePopupEdit">&#10005;</div>
-
-					<div class="popupRightItem" id="popupNom"></div>
-					<input class="popupInput" id="popupNomInput" type="text" value=""/>
-
-					<div class="popupRightItem" id="popupAuthor" title="Author"></div>
-					<input class="popupInput" id="popupAuthorInput" type="text"></input>
-
-					<div class="popupRightItem" id="popupTC">
-						<div class="popupTCdisplay" id="popupTCin"></div>
-						<div class="popupTCdisplay" id="popupTCout"></div>
-					</div>
-					<div class="popupInput" id="popupTCedit">
-						<span class="popupTClabel">start</span>
-						<input class="popupTCeditfield" id="popupTCinMin" maxlength="2"  type="text" value=""/>&nbsp;:&nbsp;<input class="popupTCeditfield" id="popupTCinSec" maxlength="2"  type="text" value=""/>
-						&nbsp;&nbsp;&nbsp;&nbsp;<div class="nowTCbtn" id="nowTCin">now</div>
-						<br/>
-						<span class="popupTClabel">end</span>
-						<input class="popupTCeditfield" id="popupTCoutMin" maxlength="2" type="text" value=""/>&nbsp;:&nbsp;<input class="popupTCeditfield" id="popupTCoutSec" maxlength="2" type="text" value=""/>
-						&nbsp;&nbsp;&nbsp;&nbsp;<div class="nowTCbtn" id="nowTCout">now</div> / <div class="nowTCbtn" id="eovTCout">end of video</div>
-					</div>
-
-					<div class="popupRightItem" id="popupLegende" title="Comment"></div>
-					<textarea class="popupInput" id="popupLegendeInput" type="text"></textarea>
-
-					<div class="popupRightItem" id="popupLink" title="Link"></div>
-					<textarea class="popupInput" id="popupLinkInput" type="text"></textarea>
-
-          <div class='popupNewTitles'>+ Playback rate</div>
-            <form id='popupSpeedInput' class='flex-row playback_form popupInput popupInputNoHide'>
-              <input type='radio' id='annotation_playback_speed_025' value='0.25' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_025'>0.25</label>
-              <input type='radio' id='annotation_playback_speed_05' value='0.5' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_05'>0.5</label>
-              <input type='radio' id='annotation_playback_speed_075' value='0.75' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_075'>0.75</label>
-              <input type='radio' id='annotation_playback_speed_1' value='1' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_1'>normal</label>
-              <input type='radio' id='annotation_playback_speed_125' value='1.25' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_125'>1.25</label>
-              <input type='radio' id='annotation_playback_speed_15' value='1.5' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_15'>1.5</label>
-              <input type='radio' id='annotation_playback_speed_175' value='1.75' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_175'>1.75</label>
-              <input type='radio' id='annotation_playback_speed_2' value='2' name='playback_rate' />
-              <label class='left_menu_item' for='annotation_playback_speed_2'>2</label>
-            </form>
-
-            <div class='popupNewTitles'>+ Tags</div>
-            <input type='text' id='new_annotation_label' placeholder='Add a new tag' class='editmode' />
-            <div id='popupLabelsInput' class='flex-col popupInput popupInputNoHide'>
-              <!-- templates -->
-              <input class='annotation_labels_template' type='checkbox' id='' value='' style='display: none;' />
-              <label class='annotation_labels_template' for='' style='display: none;'></label>
-            </div>
         </td>
 			</tr>
 		</table>
diff --git a/capsule-prototype/js/MosaicPanelWidget.js b/capsule-prototype/js/MosaicPanelWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..e117fe72cf393bc844233027b50f6d2eae972002
--- /dev/null
+++ b/capsule-prototype/js/MosaicPanelWidget.js
@@ -0,0 +1,264 @@
+((app) => {
+        function MosaicPanelWidget() {
+            const defaultOptions = {
+                getTagGradientColor: function (tag) {
+                    let isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;  // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
+                    let isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
+                    let isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;   // At least Safari 3+: "[object HTMLElementConstructor]"
+                    let isChrome = !!window.chrome && !isOpera;              // Chrome 1+
+                    // var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
+
+                    if (isOpera) {
+                        return "-o-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + tag.color + " 100%)";
+                    }
+                    if (isFirefox) {
+                        return "-moz-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + tag.color + " 100%)";
+                    }
+                    if ((isSafari) || (isChrome)) {
+                        return "-webkit-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + tag.color + " 100%)";
+                    }
+                },
+                getPreviewPath: function (tagOrDoc) {
+                    // eslint-disable-next-line no-undef
+                    return Utils.getPreviewPath(tagOrDoc);
+                },
+                htmlElement: function () {
+                    return $('#mosaic_tab')
+                },
+                isOpen: false,
+                pubsub: function () {
+                    return app.rekall.Rekall('pubSub');
+                },
+                rekall: function () {
+                    return app.rekall.Rekall('rekall');
+                },
+                filters_type_to_hide: [],
+                filter_label_to_show: [],
+                _followedItems: []
+            };
+
+            const localOptions = $.extend({}, defaultOptions);
+
+            // eslint-disable-next-line no-unused-vars
+            localOptions._resetFilters = function (event) {
+                $('.mosaic_filter_item')
+                    .removeClass('mosaic_filter_disabled');
+                $('.mosaic_label_filter_enabled').click();
+                localOptions.filters_type_to_hide = [];
+                localOptions.filter_label_to_show = [];
+                $('.mosaic_category').show();
+            }
+
+            localOptions._destroyFollowedElements = function () {
+                let elementsToDestroy = localOptions._followedItems;
+                localOptions._followedItems = [];
+                elementsToDestroy.forEach(e => e.remove());
+            }
+
+            function toggleFilterType(filterName) {
+                let filters = localOptions.filters_type_to_hide;
+                if (filters.includes(filterName)) {
+                    filters.splice(filters.indexOf(filterName), 1);
+                } else {
+                    filters.push(filterName);
+                }
+            }
+
+            function toggleLabelToShow(filterName) {
+                let filters = localOptions.filter_label_to_show;
+                if (filters.includes(filterName)) {
+                    filters.splice(filters.indexOf(filterName), 1);
+                } else {
+                    filters.push(filterName);
+                }
+            }
+
+            const getUrl = function (tagOrDoc, path) {
+                if ('undefined' === typeof path) {
+                    let [reg, type] = tagOrDoc.getMetadata("Rekall->Type").split('/');
+                    if (reg === 'rekall') {
+                        if (type === 'marker') type = 'note';
+                    }
+                    return tagOrDoc.getDownloadLink() || "../shared/css/images/img-" + type + ".png";
+                } else {
+                    return path;
+                }
+            }
+
+            const setDivVisibilityForTag = function (div, tag) {
+                if (!localOptions.filter_label_to_show.length) {
+                    div.show()
+                    return;
+                }
+
+                if (tag.getLabels().filter(value => localOptions.filter_label_to_show.includes(value.toLowerCase())).length) {
+                    div.show();
+                } else {
+                    div.hide()
+                }
+            }
+
+            const createHtmlElementForTagOrDoc = function (tagOrDoc) {
+                let path = localOptions.getPreviewPath(tagOrDoc);
+                let name = tagOrDoc.getMetadata("Rekall->Name");
+                let url = getUrl(tagOrDoc, path);
+
+                let div = $('<div/>').addClass('mosaic_item mosaic_button').on('click', function () {
+                    tagOrDoc.openPopupEdit();
+                });
+                let divImageContainer = $('<div>').addClass('mosaic_item_thumbnail_container');
+                divImageContainer
+                    .append($('<img src="'+url+'" alt="thumbnail image"/>')
+                        .addClass('mosaic_item_thumbnail_container_img')
+                        .attr('onerror', "this.src='../shared/css/images/img-document.png';"));
+                div.append(divImageContainer);
+                div.append($('<span/>')
+                    .addClass('caption')
+                    .text(name));
+
+                let divUpdater = {
+                    div: div,
+                    tag: tagOrDoc
+                };
+                divUpdater.update = function (tag) {
+                    setDivVisibilityForTag(div, tag);
+                }
+                divUpdater.remove = function () {
+                    tagOrDoc.removeObserver(divUpdater);
+                }
+                divUpdater.computeVisibility = function () {
+                    setDivVisibilityForTag(divUpdater.div, divUpdater.tag);
+                }
+                tagOrDoc.addObserver(divUpdater);
+                localOptions._followedItems.push(divUpdater);
+
+                setDivVisibilityForTag(div, tagOrDoc);
+
+                return div;
+            }
+
+            const getFilterElement = function (text, color, callback, css_class, isToggled) {
+                let result = $('<div/>')
+                    .addClass('mosaic_filter_item mosaic_button')
+                    .addClass(css_class)
+                    .on('click', callback)
+                    .css('background-color', color)
+                    .append($('<h2/>').text(text));
+
+
+                if (isToggled) {
+                    result.toggle();
+                    result.toggleClass('mosaic_filter_disabled');
+                }
+
+                return result;
+            }
+
+            const emptyPanel = function () {
+                localOptions.htmlElement().html('');
+                localOptions._destroyFollowedElements();
+            }
+
+            const fillPanel = function () {
+                let typefilterdiv = $('<div/>').addClass('mosaic_filter');
+                let labelsfilterdiv = $('<div/>').addClass('mosaic_filter');
+                typefilterdiv.append(getFilterElement(
+                    'Reset filters',
+                    'rgb(100,100,100)',
+                    localOptions._resetFilters,
+                    'mosaic_filter_item_all',
+                    false
+                ));
+                localOptions.htmlElement().append(typefilterdiv);
+                localOptions.htmlElement().append(labelsfilterdiv);
+                // eslint-disable-next-line no-undef
+                let labels = new Set();
+                for (let [k, v] of Object.entries(localOptions.rekall().sortings.colors.categories)) {
+                    let categoryName = localOptions.rekall().sortings.colors.getCategoryName(k)
+                    let category = $('<div/>').addClass('mosaic_category').css('background', localOptions.getTagGradientColor(v));
+
+                    let grid = $('<div/>').addClass('mosaic_category_grid');
+
+                    category.append($('<h2/>').text(categoryName));
+                    category.append(grid);
+
+                    typefilterdiv.append(getFilterElement(
+                        categoryName,
+                        v.color, function () {
+                            category.toggle();
+                            $(this).toggleClass('mosaic_filter_disabled');
+                            localOptions.toggle_filterType(categoryName)
+                        },
+                        '',
+                        localOptions.filters_type_to_hide.includes(categoryName)
+                    ));
+
+                    for (let i in v.tags) {
+                        grid.append(createHtmlElementForTagOrDoc(v.tags[i]));
+                        v.tags[i].getLabels().forEach(l => labels.add(l));
+                    }
+                    localOptions.htmlElement().append(category);
+                }
+
+                Array.from(labels)
+                    .sort(function (a, b) {
+                        return a.toLowerCase().localeCompare(b.toLowerCase());
+                    })
+                    .forEach(l => {
+                        let button = $('<div/>').html(l);
+                        button.addClass('mosaic_filter_item mosaic_button mosaic_label_filter');
+                        if (
+                            localOptions.filter_label_to_show.length
+                            && localOptions.filter_label_to_show.includes(l.toLowerCase())
+                        ) {
+                            button.toggleClass('mosaic_label_filter_enabled');
+                        }
+
+                        button.click(ev => {
+                            toggleLabelToShow(l.toLowerCase());
+                            $(ev.target).toggleClass('mosaic_label_filter_enabled');
+                            ev.target.my_state = !ev.target.my_state;
+                            console.debug('filters: ', localOptions.filter_label_to_show);
+                            localOptions._followedItems.forEach(value => value.computeVisibility())
+                        })
+                        labelsfilterdiv.append(button);
+                    });
+            }
+
+            localOptions.toggle_filterType = toggleFilterType;
+
+            return {
+                show: function () {
+                    if (localOptions.isOpen)
+                        return;
+
+                    localOptions.rekall().timeline.pause();
+                    fillPanel();
+                    localOptions.htmlElement().show();
+                    localOptions.updateObserver = localOptions.pubsub().subscribe('popupEdit.Closed', this.refresh);
+                    localOptions.isOpen = true;
+                },
+                hide: function () {
+                    if (!localOptions.isOpen)
+                        return;
+
+                    localOptions.pubsub().unsubscribe(localOptions.updateObserver);
+                    localOptions.updateObserver = undefined;
+                    localOptions.htmlElement().hide();
+                    emptyPanel();
+                    localOptions.isOpen = false;
+                    localOptions._destroyFollowedElements();
+                    localOptions.filters_type_to_hide = [];
+                },
+                refresh: function () {
+                    emptyPanel();
+                    fillPanel();
+                }
+            };
+
+        }
+
+        app.rekall.Rekall('mosaicWidget', MosaicPanelWidget());
+
+    }
+)(window.app = window.app || {})
diff --git a/capsule-prototype/js/PopupPanelWidget.js b/capsule-prototype/js/PopupPanelWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..680065eac293cac7677673b25ef19c5f8ef604dc
--- /dev/null
+++ b/capsule-prototype/js/PopupPanelWidget.js
@@ -0,0 +1,996 @@
+function convertToTime(seconds) {
+    let minutes = Math.floor(seconds / 60);
+    let remainingSeconds = Math.floor(seconds - (minutes * 60));
+    if (minutes < 10) minutes = "0" + minutes;
+    if (remainingSeconds < 10) remainingSeconds = "0" + remainingSeconds;
+    return minutes + ":" + remainingSeconds;
+}
+
+function PanelEdition(data) {
+    this.tag = data.tag;
+    this.readonlyDiv = undefined;
+    this.editDiv = undefined;
+    this.canEdit = data.canEdit;
+    this.popupPanelWidget = data.popupPanelWidget;
+}
+// eslint-disable-next-line no-unused-vars
+PanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+}
+PanelEdition.prototype.closeEdition = function () {
+    this.popupPanelWidget.setCurrentOpenedInput(undefined);
+    this.editDiv?.hide();
+    this.readonlyDiv?.show();
+}
+PanelEdition.prototype.openEdition = function () {
+    this.popupPanelWidget.ensureLastOpenedEditionClosed();
+    if (this.canEdit) {
+        this.popupPanelWidget.setCurrentOpenedInput(this);
+        this.readonlyDiv.hide();
+        if (this.editDiv) {
+            this.editDiv.show().focus();
+        }
+    }
+}
+
+function AnnotationNamePanelEdition(data) {
+    PanelEdition.call(this, data);
+}
+AnnotationNamePanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationNamePanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+    let that = this;
+    this.readonlyDiv = $('<div>');
+    this.readonlyDiv
+        .attr('id', 'popupNom')
+        .addClass('popupRightItem');
+
+    if (this.canEdit) {
+        this.readonlyDiv
+            .click(function (event) {
+                event.stopPropagation();
+                that.openEdition.call(that);
+            });
+    }
+
+    if (this.tag.getMetadata("Rekall->Name")?.length) {
+        this.readonlyDiv.html(this.tag.getMetadata("Rekall->Name"));
+        this.readonlyDiv.css('color', this.tag.color)
+    } else {
+        if (this.canEdit) {
+            this.readonlyDiv.html('+ Add a name').addClass('empty');
+        }
+    }
+
+    parentDiv.append(this.readonlyDiv);
+
+    if (this.canEdit) {
+        this.editDiv = $('<input>');
+        this.editDiv
+            .attr('id', 'popupNomInput')
+            .attr('type', 'text')
+            .attr('value', this.tag.getMetadata("Rekall->Name") ?? '')
+            .click(function (event) {
+                event.stopPropagation();
+            })
+            .keyup(function (event) {
+                event.stopPropagation();
+                if (13 === event.which) {
+                    that.closeEdition();
+                }
+            })
+            .hide();
+        this.editDiv.val(this.tag.getMetadata("Rekall->Name"));
+        parentDiv.append(this.editDiv);
+    }
+}
+AnnotationNamePanelEdition.prototype.closeEdition = function () {
+    if (this.canEdit) {
+        let newValue = this.editDiv.val();
+        this.editDiv.val(newValue);
+        if (this.tag.getMetadata('Rekall->Name') !== newValue) {
+            this.tag.setMetadata('Rekall->Name', newValue);
+            window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                metadata: "Rekall->Name",
+                tag: this.tag
+            });
+        }
+
+        if (newValue.length) {
+            $('#popupNom').html(newValue).removeClass("empty");
+        } else {
+            $('#popupNom').html('+ Add a name').addClass("empty");
+        }
+    }
+    PanelEdition.prototype.closeEdition.call(this);
+}
+
+function AnnotationAuthorPanelEdition(data) {
+    PanelEdition.call(this, data);
+}
+AnnotationAuthorPanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationAuthorPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+    let that = this;
+    this.readonlyDiv = $('<div>');
+    this.readonlyDiv.addClass('popupRightItem')
+        .attr('id', 'popupAuthor');
+
+    if (this.canEdit) {
+        this.readonlyDiv
+            .click(function (event) {
+                event.stopPropagation();
+                that.openEdition.call(that);
+            });
+    }
+
+    let popupAuthorName = this.tag.getMetadata("Rekall->Author");
+    if (popupAuthorName?.length) {
+        this.readonlyDiv.html(popupAuthorName);
+    } else {
+        if (this.canEdit) {
+            this.readonlyDiv.html('+ Add an author').addClass('empty');
+        }
+    }
+    parentDiv.append(this.readonlyDiv);
+
+
+    if (this.canEdit) {
+        this.editDiv = $('<input>');
+        this.editDiv
+            .attr('id', 'popupAuthorInput')
+            .attr('type', 'text')
+            .attr('value', popupAuthorName ?? '')
+            .click(function (event) {
+                event.stopPropagation();
+            })
+            .keyup(function (event) {
+                event.stopPropagation();
+                if (13 === event.which) {
+                    that.closeEdition();
+                }
+            })
+            .addClass('popupInput')
+            .hide();
+        parentDiv.append(this.editDiv);
+    }
+}
+AnnotationAuthorPanelEdition.prototype.closeEdition = function () {
+    if (this.canEdit) {
+        let newValue = this.editDiv.val();
+        this.editDiv.val(newValue);
+        if (this.tag.getMetadata('Rekall->Author') !== newValue) {
+            this.tag.setMetadata('Rekall->Author', newValue);
+            window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                metadata: "Rekall->Author",
+                tag: this.tag
+            });
+        }
+
+        if (newValue.length) {
+            this.readonlyDiv.html(newValue).removeClass("empty");
+        } else {
+            this.readonlyDiv.html('+ Add an author').addClass("empty");
+        }
+    }
+    PanelEdition.prototype.closeEdition.call(this);
+}
+
+function AnnotationTCPanelEdition(data) {
+    PanelEdition.call(this, data);
+}
+AnnotationTCPanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationTCPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+
+    // eslint-disable-next-line no-unused-vars
+    function onlyNumber(event) {
+        // remove characters that are not number
+        $(this).val($(this).val().replace(/\D/g, ''));
+    }
+    let that = this;
+
+    this.readonlyDiv = $('<div>');
+    this.readonlyDiv
+        .attr('id', 'popupTC')
+        .addClass('popupRightItem')
+        .click(function (event) {
+            event.stopPropagation();
+            that.openEdition.call(that);
+        })
+        .css("background", that.tag.color);
+
+    this.popupTcInDiv = $('<div>');
+    this.popupTcInDiv
+        .attr('id', 'popupTCin')
+        .addClass('popupTCdisplay')
+        .html(convertToTime(that.tag.getTimeStart()))
+    this.readonlyDiv.append(this.popupTcInDiv);
+
+    this.popupTcOutDiv = $('<div>');
+    this.popupTcOutDiv
+        .attr('id', 'popupTCout')
+        .addClass('popupTCdisplay')
+        .html(convertToTime(that.tag.getTimeEnd()))
+    this.readonlyDiv.append(this.popupTcOutDiv);
+    parentDiv.append(this.readonlyDiv);
+
+    if (this.canEdit) {
+        this.editDiv = $('<div>');
+        this.editDiv
+            .attr('id', 'popupTCedit')
+            .hide()
+        this.editDiv.click(function (event) {
+            event.stopPropagation();
+        })
+        this.editDiv.append($('<span>').addClass('popupTClabel').html('start'));
+        this.popupTCeditDivMinStart = $('<input class="popupTCeditfield" id="popupTCinMin" maxLength="2" type="text" value="">');
+        this.popupTCeditDivMinStart
+            .val(Math.floor(that.tag.getTimeStart()/60).toString(10).padStart(2, '0'))
+            .click(function (event) {
+                event.stopPropagation();
+            })
+            .on('input', onlyNumber);
+        this.editDiv.append(this.popupTCeditDivMinStart);
+        this.editDiv.append(':');
+        this.popupTCeditDivSecStart = $('<input class="popupTCeditfield" id="popupTCinSec" maxLength="2" type="text" value="">');
+        this.popupTCeditDivSecStart
+            .val((that.tag.getTimeStart()%60).toString(10).padStart(2, '0'))
+            .click(function (event) {
+                event.stopPropagation();
+            })
+            .on('input', onlyNumber);
+        this.editDiv.append(this.popupTCeditDivSecStart);
+        let popupTCeditStartDivNow = $('<div class="nowTCbtn" id="nowTCin">now</div>');
+        popupTCeditStartDivNow.click(function (event) {
+            event.stopPropagation();
+            let timeCurrent = convertToTime(Math.round(window.app.rekall.Rekall().timeline.timeCurrent));
+            that.popupTCeditDivMinStart.val(timeCurrent.split(":")[0]);
+            that.popupTCeditDivSecStart.val(timeCurrent.split(":")[1]);
+        });
+        this.editDiv.append(popupTCeditStartDivNow)
+        this.editDiv.append($('<br>'));
+
+        this.editDiv.append($('<span>').addClass('popupTClabel').html('end'));
+        this.popupTCeditDivMinEnd = $('<input>');
+        this.popupTCeditDivMinEnd
+            .attr('id', 'popupTCoutMin')
+            .attr('maxlength', '2')
+            .attr('type', 'text')
+            .addClass('popupTCeditfield')
+            .val(Math.floor(that.tag.getTimeEnd()/60).toString(10).padStart(2, '0'))
+            .on('input', onlyNumber);
+
+        this.popupTCeditDivMinEnd.click(function (event) {
+            event.stopPropagation();
+        });
+        this.editDiv.append(this.popupTCeditDivMinEnd);
+        this.editDiv.append(':');
+        this.popupTCeditDivSecEnd = $('<input>');
+        this.popupTCeditDivSecEnd
+            .attr('id', 'popupTCoutSec')
+            .attr('maxlength', '2')
+            .attr('type', 'text')
+            .addClass('popupTCeditfield')
+            .val(Math.floor(that.tag.getTimeEnd()%60).toString(10).padStart(2, '0'))
+            .click(function (event) {
+                event.stopPropagation();
+            })
+            .on('input', onlyNumber);
+        this.editDiv.append(this.popupTCeditDivSecEnd);
+        let popupTCeditEndDivNow = $('<div class="nowTCbtn" id="nowTCin">now</div>');
+        popupTCeditEndDivNow.click(function (event) {
+            event.stopPropagation();
+            let timeCurrent = convertToTime(Math.round(window.app.rekall.Rekall().timeline.timeCurrent));
+            that.popupTCeditDivMinEnd.val(timeCurrent.split(":")[0]);
+            that.popupTCeditDivSecEnd.val(timeCurrent.split(":")[1]);
+        });
+        this.editDiv.append(popupTCeditEndDivNow);
+        this.editDiv.append('/');
+        let popupTcEditEndDivEndVideo = $('<div class="nowTCbtn" id="eovTCout">end of video</div>');
+        popupTcEditEndDivEndVideo.click(function (event) {
+            event.stopPropagation();
+            let endVideo = window.app.rekall.Rekall().videoPlayer.duration();
+            let timeEnd = convertToTime(Math.round(endVideo));
+            that.popupTCeditDivMinEnd.val(timeEnd.split(":")[0]);
+            that.popupTCeditDivSecEnd.val(timeEnd.split(":")[1]);
+        });
+        this.editDiv.append(popupTcEditEndDivEndVideo);
+        parentDiv.append(this.editDiv);
+    }
+}
+AnnotationTCPanelEdition.prototype.closeEdition = function () {
+    if (this.canEdit) {
+        let isNaN = (maybeNaN) => maybeNaN !== maybeNaN;
+        let ensureNotNaN = (text, defaultValue = 0) => {
+            let tempVal = parseInt(text);
+            if (isNaN(tempVal)) {
+                return defaultValue;
+            }
+            return tempVal;
+        }
+        let tcInMin = ensureNotNaN(this.popupTCeditDivMinStart.val());
+        let tcInSec = ensureNotNaN(this.popupTCeditDivSecStart.val());
+        let tcOutMin = ensureNotNaN(this.popupTCeditDivMinEnd.val());
+        let tcOutSec = ensureNotNaN(this.popupTCeditDivSecEnd.val());
+        let tcIn = (60 * tcInMin) + tcInSec;
+        let tcOut = (60 * tcOutMin) + tcOutSec;
+
+        let endVideo = Math.ceil(window.app.rekall.Rekall().videoPlayer.duration());
+
+        if ((tcInMin >= 120) || (tcInSec >= 60) || (tcOutMin >= 120) || (tcOutSec >= 60) || (tcInMin < 0) || (tcInSec < 0) || (tcOutMin < 0) || (tcOutSec < 0)) {
+            window.app.rekall.Rekall('pubSub').publish('alert', {message: 'Invalid time code', buttons: "ok"});
+        } else if (tcIn > tcOut) {
+            window.app.rekall.Rekall('pubSub').publish('alert', {
+                message: "Start time must be set before end time",
+                buttons: "ok"
+            });
+        } else if (tcOut > endVideo) {
+            window.app.rekall.Rekall('pubSub').publish('alert', {
+                message: "End time must not be set after " + convertToTime(endVideo) + " (end of the video)",
+                buttons: "ok"
+            });
+        } else {
+            this.tag.setTimeStart(tcIn);
+            this.tag.setTimeEnd(tcOut);
+            this.popupTcInDiv.html(tcInMin.toString().padStart(2, '0') + ":" + tcInSec.toString().padStart(2, '0'));
+            this.popupTcOutDiv.html(tcOutMin.toString().padStart(2, '0') + ":" + tcOutSec.toString().padStart(2, '0'));
+            this.popupTCeditDivMinStart.val(tcInMin.toString().padStart(2, '0'));
+            this.popupTCeditDivSecStart.val(tcInSec.toString().padStart(2, '0'));
+            this.popupTCeditDivMinEnd.val(tcOutMin.toString().padStart(2, '0'));
+            this.popupTCeditDivSecEnd.val(tcOutSec.toString().padStart(2, '0'));
+            window.app.rekall.Rekall('pubSub').publish('tag.tc.updated', {tag: this.tag});
+        }
+    }
+    PanelEdition.prototype.closeEdition.call(this);
+}
+
+function AnnotationCommentPanelEdition(data) {
+    PanelEdition.call(this, data);
+}
+AnnotationCommentPanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationCommentPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+    let that = this;
+    this.readonlyDiv = $('<div>');
+    this.readonlyDiv.addClass('popupRightItem')
+        .attr('id', 'popupLegende')
+        .attr('title', 'Comment');
+
+    if (this.canEdit) {
+        this.readonlyDiv
+            .click(function (event) {
+                event.stopPropagation();
+                that.openEdition.call(that);
+            });
+    }
+
+    let comments = this.tag.getMetadata("Rekall->Comments");
+    if (comments && comments.length) {
+        this.readonlyDiv.html(comments).removeClass('empty');
+    } else {
+        if (this.canEdit) {
+            this.readonlyDiv.html('+ Add a comment').addClass('empty');
+        }
+    }
+    parentDiv.append(this.readonlyDiv);
+
+    if (this.canEdit) {
+        this.editDiv = $('<textarea>');
+        this.editDiv
+            .attr('id', 'popupLegendeInput')
+            .attr('type', 'text')
+            .addClass('popupInput')
+            .click(function (event) {
+                event.stopPropagation();
+            });
+        this.editDiv.val(comments ? comments : '');
+        parentDiv.append(this.editDiv);
+    }
+}
+AnnotationCommentPanelEdition.prototype.closeEdition = function () {
+    if (this.canEdit) {
+        let newValue = this.editDiv.val();
+        this.editDiv.val(newValue);
+        if (this.tag.getMetadata('Rekall->Comments') !== newValue) {
+            newValue = newValue.replace(/\n/gi, "<br/>");
+            this.tag.setMetadata('Rekall->Comments', newValue);
+            window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                metadata: "Rekall->Comments",
+                tag: this.tag
+            });
+        }
+
+        if (newValue.length) {
+            this.readonlyDiv.html(newValue).removeClass("empty");
+        } else {
+            this.readonlyDiv.html('+ Add an author').addClass("empty");
+        }
+    }
+    PanelEdition.prototype.closeEdition.call(this);
+}
+
+function AnnotationLinkPanelEdition(data) {
+    PanelEdition.call(this, data);
+}
+AnnotationLinkPanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationLinkPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+    let that = this;
+    this.readonlyDiv = $('<div>');
+    this.readonlyDiv.addClass('popupRightItem')
+        .attr('id', 'popupLink')
+        .attr('title', 'Link');
+
+    if (this.canEdit) {
+        this.readonlyDiv
+            .click(function (event) {
+                event.stopPropagation();
+                that.openEdition.call(that);
+            });
+    }
+
+    let linkValue = this.tag.getMetadata("Rekall->Link");
+    if (linkValue && linkValue.length) {
+        if (this.canEdit) {
+            this.readonlyDiv.html(linkValue).removeClass('empty');
+        } else {
+            this.readonlyDiv.html("<a href='" + linkValue + "' target='_blank'>" + linkValue + "</a>").removeClass('empty');
+        }
+    } else {
+        if (this.canEdit) {
+            this.readonlyDiv.html("+ Add a link").addClass("empty");
+        }
+    }
+    parentDiv.append(this.readonlyDiv);
+
+    if (this.canEdit) {
+        this.editDiv = $('<textarea>');
+        this.editDiv
+            .attr('id', 'popupLinkInput')
+            .attr('type', 'text')
+            .addClass('popupInput')
+            .click(function (event) {
+                event.stopPropagation();
+            })
+            .keyup(function (event) {
+                event.stopPropagation();
+                if (13 === event.which) {
+                    that.closeEdition();
+                }
+            })
+            .val(linkValue)
+            .hide();
+
+        parentDiv.append(this.editDiv);
+    }
+}
+AnnotationLinkPanelEdition.prototype.closeEdition = function () {
+    if (this.canEdit) {
+        let newValue = this.editDiv.val();
+        newValue = newValue.trim();
+        if (newValue && newValue.length && 0 !== newValue.indexOf("http")) {
+            newValue = "http://" + newValue;
+        }
+        this.editDiv.val(newValue);
+
+        if (this.tag.getMetadata('Rekall->Link') !== newValue) {
+            this.tag.setMetadata('Rekall->Link', newValue);
+            window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                metadata: "Rekall->Link",
+                tag: this.tag
+            });
+        }
+
+        if (newValue.length) {
+            this.readonlyDiv.html(newValue).removeClass("empty");
+        } else {
+            this.readonlyDiv.html('+ Add a Link').addClass("empty");
+        }
+    }
+    PanelEdition.prototype.closeEdition.call(this);
+}
+
+function AnnotationSpeedPanelEdition(data) {
+    PanelEdition.call(this, data);
+}
+AnnotationSpeedPanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationSpeedPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+    let that = this;
+    this.readonlyDiv = $('<form>');
+    this.readonlyDiv.addClass('popupRightItem')
+        .attr('id', 'popupSpeedInput')
+        .addClass('flex-row playback_form popupInput popupInputNoHide');
+
+    let input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_025')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(0.25);
+    let inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_025')
+        .html('0.25')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_05')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .prop('disabled', this.canEdit ? null : 'disabled')
+        .val(0.5);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_05')
+        .html('0.5')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_075')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(0.75);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_075')
+        .html('0.75')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_1')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(1);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_1')
+        .html('normal')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_125')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(1.25);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_125')
+        .html('1.25')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_15')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(1.5);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_15')
+        .html('1.5')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_175')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(1.75);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_175')
+        .html('1.75')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    input = $('<input>');
+    input
+        .attr('id', 'annotation_playback_speed_2')
+        .attr('type', 'radio')
+        .attr('name', 'playback_rate')
+        .attr('disabled', this.canEdit ? null : 'disabled')
+        .val(2);
+    inputLabel = $('<label>');
+    inputLabel
+        .attr('for', 'annotation_playback_speed_2')
+        .html('2')
+        .addClass('left_menu_item');
+    this.readonlyDiv.append(input);
+    this.readonlyDiv.append(inputLabel);
+
+    let speed = this.tag.getMetadata("Rekall->Speed");
+    if (!speed || !speed.trim().length)
+        speed = 1;
+    this.readonlyDiv.children('input[value="' + speed + '"]').attr('checked', true);
+    this.readonlyDiv.children('input[name=playback_rate]').change(function (event) {
+        event.stopPropagation();
+        that.closeEdition.call(that);
+    })
+
+    parentDiv.append(this.readonlyDiv);
+}
+AnnotationSpeedPanelEdition.prototype.closeEdition = function () {
+    if (this.canEdit) {
+        let newValue = this.readonlyDiv.children('input:checked').val();
+        if (this.tag.getMetadata('Rekall->Speed') !== newValue) {
+            this.tag.setMetadata('Rekall->Speed', newValue);
+            window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                metadata: "Rekall->Speed",
+                tag: this.tag
+            });
+        }
+    }
+}
+AnnotationSpeedPanelEdition.prototype.openEdition = function () {
+    this.popupPanelWidget.ensureLastOpenedEditionClosed();
+    if (this.canEdit) {
+        this.popupPanelWidget.setCurrentOpenedInput(this);
+    }
+}
+
+function AnnotationTagPanelEdition(data) {
+    PanelEdition.call(this, data);
+    this.annotationInputTemplate = $('<input class="" type="checkbox" id="" value="">')
+    this.annotationLabelTemplate = $('<label class="" for=""></label>')
+}
+AnnotationTagPanelEdition.prototype = Object.create(PanelEdition.prototype);
+AnnotationTagPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
+    let that = this;
+
+    function getProjectLabels(tag) {
+        return tag.document.getLabels();
+    }
+
+    function appendTagInputWithLabel(panel, currentLabel, panelContainsLabel) {
+        let input = panel.annotationInputTemplate.clone();
+        let id = 'annotation_label_' + panel.tag.document.project.labels.indexOf(currentLabel);
+        input
+            .attr('id', id)
+            .attr('value', currentLabel)
+            .addClass('annotation_labels')
+            .prop("checked", panelContainsLabel)
+            .hide();
+
+        if(panel.canEdit){
+            input
+            .change(function (event) {
+                event.stopPropagation();
+                that.closeEdition.call(that);
+                that.openEdition.call(that);
+            })
+        } else {
+            input
+                .change(function (event) {
+                    event.stopPropagation();
+                })
+                .attr('disabled', 'disabled');
+        }
+
+        let html_label = panel.annotationLabelTemplate.clone();
+        html_label
+            .attr('for', id)
+            .html(currentLabel)
+            .addClass('annotation_labels')
+            .css('color', panel.tag.color)
+            .css('border', '1px solid ' +panel.tag.color);
+
+        panel.readonlyDiv.append(input);
+        panel.readonlyDiv.append(html_label);
+    }
+
+    this.tagPanelLabel = $('<div class="popupNewTitles">+ Tags</div>');
+    parentDiv.append(this.tagPanelLabel);
+
+    if(that.canEdit){
+        this.tagCreationDiv = $('<input type="text" id="new_annotation_label" placeholder="Add a new tag" pattern="[a-zA-Zéàè0-9 ]+" title="Only alphanum characters" />');
+        this.tagCreationDiv.keypress(function (event) {
+            that.openEdition.call(that);
+            let keycode = (event.keyCode ? event.keyCode : event.which);
+            if (13 !== keycode) {
+                return;
+            }
+
+            if ($('#new_annotation_label')[0].validity.patternMismatch) {
+              that.tagCreationDiv.css('border', '2px solid red');
+              return;
+            }
+
+            that.tagCreationDiv.css('border', 'initial');
+            let labelToAdd = $(this).val();
+            let labels = getProjectLabels(that.tag);
+            if (labels.indexOf(labelToAdd) === -1) {
+                that.tag.document.project.labels.push(labelToAdd);
+                appendTagInputWithLabel(that, labelToAdd);
+            }
+            that.readonlyDiv.children("input[value='" + labelToAdd + '\']').attr('checked', 'checked');
+            $(this).val('');
+            that.closeEdition.call(that);
+        });
+        parentDiv.append(this.tagCreationDiv);
+    }
+
+    this.readonlyDiv = $('<div>');
+    this.readonlyDiv
+        .addClass('flex-col popupInput popupInputNoHide')
+        .attr('id', 'popupLabelsInput');
+
+    let _labels = getProjectLabels(this.tag);
+    let tagLabels = that.tag.getLabels();
+    for (let currentLabel of _labels) {
+        if (!this.canEdit && !tagLabels.includes(currentLabel))
+            continue;
+        appendTagInputWithLabel(this, currentLabel, tagLabels.includes(currentLabel));
+    }
+    parentDiv.append(this.readonlyDiv);
+}
+AnnotationTagPanelEdition.prototype.save = function () {
+    if (this.canEdit) {
+        let newValue = '';
+        this.readonlyDiv
+            .children('input:checked')
+            .each(function () {
+                newValue += $(this).val() + ';'
+            });
+        if (newValue.length) {
+            newValue = newValue.slice(0, newValue.length - 1);
+        }
+
+        if (this.tag.getMetadata('Rekall->Labels') !== newValue) {
+            this.tag.setMetadata('Rekall->Labels', newValue);
+            window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                metadata: "Rekall->Labels",
+                tag: this.tag
+            });
+        }
+    }
+}
+AnnotationTagPanelEdition.prototype.closeEdition = function () {
+    if (this !== this.popupPanelWidget.getCurrentOpenedInput())
+        this.popupPanelWidget.ensureLastOpenedEditionClosed();
+    this.save();
+}
+AnnotationTagPanelEdition.prototype.openEdition = function () {
+    if (this !== this.popupPanelWidget.getCurrentOpenedInput()) {
+        this.popupPanelWidget.ensureLastOpenedEditionClosed();
+        if (this.canEdit) {
+            this.popupPanelWidget.setCurrentOpenedInput(this);
+        }
+    }
+}
+
+
+function PopupPanelWidget(data) {
+    this.canEdit = data?.canEdit ?? function () {
+        return false;
+    };
+    this.currentOpenedInput = undefined;
+    this.videoWasPlayingOnContentCreation = false;
+}
+PopupPanelWidget.prototype.getCurrentOpenedInput = function () {
+    return this.currentOpenedInput;
+}
+PopupPanelWidget.prototype.setCurrentOpenedInput = function (input) {
+    this.currentOpenedInput = input;
+}
+PopupPanelWidget.prototype.ensureLastOpenedEditionClosed = function () {
+    if (this.currentOpenedInput) {
+        this.currentOpenedInput.closeEdition();
+        this.currentOpenedInput = undefined;
+    }
+}
+PopupPanelWidget.prototype.createPopupContent = function (tag) {
+
+    let that = this;
+    this.videoWasPlayingOnContentCreation = !window.app.rekall.Rekall().timeline.isPaused();
+    if (this.videoWasPlayingOnContentCreation) {
+        window.app.rekall.Rekall().timeline.pause();
+    }
+
+    function createLeftPopup(popupPanelWidget) {
+        let that = this;
+        let popupLeftDiv = $('#popupLeft');
+        let bgColorLeft = this.getTagGradientColor();
+        popupLeftDiv
+            .empty()
+            .css("background", bgColorLeft)
+            .attr('keydoc', this.document.key)
+
+        let highlightDiv = $('<div>')
+            .attr('id', 'popupSetHighlight')
+            .text("★ Highlight")
+            .attr("ishighlight", this.getMetadata("Rekall->Highlight")?.length ? "true" : "false")
+            .click(function (event) {
+                event.stopPropagation();
+                let isHL = !!that.getMetadata("Rekall->Highlight");
+
+                if (isHL) {
+                    that.setMetadata("Rekall->Highlight", "");
+                    $(this).attr("isHighlight", "false").removeClass("selected");
+                    $("#popupEdit").removeClass("highlightPopup");
+                } else {
+                    that.setMetadata("Rekall->Highlight", "true");
+                    $(this).attr("isHighlight", "true").addClass("selected");
+                    $("#popupEdit").addClass("highlightPopup");
+                }
+
+                window.app.rekall.Rekall('pubSub').publish('tag.metadata.updated', {
+                    metadata: "Rekall->Highlight",
+                    tag: that
+                });
+            });
+
+        if (this.getMetadata("Rekall->Highlight")?.length) {
+            highlightDiv
+                .addClass('selected');
+        }
+
+        popupLeftDiv.append(highlightDiv);
+
+        let imageBoxDiv = $('<div>');
+        imageBoxDiv.addClass('popupLeftItem')
+            .attr('id', 'popupImgBox');
+
+        let image = $('<img src="" alt="">');
+        if (this.isMarker()) {
+            image.attr('src', "../shared/css/images/img-note.png");
+        } else {
+            if (this.thumbnail.url) {
+                image.attr("src", this.getDownloadLink() || this.thumbnail.url);
+            } else {
+                let type = this.getMetadata("Rekall->Type");
+                if (type.indexOf("image") > -1) image.attr("src", "../shared/css/images/img-image.png")
+                else if (type.indexOf("pdf") > -1) image.attr("src", "../shared/css/images/img-pdf.png")
+                else if (type.indexOf("audio") > -1) image.attr("src", "../shared/css/images/img-music.png")
+                else if (type.indexOf("vcard") > -1) image.attr("src", "../shared/css/images/img-user.png")
+                else if (type.indexOf("video") > -1) image.attr("src", "../shared/css/images/img-video.png")
+                else if (type.indexOf("msword") > -1) image.attr("src", "../shared/css/images/img-word.png")
+                else if (type.indexOf("link") > -1) image.attr("src", "../shared/css/images/img-link.png")
+                else $("#popupImg").attr("src", "../shared/css/images/img-document.png");
+            }
+
+            image
+                .attr('id', 'popupImg')
+                .click(function (event) {
+                    event.stopPropagation();
+                    if (that.isLink()) {
+                        if (that.getMetadata("Rekall->Link")) window.open(that.getMetadata("Rekall->Link"), '_blank')
+                    } else {
+                        window.open(image.attr('src'), '_blank');
+                    }
+                });
+        }
+
+        imageBoxDiv.append(image);
+
+        let popupTypeDiv = $('<div>');
+        popupTypeDiv
+            .attr('id', 'popupType')
+            .addClass('popupLeftItem')
+            .html(this.getMetadata("Rekall->Type"))
+            .css('color', this.color);
+        imageBoxDiv.append(popupTypeDiv);
+
+        if (this.getMetadata("Rekall->Type")?.length
+            && this.getMetadata("Rekall->Type").split('/')[0] === 'image') {
+            if (popupPanelWidget.canEdit()) {
+                let editAnnotationPic = $('<div>');
+                editAnnotationPic
+                    .attr('id', 'editAnnotationPic')
+                    .html('edit')
+                    .addClass('popupLeftItem');
+                editAnnotationPic.click(function () {
+                    window.app.rekall.Rekall('pubSub').publish('image.annotation.edit.open', that);
+                });
+                imageBoxDiv.append(editAnnotationPic);
+            }
+
+            let displayOriginalImage = $('<div>');
+            displayOriginalImage
+                .attr('id', 'linkToOriginalImage')
+                .addClass('popupLeftItem')
+                .html('Open original image')
+                .click(function () {
+                    window.open(that.getDownloadLink(true), '_blank');
+                });
+            imageBoxDiv.append(displayOriginalImage);
+        }
+        popupLeftDiv.append(imageBoxDiv);
+
+        if (popupPanelWidget.canEdit()) {
+            let deleteElement = $('<div>');
+            if (this.isMarker()) {
+                deleteElement.html("Delete Note");
+            } else {
+                deleteElement.html("Delete File");
+            }
+            deleteElement
+                .click(function () {
+                    window.app.rekall.Rekall('pubSub').publish('', {
+                        message: "Do you really want to delete this file from the project ?",
+                        buttons: "yesnodelete"
+                    });
+                })
+                .addClass('popupLeftItem')
+                .attr('id', 'popupEditSupprimer');
+            popupLeftDiv.append(deleteElement);
+        }
+    }
+
+    function createRightPopup(popupPanelWidget) {
+        let popupRightTd = $('#popupRight');
+        popupRightTd.attr('keyDoc', tag.document.key)
+        popupRightTd.empty();
+
+        let commonDataForPanels = {
+            canEdit: popupPanelWidget.canEdit(),
+            tag: this,
+            popupPanelWidget: popupPanelWidget
+        };
+        let closePopupDiv = $('<div id="closePopupEdit">✕</div>');
+        closePopupDiv.click(function(){
+            popupPanelWidget.close();
+        })
+        popupRightTd.append(closePopupDiv);
+
+        let projectNameInput = new AnnotationNamePanelEdition(commonDataForPanels)
+        projectNameInput.appendInputEditorDiv(popupRightTd);
+
+        let projectAuthorInput = new AnnotationAuthorPanelEdition(commonDataForPanels);
+        projectAuthorInput.appendInputEditorDiv(popupRightTd);
+
+        let tcInput = new AnnotationTCPanelEdition(commonDataForPanels);
+        tcInput.appendInputEditorDiv(popupRightTd);
+
+        let commentPanel = new AnnotationCommentPanelEdition(commonDataForPanels);
+        commentPanel.appendInputEditorDiv(popupRightTd);
+
+        let linkPanel = new AnnotationLinkPanelEdition(commonDataForPanels);
+        linkPanel.appendInputEditorDiv(popupRightTd);
+
+        let speedPanel = new AnnotationSpeedPanelEdition(commonDataForPanels);
+        speedPanel.appendInputEditorDiv(popupRightTd);
+
+        let tagPanel = new AnnotationTagPanelEdition(commonDataForPanels);
+        tagPanel.appendInputEditorDiv(popupRightTd);
+    }
+
+    createRightPopup.call(tag, this);
+    createLeftPopup.call(tag, this);
+    $('#popupEdit')
+        .unbind('click')
+        .click(function (event) {
+        event.stopPropagation();
+        that.ensureLastOpenedEditionClosed();
+    });
+}
+PopupPanelWidget.prototype.show = function() {
+    $("#popupSpace").show();
+    $("#popupEdit").show();
+}
+PopupPanelWidget.prototype.close = function() {
+    this.ensureLastOpenedEditionClosed();
+    $("#popupSpace").hide();
+    $("#popupEdit").hide();
+    if(this.videoWasPlayingOnContentCreation) {
+        window.app.rekall.Rekall().timeline.play();
+    }
+    window.app.rekall.Rekall('pubSub').publish('popupEdit.Closed', {});
+}
diff --git a/capsule-prototype/js/online-rekall/NotificationEntityChanged.js b/capsule-prototype/js/online-rekall/NotificationEntityChanged.js
new file mode 100644
index 0000000000000000000000000000000000000000..3585de3ab74febd82507b070caab408bd274c6a5
--- /dev/null
+++ b/capsule-prototype/js/online-rekall/NotificationEntityChanged.js
@@ -0,0 +1,27 @@
+function NotificationEntityChanged(data) {
+    this.__observers = [];
+}
+
+NotificationEntityChanged.prototype.notifyChange = function() {
+    let observers = this.__observers;
+    observers.forEach(o => { if (o.update && typeof o.update === 'function') { o.update(this) } });
+};
+
+NotificationEntityChanged.prototype.addObserver = function (observer) {
+    if (!this.__observers.includes(observer)) {
+        this.__observers.push(observer)
+    }
+};
+
+NotificationEntityChanged.prototype.removeObserver = function (observer) {
+    if (!this.__observers.includes(observer)) {
+        this.__observers.slice(this.__observers.indexOf(observer), 1);
+    }
+};
+
+
+NotificationEntityChanged.prototype.getObserver = function (index) {
+    if (index > -1 && index < this.__observers.length) {
+        return this.__observers[index];
+    }
+};
\ No newline at end of file
diff --git a/capsule-prototype/js/online-rekall/Project.js b/capsule-prototype/js/online-rekall/Project.js
index c741f51b66d8213e29f117d76d8490f875c034db..1dca2682fe864b71c7fbae8de227d94124b02ab3 100644
--- a/capsule-prototype/js/online-rekall/Project.js
+++ b/capsule-prototype/js/online-rekall/Project.js
@@ -22,503 +22,270 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-function Project(url) {
-	this.sources = new Object();
-	this.url = url;
-	this.firstAnalysis = true;
-	this.metadata = {      
-		"Title":    "",
-		"Author":   "",
-		"Email":    "",
-		"Date": 	"",
-		"Comments": "",
-	};
-	var urlItems = rekall.baseUrl.split("/");
-	urlItems = $.grep(urlItems,function(n){ return(n) });
-	this.metadata["Title"] = urlItems[urlItems.length-1];
-
+function Project(url, rekall) {
+    this.sources = {};
+    this.url = url;
+    this.firstAnalysis = true;
+    this.metadata = {
+        "Title": "",
+        "Author": "",
+        "Email": "",
+        "Date": "",
+        "Comments": "",
+    };
+    var urlItems = rekall.baseUrl.split("/");
+    urlItems = $.grep(urlItems, function (n) {
+        return (n)
+    });
+    this.metadata["Title"] = urlItems[urlItems.length - 1];
+    this.rekall = rekall;
+    this.labels = [];
 }
 
-Project.prototype.addDocument = function(key, document) {
-	if(this.sources[key] == undefined)
-		this.sources[key] = new Source();
-	this.sources[key].addDocument(document);
-}
-Project.prototype.getDocument = function(path) {
-	var retour = undefined;
-	for (var key in this.sources) {
-		if(retour == undefined)
-			retour = this.sources[key].getDocument(path);
-	}
-	return retour;
+Project.prototype.addDocument = function (key, document) {
+    if (this.sources[key] == undefined)
+        this.sources[key] = new Source();
+    this.sources[key].addDocument(document);
 }
 
-Project.prototype.loadXML = function(xml) {
-	this.sources["Files"] = new Source();
+Project.prototype.getDocument = function (path) {
+    var retour = undefined;
+    for (var key in this.sources) {
+        if (retour == undefined)
+            retour = this.sources[key].getDocument(path);
+    }
+    return retour;
+}
 
-	var thiss = this;
-	var counts = {documents: 0, tags: 0, metadatas: 0};
-	xml.find('document').each(function() {
-		if(($(this).attr("remove") != undefined) && ($(this).attr("key") != undefined) && ($(this).attr("remove") == "true")) {
-			var rekallDoc = thiss.sources["Files"].documents[$(this).attr("key")];
-			if(rekallDoc != undefined) {
-				for (var tagIndex in rekallDoc.tags)
-					rekallDoc.tags[tagIndex].visuel.rect.remove();
-				delete rekall.project.sources["Files"].documents[rekallDoc.key];
-			}
-		}
-		else {
-			var rekallDoc = new Document();
-			counts.documents++;
-			counts.tags++;
-			$(this).find('meta').each(function() {
-				var rekallDocMeta = new Metadata();
-				rekallDocMeta.content 	  = $(this).attr('cnt');
-				rekallDocMeta.metadataKey = $(this).attr('ctg');
-				rekallDoc.setMetadata(rekallDocMeta);
-				counts.metadatas++;
-			});
-			if($(this).attr("key") != undefined)
-				rekallDoc.key = $(this).attr("key");
-			thiss.addDocument("Files", rekallDoc);
-		}
-	});
-	xml.find('edition').each(function() {
-		var key = $(this).attr('key');
-		if(thiss.sources["Files"].documents[key] != undefined) {
-			var version       = $(this).attr('version');
-			var metadataKey   = $(this).attr('metadataKey');
-			var metadataValue = $(this).attr('metadataValue');
-			thiss.sources["Files"].documents[key].setMetadata(metadataKey, metadataValue, version);
-		}
-	});
-	xml.find('tag').each(function() {
-		var key = $(this).attr('key');
-		if(thiss.sources["Files"].documents[key] != undefined) {
-			var version   = $(this).attr('version');
-			var timeStart = parseFloat($(this).attr('timeStart')) + 0.;
-			var timeEnd   = parseFloat($(this).attr('timeEnd'))   + 0.;
-			for (var index in thiss.sources["Files"].documents[key].tags) {
-				thiss.sources["Files"].documents[key].tags[index].setTimeStart(timeStart);
-				thiss.sources["Files"].documents[key].tags[index].setTimeEnd(timeEnd);
-			}
-		}
-	});
-	xml.find('projectMeta').each(function() {
-		thiss.metadata[$(this).attr('ctg')] = $(this).attr('cnt');
-	});
-	var videoUrl = undefined, videoTech = undefined;
-	xml.find('video').each(function() {
-		videoUrl  = $(this).attr('url');
-		videoTech = $(this).attr('tech');
-	});
-	
-	if((videoUrl != "") && (videoUrl != undefined)) {
-		var techOrder = ["vimeo", "youtube", "html5"];
-		if(videoUrl.indexOf("youtube") >= 0)		videoTech = "youtube";
-		if(videoUrl.indexOf("youtu.be") >= 0)		videoTech = "youtube";
-		if(videoUrl.indexOf("vimeo") >= 0)			videoTech = "vimeo";
-		if(videoUrl.indexOf("dailymotion") >= 0)	videoTech = "dailymotion";
-		if(videoUrl.indexOf("dai.ly") >= 0)			videoTech = "dailymotion";
-		if((videoTech != "") && (videoTech != undefined))
-			techOrder = [videoTech, "html5"];
+Project.prototype.loadXML = function (xml) {
+    this.sources["Files"] = new Source();
 
-		//Video
-		if(rekall.videoPlayer == undefined) {
-			videojs("video", {
-				"techOrder": techOrder,
-				"controls": true,
-				"autoplay": false,
-				"loop": 	"false",
-				"preload": 	"auto",
-				"sources": [
-					{
-						"type": "video/" + videoTech,
-						"src": videoUrl
-					}
-				]
-			}, function() {
-				rekall.videoPlayer = this;
-				$(".vjs-fullscreen-control").hide();
-				
-				rekall.videoPlayer.on("durationchange", function(e) {
-					rekall.videoPlayer.markers.removeAll == undefined;
+    let thiss = this;
+    let counts = {documents: 0, tags: 0, metadatas: 0};
+    xml.find('document').each(function () {
+        if (($(this).attr("remove") != undefined) && ($(this).attr("key") != undefined) && ($(this).attr("remove") == "true")) {
+            var rekallDoc = thiss.sources["Files"].documents[$(this).attr("key")];
+            if (rekallDoc != undefined) {
+                for (var tagIndex in rekallDoc.tags)
+                    rekallDoc.tags[tagIndex].visuel.rect.remove();
+                delete this.rekall.project.sources["Files"].documents[rekallDoc.key];
+            }
+        } else {
+            let rekallDoc = new Document({project: thiss});
+            counts.documents++;
+            counts.tags++;
+            $(this).find('meta').each(function () {
+                let rekallDocMeta = new Metadata();
+                rekallDocMeta.content = $(this).attr('cnt');
+                rekallDocMeta.metadataKey = $(this).attr('ctg');
+                rekallDoc.setMetadata(rekallDocMeta);
+                counts.metadatas++;
+            });
+            if ($(this).attr("key") != undefined)
+                rekallDoc.key = $(this).attr("key");
+            thiss.addDocument("Files", rekallDoc);
+        }
+    });
+    xml.find('edition').each(function () {
+        let key = $(this).attr('key');
+        if (thiss.sources["Files"].documents[key] != undefined) {
+            var version = $(this).attr('version');
+            var metadataKey = $(this).attr('metadataKey');
+            var metadataValue = $(this).attr('metadataValue');
+            thiss.sources["Files"].documents[key].setMetadata(metadataKey, metadataValue, version);
+        }
+    });
+    xml.find('tag').each(function () {
+        let key = $(this).attr('key');
+        if (thiss.sources["Files"].documents[key] != undefined) {
+            let version = $(this).attr('version');
+            let timeStart = parseFloat($(this).attr('timeStart')) + 0.;
+            let timeEnd = parseFloat($(this).attr('timeEnd')) + 0.;
+            for (var index in thiss.sources["Files"].documents[key].tags) {
+                thiss.sources["Files"].documents[key].tags[index].setTimeStart(timeStart);
+                thiss.sources["Files"].documents[key].tags[index].setTimeEnd(timeEnd);
+            }
+        }
+    });
+    xml.find('projectMeta').each(function () {
+        thiss.metadata[$(this).attr('ctg')] = $(this).attr('cnt');
+    });
+    let videoUrl = undefined, videoTech = undefined;
+    xml.find('video').each(function () {
+        videoUrl = $(this).attr('url');
+        videoTech = $(this).attr('tech');
+    });
 
-					if(counts.documents>0){
-						rekall.project.analyse();
-						rekall.videoPlayer.markers.initialize();
-					}
-				});
-				rekall.videoPlayer.on("ended", function(e) {
-				});
-				rekall.videoPlayer.on("error", function(e) {
-				});
-				rekall.videoPlayer.on("firstplay", function(e) {
-				});
-				rekall.videoPlayer.on("fullscreenchange", function(e) {
-				});
-				rekall.videoPlayer.on("loadedalldata", function(e) {
-				});
-				rekall.videoPlayer.on("loadeddata", function(e) {
-				});
-				rekall.videoPlayer.on("loadedmetadata", function(e) {
-					console.log(counts.documents + " documents analysés, " + counts.metadatas + " métadonnées extraites et " + counts.tags + " tags affichés !");
-					if(counts.documents>0){ 
-						rekall.project.analyse();
-					}
-				});
-				rekall.videoPlayer.on("loadstart", function(e) {
-				});
-				rekall.videoPlayer.on("pause", function(e) {
-				});
-				rekall.videoPlayer.on("play", function(e) {
-				});
-				rekall.videoPlayer.on("progress", function(e) {
-				});
-				rekall.videoPlayer.on("seeked", function(e) {
-				});
-				rekall.videoPlayer.on("seeking", function(e) {
-				});
-				rekall.videoPlayer.on("timeupdate", function(e) {
-					rekall.timeline.update(rekall.videoPlayer.currentTime());
-				});
-				rekall.videoPlayer.on("volumechange", function(e) {
-				});
-				rekall.videoPlayer.on("waiting", function(e) {
-				});
-				rekall.videoPlayer.on("resize", function(e) {
-				});
-				$(window).trigger("resize");
-	
-			});
-		}
-		else {
-			console.log(counts.documents + " documents analysés, " + counts.metadatas + " métadonnées extraites et " + counts.tags + " tags affichés !");
-			rekall.project.analyse();
-		}
-	}
-}
+    if (this.rekall.ensureVideoPlayerCreated(videoUrl, videoTech)) {
+        $(window).trigger("resize");
+    }
 
-Project.prototype.timelineUpdate = function() {
-	
+    this.analyse();
 }
 
-Project.prototype.analyse = function() {
-	$('#flattentimeline').html("<div id='flattentimeline_highlight'></div>");
-
-  var parsed_labels = $('input.annotation_labels').toArray().map(i => i.value);
+Project.prototype.timelineUpdate = function () {
 
-	//Analyse
-	Tags.flattenTimelineTags = [];
-	var filtredTags = new Array();
-	rekall.sortings["horizontal"].analyseStart();
-	rekall.sortings["colors"]    .analyseStart();
-	for (var keySource in this.sources) {      
-		for (var keyDocument in this.sources[keySource].documents) {
-			for (var key in this.sources[keySource].documents[keyDocument].tags) {
-				var tag = this.sources[keySource].documents[keyDocument].tags[key];
-				rekall.sortings["horizontal"].analyseAdd(tag);
-				rekall.sortings["colors"]    .analyseAdd(tag);
-				Tags.flattenTimelineTags.push(tag);
-        var labels = tag.getMetadata('Rekall->Labels');
-        if (labels && labels != '') {
-          var input_template = $('input.annotation_labels_template').first();
-          var html_label_template = $('label.annotation_labels_template').first();
-          var _labels = labels.split(';');
-          for (var i in _labels) {
-            var label = _labels[i];
-            if (label == '') continue;
-            if (parsed_labels.indexOf(label) != -1)
-              continue;
-            parsed_labels.push(label);
-            var inp = input_template.clone();
-            var id = 'annotation_label_' + parsed_labels.indexOf(label);
-            inp.attr('id', id);
-            inp.attr('value', label);
+}
 
-            var html_label = html_label_template.clone();
-            html_label.attr('for', id);
-            html_label.html(label);
+Project.prototype.analyse = function () {
+    $('#flattentimeline').html("<div id='flattentimeline_highlight'></div>");
 
-            html_label.show();
-            html_label.addClass('annotation_labels').removeClass('annotation_labels_template');
-            inp.addClass('annotation_labels').removeClass('annotation_labels_template');
+    // let parsed_labels = $('input.annotation_labels').toArray().map(i => i.value);
+    let parsed_labels = this.labels;
 
-            $('#popupLabelsInput').append(inp);
-            $('#popupLabelsInput').append(html_label);
-          }
+    //Analyse
+    Tags.reset();
+    this.rekall.sortings["horizontal"].analyseStart();
+    this.rekall.sortings["colors"].analyseStart();
+    for (let keySource in this.sources) {
+        for (let keyDocument in this.sources[keySource].documents) {
+            for (let key in this.sources[keySource].documents[keyDocument].tags) {
+                let tag = this.sources[keySource].documents[keyDocument].tags[key];
+                this.rekall.sortings["horizontal"].analyseAdd(tag);
+                this.rekall.sortings["colors"].analyseAdd(tag);
+                Tags.push(tag);
+                tag.getLabels().forEach(l => {
+                    if (parsed_labels.indexOf(l) === -1) {
+                        parsed_labels.push(l);
+                    }
+                })
+            }
         }
-			}
-		}
-	}
-	rekall.sortings["horizontal"].analyseEnd();
-	rekall.sortings["colors"]    .analyseEnd();
-	Tags.flattenTimelineTags.sort(function(a, b) {
-		if(a.timeStart < b.timeStart) return -1;
-		if(a.timeStart > b.timeStart) return 1;
-		return 0;
-	});
+    }
+    this.rekall.sortings["horizontal"].analyseEnd();
+    this.rekall.sortings["colors"].analyseEnd();
+    Tags.sort();
 
-	//Affichage
-	var categories = rekall.sortings["horizontal"].categories;
-	if(rekall.sortings["horizontal"].metadataKey == "Time")
-		categories = {time: {tags: Tags.flattenTimelineTags}};
-	
-	//Affectation des couleurs
-	for (var key in rekall.sortings["colors"].categories) {
-		var colorSortingCategory = rekall.sortings["colors"].categories[key];
-		for (var key in colorSortingCategory.tags) {
-			var tag = colorSortingCategory.tags[key];
-			tag.update(colorSortingCategory.color);
-			tag.isSelectable = colorSortingCategory.checked;
-		}
-	}
-	
+    //Affichage
+    let categories = rekall.sortings["horizontal"].categories;
+    if (this.rekall.sortings["horizontal"].metadataKey == "Time")
+        categories = {time: {tags: Tags.getFlattenTimeLineTags()}};
 
-	//Tags / catégories
-	var markers = [], captions = [];
-	for (var key in categories) {
-		$.each(categories[key].tags, function(index, tag) {
-			//Elements sur la timeline
-			markers.push({
-				time: 		 tag.getTimeStart()+0.1,
-				text: 		 tag.getMetadata("Rekall->Name"),
-				overlayText: tag.getMetadata("Rekall->Comments"), 
-				css: {
-					"background-color": tag.color,
-					"width": 			"2px",
-				},
-				markerTipCss: {
-					"font-familly": 	"OpenSans",
-					"color": 			tag.color,
-				},
-				onMarkerClick: function() {
-					tag.openPopupEdit();
-				}
-			});
-			
-			//Captions en popup
-			if(tag.isMarker()) {
-				captions.push({
-					startTime: 	tag.getTimeStart() * 1000,
-					endTime: 	(tag.timeStart + max(2, tag.timeEnd - tag.timeStart)) * 1000,
-					position:  	"HB",
-					data: 	 	tag.getMetadata("Rekall->Name"),
-					alignment: 	"C",
-					css: {
-						"background-color": "black",
-						"font-familly": 	"OpenSans",
-						"color": 			tag.color,
-					},
-					onCaptionChange: function() {
-						tag.openPopupEdit();
-					}
-				});
-			}
-			
-			//Analyse de vignettes
-			if(true) {
-				var thumbUrl = Utils.getPreviewPath(tag);
-				tag.thumbnail = {url: thumbUrl, tag: tag};
-			}
-			
-			
-			if((tag.getMetadata("Rekall->Highlight") != undefined) && (tag.getMetadata("Rekall->Highlight") != "")) {   
-			
-				//Dom
-				$('#flattentimeline').append(function() {
-					var styleColor = "background-color: " + tag.color + ";";
-					var textColor = "color: " + tag.color + ";";       
-					var textColor2 = "color: rgba(255,255,255,1)";     
-					var textColor3 = "color: rgba(255,255,255,.75)";
-					
-					var colorTransp = "";
+    //Affectation des couleurs
+    for (let key in this.rekall.sortings["colors"].categories) {
+        let colorSortingCategory = this.rekall.sortings["colors"].categories[key];
+        for (let tagKey in colorSortingCategory.tags) {
+            let tag = colorSortingCategory.tags[tagKey];
+            tag.update(colorSortingCategory.color);
+            tag.isSelectable = colorSortingCategory.checked;
+        }
+    }
 
-					var styleColor2 = styleColor;
-					var styleImage = "";
-					if(tag.thumbnail.url != undefined) {  
 
-						styleImage = "background-image: -webkit-linear-gradient(right bottom,  rgba(20,46,51,.60) 0%,rgba(20,46,51,.90) 100%), url(" + tag.thumbnail.url + "); background-image: -moz-linear-gradient(right bottom,  rgba(20,46,51,.60) 0%,rgba(20,46,51,.90) 100%), url(" + tag.thumbnail.url + "); background-image: -o-linear-gradient(right bottom,  rgba(20,46,51,.60) 0%,rgba(20,46,51,.90) 100%), url(" + tag.thumbnail.url + ");";
-					} else styleImage = "background-color: " + tag.color + "; background-image: -webkit-linear-gradient(right bottom, rgba(20,46,51,.5) 0%,rgba(20,46,51,.8) 100%); background-image: -moz-linear-gradient(right bottom, rgba(20,46,51,.5) 0%,rgba(20,46,51,.8) 100%); background-image: -o-linear-gradient(right bottom, rgba(20,46,51,.5) 0%,rgba(20,46,51,.8) 100%);" ;
+    //Tags / catégories
+    let markers = [], captions = [];
+    for (let key in categories) {
+        $.each(categories[key].tags, function (index, tag) {
+            //Elements sur la timeline
+            markers.push({
+                time: tag.getTimeStart() + 0.1,
+                text: tag.getMetadata("Rekall->Name"),
+                overlayText: tag.getMetadata("Rekall->Comments"),
+                css: {
+                    "background-color": tag.color,
+                    "width": "2px",
+                },
+                markerTipCss: {
+                    "font-familly": "OpenSans",
+                    "color": tag.color,
+                },
+                onMarkerClick: function () {
+                    tag.openPopupEdit();
+                }
+            });
 
-					var icnType = "";
-					var tmpType = tag.getMetadata("Rekall->Type");
-					if(tmpType.indexOf("application/msword") >=0 ) 		icnType = "background-image:url(css/images/icn-word.png);";
-					else if(tmpType.indexOf("application/pdf") >=0 ) 	icnType = "background-image:url(css/images/icn-pdf.png);";
-					else if(tmpType.indexOf("application/") >=0 ) 		icnType = "background-image:url(css/images/icn-document.png);";
-					else if(tmpType.indexOf("audio/") >=0 ) 			icnType = "background-image:url(css/images/icn-music.png);";
-					else if(tmpType.indexOf("image/") >=0 ) 			icnType = "background-image:url(css/images/icn-image.png);";
-					else if(tmpType.indexOf("text/x-vcard") >=0 ) 		icnType = "background-image:url(css/images/icn-user.png);";
-					else if(tmpType.indexOf("text/") >=0 ) 				icnType = "background-image:url(css/images/icn-document.png);";
-					else if(tmpType.indexOf("video/") >=0 ) 			icnType = "background-image:url(css/images/icn-video.png);";  
-					
-					var typeTxt = tmpType.split("/")[1].replace(/marker/g, "note");  
-					
-					var htmlHighlight = ""; 
-					htmlHighlight	+=	"<div draggable=true class='flattentimeline_item flattentimeline_highlightitem' style='"+colorTransp+" "+styleImage+"'>";
-					htmlHighlight 	+=	"<div class='flattentimeline_title' 		style='" + textColor + "' title='" + tag.getMetadata("Rekall->Name") + "'>" + tag.getMetadata("Rekall->Name") + "</div>"; 
-					
-					
-					if(tag.getMetadata("Rekall->Comments")!="") {
-						
-						var tmpComments = tag.getMetadata("Rekall->Comments");
-						if(tmpComments.length>150){
-							tmpComments = tmpComments.substring(0, 150) + "...";
-						}
-						
-						var tmpcount = 0;
-						var tmpIndex = tmpComments.indexOf("<br/>");
-						while((tmpcount<3)&&(tmpIndex!=-1)) {
-							tmpcount++;
-							tmpIndex = tmpComments.indexOf("<br/>",tmpIndex+1);
-						}
-						if(tmpIndex!=-1) {
-							tmpComments = tmpComments.substring(0, tmpIndex) + "...";
-						}
-						htmlHighlight 	+=	"<div class='flattentimeline_description'>" + tmpComments + "</div>"; 
-					}
-						
-					if(tag.getMetadata("Rekall->Author")!="")	htmlHighlight 	+=	"<div class='flattentimeline_author'>" + tag.getMetadata("Rekall->Author") + "</div>";
-				
-						
-					htmlHighlight 	+=	"<div class='flattentimeline_typeTxt'		>" + typeTxt + "</div>";      
-					htmlHighlight 	+= "<div class='flattentimeline_opacifiant' style='" + styleColor2 + "'></div>";  
-					htmlHighlight    += "</div>";
-                   
-					tag.flattenTimelineDom = $(htmlHighlight); 
-					tag.flattenTimelineDom.click(function(event) { 
-						tag.openPopupEdit();                
-					});
-					tag.flattenTimelineDom.on({
-						dragstart: function(event) {
-							event.dataTransfer.setData("key", 	  tag.document.key);
-							event.dataTransfer.setData("version", tag.version);
-						}
-					});  
-				
-					return tag.flattenTimelineDom;   
-				
-				});     
-			
-			} else {    
-			
-				//Dom
-				$('#flattentimeline').append(function() {
-					var styleColor = "background-color: " + tag.color + ";";
-					var textColor = "color: " + tag.color + ";";
-					var textColor2 = "color: rgba(255,255,255,.8)";     
-					var textColor3 = "color: rgba(0,0,0,.75);";    
-					
-					var colorTransp = styleColor.replace(/rgb/g, "rgba").replace(/\)/g, ",.75)");
+            //Captions en popup
+            if (tag.isMarker()) {
+                captions.push({
+                    startTime: tag.getTimeStart() * 1000,
+                    endTime: (tag.timeStart + max(2, tag.timeEnd - tag.timeStart)) * 1000,
+                    position: "HB",
+                    data: tag.getMetadata("Rekall->Name"),
+                    alignment: "C",
+                    css: {
+                        "background-color": "black",
+                        "font-familly": "OpenSans",
+                        "color": tag.color,
+                    },
+                    onCaptionChange: function () {
+                        tag.openPopupEdit();
+                    }
+                });
+            }
 
-					var styleColor2 = styleColor;
-					var styleImage = "";
-					if(tag.thumbnail.url != undefined) {
-						styleImage = "background-image: url(" + tag.thumbnail.url + ");";
-					} else styleImage = "background-color: rgba(255,255,255,.25)";
+            //Analyse de vignettes
+            let thumbUrl = Utils.getPreviewPath(tag);
+            tag.thumbnail = {url: thumbUrl, tag: tag};
 
-					var icnType = "";
-					var tmpType = tag.getMetadata("Rekall->Type");
-					if(tmpType.indexOf("application/msword") >=0 ) 		icnType = "background-image:url(css/images/icn-word.png);";
-					else if(tmpType.indexOf("application/pdf") >=0 ) 	icnType = "background-image:url(css/images/icn-pdf.png);";
-					else if(tmpType.indexOf("application/") >=0 ) 		icnType = "background-image:url(css/images/icn-document.png);";
-					else if(tmpType.indexOf("audio/") >=0 ) 			icnType = "background-image:url(css/images/icn-music.png);";
-					else if(tmpType.indexOf("image/") >=0 ) 			icnType = "background-image:url(css/images/icn-image.png);";
-					else if(tmpType.indexOf("text/x-vcard") >=0 ) 		icnType = "background-image:url(css/images/icn-user.png);";
-					else if(tmpType.indexOf("text/") >=0 ) 				icnType = "background-image:url(css/images/icn-document.png);";
-					else if(tmpType.indexOf("video/") >=0 ) 			icnType = "background-image:url(css/images/icn-video.png);";
-                                    
+            $('#flattentimeline').append(tag.createTimelineDiv.call(tag));
 
-					var html = ""; 
-					html	+= "<div draggable=true class='flattentimeline_item' title='" + tag.getMetadata("Rekall->Comments") + "' >";  
-					html 	+= "<div class='flattentimeline_image'      style='" + styleImage + "'></div>";   
-					html 	+= "<div class='flattentimeline_opacifiant' style='" + styleColor2 + "'></div>";                     
-					html 	+= "<div class='flattentimeline_type'		style='" + icnType +"' title='" + tmpType + "'></div>";                                                           
-					html 	+= "<div class='flattentimeline_title' 		style='" + textColor2 + "' title='" + tag.getMetadata("Rekall->Name") + "'>" + tag.getMetadata("Rekall->Name") + "</div>";     
-					html    += "</div>";           
+            //Ouverture du popup
+            if ((Tag.keyToOpenAfterLoading != undefined) && (tag.document.key == Tag.keyToOpenAfterLoading)) {
+                tag.openPopupEdit();
+                Tag.keyToOpenAfterLoading = undefined;
+            }
+        });
+    }
+    this.rekall.timeline.updateFlattenTimeline();
 
-					tag.flattenTimelineDom = $(html);
-					tag.flattenTimelineDom.click(function(event) {                
-						tag.openPopupEdit(); 
-					});
-					tag.flattenTimelineDom.on({
-						dragstart: function(event) {
-							event.dataTransfer.setData("key", 	  tag.document.key);
-							event.dataTransfer.setData("version", tag.version);
-						}
-					});        
-				
-					return tag.flattenTimelineDom;    
-				
-				});  
-			}
-			
-			//Ouverture du popup
-			if((Tag.keyToOpenAfterLoading != undefined) && (tag.document.key == Tag.keyToOpenAfterLoading)) {
-				tag.openPopupEdit();
-				Tag.keyToOpenAfterLoading = undefined;
-			}
-		});
-	}
-	rekall.timeline.updateFlattenTimeline();
+    //Initialisation des captions
+    if (this.rekall.videoPlayer.caption.updateCaption == undefined) {
+        this.rekall.videoPlayer.caption({
+            data: captions,
+            setting: {
+                captionSize: 3,
+                captionStyle: {
+                    'background-color': 'rgba(255,0,0,0.8)',
+                    'color': 'white',
+                    'padding': '3px',
+                    'font-family': 'OpenSans',
+                },
+                onCaptionChange: function (num_c) {
+                }
+            }
+        });
+    } else if ((captions) && (captions.length > 0)) {
+        this.rekall.videoPlayer.caption.loadNewCaption({data: captions});
+    }
 
-	//Initialisation des captions
-	if(rekall.videoPlayer.caption.updateCaption == undefined) {
-		rekall.videoPlayer.caption({
-			data: captions, 
-			setting: {
-				captionSize:  3,
-				captionStyle: {
-					'background-color': 'rgba(255,0,0,0.8)',
-					'color':  			'white',
-					'padding': 			'3px',
-					'font-family': 		'OpenSans',
-				},
-				onCaptionChange: function(num_c) {
-				}
-			}
-		});
-	}
-	else if((captions) && (captions.length > 0)) {
-		rekall.videoPlayer.caption.loadNewCaption({data: captions});
-	}
-	
-	//Ajout des marqueurs sur la timeline
-	if(rekall.videoPlayer.markers.removeAll == undefined) {
-		rekall.videoPlayer.markers({
-			markerStyle: {
-				'width':            '7px',
-				'border-radius':    '30%',
-				'background-color': 'yellow'
-			},
-			markerTip:{
-				display: true,
-				text: function(marker) {
-					return marker.text;
-				},
-				time: function(marker) {
-					return marker.time;
-				}
-			},
-			breakOverlay:{
-				display:     false,
-				displayTime: 3,
-				style: {
-					'width':            '100%',
-					'height':           '20%',
-					'background-color': 'rgba(0,0,0,0.7)',
-					'color':            'white',
-					'font-size':        '17px'
-				},
-				text: function(marker) {
-					return "Break overlay: " + marker.overlayText;
-				}
-			},
-			onMarkerClick: 	 function(marker) {},
-			onMarkerReached: function(marker) {},
-			markers: markers
-		});
-	}
-	else
-		rekall.videoPlayer.markers.reset(markers);
+    //Ajout des marqueurs sur la timeline
+    if (this.rekall.videoPlayer.markers.removeAll == undefined) {
+        this.rekall.videoPlayer.markers({
+            markerStyle: {
+                'width': '7px',
+                'border-radius': '30%',
+                'background-color': 'yellow'
+            },
+            markerTip: {
+                display: true,
+                text: function (marker) {
+                    return marker.text;
+                },
+                time: function (marker) {
+                    return marker.time;
+                }
+            },
+            breakOverlay: {
+                display: false,
+                displayTime: 3,
+                style: {
+                    'width': '100%',
+                    'height': '20%',
+                    'background-color': 'rgba(0,0,0,0.7)',
+                    'color': 'white',
+                    'font-size': '17px'
+                },
+                text: function (marker) {
+                    return "Break overlay: " + marker.overlayText;
+                }
+            },
+            onMarkerClick: function (marker) {
+            },
+            onMarkerReached: function (marker) {
+            },
+            markers: markers
+        });
+    } else {
+        this.rekall.videoPlayer.markers.reset(markers);
+    }
 }
diff --git a/capsule-prototype/js/online-rekall/Rekall.js b/capsule-prototype/js/online-rekall/Rekall.js
index 0f1449047b9fcd21c03f02aa6d9f0dd26251cf14..971ed994e585972f879df97e842ae97ee0b81e9e 100644
--- a/capsule-prototype/js/online-rekall/Rekall.js
+++ b/capsule-prototype/js/online-rekall/Rekall.js
@@ -23,125 +23,230 @@
 */
 
 function Rekall(options) {
-	this.sortings = new Object();
-	this.sortings["horizontal"] = new Sorting("Time");
-	this.sortings["colors"]     = new Sorting("Rekall->Type", false, "");
-	this.timeline = new Timeline();
-	this.videoPlayer = undefined;
-
-	function getDefaultBaseUrl() {
-		var oldURL = window.document.location + "";
-		var url = oldURL;
-		var index = 0;
-		url = url.replace("pro-index.html", "");
-		url = url.replace("online-index.html", "");
-		url = url.replace("index.html", "");
-		index = oldURL.indexOf('?');
-		if(index == -1)
-			index = oldURL.indexOf('#');
-		if(index != -1)
-			url = oldURL.substring(0, index);
-		if(!url.endsWith("/"))
-			url = url + "/";
-
-		return url;
-	}
-
-	var defaultSettings = {
-		_allowProjectDeletion: false,
-		baseUrl: getDefaultBaseUrl()
-	}
-
-	this.Settings = $.extend({}, defaultSettings, options);
+    this.sortings = new Object();
+    this.sortings["horizontal"] = new Sorting("Time");
+    this.sortings["colors"] = new Sorting("Rekall->Type", false, "");
+    this.timeline = new Timeline();
+    this.videoPlayer = undefined;
+
+    function getDefaultBaseUrl() {
+        var oldURL = window.document.location + "";
+        var url = oldURL;
+        var index = 0;
+        url = url.replace("pro-index.html", "");
+        url = url.replace("online-index.html", "");
+        url = url.replace("index.html", "");
+        index = oldURL.indexOf('?');
+        if (index == -1)
+            index = oldURL.indexOf('#');
+        if (index != -1)
+            url = oldURL.substring(0, index);
+        if (!url.endsWith("/"))
+            url = url + "/";
+
+        return url;
+    }
+
+    let defaultSettings = {
+        _allowProjectDeletion: false,
+        baseUrl: getDefaultBaseUrl()
+    }
+
+    this.Settings = $.extend({}, defaultSettings, options);
 }
 
-Rekall.prototype.allowProjectDeletion = function(){
-	if (arguments.length <= 0 && 'boolean' !== typeof arguments[0]){
-		return this.Settings._allowProjectDeletion;
-	}
-
-	if (arguments[0]) {
-		this.Settings._allowProjectDeletion = true;
-		$('#popupSettingsBtnDelete').attr('display', null);
-	}
-	else {
-		this.Settings._allowProjectDeletion = false;
-		$('#popupSettingsBtnDelete').hide();
-	}
-	this.Settings._allowProjectDeletion = arguments[0];
+Rekall.prototype.allowProjectDeletion = function () {
+    if (arguments.length <= 0 && 'boolean' !== typeof arguments[0]) {
+        return this.Settings._allowProjectDeletion;
+    }
+
+    if (arguments[0]) {
+        this.Settings._allowProjectDeletion = true;
+        $('#popupSettingsBtnDelete').attr('display', null);
+    } else {
+        this.Settings._allowProjectDeletion = false;
+        $('#popupSettingsBtnDelete').hide();
+    }
+    this.Settings._allowProjectDeletion = arguments[0];
 }
 
-Rekall.prototype.loadXMLFile = function() {
-	var oldURL = window.document.location + "";
-	var index = 0;
-	var url = oldURL;
-	url = url.replace("pro-index.html", "");
-	url = url.replace("online-index.html", "");
-	url = url.replace("index.html", "");
-	index = oldURL.indexOf('?');
-	if(index == -1)
-		index = oldURL.indexOf('#');
-	if(index != -1)
-		url = oldURL.substring(0, index);
-	if(!url.endsWith("/"))
-		url = url + "/";
-
-	if(!this.Settings._allowProjectDeletion){
-		$('#popupSettingsBtnDelete').hide();
-	};
-
-	this.baseUrl = url;
-	
-	var thiss = this;
-	rouletteStart();
-	$.ajax("php/file.php?r=project.xml", {
-		type: 	"GET",
-		cache: 	false,
-		data: 	{"rand": random()},
-		dataType: "xml",
-		success: function(xml) {
-			if((xml == null) || (xml == undefined))
-				openAlert("Your project is unreachable. Did you move the folder to an other location?", 60);
-			else {
-				$(xml).find('project').each(function() {
-					//if($(this).find('document').length == 0) {
-					//	openAlert("Start by adding files to your project.", 60);
-					//} else {
-						if(rekall.project == undefined)
-							rekall.project = new Project(url);
-						rekall.project.loadXML($(this));
-						//}
-				});
-			}
-			rouletteEnd();
-		},
-		error: function() {
-			$.ajax("file/project.xml", {
-				type: 	"GET",
-				cache: 	false,
-				data: 	{"rand": random()},
-				dataType: "xml",
-				success: function(xml) {
-					if((xml == null) || (xml == undefined))
-						openAlert("Your project is unreachable. Did you move the folder to an other location?", 60);
-					else {
-						$(xml).find('project').each(function() {
-							if($(this).find('document').length == 0) {
-								openAlert("Start by adding files to your project.", 60);
-							} else {
-								if(rekall.project == undefined)
-									rekall.project = new Project(url);
-								rekall.project.loadXML($(this));
-							}
-						});
-					}
-					rouletteEnd();
-				},
-				error: function() {
-					rouletteEnd();
-				}
-			});
-			rouletteEnd();
-		}
-	});
-}
\ No newline at end of file
+Rekall.prototype.publish = function (eventName, data){
+    return window.app.rekall.Rekall('pubSub').publish(eventName, data);
+}
+
+Rekall.prototype.loadXMLFile = function () {
+    var oldURL = window.document.location + "";
+    var index = 0;
+    var url = oldURL;
+    url = url.replace("pro-index.html", "");
+    url = url.replace("online-index.html", "");
+    url = url.replace("index.html", "");
+    index = oldURL.indexOf('?');
+    if (index == -1)
+        index = oldURL.indexOf('#');
+    if (index != -1)
+        url = oldURL.substring(0, index);
+    if (!url.endsWith("/"))
+        url = url + "/";
+
+    if (!this.Settings._allowProjectDeletion) {
+        $('#popupSettingsBtnDelete').hide();
+    }
+
+    this.baseUrl = url;
+
+    let that = this;
+    this.publish('roulette.started');
+    $.ajax("php/file.php?r=project.xml", {
+        type: "GET",
+        cache: false,
+        data: {"rand": Math.random()},
+        dataType: "xml",
+        success: function (xml) {
+            if ((xml == null) || (xml == undefined)) {
+                that.publish('alert', {
+                    message: "Your project is unreachable. Did you move the folder to an other location?",
+                    buttons: 60
+                });
+            } else {
+                $(xml).find('project').each(function () {
+                    //if($(this).find('document').length == 0) {
+                    //	openAlert("Start by adding files to your project.", 60);
+                    //} else {
+                    if (that.project == undefined)
+                        that.project = new Project(url, that);
+                    that.project.loadXML($(this));
+                    //}
+                });
+            }
+            that.publish('roulette.terminated', {});
+        },
+        error: function () {
+            $.ajax("file/project.xml", {
+                type: "GET",
+                cache: false,
+                data: {"rand": Math.random()},
+                dataType: "xml",
+                success: function (xml) {
+                    if ((xml == null) || (xml == undefined)) {
+                        that.publish('alert', {
+                            message: "Your project is unreachable. Did you move the folder to an other location?",
+                            buttons: 60
+                        });
+                    } else {
+                        $(xml).find('project').each(function () {
+                            if ($(this).find('document').length == 0) {
+                                that.publish('alert', {
+                                    message: "Start by adding files to your project.",
+                                    buttons: 60
+                                });
+                            } else {
+                                if (that.project == undefined)
+                                    that.project = new Project(url, that);
+                                that.project.loadXML($(this));
+                            }
+                        });
+                    }
+                    that.publish('roulette.terminated', {});
+                },
+                error: function () {
+                    that.publish('roulette.terminated', {});
+                }
+            });
+            that.publish('roulette.terminated');
+        }
+    });
+}
+
+Rekall.prototype.ensureVideoPlayerCreated = function (url, tech) {
+    if (undefined !== this.videoPlayer)
+        return false;
+
+    let that = this;
+    let techOrder = ["vimeo", "youtube", "html5"];
+    if (url.indexOf("youtube") >= 0) tech = "youtube";
+    if (url.indexOf("youtu.be") >= 0) tech = "youtube";
+    if (url.indexOf("vimeo") >= 0) tech = "vimeo";
+    if (url.indexOf("dailymotion") >= 0) tech = "dailymotion";
+    if (url.indexOf("dai.ly") >= 0) tech = "dailymotion";
+    if ((tech !== "") && (tech !== undefined))
+        techOrder = [tech, "html5"];
+
+    //Video
+    this.videoPlayer = videojs("video", {
+        "techOrder": techOrder,
+        "controls": true,
+        "autoplay": false,
+        "loop": "false",
+        "preload": "auto",
+        "sources": [
+            {
+                "type": "video/" + tech,
+                "src": url
+            }
+        ]
+    }, function () {
+        $(".vjs-fullscreen-control").hide();
+
+        that.videoPlayer.on("durationchange", function () {
+            console.log("durationchange");
+            that.project.analyse();
+            that.videoPlayer.markers.initialize();
+        });
+        that.videoPlayer.on("ended", function () {
+            console.log("ended");
+        });
+        that.videoPlayer.on("error", function () {
+            console.log("error");
+        });
+        that.videoPlayer.on("firstplay", function () {
+            console.log("firstplay");
+        });
+        that.videoPlayer.on("fullscreenchange", function () {
+            console.log("fullscreenchange");
+        });
+        that.videoPlayer.on("loadedalldata", function () {
+            console.log("loadedalldata");
+        });
+        that.videoPlayer.on("loadeddata", function () {
+            console.log("loadeddata");
+        });
+        that.videoPlayer.on("loadedmetadata", function () {
+            console.log("loadedmetadata");
+            that.project.analyse();
+        });
+        that.videoPlayer.on("loadstart", function () {
+            console.log("loadstart");
+        });
+        that.videoPlayer.on("pause", function () {
+            console.log("pause");
+        });
+        that.videoPlayer.on("play", function () {
+            console.log("play");
+        });
+        that.videoPlayer.on("progress", function () {
+            console.log("progress");
+        });
+        that.videoPlayer.on("seeked", function () {
+            console.log("seeked");
+        });
+        that.videoPlayer.on("seeking", function () {
+            console.log("seeking");
+        });
+        that.videoPlayer.on("timeupdate", function () {
+            console.log("timeupdate");
+            that.timeline.update(that.videoPlayer.currentTime());
+        });
+        that.videoPlayer.on("volumechange", function () {
+            console.log("volumechange");
+        });
+        that.videoPlayer.on("waiting", function () {
+            console.log("waiting");
+        });
+        that.videoPlayer.on("resize", function () {
+            console.log("resize");
+        });
+    });
+
+    return true;
+}
diff --git a/capsule-prototype/js/online-rekall/RekallApplication.js b/capsule-prototype/js/online-rekall/RekallApplication.js
index c214d4e070efd711ed12dd41534e6798ff399253..24b763baff26059b8d84014aeb7e3be198ab3c86 100644
--- a/capsule-prototype/js/online-rekall/RekallApplication.js
+++ b/capsule-prototype/js/online-rekall/RekallApplication.js
@@ -8,7 +8,8 @@
             'videoPlayer',
             'rekall',
             'getUrl',
-            'projectName'
+            'projectName',
+            'mosaicWidget'
         ];
 
         let localOptions = {};
@@ -25,6 +26,14 @@
                     $.extend(localOptions, opts);
                 }
             },
+            mosaicWidget : function (){
+                if (!arguments.length){
+                    return localOptions.MosaicWidget;
+                }
+                if ('object' === typeof arguments[0] && null !== arguments[0]) {
+                    localOptions.MosaicWidget = arguments[0];
+                }
+            },
             openUrl: function () {
                 if (!arguments)
                     return;
diff --git a/capsule-prototype/js/online-rekall/Tag.js b/capsule-prototype/js/online-rekall/Tag.js
index f6cd7fe6bfd0195d6e861e3b3cee41cf94f72d0b..5d0ac8cc30b2abc7162b49663b2458c76ddde113 100644
--- a/capsule-prototype/js/online-rekall/Tag.js
+++ b/capsule-prototype/js/online-rekall/Tag.js
@@ -23,125 +23,260 @@
 */
 
 function Tag(document) {
-	this.document         = document;
-	this.documentOriginal = this.document;
-	this.version          = document.currentVersion;
-	this.versionOriginal  = this.version;
-	this.timeStart = 0;
-	this.timeEnd   = 1;
-	this.color     = "#FFFFFF";
-	this.shown     = false;
+    NotificationEntityChanged.call(this, {});
+    this.document = document;
+    this.documentOriginal = this.document;
+    this.version = document.currentVersion;
+    this.versionOriginal = this.version;
+    this.timeStart = 0;
+    this.timeEnd = 1;
+    this.color = "#FFFFFF";
+    this.shown = false;
+    this.thumbnail = undefined;
 }
-Tag.keyToOpenAfterLoading = undefined;
 
+Tag.prototype = Object.create(NotificationEntityChanged.prototype);
+
+
+Tag.keyToOpenAfterLoading = undefined;
 
-Tag.prototype.getMetadata = function(metadataKey, metadataValue) {
-	return this.document.getMetadata(metadataKey, this.version);
+Tag.prototype.getMetadata = function (metadataKey, metadataValue) {
+    return this.document.getMetadata(metadataKey, this.version);
 }
-Tag.prototype.setMetadata = function(metadataKey, metadataValue) {
-	return this.document.setMetadata(metadataKey, metadataValue, this.version);
+Tag.prototype.setMetadata = function (metadataKey, metadataValue) {
+    let result = this.document.setMetadata(metadataKey, metadataValue, this.version);
+    this.notifyChange();
+    return result;
 }
-Tag.prototype.getMetadatas = function() {
-	return this.document.getMetadatas(this.version);
+Tag.prototype.getMetadatas = function () {
+    return this.document.getMetadatas(this.version);
 }
-Tag.prototype.isVideoOrAudio = function() {
-	return this.document.isVideoOrAudio(this.version); 
+Tag.prototype.isVideoOrAudio = function () {
+    return this.document.isVideoOrAudio(this.version);
 }
-Tag.prototype.isAudio = function() {
-	return this.document.isAudio(this.version); 
+Tag.prototype.isAudio = function () {
+    return this.document.isAudio(this.version);
 }
-Tag.prototype.isVideo = function() {
-	return this.document.isVideo(this.version); 
+Tag.prototype.isVideo = function () {
+    return this.document.isVideo(this.version);
 }
-Tag.prototype.isMarker = function() {
-	return this.document.isMarker(this.version); 
+Tag.prototype.isMarker = function () {
+    return this.document.isMarker(this.version);
 }
-Tag.prototype.isImage = function() {
-	return this.document.isImage(this.version); 
+Tag.prototype.isImage = function () {
+    return this.document.isImage(this.version);
 }
-Tag.prototype.isLink = function() {
-	return this.document.isLink(this.version); 
+Tag.prototype.isLink = function () {
+    return this.document.isLink(this.version);
 }
 
+Tag.prototype.getTagGradientColor = function() {
+    var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;  // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
+    var isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
+    var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;   // At least Safari 3+: "[object HTMLElementConstructor]"
+    var isChrome = !!window.chrome && !isOpera;              // Chrome 1+
+    var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
 
-Tag.prototype.getTimeStart = function() {
-	return this.timeStart;
+    if (isOpera) {
+        return "-o-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + this.color + " 100%)";
+    }
+    if (isFirefox) {
+        return "-moz-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + this.color + " 100%)";
+    }
+    if ((isSafari) || (isChrome)) {
+        return "-webkit-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + this.color + " 100%)";
+    }
 }
-Tag.prototype.getTimeEnd = function() {
-	return this.timeEnd;
+Tag.prototype.getTimeStart = function () {
+    return this.timeStart;
 }
-Tag.prototype.setTimeStart = function(val) {
-	if(!isNaN(val))
-		this.timeStart = val;
+Tag.prototype.getTimeEnd = function () {
+    return this.timeEnd;
 }
-Tag.prototype.setTimeEnd = function(val) {
-	if(!isNaN(val))
-		this.timeEnd = val;
+Tag.prototype.setTimeStart = function (val) {
+    if (!isNaN(val)) {
+        this.timeStart = val;
+        this.notifyChange();
+    }
 }
+Tag.prototype.setTimeEnd = function (val) {
+    if (!isNaN(val)) {
+        this.timeEnd = val;
+        this.notifyChange();
+    }
 
-
-
-Tag.prototype.isGoodVersion = function() {
-	return this.version == this.document.goodVersion;
 }
-
-
-Tag.prototype.openFile = function() {
-	return this.document.openFile();
+Tag.prototype.isGoodVersion = function () {
+    return this.version == this.document.goodVersion;
+}
+Tag.prototype.openFile = function () {
+    return this.document.openFile();
+}
+Tag.prototype.openFinder = function () {
+    return this.document.openFinder();
+}
+Tag.prototype.openQuickLook = function () {
+    return this.document.openQuickLook();
 }
-Tag.prototype.openFinder = function() {
-	return this.document.openFinder();
+Tag.prototype.downloadFile = function () {
+    return this.document.downloadFile();
 }
-Tag.prototype.openQuickLook = function() {
-	return this.document.openQuickLook();
+Tag.prototype.getDownloadLink = function (original = false) {
+    return this.document.getDownloadLink(original);
 }
-Tag.prototype.downloadFile = function() {
-	return this.document.downloadFile();
+Tag.prototype.getFileName = function () {
+    return this.document.getFileName();
 }
-Tag.prototype.getDownloadLink = function(original = false) {
-  return this.document.getDownloadLink(original);
+Tag.prototype.openBrowser = function () {
+    return this.document.openBrowser();
 }
-Tag.prototype.getFileName = function() {
-  return this.document.getFileName();
+Tag.prototype.openPopupEdit = function () {
+    fillPopupEdit(this);
 }
-Tag.prototype.openBrowser = function() {
-	return this.document.openBrowser();
-}   
-Tag.prototype.openPopupEdit = function() {
-	fillPopupEdit(this);  
+Tag.prototype.update = function (color, strong) {
+    if (color != undefined) {
+        this.colorRaw = color;
+        this.color = color.toString();
+        this.isMarkerCache = this.isMarker();
+    }
+    if (this.color == undefined)
+        this.color = "#000000";
 }
+Tag.prototype.createTimelineDiv = function () {
+    let that = this;
+    let styleColor = "background-color: " + this.color + ";";
+    let textColor2 = "color: rgba(255,255,255,.8)";
 
-Tag.prototype.update = function(color, strong) {
-	if(color != undefined) {
-		this.colorRaw  = color;
-		this.color     = color.toString();
-		
-		this.isMarkerCache = this.isMarker();
-		/*
-		if(this.isMarkerCache) {
-			this.visuel.rect.setCornerRadius(0);
-		}
-		else {
-			this.visuel.rect.setCornerRadius(Tag.tagHeight/3);
-		}
-		*/
-	}
-	if(this.color == undefined)
-		this.color = "#000000";
+    let styleColor2 = styleColor;
+    let styleImage = "";
+    if (this.thumbnail.url != undefined) {
+        styleImage = "background-image: url(" + encodeURI(this.thumbnail.url) + ");";
+    } else styleImage = "background-color: rgba(255,255,255,.25)";
+
+    let icnType = "";
+    let tmpType = this.getMetadata("Rekall->Type");
+    if (tmpType.indexOf("application/msword") >= 0) icnType = "background-image:url(css/images/icn-word.png);";
+    else if (tmpType.indexOf("application/pdf") >= 0) icnType = "background-image:url(css/images/icn-pdf.png);";
+    else if (tmpType.indexOf("application/") >= 0) icnType = "background-image:url(css/images/icn-document.png);";
+    else if (tmpType.indexOf("audio/") >= 0) icnType = "background-image:url(css/images/icn-music.png);";
+    else if (tmpType.indexOf("image/") >= 0) icnType = "background-image:url(css/images/icn-image.png);";
+    else if (tmpType.indexOf("text/x-vcard") >= 0) icnType = "background-image:url(css/images/icn-user.png);";
+    else if (tmpType.indexOf("text/") >= 0) icnType = "background-image:url(css/images/icn-document.png);";
+    else if (tmpType.indexOf("video/") >= 0) icnType = "background-image:url(css/images/icn-video.png);";
+
+
+    let html = "";
+    html += "<div draggable=true class='flattentimeline_item' title='" + this.getMetadata("Rekall->Comments") + "' >";
+    html += "<div class='flattentimeline_image'      style='" + styleImage + "'></div>";
+    html += "<div class='flattentimeline_opacifiant' style='" + styleColor2 + "'></div>";
+    html += "<div class='flattentimeline_type'		style='" + icnType + "' title='" + tmpType + "'></div>";
+    html += "<div class='flattentimeline_title' 		style='" + textColor2 + "' title='" + this.getMetadata("Rekall->Name") + "'>" + this.getMetadata("Rekall->Name") + "</div>";
+    html += "</div>";
+
+    this.flattenTimelineDom = $(html);
+    this.flattenTimelineDom.click(function () {
+        that.openPopupEdit();
+    });
+    this.flattenTimelineDom.on({
+        dragstart: function (event) {
+            event.dataTransfer.setData("key", that.document.key);
+            event.dataTransfer.setData("version", that.version);
+        }
+    });
+
+    return this.flattenTimelineDom;
 
-	var fillColor   = '';
-	var strokeColor = '';
-	var strokeWidth = 0;
-	var opacity = 1;
-		
-	if(this.isGoodVersion()) {
-		fillColor   = this.color;
-		strokeWidth = 0;
-		strokeColor = "";
-	}
-	else {
-		fillColor   = tinycolor(this.colorRaw.toString()).setAlpha(0.1).toString();
-		strokeColor = this.color;
-		strokeWidth = 0.8;
-	}
 }
+Tag.prototype.getLabels = function () {
+    let labels = this.getMetadata('Rekall->Labels');
+    if (!labels)
+        return [];
+
+    let isStringNullOrWhitspace = function (s) {
+        if (typeof s !== "string" || !s) {
+            return false;
+        }
+
+        return !s.trim();
+    }
+
+    return labels.split(';').filter(s => !isStringNullOrWhitspace(s));
+}
+
+function HighLightedTag(data) {
+    Tag.call(this, data.Document);
+}
+
+HighLightedTag.prototype = Object.create(Tag.prototype);
+HighLightedTag.prototype.createTimelineDiv = function () {
+    let that = this;
+    let styleColor = "background-color: " + this.color + ";";
+    let textColor = "color: " + this.color + ";";
+
+    let colorTransp = "";
+
+    let styleColor2 = styleColor;
+    let styleImage = "";
+    if (this.thumbnail?.url) {
+        styleImage = "background-image: -webkit-linear-gradient(right bottom,  rgba(20,46,51,.60) 0%,rgba(20,46,51,.90) 100%), url(" + this.thumbnail.url + "); background-image: -moz-linear-gradient(right bottom,  rgba(20,46,51,.60) 0%,rgba(20,46,51,.90) 100%), url(" + this.thumbnail.url + "); background-image: -o-linear-gradient(right bottom,  rgba(20,46,51,.60) 0%,rgba(20,46,51,.90) 100%), url(" + this.thumbnail.url + ");";
+    } else {
+        styleImage = "background-color: " + this.color + "; background-image: -webkit-linear-gradient(right bottom, rgba(20,46,51,.5) 0%,rgba(20,46,51,.8) 100%); background-image: -moz-linear-gradient(right bottom, rgba(20,46,51,.5) 0%,rgba(20,46,51,.8) 100%); background-image: -o-linear-gradient(right bottom, rgba(20,46,51,.5) 0%,rgba(20,46,51,.8) 100%);";
+    }
+
+    let tmpType = this.getMetadata("Rekall->Type");
+    let typeTxt = tmpType.split("/")[1].replace(/marker/g, "note");
+
+    let htmlHighlight = "";
+    htmlHighlight += "<div draggable=true class='flattentimeline_item flattentimeline_highlightitem' style='" + colorTransp + " " + styleImage + "'>";
+    htmlHighlight += "<div class='flattentimeline_title' 		style='" + textColor + "' title='" + this.getMetadata("Rekall->Name") + "'>" + this.getMetadata("Rekall->Name") + "</div>";
+
+
+    if (this.getMetadata("Rekall->Comments") != "") {
+
+        let tmpComments = this.getMetadata("Rekall->Comments");
+        if (tmpComments.length > 150) {
+            tmpComments = tmpComments.substring(0, 150) + "...";
+        }
+
+        let tmpcount = 0;
+        let tmpIndex = tmpComments.indexOf("<br/>");
+        while ((tmpcount < 3) && (tmpIndex != -1)) {
+            tmpcount++;
+            tmpIndex = tmpComments.indexOf("<br/>", tmpIndex + 1);
+        }
+        if (tmpIndex != -1) {
+            tmpComments = tmpComments.substring(0, tmpIndex) + "...";
+        }
+        htmlHighlight += "<div class='flattentimeline_description'>" + tmpComments + "</div>";
+    }
+
+    if (this.getMetadata("Rekall->Author") != "") htmlHighlight += "<div class='flattentimeline_author'>" + this.getMetadata("Rekall->Author") + "</div>";
+
+
+    htmlHighlight += "<div class='flattentimeline_typeTxt'		>" + typeTxt + "</div>";
+    htmlHighlight += "<div class='flattentimeline_opacifiant' style='" + styleColor2 + "'></div>";
+    htmlHighlight += "</div>";
+
+    this.flattenTimelineDom = $(htmlHighlight);
+    this.flattenTimelineDom.click(function () {
+        that.openPopupEdit();
+    });
+    this.flattenTimelineDom.on({
+        dragstart: function (event) {
+            event.dataTransfer.setData("key", that.document.key);
+            event.dataTransfer.setData("version", that.version);
+        }
+    });
+
+    return this.flattenTimelineDom;
+}
+
+
+const TagFactory = function (document) {
+
+    if (document.getMetadata("Rekall->Highlight", document.currentVersion)) {
+        return new HighLightedTag({document: document})
+    }
+
+    return new Tag(document);
+}
\ No newline at end of file
diff --git a/capsule-prototype/js/online-rekall/Tags.js b/capsule-prototype/js/online-rekall/Tags.js
index 6234ec74bcc725c1af7f660b4efb789e4bf17add..9a057be63c43c4fe09c98af8b39963800f6c8f8a 100644
--- a/capsule-prototype/js/online-rekall/Tags.js
+++ b/capsule-prototype/js/online-rekall/Tags.js
@@ -21,7 +21,39 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-function Tags() {
-}
+const Tags = (function() {
+    let flattenTimelineTags;
 
-Tags.flattenTimelineTags = new Array();
+    function getFlattenTimeLineTags() {
+        if(!flattenTimelineTags){
+            flattenTimelineTags = [];
+        }
+
+        return flattenTimelineTags;
+    }
+
+    function empty(){
+        flattenTimelineTags = [];
+    }
+
+    let sort = function() {
+        let localFlattenTimeline = getFlattenTimeLineTags();
+        localFlattenTimeline.sort(function (a, b) {
+            if (a.timeStart < b.timeStart) return -1;
+            if (a.timeStart > b.timeStart) return 1;
+            return 0;
+        });
+    }
+
+    let push = function(value) {
+        let localFlattenTimeline = getFlattenTimeLineTags();
+        localFlattenTimeline.push(value);
+    }
+
+    return {
+        getFlattenTimeLineTags: getFlattenTimeLineTags,
+        reset: empty,
+        sort: sort,
+        push: push
+    }
+})();
diff --git a/capsule-prototype/js/online-script.js b/capsule-prototype/js/online-script.js
index 34d52382b1b0832faf8d114b7c12f38e1b0054b5..4cd35c149311ef296b2ac609a2c38fd8866949ee 100644
--- a/capsule-prototype/js/online-script.js
+++ b/capsule-prototype/js/online-script.js
@@ -1,6 +1,7 @@
 var rekall = new Rekall();
 var rekall_common = new Object();
 var pubSub = null;
+var popupPanel = new PopupPanelWidget({ canEdit: () => rekall_common.owner.canEdit });
 
 window.onload = function() {
 	pubSub = window.top.PubSub;
@@ -11,10 +12,14 @@ window.onload = function() {
 	pubSub.subscribe('video', openVideo);
 	pubSub.subscribe('open_paste_modal', openPasteModal);
 	pubSub.subscribe('close_paste_modal', closePasteModal);
-	pubSub.subscribe('updated.project.name', onUpdatedProjectName)
-};
+	pubSub.subscribe('updated.project.name', onUpdatedProjectName);
+	pubSub.subscribe('alert', openAlertWrapper);
+	pubSub.subscribe('roulette.terminated', rouletteEnd);
+	pubSub.subscribe('roulette.started', rouletteStart);
+	pubSub.subscribe('tag.metadata.updated', onMetaFromDomUpdated);
+	pubSub.subscribe('tag.tc.updated', onTcUpdated);
+	pubSub.subscribe('image.annotation.edit.open', openImageEdition);
 
-$(document).ready(function() {
 	window.app.rekall.Rekall('init', {
 		Rekall: rekall
 	});
@@ -99,11 +104,7 @@ $(document).ready(function() {
 
 	$("#popupSpace").click(function(event){
 		event.stopPropagation();
-		closeEdit();
-	});
-	$("#closePopupEdit").click(function(event){
-		event.stopPropagation();
-		closeEdit();
+		popupPanel.close();
 	});
 
 	$("#popupAlertButtonCancel").click(function(){
@@ -129,53 +130,26 @@ $(document).ready(function() {
 		var tmp = shareLink();
 		openAlert("input",tmp);
 	});
-});
+};
 
 function setEditionControls() {
 
-  $('#new_annotation_label').keypress(function(event) {
-    var keycode = (event.keyCode ? event.keyCode : event.which);
-    if (keycode != 13) {
-      return;
-    }
-    var label = $('#new_annotation_label').val();
-    var input = $('input.annotation_labels_template').first().clone();
-    var html_label = $('label.annotation_labels_template').first().clone();
-    var id = 'annotation_label_' + Math.floor((Math.random()*1000));
-
-    input.attr('name', 'annotation_labels');
-    input.attr('checked', 'true');
-    input.attr('id', id);
-    input.attr('value', label);
-
-    html_label.attr('for', id);
-    html_label.html(label);
-
-    html_label.show();
-    html_label.addClass('annotation_labels').removeClass('annotation_labels_template');
-    input.addClass('annotation_labels').removeClass('annotation_labels_template');
-
-    $('#popupLabelsInput').append(input);
-    $('#popupLabelsInput').append(html_label);
-    $('#new_annotation_label').val('');
-  })
-
 	//Drag&drop files
 	$(document).on({
-		dragenter: function(event) {
+		dragenter: function (event) {
 			event.stopImmediatePropagation();
 			event.preventDefault();
 		},
-		dragleave: function(event) {
+		dragleave: function (event) {
 			event.stopImmediatePropagation();
 			event.preventDefault();
 		},
-		dragover: function(event) {
+		dragover: function (event) {
 			event.stopImmediatePropagation();
 			event.preventDefault();
 		},
-		drop: function(event) {
-			if(event.originalEvent.dataTransfer.files.length) {
+		drop: function (event) {
+			if (event.originalEvent.dataTransfer.files.length) {
 				event.stopImmediatePropagation();
 				event.preventDefault();
 				uploadFiles(event.originalEvent.dataTransfer.files);
@@ -184,18 +158,25 @@ function setEditionControls() {
 	});
 
 	$("#flattentimeline").on({
-		dragenter: function(event) { /*$(".flattentimeline_item").removeClass("draggable").addClass("drag");*/ },
-		dragleave: function(event) {  $("#flattentimeline").removeClass("draggable").removeClass("drag"); },
-		dragover: function(event) { $("#flattentimeline").removeClass("draggable").addClass("drag"); },
-		drop: function(event) { $("#flattentimeline").removeClass("draggable").removeClass("drag");  }
+		dragenter: function (event) { /*$(".flattentimeline_item").removeClass("draggable").addClass("drag");*/
+		},
+		dragleave: function (event) {
+			$("#flattentimeline").removeClass("draggable").removeClass("drag");
+		},
+		dragover: function (event) {
+			$("#flattentimeline").removeClass("draggable").addClass("drag");
+		},
+		drop: function (event) {
+			$("#flattentimeline").removeClass("draggable").removeClass("drag");
+		}
 	});
 
-	$("#left_menu_item_settings").click(function(event){
+	$("#left_menu_item_settings").click(function (event) {
 		event.stopPropagation();
 		rekall.timeline.pause();
 		$("#popupSettingsSpace").show();
 
-		if(window.app.rekall.Rekall('projectName') && window.app.rekall.Rekall('projectName') != "") {
+		if (window.app.rekall.Rekall('projectName') && window.app.rekall.Rekall('projectName') != "") {
 			$("#popupSettingsTitle").html(window.app.rekall.Rekall('projectName')).removeClass("empty");
 			$("#popupSettingsTitleLabel").show();
 		} else {
@@ -203,7 +184,7 @@ function setEditionControls() {
 			$("#popupSettingsTitleLabel").hide();
 		}
 
-		if(rekall.project.metadata["Author"]!="") {
+		if (rekall.project.metadata["Author"] != "") {
 			$("#popupSettingsAuthor").html(rekall.project.metadata["Author"]).removeClass("empty");
 			$("#popupSettingsAuthorLabel").show();
 		} else {
@@ -211,7 +192,7 @@ function setEditionControls() {
 			$("#popupSettingsAuthorLabel").hide();
 		}
 
-		if(rekall.project.metadata["Email"]!="") {
+		if (rekall.project.metadata["Email"] != "") {
 			$("#popupSettingsEmail").html(rekall.project.metadata["Email"]).removeClass("empty");
 			$("#popupSettingsEmailLabel").show();
 		} else {
@@ -219,7 +200,7 @@ function setEditionControls() {
 			$("#popupSettingsEmailLabel").hide();
 		}
 
-		if(rekall.project.metadata["Comments"]!="") {
+		if (rekall.project.metadata["Comments"] != "") {
 			$("#popupSettingsCredits").html(rekall.project.metadata["Comments"].replace(/\n/gi, "<br/>")).removeClass("empty");
 			$("#popupSettingsCreditsLabel").show();
 		} else {
@@ -227,119 +208,123 @@ function setEditionControls() {
 			$("#popupSettingsCreditsLabel").hide();
 		}
 
-		if(rekall_common.owner.canEdit) {
+		if (rekall_common.owner.canEdit) {
 			$(".empty").show();
 		}
 	});
 
-	$("#popupSettingsTitleDiv").click(function(event){
+	$("#popupSettingsTitleDiv").click(function (event) {
 		event.stopPropagation();
 		closeSettingsInputs();
 		$(this).hide();
-		if(!$("#popupSettingsTitle").hasClass("empty")) $("#popupSettingsTitleInput").val($("#popupSettingsTitle").html());
+		if (!$("#popupSettingsTitle").hasClass("empty")) $("#popupSettingsTitleInput").val($("#popupSettingsTitle").html());
 		$("#popupSettingsTitleInput").show().focus();
 	});
-	$("#popupSettingsAuthorDiv").click(function(event){
+	$("#popupSettingsAuthorDiv").click(function (event) {
 		event.stopPropagation();
 		closeSettingsInputs();
 		$(this).hide();
-		if(!$("#popupSettingsAuthor").hasClass("empty")) $("#popupSettingsAuthorInput").val($("#popupSettingsAuthor").html());
+		if (!$("#popupSettingsAuthor").hasClass("empty")) $("#popupSettingsAuthorInput").val($("#popupSettingsAuthor").html());
 		$("#popupSettingsAuthorInput").show().focus();
 	});
-	$("#popupSettingsEmailDiv").click(function(event){
+	$("#popupSettingsEmailDiv").click(function (event) {
 		event.stopPropagation();
 		closeSettingsInputs();
 		$(this).hide();
-		if(!$("#popupSettingsEmail").hasClass("empty")) $("#popupSettingsEmailInput").val($("#popupSettingsEmail").html());
+		if (!$("#popupSettingsEmail").hasClass("empty")) $("#popupSettingsEmailInput").val($("#popupSettingsEmail").html());
 		$("#popupSettingsEmailInput").show().focus();
 	});
-	$("#popupSettingsCreditsDiv").click(function(event){
+	$("#popupSettingsCreditsDiv").click(function (event) {
 		event.stopPropagation();
 		closeSettingsInputs();
 		$(this).hide();
-		if(!$("#popupSettingsCredits").hasClass("empty")) $("#popupSettingsCreditsInput").val($("#popupSettingsCredits").html());
+		if (!$("#popupSettingsCredits").hasClass("empty")) $("#popupSettingsCreditsInput").val($("#popupSettingsCredits").html());
 		$("#popupSettingsCreditsInput").show().focus();
 	});
 
-	$(".popupSettingsInput").keyup(function(event){
+	$(".popupSettingsInput").keyup(function (event) {
 		event.stopPropagation();
-		if(event.which == 13) {
+		if (event.which == 13) {
 			closeSettingsInputs();
 		}
 	});
 
-	$(".popupSettingsInput").click(function(event){
-			event.stopPropagation();
+	$(".popupSettingsInput").click(function (event) {
+		event.stopPropagation();
 	});
 
-	$("#popupSettingsCreditsInput").unbind( "keyup" );
-	$("#popupSettingsCreditsInput").keyup(function(event){
+	$("#popupSettingsCreditsInput").unbind("keyup");
+	$("#popupSettingsCreditsInput").keyup(function (event) {
 		event.stopPropagation();
 
 		var isEnter = false;
 		if (event.key !== undefined) {
 			if (event.key === 'Enter' && event.altKey) {
-			} else if(event.key === 'Enter') isEnter = true;
+			} else if (event.key === 'Enter') isEnter = true;
 		} else if (event.keyIdentifier !== undefined) {
 			if (event.keyIdentifier === "Enter" && event.altKey) {
-			} else if(event.keyIdentifier === 'Enter') isEnter = true;
+			} else if (event.keyIdentifier === 'Enter') isEnter = true;
 		} else if (event.keyCode !== undefined) {
 			if (event.keyCode === 13 && event.altKey) {
-			} else if(event.keyCode === 13) isEnter = true;
+			} else if (event.keyCode === 13) isEnter = true;
 		}
 
-		if(isEnter == true) {
+		if (isEnter == true) {
 			closeSettingsInputs();
 		}
 	});
 
-	$("#popupSettings").click(function(event){
+	$("#popupSettings").click(function (event) {
 		event.stopPropagation();
 		closeSettingsInputs();
 	});
 
-	$("#popupSettingsSpace").click(function(event){
+	$("#popupSettingsSpace").click(function (event) {
 		event.stopPropagation();
 		closeSettingsPopup();
 	});
 
-	$("#left_menu_item_preview").click(function(event){
+	$("#left_menu_item_preview").click(function (event) {
 		event.stopPropagation();
 		window.open(window.app.rekall.Rekall('getUrl', 'projectPreview'), '_blank');
 	});
 
-	$("#popupSettingsBtnDownloadXml").click(function(event){
+	$("#popupSettingsBtnDownloadXml").click(function (event) {
 		event.stopPropagation();
 		window.open("php/project.php?downloadXML=1", '_self');
 	});
-	$("#popupSettingsBtnDelete").click(function(event){
+	$("#popupSettingsBtnDelete").click(function (event) {
 		event.stopPropagation();
 		openAlert("Do you really want to delete this project ?", "yesnodeleteproject");
 	});
 
-	$("#btn_add_note").click(function(event){
+	$("#btn_add_note").click(function (event) {
 		event.stopPropagation();
 		rekall.timeline.pause();
 		uploadFiles(["New note"]);
 	});
 
-	$("#btn_add_file").mousedown(function(event) {
+	$("#btn_add_file").mousedown(function (event) {
 		rekall.timeline.pause();
 		$("#left_menu_item_btn_addfile").click();
 	});
-	$("#left_menu_item_btn_addfile").change(function(event){
+	$("#left_menu_item_btn_addfile").change(function (event) {
 		event.stopPropagation();
 		uploadFiles($("#left_menu_item_btn_addfile").get(0).files);
 	});
 
-	$("#btn_add_paste").click(function(event) { pubSub.publish('open_paste_modal'); });
-	$("#paste_modal button.cancel").click(function(event) { pubSub.publish('close_paste_modal'); });
+	$("#btn_add_paste").click(function (event) {
+		pubSub.publish('open_paste_modal');
+	});
+	$("#paste_modal button.cancel").click(function (event) {
+		pubSub.publish('close_paste_modal');
+	});
 
-	$("#paste_modal_content").on("paste", function(event) {
+	$("#paste_modal_content").on("paste", function (event) {
 		$('#paste_modal_content').empty();
 	});
 
-	$("#paste_modal button.validate").click(function(event) {
+	$("#paste_modal button.validate").click(function (event) {
 		var img = $("#paste_modal img");
 		if (img.length < 1)
 			return;
@@ -350,78 +335,80 @@ function setEditionControls() {
 		var n = b64data.length;
 		var uar = new Uint8Array(n);
 		while (n--) uar[n] = bin_str.charCodeAt(n);
-		var file = new File([uar], 'pasted_' + Math.floor(Math.random()*100000) + '.' + subtype, {type: type+'/'+subtype});
+		var file = new File([uar], 'pasted_' + Math.floor(Math.random() * 100000) + '.' + subtype, {type: type + '/' + subtype});
 		$('#left_menu_item_btn_addfile').files += file;
 		uploadFiles([file]);
 		pubSub.publish('close_paste_modal');
 	});
 
-	$("#btn_add_link").click(function(event){
+	$("#btn_add_link").click(function (event) {
 		event.stopPropagation();
 		rekall.timeline.pause();
 		$("#popupAddLinkSpace").show();
 		$("#popupAddLinkInput").focus();
 	});
 
-	$("#popupAddLinkButtonCancel").click(function(event){
+	$("#popupAddLinkButtonCancel").click(function (event) {
 		event.stopPropagation();
 		closeAddLinkPopup();
 	});
-	$("#popupAddLinkSpace").click(function(event){
+	$("#popupAddLinkSpace").click(function (event) {
 		event.stopPropagation();
 		closeAddLinkPopup();
 	});
 
-	$("#popupAddLinkButtonOk").click(function(event){
+	$("#popupAddLinkButtonOk").click(function (event) {
 		var myLink = $("#popupAddLinkInput").val();
 		addLink(myLink);
 	});
 
-	$("#popupAddLinkInput").keyup(function(event){
+	$("#popupAddLinkInput").keyup(function (event) {
 		event.stopPropagation();
-		if(event.which == 13) {
+		if (event.which == 13) {
 			var myLink = $("#popupAddLinkInput").val();
 			addLink(myLink);
 		}
 	});
 
-	$('#tab_selector_form').change(function(event) { pubSub.publish(event.target.value) });
+	$('#tab_selector_form').change(function (event) {
+		pubSub.publish(event.target.value)
+	});
 
-	$("#popupEdit").click(function(event){
+	$("#popupEdit").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 	});
 
-	$("#popupNom").click(function(event){
+	$("#popupNom").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$(this).hide();
 		$("#popupNomInput").show().focus();
 	});
 
-	$("#popupTC").click(function(event){
+	$("#popupTC").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$("#popupTC").hide();
 		$("#popupTCedit").show();
 	});
 
-	$(".popupTCeditfield").click(function(event){
+	$(".popupTCeditfield").click(function (event) {
 		event.stopPropagation();
 	});
-	$("#nowTCin").click(function(event){
+	$("#nowTCin").click(function (event) {
 		event.stopPropagation();
 		var timeCurrent = convertToTime(Math.round(rekall.timeline.timeCurrent));
 		$("#popupTCinMin").val(timeCurrent.split(":")[0]);
 		$("#popupTCinSec").val(timeCurrent.split(":")[1]);
 	});
-	$("#nowTCout").click(function(event){
+	$("#nowTCout").click(function (event) {
 		event.stopPropagation();
 		var timeCurrent = convertToTime(Math.round(rekall.timeline.timeCurrent));
 		$("#popupTCoutMin").val(timeCurrent.split(":")[0]);
 		$("#popupTCoutSec").val(timeCurrent.split(":")[1]);
 	});
-	$("#eovTCout").click(function(event){
+	$("#eovTCout").click(function (event) {
 		event.stopPropagation();
 		var endVideo = rekall.videoPlayer.duration();
 		var timeEnd = convertToTime(Math.round(endVideo));
@@ -429,30 +416,30 @@ function setEditionControls() {
 		$("#popupTCoutSec").val(timeEnd.split(":")[1]);
 	});
 
-	$("#TCvalidModif").click(function(event){
+	$("#TCvalidModif").click(function (event) {
 		event.stopPropagation();
 		var keyDoc = $(this).parent().parent().attr("keydoc");
 		var inMin = $("#popupTCinMin").val();
 		var inSec = $("#popupTCinSec").val();
 		var outMin = $("#popupTCoutMin").val();
 		var outSec = $("#popupTCoutSec").val();
-		var TCin = (inMin*60)+(inSec*1);
-		var TCout = (outMin*60)+(outSec*1);
+		var TCin = (inMin * 60) + (inSec * 1);
+		var TCout = (outMin * 60) + (outSec * 1);
 
 		var endVideo = Math.ceil(rekall.videoPlayer.duration());
 
-		if(TCin>TCout) 			openAlert("Start time must be set before end time", "ok");
-		else if(TCout>endVideo) openAlert("End time must not be set after " + convertToTime(endVideo) + " (end of the video)", "ok");
+		if (TCin > TCout) openAlert("Start time must be set before end time", "ok");
+		else if (TCout > endVideo) openAlert("End time must not be set after " + convertToTime(endVideo) + " (end of the video)", "ok");
 		else {
 			setTCFromDom(keyDoc, TCin, TCout);
 
-			$("#popupTCin").html(inMin+":"+inSec);
-			$("#popupTCout").html(outMin+":"+outSec);
+			$("#popupTCin").html(inMin + ":" + inSec);
+			$("#popupTCout").html(outMin + ":" + outSec);
 
 			closeInputs();
 		}
 	});
-	$("#TCinvalidModif").click(function(event){
+	$("#TCinvalidModif").click(function (event) {
 		event.stopPropagation();
 		var TCin = $("#popupTCin").html().split(":");
 		var TCout = $("#popupTCout").html().split(":");
@@ -463,71 +450,71 @@ function setEditionControls() {
 		closeInputs();
 	});
 
-	$("#popupLabels").click(function(event){
+	$("#popupLabels").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$(this).hide();
 		$("#popupLabelsInput").show().focus();
 	});
 
-	$("#popupSpeed").click(function(event){
+	$("#popupSpeed").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$(this).hide();
 		$("#popupSpeedInput").show().focus();
 	});
 
-	$("#popupLegende").click(function(event){
+	$("#popupLegende").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$(this).hide();
 		$("#popupLegendeInput").show().focus();
 	});
 
-	$("#popupAuthor").click(function(event){
+	$("#popupAuthor").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$(this).hide();
 		$("#popupAuthorInput").show().focus();
 	});
 
-	$("#popupLink").click(function(event){
+	$("#popupLink").click(function (event) {
 		event.stopPropagation();
 		closeInputs();
 		$(this).hide();
 		$("#popupLinkInput").show().focus();
 	});
 
-	$(".popupInput").click(function(event){
+	$(".popupInput").click(function (event) {
 		event.stopPropagation();
 	});
 
-	$("#popupNomInput").keyup(function(event){
+	$("#popupNomInput").keyup(function (event) {
 		event.stopPropagation();
-		if(event.which == 13) {
+		if (event.which == 13) {
 			closeInputs();
 		}
 	});
 
-	$("#popupAuthorInput").keyup(function(event){
+	$("#popupAuthorInput").keyup(function (event) {
 		event.stopPropagation();
-		if(event.which == 13) {
+		if (event.which == 13) {
 			closeInputs();
 		}
 	});
 
-	$("#popupLinkInput").keyup(function(event){
+	$("#popupLinkInput").keyup(function (event) {
 		event.stopPropagation();
-		if(event.which == 13) {
+		if (event.which == 13) {
 			closeInputs();
 		}
 	});
 
-	$("#popupSetHighlight").click(function(event){
+	$("#popupSetHighlight").click(function (event) {
 		event.stopPropagation();
 		var keyDoc = $(this).parent().attr("keydoc");
 		var isHL = $(this).attr("isHighlight");
-		if(isHL=="true") {
+		if (isHL == "true") {
 			setMetaFromDom(keyDoc, "Rekall->Highlight", "");
 			$(this).attr("isHighlight", "false").removeClass("selected");
 			$("#popupEdit").removeClass("highlightPopup");
@@ -539,45 +526,14 @@ function setEditionControls() {
 		}
 	});
 
-	$("#popupEditSupprimer").click(function(){
+	$("#popupEditSupprimer").click(function () {
 		openAlert("Do you really want to delete this file from the project ?", "yesnodelete");
 	});
 
-  $("#playback_speed_form").change(function(event) {
-    console.debug("set playback rate to " + event.target.value);
-    window.app.rekall.Rekall().videoPlayer.playbackRate(Number(event.target.value));
-  });
-
-  $('#editAnnotationPic').click(function(ev) {
-    $('#edit_pic_modal').show();
-    var keyDoc = ev.target.parentElement.parentElement.attributes['keydoc'];
-    var markerArea = new markerjs2.MarkerArea(document.getElementById('annotation_img_edit'));
-    markerArea.targetRoot = document.getElementById('edit_pic_modal');
-    markerArea.addEventListener('render', (event) => {
-      var state = markerArea.getState();
-      window.my_current_markerjs_data_in_ugly_global = state;
-      markerArea.close(true);
-      setMetaFromDom(keyDoc.value, "Rekall->MarkerjsState", btoa(JSON.stringify(state)));
-
-      var b64img = event.dataUrl;
-      var data = b64img.match(/data:([a-z]+)\/([a-z]+);base64,(.*)/);
-      var type = data[1], subtype = data[2], b64data = data[3];
-      var bin_str = atob(b64data);
-      var n = b64data.length;
-      var uar = new Uint8Array(n);
-      while (n--) uar[n] = bin_str.charCodeAt(n);
-      var file = new File([uar], keyDoc.value.substr(1), {type: type+'/'+subtype});
-      $('#left_menu_item_btn_addfile').files = [file];
-      uploadFiles([file], {'edited': 1});
-      $('#popupImg').attr('src', $('#popupImg').attr('src') + '&time=' + performance.now());
-    });
-    markerArea.addEventListener('close', () => $('#edit_pic_modal').hide());
-    markerArea.renderAtNaturalSize = true;
-    markerArea.show();
-    if (window.my_current_markerjs_data_in_ugly_global)
-      markerArea.restoreState(window.my_current_markerjs_data_in_ugly_global);
-
-  })
+	$("#playback_speed_form").change(function (event) {
+		console.debug("set playback rate to " + event.target.value);
+		window.app.rekall.Rekall().videoPlayer.playbackRate(Number(event.target.value));
+	});
 }
 
 function closeSettingsPopup() {
@@ -655,6 +611,10 @@ function openAlert(message, buttons) {
 	}
 }
 
+function openAlertWrapper(message, data){
+	openAlert(data.message, data.buttons);
+}
+
 function closeAlert() {
 	$("#popupAlertMessage").html("");
 	$(".popupAlertButton").hide();
@@ -729,382 +689,41 @@ function closeSettingsInputs() {
 }
 
 function closeInputs() {
-	$.each($(".popupInput"), function() {
-		if($(this).css("display") != "none") {
-			if($(this).attr("id")=="popupNomInput") {
-
-				var keyDoc = $(this).parent().attr("keydoc");
-				var newName = $(this).val().trim();
-				$(this).val(newName);
-				setMetaFromDom(keyDoc, "Rekall->Name", newName);
-
-				if(newName!="") $("#popupNom").html(newName).removeClass("empty");
-				else $("#popupNom").html("+ Add a name").addClass("empty");
-
-			} else if($(this).attr("id")=="popupLegendeInput") {
-
-				var keyDoc = $(this).parent().attr("keydoc");
-				var newComment = $(this).val().trim();
-				$(this).val(newComment);
-				setMetaFromDom(keyDoc, "Rekall->Comments", newComment.replace(/\n/gi, "<br/>"));
-
-				if(newComment!="") $("#popupLegende").html(newComment.replace(/\n/gi, "<br/>")).removeClass("empty");
-				else $("#popupLegende").html("+ Add a comment").addClass("empty");
-
-			} else if($(this).attr("id")=="popupSpeedInput") {
-
-				var keyDoc = $(this).parent().attr("keydoc");
-				var newSpeed = $('#popupSpeedInput input:checked').val();
-				setMetaFromDom(keyDoc, "Rekall->Speed", newSpeed);
-
-			} else if($(this).attr("id")=="popupLabelsInput") {
-
-				var keyDoc = $(this).parent().attr("keydoc");
-        var newLabels = '';
-        $('input.annotation_labels:checked').toArray().forEach(e => newLabels += e.value + ';')
-        setMetaFromDom(keyDoc, "Rekall->Labels", newLabels);
-
-			} else if($(this).attr("id")=="popupAuthorInput") {
-
-				var keyDoc = $(this).parent().attr("keydoc");
-				var newAuthor = $(this).val().trim();
-				$(this).val(newAuthor);
-				setMetaFromDom(keyDoc, "Rekall->Author", newAuthor);
-
-				if(newAuthor!="") $("#popupAuthor").html(newAuthor).removeClass("empty");
-				else $("#popupAuthor").html("+ Add an author").addClass("empty");
-
-			} else if($(this).attr("id")=="popupLinkInput") {
-
-				var keyDoc = $(this).parent().attr("keydoc");
-				var newLink = $(this).val().trim();
-
-				if(newLink!="") if(newLink.indexOf("http")!=0) newLink = "http://"+newLink;
-
-				$(this).val(newLink);
-				setMetaFromDom(keyDoc, "Rekall->Link", newLink);
-
-				if(newLink!="") {
-          if(rekall_common.owner.canEdit) $("#popupLink").html(newLink).removeClass("empty");
-					else $("#popupLink").html("<a href='"+newLink+"' target='_blank'>"+newLink+"</a>").removeClass("empty");
-				}
-				else $("#popupLink").html("+ Add a link").addClass("empty");
-
-			} else if($(this).attr("id")=="popupTCedit") {
-				var keyDoc = $("#popupRight").attr("keydoc");
-				var inMin = $("#popupTCinMin").val();
-				var inSec = $("#popupTCinSec").val();
-				var outMin = $("#popupTCoutMin").val();
-				var outSec = $("#popupTCoutSec").val();
-				var TCin = (inMin*60)+(inSec*1);
-				var TCout = (outMin*60)+(outSec*1);
-
-				var endVideo = Math.ceil(rekall.videoPlayer.duration());
-
-				var isReturn = true;
-
-				if((inMin>=120)||(inSec>=60)||(outMin>=120)||(outSec>=60)||(inMin<0)||(inSec<0)||(outMin<0)||(outSec<0)) openAlert("Invalid time code", "ok");
-				else if(TCin>TCout) 			openAlert("Start time must be set before end time", "ok");
-				else if(TCout>endVideo)			openAlert("End time must not be set after " + convertToTime(endVideo) + " (end of the video)", "ok");
-				else {
-					setTCFromDom(keyDoc, TCin, TCout);
-					$("#popupTCin").html(inMin+":"+inSec);
-					$("#popupTCout").html(outMin+":"+outSec);
-					isReturn = false;
-				}
-				if(isReturn) return true;
-			}
-		}
-	});
-	$(".popupInput:not(.popupInputNoHide)").hide();
+	popupPanel.ensureLastOpenedEditionClosed();
 	$(".popupRightItem").show();
-	$("#popupTC").show();
-	$("#popupTCedit").hide();
 }
 
 function closeEdit() {
-	closeInputs();
-	var isPaused = $("#popupEdit").attr("isPaused");
-	if(isPaused=="false") rekall.timeline.play();
-	$("#popupSpace").hide();
-	$("#popupEdit").hide();
+	popupPanel.close();
+	window.app.rekall.Rekall('pubSub').publish('popupEdit.Closed', {});
 }
 
 function openVideo() {
-		$('#mosaic_tab').hide();
-		$('#video_tab').show();
-		$('#left_menu_controls').show();
+	window.app.rekall.Rekall('mosaicWidget').hide();
+	$('#video_tab').show();
+	$('#left_menu_controls').show();
 }
 
 function openMosaic() {
-    function getMosaicItem(tagOrDoc) {
-        let path = Utils.getPreviewPath(tagOrDoc);
-        let name = tagOrDoc.getMetadata("Rekall->Name");
-        let url ='';
-        if ('undefined' === typeof path) {
-          let [reg, type] = tagOrDoc.getMetadata("Rekall->Type").split('/');
-          if (reg === 'rekall') {
-            if (type === 'marker') type = 'note';
-          }
-          url = "../shared/css/images/img-"+type+".png";
-        } else {
-            url = path;
-        }
-        let div=$('<div/>').addClass('mosaic_item').on('click', function() {tagOrDoc.openPopupEdit();});
-        div.append($('<img/>').attr('src', url).attr('onerror', "this.src='../shared/css/images/img-document.png';"));
-        div.append($('<span/>').addClass('caption').text(name));
-        div.attr('data-rekall-labels', '');
-        div.attr('data-rekall-labels', tagOrDoc.getMetadata('Rekall->Labels'));
-        return div;
-    }
-    function getFilterElement(text, color, callback, css_class) {
-        return $('<div/>')
-            .addClass('mosaic_filter_item')
-            .addClass(css_class)
-            .on('click', callback)
-            .css('background-color', color)
-            .append($('<h2/>').text(text));
-    }
-
-
-		rekall.timeline.pause();
-		$('#mosaic_tab').show();
-		$('#video_tab').hide();
-		$('#left_menu_controls').hide();
-
-    let container = $('#mosaic_tab');
-    container.html('');
-    let filterdiv = $('<div/>').addClass('mosaic_filter');
-    filterdiv.append(getFilterElement(
-        'Reset filters',
-        'rgb(100,100,100)',
-        function () {
-            $('.mosaic_filter_item').removeClass('mosaic_filter_disabled');
-            $('.mosaic_category').show();
-        },
-        'mosaic_filter_item_all'
-    ));
-    container.append(filterdiv);
-    var labels = new Set();
-    // TODO is there a better way to iterate over tags or documents ?
-    for ( let [k, v] of  Object.entries(rekall.sortings.colors.categories)) {
-        let categoryName = rekall.sortings.colors.getCategoryName(k)
-        let category = $('<div/>').addClass('mosaic_category').css('background', getTagGradientColor(v));
-
-        let grid = $('<div/>').addClass('mosaic_category_grid');
-
-        category.append($('<h2/>').text(categoryName));
-        category.append(grid);
-
-        filterdiv.append(getFilterElement(
-            categoryName,
-            v.color,function() {
-                category.toggle();
-                $(this).toggleClass('mosaic_filter_disabled');
-            },
-            ''
-        ));
-
-        for (let i in v.tags){
-            grid.append(getMosaicItem(v.tags[i]));
-            var current_labels = v.tags[i].getMetadata('Rekall->Labels');
-            if (current_labels && current_labels != '') {
-              current_labels.split(';').forEach(l => {if (l != '') labels.add(l)});
-            }
-        }
-        container.append(category);
-    }
-    labels.forEach(l => {
-      var button = $('<p/>').html(l);
-      button.my_state = undefined;
-      button.addClass('mosaic_label_filter');
-      button.click(ev => {
-        if (typeof(ev.target.my_state) === 'undefined') ev.target.my_state = true;
-        else ev.target.my_state = !ev.target.my_state;
-        if (ev.target.my_state) $(ev.target).addClass('mosaic_label_filter_enabled');
-        else $(ev.target).removeClass('mosaic_label_filter_enabled');
-        var labels = new Set();
-        $('.mosaic_label_filter').toArray().forEach(f => {
-          if (f.my_state == true)
-            labels.add(f.innerHTML);
-        });
-        if (!labels.size) {
-          $('.mosaic_label_filter').toArray().forEach(f => {
-            f.my_state = undefined;
-            $(f).removeClass('mosaic_label_filter_enabled');
-          });
-        }
-        console.debug('filters: ', labels);
-        $('.mosaic_item').toArray().forEach(t => {
-          if (!labels.size || Array.from(labels).map(l => t.dataset.rekallLabels.indexOf(l) != -1).includes(true))
-            $(t).show();
-          else
-            $(t).hide();
-        });
-      })
-      filterdiv.append(button);
-    });
-}
-
-function getTagGradientColor(tag) {
-	var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;  // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
-	var isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
-	var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;   // At least Safari 3+: "[object HTMLElementConstructor]"
-	var isChrome = !!window.chrome && !isOpera;              // Chrome 1+
-	var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
-
-    if(isOpera) {
-        return  "-o-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, "+tag.color+" 100%)";
-    }
-    if(isFirefox) {
-        return "-moz-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, "+tag.color+" 100%)";
-    }
-    if((isSafari)||(isChrome)){
-        return "-webkit-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, "+tag.color+" 100%)";
-    }
+	$('#video_tab').hide();
+	$('#left_menu_controls').hide();
+    window.app.rekall.Rekall('mosaicWidget').show();
 }
 
 function fillPopupEdit(tag) {
 
-	var isPaused = rekall.timeline.isPaused();
-	rekall.timeline.pause();
-
-	$("#popupEdit").attr("isPaused",isPaused);
-	$("#popupTC").css("background",tag.color);
-
-	var bgColorLeft = tag.color.replace(/rgb/g, "rgba").replace(/\)/g, ",.35)");
-
-  bgColorLeft = getTagGradientColor(tag);
-
-	$("#popupLeft").css("background",bgColorLeft);
-
-  var type = tag.getMetadata("Rekall->Type");
-
-  $("#popupImg").unbind( "click" );
-	if(tag.isMarker()==true){
-		$("#popupImg").show();
-		$("#popupImg").attr("src","../shared/css/images/img-note.png");
-	} else {
-		if(tag.thumbnail.url){
-			$("#popupImg").attr("src", tag.getDownloadLink() || tag.thumbnail.url);
-      $('#annotation_img_edit').attr('src', tag.getDownloadLink(true));
-		} else {
-			if(type.indexOf("image") > -1) $("#popupImg").attr("src","../shared/css/images/img-image.png")
-			else if(type.indexOf("pdf") > -1) $("#popupImg").attr("src","../shared/css/images/img-pdf.png")
-			else if(type.indexOf("audio") > -1) $("#popupImg").attr("src","../shared/css/images/img-music.png")
-			else if(type.indexOf("vcard") > -1) $("#popupImg").attr("src","../shared/css/images/img-user.png")
-			else if(type.indexOf("video") > -1) $("#popupImg").attr("src","../shared/css/images/img-video.png")
-			else if(type.indexOf("msword") > -1) $("#popupImg").attr("src","../shared/css/images/img-word.png")
-			else if(type.indexOf("link") > -1) $("#popupImg").attr("src","../shared/css/images/img-link.png")
-			else $("#popupImg").attr("src","../shared/css/images/img-document.png");
-		}
-		$("#popupImg").click(function(event){
-			event.stopPropagation();
-			if(tag.isLink()) {
-				if(link != "") window.open(link,'_blank');
-			}
-			else
-				window.open($('#popupImg')[0].src, '_blank');
-		});
-	}
-
-	$("#popupNom").css("color",tag.color);
-
-  window.my_current_markerjs_data_in_ugly_global = null;
-  $('#editAnnotationPic').hide();
-  $('#linkToOriginalImage').hide();
-  if (type.split('/')[0] == 'image') {
-    $('#editAnnotationPic').html('edit');
-    $('#editAnnotationPic').show();
-    $('#linkToOriginalImage').show();
-    $('#linkToOriginalImage').unbind('click');
-    $('#linkToOriginalImage').click(() => window.open(tag.getDownloadLink(true), '_blank'));
-    var data = tag.getMetadata('Rekall->MarkerjsState');
-    if (data)
-      window.my_current_markerjs_data_in_ugly_global = JSON.parse(atob(data));
-  }
-
-	$("#popupType").html(type).css("color",tag.color);
-
-	var name = tag.getMetadata("Rekall->Name");
-	if(name!="") $("#popupNom").html(name).removeClass("empty");
-	else $("#popupNom").html("+ Add a name").addClass("empty");
-
-	var startVerb = convertToTime(tag.getTimeStart());
-	$("#popupTCin").html(startVerb);
-
-	var endVerb = convertToTime(tag.getTimeEnd());
-	$("#popupTCout").html(endVerb);
-
-	var speed = tag.getMetadata("Rekall->Speed");
-  if (!speed || speed == '')
-    speed = 1;
-  $('#popupSpeedInput input').attr('checked', false);
-  $('#popupSpeedInput input[value="'+speed+'"]').attr('checked', true);
-
-	var labels = tag.getMetadata("Rekall->Labels");
-  $('input.annotation_labels').attr('checked', false);
-  if (labels && labels != '') {
-    var _labels = labels.split(';');
-    $('input.annotation_labels').toArray().forEach(e => {
-      e.checked = (_labels.indexOf(e.value) != -1);
-    })
-  }
-
-	var comments = tag.getMetadata("Rekall->Comments");
-	if((comments)&&(comments!="")) $("#popupLegende").html(comments).removeClass("empty");
-	else $("#popupLegende").html("+ Add a comment").addClass("empty");
-
-	var author = tag.getMetadata("Rekall->Author");
-	if((author)&&(author!="")) $("#popupAuthor").html(author).removeClass("empty");
-	else $("#popupAuthor").html("+ Add an author").addClass("empty");
-
-	var link = tag.getMetadata("Rekall->Link");
-	if((link)&&(link!="")) {
-		if(rekall_common.owner.canEdit) $("#popupLink").html(link).removeClass("empty");
-		else $("#popupLink").html("<a href='"+link+"' target='_blank'>"+link+"</a>").removeClass("empty");
-	}
-	else $("#popupLink").html("+ Add a link").addClass("empty");
+	popupPanel.createPopupContent(tag);
 
 	if(rekall_common.owner.canEdit) {
-		if(tag.isMarker()==true) $("#popupEditSupprimer").html("Delete Note");
-		else $("#popupEditSupprimer").html("Delete File");
-
 		$(".empty").show();
 		$(".displayMode").hide();
-		$("#popupNomInput").val(tag.getMetadata("Rekall->Name"));
-
-		$("#popupTCinMin").val(startVerb.split(":")[0]);
-		$("#popupTCinSec").val(startVerb.split(":")[1]);
-
-		$("#popupTCoutMin").val(endVerb.split(":")[0]);
-		$("#popupTCoutSec").val(endVerb.split(":")[1]);
-
-		$("#popupLegendeInput").val(""+comments.replace(/<br\/>/gi, '\n'));
-		$("#popupAuthorInput").val(""+author);
-		$("#popupLinkInput").val(""+link);
-		$("#popupSpeedInput").val(""+speed);
-
-		var highlight = tag.getMetadata("Rekall->Highlight");
-		if(highlight=="true") {
-			$("#popupSetHighlight").attr("isHighlight","true").addClass("selected");
-			$("#popupEdit").addClass("highlightPopup");
-		}
-		else {
-			$("#popupSetHighlight").attr("isHighlight","false").removeClass("selected");
-			$("#popupEdit").removeClass("highlightPopup");
-		}
 	} else {
 		$(".empty").hide();
 		$(".editmode").hide();
 		$(".displayMode").show();
 	}
 
-	$("#popupLeft") .attr("keydoc", tag.document.key);
-	$("#popupRight").attr("keydoc", tag.document.key);
-
-	$("#popupSpace").show();
-	$("#popupEdit").show();
+	popupPanel.show();
 }
 
 function convertToTime(seconds) {
@@ -1171,6 +790,14 @@ function setMetaFromDom(keyDoc, metaType, meta) {
 	});
 }
 
+function onMetaFromDomUpdated(message, data){
+	setMetaFromDom(data.tag.document.key, data.metadata, data.tag.getMetadata(data.metadata));
+}
+
+function onTcUpdated(message, data){
+	setTCFromDom(data.tag.document.key, data.tag.getTimeStart(), data.tag.getTimeEnd());
+}
+
 function setTCFromDom(keyDoc, TCin, TCout) {
 	rouletteStart();
 	$.ajax("php/project.php", {
@@ -1209,12 +836,12 @@ function deleteFromDom(keyDoc) {
 
 function deleteFromDomFinished() {
 	closeAlert();
-	closeEdit();
+	popupPanel.close();
 }
 
 //Gestion d'upload
 var filesToUpload = [], fileIsUploading = false;
-function uploadFiles(files, additionnal_post_data={}) {
+function uploadFiles(files, additionnal_post_data={}, callBack = undefined) {
 	$.each(files, function(index, file) {
 		var formData = new FormData();
 
@@ -1251,7 +878,7 @@ function uploadFiles(files, additionnal_post_data={}) {
 		formData.append("author",       rekall_common.owner.author);
 		formData.append("locationGps",  rekall_common.owner.locationGps);
 		formData.append("locationName", rekall_common.owner.locationName);
-    Object.keys(additionnal_post_data).forEach(k => formData.append(k, additionnal_post_data[k]));
+        Object.keys(additionnal_post_data).forEach(k => formData.append(k, additionnal_post_data[k]));
 
 		if(formData != undefined) {
 			filesToUpload.push({
@@ -1287,6 +914,9 @@ function uploadFiles(files, additionnal_post_data={}) {
 
 					rouletteEnd();
 					uploadFilesNext();
+					if(callBack && typeof callBack === 'function'){
+						callBack();
+					}
 				},
 				error: function(data) {
 					openAlert("Uploading error. Try again.");
@@ -1295,6 +925,9 @@ function uploadFiles(files, additionnal_post_data={}) {
 
 					rouletteEnd();
 					uploadFilesNext();
+					if(callBack && typeof callBack === 'function'){
+						callBack();
+					}
 				}
 			});
 			uploadFilesNext();
@@ -1374,3 +1007,63 @@ $(window).trigger("resize");
 function openPasteModal() { $('#paste_modal').css('display', 'flex'); $('#paste_modal_content').first().focus() }
 function closePasteModal() { $('#paste_modal').hide(); $('#paste_modal_content').empty();  }
 
+function openImageEdition(message, tag) {
+	$('#edit_pic_modal').show();
+	let keyDoc = tag.document.key;
+	let annotationEdit = $('#annotation_img_edit')[0];
+	$('#annotation_img_edit').attr('src', tag.getDownloadLink(true));
+
+	function loadImage(url, elem) {
+		return new Promise((resolve, reject) => {
+			elem.onload = () => resolve(elem);
+			elem.onerror = reject;
+			elem.src = url;
+		});
+	}
+	function openMakerArea() {
+		// eslint-disable-next-line no-empty
+		let markerArea = new markerjs2.MarkerArea($('#annotation_img_edit')[0]);
+		let imageState;
+
+		if (tag.getMetadata("Rekall->MarkerjsState") && tag.getMetadata("Rekall->MarkerjsState").trim().length) {
+			imageState = JSON.parse(atob(tag.getMetadata("Rekall->MarkerjsState")));
+		}
+
+		markerArea.targetRoot = document.getElementById('edit_pic_modal');
+		markerArea.addEventListener('render', (event) => {
+			imageState = markerArea.getState();
+			markerArea.close(true);
+			tag.setMetadata("Rekall->MarkerjsState", btoa(JSON.stringify(imageState)));
+
+			let b64img = event.dataUrl;
+			let data = b64img.match(/data:([a-z]+)\/([a-z]+);base64,(.*)/);
+			let type = data[1], subtype = data[2], b64data = data[3];
+			let bin_str = atob(b64data);
+			let n = b64data.length;
+			let uar = new Uint8Array(n);
+			while (n--) uar[n] = bin_str.charCodeAt(n);
+			let file = new File([uar], keyDoc.substr(1), {type: type + '/' + subtype});
+			$('#left_menu_item_btn_addfile').files = [file];
+			uploadFiles([file], {'edited': 1}, function () {
+				$('#popupImg').attr('src', tag.getDownloadLink());
+			});
+			setMetaFromDom(tag.document.key, "Rekall->MarkerjsState", tag.getMetadata('Rekall->MarkerjsState'));
+		});
+
+		markerArea.addEventListener('close', () => {
+			$('#edit_pic_modal').hide();
+		});
+		markerArea.renderAtNaturalSize = true;
+		markerArea.show();
+		if (imageState)
+			markerArea.restoreState(imageState);
+	}
+
+	async function changeImageSrc() {
+		await loadImage(tag.getDownloadLink(true), annotationEdit);
+		openMakerArea();
+	}
+
+	// noinspection JSIgnoredPromiseFromCall
+	changeImageSrc();
+}
diff --git a/capsule-prototype/js/rekall/Document.js b/capsule-prototype/js/rekall/Document.js
index f06c3ba2a28ebff72c67ad5c36c079a063531700..cb4a3d73cf87af6d07efa22660cd078f05ac9a3b 100644
--- a/capsule-prototype/js/rekall/Document.js
+++ b/capsule-prototype/js/rekall/Document.js
@@ -21,14 +21,19 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-function Document() {
+function Document(data) {
+	NotificationEntityChanged.call(this, {});
 	this.tags      		   = new Array();
 	this.metadatas		   = new Array();
 	this.currentVersion    = 0;
 	this.goodVersion       = 0;
 	this.setMetadata("Rekall->Group", "");
 	this.setMetadata("Rekall->Visibility", "");
+	this.project = data.project;
 }
+
+Document.prototype = Object.create(NotificationEntityChanged.prototype);
+
 Document.prototype.addTag = function(tag) {
 	this.tags.push(tag);
 }
@@ -47,6 +52,7 @@ Document.prototype.getMetadatas = function(version) {
 Document.prototype.setMetadatas = function(metadatas, version) {
 	version = this.initMetadatas();
 	this.metadatas[version] = metadatas;
+	this.notifyChange();
 }
 Document.prototype.isVideoOrAudio = function(version) {
 	return this.isAudio(version) || this.isVideo(version); 
@@ -88,13 +94,19 @@ Document.prototype.isLink = function(version) {
 }
 Document.prototype.getDownloadLink = function(original = false) {
   var path = Utils.getLocalFilePath(this, "file");
-  return original ? path.replace('file.php?r=', 'file.php?r=rk_original_af7ef02e_') : path;
+  return original ? path.replace('file.php?r=', 'file.php?r=rk_original_af7ef02e_') : path + '&timegeneration=' + performance.now();
 }
 Document.prototype.getFileName = function() {
   var path = Utils.getLocalFilePath(this, "file");
   return path.replace(/.*r=(.*)/, (f, m) => m);
 }
+Document.prototype.getLabels = function() {
+	let labels = this.project.labels;
+	if (!labels)
+		return [];
 
+	return labels;
+}
 
 
 Document.availableMetadataKeys = new Object();
@@ -120,6 +132,9 @@ Document.prototype.setMetadata = function(metadataKey, metadataValue, version) {
 			Document.availableMetadataKeys[metadataKey] = {count: 0};
 		Document.availableMetadataKeys[metadataKey].count++;
 	}
+	if(currentValue != metadataValue) {
+		this.notifyChange();
+	}
 	return (currentValue != metadataValue);
 }
 
@@ -152,7 +167,7 @@ Document.prototype.addVersion = function(document) {
 		for (var index in this.tags) {
 			var tag = this.tags[index];
 			if(tag.version == thisVersion) {
-				var newTag = new Tag(this);
+				var newTag = TagFactory(this);
 				newTag.timeStart = tag.getTimeStart();
 				newTag.timeEnd   = tag.getTimeEnd();
 				this.addTag(newTag);
diff --git a/capsule-prototype/js/rekall/Sorting.js b/capsule-prototype/js/rekall/Sorting.js
index 614f7e0ee9dd43f4d7dde03e6d566d3af5ab635b..0829e4c9785b9ae314889a5cf39bf9584fdaa2c4 100644
--- a/capsule-prototype/js/rekall/Sorting.js
+++ b/capsule-prototype/js/rekall/Sorting.js
@@ -108,9 +108,9 @@ Sorting.prototype.analyseStart = function(tags) {
 		this.valMin = 0;     
 	else {
 		this.categoriesIndex = 0;
-		for (var key in this.categories) {
+		for (let key in this.categories) {
 			this.categories[key].shoudBeRemoved = true;
-			this.categories[key].tags = new Array();
+			this.categories[key].tags = [];
 		}
 	}
 }
diff --git a/capsule-prototype/js/rekall/Source.js b/capsule-prototype/js/rekall/Source.js
index 7a1e631cb3b436f6ec9c31372231c814c2b96bcc..58080ed262002951a4cff5f97f867416f6d5b4a3 100644
--- a/capsule-prototype/js/rekall/Source.js
+++ b/capsule-prototype/js/rekall/Source.js
@@ -35,7 +35,7 @@ Source.prototype.addDocument = function(document) {
 
 	if(this.documents[document.key] == undefined) {
 		if(document.tags.length == 0) {
-			var tag = new Tag(document);
+			var tag = TagFactory(document);
 			document.addTag(tag);
 		}
 		document.metadatas[-1] = document.cloneMetadatas();
diff --git a/capsule-prototype/js/shared.js b/capsule-prototype/js/shared.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb434ceb51e5245850616d856cc33b81429d2a50
--- /dev/null
+++ b/capsule-prototype/js/shared.js
@@ -0,0 +1,17 @@
+function getTagGradientColor(tag) {
+    var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;  // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
+    var isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
+    var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;   // At least Safari 3+: "[object HTMLElementConstructor]"
+    var isChrome = !!window.chrome && !isOpera;              // Chrome 1+
+    var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
+
+    if (isOpera) {
+        return "-o-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + tag.color + " 100%)";
+    }
+    if (isFirefox) {
+        return "-moz-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + tag.color + " 100%)";
+    }
+    if ((isSafari) || (isChrome)) {
+        return "-webkit-linear-gradient(right bottom,  rgba(20,46,51,1) 0%, " + tag.color + " 100%)";
+    }
+}
\ No newline at end of file
diff --git a/capsule-prototype/php/upload.php b/capsule-prototype/php/upload.php
index 69de6b99bccabd7f334473ad56e905efa2f9f542..307ca5ee8b9cc60fa7856190c22347465234a075 100644
--- a/capsule-prototype/php/upload.php
+++ b/capsule-prototype/php/upload.php
@@ -62,7 +62,7 @@
 							//echo "Upload de ".$fileinfo."\t".$filename."...";
 							if(move_uploaded_file($_FILES[$fileinfo]['tmp_name'], $uploadFolder.$filename)) {
                 if (!file_exists($uploadFolder.'rk_original_af7ef02e_'.$filename) && $_POST['edited'] != 1) {
-                  copy($uploadFolder.$filename, $uploadFolder.'original_'.$filename);
+                  copy($uploadFolder.$filename, $uploadFolder.'rk_original_af7ef02e_'.$filename);
                   $metasAdded = addFileToProject($uploadFolder.$filename, $metas, $tcIn, $tcOut);
                   $key = $metasAdded["key"];
                   unset($metasAdded["key"]);