canvas-designer
Version:
Collaborative, extendable, JavaScript Canvas2D drawing tool, supporting dozens of builtin tools, as well as generates JavaScript code for 2D animations.
670 lines (539 loc) • 20.5 kB
HTML
<html itemscope itemtype="http://schema.org/WebPage">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0, user-scalable=no">
<title>Canvas Designer supporting Collaboration | HTML5, WebRTC</title>
<meta name="description" content="HTML5 Canvas Designer ® Muaz Khan – A tool aimed to give you a full-fledged drawing surface and also auto generate appropriate code for you in different formats! Generator/Tool/Editor – 2D API/Context">
<meta name="author" content="Muaz Khan">
<link rel="author" type="text/html" href="https://plus.google.com/+MuazKhan">
</head>
<body>
<script src="canvas-designer-widget.js"></script>
<style>
* {
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
}
input[type=text] {
-webkit-user-select: initial;
-moz-user-select: initial;
-o-user-select: initial;
-ms-user-select: initial;
user-select: initial;
}
button, input {
font-family: Myriad, Arial, Verdana;
font-weight: normal;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
padding: 4px 12px;
text-decoration: none;
color: rgb(27, 26, 26);
display: inline-block;
box-shadow: rgb(255, 255, 255) 1px 1px 0px 0px inset;
text-shadow: none;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0.05, rgb(241, 241, 241)), to(rgb(230, 230, 230)));
font-size: 20px;
border: 1px solid red;
outline:none;
}
button:hover, button:hover, input:focus {
background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(5%, rgb(221, 221, 221)), to(rgb(250, 250, 250)));
border: 1px solid rgb(142, 142, 142);
}
button[disabled], input[disabled] {
background: rgb(249, 249, 249);
border: 1px solid rgb(218, 207, 207);
color: rgb(197, 189, 189);
}
input {
background: white;
}
.extra-controls {
position: fixed;
right: 21%;
}
#room-id {
width: 55%;
margin-right: 2px;
}
/* popup box */
.black_overlay {
display: none;
position: absolute;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
background-color: black;
z-index:1001;
-moz-opacity: 0.8;
opacity:.80;
filter: alpha(opacity=80);
}
.white_content {
display: none;
position: absolute;
top: 30%;
left: 40%;
width: 16%;
height: 15%;
padding: 16px;
border: 10px solid rgb(9, 159, 243);
background-color: rgba(255, 255, 255, 0.94);
z-index:1002;
overflow: hidden;
border-radius: 5px;
min-width: 300px;
min-height: 120px;
}
.white_content hr {
border-top: 4px solid rgb(9, 159, 243);
margin: 9px -20px;
}
#btn-undo, #btn-close-undo-popup, #btn-close-dataURL-popup, #btn-close-comments-popup, #btn-getDataURL {
font-size: 1.2em;
padding: 2px 9px;
}
#btn-close-undo-popup, #btn-close-dataURL-popup, #btn-close-comments-popup {
padding: 0 6px;
border-radius: 50%;
padding-bottom: 1px;
color: red;
}
.white_content select {
font-size: 1.2em;
border: 1px solid;
padding: 0;
outline: none;
border: 1px solid black ;
float: left;
}
.white_content select option {
padding: 2px 5px;
border-bottom: 1px solid black;
}
.white_content select option:last-child {
border-bottom: 0;
}
.white_content option:checked, .white_content option[selected] {
background-color: #06CDFF;
background:linear-gradient(#06CDFF, #06CDFF);
color: white;
}
.white_content select[disabled] {
background-color: rgba(232, 229, 229, 0.17);
color: rgb(84, 82, 82);
}
div#comments-popup {
width: 100%;
left: 0;
top: 0;
height: 100%;
border: 0;
padding: 0;
border-radius: 0;
overflow: auto;
}
#btn-comments {
color: red;
margin-top: 5px;
font-size: 24px;
text-shadow: 1px 1px white;
}
#h2-close-popup {
background: yellow;
padding: 5px 10px;
border-bottom: 1px solid red;
margin: 0;
color: red;
}
</style>
<div id="widget-container" style="position: fixed;bottom: 0;right: 20%;left: 20%;height: 100%;border: 1px solid black; border-top:0; border-bottom: 0;"></div>
<div id="fade" class="black_overlay"></div>
<div id="action-controls" style="width: 19%; padding: 1%;position: absolute;left:0;">
<video controls autoplay playsinline style="width: 105%;margin-left: -15px;margin-top: -23px; display: none;"></video>
<div id="select-tools">
<h2>Select Tools</h2>
<input type="checkbox" id="pencil" checked>
<label for="pencil">pencil</label><br>
<input type="checkbox" id="marker" checked>
<label for="marker">marker</label><br>
<input type="checkbox" id="eraser" checked>
<label for="eraser">eraser</label><br>
<input type="checkbox" id="text" checked>
<label for="text">text</label><br>
<input type="checkbox" id="image" checked>
<label for="image">image</label><br>
<input type="checkbox" id="pdf" checked>
<label for="pdf">pdf</label><br>
<input type="checkbox" id="line" checked>
<label for="line">line</label><br>
<input type="checkbox" id="arrow" checked>
<label for="arrow">arrow</label><br>
<input type="checkbox" id="zoom">
<label for="zoom">zoom</label><br>
<input type="checkbox" id="dragSingle" checked>
<label for="dragSingle">dragSingle</label><br>
<input type="checkbox" id="dragMultiple" checked>
<label for="dragMultiple">dragMultiple</label><br>
<input type="checkbox" id="arc" checked>
<label for="arc">arc</label><br>
<input type="checkbox" id="rectangle" checked>
<label for="rectangle">rectangle</label><br>
<input type="checkbox" id="quadratic" checked>
<label for="quadratic">quadratic</label><br>
<input type="checkbox" id="bezier" checked>
<label for="bezier">bezier</label><br>
<input type="checkbox" id="undo" checked>
<label for="undo">undo</label><br>
<input type="checkbox" id="lineWidth" checked>
<label for="lineWidth">lineWidth</label><br>
<input type="checkbox" id="colorsPicker" checked>
<label for="colorsPicker">colorsPicker</label><br>
<input type="checkbox" id="extraOptions" checked>
<label for="extraOptions">extraOptions</label><br>
<input type="checkbox" id="code" checked>
<label for="code">code</label><br><br>
</div>
<hr>
<div>
Number of users connected with you: <b id="number-of-connected-users">0</b>
<br><br>
<div id="hide-on-datachannel-opened">
You can open a private WebRTC room to sync dashboard with private users.
<br><br>
<input type="text" id="room-id" placeholder="room-id">
<button id="open-room">Open</button>
</div>
</div>
<hr>
<h2>Shortcut Keys</h2>
ctrl+t (to display text-fonts box)<br>
ctrl+z (to undo last-single shape)<br>
ctrl+a (to select all shapes)<br>
ctrl+c (copy last-selected shape)<br>
ctrl+v (paste last-copied shape)<br><br>
ctrl+mousedown (to copy/paste all shapes)
</div>
<div class="extra-controls">
<button id="export-as-image">Export as Image</button>
<button id="btn-display-undo-popup">Undo</button>
</div>
<div id="light" class="white_content">
<button id="btn-close-undo-popup" style="float:right;">x</button>
<select id="undo-options" size="4">
<option>Last Shape</option>
<option>All Shapes</option>
<option>Last Multiple</option>
<option disabled>Specific Range</option>
</select>
<div style="display: none;margin-top: 20px;margin-bottom: 20px;margin-right: 12px;float: right;">
Digit/Number:<br>
<input type="text" id="number-of-shapes-to-undo" style="padding: 0;width: 90px;text-align: center;border-radius: 0;margin-top: 6px;">
</div>
<br><br><br><br><br>
<button id="btn-undo" style="float:right;">Undo</button>
</div>
<div id="dataURL-popup" class="white_content">
<button id="btn-close-dataURL-popup" style="float:right;">x</button>
<select id="data-url-format" size="4">
<option>image/png</option>
<option>image/jpeg</option>
<option>image/gif</option>
<option>image/webp</option>
</select>
<a id="link-to-image" target="_blank" download="image.png"></a>
<br><br><br><br><br>
<button id="btn-getDataURL" style="float:right;">Get DataURL</button>
</div>
<div id="comments-popup" class="white_content">
<button id="btn-close-comments-popup" style="float:right;">x</button>
<h2 id="h2-close-popup">Click right-side "x" button to close this popup.</h2>
<section class="experiment">
<h2 class="header" id="feedback">Feedback</h2>
<div>
<textarea id="message" style="height: 8em; margin: .2em; width: 98%; border: 1px solid rgb(189, 189, 189); outline: none; resize: vertical;" placeholder="Have any message? Suggestions or something went wrong?"></textarea>
</div>
<button id="send-message" style="font-size: 1em;">Send Message</button><small style="margin-left:1em;">Enter your email too; if you want "direct" reply!</small>
</section>
</div>
<div style="position: fixed; right: 0; width: 19%; padding: 1%;top:0; text-align: center;">
<h1>Canvas Designer!</h1>
<h2>
<a href="https://github.com/muaz-khan/Canvas-Designer">This tool is Open-Sourced!</a>
</h2>
Author: <a href="https://github.com/muaz-khan">Muaz Khan</a>
<hr>
<h2><a href="https://github.com/muaz-khan/Canvas-Designer#how-to-use">How to use in your own site?</a></h2>
<hr>
<h2><a href="https://rtcmulticonnection.herokuapp.com/demos/dashboard/">WebRTC Dashboard</a></h2>
<br><br><br>
<div style="text-align:cener;margin-top:15px;">
<p style="font-size:22px;color:red;">Ask Questions Here:</p>
<button id="btn-comments">Comments<br>Discussion</button>
</div>
</div>
<script>
document.getElementById('room-id').onkeyup = document.getElementById('room-id').onblur = function() {
localStorage.setItem('canvas-designer-roomid', this.value);
};
if (localStorage.getItem('canvas-designer-roomid')) {
document.getElementById('room-id').value = localStorage.getItem('canvas-designer-roomid');
}
document.getElementById('open-room').onclick = function() {
var roomid = document.getElementById('room-id').value;
if (!roomid.length) return alert('Please enter roomid.');
this.disabled = true;
connection.open(roomid, onOpenRoom);
this.parentNode.innerHTML = '<a href="#' + roomid + '" target="_blank">Please share this link</a>';
};
var designer = new CanvasDesigner();
// you can place widget.html anywhere
designer.widgetHtmlURL = 'widget.html';
designer.widgetJsURL = 'widget.min.js'
designer.addSyncListener(function(data) {
connection.send(data);
});
designer.setSelected('pencil');
designer.setTools({
pencil: true,
text: true,
image: true,
pdf: true,
eraser: true,
line: true,
arrow: true,
dragSingle: true,
dragMultiple: true,
arc: true,
rectangle: true,
quadratic: true,
bezier: true,
marker: true,
zoom: false,
lineWidth: true,
colorsPicker: true,
extraOptions: true,
code: true,
undo: true
});
designer.appendTo(document.getElementById('widget-container'));
Array.prototype.slice.call(document.getElementById('action-controls').querySelectorAll('input[type=checkbox]')).forEach(function(checkbox) {
checkbox.onchange = function() {
designer.destroy();
designer.addSyncListener(function(data) {
connection.send(data);
});
var tools = {};
Array.prototype.slice.call(document.getElementById('action-controls').querySelectorAll('input[type=checkbox]')).forEach(function(checkbox2) {
if (checkbox2.checked) {
tools[checkbox2.id] = true;
}
});
designer.setTools(tools);
designer.appendTo(document.getElementById('widget-container'));
};
});
var undoOptions = document.getElementById('undo-options');
document.getElementById('btn-display-undo-popup').onclick = function() {
document.getElementById('light').style.display = 'block';
document.getElementById('fade').style.display = 'block';
};
var txtNumberOfShapesToUndo = document.getElementById('number-of-shapes-to-undo');
txtNumberOfShapesToUndo.onkeyup = function() {
localStorage.setItem('number-of-shapes-to-undo', txtNumberOfShapesToUndo.value);
}
if (localStorage.getItem('number-of-shapes-to-undo')) {
txtNumberOfShapesToUndo.value = localStorage.getItem('number-of-shapes-to-undo');
txtNumberOfShapesToUndo.onkeyup();
}
undoOptions.onchange = function() {
txtNumberOfShapesToUndo.parentNode.style.display = 'none';
if (undoOptions.value === 'Specific Range') {
//
} else if (undoOptions.value === 'Last Multiple') {
txtNumberOfShapesToUndo.parentNode.style.display = 'block';
}
localStorage.setItem('undo-options', undoOptions.value);
};
undoOptions.onclick = undoOptions.onchange;
if (localStorage.getItem('undo-options')) {
undoOptions.value = localStorage.getItem('undo-options');
undoOptions.onchange();
}
document.getElementById('btn-undo').onclick = function() {
if (undoOptions.value === 'All Shapes') {
designer.undo('all');
} else if (undoOptions.value === 'Specific Range') {
designer.undo({
specificRange: {
start: -1,
end: -1
}
});
} else if (undoOptions.value === 'Last Shape') {
designer.undo(-1);
} else if (undoOptions.value === 'Last Multiple') {
var numberOfLastShapes = txtNumberOfShapesToUndo.value;
numberOfLastShapes = parseInt(numberOfLastShapes || 0) || 0;
designer.undo({
numberOfLastShapes: numberOfLastShapes
});
}
closeUndoPopup();
};
function closeUndoPopup() {
document.getElementById('light').style.display = 'none';
document.getElementById('fade').style.display = 'none';
undoOptions.onchange();
}
document.getElementById('btn-close-undo-popup').onclick = closeUndoPopup;
function closeDataURLPopup() {
document.getElementById('dataURL-popup').style.display = 'none';
document.getElementById('fade').style.display = 'none';
dataURLFormat.onchange();
}
document.getElementById('btn-close-dataURL-popup').onclick = closeDataURLPopup;
document.getElementById('export-as-image').onclick = function() {
linkToImage.innerHTML = linkToImage.href = linkToImage.style = '';
document.getElementById('dataURL-popup').style.display = 'block';
document.getElementById('fade').style.display = 'block';
getDataURL();
};
function getDataURL(callback) {
callback = callback || function() {};
var format = dataURLFormat.value;
designer.toDataURL(format || 'image/png', function(dataURL) {
linkToImage.style = 'margin-left: 10px;display: block;text-align: center;margin-bottom: -50px;';
linkToImage.href = dataURL;
linkToImage.innerHTML = 'Click to Download Image';
linkToImage.download = 'image.' + (format || 'image/png').split('/')[1];
callback(dataURL, format);
});
}
var dataURLFormat = document.getElementById('data-url-format');
var linkToImage = document.getElementById('link-to-image');
dataURLFormat.onchange = function() {
localStorage.setItem('data-url-format', dataURLFormat.value);
getDataURL();
};
dataURLFormat.onclick = dataURLFormat.onchange;
if (localStorage.getItem('data-url-format')) {
dataURLFormat.value = localStorage.getItem('data-url-format');
dataURLFormat.onchange();
}
document.getElementById('btn-getDataURL').onclick = function() {
getDataURL(function(dataURL, format) {
window.open(dataURL);
});
// closeDataURLPopup();
};
document.getElementById('btn-close-comments-popup').onclick = function() {
document.getElementById('comments-popup').style.display = 'none';
document.getElementById('fade').style.display = 'none';
dataURLFormat.onchange();
};
function showCommentsPopup(e) {
document.getElementById('comments-popup').style.display = 'block';
document.getElementById('fade').style.display = 'block';
}
document.getElementById('btn-comments').onclick = showCommentsPopup;
if (location.hash.length && location.hash.indexOf('comment') !== -1) {
showCommentsPopup();
}
</script>
<script src="https://rtcmulticonnection.herokuapp.com/dist/RTCMultiConnection.min.js"></script>
<script src="https://rtcmulticonnection.herokuapp.com/socket.io/socket.io.js"></script>
<script>
var connection = new RTCMultiConnection();
connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/';
connection.socketMessageEvent = 'canvas-designer';
connection.enableFileSharing = false;
connection.session = {
audio: true,
video: true,
data: true
};
connection.sdpConstraints.mandatory = {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
};
connection.dontCaptureUserMedia = true;
if (location.hash.replace('#', '').length) {
var roomid = location.hash.replace('#', '');
connection.join(roomid);
}
connection.onUserStatusChanged = function(event) {
var infoBar = document.getElementById('hide-on-datachannel-opened');
if (event.status == 'online') {
infoBar.innerHTML = event.userid + ' is <b>online</b>.';
}
if (event.status == 'offline') {
infoBar.innerHTML = event.userid + ' is <b>offline</b>.';
}
numberOfConnectedUsers.innerHTML = connection.getAllParticipants().length;
};
var numberOfConnectedUsers = document.getElementById('number-of-connected-users');
connection.onopen = function(event) {
var infoBar = document.getElementById('hide-on-datachannel-opened');
infoBar.innerHTML = '<b>' + event.userid + '</b> is ready to collaborate with you.';
if (designer.pointsLength <= 0) {
// make sure that remote user gets all drawings synced.
setTimeout(function() {
connection.send('plz-sync-points');
}, 1000);
}
numberOfConnectedUsers.innerHTML = connection.getAllParticipants().length;
if(connection.isInitiator) {
setTimeout(function() {
designer.renderStream();
}, 1000);
}
};
connection.onclose = connection.onerror = connection.onleave = function() {
numberOfConnectedUsers.innerHTML = connection.getAllParticipants().length;
};
connection.onmessage = function(event) {
if (event.data === 'plz-sync-points') {
designer.sync();
return;
}
designer.syncData(event.data);
};
</script>
<script src="dev/webrtc-handler.js"></script>
<script>
function onOpenRoom() {
// capture canvas-2d stream
// and share in realtime using RTCPeerConnection.addStream
// requires: dev/webrtc-handler.js
designer.captureStream(function(stream) {
connection.attachStreams = [stream];
connection.onstream({
stream: stream
});
});
}
var video = document.querySelector('video');
connection.onstream = function(event) {
if (connection.isInitiator && event.mediaElement) return;
video.style.display = '';
video.srcObject = event.stream;
document.getElementById('select-tools').style.display = 'none';
};
connection.onstreamended = function() {
video.src = null;
video.style.display = 'none';
};
</script>
<script src="https://cdn.webrtc-experiment.com/common.js" async></script>
</body>
</html>