var cards = {}; var totalcolumns = 0; var columns = []; var currentTheme = "bigcards"; var boardInitialized = false; var keyTrap = null; var baseurl = location.pathname.substring(0, location.pathname.lastIndexOf('/')); var socket = io.connect({path: baseurl + "/socket.io"}); moment.locale(navigator.language || navigator.languages[0]); marked.setOptions({sanitize: true}); //an action has happened, send it to the //server function sendAction(a, d) { //console.log('--> ' + a); var message = { action: a, data: d }; socket.json.send(message); } socket.on('connect', function() { //console.log('successful socket.io connect'); //let the final part of the path be the room name var room = location.pathname.substring(location.pathname.lastIndexOf('/')); //imediately join the room which will trigger the initializations sendAction('joinRoom', room); }); socket.on('disconnect', function() { blockUI("Serveur déconnecté. Veuillez rafraîchir la page pour essayer de vous reconnecter…"); //$('.blockOverlay').click($.unblockUI); }); socket.on('message', function(data) { getMessage(data); }); function unblockUI() { $.unblockUI({fadeOut: 50}); } function blockUI(message) { message = message || 'En attente…'; $.blockUI({ message: message, css: { border: 'none', padding: '15px', backgroundColor: '#000', '-webkit-border-radius': '10px', '-moz-border-radius': '10px', opacity: 0.5, color: '#fff', fontSize: '20px' }, fadeOut: 0, fadeIn: 10 }); } //respond to an action event function getMessage(m) { var message = m; //JSON.parse(m); var action = message.action; var data = message.data; //console.log('<-- ' + action); switch (action) { case 'roomAccept': //okay we're accepted, then request initialization //(this is a bit of unnessary back and forth but that's okay for now) sendAction('initializeMe', null); break; case 'roomDeny': //this doesn't happen yet break; case 'moveCard': moveCard($("#" + data.id), data.position); break; case 'initCards': initCards(data); break; case 'createCard': //console.log(data); drawNewCard(data.id, data.text, data.x, data.y, data.rot, data.colour, null); break; case 'deleteCard': $("#" + data.id).fadeOut(500, function() { $(this).remove(); } ); break; case 'editCard': $("#" + data.id).children('.content:first').attr('data-text', data.value); $("#" + data.id).children('.content:first').html(marked(data.value)); break; case 'initColumns': initColumns(data); break; case 'updateColumns': initColumns(data); break; case 'changeTheme': changeThemeTo(data); break; case 'join-announce': displayUserJoined(data.sid, data.user_name); break; case 'leave-announce': displayUserLeft(data.sid); break; case 'initialUsers': displayInitialUsers(data); break; case 'nameChangeAnnounce': updateName(message.data.sid, message.data.user_name); break; case 'addSticker': addSticker(message.data.cardId, message.data.stickerId); break; case 'setBoardSize': resizeBoard(message.data); break; case 'export': download(message.data.filename, message.data.text); break; case 'addRevision': addRevision(message.data); break; case 'deleteRevision': $('#revision-'+message.data).remove(); break; case 'initRevisions': $('#revisions-list').empty(); for (var i = 0; i < message.data.length; i++) { addRevision(message.data[i]); } break; default: //unknown message alert('action inconnue : ' + JSON.stringify(message)); break; } } $(document).bind('keyup', function(event) { keyTrap = event.which; }); function drawNewCard(id, text, x, y, rot, colour, sticker, animationspeed) { //cards[id] = {id: id, text: text, x: x, y: y, rot: rot, colour: colour}; var h = '
\ \ \
' + marked(text) + '
\
'; var card = $(h); card.appendTo('#board'); $("#" + id).children('.content:first').attr('data-text', text); //@TODO //Draggable has a bug which prevents blur event //http://bugs.jqueryui.com/ticket/4261 //So we have to blur all the cards and editable areas when //we click on a card //The following doesn't work so we will do the bug //fix recommended in the above bug report // card.click( function() { // $(this).focus(); // } ); card.draggable({ snap: false, snapTolerance: 5, containment: [0, 0, 2000, 2000], stack: ".card", start: function(event, ui) { keyTrap = null; }, drag: function(event, ui) { if (keyTrap == 27) { ui.helper.css(ui.originalPosition); return false; } }, handle: "div.content" }); //After a drag: card.bind("dragstop", function(event, ui) { if (keyTrap == 27) { keyTrap = null; return; } var data = { id: this.id, position: ui.position, oldposition: ui.originalPosition, }; sendAction('moveCard', data); }); card.children(".droppable").droppable({ accept: '.sticker', drop: function(event, ui) { var stickerId = ui.draggable.attr("id"); var cardId = $(this).parent().attr('id'); addSticker(cardId, stickerId); var data = { cardId: cardId, stickerId: stickerId }; sendAction('addSticker', data); //remove hover state to everything on the board to prevent //a jquery bug where it gets left around $('.card-hover-draggable').removeClass('card-hover-draggable'); }, hoverClass: 'card-hover-draggable' }); var speed = Math.floor(Math.random() * 1000); if (typeof(animationspeed) != 'undefined') speed = animationspeed; var startPosition = $("#create-card").position(); card.css('top', startPosition.top - card.height() * 0.5); card.css('left', startPosition.left - card.width() * 0.5); card.animate({ left: x + "px", top: y + "px" }, speed); card.hover( function() { $(this).addClass('hover'); $(this).children('.card-icon').fadeIn(10); }, function() { $(this).removeClass('hover'); $(this).children('.card-icon').fadeOut(150); } ); card.children('.card-icon').hover( function() { $(this).addClass('card-icon-hover'); }, function() { $(this).removeClass('card-icon-hover'); } ); card.children('.delete-card-icon').click( function() { $("#" + id).remove(); //notify server of delete sendAction('deleteCard', { 'id': id }); } ); card.children('.content').editable(function(value, settings) { $("#" + id).children('.content:first').attr('data-text', value); onCardChange(id, value); return (marked(value)); }, { type: 'textarea', data: function() { return $("#" + id).children('.content:first').attr('data-text'); }, submit: 'OK', style: 'inherit', cssclass: 'card-edit-form', placeholder: 'Double cliquez pour m’éditer', onblur: 'submit', event: 'dblclick', //event: 'mouseover' }); //add applicable sticker if (sticker !== null) addSticker(id, sticker); } function onCardChange(id, text) { sendAction('editCard', { id: id, value: text }); } function moveCard(card, position) { card.animate({ left: position.left + "px", top: position.top + "px" }, 500); } function addSticker(cardId, stickerId) { stickerContainer = $('#' + cardId + ' .filler'); if (stickerId === "nosticker") { stickerContainer.html(""); return; } if (Array.isArray(stickerId)) { for (var i in stickerId) { stickerContainer.prepend(''); } } else { if (stickerContainer.html().indexOf(stickerId) < 0) stickerContainer.prepend(''); } } //---------------------------------- // cards //---------------------------------- function createCard(id, text, x, y, rot, colour) { drawNewCard(id, text, x, y, rot, colour, null); var action = "createCard"; var data = { id: id, text: text, x: x, y: y, rot: rot, colour: colour }; sendAction(action, data); } function randomCardColour() { var colours = ['yellow', 'green', 'blue', 'white']; var i = Math.floor(Math.random() * colours.length); return colours[i]; } function initCards(cardArray) { //first delete any cards that exist $('.card').remove(); cards = cardArray; for (var i in cardArray) { card = cardArray[i]; drawNewCard( card.id, card.text, card.x, card.y, card.rot, card.colour, card.sticker, 0 ); } boardInitialized = true; unblockUI(); } //---------------------------------- // cols //---------------------------------- function drawNewColumn(columnName) { var cls = "col"; if (totalcolumns === 0) { cls = "col first"; } $('#icon-col').before('

' + columnName + '

'); $('.editable').editable(function(value, settings) { onColumnChange(this.id, value); return (value); }, { style: 'inherit', cssclass: 'card-edit-form', type: 'textarea', placeholder: 'Nouveau', onblur: 'submit', width: '', height: '', xindicator: '', event: 'dblclick', //event: 'mouseover' }); $('.col:last').fadeIn(1500); totalcolumns++; } function onColumnChange(id, text) { var names = Array(); //console.log(id + " " + text ); //Get the names of all the columns right from the DOM $('.col').each(function() { //get ID of current column we are traversing over var thisID = $(this).children("h2").attr('id'); if (id == thisID) { names.push(text); } else { names.push($(this).text()); } }); updateColumns(names); } function displayRemoveColumn() { if (totalcolumns <= 0) return false; $('.col:last').fadeOut(150, function() { $(this).remove(); } ); totalcolumns--; } function createColumn(name) { if (totalcolumns >= 8) return false; drawNewColumn(name); columns.push(name); var action = "updateColumns"; var data = columns; sendAction(action, data); } function deleteColumn() { if (totalcolumns <= 0) return false; displayRemoveColumn(); columns.pop(); var action = "updateColumns"; var data = columns; sendAction(action, data); } function updateColumns(c) { columns = c; var action = "updateColumns"; var data = columns; sendAction(action, data); } function deleteColumns(next) { //delete all existing columns: $('.col').fadeOut('slow', next()); } function initColumns(columnArray) { totalcolumns = 0; columns = columnArray; $('.col').remove(); for (var i in columnArray) { column = columnArray[i]; drawNewColumn( column ); } } function changeThemeTo(theme) { currentTheme = theme; $("link[title=cardsize]").attr("href", "css/" + theme + ".css"); } ////////////////////////////////////////////////////////// ////////// NAMES STUFF /////////////////////////////////// ////////////////////////////////////////////////////////// function setCookie(c_name, value, exdays) { var exdate = new Date(); exdate.setDate(exdate.getDate() + exdays); var c_value = escape(value) + ((exdays === null) ? "" : "; expires=" + exdate.toUTCString()); document.cookie = c_name + "=" + c_value; } function getCookie(c_name) { var i, x, y, ARRcookies = document.cookie.split(";"); for (i = 0; i < ARRcookies.length; i++) { x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("=")); y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1); x = x.replace(/^\s+|\s+$/g, ""); if (x == c_name) { return unescape(y); } } } function setName(name) { sendAction('setUserName', name); setCookie('scrumscrum-username', name, 365); } function displayInitialUsers(users) { for (var i in users) { //console.log(users); displayUserJoined(users[i].sid, users[i].user_name); } } function displayUserJoined(sid, user_name) { name = ''; if (user_name) name = user_name; else name = sid.substring(0, 5); $('#names-ul').append('
  • ' + name + '
  • '); } function displayUserLeft(sid) { name = ''; if (name) name = user_name; else name = sid; var id = '#user-' + sid.toString(); $('#names-ul').children(id).fadeOut(1000, function() { $(this).remove(); }); } function updateName(sid, name) { var id = '#user-' + sid.toString(); $('#names-ul').children(id).text(name); } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// function boardResizeHappened(event, ui) { var newsize = ui.size; sendAction('setBoardSize', newsize); } function resizeBoard(size) { $(".board-outline").animate({ height: size.height, width: size.width }); } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// function calcCardOffset() { var offsets = {}; $(".card").each(function() { var card = $(this); $(".col").each(function(i) { var col = $(this); if (col.offset().left + col.outerWidth() > card.offset().left + card.outerWidth() || i === $(".col").size() - 1) { offsets[card.attr('id')] = { col: col, x: ((card.offset().left - col.offset().left) / col.outerWidth()) }; return false; } }); }); return offsets; } //moves cards with a resize of the Board //doSync is false if you don't want to synchronize //with all the other users who are in this room function adjustCard(offsets, doSync) { $(".card").each(function() { var card = $(this); var offset = offsets[this.id]; if (offset) { var data = { id: this.id, position: { left: offset.col.position().left + (offset.x * offset.col .outerWidth()), top: parseInt(card.css('top').slice(0, -2)) }, oldposition: { left: parseInt(card.css('left').slice(0, -2)), top: parseInt(card.css('top').slice(0, -2)) } }; //use .css() instead of .position() because css' rotate //console.log(data); if (!doSync) { card.css('left', data.position.left); card.css('top', data.position.top); } else { //note that in this case, data.oldposition isn't accurate since //many moves have happened since the last sync //but that's okay becuase oldPosition isn't used right now moveCard(card, data.position); sendAction('moveCard', data); } } }); } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// function download(filename, text) { var element = document.createElement('a'); var mime = 'text/plain'; if (filename.match(/.csv$/)) { mime = 'text/csv'; } element.setAttribute('href', 'data:'+mime+';charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } function addRevision(timestamp) { var li = $('
  • '); var s1 = $(''); var s2 = $('delete revision'); if (typeof(timestamp) === 'string') { timestamp = parseInt(timestamp); } s1.text(moment(timestamp).format('LLLL')); li.append(s1); li.append(s2); $('#revisions-list').append(li); s1.click(function() { socket.json.send({ action: 'exportRevision', data: timestamp }); }); s2.click(function() { socket.json.send({ action: 'deleteRevision', data: timestamp }); }); } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// $(function() { //disable image dragging //window.ondragstart = function() { return false; }; if (boardInitialized === false) blockUI(''); //setTimeout($.unblockUI, 2000); $("#create-card") .click(function() { var rotation = Math.random() * 10 - 5; //add a bit of random rotation (+/- 10deg) uniqueID = Math.round(Math.random() * 99999999); //is this big enough to assure uniqueness? //alert(uniqueID); createCard( 'card' + uniqueID, '', 58, $('div.board-outline').height(), // hack - not a great way to get the new card coordinates, but most consistant ATM rotation, randomCardColour()); }); // Style changer $("#smallify").click(function() { if (currentTheme == "bigcards") { changeThemeTo('smallcards'); } else if (currentTheme == "smallcards") { changeThemeTo('bigcards'); } /*else if (currentTheme == "nocards") { currentTheme = "bigcards"; $("link[title=cardsize]").attr("href", "css/bigcards.css"); }*/ sendAction('changeTheme', currentTheme); return false; }); $('#icon-col').hover( function() { $('.col-icon').fadeIn(10); }, function() { $('.col-icon').fadeOut(150); } ); $('#add-col').click( function() { createColumn('Nouveau'); return false; } ); $('#delete-col').click( function() { deleteColumn(); return false; } ); // $('#cog-button').click( function(){ // $('#config-dropdown').fadeToggle(); // } ); // $('#config-dropdown').hover( // function(){ /*$('#config-dropdown').fadeIn()*/ }, // function(){ $('#config-dropdown').fadeOut() } // ); // var user_name = getCookie('scrumscrum-username'); $("#yourname-input").focus(function() { if ($(this).val() == 'anonyme') { $(this).val(""); } $(this).addClass('focused'); }); $("#yourname-input").blur(function() { if ($(this).val() === "") { $(this).val('anonyme'); } $(this).removeClass('focused'); setName($(this).val()); }); $("#yourname-input").val(user_name); $("#yourname-input").blur(); $("#yourname-li").hide(); $("#yourname-input").keypress(function(e) { code = (e.keyCode ? e.keyCode : e.which); if (code == 10 || code == 13) { $(this).blur(); } }); $(".sticker").draggable({ revert: true, zIndex: 1000 }); $(".board-outline").resizable({ ghost: false, minWidth: 700, minHeight: 400, maxWidth: 3200, maxHeight: 1800, }); //A new scope for precalculating (function() { var offsets; $(".board-outline").bind("resizestart", function() { offsets = calcCardOffset(); }); $(".board-outline").bind("resize", function(event, ui) { adjustCard(offsets, false); }); $(".board-outline").bind("resizestop", function(event, ui) { boardResizeHappened(event, ui); adjustCard(offsets, true); }); })(); $('#marker').draggable({ axis: 'x', containment: 'parent' }); $('#eraser').draggable({ axis: 'x', containment: 'parent' }); $('#export-txt').click(function() { socket.json.send({ action: 'exportTxt', data: ($('.col').length !== 0) ? $('.col').css('width').replace('px', '') : null }); }) $('#export-csv').click(function() { socket.json.send({ action: 'exportCsv', data: ($('.col').length !== 0) ? $('.col').css('width').replace('px', '') : null }); }) $('#export-json').click(function() { socket.json.send({ action: 'exportJson', data: { width: $('.board-outline').css('width').replace('px', ''), height: $('.board-outline').css('height').replace('px', '') } }); }) $('#import-file').click(function(evt) { evt.stopPropagation(); evt.preventDefault(); var f = $('#import-input').get(0).files[0]; var fr = new FileReader(); fr.onloadend = function() { var text = fr.result; socket.json.send({ action: 'importJson', data: JSON.parse(text) }); }; fr.readAsBinaryString(f); }) $('#create-revision').click(function() { socket.json.send({ action: 'createRevision', data: { width: $('.board-outline').css('width').replace('px', ''), height: $('.board-outline').css('height').replace('px', '') } }); }) }); /** Doubleclick on mobile + Layout Framemo with tabs **/ $(document).ready(function(){ if(window.location.href != window.location.protocol+'//'+window.location.host+'/') { // Not on homepage /** Double click on mobile interface **/ var clickTimer = null; var clickTarget = null; var editTarget = null; function doubletapCards( selector ) { $(selector+' .stickertarget').addClass('doubletap'); // Escape multi bound $(selector+' .doubletap').on('click', function() { clickTarget = selector.replace('#',''); if (clickTimer == null) { clickTimer = setTimeout(function () { clickTimer = null; }, 1000) } else { //console.log('doubleclick : '+clickTimer+':'+editTarget); clearTimeout(clickTimer); clickTimer = null; if(editTarget == clickTarget && clickTarget !== undefined && clickTarget !== null) { $('#'+clickTarget.replace('content:','')+' .doubletap').trigger('dblclick'); } } editTarget = clickTarget; }); } function doubletapTitle( selector ) { $(selector).addClass('doubletap'); // Escape multi bound $(selector+'.doubletap').on('click', function() { clickTarget = selector.replace('#',''); if (clickTimer == null) { clickTimer = setTimeout(function () { clickTimer = null; }, 1000) } else { //console.log('doubleclick : '+clickTimer+':'+editTarget); clearTimeout(clickTimer); clickTimer = null; if(editTarget == clickTarget && clickTarget !== undefined && clickTarget !== null) { $('#'+clickTarget+'.doubletap').trigger('dblclick'); } } editTarget = clickTarget; }); } setInterval(function() { // Add periodically the doubletap event on new cards $('.stickertarget:not(.doubletap)').each(function(){ doubletapCards('#'+$(this).attr('id').replace('content:','')); }); $('#board-table .col h2:not(.doubletap)').each(function(){ doubletapTitle('#'+$(this).attr('id')); }); }, 500); /** Layout Framemo - Tabs **/ $('.board-outline') .before( '' ) .wrap( '
    ' ) .css('margin','auto'); $('.names').css({'margin':'auto','width':'auto'}).addClass('pull-right'); $('#scrumblr') .append($('.names, .stickers, .buttons')) .after( '
    '+ '
    '+ '
    ' ); $('#export-import').append($('.export, .import')); $('#revisions').append($('.revisions')); $('#about').append($('#tuto-faq, #le-logiciel, #jardin')); // Style $('#create-card').addClass('vert fa-3x').css('opacity','1'); $('#smallify').removeClass('fa-expand').addClass('fa-search-minus').on('click',function(){ if (currentTheme == "bigcards") { $(this).removeClass('fa-search-plus').addClass('fa-search-minus'); } else { $(this).removeClass('fa-search-minus').addClass('fa-search-plus'); } }) $('#full-page').on('click', function(){ if ($('.container.ombre').length) { $(this).children('i').removeClass('fa-expand').addClass('fa-compress'); $('.container.ombre').removeClass('container').addClass('container-fluid'); } else { $(this).children('i').removeClass('fa-compress').addClass('fa-expand'); $('.container-fluid.ombre').removeClass('container-fluid').addClass('container'); } }) $('main hr').hide(); /** Mode iframe **/ if(top.location!=self.document.location) { $('#full-page').hide().trigger('click'); $('main hr, header').hide(); } } });