From c92bdaf97bd8b7af4aaee16360d7ed8f7c4c4e7a Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 19 Oct 2024 08:18:33 -0700 Subject: [PATCH 1/6] feature: add gelf, a gunified elf for managing pvp hypergraphs from elf world --- examples/gelf/gelf.js | 213 ++++++++++++++++++++++++++++++ examples/gelf/hello-world.html | 2 + examples/gelf/note.html | 27 ++++ examples/gelf/tags/hello-world.js | 3 + 4 files changed, 245 insertions(+) create mode 100644 examples/gelf/gelf.js create mode 100644 examples/gelf/hello-world.html create mode 100644 examples/gelf/note.html create mode 100644 examples/gelf/tags/hello-world.js diff --git a/examples/gelf/gelf.js b/examples/gelf/gelf.js new file mode 100644 index 000000000..2fa8f9e4a --- /dev/null +++ b/examples/gelf/gelf.js @@ -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 = ` + + `; + + 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) + } + }) +} diff --git a/examples/gelf/hello-world.html b/examples/gelf/hello-world.html new file mode 100644 index 000000000..b56f03877 --- /dev/null +++ b/examples/gelf/hello-world.html @@ -0,0 +1,2 @@ + + diff --git a/examples/gelf/note.html b/examples/gelf/note.html new file mode 100644 index 000000000..694357701 --- /dev/null +++ b/examples/gelf/note.html @@ -0,0 +1,27 @@ + + + + diff --git a/examples/gelf/tags/hello-world.js b/examples/gelf/tags/hello-world.js new file mode 100644 index 000000000..8fedf7157 --- /dev/null +++ b/examples/gelf/tags/hello-world.js @@ -0,0 +1,3 @@ +import gelf from '/examples/gelf/gelf.js' + +gelf('hello-world').draw(() => `Hello World`) From a9841efbe3a2aaa9f99f906cad97a08b9d837558 Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 19 Oct 2024 08:36:54 -0700 Subject: [PATCH 2/6] feature: gelf; add the shared terminal demo --- examples/gelf/terminal.html | 1005 +++++++++++++++++++++++++++++++++++ 1 file changed, 1005 insertions(+) create mode 100644 examples/gelf/terminal.html diff --git a/examples/gelf/terminal.html b/examples/gelf/terminal.html new file mode 100644 index 000000000..a95cc71b5 --- /dev/null +++ b/examples/gelf/terminal.html @@ -0,0 +1,1005 @@ + + + + From 444dd941451de72612d9ab8e96cff33a61b41d6f Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sat, 19 Oct 2024 11:03:31 -0700 Subject: [PATCH 3/6] feature: add primitive array support --- gun.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gun.js b/gun.js index fc34f07ba..904196fdb 100644 --- a/gun.js +++ b/gun.js @@ -1250,6 +1250,9 @@ } k && (to.path || (to.path = [])).push(k); if(!(v = valid(d)) && !(g = Gun.is(d))){ + if(as.array && d instanceof Array) { + tmp = {}; Object.keys(d).forEach(function(v,i){ tmp[i] = v }); d = tmp; + } if(!Object.plain(d)){ ran.err(as, "Invalid data: "+ check(d) +" at " + (as.via.back(function(at){at.get && tmp.push(at.get)}, tmp = []) || tmp.join('.'))+'.'+(to.path||[]).join('.')); return } var seen = as.seen || (as.seen = []), i = seen.length; while(i--){ if(d === (tmp = seen[i]).it){ v = d = tmp.link; break } } From c0067118354260762484ed06e710b768f7bff545 Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Sun, 20 Oct 2024 21:41:10 -0400 Subject: [PATCH 4/6] add array support for writing with put --- src/put.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/put.js b/src/put.js index 6caa43629..40c50157d 100644 --- a/src/put.js +++ b/src/put.js @@ -29,6 +29,9 @@ Gun.chain.put = function(data, cb, as){ // I rewrote it :) } k && (to.path || (to.path = [])).push(k); if(!(v = valid(d)) && !(g = Gun.is(d))){ + if(as.array && d instanceof Array) { + tmp = {}; Object.keys(d).forEach(function(v,i){ tmp[i] = v }); d = tmp; + } if(!Object.plain(d)){ ran.err(as, "Invalid data: "+ check(d) +" at " + (as.via.back(function(at){at.get && tmp.push(at.get)}, tmp = []) || tmp.join('.'))+'.'+(to.path||[]).join('.')); return } var seen = as.seen || (as.seen = []), i = seen.length; while(i--){ if(d === (tmp = seen[i]).it){ v = d = tmp.link; break } } From 234ef0253b4c652f3a8183b2d3fe8d3afbd7ccb5 Mon Sep 17 00:00:00 2001 From: Tyler Childs Date: Mon, 21 Oct 2024 01:36:57 -0400 Subject: [PATCH 5/6] make gelf use a gun node explicitly as context --- examples/gelf/array-test.html | 25 +++++++++++++++++++++++++ examples/gelf/gelf.js | 20 +++++++++++--------- examples/gelf/note.html | 14 +++++--------- 3 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 examples/gelf/array-test.html diff --git a/examples/gelf/array-test.html b/examples/gelf/array-test.html new file mode 100644 index 000000000..e863eb92c --- /dev/null +++ b/examples/gelf/array-test.html @@ -0,0 +1,25 @@ + + diff --git a/examples/gelf/gelf.js b/examples/gelf/gelf.js index 2fa8f9e4a..841980bb5 100644 --- a/examples/gelf/gelf.js +++ b/examples/gelf/gelf.js @@ -36,7 +36,7 @@ function update(link, target, compositor, lifeCycle={}) { function draw(link, compositor, lifeCycle={}) { insight('module:draw', link) listen(CREATE_EVENT, link, (event) => { - gun.get(this.seed).get(link).on(cache => { + this.get(link).on(cache => { database[link] = JSON.parse(cache) || {} update(link, event.target, compositor, lifeCycle) }) @@ -61,10 +61,10 @@ export function learn(link) { export function teach(link, knowledge, nuance = (s, p) => ({...s,...p})) { insight('module:teach', link) - gun.get(this.seed).get(link).once(cache => { + this.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)) + this.get(link).put(JSON.stringify(latest)) }) } @@ -76,15 +76,17 @@ export function when(link1, type, link2, callback) { export default function module(link, initialState = {}) { insight('module', link) - teach.call(this, link, initialState) + const seed = this && this.seed ? this.seed : 'root' + const root = gun.get(seed) + teach.call(root, 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), + learn: learn.bind(root, link), + draw: draw.bind(root, link), + style: style.bind(root, link), + when: when.bind(root, link), + teach: teach.bind(root, link), } } diff --git a/examples/gelf/note.html b/examples/gelf/note.html index 694357701..9a88267be 100644 --- a/examples/gelf/note.html +++ b/examples/gelf/note.html @@ -1,6 +1,8 @@ - + + +