function toggleFullScreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
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 choosed = $("#choose-card-color").val()
if (choosed !== "random") {
return choosed
}
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").length - 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 = $('
')
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 () {
$('.nav-tabs a[href="#scrumblr"]').tab("show")
socket.json.send({
action: "exportTxt",
data: $(".col").length !== 0 ? $(".col").css("width").replace("px", "") : null,
})
})
$("#export-csv").click(function () {
$('.nav-tabs a[href="#scrumblr"]').tab("show")
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.readAsText(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 **/
// Defaut board real size (not 'auto' or 'inherit') saved in database
// in order to be able to center it
var boardReady = setInterval(function () {
if (boardInitialized) {
// when board is ready
if ($(".board-outline").attr("style") === undefined) {
// check if size is imported from db
$(".board-outline").css({
width: $(".board-outline.ui-resizable").width() + 16 + "px",
height: "466px",
})
var data = {}
data.size = {
height: 466,
width: $(".board-outline.ui-resizable").width() + 16,
}
boardResizeHappened("resizestop", data) // using scrumblr function that keep size in db after a resize
}
clearInterval(boardReady)
}
}, 500)
$(".names").css({ margin: "auto", width: "auto" }).addClass("pull-right")
$("#scrumblr")
.append($(".names, .stickers, .buttons"))
.after(
'' +
'' +
'' +
''
)
$("#export-import").append($(".export, .import"))
$("#share").append($(".share"))
$("#revisions").append($(".revisions"))
$("#about").append($("#tuto-faq, #le-logiciel, #jardin"))
// Style
$("#create-card").addClass("vert fa-3x").css("opacity", "1")
$("#smallify").on("click", function () {
if (currentTheme == "bigcards") {
$(this).children("i").removeClass("fa-search-plus").addClass("fa-search-minus")
} else {
$(this).children("i").removeClass("fa-search-minus").addClass("fa-search-plus")
}
})
$("#full-page").on("click", function () {
if ($(this).children("i").hasClass("fa-expand")) {
$(this).children("i").removeClass("fa-expand").addClass("fa-compress")
$("#header-bar, .credits").hide()
} else {
$(this).children("i").removeClass("fa-compress").addClass("fa-expand")
$("#header-bar, .credits").show()
}
toggleFullScreen()
})
/** Mode iframe **/
if (top.location != self.document.location) {
$("#header-bar, .credits").hide()
}
// put URL in share input
$("#taburl").val(location)
// copy URL to clipboard
$("#copyurl").click(function (e) {
e.preventDefault()
var node = document.getElementById("taburl")
node.disabled = null
node.select()
var success = document.execCommand("copy")
if (success) {
getSelection().removeAllRanges()
node.disabled = "disabled"
alert("URL du tableau copiée dans votre presse-papier !")
} else {
alert(
"Impossible de copier l'URL du tableau dans votre presse-papier. Veuillez copier son adresse manuellement (Ctrl+C)."
)
}
})
}
})
//Close with Escape Button
window.onkeydown = function (event) {
if (event.keyCode === 27) {
document.getElementById("close").click()
}
}
function go() {
var value = document.forms[0].elements["name"].value
value = value.replace(/[\/\?]/g, "")
window.location.href = value
return false
}
$(function () {
var headerBarUrl = $("#header-bar").data("url")
if (headerBarUrl) {
var getJSON = function (url, callback) {
var xhr = new XMLHttpRequest()
xhr.open("GET", url, true)
xhr.responseType = "json"
xhr.onload = function () {
var status = xhr.status
if (status === 200) {
callback(null, xhr.response)
} else {
callback(status, xhr.response)
}
}
xhr.send()
}
getJSON(headerBarUrl, function (err, data) {
if (err !== null) {
console.log("Something went wrong: " + err)
} else {
document.getElementById("header-bar").innerHTML = data.markup
var styleElement = document.createElement("style")
styleElement.innerHTML = data.style
document.getElementById("header-bar").appendChild(styleElement)
}
})
}
})