UNPKG

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
<!DOCTYPE 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!important; border: 1px solid black !important; 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!important; background:linear-gradient(#06CDFF, #06CDFF)!important; color: white!important; } .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>