Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

pull/1/head
yflory 8 years ago
commit 33e19d5918

@ -12,6 +12,8 @@ define(function() {
*/ */
config.notificationTimeout = 5000; config.notificationTimeout = 5000;
config.enablePinning = true;
config.whiteboardPalette = [ config.whiteboardPalette = [
'#000000', // black '#000000', // black
'#FFFFFF', // white '#FFFFFF', // white

@ -17,7 +17,7 @@
"scripts": { "scripts": {
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .", "lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
"test": "node TestSelenium.js", "test": "node TestSelenium.js",
"style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css", "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css && lessc ./www/whiteboard/whiteboard.less > ./www/whiteboard/whiteboard.css",
"template": "cd customize.dist/src && node build.js" "template": "cd customize.dist/src && node build.js"
} }
} }

@ -144,16 +144,11 @@ var getChannelList = function (store, publicKey, cb) {
pins[pin] = false; pins[pin] = false;
}); });
if (!parsed[1] || parsed[1].length) { if (parsed[1] && parsed[1].length) {
break;
}
else {
parsed[1].forEach(function (channel) { parsed[1].forEach(function (channel) {
pins[channel] = true; pins[channel] = true;
}); });
break;
} }
break; break;
default: default:
console.error('invalid message read from store'); console.error('invalid message read from store');

@ -6,9 +6,11 @@ define([
'/bower_components/alertifyjs/dist/js/alertify.js', '/bower_components/alertifyjs/dist/js/alertify.js',
'/common/clipboard.js', '/common/clipboard.js',
'/customize/application_config.js', '/customize/application_config.js',
'/common/pinpad.js', /* TODO
load pinpad dynamically only after you know that it will be needed */
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
], function (Config, Messages, Store, Crypto, Alertify, Clipboard, AppConfig) { ], function (Config, Messages, Store, Crypto, Alertify, Clipboard, Pinpad, AppConfig) {
/* This file exposes functionality which is specific to Cryptpad, but not to /* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata any particular pad type. This includes functions for committing metadata
about pads to your local storage for future use and improved usability. about pads to your local storage for future use and improved usability.
@ -25,6 +27,8 @@ define([
var store; var store;
var PINNING_ENABLED = AppConfig.enablePinning;
var rpc;
var find = common.find = function (map, path) { var find = common.find = function (map, path) {
return (map && path.reduce(function (p, n) { return (map && path.reduce(function (p, n) {
@ -36,6 +40,11 @@ define([
if (store) { return store; } if (store) { return store; }
throw new Error("Store is not ready!"); throw new Error("Store is not ready!");
}; };
var getProxy = common.getProxy = function () {
if (store && store.getProxy()) {
return store.getProxy().proxy;
}
};
var getNetwork = common.getNetwork = function () { var getNetwork = common.getNetwork = function () {
if (store) { if (store) {
if (store.getProxy() && store.getProxy().info) { if (store.getProxy() && store.getProxy().info) {
@ -168,7 +177,6 @@ define([
return typeof getUserHash() === "string"; return typeof getUserHash() === "string";
}; };
// var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; };
var isArray = common.isArray = $.isArray; var isArray = common.isArray = $.isArray;
var fixHTML = common.fixHTML = function (str) { var fixHTML = common.fixHTML = function (str) {
@ -206,7 +214,7 @@ define([
return hexArray.join(""); return hexArray.join("");
}; };
var deduplicate = common.deduplicateString = function (array) { var deduplicateString = common.deduplicateString = function (array) {
var a = array.slice(); var a = array.slice();
for(var i=0; i<a.length; i++) { for(var i=0; i<a.length; i++) {
for(var j=i+1; j<a.length; j++) { for(var j=i+1; j<a.length; j++) {
@ -216,7 +224,6 @@ define([
return a; return a;
}; };
var parseHash = common.parseHash = function (hash) { var parseHash = common.parseHash = function (hash) {
var parsed = {}; var parsed = {};
if (hash.slice(0,1) !== '/' && hash.length >= 56) { if (hash.slice(0,1) !== '/' && hash.length >= 56) {
@ -289,14 +296,6 @@ define([
throw new Error("Unable to parse the key"); throw new Error("Unable to parse the key");
} }
var version = hashArray[1]; var version = hashArray[1];
/*if (version === "1") {
secret.channel = base64ToHex(hashArray[2]);
secret.key = hashArray[3].replace(/-/g, '/');
if (secret.channel.length !== 32 || secret.key.length !== 24) {
common.alert("The channel key and/or the encryption key is invalid");
throw new Error("The channel key and/or the encryption key is invalid");
}
}*/
if (version === "1") { if (version === "1") {
var mode = hashArray[2]; var mode = hashArray[2];
if (mode === 'edit') { if (mode === 'edit') {
@ -492,12 +491,6 @@ define([
var untitledIndex = 1; var untitledIndex = 1;
var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' '); var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' ');
return name; return name;
/*
* Pad titles are shared in the document so it does not make sense anymore to avoid duplicates
if (isNameAvailable(name, parsed, recentPads)) { return name; }
while (!isNameAvailable(name + ' - ' + untitledIndex, parsed, recentPads)) { untitledIndex++; }
return name + ' - ' + untitledIndex;
*/
}; };
var isDefaultName = common.isDefaultName = function (parsed, title) { var isDefaultName = common.isDefaultName = function (parsed, title) {
var name = getDefaultName(parsed, []); var name = getDefaultName(parsed, []);
@ -579,6 +572,7 @@ define([
// STORAGE // STORAGE
/* commit a list of pads to localStorage */ /* commit a list of pads to localStorage */
// TODO integrate pinning if enabled
var setRecentPads = common.setRecentPads = function (pads, cb) { var setRecentPads = common.setRecentPads = function (pads, cb) {
getStore().setDrive(storageKey, pads, function (err, data) { getStore().setDrive(storageKey, pads, function (err, data) {
cb(err, data); cb(err, data);
@ -605,6 +599,7 @@ define([
// STORAGE // STORAGE
// TODO integrate pinning if enabled
var forgetPad = common.forgetPad = function (href, cb) { var forgetPad = common.forgetPad = function (href, cb) {
var parsed = parsePadUrl(href); var parsed = parsePadUrl(href);
@ -686,6 +681,8 @@ define([
var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) { var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) {
return findStronger(href, recents); return findStronger(href, recents);
}; };
// TODO integrate pinning
var setPadTitle = common.setPadTitle = function (name, cb) { var setPadTitle = common.setPadTitle = function (name, cb) {
var href = window.location.href; var href = window.location.href;
var parsed = parsePadUrl(href); var parsed = parsePadUrl(href);
@ -821,12 +818,14 @@ define([
// local name? // local name?
common.ready = function (f) { common.ready = function (f) {
var state = 0; var block = 0;
var env = {}; var env = {};
var cb = function () { var cb = function () {
f(void 0, env); block--;
if (!block) {
f(void 0, env);
}
}; };
if (sessionStorage[newPadNameKey]) { if (sessionStorage[newPadNameKey]) {
@ -841,6 +840,9 @@ define([
Store.ready(function (err, storeObj) { Store.ready(function (err, storeObj) {
store = common.store = env.store = storeObj; store = common.store = env.store = storeObj;
var proxy = getProxy();
var network = getNetwork();
$(function() { $(function() {
// Race condition : if document.body is undefined when alertify.js is loaded, Alertify // Race condition : if document.body is undefined when alertify.js is loaded, Alertify
// won't work. We have to reset it now to make sure it uses a correct "body" // won't work. We have to reset it now to make sure it uses a correct "body"
@ -864,8 +866,34 @@ define([
} }
}; };
if (PINNING_ENABLED && isLoggedIn()) {
console.log("logged in. pads will be pinned");
block++;
// TODO setTimeout in case rpc doesn't
// activate in reasonable time?
Pinpad.create(network, proxy, function (e, call) {
if (e) {
console.error(e);
return cb();
}
console.log('RPC handshake complete');
rpc = env.rpc = call;
// TODO check if pin list is up to date
// if not, reset
cb();
});
} else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned');
} else {
console.log('pinning disabled');
}
// Everything's ready, continue... // Everything's ready, continue...
if($('#pad-iframe').length) { if($('#pad-iframe').length) {
block++;
var $iframe = $('#pad-iframe'); var $iframe = $('#pad-iframe');
var iframe = $iframe[0]; var iframe = $iframe[0];
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
@ -876,6 +904,8 @@ define([
$iframe.load(cb); $iframe.load(cb);
return; return;
} }
block++;
cb(); cb();
}); });
}, common); }, common);
@ -966,6 +996,7 @@ define([
/* /*
* Buttons * Buttons
*/ */
// TODO integrate pinning if enabled
var renamePad = common.renamePad = function (title, callback) { var renamePad = common.renamePad = function (title, callback) {
if (title === null) { return; } if (title === null) { return; }
@ -982,30 +1013,6 @@ define([
} }
callback(null, title); callback(null, title);
}); });
/* Pad titles are shared in the document. We don't check for duplicates anymore.
common.causesNamingConflict(title, function (err, conflicts) {
if (err) {
console.log("Unable to determine if name caused a conflict");
console.error(err);
callback(err, title);
return;
}
if (conflicts) {
common.alert(Messages.renameConflict);
return;
}
common.setPadTitle(title, function (err, data) {
if (err) {
console.log("unable to set pad title");
console.log(err);
return;
}
callback(null, title);
});
});
*/
}; };
var getUserChannelList = common.getUserChannelList = function () { var getUserChannelList = common.getUserChannelList = function () {
@ -1047,6 +1054,10 @@ define([
return list; return list;
}; };
var getCanonicalChannelList = common.getCanonicalChannelList = function () {
return deduplicateString(getUserChannelList()).sort();
};
var createButton = common.createButton = function (type, rightside, data, callback) { var createButton = common.createButton = function (type, rightside, data, callback) {
var button; var button;
var size = "17px"; var size = "17px";

@ -1,69 +1,12 @@
define([ define([
'/common/cryptpad-common.js',
'/common/rpc.js', '/common/rpc.js',
'/bower_components/tweetnacl/nacl-fast.min.js' '/bower_components/tweetnacl/nacl-fast.min.js'
], function (Cryptpad, Rpc) { ], function (Rpc) {
var Nacl = window.nacl; var Nacl = window.nacl;
var uniqueChannelList = function (list) { var create = function (network, proxy, cb) {
list = list || Cryptpad.getUserChannelList(); if (!network) { return void cb('INVALID_NETWORK'); }
return Cryptpad.deduplicateString(list).sort(); if (!proxy) { return void cb('INVALID_PROXY'); }
};
var localChannelsHash = function (fileList) {
var uniqueList = uniqueChannelList(fileList);
var hash = Nacl.util.encodeBase64(Nacl
.hash(Nacl.util.decodeUTF8( JSON.stringify(uniqueList) )));
return hash;
};
var getServerHash = function (rpc, edPublic, cb) {
rpc.send('GET_HASH', edPublic, function (e, hash) {
cb(e, hash[0]);
});
};
var getFileSize = function (rpc, file, cb) {
rpc.send('GET_FILE_SIZE', file, cb);
};
var getFileListSize = function (rpc, cb) {
return rpc.send('GET_TOTAL_SIZE', undefined, cb);
};
var pinChannel = function (rpc, channel, cb) {
rpc.send('PIN', channel, cb);
};
var unpinChannel = function (rpc, channel, cb) {
rpc.send('UNPIN', channel, cb);
};
var reset = function (rpc, cb) {
var list = uniqueChannelList();
rpc.send('RESET', list, cb);
};
/*
1. every time you want to pin or unpid a pad you send a message to the server
2. the server sends back a hash of the sorted list of your pinned pads
3. you hash your sorted list of pinned pads that you should have according to your drive
4. compare them, if same
AWESOME
if they are not
UNPIN all, send all
*/
// Don't use create until Cryptpad is ready
// (use Cryptpad.ready)
var create = function (cb) {
// you will need to communicate with the server
// use an already established
var network = Cryptpad.getNetwork();
// your user proxy contains credentials you will need to make RPC calls
var proxy = Cryptpad.getStore().getProxy().proxy;
var edPrivate = proxy.edPrivate; var edPrivate = proxy.edPrivate;
var edPublic = proxy.edPublic; var edPublic = proxy.edPublic;
@ -74,32 +17,52 @@ define([
if (e) { return void cb(e); } if (e) { return void cb(e); }
var exp = {}; var exp = {};
// expose the supplied publicKey as an identifier
exp.publicKey = edPublic; exp.publicKey = edPublic;
// expose the RPC module's raw 'send' command
exp.send = rpc.send; exp.send = rpc.send;
exp.uniqueChannelList = uniqueChannelList; // you can ask the server to pin a particular channel for you
exp.pin = function (channel, cb) {
rpc.send('PIN', channel, cb);
};
exp.getFileSize = function (file, cb) { // you can also ask to unpin a particular channel
getFileSize(rpc, file, cb); exp.unpin = function (channel, cb) {
rpc.send('UNPIN', channel, cb);
}; };
exp.getFileListSize = function (cb) {
getFileListSize(rpc, cb); // This implementation must match that on the server
// it's used for a checksum
exp.hashChannelList = function (list) {
return Nacl.util.encodeBase64(Nacl.hash(Nacl.util
.decodeUTF8(JSON.stringify(list))));
}; };
// ask the server what it thinks your hash is
exp.getServerHash = function (cb) { exp.getServerHash = function (cb) {
getServerHash(rpc, edPublic, cb); rpc.send('GET_HASH', edPublic, function (e, hash) {
cb(e, hash[0]);
});
}; };
exp.pin = function (channel, cb) { // if local and remote hashes don't match, send a reset
pinChannel(rpc, channel, cb); exp.reset = function (list, cb) {
}; rpc.send('RESET', list, cb);
exp.unpin = function (channel, cb) {
unpinChannel(rpc, channel, cb);
}; };
exp.reset = function (cb) {
reset(rpc, cb); // get the total stored size of a channel's patches (in bytes)
exp.getFileSize = function (file, cb) {
rpc.send('GET_FILE_SIZE', file, cb);
}; };
exp.localChannelsHash = localChannelsHash; // get the combined size of all channels (in bytes) for all the
// channels which the server has pinned for your publicKey
exp.getFileListSize = function (cb) {
rpc.send('GET_TOTAL_SIZE', undefined, cb);
};
cb(e, exp); cb(e, exp);
}); });

@ -53,7 +53,8 @@ types of messages:
var pending = ctx.pending[txid]; var pending = ctx.pending[txid];
if (!(parsed && parsed.slice)) { if (!(parsed && parsed.slice)) {
return void console.error('MALFORMED_RPC_RESPONSE'); // RPC responses are arrays. this message isn't meant for us.
return;
} }
var response = parsed.slice(2); var response = parsed.slice(2);
@ -70,9 +71,8 @@ types of messages:
} }
} }
pending(void 0, response); pending(void 0, response);
} else {
console.log("No callback provided");
} }
//else { console.log("No callback provided"); }
}; };
var create = function (network, edPrivateKey, edPublicKey, cb) { var create = function (network, edPrivateKey, edPublicKey, cb) {

@ -9,46 +9,16 @@ define([
Cryptpad: Cryptpad, Cryptpad: Cryptpad,
}; };
var then = function (call) {
call.getFileSize('26f014b2ab959418605ea37a6785f317', function (e, msg) {
if (e) {
if (e === 'ENOENT') { return; }
return void console.error(e);
}
console.error("EXPECTED ENOENT");
console.log(msg);
});
call.getFileSize('pewpew', function (e, msg) {
if (e) {
if (e === 'INVALID_CHAN') { return; }
return void console.error(e);
}
console.log(msg);
});
var list = Cryptpad.getUserChannelList();
if (list.length) {
call.getFileSize(list[0], function (e, msg) {
if (e) {
return void console.error(e);
}
console.log(msg);
});
}
call.getServerHash(function (e, hash) {
if (e) { return void console.error(e); }
console.log("the server believes your user hash is [%s]", hash);
});
};
var synchronize = function (call) { var synchronize = function (call) {
var localHash = call.localChannelsHash(); // provide a sorted list of unique channels
var list = Cryptpad.getCanonicalChannelList();
var localHash = call.hashChannelList(list);
var serverHash; var serverHash;
call.getFileListSize(function (e, bytes) { call.getFileListSize(function (e, bytes) {
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
console.log("%s total bytes used", bytes); console.log("total %sK bytes used", bytes / 1000);
}); });
call.getServerHash(function (e, hash) { call.getServerHash(function (e, hash) {
@ -59,26 +29,22 @@ define([
return console.log("all your pads are pinned. There is nothing to do"); return console.log("all your pads are pinned. There is nothing to do");
} }
call.reset(function (e, response) { call.reset(list, function (e, response) {
if (e) { return console.error(e); } if (e) { return console.error(e); }
else { else {
return console.log('reset pin list. new hash is [%s]', response); return console.log('reset pin list. new hash is [%s]', response);
} }
}); });
/*
console.log(JSON.stringify({
local: localHash,
remote: serverHash,
}, null, 2));*/
}); });
}; };
$(function () { $(function () {
Cryptpad.ready(function (err, env) { Cryptpad.ready(function (err, env) {
Pinpad.create(function (e, call) { var network = Cryptpad.getNetwork();
var proxy = Cryptpad.getStore().getProxy().proxy;
Pinpad.create(network, proxy, function (e, call) {
if (e) { return void console.error(e); } if (e) { return void console.error(e); }
// then(call);
synchronize(call); synchronize(call);
}); });
}); });

@ -11,82 +11,7 @@
id="favicon" /> id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/customize/main.css" /> <link rel="stylesheet" href="/customize/main.css" />
<style> <link rel="stylesheet" href="whiteboard.css" />
html, body{
padding: 0px;
margin: 0px;
box-sizing: border-box;
}
body {
display: flex;
flex-flow: column;
height: 100%;
background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
#canvas-area {
flex: 1;
display: flex;
}
.canvas-container {
border: 5px solid black;
margin: auto;
background: white;
}
#clear, #toggleDraw {
display: inline;
vertical-align: middle;
}
#colors {
z-index: 100;
border: 3px solid black;
padding: 5px;
vertical-align: top;
background: white;
}
.selected {
margin-left: 20px;
display: inline-block;
height: 100px;
}
.selected img {
vertical-align: middle;
}
#copy {
padding-left: 75px;
}
span.palette-color {
height: 4vw;
width: 4vw;
display: inline-block;
margin: 5px;
border: 2px solid black;
vertical-align: top;
}
#controls {
display: block;
position: relative;
border: 3px solid black;
background: white;
height: 100px;
line-height: 100px;
}
#width, #colors {
position: relative;
vertical-align: middle;
}
#color-picker {
display: block;
}
#pickers {
visibility: hidden;
position: absolute;
width: 0;
height: 0;
z-index: -5;
}
</style>
</head> </head>
<body> <body>
<div id="toolbar" class="toolbar-container"></div> <div id="toolbar" class="toolbar-container"></div>

@ -106,10 +106,9 @@ define([
var $picker = $('<input>', { var $picker = $('<input>', {
type: 'color', type: 'color',
value: '#FFFFFF', value: '#FFFFFF',
}) })
.css({ // TODO confirm that this is safe to remove
visibility: 'hidden' //.css({ visibility: 'hidden' })
})
.on('change', function () { .on('change', function () {
var color = this.value; var color = this.value;
cb(color); cb(color);
@ -242,6 +241,7 @@ define([
}; };
var addColorToPalette = function (color, i) { var addColorToPalette = function (color, i) {
if (readOnly) { return; }
var $color = $('<span>', { var $color = $('<span>', {
'class': 'palette-color', 'class': 'palette-color',
}) })
@ -252,7 +252,6 @@ define([
var c = rgb2hex($color.css('background-color')); var c = rgb2hex($color.css('background-color'));
setColor(c); setColor(c);
}) })
// FIXME double click doesn't seem to work in chromium currently
.on('dblclick', function (e) { .on('dblclick', function (e) {
e.preventDefault(); e.preventDefault();
pickColor(rgb2hex($color.css('background-color')), function (c) { pickColor(rgb2hex($color.css('background-color')), function (c) {
@ -263,19 +262,17 @@ define([
config.onLocal(); config.onLocal();
setColor(c); setColor(c);
}); });
// TODO commit chosen color to pad metadata:
// json.metadata.palette[i]
}); });
$colors.append($color); $colors.append($color);
}; };
palette.forEach(addColorToPalette);
var updatePalette = function (newPalette) { var updatePalette = function (newPalette) {
palette = newPalette; palette = newPalette;
$colors.html('&nbsp;'); $colors.html('<div class="hidden">&nbsp;</div>');
palette.forEach(addColorToPalette); palette.forEach(addColorToPalette);
}; };
updatePalette(palette);
var suggestName = function (fallback) { var suggestName = function (fallback) {
if (document.title === defaultName) { if (document.title === defaultName) {

@ -0,0 +1,82 @@
.hidden {
display: none;
}
html,
body {
padding: 0px;
margin: 0px;
box-sizing: border-box;
}
body {
display: flex;
flex-flow: column;
height: 100%;
background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
#canvas-area {
flex: 1;
display: flex;
}
.canvas-container {
border: 1px solid black;
margin: auto;
background: white;
}
#controls {
display: block;
position: relative;
border-top: 1px solid black;
background: white;
height: 100px;
line-height: 100px;
padding-bottom: 5px;
}
#controls #width {
position: relative;
vertical-align: middle;
}
#controls #clear,
#controls #toggleDraw {
display: inline;
vertical-align: middle;
}
#controls .selected {
margin-left: 20px;
display: inline-block;
height: 135px;
width: 135px;
z-index: 9001;
text-align: center;
}
#controls .selected img {
vertical-align: middle;
}
/* Colors */
#colors {
position: relative;
vertical-align: middle;
z-index: 100;
background: white;
display: flex;
justify-content: space-between;
}
#colors span.palette-color {
height: 4vw;
width: 4vw;
display: inline-block;
margin: 5px;
border: 1px solid black;
vertical-align: top;
}
#color-picker {
display: block;
}
#pickers {
visibility: hidden;
position: absolute;
width: 0;
height: 0;
z-index: -5;
}

@ -0,0 +1,97 @@
.middle () {
position: relative;
vertical-align: middle;
}
.hidden {
display: none;
}
html, body{
padding: 0px;
margin: 0px;
box-sizing: border-box;
}
body {
display: flex;
flex-flow: column;
height: 100%;
background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
// created in the html
#canvas-area {
flex: 1;
display: flex;
}
// created by fabricjs. styled so defaults don't break anything
.canvas-container {
border: 1px solid black;
margin: auto;
background: white;
}
// contains user tools
#controls {
display: block;
position: relative;
border-top: 1px solid black;
background: white;
height: 100px;
line-height: 100px;
padding-bottom: 5px;
#width {
.middle;
}
#clear, #toggleDraw {
display: inline;
vertical-align: middle;
}
.selected {
margin-left: 20px;
display: inline-block;
height: 135px;
width: 135px;
z-index: 9001;
text-align: center;
img {
vertical-align: middle;
}
}
}
/* Colors */
#colors {
.middle;
z-index: 100;
background: white;
display: flex;
justify-content: space-between;
span.palette-color {
height: 4vw;
width: 4vw;
display: inline-block;
margin: 5px;
border: 1px solid black;
vertical-align: top;
}
}
// used in the toolbar if supported
#color-picker {
display: block;
}
// input[type=color] must exist in the dom to work correctly
// styled so that they don't break layouts
#pickers {
visibility: hidden;
position: absolute;
width: 0;
height: 0;
z-index: -5;
}
Loading…
Cancel
Save