Skip to content

Instantly share code, notes, and snippets.

@Utopiah
Created March 12, 2021 16:34
Show Gist options
  • Save Utopiah/2bc607c95d24309dbaa117c2f5579055 to your computer and use it in GitHub Desktop.
Save Utopiah/2bc607c95d24309dbaa117c2f5579055 to your computer and use it in GitHub Desktop.
Whiteboard PmWiki template
<!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;">&#9650;</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