Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: incorporate plan1 foundation for peersky parity #276

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 260 additions & 0 deletions app/pages/elves/elf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
if(!self.crypto.randomUUID) {
self.crypto.randomUUID = () => {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
);
}
}

const logs = {}

export function insights() {
return logs
}

function insight(name, link) {
if(!logs[`${name}:${link}`]) {
logs[`${name}:${link}`] = 0
}
logs[`${name}:${link}`] += 1
}

const CREATE_EVENT = 'create'

const observableEvents = [CREATE_EVENT]
const reactiveFunctions = {}


function react(link) {
if(!reactiveFunctions[link]) return

Object.keys(reactiveFunctions[link])
.map(id => reactiveFunctions[link][id]())
}

const notifications = {
[react.toString()]: react
}

function notify(link) {
Object.keys(notifications)
.map(key => notifications[key](link))
}

const store = createStore({}, notify)


function update(link, target, compositor, lifeCycle={}) {
insight('elf:update', link)
if(lifeCycle.beforeUpdate) {
lifeCycle.beforeUpdate.call(this, target)
}

const html = compositor.call(this, target)
if(html) target.innerHTML = html

if(lifeCycle.afterUpdate) {
lifeCycle.afterUpdate.call(this, target)
}
}

function draw(link, compositor, lifeCycle={}) {
insight('elf:draw', link)
if(!reactiveFunctions[link]) {
reactiveFunctions[link] = {}
}

listen(CREATE_EVENT, link, (event) => {
const draw = update.bind(this, link, event.target, compositor, lifeCycle)
reactiveFunctions[link][event.target.id] = draw
draw()
})
}

function style(link, stylesheet) {
insight('elf:style', link)
const styles = `
<style type="text/css" data-link="${link}">
${stylesheet.replaceAll('&', link)}
</style>
`;

document.body.insertAdjacentHTML("beforeend", styles)
}

export function learn(link) {
insight('elf:learn', link)
return store.get(link) || {}
}

export function teach(link, knowledge, nuance = (s, p) => ({...s,...p})) {
insight('elf:teach', link)
store.set(link, knowledge, nuance)
}

export function when(link1, type, link2, callback) {
const link = `${link1} ${link2}`
insight('elf:when:'+type, link)
listen.call(this, type, link, callback)
}

export default function elf(link, initialState = {}) {
insight('elf', link)
teach(link, initialState)

return {
link,
learn: learn.bind(this, link),
draw: draw.bind(this, link),
style: style.bind(this, link),
when: when.bind(this, link),
teach: teach.bind(this, link),
}
}

export function subscribe(fun) {
notifications[fun.toString] = fun
}

export function unsubscribe(fun) {
if(notifications[fun.toString]) {
delete notifications[fun.toString]
}
}

export function listen(type, link, handler = () => null) {
const callback = (event) => {
if(
event.target &&
event.target.matches &&
event.target.matches(link)
) {

insight('elf:listen:'+type, link)
handler.call(this, event);
}
};

document.addEventListener(type, callback, true);

if(observableEvents.includes(type)) {
observe(link);
}

return function unlisten() {
if(type === CREATE_EVENT) {
disregard(link);
}

document.removeEventListener(type, callback, true);
}
}

let links = []

function observe(link) {
links = [...new Set([...links, link])];
maybeCreateReactive([...document.querySelectorAll(link)])
}

function disregard(link) {
const index = links.indexOf(link);
if(index >= 0) {
links = [
...links.slice(0, index),
...links.slice(index + 1)
];
}
}

function maybeCreateReactive(targets) {
targets
.filter(x => !x.reactive)
.forEach(dispatchCreate)
}

function getSubscribers({ target }) {
if(links.length > 0)
return [...target.querySelectorAll(links.join(', '))];
else
return []
}

function dispatchCreate(target) {
insight('elf:create', target.localName)
if(!target.id) target.id = self.crypto.randomUUID()
target.dispatchEvent(new Event(CREATE_EVENT))
target.reactive = true
}

function elves() {
new MutationObserver((mutationsList) => {
const targets = [...mutationsList]
.map(getSubscribers)
.flatMap(x => x)
maybeCreateReactive(targets)
lazy()
}).observe(document.body, { childList: true, subtree: true });

lazy()
}

function lazy() {
const tags = new Set(
[...document.querySelectorAll(':not(:defined)')]
.map(({ tagName }) => tagName.toLowerCase())
)

tags.forEach(async (tag) => {
const url = `agregore://elves/${tag}.js`
const exists = (await fetch(url, { method: 'HEAD' })).ok
if(!exists) return
let definable = true
await import(url).catch((e) => {
definable = false
console.error(e)
})
try {
definable = definable && document.querySelector(tag) && document.querySelector(tag).matches(':not(:defined)')
if(definable) {
customElements.define(tag, class WebComponent extends HTMLElement {
constructor() {
super();
}
});
}
} catch(e) {
console.log('Error defining module:', tag, e)
}
})
}

try {
elves()
} catch(e) {
setTimeout(elves,1000)
}

function createStore(initialState = {}, subscribe = () => null) {
let state = {
...initialState
};

return {
set: function(link, knowledge, nuance) {
const wisdom = nuance(state[link] || {}, knowledge);

state = {
...state,
[link]: wisdom
};

subscribe(link);
},

get: function(link) {
return state[link];
}
}
}

31 changes: 31 additions & 0 deletions app/pages/elves/goodbye-world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import elf from 'agregore://elves/elf.js'

const $ = elf('goodbye-world', {
planet: 'World',
defunctPlanets: []
})

$.draw((target) => {
const data = $.learn()
return `<button>Goodbye ${data.planet}!</button>`
}, {
afterUpdate: (target) => {
{
const data = $.learn()
if(data.defunctPlanets.includes('pluto')) {
alert('bring back pluto')
}
}
}
})

$.when('click', 'button', (event) => {
$.teach(
{ planet: 'pluto'},
(state, payload) => {
return {
...state,
defunctPlanets: [...new Set([...state.defunctPlanets, payload.planet])]
}
})
})
3 changes: 3 additions & 0 deletions app/pages/elves/hello-world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import elf from 'agregore://elves/elf.js'

elf('hello-world').draw((_target) => `Hello World`)
74 changes: 74 additions & 0 deletions app/pages/elves/qr-code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import elf from 'agregore://elves/elf.js'
import QrCreator from 'agregore://vendor/qr-creator/qr-creator.js'

// utilize this to hop off the bifrost
function sleep(D) { return new Promise(x => setTimeout(x,D))}

const $ = elf('qr-code')

$.draw(target => {
const codes = $.learn()
const code = target.getAttribute('src')
const image = codes[code]
const { fg='saddlebrown', bg='lemonchiffon' } = target.dataset
generate(target, code, {fg, bg})
return image ? `
<button class="portal" style="--fg: ${fg}; --bg: ${bg}">
${image}
</button>
` : 'loading...'
})

async function generate(target, code, {fg, bg}) {
if(target.code === code) return
target.code = code
await sleep(1) // get this off the bifrost
const node = document.createElement('div')

QrCreator.render({
text: code,
radius: 0.5, // 0.0 to 0.5
ecLevel: 'L', // L, M, Q, H
fill: fg, // foreground color
background: bg, // color or null for transparent
size: 1080 // in pixels
}, node);

const dataURL = node.querySelector('canvas').toDataURL()

$.teach({ [code]: `<img src="${dataURL}" alt="code" />`})
}

$.when('click', '.portal', (event) => {
const link = event.target.closest($.link)
const code = link.getAttribute('src') || link.getAttribute('text')
window.location.href = code
})

$.style(`
& {
display: block;
max-height: 100%;
max-width: 100%;
min-width: 120px;
aspect-ratio: 1;
position: relative;
margin: auto;
}
& .portal {
display: grid;
height: 100%;
width: 100%;
place-content: center;
border: 0;
background: transparent;
border-radius: 0;
}
& img {
position: absolute;
inset: 0;
max-height: 100%;
margin: auto;
}
`)

Loading