Skip to content

Instantly share code, notes, and snippets.

@Utopiah
Last active Jan 14, 2022
Embed
What would you like to do?
code for Whiteboard
<!DOCTYPE html>
<html lang="en"><!-- as PmWiki tmpl file -->
<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/peerjs@1.3.1/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 src="whiteboard.js"></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>
// 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"
}
@Utopiah
Copy link
Author

Utopiah commented May 13, 2021

Could be generalized to be used on any page (not just as a PmWiki skin) by :

  1. abstracting away the element type (e.g #span4 to li)
  2. making complex dependencies optional (esp. PeerJS, Jitsi)
  3. insure easy access (copy pasting in the console or better, Web Extension)
  4. test mobile version to have it available in non-lieux.

Motivation : transform https://fabien.benetou.fr/innovativ.it/www/HistoricalArchives/Seedea/Content/Newconcepts to memorable flashcards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment