Display other users' cursor

pull/1/head
yflory 5 years ago
parent 0674f410f5
commit 4690392cd9

@ -2,6 +2,7 @@
@import (reference) "../../customize/src/less2/include/framework.less"; @import (reference) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/tools.less"; @import (reference) "../../customize/src/less2/include/tools.less";
@import (reference) "../../customize/src/less2/include/markdown.less"; @import (reference) "../../customize/src/less2/include/markdown.less";
@import (reference) "../../customize/src/less2/include/avatar.less";
// body // body
&.cp-app-kanban { &.cp-app-kanban {
@ -99,6 +100,16 @@
flex-flow: column; flex-flow: column;
overflow: hidden; overflow: hidden;
} }
#cp-kanban-edit-conflicts {
padding: 5px;
background: #eee;
color: @cryptpad_text_col;
font-size: 14px;
.cp-kanban-cursors {
margin-top: 5px;
}
margin-bottom: 5px;
}
#cp-kanban-edit-body { #cp-kanban-edit-body {
border: 1px solid @colortheme_modal-input; border: 1px solid @colortheme_modal-input;
.CodeMirror { .CodeMirror {
@ -154,12 +165,30 @@
padding: 5px; padding: 5px;
} }
.cp-kanban-cursors {
&:empty { display: none; }
order: 2;
width: 100%;
&> span {
display: inline-block;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
margin-right: 5px;
.tools_unselectable();
cursor: default;
}
}
.kanban-item { .kanban-item {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 5px; padding: 5px;
flex-wrap: wrap; flex-wrap: wrap;
.cp-kanban-cursors {
margin-top: 10px;
}
.kanban-item-body, .kanban-item-tags { .kanban-item-body, .kanban-item-tags {
.tools_unselectable(); .tools_unselectable();
width: 100%; width: 100%;
@ -216,6 +245,7 @@
} }
header { header {
display: flex; display: flex;
flex-wrap: wrap;
align-items: center; align-items: center;
padding: 5px 10px; padding: 5px 10px;
.kanban-title-board { .kanban-title-board {

@ -8,6 +8,7 @@ define([
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/modes.js', '/common/modes.js',
'/customize/messages.js', '/customize/messages.js',
'/common/hyperscript.js', '/common/hyperscript.js',
@ -38,6 +39,7 @@ define([
Util, Util,
Hash, Hash,
UI, UI,
UIElements,
Modes, Modes,
Messages, Messages,
h, h,
@ -51,6 +53,8 @@ define([
var verbose = function (x) { console.log(x); }; var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging verbose = function () {}; // comment out to enable verbose logging
var onRedraw = Util.mkEvent(); var onRedraw = Util.mkEvent();
var onCursorUpdate = Util.mkEvent();
var remoteCursors = {};
Messages.kanban_title = "Title"; // XXX Messages.kanban_title = "Title"; // XXX
Messages.kanban_body = "Body"; // XXX Messages.kanban_body = "Body"; // XXX
@ -59,6 +63,7 @@ define([
Messages.kanban_delete = "Delete"; // XXX Messages.kanban_delete = "Delete"; // XXX
Messages.kanban_tags = "Filter tags"; // XXX Messages.kanban_tags = "Filter tags"; // XXX
Messages.kanban_noTags = "No tags"; // XXX Messages.kanban_noTags = "No tags"; // XXX
Messages.kanban_conflicts = "Currently editing:"; // XXX
// XXX // XXX
// Conflicts // Conflicts
@ -83,6 +88,46 @@ define([
input.selectionEnd = selects[1]; input.selectionEnd = selects[1];
}; };
var getTextColor = function (hex) {
if (hex && /^#/.test(hex)) { hex = hex.slice(1); }
if (!/^[0-9a-f]{6}$/i.test(hex)) {
return '#000000';
}
var r = parseInt(hex.slice(0,2), 16);
var g = parseInt(hex.slice(2,4), 16);
var b = parseInt(hex.slice(4,6), 16);
if ((r*0.213 + g*0.715 + b*0.072) > 255/2) {
return '#000000';
}
return '#FFFFFF';
};
var getAvatar = function (cursor, noClear) {
// Tippy
var html = '<span class="cp-cursor-avatar">';
if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) {
html += UIElements.getAvatar(cursor.avatar);
}
html += cursor.name + '</span>';
var l = (cursor.name || Messages.anonymous).slice(0,1).toUpperCase();
var text = '';
if (cursor.color) {
text = 'color:'+getTextColor(cursor.color)+';';
}
var avatar = h('span.cp-cursor.cp-tippy-html', {
style: "background-color: " + (cursor.color || 'red') + ";"+text,
title: html
}, l);
if (!noClear) {
cursor.clear = function () {
$(avatar).remove();
};
}
return avatar;
};
var getExistingTags = function (boards) { var getExistingTags = function (boards) {
var tags = []; var tags = [];
boards = boards || {}; boards = boards || {};
@ -112,8 +157,12 @@ define([
addEditItemButton(framework, kanban); addEditItemButton(framework, kanban);
}; };
if (editModal) { return editModal; } if (editModal) { return editModal; }
var titleInput, tagsDiv, colors, text; var conflicts, conflictContainer, titleInput, tagsDiv, colors, text;
var content = h('div', [ var content = h('div', [
conflictContainer = h('div#cp-kanban-edit-conflicts', [
h('div', Messages.kanban_conflicts),
conflicts = h('div.cp-kanban-cursors')
]),
h('label', {for:'cp-kanban-edit-title'}, Messages.kanban_title), h('label', {for:'cp-kanban-edit-title'}, Messages.kanban_title),
titleInput = h('input#cp-kanban-edit-title'), titleInput = h('input#cp-kanban-edit-title'),
h('label', {for:'cp-kanban-edit-body'}, Messages.kanban_body), h('label', {for:'cp-kanban-edit-body'}, Messages.kanban_body),
@ -126,6 +175,27 @@ define([
colors = h('div#cp-kanban-edit-colors'), colors = h('div#cp-kanban-edit-colors'),
]); ]);
var $conflict = $(conflicts);
var $cc = $(conflictContainer);
var conflict = {
setValue: function () {
$conflict.empty();
var i = 0;
$cc.hide();
Object.keys(remoteCursors).forEach(function (nid) {
var c = remoteCursors[nid];
var avatar = getAvatar(c, true);
if (Number(c.item) === Number(id) || Number(c.board) === Number(id)) {
$conflict.append(avatar);
i++;
}
});
if (!i) { return; }
$cc.show();
}
};
// Title // Title
var $title = $(titleInput); var $title = $(titleInput);
$title.on('change keyup', function () { $title.on('change keyup', function () {
@ -248,11 +318,17 @@ define([
isBoard = _isBoard; isBoard = _isBoard;
id = _id; id = _id;
if (_isBoard) { if (_isBoard) {
onCursorUpdate.fire({
board: _id
});
dataObject = kanban.getBoardJSON(id); dataObject = kanban.getBoardJSON(id);
$(content) $(content)
.find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]') .find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]')
.hide(); .hide();
} else { } else {
onCursorUpdate.fire({
item: _id
});
dataObject = kanban.getItemJSON(id); dataObject = kanban.getItemJSON(id);
$(content) $(content)
.find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]') .find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]')
@ -287,6 +363,7 @@ define([
className: 'primary', className: 'primary',
name: Messages.filePicker_close, name: Messages.filePicker_close,
onClick: function () { onClick: function () {
onCursorUpdate.fire({});
}, },
keys: [] keys: []
}]; }];
@ -310,6 +387,7 @@ define([
return; return;
} }
// Not deleted, apply updates // Not deleted, apply updates
editModal.conflict.setValue();
PROPERTIES.forEach(function (type) { PROPERTIES.forEach(function (type) {
editModal[type].setValue(dataObject[type], true); editModal[type].setValue(dataObject[type], true);
}); });
@ -321,7 +399,8 @@ define([
title: title, title: title,
body: body, body: body,
tags: tags, tags: tags,
color: color color: color,
conflict: conflict
}; };
}; };
var getItemEditModal = function (framework, kanban, eid) { var getItemEditModal = function (framework, kanban, eid) {
@ -331,6 +410,7 @@ define([
var boards = kanban.options.boards || {}; var boards = kanban.options.boards || {};
var item = (boards.items || {})[eid]; var item = (boards.items || {})[eid];
if (!item) { return void UI.warn(Messages.error); } if (!item) { return void UI.warn(Messages.error); }
editModal.conflict.setValue();
PROPERTIES.forEach(function (type) { PROPERTIES.forEach(function (type) {
if (!editModal[type]) { return; } if (!editModal[type]) { return; }
editModal[type].setValue(item[type]); editModal[type].setValue(item[type]);
@ -345,6 +425,7 @@ define([
var boards = kanban.options.boards || {}; var boards = kanban.options.boards || {};
var board = (boards.data || {})[id]; var board = (boards.data || {})[id];
if (!board) { return void UI.warn(Messages.error); } if (!board) { return void UI.warn(Messages.error); }
editModal.conflict.setValue();
BOARD_PROPERTIES.forEach(function (type) { BOARD_PROPERTIES.forEach(function (type) {
if (!editModal[type]) { return; } if (!editModal[type]) { return; }
editModal[type].setValue(board[type]); editModal[type].setValue(board[type]);
@ -482,6 +563,12 @@ define([
} }
var eid = $(el).attr('data-eid'); var eid = $(el).attr('data-eid');
kanban.inEditMode = eid; kanban.inEditMode = eid;
setTimeout(function () {
// Make sure the click is sent after the "blur" in case we move from a card to another
onCursorUpdate.fire({
item: eid
});
});
var name = $(el).text(); var name = $(el).text();
$(el).html(''); $(el).html('');
@ -500,8 +587,9 @@ define([
kanban.onChange(); kanban.onChange();
// Unlock edit mode // Unlock edit mode
kanban.inEditMode = false; kanban.inEditMode = false;
onCursorUpdate.fire({});
}; };
$input.blur(save); //$input.blur(save);
$input.keydown(function (e) { $input.keydown(function (e) {
if (e.which === 13) { if (e.which === 13) {
e.preventDefault(); e.preventDefault();
@ -515,10 +603,7 @@ define([
if (e.which === 27) { if (e.which === 27) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$(el).text(name); save();
kanban.inEditMode = false;
addEditItemButton(framework, kanban);
return;
} }
}); });
$input.on('change keyup', function () { $input.on('change keyup', function () {
@ -540,6 +625,12 @@ define([
} }
var boardId = $(el).closest('.kanban-board').attr("data-id"); var boardId = $(el).closest('.kanban-board').attr("data-id");
kanban.inEditMode = boardId; kanban.inEditMode = boardId;
setTimeout(function () {
// Make sure the click is sent after the "blur" in case we move from a card to another
onCursorUpdate.fire({
board: boardId
});
});
var name = $(el).text(); var name = $(el).text();
$(el).html(''); $(el).html('');
@ -559,6 +650,7 @@ define([
kanban.onChange(); kanban.onChange();
// Unlock edit mode // Unlock edit mode
kanban.inEditMode = false; kanban.inEditMode = false;
onCursorUpdate.fire({});
}; };
$input.blur(save); $input.blur(save);
$input.keydown(function (e) { $input.keydown(function (e) {
@ -571,8 +663,7 @@ define([
if (e.which === 27) { if (e.which === 27) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$(el).text(name); save();
kanban.inEditMode = false;
return; return;
} }
}); });
@ -604,6 +695,7 @@ define([
var save = function () { var save = function () {
$item.remove(); $item.remove();
kanban.inEditMode = false; kanban.inEditMode = false;
onCursorUpdate.fire({});
if (!$input.val()) { return; } if (!$input.val()) { return; }
var id = Util.createRandomInteger(); var id = Util.createRandomInteger();
var item = { var item = {
@ -630,6 +722,7 @@ define([
e.stopPropagation(); e.stopPropagation();
$item.remove(); $item.remove();
kanban.inEditMode = false; kanban.inEditMode = false;
onCursorUpdate.fire({});
return; return;
} }
}); });
@ -638,6 +731,9 @@ define([
return DiffMd.render(md, true, false); return DiffMd.render(md, true, false);
}, },
addItemButton: true, addItemButton: true,
getTextColor: getTextColor,
getAvatar: getAvatar,
cursors: remoteCursors,
boards: boards boards: boards
}); });
@ -664,9 +760,6 @@ define([
}); });
var $container = $('#cp-app-kanban-content'); var $container = $('#cp-app-kanban-content');
$container[0].onclick = function (e) {
console.warn(e);
};
var $cContainer = $('#cp-app-kanban-container'); var $cContainer = $('#cp-app-kanban-container');
var addControls = function () { var addControls = function () {
// Quick or normal mode // Quick or normal mode
@ -986,6 +1079,47 @@ define([
kanban = initKanban(framework); kanban = initKanban(framework);
}); });
var myCursor = {};
onCursorUpdate.reg(function (data) {
myCursor = data;
framework.updateCursor();
});
framework.onCursorUpdate(function (data) {
if (!data) { return; }
var id = data.id;
// Clear existing cursor
if (remoteCursors[id] && remoteCursors[id].clear) {
remoteCursors[id].clear();
}
delete remoteCursors[id];
var cursor = data.cursor;
if (data.leave || !cursor) { return; }
if (!cursor.item && !cursor.board) { return; }
// Add new cursor
var avatar = getAvatar(cursor);
var $item = $('.kanban-item[data-eid="'+cursor.item+'"]');
var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
if ($item.length) {
remoteCursors[id] = cursor;
$item.find('.cp-kanban-cursors').append(avatar);
return;
}
if ($board.length) {
remoteCursors[id] = cursor;
$board.find('header .cp-kanban-cursors').append(avatar);
}
});
framework.onCursorUpdate(function () {
if (!editModal || !editModal.conflict) { return; }
editModal.conflict.setValue();
});
framework.setCursorGetter(function () {
return myCursor;
});
framework.start(); framework.start();
}; };

@ -56,6 +56,9 @@
items: {}, items: {},
list: [] list: []
}, },
getAvatar: function () {},
getTextColor: function () { return '#000'; },
cursors: {},
tags: [], tags: [],
dragBoards: true, dragBoards: true,
addItemButton: false, addItemButton: false,
@ -351,19 +354,6 @@
} }
}; };
var getTextColor = function (hex) {
if (!/^[0-9a-f]{6}$/.test(hex)) {
return '#000000';
}
var r = parseInt(hex.slice(0,2), 16);
var g = parseInt(hex.slice(2,4), 16);
var b = parseInt(hex.slice(4,6), 16);
if ((r*0.213 + g*0.715 + b*0.072) > 255/2) {
return '#000000';
}
return '#FFFFFF';
};
var getElementNode = function (element) { var getElementNode = function (element) {
var nodeItem = document.createElement('div'); var nodeItem = document.createElement('div');
nodeItem.classList.add('kanban-item'); nodeItem.classList.add('kanban-item');
@ -374,10 +364,18 @@
nodeItem.classList.add('cp-kanban-palette-'+element.color); nodeItem.classList.add('cp-kanban-palette-'+element.color);
} else { } else {
// Hex color code // Hex color code
var textColor = getTextColor(element.color); var textColor = self.options.getTextColor(element.color);
nodeItem.setAttribute('style', 'background-color:#'+element.color+';color:'+textColor+';'); nodeItem.setAttribute('style', 'background-color:#'+element.color+';color:'+textColor+';');
} }
} }
var nodeCursors = document.createElement('div');
nodeCursors.classList.add('cp-kanban-cursors');
Object.keys(self.options.cursors).forEach(function (id) {
var c = self.options.cursors[id];
if (Number(c.item) !== Number(element.id)) { return; }
var el = self.options.getAvatar(c);
nodeCursors.appendChild(el);
});
var nodeItemText = document.createElement('div'); var nodeItemText = document.createElement('div');
nodeItemText.classList.add('kanban-item-text'); nodeItemText.classList.add('kanban-item-text');
nodeItemText.dataset.eid = element.id; nodeItemText.dataset.eid = element.id;
@ -413,6 +411,7 @@
}); });
nodeItem.appendChild(nodeTags); nodeItem.appendChild(nodeTags);
} }
nodeItem.appendChild(nodeCursors);
//add function //add function
nodeItem.clickfn = element.click; nodeItem.clickfn = element.click;
nodeItem.dragfn = element.drag; nodeItem.dragfn = element.drag;
@ -482,10 +481,11 @@
headerBoard.classList.add("kanban-header-" + board.color); headerBoard.classList.add("kanban-header-" + board.color);
} else { } else {
// Hex color code // Hex color code
var textColor = getTextColor(board.color); var textColor = self.options.getTextColor(board.color);
headerBoard.setAttribute('style', 'background-color:#'+board.color+';color:'+textColor+';'); headerBoard.setAttribute('style', 'background-color:#'+board.color+';color:'+textColor+';');
} }
} }
titleBoard = document.createElement('div'); titleBoard = document.createElement('div');
titleBoard.classList.add('kanban-title-board'); titleBoard.classList.add('kanban-title-board');
titleBoard.innerText = board.title; titleBoard.innerText = board.title;
@ -494,6 +494,16 @@
__onboardTitleClickHandler(titleBoard); __onboardTitleClickHandler(titleBoard);
headerBoard.appendChild(titleBoard); headerBoard.appendChild(titleBoard);
var nodeCursors = document.createElement('div');
nodeCursors.classList.add('cp-kanban-cursors');
Object.keys(self.options.cursors).forEach(function (id) {
var c = self.options.cursors[id];
if (Number(c.board) !== Number(board.id)) { return; }
var el = self.options.getAvatar(c);
nodeCursors.appendChild(el);
});
headerBoard.appendChild(nodeCursors);
//content board //content board
var contentBoard = document.createElement('main'); var contentBoard = document.createElement('main');
contentBoard.classList.add('kanban-drag'); contentBoard.classList.add('kanban-drag');

Loading…
Cancel
Save