Created
March 12, 2021 16:34
-
-
Save Utopiah/2bc607c95d24309dbaa117c2f5579055 to your computer and use it in GitHub Desktop.
Whiteboard PmWiki template
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>$WikiTitle | {$Group} / {$Title} $ActionTitle</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta name="description" content=""> | |
<meta name="author" content=""> | |
<meta name="monetization" content="$ilp.gatehub.net/360717042"> | |
<!-- Le styles --> | |
<!--HTMLHeader--> | |
<!-- HTML5 shim, for IE6-8 support of HTML5 elements --> | |
<!--[if lt IE 9]> | |
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script> | |
<![endif]--> | |
<!-- Fav and touch icons --> | |
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="$SkinDirUrl/images/ico/apple-touch-icon-144-precomposed.png"> | |
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="$SkinDirUrl/images/ico/apple-touch-icon-114-precomposed.png"> | |
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="$SkinDirUrl/images/ico/apple-touch-icon-72-precomposed.png"> | |
<link rel="apple-touch-icon-precomposed" href="$SkinDirUrl/images/ico/apple-touch-icon-57-precomposed.png"> | |
<link rel="shortcut icon" href="//fabien.benetou.fr/favicon.ico" type="image/x-icon" /> | |
<link rel="stylesheet" href="//fabien.benetou.fr/pub/reveal.js-master/css/reveal.css" type="text/css"> | |
<script src="https://unpkg.com/[email protected]/dist/peerjs.min.js"></script> | |
<script src='https://meet.benetou.fr/external_api.js'></script> | |
</head> | |
<body> | |
<style> | |
.span4 { margin:0; } | |
.grid { height: 100%; width: 100%; margin: 0; } | |
.grid { | |
background-image: | |
repeating-linear-gradient(#ccc 0 1px, transparent 1px 100%), | |
repeating-linear-gradient(90deg, #ccc 0 1px, transparent 1px 100%); | |
background-size: 100px 100px; | |
display:none; | |
} | |
</style> | |
<div class="grid"></div> | |
<span id="remotepointer" style="z-index:999; display:none; position:absolute; top:0px; left:0px;">▲</span> | |
<div onclick="connect()" id="connect" style="display:none; opacity:0.3; position: absolute; top:0px; height: 100px; left:100px">Getting peer ID</div> | |
<div onclick="joinVideoCall(this)" id="videocall" style="position: absolute; top:0px; height: 100px; left:200px">Join video call</div> | |
<div onclick="toggleGridAndSnap(this)" id="snap" style="position: absolute; top:0px; height: 100px; left:400px">Disable snapping</div> | |
<div onclick="generatePresentation()" id="generate" style="position: absolute; top:0px; height: 100px;">Present</div> | |
<div onclick="whiteboard()" id="whiteboard" style="z-index:100; position: Absolute; bottom:0px; ;height: 20px;">whiteboard</div> | |
<div id="strip" style="position: absolute; top:100px; width: 100%; opacity: 0.7; background-color: #ccc;height: 600px;"></div> | |
<div id="listslides" style="position: absolute; top:200px; width: 10%; opacity: 0.7; background-color: #ccc;height: 400px;"></div> | |
<div class="reveal" style="display:none"><div class="slides"></div></div> | |
<!--PageText--> | |
<script> | |
// see also ~/Prototypes/pim-add-url-webextension for on the fly presentation preparation | |
// and more permanent online saving | |
// see https://fabien.benetou.fr/Tools/Ffmpeg?action=serverrender for per page image rendering to avoid iframes | |
var selectedElement = null; | |
var presentation = [] | |
var peerId = null | |
var conn | |
var peer = new Peer() // somehow worked at the beginning of the week... but not anymore?! | |
var jitsiAPI | |
var snap = false | |
//getLinksFromBucket() | |
// tricky with CSP/CORS/etc so might have to solely rely on server side generated preview | |
const urlParams = new URLSearchParams(window.location.search); | |
const peerTargetId = urlParams.get('peertargetid') | |
function getLinksFromBucket(){ | |
fetch('https://fabien.benetou.fr/PIMVRdata/URLBucketTest?action=source').then( | |
response => { return response.text() } ).then( | |
data => { addCards(data.split("\n")) }) | |
} | |
function addCards(cards){ | |
for (var url of cards){ | |
var el = document.createElement("div") | |
el.className = "span4" | |
var h2 = document.createElement("h2") | |
h2.innerText = url | |
el.appendChild(h2) | |
var iframe = document.createElement("iframe") | |
iframe.src = url | |
el.appendChild(iframe) | |
// could add a iframe vignettes as span4 | |
el.style = `border: dotted; position: absolute; top: ${Math.random()*1000}px; left: ${Math.random()*4000}px;` | |
document.querySelector("#wikitext").appendChild( el ) | |
} | |
} | |
function toggleGridAndSnap(el){ | |
snap = !snap | |
if (snap) { | |
el.innerText = "Disable snapping" | |
document.querySelector(".grid").style.display = "block" | |
} else { | |
el.innerText = "Enable snapping" | |
document.querySelector(".grid").style.display = "none" | |
} | |
} | |
function joinVideoCall(el){ | |
el.style.opacity = 0.3 | |
el.innerText = "Joining call" | |
el.innerText += " (click to close)" | |
jitsiAPI = new JitsiMeetExternalAPI('meet.benetou.fr', { | |
roomName:'whiteboard'+window.location.pathname.replaceAll("/","_"), | |
width: '600px', | |
height: '400px', | |
}) | |
jitsiAPI.getIFrame().style.position = "absolute" | |
jitsiAPI.getIFrame().style.right = "0px" | |
jitsiAPI.addListener("participantJoined", e => { | |
el.innerText = jitsiAPI.getNumberOfParticipants() + " participants in call" | |
el.innerText += " (click to close)" | |
}) | |
jitsiAPI.addListener("participantLeft", e => { | |
el.innerText = jitsiAPI.getNumberOfParticipants() + " participants in call" | |
el.innerText += " (click to close)" | |
}) | |
el.onclick = e => { jitsiAPI.dispose(); el.innerText = "Closed call" } | |
} | |
peer.on('connection', function(conn) { | |
var p = document.querySelector("#remotepointer") | |
p.style.display = "block" | |
conn.on('data', function(data){ | |
console.log('peerjs', data) | |
if (data.x){ | |
p.style.top = data.y + 'px' | |
p.style.left = data.x + 'px' | |
// could update card if moving | |
} | |
}) | |
document.querySelector("#connect").style.opacity = 0.3 | |
document.querySelector("#connect").innerText = "Connected" | |
document.querySelector("#connect").onclick = {} | |
}) | |
peer.on('open', function(id) { | |
peerId = id | |
document.querySelector("#connect").style.opacity = 1 | |
if (peerTargetId) conn = peer.connect(peerTargetId) | |
if (conn) { | |
document.querySelector("#connect").style.opacity = 0.3 | |
document.querySelector("#connect").innerText = "Connected" | |
document.querySelector("#connect").onclick = {} | |
} | |
}) | |
function connect(){ | |
if (peerId) window.open(window.location.href+'&peertargetid='+peerId) | |
} | |
function updatePresentation(){ | |
presentation = [] | |
document.querySelector("#listslides").innerText = "" | |
for (var el of document.querySelectorAll(".span4")){ | |
var h2 = el.querySelector("h2") | |
if (h2) { | |
var title = h2.innerText | |
var top = Number(el.style.top.replace("px","")) | |
var left = Number(el.style.left.replace("px","")) | |
if (top && top >= 100 && top < 100+600) presentation.push({title:title, top:top, left:left, el:el}) | |
} | |
} | |
presentation = presentation.sort( (a,b) => (a.left>b.left)) | |
var list = document.createElement("ol") | |
for (var slide of presentation) { | |
var item = document.createElement("li") | |
item.innerText = slide.title | |
list.appendChild(item) | |
} | |
document.querySelector("#listslides").appendChild(list) | |
} | |
function generatePresentation(){ | |
var slides = document.querySelector(".slides") | |
slides.innerHTML = "" | |
for (var slide of presentation){ | |
var section = document.createElement("section") | |
//section.innerHTML = slide.title | |
slide.el.className = "" | |
slide.el.style.formerTop = slide.el.style.top | |
slide.el.style.formerLeft = slide.el.style.left | |
slide.el.style = "" | |
section.appendChild( slide.el ) | |
// cloning doesnt work with an iframe and importing doesn't work due to CSP | |
//section.appendChild( slide.el.cloneNode(true) ) | |
//section.appendChild( document.importNode(slide.el.querySelector('iframe').contentWindow.document, true) ) | |
slides.appendChild(section) | |
} | |
document.querySelector(".reveal").style.display = "block" | |
document.querySelector("#strip").style.display = "none" | |
document.querySelector("#wikitext").style.display = "none" | |
for (var el of document.querySelectorAll(".span4")) el.parentNode.style.display = "none" | |
Reveal.initialize() | |
el.innerText = "" | |
document.querySelector(".grid").style.display = "none" | |
} | |
function whiteboard(){ | |
var snapEl = document.querySelector("#snap") | |
if (snap) { | |
snapEl.innerText = "Disable snapping" | |
document.querySelector(".grid").style.display = "block" | |
} else { | |
snapEl.innerText = "Enable snapping" | |
document.querySelector(".grid").style.display = "none" | |
} | |
document.querySelector(".reveal").style.display = "none" | |
document.querySelector("#strip").style.display = "block" | |
document.querySelector("#wikitext").style.display = "block" | |
for (var el of document.querySelectorAll(".span4")) el.parentNode.style.display = "block" | |
for (var slide of presentation){ | |
slide.el.className = "span4" | |
slide.el.style.top = slide.el.style.formerTop | |
slide.el.style.left = slide.el.style.formerLeft | |
slide.el.style.border = 'dotted' | |
slide.el.style.position = 'absolute' | |
document.querySelector("#wikitext").appendChild( slide.el ) | |
} | |
var slides = document.querySelector(".slides") | |
slides.innerHTML = "" | |
} | |
// could adapt to a function, would have to see if it can be done again for new embed live | |
// otherwise wrapper | |
for (var el of document.querySelector("#wikitext").children) | |
el.style.display = "none" | |
for (var el of document.querySelectorAll(".span8")) | |
el.style.display = "none" | |
for (var el of document.querySelectorAll(".row-fluid")) | |
el.className = "" | |
for (var el of document.querySelectorAll(".span4")){ | |
el.parentNode.style.display = "block" // enough for twitter Cards | |
el.parentNode.parentNode.style.display = "block" // used for PoCs e.g. Portfolio page | |
// could traverse safely instead... | |
var h2 = el.querySelector("h2") | |
if (!h2) { | |
el.style.display = "none" | |
} else { | |
var title = h2.innerText | |
var coords = JSON.parse(localStorage.getItem('wb' + title)) | |
el.style = `border: dotted; position: absolute; top: ${Math.random()*1000}px; left: ${Math.random()*4000}px;` | |
if (coords) { | |
el.style.top = coords.top | |
el.style.left = coords.left | |
var ttop = Number(coords.top.replace("px","")) | |
if (ttop > 100 && ttop < 100+600) { el.style.border = "solid"; updatePresentation(); } | |
} | |
} | |
} | |
window.onmousemove = e => { | |
var title | |
if (selectedElement) { | |
title = selectedElement.querySelector("h2").innerText | |
selectedElement.style.margin = "1px"; | |
selectedElement.style.top = e.clientY+'px'; | |
selectedElement.style.left = e.clientX+'px'; } | |
if (conn) { | |
conn.send({x:e.clientX, y:e.clientY }) | |
//conn.send({x:e.clientX, y:e.clientY, movingElTitle: title}) | |
} | |
/* | |
if (document.body.style.transform.indexOf("scale(1)")>-1) | |
document.body.style.transform += "translate(" + e.movementX + "px, " + e.movementY + "px)" | |
*/ | |
} | |
ontouchstart = e => { | |
onclick(e) | |
} | |
ontouchend = e => { | |
if (selectedElement) { | |
// releasing currently moving entity | |
var title = selectedElement.querySelector("h2").innerText | |
localStorage.setItem('wb' + title, JSON.stringify({top: selectedElement.style.top, left:selectedElement.style.left })); | |
// could be saved back on the wiki instead for a shared/public mode | |
selectedElement.style.border = "dotted" | |
var top = Number(selectedElement.style.top.replace("px","")) | |
var left = Number(selectedElement.style.left.replace("px","")) | |
if (top && top > 100 && top < 100+600) { | |
selectedElement.style.border = "solid" | |
} | |
updatePresentation() | |
} | |
} | |
ontouchmove = e => { | |
if (selectedElement) { selectedElement.style.top = e.touches[0].clientY+'px'; selectedElement.style.left = e.touches[0].clientX+'px'; } | |
} | |
onwheel = e => { | |
e.preventDefault() | |
var scale = Number(document.body.style.transform.replace("scale(",'').replace(")",'')) | |
document.body.style.transform = "scale(" + (scale + e.deltaY/30) + ")" | |
// somwhow getting 3 or -3, might be system specific due to margins... | |
} | |
onclick = e => { | |
if (selectedElement) { | |
// releasing currently moving entity | |
var title = selectedElement.querySelector("h2").innerText | |
localStorage.setItem('wb' + title, JSON.stringify({top: selectedElement.style.top, left:selectedElement.style.left })); | |
// could be saved back on the wiki instead for a shared/public mode | |
selectedElement.style.border = "dotted" | |
var top = Number(selectedElement.style.top.replace("px","")) | |
var left = Number(selectedElement.style.left.replace("px","")) | |
if (snap) { | |
selectedElement.style.top = Math.round(top / 100) * 100 + "px" | |
selectedElement.style.left = Math.round(left / 100) * 100 + "px" | |
} | |
if (top && top > 100 && top < 100+600) { | |
selectedElement.style.border = "solid" | |
} | |
updatePresentation() | |
} | |
if (e.target.className == "span4") | |
selectedElement = e.target; | |
else { | |
selectedElement = null; | |
if (e.target.parentNode.className && e.target.parentNode.className == "span4") | |
selectedElement = e.target.parentNode | |
/* could keep on checking with older parents */ | |
} | |
if (selectedElement) selectedElement.style.border = "dashed" | |
} | |
</script> | |
<script src="//fabien.benetou.fr/pub/reveal.js-master/js/reveal.js"></script> | |
<!-- Le javascript | |
================================================== --> | |
<!-- Placed at the end of the document so the pages load faster --> | |
<script src="$SkinDirUrl/javascripts/jquery-1.8.3.min.js"></script> | |
<script src="$SkinDirUrl/javascripts/bootstrap.min.js"></script> | |
<!--HTMLFooter--> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment