From 177999824e4208b7e05b13e12699d795ed32f882 Mon Sep 17 00:00:00 2001 From: Will Hanlen Date: Wed, 24 Jul 2024 00:24:15 -0500 Subject: [PATCH] re-implement sky (and window wrapper) as iframes composer --- pkg/arvo/app/neo.hoon | 2 + pkg/arvo/neo/cod/std/src/con/sky-htmx.hoon | 155 ++-- pkg/arvo/neo/cod/std/src/fil/a-i-r.js | 2 +- pkg/arvo/neo/cod/std/src/fil/feather.css | 3 + pkg/arvo/neo/cod/std/src/fil/s-k-y.js | 521 +++++++++++++ pkg/arvo/neo/cod/std/src/fil/wi-nd.js | 285 +++++++ .../cod/std/src/imp/hawk-eyre-handler.hoon | 729 +++++++++++++++++- pkg/arvo/neo/cod/std/src/imp/tree-eyre.hoon | 57 ++ pkg/arvo/neo/cod/std/src/lib/sky-wrapper.hoon | 425 ++++++++++ 9 files changed, 2125 insertions(+), 54 deletions(-) create mode 100644 pkg/arvo/neo/cod/std/src/fil/s-k-y.js create mode 100644 pkg/arvo/neo/cod/std/src/fil/wi-nd.js create mode 100644 pkg/arvo/neo/cod/std/src/imp/tree-eyre.hoon create mode 100644 pkg/arvo/neo/cod/std/src/lib/sky-wrapper.hoon diff --git a/pkg/arvo/app/neo.hoon b/pkg/arvo/app/neo.hoon index 0230f1331c..4f9e915daf 100644 --- a/pkg/arvo/app/neo.hoon +++ b/pkg/arvo/app/neo.hoon @@ -1561,6 +1561,8 @@ (emit (do-card #/[p/our.bowl]/sky %make %sky ~ ~)) =. run (emit (do-card #/[p/our.bowl]/srv/hawk %make %hawk-eyre ~ ~)) + =. run + (emit (do-card #/[p/our.bowl]/srv/tree %make %tree-eyre ~ ~)) =. run (emit (do-card #/[p/our.bowl]/srv/sky %make %sky-eyre ~ ~)) run diff --git a/pkg/arvo/neo/cod/std/src/con/sky-htmx.hoon b/pkg/arvo/neo/cod/std/src/con/sky-htmx.hoon index 34a5653300..d5af06fd32 100644 --- a/pkg/arvo/neo/cod/std/src/con/sky-htmx.hoon +++ b/pkg/arvo/neo/cod/std/src/con/sky-htmx.hoon @@ -3,6 +3,8 @@ /- feather-icons /* date-now /* a-i-r +/* s-k-y +/* wi-nd /* feather /* reset /* hawk-icon @@ -20,27 +22,12 @@ ?: menu.sky m m(a.g [[%closed ""] a.g.m]) ^- manx - ;a-i-r.wf.hf.relative + ;s-k-y.wf.hf.relative + =our (scow %p our.bowl) =style "opacity: var(--sky-opacity); padding: var(--sky-outer-gap);" =id "air" =hawks "{}" - =morph-retain "closed" - =hx-on-hawks-moved hawks-moved-js - =hx-on-htmx-after-request - """ - let verb = event.detail.requestConfig.verb; - let url = new URL(event.detail.xhr.responseURL); - let pams = new URLSearchParams(url.search); - if (verb === 'get' && - url.pathname.startsWith('/neo/hawk/~') && - !pams.has('no-save') - ) \{ - $('.nav-refresher').emit('refresh'); - } - """ ;* p:(spin (scag open.sky hawks.sky) 0 ha-wk) - ;+ menu-btn - ;+ nav == :: ++ hawks-moved-js @@ -135,15 +122,12 @@ ?: =(pith /) "" (en-tape:pith:neo pith) =/ idt `tape`(zing (scan +:(scow %da id) (most dot (star ;~(less dot prn))))) - ;div.wf.hf.fc.jc.ac.f2.s3.spinner + ;wi-nd + :: =sandbox "allow-scripts allow-forms allow-same-origin" =slot "s{}" - =id "hawk-{idt}" - =hx-get "/neo/hawk{ext}?slot={}&hawk-id={}&no-save" - =morph-retain "slot" - =hx-trigger "load" - =hx-target "this" - =hx-swap "morph" - ;+ loading.feather-icons + :: =here "/neo/hawk{ext}?slot={}&hawk-id={}&no-save" + =here ext + ; == ++ nav ;nav.wf.hf.p2.fc.g2.sky-nav @@ -187,16 +171,15 @@ }); $(this).emit('hawks-moved'); ''' - :: =/ maximize-hawk-js - :: %- trip - :: ''' - :: let air = $('a-i-r'); - :: let num = parseInt(air.attr('hawks')); - :: air.attr('hawks', num+1); - :: let toMax = '#hawk-' + this.getAttribute('hawk'); - :: $(toMax).attr('slot', 's-1'); - :: $(this).emit('hawks-moved'); - :: ''' + =/ maximize-hawk-js + %- trip + ''' + let air = $('a-i-r'); + let num = parseInt(air.attr('hawks')); + air.attr('hawks', num+1); + let toMax = '#hawk-' + this.getAttribute('hawk'); + $(toMax).attr('slot', 's-1'); + ''' ;div =id "hawk-tab-{idt}" =hx-ext "ignore:html-enc" @@ -204,11 +187,15 @@ ;button =class "loader p2 tl br1 hover grow {color}" =hx-post "/neo/hawk/{}/sky?stud=sky-diff&head=maximize&slot={}" - :: XX optimistically render - ::=hx-on-htmx-after-request maximize-hawk-js + =hx-on-htmx-before-send maximize-hawk-js + =hx-on-htmx-after-request + """ + $(this).emit('hawks-moved'); + """ =hawk idt =hx-target "find .loading" - =hx-swap "outerHTML" + :: =hx-swap "outerHTML" + =hx-swap "none" ;span.loaded: {(en-tape:pith:neo pith)} ;span.loading ;+ loading.feather-icons @@ -446,14 +433,19 @@ ;script: {(trip htmx-response-targets)} ;script: {(trip htmx-idiomorph)} ;script: {htmx-extensions} + :: ;link + :: =rel "stylesheet" + :: =href "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/themes/light.css" + :: ; + :: == + :: ;script + :: =type "module" + :: =src "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/shoelace.js" + :: ; + :: == ;link + =href "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" =rel "stylesheet" - =href "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/themes/light.css" - ; - == - ;script - =type "module" - =src "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/shoelace.js" ; == ;meta @@ -484,6 +476,14 @@ ::== ;style: {(trip reset)} ;style: {(trip feather)} + ;script + ;+ ;/ + """ + const sharedStyles = new CSSStyleSheet(); + sharedStyles.replaceSync(`{(trip reset)}\0a{(trip feather)}`); + document.adoptedStyleSheets = [sharedStyles]; + """ + == ;script ;+ ;/ %- trip ''' @@ -493,7 +493,11 @@ } }; jQuery.fn.log = function (msg) { - console.log(msg, this); + if (msg) { + console.log(msg, this); + } else { + console.log(this); + } return this; }; jQuery.fn.emit = function (name) { @@ -505,6 +509,15 @@ ); return this; }; + jQuery.fn.poke = function (name) { + (this[0]).dispatchEvent( + new Event( + name, + { bubbles: false, cancelable: true, composed: true } + ) + ); + return this; + }; function urbitTimestamp() { let now = new Date(); let year = now.getFullYear(); @@ -515,9 +528,60 @@ let sec = String(now.getSeconds()).padStart(2, '0'); return `~${year}.${month}.${date}..${hour}.${min}.${sec}`; } + function urbitOur() { + return document.body.getAttribute('our'); + } + function hawkAtSlot(slot) { + return document.querySelector(`.hawk[slot='${slot}']`); + } + function hawkDrag(event) { + let fromHawk = $(event.target).closest('.hawk').get(0); + event.dataTransfer.setData("text", fromHawk.slot); + } + function hawkDragEnter(event) { + event.preventDefault(); + $('a-i-r').find('.drag-overlay').addClass('hidden'); + $(event.target).closest('.hawk').children('.drag-overlay').removeClass('hidden'); + } + function hawkDragAllow(event) { + event.preventDefault(); + } + function hawkDragLeave(event) { + event.preventDefault(); + } + function hawkDrop(event) { + event.preventDefault(); + $('a-i-r').find('.drag-overlay').addClass('hidden'); + let fromSlot = event.dataTransfer.getData("text"); + let fromHawk = $('a-i-r').children(`.hawk[slot='${fromSlot}']`).get(0); + let toHawk = $(event.target).closest('.hawk').get(0); + let toSlot = toHawk.slot; + if (toSlot != fromSlot) { + toHawk.setAttribute('slot', fromSlot); + fromHawk.setAttribute('slot', toSlot); + const formData = new URLSearchParams(); + formData.append('from', fromSlot.slice(1)); + formData.append('to', toSlot.slice(1)); + fetch(`/neo/hawk/${urbitOur()}/sky?stud=sky-diff&head=swap-hawks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: formData.toString(), + }) + $('a-i-r').emit('hawks-moved'); + } + } + window.addEventListener('message', function(event) { + let wid = event.data.wid; + let path = event.data.path; + document.querySelector(`[wid='${wid}']`)?.setAttribute('here', path); + }); ''' == ;script: {(trip a-i-r)} + ;script: {(trip s-k-y)} + ;script: {(trip wi-nd)} ;script: {(trip date-now)} ;+ favicon ;+ manifest @@ -528,6 +592,7 @@ =hx-boost "true" =hx-history "false" =hx-replace-url "/neo/sky" + =our (scow %p our.bowl) =style """ background-color: var(--b1); diff --git a/pkg/arvo/neo/cod/std/src/fil/a-i-r.js b/pkg/arvo/neo/cod/std/src/fil/a-i-r.js index 9ab320d765..4ff7fc2b6f 100644 --- a/pkg/arvo/neo/cod/std/src/fil/a-i-r.js +++ b/pkg/arvo/neo/cod/std/src/fil/a-i-r.js @@ -14,7 +14,7 @@ class extends HTMLElement { box-sizing: border-box; } ::slotted(*) { - overflow: auto; + /* overflow: auto; */ } :host { display: grid; diff --git a/pkg/arvo/neo/cod/std/src/fil/feather.css b/pkg/arvo/neo/cod/std/src/fil/feather.css index b79799a4f7..dd3f638f67 100644 --- a/pkg/arvo/neo/cod/std/src/fil/feather.css +++ b/pkg/arvo/neo/cod/std/src/fil/feather.css @@ -704,3 +704,6 @@ button { user-select: none; cursor: pointer; } +.grabber { + cursor: grab; +} diff --git a/pkg/arvo/neo/cod/std/src/fil/s-k-y.js b/pkg/arvo/neo/cod/std/src/fil/s-k-y.js new file mode 100644 index 0000000000..3162122901 --- /dev/null +++ b/pkg/arvo/neo/cod/std/src/fil/s-k-y.js @@ -0,0 +1,521 @@ +customElements.define('s-k-y', +class extends HTMLElement { + static get observedAttributes() { + // + return ["our", "closed", "hawks"]; + } + constructor() { + // + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [sharedStyles]; + this.shadowRoot.innerHTML = ` + +
+ + + + + + +
+ +
+ + + + +
+ + ` + } + get hawks() { + return parseInt(this.getAttribute("hawks") || "0"); + } + get our() { + return this.getAttribute('our'); + } + get windows() { + let slots = $(this).children('wi-nd[slot]').get().toSorted((a, b) => { + let aSlot = parseFloat(a.getAttribute('slot').slice(1)); + let bSlot = parseFloat(b.getAttribute('slot').slice(1)); + if (aSlot > bSlot) return 1; + if (aSlot < bSlot) return -1; + return 0; + }); + let noslots = $(this).children('wi-nd:not([slot])').get(); + return [...slots, ...noslots]; + } + qs(sel) { + return this.shadowRoot.querySelector(sel); + } + gid(id) { + return this.shadowRoot.getElementById(id); + } + connectedCallback() { + $(this).off(); + $(this).on("sky-open", (e) => { + this.toggleAttribute("closed"); + }) + $(this).on('fix-slots', () => { + this.fixSlots(); + }) + $(this).on('new-window', () => { + let wind = document.createElement('wi-nd'); + $(wind).attr('here', `/${this.our}/home`); + $(wind).attr('slot', `s-1`); + $(this).append(wind); + this.growFlock(); + this.fixSlots(); + }) + $(this).on('close-window', (e) => { + let wind = $(e.target); + if (wind.attr('slot') != undefined) { + this.shrinkFlock(); + } + wind.remove(); + this.fixSlots(); + this.renderTabs(); + }) + $(this).on('minimize-window', (e) => { + let wind = $(e.target); + if (wind.attr('slot') != undefined) { + wind.removeAttr('slot'); + this.shrinkFlock(); + } + this.fixSlots(); + this.renderTabs(); + }) + $(this).on('maximize-window', (e) => { + let wind = $(e.target); + if (!wind.attr('slot')) { + this.growFlock(); + } + wind.attr('slot', 's-1'); + this.fixSlots(); + this.renderTabs(); + }) + $(this).on('drag-start', (e) => { + $(this.windows).attr('dragging', ''); + $(this.gid('nav')).addClass('active'); + }) + $(this).on('drag-end', (e) => { + $(this.windows).removeAttr('dragging'); + }) + $(this).on('here-moved', () => { + this.renderTabs(); + }) + $(this.gid('s0')).off(); + $(this.gid('s0')).on('slotchange', (e) => { + this.renderTabs(); + }); + $(this.gid('s1')).off(); + $(this.gid('s1')).on('slotchange', () => { + this.renderTabs(); + }); + $(this.gid('s2')).off(); + $(this.gid('s2')).on('slotchange', () => { + this.renderTabs(); + }); + $(this.gid('s3')).off(); + $(this.gid('s3')).on('slotchange', () => { + this.renderTabs(); + }); + $(this.gid('default')).off(); + $(this.gid('default')).on('slotchange', () => { + this.renderTabs(); + }); + + $(this.gid('nav')).off(); + $(this.gid('nav')).on('dragover', (e) => { + e.preventDefault(); + }) + $(this.gid('nav')).on('drop', (e) => { + e.preventDefault(); + let wid = e.originalEvent.dataTransfer.getData('text/plain'); + let wind = $(`[wid='${wid}']`); + wind.poke('minimize'); + }); + } + attributeChangedCallback(name, oldValue, newValue) { + // + if (name === "closed") { + this.classList.toggle("closed"); + } else if (name === "hawks") { + this.qs("main").className = `open-${this.hawks}`; + } + } + renderIcon(name) { + let s = document.createElement('span'); + s.className = 'mso'; + s.textContent = name; + return s; + } + renderTabs() { + let tabs = $(this.gid('tabs')); + tabs.children().remove(); + let hawks = this.hawks; + let that = this; + $(this.windows).each(function(i) { + let wind = this; + let tab = document.createElement('div'); + $(tab).addClass('b2 br1 fr ac js g1 bd1'); + if (i < hawks) { + $(tab).addClass('toggled'); + } + + let max = document.createElement('button'); + $(max).text($(wind).attr('here')); + $(max).addClass('b2 hover br1 bd0 p2 grow tl'); + max.style = 'overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-align: left;'; + $(max).on('click', () => { + $(wind).emit('maximize-window'); + }); + + let min = document.createElement('button'); + $(min).append(that.renderIcon('minimize')); + $(min).addClass('b2 hover br1 bd0 p1 f3'); + $(min).on('click', () => { + $(wind).emit('minimize-window'); + }); + if (i >= hawks) { + $(min).hide(); + } + + let close = document.createElement('button'); + $(close).append(that.renderIcon('close')); + $(close).addClass('b2 hover br1 bd0 p1 f3'); + $(close).on('click', () => { + $(wind).emit('close-window'); + }); + + $(tab).append(max); + $(tab).append(min); + $(tab).append(close); + tabs.append(tab); + }) + } + fixSlots() { + let slotted = $(this.windows).filter('[slot]').get().slice(0, 3); + $(this.windows).removeAttr('slot'); + slotted.forEach((s, i) => { + s.setAttribute('slot', `s${i}`); + }) + } + growFlock() { + $(this).attr('hawks', Math.min(3, this.hawks + 1)); + } + shrinkFlock() { + $(this).attr('hawks', Math.max(0, this.hawks - 1)); + } +}); diff --git a/pkg/arvo/neo/cod/std/src/fil/wi-nd.js b/pkg/arvo/neo/cod/std/src/fil/wi-nd.js new file mode 100644 index 0000000000..78d3204a73 --- /dev/null +++ b/pkg/arvo/neo/cod/std/src/fil/wi-nd.js @@ -0,0 +1,285 @@ +customElements.define('wi-nd', +class extends HTMLElement { + static get observedAttributes() { + // + return [ + "wid", + "here", + "searching", // boolean. true is user is using the search bar in the header + "tabs", // currently unused. soon be space-separated list of iframe prefixes for each renderer + "current", // currently boolean. soon interpret to match prefix(tab) + "dragging", + ]; + } + constructor() { + // + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [sharedStyles]; + this.shadowRoot.innerHTML = ` + + +
+ + + +
+ + +
+ drag_indicator +
+
+
+
+
+ ` + } + connectedCallback() { + $(this.gid('searchbar')).off(); + $(this.gid('searchbar')).on('submit', (e) => { + e.preventDefault(); + this.setAttribute('here', $(this.gid('input-here')).val()); + }); + $(this.gid('input-here')).off(); + $(this.gid('input-here')).on('focusout', (e) => { + $(this).removeAttr('searching'); + }); + $(this.gid('input-here')).on('blur', (e) => { + $(this).removeAttr('searching'); + }); + $(this.gid('tree-toggle')).off(); + $(this.gid('tree-toggle')).on('click', (e) => { + this.toggleAttribute('current'); + }); + + $(this.gid('dragger')).off(); + $(this.gid('dragger')).on('dragstart', (e) => { + $(this).emit('drag-start'); + e.originalEvent.dataTransfer.setData('text/plain', this.getAttribute('wid')); + }) + $(this.gid('dragger')).on('dragend', () => { + $(this).emit('drag-end'); + }) + + $(this).off(); + $(this).on('close', () => { + $(this).emit('close-window'); + }) + $(this).on('minimize', () => { + $(this).emit('minimize-window'); + }) + $(this).on('dragenter', (e) => { + $(this).addClass('dragging'); + }) + $(this).on('dragover', (e) => { + e.preventDefault(); + }) + $(this).on('dragleave', (e) => { + $(this).removeClass('dragging'); + }) + $(this).on('drop', (e) => { + e.preventDefault(); + $(this).removeClass('dragging'); + $(this).emit('drag-end'); + let wid = e.originalEvent.dataTransfer.getData('text/plain'); + let wind = $(`[wid='${wid}']`); + let newSlot = parseInt(this.getAttribute('slot').slice(1)); + let oldSlot = parseInt(wind.attr('slot')?.slice(1)); + if (!isNaN(oldSlot) && oldSlot < newSlot) { + newSlot = newSlot + 0.5; + } else { + newSlot = newSlot - 0.5; + } + wind.attr('slot', `s${newSlot}`); + $(this).emit('fix-slots'); + }) + this.setAttribute('wid', `${Date.now()}`); + } + attributeChangedCallback(name, oldValue, newValue) { + // + if (name === "here") { + if (oldValue !== newValue) { + let tabs = $(this.gid('tabs')); + tabs.children().remove(); + let hawk = this.createIframe('/neo/hawk', newValue, $(this).attr('current') != undefined); + let tree = this.createIframe('/neo/tree', newValue, $(this).attr('current') == undefined); + tabs.append(hawk); + tabs.append(tree); + this.buildBreadcrumbs(); + $(this.gid('input-here')).val(newValue); + $(this).emit('here-moved'); + } + } else if (name === "searching") { + if (newValue === null) { + $(this.gid('breadcrumbs')).removeClass('hidden'); + $(this.gid('searchbar')).addClass('hidden'); + } else { + $(this.gid('breadcrumbs')).addClass('hidden'); + $(this.gid('searchbar')).removeClass('hidden'); + this.gid('input-here').focus(); + } + } else if (name === "current") { + if (newValue === null) { + $(this.gid('tabs')).children(`[prefix='/neo/hawk']`).show() + $(this.gid('tabs')).children(`[prefix='/neo/tree']`).hide(); + $(this.gid('tree-toggle')).removeClass('toggled'); + } else { + $(this.gid('tabs')).children(`[prefix='/neo/hawk']`).hide() + $(this.gid('tabs')).children(`[prefix='/neo/tree']`).show(); + $(this.gid('tree-toggle')).addClass('toggled'); + } + } else if (name === "dragging") { + if (newValue === null) { + $(this).removeClass('dragging'); + $(this.gid('drag-overlay')).hide(); + } else { + $(this.gid('drag-overlay')).show(); + } + } + } + qs(sel) { + return this.shadowRoot.querySelector(sel); + } + gid(id) { + return this.shadowRoot.getElementById(id); + } + get path() { + let here = this.getAttribute("here") || "/"; + return here.slice(1).split("/").filter(s => !!s.trim().length); + } + createIframe(prefix, here, hidden) { + let el = document.createElement('iframe'); + el.setAttribute('prefix', prefix); + el.setAttribute('lazy', ''); + el.setAttribute('src', prefix+here); + el.setAttribute('style', 'width: 100%; flex-grow: 1; border: none; background: var(--b0);'); + if (hidden) { + el.hidden = true; + } + el.addEventListener('load', () => { + this.registerServiceWorker(el, prefix); + }); + return el; + } + registerServiceWorker(iframe, prefix) { + const iframeWindow = iframe.contentWindow; + const iframeDoc = iframeWindow.document; + let wid = this.getAttribute('wid'); + let pre = prefix.length; + const inlineScript = iframeDoc.createElement('script'); + inlineScript.textContent = ` + function notifySky() { + window.parent.postMessage({wid: '${wid}', path: window.location.pathname.slice(${pre})}, '*'); + } + window.addEventListener('beforeunload', function (e) { + notifySky(); + }); + window.addEventListener('htmx:beforeHistorySave', function (e) { + notifySky(); + }); + window.addEventListener('htmx:beforeRequest', function (e) { + notifySky(); + }); + window.addEventListener('htmx:afterSwap', function (e) { + notifySky(); + }); + `; + iframeDoc.body.appendChild(inlineScript); + } + buildBreadcrumbs() { + let breadcrumbs = $(this.gid('breadcrumbs')); + breadcrumbs.children().remove(); + // + this.path.forEach((p, i) => { + let chevron = $(document.createElement('span')); + chevron.addClass('s-2 f4 o6 fc ac jc'); + chevron.text('›'); + breadcrumbs.append(chevron); + // + let crumb = $(document.createElement('button')); + crumb.addClass((i === 0 ? 'p-1' : 'p1') + ' b2 hover br1 s-1 f2'); + crumb.text(i === 0 ? "/" : this.path[i]); + crumb.on('click', () => { + $(this).attr('here', "/"+this.path.slice(0, i+1).join("/")); + }); + breadcrumbs.append(crumb); + }) + let spacer = $(document.createElement('button')); + spacer.addClass('grow b2 br1 hover') + spacer.on('click', () => { + $(this).attr('searching', ''); + }); + breadcrumbs.append(spacer); + } +}); diff --git a/pkg/arvo/neo/cod/std/src/imp/hawk-eyre-handler.hoon b/pkg/arvo/neo/cod/std/src/imp/hawk-eyre-handler.hoon index c3777e72ec..12ce27b039 100644 --- a/pkg/arvo/neo/cod/std/src/imp/hawk-eyre-handler.hoon +++ b/pkg/arvo/neo/cod/std/src/imp/hawk-eyre-handler.hoon @@ -1,6 +1,7 @@ /@ htmx-type=htmx /- feather-icons /- serv=sky-server +/- sky-wrapper /> htmx /< node /< http-request @@ -59,7 +60,7 @@ bowl 404 ['content-type' 'text/html']~ - ~(lift hawk pit stub stub & meta) + ~(lift hawk pit stub stub & meta bowl) == =/ here p.u.src ^- (list card:neo) @@ -77,7 +78,7 @@ bowl 200 ['content-type' 'text/html']~ - ~(lift hawk here.bol stub stub & meta) + ~(lift hawk here.bol stub stub & meta bowl) == =/ root=idea:neo u.reet =/ bol *bowl:neo @@ -105,13 +106,14 @@ == =; c ?: (~(has by pam.purl) 'no-save') c - [(sky-move-tab bol slot) c] + c + :: [(sky-move-tab bol slot) c] %: eyre-cards eyre-id bowl 200 ['content-type' 'text/html']~ - ~(lift hawk here.bol main (raw-view bol(kids q.u.src)) has-conversion meta) + ~(lift hawk here.bol main (raw-view bol(kids q.u.src)) has-conversion meta bowl) == :: %'POST' @@ -342,7 +344,7 @@ == == ++ hawk - |_ [here=pith main=manx raw=manx has-app=? meta=[@da @ud]] + |_ [here=pith main=manx raw=manx has-app=? meta=[@da @ud] =bowl:neo] ++ our-tape =/ f (snag 0 here) ?@(f (trip f) (scow f)) @@ -351,7 +353,7 @@ ++ slot-tag :: XX oh boy this is hacky. :: working with slots in ssr is tough - ?: =(slot 999) "s-1" + ?: =(slot 999) "s0" "s{}" ++ idt `tape`(zing (scan +:(scow %da id) (most dot (star ;~(less dot prn))))) ++ vals @@ -362,7 +364,9 @@ '}' ++ lift ^- manx - ;div.hawk.fc.wf.hf.br1 + %- + ~(lift sky-wrapper bowl) + ;div.hawk.fc.wf.hf.br1.iframe-switch =id "hawk-{idt}" =hawk-id (scow %da id) =slot slot-tag @@ -371,7 +375,7 @@ =hx-target "closest .hawk" =hx-target-x "closest .rendered" =hx-target-404 "this" - ;+ header + :: ;+ header ;div =class "raw wf hf b0 scroll-y scroll-x {(trip ?:(has-app 'hidden' ''))}" ;+ raw @@ -538,4 +542,713 @@ == == -- +++ feather +''' +/** feather.css + * ~2024.4.6 + * + * styling resets + * and + * utility classes + * + **/ + +:root { + + /* --font: 'Urbit Sans'; + --font-mono: 'Monaco'; + --mono-scale: 0.8; + --letter-spacing: 0.024em; + --line-height: 1.4; + */ + --0in: calc(0 * var(--1in)); + --1in: 4px; + --font-size: calc(4 * var(--1in)); + --2in: calc(2 * var(--1in)); + --3in: calc(3 * var(--1in)); + --4in: calc(4 * var(--1in)); + --5in: calc(5 * var(--1in)); + --6in: calc(6 * var(--1in)); + --7in: calc(7 * var(--1in)); + --8in: calc(8 * var(--1in)); + --9in: calc(9 * var(--1in)); + + --10in: calc(10 * var(--1in)); + --11in: calc(11 * var(--1in)); + --12in: calc(12 * var(--1in)); + --13in: calc(13 * var(--1in)); + --14in: calc(14 * var(--1in)); + --15in: calc(15 * var(--1in)); + --16in: calc(16 * var(--1in)); + --17in: calc(17 * var(--1in)); + --18in: calc(18 * var(--1in)); + --19in: calc(19 * var(--1in)); + + --20in: calc(20 * var(--1in)); + --21in: calc(21 * var(--1in)); + --22in: calc(22 * var(--1in)); + --23in: calc(23 * var(--1in)); + --24in: calc(24 * var(--1in)); + --25in: calc(25 * var(--1in)); + --26in: calc(26 * var(--1in)); + --27in: calc(27 * var(--1in)); + --28in: calc(28 * var(--1in)); + --29in: calc(29 * var(--1in)); + + --30in: calc(30 * var(--1in)); + --31in: calc(31 * var(--1in)); + --32in: calc(32 * var(--1in)); + --33in: calc(33 * var(--1in)); + --34in: calc(34 * var(--1in)); + --35in: calc(35 * var(--1in)); + --36in: calc(36 * var(--1in)); + --37in: calc(37 * var(--1in)); + --38in: calc(38 * var(--1in)); + --39in: calc(39 * var(--1in)); + --40in: calc(40 * var(--1in)); + + + /* --light-b-3: #dd5522; + --light-b-2: #ddaa33; + --light-b-1: #55dd33; + --light-b0: #dddddd; + --light-b1: #cccccc; + --light-b2: #bbbbbb; + --light-b3: #aaaaaa; + --light-b4: #999999; + --light-f-3: #993311; + --light-f-2: #aaaa22; + --light-f-1: #339911; + --light-f0: #111111; + --light-f1: #333333; + --light-f2: #444444; + --light-f3: #555555; + --light-f4: #777777; + + --dark-b-3: #551111; + --dark-b-2: #555511; + --dark-b-1: #225511; + --dark-b0: #222222; + --dark-b1: #333333; + --dark-b2: #444444; + --dark-b3: #555555; + --dark-b4: #666666; + --dark-f-3: #ee7755; + --dark-f-2: #ccbb33; + --dark-f-1: #55cc33; + --dark-f0: #eeeeee; + --dark-f1: #cccccc; + --dark-f2: #bbbbbb; + --dark-f3: #aaaaaa; + --dark-f4: #888888; + */ + --b-3: var(--light-b-3); + --b-2: var(--light-b-2); + --b-1: var(--light-b-1); + --b0: var(--light-b0); + --b1: var(--light-b1); + --b2: var(--light-b2); + --b3: var(--light-b3); + --b4: var(--light-b4); + --f-3: var(--light-f-3); + --f-2: var(--light-f-2); + --f-1: var(--light-f-1); + --f0: var(--light-f0); + --f1: var(--light-f1); + --f2: var(--light-f2); + --f3: var(--light-f3); + --f4: var(--light-f4); +} +@media (prefers-color-scheme: dark) { + :root { + --b-3: var(--dark-b-3); + --b-2: var(--dark-b-2); + --b-1: var(--dark-b-1); + --b0: var(--dark-b0); + --b1: var(--dark-b1); + --b2: var(--dark-b2); + --b3: var(--dark-b3); + --b4: var(--dark-b4); + --f-3: var(--dark-f-3); + --f-2: var(--dark-f-2); + --f-1: var(--dark-f-1); + --f0: var(--dark-f0); + --f1: var(--dark-f1); + --f2: var(--dark-f2); + --f3: var(--dark-f3); + --f4: var(--dark-f4); + } +} +* { + font-size: var(--font-size); +} +/* +@media (max-width: 900px) { + :root { + --font-size: calc(1.3 * var(--font-size)); + } +} +*/ +body { + font-family: var(--font); + letter-spacing: var(--letter-spacing); + line-height: 1; + height: 100%; +} +button { + border: inherit; + background: inherit; + color: inherit; +} + +.break { + word-break: break-word; +} +.action { + touch-action: manipulation; +} +.hidden, +.folded { + display: none !important; +} +.jc { + justify-content: center; +} +.jb { + justify-content: space-between; +} +.ja { + justify-content: space-around; +} +.js { + justify-content: start; +} +.je { + justify-content: end; +} +.js { + justify-content: start; +} +.as { + align-items: start; +} +.af { + align-items: stretch; +} +.ae { + align-items: end; +} +.ac { + align-items: center; +} +.wfc { + width: fit-content; +} +.wf { + width: 100%; +} +.mw-page { + max-width: 650px; +} +.hf { + height: 100%; +} +.hfc { + height: fit-content; +} +.mono { + font-family: var(--font-mono), monospace; + font-size: calc(1em * var(--mono-scale)); +} +.italic { + font-style: italic; +} +.underline { + text-decoration: underline; +} +.bold { + font-weight: bold; +} +.strike { + text-decoration: line-through; +} + +.pre { + white-space: pre; +} +.pre-line { + white-space: pre-line; +} + +.tl { + text-align: left; +} +.tc { + text-align: center; +} +.tr { + text-align: right; +} + +.block { + display: block; +} +.inline { + display: inline-block; +} + +.fc { + display: flex; + flex-direction: column; +} +.fcr { + display: flex; + flex-direction: column-reverse; +} +.fr { + display: flex; + flex-direction: row; +} +.frw { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +.basis-full { + flex-basis: 100%; +} +.basis-half { + flex-basis: 50%; + flex-shrink: 0; +} +.basis-none { + flex-basis: 0%; + flex-shrink: 1; +} +.shrink-0 { + flex-shrink: 0; +} +.relative { + position: relative; +} +.absolute { + position: absolute; +} +.fixed { + position: fixed; +} +.sticky { + position: sticky; +} + +.z-2 { + z-index: -20; +} +.z-1 { + z-index: -10; +} +.z0 { + z-index: 0; +} +.z1 { + z-index: 10; +} +.z2 { + z-index: 20; +} + +.grow { + flex-grow: 1; +} + +.g0 { + gap: 0; +} +.g1 { + gap: 4px; +} +.g2 { + gap: 8px; +} +.g3 { + gap: 12px; +} +.g4 { + gap: 16px; +} +.g5 { + gap: 20px; +} +.g6 { + gap: 24px; +} +.g7 { + gap: 32px; +} +.g8 { + gap: 40px; +} + + + +.p-8 { + padding: 32px 64px; +} +.p-7 { + padding: 28px 56px; +} +.p-6 { + padding: 24px 48px; +} +.p-5 { + padding: 20px 40px; +} +.p-4 { + padding: 16px 32px; +} +.p-3 { + padding: 12px 24px; +} +.p-2 { + padding: 8px 16px; +} +.p-1 { + padding: 4px 8px; +} +.p0 { + padding: 0; +} +.p1 { + padding: 4px; +} +.p2 { + padding: 8px; +} +.p3 { + padding: 12px; +} +.p4 { + padding: 16px; +} +.p5 { + padding: 24px; +} +.p6 { + padding: 30px; +} +.p7 { + padding: 34px; +} +.p8 { + padding: 38px; +} +.p-page { + padding-top: 30px; + padding-bottom: 200px; + padding-left: 12px; + padding-right: 12px; +} + + +.ma { + margin: auto; +} + +.mt1 { + margin-top: 1rem; +} +.mt2 { + margin-top: 2rem; +} +.mt3 { + margin-top: 3rem; +} + +.m0 { + margin: 0; +} + + + + +.o0 { + opacity: 0%; +} +.o1 { + opacity: 10%; +} +.o2 { + opacity: 20%; +} +.o3 { + opacity: 30%; +} +.o4 { + opacity: 40%; +} +.o5 { + opacity: 50%; +} +.o6 { + opacity: 60%; +} +.o7 { + opacity: 70%; +} +.o8 { + opacity: 80%; +} +.o9 { + opacity: 90%; +} +.o10 { + opacity: 100%; +} + + + + +.scroll-y { + overflow-y: auto; +} +.scroll-x { + overflow-x: auto; +} +.scroll-hidden { + overflow: hidden; +} +.loader { + position: relative; +} +.loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + pointer-events: none; +} +.loader .loading { + opacity: 0; + transition: opacity 300ms; +} +.htmx-request .loader .loading, +.loader.htmx-request .loading { + opacity: 1; +} +.loader .loaded { + opacity: 1; + transition: opacity 300ms; +} +.htmx-request .loader .loaded, +.loader.htmx-request .loaded { + opacity: 0; +} + +.f-3 { + color: var(--f-3); +} +.f-2 { + color: var(--f-2); +} +.f-1 { + color: var(--f-1); +} +.f0 { + color: var(--f0); +} +.f1 { + color: var(--f1); +} +.f2 { + color: var(--f2); +} +.f3 { + color: var(--f3); +} +.f4 { + color: var(--f4); +} + +.b-3 { + background-color: var(--b-3); +} +.b-2 { + background-color: var(--b-2); +} +.b-1 { + background-color: var(--b-1); +} +.b0 { + background-color: var(--b0); +} +.b1 { + background-color: var(--b1); +} +.b2 { + background-color: var(--b2); +} +.b3 { + background-color: var(--b3); +} +.b4 { + background-color: var(--b4); +} + +.s-2 { + font-size: 0.7rem; + } +.s-1 { + font-size: 0.85rem; +} +.s0 { + font-size: 1rem; +} +.s1 { + font-size: 1.15rem; +} +.s2 { + font-size: 1.3rem; +} +.s3 { + font-size: 1.45rem; +} +.s4 { + font-size: 1.6rem; +} +.s5 { + font-size: 2rem; +} +.s6 { + font-size: 2.4rem; +} +.bd0 { + border: none; +} +.bd1 { + border: 0.8px solid var(--b3); +} +.bd2 { + border: 0.8px solid var(--f3); +} +.bd3 { + border: 2px solid var(--f1); +} +/* deprecated */ +.border { + border: 0.8px solid var(--f3); +} + +.br1 { + border-radius: 3px; +} +.br2 { + border-radius: 6px; +} +.br3 { + border-radius: 12px; +} + +.hover:hover { + filter: invert(20%); +} +.toggled { + filter: invert(100%); +} +.hover.toggled:hover { + filter: invert(100%); +} +.active, +.selected { + filter: invert(10%); +} +.hover.active:hover, +.hover.selected:hover { + filter: invert(25%); +} +.numbered > *:before { + content: counter(line); + display: inline-block; + /* border-right: 1px solid var(--f3); */ + /* padding: 0 .5em; */ + /* margin-right: .5em; */ + color: var(--f3); + /* min-width: 34px; */ + text-align: right; +} +.numbered > * { + display: block; + counter-increment: line; +} +.prose h1 { + font-size: 1.45rem; + margin: 1rem 0; +} +.prose h2 { + font-size: 1.3rem; + margin: 1rem 0; +} +.prose h3 { + font-size: 1.15rem; + margin: 1rem 0; +} +.prose h1, .prose h2, .prose h3 { + font-weight: bold; +} +.prose p { + margin-bottom: 1rem; + line-height: var(--line-height); +} +.prose img { + max-width: 100%; + display: block; + max-height: 350px; +} +.prose details { + margin-bottom: 1rem; +} +.prose a { + text-decoration: underline; +} +.prose blockquote { + margin-left: 10px; + border-left: 2px solid var(--b3); + padding: 4px; + padding-left: 12px; + color: var(--f2); +} +.prose pre { + font-family: var(--font-mono); + font-size: calc(1em * var(--mono-scale)); + overflow-x: auto; + width: 100%; + display: block; + padding: 8px; +} +.prose code { + font-family: var(--font-mono); + font-size: calc(1em * var(--mono-scale)); + color: var(--f2); +} +.prose ul, +.prose ol { + padding-left: 29px; + line-height: calc(calc(1 + var(--line-height)) / 2); + margin-bottom: 0.6rem; +} +.prose ul p, +.prose ol p { + margin-bottom: 0; + line-height: var(--line-height); +} +.prose ul ul, +.prose ol ul, +.prose ul ol, +.prose ol ol { + margin-bottom: 0; +} +.prose summary { + user-select: none; + cursor: pointer; +} +''' -- diff --git a/pkg/arvo/neo/cod/std/src/imp/tree-eyre.hoon b/pkg/arvo/neo/cod/std/src/imp/tree-eyre.hoon new file mode 100644 index 0000000000..a21253e909 --- /dev/null +++ b/pkg/arvo/neo/cod/std/src/imp/tree-eyre.hoon @@ -0,0 +1,57 @@ +/@ eyre-reqs +/- serv=server +^- kook:neo +|% +++ state pro/%sig +++ poke (sy %eyre-task ~) +++ kids + :+ ~ %y + %- ~(gas by *lads:neo) + ~ +++ deps + %- ~(gas by *band:neo) + ~ +:: +++ form + |_ [=bowl:neo =aeon:neo =pail:neo] + ++ poke + |= [=stud:neo vax=vase] + ^- (quip card:neo pail:neo) + ?+ stud ~|(bad-stud/stud !!) + %eyre-task + =+ !<(=task:eyre:neo vax) + =/ [eyre-id=@ta req=inbound-request:eyre] task + :_ sig/!>(~) + =/ doc + %- en-xml:html + ;html + ;body + ;style + ;+ ;/ %- trip + ''' + body { + color: red; + } + ''' + == + ;h1: tree stub: {(trip url.request.req)} + == + == + =/ head=sign:eyre:neo [eyre-id %head [200 ~]] + =/ data=sign:eyre:neo [eyre-id %data `(as-octt:mimes:html doc)] + =/ done=sign:eyre:neo [eyre-id %done ~] + :~ [#/[p/our.bowl]/$/eyre %poke eyre-sign/!>(head)] + [#/[p/our.bowl]/$/eyre %poke eyre-sign/!>(data)] + [#/[p/our.bowl]/$/eyre %poke eyre-sign/!>(done)] + == + == + ++ init + |= pal=(unit pail:neo) + :_ sig/!>(~) + =/ =pith:neo #/[p/our.bowl]/$/eyre + =/ =binding:eyre [~ ~[%neo %tree]] + =/ =req:eyre:neo [%connect binding here.bowl] + :~ [pith %poke eyre-req/!>(req)] + == + -- +-- diff --git a/pkg/arvo/neo/cod/std/src/lib/sky-wrapper.hoon b/pkg/arvo/neo/cod/std/src/lib/sky-wrapper.hoon new file mode 100644 index 0000000000..f351ea2618 --- /dev/null +++ b/pkg/arvo/neo/cod/std/src/lib/sky-wrapper.hoon @@ -0,0 +1,425 @@ +/- feather-icons +/* date-now +/* a-i-r +/* feather +/* reset +/* hawk-icon +/* jquery +/* htmx-js +/* htmx-response-targets +/* htmx-idiomorph +|_ =bowl:neo +++ hawks-moved-js + :: js to run whenever the order or number of hawks changed. + :: it will: + :: - trigger a refresh of the nav + :: - loop through any slotted hawks and reslot them starting from 0 + %- trip + ''' + let air = $(this); + let num = parseInt(air.attr('hawks')); + let hawks = air.children('[slot]').filter('.hawk').get(); + hawks.sort((a, b) => { + return (a.getAttribute('slot') < b.getAttribute('slot')) ? -1 : 1; + }).forEach((h, i) => { + h.setAttribute('slot', `s${i}`); + }); + air.find('.nav-refresher').emit('refresh'); + ''' +++ map-to-css-tape + |= m=(map @t @t) + ^- tape + %- zing + %+ turn ~(tap by m) + |= [key=@t val=@t] + """ + --{(trip key)}: {(trip val)}; + """ +++ eye + ;div#eye.fixed.hidden + =style "bottom: 30px; left: 30px;" + =morph-no-swap "" + ;script + ;+ ;/ %- trip + ''' + function handleKey(e) { + let focused = document.activeElement; + let textarea = ['TEXTAREA'].includes(focused.nodeName) + let textinput = ['text', 'number', 'email', 'password'].includes(focused.type); + if (textarea || textinput) { + if (e.key === 'Escape') { + document.activeElement.blur(); + } + return; + } + else if (e.key === 'Escape') { + closeEye(); + document.activeElement.blur(); + } + else if (e.key === ' ') { + e.preventDefault(); + if (window.eye.open) { + closeEye(); + } else { + openEye(); + } + } else if (e.key === 'Escape') { + e.preventDefault(); + document.activeElement.blur(); + } else if (!e.ctrlKey && !e.metaKey && !e.altKey) { + if (window.eye?.open) { + e.preventDefault(); + let area = window.eye?.spots?.filter(s => s[0][0] === e.key); + if (area.length === 1) { + let btn = area[0][1]; + btn.click(); + btn.focus(); + closeEye(); + } + else if (!!area.length) { + let news = area.map(c => (c[0].length < 2) ? c : [c[0].slice(1), c[1]]); + window.eye.spots = news; + closeEye(true); + openEye(); + } else { + closeEye(); + } + } + } + } + function openEye() { + window.eye.open = true; + document.getElementById('eye').classList.remove('hidden'); + buildGazeSpots(); + } + function closeEye(keep) { + window.eye.open = false; + if (!keep) { + window.eye.spots = null; + } + document.getElementById('eye').classList.add('hidden'); + document.querySelectorAll('.gaze').forEach(g => g.remove()); + } + function buildGazeSpots() { + let buttons = window.eye?.spots?.map(s => s[1]) || + document.querySelectorAll( + 'a, button, summary, [role="button"], input, textarea, .clickable' + ); + let chars = ['a', 's', 'd', 'f', 'k', 'm', 'n', 'r', 't', 'y', 'u', 'i', 'c', 'v', 'b']; + buttons.forEach((b, i) => { + let d = b.getBoundingClientRect(); + if (d.right > 0 && d.right > 0) { + let t = document.createElement('div'); + var lent = Math.floor((i / chars.length) + 1); + var word = '' + while (lent > 0) { + let ch = chars[i % chars.length]; + word = `${word}${ch}`; + lent = lent - 1; + } + t.textContent = word.slice(-1); + t.className = 'b-1 br2 p1 s0 bold fixed gaze z2' + t.style = `top: ${Math.max(0, d.top - 10)}px; left: ${Math.max(0, d.left - 10)}px;` + document.getElementById('eye')?.parentNode.appendChild(t); + window.eye.spots = [[word, b], ...(window.eye?.spots || [])] + } + }) + } + window.addEventListener('keydown', handleKey); + ''' + == + == +++ icon-url + ^~ + %- trip + %^ cat + 3 + 'data:image/png;base64,' + %- ~(en base64:mimes:html & |) + (as-octs:mimes:html hawk-icon) +++ favicon + ^~ + =; m m(a.g [[%href icon-url] a.g.m]) + ^- manx + ;link + =rel "icon" + =type "image/png" + ; + == +++ manifest-url + ^~ + %- trip + %^ cat + 3 + 'data:application/json;utf-8,' + %- en:json:html + %- pairs:enjs:format + :~ + ['name' s+'sky'] + ['description' s+'an urbit namespace viewer'] + ['display' s+'standalone'] + ['background_color' s+'black'] + :+ 'icons' %a + :~ + %- pairs:enjs:format + :~ + ['src' s+(crip icon-url)] + ['sizes' s+'196x196'] + ['type' s+'image/png'] + == + == + == +++ manifest + ^~ + =; m m(a.g [[%href manifest-url] a.g.m]) + ^- manx + ;link + =rel "manifest" + ; + == +++ htmx-extensions + :: htmx extension which encodes the request + :: as the serialized HTML of the calling element + :: + :: XX usage of this should be optional. + :: requests should default to form-encoded. + %- trip + ''' + htmx.defineExtension('html-enc', { + onEvent: function (name, evt) { + if (name === "htmx:configRequest") { + evt.detail.headers['Content-Type'] = "text/html"; + } + }, + encodeParameters : function(xhr, parameters, elt) { + xhr.overrideMimeType('text/html'); + let xmls = new XMLSerializer(); + return (xmls.serializeToString(elt)); + } + }); + Idiomorph.defaults.ignoreActive = true; + Idiomorph.defaults.callbacks.beforeAttributeUpdated = (name, node, type) => { + if (node.hasAttribute('morph-retain')) { + let ribs = node.getAttribute('morph-retain').split(',').map(t => t.trim()); + if (ribs.includes(name)) { + return false; + } + } + } + Idiomorph.defaults.callbacks.beforeNodeMorphed = (oldNode, newNode) => { + if (oldNode?.nodeName !== "#text") { + if (oldNode.hasAttribute('morph-no-swap') && oldNode.id === newNode.id) { + return false; + } + else if ( + newNode.hasAttribute('morph-if-class') && + !oldNode.classList.contains(newNode.getAttribute('morph-if-class')) + ) { + return false; + } + } + } + ''' +:: +++ lift + |= in=manx + ^- manx + ;html + ;head + ;meta(charset "UTF-8"); + ;title: hawk + ;script: {(trip jquery)} + ;script: {(trip htmx-js)} + ;script: {(trip htmx-response-targets)} + ;script: {(trip htmx-idiomorph)} + ;script: {htmx-extensions} + :: ;link + :: =rel "stylesheet" + :: =href "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/themes/light.css" + :: ; + :: == + :: ;script + :: =type "module" + :: =src "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/shoelace.js" + :: ; + :: == + ;meta + =name "viewport" + =content + """ + width=device-width, + initial-scale=1.0, + maximum-scale=1.0 + """ + ; + == + ;meta + =name "htmx-config" + =content (trip '{"ignoreTitle":"true"}') + ; + == + ::;style + :: ;+ ;/ %- trip + :: ''' + :: @font-face { + :: font-family: 'Urbit Sans'; + :: src: url("https://media.urbit.org/fonts/UrbitSans/UrbitSansVFWeb-Regular.woff2") format("woff2"); + :: font-style: normal; + :: font-weight: 100 700; + :: } + :: ''' + ::== + ;style: {(trip reset)} + ;style: {(trip feather)} + ;script + ;+ ;/ %- trip + ''' + window.log = function() { + if (this.console) { + console.log(Array.prototype.slice.call(arguments)); + } + }; + jQuery.fn.log = function (msg) { + console.log(this); + return this; + }; + jQuery.fn.emit = function (name) { + (this[0]).dispatchEvent( + new Event( + name, + { bubbles: true, cancelable: true, composed: true } + ) + ); + return this; + }; + function urbitTimestamp() { + let now = new Date(); + let year = now.getFullYear(); + let month = now.getMonth() + 1; + let date = now.getDate(); + let hour = String(now.getHours()).padStart(2, '0'); + let min = String(now.getMinutes()).padStart(2, '0'); + let sec = String(now.getSeconds()).padStart(2, '0'); + return `~${year}.${month}.${date}..${hour}.${min}.${sec}`; + } + function urbitOur() { + return document.body.getAttribute('our'); + } + function hawkAtSlot(slot) { + return document.querySelector(`.hawk[slot='${slot}']`); + } + function hawkDrag(event) { + let fromHawk = $(event.target).closest('.hawk').get(0); + event.dataTransfer.setData("text", fromHawk.slot); + } + function hawkDragEnter(event) { + event.preventDefault(); + $('a-i-r').find('.drag-overlay').addClass('hidden'); + $(event.target).closest('.hawk').children('.drag-overlay').removeClass('hidden'); + } + function hawkDragAllow(event) { + event.preventDefault(); + } + function hawkDragLeave(event) { + event.preventDefault(); + } + function hawkDrop(event) { + event.preventDefault(); + $('a-i-r').find('.drag-overlay').addClass('hidden'); + let fromSlot = event.dataTransfer.getData("text"); + let fromHawk = $('a-i-r').children(`.hawk[slot='${fromSlot}']`).get(0); + let toHawk = $(event.target).closest('.hawk').get(0); + let toSlot = toHawk.slot; + if (toSlot != fromSlot) { + toHawk.setAttribute('slot', fromSlot); + fromHawk.setAttribute('slot', toSlot); + const formData = new URLSearchParams(); + formData.append('from', fromSlot.slice(1)); + formData.append('to', toSlot.slice(1)); + fetch(`/neo/hawk/${urbitOur()}/sky?stud=sky-diff&head=swap-hawks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: formData.toString(), + }) + $('a-i-r').emit('hawks-moved'); + } + } + ''' + == + ;script: {(trip a-i-r)} + ;script: {(trip date-now)} + ;+ favicon + ;+ manifest + == + ;body + =hx-ext "html-enc,response-targets,morph" + =hx-swap "outerHTML" + =hx-boost "true" + =hx-history "false" + =our (scow %p our.bowl) + =style + """ + background-color: var(--b1); + background-image: var(--sky-bg-url); + background-size: var(--sky-bg-size); + background-repeat: var(--sky-bg-repeat); + """ + ;+ in + ;+ eye + ;+ stub-styling + == + == +++ stub-styling +;style + ;+ ;/ %- trip + ''' + html { + --line-height: 1.4; + --sky-outer-gap: 8px; + --dark-b-1: #225511; + --dark-b3: #555555; + --light-b-1: #55dd33; + --letter-spacing: 0.024em; + --dark-f-2: #ccbb33; + --light-b1: #cccccc; + --light-b0: #dddddd; + --light-b3: #aaaaaa; + --light-b-3: #dd5522; + --dark-b4: #666666; + --light-f2: #444444; + --dark-b-3: #551111; + --dark-f4: #888888; + --dark-f-3: #ee7755; + --light-b-2: #ddaa33; + --sky-opacity: 0.88; + --dark-b0: #222222; + --light-b2: #bbbbbb; + --light-f-3: #993311; + --light-f1: #333333; + --dark-f-1: #55cc33; + --dark-b1: #333333; + --dark-f1: #cccccc; + --light-f3: #555555; + --light-b4: #999999; + --sky-bg-url: ; + --dark-f3: #aaaaaa; + --light-f0: #111111; + --sky-bg-size: contain; + --light-f4: #777777; + --1in: 4px; + --font-mono: monospace; + --dark-f0: #eeeeee; + --sky-inner-gap: 8px; + --dark-b2: #444444; + --dark-b-2: #555511; + --font: Urbit Sans, sans-serif; + --mono-scale: 0.8; + --light-f-2: #aaaa22; + --dark-f2: #bbbbbb; + --light-f-1: #339911; + } + ''' +== +--