Skip to content

Instantly share code, notes, and snippets.

@dralletje
Created October 8, 2020 14:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dralletje/9ae74e22b535ee83382eb411178a913c to your computer and use it in GitHub Desktop.
Save dralletje/9ae74e22b535ee83382eb411178a913c to your computer and use it in GitHub Desktop.
### A Pluto.jl notebook ###
# v0.11.14
using Markdown
using InteractiveUtils
# ╔═╡ cf2958da-f8da-11ea-3e03-5fd8e443ce37
html"""<script>
let firebase = await require.alias({
"@firebase/app": "https://www.gstatic.com/firebasejs/6.3.1/firebase-app.js",
"@firebase/firestore":
"https://www.gstatic.com/firebasejs/6.3.1/firebase-auth.js",
"@firebase/database":
"https://www.gstatic.com/firebasejs/6.3.1/firebase-database.js"
})("@firebase/app", "@firebase/firestore", "@firebase/database")
var firebaseConfig = {
apiKey: "AIzaSyDjOnfUxXqV3mcunbn6z8bdztGXOMxTH3M",
authDomain: "plutojl.firebaseapp.com",
databaseURL: "https://plutojl.firebaseio.com",
projectId: "plutojl",
storageBucket: "plutojl.appspot.com",
messagingSenderId: "1096981201955",
appId: "1:1096981201955:web:187dfe8dc4f22602e2dd96",
measurementId: "G-0MQBLBDYX5"
};
let app = firebase.initializeApp(firebaseConfig);
await app.auth().signInAnonymously()
window.app = app;
console.log('Logged in');
"""
# ╔═╡ 91ce3b70-f8e1-11ea-25c6-07873559b55e
html"""
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
<script>
if (window.app == null) return html`
<pre>You have to reload this cell</pre>
`
let notebook_id = currentScript.closest('pluto-notebook').id;
let user_id = app.auth().currentUser.uid;
app.database().ref(`notebooks/${notebook_id}/users/${user_id}`).onDisconnect().remove()
invalidation.then(() => {
return;
let ref = app.database().ref(`notebooks/${notebook_id}/users/${user_id}`)
ref.remove()
ref.onDisconnect().cancel()
})
let addEventListener = ({ target, event, handler, passive }) => {
target.addEventListener(event, handler, { passive: passive })
invalidation.then(() => {
target.removeEventListener(event, handler)
})
}
addEventListener({
target: document,
event: "focusin",
passive: true,
handler: (e) => {
let cell_node = e.target.closest('pluto-cell');
let notebook_id = cell_node.closest('pluto-notebook').id;
let cell_id = cell_node.id;
let user_id = app.auth().currentUser.uid;
app.database()
.ref(`notebooks/${notebook_id}/users/${user_id}`)
.update({ cell_in_focus: cell_id });
},
})
addEventListener({
target: window,
event: "scroll",
passive: true,
handler: _.debounce((e) => {
if (invalidation.isInvalidated) return
let notebook_id = currentScript.closest('pluto-notebook')?.id;
let user_id = app.auth().currentUser.uid;
if (notebook_id == null) return;
app.database()
.ref(`notebooks/${notebook_id}/users/${user_id}`)
.update({
viewport: [
document.documentElement.scrollTop,
document.documentElement.scrollTop + document.documentElement.clientHeight
]
});
}, 500),
})
let track_cell_state = _.debounce(() => {
if (invalidation.isInvalidated) return
let selection = document.getSelection();
let cm_node = selection.anchorNode.closest('pluto-input > .CodeMirror')
let code_mirror = cm_node.CodeMirror
let start_cursor = code_mirror.getCursor(true)
let end_cursor = code_mirror.getCursor(false)
let cell_node = cm_node.closest('pluto-cell');
let notebook_id = cell_node.closest('pluto-notebook').id;
let cell_id = cell_node.id;
let user_id = app.auth().currentUser.uid;
let changed_cells = [...document.querySelectorAll('pluto-cell.code_differs')].map(x => x.id)
console.log('changed_cells:', changed_cells)
app.database()
.ref(`notebooks/${notebook_id}/users/${user_id}`)
.update({
changed_cells: changed_cells,
selection: {
cell: cell_id,
start: { line: start_cursor.line, column: start_cursor.ch },
end: { line: end_cursor.line, column: end_cursor.ch },
}
});
}, 500)
addEventListener({
target: document,
event: 'selectionchange',
passive: true,
handler: track_cell_state,
})
addEventListener({
target: document,
event: 'keyup',
passive: true,
handler: track_cell_state,
})
addEventListener({
target: document,
event: 'mouseup',
passive: true,
handler: track_cell_state,
})
"""
# ╔═╡ c4f7460e-f8f5-11ea-1222-3768024237da
html"""
<style>
.styled-background {
background-color: red;
}
</style>
<script id="main">
if (window.app == null) return html`
<pre>You need to reload this cell</pre>
`
let Preact = await import("https://cdn.jsdelivr.net/npm/htm@3/preact/standalone.module.js")
let renderComponent = (element = DOM.element('div'), component) => {
Preact.render(Preact.html`<${component} />`, element)
invalidation.then(() => {
Preact.render(null, element)
})
return element
}
let onValue = ({ ref, onValue }) => {
let subscription = ref.on('value', snapshot => onValue(snapshot.val()))
invalidation.then(() => {
ref.off('value', subscription);
})
}
let toRGB = (string) => {
var hash = 0;
for (let character of string) {
hash = character.charCodeAt(0) + ((hash << 5) - hash);
hash = hash & hash;
}
var rgb = [0, 0, 0];
for (var i = 0; i < 3; i++) {
var value = Math.abs(((hash * i) & 255));
rgb[i] = value;
}
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.2)`;
}
let { html: jsx, useEffect, useState, useRef } = Preact;
let notebook_id = currentScript.closest('pluto-notebook').id;
let element = this?.gutter_element ?? DOM.element('div')
// let element = DOM.element('div')
document.body.appendChild(element)
let MarkText = ({ cell, start, end, color }) => {
Preact.useEffect(() => {
let editor = document.getElementById(cell).querySelector(`.CodeMirror`).CodeMirror
let x = editor.markText(
{ line: start.line, ch: start.column },
{ line: end.line, ch: end.column },
{ css: `background-color: ${color}` }
);
return () => x.clear()
}, [cell, start.line, start.character, end.line, end.character])
return null
}
let LockCell = ({ cell_id, color }) => {
Preact.useEffect(() => {
let cell_node = document.getElementById(cell_id)
let codemirror_node = cell_node.querySelector('.CodeMirror')
let cm_instance = codemirror_node.CodeMirror;
console.log('#10')
cm_instance.setOption("readOnly", true)
codemirror_node.style.backgroundColor = color
cm_instance.readOnly_listeners = (cm_instance.readOnly_listeners ?? 0) + 1
return () => {
cm_instance.readOnly_listeners = cm_instance.readOnly_listeners - 1
if (cm_instance.readOnly_listeners === 0) {
cm_instance.setOption("readOnly", false);
codemirror_node.style.backgroundColor = ""
}
}
}, [cell_id, color])
return null;
}
let GUTTER_VERTICAL_MARGIN = 40;
let ViewportGutter = ({ viewport, title, color }) => {
return jsx`
<div
title=${title}
style=${{
transition: 'transform .5s',
marginLeft: 5,
transform: `translateY(${viewport[0] + GUTTER_VERTICAL_MARGIN}px)`,
height: viewport[1] - viewport[0] - GUTTER_VERTICAL_MARGIN * 2,
width: 5,
backgroundColor: color,
}}
/>
`
}
renderComponent(element, () => {
let [notebook, set_notebook] = useState(null)
useEffect(() => {
onValue({
ref: app.database().ref(`notebooks/${notebook_id}`),
onValue: (value) => {
window.requestAnimationFrame(() => {
set_notebook(value)
})
},
})
}, []);
console.log('notebook:', notebook)
return jsx`
<div
style=${{
position: 'absolute',
top: 0, bottom: 0,
left: 0,
display: 'flex',
flexDirection: 'row',
overflowY: 'hidden',
}}
>
${Object.entries(notebook?.users ?? {})
.filter(([id, user]) => id !== app.auth().currentUser.uid)
.map(([id, user]) => jsx`
<${Preact.Fragment} key=${id}>
${user.viewport && jsx`
<${ViewportGutter}
key={id}
title={id}
viewport=${user.viewport}
color=${toRGB(id)}
/>
`}
${(user.changed_cells ?? []).map(cell_id => jsx`
<${LockCell}
key=${cell_id}
cell_id=${cell_id}
color=${toRGB(id)}
/>
`)}
${user.selection && jsx`
<${MarkText}
...${user.selection}
color=${toRGB(id)}
/>
`}
</${Preact.Fragment}>
`)}
</div>
`
})
let return_element = html`
<pre>Running UI</pre>
`
return_element.gutter_element = element
return return_element
"""
# ╔═╡ Cell order:
# ╠═cf2958da-f8da-11ea-3e03-5fd8e443ce37
# ╠═91ce3b70-f8e1-11ea-25c6-07873559b55e
# ╠═c4f7460e-f8f5-11ea-1222-3768024237da
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment