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: add gelf, a gunified elf for managing pvp hypergraphs from elf world #1389

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feature: add gelf, a gunified elf for managing pvp hypergraphs from e…
…lf world
  • Loading branch information
Tyler Childs committed Oct 19, 2024
commit c92bdaf97bd8b7af4aaee16360d7ed8f7c4c4e7a
213 changes: 213 additions & 0 deletions examples/gelf/gelf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import '../../../gun/gun.js'
const gun = window.Gun(location.origin + '/gun');

const database = {}
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]

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

const html = compositor(target)
if(html) target.innerHTML = html

if(lifeCycle.afterUpdate) {
lifeCycle.afterUpdate(target)
}
}

function draw(link, compositor, lifeCycle={}) {
insight('module:draw', link)
listen(CREATE_EVENT, link, (event) => {
gun.get(this.seed).get(link).on(cache => {
database[link] = JSON.parse(cache) || {}
update(link, event.target, compositor, lifeCycle)
})
})
}

function style(link, stylesheet) {
insight('module: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('module:learn', link)
return database[link] || {}
}

export function teach(link, knowledge, nuance = (s, p) => ({...s,...p})) {
insight('module:teach', link)
gun.get(this.seed).get(link).once(cache => {
const data = cache ? JSON.parse(cache) : {}
const latest = nuance(data, knowledge);
gun.get(this.seed).get(link).put(JSON.stringify(latest))
})
}

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

export default function module(link, initialState = {}) {
insight('module', link)
teach.call(this, 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('module:listen:'+type, link)
handler.call(null, 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('module:create', target.localName)
if(!target.id) target.id = sufficientlyUniqueId()
target.dispatchEvent(new Event(CREATE_EVENT))
target.reactive = true
}

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

function sufficientlyUniqueId() {
// https://stackoverflow.com/a/2117523
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

tags({ registry: '/examples/gelf/tags' })
new MutationObserver(() => {
tags({ registry: '/examples/gelf/tags' })
}).observe(document.body, { childList: true, subtree: true });
function tags({ registry }) {
const tags = new Set(
[...document.querySelectorAll(':not(:defined)')]
.map(({ tagName }) => tagName.toLowerCase())
)

tags.forEach(async (tag) => {
const url = `${registry || '.'}/${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)
}
})
}
2 changes: 2 additions & 0 deletions examples/gelf/hello-world.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<hello-world></hello-world>
<script type="module" src='./gelf.js'></script>
27 changes: 27 additions & 0 deletions examples/gelf/note.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<style>html, body, textarea { width: 100%; height: 100%; padding: 0; margin: 0; }</style>
<gelf-note></gelf-note>
<script type="module">
import gelf from './gelf.js'

const context = {
seed: location.hash.replace('#','') || 1
}

const $ = gelf.call(context, 'gelf-note')

$.draw((target) => {
const { value } = $.learn()

if(!target.innerHTML) {
target.innerHTML = `<textarea id="view" placeholder="write here..."></textarea>`
target.view = target.querySelector('#view')
}

target.view.value = value
})

$.when('input', 'textarea', ({target}) => $.teach({
value: target.value
}))
</script>
3 changes: 3 additions & 0 deletions examples/gelf/tags/hello-world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import gelf from '/examples/gelf/gelf.js'

gelf('hello-world').draw(() => `Hello World`)