Merge branch 'whiteboard' into soon

pull/1/head
yflory 5 years ago
commit fe14399e67

@ -11,6 +11,7 @@ var Meta = require("../metadata");
var Extras = require("../hk-util"); var Extras = require("../hk-util");
const readFileBin = require("../stream-file").readFileBin; const readFileBin = require("../stream-file").readFileBin;
const BatchRead = require("../batch-read");
const Schedule = require("../schedule"); const Schedule = require("../schedule");
const isValidChannelId = function (id) { const isValidChannelId = function (id) {
@ -62,7 +63,7 @@ const destroyStream = function (stream) {
if (!stream) { return; } if (!stream) { return; }
try { stream.close(); } catch (err) { console.error(err); } try { stream.close(); } catch (err) { console.error(err); }
setTimeout(function () { setTimeout(function () {
try { stream.destroy(); } catch (err) { console.log(err); } try { stream.destroy(); } catch (err) { console.error(err); }
}, 5000); }, 5000);
}; };
@ -142,7 +143,6 @@ var closeChannel = function (env, channelName, cb) {
destroyStream(stream, channelName); destroyStream(stream, channelName);
} }
delete env.channels[channelName]; delete env.channels[channelName];
env.openFiles--;
cb(); cb();
} catch (err) { } catch (err) {
cb(err); cb(err);
@ -291,7 +291,7 @@ var writeMetadata = function (env, channelId, data, cb) {
// check if a file exists at $path // check if a file exists at $path
var checkPath = function (path, callback) { var checkPath = function (path, callback) { // callback's second arg is never used...
Fs.stat(path, function (err) { Fs.stat(path, function (err) {
if (!err) { if (!err) {
callback(undefined, true); callback(undefined, true);
@ -633,7 +633,7 @@ var unarchiveChannel = function (env, channelName, cb) {
})); }));
}); });
}; };
/*
var flushUnusedChannels = function (env, cb, frame) { var flushUnusedChannels = function (env, cb, frame) {
var currentTime = +new Date(); var currentTime = +new Date();
@ -655,6 +655,7 @@ var flushUnusedChannels = function (env, cb, frame) {
}); });
cb(); cb();
}; };
*/
/* channelBytes /* channelBytes
calls back with an error or the size (in bytes) of a channel and its metadata calls back with an error or the size (in bytes) of a channel and its metadata
@ -687,106 +688,63 @@ var channelBytes = function (env, chanName, cb) {
}); });
}; };
/*:: var getChannel = function (env, id, _callback) {
export type ChainPadServer_ChannelInternal_t = { var cb = Util.once(Util.mkAsync(_callback));
atime: number,
writeStream: typeof(process.stdout), // if the channel is in memory
whenLoaded: ?Array<(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void>,
onError: Array<(?Error)=>void>,
path: string
};
*/
var getChannel = function ( // XXX BatchRead
env,
id,
_callback /*:(err:?Error, chan:?ChainPadServer_ChannelInternal_t)=>void*/
) {
//console.log("getting channel [%s]", id);
var callback = Util.once(Util.mkAsync(_callback));
if (env.channels[id]) { if (env.channels[id]) {
var chan = env.channels[id]; var chan = env.channels[id];
chan.atime = +new Date(); // delay its pending close a little longer
if (chan.whenLoaded) { chan.delayClose();
chan.whenLoaded.push(callback); // and return its writeStream
} else { return void cb(void 0, chan);
callback(undefined, chan);
}
return;
} }
if (env.openFiles >= env.openFileLimit) { // otherwise you need to open it or wait until its pending open completes
// FIXME warn if this is the case? return void env.batchGetChannel(id, cb, function (done) {
// alternatively use graceful-fs to handle lots of concurrent reads
// if you're running out of open files, asynchronously clean up expired files
// do it on a shorter timeframe, though (half of normal)
setTimeout(function () {
//console.log("FLUSHING UNUSED CHANNELS");
flushUnusedChannels(env, function () {
if (env.verbose) {
console.log("Approaching open file descriptor limit. Cleaning up");
}
}, env.channelExpirationMs / 2);
});
}
var path = mkPath(env, id); var path = mkPath(env, id);
var channel /*:ChainPadServer_ChannelInternal_t*/ = env.channels[id] = { var channel = {
atime: +new Date(),
writeStream: (undefined /*:any*/),
whenLoaded: [ callback ],
onError: [], onError: [],
path: path
};
var complete = function (err) {
var whenLoaded = channel.whenLoaded;
// no guarantee stream.on('error') will not cause this to be called multiple times
if (!whenLoaded) { return; }
channel.whenLoaded = undefined;
if (err) {
delete env.channels[id];
}
if (!channel.writeStream) {
throw new Error("getChannel() complete called without channel writeStream"); // XXX
}
whenLoaded.forEach(function (wl) { wl(err, (err) ? undefined : channel); });
}; };
var fileExists; nThen(function (w) {
nThen(function (waitFor) { // create the path to the file if it doesn't exist
checkPath(path, waitFor(function (err, exists) { checkPath(path, w(function (err) {
if (err) { if (err) {
waitFor.abort(); w.abort();
return void complete(err); return void done(err);
} }
fileExists = exists;
})); }));
}).nThen(function (waitFor) { }).nThen(function (w) {
var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' }); // XXX var stream = channel.writeStream = Fs.createWriteStream(path, { flags: 'a' });
env.openFiles++; stream.on('open', w());
stream.on('open', waitFor()); stream.on('error', function (err) {
stream.on('error', function (err /*:?Error*/) { w.abort();
env.openFiles--;
// this might be called after this nThen block closes. // this might be called after this nThen block closes.
if (channel.whenLoaded) {
complete(err);
} else {
channel.onError.forEach(function (handler) { channel.onError.forEach(function (handler) {
handler(err); handler(err);
}); });
}
}); });
}).nThen(function () { }).nThen(function () {
complete(); channel.delayClose = Util.throttle(function () {
delete env.channels[id];
destroyStream(channel.writeStream, path);
//console.log("closing writestream");
}, 30000);
channel.delayClose();
env.channels[id] = channel;
done(void 0, channel);
});
}); });
}; };
// write a message to the disk as raw bytes // write a message to the disk as raw bytes
const messageBin = (env, chanName, msgBin, cb) => { const messageBin = (env, chanName, msgBin, cb) => {
var complete = Util.once(cb); var complete = Util.once(cb);
getChannel(env, chanName, function (err, chan) { // XXX getChannel(env, chanName, function (err, chan) {
if (!chan) { return void complete(err); } if (!chan) { return void complete(err); }
chan.onError.push(complete); chan.onError.push(complete);
chan.writeStream.write(msgBin, function () { chan.writeStream.write(msgBin, function () {
chan.onError.splice(chan.onError.indexOf(complete), 1); chan.onError.splice(chan.onError.indexOf(complete), 1);
chan.atime = +new Date(); // XXX we should just throttle closing, much simpler and less error prone
complete(); complete();
}); });
}); });
@ -1000,8 +958,7 @@ module.exports.create = function (conf, cb) {
channels: { }, channels: { },
channelExpirationMs: conf.channelExpirationMs || 30000, channelExpirationMs: conf.channelExpirationMs || 30000,
verbose: conf.verbose, verbose: conf.verbose,
openFiles: 0, batchGetChannel: BatchRead('store_batch_channel'),
openFileLimit: conf.openFileLimit || 2048,
}; };
var it; var it;
@ -1240,7 +1197,8 @@ module.exports.create = function (conf, cb) {
}, },
// iterate over open channels and close any that are not active // iterate over open channels and close any that are not active
flushUnusedChannels: function (cb) { flushUnusedChannels: function (cb) {
flushUnusedChannels(env, cb); cb("DEPRECATED");
//flushUnusedChannels(env, cb);
}, },
// write to a log file // write to a log file
log: function (channelName, content, cb) { log: function (channelName, content, cb) {
@ -1255,7 +1213,8 @@ module.exports.create = function (conf, cb) {
} }
}); });
}); });
/*
it = setInterval(function () { it = setInterval(function () {
flushUnusedChannels(env, function () { }); flushUnusedChannels(env, function () { });
}, 5000); }, 5000);*/
}; };

@ -43,6 +43,7 @@ define([
'stats': [ 'stats': [
'cp-admin-active-sessions', 'cp-admin-active-sessions',
'cp-admin-active-pads', 'cp-admin-active-pads',
'cp-admin-open-files',
'cp-admin-registered', 'cp-admin-registered',
'cp-admin-disk-usage', 'cp-admin-disk-usage',
], ],
@ -119,6 +120,17 @@ define([
}); });
return $div; return $div;
}; };
create['open-files'] = function () {
var key = 'open-files'; // XXX
var $div = makeBlock(key);
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_FILE_DESCRIPTOR_COUNT',
}, function (e, data) {
console.log(e, data);
$div.append(h('pre', String(data)));
});
return $div;
};
create['registered'] = function () { create['registered'] = function () {
var key = 'registered'; var key = 'registered';
var $div = makeBlock(key); var $div = makeBlock(key);

@ -803,6 +803,17 @@ define([
var chan = [data.channel]; var chan = [data.channel];
if (data.rtChannel) { chan.push(data.rtChannel); } if (data.rtChannel) { chan.push(data.rtChannel); }
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); } if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
var channels = chan.filter(function (c) { return c.length === 32; }).map(function (id) {
if (id === data.rtChannel && data.lastVersion && data.lastCpHash) {
return {
channel: id,
lastKnownHash: data.lastCpHash
};
}
return {
channel: id
};
});
var history = common.makeUniversal('history'); var history = common.makeUniversal('history');
var trimChannels = []; var trimChannels = [];
NThen(function (waitFor) { NThen(function (waitFor) {
@ -819,7 +830,7 @@ define([
if (!owned) { return; } if (!owned) { return; }
history.execCommand('GET_HISTORY_SIZE', { history.execCommand('GET_HISTORY_SIZE', {
pad: true, pad: true,
channels: chan.filter(function (c) { return c.length === 32; }), channels: channels,
teamId: typeof(owned) === "number" && owned teamId: typeof(owned) === "number" && owned
}, waitFor(function (obj) { }, waitFor(function (obj) {
if (obj && obj.error) { return; } if (obj && obj.error) { return; }

@ -259,12 +259,14 @@ define([
}; };
// Handle disconnect/reconnect // Handle disconnect/reconnect
var setEditable = function (state) { var setEditable = function (state, isHistory) {
if (APP.closed || !APP.$content || !$.contains(document.documentElement, APP.$content[0])) { return; } if (APP.closed || !APP.$content || !$.contains(document.documentElement, APP.$content[0])) { return; }
APP.editable = !APP.readOnly && state; APP.editable = !APP.readOnly && state;
if (!state) { if (!state) {
APP.$content.addClass('cp-app-drive-readonly'); APP.$content.addClass('cp-app-drive-readonly');
if (!isHistory) {
$('#cp-app-drive-connection-state').show(); $('#cp-app-drive-connection-state').show();
}
$('[draggable="true"]').attr('draggable', false); $('[draggable="true"]').attr('draggable', false);
} }
else { else {
@ -3865,6 +3867,7 @@ define([
var opts = {}; var opts = {};
opts.href = Hash.getRelativeHref(data.href || data.roHref); opts.href = Hash.getRelativeHref(data.href || data.roHref);
opts.channel = data.channel;
if (manager.isSharedFolder(el)) { if (manager.isSharedFolder(el)) {
var ro = folders[el] && folders[el].version >= 2; var ro = folders[el] && folders[el].version >= 2;

@ -104,10 +104,14 @@ define([
return metadataMgr.getNetfluxId() + '-' + privateData.clientId; return metadataMgr.getNetfluxId() + '-' + privateData.clientId;
}; };
var getEditor = function () {
return window.frames[0].editor || window.frames[0].editorCell;
};
var setEditable = function (state) { var setEditable = function (state) {
$('#cp-app-oo-editor').find('#cp-app-oo-offline').remove(); $('#cp-app-oo-editor').find('#cp-app-oo-offline').remove();
try { try {
window.frames[0].editor.asc_setViewMode(!state); getEditor().asc_setViewMode(!state);
//window.frames[0].editor.setViewModeDisconnect(true); //window.frames[0].editor.setViewModeDisconnect(true);
} catch (e) {} } catch (e) {}
if (!state && !readOnly) { if (!state && !readOnly) {
@ -241,10 +245,6 @@ define([
cpIndex: 0 cpIndex: 0
}; };
var getEditor = function () {
return window.frames[0].editor || window.frames[0].editorCell;
};
var getContent = function () { var getContent = function () {
try { try {
return getEditor().asc_nativeGetFile(); return getEditor().asc_nativeGetFile();
@ -325,6 +325,7 @@ define([
body: $('body'), body: $('body'),
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
if (!data || !data.url) { return; } if (!data || !data.url) { return; }
data.hash = ev.hash;
sframeChan.query('Q_OO_SAVE', data, function (err) { sframeChan.query('Q_OO_SAVE', data, function (err) {
onUploaded(ev, data, err); onUploaded(ev, data, err);
}); });
@ -846,7 +847,7 @@ define([
"id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d", "id": String(myOOId), //"c0c3bf82-20d7-4663-bf6d-7fa39c598b1d",
"firstname": metadataMgr.getUserData().name || Messages.anonymous, "firstname": metadataMgr.getUserData().name || Messages.anonymous,
}, },
"mode": readOnly || lock ? "view" : "edit", "mode": lock ? "view" : "edit",
"lang": (navigator.language || navigator.userLanguage || '').slice(0,2) "lang": (navigator.language || navigator.userLanguage || '').slice(0,2)
}, },
"events": { "events": {
@ -1556,6 +1557,7 @@ define([
content = hjson.content || content; content = hjson.content || content;
var newLatest = getLastCp(); var newLatest = getLastCp();
sframeChan.query('Q_OO_SAVE', { sframeChan.query('Q_OO_SAVE', {
hash: newLatest.hash,
url: newLatest.file url: newLatest.file
}, function () { }); }, function () { });
newDoc = !content.hashes || Object.keys(content.hashes).length === 0; newDoc = !content.hashes || Object.keys(content.hashes).length === 0;
@ -1649,6 +1651,7 @@ define([
var newLatest = getLastCp(); var newLatest = getLastCp();
if (newLatest.index > latest.index) { if (newLatest.index > latest.index) {
sframeChan.query('Q_OO_SAVE', { sframeChan.query('Q_OO_SAVE', {
hash: newLatest.hash,
url: newLatest.file url: newLatest.file
}, function () { }); }, function () { });
} }

@ -61,6 +61,7 @@ define([
if (e) { return void cb(e); } if (e) { return void cb(e); }
Cryptpad.setPadAttribute('lastVersion', data.url, cb); Cryptpad.setPadAttribute('lastVersion', data.url, cb);
}); });
Cryptpad.setPadAttribute('lastCpHash', data.hash, cb);
}); });
sframeChan.on('Q_OO_OPENCHANNEL', function (data, cb) { sframeChan.on('Q_OO_OPENCHANNEL', function (data, cb) {
Cryptpad.getPadAttribute('rtChannel', function (err, res) { Cryptpad.getPadAttribute('rtChannel', function (err, res) {

@ -36,6 +36,7 @@ define([
return void cb(); return void cb();
} }
var txid = Math.floor(Math.random() * 1000000);
var onOpen = function (wc) { var onOpen = function (wc) {
ctx.channels[channel] = ctx.channels[channel] || { ctx.channels[channel] = ctx.channels[channel] || {
@ -91,6 +92,7 @@ define([
var hk = network.historyKeeper; var hk = network.historyKeeper;
var cfg = { var cfg = {
txid: txid,
lastKnownHash: chan.lastKnownHash || chan.lastCpHash, lastKnownHash: chan.lastKnownHash || chan.lastCpHash,
metadata: { metadata: {
validateKey: obj.validateKey, validateKey: obj.validateKey,
@ -121,6 +123,8 @@ define([
} catch (e) {} } catch (e) {}
if (!parsed) { return; } if (!parsed) { return; }
// If there is a txid, make sure it's ours or abort
if (parsed.txid && parsed.txid !== txid) { return; }
// Keep only metadata messages for the current channel // Keep only metadata messages for the current channel
if (parsed.channel && parsed.channel !== channel) { return; } if (parsed.channel && parsed.channel !== channel) { return; }
@ -138,6 +142,11 @@ define([
} }
if (parsed.error && parsed.channel) { return; } if (parsed.error && parsed.channel) { return; }
// If there is a txid, make sure it's ours or abort
if (Array.isArray(parsed) && parsed[0] && parsed[0] !== txid) {
return;
}
msg = parsed[4]; msg = parsed[4];
// Keep only the history for our channel // Keep only the history for our channel

@ -54,6 +54,9 @@ define([
if (!lm.proxy.notifications) { if (!lm.proxy.notifications) {
lm.proxy.notifications = Util.find(ctx.store.proxy, ['mailboxes', 'notifications', 'channel']); lm.proxy.notifications = Util.find(ctx.store.proxy, ['mailboxes', 'notifications', 'channel']);
} }
if (!lm.proxy.edPublic) {
lm.proxy.edPublic = ctx.store.proxy.edPublic;
}
if (ctx.onReadyHandlers.length) { if (ctx.onReadyHandlers.length) {
ctx.onReadyHandlers.forEach(function (f) { ctx.onReadyHandlers.forEach(function (f) {
try { try {

@ -203,7 +203,6 @@ define([
var base = exp.getStructure(); var base = exp.getStructure();
return typeof (obj) === "object" && return typeof (obj) === "object" &&
Object.keys(base).every(function (key) { Object.keys(base).every(function (key) {
console.log(key, obj[key], type(obj[key]));
return obj[key] && type(base[key]) === type(obj[key]); return obj[key] && type(base[key]) === type(obj[key]);
}); });
}; };

@ -119,7 +119,7 @@ define([
var setHistory = function (bool, update) { var setHistory = function (bool, update) {
history.isHistoryMode = bool; history.isHistoryMode = bool;
setEditable(!bool); setEditable(!bool, true);
if (!bool && update) { if (!bool && update) {
history.onLeaveHistory(); history.onLeaveHistory();
} }

@ -457,6 +457,34 @@ define([
APP.editor.save(); APP.editor.save();
}; };
Messages.profile_copyKey = "Copy edPublic key"; // XXX
var addPublicKey = function ($container) {
if (!APP.readOnly) { return; }
var $div = $(h('div.cp-sidebarlayout-element')).appendTo($container);
APP.$edPublic = $('<button>', {
'class': 'btn btn-success',
}).append(h('i.fa.fa-key'))
.append(h('span', Messages.profile_copyKey))
.click(function () {
if (!APP.getEdPublic) { return; }
APP.getEdPublic();
}).appendTo($div).hide();
};
var setPublicKeyButton = function (data) {
if (!data.edPublic || APP.getEdPublic || !APP.readOnly) { return; }
APP.$edPublic.show();
APP.getEdPublic = function () {
var metadataMgr = APP.common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var name = data.name.toLowerCase().replace(/[^a-zA-Z0-9]+/g, "-");
var ed = data.edPublic.replace(/\//g, '-');
var url = privateData.origin + '/user/#/1/' + name + '/' + ed;
var success = Clipboard.copy(url);
if (success) { UI.log(Messages.shareSuccess); }
};
};
var createLeftside = function () { var createLeftside = function () {
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'}).appendTo(APP.$leftside); var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'}).appendTo(APP.$leftside);
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories); var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
@ -477,6 +505,7 @@ define([
addFriendRequest($rightside); addFriendRequest($rightside);
addMuteButton($rightside); addMuteButton($rightside);
addDescription(APP.$rightside); addDescription(APP.$rightside);
addPublicKey(APP.$rightside);
addViewButton($rightside); addViewButton($rightside);
APP.initialized = true; APP.initialized = true;
createLeftside(); createLeftside();
@ -490,6 +519,7 @@ define([
refreshDescription(data); refreshDescription(data);
refreshFriendRequest(data); refreshFriendRequest(data);
refreshMute(data); refreshMute(data);
setPublicKeyButton(data);
}; };
var createToolbar = function () { var createToolbar = function () {

@ -35,7 +35,6 @@
#cp-app-whiteboard-container { #cp-app-whiteboard-container {
flex: 1; flex: 1;
display: flex; display: flex;
flex-flow: column;
overflow: auto; overflow: auto;
} }
@ -43,14 +42,17 @@
#cp-app-whiteboard-canvas-area { #cp-app-whiteboard-canvas-area {
flex: 1; flex: 1;
display: flex; display: flex;
min-height: 0;
} }
// created by fabricjs. styled so defaults don't break anything // created by fabricjs. styled so defaults don't break anything
.cp-app-whiteboard-canvas-container { .cp-app-whiteboard-canvas-container {
width: 100%;
margin: auto; margin: auto;
background: white; background: white;
flex: 1; flex: 1;
min-height: 0; min-height: 0;
& > canvas { & > canvas {
width: 100%;
border: 1px solid black; border: 1px solid black;
} }
} }

@ -137,7 +137,7 @@ define([
canvas.discardActiveGroup(); canvas.discardActiveGroup();
} }
canvas.renderAll(); canvas.renderAll();
framework.localChange(); APP.onLocal();
}; };
$deleteButton.click(deleteSelection); $deleteButton.click(deleteSelection);
$(window).on('keyup', function (e) { $(window).on('keyup', function (e) {
@ -220,7 +220,7 @@ define([
var metadata = JSON.parse(JSON.stringify(metadataMgr.getMetadata())); var metadata = JSON.parse(JSON.stringify(metadataMgr.getMetadata()));
metadata.palette = newPalette; metadata.palette = newPalette;
metadataMgr.updateMetadata(metadata); metadataMgr.updateMetadata(metadata);
framework.localChange(); APP.onLocal();
}; };
var makeColorButton = function ($container) { var makeColorButton = function ($container) {
@ -284,6 +284,34 @@ define([
}); });
var $canvas = $('canvas'); var $canvas = $('canvas');
var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container'); var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container');
var $container = $('#cp-app-whiteboard-container');
// Max for old macs: 2048×1464
// Max for IE: 8192x8192
var MAX = 8192;
var onResize = APP.onResize = function () {
var w = $container.width();
var h = $container.height();
canvas.forEachObject(function (obj) {
var c = obj.getCoords();
Object.keys(c).forEach(function (k) {
if (c[k].x > w) { w = c[k].x + 1; }
if (c[k].y > h) { h = c[k].y + 1; }
});
});
w = Math.min(w, MAX);
h = Math.min(h, MAX);
canvas.setWidth(w);
canvas.setHeight(h);
canvas.calcOffset();
};
$(window).on('resize', onResize);
var onLocal = APP.onLocal = function () {
framework.localChange();
APP.onResize();
};
var $controls = $('#cp-app-whiteboard-controls'); var $controls = $('#cp-app-whiteboard-controls');
var metadataMgr = framework._.cpNfInner.metadataMgr; var metadataMgr = framework._.cpNfInner.metadataMgr;
@ -333,7 +361,7 @@ define([
} }
var cImg = new Fabric.Image(img, { left:0, top:0, angle:0, }); var cImg = new Fabric.Image(img, { left:0, top:0, angle:0, });
APP.canvas.add(cImg); APP.canvas.add(cImg);
framework.localChange(); onLocal();
}; };
// Embed image // Embed image
@ -403,7 +431,7 @@ define([
$('#cp-app-whiteboard-clear').on('click', function () { $('#cp-app-whiteboard-clear').on('click', function () {
canvas.clear(); canvas.clear();
framework.localChange(); onLocal();
}); });
// --------------------------------------------- // ---------------------------------------------
@ -432,6 +460,7 @@ define([
var content = newContent.content; var content = newContent.content;
canvas.loadFromJSON(content, waitFor(function () { canvas.loadFromJSON(content, waitFor(function () {
canvas.renderAll(); canvas.renderAll();
onResize();
})); }));
}); });
@ -461,7 +490,7 @@ define([
window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST); window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
}); });
canvas.on('mouse:up', framework.localChange); canvas.on('mouse:up', onLocal);
framework.start(); framework.start();
}; };
@ -471,8 +500,6 @@ define([
h('div#cp-app-whiteboard-canvas-area', h('div#cp-app-whiteboard-canvas-area',
h('div#cp-app-whiteboard-container', h('div#cp-app-whiteboard-container',
h('canvas#cp-app-whiteboard-canvas', { h('canvas#cp-app-whiteboard-canvas', {
width: 600,
height: 600
}) })
) )
), ),

Loading…
Cancel
Save