diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a63acbc --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "standardx", + "rules": { + "camelcase": "off" + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e61ecf1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.detectIndentation": false -} \ No newline at end of file diff --git a/bin/tree.js b/bin/tree.js new file mode 100644 index 0000000..1f97cc3 --- /dev/null +++ b/bin/tree.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +const fs = require('fs').promises +const path = require('path') + +const default_ignore_patterns = [ + '/node_modules', + '.git' +] + +function should_ignore (file_path, ignore_patterns) { + const normalized_path = file_path.replace(/\\/g, '/') + + for (const pattern of ignore_patterns) { + if (pattern.startsWith('/')) { + const pattern_without_slash = pattern.slice(1) + if (normalized_path === pattern_without_slash || normalized_path.startsWith(`${pattern_without_slash}/`)) { + return true + } + continue + } + const regex_pattern = pattern + .replace(/[.+^${}()|[\]\\]/g, '\\$&') + .replace(/\*/g, '.*') + + const regex = new RegExp(`^${regex_pattern}$|${regex_pattern}/|/${regex_pattern}$`) + if (regex.test(normalized_path)) { + return true + } + } + return false +} + +async function walk_directory (dir, ignore_patterns) { + const files = [] + const root_dir = process.cwd() + + async function walk (current_path, relative_path = '') { + const entries = await fs.readdir(current_path, { withFileTypes: true }) + + for (const entry of entries) { + const full_path = path.join(current_path, entry.name) + const rel_path = path.join(relative_path, entry.name) + const normalized_path = rel_path.replace(/\\/g, '/') + + if (should_ignore(normalized_path, ignore_patterns)) { + continue + } + if (entry.isDirectory()) { + await walk(full_path, rel_path) + } else { + files.push(normalized_path) + } + } + } + + await walk(dir) + return files +} + +async function main () { + const files = await walk_directory(process.cwd(), default_ignore_patterns) + files.sort() + await fs.writeFile( + path.join(process.cwd(), 'index.json'), + JSON.stringify(files, null, 2) + ) + console.log('Successfully generated index.json') +} + +main() diff --git a/bundle.js b/bundle.js index 5cae7df..b0b1a47 100644 --- a/bundle.js +++ b/bundle.js @@ -1,4844 +1,3802 @@ (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i` -const icon32 = bel`` -const icon16 = bel`` -const webmanifest = bel`` -document.head.append(appleTouch, icon32, icon16, webmanifest) - -const params = new URL(location.href).searchParams -const lang = params.get('lang') - -if (lang === 'en') { - params.delete('lang') - location.search = params -} - -const styles = csjs` -html { - font-size: 82.5%; - scroll-behavior: smooth; -} -body { - font-family: var(--bodyFont); - font-size: 1.4rem; - color: var(--bodyColor); - margin: 0; - padding: 0; - background-color: var(--bodyBg); - overflow-x: hidden; -} -a { - text-decoration: none; -} -button { - outline: none; - border: none; - font-family: var(--titleFont); - font-size: var(--sectionButtonSize); - color: var(--titleColor); - border-radius: 2rem; - padding: 1.2rem 3.8rem; - cursor: pointer; -} -img { - width: 100%; - height: auto; -} -article { - font-size: var(--articleSize); - color: var(--articleColor); - line-height: 2.5rem; - padding-bottom: 4rem; -} -@media only screen and (min-width: 2561px) { - article { - font-size: calc(var(--articleSize) * 1.5 ); - line-height: calc(2.5rem * 1.5); - } - button { - font-size: calc(var(--sectionButtonSize) * 1.5 ); -} -} -@media only screen and (min-width: 4096px) { - article { - font-size: calc(var(--articleSize) * 2.25 ); - line-height: calc(2.5rem * 2.25); - } - button { - font-size: calc(var(--sectionButtonSize) * 2.25 ); - } -} -` - -// callback done() -const el = (err, landingPage) => { - const vars = theme - - if (err) { - document.body.style = `color: red; font-size: 1.6rem; text-align:center; background-color: #d9d9d9;` - document.body.innerHTML = `

${err.stack}

` - } else { - document.body.appendChild(landingPage) - } - - updateTheme(vars) -} - -function updateTheme (vars) { - Object.keys(vars).forEach(name => { - document.body.style.setProperty(`--${name}`, vars[name]) - }) -} +(function (global){(function (){ -make_page({theme}, el, lang) -},{"../":29,"bel":4,"csjs-inject":7,"theme":2}],2:[function(require,module,exports){ -const bel = require('bel') -const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' -const loadFont = bel`` -document.head.appendChild(loadFont) - -const defines = { - fonts: { - slackey : `'Slackey', Arial, sans-serif`, - nunito : `'Nunito', Arial, sans-serif`, - }, - sizes: { - 'xx-small' : '1.2rem', - 'x-small' : '1.3rem', - small : '1.4rem', - medium : '1.6rem', - large : '2rem', - 'x-large' : '3rem', - 'xx-large' : '4rem', - 'xxx-large' : '5rem', - }, - colors: { - white : '#fff', - skyblue : '#b3e2ff', - turquoise : '#aae6ed', - pink : '#e14365', - grey : '#333333', - lightGrey : '#999999', - lightGreen : '#a1e9da', - blueGreen : '#00a6ad', - purple : '#b337fb', - lightBluePurple : '#9db9ee', - bluePurple : '#9a91ff', - lightPurple : '#beb2d7', - lightYellow : '#eddca4', - lightSky : '#b4e4fd', - green : '#4aa95b', - lowYellow : '#fdfbee', - brown : '#b06d56', - } -} +// ------------------------------------------ +// Rellax.js +// Buttery smooth parallax library +// Copyright (c) 2016 Moe Amaya (@moeamaya) +// MIT license +// +// Thanks to Paraxify.js and Jaime Cabllero +// for parallax concepts +// ------------------------------------------ -const theme = { - bodyFont : defines.fonts.nunito, - bodyColor : defines.colors.grey, - bodyBg : defines.colors.lightSky, - menuSize : defines.sizes.small, - titleFont : defines.fonts.slackey, - titleSize : defines.sizes['xxx-large'], - titleSizeM : '3.6rem', - titlesSizeS : '2.8rem', - titleColor : defines.colors.white, - playBgGStart : defines.colors.skyblue, - playBgGEnd : defines.colors.turquoise, - subTitleSize : '4.2rem', - section1TitleColor : defines.colors.pink, - section2TitleColor : defines.colors.blueGreen, - section3TitleColor : defines.colors.purple, - section4TitleColor : defines.colors.brown, - section5TitleColor : defines.colors.green, - articleSize : defines.sizes.small, - articleColor : defines.colors.grey, - section1BgGStart : defines.colors.turquoise, - section1BgGEnd : defines.colors.lightGreen, - section2BgGStart : defines.colors.lightGreen, - section2BgGEnd : defines.colors.lightBluePurple, - section3BgGStart : defines.colors.lightBluePurple, - section3BgGEnd : defines.colors.bluePurple, - section4BgGStart : defines.colors.bluePurple, - section4BgGEnd : defines.colors.lightPurple, - section5BgGStart : defines.colors.lightPurple, - section5BgGMiddle : defines.colors.lightYellow, - section5BgGEnd : defines.colors.lightSky, - sectionButtonSize : defines.sizes.small, - roadmapHeadlline : '4rem', - roadmapHeadllineM : '3rem', - roadmapHeadllineS : '1.6rem', - roadmapTitleSize : defines.sizes.large, - roadmapTitleSizeM : defines.sizes.medium, - roadmapTitleColor : defines.colors.blueGreen, - roadmapTextSize : defines.sizes.medium, - roadmapTextSizeM : defines.sizes["x-small"], - contributorsBg : defines.colors.lowYellow, - contributorsTextSize : defines.sizes.small, - contributorsTextSizeS : defines.sizes["xx-small"], - contributorsCareerColor : defines.colors.lightGrey, - footerTextColor : defines.colors.grey, - footerBg : defines.colors.lightSky -} +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.Rellax = factory(); + } +}(typeof window !== "undefined" ? window : global, function () { + var Rellax = function(el, options){ + "use strict"; -module.exports = theme + var self = Object.create(Rellax.prototype); -},{"bel":4}],3:[function(require,module,exports){ -var trailingNewlineRegex = /\n[\s]+$/ -var leadingNewlineRegex = /^\n[\s]+/ -var trailingSpaceRegex = /[\s]+$/ -var leadingSpaceRegex = /^[\s]+/ -var multiSpaceRegex = /[\n\s]+/g + var posY = 0; + var screenY = 0; + var posX = 0; + var screenX = 0; + var blocks = []; + var pause = true; -var TEXT_TAGS = [ - 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'data', 'dfn', 'em', 'i', - 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby', 's', 'amp', 'small', 'span', - 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr' -] + // check what requestAnimationFrame to use, and if + // it's not supported, use the onscroll event + var loop = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.msRequestAnimationFrame || + window.oRequestAnimationFrame || + function(callback){ return setTimeout(callback, 1000 / 60); }; -var VERBATIM_TAGS = [ - 'code', 'pre', 'textarea' -] + // store the id for later use + var loopId = null; -module.exports = function appendChild (el, childs) { - if (!Array.isArray(childs)) return + // Test via a getter in the options object to see if the passive property is accessed + var supportsPassive = false; + try { + var opts = Object.defineProperty({}, 'passive', { + get: function() { + supportsPassive = true; + } + }); + window.addEventListener("testPassive", null, opts); + window.removeEventListener("testPassive", null, opts); + } catch (e) {} - var nodeName = el.nodeName.toLowerCase() + // check what cancelAnimation method to use + var clearLoop = window.cancelAnimationFrame || window.mozCancelAnimationFrame || clearTimeout; - var hadText = false - var value, leader + // check which transform property to use + var transformProp = window.transformProp || (function(){ + var testEl = document.createElement('div'); + if (testEl.style.transform === null) { + var vendors = ['Webkit', 'Moz', 'ms']; + for (var vendor in vendors) { + if (testEl.style[ vendors[vendor] + 'Transform' ] !== undefined) { + return vendors[vendor] + 'Transform'; + } + } + } + return 'transform'; + })(); - for (var i = 0, len = childs.length; i < len; i++) { - var node = childs[i] - if (Array.isArray(node)) { - appendChild(el, node) - continue - } + // Default Settings + self.options = { + speed: -2, + verticalSpeed: null, + horizontalSpeed: null, + breakpoints: [576, 768, 1201], + center: false, + wrapper: null, + relativeToWrapper: false, + round: true, + vertical: true, + horizontal: false, + verticalScrollAxis: "y", + horizontalScrollAxis: "x", + callback: function() {}, + }; - if (typeof node === 'number' || - typeof node === 'boolean' || - typeof node === 'function' || - node instanceof Date || - node instanceof RegExp) { - node = node.toString() + // User defined options (might have more in the future) + if (options){ + Object.keys(options).forEach(function(key){ + self.options[key] = options[key]; + }); } - var lastChild = el.childNodes[el.childNodes.length - 1] - - // Iterate over text nodes - if (typeof node === 'string') { - hadText = true - - // If we already had text, append to the existing text - if (lastChild && lastChild.nodeName === '#text') { - lastChild.nodeValue += node - - // We didn't have a text node yet, create one - } else { - node = document.createTextNode(node) - el.appendChild(node) - lastChild = node - } - - // If this is the last of the child nodes, make sure we close it out - // right - if (i === len - 1) { - hadText = false - // Trim the child text nodes if the current node isn't a - // node where whitespace matters. - if (TEXT_TAGS.indexOf(nodeName) === -1 && - VERBATIM_TAGS.indexOf(nodeName) === -1) { - value = lastChild.nodeValue - .replace(leadingNewlineRegex, '') - .replace(trailingSpaceRegex, '') - .replace(trailingNewlineRegex, '') - .replace(multiSpaceRegex, ' ') - if (value === '') { - el.removeChild(lastChild) - } else { - lastChild.nodeValue = value - } - } else if (VERBATIM_TAGS.indexOf(nodeName) === -1) { - // The very first node in the list should not have leading - // whitespace. Sibling text nodes should have whitespace if there - // was any. - leader = i === 0 ? '' : ' ' - value = lastChild.nodeValue - .replace(leadingNewlineRegex, leader) - .replace(leadingSpaceRegex, ' ') - .replace(trailingSpaceRegex, '') - .replace(trailingNewlineRegex, '') - .replace(multiSpaceRegex, ' ') - lastChild.nodeValue = value - } - } - - // Iterate over DOM nodes - } else if (node && node.nodeType) { - // If the last node was a text node, make sure it is properly closed out - if (hadText) { - hadText = false - - // Trim the child text nodes if the current node isn't a - // text node or a code node - if (TEXT_TAGS.indexOf(nodeName) === -1 && - VERBATIM_TAGS.indexOf(nodeName) === -1) { - value = lastChild.nodeValue - .replace(leadingNewlineRegex, '') - .replace(trailingNewlineRegex, '') - .replace(multiSpaceRegex, ' ') - - // Remove empty text nodes, append otherwise - if (value === '') { - el.removeChild(lastChild) - } else { - lastChild.nodeValue = value + function validateCustomBreakpoints () { + if (self.options.breakpoints.length === 3 && Array.isArray(self.options.breakpoints)) { + var isAscending = true; + var isNumerical = true; + var lastVal; + self.options.breakpoints.forEach(function (i) { + if (typeof i !== 'number') isNumerical = false; + if (lastVal !== null) { + if (i < lastVal) isAscending = false; } - // Trim the child nodes if the current node is not a node - // where all whitespace must be preserved - } else if (VERBATIM_TAGS.indexOf(nodeName) === -1) { - value = lastChild.nodeValue - .replace(leadingSpaceRegex, ' ') - .replace(leadingNewlineRegex, '') - .replace(trailingNewlineRegex, '') - .replace(multiSpaceRegex, ' ') - lastChild.nodeValue = value - } + lastVal = i; + }); + if (isAscending && isNumerical) return; } + // revert defaults if set incorrectly + self.options.breakpoints = [576, 768, 1201]; + console.warn("Rellax: You must pass an array of 3 numbers in ascending order to the breakpoints option. Defaults reverted"); + } - // Store the last nodename - var _nodeName = node.nodeName - if (_nodeName) nodeName = _nodeName.toLowerCase() + if (options && options.breakpoints) { + validateCustomBreakpoints(); + } - // Append the node to the DOM - el.appendChild(node) + // By default, rellax class + if (!el) { + el = '.rellax'; } - } -} -},{}],4:[function(require,module,exports){ -var hyperx = require('hyperx') -var appendChild = require('./appendChild') - -var SVGNS = 'http://www.w3.org/2000/svg' -var XLINKNS = 'http://www.w3.org/1999/xlink' - -var BOOL_PROPS = [ - 'autofocus', 'checked', 'defaultchecked', 'disabled', 'formnovalidate', - 'indeterminate', 'readonly', 'required', 'selected', 'willvalidate' -] - -var COMMENT_TAG = '!--' - -var SVG_TAGS = [ - 'svg', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateColor', - 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'color-profile', - 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', - 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', - 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', - 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', - 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', - 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', - 'font', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', - 'font-face-uri', 'foreignObject', 'g', 'glyph', 'glyphRef', 'hkern', 'image', - 'line', 'linearGradient', 'marker', 'mask', 'metadata', 'missing-glyph', - 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', - 'set', 'stop', 'switch', 'symbol', 'text', 'textPath', 'title', 'tref', - 'tspan', 'use', 'view', 'vkern' -] - -function belCreateElement (tag, props, children) { - var el - - // If an svg tag, it needs a namespace - if (SVG_TAGS.indexOf(tag) !== -1) { - props.namespace = SVGNS - } + // check if el is a className or a node + var elements = typeof el === 'string' ? document.querySelectorAll(el) : [el]; - // If we are using a namespace - var ns = false - if (props.namespace) { - ns = props.namespace - delete props.namespace - } + // Now query selector + if (elements.length > 0) { + self.elems = elements; + } - // Create the element - if (ns) { - el = document.createElementNS(ns, tag) - } else if (tag === COMMENT_TAG) { - return document.createComment(props.comment) - } else { - el = document.createElement(tag) - } + // The elements don't exist + else { + console.warn("Rellax: The elements you're trying to select don't exist."); + return; + } - // Create the properties - for (var p in props) { - if (props.hasOwnProperty(p)) { - var key = p.toLowerCase() - var val = props[p] - // Normalize className - if (key === 'classname') { - key = 'class' - p = 'class' - } - // The for attribute gets transformed to htmlFor, but we just set as for - if (p === 'htmlFor') { - p = 'for' - } - // If a property is boolean, set itself to the key - if (BOOL_PROPS.indexOf(key) !== -1) { - if (val === 'true') val = key - else if (val === 'false') continue - } - // If a property prefers being set directly vs setAttribute - if (key.slice(0, 2) === 'on') { - el[p] = val - } else { - if (ns) { - if (p === 'xlink:href') { - el.setAttributeNS(XLINKNS, p, val) - } else if (/^xmlns($|:)/i.test(p)) { - // skip xmlns definitions - } else { - el.setAttributeNS(null, p, val) - } + // Has a wrapper and it exists + if (self.options.wrapper) { + if (!self.options.wrapper.nodeType) { + var wrapper = document.querySelector(self.options.wrapper); + + if (wrapper) { + self.options.wrapper = wrapper; } else { - el.setAttribute(p, val) + console.warn("Rellax: The wrapper you're trying to use doesn't exist."); + return; } } } - } - appendChild(el, children) - return el -} + // set a placeholder for the current breakpoint + var currentBreakpoint; -module.exports = hyperx(belCreateElement, {comments: true}) -module.exports.default = module.exports -module.exports.createElement = belCreateElement + // helper to determine current breakpoint + var getCurrentBreakpoint = function (w) { + var bp = self.options.breakpoints; + if (w < bp[0]) return 'xs'; + if (w >= bp[0] && w < bp[1]) return 'sm'; + if (w >= bp[1] && w < bp[2]) return 'md'; + return 'lg'; + }; -},{"./appendChild":3,"hyperx":25}],5:[function(require,module,exports){ -(function (global){ -'use strict'; + // Get and cache initial position of all elements + var cacheBlocks = function() { + for (var i = 0; i < self.elems.length; i++){ + var block = createBlock(self.elems[i]); + blocks.push(block); + } + }; -var csjs = require('csjs'); -var insertCss = require('insert-css'); -function csjsInserter() { - var args = Array.prototype.slice.call(arguments); - var result = csjs.apply(null, args); - if (global.document) { - insertCss(csjs.getCss(result)); - } - return result; -} + // Let's kick this script off + // Build array for cached element values + var init = function() { + for (var i = 0; i < blocks.length; i++){ + self.elems[i].style.cssText = blocks[i].style; + } -module.exports = csjsInserter; + blocks = []; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"csjs":10,"insert-css":26}],6:[function(require,module,exports){ -'use strict'; + screenY = window.innerHeight; + screenX = window.innerWidth; + currentBreakpoint = getCurrentBreakpoint(screenX); -module.exports = require('csjs/get-css'); + setPosition(); -},{"csjs/get-css":9}],7:[function(require,module,exports){ -'use strict'; + cacheBlocks(); -var csjs = require('./csjs'); + animate(); -module.exports = csjs; -module.exports.csjs = csjs; -module.exports.getCss = require('./get-css'); + // If paused, unpause and set listener for window resizing events + if (pause) { + window.addEventListener('resize', init); + pause = false; + // Start the loop + update(); + } + }; -},{"./csjs":5,"./get-css":6}],8:[function(require,module,exports){ -'use strict'; + // We want to cache the parallax blocks' + // values: base, top, height, speed + // el: is dom object, return: el cache values + var createBlock = function(el) { + var dataPercentage = el.getAttribute( 'data-rellax-percentage' ); + var dataSpeed = el.getAttribute( 'data-rellax-speed' ); + var dataXsSpeed = el.getAttribute( 'data-rellax-xs-speed' ); + var dataMobileSpeed = el.getAttribute( 'data-rellax-mobile-speed' ); + var dataTabletSpeed = el.getAttribute( 'data-rellax-tablet-speed' ); + var dataDesktopSpeed = el.getAttribute( 'data-rellax-desktop-speed' ); + var dataVerticalSpeed = el.getAttribute('data-rellax-vertical-speed'); + var dataHorizontalSpeed = el.getAttribute('data-rellax-horizontal-speed'); + var dataVericalScrollAxis = el.getAttribute('data-rellax-vertical-scroll-axis'); + var dataHorizontalScrollAxis = el.getAttribute('data-rellax-horizontal-scroll-axis'); + var dataZindex = el.getAttribute( 'data-rellax-zindex' ) || 0; + var dataMin = el.getAttribute( 'data-rellax-min' ); + var dataMax = el.getAttribute( 'data-rellax-max' ); + var dataMinX = el.getAttribute('data-rellax-min-x'); + var dataMaxX = el.getAttribute('data-rellax-max-x'); + var dataMinY = el.getAttribute('data-rellax-min-y'); + var dataMaxY = el.getAttribute('data-rellax-max-y'); + var mapBreakpoints; + var breakpoints = true; -module.exports = require('./lib/csjs'); + if (!dataXsSpeed && !dataMobileSpeed && !dataTabletSpeed && !dataDesktopSpeed) { + breakpoints = false; + } else { + mapBreakpoints = { + 'xs': dataXsSpeed, + 'sm': dataMobileSpeed, + 'md': dataTabletSpeed, + 'lg': dataDesktopSpeed + }; + } -},{"./lib/csjs":14}],9:[function(require,module,exports){ -'use strict'; - -module.exports = require('./lib/get-css'); + // initializing at scrollY = 0 (top of browser), scrollX = 0 (left of browser) + // ensures elements are positioned based on HTML layout. + // + // If the element has the percentage attribute, the posY and posX needs to be + // the current scroll position's value, so that the elements are still positioned based on HTML layout + var wrapperPosY = self.options.wrapper ? self.options.wrapper.scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + // If the option relativeToWrapper is true, use the wrappers offset to top, subtracted from the current page scroll. + if (self.options.relativeToWrapper) { + var scrollPosY = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + wrapperPosY = scrollPosY - self.options.wrapper.offsetTop; + } + var posY = self.options.vertical ? ( dataPercentage || self.options.center ? wrapperPosY : 0 ) : 0; + var posX = self.options.horizontal ? ( dataPercentage || self.options.center ? self.options.wrapper ? self.options.wrapper.scrollLeft : (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) : 0 ) : 0; -},{"./lib/get-css":18}],10:[function(require,module,exports){ -'use strict'; + var blockTop = posY + el.getBoundingClientRect().top; + var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight; -var csjs = require('./csjs'); + var blockLeft = posX + el.getBoundingClientRect().left; + var blockWidth = el.clientWidth || el.offsetWidth || el.scrollWidth; -module.exports = csjs(); -module.exports.csjs = csjs; -module.exports.noScope = csjs({ noscope: true }); -module.exports.getCss = require('./get-css'); + // apparently parallax equation everyone uses + var percentageY = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY); + var percentageX = dataPercentage ? dataPercentage : (posX - blockLeft + screenX) / (blockWidth + screenX); + if(self.options.center){ percentageX = 0.5; percentageY = 0.5; } -},{"./csjs":8,"./get-css":9}],11:[function(require,module,exports){ -'use strict'; + // Optional individual block speed as data attr, otherwise global speed + var speed = (breakpoints && mapBreakpoints[currentBreakpoint] !== null) ? Number(mapBreakpoints[currentBreakpoint]) : (dataSpeed ? dataSpeed : self.options.speed); + var verticalSpeed = dataVerticalSpeed ? dataVerticalSpeed : self.options.verticalSpeed; + var horizontalSpeed = dataHorizontalSpeed ? dataHorizontalSpeed : self.options.horizontalSpeed; -/** - * base62 encode implementation based on base62 module: - * https://github.com/andrew/base62.js - */ + // Optional individual block movement axis direction as data attr, otherwise gobal movement direction + var verticalScrollAxis = dataVericalScrollAxis ? dataVericalScrollAxis : self.options.verticalScrollAxis; + var horizontalScrollAxis = dataHorizontalScrollAxis ? dataHorizontalScrollAxis : self.options.horizontalScrollAxis; -var CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + var bases = updatePosition(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed); -module.exports = function encode(integer) { - if (integer === 0) { - return '0'; - } - var str = ''; - while (integer > 0) { - str = CHARS[integer % 62] + str; - integer = Math.floor(integer / 62); - } - return str; -}; + // ~~Store non-translate3d transforms~~ + // Store inline styles and extract transforms + var style = el.style.cssText; + var transform = ''; -},{}],12:[function(require,module,exports){ -'use strict'; - -var makeComposition = require('./composition').makeComposition; - -module.exports = function createExports(classes, keyframes, compositions) { - var keyframesObj = Object.keys(keyframes).reduce(function(acc, key) { - var val = keyframes[key]; - acc[val] = makeComposition([key], [val], true); - return acc; - }, {}); - - var exports = Object.keys(classes).reduce(function(acc, key) { - var val = classes[key]; - var composition = compositions[key]; - var extended = composition ? getClassChain(composition) : []; - var allClasses = [key].concat(extended); - var unscoped = allClasses.map(function(name) { - return classes[name] ? classes[name] : name; - }); - acc[val] = makeComposition(allClasses, unscoped); - return acc; - }, keyframesObj); - - return exports; -} + // Check if there's an inline styled transform + var searchResult = /transform\s*:/i.exec(style); + if (searchResult) { + // Get the index of the transform + var index = searchResult.index; -function getClassChain(obj) { - var visited = {}, acc = []; + // Trim the style to the transform point and get the following semi-colon index + var trimmedStyle = style.slice(index); + var delimiter = trimmedStyle.indexOf(';'); - function traverse(obj) { - return Object.keys(obj).forEach(function(key) { - if (!visited[key]) { - visited[key] = true; - acc.push(key); - traverse(obj[key]); + // Remove "transform" string and save the attribute + if (delimiter) { + transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g,''); + } else { + transform = " " + trimmedStyle.slice(11).replace(/\s/g,''); + } } - }); - } - - traverse(obj); - return acc; -} -},{"./composition":13}],13:[function(require,module,exports){ -'use strict'; - -module.exports = { - makeComposition: makeComposition, - isComposition: isComposition, - ignoreComposition: ignoreComposition -}; - -/** - * Returns an immutable composition object containing the given class names - * @param {array} classNames - The input array of class names - * @return {Composition} - An immutable object that holds multiple - * representations of the class composition - */ -function makeComposition(classNames, unscoped, isAnimation) { - var classString = classNames.join(' '); - return Object.create(Composition.prototype, { - classNames: { // the original array of class names - value: Object.freeze(classNames), - configurable: false, - writable: false, - enumerable: true - }, - unscoped: { // the original array of class names - value: Object.freeze(unscoped), - configurable: false, - writable: false, - enumerable: true - }, - className: { // space-separated class string for use in HTML - value: classString, - configurable: false, - writable: false, - enumerable: true - }, - selector: { // comma-separated, period-prefixed string for use in CSS - value: classNames.map(function(name) { - return isAnimation ? name : '.' + name; - }).join(', '), - configurable: false, - writable: false, - enumerable: true - }, - toString: { // toString() method, returns class string for use in HTML - value: function() { - return classString; - }, - configurable: false, - writeable: false, - enumerable: false - } - }); -} + return { + baseX: bases.x, + baseY: bases.y, + top: blockTop, + left: blockLeft, + height: blockHeight, + width: blockWidth, + speed: speed, + verticalSpeed: verticalSpeed, + horizontalSpeed: horizontalSpeed, + verticalScrollAxis: verticalScrollAxis, + horizontalScrollAxis: horizontalScrollAxis, + style: style, + transform: transform, + zindex: dataZindex, + min: dataMin, + max: dataMax, + minX: dataMinX, + maxX: dataMaxX, + minY: dataMinY, + maxY: dataMaxY + }; + }; -/** - * Returns whether the input value is a Composition - * @param value - value to check - * @return {boolean} - whether value is a Composition or not - */ -function isComposition(value) { - return value instanceof Composition; -} + // set scroll position (posY, posX) + // side effect method is not ideal, but okay for now + // returns true if the scroll changed, false if nothing happened + var setPosition = function() { + var oldY = posY; + var oldX = posX; -function ignoreComposition(values) { - return values.reduce(function(acc, val) { - if (isComposition(val)) { - val.classNames.forEach(function(name, i) { - acc[name] = val.unscoped[i]; - }); - } - return acc; - }, {}); -} + posY = self.options.wrapper ? self.options.wrapper.scrollTop : (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset; + posX = self.options.wrapper ? self.options.wrapper.scrollLeft : (document.documentElement || document.body.parentNode || document.body).scrollLeft || window.pageXOffset; + // If option relativeToWrapper is true, use relative wrapper value instead. + if (self.options.relativeToWrapper) { + var scrollPosY = (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset; + posY = scrollPosY - self.options.wrapper.offsetTop; + } -/** - * Private constructor for use in `instanceof` checks - */ -function Composition() {} - -},{}],14:[function(require,module,exports){ -'use strict'; - -var extractExtends = require('./css-extract-extends'); -var composition = require('./composition'); -var isComposition = composition.isComposition; -var ignoreComposition = composition.ignoreComposition; -var buildExports = require('./build-exports'); -var scopify = require('./scopeify'); -var cssKey = require('./css-key'); -var extractExports = require('./extract-exports'); - -module.exports = function csjsTemplate(opts) { - opts = (typeof opts === 'undefined') ? {} : opts; - var noscope = (typeof opts.noscope === 'undefined') ? false : opts.noscope; - - return function csjsHandler(strings, values) { - // Fast path to prevent arguments deopt - var values = Array(arguments.length - 1); - for (var i = 1; i < arguments.length; i++) { - values[i - 1] = arguments[i]; - } - var css = joiner(strings, values.map(selectorize)); - var ignores = ignoreComposition(values); - - var scope = noscope ? extractExports(css) : scopify(css, ignores); - var extracted = extractExtends(scope.css); - var localClasses = without(scope.classes, ignores); - var localKeyframes = without(scope.keyframes, ignores); - var compositions = extracted.compositions; - - var exports = buildExports(localClasses, localKeyframes, compositions); - - return Object.defineProperty(exports, cssKey, { - enumerable: false, - configurable: false, - writeable: false, - value: extracted.css - }); - } -} -/** - * Replaces class compositions with comma seperated class selectors - * @param value - the potential class composition - * @return - the original value or the selectorized class composition - */ -function selectorize(value) { - return isComposition(value) ? value.selector : value; -} + if (oldY != posY && self.options.vertical) { + // scroll changed, return true + return true; + } -/** - * Joins template string literals and values - * @param {array} strings - array of strings - * @param {array} values - array of values - * @return {string} - strings and values joined - */ -function joiner(strings, values) { - return strings.map(function(str, i) { - return (i !== values.length) ? str + values[i] : str; - }).join(''); -} + if (oldX != posX && self.options.horizontal) { + // scroll changed, return true + return true; + } -/** - * Returns first object without keys of second - * @param {object} obj - source object - * @param {object} unwanted - object with unwanted keys - * @return {object} - first object without unwanted keys - */ -function without(obj, unwanted) { - return Object.keys(obj).reduce(function(acc, key) { - if (!unwanted[key]) { - acc[key] = obj[key]; - } - return acc; - }, {}); -} + // scroll did not change + return false; + }; -},{"./build-exports":12,"./composition":13,"./css-extract-extends":15,"./css-key":16,"./extract-exports":17,"./scopeify":23}],15:[function(require,module,exports){ -'use strict'; + // Ahh a pure function, gets new transform value + // based on scrollPosition and speed + // Allow for decimal pixel values + var updatePosition = function(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed) { + var result = {}; + var valueX = ((horizontalSpeed ? horizontalSpeed : speed) * (100 * (1 - percentageX))); + var valueY = ((verticalSpeed ? verticalSpeed : speed) * (100 * (1 - percentageY))); -var makeComposition = require('./composition').makeComposition; + result.x = self.options.round ? Math.round(valueX) : Math.round(valueX * 100) / 100; + result.y = self.options.round ? Math.round(valueY) : Math.round(valueY * 100) / 100; -var regex = /\.([^\s]+)(\s+)(extends\s+)(\.[^{]+)/g; + return result; + }; -module.exports = function extractExtends(css) { - var found, matches = []; - while (found = regex.exec(css)) { - matches.unshift(found); - } + // Remove event listeners and loop again + var deferredUpdate = function() { + window.removeEventListener('resize', deferredUpdate); + window.removeEventListener('orientationchange', deferredUpdate); + (self.options.wrapper ? self.options.wrapper : window).removeEventListener('scroll', deferredUpdate); + (self.options.wrapper ? self.options.wrapper : document).removeEventListener('touchmove', deferredUpdate); - function extractCompositions(acc, match) { - var extendee = getClassName(match[1]); - var keyword = match[3]; - var extended = match[4]; + // loop again + loopId = loop(update); + }; - // remove from output css - var index = match.index + match[1].length + match[2].length; - var len = keyword.length + extended.length; - acc.css = acc.css.slice(0, index) + " " + acc.css.slice(index + len + 1); + // Loop + var update = function() { + if (setPosition() && pause === false) { + animate(); - var extendedClasses = splitter(extended); + // loop again + loopId = loop(update); + } else { + loopId = null; - extendedClasses.forEach(function(className) { - if (!acc.compositions[extendee]) { - acc.compositions[extendee] = {}; - } - if (!acc.compositions[className]) { - acc.compositions[className] = {}; + // Don't animate until we get a position updating event + window.addEventListener('resize', deferredUpdate); + window.addEventListener('orientationchange', deferredUpdate); + (self.options.wrapper ? self.options.wrapper : window).addEventListener('scroll', deferredUpdate, supportsPassive ? { passive: true } : false); + (self.options.wrapper ? self.options.wrapper : document).addEventListener('touchmove', deferredUpdate, supportsPassive ? { passive: true } : false); } - acc.compositions[extendee][className] = acc.compositions[className]; - }); - return acc; - } - - return matches.reduce(extractCompositions, { - css: css, - compositions: {} - }); + }; -}; + // Transform3d on parallax element + var animate = function() { + var positions; + for (var i = 0; i < self.elems.length; i++){ + // Determine relevant movement directions + var verticalScrollAxis = blocks[i].verticalScrollAxis.toLowerCase(); + var horizontalScrollAxis = blocks[i].horizontalScrollAxis.toLowerCase(); + var verticalScrollX = verticalScrollAxis.indexOf("x") != -1 ? posY : 0; + var verticalScrollY = verticalScrollAxis.indexOf("y") != -1 ? posY : 0; + var horizontalScrollX = horizontalScrollAxis.indexOf("x") != -1 ? posX : 0; + var horizontalScrollY = horizontalScrollAxis.indexOf("y") != -1 ? posX : 0; -function splitter(match) { - return match.split(',').map(getClassName); -} + var percentageY = ((verticalScrollY + horizontalScrollY - blocks[i].top + screenY) / (blocks[i].height + screenY)); + var percentageX = ((verticalScrollX + horizontalScrollX - blocks[i].left + screenX) / (blocks[i].width + screenX)); -function getClassName(str) { - var trimmed = str.trim(); - return trimmed[0] === '.' ? trimmed.substr(1) : trimmed; -} + // Subtracting initialize value, so element stays in same spot as HTML + positions = updatePosition(percentageX, percentageY, blocks[i].speed, blocks[i].verticalSpeed, blocks[i].horizontalSpeed); + var positionY = positions.y - blocks[i].baseY; + var positionX = positions.x - blocks[i].baseX; -},{"./composition":13}],16:[function(require,module,exports){ -'use strict'; + // The next two "if" blocks go like this: + // Check if a limit is defined (first "min", then "max"); + // Check if we need to change the Y or the X + // (Currently working only if just one of the axes is enabled) + // Then, check if the new position is inside the allowed limit + // If so, use new position. If not, set position to limit. -/** - * CSS identifiers with whitespace are invalid - * Hence this key will not cause a collision - */ + // Check if a min limit is defined + if (blocks[i].min !== null) { + if (self.options.vertical && !self.options.horizontal) { + positionY = positionY <= blocks[i].min ? blocks[i].min : positionY; + } + if (self.options.horizontal && !self.options.vertical) { + positionX = positionX <= blocks[i].min ? blocks[i].min : positionX; + } + } -module.exports = ' css '; + // Check if directional min limits are defined + if (blocks[i].minY != null) { + positionY = positionY <= blocks[i].minY ? blocks[i].minY : positionY; + } + if (blocks[i].minX != null) { + positionX = positionX <= blocks[i].minX ? blocks[i].minX : positionX; + } -},{}],17:[function(require,module,exports){ -'use strict'; + // Check if a max limit is defined + if (blocks[i].max !== null) { + if (self.options.vertical && !self.options.horizontal) { + positionY = positionY >= blocks[i].max ? blocks[i].max : positionY; + } + if (self.options.horizontal && !self.options.vertical) { + positionX = positionX >= blocks[i].max ? blocks[i].max : positionX; + } + } -var regex = require('./regex'); -var classRegex = regex.classRegex; -var keyframesRegex = regex.keyframesRegex; + // Check if directional max limits are defined + if (blocks[i].maxY != null) { + positionY = positionY >= blocks[i].maxY ? blocks[i].maxY : positionY; + } + if (blocks[i].maxX != null) { + positionX = positionX >= blocks[i].maxX ? blocks[i].maxX : positionX; + } -module.exports = extractExports; + var zindex = blocks[i].zindex; -function extractExports(css) { - return { - css: css, - keyframes: getExport(css, keyframesRegex), - classes: getExport(css, classRegex) - }; -} + // Move that element + // (Set the new translation and append initial inline transforms.) + var translate = 'translate3d(' + (self.options.horizontal ? positionX : '0') + 'px,' + (self.options.vertical ? positionY : '0') + 'px,' + zindex + 'px) ' + blocks[i].transform; + self.elems[i].style[transformProp] = translate; + } + self.options.callback(positions); + }; -function getExport(css, regex) { - var prop = {}; - var match; - while((match = regex.exec(css)) !== null) { - var name = match[2]; - prop[name] = name; - } - return prop; -} + self.destroy = function() { + for (var i = 0; i < self.elems.length; i++){ + self.elems[i].style.cssText = blocks[i].style; + } -},{"./regex":20}],18:[function(require,module,exports){ -'use strict'; + // Remove resize event listener if not pause, and pause + if (!pause) { + window.removeEventListener('resize', init); + pause = true; + } -var cssKey = require('./css-key'); + // Clear the animation loop to prevent possible memory leak + clearLoop(loopId); + loopId = null; + }; -module.exports = function getCss(csjs) { - return csjs[cssKey]; -}; + // Init + init(); -},{"./css-key":16}],19:[function(require,module,exports){ -'use strict'; + // Allow to recalculate the initial values whenever we want + self.refresh = init; -/** - * djb2 string hash implementation based on string-hash module: - * https://github.com/darkskyapp/string-hash - */ - -module.exports = function hashStr(str) { - var hash = 5381; - var i = str.length; - - while (i) { - hash = (hash * 33) ^ str.charCodeAt(--i) - } - return hash >>> 0; -}; - -},{}],20:[function(require,module,exports){ -'use strict'; - -var findClasses = /(\.)(?!\d)([^\s\.,{\[>+~#:)]*)(?![^{]*})/.source; -var findKeyframes = /(@\S*keyframes\s*)([^{\s]*)/.source; -var ignoreComments = /(?!(?:[^*/]|\*[^/]|\/[^*])*\*+\/)/.source; - -var classRegex = new RegExp(findClasses + ignoreComments, 'g'); -var keyframesRegex = new RegExp(findKeyframes + ignoreComments, 'g'); - -module.exports = { - classRegex: classRegex, - keyframesRegex: keyframesRegex, - ignoreComments: ignoreComments, -}; - -},{}],21:[function(require,module,exports){ -var ignoreComments = require('./regex').ignoreComments; - -module.exports = replaceAnimations; - -function replaceAnimations(result) { - var animations = Object.keys(result.keyframes).reduce(function(acc, key) { - acc[result.keyframes[key]] = key; - return acc; - }, {}); - var unscoped = Object.keys(animations); - - if (unscoped.length) { - var regexStr = '((?:animation|animation-name)\\s*:[^};]*)(' - + unscoped.join('|') + ')([;\\s])' + ignoreComments; - var regex = new RegExp(regexStr, 'g'); - - var replaced = result.css.replace(regex, function(match, preamble, name, ending) { - return preamble + animations[name] + ending; - }); - - return { - css: replaced, - keyframes: result.keyframes, - classes: result.classes - } - } - - return result; -} - -},{"./regex":20}],22:[function(require,module,exports){ -'use strict'; - -var encode = require('./base62-encode'); -var hash = require('./hash-string'); - -module.exports = function fileScoper(fileSrc) { - var suffix = encode(hash(fileSrc)); - - return function scopedName(name) { - return name + '_' + suffix; - } -}; - -},{"./base62-encode":11,"./hash-string":19}],23:[function(require,module,exports){ -'use strict'; - -var fileScoper = require('./scoped-name'); -var replaceAnimations = require('./replace-animations'); -var regex = require('./regex'); -var classRegex = regex.classRegex; -var keyframesRegex = regex.keyframesRegex; - -module.exports = scopify; - -function scopify(css, ignores) { - var makeScopedName = fileScoper(css); - var replacers = { - classes: classRegex, - keyframes: keyframesRegex + return self; }; + return Rellax; +})); - function scopeCss(result, key) { - var replacer = replacers[key]; - function replaceFn(fullMatch, prefix, name) { - var scopedName = ignores[name] ? name : makeScopedName(name); - result[key][scopedName] = name; - return prefix + scopedName; +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],2:[function(require,module,exports){ +(function (__filename){(function (){ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'app' +const statedb = STATE(__filename) +const shopts = { mode: 'closed' } +// ---------------------------------------- +const { sdb, subs: [get], sub_modules } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return { + _: { + topnav: {}, + theme_widget: {}, + header: {}, + footer: {} } - return { - css: result.css.replace(replacer, replaceFn), - keyframes: result.keyframes, - classes: result.classes - }; } - - var result = Object.keys(replacers).reduce(scopeCss, { - css: css, - keyframes: {}, - classes: {} - }); - - return replaceAnimations(result); -} - -},{"./regex":20,"./replace-animations":21,"./scoped-name":22}],24:[function(require,module,exports){ -module.exports = attributeToProperty - -var transform = { - 'class': 'className', - 'for': 'htmlFor', - 'http-equiv': 'httpEquiv' } - -function attributeToProperty (h) { - return function (tagName, attrs, children) { - for (var attr in attrs) { - if (attr in transform) { - attrs[transform[attr]] = attrs[attr] - delete attrs[attr] +function fallback_instance () { + return { + _: { + topnav: {}, + theme_widget: {}, + header: {}, + footer: {} + }, + inputs: { + 'app.css': { + $ref: new URL('src/node_modules/css/default/app.css', location).href } } - return h(tagName, attrs, children) } } - -},{}],25:[function(require,module,exports){ -var attrToProp = require('hyperscript-attribute-to-property') - -var VAR = 0, TEXT = 1, OPEN = 2, CLOSE = 3, ATTR = 4 -var ATTR_KEY = 5, ATTR_KEY_W = 6 -var ATTR_VALUE_W = 7, ATTR_VALUE = 8 -var ATTR_VALUE_SQ = 9, ATTR_VALUE_DQ = 10 -var ATTR_EQ = 11, ATTR_BREAK = 12 -var COMMENT = 13 - -module.exports = function (h, opts) { - if (!opts) opts = {} - var concat = opts.concat || function (a, b) { - return String(a) + String(b) - } - if (opts.attrToProp !== false) { - h = attrToProp(h) +function override ([topnav]) { + const data = topnav() + console.log(data) + data['topnav.json'].data.links.push({ + id: 'app', + text: 'app', + url: 'app' + }) + return data +} +/****************************************************************************** + MAKE_PAGE COMPONENT +******************************************************************************/ +const IO = require('io') +const modules = { + [sub_modules.theme_widget]: require('theme_widget'), + [sub_modules.topnav]: require('topnav'), + [sub_modules.header]: require('header'), + // datdot : require('datdot'), + // editor : require('editor'), + // smartcontract_codes : require('smartcontract_codes'), + // supporters : require('supporters'), + // our_contributors : require('our_contributors'), + [sub_modules.footer]: require('footer') +} +module.exports = app + +async function app (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const on = { + jump, + css: inject } - return function (strings) { - var state = TEXT, reg = '' - var arglen = arguments.length - var parts = [] - - for (var i = 0; i < strings.length; i++) { - if (i < arglen - 1) { - var arg = arguments[i+1] - var p = parse(strings[i]) - var xstate = state - if (xstate === ATTR_VALUE_DQ) xstate = ATTR_VALUE - if (xstate === ATTR_VALUE_SQ) xstate = ATTR_VALUE - if (xstate === ATTR_VALUE_W) xstate = ATTR_VALUE - if (xstate === ATTR) xstate = ATTR_KEY - if (xstate === OPEN) { - if (reg === '/') { - p.push([ OPEN, '/', arg ]) - reg = '' - } else { - p.push([ OPEN, arg ]) - } - } else if (xstate === COMMENT && opts.comments) { - reg += String(arg) - } else if (xstate !== COMMENT) { - p.push([ VAR, xstate, arg ]) - } - parts.push.apply(parts, p) - } else parts.push.apply(parts, parse(strings[i])) - } - - var tree = [null,{},[]] - var stack = [[tree,-1]] - for (var i = 0; i < parts.length; i++) { - var cur = stack[stack.length-1][0] - var p = parts[i], s = p[0] - if (s === OPEN && /^\//.test(p[1])) { - var ix = stack[stack.length-1][1] - if (stack.length > 1) { - stack.pop() - stack[stack.length-1][0][2][ix] = h( - cur[0], cur[1], cur[2].length ? cur[2] : undefined - ) - } - } else if (s === OPEN) { - var c = [p[1],{},[]] - cur[2].push(c) - stack.push([c,cur[2].length-1]) - } else if (s === ATTR_KEY || (s === VAR && p[1] === ATTR_KEY)) { - var key = '' - var copyKey - for (; i < parts.length; i++) { - if (parts[i][0] === ATTR_KEY) { - key = concat(key, parts[i][1]) - } else if (parts[i][0] === VAR && parts[i][1] === ATTR_KEY) { - if (typeof parts[i][2] === 'object' && !key) { - for (copyKey in parts[i][2]) { - if (parts[i][2].hasOwnProperty(copyKey) && !cur[1][copyKey]) { - cur[1][copyKey] = parts[i][2][copyKey] - } - } - } else { - key = concat(key, parts[i][2]) - } - } else break - } - if (parts[i][0] === ATTR_EQ) i++ - var j = i - for (; i < parts.length; i++) { - if (parts[i][0] === ATTR_VALUE || parts[i][0] === ATTR_KEY) { - if (!cur[1][key]) cur[1][key] = strfn(parts[i][1]) - else parts[i][1]==="" || (cur[1][key] = concat(cur[1][key], parts[i][1])); - } else if (parts[i][0] === VAR - && (parts[i][1] === ATTR_VALUE || parts[i][1] === ATTR_KEY)) { - if (!cur[1][key]) cur[1][key] = strfn(parts[i][2]) - else parts[i][2]==="" || (cur[1][key] = concat(cur[1][key], parts[i][2])); - } else { - if (key.length && !cur[1][key] && i === j - && (parts[i][0] === CLOSE || parts[i][0] === ATTR_BREAK)) { - // https://html.spec.whatwg.org/multipage/infrastructure.html#boolean-attributes - // empty string is falsy, not well behaved value in browser - cur[1][key] = key.toLowerCase() - } - if (parts[i][0] === CLOSE) { - i-- - } - break - } - } - } else if (s === ATTR_KEY) { - cur[1][p[1]] = true - } else if (s === VAR && p[1] === ATTR_KEY) { - cur[1][p[2]] = true - } else if (s === CLOSE) { - if (selfClosing(cur[0]) && stack.length) { - var ix = stack[stack.length-1][1] - stack.pop() - stack[stack.length-1][0][2][ix] = h( - cur[0], cur[1], cur[2].length ? cur[2] : undefined - ) - } - } else if (s === VAR && p[1] === TEXT) { - if (p[2] === undefined || p[2] === null) p[2] = '' - else if (!p[2]) p[2] = concat('', p[2]) - if (Array.isArray(p[2][0])) { - cur[2].push.apply(cur[2], p[2]) - } else { - cur[2].push(p[2]) - } - } else if (s === TEXT) { - cur[2].push(p[1]) - } else if (s === ATTR_EQ || s === ATTR_BREAK) { - // no-op - } else { - throw new Error('unhandled: ' + s) - } - } - - if (tree[2].length > 1 && /^\s*$/.test(tree[2][0])) { - tree[2].shift() - } + const send = await IO(id, name, on) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+ ` + const style = shadow.querySelector('style') + const main = shadow.querySelector('div') + + const subs = await sdb.watch(onbatch) + + console.log(subs, modules) + main.append(...await Promise.all( + subs.map(async ({ sid, type }) => { + const el = document.createElement('div') + el.name = type + const shadow = el.attachShadow(shopts) + shadow.append(await modules[type]({ sid, hub: [id] })) + return el + }))) + return el - if (tree[2].length > 2 - || (tree[2].length === 2 && /\S/.test(tree[2][1]))) { - if (opts.createFragment) return opts.createFragment(tree[2]) - throw new Error( - 'multiple root elements must be wrapped in an enclosing tag' - ) - } - if (Array.isArray(tree[2][0]) && typeof tree[2][0][0] === 'string' - && Array.isArray(tree[2][0][2])) { - tree[2][0] = h(tree[2][0][0], tree[2][0][1], tree[2][0][2]) - } - return tree[2][0] - - function parse (str) { - var res = [] - if (state === ATTR_VALUE_W) state = ATTR - for (var i = 0; i < str.length; i++) { - var c = str.charAt(i) - if (state === TEXT && c === '<') { - if (reg.length) res.push([TEXT, reg]) - reg = '' - state = OPEN - } else if (c === '>' && !quot(state) && state !== COMMENT) { - if (state === OPEN && reg.length) { - res.push([OPEN,reg]) - } else if (state === ATTR_KEY) { - res.push([ATTR_KEY,reg]) - } else if (state === ATTR_VALUE && reg.length) { - res.push([ATTR_VALUE,reg]) - } - res.push([CLOSE]) - reg = '' - state = TEXT - } else if (state === COMMENT && /-$/.test(reg) && c === '-') { - if (opts.comments) { - res.push([ATTR_VALUE,reg.substr(0, reg.length - 1)]) - } - reg = '' - state = TEXT - } else if (state === OPEN && /^!--$/.test(reg)) { - if (opts.comments) { - res.push([OPEN, reg],[ATTR_KEY,'comment'],[ATTR_EQ]) - } - reg = c - state = COMMENT - } else if (state === TEXT || state === COMMENT) { - reg += c - } else if (state === OPEN && c === '/' && reg.length) { - // no-op, self closing tag without a space
- } else if (state === OPEN && /\s/.test(c)) { - if (reg.length) { - res.push([OPEN, reg]) - } - reg = '' - state = ATTR - } else if (state === OPEN) { - reg += c - } else if (state === ATTR && /[^\s"'=/]/.test(c)) { - state = ATTR_KEY - reg = c - } else if (state === ATTR && /\s/.test(c)) { - if (reg.length) res.push([ATTR_KEY,reg]) - res.push([ATTR_BREAK]) - } else if (state === ATTR_KEY && /\s/.test(c)) { - res.push([ATTR_KEY,reg]) - reg = '' - state = ATTR_KEY_W - } else if (state === ATTR_KEY && c === '=') { - res.push([ATTR_KEY,reg],[ATTR_EQ]) - reg = '' - state = ATTR_VALUE_W - } else if (state === ATTR_KEY) { - reg += c - } else if ((state === ATTR_KEY_W || state === ATTR) && c === '=') { - res.push([ATTR_EQ]) - state = ATTR_VALUE_W - } else if ((state === ATTR_KEY_W || state === ATTR) && !/\s/.test(c)) { - res.push([ATTR_BREAK]) - if (/[\w-]/.test(c)) { - reg += c - state = ATTR_KEY - } else state = ATTR - } else if (state === ATTR_VALUE_W && c === '"') { - state = ATTR_VALUE_DQ - } else if (state === ATTR_VALUE_W && c === "'") { - state = ATTR_VALUE_SQ - } else if (state === ATTR_VALUE_DQ && c === '"') { - res.push([ATTR_VALUE,reg],[ATTR_BREAK]) - reg = '' - state = ATTR - } else if (state === ATTR_VALUE_SQ && c === "'") { - res.push([ATTR_VALUE,reg],[ATTR_BREAK]) - reg = '' - state = ATTR - } else if (state === ATTR_VALUE_W && !/\s/.test(c)) { - state = ATTR_VALUE - i-- - } else if (state === ATTR_VALUE && /\s/.test(c)) { - res.push([ATTR_VALUE,reg],[ATTR_BREAK]) - reg = '' - state = ATTR - } else if (state === ATTR_VALUE || state === ATTR_VALUE_SQ - || state === ATTR_VALUE_DQ) { - reg += c - } - } - if (state === TEXT && reg.length) { - res.push([TEXT,reg]) - reg = '' - } else if (state === ATTR_VALUE && reg.length) { - res.push([ATTR_VALUE,reg]) - reg = '' - } else if (state === ATTR_VALUE_DQ && reg.length) { - res.push([ATTR_VALUE,reg]) - reg = '' - } else if (state === ATTR_VALUE_SQ && reg.length) { - res.push([ATTR_VALUE,reg]) - reg = '' - } else if (state === ATTR_KEY) { - res.push([ATTR_KEY,reg]) - reg = '' - } - return res + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) } } - - function strfn (x) { - if (typeof x === 'function') return x - else if (typeof x === 'string') return x - else if (x && typeof x === 'object') return x - else if (x === null || x === undefined) return x - else return concat('', x) + async function jump ({ data }) { + main.querySelector('#' + data).scrollIntoView({ behavior: 'smooth' }) + } + async function inject (data) { + style.innerHTML = data.join('\n') } } -function quot (state) { - return state === ATTR_VALUE_SQ || state === ATTR_VALUE_DQ -} - -var closeRE = RegExp('^(' + [ - 'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'command', 'embed', - 'frame', 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param', - 'source', 'track', 'wbr', '!--', - // SVG TAGS - 'animate', 'animateTransform', 'circle', 'cursor', 'desc', 'ellipse', - 'feBlend', 'feColorMatrix', 'feComposite', - 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', - 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', - 'feGaussianBlur', 'feImage', 'feMergeNode', 'feMorphology', - 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', - 'feTurbulence', 'font-face-format', 'font-face-name', 'font-face-uri', - 'glyph', 'glyphRef', 'hkern', 'image', 'line', 'missing-glyph', 'mpath', - 'path', 'polygon', 'polyline', 'rect', 'set', 'stop', 'tref', 'use', 'view', - 'vkern' -].join('|') + ')(?:[\.#][a-zA-Z0-9\u007F-\uFFFF_:-]+)*$') -function selfClosing (tag) { return closeRE.test(tag) } - -},{"hyperscript-attribute-to-property":24}],26:[function(require,module,exports){ -var inserted = {}; - -module.exports = function (css, options) { - if (inserted[css]) return; - inserted[css] = true; - - var elem = document.createElement('style'); - elem.setAttribute('type', 'text/css'); - - if ('textContent' in elem) { - elem.textContent = css; - } else { - elem.styleSheet.cssText = css; - } - - var head = document.getElementsByTagName('head')[0]; - if (options && options.prepend) { - head.insertBefore(elem, head.childNodes[0]); - } else { - head.appendChild(elem); - } -}; - -},{}],27:[function(require,module,exports){ -(function (global){ - -// ------------------------------------------ -// Rellax.js -// Buttery smooth parallax library -// Copyright (c) 2016 Moe Amaya (@moeamaya) -// MIT license -// -// Thanks to Paraxify.js and Jaime Cabllero -// for parallax concepts -// ------------------------------------------ +}).call(this)}).call(this,"/src/app.js") +},{"STATE":3,"footer":4,"header":9,"io":10,"theme_widget":14,"topnav":16}],3:[function(require,module,exports){ +const localdb = require('localdb') +const io = require('io') -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([], factory); - } else if (typeof module === 'object' && module.exports) { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is window) - root.Rellax = factory(); +const db = localdb() +/** Data stored in a entry in db by STATE (Schema): + * id (String): Node Path + * name (String/Optional): Any (To be used in theme_widget) + * type (String): Module Name for module / Module id for instances + * hubs (Array): List of hub-nodes + * subs (Array): List of sub-nodes + * inputs (Array): List of input files + */ +// Constants and initial setup (global level) +const VERSION = 13 +const HELPER_MODULES = ['io', 'localdb', 'STATE'] +const FALLBACK_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#defining-fallbacks' +const FALLBACK_SYNTAX_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#key-descriptions' +const FALLBACK_SUBS_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#shadow-dom-integration' +const status = { + root_module: true, + root_instance: true, + overrides: {}, + tree: {}, + tree_pointers: {}, + modulepaths: {}, + inits: [], + open_branches: {}, + db, + local_statuses: {}, + listeners: {}, + missing_supers: new Set(), + imports: {}, + expected_imports: {}, + used_ids: new Set(), + a2i: {}, + i2a: {}, + services: {}, + args: {} +} +window.STATEMODULE = status + +// Version check and initialization +status.fallback_check = Boolean(check_version()) +status.fallback_check && db.add(['playproject_version'], VERSION) + + +// Symbol mappings +const s2i = {} +const i2s = {} +let admins = [0] + +// Inner Function +function STATE (address, modulepath, dependencies) { + !status.ROOT_ID && (status.ROOT_ID = modulepath) + status.modulepaths[modulepath] = 0 + //Variables (module-level) + + const local_status = { + name: extract_filename(address), + module_id: modulepath, + deny: {}, + sub_modules: [], + sub_instances: {} } -}(typeof window !== "undefined" ? window : global, function () { - var Rellax = function(el, options){ - "use strict"; - - var self = Object.create(Rellax.prototype); - - var posY = 0; - var screenY = 0; - var posX = 0; - var screenX = 0; - var blocks = []; - var pause = true; - - // check what requestAnimationFrame to use, and if - // it's not supported, use the onscroll event - var loop = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.msRequestAnimationFrame || - window.oRequestAnimationFrame || - function(callback){ return setTimeout(callback, 1000 / 60); }; - - // store the id for later use - var loopId = null; - - // Test via a getter in the options object to see if the passive property is accessed - var supportsPassive = false; - try { - var opts = Object.defineProperty({}, 'passive', { - get: function() { - supportsPassive = true; - } - }); - window.addEventListener("testPassive", null, opts); - window.removeEventListener("testPassive", null, opts); - } catch (e) {} - - // check what cancelAnimation method to use - var clearLoop = window.cancelAnimationFrame || window.mozCancelAnimationFrame || clearTimeout; - - // check which transform property to use - var transformProp = window.transformProp || (function(){ - var testEl = document.createElement('div'); - if (testEl.style.transform === null) { - var vendors = ['Webkit', 'Moz', 'ms']; - for (var vendor in vendors) { - if (testEl.style[ vendors[vendor] + 'Transform' ] !== undefined) { - return vendors[vendor] + 'Transform'; - } - } - } - return 'transform'; - })(); - - // Default Settings - self.options = { - speed: -2, - verticalSpeed: null, - horizontalSpeed: null, - breakpoints: [576, 768, 1201], - center: false, - wrapper: null, - relativeToWrapper: false, - round: true, - vertical: true, - horizontal: false, - verticalScrollAxis: "y", - horizontalScrollAxis: "x", - callback: function() {}, - }; - - // User defined options (might have more in the future) - if (options){ - Object.keys(options).forEach(function(key){ - self.options[key] = options[key]; - }); + status.local_statuses[modulepath] = local_status + return statedb + + function statedb (fallback) { + const data = fallback(status.args[modulepath], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }) + local_status.fallback_instance = data.api + const super_id = modulepath.split(/>(?=[^>]*$)/)[0] + + if(super_id === status.current_node){ + status.expected_imports[super_id].splice(status.expected_imports[super_id].indexOf(modulepath), 1) + } + else if((status?.current_node?.split('>').length || 0) < super_id.split('>').length){ + let temp = super_id + while(temp !== status.current_node && temp.includes('>')){ + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } } - - function validateCustomBreakpoints () { - if (self.options.breakpoints.length === 3 && Array.isArray(self.options.breakpoints)) { - var isAscending = true; - var isNumerical = true; - var lastVal; - self.options.breakpoints.forEach(function (i) { - if (typeof i !== 'number') isNumerical = false; - if (lastVal !== null) { - if (i < lastVal) isAscending = false; - } - lastVal = i; - }); - if (isAscending && isNumerical) return; + else{ + let temp = status.current_node + while(temp !== super_id && temp.includes('>')){ + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] } - // revert defaults if set incorrectly - self.options.breakpoints = [576, 768, 1201]; - console.warn("Rellax: You must pass an array of 3 numbers in ascending order to the breakpoints option. Defaults reverted"); } - if (options && options.breakpoints) { - validateCustomBreakpoints(); + if(data._){ + status.open_branches[modulepath] = Object.values(data._).filter(node => node).length + status.expected_imports[modulepath] = Object.keys(data._) + status.current_node = modulepath } - // By default, rellax class - if (!el) { - el = '.rellax'; + local_status.fallback_module = new Function(`return ${fallback.toString()}`)() + verify_imports(modulepath, dependencies, data) + const updated_status = append_tree_node(modulepath, status) + Object.assign(status.tree_pointers, updated_status.tree_pointers) + Object.assign(status.open_branches, updated_status.open_branches) + status.inits.push(init_module) + + if(!Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)){ + status.inits.forEach(init => init()) } + + const sdb = create_statedb_interface(local_status, modulepath, xtype = 'module') + status.dataset = sdb.private_api - // check if el is a className or a node - var elements = typeof el === 'string' ? document.querySelectorAll(el) : [el]; - - // Now query selector - if (elements.length > 0) { - self.elems = elements; + const get = init_instance + const extra_fallbacks = Object.entries(local_status.fallback_instance || {}) + extra_fallbacks.length && extra_fallbacks.forEach(([key]) => { + get[key] = (sid) => get(sid, key) + }) + if(!status.a2i[modulepath]){ + status.i2a[status.a2i[modulepath] = encode(modulepath)] = modulepath } - - // The elements don't exist - else { - console.warn("Rellax: The elements you're trying to select don't exist."); - return; + return { + id: modulepath, + sdb: sdb.public_api, + get: init_instance, + io: io(status.a2i[modulepath], modulepath) + // sub_modules } - - // Has a wrapper and it exists - if (self.options.wrapper) { - if (!self.options.wrapper.nodeType) { - var wrapper = document.querySelector(self.options.wrapper); - - if (wrapper) { - self.options.wrapper = wrapper; - } else { - console.warn("Rellax: The wrapper you're trying to use doesn't exist."); - return; + } + function append_tree_node (id, status) { + const [super_id, name] = id.split(/>(?=[^>]*$)/) + + if(name){ + if(status.tree_pointers[super_id]){ + status.tree_pointers[super_id]._[name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[super_id]._[name].$ + status.open_branches[super_id]-- + } + else{ + let temp_name, new_name = name + let new_super_id = super_id + + while(!status.tree_pointers[new_super_id]){ + [new_super_id, temp_name] = new_super_id.split(/>(?=[^>]*$)/) + new_name = temp_name + '>' + new_name } + status.tree_pointers[new_super_id]._[new_name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[new_super_id]._[new_name].$ + if(!status.missing_supers.has(super_id)) + status.open_branches[new_super_id]-- + status.missing_supers.add(super_id) } } - - // set a placeholder for the current breakpoint - var currentBreakpoint; - - // helper to determine current breakpoint - var getCurrentBreakpoint = function (w) { - var bp = self.options.breakpoints; - if (w < bp[0]) return 'xs'; - if (w >= bp[0] && w < bp[1]) return 'sm'; - if (w >= bp[1] && w < bp[2]) return 'md'; - return 'lg'; - }; - - // Get and cache initial position of all elements - var cacheBlocks = function() { - for (var i = 0; i < self.elems.length; i++){ - var block = createBlock(self.elems[i]); - blocks.push(block); - } - }; - - - // Let's kick this script off - // Build array for cached element values - var init = function() { - for (var i = 0; i < blocks.length; i++){ - self.elems[i].style.cssText = blocks[i].style; - } - - blocks = []; - - screenY = window.innerHeight; - screenX = window.innerWidth; - currentBreakpoint = getCurrentBreakpoint(screenX); - - setPosition(); - - cacheBlocks(); - - animate(); - - // If paused, unpause and set listener for window resizing events - if (pause) { - window.addEventListener('resize', init); - pause = false; - // Start the loop - update(); - } - }; - - // We want to cache the parallax blocks' - // values: base, top, height, speed - // el: is dom object, return: el cache values - var createBlock = function(el) { - var dataPercentage = el.getAttribute( 'data-rellax-percentage' ); - var dataSpeed = el.getAttribute( 'data-rellax-speed' ); - var dataXsSpeed = el.getAttribute( 'data-rellax-xs-speed' ); - var dataMobileSpeed = el.getAttribute( 'data-rellax-mobile-speed' ); - var dataTabletSpeed = el.getAttribute( 'data-rellax-tablet-speed' ); - var dataDesktopSpeed = el.getAttribute( 'data-rellax-desktop-speed' ); - var dataVerticalSpeed = el.getAttribute('data-rellax-vertical-speed'); - var dataHorizontalSpeed = el.getAttribute('data-rellax-horizontal-speed'); - var dataVericalScrollAxis = el.getAttribute('data-rellax-vertical-scroll-axis'); - var dataHorizontalScrollAxis = el.getAttribute('data-rellax-horizontal-scroll-axis'); - var dataZindex = el.getAttribute( 'data-rellax-zindex' ) || 0; - var dataMin = el.getAttribute( 'data-rellax-min' ); - var dataMax = el.getAttribute( 'data-rellax-max' ); - var dataMinX = el.getAttribute('data-rellax-min-x'); - var dataMaxX = el.getAttribute('data-rellax-max-x'); - var dataMinY = el.getAttribute('data-rellax-min-y'); - var dataMaxY = el.getAttribute('data-rellax-max-y'); - var mapBreakpoints; - var breakpoints = true; - - if (!dataXsSpeed && !dataMobileSpeed && !dataTabletSpeed && !dataDesktopSpeed) { - breakpoints = false; - } else { - mapBreakpoints = { - 'xs': dataXsSpeed, - 'sm': dataMobileSpeed, - 'md': dataTabletSpeed, - 'lg': dataDesktopSpeed - }; - } - - // initializing at scrollY = 0 (top of browser), scrollX = 0 (left of browser) - // ensures elements are positioned based on HTML layout. - // - // If the element has the percentage attribute, the posY and posX needs to be - // the current scroll position's value, so that the elements are still positioned based on HTML layout - var wrapperPosY = self.options.wrapper ? self.options.wrapper.scrollTop : (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - // If the option relativeToWrapper is true, use the wrappers offset to top, subtracted from the current page scroll. - if (self.options.relativeToWrapper) { - var scrollPosY = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - wrapperPosY = scrollPosY - self.options.wrapper.offsetTop; - } - var posY = self.options.vertical ? ( dataPercentage || self.options.center ? wrapperPosY : 0 ) : 0; - var posX = self.options.horizontal ? ( dataPercentage || self.options.center ? self.options.wrapper ? self.options.wrapper.scrollLeft : (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) : 0 ) : 0; - - var blockTop = posY + el.getBoundingClientRect().top; - var blockHeight = el.clientHeight || el.offsetHeight || el.scrollHeight; - - var blockLeft = posX + el.getBoundingClientRect().left; - var blockWidth = el.clientWidth || el.offsetWidth || el.scrollWidth; - - // apparently parallax equation everyone uses - var percentageY = dataPercentage ? dataPercentage : (posY - blockTop + screenY) / (blockHeight + screenY); - var percentageX = dataPercentage ? dataPercentage : (posX - blockLeft + screenX) / (blockWidth + screenX); - if(self.options.center){ percentageX = 0.5; percentageY = 0.5; } - - // Optional individual block speed as data attr, otherwise global speed - var speed = (breakpoints && mapBreakpoints[currentBreakpoint] !== null) ? Number(mapBreakpoints[currentBreakpoint]) : (dataSpeed ? dataSpeed : self.options.speed); - var verticalSpeed = dataVerticalSpeed ? dataVerticalSpeed : self.options.verticalSpeed; - var horizontalSpeed = dataHorizontalSpeed ? dataHorizontalSpeed : self.options.horizontalSpeed; - - // Optional individual block movement axis direction as data attr, otherwise gobal movement direction - var verticalScrollAxis = dataVericalScrollAxis ? dataVericalScrollAxis : self.options.verticalScrollAxis; - var horizontalScrollAxis = dataHorizontalScrollAxis ? dataHorizontalScrollAxis : self.options.horizontalScrollAxis; - - var bases = updatePosition(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed); - - // ~~Store non-translate3d transforms~~ - // Store inline styles and extract transforms - var style = el.style.cssText; - var transform = ''; - - // Check if there's an inline styled transform - var searchResult = /transform\s*:/i.exec(style); - if (searchResult) { - // Get the index of the transform - var index = searchResult.index; - - // Trim the style to the transform point and get the following semi-colon index - var trimmedStyle = style.slice(index); - var delimiter = trimmedStyle.indexOf(';'); - - // Remove "transform" string and save the attribute - if (delimiter) { - transform = " " + trimmedStyle.slice(11, delimiter).replace(/\s/g,''); - } else { - transform = " " + trimmedStyle.slice(11).replace(/\s/g,''); - } - } - - return { - baseX: bases.x, - baseY: bases.y, - top: blockTop, - left: blockLeft, - height: blockHeight, - width: blockWidth, - speed: speed, - verticalSpeed: verticalSpeed, - horizontalSpeed: horizontalSpeed, - verticalScrollAxis: verticalScrollAxis, - horizontalScrollAxis: horizontalScrollAxis, - style: style, - transform: transform, - zindex: dataZindex, - min: dataMin, - max: dataMax, - minX: dataMinX, - maxX: dataMaxX, - minY: dataMinY, - maxY: dataMaxY - }; - }; - - // set scroll position (posY, posX) - // side effect method is not ideal, but okay for now - // returns true if the scroll changed, false if nothing happened - var setPosition = function() { - var oldY = posY; - var oldX = posX; - - posY = self.options.wrapper ? self.options.wrapper.scrollTop : (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset; - posX = self.options.wrapper ? self.options.wrapper.scrollLeft : (document.documentElement || document.body.parentNode || document.body).scrollLeft || window.pageXOffset; - // If option relativeToWrapper is true, use relative wrapper value instead. - if (self.options.relativeToWrapper) { - var scrollPosY = (document.documentElement || document.body.parentNode || document.body).scrollTop || window.pageYOffset; - posY = scrollPosY - self.options.wrapper.offsetTop; - } - - - if (oldY != posY && self.options.vertical) { - // scroll changed, return true - return true; - } - - if (oldX != posX && self.options.horizontal) { - // scroll changed, return true - return true; - } - - // scroll did not change - return false; - }; - - // Ahh a pure function, gets new transform value - // based on scrollPosition and speed - // Allow for decimal pixel values - var updatePosition = function(percentageX, percentageY, speed, verticalSpeed, horizontalSpeed) { - var result = {}; - var valueX = ((horizontalSpeed ? horizontalSpeed : speed) * (100 * (1 - percentageX))); - var valueY = ((verticalSpeed ? verticalSpeed : speed) * (100 * (1 - percentageY))); - - result.x = self.options.round ? Math.round(valueX) : Math.round(valueX * 100) / 100; - result.y = self.options.round ? Math.round(valueY) : Math.round(valueY * 100) / 100; - - return result; - }; - - // Remove event listeners and loop again - var deferredUpdate = function() { - window.removeEventListener('resize', deferredUpdate); - window.removeEventListener('orientationchange', deferredUpdate); - (self.options.wrapper ? self.options.wrapper : window).removeEventListener('scroll', deferredUpdate); - (self.options.wrapper ? self.options.wrapper : document).removeEventListener('touchmove', deferredUpdate); - - // loop again - loopId = loop(update); - }; - - // Loop - var update = function() { - if (setPosition() && pause === false) { - animate(); - - // loop again - loopId = loop(update); - } else { - loopId = null; - - // Don't animate until we get a position updating event - window.addEventListener('resize', deferredUpdate); - window.addEventListener('orientationchange', deferredUpdate); - (self.options.wrapper ? self.options.wrapper : window).addEventListener('scroll', deferredUpdate, supportsPassive ? { passive: true } : false); - (self.options.wrapper ? self.options.wrapper : document).addEventListener('touchmove', deferredUpdate, supportsPassive ? { passive: true } : false); - } - }; - - // Transform3d on parallax element - var animate = function() { - var positions; - for (var i = 0; i < self.elems.length; i++){ - // Determine relevant movement directions - var verticalScrollAxis = blocks[i].verticalScrollAxis.toLowerCase(); - var horizontalScrollAxis = blocks[i].horizontalScrollAxis.toLowerCase(); - var verticalScrollX = verticalScrollAxis.indexOf("x") != -1 ? posY : 0; - var verticalScrollY = verticalScrollAxis.indexOf("y") != -1 ? posY : 0; - var horizontalScrollX = horizontalScrollAxis.indexOf("x") != -1 ? posX : 0; - var horizontalScrollY = horizontalScrollAxis.indexOf("y") != -1 ? posX : 0; - - var percentageY = ((verticalScrollY + horizontalScrollY - blocks[i].top + screenY) / (blocks[i].height + screenY)); - var percentageX = ((verticalScrollX + horizontalScrollX - blocks[i].left + screenX) / (blocks[i].width + screenX)); - - // Subtracting initialize value, so element stays in same spot as HTML - positions = updatePosition(percentageX, percentageY, blocks[i].speed, blocks[i].verticalSpeed, blocks[i].horizontalSpeed); - var positionY = positions.y - blocks[i].baseY; - var positionX = positions.x - blocks[i].baseX; - - // The next two "if" blocks go like this: - // Check if a limit is defined (first "min", then "max"); - // Check if we need to change the Y or the X - // (Currently working only if just one of the axes is enabled) - // Then, check if the new position is inside the allowed limit - // If so, use new position. If not, set position to limit. - - // Check if a min limit is defined - if (blocks[i].min !== null) { - if (self.options.vertical && !self.options.horizontal) { - positionY = positionY <= blocks[i].min ? blocks[i].min : positionY; - } - if (self.options.horizontal && !self.options.vertical) { - positionX = positionX <= blocks[i].min ? blocks[i].min : positionX; - } - } - - // Check if directional min limits are defined - if (blocks[i].minY != null) { - positionY = positionY <= blocks[i].minY ? blocks[i].minY : positionY; - } - if (blocks[i].minX != null) { - positionX = positionX <= blocks[i].minX ? blocks[i].minX : positionX; - } - - // Check if a max limit is defined - if (blocks[i].max !== null) { - if (self.options.vertical && !self.options.horizontal) { - positionY = positionY >= blocks[i].max ? blocks[i].max : positionY; - } - if (self.options.horizontal && !self.options.vertical) { - positionX = positionX >= blocks[i].max ? blocks[i].max : positionX; - } - } - - // Check if directional max limits are defined - if (blocks[i].maxY != null) { - positionY = positionY >= blocks[i].maxY ? blocks[i].maxY : positionY; - } - if (blocks[i].maxX != null) { - positionX = positionX >= blocks[i].maxX ? blocks[i].maxX : positionX; - } - - var zindex = blocks[i].zindex; - - // Move that element - // (Set the new translation and append initial inline transforms.) - var translate = 'translate3d(' + (self.options.horizontal ? positionX : '0') + 'px,' + (self.options.vertical ? positionY : '0') + 'px,' + zindex + 'px) ' + blocks[i].transform; - self.elems[i].style[transformProp] = translate; - } - self.options.callback(positions); - }; - - self.destroy = function() { - for (var i = 0; i < self.elems.length; i++){ - self.elems[i].style.cssText = blocks[i].style; - } - - // Remove resize event listener if not pause, and pause - if (!pause) { - window.removeEventListener('resize', init); - pause = true; - } - - // Clear the animation loop to prevent possible memory leak - clearLoop(loopId); - loopId = null; - }; - - // Init - init(); - - // Allow to recalculate the initial values whenever we want - self.refresh = init; - - return self; - }; - return Rellax; -})); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],28:[function(require,module,exports){ -/** - * Zenscroll 4.0.2 - * https://github.com/zengabor/zenscroll/ - * - * Copyright 2015–2018 Gabor Lenard - * - * This is free and unencumbered software released into the public domain. - * - * Anyone is free to copy, modify, publish, use, compile, sell, or - * distribute this software, either in source code form or as a compiled - * binary, for any purpose, commercial or non-commercial, and by any - * means. - * - * In jurisdictions that recognize copyright laws, the author or authors - * of this software dedicate any and all copyright interest in the - * software to the public domain. We make this dedication for the benefit - * of the public at large and to the detriment of our heirs and - * successors. We intend this dedication to be an overt act of - * relinquishment in perpetuity of all present and future rights to this - * software under copyright law. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * For more information, please refer to - * - */ - -/*jshint devel:true, asi:true */ - -/*global define, module */ - - -(function (root, factory) { - if (typeof define === "function" && define.amd) { - define([], factory()) - } else if (typeof module === "object" && module.exports) { - module.exports = factory() - } else { - (function install() { - // To make sure Zenscroll can be referenced from the header, before `body` is available - if (document && document.body) { - root.zenscroll = factory() - } else { - // retry 9ms later - setTimeout(install, 9) - } - })() - } -}(this, function () { - "use strict" - - - // Detect if the browser already supports native smooth scrolling (e.g., Firefox 36+ and Chrome 49+) and it is enabled: - var isNativeSmoothScrollEnabledOn = function (elem) { - return elem && "getComputedStyle" in window && - window.getComputedStyle(elem)["scroll-behavior"] === "smooth" - } - - - // Exit if it’s not a browser environment: - if (typeof window === "undefined" || !("document" in window)) { - return {} - } - - - var makeScroller = function (container, defaultDuration, edgeOffset) { - - // Use defaults if not provided - defaultDuration = defaultDuration || 999 //ms - if (!edgeOffset && edgeOffset !== 0) { - // When scrolling, this amount of distance is kept from the edges of the container: - edgeOffset = 9 //px - } - - // Handling the life-cycle of the scroller - var scrollTimeoutId - var setScrollTimeoutId = function (newValue) { - scrollTimeoutId = newValue - } - - /** - * Stop the current smooth scroll operation immediately - */ - var stopScroll = function () { - clearTimeout(scrollTimeoutId) - setScrollTimeoutId(0) - } - - var getTopWithEdgeOffset = function (elem) { - return Math.max(0, container.getTopOf(elem) - edgeOffset) - } - - /** - * Scrolls to a specific vertical position in the document. - * - * @param {targetY} The vertical position within the document. - * @param {duration} Optionally the duration of the scroll operation. - * If not provided the default duration is used. - * @param {onDone} An optional callback function to be invoked once the scroll finished. - */ - var scrollToY = function (targetY, duration, onDone) { - stopScroll() - if (duration === 0 || (duration && duration < 0) || isNativeSmoothScrollEnabledOn(container.body)) { - container.toY(targetY) - if (onDone) { - onDone() - } - } else { - var startY = container.getY() - var distance = Math.max(0, targetY) - startY - var startTime = new Date().getTime() - duration = duration || Math.min(Math.abs(distance), defaultDuration); - (function loopScroll() { - setScrollTimeoutId(setTimeout(function () { - // Calculate percentage: - var p = Math.min(1, (new Date().getTime() - startTime) / duration) - // Calculate the absolute vertical position: - var y = Math.max(0, Math.floor(startY + distance*(p < 0.5 ? 2*p*p : p*(4 - p*2)-1))) - container.toY(y) - if (p < 1 && (container.getHeight() + y) < container.body.scrollHeight) { - loopScroll() - } else { - setTimeout(stopScroll, 99) // with cooldown time - if (onDone) { - onDone() - } - } - }, 9)) - })() - } - } - - /** - * Scrolls to the top of a specific element. - * - * @param {elem} The element to scroll to. - * @param {duration} Optionally the duration of the scroll operation. - * @param {onDone} An optional callback function to be invoked once the scroll finished. - */ - var scrollToElem = function (elem, duration, onDone) { - scrollToY(getTopWithEdgeOffset(elem), duration, onDone) - } - - /** - * Scrolls an element into view if necessary. - * - * @param {elem} The element. - * @param {duration} Optionally the duration of the scroll operation. - * @param {onDone} An optional callback function to be invoked once the scroll finished. - */ - var scrollIntoView = function (elem, duration, onDone) { - var elemHeight = elem.getBoundingClientRect().height - var elemBottom = container.getTopOf(elem) + elemHeight - var containerHeight = container.getHeight() - var y = container.getY() - var containerBottom = y + containerHeight - if (getTopWithEdgeOffset(elem) < y || (elemHeight + edgeOffset) > containerHeight) { - // Element is clipped at top or is higher than screen. - scrollToElem(elem, duration, onDone) - } else if ((elemBottom + edgeOffset) > containerBottom) { - // Element is clipped at the bottom. - scrollToY(elemBottom - containerHeight + edgeOffset, duration, onDone) - } else if (onDone) { - onDone() - } - } - - /** - * Scrolls to the center of an element. - * - * @param {elem} The element. - * @param {duration} Optionally the duration of the scroll operation. - * @param {offset} Optionally the offset of the top of the element from the center of the screen. - * A value of 0 is ignored. - * @param {onDone} An optional callback function to be invoked once the scroll finished. - */ - var scrollToCenterOf = function (elem, duration, offset, onDone) { - scrollToY(Math.max(0, container.getTopOf(elem) - container.getHeight()/2 + (offset || elem.getBoundingClientRect().height/2)), duration, onDone) - } - - /** - * Changes default settings for this scroller. - * - * @param {newDefaultDuration} Optionally a new value for default duration, used for each scroll method by default. - * Ignored if null or undefined. - * @param {newEdgeOffset} Optionally a new value for the edge offset, used by each scroll method by default. Ignored if null or undefined. - * @returns An object with the current values. - */ - var setup = function (newDefaultDuration, newEdgeOffset) { - if (newDefaultDuration === 0 || newDefaultDuration) { - defaultDuration = newDefaultDuration - } - if (newEdgeOffset === 0 || newEdgeOffset) { - edgeOffset = newEdgeOffset - } - return { - defaultDuration: defaultDuration, - edgeOffset: edgeOffset - } - } - - return { - setup: setup, - to: scrollToElem, - toY: scrollToY, - intoView: scrollIntoView, - center: scrollToCenterOf, - stop: stopScroll, - moving: function () { return !!scrollTimeoutId }, - getY: container.getY, - getTopOf: container.getTopOf - } - - } - - - var docElem = document.documentElement - var getDocY = function () { return window.scrollY || docElem.scrollTop } - - // Create a scroller for the document: - var zenscroll = makeScroller({ - body: document.scrollingElement || document.body, - toY: function (y) { window.scrollTo(0, y) }, - getY: getDocY, - getHeight: function () { return window.innerHeight || docElem.clientHeight }, - getTopOf: function (elem) { return elem.getBoundingClientRect().top + getDocY() - docElem.offsetTop } - }) - - - /** - * Creates a scroller from the provided container element (e.g., a DIV) - * - * @param {scrollContainer} The vertical position within the document. - * @param {defaultDuration} Optionally a value for default duration, used for each scroll method by default. - * Ignored if 0 or null or undefined. - * @param {edgeOffset} Optionally a value for the edge offset, used by each scroll method by default. - * Ignored if null or undefined. - * @returns A scroller object, similar to `zenscroll` but controlling the provided element. - */ - zenscroll.createScroller = function (scrollContainer, defaultDuration, edgeOffset) { - return makeScroller({ - body: scrollContainer, - toY: function (y) { scrollContainer.scrollTop = y }, - getY: function () { return scrollContainer.scrollTop }, - getHeight: function () { return Math.min(scrollContainer.clientHeight, window.innerHeight || docElem.clientHeight) }, - getTopOf: function (elem) { return elem.offsetTop } - }, defaultDuration, edgeOffset) - } - - - // Automatic link-smoothing on achors - // Exclude IE8- or when native is enabled or Zenscroll auto- is disabled - if ("addEventListener" in window && !window.noZensmooth && !isNativeSmoothScrollEnabledOn(document.body)) { - - var isHistorySupported = "history" in window && "pushState" in history - var isScrollRestorationSupported = isHistorySupported && "scrollRestoration" in history - - // On first load & refresh make sure the browser restores the position first - if (isScrollRestorationSupported) { - history.scrollRestoration = "auto" - } - - window.addEventListener("load", function () { - - if (isScrollRestorationSupported) { - // Set it to manual - setTimeout(function () { history.scrollRestoration = "manual" }, 9) - window.addEventListener("popstate", function (event) { - if (event.state && "zenscrollY" in event.state) { - zenscroll.toY(event.state.zenscrollY) - } - }, false) - } - - // Add edge offset on first load if necessary - // This may not work on IE (or older computer?) as it requires more timeout, around 100 ms - if (window.location.hash) { - setTimeout(function () { - // Adjustment is only needed if there is an edge offset: - var edgeOffset = zenscroll.setup().edgeOffset - if (edgeOffset) { - var targetElem = document.getElementById(window.location.href.split("#")[1]) - if (targetElem) { - var targetY = Math.max(0, zenscroll.getTopOf(targetElem) - edgeOffset) - var diff = zenscroll.getY() - targetY - // Only do the adjustment if the browser is very close to the element: - if (0 <= diff && diff < 9 ) { - window.scrollTo(0, targetY) - } - } - } - }, 9) - } - - }, false) - - // Handling clicks on anchors - var RE_noZensmooth = new RegExp("(^|\\s)noZensmooth(\\s|$)") - window.addEventListener("click", function (event) { - var anchor = event.target - while (anchor && anchor.tagName !== "A") { - anchor = anchor.parentNode - } - // Let the browser handle the click if it wasn't with the primary button, or with some modifier keys: - if (!anchor || event.which !== 1 || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) { - return - } - // Save the current scrolling position so it can be used for scroll restoration: - if (isScrollRestorationSupported) { - var historyState = history.state && typeof history.state === "object" ? history.state : {} - historyState.zenscrollY = zenscroll.getY() - try { - history.replaceState(historyState, "") - } catch (e) { - // Avoid the Chrome Security exception on file protocol, e.g., file://index.html - } - } - // Find the referenced ID: - var href = anchor.getAttribute("href") || "" - if (href.indexOf("#") === 0 && !RE_noZensmooth.test(anchor.className)) { - var targetY = 0 - var targetElem = document.getElementById(href.substring(1)) - if (href !== "#") { - if (!targetElem) { - // Let the browser handle the click if the target ID is not found. - return - } - targetY = zenscroll.getTopOf(targetElem) - } - event.preventDefault() - // By default trigger the browser's `hashchange` event... - var onDone = function () { window.location = href } - // ...unless there is an edge offset specified - var edgeOffset = zenscroll.setup().edgeOffset - if (edgeOffset) { - targetY = Math.max(0, targetY - edgeOffset) - if (isHistorySupported) { - onDone = function () { history.pushState({}, "", href) } - } - } - zenscroll.toY(targetY, null, onDone) - } - }, false) - - } - - - return zenscroll - - -})); - -},{}],29:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') - -// pages -const topnav = require('topnav') -const Header = require('header') -const datdot = require('datdot') -const editor = require('editor') -const smartcontract_codes = require('smartcontract-codes') -const supporters = require('supporters') -const our_contributors = require('our-contributors') -const Footer = require('footer') -const fetch_data = require('fetch-data') - -module.exports = make_page - -function make_page(opts, done, lang) { - switch(lang) { - case 'zh-tw': - case 'ja': - case 'th': - case 'fr': - var path = `./src/node_modules/lang/${lang}.json` - break - default: - var path = `./src/node_modules/lang/en-us.json` - } - fetch_data(path).then(async (text) => { - let { menu, header, section1, section2, section3, section4, section5, footer } = text.pages - const {theme} = opts - const css = styles - const landingPage = bel` -
- ${await topnav(menu)} - ${await Header(header)} - ${await datdot(section1)} - ${await editor(section2)} - ${await smartcontract_codes(section3)} - ${await supporters(section4)} - ${await our_contributors(section5)} - ${await Footer(footer)} -
` - return done(null, landingPage) - - }).catch( err => { - return done(err, null) - }) -} - -const styles = csjs` -.wrap { - background: var(--bodyBg); -} -[class^="cloud"] { - transition: left 0.6s, bottom 0.5s, top 0.5s linear; -}` - -},{"bel":4,"csjs-inject":7,"datdot":33,"editor":34,"fetch-data":35,"footer":36,"header":38,"our-contributors":40,"smartcontract-codes":41,"supporters":42,"topnav":43}],30:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') - -function content(data, theme) { - const css = Object.assign({}, styles, theme) - let el = bel` -
-

${data.title}

-
${data.article}
- ${data.action} -
- ` - return el -} - -let styles = csjs` -.content { - -} -.subTitle { - font-family: var(--titleFont); - font-size: var(--subTitleSize); - margin-bottom: 2.5rem; -} -.subTitleColor { - color: var(--section2TitleColor); -} -.article { - -} -.button { - display: inline-block; - outline: none; - border: none; - font-family: var(--titleFont); - font-size: var(--sectionButtonSize); - color: var(--titleColor); - border-radius: 2rem; - padding: 1.2rem 3.8rem; - cursor: pointer; -} -.buttonBg { - -} -@media screen and (min-width: 2561px) { - .subTitle { - font-size: calc(var(--subTitleSize) * 1.5); - } -} -} -@media screen and (min-width: 4096px) { - .subTitle { - font-size: calc(var(--subTitleSize) * 2.25); - } -} -@media screen and (max-width: 414px) { - .subTitle { - font-size: var(--titlesSizeS); - margin-bottom: 1.5rem; - } -} -` -module.exports = content -},{"bel":4,"csjs-inject":7}],31:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const Graphic = require('graphic') - -module.exports = contributor - -async function contributor(person, className, theme) { - let css = Object.assign({}, styles, theme) - let lifeIsland = await Graphic(css.lifeIsland,'./src/node_modules/assets/svg/life-island.svg') - let el = bel` -
-
- ${person.name} -
-

${person.name}

- ${person.careers && - person.careers.map( career => - bel`${career}` - ) - } -
-
- ${lifeIsland} -
- ` - return el -} - -const styles = csjs` -.member { - position: absolute; - z-index: 1; - display: grid; - grid-template: 1fr / 40% 60%; - width: 70%; - top: 20%; -} -.avatar { - position: relative; - z-index: 2; -} -.info { - display: flex; - flex-direction: column; - justify-content: center; - font-size: var(--contributorsTextSize); - text-align: center; - background-color: var(--contributorsBg); - padding: 0% 2% 4% 20%; - margin-left: -20%; -} -.name { - color: var(--section5TitleColor); - margin-top: 0; - margin-bottom: 3%; -} -.career { - display: block; - color: var(--contributorsCareerColor); -} -.lifeIsland { - width: 100%; -} -@media only screen and (max-width: 1550px) { - .member { - width: 280px; - top: 15%; - left: -2vw; - } -} -@media only screen and (max-width: 1200px) { - .lifeIsland { - width: 115%; - } -} -@media only screen and (max-width: 1280px) { - .member { - top: 12%; - left: -4vw; - } -} - -@media only screen and (max-width: 1130px) { - .member { - top: 1vw; - left: -6vw; - } -} -@media only screen and (max-width: 1024px) { - .lifeIsland { - width: 100%; - } - .member { - width: 32vw; - top: 6vw; - left: -2vw; - } -} -@media only screen and (max-width: 768px) { - .member { - width: 85%; - top: 5vw; - left: -4vw; - } -} -@media only screen and (max-width: 640px) { - .member { - width: 75%; - top: 9vw; - } -} -@media only screen and (max-width: 414px) { - .member { - width: 90%; - top: 5vw; - left: -10vw; - } -} -` - - - -},{"bel":4,"csjs-inject":7,"graphic":37}],32:[function(require,module,exports){ -const bel = require('bel') - -function crystalIsland({date, info}, deco, island, css, title) { - let el = bel` -
-
-
-

${date}

- ${ info === 'Coming soon' ? bel`

${info}

` : bel`

${info}

` } -
- ${deco.map(item => item)} -
- ${title} - ${island} -
- ` - return el -} - -module.exports = crystalIsland -},{"bel":4}],33:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const content = require('content') - -async function datdot(data) { - const css = styles - var graphics = [ - graphic(css.blockchainIsland, './src/node_modules/assets/svg/blockchian-island.svg'), - graphic(css.blossomIsland, './src/node_modules/assets/svg/blossom-island.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [blockchainIsland, blossomIsland, cloud1, cloud2, cloud3, cloud4, cloud5] = await Promise.all(graphics) - // Parallax effects - let cloud1Rellax = new Rellax( cloud1, { speed: 4}) - let cloud2Rellax = new Rellax( cloud2, { speed: 2}) - let cloud3Rellax = new Rellax( cloud3, { speed: 5}) - let cloud4Rellax = new Rellax( cloud4, { speed: 2}) - let cloud5Rellax = new Rellax( cloud5, { speed: 4}) - - let el = bel` -
- ${content(data, css)} - ${blockchainIsland} - ${blossomIsland} - ${cloud1} - ${cloud2} - ${cloud3} - ${cloud4} - ${cloud5} -
- ` - return el -} - -const styles = csjs` -.section { - position: relative; - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 60% 40%; - background-image: linear-gradient(0deg, var(--section1BgGEnd), var(--section1BgGStart)); - padding: 0 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-column-start: 2; - grid-column-end: 3; - text-align: center; - padding: 0 5%; -} -.subTitleColor { - color: var(--section1TitleColor); -} -.buttonBg { - background-image: linear-gradient(0deg, #ed6e87, #e9627e); -} -.blockchainIsland { - position: relative; - z-index: 2; - grid-row-start: 1; - grid-row-end: 3; - grid-column-start: 1; -} -.blossomIsland { - position: relative; - z-index: 2; - grid-column-start: 2; - grid-row-start: 2; - grid-row-end: 3; - padding-left: 2rem; - align-self: end; - width: 90%; -} -.cloud1 { - position: absolute; - z-index: 4; - width: 10vw; - bottom: 10vh; - left: 5vw; -} -.cloud2 { - position: absolute; - z-index: 4; - width: 14vw; - bottom: -8vh; - left: 42vw; -} -.cloud3 { - position: absolute; - z-index: 1; - width: 8vw; - bottom: 15vh; - left: 52vw; -} -.cloud4 { - position: absolute; - width: 6vw; - bottom: 60%; - right: 5vw; -} -.cloud5 { - position: absolute; - z-index: 1; - width: 18vw; - bottom: -10vh; - right: 2vw; -} -@media only screen and (max-width: 1560px) { - .content { - padding: 0; - } - .blossomIsland { - margin-top: 30px; - width: 35vw; - } -} -@media only screen and (max-width: 1024px) { - .section1 { - grid-template-columns: 55% 45%; - } - .content { - grid-column-start: 1; - padding: 0 15vw; - } - .blockchainIsland { - grid-row-start: 2; - } - .blossomIsland { - width: 90%; - margin-left: 2vw; - align-self: center; - } - .cloud1 { - bottom: 0vh; - } - .cloud2 { - bottom: -5vh; - } - .cloud3 { - bottom: 10%; - } - .cloud4 { - bottom: 60%; - width: 12vw; - } - .cloud5 { - bottom: -4vh; - } -} -@media only screen and (max-width: 812px) { - .cloud3 { - bottom: 10%; - } - .cloud4 { - bottom: 50%; - } -} -@media only screen and (max-width: 768px) { - .cloud3 { - bottom: 12%; - } -} -@media only screen and (max-width: 640px) { - .section1 { - grid-template-rows: repeat(3, auto); - grid-template-columns: 100%; - } - .content { - padding-bottom: 10%; - } - .blockchainIsland { - grid-column-end: 3; - } - .blossomIsland { - grid-row-start: 3; - grid-column-start: 1; - width: 100%; - justify-self: end; - } - .cloud1 { - width: 15vw; - } - .cloud2 { - width: 30vw; - left: 50vw; - bottom: -50vw; - } - .cloud3 { - width: 20vw; - bottom: 5vw; - } - .cloud4 { - top: 30vw; - } -} -@media only screen and (max-width: 414px) { - .content { - padding: 0 5vw 5vh 5vw; - } - .article { - padding-bottom: 2rem; - } - .section { - margin-top: 0; - } - .blossomIsland { - width: 60vw; - margin-left: 35vw; - } - .cloud3 { - bottom: 5vh; - } - .cloud4 { - bottom: 35%; - width: 15vw; - } -} -` - -module.exports = datdot -},{"bel":4,"content":30,"csjs-inject":7,"graphic":37,"rellax":27}],34:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const Content = require('content') - -async function editor (data) { - const css = styles - const graphics = [ - graphic(css.island, './src/node_modules/assets/svg/floating-island.svg'), - graphic(css.energyIsland, './src/node_modules/assets/svg/energy-island.svg'), - graphic(css.tree, './src/node_modules/assets/svg/single-tree.svg'), - graphic(css.stone, './src/node_modules/assets/svg/stone.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [island, energyIsland, tree, stone, cloud1, cloud2, cloud3, cloud4, cloud5] = await Promise.all(graphics) - - // Parallax effects - let cloud1Rellax = new Rellax( cloud1, { speed: 2}) - let cloud2Rellax = new Rellax( cloud2, { speed: 3}) - let cloud3Rellax = new Rellax( cloud3, { speed: 4}) - let cloud4Rellax = new Rellax( cloud4, { speed: 4}) - let cloud5Rellax = new Rellax( cloud5, { speed: 3}) - - let el = bel` -
- ${Content(data, css)} - -
-
- ${data.title} logo - ${data.title} -
- ${stone} - ${tree} -
-
- ${island} -
- ${energyIsland} - ${cloud1} - ${cloud2} - ${cloud3} - ${cloud4} - ${cloud5} -
- ` - return el -} - -const styles = csjs` -.section { - position: relative; - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 40% 60%; - background-image: linear-gradient(0deg, var(--section2BgGEnd), var(--section2BgGStart)); - padding: 5vw 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-column-start: 1; - grid-column-end: 2; - text-align: center; - padding: 0 5%; - margin-bottom: 86px; -} -.subTitleColor { - color: var(--section2TitleColor); -} -.buttonBg { - background-image: linear-gradient(0deg, #4dc7be, #35bdb9); -} -.scene { - position: relative; - grid-row-start: span 2; - grid-column-start: 2; -} -.objects { - position: relative; -} -.screenshot { - width: 80%; - margin-bottom: -5.5%; - margin-left: 10%; -} -.logo { - position: absolute; - left: 0%; - bottom: -20%; - width: 20%; -} -.deco { - position: absolute; - right: 0; - bottom: -18.5%; - width: 100%; - display: flex; - align-items: flex-end; - justify-content: flex-end; -} -.tree { - width: 13%; -} -.stone { - position: relative; - width: 10%; - right: -3%; -} -.island { -} -.energyIsland { - grid-row-start: 2; - grid-column-start: 1; - grid-column-end: 2; - width: 80%; - justify-self: center; -} -.cloud1 { - position: absolute; - width: 10vw; - left: 2vw; - bottom: 0; - z-index: 3; -} -.cloud2 { - position: absolute; - width: 15vw; - left: 38vw; - bottom: -35vw; - z-index: 2; -} -.cloud3 { - position: absolute; - width: 8vw; - right: 30vw; - bottom: -34vw; - z-index: 3; -} -.cloud4 { - position: absolute; - width: 14vw; - right: 6vw; - bottom: -40vw; - z-index: 3; -} -.cloud5 { - position: absolute; - width: 8vw; - right: 2vw; - bottom: -10vw; - z-index: 2; -} -@media only screen and (max-width: 1024px) { - .content { - grid-row-start: 1; - grid-column-end: 3; - } - .scene { - grid-row-start: 2; - } - .energyIsland { - align-self: end; - } -} - -@media only screen and (max-width: 640px) { - .scene { - grid-column-start: 1; - grid-column-end: 3; - } - .energyIsland { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 3; - width: 60%; - justify-self: start; - } - .cloud1 { - width: 16vw; - } - .cloud2 { - width: 20vw; - left: 50vw; - bottom: 10vw; - } - .cloud3 { - width: 15vw; - bottom: 50vw; - } - .cloud4 { - width: 25vw; - bottom: -85vw; - } - .cloud5 { - width: 15vw; - bottom: 30vw; - } -} -` - -module.exports = editor -},{"bel":4,"content":30,"csjs-inject":7,"graphic":37,"rellax":27}],35:[function(require,module,exports){ -module.exports = fetch_data - -async function fetch_data(path) { - let response = await fetch(path) - if (response.status == 200) { - let texts = await response.json() - return texts - } - throw new Error(response.status) -} -},{}],36:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') - -async function footer(footer) { - const css = styles - let island = await graphic(css.island, './src/node_modules/assets/svg/deco-island.svg') - const graphics = footer.icons.map(icon => graphic(css.icon, icon.imgURL)) - const icons = await Promise.all(graphics) - - let el = bel` -
-
- ${island} - -
- -

${footer.copyright}

-
- ` - return el -} - -let styles = csjs` -.footer { - display: grid; - grid-template-rows: auto; - grid-template-columns: 1fr; - color: var(--footerTextColor); - padding-top: 4vw; - padding-bottom: 0.5%; - background-color: var(--footerBg); -} -.copyright { - text-align: center; - align-self: center; -} -.scene { - position: relative; - width: 60%; - max-width: 1200px; - margin: 0 auto; - display: grid; - grid-template-rows: auto; - grid-template-columns: repeat(4, 25%); -} -.contacts { - display: flex; - justify-content: center; - align-items: center; - grid-row-start: 2; - grid-column-start: 2; - grid-column-end: 4; - margin-top: -2%; -} -.contacts a { - margin: 0 2rem; -} -.icon { - width: 6vw; -} -.island { - grid-row-start: 1; - grid-row-end: 6; - grid-column-start: 1; - grid-column-end: 5; -} -@media only screen and (min-width: 1440px) { - .icon { - max-width: 10rem; - } -} -@media only screen and (max-width: 1200px) { - .contacts a { - margin: 0 1.5vw; - } -} -@media only screen and (max-width: 1024px) { - .scene { - width: 80%; - } - .icon { - width: 8vw; - } -} -` - -module.exports = footer -},{"bel":4,"csjs-inject":7,"graphic":37}],37:[function(require,module,exports){ -const loadSVG = require('loadSVG') - -function graphic(className, url) { - - return new Promise((resolve, reject) => { - let el = document.createElement('div') - el.classList.add(className) - loadSVG(url, (err, svg) => { - if (err) return console.error(err) - el.append(svg) - resolve(el) - }) - }) -} - -module.exports = graphic -},{"loadSVG":39}],38:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -const Rellax = require('rellax') - -module.exports = header - -async function header(data) { - const css = styles - var graphics = [ - graphic(css.playIsland, './src/node_modules/assets/svg/play-island.svg'), - graphic(css.sun, './src/node_modules/assets/svg/sun.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud6, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud7, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [playIsland, sun, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) - - // Parallax effects - // let playRellax = new Rellax(playIsland, { speed: 2 }) - let sunRellax = new Rellax(sun, { speed: 2 }) - let cloud1Rellax = new Rellax(cloud1, { speed: 4 }) - let cloud2Rellax = new Rellax(cloud2, { speed: 2 }) - let cloud3Rellax = new Rellax(cloud3, { speed: 4 }) - let cloud4Rellax = new Rellax(cloud4, { speed: 2 }) - let cloud5Rellax = new Rellax(cloud5, { speed: 4 }) - let cloud6Rellax = new Rellax(cloud6, { speed: 3 }) - let cloud7Rellax = new Rellax(cloud7, { speed: 3 }) - - let el = bel` -
-

${data.title}

-
-
- ${cloud1} - ${sun} - ${cloud2} -
- ${cloud3} - ${cloud4} - ${cloud5} - ${cloud6} - ${cloud7} - ${playIsland} -
-
- ` - return el -} - -let styles = csjs` -.header { - position: relative; - padding-top: 0vw; - background-image: linear-gradient(0deg, var(--playBgGEnd), var(--playBgGStart)); - overflow: hidden; -} -.scene { - position: relative; - margin-top: 5vw; -} -.playIsland { - position: relative; - width: 90%; - margin-top: 0; - margin-left: 5vw; - z-index: 2; -} -.sunCloud { - position: absolute; - top: -4%; - width: 12%; - margin-left: 8vw; - z-index: 1; -} -.sun { - width: 100%; -} -[class^="cloud"] { - transition: left 0.6s, bottom 0.5s, top 0.5s linear; -} -.cloud1 { - position: absolute; - z-index: 2; - width: 7vw; - left: -3vw; - bottom: 0; -} -.cloud2 { - position: absolute; - z-index: 1; - width: 7vw; - left: 10vw; - top: 25%; -} -.cloud3 { - position: absolute; - z-index: 2; - width: 7vw; - height: auto; - top: -2.5%; - right: 14vw; -} -.cloud4 { - position: absolute; - z-index: 1; - width: 5vw; - height: auto; - top: 8%; - right: 6vw; -} -.cloud5 { - position: absolute; - z-index: 1; - width: 12vw; - height: auto; - top: 50%; - left: 2vw; -} -.cloud6 { - position: absolute; - z-index: 3; - width: 12vw; - height: auto; - bottom: 15%; - left: 15vw; -} -.cloud7 { - position: absolute; - z-index: 4; - width: 18vw; - height: auto; - bottom: 25%; - right: 5vw; -} -.title { - position: relative; - z-index: 4; - font-size: var(--titleSize); - font-family: var(--titleFont); - color: var(--titleColor); - text-align: center; - margin: 0; - padding: 2% 2%; -} -.sun { - will-change: transform; -} -.cloud1, .cloud2, .cloud3, .cloud4, .cloud5, .cloud6, .cloud7 { - will-change: transform; -} -@media only screen and (min-width: 1680px) { - .scrollUp .header { - padding-top: 2.5%; - } -} -@media only screen and (min-width: 2561px) { - .scene { - max-width: 90%; - margin-left: auto; - margin-right: auto; - } - .title { - font-size: calc(var(--titleSize) * 1.5); - margin-bottom: 6vh; - } -} -@media only screen and (min-width: 4096px) { - .title { - font-size: calc(var(--titleSize) * 2.25); - } -} -@media only screen and (max-width: 1680px) { - .header { - padding-top: 2vw; - } -} -@media only screen and (max-width: 1280px) { - .header { - padding-top: 3vw; - } - .scrollUp .header { - padding-top: 6.5vh; - } -} -@media only screen and (max-width: 1024px) { - .header { - padding-top: 0%; - } -} -@media only screen and (max-width: 812px) { - .header { - padding-top: 5vh; - } - .title { - padding: 0 5%; - font-size: var(--titleSizeM); - } -} -@media only screen and (max-width: 414px) { - .header { - padding-top: 8vh; - } - .title { - font-size: var(--titlesSizeS); - } - .playIsland { - width: 150%; - margin-left: -26vw; - } - .sunCloud { - top: -2vh; - left: -3vw; - } - .cloud5 { - width: 12vw; - left: -4vw; - top: 64%; - } - .cloud6 { - width: 15vw; - left: 5vw; - } - .cloud7 { - width: 20vw; - right: -5vw; - } -} -` - -},{"bel":4,"csjs-inject":7,"graphic":37,"rellax":27}],39:[function(require,module,exports){ -async function loadSVG (url, done) { - const parser = document.createElement('div') - let response = await fetch(url) - if (response.status == 200) { - let svg = await response.text() - parser.innerHTML = svg - return done(null, parser.children[0]) - } - throw new Error(response.status) -} - -module.exports = loadSVG -},{}],40:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const Content = require('content') -const Contributor = require('contributor') - -async function our_contributors (data) { - const css = styles - const graphics = [ - graphic(css.island,'./src/node_modules/assets/svg/waterfall-island.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud6, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud7, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [island, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) - const contributors = await Promise.all(data.contributors.map(person => Contributor( person, css.group, css))) - - let cloud1Rellax = new Rellax( cloud1, { speed: 0.3}) - let cloud2Rellax = new Rellax( cloud2, { speed: 0.4}) - let cloud3Rellax = new Rellax( cloud3, { speed: 0.3}) - - let el = bel` -
- ${Content(data, css)} - -
- ${island} - ${cloud1} - ${cloud2} - ${cloud3} -
- -
- ${contributors} -
- - ${cloud4} - ${cloud5} - ${cloud6} - ${cloud7} -
- ` - - return el - - function spacing() { - let subTitle = content.querySelector(`.${css.subTitle}`) - let article = content.querySelector(`.${css.article}`) - let contentH = content.offsetTop + subTitle.clientHeight + article.clientHeight - let groups = document.querySelector(`.${css.groups}`) - let screen = window.innerWidth - - } -} - -let styles = csjs` -.section { - position: relative; - background-image: linear-gradient(0deg, var(--section5BgGEnd), var(--section5BgGMiddle), var(--section5BgGStart)); - display: grid; - grid-template-rows: auto; - grid-template-columns: repeat(3, 1fr); - padding: 5vw 2vw 10vw 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-row-end: 2; - grid-column-start: 3; - text-align: center; - padding: 0; -} -.subTitleColor { - color: var(--section5TitleColor); - margin: 0; - padding: 2.5rem 0; -} -.inner { - position: relative; - grid-row-start: 1; - grid-row-end: 3; - grid-column-start: 1; - grid-column-end: 4; -} -.island { - position: relative; - z-index: 7; - width: 62%; -} -.groups { - position: relative; - z-index: 9; - grid-row-start: 2; - grid-row-end: 3; - grid-column-start: 2; - grid-column-end: 4; - width: 100%; - display: grid; - grid-template-rows: auto; - grid-template-columns: repeat(12, 12.5%); - justify-self: end; -} -.group { - position: relative; - z-index: 4; - width: 100%; -} -.group:nth-child(1) { - grid-row-start: 1; - grid-column-start: 4; - grid-column-end: 7; -} -.group:nth-child(2) { - grid-row-start: 2; - grid-column-start: 2; - grid-column-end: 5; - margin-top: -10%; - margin-left: -5%; -} -.group:nth-child(3) { - grid-row-start: 2; - grid-column-start: 6; - grid-column-end: 9; - margin-left: 0vw; - margin-top: -10%; -} -.group:nth-child(4) { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 4; - margin-left: -10%; -} -.group:nth-child(5) { - grid-row-start: 3; - grid-column-start: 5; - grid-column-end: 8; - margin-left: -20%; -} -.group:nth-child(6) { - grid-row-start: 4; - grid-column-start: 3; - grid-column-end: 6; -} -.group:nth-child(7) { - grid-row-start: 4; - grid-column-start: 8; - grid-column-end: 11; -} -.group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 1; - grid-column-end: 4; -} -.group:nth-child(9) { - grid-row-start: 5; - grid-column-start: 5; - grid-column-end: 8; -} -.group:nth-child(10) { - grid-row-start: 5; - grid-column-start: 9; - grid-column-end: 12; -} -.group:nth-child(11) { - grid-row-start: 6; - grid-column-start: 2; - grid-column-end:5; -} -.group:nth-child(12) { - grid-row-start: 6; - grid-column-start: 7; - grid-column-end: 10; -} -.group:nth-child(13) { - grid-row-start: 7; - grid-column-start: 1; - grid-column-end: 4; -} -.group:nth-child(14) { - grid-row-start: 7; - grid-column-start: 5; - grid-column-end: 8; -} -.avatar { - position: relative; - z-index: 2; -} -.info { - display: flex; - flex-direction: column; - justify-content: center; - font-size: var(--contributorsTextSize); - text-align: center; - background-color: var(--contributorsBg); - padding: 0% 2% 4% 20%; - margin-left: -20%; -} -.name { - color: var(--section5TitleColor); - margin-top: 0; - margin-bottom: 3%; -} -.career { - display: block; - color: var(--contributorsCareerColor); -} -.cloud1 { - position: absolute; - z-index: 2; - width: 8vw; - top: 10vw; - left: 5vw; -} -.cloud2 { - position: absolute; - z-index: 3; - width: 12vw; - top: 5vw; - left: 20vw; -} -.cloud3 { - position: absolute; - z-index: 4; - width: 6vw; - top: 15vw; - left: 50vw; -} -.cloud4 { - position: absolute; - z-index: 5; - width: 12vw; - bottom: 12vw; - left: 5vw; -} -.cloud5 { - position: absolute; - z-index: 5; - width: 8vw; - bottom: 5vw; - left: 30vw; -} -.cloud6 { - position: absolute; - z-index: 4; - width: 14vw; - bottom: 0; - right: 25vw; -} -.cloud7 { - position: absolute; - z-index: 3; - width: 6vw; - bottom: 5vw; - right: 10vw; -} -@media only screen and (min-width: 2561px) { - .info { - font-size: calc(var(--contributorsTextSize) * 1.35); - } -} -@media only screen and (min-width: 1920px) { - .groups { - grid-template-columns: repeat(12, 8.33%); - margin-top: 2vw; - } - .group:nth-child(1) { - grid-row-start: 1; - grid-column-start: 7; - grid-column-end: 11; - margin-left: 0; - } - .group:nth-child(2) { - grid-row-start: 2; - grid-column-start: 4; - grid-column-end: 8; + else{ + status.tree[id] = { $: { _: {} } } + status.tree_pointers[id] = status.tree[id].$ } - .group:nth-child(3) { - grid-row-start: 2; - grid-column-start: 9; - grid-column-end: 13; - } - .group:nth-child(4) { - grid-row-start: 3; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(5) { - grid-row-start: 3; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(6) { - grid-row-start: 4; - grid-column-start: 4; - grid-column-end: 8; - margin-left: 0; - } - .group:nth-child(7) { - grid-row-start: 4; - grid-column-start: 9; - grid-column-end: 13; - margin-left: 0; - } - .group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(9) { - grid-row-start: 5; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(9) { - grid-row-start: 5; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(10) { - grid-row-start: 6; - grid-column-start: 4; - grid-column-end: 8; - margin-left: 0; - } - .group:nth-child(11) { - grid-row-start: 6; - grid-column-start: 9; - grid-column-end: 13; - margin-left: 0; - } - .group:nth-child(12) { - grid-row-start: 7; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(13) { - grid-row-start: 7; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(14) { - grid-row-start: 8; - grid-column-start: 4; - grid-column-end: 8; - } - .group:nth-child(15) { - grid-row-start: 8; - grid-column-start: 9; - grid-column-end: 13; - } -} -@media only screen and (max-width: 1900px) { - .group:nth-child(1) { - margin-left: 15%; + return status } - .group:nth-child(2) { - grid-column-start: 2; - grid-column-end: 5; - } - .group:nth-child(3) { - grid-column-start: 6; - grid-column-end: 9; - } - .group:nth-child(4) { - grid-column-start: 1; - grid-column-end: 4; - } - .group:nth-child(5) { - grid-column-start: 5; - grid-column-end: 8; - } - .group:nth-child(6) { - grid-row-start: 4; - grid-column-start: 3; - grid-column-end: 6; - } - .group:nth-child(7) { - grid-row-start: 5; - grid-column-start: 1; - grid-column-end: 4; - } - .group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 5; - grid-column-end: 8; - } - .group:nth-child(9) { - grid-row-start: 6; - grid-column-start: 2; - grid-column-end: 5; - } - .group:nth-child(10) { - grid-row-start: 6; - grid-column-start: 6; - grid-column-end: 9; - } - .group:nth-child(11) { - grid-row-start: 7; - grid-column-start: 3; - grid-column-end: 6; - } - .group:nth-child(12) { - grid-row-start: 8; - grid-column-start: 1; - grid-column-end: 4; - } - .group:nth-child(13) { - grid-row-start: 8; - grid-column-start: 5; - grid-column-end: 8; - } - .group:nth-child(14) { - grid-row-start: 9; - grid-column-start: 2; - grid-column-end: 5; - } - .group:nth-child(15) { - grid-row-start: 9; - grid-column-start: 6; - grid-column-end: 9; - } -} -@media only screen and (max-width: 1024px) { - .section { - grid-template-columns: 1fr; - } - .content { - grid-row-start: 1; - grid-row-end: 2; - grid-column-start: 1; - padding: 0 5vw; - } - .inner { - justify-content: center; - grid-row-start: 2; - grid-row-end: 3; - padding-top: 10%; - } - .island { - width: 90%; - } - .groups { - grid-row-start: 3; - grid-row-end: 4; - grid-column-start: 1; - width: 90%; - grid-template-columns: 1fr 1fr; - margin: 0 auto; - } - .group:nth-child(1) { - grid-column-start: 1; - grid-column-end: 1; - margin-left: 0; - } - .group:nth-child(2) { - grid-row-start: 2; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(3) { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(4) { - grid-row-start: 4; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(5) { - grid-row-start: 5; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(6) { - grid-row-start: 6; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(7) { - grid-row-start: 7; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(8) { - grid-row-start: 8; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(9) { - grid-row-start: 9; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(10) { - grid-row-start: 10; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(11) { - grid-row-start: 11; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(12) { - grid-row-start: 12; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(13) { - grid-row-start: 13; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(14) { - grid-row-start: 14; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(15) { - grid-row-start: 15; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .cloud1 { - width: 10vw; - top: 35vw; - left: 8vw; - } - .cloud2 { - width: 20vw; - top : 22vw; - left: 30vw; - } - .cloud3 { - width: 10vw; - top: 38vw; - left: 70vw; - } -} - -@media only screen and (max-width: 960px) { - .cloud1 { - top: 22vw; - } - .cloud2 { - top: 12vw; - } - .cloud3 { - top: 30vw; - } -} -@media only screen and (max-width: 640px) { - .groups { - position: relative; - z-index: 3; - grid-template-columns: 1fr; - } - .groups > div { - width: 80%; - margin-bottom: 5vw; - } - .group:nth-child(1) { - margin-left: 8vw; - } - .group:nth-child(2) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(3) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -3vw; - } - .group:nth-child(4) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .group:nth-child(5) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -5vw; - } - .group:nth-child(6) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(7) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -3vw; - } - .group:nth-child(8) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .group:nth-child(9) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -5vw; - } - .group:nth-child(10) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(11) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -3vw; - } - .group:nth-child(12) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .group:nth-child(13) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -5vw; - } - .group:nth-child(14) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(15) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -10vw; - } - .group:nth-child(16) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .info { - font-size: var(--contributorsTextSizeS); - } - .island { - width: 98%; - } - .cloud1 { - width: 12vw; - top: 30vw; - } - .cloud2 { - top: 22vw; - } - .cloud3 { - width: 12vw; - top: 35vw; - left: 75vw; - } - .cloud4 { - z-index: 1; - width: 20vw; - bottom: 40vw; - } - .cloud5 { - width: 15vw; - left: 10vw; - bottom: 20vw; - } - .cloud6 { - width: 30vw; - bottom: 5vw; - right: 35vw; - } - .cloud7 { - width: 15vw; - bottom: 20vw; - } -} -@media only screen and (max-width: 414px) { - .groups { - width: 100%; - } - .cloud1 { - top: 63vw; - } - .cloud2 { - top: 56vw; - } - .cloud3 { - top: 65vw; - } - .cloud4 { - bottom: 30vw; + function init_module () { + const {statedata, state_entries, newstatus, updated_local_status} = get_module_data(local_status.fallback_module) + statedata.orphan && (local_status.orphan = true) + //side effects + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + console.log('Main module: ', statedata.id, '\n', state_entries) + updated_local_status && Object.assign(local_status, updated_local_status) + // console.log('Local status: ', local_status.fallback_instance, statedata.api) + const old_fallback = local_status.fallback_instance + + if(local_status.fallback_instance ? local_status.fallback_instance?.toString() === statedata.api?.toString() : false) + local_status.fallback_instance = statedata.api + else + local_status.fallback_instance = (args, tools) => { + return statedata.api(args, tools, [old_fallback]) + } + const extra_fallbacks = Object.entries(old_fallback || {}) + extra_fallbacks.length && extra_fallbacks.forEach(([key, value]) => { + local_status.fallback_instance[key] = (args, tools) => { + console.log('Extra fallback: ', statedata.api[key] ? statedata.api[key] : old_fallback[key]) + return (statedata.api[key] ? statedata.api[key] : old_fallback[key])(args, tools, [value]) + } + }) + db.append(['state'], state_entries) + // add_source_code(statedata.inputs) // @TODO: remove side effect + } + [local_status.sub_modules, symbol2ID, ID2Symbol, address2ID, ID2Address] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + Object.assign(status.a2i, address2ID) + Object.assign(status.i2a, ID2Address) + + //Setup local data (module level) + if(status.root_module){ + status.root_module = false + statedata.admins && admins.push(...statedata.admins) + } + // @TODO: handle sub_modules when dynamic require is implemented + // const sub_modules = {} + // statedata.subs && statedata.subs.forEach(id => { + // sub_modules[db.read(['state', id]).type] = id + // }) + } + function init_instance (sid, fallback_key) { + const fallback = local_status.fallback_instance[fallback_key] || local_status.fallback_instance + const {statedata, state_entries, newstatus} = get_instance_data(sid, fallback) + + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + Object.assign(status.tree, newstatus.tree) + console.log('Main instance: ', statedata.id, '\n', state_entries) + db.append(['state'], state_entries) + } + [local_status.sub_instances[statedata.id], symbol2ID, ID2Symbol, address2ID, ID2Address] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + Object.assign(status.a2i, address2ID) + Object.assign(status.i2a, ID2Address) + + const sdb = create_statedb_interface(local_status, statedata.id, xtype = 'instance') + + const sanitized_event = {} + statedata.net && Object.entries(statedata.net?.event).forEach(([def, action]) => { + sanitized_event[def] = action.map(msg => { + msg.id = status.a2i[msg.address] || (a2i[msg.address] = encode(msg.address)) + return msg + }) + }) + if(statedata.net) + statedata.net.event = sanitized_event + return { + id: statedata.id, + net: statedata.net, + sdb: sdb.public_api, + io: io(status.a2i[statedata.id], modulepath) } - .cloud5 { - bottom: 10vw; + } + function get_module_data (fallback) { + let data = db.read(['state', modulepath]) + if (status.fallback_check) { + if (data) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: data }) + } + else if (status.root_module) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: {id: modulepath}}) + } + else { + var {sanitized_data, updated_status, updated_local_status} = find_super({ xtype: 'module', fallback, fun_status:status, local_status }) + } + data = sanitized_data.entry } - .cloud6 { - bottom: 5vw; + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + updated_local_status } - .cloud7 { - bottom: 8vw; + } + function get_instance_data (sid, fallback) { + let id = s2i[sid] + if(id && (id.split(':')[0] !== modulepath || !id.includes(':'))) + throw new Error(`Access denied! Wrong SID '${id}' used by instance of '${modulepath}'` + FALLBACK_SUBS_POST_ERROR) + if(status.used_ids.has(id)) + throw new Error(`Access denied! SID '${id}' is already used` + FALLBACK_SUBS_POST_ERROR) + + id && status.used_ids.add(id) + let data = id && db.read(['state', id]) + let sanitized_data, updated_status = status + if (status.fallback_check) { + if (!data && !status.root_instance) { + ({sanitized_data, updated_status} = find_super({ xtype: 'instance', fallback, fun_status: status })) + } else { + ({sanitized_data, updated_status} = validate_and_preprocess({ + fun_status: status, + fallback, + xtype: 'instance', + pre_data: data || {id: get_instance_path(modulepath)} + })) + updated_status.root_instance = false + } + data = sanitized_data.entry } -} -@media only screen and (min-width: 414px) -and (max-width: 736px) and (orientation: landscape) { - .section { - margin-top: -1px; + else if (status.root_instance) { + data = db.read(['state', id || get_instance_path(modulepath)]) + updated_status.tree = JSON.parse(JSON.stringify(status.tree)) + updated_status.root_instance = false } - .cloud1 { - top: 50vw; + + if (!data && local_status.orphan) { + data = db.read(['state', get_instance_path(modulepath)]) } - .cloud2 { - top: 48vw; + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, } - .cloud3 { - top: 55vw; + } + function find_super ({ xtype, fallback, fun_status, local_status }) { + let modulepath_super = modulepath.split(/\>(?=[^>]*$)/)[0] + let modulepath_grand = modulepath_super.split(/\>(?=[^>]*$)/)[0] + const split = modulepath.split('>') + let data + const entries = {} + if(xtype === 'module'){ + let name = split.at(-1) + while(!data && modulepath_grand.includes('>')){ + data = db.read(['state', modulepath_super]) + const split = modulepath_super.split(/\>(?=[^>]*$)/) + modulepath_super = split[0] + name = split[1] + '>' + name + } + data.path = data.id = modulepath_super + '>' + name + modulepath = modulepath_super + '>' + name + local_status.name = name + + const super_data = db.read(['state', modulepath_super]) + super_data.subs.forEach((sub_id, i) => { + if(sub_id === modulepath_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + else{ + //@TODO: Make the :0 dynamic + let instance_path_super = modulepath_super + ':0' + let temp + while(!data && temp !== modulepath_super){ + data = db.read(['state', instance_path_super]) + temp = modulepath_super + modulepath_grand = modulepath_super = modulepath_super.split(/\>(?=[^>]*$)/)[0] + instance_path_super = modulepath_super + ':0' + } + data.path = data.id = get_instance_path(modulepath) + temp = null + let super_data + let instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + + while(!super_data?.subs && temp !== modulepath_grand){ + super_data = db.read(['state', instance_path_grand]) + temp = modulepath_grand + modulepath_grand = modulepath_grand.split(/\>(?=[^>]*$)/)[0] + instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + } + + super_data.subs.forEach((sub_id, i) => { + if(sub_id === instance_path_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + data.name = split.at(-1) + return { updated_local_status: local_status, + ...validate_and_preprocess({ + fun_status, + fallback, xtype, + pre_data: data, + orphan_check: true, entries }) } + } + function validate_and_preprocess ({ fallback, xtype, pre_data = {}, orphan_check, fun_status, entries }) { + const used_keys = new Set() + let {id: pre_id, hubs: pre_hubs, mapping} = pre_data + let fallback_data + try { + validate(fallback(status.args[pre_id], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }), xtype) + } catch (error) { + throw new Error(`in fallback function of ${pre_id} ${xtype}\n${error.stack}`) + } + if(fun_status.overrides[pre_id]){ + fallback_data = fun_status.overrides[pre_id].fun[0](status.args[pre_id], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }, get_fallbacks({ fallback, modulename: local_status.name, modulepath, instance_path: pre_id })) + console.log('Override used: ', pre_id) + fun_status.overrides[pre_id].by.splice(0, 1) + fun_status.overrides[pre_id].fun.splice(0, 1) + } + else + fallback_data = fallback(status.args[pre_id], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }) + + console.log('fallback_data: ', fallback) + fun_status.overrides = register_overrides({ overrides: fun_status.overrides, tree: fallback_data, path: modulepath, id: pre_id }) + console.log('overrides: ', Object.keys(fun_status.overrides)) + orphan_check && (fallback_data.orphan = orphan_check) + //This function makes changes in fun_status (side effect) + return { + sanitized_data: sanitize_state({ local_id: '', entry: fallback_data, path: pre_id, xtype, mapping, entries }), + updated_status: fun_status } -} -` - -module.exports = our_contributors - -},{"bel":4,"content":30,"contributor":31,"csjs-inject":7,"graphic":37,"rellax":27}],41:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const graphic = require('graphic') -const Content = require('content') - -module.exports = smartcontract_codes - -async function smartcontract_codes (data) { - const css = styles - const graphics = [ - graphic(css.island, './src/node_modules/assets/svg/floating-island1.svg'), - graphic(css.islandMiddle, './src/node_modules/assets/svg/floating-island2.svg'), - graphic(css.islandRight, './src/node_modules/assets/svg/floating-island2.svg'), - graphic(css.blossom, './src/node_modules/assets/svg/blossom-tree.svg'), - graphic(css.tree, './src/node_modules/assets/svg/single-tree.svg'), - graphic(css.trees, './src/node_modules/assets/svg/two-trees.svg'), - graphic(css.stone, './src/node_modules/assets/svg/stone.svg'), - graphic(css.smallStone, './src/node_modules/assets/svg/small-stone.svg'), - ] + + function sanitize_state ({ local_id, entry, path, hub_entry, local_tree, entries = {}, xtype, mapping, xkey }) { + [path, entry, local_tree] = extract_data({ local_id, entry, path, hub_entry, local_tree, xtype, xkey }) - const [island, islandMiddle, islandRight, blossom, tree, trees, stone, smallStone] = await Promise.all(graphics) + entry.id = path + entry.name = entry.name || local_id.split(':')[0] || local_status.name + mapping && (entry.mapping = mapping) + + entries = {...entries, ...sanitize_subs({ local_id, entry, path, local_tree, xtype, mapping })} + delete entry._ + entries[entry.id] = entry + // console.log('Entry: ', entry) + return {entries, entry} + } + function extract_data ({ local_id, entry, path, hub_entry, xtype, xkey }) { + if (local_id) { + entry.hubs = [hub_entry.id] + if (xtype === 'instance') { + let temp_path = path.split(':')[0] + temp_path = temp_path ? temp_path + '>' : temp_path + const module_id = temp_path + local_id + entry.type = module_id + path = module_id + ':' + xkey + temp = Number(xkey)+1 + temp2 = db.read(['state', path]) + while(temp2 || used_keys.has(path)){ + path = module_id + ':' + temp + temp2 = db.read(['state', path]) + temp++ + } + } + else { + entry.type = local_id + path = path ? path + '>' : '' + path = path + local_id + } + } + else { + if (xtype === 'instance') { + entry.type = local_status.module_id + } else { + local_tree = JSON.parse(JSON.stringify(entry)) + // @TODO Handle JS file entry + // console.log('pre_id:', pre_id) + // const file_id = local_status.name + '.js' + // entry.drive || (entry.drive = {}) + // entry.drive[file_id] = { $ref: address } + entry.type = local_status.name + } + pre_hubs && (entry.hubs = pre_hubs) + } + return [path, entry, local_tree] + } + function sanitize_subs ({ local_id, entry, path, local_tree, xtype, mapping }) { + const entries = {} + if (!local_id) { + entry.subs = [] + if(entry._){ + //@TODO refactor when fallback structure improves + Object.entries(entry._).forEach(([local_id, value]) => { + Object.entries(value).forEach(([key, override]) => { + if(key === 'mapping' || (key === '$' && xtype === 'instance')) + return + const sub_instance = sanitize_state({ local_id, entry: value, path, hub_entry: entry, local_tree, xtype: key === '$' ? 'module' : 'instance', mapping: value['mapping'], xkey: key }).entry + entries[sub_instance.id] = JSON.parse(JSON.stringify(sub_instance)) + entry.subs.push(sub_instance.id) + used_keys.add(sub_instance.id) + }) + })} + if (entry.drive) { + // entry.drive.theme && (entry.theme = entry.drive.theme) + // entry.drive.lang && (entry.lang = entry.drive.lang) + entry.inputs = [] + const new_drive = [] + Object.entries(entry.drive).forEach(([dataset_type, dataset]) => { + dataset_type = dataset_type.split('/')[0] + + const new_dataset = { files: [], mapping: {} } + Object.entries(dataset).forEach(([key, value]) => { + const sanitized_file = sanitize_file(key, value, entry, entries) + entries[sanitized_file.id] = sanitized_file + new_dataset.files.push(sanitized_file.id) + }) + new_dataset.id = local_status.name + '.' + dataset_type + '.dataset' + new_dataset.type = dataset_type + new_dataset.name = 'default' + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + entries[new_dataset.id] = new_dataset + let check_name = true + entry.inputs.forEach(dataset_id => { + const ds = entries[dataset_id] + if(ds.type === new_dataset.type) + check_name = false + }) + check_name && entry.inputs.push(new_dataset.id) + new_drive.push(new_dataset.id) + + + if(!status.root_module){ + const hub_entry = db.read(['state', entry.hubs[0]]) + if(!mapping?.[dataset_type]) + throw new Error(`No mapping found for dataset "${dataset_type}" of subnode "${entry.id}" in node "${hub_entry.id}"\nTip: Add a mapping prop for "${dataset_type}" dataset in "${hub_entry.id}"'s fallback for "${entry.id}"` + FALLBACK_POST_ERROR) + const mapped_file_type = mapping[dataset_type] + hub_entry.inputs.forEach(input_id => { + const input = db.read(['state', input_id]) + if(mapped_file_type === input.type){ + input.mapping[entry.id] = new_dataset.id + entries[input_id] = input + return + } + }) + } + }) + entry.drive = new_drive + } + } + return entries + } + function sanitize_file (file_id, file, entry, entries) { + const type = file_id.split('.').at(-1) - let el = bel` -
+ if (!isNaN(Number(file_id))) return file_id - ${Content(data, css)} + const raw_id = local_status.name + '.' + type + file.id = raw_id + file.name = file.name || file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [entry.id] + if(file.$ref){ + file.$ref = address.substring(0, address.lastIndexOf("/")) + '/' + file.$ref + } + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + while(entries[file.id]){ + const no = file.id.split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + return file + } + } +} -
-
- ${data.title} logo - ${data.title} - ${trees} -
- ${island} -
-
-
-
- ${smallStone} - ${stone} - ${blossom} -
- ${islandMiddle} -
-
- ${tree} - ${islandRight} -
-
- -
- ` +// External Function (helper) +function validate (data, xtype) { + /** Expected structure and types + * Sample : "key1|key2:*:type1|type2" + * ":" : separator + * "|" : OR + * "*" : Required key + * */ + const expected_structure = { + 'api::function': () => {}, + '_::object': { + ":*:object|number": xtype === 'module' ? { + ":*:function|string|object": '', + "mapping::": {} + } : { // Required key, any name allowed + ":*:function|string|object": () => {}, // Optional key + "mapping::": {} + }, + }, + 'drive::object': { + "::object": { + "::object": { // Required key, any name allowed + "raw|$ref:*:object|string": {}, // data or $ref are names, required, object or string are types + "$ref": "string" + } + }, + }, + 'net::object': {} + } - return el -} + validate_shape(data, expected_structure) + + function validate_shape (obj, expected, super_node = 'root', path = '') { + const keys = Object.keys(obj) + const values = Object.values(obj) + let strict = Object.keys(expected).length + + const all_keys = [] + Object.entries(expected).forEach(([expected_key, expected_value]) => { + let [expected_key_names, required, expected_types] = expected_key.split(':') + expected_types = expected_types ? expected_types.split('|') : [typeof(expected_value)] + let absent = true + if(expected_key_names) + expected_key_names.split('|').forEach(expected_key_name => { + const value = obj[expected_key_name] + if(value !== undefined){ + all_keys.push(expected_key_name) + const type = typeof(value) + absent = false + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, expected_key_name, path + '/' + expected_key_name) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${expected_key_name}" at:` + path + FALLBACK_POST_ERROR) + } + }) + else{ + strict = false + values.forEach((value, index) => { + absent = false + const type = typeof(value) + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, keys[index], path + '/' + keys[index]) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${keys[index]}" at: ` + path + FALLBACK_POST_ERROR) + }) + } + if(absent && required){ + if(expected_key_names) + throw new Error(`Can't find required key "${expected_key_names.replace('|', ' or ')}" at: ` + path + FALLBACK_POST_ERROR) + else + throw new Error(`No sub-nodes found for super key "${super_node}" at sub: ` + path + FALLBACK_POST_ERROR) + } + }) -const styles = csjs` -.section { - position: relative; - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 60% 40%; - background-image: linear-gradient(0deg, var(--section3BgGEnd), var(--section3BgGStart)); - padding: 3vw 2vw 0 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-column-start: 2; - grid-column-end: 3; - text-align: center; - padding: 0 5%; -} -.subTitleColor { - color: var(--section3TitleColor); - margin-top: 0; -} -.buttonBg { - background-image: linear-gradient(0deg, #900df8, #ac1cf6); -} -.scene { - grid-row-start: span 2; - grid-column-start: 1; -} -.deco { - position: relative; -} -.screenshot { - width: 65%; - margin-left: 15%; - margin-bottom: -6%; -} -.trees { - position: absolute; - right: 10%; - bottom: -20%; - width: 15%; -} -.logo { - position: absolute; - left:6%; - bottom: -20%; - width: 15%; + strict && keys.forEach(key => { + if(!all_keys.includes(key)){ + throw new Error(`Unknown key detected: '${key}' is an unknown property at: ${path || 'root'}` + FALLBACK_POST_ERROR) + } + }) + } } -.island { +function extract_filename (address) { + const parts = address.split('/node_modules/') + const last = parts.at(-1).split('/') + if(last.at(-1) === 'index.js') + return last.at(-2) + return last.at(-1).slice(0, -3) } -.sceneMedium { - grid-row-start: 2; - grid-column-start: 2; - display: grid; - grid-template: 1fr / 65% 35%; - align-items: center; +function get_instance_path (modulepath, modulepaths = status.modulepaths) { + return modulepath + ':' + modulepaths[modulepath]++ } -.container { - position: relative; +async function get_input ({ id, name, $ref, type, raw }) { + const xtype = (typeof(id) === "number" ? name : id).split('.').at(-1) + let result = db.read([type, id]) + + if (!result) { + if (raw === undefined){ + let ref_url = $ref + // Patch: Prepend GitHub project name if running on GitHub Pages + if (typeof window !== 'undefined' && window.location.hostname.endsWith('github.io')) { + const path_parts = window.location.pathname.split('/').filter(Boolean) + if (path_parts.length > 0 && !$ref.startsWith('/' + path_parts[0])) { + ref_url = '/' + path_parts[0] + ($ref.startsWith('/') ? '' : '/') + $ref + } + } + const response = await fetch(ref_url) + if (!response.ok) + throw new Error(`Failed to fetch data from '${ref_url}' for '${id}'` + FALLBACK_SYNTAX_POST_ERROR) + else + result = await response[xtype === 'json' ? 'json' : 'text']() + } + else + result = raw + } + return result } -.sceneMedium .deco:nth-child(1) { - width: 80%; - justify-self: center; +//Unavoidable side effect +function add_source_code (hubs) { + hubs.forEach(async id => { + const data = db.read(['state', id]) + if (data.type === 'js') { + data.data = await get_input(data) + db.add(['state', data.id], data) + return + } + }) } -.sceneMedium .deco:nth-child(2) { +function verify_imports (id, imports, data) { + const state_address = imports.find(imp => imp.includes('STATE')) + HELPER_MODULES.push(state_address) + imports = imports.filter(imp => !HELPER_MODULES.includes(imp)) + if(!data._){ + if(imports.length > 1){ + imports.splice(imports.indexOf(state_address), 1) + throw new Error(`No sub-nodes found for required modules "${imports.join(', ')}" in the fallback of "${status.local_statuses[id].module_id}"` + FALLBACK_POST_ERROR) + } + else return + } + const fallback_imports = Object.keys(data._) -} -.blossom { - width: 55%; - margin: 0 0 -10% 12%; -} -.islandMiddle { + imports.forEach(imp => { + let check = true + fallback_imports.forEach(fallimp => { + if(imp === fallimp) + check = false + }) -} -.tree { - position: relative; - width: 50%; - margin: 0 auto; - margin-bottom: -11%; - z-index: 2; -} -.islandRight { + if(check) + throw new Error('Required module "'+imp+'" is not defined in the fallback of '+status.local_statuses[id].module_id + FALLBACK_POST_ERROR) + }) + + fallback_imports.forEach(fallimp => { + let check = true + imports.forEach(imp => { + if(imp === fallimp) + check = false + }) + if(check) + throw new Error('Module "'+fallimp+'" defined in the fallback of '+status.local_statuses[id].module_id+' is not required') + }) + } -.stone { - position: absolute; - right: 12%; - bottom: 3%; - width: 22%; -} -.smallStone { - position: absolute; - left: 7%; - bottom: 5%; - width: 14%; +function symbolfy (data) { + const i2s = {} + const s2i = {} + const i2a = {} + const a2i = {} + const subs = [] + data.subs && data.subs.forEach(sub => { + const substate = db.read(['state', sub]) + i2a[a2i[sub] = encode(sub)] = sub + s2i[i2s[sub] = Symbol(a2i[sub])] = sub + subs.push({ sid: i2s[sub], type: substate.type }) + }) + return [subs, s2i, i2s, a2i, i2a] } -@media screen and (min-width: 2561px) { - .tree { - margin-bottom: -10.5%; +function encode(text) { + let code = '' + while (code.length < 50) { + for (let i = 0; i < text.length && code.length < 50; i++) { + code += Math.floor(10 + Math.random() * 90) } + } + return code } -@media screen and (min-width: 1025px) and (max-width: 1200px) { - .sceneMedium { - margin-top: 4.5rem; +function listfy(tree, prefix = '') { + if (!tree) + return [] + + const result = [] + + function walk(current, prefix = '') { + for (const key in current) { + if (key === '$' && current[key]._ && typeof current[key]._ === 'object') { + walk(current[key]._, prefix) + } else { + const path = prefix ? `${prefix}>${key}` : key + result.push(path) + if (current[key]?.$?._ && typeof current[key].$._ === 'object') { + walk(current[key].$._, path) + } + } } + } + + if (tree._ && typeof tree._ === 'object') { + walk(tree._, prefix) + } + + return result +} +function register_overrides ({overrides, ...args}) { + recurse(args) + return overrides + function recurse ({ tree, path = '', id, xtype = 'instance', local_modulepaths = {} }) { + + tree._ && Object.entries(tree._).forEach(([type, instances]) => { + const sub_path = path + '>' + type + Object.entries(instances).forEach(([id, override]) => { + const resultant_path = id === '$' ? sub_path : sub_path + ':' + id + if(typeof(override) === 'function'){ + if(overrides[resultant_path]){ + overrides[resultant_path].fun.push(override) + overrides[resultant_path].by.push(id) + } + else + overrides[resultant_path] = {fun: [override], by: [id]} + } + else if ( ['object', 'string'].includes(typeof(override)) && id !== 'mapping' && override._ === undefined) + status.args[resultant_path] = structuredClone(override) + else + recurse({ tree: override, path: sub_path, id, xtype, local_modulepaths }) + }) + }) + } } -@media screen and (max-width: 1024px) { - .content { - grid-column-start: 1; - margin-bottom: 60px; +function get_fallbacks ({ fallback, modulename, modulepath, instance_path }) { + return [mutated_fallback, ...status.overrides[instance_path].fun] + + function mutated_fallback () { + console.log('Args: ', status.args[instance_path]) + const data = fallback(status.args[instance_path], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }) + + data.overrider = status.overrides[instance_path].by[0] + merge_trees(data, modulepath) + return data + + function merge_trees (data, path) { + if (data._) { + Object.entries(data._).forEach(([type, data]) => merge_trees(data, path + '>' + type.split('$')[0].replace('.', '>'))) + } else { + data.$ = { _: status.tree_pointers[path]?._ } + } } + } } -@media screen and (max-width: 640px) { - .scene { - grid-row-start: 2; - grid-column-end: 3; - } - .sceneMedium { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 3; +function check_version () { + if (db.read(['playproject_version']) != VERSION) { + localStorage.clear() + return true + } +} + +// Public Function +function create_statedb_interface (local_status, node_id, xtype) { + const api = { + public_api: { + watch, get_sub, drive: { + get, has, put, list + } + }, + private_api: { + xget: (id) => db.read(['state', id]), + get_all: () => db.read_all(['state']), + get_db, + register, + load: (snapshot) => { + localStorage.clear() + Object.entries(snapshot).forEach(([key, value]) => { + db.add([key], JSON.parse(value), true) + }) + window.location.reload() + }, + swtch, + unregister, + status, } - .sceneMedium .deco:nth-child(1) { - width: 90%; + } + node_id === status.ROOT_ID && (api.public_api.admin = api.private_api) + return api + + async function watch (listener, on) { + if(on) + status.services[node_id] = Object.keys(on) + const data = db.read(['state', node_id]) + if(listener){ + status.listeners[data.id] = listener + await listener(await make_input_map(data.inputs)) + } + return xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + } + function get_sub (type) { + return local_status.subs.filter(sub => { + const dad = db.read(['state', sub.type]) + return dad.type === type + }) + } + function get_db ({ type: dataset_type, name: dataset_name } = {}) { + const node = db.read(['state', status.ROOT_ID]) + if(dataset_type){ + const dataset_list = [] + node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.type === dataset_type) + dataset_list.push(dataset.name) + }) + if(dataset_name){ + return recurse(status.ROOT_ID, dataset_type) + } + return dataset_list } - .sceneMedium .deco:nth-child(2) { - width: 80%; - justify-self: center; - align-self: center; + const datasets = [] + node.inputs && node.inputs.forEach(dataset_id => { + datasets.push(db.read(['state', dataset_id]).type) + }) + return datasets + + function recurse (node_id, dataset_type){ + const node_list = [] + const entry = db.read(['state', node_id]) + const temp = entry.mapping ? Object.keys(entry.mapping).find(key => entry.mapping[key] === dataset_type) : null + const mapped_type = temp || dataset_type + entry.drive && entry.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === mapped_type){ + node_list.push(node_id) + return + } + }) + entry.subs && entry.subs.forEach(sub_id => node_list.push(...recurse(sub_id, mapped_type))) + return node_list } - .tree { - bottom: -5.5%; + } + function register ({ type: dataset_type, name: dataset_name, dataset}) { + Object.entries(dataset).forEach(([node_id, files]) => { + const new_dataset = { files: [] } + Object.entries(files).forEach(([file_id, file]) => { + const type = file_id.split('.').at(-1) + + file.id = local_status.name + '.' + type + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [node_id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + db.add(['state', file.id], file) + new_dataset.files.push(file.id) + }) + + const node = db.read(['state', node_id]) + new_dataset.id = node.name + '.' + dataset_type + '.dataset' + new_dataset.name = dataset_name + new_dataset.type = dataset_type + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + db.push(['state', node_id, 'drive'], new_dataset.id) + db.add(['state', new_dataset.id], new_dataset) + }) + console.log(' registered ' + dataset_name + '.' + dataset_type) + } + function unregister ({ type: dataset_type, name: dataset_name } = {}) { + return recurse(status.ROOT_ID) + + function recurse (node_id){ + const node = db.read(['state', node_id]) + node.drive && node.drive.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.drive.splice(node.drive.indexOf(dataset_id), 1) + return true + } + }) + node.inputs && node.inputs.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.inputs.splice(node.inputs.indexOf(dataset_id), 1) + swtch(dataset_type) + return true + } + }) + db.add(['state', node_id], node) + node.subs.forEach(sub_id => recurse(sub_id)) } -} -` -},{"bel":4,"content":30,"csjs-inject":7,"graphic":37}],42:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const crystalIsland = require('crystalIsland') - -async function supporters (data) { - const css = styles - let pageTitle = bel`
${data.title}
` - const graphics = [ - // crystals - graphic(css.yellowCrystal,'./src/node_modules/assets/svg/crystal-yellow.svg'), - graphic(css.purpleCrystal,'./src/node_modules/assets/svg/crystal-purple.svg'), - graphic(css.blueCrystal,'./src/node_modules/assets/svg/crystal-blue.svg'), - // stone - graphic(css.stone,'./src/node_modules/assets/svg/stone1.svg'), - // trees - graphic(css.tree,'./src/node_modules/assets/svg/big-tree.svg'), - graphic(css.tree,'./src/node_modules/assets/svg/single-tree1.svg'), - graphic(css.tree,'./src/node_modules/assets/svg/single-tree3.svg'), - graphic(css.tree,'./src/node_modules/assets/svg/single-tree2.svg'), - // islands - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - // clouds - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud6, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [yellowCrystal, purpleCrystal, blueCrystal, stone, tree, tree1, tree2, tree3, - island, island1, island2, island3, island4, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6] = await Promise.all(graphics) - - // Parallax effects - let cloud1Rellax = new Rellax( cloud1, { speed: 1.5}) - let cloud2Rellax = new Rellax( cloud2, { speed: 1}) - let cloud3Rellax = new Rellax( cloud3, { speed: 1.5}) - let cloud4Rellax = new Rellax( cloud4, { speed: 4}) - let cloud5Rellax = new Rellax( cloud5, { speed: 1.5}) - let cloud6Rellax = new Rellax( cloud6, { speed: 3}) - - let el = bel` -
- - ${crystalIsland(data.supporters[0], [yellowCrystal, tree], island, css, pageTitle)} - ${crystalIsland(data.supporters[1], [stone, tree1], island1, css)} - ${crystalIsland(data.supporters[2], [purpleCrystal], island2, css)} - ${crystalIsland(data.supporters[3], [blueCrystal, tree2], island3, css)} - -
- ${tree3} - ${island4} -
- - ${cloud1} - ${cloud2} - ${cloud3} - ${cloud4} - ${cloud5} - ${cloud6} - -
- ` - return el -} + } + function swtch ({ type: dataset_type, name: dataset_name = 'default'}) { + recurse(dataset_type, dataset_name, status.ROOT_ID) -let styles = csjs` -.section { - position: relative; - background-image: linear-gradient(0deg, var(--section4BgGEnd), var(--section4BgGStart)); - display: grid; - grid-template-rows: repeat(2, auto); - grid-template-columns: 15% 35% 35% 15%; - padding-top: 10vw; -} -.scene { -} -.scene:nth-child(1) { - position: relative; - z-index: 3; - width: 50vw; - grid-row-start: 1; - grid-row-end: 2; - grid-column-start: 1; - grid-column-end: 4; - margin-left: 30vw; -} -.scene:nth-child(2) { - position: relative; - z-index: 4; - width: 24vw; - grid-row-start: 2; - grid-column-start: 1; - grid-column-end: 3; - margin-top: -15vw; - margin-left: 5vw; -} -.scene:nth-child(3) { - position: relative; - z-index: 4; - width: 24vw; - grid-row-start: 2; - grid-column-start: 2; - margin-top: 3vw; - margin-left: 8vw; -} -.scene:nth-child(4) { - position: relative; - z-index: 4; - width: 30vw; - grid-row-start: 2; - grid-column-start: 3; - margin-top: 5vw; - margin-left: 7vw; -} -.scene:nth-child(5) { - position: relative; - z-index: 2; - grid-row-start: 1; - grid-column-start: 4; - width: 10vw; - align-self: end; - margin-bottom: 12vw; -} -.scene:nth-child(5) .tree { - width: 80%; - margin: 0 auto -1.5vw auto; -} -.deco { - position: relative; -} -.tree { - position: relative; - width: 50%; - margin: 0 0 -11% -9%; - z-index: 2; -} -.yellowCrystal { - position: absolute; - width: 25%; - left: 20%; - bottom: 1%; - z-index: 3; -} -.title { - position: absolute; - bottom: 35%; - right: 18%; - z-index: 5; - font-family: var(--titleFont); - font-size: var(--supportersHeadlline); - color: var(--section4TitleColor); -} -.island { + async function recurse (target_type, target_name, id) { + const node = db.read(['state', id]) + + let target_dataset + node.drive && node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(target_name === dataset.name && target_type === dataset.type){ + target_dataset = dataset + return + } + }) + if(target_dataset){ + node.inputs.forEach((dataset_id, i) => { + const dataset = db.read(['state', dataset_id]) + if(target_type === dataset.type){ + node.inputs.splice(i, 1) + return + } + }) + node.inputs.push(target_dataset.id) + } + db.add(['state', id], node) + status.listeners[id] && status.listeners[id](await make_input_map(node.inputs)) + node.subs && node.subs.forEach(sub_id => { + const subdataset_id = target_dataset?.mapping?.[sub_id] + recurse(target_type, db.read(['state', subdataset_id])?.name || target_name, sub_id) + }) + } + } + + function list (path) { + const node = db.read(['state', node_id]) + const dataset_names = node.drive.map(dataset_id => { + return dataset_id.split('.').at(1) + '/' + }) + if (path) { + let index + dataset_names.some((dataset_name, i) => { + if (path.includes(dataset_name)) { + index = i + return true + } + }) + if (index === undefined) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + const dataset = db.read(['state', node.drive[index]]) + return dataset.files.map(fileId => { + const file = db.read(['state', fileId]) + return file.name + }) + } + return dataset_names + } + async function get (path) { + const [dataset_name, file_name] = path.split('/') + const node = db.read(['state', node_id]) + let dataset + if(!node.drive) + throw new Error(`Node ${node.id} has no drive defined in the fallback` + FALLBACK_POST_ERROR) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(1)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + let target_file + for (const file_id of dataset.files) { + const file = db.read(['state', file_id]) + if (file.name === file_name) { + target_file = { id: file.id, name: file.name, type: file.type, raw: await get_input(file)} + break + } + } + if (!target_file) + throw new Error(`File "${path}" not found`) + return target_file + } + function put (path, buffer) { + const [dataset_name, filename] = path.split('/') + let dataset + const node = db.read(['state', node_id]) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(1)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + const type = filename.split('.').pop() + let file_id = filename + let count = 1 + while (db.read(['state', file_id])) { + file_id = `${filename}:${count++}` + } + const file = { + id: file_id, + name: filename, + type, + raw: buffer + } + db.add(['state', file_id], file) + dataset.files.push(file_id) + db.add(['state', dataset.id], dataset) + return { id: file_id, name: filename, type, raw: buffer } + } + function has (path) { + const [dataset_name, filename] = path.split('/') + let dataset + const node = db.read(['state', node_id]) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(1)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + return dataset.files.some(file_id => { + const file = db.read(['state', file_id]) + return file && file.name === filename + }) + } } -.content { - position: absolute; - display: flex; - flex-direction: column; - justify-content: center -} -.scene:nth-child(1) .content { - width: 35%; - left: 21vw; - bottom: 2%; - background: url('./src/node_modules/assets/svg/card1.svg') no-repeat; - background-size: cover; - padding: 8% 5% 8% 8%; -} -.content h3 { - font-family: var(--titleFont); - font-size: var(--supportersTitleSize); - text-align: center; - color: var(--supportersTitleColor); - margin-top: 0; -} -.content p { - font-size: var(--supportersTextSize); - text-align: center; - margin: 0; -} -.scene:nth-child(2) .content { - width: 45%; - left: 35%; - bottom: -2vw; - background: url('./src/node_modules/assets/svg/card2.svg') no-repeat; - background-size: cover; - padding: 10% 5% 10% 8%; -} -.scene:nth-child(2) .tree { - position: absolute; - width: 45%; - left: 2vw; - bottom: -0.5vw; -} -.scene:nth-child(2) .stone { - position: absolute; - width: 24%; - right: 5%; - bottom: -2.5vw; - z-index: 2; -} -.scene:nth-child(3) .content { - width: 60%; - left: 5vw; - bottom: -2.2vw; - background: url('./src/node_modules/assets/svg/card3.svg') no-repeat; - background-size: cover; - padding: 10% 5% 10% 8%; -} -.purpleCrystal { - position: absolute; - width: 40%; - left: -6%; - bottom: -2.5vw; - z-index: 3; -} -.scene:nth-child(4) .content { - width: 50%; - left: 6vw; - bottom: -2.5vw; - background: url('./src/node_modules/assets/svg/card4.svg') no-repeat; - background-size: cover; - padding: 10% 5% 10% 8%; -} -.scene:nth-child(4) h3 { - margin-bottom: 10px; -} -.scene:nth-child(4) h3:last-child { - margin-bottom: 0; -} -.scene:nth-child(4) .tree { - position: absolute; - width: 24%; - right: -0.5vw; - bottom: 0vw; -} -.blueCrystal { - position: absolute; - width: 22%; - left: 1vw; - bottom: -3vw; - z-index: 3; -} -.cloud1 { - position: absolute; - width: 8vw; - top: 25vw; - left: 8vw; - z-index: 5; -} -.cloud2 { - position: absolute; - width: 15vw; - top: 10vw; - left: 50vw; - z-index: 6; -} -.cloud3 { - position: absolute; - width: 15vw; - top: 30vw; - right: 10vw; - z-index: 5; -} -.cloud4 { - position: absolute; - width: 8vw; - bottom: 28vw; - right: 5vw; - z-index: 4; -} -.cloud5 { - position: absolute; - width: 12vw; - bottom: -3vw; - right: 6vw; - z-index: 5; -} -.cloud6 { - position: absolute; - width: 8vw; - bottom: -10vw; - right: 2vw; - z-index: 6; +async function make_input_map (inputs) { + const input_map = [] + if (inputs) { + await Promise.all(inputs.map(async input => { + let files = [] + const dataset = db.read(['state', input]) + await Promise.all(dataset.files.map(async file_id => { + const input_state = db.read(['state', file_id]) + files.push(dataset.id.split('.').at(1) + '/' + input_state.name) + })) + input_map.push({ type: dataset.type, paths: files }) + })) + } + return input_map } -@media only screen and (min-width: 3840px) { - .info h3 { - margin-bottom: 6px; - font-size: calc( var(--supportersTitleSizeM) * 2); - } - .info p { - font-size: calc( var(--supportersTextSizeM) * 2); + + +module.exports = STATE +},{"io":10,"localdb":12}],4:[function(require,module,exports){ +(function (__filename){(function (){ +const graphic = require('graphic') +const IO = require('io') +const STATE = require('STATE') +const name = 'footer' +const statedb = STATE(__filename) +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + const data = require('./instance.json') + data.inputs['footer.css'] = { + $ref: new URL('src/node_modules/css/default/footer.css', location).href + } + return data +} +/****************************************************************************** + APP FOOTER COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = footer + +async function footer (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const on = { + css: inject, + scroll, + json: fill + } + + const send = await IO(id, name, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + let island = await graphic('island', './src/node_modules/assets/svg/deco-island.svg') + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ` + // ---------------------------------------- + const style = shadow.querySelector('style') + const footer = shadow.querySelector('footer') + + sdb.watch(onbatch) + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill ([ opts ]) { + const graphics = opts.icons.map(icon => graphic('icon', icon.imgURL)) + const icons = await Promise.all(graphics) + footer.innerHTML = ` +
+ ${island.outerHTML} + +
+ + ` + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null } + } } -@media only screen and (max-width: 1366px) { - .title { - bottom: 17vw; + +}).call(this)}).call(this,"/src/node_modules/footer/footer.js") +},{"./instance.json":5,"STATE":3,"graphic":8,"io":10}],5:[function(require,module,exports){ +module.exports={ + "inputs": { + "footer.json": { + "data": { + "copyright": " PlayProject", + "icons": [ + { + "id": "1", + "name": "email", + "imgURL": "./src/node_modules/assets/svg/email.svg", + "url": "mailto:ninabreznik@gmail.com" + }, + { + "id": "2", + "name": "twitter", + "imgURL": "./src/node_modules/assets/svg/twitter.svg", + "url": "https://twitter.com/playproject_io" + }, + { + "id": "3", + "name": "Github", + "imgURL": "./src/node_modules/assets/svg/github.svg", + "url": "https://github.com/playproject-io" + }, + { + "id": "4", + "name": "Gitter", + "imgURL": "./src/node_modules/assets/svg/gitter.svg", + "url": "https://gitter.im/playproject-io/community" + } + ] + } } + } } -@media only screen and (max-width: 1024px) { - .section { - grid-template-columns: repeat(2, 50vw); - } - .scene:nth-child(1) { - grid-row-start: 1; - width: 70vw; - margin-left: 10vw; - } - .scene:nth-child(1) .content { - width: 35%; - left: 28vw; - } - .scene:nth-child(2) { - grid-row-start: 2; - grid-column-start: 1; - width: 40vw; - margin-top: 15vw; - margin-left: 5vw; - } - .scene:nth-child(2) .content { - bottom: -3vw; - left: 28%; - } - .scene:nth-child(2) .stone { - bottom: -4vw; - right: 9%; - } - .scene:nth-child(3) { - grid-row-start: 2; - grid-column-start: 2; - width: 40vw; - margin-top: 20vw; - margin-left: 5vw; - } - .scene:nth-child(3) .content { - left: 8vw; - bottom: -3vw; - } - .purpleCrystal { - bottom: -3.5vw; - } - .scene:nth-child(4) { - grid-row-start: 3; - grid-column-start: 2; - width: 50vw; - margin: 10vw 0 0 -25vw; - } - .scene:nth-child(4) .content { - bottom: -4.5vw; - width: 50%; - left: 8vw; +},{}],6:[function(require,module,exports){ +(function (__filename){(function (){ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const localdb = require('localdb') +const name = 'graph_explorer' +const statedb = STATE(__filename) +const default_slots = [['hubs', 'subs'], ['inputs', 'outputs']] +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + return { + inputs: { + 'graph_explorer.css': { + $ref: new URL('src/node_modules/css/default/graph_explorer.css', location).href + } } - .blueCrystal { - bottom: -5vw; + } +} + +const IO = require('io') +const {copy, get_color, download_json} = require('helper') +/****************************************************************************** + GRAPH COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- + +module.exports = graph_explorer + +async function graph_explorer (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const db = localdb() + const { id, sdb } = await get(opts.sid) + const hub_id = opts.hub[0] + const status = { tab_id: 0, count: 0, entry_types: {}, menu_ids: [] } + const on = { + init, + css: inject, + scroll + } + + const on_add = { + entry: add_entry, + entry_compact: add_entry_compact, + menu: add_action + } + const admin = sdb.req_access(opts.sid) + const send = await IO(id, name, on) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + const style = document.createElement('style') + await sdb.watch(onbatch) + shadow.innerHTML = ` +
+ + + +
+ Compact mode + +
+ +
` + const main = shadow.querySelector('main') + const compact_switch = shadow.querySelector('.toggle_switch > input') + const upload = shadow.querySelector('.upload') + const import_btn = shadow.querySelector('.import') + const export_btn = shadow.querySelector('.export') + shadow.append(style) + shadow.addEventListener('copy', oncopy) + /************************************ + Listeners + *************************************/ + compact_switch.onchange = e => add_root(e.target.checked) + export_btn.onclick = export_fn + import_btn.onclick = () => upload.click() + upload.onchange = import_fn + return el + + /****************************************** + Mix + ******************************************/ + function onbatch (batch) { + for (const {type, data} of batch) { + on[type](data) + } + } + async function oncopy (e) { + const selection = shadow.getSelection() + e.clipboardData.setData('text/plain', copy(selection)) + e.preventDefault() + } + function export_fn () { + const blob = new Blob([JSON.stringify(localStorage, null, 2)], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = 'snapshot.json' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + function import_fn () { + const file = upload.files[0] + const reader = new FileReader() + reader.onload = e => { + const blob = JSON.parse(e.target.result) + admin.load(blob) + } + reader.readAsText(file) + } + async function init ({ data }) { + let id = Object.keys(data).length + 1 + + add({ id, name: 'edit', type: 'action', hubs: [] }) + add({ id, name: 'link', type: 'action', hubs: [] }) + add({ id, name: 'unlink', type: 'action', hubs: [] }) + add({ id, name: 'drop', type: 'action', hubs: [] }) + + status.graph = data + add_root(false) + + function add (args){ + status.menu_ids.push(args.id) + data[id++] = args } - .scene:nth-child(5) { - grid-row-start: 1; - grid-column-start: 3; - width: 15vw; - align-self: start; - margin-top: 15vw; - margin-left: -20vw; + } + async function add_root(compact) { + status.xentry = null + status.entry_types = {} + status.count = 0 + status.tab_id = 0 + main.innerHTML = '' + const root_entries = Object.values(status.graph).filter(entry => !entry.hubs) + if(compact) + root_entries.forEach((data, i) => add_entry_compact({hub_el: main, data, last: i === root_entries.length - 1, ancestry:[] })) + else + root_entries.forEach((data, i) => add_entry({hub_el: main, data, last: i === root_entries.length - 1, ancestry:[] })) + } + function html_template (data, space, pos){ + const element = document.createElement('div') + element.classList.add(data.type, 'entry', 'a'+data.id) + element.tabIndex = '0' + element.dataset.space = space + element.dataset.pos = pos + return element + } + /****************************************** + Addition Operation + ******************************************/ + // function add_el ({ data, parent, space, grand_last, type }){ + // const is_single = parent.children.length ? false : true + // if(data.root){ + // parent.prepend(add_root({ data, last: false})) + // return + // } + // //hub or sub node check + // if(type === 'inputs') + // parent.append(on_add[type]({ data, space, grand_last, first: is_single})) + // else + // parent.prepend(on_add[type]({ data, space, grand_last, last: is_single})) + // } + + function add_action ({ hub_el, data, last, space = '' }) { + const element = html_template(data, last, space) + hub_el.append(element) + !status.entry_types[data.type] && (status.entry_types[data.type] = Object.keys(status.entry_types).length) + + element.innerHTML = ` +
+ ${space} + + ${data.name} +
` + const name = element.querySelector('.slot_list > .name') + name.onclick = () => send({ type: 'click', to: hub_id, data }) + + } + function add_entry_compact ({ hub_el, data, first, last, space = '', pos, ancestry }) { + //Init + const element = html_template(data, last, space, pos) + !status.entry_types[data.type] && (status.entry_types[data.type] = Object.keys(status.entry_types).length) + ancestry = [...ancestry] + let lo_space = space + (last ? ' ' : '│') + let hi_space = space + (first ? ' ' : '│') + const space_handle = [], els = [] + let slot_no = 0, slot_on + + //HTML + element.innerHTML = ` +
+ ${space}${last ? '└' : first ? "┌" : '├'}${last ? '┗' : first ? "┏" : '┠'} + ${data.name} +
+ + ` + + //Unavoidable mix + const slot_list = element.querySelector('.slot_list') + const name = element.querySelector('.slot_list > .name') + hub_el.append(element) + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + if(copies.length > 1){ + copies.forEach(copy => !copy.previousElementSibling && (copy.style.color = '#000000')) + } + if(ancestry.includes(data.id)){ + name.onclick = () => { + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + copies.forEach((copy, i) => { + if(copy === slot_list) + return + const temp1 = copy.style.color + const temp2 = copy.style.backgroundColor + copy.style.color = '#fff' + copy.style.backgroundColor = '#000000' + setTimeout(() => { + copy.style.color = temp1 + copy.style.backgroundColor = temp2 + }, 1000) + }) + } + return + } + ancestry.push(data.id) + + //Elements + const menu_emo = element.querySelector('.slot_list > .menu_emo') + const type_emo = element.querySelector('.slot_list > .type_emo') + const menu = element.querySelector('.menu') + + //Listeners + type_emo.onclick = type_click + name.onclick = () => send({ type: 'click', to: hub_id, data }) + const slotmap = [] + const data_keys = Object.keys(data) + const new_pair = [[], []] + const slot_handle = [] + let check = false + default_slots.forEach(pair => { + pair.forEach((slot, i) => { + if(data_keys.includes(slot)){ + new_pair[i].push(slot) + check = true + } + }) + }) + check && slotmap.push(new_pair) + slotmap.forEach(handle_slot) + menu_click({el: menu, emo: menu_emo, data: status.menu_ids, pos: 0, type: 'menu'}) + if(getComputedStyle(type_emo, '::before').content === 'none') + type_emo.innerHTML = `[${status.entry_types[data.type]}]` + + //Procedures + async function handle_slot (pair, i) { + const slot_check = [false, false] + const slot_emo = document.createElement('span') + slot_emo.innerHTML = '' + menu_emo.before(slot_emo) + slot_no++ + + pair.forEach((x, j) => { + let gap, mode, emo_on + const pos = !j + const count = status.count++ + const style = document.createElement('style') + const entries = document.createElement('div') + entries.classList.add('entries') + + element.append(style) + if(pos){ + slot_list.before(entries) + mode= 'hi' + gap = hi_space + hi_space += ` ${x.length ? '' : ''}  ` + } + else{ + menu.after(entries) + mode = 'lo' + gap = lo_space + lo_space += ` ${x.length ? '' : ''}  ` + } + style.innerHTML = `.space${count} > .x${mode}{display: none;}` + els.push(slot_emo) + space_handle.push(() => style.innerHTML = `.space${count}${slot_on ? ` > .x${mode}` : ''}{display: none;}`) + if(!x.length){ + const space = document.createElement('span') + space.innerHTML = '   ' + return + } + slot_emo.classList.add('compact') + + slot_handle.push(() => { + slot_emo.classList.add('on') + style.innerHTML = `.space${count} > .${emo_on ? 'x' : ''}${mode}{display: none;}` + // emo_on && space_handle[i]() + slot_check[j] = emo_on = !emo_on + if(slot_check[0] && slot_check[1]) + slot_emo.children[0].innerHTML = '┼' + else if(slot_check[0] && !slot_check[1]) + slot_emo.children[0].innerHTML = '┴' + else if(!slot_check[0] && slot_check[1]) + slot_emo.children[0].innerHTML = '┬' + else{ + slot_emo.children[0].innerHTML = '─' + slot_emo.classList.remove('on') + } + const ids = [] + x.forEach(slot => ids.push(...data[slot])) + handle_click({space: gap, pos, el: entries, data: ids, ancestry, type: 'entry_compact' }) + }) + }) + if(getComputedStyle(slot_emo, '::before').content === 'none') + slot_emo.innerHTML = `${slot_no}─` + } + async function type_click() { + slot_on = !slot_on + // if(status.xentry === type_emo) + // status.xentry = null + // else{ + // status.xentry?.click() + // status.xentry = type_emo + // } + slot_list.classList.toggle('on') + let temp = element + //Find path to root + while(temp.tagName !== 'MAIN'){ + if(temp.classList.contains('entry')){ + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + while(temp.previousElementSibling){ + temp = temp.previousElementSibling + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + } + } + temp = temp.parentElement + } + els.forEach((emo, i) => { + if(!emo.classList.contains('on')){ + space_handle[i]() + } + }) + slot_handle[0] && slot_handle[0]() + slot_handle[1] && slot_handle[1]() + } + async function menu_click({ emo, emo_on, ...rest }, i) { + emo.onclick = () => { + emo.classList.toggle('on') + emo_on = !emo_on + handle_click({space: lo_space, ...rest }) + } } - .scene:nth-child(5) .tree { - width: 65%; - margin: 0 auto -2vw auto; + } + function add_entry ({ hub_el, data, first, last, space = '', pos, ancestry }) { + //Init + const element = html_template(data, last, space, pos) + !status.entry_types[data.type] && (status.entry_types[data.type] = Object.keys(status.entry_types).length) + ancestry = [...ancestry] + let lo_space = space + (last ? '   ' : '│  ') + let hi_space = space + (first ? '   ' : '│  ') + const space_handle = [], els = [] + let slot_no = 0, slot_on + + //HTML + element.innerHTML = ` +
+ ${space}${last ? '└' : first ? "┌" : '├'}${last ? '┗' : first ? "┏" : '┠'} + ${data.name} +
+ + ` + + //Unavoidable mix + const slot_list = element.querySelector('.slot_list') + const name = element.querySelector('.slot_list > .name') + hub_el.append(element) + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + if(copies.length > 1){ + copies.forEach(copy => !copy.previousElementSibling && (copy.style.color = '#000000')) + } + if(ancestry.includes(data.id)){ + name.onclick = () => { + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + copies.forEach((copy, i) => { + if(copy === slot_list) + return + const temp1 = copy.style.color + const temp2 = copy.style.backgroundColor + copy.style.color = '#fff' + copy.style.backgroundColor = '#000000' + setTimeout(() => { + copy.style.color = temp1 + copy.style.backgroundColor = temp2 + }, 1000) + }) + } + return + } + ancestry.push(data.id) + + //Elements + const menu_emo = element.querySelector('.slot_list > .menu_emo') + const type_emo = element.querySelector('.slot_list > .type_emo') + const space_emo = element.querySelector('.slot_list > .space') + const menu = element.querySelector('.menu') + + //Listeners + space_emo.onclick = () => type_click(0) + type_emo.onclick = () => type_click(1) + name.onclick = () => send({ type: 'click', to: hub_id, data }) + const slotmap = [] + const data_keys = Object.keys(data) + const new_pair = [[], []] + const slot_handle = [] + let check = false + default_slots.forEach(pair => { + pair.forEach((slot, i) => { + if(data_keys.includes(slot)){ + new_pair[i].push(slot) + check = true + } + }) + }) + check && slotmap.push(new_pair) + slotmap.forEach(handle_slot) + menu_click({el: menu, emo: menu_emo, data: status.menu_ids, pos: 0, type: 'menu'}) + if(getComputedStyle(type_emo, '::before').content === 'none') + type_emo.innerHTML = `[${status.entry_types[data.type]}]` + + //Procedures + async function handle_slot (pair, i) { + const slot_check = [false, false] + const slot_emo = document.createElement('span') + slot_emo.innerHTML = '' + menu_emo.before(slot_emo) + slot_no++ + + pair.forEach((x, j) => { + let gap, mode, emo_on + const pos = !j + const count = status.count++ + const style = document.createElement('style') + const entries = document.createElement('div') + entries.classList.add('entries') + + element.append(style) + if(pos){ + slot_list.before(entries) + mode= 'hi' + gap = hi_space + hi_space += ` ${x.length ? '' : ''}  ` + } + else{ + menu.after(entries) + mode = 'lo' + gap = lo_space + lo_space += ` ${x.length ? '' : ''}  ` + } + style.innerHTML = `.space${count} > .x${mode}{display: none;}` + els.push(slot_emo) + space_handle.push(() => style.innerHTML = `.space${count}${slot_on ? ` > .x${mode}` : ''}{display: none;}`) + if(!x.length){ + const space = document.createElement('span') + space.innerHTML = '   ' + return + } + slot_emo.classList.add('compact') + + slot_handle.push(() => { + slot_emo.classList.add('on') + style.innerHTML = `.space${count} > .${emo_on ? 'x' : ''}${mode}{display: none;}` + // emo_on && space_handle[i]() + slot_check[j] = emo_on = !emo_on + if(slot_check[0] && slot_check[1]) + slot_emo.children[1].innerHTML = '┼' + else if(slot_check[0] && !slot_check[1]) + slot_emo.children[1].innerHTML = '┴' + else if(!slot_check[0] && slot_check[1]) + slot_emo.children[1].innerHTML = '┬' + else{ + slot_emo.children[1].innerHTML = '─' + slot_emo.classList.remove('on') + } + const ids = [] + x.forEach(slot => ids.push(...data[slot])) + handle_click({space: gap, pos, el: entries, data: ids, ancestry }) + }) + }) + if(getComputedStyle(slot_emo, '::before').content === 'none') + slot_emo.innerHTML = `${slot_no}─` + } + async function type_click(i) { + slot_on = !slot_on + // if(status.xentry === type_emo) + // status.xentry = null + // else{ + // status.xentry?.click() + // status.xentry = type_emo + // } + slot_list.classList.toggle('on') + let temp = element + //Find path to root + while(temp.tagName !== 'MAIN'){ + if(temp.classList.contains('entry')){ + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + while(temp.previousElementSibling){ + temp = temp.previousElementSibling + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + } + } + temp = temp.parentElement + } + els.forEach((emo, i) => { + if(!emo.classList.contains('on')){ + space_handle[i]() + } + }) + // slot_handle[0] && slot_handle[0]() + slot_handle[i] && slot_handle[i]() } - .title { - font-size: var(--supportersHeadllineM); - bottom: 25vw; - right: 11vw; + + async function menu_click({ emo, emo_on, ...rest }, i) { + emo.onclick = () => { + emo.classList.toggle('on') + emo_on = !emo_on + handle_click({space: lo_space, ...rest }) + } } - .cloud1 { - width: 15vw; - top: 30vw; + } + // async function add_node_data (name, type, parent_id, users, author){ + // const node_id = status.graph.length + // status.graph.push({ id: node_id, name, type: state.code_words[type], room: {}, users }) + // if(parent_id){ + // save_msg({ + // head: [id], + // type: 'save_msg', + // data: {username: 'system', content: author + ' added ' + type.slice(0,-1)+': '+name, chat_id: parent_id} + // }) + // //Add a message in the chat + // if(state.chat_task && parent_id === state.chat_task.id.slice(1)) + // channel_up.send({ + // head: [id, channel_up.send.id, channel_up.mid++], + // type: 'render_msg', + // data: {username: 'system', content: author+' added '+type.slice(0,-1)+': '+name} + // }) + // const sub_nodes = graph[parent_id][state.add_words[type]] + // sub_nodes ? sub_nodes.push(node_id) : graph[parent_id][state.add_words[type]] = [node_id] + // } + // else{ + // graph[node_id].root = true + // graph[node_id].users = [opts.host] + // } + // save_msg({ + // head: [id], + // type: 'save_msg', + // data: {username: 'system', content: author + ' created ' + type.slice(0,-1)+': '+name, chat_id: node_id} + // }) + // const channel = state.net[state.aka.taskdb] + // channel.send({ + // head: [id, channel.send.id, channel.mid++], + // type: 'set', + // data: graph + // }) + + // } + // async function on_add_node (data) { + // const node = data.id ? shadow.querySelector('#a' + data.id + ' > .'+data.type) : main + // node && node.children.length && add_el({ data: { name: data.name, id: status.graph.length, type: state.code_words[data.type] }, parent: node, grand_last: data.grand_last, type: data.type, space: data.space }) + // add_node_data(data.name, data.type, data.id, data.users, data.user) + // } + /****************************************** + Event handlers + ******************************************/ + function handle_focus (e) { + state.xtask = e.target + state.xtask.classList.add('focus') + state.xtask.addEventListener('blur', e => { + if(e.relatedTarget && e.relatedTarget.classList.contains('noblur')) + return + state.xtask.classList.remove('focus') + state.xtask = undefined + }, { once: true }) + } + function handle_popup (e) { + const el = e.target + el.classList.add('show') + popup.style.top = el.offsetTop - 20 + 'px' + popup.style.left = el.offsetLeft - 56 + 'px' + popup.focus() + popup.addEventListener('blur', () => { + el.classList.remove('show') + }, { once: true }) + } + function handle_click ({ el, data, pos, hub_id, type = 'entry', ...rest }) { + el.classList.toggle('show') + if(data && el.children.length < 1){ + length = data.length - 1 + data.forEach((value, i) => on_add[type]({ hub_el: el, data: {...status.graph[value], hub_id}, first: pos ? 0 === i : false, last: pos ? false : length === i, pos, ...rest })) } - .cloud2 { - width: 25vw; + } + async function handle_export () { + const data = await traverse( state.xtask.id.slice(1) ) + download_json(data) + } + async function handle_add (data) { + data = data.slice(2).trim().toLowerCase() + 's' + const input = document.createElement('input') + let node, task_id, space = '', grand_last = true, root = true + //expand other siblings + if(state.xtask){ + node = state.xtask.querySelector('.' + data) + task_id = state.xtask.id.slice(1) + const before = state.xtask.querySelector('.' + data.slice(0,3)) + before.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable: true, view: window})) + node.classList.add('show') + grand_last = state.xtask.dataset.grand_last + space = state.xtask.dataset.space + state.xtask.classList.remove('focus') + state.xtask = undefined + root = false + } + else{ + node = main + task_id = '' + } + node.prepend(input) + input.onkeydown = async (event) => { + if (event.key === 'Enter') { + input.blur() + add_el({ data : { name: input.value, id: status.graph.length, type: state.code_words[data], root }, space, grand_last, type: data, parent: node }) + const users = task_id ? graph[task_id].users : [host] + add_node_data(input.value, data, task_id, users, host) + //sync with other users + if(users.length > 1) + channel_up.send({ + head: [id, channel_up.send.id, channel_up.mid++], + type: 'send', + data: {to: 'task_explorer', route: ['up', 'task_explorer'], users: graph[task_id].users.filter(user => user !== host), type: 'on_add_node', data: {name: input.value, id: task_id, type: data, users, grand_last, space, user: host} } + }) + } } - .cloud3 { - width: 18vw; - top: 40vw; - right: 5vw; + input.focus() + input.onblur = () => input.remove() + } + /****************************************** + Tree traversal + ******************************************/ + async function jump (e){ + let target_id = e.currentTarget.dataset.id + const el = main.querySelector('#a'+target_id) + if(el) + el.focus() + else{ + const path = [] + let temp + for(temp = status.graph[target_id]; temp.hub; temp = status.graph[temp.hub[0]]) + path.push(temp.id) + temp = main.querySelector('#a'+temp.id) + target_id = 'a'+target_id + while(temp.id !== target_id){ + const sub_emo = temp.querySelector('.sub_emo') + sub_emo.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable: true, view: window})) + temp.classList.add('show') + temp = temp.querySelector('#a'+path.pop()) + } + temp.focus() } - .cloud4 { - width: 15vw; - bottom: -10vw; + + } + async function traverse (id) { + state.result = [] + state.track = [] + recurse(id) + return state.result + } + function recurse (id){ + if(state.track.includes(id)) + return + state.result.push(graph[id]) + state.track.push(id) + for(temp = 0; graph[id].sub && temp < graph[id].sub.length; temp++) + recurse(graph[id].sub[temp]) + for(temp = 0; graph[id].inputs && temp < graph[id].inputs.length; temp++) + recurse(graph[id].inputs[temp]) + for(temp = 0; graph[id].outputs && temp < graph[id].outputs.length; temp++) + recurse(graph[id].outputs[temp]) + } + /****************************************** + Communication + ******************************************/ + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null } - .cloud5 { - width: 15vw; - right: 15vw; - bottom: -30vw; - } - .cloud6 { - width: 10vw; - } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } } -@media only screen and (max-width: 960px) { - .scene:nth-child(2) .tree { - width: 30%; - } - .scene:nth-child(2) .content { - width: 60%; - left: 18%; - } - .scene:nth-child(5) .tree { - bottom: -0.5vw; - } +}).call(this)}).call(this,"/src/node_modules/graph_explorer/graph_explorer.js") +},{"STATE":3,"helper":7,"io":10,"localdb":12}],7:[function(require,module,exports){ +function copy (selection) { + const range = selection.getRangeAt(0) + const selectedElements = [] + const walker = document.createTreeWalker( + range.commonAncestorContainer, + NodeFilter.SHOW_ELEMENT, + { + acceptNode: function(node) { + return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT + } + }, + false + ) + + while (walker.nextNode()) { + walker.currentNode.tagName === 'SPAN' && selectedElements.push(walker.currentNode) + } + let text = '' + selectedElements.forEach(el => { + const before = getComputedStyle(el, '::before').content + text += (before === 'none' ? '' : before.slice(1, -1)) + el.textContent + text += el.classList.contains('name') ? '\n' : '' + }) + return text } -@media only screen and (max-width: 812px) { - .info h3 { - margin-bottom: 6px; - font-size: var(--supportersTitleSizeM); - } - .info p { - font-size: var(--supportersTextSizeM); +function get_color () { + const letters = 'CDEF89' + let color = '#' + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * letters.length)] + } + return color; +} +function download_json (data) { + const json_string = JSON.stringify(data, null, 2); + const blob = new Blob([json_string], { type: 'application/json' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'data.json'; + link.click(); +} +module.exports = {copy, get_color, download_json} +},{}],8:[function(require,module,exports){ +const loadSVG = require('loadSVG') + +function graphic(className, url) { + + return new Promise((resolve, reject) => { + const el = document.createElement('div') + el.classList.add(className) + loadSVG(url, (err, svg) => { + if (err) return console.error(err) + el.append(svg) + resolve(el) + }) + }) +} + +module.exports = graphic +},{"loadSVG":11}],9:[function(require,module,exports){ +(function (__filename){(function (){ +const graphic = require('graphic') +const Rellax = require('rellax') +const IO = require('io') +const STATE = require('STATE') +const name = 'header' +const statedb = STATE(__filename) +const shopts = { mode: 'closed' } +/****************************************************************************** + HEADER COMPONENT +******************************************************************************/ + +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + return { + inputs: { + 'header.css': { + $ref: new URL('src/node_modules/css/default/header.css', location).href + }, + "header.json": { + data: { + "title": "Infrastructure for the next-generation Internet" + } + } } + } } -@media only screen and (max-width: 640px) { - .scene:nth-child(2) { - width: 50vw; - margin-top: 20vw; - } - .scene:nth-child(3) { - width: 50vw; - grid-row-start: 3; - grid-column-start: 2; - margin-left: 0; - margin-top: 5vw; - } - .scene:nth-child(4) { - width: 66vw; - grid-row-start: 4; - grid-column-start: 1; - grid-column-end: 2; - margin-left: 10vw; - margin-top: 20vw; - } - .scene:nth-child(5) { - margin-left: -18vw; - } - .title { - font-size: var(--supportersHeadllineS); - bottom: 25vw; - right: 12vw; +module.exports = header + +async function header (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const on = { + css: inject, + json: fill, + scroll + } + const send = await IO(id, name, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + var graphics = [ + graphic('playIsland', './src/node_modules/assets/svg/play-island.svg'), + graphic('sun', './src/node_modules/assets/svg/sun.svg'), + graphic('cloud1', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud2', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud3', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud4', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud5', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud6', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud7', './src/node_modules/assets/svg/cloud.svg'), + ] + + const [playIsland, sun, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) + + // Parallax effects + // let playRellax = new Rellax(playIsland, { speed: 2 }) + let sunRellax = new Rellax(sun, { speed: 2 }) + let cloud1Rellax = new Rellax(cloud1, { speed: 4 }) + let cloud2Rellax = new Rellax(cloud2, { speed: 2 }) + let cloud3Rellax = new Rellax(cloud3, { speed: 4 }) + let cloud4Rellax = new Rellax(cloud4, { speed: 2 }) + let cloud5Rellax = new Rellax(cloud5, { speed: 4 }) + let cloud6Rellax = new Rellax(cloud6, { speed: 3 }) + let cloud7Rellax = new Rellax(cloud7, { speed: 3 }) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+

+
+
+
+
+
+ ` + // ---------------------------------------- + const style = shadow.querySelector('style') + const scene = shadow.querySelector('.scene') + const sunCloud = shadow.querySelector('.sunCloud') + const title = shadow.querySelector('.title') + await sdb.watch(onbatch) + scene.append(cloud3, cloud4, cloud5, cloud6, cloud7, playIsland) + sunCloud.append(cloud1, sun, cloud2) + + return el + + function onbatch(batch) { + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill ([ opts ]) { + title.innerHTML = opts.title + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null } + } } -@media only screen and (max-width: 480px) { - .scene:nth-child(1) { - width: 90vw; - margin-left: 10vw; + + +}).call(this)}).call(this,"/src/node_modules/header/header.js") +},{"STATE":3,"graphic":8,"io":10,"rellax":1}],10:[function(require,module,exports){ +const taken = {} + +module.exports = io +function io(seed, alias) { + if (taken[seed]) throw new Error(`seed "${seed}" already taken`) + // const pk = seed.slice(0, seed.length / 2) + // const sk = seed.slice(seed.length / 2, seed.length) + const self = taken[seed] = { id: seed, alias, peer: {} } + const io = { at, on } + return io + + async function at (id, signal = AbortSignal.timeout(1000)) { + if (id === seed) throw new Error('cannot connect to loopback address') + if (!self.online) throw new Error('network must be online') + const peer = taken[id] || {} + // if (self.peer[id] && peer.peer[pk]) { + // self.peer[id].close() || delete self.peer[id] + // peer.peer[pk].close() || delete peer.peer[pk] + // return console.log('disconnect') + // } + // self.peer[id] = peer + if (!peer.online) return wait() // peer with id is offline or doesnt exist + return connect() + function wait () { + const { resolve, reject, promise } = Promise.withResolvers() + signal.onabort = () => reject(`timeout connecting to "${id}"`) + peer.online = { resolve } + return promise.then(connect) + } + function connect () { + signal.onabort = null + const { port1, port2 } = new MessageChannel() + port2.by = port1.to = id + port2.to = port1.by = seed + self.online(self.peer[id] = port1) + peer.online(peer.peer[seed] = port2) + return port1 } - .scene:nth-child(1) .content { - width: 40%; - left: 36%; + } + function on (online) { + if (!online) return self.online = null + const resolve = self.online?.resolve + self.online = online + if (resolve) resolve(online) + } +} +},{}],11:[function(require,module,exports){ +async function loadSVG (url, done) { + const parser = document.createElement('div') + let response = await fetch(url) + if (response.status == 200) { + let svg = await response.text() + parser.innerHTML = svg + return done(null, parser.children[0]) } - .scene:nth-child(2) { - width: 70vw; - margin-top: 20vw; - margin-left: 10vw; + throw new Error(response.status) +} + +module.exports = loadSVG +},{}],12:[function(require,module,exports){ +/****************************************************************************** + LOCALDB COMPONENT +******************************************************************************/ +module.exports = localdb + +function localdb () { + const prefix = '153/' + return { add, read_all, read, drop, push, length, append, find } + + function length (keys) { + const address = prefix + keys.join('/') + return Object.keys(localStorage).filter(key => key.includes(address)).length + } + /** + * Assigns value to the key of an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function add (keys, value, precheck) { + localStorage[(precheck ? '' : prefix) + keys.join('/')] = JSON.stringify(value) + } + /** + * Appends values into an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function append (keys, data) { + const pre = keys.join('/') + Object.entries(data).forEach(([key, value]) => { + localStorage[prefix + pre+'/'+key] = JSON.stringify(value) + }) + } + /** + * Pushes value to an array already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function push (keys, value) { + const independent_key = keys.slice(0, -1) + const data = JSON.parse(localStorage[prefix + independent_key.join('/')]) + data[keys.at(-1)].push(value) + localStorage[prefix + independent_key.join('/')] = JSON.stringify(data) + } + function read (keys) { + const result = localStorage[prefix + keys.join('/')] + return result && JSON.parse(result) + } + function read_all (keys) { + const address = prefix + keys.join('/') + let result = {} + Object.entries(localStorage).forEach(([key, value]) => { + if(key.includes(address)) + result[key.split('/').at(-1)] = JSON.parse(value) + }) + return result + } + function drop (keys) { + if(keys.length > 1){ + const data = JSON.parse(localStorage[keys[0]]) + let temp = data + keys.slice(1, -1).forEach(key => { + temp = temp[key] + }) + if(Array.isArray(temp)) + temp.splice(keys[keys.length - 1], 1) + else + delete(temp[keys[keys.length - 1]]) + localStorage[keys[0]] = JSON.stringify(data) + } + else + delete(localStorage[keys[0]]) + } + function find (keys, filters, index = 0) { + let index_count = 0 + const address = prefix + keys.join('/') + const target_key = Object.keys(localStorage).find(key => { + if(key.includes(address)){ + const entry = JSON.parse(localStorage[key]) + let count = 0 + Object.entries(filters).some(([search_key, value]) => { + if(entry[search_key] !== value) + return + count++ + }) + if(count === Object.keys(filters).length){ + if(index_count === index) + return key + index_count++ + } + } + }, undefined) + return target_key && JSON.parse(localStorage[target_key]) + } +} +},{}],13:[function(require,module,exports){ +(function (__filename){(function (){ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'theme_editor' +const statedb = STATE(__filename) +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + return { + inputs: { + 'theme_editor.css': { + $ref: new URL('src/node_modules/css/default/theme_editor.css', location).href + } } - .scene:nth-child(2) .content { - width: 60%; - padding: 10% 5% 10% 12%; - bottom: -6vw; + } +} +/****************************************************************************** + THEME_EDITOR COMPONENT +******************************************************************************/ +const DB = require('localdb') +const IO = require('io') +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = theme_editor +async function theme_editor (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const status = { tab_id: 0 } + const db = DB() + const on = { + init, + hide, + css: inject + } + const {xget} = sdb.req_access(opts.sid) + const send = await IO(id, name, on) + + status.themes = { + builtin: Object.keys(opts.paths), + saved: Object.keys(JSON.parse(localStorage.index || (localStorage.index = '{}'))) + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + const style = document.createElement('style') + await sdb.watch(onbatch) + + shadow.innerHTML = ` +
+
+
+
+ +
+
+ + + + + + + + + + + +

+

+
+
+ + +
+
` + const main = shadow.querySelector('main') + const inject_btn = shadow.querySelector('.inject') + const load_btn = shadow.querySelector('.load') + const save_file_btn = shadow.querySelector('.save_file') + const save_pref_btn = shadow.querySelector('.save_pref') + const add_btn = shadow.querySelector('.add') + const drop_theme_btn = shadow.querySelector('.drop_theme') + const drop_file_btn = shadow.querySelector('.drop_file') + const reset_btn = shadow.querySelector('.reset') + const upload = shadow.querySelector('.upload') + const import_btn = shadow.querySelector('.import') + const export_btn = shadow.querySelector('.export') + const title = shadow.querySelector('h3') + const content = shadow.querySelector('.content') + const tabs = shadow.querySelector('.tabs > .box') + const plus = shadow.querySelector('.plus') + const select_theme = shadow.querySelector('div.theme') + const input = shadow.querySelector('input.theme') + + input.onfocus = () => select_theme.classList.add('active') + input.onblur = () => setTimeout(() => select_theme.classList.remove('active'), 200) + input.oninput = update_select_theme + inject_btn.onclick = on_inject + load_btn.onclick = () => load(input.value, false) + save_file_btn.onclick = save_file + save_pref_btn.onclick = save_pref + add_btn.onclick = () => add(input.value) + drop_theme_btn.onclick = drop_theme + drop_file_btn.onclick = drop_file + export_btn.onclick = export_fn + import_btn.onclick = () => upload.click() + upload.onchange = import_fn + reset_btn.onclick = () => {localStorage.clear(), location.reload()} + plus.onclick = () => add_tab('New') + shadow.append(style) + update_select_theme() + + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function hide () { + main.classList.toggle('select') + status.select = !status.select + } + async function export_fn () { + const theme = db.read([ input.value ]) + const index = db.read([ 'index', input.value ]) + const blob = new Blob([JSON.stringify({theme, index}, null, 2)], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = input.value + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + async function import_fn () { + const file = upload.files[0] + const name = file.name.split('.')[0] + await add(name) + const reader = new FileReader() + reader.onload = e => { + const blob = JSON.parse(e.target.result) + db.add([name], blob.theme) + db.add(['index', name], blob.index) + load(name) + } + reader.readAsText(file) + } + async function add (theme) { + db.add([theme], []) + status.themes.saved.push(theme) + db.add(['index', theme], []) + update_select_theme() + } + async function drop_theme () { + db.drop([input.value]) + db.drop(['index', input.value]) + status.themes.saved = status.themes.saved.filter(v => v != input.value) + update_select_theme() + input.value = 'default' + load('default') + } + async function drop_file () { + db.drop([status.active_tab.dataset.theme, status.active_tab.dataset.id]) + db.drop(['index', status.active_tab.dataset.theme, status.active_tab.dataset.id]) + close_tab(status.active_tab) + } + async function forget_changes () { + status.active_el.classList.remove('dirty') + const dirt = JSON.parse(localStorage.dirt) + delete(dirt[status.title]) + localStorage.dirt = JSON.stringify(dirt) + } + async function save_file () { + // forget_changes() + if(db.read([input.value])){ + db.push(['index', input.value], status.active_tab.dataset.name) + db.push([input.value], status.textarea.value) } - .scene:nth-child(2) .stone { - bottom: -8vw; - right: 0; + } + async function save_pref () { + const pref = db.read(['pref']) + if(status.select){ + var ids = await get_select() + ids.forEach(id => pref[id] = []) + } + pref[status.instance_id] = [] + pref[status.title] = [] + Array.from(tabs.children).forEach(tab => { + if(tab.dataset.access === "uniq"){ + if(ids) + ids.forEach(id => + pref[id].push({theme: tab.dataset.theme, id: tab.dataset.id, local: status.themes.builtin.includes(tab.dataset.theme)}) + ) + else + pref[status.instance_id].push({theme: tab.dataset.theme, id: tab.dataset.id, local: status.themes.builtin.includes(tab.dataset.theme)}) + } + else + pref[status.title].push({theme: tab.dataset.theme, id: tab.dataset.id, local: status.themes.builtin.includes(tab.dataset.theme) }) + }) + db.add(['pref'], pref) + } + async function unsave () { + status.active_el.classList.add('dirty') + let theme = localStorage[input.value] && JSON.parse(localStorage[input.value]) + if(theme){ + theme.css[status.title] = textarea.value + localStorage[input.value] = JSON.stringify(theme) + const dirt = JSON.parse(localStorage.dirt) + dirt[status.title] = input.value + localStorage.dirt = JSON.stringify(dirt) + } + else{ + const name = input.value + '*' + theme = localStorage[name] && JSON.parse(localStorage[name]) + if(theme){ + theme.css[status.title] = textarea.value + localStorage[name] = JSON.stringify(theme) + const dirt = JSON.parse(localStorage.dirt) + dirt[status.title] = name + localStorage.dirt = JSON.stringify(dirt) + } + else{ + theme = { theme: true, css: {} } + theme.css[status.title] = textarea.value + localStorage[name] = JSON.stringify(theme) + status.themes.saved.push(name) + const dirt = JSON.parse(localStorage.dirt) + dirt[status.title] = name + localStorage.dirt = JSON.stringify(dirt) + update_select_theme() + input.value = name + } } - .scene:nth-child(2) .tree { - left: 5vw; + } + async function on_inject () { + if(status.active_tab.dataset.type === 'json'){ + const id = add_data(status.textarea.value) + const hub = xget(xget(id).hub).id + send({type: 'refresh', to: hub}) + } + else{ + if(status.select){ + const ids = await get_select() + ids.forEach(id => { + send({ type: 'inject', to: id, data: status.textarea.value }) + }) + } + else + send({ type: 'inject', to: status.node_data.hub_id, data: status.textarea.value }) } - .scene:nth-child(3) { - width: 70vw; - margin-left: 20vw; - margin-top: 25vw; - grid-column-start: 1; + } + async function get_select () { + return await send({ type: 'get_select', to: 'theme_widget'}) + } + async function load (theme, clear = true) { + if(clear){ + content.innerHTML = '' + tabs.innerHTML = '' + } + if(status.themes.builtin.includes(theme)){ + const index = opts.paths[theme].length + for(let i = 0; i < index; i++){ + const temp = await fetch(`./src/node_modules/css/${theme}/${i}.css`) + add_tab(i, await temp.text(), '', theme, status.title) + } } - .scene:nth-child(3) .content { - left: 12vw; - bottom: -6vw; + else{ + const temp = db.read([theme]) + temp.forEach((file, i) => { + add_tab(i, file, '', theme, status.title) + }) } - .purpleCrystal { - bottom: -7vw; + // forget_changes() + } + async function init ({ data }) { + title.innerHTML = data.id + status.title = data.type + status.instance_id = data.id + let value = data.file ? db.read([data.xtype, data.id]) : data + if(data.type === 'json' || !data.file) + value = JSON.stringify(value, null, 2) + add_tab(data.name, value) + } + async function add_tab (id, value = '', access = 'uniq', theme = 'default') { + if(id === 'New' && status.themes.builtin.includes(theme)){ + theme += '*' + add(theme) + } + const tab = document.createElement('span') + const tab_id = '_' + status.tab_id++ + tab.id = tab_id + const index = opts.paths[theme] || db.read(['index', theme]) + tabs.append(tab) + const btn = document.createElement('span') + btn.innerHTML = index[id] || id + tab.dataset.id = id + tab.dataset.name = btn.innerHTML + tab.dataset.theme = theme + tab.dataset.access = access + btn.onclick = () => switch_tab(tab.id) + btn.ondblclick = rename + const btn_x = document.createElement('span') + btn_x.innerHTML = 'x' + tab.append(btn, btn_x) + tab.tabIndex = '0' + tab.onkeydown = e => { + if(e.key === 'ArrowRight' && tab.nextElementSibling) + tab.nextElementSibling.after(tab) + else if(e.key === 'ArrowLeft' && tab.previousElementSibling) + tab.previousElementSibling.before(tab) + tab.focus() + } + const textarea = document.createElement('textarea') + textarea.value = value + textarea.id = tab_id + content.append(textarea) + btn_x.onclick = () => close_tab(tab) + switch_tab(tab_id) + } + async function close_tab (tab) { + content.querySelector('#' + tab.id).remove() + tab.remove() + if(tabs.children.length) + switch_tab(tabs.children[tabs.children.length - 1].id) + else + add_tab('New') + } + async function switch_tab (tab_id) { + status.textarea && status.textarea.classList.remove('active') + status.textarea = content.querySelector('#' + tab_id) + status.textarea.classList.add('active') + status.active_tab && status.active_tab.classList.remove('active') + status.active_tab = tabs.querySelector('#' + tab_id) + status.active_tab.classList.add('active') + status.active_tab.focus() + input.value = status.active_tab.dataset.theme + } + async function rename (e) { + const btn = e.target + const hub = btn.parentElement + const input = document.createElement('input') + input.value = btn.innerHTML + btn.innerHTML = '' + btn.append(input) + input.onkeydown = e => { + if(e.key === 'Enter'){ + btn.innerHTML = input.value + db.add([hub.dataset.theme, hub.dataset.id], input.value) + } } - .scene:nth-child(4) { - width: 66vw; - margin-left: 10vw; - margin-top: 30vw; + input.onblur = e => { + if(e.relatedTarget) + btn.innerHTML = hub.dataset.name } - .scene:nth-child(4) .content { - width: 56%; + input.focus() + } + async function update_select_theme () { + const builtin = document.createElement('div') + builtin.classList.add('cat') + status.themes.builtin.forEach(theme => { + const el = document.createElement('div') + el.innerHTML = theme + el.onclick = () => input.value = theme + theme.includes(input.value) && builtin.append(el) + }) + builtin.innerHTML && builtin.insertAdjacentHTML('afterbegin', 'builtin') + const saved = document.createElement('div') + saved.classList.add('cat') + status.themes.saved.forEach(theme => { + const el = document.createElement('div') + el.innerHTML = theme + el.onclick = () => input.value = theme + theme.includes(input.value) && saved.append(el) + }) + saved.innerHTML && saved.insertAdjacentHTML('afterbegin', 'saved') + select_theme.innerHTML = '' + select_theme.append(builtin, saved) + } + async function inject (data){ + style.innerHTML = data.join('\n') + } +} + +}).call(this)}).call(this,"/src/node_modules/theme_editor/theme_editor.js") +},{"STATE":3,"io":10,"localdb":12}],14:[function(require,module,exports){ +(function (__filename){(function (){ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'theme_widget' +const statedb = STATE(__filename) +const shopts = { mode: 'closed' } +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return { + _: { + 'theme_editor': {}, + 'graph_explorer': {} } - .blueCrystal { - bottom: -6vw; + } +} +function fallback_instance () { + return { + _: { + 'theme_editor': {}, + 'graph_explorer': {} + }, + inputs: { + 'theme_widget.css': { + $ref: new URL('src/node_modules/css/default/theme_widget.css', location).href + } } - .scene:nth-child(5) { - grid-row-start: 4; - margin-top: -5vw; - margin-left: -22vw; + } +} +/****************************************************************************** + THEME_WIDGET COMPONENT +******************************************************************************/ +const theme_editor = require('theme_editor') +const graph_explorer = require('graph_explorer') +const IO = require('io') +// ---------------------------------------- +module.exports = theme_widget + +async function theme_widget (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const status = { tab_id: 0, init_check: true } + const on = { + refresh, + get_select, + css: inject, + scroll, + click + } + const {get_all} = sdb.req_access(opts.sid) + const send = await IO(id, name, on) + + status.clickables = ['css', 'json', 'js'] + status.dirts = JSON.parse(localStorage.dirt || (localStorage.dirt = '{}')) + localStorage.pref || (localStorage.pref = '{}') + const paths = JSON.parse(await(await fetch('./src/node_modules/css/index.json')).text()) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ⚙️ +
+ +
+ ` + const style = shadow.querySelector('style') + const btn = shadow.querySelector('.btn') + const popup = shadow.querySelector('.popup') + const box = popup.querySelector('.box') + const list = box.querySelector('.list') + const editor = popup.querySelector('.editor') + const stats = box.querySelector('.stats') + const select = box.querySelector('.select') + const slider = box.querySelector('input') + + const theme_editor_sub = sdb.get_sub('theme_editor') + const graph_explorer_sub = sdb.get_sub('graph_explorer') + await sdb.watch(onbatch) + + editor.append(await theme_editor({ sid: theme_editor_sub[0].sid, hub: [id], paths })) + box.prepend(await graph_explorer({ sid: graph_explorer_sub[0].sid, hub: [id] })) + select.onclick = on_select + slider.oninput = blur + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function blur(e) { + popup.style.opacity = e.target.value/100 + } + async function on_select () { + list.classList.toggle('active') + send({to: 'theme_editor', type: 'hide'}) + } + async function get_select () { + const inputs = list.querySelectorAll('input') + const output = [] + inputs.forEach(el => el.checked && output.push(el.nextElementSibling.id)) + send({type: 'send', to: 'theme_editor', data: output}) + } + async function refresh () { + const data = get_all() + status.tree = data + stats.innerHTML = `Entries: ${Object.keys(data).length}` + btn.onclick = () => { + popup.classList.toggle('active') + status.init_check && send({type: 'init', to: 'graph_explorer' , data:status.tree}) + status.init_check = false } - .scene:nth-child(5) .tree { - bottom: -1vw; + } + async function click ({ data }) { + send({ to: 'theme_editor', type: 'init', data}) + status.active_el && status.active_el.classList.remove('active') + if(status.instance_id === data.id) + editor.classList.toggle('active') + else{ + editor.classList.add('active') + el.classList.add('active') + } + status.instance_id = data.id + status.active_el = el + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null } - .title { - bottom: 32vw; - right: 15vw; + } + async function inject (data){ + style.innerHTML = data.join('\n') + } +} + +}).call(this)}).call(this,"/src/node_modules/theme_widget/theme_widget.js") +},{"STATE":3,"graph_explorer":6,"io":10,"theme_editor":13}],15:[function(require,module,exports){ +module.exports={ + "inputs": { + "topnav.json": { + "type": "content", + "data": { + "links": [ + { + "id": "datdot", + "text": "DatDot", + "url": "datdot" + }, + { + "id": "editor", + "text": "Play Editor", + "url": "editor" + }, + { + "id": "smartcontract_codes", + "text": "Smart Contract Codes", + "url": "smartcontract_codes" + }, + { + "id": "supporters", + "text": "Supporters", + "url": "supporters" + }, + { + "id": "our_contributors", + "text": "Contributors", + "url": "our_contributors" + } + ] + } } + } + +} +},{}],16:[function(require,module,exports){ +(function (__filename){(function (){ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'topnav' +const statedb = STATE(__filename) +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + const data = require('./instance.json') + data.inputs['topnav.css'] = { + $ref: new URL('src/node_modules/css/default/topnav.css', location).href + } + return data } -` -module.exports = supporters -},{"bel":4,"crystalIsland":32,"csjs-inject":7,"graphic":37,"rellax":27}],43:[function(require,module,exports){ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets +/****************************************************************************** + OUR CONTRIBUTORS COMPONENT +******************************************************************************/ const graphic = require('graphic') -// plugins -const zenscroll = require('zenscroll') - +const IO = require('io') +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- module.exports = topnav -async function topnav(data) { - const playLogo = await graphic(css.playLogo, './src/node_modules/assets/svg/logo.svg') +async function topnav (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const status = {} + const on = { + css: inject, + scroll, + content: fill + } + + const send = await IO(id, name, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + + const playLogo = await graphic('playLogo', './src/node_modules/assets/svg/logo.svg') + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+ ${playLogo.outerHTML} + +
+ ` + const style = shadow.querySelector('style') + const menu = shadow.querySelector('.menu') + const body = shadow.querySelector('section') + const scrollUp = 'scrollUp' + const scrollDown = 'scrollDown' + let lastScroll = 0 + + window.addEventListener('scroll', ()=> { + if (window.innerWidth >= 1024) { + let currentScroll = window.pageYOffset + if (currentScroll < 1) { + body.classList.remove(scrollUp) + body.classList.remove(scrollDown) + return + } + if (currentScroll > lastScroll && !body.classList.contains(scrollDown)) { + body.classList.add(scrollDown) + body.classList.remove(scrollUp) + } else if (currentScroll < lastScroll) { + body.classList.add(scrollUp) + body.classList.remove(scrollDown) + } + lastScroll = currentScroll + } + }) - function click(url) { - let id = document.querySelector(`#${url}`) - zenscroll.to(id, 20000) + window.addEventListener('resize', ()=> { + if (window.innerWidth <= 1024) { + body.classList.remove(scrollUp) + body.classList.remove(scrollDown) } + }) + sdb.watch(onbatch) + + return el - const body = document.body - const scrollUp = css.scrollUp - const scrollDown = css.scrollDown - let lastScroll = 0 - - window.addEventListener('scroll', ()=> { - if (window.innerWidth >= 1024) { - let currentScroll = window.pageYOffset - if (currentScroll < 1) { - body.classList.remove(scrollUp) - body.classList.remove(scrollDown) - return - } - if (currentScroll > lastScroll && !body.classList.contains(scrollDown)) { - body.classList.add(scrollDown) - body.classList.remove(scrollUp) - } else if (currentScroll < lastScroll) { - body.classList.add(scrollUp) - body.classList.remove(scrollDown) - } - lastScroll = currentScroll - } - }) - - window.addEventListener('resize', ()=> { - if (window.innerWidth <= 1024) { - body.classList.remove(scrollUp) - body.classList.remove(scrollDown) - } - }) - - return bel` -
- ${playLogo} - -
- ` + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + function fill ([ opts ]) { + menu.replaceChildren(...opts.links.map(make_link)) + } + function click(url) { + send({to:'index', type: 'jump', data: url }) + } + function make_link(link){ + const a = document.createElement('a') + a.href = `#${link.url}` + a.textContent = link.text + a.onclick = () => click(link.url) + return a + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } } -let css = csjs` -.topnav { - position: relative; - width: 100%; - z-index: 20; - display: grid; - grid-template: 1fr / auto; - background-color: var(--playBgGStart); - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - opacity: 1; - transition: background-color .6s, -webkit-transform .4s, transform .4s, opacity .3s linear; -} -.playLogo { - position: absolute; - top: 10px; - left: 0; - width: 15rem; - z-index: 99; - transition: width .6s ease-in-out; -} -.menu { - padding: 2.5rem; - text-align: right; -} -.menu a { - font-size: var(--menuSize); - margin-left: 1.75%; - color: #575551; - text-transform: uppercase; - transition: color .6s linear; -} -.menu a:hover { - color: #00acff; -} -.scrollUp .topnav { - position: fixed; - background-color: white; - -webkit-transform: none; - transform: none; -} -.scrollDown .topnav { - position: fixed; - -webkit-transform: translate3d(0, -100%, 0); - transform: translate3d(0, -100%, 0); - opacity: 0; -} -.scrollUp .playLogo { - width: 10rem; -} - .scrollDown .playLogo { - width: 10rem; - top: 0; -} -@media only screen and (min-width: 4096px) { - .menu a { - font-size: calc(var(--menuSize) * 1.5); - } +}).call(this)}).call(this,"/src/node_modules/topnav/topnav.js") +},{"./instance.json":15,"STATE":3,"graphic":8,"io":10}],17:[function(require,module,exports){ +patch_cache_in_browser(arguments[4], arguments[5]) + +function patch_cache_in_browser (source_cache, module_cache) { + const meta = { modulepath: [], paths: {} } + for (const key of Object.keys(source_cache)) { + const [module, names] = source_cache[key] + const dependencies = names || {} + source_cache[key][0] = patch(module, dependencies, meta) + } + function patch (module, dependencies, meta) { + const MAP = {} + for (const [name, number] of Object.entries(dependencies)) MAP[name] = number + return (...args) => { + const original = args[0] + require.cache = module_cache + require.resolve = resolve + args[0] = require + return module(...args) + function require (name) { + const identifier = resolve(name) + if (name.endsWith('node_modules/STATE')) { + const modulepath = meta.modulepath.join('/') + const original_export = require.cache[identifier] || (require.cache[identifier] = original(name)) + const exports = (...args) => original_export(...args, modulepath) + return exports + } else if (require.cache[identifier]) return require.cache[identifier] + else { + const counter = meta.modulepath.concat(name).join('/') + if (!meta.paths[counter]) meta.paths[counter] = 0 + const localid = `${name}${meta.paths[counter] ? '#' + meta.paths[counter] : ''}` + meta.paths[counter]++ + meta.modulepath.push(localid) + } + const exports = require.cache[identifier] = original(name) + if (!name.endsWith('node_modules/STATE')) meta.modulepath.pop(name) + return exports + } + } + function resolve (name) { return MAP[name] } + } } -@media only screen and (max-width: 1024px) { - .playLogo { - width: 9vw; - min-width: 100px; - } +require('./demo') // or whatever is otherwise the main entry of our project + +},{"./demo":18}],18:[function(require,module,exports){ +(function (__filename,__dirname){(function (){ +const STATE = require('../src/node_modules/STATE') +/****************************************************************************** + INITIALIZE PAGE +******************************************************************************/ +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) + +const make_page = require('../src/app') + +function fallback_module () { // -> set database defaults or load from database + return { + admins: ['theme_editor', 'theme_widget', 'graph_explorer'], + _: { + app: {} + } + } } -@media only screen and (max-width: 960px) { - .topnav { - position: relative; - } - .menu { - padding-top: 3%; - padding-right: 2.5vw; - } - .menu a { - margin-left: 1.5%; - } +function fallback_instance () { + return { + _: { + app: { + 0: override + } + }, + inputs: { + 'demo.css': { + $ref: new URL('src/node_modules/css/default/demo.css', location).href + } + } + } } -@media only screen and (max-width: 812px) { - .menu { - display: none; - } - .playLogo { - top: 20px; - min-width: 12vw; - } +function override ([app], path) { + const data = app() + console.log(path._.app._.topnav) + return data +} +/****************************************************************************** + CSS & HTML Defaults +******************************************************************************/ +const sheet = new CSSStyleSheet() +config().then(() => boot({ })) + +async function config () { + const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const appleTouch = '' + const icon32 = '' + const icon16 = '' + const webmanifest = '' + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += appleTouch + icon16 + icon32 + webmanifest + loadFont + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC +} +/****************************************************************************** + PAGE BOOT +******************************************************************************/ +async function boot () { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get('') // hub is "parent's" io "id" to send/receive messages + const [opts] = sdb.get_sub('app') + const on = { + css: inject + } + sdb.watch(onbatch) + const status = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // desktop + const element = await make_page(opts) + shadow.append(element) + } + // ---------------------------------------- + // INIT + // ---------------------------------------- + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } } -@media only screen and (max-width: 414px) { - .playLogo { - min-width: 20vw; - } +async function inject (data) { + sheet.replaceSync(data.join('\n')) } -` -},{"bel":4,"csjs-inject":7,"graphic":37,"zenscroll":28}]},{},[1]); + +}).call(this)}).call(this,"/web/demo.js","/web") +},{"../src/app":2,"../src/node_modules/STATE":3}]},{},[17]); diff --git a/data.json b/data.json new file mode 100644 index 0000000..d25adc4 --- /dev/null +++ b/data.json @@ -0,0 +1,451 @@ +{ + "0": { + "comp": "index", + "id": "0", + "sub": { + "theme_widget": ["1"], + "topnav": ["2"] + }, + "admins": [ + "theme_editor" + ] + }, + "11": { + "id": "11", + "comp": "theme_editor", + "admin": "true" + }, + "10": { + "id": "10", + "comp": "graph_explorer", + "hub_off": "📪", + "hub_on": "📭", + "sub_off": "📪", + "sub_on": "📭", + "in_off": "🗃", + "in_on": "🗂", + "out_off": "🗃", + "out_on": "🗂" + }, + "1": { + "id": "1", + "comp": "theme_widget", + "admin": "true", + "sub": { + "graph_explorer": ["10"], + "theme_editor": ["11"] + } + }, + "2": { + "id": "2", + "comp": "topnav", + "links": [ + { + "id": "datdot", + "text": "DatDot", + "url": "datdot" + }, + { + "id": "editor", + "text": "Play Editor", + "url": "editor" + }, + { + "id": "smartcontract_codes", + "text": "Smart Contract Codes", + "url": "smartcontract_codes" + }, + { + "id": "supporters", + "text": "Supporters", + "url": "supporters" + }, + { + "id": "our_contributors", + "text": "Contributors", + "url": "our_contributors" + } + ] + }, + "3": { + "id": "3", + "comp": "header", + "title": "Infrastructure for the next-generation Internet" + }, + "4": { + "id": "4", + "comp": "datdot", + "sub": { + "content": ["12"] + }, + "logo": "", + "image": "" + }, + "12": { + "id": "12", + "comp": "content", + "title": "DatDot", + "article": "A system that enables peer-to-peer sharing of storage space and data seeding, eliminating the need for users to rely on renting servers for data hosting or accept the potential unreliability of P2P data sharing. To achieve this goal, our protocol is designed to automate the matchmaking process and conduct periodic checks to ensure reliable hosting and serving of data to readers.", + "action": "Learn more", + "url": "https://datdot.org/" + }, + "5": { + "id": "5", + "comp": "editor", + "sub": { + "content": ["13"] + }, + "logo": "https://smartcontract-codes.github.io/play-ed/assets/logo.png", + "image": "./src/node_modules/assets/images/smart-contract-ui.jpg" + }, + "13": { + "id": "13", + "comp": "content", + "title": "Play Editor", + "article": "Web based IDE with interactive UI generator for easy writing, deploying and interacting with Solidity smart contracts.", + "action": "Learn more", + "url": "https://smartcontract-codes.github.io/play-ed/" + }, + "6": { + "id": "6", + "comp": "smartcontract_codes", + "sub": { + "content": ["14"] + }, + "logo": "https://smartcontract.codes/src/assets/images/logo-1.png", + "image": "./src/node_modules/assets/images/smart-contract-codes.jpg" + }, + "14": { + "id": "14", + "comp": "content", + "title": "Smart contract codes", + "article": "Experimental peer-to peer search engine for smart contract source codes. The project aims to make smart contracts verifiable and more transparent.", + "action": "Learn more", + "url": "https://smartcontract.codes/" + }, + "7": { + "id": "7", + "comp": "supporters", + "title": "Supporters", + "sub": { + "crystal_island": [ + "34", "35", "36", "37", "38" + ] + } + }, + "8": { + "id": "8", + "comp": "our_contributors", + "sub": { + "content": ["15"], + "contributor": [ + "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33" + ] + } + }, + "15": { + "id": "15", + "comp": "content", + "title": "Contributors", + "article": "We're a community of developers researching how to improve and sustain the Web. We're currently exploring the role that peer-to-peer protocols might play in making the Web more open, equitable and playful." + }, + "9": { + "id": "9", + "comp": "footer", + "copyright": " PlayProject", + "icons": [ + { + "id": "1", + "name": "email", + "imgURL": "./src/node_modules/assets/svg/email.svg", + "url": "mailto:ninabreznik@gmail.com" + }, + { + "id": "2", + "name": "twitter", + "imgURL": "./src/node_modules/assets/svg/twitter.svg", + "url": "https://twitter.com/playproject_io" + }, + { + "id": "3", + "name": "Github", + "imgURL": "./src/node_modules/assets/svg/github.svg", + "url": "https://github.com/playproject-io" + }, + { + "id": "4", + "name": "Gitter", + "imgURL": "./src/node_modules/assets/svg/gitter.svg", + "url": "https://gitter.im/playproject-io/community" + } + ] + }, + "16":{ + "id": "16", + "name": "Nina", + "comp": "contributor", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-nina.png" + }, + "17":{ + "id": "17", + "comp": "contributor", + "name": "Serapath", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "" + }, + "css": [{ "id": "contributor.css"},{ "id": "contributor_2.css" }], + "avatar": "./src/node_modules/assets/images/avatar-alex.png" + }, + "18": { + "id": "18", + "name": "Jam", + "comp": "contributor", + "careers": ["Substrate"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-joshua.png" + }, + "19": { + "id": "19", + "name": "Mauve", + "comp": "contributor", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-mauve.png" + }, + "20": { + "id": "20", + "name": "Fiona", + "comp": "contributor", + "careers": ["UI/UX & Frontend"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-fiona.png" + }, + "21": { + "id": "21", + "name": "Toshi", + "comp": "contributor", + "careers": ["UI/UX & Frontend"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-toshi.png" + }, + "22": { + "id": "22", + "name": "Ailin", + "comp": "contributor", + "careers": ["NodeJS & Web3"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-ailin.png" + }, + "23": { + "id": "23", + "name": "Kayla", + "comp": "contributor", + "careers": ["UX/UI"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-kayla.png" + }, + "24": { + "id": "24", + "name": "Tommings", + "comp": "contributor", + "careers": ["Illustration & Design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-tommings.png" + }, + "25": { + "id": "25", + "name": "Santies", + "comp": "contributor", + "careers": ["3D design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-santies.png" + }, + "26": { + "id": "26", + "name": "Pepe", + "comp": "contributor", + "careers": ["Fullstack & 3D"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-pepe.png" + }, + "27": { + "id": "27", + "name": "Jannis", + "comp": "contributor", + "careers": ["NodeJS"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-jannis.png" + }, + "28": { + "id": "28", + "name": "Nora", + "comp": "contributor", + "careers": ["Frontend & Web3"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-nora.png" + }, + "29": { + "id": "29", + "name": "Mimi", + "comp": "contributor", + "careers": ["UI/UX & Frontend"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-mimi.png" + }, + "30": { + "id": "30", + "name": "Helen", + "comp": "contributor", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + "31": { + "id": "31", + "name": "Ali", + "comp": "contributor", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + "32": { + "id": "32", + "name": "Ibrar", + "comp": "contributor", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + "33": { + "id": "33", + "name": "Cypher", + "comp": "contributor", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + "34": { + "id": "34", + "comp": "crystalIsland", + "date": "2015 - today", + "info": "Various private donations & volunteering", + "deco" : ["yellowCrystal", "card", "tree"] + }, + "35": { + "id": "35", + "comp": "crystalIsland", + "date": "2018", + "info": "$48.000 / Ethereum Foundation", + "deco" : ["stone", "card", "tree1"] + }, + "36": { + "id": "36", + "comp": "crystalIsland", + "date": "2020", + "info": "€30.000 / Web3 Foundation", + "deco" : ["purpleCrystal", "card", "tree2"] + }, + "37": { + "id": "37", + "comp": "crystalIsland", + "date": "2022", + "info": "DOT 5530 / Polkadot Treasury Fund", + "deco" : ["blueCrystal", "card", "tree3"] + }, + "38": { + "id": "38", + "comp": "crystalIsland", + "date": "2022", + "info": "DOT 5530 / Polkadot Treasury Fund", + "deco" : ["blueCrystal", "card", "tree3"] + } +} diff --git a/demo/demo.js b/demo/demo.js deleted file mode 100755 index 4086807..0000000 --- a/demo/demo.js +++ /dev/null @@ -1,97 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -const make_page = require('../') -const theme = require('theme') - -const appleTouch = bel`` -const icon32 = bel`` -const icon16 = bel`` -const webmanifest = bel`` -document.head.append(appleTouch, icon32, icon16, webmanifest) - -const params = new URL(location.href).searchParams -const lang = params.get('lang') - -if (lang === 'en') { - params.delete('lang') - location.search = params -} - -const styles = csjs` -html { - font-size: 82.5%; - scroll-behavior: smooth; -} -body { - font-family: var(--bodyFont); - font-size: 1.4rem; - color: var(--bodyColor); - margin: 0; - padding: 0; - background-color: var(--bodyBg); - overflow-x: hidden; -} -a { - text-decoration: none; -} -button { - outline: none; - border: none; - font-family: var(--titleFont); - font-size: var(--sectionButtonSize); - color: var(--titleColor); - border-radius: 2rem; - padding: 1.2rem 3.8rem; - cursor: pointer; -} -img { - width: 100%; - height: auto; -} -article { - font-size: var(--articleSize); - color: var(--articleColor); - line-height: 2.5rem; - padding-bottom: 4rem; -} -@media only screen and (min-width: 2561px) { - article { - font-size: calc(var(--articleSize) * 1.5 ); - line-height: calc(2.5rem * 1.5); - } - button { - font-size: calc(var(--sectionButtonSize) * 1.5 ); -} -} -@media only screen and (min-width: 4096px) { - article { - font-size: calc(var(--articleSize) * 2.25 ); - line-height: calc(2.5rem * 2.25); - } - button { - font-size: calc(var(--sectionButtonSize) * 2.25 ); - } -} -` - -// callback done() -const el = (err, landingPage) => { - const vars = theme - - if (err) { - document.body.style = `color: red; font-size: 1.6rem; text-align:center; background-color: #d9d9d9;` - document.body.innerHTML = `

${err.stack}

` - } else { - document.body.appendChild(landingPage) - } - - updateTheme(vars) -} - -function updateTheme (vars) { - Object.keys(vars).forEach(name => { - document.body.style.setProperty(`--${name}`, vars[name]) - }) -} - -make_page({theme}, el, lang) \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..63bdee9 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,79 @@ +## Data structures +Required keys end with *. + +- ### State Data +``` +id = [{ + id*: int, + name*: string, + type*: string, + xtype: string, + slot*: { + "": [[string, string], ....] + string: [int, ...] + } +}] +``` +#### Example +```JSON +"15": { + "id": 15, + "name": "topnav.json", + "type": "content", + "file": "src/content/topnav.json", + "slot": { + "": [["hubs"]] + "hubs": [4, 14], + } +} +``` +- **Params:** + - `id`: *Number* + - `name`: *string* + The entry name display in graph_exlorer + - `type`: *string* + Used to categorize the entry + - `xtype`: *string* + Used to sub-categorize the entry (this is used in rare cases) + - `file`: *string* + Address of the file which contains data + - `slot`: *object* + Defines relationship and points to related entries + + +- ### Theme Data +This is unique and old maybe. The array indexes are used as IDs. +``` +index = { + theme_name: ['file1.css', 'file2.css', ....] +} +theme_name = ['file1_content', 'file2_content', ....] +``` +#### Example +```JSON +{ + "theme": [ + "div{\n display: none;\n}" + ], + "index": [ + "uniq2" + ] +} +``` + +## Terminologies +### Module +A file that returns a function or multiple functions. +### Instance +A return of data by calling a function provided by a module. +### Node +Used to refer to either Module or Instance in general cases. + +## Graph Explorer +### Entry +An entity in graph explorer which may represent data file, modules or instances in different forms. +### Link +The relation b/w two or more entities that can be of type hub, sub, input or output. Graphically, it is represented by a line. +### Slot +The relation b/w two entities is defined by a slot. Graphically, it is an icon. + diff --git a/doc/state/example/boot.js b/doc/state/example/boot.js new file mode 100644 index 0000000..2d9d05a --- /dev/null +++ b/doc/state/example/boot.js @@ -0,0 +1,16 @@ +const hash = 'a31832ad3cb24fe15ab36bdc73a929f43179d7b8' +const base = 'https://raw.githubusercontent.com/alyhxn/playproject/' +const prefix = base + hash +const init_url = location.hash === '#dev' ? '/src/node_modules/init.js' : prefix + '/src/node_modules/init.js' +const args = arguments + + + +fetch(init_url, { cache: 'no-store' }).then(res => res.text()).then(async source => { + const module = { exports: {} } + const f = new Function('module', 'require', source) + f(module, require) + const init = module.exports + await init(args, prefix) + require('./page') // or whatever is otherwise the main entry of our project +}) diff --git a/doc/state/example/bundle.js b/doc/state/example/bundle.js new file mode 100644 index 0000000..3e3790b --- /dev/null +++ b/doc/state/example/bundle.js @@ -0,0 +1,2448 @@ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i res.text()).then(async source => { + const module = { exports: {} } + const f = new Function('module', 'require', source) + f(module, require) + const init = module.exports + await init(args, prefix) + require('./page') // or whatever is otherwise the main entry of our project +}) + +},{"./page":11}],2:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + PAGE +******************************************************************************/ +const head = require('head') +const foot = require('foot') + +module.exports = app +async function app(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // sub nodes + shadow.append(await head(subs[0]), await foot(subs[1])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module (args) { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { "head": { $: '', }, "foot": { $: '' } } + } + function fallback_instance () { + return { + _: { "head": { 0: '', + mapping: { + 'theme': 'theme', + } + }, "foot": { 0: '', + mapping: { + 'theme': 'theme', + } + } }, + drive: { + 'theme/': { + 'style.css': { + raw: '' + } + } + } + } + } +} + +}).call(this)}).call(this,"/doc/state/example/node_modules/app.js") +},{"../../../../src/node_modules/STATE":12,"foot":5,"head":6}],3:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + BTN +******************************************************************************/ +const icon = require('icon') + +module.exports = {btn, btn_small} +async function btn(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb, io, net } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + const connections = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const title = shadow.querySelector('button > span') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + io.on(port => { + const { by, to } = port + port.onmessage = event => { + const txt = event.data + const key = `[${by} -> ${to}]` + } + }) + // net.event?.click.length && net.event.click.forEach(async msg => { + // connections[msg.id] = { port: await io.at(msg.id), data_index: 0 } + // }) + // button.onclick = () => { + // net.event.click.forEach(msg => { + // const connection = connections[msg.id] + // if(msg.args.length){ + // connection.data_index++ + // connection.data_index %= msg.args.length + // } + // const temp = JSON.parse(JSON.stringify(msg)) + // temp.args = msg.args.length ? msg.args[connection.data_index] : msg.args + // connection.port.postMessage(temp) + + // }) + // } + + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + } +} +async function btn_small(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + button.innerHTML = data.title + } +} + + +function fallback_module () { + return { + api: fallback_instance, + _: { icon: {$: ''} } + } + function fallback_instance () { + return { + _: { icon: {0: ''} }, + drive: { + 'lang/': { + 'en-us.json': { + raw: { + title: 'Click me' + } + } + }, + }, + net: { + api: ['inject', 'fill'], + event: { + click: [], + } + } + } + } +} + +}).call(this)}).call(this,"/doc/state/example/node_modules/btn.js") +},{"../../../../src/node_modules/STATE":12,"icon":7}],4:[function(require,module,exports){ +function fallback_module () { // -> set database defaults or load from database + return { + _: { + "nav": {}, + } + } +} +function fallback_instance () { + return { + _: { + "nav": { + 0: override_nav, + mapping: { + 'style.css': 'style.css' + } + }, + drive: { + 'theme/': 'style.css', + 'style.css': { + raw: '' + } + } + } + } +} +function override_nav ([nav]) { + const data = nav() + console.log(JSON.parse(JSON.stringify(data))) + data.inputs['nav.json'].data.links.push('Page') + return data +} +/****************************************************************************** + FOO +******************************************************************************/ +const nav = require('nav') + +module.exports = foo +async function foo(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const main = shadow.querySelector('nav') + const style = shadow.querySelector('style') + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await nav()) + } + return el +} + +},{"nav":9}],5:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + FOOT +******************************************************************************/ +const text = require('text') + +module.exports = foot +async function foot(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + shadow.prepend(await text(subs[0])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + _:{ text: { $: '' } } + } + function fallback_instance () { + return { + _:{ text: { 0: '' } } } + } +} + +}).call(this)}).call(this,"/doc/state/example/node_modules/foot.js") +},{"../../../../src/node_modules/STATE":12,"text":10}],6:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + HEAD +******************************************************************************/ +const foo = require('foo') + +module.exports = head +async function head(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await foo(subs[0])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { "foo": { $: '' } } + } + function fallback_instance ({ args }) { + return { + _: { "foo": { 0: '', + mapping: { + 'theme': 'theme', + 'lang': 'lang', + } + } }, + drive: { + 'theme/': { + 'style.css': { + raw: '' + } + } + } + } + } +} +}).call(this)}).call(this,"/doc/state/example/node_modules/head.js") +},{"../../../../src/node_modules/STATE":12,"foo":4}],7:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + ICON +******************************************************************************/ +module.exports = icon +async function icon(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + 🗃 + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + } + function fallback_instance () { + return {} + } +} + +}).call(this)}).call(this,"/doc/state/example/node_modules/icon.js") +},{"../../../../src/node_modules/STATE":12}],8:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get, io } = statedb(fallback_module) + +/****************************************************************************** + MENU +******************************************************************************/ +const {btn, btn_small} = require('btn') + + +module.exports = {menu, menu_hover} +async function menu(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb, net, io } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + style: inject, + lang: fill, + } + const { drive } = sdb + io.on(port => { + const { by, to } = port + port.onmessage = event => { + const txt = event.data + const key = `[${by} -> ${to}]` + console.log(key, txt) + } + port.postMessage({type: 'register', args: { + type: 'theme', + name: 'rainbow', + dataset: { + 'page': { + 'style.css': { + raw: `body { font-family: cursive; }`, + } + }, + 'page>app>head>foo>nav:0': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b2d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + }, + + } + }}) + + }) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + + title.onclick = () => { + main.classList.toggle('active') + } + title.onblur = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } +} +async function menu_hover(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = get.hover(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + style: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + title.onmouseover = () => { + main.classList.add('active') + } + title.onmouseout = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } +} + + +function fallback_module () { + const api = fallback_instance + api.hover = fallback_instance_hover + return { + api, + _: { btn: { $: '' }} + } + function fallback_instance () { + return { + _: { + btn: { 0: '' , 1: '', + mapping: { + 'lang': 'lang', + } + }}, + drive: { + 'style/': { + 'theme.css': { + raw: ` + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .title:hover{ + --underline-width: 100% + } + ul{ + background: #273d3d; + list-style: none; + display: none; + position: absolute; + padding: 10px; + box-shadow: 0px 1px 6px 1px gray; + border-radius: 5px; + } + ul.active{ + display: block; + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + title: 'menu', + links: ['link1', 'link2'], + } + }, + }, + } + } + } + function fallback_instance_hover () { + return { + _: { + btn: { 0: '' , 1: '', + mapping: { + 'lang': 'lang', + } + }}, + drive: { + 'style/': { + 'theme.css': { + raw: ` + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .title:hover{ + --underline-width: 100% + } + ul{ + background: #273d3d; + list-style: none; + display: none; + position: absolute; + padding: 10px; + box-shadow: 0px 1px 6px 1px gray; + border-radius: 5px; + } + ul.active{ + display: block; + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + title: 'menu', + links: ['link1', 'link2'], + } + }, + }, + } + } + } +} +}).call(this)}).call(this,"/doc/state/example/node_modules/menu.js") +},{"../../../../src/node_modules/STATE":12,"btn":3}],9:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + NAV +******************************************************************************/ +const {menu, menu_hover} = require('menu') +const {btn, btn_small} = require('btn') + +module.exports = nav +async function nav(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts?.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + + const { drive } = sdb + // console.log(await drive.put('lang/en-uk.json', { links: ['Home', 'About', 'Contact'] })) + // console.log(await drive.get('lang/en-uk.json')) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const main = shadow.querySelector('nav') + const div = shadow.querySelector('div') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + console.log(subs) + { //menu + main.append(await menu(subs[0]), await menu(subs[1]), await menu(subs[2]), await menu_hover(subs[3]), await btn(subs[4]), await btn(subs[5])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + div.replaceChildren(...data.links.map(link => { + const el = document.createElement('div') + el.classList.add('title') + el.innerHTML = link + return el + })) + } +} + + +function fallback_module () { // -> set database defaults or load from database + return { api, _: { 'menu':{ $: menu$ }, btn: { $: btn$ } } } + function api () { + const links = ['Marketing', 'Design', 'Web Dev', 'Ad Compaign'] + const opts_menu = { title: 'Services', links } + const opts_menu_hover = { title: 'Services#hover', links } + return { + _: { + menu: { + 0: opts_menu, + 1: opts_menu, + 2: '', + 3: opts_menu_hover, + mapping: { 'style': 'theme', 'lang': 'lang', 'io': 'io' } + }, + btn: { + 0: 'Register', + 1: 'Switch', + mapping: { 'lang': 'lang' } + } + }, + drive: { + 'theme/': { + 'style.css': { + $ref: 'nav.css' + } + }, + 'lang/': { + 'en-us.json': { + raw: { + links: ['Home', 'About', 'Contact'] + } + } + } + } + } + } + function menu$ (args, tools, [menu]){ + const state = menu() + state.api = api + state.api.hover = api + return state + function api (args, tools, [menu]) { + console.log('menu$ called', args) + const data = menu() + if (args) data.drive['lang/']['en-us.json'].raw = args + return data + } + } + function btn$ (args, tools, [btn]){ + const data = btn() + data.api = api + return data + function api (args, tools, [btn]) { + console.log('btn$ called', args) + const data = btn() + if (args) data.drive['lang/']['en-us.json'].raw.title = args + return data + } + } + +} +}).call(this)}).call(this,"/doc/state/example/node_modules/nav/nav.js") +},{"../../../../../src/node_modules/STATE":12,"btn":3,"menu":8}],10:[function(require,module,exports){ +(function (__filename){(function (){ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + TEXT +******************************************************************************/ +module.exports = text +async function text(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + Copyright © 2024 Playproject Inc. + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + } + function fallback_instance () { + return {} + } +} + +}).call(this)}).call(this,"/doc/state/example/node_modules/text.js") +},{"../../../../src/node_modules/STATE":12}],11:[function(require,module,exports){ +(function (__filename,__dirname){(function (){ +const STATE = require('../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { id, sdb, io } = statedb(fallback_module) + +/****************************************************************************** + PAGE +******************************************************************************/ +const app = require('app') +const sheet = new CSSStyleSheet() +config().then(() => boot({ sid: '' })) + +async function config() { + const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += loadFont + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC +} +/****************************************************************************** + PAGE BOOT +******************************************************************************/ +async function boot(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const on = { + theme: inject, + ...sdb.admin + } + const { drive } = sdb + + const subs = await sdb.watch(onbatch, on) + + io.on(port => { + const { by, to } = port + port.onmessage = event => { + console.log(event.data) + const data = event.data + on[data.type] && on[data.type](data.args) + } + }) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // desktop + shadow.append(await app(subs[0])) + } + // ---------------------------------------- + // INIT + // ---------------------------------------- + + async function onbatch(batch) { + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } +} +async function inject(data) { + sheet.replaceSync(data.join('\n')) +} + +function fallback_module (args, { listfy, tree }) { + listfy(tree) + const rainbow_theme = { + type: 'theme', + name: 'rainbow', + dataset: { + page: { + 'style.css': { + raw: 'body { font-family: cursive; }' + } + }, + 'page>app>head>foo>nav:0': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b2d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + } + + } + } + return { + _: { + app: { $: { x: 0, y: 1 }, 0: app0, mapping: { theme: 'theme' } } + }, + drive: { + 'theme/': { 'style.css': { raw: "body { font-family: 'system-ui'; }" } }, + 'lang/': {} + } + } + function app0 (args, tools, [app]) { + const data = app() + const foonav_ = data._.head.$._['foo>nav'].$._ + foonav_.menu[0] = menu0 + foonav_.btn[0] = btn0 + foonav_.btn[1] = btn1 + + function menu0 (args, tools, [menu, nav$menu]) { + const data = menu() + // console.log(nav$menu([menu])) + data.drive['lang/']['en-us.json'].raw = { + links: ['custom', 'menu'], + title: 'Custom' + } + return data + } + function btn0 (args, tools, [btn, btn1]) { + const data = btn() + // console.log(nav$menu([menu])) + data.drive['lang/']['en-us.json'].raw = { + title: 'Register' + } + data.net.page = { click: { type: 'register', args: rainbow_theme } } + return data + } + function btn1 (args, tools, [btn, btn1]) { + const data = btn() + // console.log(nav$menu([menu])) + data.drive['lang/']['en-us.json'].raw = { + title: 'Switch' + } + data.net.page = { + click: { + type: 'swtch', + args: [ + { type: 'theme', name: 'default' }, + { type: 'theme', name: 'rainbow' } + ] + } + } + return data + } + return data + } +} + +}).call(this)}).call(this,"/doc/state/example/page.js","/doc/state/example") +},{"../../../src/node_modules/STATE":12,"app":2}],12:[function(require,module,exports){ +const localdb = require('localdb') +const io = require('io') + +const db = localdb() +/** Data stored in a entry in db by STATE (Schema): + * id (String): Node Path + * name (String/Optional): Any (To be used in theme_widget) + * type (String): Module Name for module / Module id for instances + * hubs (Array): List of hub-nodes + * subs (Array): List of sub-nodes + * inputs (Array): List of input files + */ +// Constants and initial setup (global level) +const VERSION = 13 +const HELPER_MODULES = ['io', 'localdb', 'STATE'] +const FALLBACK_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#defining-fallbacks' +const FALLBACK_SYNTAX_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#key-descriptions' +const FALLBACK_SUBS_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#shadow-dom-integration' +const status = { + root_module: true, + root_instance: true, + overrides: {}, + tree: {}, + tree_pointers: {}, + modulepaths: {}, + inits: [], + open_branches: {}, + db, + local_statuses: {}, + listeners: {}, + missing_supers: new Set(), + imports: {}, + expected_imports: {}, + used_ids: new Set(), + a2i: {}, + i2a: {}, + s2i: {}, + i2s: {}, + services: {}, + args: {} +} +window.STATEMODULE = status + +// Version check and initialization + +status.fallback_check = Boolean(check_version()) +status.fallback_check && db.add(['playproject_version'], VERSION) + + +// Symbol mappings +let admins = [0] + +// Inner Function +function STATE (address, modulepath, dependencies) { + !status.ROOT_ID && (status.ROOT_ID = modulepath) + status.modulepaths[modulepath] = 0 + //Variables (module-level) + + const local_status = { + name: extract_filename(address), + module_id: modulepath, + deny: {}, + sub_modules: [], + sub_instances: {} + } + status.local_statuses[modulepath] = local_status + return statedb + + function statedb (fallback) { + const data = fallback(status.args[modulepath], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }) + local_status.fallback_instance = data.api + const super_id = modulepath.split(/>(?=[^>]*$)/)[0] + + if(super_id === status.current_node){ + status.expected_imports[super_id].splice(status.expected_imports[super_id].indexOf(modulepath), 1) + } + else if((status?.current_node?.split('>').length || 0) < super_id.split('>').length){ + let temp = super_id + while(temp !== status.current_node && temp.includes('>')){ + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } + } + else{ + let temp = status.current_node + while(temp !== super_id && temp.includes('>')){ + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } + } + + if(data._){ + status.open_branches[modulepath] = Object.values(data._).filter(node => node).length + status.expected_imports[modulepath] = Object.keys(data._) + status.current_node = modulepath + } + + local_status.fallback_module = new Function(`return ${fallback.toString()}`)() + verify_imports(modulepath, dependencies, data) + const updated_status = append_tree_node(modulepath, status) + Object.assign(status.tree_pointers, updated_status.tree_pointers) + Object.assign(status.open_branches, updated_status.open_branches) + status.inits.push(init_module) + + // console.log(Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)) + if(!Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)){ + status.inits.forEach(init => init()) + } + + const sdb = create_statedb_interface(local_status, modulepath, xtype = 'module') + sdb.id = modulepath + status.dataset = sdb.private_api + + const get = init_instance + const extra_fallbacks = Object.entries(local_status.fallback_instance || {}) + extra_fallbacks.length && extra_fallbacks.forEach(([key]) => { + get[key] = (sid) => get(sid, key) + }) + if(!status.a2i[modulepath]){ + status.i2a[status.a2i[modulepath] = encode(modulepath)] = modulepath + } + try { + return { + id: modulepath, + sdb: sdb.public_api, + get: init_instance, + io: io(status.a2i[modulepath], modulepath) + // sub_modules + } + } catch (error) { + throw new Error(`ID: ${modulepath}\n`+error) + } + + } + function append_tree_node (id, status) { + const [super_id, name] = id.split(/>(?=[^>]*$)/) + + if(Object.keys(status.tree).length){ + if(status.tree_pointers[super_id]){ + status.tree_pointers[super_id]._[name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[super_id]._[name].$ + status.open_branches[super_id]-- + } + else{ + let new_name = name + let [new_super_id, temp_name] = super_id.split(/>(?=[^>]*$)/) + + new_name = temp_name + '>' + new_name + + while(!status.tree_pointers[new_super_id]){ + [new_super_id, temp_name] = new_super_id.split(/>(?=[^>]*$)/) + new_name = temp_name + '>' + new_name + } + status.tree_pointers[new_super_id]._[new_name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[new_super_id]._[new_name].$ + if(!status.missing_supers.has(super_id)) + status.open_branches[new_super_id]-- + status.missing_supers.add(super_id) + } + } + else{ + status.tree[id] = { $: { _: {} } } + status.tree_pointers[id] = status.tree[id].$ + } + return status + } + function init_module () { + const {statedata, state_entries, newstatus, updated_local_status} = get_module_data(local_status.fallback_module) + statedata.orphan && (local_status.orphan = true) + //side effects + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + console.log('Main module: ', statedata.id, '\n', state_entries) + updated_local_status && Object.assign(local_status, updated_local_status) + // console.log('Local status: ', local_status.fallback_instance, statedata.api) + const old_fallback = local_status.fallback_instance + + if(local_status.fallback_instance ? local_status.fallback_instance?.toString() === statedata.api?.toString() : false) + local_status.fallback_instance = statedata.api + else + local_status.fallback_instance = (args, tools) => { + return statedata.api(args, tools, [old_fallback]) + } + const extra_fallbacks = Object.entries(old_fallback || {}) + extra_fallbacks.length && extra_fallbacks.forEach(([key, value]) => { + local_status.fallback_instance[key] = (args, tools) => { + console.log('Extra fallback: ', statedata.api[key] ? statedata.api[key] : old_fallback[key]) + return (statedata.api[key] ? statedata.api[key] : old_fallback[key])(args, tools, [value]) + } + }) + db.append(['state'], state_entries) + // add_source_code(statedata.inputs) // @TODO: remove side effect + } + [local_status.sub_modules, symbol2ID, ID2Symbol, address2ID, ID2Address] = symbolfy(statedata, local_status) + Object.assign(status.s2i, symbol2ID) + Object.assign(status.i2s, ID2Symbol) + Object.assign(status.a2i, address2ID) + Object.assign(status.i2a, ID2Address) + + //Setup local data (module level) + if(status.root_module){ + status.root_module = false + statedata.admins && admins.push(...statedata.admins) + } + // @TODO: handle sub_modules when dynamic require is implemented + // const sub_modules = {} + // statedata.subs && statedata.subs.forEach(id => { + // sub_modules[db.read(['state', id]).type] = id + // }) + } + function init_instance (sid, fallback_key) { + const fallback = local_status.fallback_instance[fallback_key] || local_status.fallback_instance + const {statedata, state_entries, newstatus} = get_instance_data(sid, fallback) + + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + Object.assign(status.tree, newstatus.tree) + console.log('Main instance: ', statedata.id, '\n', state_entries) + db.append(['state'], state_entries) + } + [local_status.sub_instances[statedata.id], symbol2ID, ID2Symbol, address2ID, ID2Address] = symbolfy(statedata, local_status) + Object.assign(status.s2i, symbol2ID) + Object.assign(status.i2s, ID2Symbol) + Object.assign(status.a2i, address2ID) + Object.assign(status.i2a, ID2Address) + + const sdb = create_statedb_interface(local_status, statedata.id, xtype = 'instance') + sdb.id = statedata.id + + const sanitized_event = {} + statedata.net && Object.keys(statedata.net).forEach(node => { + statedata.net[node].id = status.a2i[node] || (status.a2i[node] = encode(node)) + }) + return { + id: statedata.id, + net: statedata.net, + sdb: sdb.public_api, + io: io(status.a2i[statedata.id], modulepath) + } + } + function get_module_data (fallback) { + let data = db.read(['state', modulepath]) + console.log(modulepath) + if (status.fallback_check) { + if (data) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: data }) + } + else if (status.root_module) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: {id: modulepath}}) + } + else { + var {sanitized_data, updated_status, updated_local_status} = find_super({ xtype: 'module', fallback, fun_status:status, local_status }) + } + data = sanitized_data.entry + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + updated_local_status + } + } + function get_instance_data (sid, fallback) { + let id = status.s2i[sid] + if(id && (id.split(':')[0] !== modulepath || !id.includes(':'))) + throw new Error(`Access denied! Wrong SID '${id}' used by instance of '${modulepath}'` + FALLBACK_SUBS_POST_ERROR) + if(status.used_ids.has(id)) + throw new Error(`Access denied! SID '${id}' is already used` + FALLBACK_SUBS_POST_ERROR) + + id && status.used_ids.add(id) + let data = id && db.read(['state', id]) + let sanitized_data, updated_status = status + if (status.fallback_check) { + if (!data && !status.root_instance) { + ({sanitized_data, updated_status} = find_super({ xtype: 'instance', fallback, fun_status: status })) + } else { + ({sanitized_data, updated_status} = validate_and_preprocess({ + fun_status: status, + fallback, + xtype: 'instance', + pre_data: data || {id: get_instance_path(modulepath)} + })) + updated_status.root_instance = false + } + data = sanitized_data.entry + } + else if (status.root_instance) { + data = db.read(['state', id || get_instance_path(modulepath)]) + updated_status.tree = JSON.parse(JSON.stringify(status.tree)) + updated_status.root_instance = false + } + + if (!data && local_status.orphan) { + data = db.read(['state', get_instance_path(modulepath)]) + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + } + } + function find_super ({ xtype, fallback, fun_status, local_status }) { + let modulepath_super = modulepath.split(/\>(?=[^>]*$)/)[0] + let modulepath_grand = modulepath_super.split(/\>(?=[^>]*$)/)[0] + if(status.modulepaths[modulepath_super] !== undefined){ + throw new Error(`Node "${modulepath}" is not defined in the fallback of "${modulepath_super}"` + FALLBACK_SUBS_POST_ERROR) + } + const split = modulepath.split('>') + let data + const entries = {} + if(xtype === 'module'){ + let name = split.at(-1) + while(!data && modulepath_grand.includes('>')){ + data = db.read(['state', modulepath_super]) + const split = modulepath_super.split(/\>(?=[^>]*$)/) + modulepath_super = split[0] + name = split[1] + '>' + name + } + console.log(data) + data.path = data.id = modulepath_super + '>' + name + modulepath = modulepath_super + '>' + name + local_status.name = name + + const super_data = db.read(['state', modulepath_super]) + super_data.subs.forEach((sub_id, i) => { + if(sub_id === modulepath_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + else{ + //@TODO: Make the :0 dynamic + let instance_path_super = modulepath_super + ':0' + let temp + while(!data && temp !== modulepath_super){ + data = db.read(['state', instance_path_super]) + temp = modulepath_super + modulepath_grand = modulepath_super = modulepath_super.split(/\>(?=[^>]*$)/)[0] + instance_path_super = modulepath_super + ':0' + } + data.path = data.id = get_instance_path(modulepath) + temp = null + let super_data + let instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + + while(!super_data?.subs && temp !== modulepath_grand){ + super_data = db.read(['state', instance_path_grand]) + temp = modulepath_grand + modulepath_grand = modulepath_grand.split(/\>(?=[^>]*$)/)[0] + instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + } + + super_data.subs.forEach((sub_id, i) => { + if(sub_id === instance_path_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + data.name = split.at(-1) + return { updated_local_status: local_status, + ...validate_and_preprocess({ + fun_status, + fallback, xtype, + pre_data: data, + orphan_check: true, entries }) } + } + function validate_and_preprocess ({ fallback, xtype, pre_data = {}, orphan_check, fun_status, entries }) { + const used_keys = new Set() + let {id: pre_id, hubs: pre_hubs, mapping} = pre_data + let fallback_data + try { + validate(fallback(status.args[pre_id], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }), xtype) + } catch (error) { + throw new Error(`in fallback function of ${pre_id} ${xtype}\n${error.stack}`) + } + if(fun_status.overrides[pre_id]){ + fallback_data = fun_status.overrides[pre_id].fun[0](status.args[pre_id], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }, get_fallbacks({ fallback, modulename: local_status.name, modulepath, instance_path: pre_id })) + console.log('Override used: ', pre_id) + fun_status.overrides[pre_id].by.splice(0, 1) + fun_status.overrides[pre_id].fun.splice(0, 1) + } + else + fallback_data = fallback(status.args[pre_id], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }) + + // console.log('fallback_data: ', fallback) + fun_status.overrides = register_overrides({ overrides: fun_status.overrides, tree: fallback_data, path: modulepath, id: pre_id }) + console.log('overrides: ', Object.keys(fun_status.overrides)) + orphan_check && (fallback_data.orphan = orphan_check) + //This function makes changes in fun_status (side effect) + return { + sanitized_data: sanitize_state({ local_id: '', entry: fallback_data, path: pre_id, xtype, mapping, entries }), + updated_status: fun_status + } + + function sanitize_state ({ local_id, entry, path, hub_entry, local_tree, entries = {}, xtype, mapping, xkey }) { + [path, entry, local_tree] = extract_data({ local_id, entry, path, hub_entry, local_tree, xtype, xkey }) + + entry.id = path + entry.name = entry.name || local_id.split(':')[0] || local_status.name + mapping && (entry.mapping = mapping) + + entries = {...entries, ...sanitize_subs({ local_id, entry, path, local_tree, xtype, mapping })} + delete entry._ + entries[entry.id] = entry + // console.log('Entry: ', entry) + return {entries, entry} + } + function extract_data ({ local_id, entry, path, hub_entry, xtype, xkey }) { + if (local_id) { + entry.hubs = [hub_entry.id] + if (xtype === 'instance') { + let temp_path = path.split(':')[0] + temp_path = temp_path ? temp_path + '>' : temp_path + const module_id = temp_path + local_id + entry.type = module_id + path = module_id + ':' + xkey + temp = Number(xkey)+1 + temp2 = db.read(['state', path]) + while(temp2 || used_keys.has(path)){ + path = module_id + ':' + temp + temp2 = db.read(['state', path]) + temp++ + } + } + else { + entry.type = local_id + path = path ? path + '>' : '' + path = path + local_id + } + } + else { + if (xtype === 'instance') { + entry.type = local_status.module_id + } else { + local_tree = JSON.parse(JSON.stringify(entry)) + // @TODO Handle JS file entry + // console.log('pre_id:', pre_id) + // const file_id = local_status.name + '.js' + // entry.drive || (entry.drive = {}) + // entry.drive[file_id] = { $ref: address } + entry.type = local_status.name + } + pre_hubs && (entry.hubs = pre_hubs) + } + return [path, entry, local_tree] + } + function sanitize_subs ({ local_id, entry, path, local_tree, xtype, mapping }) { + const entries = {} + if (!local_id) { + entry.subs = [] + if(entry._){ + //@TODO refactor when fallback structure improves + Object.entries(entry._).forEach(([local_id, value]) => { + Object.entries(value).forEach(([key, override]) => { + if(key === 'mapping' || (key === '$' && xtype === 'instance')) + return + const sub_instance = sanitize_state({ local_id, entry: value, path, hub_entry: entry, local_tree, xtype: key === '$' ? 'module' : 'instance', mapping: value['mapping'], xkey: key }).entry + entries[sub_instance.id] = JSON.parse(JSON.stringify(sub_instance)) + entry.subs.push(sub_instance.id) + used_keys.add(sub_instance.id) + }) + })} + if (entry.drive) { + // entry.drive.theme && (entry.theme = entry.drive.theme) + // entry.drive.lang && (entry.lang = entry.drive.lang) + entry.inputs = [] + const new_drive = [] + Object.entries(entry.drive).forEach(([dataset_type, dataset]) => { + dataset_type = dataset_type.split('/')[0] + + const new_dataset = { files: [], mapping: {} } + Object.entries(dataset).forEach(([key, value]) => { + const sanitized_file = sanitize_file(key, value, entry, entries) + entries[sanitized_file.id] = sanitized_file + new_dataset.files.push(sanitized_file.id) + }) + new_dataset.id = entry.id + '.' + dataset_type + '.dataset' + new_dataset.type = dataset_type + new_dataset.name = 'default' + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + entries[new_dataset.id] = new_dataset + let check_name = true + entry.inputs.forEach(dataset_id => { + const ds = entries[dataset_id] + if(ds.type === new_dataset.type) + check_name = false + }) + check_name && entry.inputs.push(new_dataset.id) + new_drive.push(new_dataset.id) + + + if(!status.root_module){ + const hub_entry = db.read(['state', entry.hubs[0]]) + console.log(hub_entry, entry) + if(!hub_entry.inputs) + throw new Error(`Node "${hub_entry.id}" has no "drive" defined in its fallback` + FALLBACK_SUBS_POST_ERROR) + if(!mapping?.[dataset_type]) + throw new Error(`No mapping found for dataset "${dataset_type}" of subnode "${entry.id}" in node "${hub_entry.id}"\nTip: Add a mapping prop for "${dataset_type}" dataset in "${hub_entry.id}"'s fallback for "${entry.id}"` + FALLBACK_POST_ERROR) + const mapped_file_type = mapping[dataset_type] + hub_entry.inputs.some(input_id => { + const input = db.read(['state', input_id]) + if(mapped_file_type === input.type){ + input.mapping[entry.id] = new_dataset.id + entries[input_id] = input + return + } + }) + } + }) + entry.drive = new_drive + } + } + return entries + } + function sanitize_file (file_id, file, entry, entries) { + const type = file_id.split('.').at(-1) + + if (!isNaN(Number(file_id))) return file_id + + const raw_id = local_status.name + '.' + type + file.id = raw_id + file.name = file.name || file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [entry.id] + if(file.$ref){ + file.$ref = address.substring(0, address.lastIndexOf("/")) + '/' + file.$ref + } + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + while(entries[file.id]){ + const no = file.id.split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + return file + } + } +} + +// External Function (helper) +function validate (data, xtype) { + /** Expected structure and types + * Sample : "key1|key2:*:type1|type2" + * ":" : separator + * "|" : OR + * "*" : Required key + * */ + const expected_structure = { + 'api::function': () => {}, + '_::object': { + ":*:object|number": { + ":*:function|string|object": '', + "mapping::": {} + } + }, + 'drive::object': { + "::object": { + "::object": { // Required key, any name allowed + "raw|$ref:*:object|string": {}, // data or $ref are names, required, object or string are types + "$ref": "string" + } + }, + }, + 'net::object': {} + } + + validate_shape(data, expected_structure) + + function validate_shape (obj, expected, super_node = 'root', path = '') { + const keys = Object.keys(obj) + const values = Object.values(obj) + let strict = Object.keys(expected).length + + const all_keys = [] + Object.entries(expected).forEach(([expected_key, expected_value]) => { + let [expected_key_names, required, expected_types] = expected_key.split(':') + expected_types = expected_types ? expected_types.split('|') : [typeof(expected_value)] + let absent = true + if(expected_key_names) + expected_key_names.split('|').forEach(expected_key_name => { + const value = obj[expected_key_name] + if(value !== undefined){ + all_keys.push(expected_key_name) + const type = typeof(value) + absent = false + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, expected_key_name, path + '/' + expected_key_name) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${expected_key_name}" at:` + path + FALLBACK_POST_ERROR) + } + }) + else{ + strict = false + values.forEach((value, index) => { + absent = false + const type = typeof(value) + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, keys[index], path + '/' + keys[index]) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${keys[index]}" at: ` + path + FALLBACK_POST_ERROR) + }) + } + if(absent && required){ + if(expected_key_names) + throw new Error(`Can't find required key "${expected_key_names.replace('|', ' or ')}" at: ` + path + FALLBACK_POST_ERROR) + else + throw new Error(`No sub-nodes found for super key "${super_node}" at sub: ` + path + FALLBACK_POST_ERROR) + } + }) + + strict && keys.forEach(key => { + if(!all_keys.includes(key)){ + throw new Error(`Unknown key detected: '${key}' is an unknown property at: ${path || 'root'}` + FALLBACK_POST_ERROR) + } + }) + } +} +function extract_filename (address) { + const parts = address.split('/node_modules/') + const last = parts.at(-1).split('/') + if(last.at(-1) === 'index.js') + return last.at(-2) + return last.at(-1).slice(0, -3) +} +function get_instance_path (modulepath, modulepaths = status.modulepaths) { + return modulepath + ':' + modulepaths[modulepath]++ +} +async function get_input ({ id, name, $ref, type, raw }) { + const xtype = (typeof(id) === "number" ? name : id).split('.').at(-1) + let result = db.read([type, id]) + + if (!result) { + if (raw === undefined){ + let ref_url = $ref + // Patch: Prepend GitHub project name if running on GitHub Pages + if (typeof window !== 'undefined' && window.location.hostname.endsWith('github.io')) { + const path_parts = window.location.pathname.split('/').filter(Boolean) + if (path_parts.length > 0 && !$ref.startsWith('/' + path_parts[0])) { + ref_url = '/' + path_parts[0] + ($ref.startsWith('/') ? '' : '/') + $ref + } + } + const response = await fetch(ref_url) + if (!response.ok) + throw new Error(`Failed to fetch data from '${ref_url}' for '${id}'` + FALLBACK_SYNTAX_POST_ERROR) + else + result = await response[xtype === 'json' ? 'json' : 'text']() + } + else + result = raw + } + return result +} +//Unavoidable side effect +function add_source_code (hubs) { + hubs.forEach(async id => { + const data = db.read(['state', id]) + if (data.type === 'js') { + data.data = await get_input(data) + db.add(['state', data.id], data) + return + } + }) +} +function verify_imports (id, imports, data) { + const state_address = imports.find(imp => imp.includes('STATE')) + HELPER_MODULES.push(state_address) + imports = imports.filter(imp => !HELPER_MODULES.includes(imp)) + if(!data._){ + if(imports.length > 1){ + imports.splice(imports.indexOf(state_address), 1) + throw new Error(`No sub-nodes found for required modules "${imports.join(', ')}" in the fallback of "${status.local_statuses[id].module_id}"` + FALLBACK_POST_ERROR) + } + else return + } + const fallback_imports = Object.keys(data._) + + imports.forEach(imp => { + let check = true + fallback_imports.forEach(fallimp => { + if(imp === fallimp) + check = false + }) + + if(check) + throw new Error('Required module "'+imp+'" is not defined in the fallback of '+status.local_statuses[id].module_id + FALLBACK_POST_ERROR) + }) + + fallback_imports.forEach(fallimp => { + let check = true + imports.forEach(imp => { + if(imp === fallimp) + check = false + }) + + if(check) + throw new Error('Module "'+fallimp+'" defined in the fallback of '+status.local_statuses[id].module_id+' is not required') + }) + +} +function symbolfy (data) { + const i2s = {} + const s2i = {} + const i2a = {} + const a2i = {} + const subs = [] + data.subs && data.subs.forEach(sub => { + const substate = db.read(['state', sub]) + i2a[a2i[sub] = encode(sub)] = sub + s2i[i2s[sub] = Symbol(a2i[sub])] = sub + subs.push({ sid: i2s[sub], type: substate.type }) + }) + return [subs, s2i, i2s, a2i, i2a] +} +function encode(text) { + let code = '' + while (code.length < 10) { + for (let i = 0; i < text.length && code.length < 10; i++) { + code += Math.floor(10 + Math.random() * 90) + } + } + return code +} +function listfy(tree, prefix = '') { + if (!tree) + return [] + + const result = [] + + function walk(current, prefix = '') { + for (const key in current) { + if (key === '$' && current[key]._ && typeof current[key]._ === 'object') { + walk(current[key]._, prefix) + } else { + const path = prefix ? `${prefix}>${key}` : key + result.push(path) + if (current[key]?.$?._ && typeof current[key].$._ === 'object') { + walk(current[key].$._, path) + } + } + } + } + + if (tree._ && typeof tree._ === 'object') { + walk(tree._, prefix) + } + + return result +} +function register_overrides ({overrides, ...args}) { + recurse(args) + return overrides + function recurse ({ tree, path = '', id, xtype = 'instance', local_modulepaths = {} }) { + + tree._ && Object.entries(tree._).forEach(([type, instances]) => { + const sub_path = path + '>' + type + Object.entries(instances).forEach(([id, override]) => { + const resultant_path = id === '$' ? sub_path : sub_path + ':' + id + if(typeof(override) === 'function'){ + if(overrides[resultant_path]){ + overrides[resultant_path].fun.push(override) + overrides[resultant_path].by.push(id) + } + else + overrides[resultant_path] = {fun: [override], by: [id]} + } + else if ( ['object', 'string'].includes(typeof(override)) && id !== 'mapping' && override._ === undefined) + status.args[resultant_path] = structuredClone(override) + else + recurse({ tree: override, path: sub_path, id, xtype, local_modulepaths }) + }) + }) + } +} +function get_fallbacks ({ fallback, modulename, modulepath, instance_path }) { + return [mutated_fallback, ...status.overrides[instance_path].fun] + + function mutated_fallback () { + const data = fallback(status.args[instance_path], { listfy: tree => listfy(tree, modulepath), tree: status.tree_pointers[modulepath] }) + + data.overrider = status.overrides[instance_path].by[0] + merge_trees(data, modulepath) + return data + + function merge_trees (data, path) { + if (data._) { + Object.entries(data._).forEach(([type, data]) => merge_trees(data, path + '>' + type.split('$')[0].replace('.', '>'))) + } else { + data.$ = { _: status.tree_pointers[path]?._ } + } + } + } +} +function check_version () { + if (db.read(['playproject_version']) != VERSION) { + localStorage.clear() + return true + } +} + +// Public Function +function create_statedb_interface (local_status, node_id, xtype) { + const drive = { + get, has, put, list + } + const api = { + public_api: { + watch, get_sub, drive + }, + private_api: { + drive, + xget: (id) => db.read(['state', id]), + get_all: () => db.read_all(['state']), + get_db, + register, + load: (snapshot) => { + localStorage.clear() + Object.entries(snapshot).forEach(([key, value]) => { + db.add([key], JSON.parse(value), true) + }) + window.location.reload() + }, + swtch, + unregister, + status, + } + } + node_id === status.ROOT_ID && (api.public_api.admin = api.private_api) + return api + + async function watch (listener, on) { + if(on) + status.services[node_id] = Object.keys(on) + const data = db.read(['state', node_id]) + if(listener){ + status.listeners[data.id] = listener + await listener(await make_input_map(data.inputs)) + } + return xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + } + function get_sub (type) { + const subs = xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + return subs.filter(sub => sub.type === type) + } + function get_db ({ type: dataset_type, name: dataset_name } = {}) { + const node = db.read(['state', status.ROOT_ID]) + if(dataset_type){ + const dataset_list = [] + node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.type === dataset_type) + dataset_list.push(dataset.name) + }) + if(dataset_name){ + return recurse(status.ROOT_ID, dataset_type) + } + return dataset_list + } + const datasets = [] + node.inputs && node.inputs.forEach(dataset_id => { + datasets.push(db.read(['state', dataset_id]).type) + }) + return datasets + + function recurse (node_id, dataset_type){ + const node_list = [] + const entry = db.read(['state', node_id]) + const temp = entry.mapping ? Object.keys(entry.mapping).find(key => entry.mapping[key] === dataset_type) : null + const mapped_type = temp || dataset_type + entry.drive && entry.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === mapped_type){ + node_list.push(node_id) + return + } + }) + entry.subs && entry.subs.forEach(sub_id => node_list.push(...recurse(sub_id, mapped_type))) + return node_list + } + } + function register ({ type: dataset_type, name: dataset_name, dataset}) { + Object.entries(dataset).forEach(([node_id, files]) => { + const new_dataset = { files: [] } + Object.entries(files).forEach(([file_id, file]) => { + const type = file_id.split('.').at(-1) + + file.id = local_status.name + '.' + type + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [node_id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + db.add(['state', file.id], file) + new_dataset.files.push(file.id) + }) + + const node = db.read(['state', node_id]) + new_dataset.id = node.name + '.' + dataset_type + '.dataset' + new_dataset.name = dataset_name + new_dataset.type = dataset_type + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + db.push(['state', node_id, 'drive'], new_dataset.id) + db.add(['state', new_dataset.id], new_dataset) + }) + console.log(' registered ' + dataset_name + '.' + dataset_type) + } + function unregister ({ type: dataset_type, name: dataset_name } = {}) { + return recurse(status.ROOT_ID) + + function recurse (node_id){ + const node = db.read(['state', node_id]) + node.drive && node.drive.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.drive.splice(node.drive.indexOf(dataset_id), 1) + return true + } + }) + node.inputs && node.inputs.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.inputs.splice(node.inputs.indexOf(dataset_id), 1) + swtch(dataset_type) + return true + } + }) + db.add(['state', node_id], node) + node.subs.forEach(sub_id => recurse(sub_id)) + } + } + function swtch ({ type: dataset_type, name: dataset_name = 'default'}) { + recurse(dataset_type, dataset_name, status.ROOT_ID) + + async function recurse (target_type, target_name, id) { + const node = db.read(['state', id]) + + let target_dataset + node.drive && node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(target_name === dataset.name && target_type === dataset.type){ + target_dataset = dataset + return + } + }) + if(target_dataset){ + node.inputs.forEach((dataset_id, i) => { + const dataset = db.read(['state', dataset_id]) + if(target_type === dataset.type){ + node.inputs.splice(i, 1) + return + } + }) + node.inputs.push(target_dataset.id) + } + db.add(['state', id], node) + status.listeners[id] && status.listeners[id](await make_input_map(node.inputs)) + node.subs && node.subs.forEach(sub_id => { + const subdataset_id = target_dataset?.mapping?.[sub_id] + recurse(target_type, db.read(['state', subdataset_id])?.name || target_name, sub_id) + }) + } + } + + function list (path, id = node_id) { + const node = db.read(['state', id]) + if(!node.drive) + throw new Error(`Node "${id}" has no drive`) + const dataset_names = node.drive.map(dataset_id => { + return dataset_id.split('.').at(-2) + '/' + }) + if (path) { + let index + dataset_names.some((dataset_name, i) => { + if (path.includes(dataset_name)) { + index = i + return true + } + }) + if (index === undefined) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + const dataset = db.read(['state', node.drive[index]]) + return dataset.files.map(fileId => { + const file = db.read(['state', fileId]) + return file.name + }) + } + return dataset_names + } + async function get (path, id = node_id) { + const [dataset_name, file_name] = path.split('/') + const node = db.read(['state', id]) + let dataset + if(!node.drive) + throw new Error(`Node ${node.id} has no drive defined in its fallback` + FALLBACK_POST_ERROR) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(-2)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + + let target_file + for (const file_id of dataset.files) { + const file = db.read(['state', file_id]) + if (file.name === file_name) { + target_file = { id: file.id, name: file.name, type: file.type, raw: await get_input(file)} + break + } + } + if (!target_file) + throw new Error(`File "${path}" not found`) + return target_file + } + async function put (path, buffer, id = node_id) { + const [dataset_name, filename] = path.split('/') + let dataset + const node = db.read(['state', id]) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(-2)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + const type = filename.split('.').pop() + const raw_id = node.name + '.' + type + const file = { + id: raw_id, + name: filename, + type, + raw: buffer + } + for (const file_id of dataset.files) { + const temp_file = db.read(['state', file_id]) + if(file.name === filename){ + file.id = file_id + break + } + } + if(!dataset.files.includes(file.id)){ + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + dataset.files.push(file.id) + db.add(['state', dataset.id], dataset) + } + db.add(['state', file.id], file) + await status.listeners[node.id](await make_input_map(node.inputs)) + + return { id: file.id, name: filename, type, raw: buffer } + } + function has (path) { + const [dataset_name, filename] = path.split('/') + let dataset + const node = db.read(['state', node_id]) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(-2)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + return dataset.files.some(file_id => { + const file = db.read(['state', file_id]) + return file && file.name === filename + }) + } +} +async function make_input_map (inputs) { + const input_map = [] + if (inputs) { + await Promise.all(inputs.map(async input => { + let files = [] + const dataset = db.read(['state', input]) + await Promise.all(dataset.files.map(async file_id => { + const input_state = db.read(['state', file_id]) + files.push(dataset.id.split('.').at(-2) + '/' + input_state.name) + })) + input_map.push({ type: dataset.type, paths: files }) + })) + } + return input_map +} + + +module.exports = STATE +},{"io":13,"localdb":14}],13:[function(require,module,exports){ +const taken = {} + +module.exports = io +function io(seed, alias) { + if (taken[seed]) throw new Error(`seed "${seed}" already taken`) + // const pk = seed.slice(0, seed.length / 2) + // const sk = seed.slice(seed.length / 2, seed.length) + const self = taken[seed] = { id: seed, alias, peer: {} } + const io = { at, on } + return io + + async function at (id, signal = AbortSignal.timeout(1000)) { + if (id === seed) throw new Error('cannot connect to loopback address') + if (!self.online) throw new Error('network must be online') + const peer = taken[id] || {} + // if (self.peer[id] && peer.peer[pk]) { + // self.peer[id].close() || delete self.peer[id] + // peer.peer[pk].close() || delete peer.peer[pk] + // return console.log('disconnect') + // } + // self.peer[id] = peer + if (!peer.online) return wait() // peer with id is offline or doesnt exist + return connect() + function wait () { + const { resolve, reject, promise } = Promise.withResolvers() + signal.onabort = () => reject(`timeout connecting to "${id}"`) + peer.online = { resolve } + return promise.then(connect) + } + function connect () { + signal.onabort = null + const { port1, port2 } = new MessageChannel() + port2.by = port1.to = id + port2.to = port1.by = seed + self.online(self.peer[id] = port1) + peer.online(peer.peer[seed] = port2) + return port1 + } + } + function on (online) { + if (!online) return self.online = null + const resolve = self.online?.resolve + self.online = online + if (resolve) resolve(online) + } +} +},{}],14:[function(require,module,exports){ +/****************************************************************************** + LOCALDB COMPONENT +******************************************************************************/ +module.exports = localdb + +function localdb () { + const prefix = '153/' + return { add, read_all, read, drop, push, length, append, find } + + function length (keys) { + const address = prefix + keys.join('/') + return Object.keys(localStorage).filter(key => key.includes(address)).length + } + /** + * Assigns value to the key of an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function add (keys, value, precheck) { + localStorage[(precheck ? '' : prefix) + keys.join('/')] = JSON.stringify(value) + } + /** + * Appends values into an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function append (keys, data) { + const pre = keys.join('/') + Object.entries(data).forEach(([key, value]) => { + localStorage[prefix + pre+'/'+key] = JSON.stringify(value) + }) + } + /** + * Pushes value to an array already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function push (keys, value) { + const independent_key = keys.slice(0, -1) + const data = JSON.parse(localStorage[prefix + independent_key.join('/')]) + data[keys.at(-1)].push(value) + localStorage[prefix + independent_key.join('/')] = JSON.stringify(data) + } + function read (keys) { + const result = localStorage[prefix + keys.join('/')] + return result && JSON.parse(result) + } + function read_all (keys) { + const address = prefix + keys.join('/') + let result = {} + Object.entries(localStorage).forEach(([key, value]) => { + if(key.includes(address)) + result[key.split('/').at(-1)] = JSON.parse(value) + }) + return result + } + function drop (keys) { + if(keys.length > 1){ + const data = JSON.parse(localStorage[keys[0]]) + let temp = data + keys.slice(1, -1).forEach(key => { + temp = temp[key] + }) + if(Array.isArray(temp)) + temp.splice(keys[keys.length - 1], 1) + else + delete(temp[keys[keys.length - 1]]) + localStorage[keys[0]] = JSON.stringify(data) + } + else + delete(localStorage[keys[0]]) + } + function find (keys, filters, index = 0) { + let index_count = 0 + const address = prefix + keys.join('/') + const target_key = Object.keys(localStorage).find(key => { + if(key.includes(address)){ + const entry = JSON.parse(localStorage[key]) + let count = 0 + Object.entries(filters).some(([search_key, value]) => { + if(entry[search_key] !== value) + return + count++ + }) + if(count === Object.keys(filters).length){ + if(index_count === index) + return key + index_count++ + } + } + }, undefined) + return target_key && JSON.parse(localStorage[target_key]) + } +} +},{}]},{},[1]); diff --git a/doc/state/example/index.html b/doc/state/example/index.html new file mode 100644 index 0000000..cb7c703 --- /dev/null +++ b/doc/state/example/index.html @@ -0,0 +1,12 @@ + + + + + + + Playproject.io + + + + + diff --git a/doc/state/example/node_modules/app.js b/doc/state/example/node_modules/app.js new file mode 100644 index 0000000..31ba834 --- /dev/null +++ b/doc/state/example/node_modules/app.js @@ -0,0 +1,80 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + PAGE +******************************************************************************/ +const head = require('head') +const foot = require('foot') + +module.exports = app +async function app(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // sub nodes + shadow.append(await head(subs[0]), await foot(subs[1])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module (args) { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { "head": { $: '', }, "foot": { $: '' } } + } + function fallback_instance () { + return { + _: { "head": { 0: '', + mapping: { + 'theme': 'theme', + } + }, "foot": { 0: '', + mapping: { + 'theme': 'theme', + } + } }, + drive: { + 'theme/': { + 'style.css': { + raw: '' + } + } + } + } + } +} diff --git a/doc/state/example/node_modules/btn.js b/doc/state/example/node_modules/btn.js new file mode 100644 index 0000000..6c1124b --- /dev/null +++ b/doc/state/example/node_modules/btn.js @@ -0,0 +1,159 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + BTN +******************************************************************************/ +const icon = require('icon') + +module.exports = {btn, btn_small} +async function btn(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb, io, net } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + const connections = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const title = shadow.querySelector('button > span') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + io.on(port => { + const { by, to } = port + port.onmessage = event => { + const txt = event.data + const key = `[${by} -> ${to}]` + } + }) + // net.event?.click.length && net.event.click.forEach(async msg => { + // connections[msg.id] = { port: await io.at(msg.id), data_index: 0 } + // }) + // button.onclick = () => { + // net.event.click.forEach(msg => { + // const connection = connections[msg.id] + // if(msg.args.length){ + // connection.data_index++ + // connection.data_index %= msg.args.length + // } + // const temp = JSON.parse(JSON.stringify(msg)) + // temp.args = msg.args.length ? msg.args[connection.data_index] : msg.args + // connection.port.postMessage(temp) + + // }) + // } + + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + } +} +async function btn_small(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + button.innerHTML = data.title + } +} + + +function fallback_module () { + return { + api: fallback_instance, + _: { icon: {$: ''} } + } + function fallback_instance () { + return { + _: { icon: {0: ''} }, + drive: { + 'lang/': { + 'en-us.json': { + raw: { + title: 'Click me' + } + } + }, + }, + net: { + api: ['inject', 'fill'], + event: { + click: [], + } + } + } + } +} diff --git a/doc/state/example/node_modules/foo.js b/doc/state/example/node_modules/foo.js new file mode 100644 index 0000000..9ae8bb7 --- /dev/null +++ b/doc/state/example/node_modules/foo.js @@ -0,0 +1,59 @@ +function fallback_module () { // -> set database defaults or load from database + return { + _: { + "nav": {}, + } + } +} +function fallback_instance () { + return { + _: { + "nav": { + 0: override_nav, + mapping: { + 'style.css': 'style.css' + } + }, + drive: { + 'theme/': 'style.css', + 'style.css': { + raw: '' + } + } + } + } +} +function override_nav ([nav]) { + const data = nav() + console.log(JSON.parse(JSON.stringify(data))) + data.inputs['nav.json'].data.links.push('Page') + return data +} +/****************************************************************************** + FOO +******************************************************************************/ +const nav = require('nav') + +module.exports = foo +async function foo(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const main = shadow.querySelector('nav') + const style = shadow.querySelector('style') + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await nav()) + } + return el +} diff --git a/doc/state/example/node_modules/foot.js b/doc/state/example/node_modules/foot.js new file mode 100644 index 0000000..b22f357 --- /dev/null +++ b/doc/state/example/node_modules/foot.js @@ -0,0 +1,62 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + FOOT +******************************************************************************/ +const text = require('text') + +module.exports = foot +async function foot(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + shadow.prepend(await text(subs[0])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + _:{ text: { $: '' } } + } + function fallback_instance () { + return { + _:{ text: { 0: '' } } } + } +} diff --git a/doc/state/example/node_modules/head.js b/doc/state/example/node_modules/head.js new file mode 100644 index 0000000..45acf7d --- /dev/null +++ b/doc/state/example/node_modules/head.js @@ -0,0 +1,75 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + HEAD +******************************************************************************/ +const foo = require('foo') + +module.exports = head +async function head(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await foo(subs[0])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { "foo": { $: '' } } + } + function fallback_instance ({ args }) { + return { + _: { "foo": { 0: '', + mapping: { + 'theme': 'theme', + 'lang': 'lang', + } + } }, + drive: { + 'theme/': { + 'style.css': { + raw: '' + } + } + } + } + } +} \ No newline at end of file diff --git a/doc/state/example/node_modules/icon.js b/doc/state/example/node_modules/icon.js new file mode 100644 index 0000000..efd30a2 --- /dev/null +++ b/doc/state/example/node_modules/icon.js @@ -0,0 +1,56 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + ICON +******************************************************************************/ +module.exports = icon +async function icon(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + 🗃 + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + } + function fallback_instance () { + return {} + } +} diff --git a/doc/state/example/node_modules/menu.js b/doc/state/example/node_modules/menu.js new file mode 100644 index 0000000..efb8163 --- /dev/null +++ b/doc/state/example/node_modules/menu.js @@ -0,0 +1,282 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get, io } = statedb(fallback_module) + +/****************************************************************************** + MENU +******************************************************************************/ +const {btn, btn_small} = require('btn') + + +module.exports = {menu, menu_hover} +async function menu(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb, net, io } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + style: inject, + lang: fill, + } + const { drive } = sdb + io.on(port => { + const { by, to } = port + port.onmessage = event => { + const txt = event.data + const key = `[${by} -> ${to}]` + console.log(key, txt) + } + port.postMessage({type: 'register', args: { + type: 'theme', + name: 'rainbow', + dataset: { + 'page': { + 'style.css': { + raw: `body { font-family: cursive; }`, + } + }, + 'page>app>head>foo>nav:0': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b2d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + }, + + } + }}) + + }) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + + title.onclick = () => { + main.classList.toggle('active') + } + title.onblur = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } +} +async function menu_hover(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = get.hover(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + style: inject, + lang: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + title.onmouseover = () => { + main.classList.add('active') + } + title.onmouseout = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } +} + + +function fallback_module () { + const api = fallback_instance + api.hover = fallback_instance_hover + return { + api, + _: { btn: { $: '' }} + } + function fallback_instance () { + return { + _: { + btn: { 0: '' , 1: '', + mapping: { + 'lang': 'lang', + } + }}, + drive: { + 'style/': { + 'theme.css': { + raw: ` + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .title:hover{ + --underline-width: 100% + } + ul{ + background: #273d3d; + list-style: none; + display: none; + position: absolute; + padding: 10px; + box-shadow: 0px 1px 6px 1px gray; + border-radius: 5px; + } + ul.active{ + display: block; + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + title: 'menu', + links: ['link1', 'link2'], + } + }, + }, + } + } + } + function fallback_instance_hover () { + return { + _: { + btn: { 0: '' , 1: '', + mapping: { + 'lang': 'lang', + } + }}, + drive: { + 'style/': { + 'theme.css': { + raw: ` + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .title:hover{ + --underline-width: 100% + } + ul{ + background: #273d3d; + list-style: none; + display: none; + position: absolute; + padding: 10px; + box-shadow: 0px 1px 6px 1px gray; + border-radius: 5px; + } + ul.active{ + display: block; + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + title: 'menu', + links: ['link1', 'link2'], + } + }, + }, + } + } + } +} \ No newline at end of file diff --git a/doc/state/example/node_modules/nav/nav.css b/doc/state/example/node_modules/nav/nav.css new file mode 100644 index 0000000..5386a2b --- /dev/null +++ b/doc/state/example/node_modules/nav/nav.css @@ -0,0 +1,21 @@ +nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b6d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; +} +.title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; +} +.box{ + display: flex; + gap: 20px; +} +.title:hover{ + --underline-width: 100% +} \ No newline at end of file diff --git a/doc/state/example/node_modules/nav/nav.js b/doc/state/example/node_modules/nav/nav.js new file mode 100644 index 0000000..acfecb1 --- /dev/null +++ b/doc/state/example/node_modules/nav/nav.js @@ -0,0 +1,132 @@ +const STATE = require('../../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + NAV +******************************************************************************/ +const {menu, menu_hover} = require('menu') +const {btn, btn_small} = require('btn') + +module.exports = nav +async function nav(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts?.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + + const { drive } = sdb + // console.log(await drive.put('lang/en-uk.json', { links: ['Home', 'About', 'Contact'] })) + // console.log(await drive.get('lang/en-uk.json')) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const main = shadow.querySelector('nav') + const div = shadow.querySelector('div') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + console.log(subs) + { //menu + main.append(await menu(subs[0]), await menu(subs[1]), await menu(subs[2]), await menu_hover(subs[3]), await btn(subs[4]), await btn(subs[5])) + } + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + div.replaceChildren(...data.links.map(link => { + const el = document.createElement('div') + el.classList.add('title') + el.innerHTML = link + return el + })) + } +} + + +function fallback_module () { // -> set database defaults or load from database + return { api, _: { 'menu':{ $: menu$ }, btn: { $: btn$ } } } + function api () { + const links = ['Marketing', 'Design', 'Web Dev', 'Ad Compaign'] + const opts_menu = { title: 'Services', links } + const opts_menu_hover = { title: 'Services#hover', links } + return { + _: { + menu: { + 0: opts_menu, + 1: opts_menu, + 2: '', + 3: opts_menu_hover, + mapping: { 'style': 'theme', 'lang': 'lang', 'io': 'io' } + }, + btn: { + 0: 'Register', + 1: 'Switch', + mapping: { 'lang': 'lang' } + } + }, + drive: { + 'theme/': { + 'style.css': { + $ref: 'nav.css' + } + }, + 'lang/': { + 'en-us.json': { + raw: { + links: ['Home', 'About', 'Contact'] + } + } + } + } + } + } + function menu$ (args, tools, [menu]){ + const state = menu() + state.api = api + state.api.hover = api + return state + function api (args, tools, [menu]) { + console.log('menu$ called', args) + const data = menu() + if (args) data.drive['lang/']['en-us.json'].raw = args + return data + } + } + function btn$ (args, tools, [btn]){ + const data = btn() + data.api = api + return data + function api (args, tools, [btn]) { + console.log('btn$ called', args) + const data = btn() + if (args) data.drive['lang/']['en-us.json'].raw.title = args + return data + } + } + +} \ No newline at end of file diff --git a/doc/state/example/node_modules/nav/package.json b/doc/state/example/node_modules/nav/package.json new file mode 100644 index 0000000..99117be --- /dev/null +++ b/doc/state/example/node_modules/nav/package.json @@ -0,0 +1,3 @@ +{ + "main": "nav.js" +} \ No newline at end of file diff --git a/doc/state/example/node_modules/template.js b/doc/state/example/node_modules/template.js new file mode 100644 index 0000000..3559186 --- /dev/null +++ b/doc/state/example/node_modules/template.js @@ -0,0 +1,83 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +/****************************************************************************** + PAGE +******************************************************************************/ +const sub_module = require('sub_module_address') + +module.exports = module_name +async function module_name(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + shadow.append(await sub_module(subs[0])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + _: { + sub_module_name: { + $: '' + } + }, + drive: { + dataset_name: { + file_name: { + raw: {} + } + } + } + } + function fallback_instance () { + return { + _: { + sub_module_name: { + 0: '' + } + }, + drive: { + dataset_name: { + file_name: { + raw: {} + } + } + } + } + } +} \ No newline at end of file diff --git a/doc/state/example/node_modules/text.js b/doc/state/example/node_modules/text.js new file mode 100644 index 0000000..7cd493c --- /dev/null +++ b/doc/state/example/node_modules/text.js @@ -0,0 +1,56 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, get } = statedb(fallback_module) + +/****************************************************************************** + TEXT +******************************************************************************/ +module.exports = text +async function text(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + const { drive } = sdb + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + Copyright © 2024 Playproject Inc. + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} + + +function fallback_module () { + return { + api: fallback_instance, + } + function fallback_instance () { + return {} + } +} diff --git a/doc/state/example/page.js b/doc/state/example/page.js new file mode 100644 index 0000000..9834d62 --- /dev/null +++ b/doc/state/example/page.js @@ -0,0 +1,172 @@ +const STATE = require('../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { id, sdb, io } = statedb(fallback_module) + +/****************************************************************************** + PAGE +******************************************************************************/ +const app = require('app') +const sheet = new CSSStyleSheet() +config().then(() => boot({ sid: '' })) + +async function config() { + const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += loadFont + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC +} +/****************************************************************************** + PAGE BOOT +******************************************************************************/ +async function boot(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const on = { + theme: inject, + ...sdb.admin + } + const { drive } = sdb + + const subs = await sdb.watch(onbatch, on) + + io.on(port => { + const { by, to } = port + port.onmessage = event => { + console.log(event.data) + const data = event.data + on[data.type] && on[data.type](data.args) + } + }) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // desktop + shadow.append(await app(subs[0])) + } + // ---------------------------------------- + // INIT + // ---------------------------------------- + + async function onbatch(batch) { + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } + } +} +async function inject(data) { + sheet.replaceSync(data.join('\n')) +} + +function fallback_module (args, { listfy, tree }) { + listfy(tree) + const rainbow_theme = { + type: 'theme', + name: 'rainbow', + dataset: { + page: { + 'style.css': { + raw: 'body { font-family: cursive; }' + } + }, + 'page>app>head>foo>nav:0': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b2d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + } + + } + } + return { + _: { + app: { $: { x: 0, y: 1 }, 0: app0, mapping: { theme: 'theme' } } + }, + drive: { + 'theme/': { 'style.css': { raw: "body { font-family: 'system-ui'; }" } }, + 'lang/': {} + } + } + function app0 (args, tools, [app]) { + const data = app() + const foonav_ = data._.head.$._['foo>nav'].$._ + foonav_.menu[0] = menu0 + foonav_.btn[0] = btn0 + foonav_.btn[1] = btn1 + + function menu0 (args, tools, [menu, nav$menu]) { + const data = menu() + // console.log(nav$menu([menu])) + data.drive['lang/']['en-us.json'].raw = { + links: ['custom', 'menu'], + title: 'Custom' + } + return data + } + function btn0 (args, tools, [btn, btn1]) { + const data = btn() + // console.log(nav$menu([menu])) + data.drive['lang/']['en-us.json'].raw = { + title: 'Register' + } + data.net.page = { click: { type: 'register', args: rainbow_theme } } + return data + } + function btn1 (args, tools, [btn, btn1]) { + const data = btn() + // console.log(nav$menu([menu])) + data.drive['lang/']['en-us.json'].raw = { + title: 'Switch' + } + data.net.page = { + click: { + type: 'swtch', + args: [ + { type: 'theme', name: 'default' }, + { type: 'theme', name: 'rainbow' } + ] + } + } + return data + } + return data + } +} diff --git a/doc/state/example2/boot.js b/doc/state/example2/boot.js new file mode 100644 index 0000000..a33ab7c --- /dev/null +++ b/doc/state/example2/boot.js @@ -0,0 +1,42 @@ +patch_cache_in_browser(arguments[4], arguments[5]) + +function patch_cache_in_browser (source_cache, module_cache) { + const meta = { modulepath: ['page'], paths: {} } + for (const key of Object.keys(source_cache)) { + const [module, names] = source_cache[key] + const dependencies = names || {} + source_cache[key][0] = patch(module, dependencies, meta) + } + function patch (module, dependencies, meta) { + const MAP = {} + for (const [name, number] of Object.entries(dependencies)) MAP[name] = number + return (...args) => { + const original = args[0] + require.cache = module_cache + require.resolve = resolve + args[0] = require + return module(...args) + function require (name) { + const identifier = resolve(name) + if (name.endsWith('STATE')) { + const modulepath = meta.modulepath.join('/') + const original_export = require.cache[identifier] || (require.cache[identifier] = original(name)) + const exports = (...args) => original_export(...args, modulepath) + return exports + } else if (require.cache[identifier]) return require.cache[identifier] + else { + const counter = meta.modulepath.concat(name).join('/') + if (!meta.paths[counter]) meta.paths[counter] = 0 + const localid = `${name}${meta.paths[counter] ? '#' + meta.paths[counter] : ''}` + meta.paths[counter]++ + meta.modulepath.push(localid) + } + const exports = require.cache[identifier] = original(name) + if (!name.endsWith('STATE')) meta.modulepath.pop(name) + return exports + } + } + function resolve (name) { return MAP[name] } + } +} +require('./page') // or whatever is otherwise the main entry of our project diff --git a/doc/state/example2/bundle.js b/doc/state/example2/bundle.js new file mode 100644 index 0000000..d37fb50 --- /dev/null +++ b/doc/state/example2/bundle.js @@ -0,0 +1,1882 @@ +(function () { function r (e, n, t) { function o (i, f) { if (!n[i]) { if (!e[i]) { const c = typeof require === 'function' && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); const a = new Error("Cannot find module '" + i + "'"); throw a.code = 'MODULE_NOT_FOUND', a } const p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { const n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = typeof require === 'function' && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({ + 1: [function (require, module, exports) { + patch_cache_in_browser(arguments[4], arguments[5]) + + function patch_cache_in_browser (source_cache, module_cache) { + const meta = { modulepath: ['page'], paths: {} } + for (const key of Object.keys(source_cache)) { + const [module, names] = source_cache[key] + const dependencies = names || {} + source_cache[key][0] = patch(module, dependencies, meta) + } + function patch (module, dependencies, meta) { + const MAP = {} + for (const [name, number] of Object.entries(dependencies)) MAP[name] = number + return (...args) => { + const original = args[0] + require.cache = module_cache + require.resolve = resolve + args[0] = require + return module(...args) + function require (name) { + const identifier = resolve(name) + if (name.endsWith('STATE')) { + const modulepath = meta.modulepath.join('/') + const original_export = require.cache[identifier] || (require.cache[identifier] = original(name)) + const exports = (...args) => original_export(...args, modulepath) + return exports + } else if (require.cache[identifier]) return require.cache[identifier] + else { + const counter = meta.modulepath.concat(name).join('/') + if (!meta.paths[counter]) meta.paths[counter] = 0 + const localid = `${name}${meta.paths[counter] ? '#' + meta.paths[counter] : ''}` + meta.paths[counter]++ + meta.modulepath.push(localid) + } + const exports = require.cache[identifier] = original(name) + if (!name.endsWith('STATE')) meta.modulepath.pop(name) + return exports + } + } + function resolve (name) { return MAP[name] } + } + } + require('./page') // or whatever is otherwise the main entry of our project + }, { './page': 12 }], + 2: [function (require, module, exports) { + const localdb = require('../../../../src/node_modules/localdb') + const db = localdb() + /** Data stored in a entry in db by STATE (Schema): + * id (String): Node Path + * name (String/Optional): Any (To be used in theme_widget) + * type (String): Module Name for module / Module id for instances + * hubs (Array): List of hub-nodes + * subs (Array): List of sub-nodes + * inputs (Array): List of input files + */ + // Constants and initial setup (global level) + const VERSION = 10 + const ROOT_ID = 'page' + + const status = { + root_module: true, + root_instance: true, + overrides: {}, + tree: {}, + tree_pointers: {}, + modulepaths: {}, + inits: [], + open_branches: {}, + db, + local_statuses: {}, + listeners: {} + } + window.STATEMODULE = status + + // Version check and initialization + status.fallback_check = Boolean(check_version()) + status.fallback_check && db.add(['playproject_version'], VERSION) + + // Symbol mappings + const s2i = {} + const i2s = {} + const admins = [0, 'menu'] + + // Inner Function + function STATE (address, modulepath) { + status.modulepaths[modulepath] = 0 + // Variables (module-level) + + const local_status = { + name: extract_filename(address), + module_id: modulepath, + deny: {}, + sub_modules: [], + sub_instances: {} + } + status.local_statuses[modulepath] = local_status + return statedb + + function statedb (fallback) { + const data = fallback() + local_status.fallback_instance = data.api + + if (data._) { status.open_branches[modulepath] = Object.keys(data._).length } + + local_status.fallback_module = new Function(`return ${fallback.toString()}`)() + const updated_status = append_tree_node(modulepath, status) + Object.assign(status.tree_pointers, updated_status.tree_pointers) + Object.assign(status.open_branches, updated_status.open_branches) + status.inits.push(init_module) + + if (!Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)) { status.inits.forEach(init => init()) } + + const sdb = create_statedb_interface(local_status, modulepath, xtype = 'module') + status.dataset = sdb.private_api + return { + id: modulepath, + sdb: sdb.public_api, + subs: [get] + // sub_modules + } + } + function append_tree_node (id, status) { + const [super_id, name] = id.split(/\/(?=[^\/]*$)/) + + if (name) { + if (status.tree_pointers[super_id]) { + status.tree_pointers[super_id]._[name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[super_id]._[name].$ + status.open_branches[super_id]-- + } else { + let temp_name; let new_name = name + let new_super_id = super_id + while (!status.tree_pointers[new_super_id]) { + [new_super_id, temp_name] = new_super_id.split(/\/(?=[^\/]*$)/) + new_name = temp_name + '.' + new_name + } + status.tree_pointers[new_super_id]._[new_name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[new_super_id]._[new_name].$ + status.open_branches[new_super_id]-- + } + } else { + status.tree[id] = { $: { _: {} } } + status.tree_pointers[id] = status.tree[id].$ + } + return status + } + function init_module () { + const { statedata, state_entries, newstatus, updated_local_status } = get_module_data(local_status.fallback_module) + statedata.orphan && (local_status.orphan = true) + // side effects + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + console.log('Main module: ', statedata.name, '\n', state_entries) + updated_local_status && Object.assign(local_status, updated_local_status) + const old_fallback = local_status.fallback_instance + local_status.fallback_instance = () => statedata.api([old_fallback]) + db.append(['state'], state_entries) + // add_source_code(statedata.inputs) // @TODO: remove side effect + } + + [local_status.sub_modules, symbol2ID, ID2Symbol] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + + // Setup local data (module level) + if (status.root_module) { + status.root_module = false + statedata.admins && admins.push(...statedata.admins) + } + // @TODO: handle sub_modules when dynamic require is implemented + // const sub_modules = {} + // statedata.subs && statedata.subs.forEach(id => { + // sub_modules[db.read(['state', id]).type] = id + // }) + } + function get (sid) { + const { statedata, state_entries, newstatus } = get_instance_data(sid) + + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + Object.assign(status.tree, newstatus.tree) + console.log('Main instance: ', statedata.name, '\n', state_entries) + db.append(['state'], state_entries) + } + [local_status.sub_instances[statedata.id], symbol2ID, ID2Symbol] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + const sdb = create_statedb_interface(local_status, statedata.id, xtype = 'instance') + return { + id: statedata.id, + sdb: sdb.public_api + } + } + function get_module_data (fallback) { + let data = db.read(['state', modulepath]) + + if (status.fallback_check) { + if (data) { + var { sanitized_data, updated_status } = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: data }) + } else if (status.root_module) { + var { sanitized_data, updated_status } = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: { id: modulepath } }) + } else { + var { sanitized_data, updated_status, updated_local_status } = find_super({ xtype: 'module', fallback, fun_status: status, local_status }) + } + data = sanitized_data.entry + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + updated_local_status + } + } + function get_instance_data (sid) { + const id = s2i[sid] + let data = id && db.read(['state', id]) + let sanitized_data; let updated_status = status + if (status.fallback_check) { + if (!data && !status.root_instance) { + ({ sanitized_data, updated_status } = find_super({ xtype: 'instance', fallback: local_status.fallback_instance, fun_status: status })) + } else { + ({ sanitized_data, updated_status } = validate_and_preprocess({ + fun_status: status, + fallback: local_status.fallback_instance, + xtype: 'instance', + pre_data: data || { id: get_instance_path(modulepath) } + })) + updated_status.root_instance = false + } + data = sanitized_data.entry + } else if (status.root_instance) { + data = db.read(['state', id || get_instance_path(modulepath)]) + updated_status.tree = JSON.parse(JSON.stringify(status.tree)) + updated_status.root_instance = false + } + + if (!data && local_status.orphan) { + data = db.read(['state', get_instance_path(modulepath)]) + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status + } + } + function find_super ({ xtype, fallback, fun_status, local_status }) { + const modulepath_super = modulepath.split(/\/(?=[^\/]*$)/)[0] + const modulepath_grand = modulepath_super.split(/\/(?=[^\/]*$)/)[0] + const split = modulepath.split('/') + const name = split.at(-2) + '.' + split.at(-1) + let data + const entries = {} + if (xtype === 'module') { + data = db.read(['state', modulepath_super]) + console.log(modulepath_super) + data.path = data.id = modulepath + local_status.name = name + + const super_data = db.read(['state', modulepath_grand]) + console.log(modulepath_grand) + super_data.subs.forEach((sub_id, i) => { + if (sub_id === modulepath_super) { + super_data.subs.splice(i, 1) + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } else { + // @TODO: Make the :0 dynamic + const instance_path_super = modulepath_super + ':0' + data = db.read(['state', instance_path_super]) + data.path = data.id = get_instance_path(modulepath) + + const super_data = db.read(['state', modulepath_grand + ':0']) + super_data.subs.forEach((sub_id, i) => { + if (sub_id === instance_path_super) { + super_data.subs.splice(i, 1) + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + data.name = split.at(-1) + return { + updated_local_status: local_status, + ...validate_and_preprocess({ + fun_status, + fallback, + xtype, + pre_data: data, + orphan_check: true, + entries + }) + } + } + function validate_and_preprocess ({ fallback, xtype, pre_data = {}, orphan_check, fun_status, entries }) { + const { id: pre_id, hubs: pre_hubs, mapping } = pre_data + let fallback_data + try { + validate(fallback(), xtype) + } catch (error) { + throw new Error(`Fallback function of ${pre_id} ${xtype}\n${error.stack}`) + } + if (fun_status.overrides[pre_id]) { + fallback_data = fun_status.overrides[pre_id].fun[0]([fallback]) + fun_status.overrides[pre_id].by.splice(0, 1) + fun_status.overrides[pre_id].fun.splice(0, 1) + } else { fallback_data = fallback() } + + // console.log('fallback_data: ', fallback_data) + fun_status.overrides = register_overrides({ overrides: fun_status.overrides, tree: fallback_data, path: modulepath, id: pre_id }) + console.log('overrides: ', Object.keys(fun_status.overrides)) + orphan_check && (fallback_data.orphan = orphan_check) + // This function makes changes in fun_status (side effect) + return { + sanitized_data: sanitize_state({ local_id: '', entry: fallback_data, path: pre_id, xtype, mapping, entries }), + updated_status: fun_status + } + + function sanitize_state ({ local_id, entry, path, hub_entry, local_tree, entries = {}, xtype, mapping }) { + [path, entry, local_tree] = extract_data({ local_id, entry, path, hub_entry, local_tree, xtype }) + + entry.id = path + entry.name = entry.name || local_id.split(':')[0] || local_status.name + mapping && (entry.mapping = mapping) + + entries = { ...entries, ...sanitize_subs({ local_id, entry, path, local_tree, xtype, mapping }) } + + delete entry._ + entries[entry.id] = entry + // console.log('Entry: ', entry) + return { entries, entry } + } + function extract_data ({ local_id, entry, path, hub_entry, xtype }) { + if (local_id) { + entry.hubs = [hub_entry.id] + if (xtype === 'instance') { + let temp_path = path.split(':')[0] + temp_path = temp_path ? temp_path + '/' : temp_path + const module_id = temp_path + local_id + entry.type = module_id + path = module_id + ':' + (status.modulepaths[module_id]++ || 0) + } else { + entry.type = local_id + path = path ? path + '/' : '' + path = path + local_id + } + } else { + if (xtype === 'instance') { + entry.type = local_status.module_id + } else { + local_tree = JSON.parse(JSON.stringify(entry)) + // @TODO Handle JS file entry + // console.log('pre_id:', pre_id) + // const file_id = local_status.name + '.js' + // entry.drive || (entry.drive = {}) + // entry.drive[file_id] = { $ref: address } + entry.type = local_status.name + } + pre_hubs && (entry.hubs = pre_hubs) + } + return [path, entry, local_tree] + } + function sanitize_subs ({ local_id, entry, path, local_tree, xtype, mapping }) { + const entries = {} + if (!local_id) { + entry.subs = [] + if (entry._) { + // @TODO refactor when fallback structure improves + Object.entries(entry._).forEach(([local_id, value]) => { + Object.entries(value).forEach(([key, override]) => { + if (key === 'mapping' || typeof (override) === 'object') { return } + const sub_instance = sanitize_state({ local_id, entry: value, path, hub_entry: entry, local_tree, xtype: key === '$' ? 'module' : 'instance', mapping: value.mapping }).entry + entries[sub_instance.id] = JSON.parse(JSON.stringify(sub_instance)) + entry.subs.push(sub_instance.id) + }) + }) + } + if (entry.drive) { + // entry.drive.theme && (entry.theme = entry.drive.theme) + // entry.drive.lang && (entry.lang = entry.drive.lang) + entry.inputs = [] + const new_drive = [] + Object.entries(entry.drive).forEach(([dataset_type, dataset]) => { + dataset_type = dataset_type.split('/')[0] + const new_dataset = { files: [], mapping: {} } + Object.entries(dataset).forEach(([key, value]) => { + const sanitized_file = sanitize_file(key, value, entry, entries) + entries[sanitized_file.id] = sanitized_file + new_dataset.files.push(sanitized_file.id) + }) + new_dataset.id = local_status.name + '.' + dataset_type + '.dataset' + new_dataset.type = dataset_type + new_dataset.name = 'default' + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + entries[new_dataset.id] = new_dataset + let check_name = true + entry.inputs.forEach(dataset_id => { + const ds = entries[dataset_id] + if (ds.type === new_dataset.type) { check_name = false } + }) + check_name && entry.inputs.push(new_dataset.id) + new_drive.push(new_dataset.id) + + if (!status.root_module) { + const hub_entry = db.read(['state', entry.hubs[0]]) + const mapped_file_type = mapping?.[dataset_type] || dataset_type + hub_entry.inputs && hub_entry.inputs.forEach(input_id => { + const input = db.read(['state', input_id]) + if (mapped_file_type === input.type) { + input.mapping[entry.id] = new_dataset.id + entries[input_id] = input + } + }) + } + }) + entry.drive = new_drive + } + } + return entries + } + function sanitize_file (file_id, file, entry, entries) { + const type = file_id.split('.').at(-1) + + if (!isNaN(Number(file_id))) return file_id + + file.id = local_status.name + '.' + type + file.name = file.name || file.id + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [entry.id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + while (entries[file.id]) { + const no = file.id.split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + return file + } + } + } + + // External Function (helper) + function validate (data, xtype) { + /** Expected structure and types + * Sample : "key1|key2:*:type1|type2" + * ":" : separator + * "|" : OR + * "*" : Required key + * + * */ + const expected_structure = { + '_::object': { + ':*:object': xtype === 'module' ? { + '$:*:function|string': '' + } : { // Required key, any name allowed + ':*:function|string': () => {} // Optional key + } + }, + 'drive::object': { + '::object': { + '::object': { // Required key, any name allowed + 'raw|link:*:object|string': {}, // data or link are names, required, object or string are types + link: 'string' + } + } + } + } + + validate_shape(data, expected_structure) + + function validate_shape (obj, expected, super_node = 'root', path = '') { + const keys = Object.keys(obj) + const values = Object.values(obj) + + Object.entries(expected).forEach(([expected_key, expected_value]) => { + let [expected_key_names, required, expected_types] = expected_key.split(':') + expected_types = expected_types ? expected_types.split('|') : [typeof (expected_value)] + let absent = true + if (expected_key_names) { + expected_key_names.split('|').forEach(expected_key_name => { + const value = obj[expected_key_name] + if (value !== undefined) { + const type = typeof (value) + absent = false + + if (expected_types.includes(type)) { type === 'object' && validate_shape(value, expected_value, expected_key_name, path + '/' + expected_key_name) } else { throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${expected_key_name}" at:` + path) } + } + }) + } else { + values.forEach((value, index) => { + absent = false + const type = typeof (value) + + if (expected_types.includes(type)) { type === 'object' && validate_shape(value, expected_value, keys[index], path + '/' + keys[index]) } else { throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${keys[index]}" at: ` + path) } + }) + } + if (absent && required) { + if (expected_key_names) { throw new Error(`Can't find required key "${expected_key_names.replace('|', ' or ')}" at: ` + path) } else { throw new Error(`No subnodes found for super key "${super_node}" at sub: ` + path) } + } + }) + } + } + function extract_filename (address) { + const parts = address.split('/node_modules/') + const last = parts.at(-1).split('/') + return last.at(-1).slice(0, -3) + } + function get_instance_path (modulepath, modulepaths = status.modulepaths) { + return modulepath + ':' + modulepaths[modulepath]++ + } + async function get_input ({ id, name, $ref, type, raw }) { + const xtype = (typeof (id) === 'number' ? name : id).split('.').at(-1) + let result = db.read([type, id]) + + if (!result) { + result = raw !== undefined ? raw : await ((await fetch($ref))[xtype === 'json' ? 'json' : 'text']()) + } + return result + } + // Unavoidable side effect + function add_source_code (hubs) { + hubs.forEach(async id => { + const data = db.read(['state', id]) + if (data.type === 'js') { + data.data = await get_input(data) + db.add(['state', data.id], data) + } + }) + } + function symbolfy (data) { + const s2i = {} + const i2s = {} + const subs = [] + data.subs && data.subs.forEach(sub => { + const substate = db.read(['state', sub]) + s2i[i2s[sub] = Symbol(sub)] = sub + subs.push({ sid: i2s[sub], type: substate.type }) + }) + return [subs, s2i, i2s] + } + function register_overrides ({ overrides, ...args }) { + recurse(args) + return overrides + function recurse ({ tree, path = '', id, xtype = 'instance', local_modulepaths = {} }) { + tree._ && Object.entries(tree._).forEach(([type, instances]) => { + const sub_path = path + '/' + type.replace('.', '/') + Object.entries(instances).forEach(([id, override]) => { + if (typeof (override) === 'function') { + const resultant_path = id === '$' ? sub_path : sub_path + ':' + id + if (overrides[resultant_path]) { + overrides[resultant_path].fun.push(override) + overrides[resultant_path].by.push(id) + } else { overrides[resultant_path] = { fun: [override], by: [id] } } + } else { + recurse({ tree: override, path: sub_path, id, xtype, local_modulepaths }) + } + }) + }) + } + } + function check_version () { + if (db.read(['playproject_version']) != VERSION) { + localStorage.clear() + return true + } + } + + // Public Function + function create_statedb_interface (local_status, node_id, xtype) { + const api = { + public_api: { + watch, get_sub, req_access + }, + private_api: { + get, register, swtch, unregister + } + } + api.public_api.admin = node_id === ROOT_ID && api.private_api + return api + + async function watch (listener) { + const data = db.read(['state', node_id]) + if (listener) { + status.listeners[data.id] = listener + listener(await make_input_map(data.inputs)) + } + return xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + } + function get_sub (type) { + return local_status.subs.filter(sub => { + const dad = db.read(['state', sub.type]) + return dad.type === type + }) + } + function req_access (sid) { + if (local_status.deny[sid]) throw new Error('access denied') + const el = db.read(['state', s2i[sid]]) + if (admins.includes(s2i[sid]) || admins.includes(el?.name)) { + return { + xget: (id) => db.read(['state', id]), + get_all: () => db.read_all(['state']), + add_admins: (ids) => { admins.push(...ids) }, + get, + register, + load: (snapshot) => { + localStorage.clear() + Object.entries(snapshot).forEach(([key, value]) => { + db.add([key], JSON.parse(value), true) + }) + window.location.reload() + }, + swtch + } + } + } + function get (dataset_type, dataset_name) { + const node = db.read(['state', ROOT_ID]) + if (dataset_type) { + const dataset_list = [] + node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.type === dataset_type) { dataset_list.push(dataset.name) } + }) + if (dataset_name) { + return recurse(ROOT_ID, dataset_type) + } + return dataset_list + } + const datasets = [] + node.inputs && node.inputs.forEach(dataset_id => { + datasets.push(db.read(['state', dataset_id]).type) + }) + return datasets + + function recurse (node_id, dataset_type) { + const node_list = [] + const entry = db.read(['state', node_id]) + const temp = entry.mapping ? Object.keys(entry.mapping).find(key => entry.mapping[key] === dataset_type) : null + const mapped_type = temp || dataset_type + entry.drive && entry.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.name === dataset_name && dataset.type === mapped_type) { + node_list.push(node_id) + } + }) + entry.subs && entry.subs.forEach(sub_id => node_list.push(...recurse(sub_id, mapped_type))) + return node_list + } + } + function register (dataset_type, dataset_name, dataset) { + Object.entries(dataset).forEach(([node_id, files]) => { + const new_dataset = { files: [] } + Object.entries(files).forEach(([file_id, file]) => { + const type = file_id.split('.').at(-1) + + file.id = local_status.name + '.' + type + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [node_id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + db.add(['state', file.id], file) + new_dataset.files.push(file.id) + }) + + const node = db.read(['state', node_id]) + new_dataset.id = node.name + '.' + dataset_type + '.dataset' + new_dataset.name = dataset_name + new_dataset.type = dataset_type + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + db.push(['state', node_id, 'drive'], new_dataset.id) + db.add(['state', new_dataset.id], new_dataset) + }) + return ' registered ' + dataset_name + '.' + dataset_type + } + function unregister (dataset_type, dataset_name) { + return recurse(ROOT_ID) + + function recurse (node_id) { + const node = db.read(['state', node_id]) + node.drive && node.drive.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.name === dataset_name && dataset.type === dataset_type) { + node.drive.splice(node.drive.indexOf(dataset_id), 1) + return true + } + }) + node.inputs && node.inputs.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.name === dataset_name && dataset.type === dataset_type) { + node.inputs.splice(node.inputs.indexOf(dataset_id), 1) + swtch(dataset_type) + return true + } + }) + db.add(['state', node_id], node) + node.subs.forEach(sub_id => recurse(sub_id)) + } + } + function swtch (dataset_type, dataset_name = 'default') { + recurse(dataset_type, dataset_name, ROOT_ID) + + async function recurse (target_type, target_name, id) { + const node = db.read(['state', id]) + + let target_dataset + node.drive && node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (target_name === dataset.name && target_type === dataset.type) { + target_dataset = dataset + } + }) + if (target_dataset) { + node.inputs.forEach((dataset_id, i) => { + const dataset = db.read(['state', dataset_id]) + if (target_type === dataset.type) { + node.inputs.splice(i, 1) + } + }) + node.inputs.push(target_dataset.id) + } + db.add(['state', id], node) + status.listeners[id] && status.listeners[id](await make_input_map(node.inputs)) + node.subs && node.subs.forEach(sub_id => { + const subdataset_id = target_dataset?.mapping?.[sub_id] + recurse(target_type, db.read(['state', subdataset_id])?.name || target_name, sub_id) + }) + } + } + } + async function make_input_map (inputs) { + const input_map = [] + if (inputs) { + await Promise.all(inputs.map(async input => { + const files = [] + const dataset = db.read(['state', input]) + await Promise.all(dataset.files.map(async file_id => { + const input_state = db.read(['state', file_id]) + files.push(await get_input(input_state)) + })) + input_map.push({ type: dataset.type, data: files }) + })) + } + return input_map + } + + module.exports = STATE + }, { '../../../../src/node_modules/localdb': 13 }], + 3: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { + head: { + $: '' + }, + foot: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + head: { + 0: '' + }, + foot: { + 0: '' + } + } + } + } + } + + /****************************************************************************** + PAGE +******************************************************************************/ + const head = require('head') + const foot = require('foot') + + module.exports = app + async function app (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await head(subs[0]), await foot(subs[1])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/app.js') + }, { STATE: 2, foot: 6, head: 7 }], + 4: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { + return { + api: fallback_instance, + _: { + icon: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + icon: { + 0: '' + } + }, + drive: { + 'lang/': { + 'en-us.json': { + raw: { + title: 'Click me' + } + } + } + } + } + } + } + /****************************************************************************** + BTN +******************************************************************************/ + delete require.cache[require.resolve('icon')] + const icon = require('icon') + + module.exports = { btn, btn_small } + async function btn (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + button.append(data.title) + } + } + async function btn_small (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + button.innerHTML = data.title + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/btn.js') + }, { STATE: 2, icon: 8 }], + 5: [function (require, module, exports) { + function fallback_module () { // -> set database defaults or load from database + return { + _: { + nav: {}, + 'nav#1': {} + } + } + } + function fallback_instance () { + return { + _: { + nav: { + 0: override_nav + }, + 'nav#1': {} + } + } + } + function override_nav ([nav]) { + const data = nav() + console.log(JSON.parse(JSON.stringify(data))) + data.inputs['nav.json'].data.links.push('Page') + return data + } + /****************************************************************************** + FOO +******************************************************************************/ + const nav = require('nav') + + module.exports = foo + async function foo (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const main = shadow.querySelector('nav') + const style = shadow.querySelector('style') + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await nav()) + } + return el + } + }, { nav: 10 }], + 6: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { + return { + api: fallback_instance, + _: { + text: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + text: { + 0: '' + } + } + } + } + } + /****************************************************************************** + FOOT +******************************************************************************/ + const text = require('text') + + module.exports = foot + async function foot (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + shadow.prepend(await text(subs[0])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/foot.js') + }, { STATE: 2, text: 11 }], + 7: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { + foo: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + foo: { + 0: '' + } + } + } + } + } + /****************************************************************************** + HEAD +******************************************************************************/ + const foo = require('foo') + + module.exports = head + async function head (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await foo(subs[0])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/head.js') + }, { STATE: 2, foo: 5 }], + 8: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { + return { + api: fallback_instance + } + function fallback_instance () { + return {} + } + } + /****************************************************************************** + ICON +******************************************************************************/ + module.exports = icon + async function icon (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + 🗃 + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/icon.js') + }, { STATE: 2 }], + 9: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { + return { + api: fallback_instance, + _: { + btn: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + btn: { + 0: '' + }, + btn$small: { + 0: '' + } + }, + drive: { + 'style/': { + 'theme.css': { + raw: ` + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .title:hover{ + --underline-width: 100% + } + ul{ + background: #273d3d; + list-style: none; + display: none; + position: absolute; + padding: 10px; + box-shadow: 0px 1px 6px 1px gray; + border-radius: 5px; + } + ul.active{ + display: block; + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + title: 'menu', + links: ['link1', 'link2'] + } + } + } + } + } + } + } + /****************************************************************************** + MENU +******************************************************************************/ + delete require.cache[require.resolve('btn')] + const { btn, btn_small } = require('btn') + + module.exports = { menu, menu_hover } + async function menu (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const admin = sdb.req_access(opts.sid) + const on = { + style: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + title.onclick = () => { + main.classList.toggle('active') + admin.register('theme', 'rainbow', { + page: { + 'style.css': { + raw: 'body { font-family: cursive; }' + } + }, + 'page/app/head/foo/nav:0': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b2d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + } + + }) + } + title.onblur = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } + } + async function menu_hover (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + style: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + title.onmouseover = () => { + main.classList.add('active') + } + title.onmouseout = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/menu.js') + }, { STATE: 2, btn: 4 }], + 10: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { + menu: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + menu: { + 0: override_menu + }, + menu$hover: { + 0: override_menu_hover + } + }, + drive: { + 'theme/': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b6d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + links: ['Home', 'About', 'Contact'] + } + } + } + } + } + } + function override_menu ([menu], path) { + const data = menu() + data.drive['lang/']['en-us.json'].raw = { + title: 'Services', + links: ['Marketing', 'Design', 'Web Dev', 'Ad Compaign'] + } + return data + } + function override_menu_hover ([menu], path) { + const data = menu() + data.drive['lang/']['en-us.json'].raw = { + title: 'Services#hover', + links: ['Marketing', 'Design', 'Web Dev', 'Ad Compaign'] + } + return data + } + } + /****************************************************************************** + NAV +******************************************************************************/ + delete require.cache[require.resolve('menu')] + const { menu, menu_hover } = require('menu') + + module.exports = nav + async function nav (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts?.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const main = shadow.querySelector('nav') + const div = shadow.querySelector('div') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // menu + main.append(await menu(subs[0]), await menu_hover(subs[1])) + } + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + div.replaceChildren(...data.links.map(link => { + const el = document.createElement('div') + el.classList.add('title') + el.innerHTML = link + return el + })) + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/nav.js') + }, { STATE: 2, menu: 9 }], + 11: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + function fallback_module () { + return { + api: fallback_instance + } + function fallback_instance () { + return {} + } + } + /****************************************************************************** + TEXT +******************************************************************************/ + module.exports = text + async function text (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + Copyright © 2024 Playproject Inc. + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } + async function inject (data) { + style.innerHTML = data.join('\n') + } + async function fill ([data]) { + } + } + }).call(this) + }).call(this, '/doc/state/example2/node_modules/text.js') + }, { STATE: 2 }], + 12: [function (require, module, exports) { + (function (__filename, __dirname) { + (function () { + const STATE = require('STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + function fallback_module () { // -> set database defaults or load from database + return { + _: { + app: { + $: '', + 0: override_app + } + }, + drive: { + 'theme/': { + 'style.css': { + raw: 'body { font-family: \'system-ui\'; }' + } + }, + 'lang/': { + + } + } + } + function override_app ([app]) { + const data = app() + console.log(JSON.parse(JSON.stringify(data._.head))) + data._.head[0] = page$head_override + return data + } + function page$head_override ([head]) { + const data = head() + data._['foo.nav'] = { + 0: page$nav_override + } + return data + } + function page$foo_override ([foo]) { + const data = foo() + data._.nav[0] = page$nav_override + return data + } + function page$nav_override ([nav]) { + const data = nav() + data._.menu[0] = page$menu_override + return data + } + function page$menu_override ([menu]) { + const data = menu() + console.log(data) + data.drive['lang/']['en-us.json'].raw = { + links: ['custom', 'menu'], + title: 'Custom' + } + return data + } + } + /****************************************************************************** + PAGE +******************************************************************************/ + const app = require('app') + const sheet = new CSSStyleSheet() + config().then(() => boot({ })) + + async function config () { + const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const appleTouch = '' + const icon32 = '' + const icon16 = '' + const webmanifest = '' + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += appleTouch + icon16 + icon32 + webmanifest + loadFont + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC + } + /****************************************************************************** + PAGE BOOT +******************************************************************************/ + async function boot () { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const on = { + theme: inject + } + const subs = await sdb.watch(onbatch) + const status = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // desktop + shadow.append(await app(subs[0])) + } + // ---------------------------------------- + // INIT + // ---------------------------------------- + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + } + async function inject (data) { + sheet.replaceSync(data.join('\n')) + } + }).call(this) + }).call(this, '/doc/state/example2/page.js', '/doc/state/example2') + }, { STATE: 2, app: 3 }], + 13: [function (require, module, exports) { + /****************************************************************************** + LOCALDB COMPONENT +******************************************************************************/ + module.exports = localdb + + function localdb () { + const prefix = '153/' + return { add, read_all, read, drop, push, length, append, find } + + function length (keys) { + const address = prefix + keys.join('/') + return Object.keys(localStorage).filter(key => key.includes(address)).length + } + /** + * Assigns value to the key of an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function add (keys, value, precheck) { + localStorage[(precheck ? '' : prefix) + keys.join('/')] = JSON.stringify(value) + } + /** + * Appends values into an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function append (keys, data) { + const pre = keys.join('/') + Object.entries(data).forEach(([key, value]) => { + localStorage[prefix + pre + '/' + key] = JSON.stringify(value) + }) + } + /** + * Pushes value to an array already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function push (keys, value) { + const independent_key = keys.slice(0, -1) + const data = JSON.parse(localStorage[prefix + independent_key.join('/')]) + data[keys.at(-1)].push(value) + localStorage[prefix + independent_key.join('/')] = JSON.stringify(data) + } + function read (keys) { + const result = localStorage[prefix + keys.join('/')] + return result && JSON.parse(result) + } + function read_all (keys) { + const address = prefix + keys.join('/') + const result = {} + Object.entries(localStorage).forEach(([key, value]) => { + if (key.includes(address)) { result[key.split('/').at(-1)] = JSON.parse(value) } + }) + return result + } + function drop (keys) { + if (keys.length > 1) { + const data = JSON.parse(localStorage[keys[0]]) + let temp = data + keys.slice(1, -1).forEach(key => { + temp = temp[key] + }) + if (Array.isArray(temp)) { temp.splice(keys[keys.length - 1], 1) } else { delete (temp[keys[keys.length - 1]]) } + localStorage[keys[0]] = JSON.stringify(data) + } else { delete (localStorage[keys[0]]) } + } + function find (keys, filters, index = 0) { + let index_count = 0 + const address = prefix + keys.join('/') + const target_key = Object.keys(localStorage).find(key => { + if (key.includes(address)) { + const entry = JSON.parse(localStorage[key]) + let count = 0 + Object.entries(filters).some(([search_key, value]) => { + if (entry[search_key] !== value) { return } + count++ + }) + if (count === Object.keys(filters).length) { + if (index_count === index) { return key } + index_count++ + } + } + }, undefined) + return target_key && JSON.parse(localStorage[target_key]) + } + } + }, {}] +}, {}, [1]) diff --git a/doc/state/example2/index.html b/doc/state/example2/index.html new file mode 100644 index 0000000..cb7c703 --- /dev/null +++ b/doc/state/example2/index.html @@ -0,0 +1,12 @@ + + + + + + + Playproject.io + + + + + diff --git a/doc/state/example2/node_modules/STATE.js b/doc/state/example2/node_modules/STATE.js new file mode 100644 index 0000000..a8e6f13 --- /dev/null +++ b/doc/state/example2/node_modules/STATE.js @@ -0,0 +1,755 @@ +const localdb = require('../../../../src/node_modules/localdb') +const db = localdb() +/** Data stored in a entry in db by STATE (Schema): + * id (String): Node Path + * name (String/Optional): Any (To be used in theme_widget) + * type (String): Module Name for module / Module id for instances + * hubs (Array): List of hub-nodes + * subs (Array): List of sub-nodes + * inputs (Array): List of input files + */ +// Constants and initial setup (global level) +const VERSION = 10 +const ROOT_ID = 'page' + +const status = { + root_module: true, + root_instance: true, + overrides: {}, + tree: {}, + tree_pointers: {}, + modulepaths: {}, + inits: [], + open_branches: {}, + db, + local_statuses: {}, + listeners: {}, +} +window.STATEMODULE = status + +// Version check and initialization +status.fallback_check = Boolean(check_version()) +status.fallback_check && db.add(['playproject_version'], VERSION) + + +// Symbol mappings +const s2i = {} +const i2s = {} +let admins = [0, 'menu'] + +// Inner Function +function STATE (address, modulepath) { + status.modulepaths[modulepath] = 0 + //Variables (module-level) + + const local_status = { + name: extract_filename(address), + module_id: modulepath, + deny: {}, + sub_modules: [], + sub_instances: {} + } + status.local_statuses[modulepath] = local_status + return statedb + + function statedb (fallback) { + const data = fallback() + local_status.fallback_instance = data.api + + if(data._) + status.open_branches[modulepath] = Object.keys(data._).length + + local_status.fallback_module = new Function(`return ${fallback.toString()}`)() + const updated_status = append_tree_node(modulepath, status) + Object.assign(status.tree_pointers, updated_status.tree_pointers) + Object.assign(status.open_branches, updated_status.open_branches) + status.inits.push(init_module) + + if(!Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)) + status.inits.forEach(init => init()) + + const sdb = create_statedb_interface(local_status, modulepath, xtype = 'module') + status.dataset = sdb.private_api + return { + id: modulepath, + sdb: sdb.public_api, + subs: [get], + // sub_modules + } + } + function append_tree_node (id, status) { + const [super_id, name] = id.split(/\/(?=[^\/]*$)/) + + if(name){ + if(status.tree_pointers[super_id]){ + status.tree_pointers[super_id]._[name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[super_id]._[name].$ + status.open_branches[super_id]-- + } + else{ + let temp_name, new_name = name + let new_super_id = super_id + while(!status.tree_pointers[new_super_id]){ + [new_super_id, temp_name] = new_super_id.split(/\/(?=[^\/]*$)/) + new_name = temp_name + '.' + new_name + } + status.tree_pointers[new_super_id]._[new_name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[new_super_id]._[new_name].$ + status.open_branches[new_super_id]-- + } + } + else{ + status.tree[id] = { $: { _: {} } } + status.tree_pointers[id] = status.tree[id].$ + } + return status + } + function init_module () { + const {statedata, state_entries, newstatus, updated_local_status} = get_module_data(local_status.fallback_module) + statedata.orphan && (local_status.orphan = true) + //side effects + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + console.log('Main module: ', statedata.name, '\n', state_entries) + updated_local_status && Object.assign(local_status, updated_local_status) + const old_fallback = local_status.fallback_instance + local_status.fallback_instance = () => statedata.api([old_fallback]) + db.append(['state'], state_entries) + // add_source_code(statedata.inputs) // @TODO: remove side effect + } + + [local_status.sub_modules, symbol2ID, ID2Symbol] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + + //Setup local data (module level) + if(status.root_module){ + status.root_module = false + statedata.admins && admins.push(...statedata.admins) + } + // @TODO: handle sub_modules when dynamic require is implemented + // const sub_modules = {} + // statedata.subs && statedata.subs.forEach(id => { + // sub_modules[db.read(['state', id]).type] = id + // }) + } + function get (sid) { + const {statedata, state_entries, newstatus} = get_instance_data(sid) + + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + Object.assign(status.tree, newstatus.tree) + console.log('Main instance: ', statedata.name, '\n', state_entries) + db.append(['state'], state_entries) + } + [local_status.sub_instances[statedata.id], symbol2ID, ID2Symbol] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + const sdb = create_statedb_interface(local_status, statedata.id, xtype = 'instance') + return { + id: statedata.id, + sdb: sdb.public_api, + } + } + function get_module_data (fallback) { + let data = db.read(['state', modulepath]) + + if (status.fallback_check) { + if (data) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: data }) + } + else if (status.root_module) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: {id: modulepath}}) + } + else { + var {sanitized_data, updated_status, updated_local_status} = find_super({ xtype: 'module', fallback, fun_status:status, local_status }) + } + data = sanitized_data.entry + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + updated_local_status + } + } + function get_instance_data (sid) { + let id = s2i[sid] + let data = id && db.read(['state', id]) + let sanitized_data, updated_status = status + if (status.fallback_check) { + if (!data && !status.root_instance) { + ({sanitized_data, updated_status} = find_super({ xtype: 'instance', fallback: local_status.fallback_instance, fun_status: status })) + } else { + ({sanitized_data, updated_status} = validate_and_preprocess({ + fun_status: status, + fallback: local_status.fallback_instance, + xtype: 'instance', + pre_data: data || {id: get_instance_path(modulepath)} + })) + updated_status.root_instance = false + } + data = sanitized_data.entry + } + else if (status.root_instance) { + data = db.read(['state', id || get_instance_path(modulepath)]) + updated_status.tree = JSON.parse(JSON.stringify(status.tree)) + updated_status.root_instance = false + } + + if (!data && local_status.orphan) { + data = db.read(['state', get_instance_path(modulepath)]) + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + } + } + function find_super ({ xtype, fallback, fun_status, local_status }) { + const modulepath_super = modulepath.split(/\/(?=[^\/]*$)/)[0] + const modulepath_grand = modulepath_super.split(/\/(?=[^\/]*$)/)[0] + const split = modulepath.split('/') + const name = split.at(-2) + '.' + split.at(-1) + let data + const entries = {} + if(xtype === 'module'){ + data = db.read(['state', modulepath_super]) + console.log(modulepath_super) + data.path = data.id = modulepath + local_status.name = name + + const super_data = db.read(['state', modulepath_grand]) + console.log(modulepath_grand) + super_data.subs.forEach((sub_id, i) => { + if(sub_id === modulepath_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + else{ + //@TODO: Make the :0 dynamic + const instance_path_super = modulepath_super + ':0' + data = db.read(['state', instance_path_super]) + data.path = data.id = get_instance_path(modulepath) + + + const super_data = db.read(['state', modulepath_grand + ':0']) + super_data.subs.forEach((sub_id, i) => { + if(sub_id === instance_path_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + data.name = split.at(-1) + return { updated_local_status: local_status, + ...validate_and_preprocess({ + fun_status, + fallback, xtype, + pre_data: data, + orphan_check: true, entries }) } + } + function validate_and_preprocess ({ fallback, xtype, pre_data = {}, orphan_check, fun_status, entries }) { + let {id: pre_id, hubs: pre_hubs, mapping} = pre_data + let fallback_data + try { + validate(fallback(), xtype) + } catch (error) { + throw new Error(`Fallback function of ${pre_id} ${xtype}\n${error.stack}`); + } + if(fun_status.overrides[pre_id]){ + fallback_data = fun_status.overrides[pre_id].fun[0]([fallback]) + fun_status.overrides[pre_id].by.splice(0, 1) + fun_status.overrides[pre_id].fun.splice(0, 1) + } + else + fallback_data = fallback() + + // console.log('fallback_data: ', fallback_data) + fun_status.overrides = register_overrides({ overrides: fun_status.overrides, tree: fallback_data, path: modulepath, id: pre_id }) + console.log('overrides: ', Object.keys(fun_status.overrides)) + orphan_check && (fallback_data.orphan = orphan_check) + //This function makes changes in fun_status (side effect) + return { + sanitized_data: sanitize_state({ local_id: '', entry: fallback_data, path: pre_id, xtype, mapping, entries }), + updated_status: fun_status + } + + function sanitize_state ({ local_id, entry, path, hub_entry, local_tree, entries = {}, xtype, mapping }) { + [path, entry, local_tree] = extract_data({ local_id, entry, path, hub_entry, local_tree, xtype }) + + entry.id = path + entry.name = entry.name || local_id.split(':')[0] || local_status.name + mapping && (entry.mapping = mapping) + + entries = {...entries, ...sanitize_subs({ local_id, entry, path, local_tree, xtype, mapping })} + + delete entry._ + entries[entry.id] = entry + // console.log('Entry: ', entry) + return {entries, entry} + } + function extract_data ({ local_id, entry, path, hub_entry, xtype }) { + if (local_id) { + entry.hubs = [hub_entry.id] + if (xtype === 'instance') { + let temp_path = path.split(':')[0] + temp_path = temp_path ? temp_path + '/' : temp_path + const module_id = temp_path + local_id + entry.type = module_id + path = module_id + ':' + (status.modulepaths[module_id]++ || 0) + } + else { + entry.type = local_id + path = path ? path + '/' : '' + path = path + local_id + } + } + else { + if (xtype === 'instance') { + entry.type = local_status.module_id + } else { + local_tree = JSON.parse(JSON.stringify(entry)) + // @TODO Handle JS file entry + // console.log('pre_id:', pre_id) + // const file_id = local_status.name + '.js' + // entry.drive || (entry.drive = {}) + // entry.drive[file_id] = { $ref: address } + entry.type = local_status.name + } + pre_hubs && (entry.hubs = pre_hubs) + } + return [path, entry, local_tree] + } + function sanitize_subs ({ local_id, entry, path, local_tree, xtype, mapping }) { + const entries = {} + if (!local_id) { + entry.subs = [] + if(entry._){ + //@TODO refactor when fallback structure improves + Object.entries(entry._).forEach(([local_id, value]) => { + Object.entries(value).forEach(([key, override]) => { + if(key === 'mapping' || typeof(override) === 'object') + return + const sub_instance = sanitize_state({ local_id, entry: value, path, hub_entry: entry, local_tree, xtype: key === '$' ? 'module' : 'instance', mapping: value['mapping'] }).entry + entries[sub_instance.id] = JSON.parse(JSON.stringify(sub_instance)) + entry.subs.push(sub_instance.id) + }) + })} + if (entry.drive) { + // entry.drive.theme && (entry.theme = entry.drive.theme) + // entry.drive.lang && (entry.lang = entry.drive.lang) + entry.inputs = [] + const new_drive = [] + Object.entries(entry.drive).forEach(([dataset_type, dataset]) => { + dataset_type = dataset_type.split('/')[0] + const new_dataset = { files: [], mapping: {} } + Object.entries(dataset).forEach(([key, value]) => { + const sanitized_file = sanitize_file(key, value, entry, entries) + entries[sanitized_file.id] = sanitized_file + new_dataset.files.push(sanitized_file.id) + }) + new_dataset.id = local_status.name + '.' + dataset_type + '.dataset' + new_dataset.type = dataset_type + new_dataset.name = 'default' + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + entries[new_dataset.id] = new_dataset + let check_name = true + entry.inputs.forEach(dataset_id => { + const ds = entries[dataset_id] + if(ds.type === new_dataset.type) + check_name = false + }) + check_name && entry.inputs.push(new_dataset.id) + new_drive.push(new_dataset.id) + + if(!status.root_module){ + const hub_entry = db.read(['state', entry.hubs[0]]) + const mapped_file_type = mapping?.[dataset_type] || dataset_type + hub_entry.inputs && hub_entry.inputs.forEach(input_id => { + const input = db.read(['state', input_id]) + if(mapped_file_type === input.type){ + input.mapping[entry.id] = new_dataset.id + entries[input_id] = input + return + } + }) + } + }) + entry.drive = new_drive + } + } + return entries + } + function sanitize_file (file_id, file, entry, entries) { + const type = file_id.split('.').at(-1) + + if (!isNaN(Number(file_id))) return file_id + + + file.id = local_status.name + '.' + type + file.name = file.name || file.id + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [entry.id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + while(entries[file.id]){ + const no = file.id.split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + return file + } + } +} + +// External Function (helper) +function validate (data, xtype) { + /** Expected structure and types + * Sample : "key1|key2:*:type1|type2" + * ":" : separator + * "|" : OR + * "*" : Required key + * + * */ + const expected_structure = { + '_::object': { + ":*:object": xtype === 'module' ? { + "$:*:function|string": '' + } : { // Required key, any name allowed + ":*:function|string": () => {}, // Optional key + }, + }, + 'drive::object': { + "::object": { + "::object": { // Required key, any name allowed + "raw|link:*:object|string": {}, // data or link are names, required, object or string are types + "link": "string" + } + }, + }, + } + + + validate_shape(data, expected_structure) + + function validate_shape (obj, expected, super_node = 'root', path = '') { + const keys = Object.keys(obj) + const values = Object.values(obj) + + Object.entries(expected).forEach(([expected_key, expected_value]) => { + let [expected_key_names, required, expected_types] = expected_key.split(':') + expected_types = expected_types ? expected_types.split('|') : [typeof(expected_value)] + let absent = true + if(expected_key_names) + expected_key_names.split('|').forEach(expected_key_name => { + const value = obj[expected_key_name] + if(value !== undefined){ + const type = typeof(value) + absent = false + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, expected_key_name, path + '/' + expected_key_name) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${expected_key_name}" at:` + path) + } + }) + else{ + values.forEach((value, index) => { + absent = false + const type = typeof(value) + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, keys[index], path + '/' + keys[index]) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${keys[index]}" at: ` + path) + }) + } + if(absent && required){ + if(expected_key_names) + throw new Error(`Can't find required key "${expected_key_names.replace('|', ' or ')}" at: ` + path) + else + throw new Error(`No subnodes found for super key "${super_node}" at sub: ` + path) + } + }) + } +} +function extract_filename (address) { + const parts = address.split('/node_modules/') + const last = parts.at(-1).split('/') + return last.at(-1).slice(0, -3) +} +function get_instance_path (modulepath, modulepaths = status.modulepaths) { + return modulepath + ':' + modulepaths[modulepath]++ +} +async function get_input ({ id, name, $ref, type, raw }) { + const xtype = (typeof(id) === "number" ? name : id).split('.').at(-1) + let result = db.read([type, id]) + + if (!result) { + result = raw !== undefined ? raw : await((await fetch($ref))[xtype === 'json' ? 'json' : 'text']()) + } + return result +} +//Unavoidable side effect +function add_source_code (hubs) { + hubs.forEach(async id => { + const data = db.read(['state', id]) + if (data.type === 'js') { + data.data = await get_input(data) + db.add(['state', data.id], data) + return + } + }) +} +function symbolfy (data) { + const s2i = {} + const i2s = {} + const subs = [] + data.subs && data.subs.forEach(sub => { + const substate = db.read(['state', sub]) + s2i[i2s[sub] = Symbol(sub)] = sub + subs.push({ sid: i2s[sub], type: substate.type }) + }) + return [subs, s2i, i2s] +} +function register_overrides ({overrides, ...args}) { + recurse(args) + return overrides + function recurse ({ tree, path = '', id, xtype = 'instance', local_modulepaths = {} }) { + + tree._ && Object.entries(tree._).forEach(([type, instances]) => { + const sub_path = path + '/' + type.replace('.', '/') + Object.entries(instances).forEach(([id, override]) => { + if(typeof(override) === 'function'){ + let resultant_path = id === '$' ? sub_path : sub_path + ':' + id + if(overrides[resultant_path]){ + overrides[resultant_path].fun.push(override) + overrides[resultant_path].by.push(id) + } + else + overrides[resultant_path] = {fun: [override], by: [id]} + } + else{ + recurse({ tree: override, path: sub_path, id, xtype, local_modulepaths }) + } + }) + }) + } +} +function check_version () { + if (db.read(['playproject_version']) != VERSION) { + localStorage.clear() + return true + } +} + +// Public Function +function create_statedb_interface (local_status, node_id, xtype) { + const api = { + public_api: { + watch, get_sub, req_access + }, + private_api: { + get, register, swtch, unregister + } + } + api.public_api.admin = node_id === ROOT_ID && api.private_api + return api + + async function watch (listener) { + const data = db.read(['state', node_id]) + if(listener){ + status.listeners[data.id] = listener + listener(await make_input_map(data.inputs)) + } + return xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + } + function get_sub (type) { + return local_status.subs.filter(sub => { + const dad = db.read(['state', sub.type]) + return dad.type === type + }) + } + function req_access (sid) { + if (local_status.deny[sid]) throw new Error('access denied') + const el = db.read(['state', s2i[sid]]) + if (admins.includes(s2i[sid]) || admins.includes(el?.name)) { + return { + xget: (id) => db.read(['state', id]), + get_all: () => db.read_all(['state']), + add_admins: (ids) => { admins.push(...ids) }, + get, + register, + load: (snapshot) => { + localStorage.clear() + Object.entries(snapshot).forEach(([key, value]) => { + db.add([key], JSON.parse(value), true) + }) + window.location.reload() + }, + swtch + } + } + } + function get (dataset_type, dataset_name) { + const node = db.read(['state', ROOT_ID]) + if(dataset_type){ + const dataset_list = [] + node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.type === dataset_type) + dataset_list.push(dataset.name) + }) + if(dataset_name){ + return recurse(ROOT_ID, dataset_type) + } + return dataset_list + } + const datasets = [] + node.inputs && node.inputs.forEach(dataset_id => { + datasets.push(db.read(['state', dataset_id]).type) + }) + return datasets + + function recurse (node_id, dataset_type){ + const node_list = [] + const entry = db.read(['state', node_id]) + const temp = entry.mapping ? Object.keys(entry.mapping).find(key => entry.mapping[key] === dataset_type) : null + const mapped_type = temp || dataset_type + entry.drive && entry.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === mapped_type){ + node_list.push(node_id) + return + } + }) + entry.subs && entry.subs.forEach(sub_id => node_list.push(...recurse(sub_id, mapped_type))) + return node_list + } + } + function register (dataset_type, dataset_name, dataset) { + Object.entries(dataset).forEach(([node_id, files]) => { + const new_dataset = { files: [] } + Object.entries(files).forEach(([file_id, file]) => { + const type = file_id.split('.').at(-1) + + file.id = local_status.name + '.' + type + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [node_id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + db.add(['state', file.id], file) + new_dataset.files.push(file.id) + }) + + const node = db.read(['state', node_id]) + new_dataset.id = node.name + '.' + dataset_type + '.dataset' + new_dataset.name = dataset_name + new_dataset.type = dataset_type + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + db.push(['state', node_id, 'drive'], new_dataset.id) + db.add(['state', new_dataset.id], new_dataset) + }) + return ' registered ' + dataset_name + '.' + dataset_type + } + function unregister (dataset_type, dataset_name) { + return recurse(ROOT_ID) + + function recurse (node_id){ + const node = db.read(['state', node_id]) + node.drive && node.drive.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.drive.splice(node.drive.indexOf(dataset_id), 1) + return true + } + }) + node.inputs && node.inputs.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.inputs.splice(node.inputs.indexOf(dataset_id), 1) + swtch(dataset_type) + return true + } + }) + db.add(['state', node_id], node) + node.subs.forEach(sub_id => recurse(sub_id)) + } + } + function swtch (dataset_type, dataset_name = 'default') { + recurse(dataset_type, dataset_name, ROOT_ID) + + async function recurse (target_type, target_name, id) { + const node = db.read(['state', id]) + + let target_dataset + node.drive && node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(target_name === dataset.name && target_type === dataset.type){ + target_dataset = dataset + return + } + }) + if(target_dataset){ + node.inputs.forEach((dataset_id, i) => { + const dataset = db.read(['state', dataset_id]) + if(target_type === dataset.type){ + node.inputs.splice(i, 1) + return + } + }) + node.inputs.push(target_dataset.id) + } + db.add(['state', id], node) + status.listeners[id] && status.listeners[id](await make_input_map(node.inputs)) + node.subs && node.subs.forEach(sub_id => { + const subdataset_id = target_dataset?.mapping?.[sub_id] + recurse(target_type, db.read(['state', subdataset_id])?.name || target_name, sub_id) + }) + } + } +} +async function make_input_map (inputs) { + const input_map = [] + if (inputs) { + await Promise.all(inputs.map(async input => { + let files = [] + const dataset = db.read(['state', input]) + await Promise.all(dataset.files.map(async file_id => { + const input_state = db.read(['state', file_id]) + files.push(await get_input(input_state)) + })) + input_map.push({ type: dataset.type, data: files }) + })) + } + return input_map +} + + +module.exports = STATE \ No newline at end of file diff --git a/doc/state/example2/node_modules/app.js b/doc/state/example2/node_modules/app.js new file mode 100644 index 0000000..ed854d0 --- /dev/null +++ b/doc/state/example2/node_modules/app.js @@ -0,0 +1,75 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { + "head": { + $: '' + }, + "foot": { + $: '' + }, + } + } + function fallback_instance () { + return { + _: { + "head": { + 0: '' + }, + "foot": { + 0: '' + }, + } + } + } +} + +/****************************************************************************** + PAGE +******************************************************************************/ +const head = require('head') +const foot = require('foot') + +module.exports = app +async function app(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await head(subs[0]), await foot(subs[1])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} diff --git a/doc/state/example2/node_modules/btn.js b/doc/state/example2/node_modules/btn.js new file mode 100644 index 0000000..8e75d95 --- /dev/null +++ b/doc/state/example2/node_modules/btn.js @@ -0,0 +1,125 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { + return { + api: fallback_instance, + _: { + icon: { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + icon: { + 0: '' + } + }, + drive: { + 'lang/': { + 'en-us.json': { + raw: { + title: 'Click me' + } + } + } + } + } + } +} +/****************************************************************************** + BTN +******************************************************************************/ +delete require.cache[require.resolve('icon')] +const icon = require('icon') + +module.exports = {btn, btn_small} +async function btn(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + button.append(data.title) + } +} +async function btn_small(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const style = shadow.querySelector('style') + const button = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + button.append(await icon(subs[0])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + button.innerHTML = data.title + } +} diff --git a/doc/state/example2/node_modules/foo.js b/doc/state/example2/node_modules/foo.js new file mode 100644 index 0000000..14b8ec5 --- /dev/null +++ b/doc/state/example2/node_modules/foo.js @@ -0,0 +1,52 @@ +function fallback_module () { // -> set database defaults or load from database + return { + _: { + "nav": {}, + "nav#1": {} + } + } +} +function fallback_instance () { + return { + _: { + "nav": { + 0: override_nav + }, + "nav#1": {}, + } + } +} +function override_nav ([nav]) { + const data = nav() + console.log(JSON.parse(JSON.stringify(data))) + data.inputs['nav.json'].data.links.push('Page') + return data +} +/****************************************************************************** + FOO +******************************************************************************/ +const nav = require('nav') + +module.exports = foo +async function foo(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const main = shadow.querySelector('nav') + const style = shadow.querySelector('style') + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await nav()) + } + return el +} diff --git a/doc/state/example2/node_modules/foot.js b/doc/state/example2/node_modules/foot.js new file mode 100644 index 0000000..fa17515 --- /dev/null +++ b/doc/state/example2/node_modules/foot.js @@ -0,0 +1,67 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { + return { + api: fallback_instance, + _:{ + text: { + $: '' + } + } + } + function fallback_instance () { + return { + _:{ + text: { + 0: '' + } + } + } + } +} +/****************************************************************************** + FOOT +******************************************************************************/ +const text = require('text') + +module.exports = foot +async function foot(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { + shadow.prepend(await text(subs[0])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} diff --git a/doc/state/example2/node_modules/head.js b/doc/state/example2/node_modules/head.js new file mode 100644 index 0000000..472d2c7 --- /dev/null +++ b/doc/state/example2/node_modules/head.js @@ -0,0 +1,67 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { + "foo": { + $: '' + } + } + } + function fallback_instance () { + return { + _: { + "foo": { + 0: '' + }, + } + } + } +} +/****************************************************************************** + HEAD +******************************************************************************/ +const foo = require('foo') + +module.exports = head +async function head(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // nav + shadow.append(await foo(subs[0])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} diff --git a/doc/state/example2/node_modules/icon.js b/doc/state/example2/node_modules/icon.js new file mode 100644 index 0000000..8ec2808 --- /dev/null +++ b/doc/state/example2/node_modules/icon.js @@ -0,0 +1,52 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { + return { + api: fallback_instance, + } + function fallback_instance () { + return {} + } +} +/****************************************************************************** + ICON +******************************************************************************/ +module.exports = icon +async function icon(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + 🗃 + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} diff --git a/doc/state/example2/node_modules/menu.js b/doc/state/example2/node_modules/menu.js new file mode 100644 index 0000000..be3388c --- /dev/null +++ b/doc/state/example2/node_modules/menu.js @@ -0,0 +1,222 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { + return { + api: fallback_instance, + _: { + btn: { + $: '' + }, + } + } + function fallback_instance () { + return { + _: { + btn: { + 0: '' + }, + 'btn$small': { + 0: '' + }, + }, + drive: { + 'style/': { + 'theme.css': { + raw: ` + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .title:hover{ + --underline-width: 100% + } + ul{ + background: #273d3d; + list-style: none; + display: none; + position: absolute; + padding: 10px; + box-shadow: 0px 1px 6px 1px gray; + border-radius: 5px; + } + ul.active{ + display: block; + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + title: 'menu', + links: ['link1', 'link2'], + } + }, + }, + } + } + } +} +/****************************************************************************** + MENU +******************************************************************************/ +delete require.cache[require.resolve('btn')] +const {btn, btn_small} = require('btn') + + +module.exports = {menu, menu_hover} +async function menu(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const admin = sdb.req_access(opts.sid) + const on = { + style: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + title.onclick = () => { + main.classList.toggle('active') + admin.register('theme', 'rainbow', { + 'page': { + 'style.css': { + raw: `body { font-family: cursive; }`, + } + }, + 'page/app/head/foo/nav:0': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b2d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + }, + + }) + } + title.onblur = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } +} +async function menu_hover(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + style: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
    +
+ ` + const main = shadow.querySelector('ul') + const title = shadow.querySelector('.title') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // EVENT LISTENERS + // ---------------------------------------- + title.onmouseover = () => { + main.classList.add('active') + } + title.onmouseout = () => { + main.classList.remove('active') + } + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //btn + main.append(await btn(subs[0]), await btn_small(subs[1])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + title.replaceChildren(data.title) + main.replaceChildren(...data.links.map(link => { + const el = document.createElement('li') + el.innerHTML = link + return el + })) + } +} diff --git a/doc/state/example2/node_modules/nav.js b/doc/state/example2/node_modules/nav.js new file mode 100644 index 0000000..9ddc858 --- /dev/null +++ b/doc/state/example2/node_modules/nav.js @@ -0,0 +1,136 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { // -> set database defaults or load from database + return { + api: fallback_instance, + _: { + 'menu':{ + $: '' + }, + } + } + function fallback_instance () { + return { + _: { + 'menu':{ + 0: override_menu + }, + 'menu$hover': { + 0: override_menu_hover + } + }, + drive: { + 'theme/': { + 'style.css': { + raw: ` + nav{ + display: flex; + gap: 20px; + padding: 20px; + background: #4b6d6d; + color: white; + box-shadow: 0px 1px 6px 1px gray; + margin: 5px; + } + .title{ + background: linear-gradient(currentColor 0 0) 0 100% / var(--underline-width, 0) .1em no-repeat; + transition: color .5s ease, background-size .5s; + cursor: pointer; + } + .box{ + display: flex; + gap: 20px; + } + .title:hover{ + --underline-width: 100% + } + ` + } + }, + 'lang/': { + 'en-us.json': { + raw: { + links: ['Home', 'About', 'Contact'] + } + } + } + } + } + } + function override_menu ([menu], path){ + const data = menu() + data.drive['lang/']['en-us.json'].raw = { + title: 'Services', + links: ['Marketing', 'Design', 'Web Dev', 'Ad Compaign'] + } + return data + } + function override_menu_hover ([menu], path){ + const data = menu() + data.drive['lang/']['en-us.json'].raw = { + title: 'Services#hover', + links: ['Marketing', 'Design', 'Web Dev', 'Ad Compaign'] + } + return data + } +} +/****************************************************************************** + NAV +******************************************************************************/ +delete require.cache[require.resolve('menu')] +const {menu, menu_hover} = require('menu') + +module.exports = nav +async function nav(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts?.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + theme: inject, + lang: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + + ` + const main = shadow.querySelector('nav') + const div = shadow.querySelector('div') + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { //menu + main.append(await menu(subs[0]), await menu_hover(subs[1])) + } + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type] && on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + div.replaceChildren(...data.links.map(link => { + const el = document.createElement('div') + el.classList.add('title') + el.innerHTML = link + return el + })) + } +} diff --git a/doc/state/example2/node_modules/template.js b/doc/state/example2/node_modules/template.js new file mode 100644 index 0000000..47d9b30 --- /dev/null +++ b/doc/state/example2/node_modules/template.js @@ -0,0 +1,50 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { + return {} + function fallback_instance () { + return {} + } +} + +/****************************************************************************** + PAGE +******************************************************************************/ +module.exports = module_name +async function module_name(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} diff --git a/doc/state/example2/node_modules/text.js b/doc/state/example2/node_modules/text.js new file mode 100644 index 0000000..3939a67 --- /dev/null +++ b/doc/state/example2/node_modules/text.js @@ -0,0 +1,52 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +function fallback_module () { + return { + api: fallback_instance, + } + function fallback_instance () { + return {} + } +} +/****************************************************************************** + TEXT +******************************************************************************/ +module.exports = text +async function text(opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const on = { + css: inject, + json: fill + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` + Copyright © 2024 Playproject Inc. + ` + const style = shadow.querySelector('style') + const subs = await sdb.watch(onbatch) + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill([data]) { + } +} diff --git a/doc/state/example2/page.js b/doc/state/example2/page.js new file mode 100644 index 0000000..d3705df --- /dev/null +++ b/doc/state/example2/page.js @@ -0,0 +1,117 @@ +const STATE = require('STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) +function fallback_module () { // -> set database defaults or load from database + return { + _: { + app: { + $: '', + 0: override_app + } + }, + drive: { + 'theme/': { + 'style.css': { + raw: 'body { font-family: \'system-ui\'; }' + } + }, + 'lang/': {} + } + } + function override_app ([app]) { + const data = app() + console.log(JSON.parse(JSON.stringify(data._.head))) + data._.head[0] = page$head_override + return data + } + function page$head_override ([head]) { + const data = head() + data._['foo.nav'] = { + 0: page$nav_override + } + return data + } + function page$foo_override ([foo]) { + const data = foo() + data._.nav[0] = page$nav_override + return data + } + function page$nav_override ([nav]) { + const data = nav() + data._.menu[0] = page$menu_override + return data + } + function page$menu_override ([menu]) { + const data = menu() + console.log(data) + data.drive['lang/']['en-us.json'].raw = { + links: ['custom', 'menu'], + title: 'Custom' + } + return data + } +} +/****************************************************************************** + PAGE +******************************************************************************/ +const app = require('app') +const sheet = new CSSStyleSheet() +config().then(() => boot({ })) + +async function config () { + const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const appleTouch = '' + const icon32 = '' + const icon16 = '' + const webmanifest = '' + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += appleTouch + icon16 + icon32 + webmanifest + loadFont + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC +} +/****************************************************************************** + PAGE BOOT +******************************************************************************/ +async function boot () { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const on = { + theme: inject + } + const subs = await sdb.watch(onbatch) + const status = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // desktop + shadow.append(await app(subs[0])) + } + // ---------------------------------------- + // INIT + // ---------------------------------------- + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } +} +async function inject (data) { + sheet.replaceSync(data.join('\n')) +} diff --git a/doc/state/example3/boot.js b/doc/state/example3/boot.js new file mode 100644 index 0000000..1a1b206 --- /dev/null +++ b/doc/state/example3/boot.js @@ -0,0 +1,11 @@ +const init_url = location.hash === '#dev' ? '/doc/state/example/init.js' : 'https://raw.githubusercontent.com/alyhxn/playproject/refs/heads/main/doc/state/example/init.js' +const args = arguments + +fetch(init_url, { cache: 'no-store' }).then(res => res.text()).then(async source => { + const module = { exports: {} } + const f = new Function('module', 'require', source) + f(module, require) + const init = module.exports + await init(args) + require('./page') // or whatever is otherwise the main entry of our project +}) diff --git a/doc/state/example3/bundle.js b/doc/state/example3/bundle.js new file mode 100644 index 0000000..296ded9 --- /dev/null +++ b/doc/state/example3/bundle.js @@ -0,0 +1,1287 @@ +(function () { function r (e, n, t) { function o (i, f) { if (!n[i]) { if (!e[i]) { const c = typeof require === 'function' && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); const a = new Error("Cannot find module '" + i + "'"); throw a.code = 'MODULE_NOT_FOUND', a } const p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { const n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = typeof require === 'function' && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({ + 1: [function (require, module, exports) { + patch_cache_in_browser(arguments[4], arguments[5]) + + function patch_cache_in_browser (source_cache, module_cache) { + const meta = { modulepath: ['page'], paths: {} } + for (const key of Object.keys(source_cache)) { + const [module, names] = source_cache[key] + const dependencies = names || {} + source_cache[key][0] = patch(module, dependencies, meta) + } + function patch (module, dependencies, meta) { + const MAP = {} + for (const [name, number] of Object.entries(dependencies)) MAP[name] = number + return (...args) => { + const original = args[0] + require.cache = module_cache + require.resolve = resolve + args[0] = require + return module(...args) + function require (name) { + const identifier = resolve(name) + if (name.endsWith('STATE') || name === 'io') { + const modulepath = meta.modulepath.join('>') + const original_export = require.cache[identifier] || (require.cache[identifier] = original(name)) + const exports = (...args) => original_export(...args, modulepath, Object.keys(dependencies)) + return exports + } else { + // Clear cache for non-STATE and non-io modules + delete require.cache[identifier] + const counter = meta.modulepath.concat(name).join('>') + if (!meta.paths[counter]) meta.paths[counter] = 0 + const localid = `${name}${meta.paths[counter] ? '#' + meta.paths[counter] : ''}` + meta.paths[counter]++ + meta.modulepath.push(localid.replace(/^\.\+/, '').replace('>', ',')) + const exports = original(name) + meta.modulepath.pop(name) + return exports + } + } + } + function resolve (name) { return MAP[name] } + } + } + require('./page') // or whatever is otherwise the main entry of our project + }, { './page': 5 }], + 2: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('../../../../src/node_modules/STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + const btn = require('btn') + const text = require('text') + + module.exports = test_menu + async function test_menu (opts) { + const on = { + style: inject + } + const el = document.createElement('div') + const shadow = el.attachShadow({ mode: 'closed' }) + shadow.innerHTML = '
' + const sheet = new CSSStyleSheet() + shadow.adoptedStyleSheets = [sheet] + const menu = shadow.querySelector('.menu') + const text_container = shadow.querySelector('.text-container') + const subs = await sdb.watch(onbatch) + console.log(subs) + menu.append( + await btn(subs[0]), + await btn(subs[1]), + await btn(subs[2]), + await btn(subs[3]) + ) + text_container.append(await text(subs[5])) + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + sheet.replaceSync(data) + console.log(data) + } + } + + function fallback_module () { + return { + _: { + btn: { + $: '', + 0: '', + 1: '', + 2: '', + 3: '' + }, + text: { + $: '', + 0: '' + } + }, + drive: { + style: { + 'theme.css': { + raw: ` + .menu { + display: flex; + justify-content: center; + margin: 10px 0px 10px 0px; + } + .text-container { + border: 1px solid #ccc; + padding: 10px; + }` + } + } + } + } + } + }).call(this) + }).call(this, '/doc/state/example3/node_modules/app.js') + }, { '../../../../src/node_modules/STATE': 6, btn: 3, text: 4 }], + 3: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('../../../../src/node_modules/STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + module.exports = btn + async function btn (opts) { + const { id, sdb } = await get(opts.sid) + const on = { + lang: fill, + style: inject + } + + const el = document.createElement('div') + const shadow = el.attachShadow({ mode: 'closed' }) + shadow.innerHTML = '' + const sheet = new CSSStyleSheet() + shadow.adoptedStyleSheets = [sheet] + const button_el = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + + button_el.onclick = btn_click + return el + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function fill (data) { + button_el.textContent = data[0].label + } + async function inject ([data]) { + sheet.replaceSync(data) + } + async function btn_click (event) { + const button_el = event.target + const isToggled = button_el.dataset.toggled === 'true' + if (isToggled) { + button_el.style.backgroundColor = '' + button_el.dataset.toggled = 'false' + } else { + button_el.style.backgroundColor = 'lightblue' + button_el.dataset.toggled = 'true' + } + } + } + function fallback_module () { + return { + api: fallback_instance, + drive: { + lang: { + 'en-us.json': { + raw: { + label: 'Button' + } + } + }, + style: { + 'theme.css': { + raw: ` + button { + padding: 8px 16px; + margin: 0px 40px; + }` + } + } + } + } + function fallback_instance () { + return { + drive: { + lang: { + 'en-us.json': { + raw: { + label: 'Button' + } + } + }, + style: { + 'theme.css': { + raw: ` + button { + padding: 8px 16px; + margin: 0px 40px; + }` + } + } + } + } + } + } + }).call(this) + }).call(this, '/doc/state/example3/node_modules/btn.js') + }, { '../../../../src/node_modules/STATE': 6 }], + 4: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('../../../../src/node_modules/STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + + module.exports = text + async function text (opts) { + const on = { + lang: fill, + style: inject + } + console.log(sdb) + const el = document.createElement('div') + const shadow = el.attachShadow({ mode: 'closed' }) + shadow.innerHTML = '' + const sheet = new CSSStyleSheet() + shadow.adoptedStyleSheets = [sheet] + const label = shadow.querySelector('span') + const subs = await sdb.watch(onbatch) + + return el + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function fill ([data]) { + label.textContent = data.label + } + async function inject ([data]) { + sheet.replaceSync(data) + } + } + function fallback_module () { + return { + drive: { + lang: { + 'en-us.json': { + raw: { + label: 'Text' + } + } + }, + style: { + 'theme.css': { + raw: ` + span { + display: flex; + justify-content: center; + padding: 10px; + color: red; + }` + } + } + } + } + } + }).call(this) + }).call(this, '/doc/state/example3/node_modules/text.js') + }, { '../../../../src/node_modules/STATE': 6 }], + 5: [function (require, module, exports) { + (function (__filename) { + (function () { + const STATE = require('../../../src/node_modules/STATE') + const statedb = STATE(__filename) + const { sdb, subs: [get] } = statedb(fallback_module) + function fallback_module () { + return { + _: { + app: { + $: '', + 0: override_app + } + }, + drive: { + theme: { + 'style.css': { + raw: 'body { font-family: \'system-ui\'; }' + } + } + } + } + + function override_app ([app]) { + const data = app() + return data + } + } + + /****************************************************************************** + PAGE +******************************************************************************/ + const app = require('app') + const sheet = new CSSStyleSheet() + config().then(() => boot({ sid: '' })) + + async function config () { + // const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const appleTouch = '' + // const icon32 = '' + // const icon16 = '' + // const webmanifest = '' + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += appleTouch + loadFont // + icon16 + icon32 + webmanifest + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC + } + /****************************************************************************** + PAGE BOOT +******************************************************************************/ + async function boot (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const on = { + theme: inject + } + const subs = await sdb.watch(onbatch) + // const status = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + // desktop + shadow.append(await app(subs[1])) + + // ---------------------------------------- + // INIT + // ---------------------------------------- + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + } + async function inject (data) { + sheet.replaceSync(data.join('\n')) + } + }).call(this) + }).call(this, '/doc/state/example3/page.js') + }, { '../../../src/node_modules/STATE': 6, app: 2 }], + 6: [function (require, module, exports) { + const localdb = require('localdb') + const db = localdb() + /** Data stored in a entry in db by STATE (Schema): + * id (String): Node Path + * name (String/Optional): Any (To be used in theme_widget) + * type (String): Module Name for module / Module id for instances + * hubs (Array): List of hub-nodes + * subs (Array): List of sub-nodes + * inputs (Array): List of input files + */ + // Constants and initial setup (global level) + const VERSION = 10 + const HELPER_MODULES = ['io', 'localdb', 'STATE'] + const fallback_post_error = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#defining-fallbacks' + + const status = { + root_module: true, + root_instance: true, + overrides: {}, + tree: {}, + tree_pointers: {}, + modulepaths: {}, + inits: [], + open_branches: {}, + db, + local_statuses: {}, + listeners: {}, + missing_supers: new Set(), + imports: {}, + expected_imports: {} + } + window.STATEMODULE = status + + // Version check and initialization + status.fallback_check = Boolean(check_version()) + status.fallback_check && db.add(['playproject_version'], VERSION) + + // Symbol mappings + const s2i = {} + const i2s = {} + const admins = [0] + + // Inner Function + function STATE (address, modulepath, dependencies) { + !status.ROOT_ID && (status.ROOT_ID = modulepath) + status.modulepaths[modulepath] = 0 + // Variables (module-level) + + const local_status = { + name: extract_filename(address), + module_id: modulepath, + deny: {}, + sub_modules: [], + sub_instances: {} + } + status.local_statuses[modulepath] = local_status + return statedb + + function statedb (fallback) { + const data = fallback() + local_status.fallback_instance = data.api + const super_id = modulepath.split(/>(?=[^>]*$)/)[0] + + if (super_id === status.current_node) { + status.expected_imports[super_id].splice(status.expected_imports[super_id].indexOf(modulepath), 1) + } else if ((status?.current_node?.split('>').length || 0) < super_id.split('>').length) { + let temp = super_id + while (temp !== status.current_node && temp.includes('>')) { + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } + } else { + let temp = status.current_node + while (temp !== super_id && temp.includes('>')) { + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } + } + + if (data._) { + status.open_branches[modulepath] = Object.keys(data._).length + status.expected_imports[modulepath] = Object.keys(data._) + status.current_node = modulepath + } + + local_status.fallback_module = new Function(`return ${fallback.toString()}`)() + verify_imports(modulepath, dependencies, data) + const updated_status = append_tree_node(modulepath, status) + Object.assign(status.tree_pointers, updated_status.tree_pointers) + Object.assign(status.open_branches, updated_status.open_branches) + status.inits.push(init_module) + + if (!Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)) { + status.inits.forEach(init => init()) + } + + const sdb = create_statedb_interface(local_status, modulepath, xtype = 'module') + status.dataset = sdb.private_api + return { + id: modulepath, + sdb: sdb.public_api, + subs: [get] + // sub_modules + } + } + function append_tree_node (id, status) { + const [super_id, name] = id.split(/>(?=[^>]*$)/) + + if (name) { + if (status.tree_pointers[super_id]) { + status.tree_pointers[super_id]._[name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[super_id]._[name].$ + status.open_branches[super_id]-- + } else { + let temp_name; let new_name = name + let new_super_id = super_id + + while (!status.tree_pointers[new_super_id]) { + [new_super_id, temp_name] = new_super_id.split(/>(?=[^>]*$)/) + new_name = temp_name + '>' + new_name + } + status.tree_pointers[new_super_id]._[new_name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[new_super_id]._[new_name].$ + if (!status.missing_supers.has(super_id)) { status.open_branches[new_super_id]-- } + status.missing_supers.add(super_id) + } + } else { + status.tree[id] = { $: { _: {} } } + status.tree_pointers[id] = status.tree[id].$ + } + return status + } + function init_module () { + const { statedata, state_entries, newstatus, updated_local_status } = get_module_data(local_status.fallback_module) + statedata.orphan && (local_status.orphan = true) + // side effects + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + console.log('Main module: ', statedata.name, '\n', state_entries) + updated_local_status && Object.assign(local_status, updated_local_status) + const old_fallback = local_status.fallback_instance + local_status.fallback_instance = () => statedata.api([old_fallback]) + db.append(['state'], state_entries) + // add_source_code(statedata.inputs) // @TODO: remove side effect + } + + [local_status.sub_modules, symbol2ID, ID2Symbol] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + + // Setup local data (module level) + if (status.root_module) { + status.root_module = false + statedata.admins && admins.push(...statedata.admins) + } + // @TODO: handle sub_modules when dynamic require is implemented + // const sub_modules = {} + // statedata.subs && statedata.subs.forEach(id => { + // sub_modules[db.read(['state', id]).type] = id + // }) + } + function get (sid) { + const { statedata, state_entries, newstatus } = get_instance_data(sid) + + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + Object.assign(status.tree, newstatus.tree) + console.log('Main instance: ', statedata.name, '\n', state_entries) + db.append(['state'], state_entries) + } + [local_status.sub_instances[statedata.id], symbol2ID, ID2Symbol] = symbolfy(statedata, local_status) + Object.assign(s2i, symbol2ID) + Object.assign(i2s, ID2Symbol) + const sdb = create_statedb_interface(local_status, statedata.id, xtype = 'instance') + return { + id: statedata.id, + sdb: sdb.public_api + } + } + function get_module_data (fallback) { + let data = db.read(['state', modulepath]) + if (status.fallback_check) { + if (data) { + var { sanitized_data, updated_status } = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: data }) + } else if (status.root_module) { + var { sanitized_data, updated_status } = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: { id: modulepath } }) + } else { + var { sanitized_data, updated_status, updated_local_status } = find_super({ xtype: 'module', fallback, fun_status: status, local_status }) + } + data = sanitized_data.entry + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + updated_local_status + } + } + function get_instance_data (sid) { + const id = s2i[sid] + let data = id && db.read(['state', id]) + let sanitized_data; let updated_status = status + if (status.fallback_check) { + if (!data && !status.root_instance) { + ({ sanitized_data, updated_status } = find_super({ xtype: 'instance', fallback: local_status.fallback_instance, fun_status: status })) + } else { + ({ sanitized_data, updated_status } = validate_and_preprocess({ + fun_status: status, + fallback: local_status.fallback_instance, + xtype: 'instance', + pre_data: data || { id: get_instance_path(modulepath) } + })) + updated_status.root_instance = false + } + data = sanitized_data.entry + } else if (status.root_instance) { + data = db.read(['state', id || get_instance_path(modulepath)]) + updated_status.tree = JSON.parse(JSON.stringify(status.tree)) + updated_status.root_instance = false + } + + if (!data && local_status.orphan) { + data = db.read(['state', get_instance_path(modulepath)]) + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status + } + } + function find_super ({ xtype, fallback, fun_status, local_status }) { + let modulepath_super = modulepath.split(/\>(?=[^>]*$)/)[0] + let modulepath_grand = modulepath_super.split(/\>(?=[^>]*$)/)[0] + const split = modulepath.split('>') + let data + const entries = {} + if (xtype === 'module') { + let name = split.at(-1) + while (!data && modulepath_grand.includes('>')) { + data = db.read(['state', modulepath_super]) + const split = modulepath_super.split(/\>(?=[^>]*$)/) + modulepath_super = split[0] + name = split[1] + '>' + name + } + data.path = data.id = modulepath_super + '>' + name + modulepath = modulepath_super + '>' + name + local_status.name = name + + const super_data = db.read(['state', modulepath_super]) + super_data.subs.forEach((sub_id, i) => { + if (sub_id === modulepath_super) { + super_data.subs.splice(i, 1) + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } else { + // @TODO: Make the :0 dynamic + let instance_path_super = modulepath_super + ':0' + let temp + while (!data && temp !== modulepath_super) { + data = db.read(['state', instance_path_super]) + temp = modulepath_super + modulepath_grand = modulepath_super = modulepath_super.split(/\>(?=[^>]*$)/)[0] + instance_path_super = modulepath_super + ':0' + } + data.path = data.id = get_instance_path(modulepath) + temp = null + let super_data + let instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + + while (!super_data?.subs && temp !== modulepath_grand) { + super_data = db.read(['state', instance_path_grand]) + temp = modulepath_grand + modulepath_grand = modulepath_grand.split(/\>(?=[^>]*$)/)[0] + instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + } + + super_data.subs.forEach((sub_id, i) => { + if (sub_id === instance_path_super) { + super_data.subs.splice(i, 1) + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + data.name = split.at(-1) + return { + updated_local_status: local_status, + ...validate_and_preprocess({ + fun_status, + fallback, + xtype, + pre_data: data, + orphan_check: true, + entries + }) + } + } + function validate_and_preprocess ({ fallback, xtype, pre_data = {}, orphan_check, fun_status, entries }) { + const { id: pre_id, hubs: pre_hubs, mapping } = pre_data + let fallback_data + try { + validate(fallback(), xtype) + } catch (error) { + throw new Error(`Error in fallback function of ${pre_id} ${xtype}\n${error.stack}`) + } + if (fun_status.overrides[pre_id]) { + fallback_data = fun_status.overrides[pre_id].fun[0](get_fallbacks({ fallback, modulename: local_status.name, modulepath, instance_path: pre_id })) + console.log('Override used: ', pre_id) + fun_status.overrides[pre_id].by.splice(0, 1) + fun_status.overrides[pre_id].fun.splice(0, 1) + } else { fallback_data = fallback() } + + // console.log('fallback_data: ', fallback_data) + fun_status.overrides = register_overrides({ overrides: fun_status.overrides, tree: fallback_data, path: modulepath, id: pre_id }) + console.log('overrides: ', Object.keys(fun_status.overrides)) + orphan_check && (fallback_data.orphan = orphan_check) + // This function makes changes in fun_status (side effect) + return { + sanitized_data: sanitize_state({ local_id: '', entry: fallback_data, path: pre_id, xtype, mapping, entries }), + updated_status: fun_status + } + + function sanitize_state ({ local_id, entry, path, hub_entry, local_tree, entries = {}, xtype, mapping, xkey }) { + [path, entry, local_tree] = extract_data({ local_id, entry, path, hub_entry, local_tree, xtype, xkey }) + + entry.id = path + entry.name = entry.name || local_id.split(':')[0] || local_status.name + mapping && (entry.mapping = mapping) + + entries = { ...entries, ...sanitize_subs({ local_id, entry, path, local_tree, xtype, mapping }) } + delete entry._ + entries[entry.id] = entry + // console.log('Entry: ', entry) + return { entries, entry } + } + function extract_data ({ local_id, entry, path, hub_entry, xtype, xkey }) { + if (local_id) { + entry.hubs = [hub_entry.id] + if (xtype === 'instance') { + let temp_path = path.split(':')[0] + temp_path = temp_path ? temp_path + '>' : temp_path + const module_id = temp_path + local_id + entry.type = module_id + path = module_id + ':' + xkey + } else { + entry.type = local_id + path = path ? path + '>' : '' + path = path + local_id + } + } else { + if (xtype === 'instance') { + entry.type = local_status.module_id + } else { + local_tree = JSON.parse(JSON.stringify(entry)) + // @TODO Handle JS file entry + // console.log('pre_id:', pre_id) + // const file_id = local_status.name + '.js' + // entry.drive || (entry.drive = {}) + // entry.drive[file_id] = { $ref: address } + entry.type = local_status.name + } + pre_hubs && (entry.hubs = pre_hubs) + } + return [path, entry, local_tree] + } + function sanitize_subs ({ local_id, entry, path, local_tree, xtype, mapping }) { + const entries = {} + if (!local_id) { + entry.subs = [] + if (entry._) { + // @TODO refactor when fallback structure improves + Object.entries(entry._).forEach(([local_id, value]) => { + Object.entries(value).forEach(([key, override]) => { + if (key === 'mapping') { return } + const sub_instance = sanitize_state({ local_id, entry: value, path, hub_entry: entry, local_tree, xtype: key === '$' ? 'module' : 'instance', mapping: value.mapping, xkey: key }).entry + entries[sub_instance.id] = JSON.parse(JSON.stringify(sub_instance)) + entry.subs.push(sub_instance.id) + }) + }) + } + if (entry.drive) { + // entry.drive.theme && (entry.theme = entry.drive.theme) + // entry.drive.lang && (entry.lang = entry.drive.lang) + entry.inputs = [] + const new_drive = [] + Object.entries(entry.drive).forEach(([dataset_type, dataset]) => { + dataset_type = dataset_type.split('/')[0] + const new_dataset = { files: [], mapping: {} } + Object.entries(dataset).forEach(([key, value]) => { + const sanitized_file = sanitize_file(key, value, entry, entries) + entries[sanitized_file.id] = sanitized_file + new_dataset.files.push(sanitized_file.id) + }) + new_dataset.id = local_status.name + '.' + dataset_type + '.dataset' + new_dataset.type = dataset_type + new_dataset.name = 'default' + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + entries[new_dataset.id] = new_dataset + let check_name = true + entry.inputs.forEach(dataset_id => { + const ds = entries[dataset_id] + if (ds.type === new_dataset.type) { check_name = false } + }) + check_name && entry.inputs.push(new_dataset.id) + new_drive.push(new_dataset.id) + + if (!status.root_module) { + const hub_entry = db.read(['state', entry.hubs[0]]) + const mapped_file_type = mapping?.[dataset_type] || dataset_type + hub_entry.inputs.forEach(input_id => { + const input = db.read(['state', input_id]) + if (mapped_file_type === input.type) { + input.mapping[entry.id] = new_dataset.id + entries[input_id] = input + } + }) + } + }) + entry.drive = new_drive + } + } + return entries + } + function sanitize_file (file_id, file, entry, entries) { + const type = file_id.split('.').at(-1) + + if (!isNaN(Number(file_id))) return file_id + + file.id = local_status.name + '.' + type + file.name = file.name || file.id + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [entry.id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + while (entries[file.id]) { + const no = file.id.split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + return file + } + } + } + + // External Function (helper) + function validate (data, xtype) { + /** Expected structure and types + * Sample : "key1|key2:*:type1|type2" + * ":" : separator + * "|" : OR + * "*" : Required key + * + * */ + const expected_structure = { + '_::object': { + ':*:object': xtype === 'module' ? { + '$:*:function|string|object': '', + 'mapping::': {} + } : { // Required key, any name allowed + ':*:function|string|object': () => {}, // Optional key + 'mapping::': {} + } + }, + 'drive::object': { + '::object': { + '::object': { // Required key, any name allowed + 'raw|link:*:object|string': {}, // data or link are names, required, object or string are types + link: 'string' + } + } + } + } + + validate_shape(data, expected_structure) + + function validate_shape (obj, expected, super_node = 'root', path = '') { + const keys = Object.keys(obj) + const values = Object.values(obj) + + Object.entries(expected).forEach(([expected_key, expected_value]) => { + let [expected_key_names, required, expected_types] = expected_key.split(':') + expected_types = expected_types ? expected_types.split('|') : [typeof (expected_value)] + let absent = true + if (expected_key_names) { + expected_key_names.split('|').forEach(expected_key_name => { + const value = obj[expected_key_name] + if (value !== undefined) { + const type = typeof (value) + absent = false + + if (expected_types.includes(type)) { type === 'object' && validate_shape(value, expected_value, expected_key_name, path + '/' + expected_key_name) } else { throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${expected_key_name}" at:` + path + fallback_post_error) } + } + }) + } else { + values.forEach((value, index) => { + absent = false + const type = typeof (value) + + if (expected_types.includes(type)) { type === 'object' && validate_shape(value, expected_value, keys[index], path + '/' + keys[index]) } else { throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${keys[index]}" at: ` + path) } + }) + } + if (absent && required) { + if (expected_key_names) { throw new Error(`Can't find required key "${expected_key_names.replace('|', ' or ')}" at: ` + path + fallback_post_error) } else { throw new Error(`No sub-nodes found for super key "${super_node}" at sub: ` + path + fallback_post_error) } + } + }) + } + } + function extract_filename (address) { + const parts = address.split('/node_modules/') + const last = parts.at(-1).split('/') + if (last.at(-1) === 'index.js') { return last.at(-2) } + return last.at(-1).slice(0, -3) + } + function get_instance_path (modulepath, modulepaths = status.modulepaths) { + return modulepath + ':' + modulepaths[modulepath]++ + } + async function get_input ({ id, name, $ref, type, raw }) { + const xtype = (typeof (id) === 'number' ? name : id).split('.').at(-1) + let result = db.read([type, id]) + + if (!result) { + result = raw !== undefined ? raw : await ((await fetch($ref))[xtype === 'json' ? 'json' : 'text']()) + } + return result + } + // Unavoidable side effect + function add_source_code (hubs) { + hubs.forEach(async id => { + const data = db.read(['state', id]) + if (data.type === 'js') { + data.data = await get_input(data) + db.add(['state', data.id], data) + } + }) + } + function verify_imports (id, imports, data) { + const state_address = imports.find(imp => imp.includes('STATE')) + HELPER_MODULES.push(state_address) + imports = imports.filter(imp => !HELPER_MODULES.includes(imp)) + if (!data._) { + if (imports.length > 1) { + imports.splice(imports.indexOf(state_address), 1) + throw new Error(`No sub-nodes found for required modules "${imports.join(', ')}" in the fallback of "${status.local_statuses[id].module_id}"` + fallback_post_error) + } else return + } + const fallback_imports = Object.keys(data._) + + imports.forEach(imp => { + let check = true + fallback_imports.forEach(fallimp => { + if (imp === fallimp) { check = false } + }) + + if (check) { throw new Error('Required module "' + imp + '" is not defined in the fallback of ' + status.local_statuses[id].module_id + fallback_post_error) } + }) + + fallback_imports.forEach(fallimp => { + let check = true + imports.forEach(imp => { + if (imp === fallimp) { check = false } + }) + + if (check) { throw new Error('Module "' + fallimp + '" defined in the fallback of ' + status.local_statuses[id].module_id + ' is not required') } + }) + } + function symbolfy (data) { + const s2i = {} + const i2s = {} + const subs = [] + data.subs && data.subs.forEach(sub => { + const substate = db.read(['state', sub]) + s2i[i2s[sub] = Symbol(sub)] = sub + subs.push({ sid: i2s[sub], type: substate.type }) + }) + return [subs, s2i, i2s] + } + function register_overrides ({ overrides, ...args }) { + recurse(args) + return overrides + function recurse ({ tree, path = '', id, xtype = 'instance', local_modulepaths = {} }) { + tree._ && Object.entries(tree._).forEach(([type, instances]) => { + const sub_path = path + '>' + type + Object.entries(instances).forEach(([id, override]) => { + if (typeof (override) === 'function') { + const resultant_path = id === '$' ? sub_path : sub_path + ':' + id + if (overrides[resultant_path]) { + overrides[resultant_path].fun.push(override) + overrides[resultant_path].by.push(id) + } else { overrides[resultant_path] = { fun: [override], by: [id] } } + } else { + recurse({ tree: override, path: sub_path, id, xtype, local_modulepaths }) + } + }) + }) + } + } + function get_fallbacks ({ fallback, modulename, modulepath, instance_path }) { + return [mutated_fallback, ...status.overrides[instance_path].fun] + + function mutated_fallback () { + const data = fallback() + + data.overrider = status.overrides[instance_path].by[0] + merge_trees(data, modulepath) + return data + + function merge_trees (data, path) { + if (data._) { + Object.entries(data._).forEach(([type, data]) => merge_trees(data, path + '>' + type.split('$')[0].replace('.', '>'))) + } else { + data.$ = { _: status.tree_pointers[path]?._ } + } + } + } + } + function check_version () { + if (db.read(['playproject_version']) != VERSION) { + localStorage.clear() + return true + } + } + + // Public Function + function create_statedb_interface (local_status, node_id, xtype) { + const api = { + public_api: { + watch, get_sub + }, + private_api: { + xget: (id) => db.read(['state', id]), + get_all: () => db.read_all(['state']), + get, + register, + load: (snapshot) => { + localStorage.clear() + Object.entries(snapshot).forEach(([key, value]) => { + db.add([key], JSON.parse(value), true) + }) + window.location.reload() + }, + swtch, + unregister + } + } + node_id === status.ROOT_ID && (api.public_api.admin = api.private_api) + return api + + async function watch (listener) { + const data = db.read(['state', node_id]) + if (listener) { + status.listeners[data.id] = listener + listener(await make_input_map(data.inputs)) + } + return xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + } + function get_sub (type) { + return local_status.subs.filter(sub => { + const dad = db.read(['state', sub.type]) + return dad.type === type + }) + } + function get ({ type: dataset_type, name: dataset_name } = {}) { + const node = db.read(['state', status.ROOT_ID]) + if (dataset_type) { + const dataset_list = [] + node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.type === dataset_type) { dataset_list.push(dataset.name) } + }) + if (dataset_name) { + return recurse(status.ROOT_ID, dataset_type) + } + return dataset_list + } + const datasets = [] + node.inputs && node.inputs.forEach(dataset_id => { + datasets.push(db.read(['state', dataset_id]).type) + }) + return datasets + + function recurse (node_id, dataset_type) { + const node_list = [] + const entry = db.read(['state', node_id]) + const temp = entry.mapping ? Object.keys(entry.mapping).find(key => entry.mapping[key] === dataset_type) : null + const mapped_type = temp || dataset_type + entry.drive && entry.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.name === dataset_name && dataset.type === mapped_type) { + node_list.push(node_id) + } + }) + entry.subs && entry.subs.forEach(sub_id => node_list.push(...recurse(sub_id, mapped_type))) + return node_list + } + } + function register ({ type: dataset_type, name: dataset_name, dataset }) { + Object.entries(dataset).forEach(([node_id, files]) => { + const new_dataset = { files: [] } + Object.entries(files).forEach(([file_id, file]) => { + const type = file_id.split('.').at(-1) + + file.id = local_status.name + '.' + type + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [node_id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + db.add(['state', file.id], file) + new_dataset.files.push(file.id) + }) + + const node = db.read(['state', node_id]) + new_dataset.id = node.name + '.' + dataset_type + '.dataset' + new_dataset.name = dataset_name + new_dataset.type = dataset_type + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + db.push(['state', node_id, 'drive'], new_dataset.id) + db.add(['state', new_dataset.id], new_dataset) + }) + console.log(' registered ' + dataset_name + '.' + dataset_type) + } + function unregister ({ type: dataset_type, name: dataset_name } = {}) { + return recurse(status.ROOT_ID) + + function recurse (node_id) { + const node = db.read(['state', node_id]) + node.drive && node.drive.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.name === dataset_name && dataset.type === dataset_type) { + node.drive.splice(node.drive.indexOf(dataset_id), 1) + return true + } + }) + node.inputs && node.inputs.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (dataset.name === dataset_name && dataset.type === dataset_type) { + node.inputs.splice(node.inputs.indexOf(dataset_id), 1) + swtch(dataset_type) + return true + } + }) + db.add(['state', node_id], node) + node.subs.forEach(sub_id => recurse(sub_id)) + } + } + function swtch ({ type: dataset_type, name: dataset_name = 'default' }) { + recurse(dataset_type, dataset_name, status.ROOT_ID) + + async function recurse (target_type, target_name, id) { + const node = db.read(['state', id]) + + let target_dataset + node.drive && node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if (target_name === dataset.name && target_type === dataset.type) { + target_dataset = dataset + } + }) + if (target_dataset) { + node.inputs.forEach((dataset_id, i) => { + const dataset = db.read(['state', dataset_id]) + if (target_type === dataset.type) { + node.inputs.splice(i, 1) + } + }) + node.inputs.push(target_dataset.id) + } + db.add(['state', id], node) + status.listeners[id] && status.listeners[id](await make_input_map(node.inputs)) + node.subs && node.subs.forEach(sub_id => { + const subdataset_id = target_dataset?.mapping?.[sub_id] + recurse(target_type, db.read(['state', subdataset_id])?.name || target_name, sub_id) + }) + } + } + } + async function make_input_map (inputs) { + const input_map = [] + if (inputs) { + await Promise.all(inputs.map(async input => { + const files = [] + const dataset = db.read(['state', input]) + await Promise.all(dataset.files.map(async file_id => { + const input_state = db.read(['state', file_id]) + files.push(await get_input(input_state)) + })) + input_map.push({ type: dataset.type, data: files }) + })) + } + return input_map + } + + module.exports = STATE + }, { localdb: 7 }], + 7: [function (require, module, exports) { + /****************************************************************************** + LOCALDB COMPONENT +******************************************************************************/ + module.exports = localdb + + function localdb () { + const prefix = '153/' + return { add, read_all, read, drop, push, length, append, find } + + function length (keys) { + const address = prefix + keys.join('/') + return Object.keys(localStorage).filter(key => key.includes(address)).length + } + /** + * Assigns value to the key of an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function add (keys, value, precheck) { + localStorage[(precheck ? '' : prefix) + keys.join('/')] = JSON.stringify(value) + } + /** + * Appends values into an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function append (keys, data) { + const pre = keys.join('/') + Object.entries(data).forEach(([key, value]) => { + localStorage[prefix + pre + '/' + key] = JSON.stringify(value) + }) + } + /** + * Pushes value to an array already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function push (keys, value) { + const independent_key = keys.slice(0, -1) + const data = JSON.parse(localStorage[prefix + independent_key.join('/')]) + data[keys.at(-1)].push(value) + localStorage[prefix + independent_key.join('/')] = JSON.stringify(data) + } + function read (keys) { + const result = localStorage[prefix + keys.join('/')] + return result && JSON.parse(result) + } + function read_all (keys) { + const address = prefix + keys.join('/') + const result = {} + Object.entries(localStorage).forEach(([key, value]) => { + if (key.includes(address)) { result[key.split('/').at(-1)] = JSON.parse(value) } + }) + return result + } + function drop (keys) { + if (keys.length > 1) { + const data = JSON.parse(localStorage[keys[0]]) + let temp = data + keys.slice(1, -1).forEach(key => { + temp = temp[key] + }) + if (Array.isArray(temp)) { temp.splice(keys[keys.length - 1], 1) } else { delete (temp[keys[keys.length - 1]]) } + localStorage[keys[0]] = JSON.stringify(data) + } else { delete (localStorage[keys[0]]) } + } + function find (keys, filters, index = 0) { + let index_count = 0 + const address = prefix + keys.join('/') + const target_key = Object.keys(localStorage).find(key => { + if (key.includes(address)) { + const entry = JSON.parse(localStorage[key]) + let count = 0 + Object.entries(filters).some(([search_key, value]) => { + if (entry[search_key] !== value) { return } + count++ + }) + if (count === Object.keys(filters).length) { + if (index_count === index) { return key } + index_count++ + } + } + }, undefined) + return target_key && JSON.parse(localStorage[target_key]) + } + } + }, {}] +}, {}, [1]) diff --git a/doc/state/example3/index.html b/doc/state/example3/index.html new file mode 100644 index 0000000..cb7c703 --- /dev/null +++ b/doc/state/example3/index.html @@ -0,0 +1,12 @@ + + + + + + + Playproject.io + + + + + diff --git a/doc/state/example3/node_modules/app.js b/doc/state/example3/node_modules/app.js new file mode 100644 index 0000000..8850795 --- /dev/null +++ b/doc/state/example3/node_modules/app.js @@ -0,0 +1,74 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +const btn = require('btn') +const text = require('text') + +module.exports = test_menu +async function test_menu (opts) { + const on = { + style: inject + } + const el = document.createElement('div') + const shadow = el.attachShadow({ mode: 'closed' }) + shadow.innerHTML = `
` + const sheet = new CSSStyleSheet() + shadow.adoptedStyleSheets = [sheet] + const menu = shadow.querySelector('.menu') + const text_container = shadow.querySelector('.text-container') + const subs = await sdb.watch(onbatch) + console.log(subs) + menu.append( + await btn(subs[0]), + await btn(subs[1]), + await btn(subs[2]), + await btn(subs[3]) + ) + text_container.append(await text(subs[5])) + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function inject (data) { + sheet.replaceSync(data) + console.log(data) + } +} + +function fallback_module () { + return { + _: { + 'btn': { + $: '', + 0: '', + 1: '', + 2: '', + 3: '' + }, + 'text': { + $: '', + 0: '' + } + }, + drive: { + style: { + 'theme.css': { + raw: ` + .menu { + display: flex; + justify-content: center; + margin: 10px 0px 10px 0px; + } + .text-container { + border: 1px solid #ccc; + padding: 10px; + }` + } + } + } + } +} \ No newline at end of file diff --git a/doc/state/example3/node_modules/btn.js b/doc/state/example3/node_modules/btn.js new file mode 100644 index 0000000..6e641ba --- /dev/null +++ b/doc/state/example3/node_modules/btn.js @@ -0,0 +1,90 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +module.exports = btn +async function btn (opts) { + const { id, sdb } = await get(opts.sid) + const on = { + lang: fill, + style: inject + } + + const el = document.createElement('div') + const shadow = el.attachShadow({ mode: 'closed' }) + shadow.innerHTML = `` + const sheet = new CSSStyleSheet() + shadow.adoptedStyleSheets = [sheet] + const button_el = shadow.querySelector('button') + const subs = await sdb.watch(onbatch) + + button_el.onclick = btn_click + return el + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function fill (data) { + button_el.textContent = data[0].label + } + async function inject ([data]) { + sheet.replaceSync(data) + } + async function btn_click(event) { + const button_el = event.target + let isToggled = button_el.dataset.toggled === 'true' + if (isToggled) { + button_el.style.backgroundColor = '' + button_el.dataset.toggled = 'false' + } else { + button_el.style.backgroundColor = 'lightblue' + button_el.dataset.toggled = 'true' + } + } +} +function fallback_module () { + return { + api: fallback_instance, + drive: { + lang: { + 'en-us.json': { + raw: { + label: 'Button' + } + } + }, + style: { + 'theme.css': { + raw: ` + button { + padding: 8px 16px; + margin: 0px 40px; + }` + } + } + } + } + function fallback_instance () { + return { + drive: { + lang: { + 'en-us.json': { + raw: { + label: 'Button' + } + } + }, + style: { + 'theme.css': { + raw: ` + button { + padding: 8px 16px; + margin: 0px 40px; + }` + } + } + } + } + } +} \ No newline at end of file diff --git a/doc/state/example3/node_modules/text.js b/doc/state/example3/node_modules/text.js new file mode 100644 index 0000000..c2ecca7 --- /dev/null +++ b/doc/state/example3/node_modules/text.js @@ -0,0 +1,56 @@ +const STATE = require('../../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) + +module.exports = text +async function text (opts) { + const on = { + lang: fill, + style: inject + } + console.log(sdb) + const el = document.createElement('div') + const shadow = el.attachShadow({ mode: 'closed' }) + shadow.innerHTML = `` + const sheet = new CSSStyleSheet() + shadow.adoptedStyleSheets = [sheet] + const label = shadow.querySelector('span') + const subs = await sdb.watch(onbatch) + + return el + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } + async function fill ([data]) { + label.textContent = data.label + } + async function inject ([data]) { + sheet.replaceSync(data) + } +} +function fallback_module () { + return { + drive: { + lang: { + 'en-us.json': { + raw: { + label: 'Text' + } + } + }, + style: { + 'theme.css': { + raw: ` + span { + display: flex; + justify-content: center; + padding: 10px; + color: red; + }` + } + } + } + } +} \ No newline at end of file diff --git a/doc/state/example3/page.js b/doc/state/example3/page.js new file mode 100644 index 0000000..98c1110 --- /dev/null +++ b/doc/state/example3/page.js @@ -0,0 +1,90 @@ +const STATE = require('../../../src/node_modules/STATE') +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module) +function fallback_module () { + return { + _: { + app: { + $: '', + 0: override_app + } + }, + drive: { + theme: { + 'style.css': { + raw: 'body { font-family: \'system-ui\'; }' + } + } + } + } + + function override_app ([app]) { + const data = app() + return data + } +} + +/****************************************************************************** + PAGE +******************************************************************************/ +const app = require('app') +const sheet = new CSSStyleSheet() +config().then(() => boot({ sid: '' })) + +async function config () { + // const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const appleTouch = '' + // const icon32 = '' + // const icon16 = '' + // const webmanifest = '' + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += appleTouch + loadFont // + icon16 + icon32 + webmanifest + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC +} +/****************************************************************************** + PAGE BOOT +******************************************************************************/ +async function boot (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const on = { + theme: inject + } + const subs = await sdb.watch(onbatch) + // const status = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + // desktop + shadow.append(await app(subs[1])) + + // ---------------------------------------- + // INIT + // ---------------------------------------- + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type] && on[type](data) + } + } +} +async function inject (data) { + sheet.replaceSync(data.join('\n')) +} diff --git a/doc/state/io.md b/doc/state/io.md new file mode 100644 index 0000000..5ff7f92 --- /dev/null +++ b/doc/state/io.md @@ -0,0 +1,107 @@ +# Using the `io` Module + +## Overview + +The `io` module provides a simple in-memory peer-to-peer communication interface for modules within the same process. It enables message passing between module instances using unique IDs, simulating network-like connections for local stateful modules. + +--- + +## Initializing `io` + +```js +const io = require('io') +const peer = io(seed, alias) +``` + +1. Import the `io` module and initialize it with a unique `seed` (string) and an optional `alias`. +2. The returned `peer` object exposes two main methods: `at` (to connect to another peer) and `on` (to handle incoming connections). + +--- + +## API Reference + +### `io(seed, alias)` + +- **Parameters:** + - `seed` (string): Unique identifier for the peer. Must not collide with other peers. + - `alias` (string, optional): Human-readable alias for the peer. +- **Returns:** An object with methods: + - `at(id, signal)`: Initiates a connection to another peer by `id`. + - `on(handler)`: Registers a handler for incoming connections. + +#### Example + +```js +const peerA = io('peerA') +const peerB = io('peerB') + +peerB.on(port => { + port.onmessage = event => { + console.log('Received:', event.data) + } +}) + +peerA.at('peerB').then(() => { + // Send a message after connection + peerA.peer['peerB'].postMessage({ type: 'greet', args: 'Hello!' }) +}) +``` + +--- + +## Method Details + +### `at(id, signal)` + +- **Purpose:** Connects to another peer by their `id`. +- **Parameters:** + - `id` (string): The target peer's unique ID. + - `signal` (AbortSignal, optional): Timeout or cancellation signal (default: 1000ms). +- **Behavior:** + - Throws if attempting to connect to self. + - Waits if the target peer is offline, or connects immediately if online. + - Establishes a `MessageChannel` between peers for bidirectional communication. + +### `on(handler)` + +- **Purpose:** Registers a callback to handle incoming connections. +- **Parameters:** + - `handler` (function): Receives a `port` object representing the connection. +- **Behavior:** + - The handler is called when another peer connects. + - The `port` object supports `onmessage` and `postMessage` for message exchange. + +--- + +## Usage Patterns + +### Peer-to-Peer Messaging + +- Each peer must have a unique `seed`. +- Use `at` to initiate connections and `on` to handle them. +- Message passing is done via the `port` object, which mimics the Web `MessagePort` API. + +### Error Handling + +- Attempting to reuse a `seed` throws an error. +- Connecting to self is not allowed. +- If the target peer is offline, the connection waits until the peer comes online or the signal times out. + +--- + +## Best Practices + +1. **Unique Seeds:** Always use unique `seed` values for each peer to avoid conflicts. +2. **Connection Management:** Handle connection timeouts and errors gracefully. +3. **Message Structure:** Use consistent message formats (e.g., `{ type, args }`) for clarity. +4. **Cleanup:** Remove or close unused peers to free resources. + +--- + +## Example Integration + +The `io` module is typically used in conjunction with stateful modules (see `STATE` documentation) to enable inter-module communication. For example, UI components can use `io` to send events or synchronize state across isolated instances. + +--- + +This documentation provides a comprehensive guide to using the `io` module for local peer-to-peer communication in your applications. For advanced usage, refer to example modules and integration patterns in the codebase. diff --git a/doc/state/net.md b/doc/state/net.md new file mode 100644 index 0000000..60f0a8b --- /dev/null +++ b/doc/state/net.md @@ -0,0 +1,18 @@ +### Proposal + +```js +const net = { + api: [ 'inject', 'fill' ], + event: { + click: [] + hover: [] + } +} +``` + + + +### Usage in override +```js + data.net.event.click.push({ address: 'page', type: 'register', args: rainbow_theme }) +``` \ No newline at end of file diff --git a/doc/state/newbie-guide.md b/doc/state/newbie-guide.md new file mode 100644 index 0000000..a4426ce --- /dev/null +++ b/doc/state/newbie-guide.md @@ -0,0 +1,84 @@ +## STATE Module Guide + +**1. What's the purpose of the STATE Module?** + +The STATE Module is a state management tool specifically designed for modular components. It offers a structured approach to manage the data (state) that each module and instance requires to function correctly. The primary goal is to simplify data handling, dependency management, and **persistence** (ensuring the application retains its state even after a browser reload) in complex applications. + +**2. What does the STATE Module do?** + +The STATE Module provides the following functionalities: + +* **Persistent Storage :** Leverages `localdb` to store module and instance state in the browser's `localStorage`. This guarantees data persistence across sessions. +* **State Definition :** Enables developers to define initial state using "fallbacks" and modify it through "overrides." +* **Dependency Tracking :** Manages relationships between modules, submodules, and instances, enabling controlled state updates across components. +* **Real-time Updates :** Implements a `watch` mechanism, allowing components to react to state changes and re-render accordingly. +* **Dataset Handling :** Facilitates the management of files or structured data sets associated with modules. + +**3. How does the STATE Module work?** + +The STATE Module operates through the following steps: + +1. **Initialization :** Initializing the state for a module +2. **Fallback Setup :** Each module and instance defines its default state using the `statedb(fallback_instance)` function, which returns an `sdb` object. +3. **Instance Creation :** Create new instances of a submodule using `fallback_instance`. +4. **State Auto Updates :** Modules can modify their own state drive, and submodules/instances automatically render the new content from drive. +5. **Persistence :** The `localdb` automatically persists all state changes to the browser's `localStorage`. + +**4. What is `sdb`?** + +`sdb` represents the **state database** object. It's the main interface returned by the `statedb(fallback_module)` function, offering methods to interact with and manage the state of a specific module or instance. + +**5. What are modules and instances?** + +* **Module :** A module is a reusable unit of code containing specific functionality. It's represented as a function that returns component elements (e.g., a `Button` module, a `Menu` module). +* **Instance :** An instance is a specific occurrence of a module. Each instance has its own unique ID and its own data that may differ from other instances of the same module. + +**6. What are fallbacks?** + +Fallbacks are functions that return the default data or state structure for a module or instance. These are used when the module/instance is first created or when no existing state is found in `localStorage`. The `statedb()` function uses a `fallback_module` as a parameter. Let's break down fallbacks in depth: +* **`fallback_module`**: This is a function that returns a object containing properties `api` and `_`. +* **`fallback_instance`**: This is a function that is assigned to the `api` of the `fallback_module` and it returns a object containing `_` property which is used to create instances from modules defined in `-` property from the `fallback_module` and `drive` property which holds app's data. + +**7. What are `_`, `api`, and `drive` in fallbacks?** + +These are key properties within the object returned by a fallback function: + +* **`_` (Underscore) :** This is a function that returns a object containing `_` property which is used to create instances from modules defined in `-` property from the `fallback_module` and `drive` property which holds app's data. +* **`api` :** A way to create an instance of the module from a `fallback_instance` function. +* **`drive` :** It is defined in `fallback_instance`. This is where the actual data of the app (which contains sub modules) is defined and later changed. + +**8. What is `drive` and datasets?** + +* **`drive` :** The `drive` is a container within the state database that stores the currently active state of a module or instance. The content of the drive can be changed using overrides, and it's linked with a function called `sdb.watch`, which automatically renders the updated content. +* **Datasets :** These are files containing data required for rendering, forming the basic structure of folders and files within the drive. + +**9. How do we represent modules and instances in components?** + +* Modules are represented in the code of `fallback_module` inside the `_` property. +* Instances are represented in `fallback_instance` inside the module which exists in the `_` of `fallback_module`. + +**10. What are `$`, `0`, `1`, `2`, `3`, `4` in fallbacks?** + +Within the `_` property of a fallback, these notations define relationships to modules and instances: + +* **`$` :** Represents the data fetched from the default fallbacks of the core module where the module is coded. An override can be assigned to change the default data (like `api` or `drive`) of the module. +* **`0`, `1`, `2`, ... :** Specifies instances of the module. These are written inside the module from `_` in the `fallback_module`. Each instance can be assigned an override function that changes the data for that specific instance of component. + +**11. What's an override, and how to use it?** + +An override is a mechanism to modify the default state (defined by the fallback) of a module or instance. This allows you to customize a module's behavior or appearance in specific situations *without* changing the core module code. They're a way to override the state of a child module. + +**12. What are SIDs, and how does `sdb.watch(onbatch)` work?** + +* **`SID` :** Stands for "Symbolic ID." It's a unique symbol that the STATE module uses internally to identify modules and instances, preventing naming collisions. +* **`sdb.watch(onbatch)` :** This function registers a listener (`onbatch`) that is triggered whenever the state of the module or instance is modified (basically whenever the `drive` content is changed). + + * **`onbatch` :** A listener function that uses the `on` object to trigger function calls based on the type of `drive` data which is changed. Also returns an array which contains the SIDs of all the sub instances defined in the fallbacks. + +**13. Basic template for creating components explained step by step :-** + +**`@todo`** + +**14. Link a example with step by step explanation for creating components :-** + +**`@todo`** \ No newline at end of file diff --git a/doc/state/rules.md b/doc/state/rules.md new file mode 100644 index 0000000..d0b3bb7 --- /dev/null +++ b/doc/state/rules.md @@ -0,0 +1,6 @@ +# Rules + +1. `Trial`: Fallback needs to be defined for every module that uses `STATE`. +2. `Trial`: Sub-modules `require` statement names should match to fallback sub-nodes `_` exactly. +3. `Stable`: Dataset type names should match the keys of `on` otherwise it will not work as expected. +4. `Stable`: Try to avoid use of async function where possible. \ No newline at end of file diff --git a/doc/state/state-usage.md b/doc/state/state-usage.md new file mode 100644 index 0000000..f4d15cb --- /dev/null +++ b/doc/state/state-usage.md @@ -0,0 +1,271 @@ +## Fallbacks + +Modules and instances are entities created to have their behavior dictated by the input data. The fallback system is designed to manage this data, allowing each node (module or instance) to inherit or override functionality from a parent. This system supports multiple levels of overrides and fallbacks, with constraints on how IDs are used to uniquely identify each sub-instance within a hierarchy. Moreover, it makes sure that the website never reaches a situation where entities have no data to be guided. + +**Rules**: +- Each node (module/instance) has a unique ID in a component except for ID `0`. +- ID `0` is reserved for the root module/instance and is responsible for managing the primary state and possible sub-instances `subs`. +- Sub-instances use IDs `n`, which needs to be unique within the fallbacks inside a component. +- Fallbacks allow for cascading behavior, where higher-level modules or instances can define behaviors or structures which can override default behaviors of lower-level elements. + +### Format + +Each fallback consists of two main parts: +- **Module Fallback**: Defines the hierarchical structure of modules that is critical for defining the next structure. +- **Instance Fallback**: Defines the hierarchical structure of instances which are the building blocks of the website. + ```js + function fallback_module(){ + const old_dataset_name1 = 'foo' + const old_dataset_name2 = 'bar' + const new_dataset_name1 = 'baz' + const new_dataset_name2 = 'meh' + return { + api: fallback_instance + _: { + "": { + 0: override_function || '', + 1: override_function || '', + ... + mapping: { + [new_dataset_name1]: old_dataset_name1, + [new_dataset_name2]: old_dataset_name2, + ... + } + } + "": { + ... + } + }, + drive: { + dataset1: { + file1, + file2, + ... + }, + dataset2: { + ... + } + } + } + function fallback_instance(){ + const old_dataset_name1 = 'foo' + const old_dataset_name2 = 'bar' + const new_dataset_name1 = 'baz' + const new_dataset_name2 = 'meh' + return { + _: { + "": { + 0: override_function || '', + 1: override_function || '', + ... + mapping: { + [new_dataset_name1]: old_dataset_name1, + [new_dataset_name2]: old_dataset_name2, + ... + } + } + "": { + ... + }, + ... + }, + drive: { + dataset1: { + file1, + file2, + ... + }, + dataset2: { + ... + }, + ... + } + } + } + } + ``` +**Explanation**: +- Root props `_` and `drive` are optional +- `_` represents sub-modules. +- `drive` represents the local drive. +- The direct sub-entries of `_` are always sub-modules. +- Sub-modules contains their instances which may consist of an override function +- Mapping is required to match a dataset being passed down to sub-node's dataset having same type but differnet name. +- `drive` contains datasets which are similar to folders but have amazing capabilities of groupiing and switch same kind of data. +- `dataset` contains files. + + +## Overrides + +Overrides allow specific instances or sub-modules to change the default behavior of their sub-nodes. These overrides are defined at the both module and instance level. The behavior of a module or instance is defined the by the data it is fed which is what the overrides will deal with. + +### Format + +The system supports multiple levels of modules and instances, with each level being able to define its own fallbacks and overrides for lower levels. Since, both module and instance fallback have similar structure, only module's format will be shown. The format involves: +- **Shallow Override**: When a node overrides the default data of a sub-node. +```js +function fallback_module () { + return { + _: { + "submodule": override_submodule + } + } + function override_submodule ([submodule]) { + const state = submodule() + state.drive.dataset = { + new_file: { + raw: 'content' + } + } + return state + } +} +``` +- **Deep Override**: When a node overrides deep sub-nodes using the component tree provided by STATE. +```js +function fallback_module () { + return { + _: { + "submodule": override_submodule + } + } + function override_submodule ([submodule]) { + const state = submodule() + state._.sub_module1._sub_module2 = deep_override_submodule + return state + } + function deep_override_submodule ([deep_submodule]) { + const state = deep_submodule() + state.drive.dataset = { + new_file: { + raw: 'content' + } + } + return state + } +} +``` + + +## Example +```js +// given: demo > app > foo > head > nav > menu > (btn | btm.small) > icon + +// 7. make demo (=`FB_MD`) redo the menu override +// demo.js +function FB_MD () { + return { + _ { + app: app_override + } + } + function app_override ([app]) { + const state = app() + state._.foo._.head._.nav._.menu[0] = menu_override + return state + } + function menu_override ([menu]) { + const state = menu() + state.drive.theme['style.css'].raw = 'content' + return state + } + //for more info visit: https://github.com/alyhxn/playproject/blob/main/doc/state/example/page.js#L19 +} +// app.js +// foo.js +// head.js + +// 6. make nav (=`FB_IN`) undo the menu overide for button module instances +// nav.js +function FB_IN () { + return { + _: { + menu: { + 0: menu_override + } + } + } + function menu_override (menu) { + const state = menu() + Object.keys(state._.btn).forEach(id => { + state._.btn[id] = null + }) + return state + } +} + +// 5. make menu code require 2 button module instances, one for small button, one for normal button +// 4. make menu (=`FB_IM`) override button label + reset icon back to original from what button changed +// menu.js +function FB_MM () { + return { + api: FB_IM, + _: { + btn: {}, + } + } + function FB_IM () { + return { + _: { + btn: { + 0: override_btn + }, + 'btn$small': { + 0: override_btn + }, + } + } + }// For more info visit: https://github.com/alyhxn/playproject/blob/main/doc/state/example/node_modules/menu.js#L12 + function override_btn (btn) { + const state = btn() + state.drive.lang['en-us.json'].raw.label ='beep boop' + Object.keys(state._.icon).forEach(id => { + state._.icon[id] = null + }) + return state + } +} + + +// 3. make button override icon `image.svg` +// 2. set button default fallback (=`FB_IB1` + `FB_IB2`) to `label/size` +// btn.js +// FB_MB +function FB_IB () { + return { + _ { + icon: { + 0: icon_override + } + }, + drive:{ + lang: { + 'en-us.json': { + raw: { + label: 'button', + size: 'small', + } + } + } + } + } + function icon_override (icon) { + const state = icon() + state.drive.svgs['image.svg'].raw = `🧸` + return state + } +} //For more info visit: https://github.com/alyhxn/playproject/blob/main/doc/state/example/node_modules/nav.js#L62 + +// 1. make icon set its default fallback (=`FB_II`) using the `image.svg` in the above snippet +// icon.js +// FB_MI +function FB_II () { + return { + drive:{ + svgs: { + 'image.svg': { + raw: `▶️` + } + } + } +} \ No newline at end of file diff --git a/doc/state/state.md b/doc/state/state.md new file mode 100644 index 0000000..52ddb10 --- /dev/null +++ b/doc/state/state.md @@ -0,0 +1,45 @@ + +## Helper Methods + +### `fetch_save(...)` +Fetches content of a file entry and saves it into `DB` + +```js + fetch_save(file_entry) +``` +- **Params:** + - `file_entry`: *Object* + A file's state from `DB` +- **Returns** + - `result` : *Object* or *String* + The content of the file + +### `symbolfy(...)` +Converts the sub-module IDs into symbols and maps them for internal tracking. This is used to handle references between components and modules. + +```js + symbolfy(data) +``` +- **Params:** + - `data`: *Object* + The state data containing components and sub-module IDs. + +- **Returns:** + `void` + +--- + +## Internal Data Structures + +- `s2i`: *Object* + Maps symbols to instance IDs. + +- `i2s`: *Object* + Maps instance IDs to symbols. + +- `admins`: *Array* + Stores a list of admin IDs that have special access permissions. + +--- + +This documentation captures the key methods, parameters, and internal workings of the `STATE` module. It's designed for managing state data in modular systems, with provisions for admin management, database access, and component-module mappings. \ No newline at end of file diff --git a/doc/state/temp.md b/doc/state/temp.md new file mode 100644 index 0000000..1eb6bc0 --- /dev/null +++ b/doc/state/temp.md @@ -0,0 +1,295 @@ +# Using the `STATE` Module + +## Initializing `STATE` +```js +const STATE = require('STATE') +const state_db = STATE(__filename) +const { sdb, get, io } = state_db(fallback_module) +``` +1. Import `STATE` and pass `__filename` (a built-in variable containing the file's path) to it. +2. The returned `state_db` function registers fallbacks and provides `sdb` (the main interface) and `get` (a function for accessing the staet of an instance). +3. This initialization is consistent across most modules. + +--- +## Defining Fallbacks +```js +function fallback_module() { + //..... +} +``` +1. Fallbacks provide default data when custom data is absent. +2. Defined as functions instead of objects for flexibility (`@TODO` for further explanation). +3. Two fallback functions are commonly used: + - `fallback_module()` (module-level fallback) + - `fallback_instance()` (instance-level fallback) + +### Fallback Syntax +The fallback structure follows a specific format: + +**Pattern:** +`"name1|name2:*:type1|type2"` +- `:` separates parameters. +- `|` represents an OR condition. +- `*` denotes a required key. + +A key consists of three parameters: +1. Allowed key names or reserved keywords (`name1|name2`). +2. Whether the key is required (`*` for required, optional if absent). +3. Allowed value types (`type1|type2`). + +#### Rules +```js +const expected_structure = { + 'api::function': () => {}, + '_::object': { + ":*:object": xtype === 'module' ? { + ":*:function|string|object": '', + "mapping::": {} + } : { // Required key, any name allowed + ":*:function|string|object": () => {}, // Optional key + "mapping::": {} + }, + }, + 'drive::object': { + "::object": { + "::object": { // Required key, any name allowed + "raw|$ref:*:object|string": {}, // data or $ref are names, required, object or string are types + "$ref": "string" + } + }, + }, + 'net::array': [] +} +``` + +### Fallback Semantics +```js +function fallback_module() { + return { + api: fallback_instance, + _: { + sub_module: { + $: '', + instance_number: override_sub_module, + }, + }, + drive: { + dataset_type: { + file: { + raw: {}, + link: '', + }, + }, + }, + } + + function fallback_instance() { + return { + _: { + sub_module: { + instance_number: sub_instance$, + }, + }, + drive: { + dataset_type: { + file: { + raw: {}, + link: '', + }, + }, + }, + net: { + node_id: { + event_name: message_to_node_id + } + } + } + } + + function override_sub_module() { + // See template.js + } +} +``` + +#### Key Descriptions +1. **`_` (Sub-Nodes)** + - Represents sub-modules of the current node. + - Reserved and optional but must always be an object. + + - **`sub_module`** + - A required module name. + - Can be numbered (e.g., `module:1`) if duplicates exist. + - Used to group instances of a module. + - Must contain items to be meaningful. + + - **`$`** (Module Creation/Import) + - Reserved key for creating/importing a module. + - Accepts a `string` or `function`. + - Empty string means default data is used. + - A function allows overriding default data. + + - **Instance Numbers (`number`)** + - Represents numbered instances (e.g., `1`, `2`). + - Same behavior as the `$` key. + + - **`mapping`** + - Reserved key for mapping datasets with sub-datasets by `dataset_type` + - Each dataset used by sub-node needs to be mapped to a dataset of its super + + +2. **`drive` (Storage)** + - Represents stored data accessible by the node. + - Allows smooth insertion and modification of node data. + + - **`dataset_type`** + - Represents datasets categorized by type. + - A node cannot use multiple datasets of the same type. + + - **`file`** (File Storage) + - **`raw`** (Raw Content) + - Stores raw file content as an `object` or `string`. + - **`$ref`** (External File Link) + - Stores a link to an external file (any type), the file needs to be inside the module folder. + - **`net`** (Communication) + - **`node_id`**(Node addrress/Decoded ID) + - Used in override to share the encoded ID of a receiver to a sender node + - **`event_name`** (Message condition) + - Send a message on this event + - **`message_to_node_id`** (Message) + - The content of the message + + +--- +## Module Structure and Usage + +### Basic Module Setup +```js +module.exports = module_name +async function module_name(opts) { + const { id, sdb } = await get(opts.sid) + // ... module implementation +} +``` +1. The module follows a consistent pattern where it exports a function as an instance. +2. `opts` contains the `sid`(Symbol ID) and `type`(Module's ID) of the instance created. +3. The provided `sid` can then be used to access `sdb` interface using get API. +4. `sdb` interface provides access to a number of different API for STATE management. + +### State Management +The `STATE` module provides several key features for state management: + +#### 1. Instance Isolation + - Each instance of a module gets its own isolated state + - State is accessed through the `sdb` interface + - Instances can be created and destroyed independently + +#### 2. sdb Interface +Provides access to following two APIs: + +**sdb.watch(onbatch)** +```js +const subs = await sdb.watch(onbatch) +const { drive } = sdb +async function onbatch(batch){ + for (const {type, paths} of batch) { + const data = await Promise.all(paths.map(path => drive.get(path).then(file => file.raw))) + on[type] && on[type](data) + } +} +``` +- Modules can watch for state changes +- Changes are batched and processed through the `onbatch` handler +- Different types of changes can be handled separately using `on`. +- `type` refers to the `dataset_type` used in fallbacks. The key names need to match. E.g. see `template.js` +- `paths` refers to the paths to the files inside the dataset. + +**sdb.get_sub** + @TODO +**sdb.drive** +The `sdb.drive` object provides an interface for managing datasets and files attached to the current node. It allows you to list, retrieve, add, and check files within datasets defined in the module's state. + +- **sdb.drive.list(path?)** + - Lists all dataset names (as folders) attached to the current node. + - If a `path` (dataset name) is provided, returns the list of file names within that dataset. + - Example: + ```js + const datasets = sdb.drive.list(); // ['mydata/', 'images/'] + const files = sdb.drive.list('mydata/'); // ['file1.json', 'file2.txt'] + ``` + +- **sdb.drive.get(path)** + - Retrieves a file object from a dataset. + - `path` should be in the format `'dataset_name/filename.ext'`. + - Returns an object: `{ id, name, type, raw }` or `null` if not found. + - Example: + ```js + const file = sdb.drive.get('mydata/file1.json'); + // file: { id: '...', name: 'file1.json', type: 'json', raw: ... } + ``` + +- **sdb.drive.put(path, buffer)** + - Adds a new file to a dataset. + - `path` is `'dataset_name/filename.ext'`. + - `buffer` is the file content (object, string, etc.). + - Returns the created file object: `{ id, name, type, raw }`. + - Example: + ```js + sdb.drive.put('mydata/newfile.txt', 'Hello World'); + ``` + +- **sdb.drive.has(path)** + - Checks if a file exists in a dataset. + - `path` is `'dataset_name/filename.ext'`. + - Returns `true` if the file exists, otherwise `false`. + - Example: + ```js + if (sdb.drive.has('mydata/file1.json')) { /* ... */ } + ``` + +**Notes:** +- Dataset names are defined in the fallback structure and must be unique within a node. +- File types are inferred from the file extension. +- All file operations are isolated to the current node's state and changes are persisted immediately. + +### Shadow DOM Integration + ```js + const el = document.createElement('div') + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + ``` + - Modules can create isolated DOM environments + - Styles and markup are encapsulated + - Prevents style leakage between modules + + **Sub-Module Integration** + ```js + const sub_module = require('sub_module_address') + // ... + shadow.append(await sub_module(subs[0])) + ``` + - Sub-modules can be dynamically loaded + - State can be passed down to sub-modules + - Hierarchical module structure is supported + - The `SID` of a sub-instance needs to match the instance as defined in the fallback. + - @TODO The `SID` of sub-modules are currently useless, they need to be ignored for now. +### Best Practices + +1. **State Organization** + - Keep state structure consistent with fallback definitions + - Use meaningful names for datasets and files + - Group related data together + +2. **Error Handling** + - Always handle async operations properly + - Validate state updates before applying them + - Provide fallback values for missing data + +3. **Performance** + - Minimize state updates + - Batch related changes together + - Clean up watchers when no longer needed + + +This documentation provides a comprehensive guide to using the `STATE` module effectively in your applications. For more specific examples and advanced usage patterns, refer to the example modules in the codebase. + diff --git a/doc/state/usage.md b/doc/state/usage.md new file mode 100644 index 0000000..0e5c10b --- /dev/null +++ b/doc/state/usage.md @@ -0,0 +1,160 @@ +## `STATE` Module + +The `STATE` module is a database management system that enables managing state data across multiple modules or instances. It provides methods for setting up state databases, accessing state data, managing admins, and handling permissions. + +--- + +## Module Functions + +### `STATE(...)` +Creates an instance of the state database for a specific module or initializes the root database. Returns methods for interacting with the state data. + +```js + const statedb = STATE({ modulename }) +``` +- **Params:** + - `modulename`: *String* (optional) + The name of the module to create a state instance for. If not provided, it initializes the root state database. + +- **Returns:** + `Object` — Returns `statedb`. + +--- + +### `statedb(...)` +Initializes or retrieves a state database for the provided module name. This method will either return the state for the given `modulename` or invoke a fallback function to initialize default data. + + +```js + const { id, sdb, getdb, admin } = statedb(db => { + return require('data.json') + }) +``` +- **Params:** + - `fallback`: *Function* + A callback function that provides default data when no state exists for the module. + +- **Returns:** + `Object` — Contains the following properties: + - `id`: *Number* — The ID of the current module. + - `sdb`: *Object* — Contains methods (`on`, `sub`, `req_access`) for interacting with state data. + - `getdb`: *Function* — Method for initializing the instance state data. +--- + +### `statedb_root(...)` +Initializes the root state database. This is called when no specific `modulename` is provided. It loads or initializes the root database, sets up admin permissions, and populates default data. + +```js + const { id, sdb, getdb } = statedb(async db => { + return await (await fetch('data.json')).json() + }) +``` +- **Params:** + - `fallback`: *Function* + A callback function to provide default data for the root state database. + +- **Returns:** + `Object` — Contains the following properties: + - `id`: *Number* — The ID of the root database. + - `sdb`: *Object* — Methods (`on`, `sub`, `req_access`) for interacting with the state database. + - `getdb`: *Function* — Retrieves a state database by session ID. + - `admin`: *Object* — Contains admin-specific methods (`xget`, `add_admins`). + +--- + +## Methods in `sdb` + +### `getdb(...)` +Retrieves or initializes a state database for a given session ID (`sid`). If no state exists, it calls the provided fallback function to populate default data. + +```js + const { id, sdb } = await getdb(sid, fallback) +``` +- **Params:** + - `sid`: *Symbol* + The session ID of the requested state. + - `fallback`: *Function* + A fallback function to provide default state data if the session ID is not found. + +- **Returns:** + `Object` — Contains the following properties: + - `id`: *Number* — The ID of the session. + - `sdb`: *Object* — Provides methods to interact with the session data. + +--- + +### `sdb.watch(...)` +Retrieves the current usage of components in the state. It tracks data for each component by its ID. + +```js + const subs = await sdb.watch(onbatch) + function onbatch (batch) { } +``` +- **Params:** + - `onbatch`: *Function* + A function handles an changes to input. + +- **Returns:** + `Object` — Returns the sub-instance object that stores component and module mappings. + +--- + +### `sdb.get_sub(...)` +Returns all the sub-modules associated with the given component `name`. + +```js + const card_sids = sdb.get_sub('card') +``` +- **Params:** + - `name`: *String* + The name of the component whose sub-modules are being requested. + +- **Returns:** + `Array` — An array of sub-module symbols for the specified component. + +--- + +### `sdb.req_access(...)` +Checks if a given session ID (`sid`) has admin-level access. If the session belongs to an admin, it returns the admin object. +```js + const admin = sdb.req_access(sid) +``` +- **Params:** + - `sid`: *Symbol* + The session ID of the requested state. + +- **Returns:** + `Object` — Returns the `admin` object if the session has access. Throws an error if access is denied. + +--- + +## Admin Methods + +### `admin.xget(...)` +Fetches state data for a given ID from the root database. This is an admin-only operation. + +```js + const data = admin.xget(id) +``` +- **Params:** + - `id`: *Number* + The ID of the state data to retrieve. + +- **Returns:** + `Object` — The state data associated with the provided ID. + +--- + +### `admin.add_admins(...)` +Adds new admin IDs to the list of authorized admins. + +```js + admin.add_admins(ids || modulename) +``` +- **Params:** + - `ids or modulename`: *Array* + An array of admin IDs/Modules to be added to the admin list. + +- **Returns:** + `void` + diff --git a/index.json b/index.json new file mode 100644 index 0000000..a2e1ed4 --- /dev/null +++ b/index.json @@ -0,0 +1,168 @@ +[ + ".github/FUNDING.yml", + ".gitignore", + "CNAME", + "LICENSE", + "README.md", + "bin/tree.js", + "bundle.js", + "data.json", + "doc/README.md", + "doc/STATE/state.md", + "doc/STATE/usage.md", + "index.html", + "index.json", + "package-lock.json", + "package.json", + "snapshot.json", + "src/content/graph_explorer.json", + "src/content/topnav.json", + "src/index.js", + "src/instance.json", + "src/module.json", + "src/node_modules/Icon.js", + "src/node_modules/STATE.js", + "src/node_modules/assets/images/avatar-ailin.png", + "src/node_modules/assets/images/avatar-alex.png", + "src/node_modules/assets/images/avatar-david.png", + "src/node_modules/assets/images/avatar-fiona.png", + "src/node_modules/assets/images/avatar-helen.png", + "src/node_modules/assets/images/avatar-jannis.png", + "src/node_modules/assets/images/avatar-joshua.png", + "src/node_modules/assets/images/avatar-kayla.png", + "src/node_modules/assets/images/avatar-mauve.png", + "src/node_modules/assets/images/avatar-mimi.png", + "src/node_modules/assets/images/avatar-nina.png", + "src/node_modules/assets/images/avatar-nora.png", + "src/node_modules/assets/images/avatar-pepe.png", + "src/node_modules/assets/images/avatar-santies.png", + "src/node_modules/assets/images/avatar-tommings.png", + "src/node_modules/assets/images/avatar-toshi.png", + "src/node_modules/assets/images/favicon/android-chrome-192x192.png", + "src/node_modules/assets/images/favicon/android-chrome-512x512.png", + "src/node_modules/assets/images/favicon/apple-touch-icon.png", + "src/node_modules/assets/images/favicon/favicon-16x16.png", + "src/node_modules/assets/images/favicon/favicon-32x32.png", + "src/node_modules/assets/images/favicon/favicon.ico", + "src/node_modules/assets/images/favicon/site.webmanifest", + "src/node_modules/assets/images/smart-contract-codes.jpg", + "src/node_modules/assets/images/smart-contract-ui.jpg", + "src/node_modules/assets/svg/big-tree.svg", + "src/node_modules/assets/svg/bird-left.svg", + "src/node_modules/assets/svg/bird-right.svg", + "src/node_modules/assets/svg/blockchian-island.svg", + "src/node_modules/assets/svg/blossom-island.svg", + "src/node_modules/assets/svg/blossom-tree.svg", + "src/node_modules/assets/svg/card1.svg", + "src/node_modules/assets/svg/card2.svg", + "src/node_modules/assets/svg/card3.svg", + "src/node_modules/assets/svg/card4.svg", + "src/node_modules/assets/svg/cloud.svg", + "src/node_modules/assets/svg/crystal-blue.svg", + "src/node_modules/assets/svg/crystal-purple.svg", + "src/node_modules/assets/svg/crystal-yellow.svg", + "src/node_modules/assets/svg/deco-island.svg", + "src/node_modules/assets/svg/email.svg", + "src/node_modules/assets/svg/energy-island.svg", + "src/node_modules/assets/svg/floating-island.svg", + "src/node_modules/assets/svg/floating-island1.svg", + "src/node_modules/assets/svg/floating-island2.svg", + "src/node_modules/assets/svg/floating-island3.svg", + "src/node_modules/assets/svg/github.svg", + "src/node_modules/assets/svg/gitter.svg", + "src/node_modules/assets/svg/life-island.svg", + "src/node_modules/assets/svg/logo.svg", + "src/node_modules/assets/svg/play-island.svg", + "src/node_modules/assets/svg/single-tree.svg", + "src/node_modules/assets/svg/single-tree1.svg", + "src/node_modules/assets/svg/single-tree2.svg", + "src/node_modules/assets/svg/single-tree3.svg", + "src/node_modules/assets/svg/small-stone.svg", + "src/node_modules/assets/svg/stone.svg", + "src/node_modules/assets/svg/stone1.svg", + "src/node_modules/assets/svg/sun.svg", + "src/node_modules/assets/svg/twitter.svg", + "src/node_modules/assets/svg/two-trees.svg", + "src/node_modules/assets/svg/waterfall-island.svg", + "src/node_modules/content/content.js", + "src/node_modules/content/data.json", + "src/node_modules/content/package.json", + "src/node_modules/contributor/contributor.js", + "src/node_modules/contributor/data.json", + "src/node_modules/contributor/package.json", + "src/node_modules/crystal_island/crystal_island.js", + "src/node_modules/crystal_island/data.json", + "src/node_modules/crystal_island/package.json", + "src/node_modules/css/dark/our_contributors.css", + "src/node_modules/css/default/content.css", + "src/node_modules/css/default/contributor.css", + "src/node_modules/css/default/contributor_1.css", + "src/node_modules/css/default/contributor_2.css", + "src/node_modules/css/default/crystal_island.css", + "src/node_modules/css/default/datdot.css", + "src/node_modules/css/default/demo.css", + "src/node_modules/css/default/editor.css", + "src/node_modules/css/default/footer.css", + "src/node_modules/css/default/graph_explorer.css", + "src/node_modules/css/default/header.css", + "src/node_modules/css/default/index.css", + "src/node_modules/css/default/our_contributors.css", + "src/node_modules/css/default/smartcontract_codes.css", + "src/node_modules/css/default/supporters.css", + "src/node_modules/css/default/theme_editor.css", + "src/node_modules/css/default/theme_widget.css", + "src/node_modules/css/default/topnav.css", + "src/node_modules/css/index.json", + "src/node_modules/datdot/data.json", + "src/node_modules/datdot/datdot.js", + "src/node_modules/datdot/package.json", + "src/node_modules/editor/data.json", + "src/node_modules/editor/editor.js", + "src/node_modules/editor/package.json", + "src/node_modules/fetch-data.js", + "src/node_modules/footer/data.json", + "src/node_modules/footer/footer.js", + "src/node_modules/footer/package.json", + "src/node_modules/graph_explorer/graph_explorer.js", + "src/node_modules/graph_explorer/instance.json", + "src/node_modules/graph_explorer/module.json", + "src/node_modules/graph_explorer/node_modules/helper.js", + "src/node_modules/graph_explorer/package.json", + "src/node_modules/graphic.js", + "src/node_modules/header/data.json", + "src/node_modules/header/header.js", + "src/node_modules/header/package.json", + "src/node_modules/io.js", + "src/node_modules/lang/en-us.json", + "src/node_modules/lang/fr.json", + "src/node_modules/lang/ja.json", + "src/node_modules/lang/th.json", + "src/node_modules/lang/zh-tw.json", + "src/node_modules/loadSVG.js", + "src/node_modules/localdb.js", + "src/node_modules/our_contributors/data.json", + "src/node_modules/our_contributors/our_contributors.js", + "src/node_modules/our_contributors/package.json", + "src/node_modules/parallax.js", + "src/node_modules/smartcontract_codes/data.json", + "src/node_modules/smartcontract_codes/package.json", + "src/node_modules/smartcontract_codes/smartcontract_codes.js", + "src/node_modules/supporters/data.json", + "src/node_modules/supporters/package.json", + "src/node_modules/supporters/supporters.js", + "src/node_modules/theme_editor/instance.json", + "src/node_modules/theme_editor/module.json", + "src/node_modules/theme_editor/package.json", + "src/node_modules/theme_editor/theme_editor.js", + "src/node_modules/theme_widget/instance.json", + "src/node_modules/theme_widget/module.json", + "src/node_modules/theme_widget/package.json", + "src/node_modules/theme_widget/theme_widget.js", + "src/node_modules/topnav/instance.json", + "src/node_modules/topnav/module.json", + "src/node_modules/topnav/package.json", + "src/node_modules/topnav/topnav.js", + "web/demo.js", + "web/node_modules/theme.js", + "web/temp.json" +] \ No newline at end of file diff --git a/package.json b/package.json index 3e0b41f..4f6196f 100755 --- a/package.json +++ b/package.json @@ -4,8 +4,17 @@ "description": "Playproject.io official landing page", "main": "src/index.js", "scripts": { - "start": "budo demo/demo.js:bundle.js --dir ./ --live --open", - "build": "browserify demo/demo.js -o bundle.js" + "start": "budo web/boot.js:bundle.js --dir ./ --live --open", + "build": "browserify web/boot.js -o bundle.js", + "lint": "standardx --fix", + "start:doc": "budo doc/state/example/boot.js:doc/state/example/bundle.js --dir ./ --live --open", + "build:doc": "browserify doc/state/example/boot.js > doc/state/example/bundle.js", + "start:doc2": "budo doc/state/example2/boot.js:doc/state/example2/bundle.js --dir ./ --live --open", + "build:doc2": "browserify doc/state/example2/boot.js > doc/state/example2/bundle.js", + "start:doc3": "budo doc/state/example3/boot.js:doc/state/example3/bundle.js --dir ./ --live --open", + "build:doc3": "browserify doc/state/example3/boot.js > doc/state/example3/bundle.js", + "start:doc4": "budo doc/state/example4/boot.js:doc/state/example4/bundle.js --dir ./ --live --open", + "build:doc4": "browserify doc/state/example4/boot.js > doc/state/example4/bundle.js" }, "repository": { "type": "git", @@ -34,12 +43,11 @@ }, "homepage": "https://github.com/fionataeyang/playproject-io.github.io#readme", "devDependencies": { - "budo": "^11.6.3" + "budo": "^11.6.3", + "eslint-config-standardx": "^16.0.3", + "standardx": "^7.0.0" }, "dependencies": { - "bel": "^6.0.0", - "csjs-inject": "^1.0.1", - "rellax": "^1.12.0", - "zenscroll": "^4.0.2" + "rellax": "^1.12.0" } } diff --git a/snapshot.json b/snapshot.json new file mode 100644 index 0000000..4fe7683 --- /dev/null +++ b/snapshot.json @@ -0,0 +1,320 @@ +{ + "0": { + "id": 0, + "name": "demo", + "type": "module", + "xtype": "demo", + "admins": ["theme_editor", "theme_widget"], + "slot": { + "": [["", "subs"]], + "subs": [1] + } + }, + "1": { + "id": 1, + "name": "demo", + "type": "instance", + "xtype": "demo", + "slot": { + "": [["hubs", "subs"], ["inputs"]], + "hubs": [0], + "subs": [7], + "inputs": [10] + } + }, + "2": { + "id": 2, + "name": "modules", + "type": "folder", + "slot": { + "": [["", "subs"]], + "subs": [6, 12, 16, 21, 25] + } + }, + "3": { + "id": 3, + "name": "css", + "type": "folder", + "slot": { + "": [["", "subs"]], + "subs": [14, 18, 23, 27] + } + }, + "4": { + "id": 4, + "name": "content", + "type": "folder", + "slot": { + "": [["", "subs"]], + "subs": [19, 28] + } + }, + "5": { + "id": 5, + "name": "source", + "type": "folder", + "slot": { + "": [["", "subs"]], + "subs": [9, 11, 15, 20, 24] + } + }, + "6": { + "id": 6, + "name": "index", + "type": "module", + "xtype": "index", + "slot": { + "": [["hubs", "subs"]], + "hubs": [2, 9], + "subs": [7] + } + }, + "7": { + "id": 7, + "name": "index", + "type": "instance", + "xtype": "index", + "slot": { + "": [["hubs", "subs"], ["inputs"]], + "hubs": [1, 6, 9], + "inputs": [8], + "subs": [13, 26] + } + }, + "8": { + "id": 8, + "name": "index.css", + "type": "css", + "xtype": "css", + "file": "src/node_modules/css/default/index.css", + "slot": { + "": [["hub"]], + "hubs": [3, 7] + } + }, + "9": { + "id": 9, + "name": "index.js", + "type": "js", + "xtype": "js", + "file": "src/index.js", + "slot": { + "": [["hubs", "subs"]], + "hubs": [5], + "subs": [6, 7] + } + }, + "10": { + "id": 10, + "name": "demo.css", + "type": "css", + "xtype": "css", + "file": "src/node_modules/css/default/demo.css", + "slot": { + "": [["hub"]], + "hubs": [3, 1] + } + }, + "11": { + "id": 11, + "name": "theme_widget.js", + "type": "js", + "xtype": "js", + "file": "src/node_modules/theme_widget/theme_widget.js", + "slot": { + "": [["hubs", "subs"]], + "hubs": [5], + "subs": [12, 13] + } + }, + "12": { + "id": 12, + "name": "theme_widget", + "type": "module", + "xtype": "theme_widget", + "slot": { + "": [["hubs", "subs"]], + "hubs": [2, 11], + "subs": [13] + } + }, + "13": { + "id": 13, + "name": "theme_widget", + "type": "instance", + "xtype": "theme_widget", + "slot": { + "": [["hubs", "subs"], ["inputs"]], + "hubs": [12, 7, 11], + "inputs": [14], + "subs": [17, 22] + } + }, + "14": { + "id": 14, + "name": "theme_widget.css", + "type": "css", + "xtype": "css", + "file": "src/node_modules/css/default/theme_widget.css", + "slot": { + "": [["hubs"]], + "hubs": [3, 13] + } + }, + "15": { + "id": 15, + "name": "graph_explorer.js", + "type": "js", + "xtype": "js", + "file": "src/node_modules/graph_explorer/graph_explorer.js", + "slot": { + "": [["hubs", "subs"]], + "hubs": [5], + "subs": [16, 17] + } + }, + "16": { + "id": 16, + "name": "graph_explorer", + "type": "module", + "xtype": "graph_explorer", + "slot": { + "": [["hubs", "subs"]], + "hubs": [2, 15], + "subs": [17] + } + }, + "17": { + "id": 17, + "name": "graph_explorer", + "type": "instance", + "xtype": "graph_explorer", + "slot": { + "": [["hubs"], ["inputs"]], + "hubs": [16, 13, 15], + "inputs": [18, 19] + } + }, + "18": { + "id": 18, + "name": "graph_explorer.css", + "type": "css", + "xtype": "css", + "file": "src/node_modules/css/default/graph_explorer.css", + "slot": { + "": [["hubs"]], + "hubs": [3, 17] + } + }, + "19": { + "id": 19, + "name": "graph_explorer.json", + "type": "json", + "xtype": "content", + "file": "src/content/graph_explorer.json", + "slot": { + "": [["hubs"]], + "hubs": [4, 17] + } + }, + "20": { + "id": 20, + "name": "theme_editor.js", + "type": "js", + "xtype": "js", + "file": "src/node_modules/theme_editor/theme_editor.js", + "slot": { + "": [["hubs", "subs"]], + "hubs": [5], + "subs": [21, 22] + } + }, + "21": { + "id": 21, + "name": "theme_editor", + "type": "module", + "xtype": "theme_editor", + "slot": { + "": [["hubs", "subs"]], + "hubs": [2, 20], + "subs": [22] + } + }, + "22": { + "id": 22, + "name": "theme_editor", + "type": "instance", + "xtype": "theme_editor", + "slot": { + "": [["hubs"], ["inputs"]], + "hubs": [21, 13, 20], + "inputs": [23] + } + }, + "23": { + "id": 23, + "name": "theme_editor.css", + "type": "css", + "xtype": "css", + "file": "src/node_modules/css/default/theme_editor.css", + "slot": { + "": [["hubs"]], + "hubs": [3, 22] + } + }, + "24": { + "id": 24, + "name": "topnav.js", + "type": "js", + "xtype": "js", + "file": "src/node_modules/topnav/topnav.js", + "slot": { + "": [["hubs", "subs"]], + "hubs": [5], + "subs": [25, 26] + } + }, + "25": { + "id": 25, + "name": "topnav", + "type": "module", + "xtype": "topnav", + "slot": { + "": [["hubs", "subs"]], + "hubs": [2, 24], + "subs": [26] + } + }, + "26": { + "id": 26, + "name": "topnav", + "type": "instance", + "xtype": "topnav", + "slot": { + "": [["hubs"], ["inputs"]], + "hubs": [25, 7, 24], + "inputs": [27, 28] + } + }, + "27": { + "id": 27, + "name": "topnav.css", + "type": "css", + "xtype": "css", + "file": "src/node_modules/css/default/topnav.css", + "slot": { + "": [["hubs"]], + "hubs": [3, 26] + } + }, + "28": { + "id": 28, + "name": "topnav.json", + "type": "content", + "file": "src/content/topnav.json", + "slot": { + "": [["hubs"]], + "hubs": [4, 26] + } + } +} diff --git a/src/app.js b/src/app.js new file mode 100755 index 0000000..51ed2ea --- /dev/null +++ b/src/app.js @@ -0,0 +1,108 @@ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'app' +const statedb = STATE(__filename) +const shopts = { mode: 'closed' } +// ---------------------------------------- +const { sdb, subs: [get], sub_modules } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return { + _: { + topnav: {}, + theme_widget: {}, + header: {}, + footer: {} + } + } +} +function fallback_instance () { + return { + _: { + topnav: {}, + theme_widget: {}, + header: {}, + footer: {} + }, + inputs: { + 'app.css': { + $ref: new URL('src/node_modules/css/default/app.css', location).href + } + } + } +} +function override ([topnav]) { + const data = topnav() + console.log(data) + data['topnav.json'].data.links.push({ + id: 'app', + text: 'app', + url: 'app' + }) + return data +} +/****************************************************************************** + MAKE_PAGE COMPONENT +******************************************************************************/ +const IO = require('io') +const modules = { + [sub_modules.theme_widget]: require('theme_widget'), + [sub_modules.topnav]: require('topnav'), + [sub_modules.header]: require('header'), + // datdot : require('datdot'), + // editor : require('editor'), + // smartcontract_codes : require('smartcontract_codes'), + // supporters : require('supporters'), + // our_contributors : require('our_contributors'), + [sub_modules.footer]: require('footer') +} +module.exports = app + +async function app (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const on = { + jump, + css: inject + } + + const send = await IO(id, name, on) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+ ` + const style = shadow.querySelector('style') + const main = shadow.querySelector('div') + + const subs = await sdb.watch(onbatch) + + console.log(subs, modules) + main.append(...await Promise.all( + subs.map(async ({ sid, type }) => { + const el = document.createElement('div') + el.name = type + const shadow = el.attachShadow(shopts) + shadow.append(await modules[type]({ sid, hub: [id] })) + return el + }))) + return el + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } + async function jump ({ data }) { + main.querySelector('#' + data).scrollIntoView({ behavior: 'smooth' }) + } + async function inject (data) { + style.innerHTML = data.join('\n') + } +} diff --git a/src/index.js b/src/index.js deleted file mode 100755 index 770d81f..0000000 --- a/src/index.js +++ /dev/null @@ -1,56 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') - -// pages -const topnav = require('topnav') -const Header = require('header') -const datdot = require('datdot') -const editor = require('editor') -const smartcontract_codes = require('smartcontract-codes') -const supporters = require('supporters') -const our_contributors = require('our-contributors') -const Footer = require('footer') -const fetch_data = require('fetch-data') - -module.exports = make_page - -function make_page(opts, done, lang) { - switch(lang) { - case 'zh-tw': - case 'ja': - case 'th': - case 'fr': - var path = `./src/node_modules/lang/${lang}.json` - break - default: - var path = `./src/node_modules/lang/en-us.json` - } - fetch_data(path).then(async (text) => { - let { menu, header, section1, section2, section3, section4, section5, footer } = text.pages - const {theme} = opts - const css = styles - const landingPage = bel` -
- ${await topnav(menu)} - ${await Header(header)} - ${await datdot(section1)} - ${await editor(section2)} - ${await smartcontract_codes(section3)} - ${await supporters(section4)} - ${await our_contributors(section5)} - ${await Footer(footer)} -
` - return done(null, landingPage) - - }).catch( err => { - return done(err, null) - }) -} - -const styles = csjs` -.wrap { - background: var(--bodyBg); -} -[class^="cloud"] { - transition: left 0.6s, bottom 0.5s, top 0.5s linear; -}` diff --git a/src/node_modules/Icon.js b/src/node_modules/Icon.js index 0411d17..05358b2 100755 --- a/src/node_modules/Icon.js +++ b/src/node_modules/Icon.js @@ -1,7 +1,7 @@ -const bel = require('bel') function Icon(svg) { - const el = bel`
${svg}
` + const el = document.createElement('div') + el.innerHTML = `${svg}` return el } diff --git a/src/node_modules/STATE.js b/src/node_modules/STATE.js new file mode 100644 index 0000000..31abbf1 --- /dev/null +++ b/src/node_modules/STATE.js @@ -0,0 +1,1118 @@ +const localdb = require('localdb') +const io = require('io') + +const db = localdb() +/** Data stored in a entry in db by STATE (Schema): + * id (String): Node Path + * name (String/Optional): Any (To be used in theme_widget) + * type (String): Module Name for module / Module id for instances + * hubs (Array): List of hub-nodes + * subs (Array): List of sub-nodes + * inputs (Array): List of input files + */ +// Constants and initial setup (global level) +const VERSION = 13 +const HELPER_MODULES = ['io', 'localdb', 'STATE'] +const FALLBACK_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#defining-fallbacks' +const FALLBACK_SYNTAX_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#key-descriptions' +const FALLBACK_SUBS_POST_ERROR = '\nFor more info visit https://github.com/alyhxn/playproject/blob/main/doc/state/temp.md#shadow-dom-integration' +const status = { + root_module: true, + root_instance: true, + overrides: {}, + tree: {}, + tree_pointers: {}, + modulepaths: {}, + inits: [], + open_branches: {}, + db, + local_statuses: {}, + listeners: {}, + missing_supers: new Set(), + imports: {}, + expected_imports: {}, + used_ids: new Set(), + a2i: {}, + i2a: {}, + s2i: {}, + i2s: {}, + services: {}, + args: {}, + helpers: { + listify + } +} +window.STATEMODULE = status + +// Version check and initialization + +status.fallback_check = Boolean(check_version()) +status.fallback_check && db.add(['playproject_version'], VERSION) + + +// Symbol mappings +let admins = [0] + +// Inner Function +function STATE (address, modulepath, dependencies) { + !status.ROOT_ID && (status.ROOT_ID = modulepath) + status.modulepaths[modulepath] = 0 + //Variables (module-level) + + const local_status = { + name: extract_filename(address), + module_id: modulepath, + deny: {}, + sub_modules: [], + sub_instances: {} + } + status.local_statuses[modulepath] = local_status + return statedb + + function statedb (fallback) { + const data = fallback(status.args[modulepath], { listify: tree => listify(tree, modulepath), tree: status.tree_pointers[modulepath] }) + local_status.fallback_instance = data.api + const super_id = modulepath.split(/>(?=[^>]*$)/)[0] + + if(super_id === status.current_node){ + status.expected_imports[super_id].splice(status.expected_imports[super_id].indexOf(modulepath), 1) + } + else if((status?.current_node?.split('>').length || 0) < super_id.split('>').length){ + let temp = super_id + while(temp !== status.current_node && temp.includes('>')){ + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } + } + else{ + let temp = status.current_node + while(temp !== super_id && temp.includes('>')){ + status.open_branches[temp] = 0 + temp = temp.split(/>(?=[^>]*$)/)[0] + } + } + + if(data._){ + status.open_branches[modulepath] = Object.values(data._).filter(node => node).length + status.expected_imports[modulepath] = Object.keys(data._) + status.current_node = modulepath + } + + local_status.fallback_module = new Function(`return ${fallback.toString()}`)() + verify_imports(modulepath, dependencies, data) + const updated_status = append_tree_node(modulepath, status) + Object.assign(status.tree_pointers, updated_status.tree_pointers) + Object.assign(status.open_branches, updated_status.open_branches) + status.inits.push(init_module) + + // console.log(Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)) + if(!Object.values(status.open_branches).reduce((acc, curr) => acc + curr, 0)){ + status.inits.forEach(init => init()) + } + + const sdb = create_statedb_interface(local_status, modulepath, xtype = 'module') + sdb.id = modulepath + status.dataset = sdb.private_api + + const get = init_instance + const extra_fallbacks = Object.entries(local_status.fallback_instance || {}) + extra_fallbacks.length && extra_fallbacks.forEach(([key]) => { + get[key] = (sid) => get(sid, key) + }) + if(!status.a2i[modulepath]){ + status.i2a[status.a2i[modulepath] = encode(modulepath)] = modulepath + } + try { + return { + id: modulepath, + sdb: sdb.public_api, + get: init_instance, + io: io(status.a2i[modulepath], modulepath) + // sub_modules + } + } catch (error) { + throw new Error(`ID: ${modulepath}\n`+error) + } + + } + function append_tree_node (id, status) { + const [super_id, name] = id.split(/>(?=[^>]*$)/) + + if(Object.keys(status.tree).length){ + if(status.tree_pointers[super_id]){ + status.tree_pointers[super_id]._[name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[super_id]._[name].$ + status.open_branches[super_id]-- + } + else{ + let new_name = name + let [new_super_id, temp_name] = super_id.split(/>(?=[^>]*$)/) + + new_name = temp_name + '>' + new_name + + while(!status.tree_pointers[new_super_id]){ + [new_super_id, temp_name] = new_super_id.split(/>(?=[^>]*$)/) + new_name = temp_name + '>' + new_name + } + status.tree_pointers[new_super_id]._[new_name] = { $: { _: {} } } + status.tree_pointers[id] = status.tree_pointers[new_super_id]._[new_name].$ + if(!status.missing_supers.has(super_id)) + status.open_branches[new_super_id]-- + status.missing_supers.add(super_id) + } + } + else{ + status.tree[id] = { $: { _: {} } } + status.tree_pointers[id] = status.tree[id].$ + } + return status + } + function init_module () { + const {statedata, state_entries, newstatus, updated_local_status} = get_module_data(local_status.fallback_module) + statedata.orphan && (local_status.orphan = true) + //side effects + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + console.log('Main module: ', statedata.id, '\n', state_entries) + updated_local_status && Object.assign(local_status, updated_local_status) + // console.log('Local status: ', local_status.fallback_instance, statedata.api) + const old_fallback = local_status.fallback_instance + + if(local_status.fallback_instance ? local_status.fallback_instance?.toString() === statedata.api?.toString() : false) + local_status.fallback_instance = statedata.api + else + local_status.fallback_instance = (args, tools) => { + return statedata.api(args, tools, [old_fallback]) + } + const extra_fallbacks = Object.entries(old_fallback || {}) + extra_fallbacks.length && extra_fallbacks.forEach(([key, value]) => { + local_status.fallback_instance[key] = (args, tools) => { + console.log('Extra fallback: ', statedata.api[key] ? statedata.api[key] : old_fallback[key]) + return (statedata.api[key] ? statedata.api[key] : old_fallback[key])(args, tools, [value]) + } + }) + db.append(['state'], state_entries) + // add_source_code(statedata.inputs) // @TODO: remove side effect + } + [local_status.sub_modules, symbol2ID, ID2Symbol, address2ID, ID2Address] = symbolfy(statedata, local_status) + Object.assign(status.s2i, symbol2ID) + Object.assign(status.i2s, ID2Symbol) + Object.assign(status.a2i, address2ID) + Object.assign(status.i2a, ID2Address) + + //Setup local data (module level) + if(status.root_module){ + status.root_module = false + statedata.admins && admins.push(...statedata.admins) + } + // @TODO: handle sub_modules when dynamic require is implemented + // const sub_modules = {} + // statedata.subs && statedata.subs.forEach(id => { + // sub_modules[db.read(['state', id]).type] = id + // }) + } + function init_instance (sid, fallback_key) { + const fallback = local_status.fallback_instance[fallback_key] || local_status.fallback_instance + const {statedata, state_entries, newstatus} = get_instance_data(sid, fallback) + + if (status.fallback_check) { + Object.assign(status.root_module, newstatus.root_module) + Object.assign(status.overrides, newstatus.overrides) + Object.assign(status.tree, newstatus.tree) + console.log('Main instance: ', statedata.id, '\n', state_entries) + db.append(['state'], state_entries) + } + [local_status.sub_instances[statedata.id], symbol2ID, ID2Symbol, address2ID, ID2Address] = symbolfy(statedata, local_status) + Object.assign(status.s2i, symbol2ID) + Object.assign(status.i2s, ID2Symbol) + Object.assign(status.a2i, address2ID) + Object.assign(status.i2a, ID2Address) + + const sdb = create_statedb_interface(local_status, statedata.id, xtype = 'instance') + sdb.id = statedata.id + + const sanitized_event = {} + statedata.net && Object.keys(statedata.net).forEach(node => { + statedata.net[node].id = status.a2i[node] || (status.a2i[node] = encode(node)) + }) + return { + id: statedata.id, + net: statedata.net, + sdb: sdb.public_api, + io: io(status.a2i[statedata.id], modulepath) + } + } + function get_module_data (fallback) { + let data = db.read(['state', modulepath]) + if (status.fallback_check) { + if (data) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: data }) + } + else if (status.root_module) { + var {sanitized_data, updated_status} = validate_and_preprocess({ fun_status: status, fallback, xtype: 'module', pre_data: {id: modulepath}}) + } + else { + var {sanitized_data, updated_status, updated_local_status} = find_super({ xtype: 'module', fallback, fun_status:status, local_status }) + } + data = sanitized_data.entry + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + updated_local_status + } + } + function get_instance_data (sid, fallback) { + let id = status.s2i[sid] + if(id && (id.split(':')[0] !== modulepath || !id.includes(':'))) + throw new Error(`Access denied! Wrong SID '${id}' used by instance of '${modulepath}'` + FALLBACK_SUBS_POST_ERROR) + if(status.used_ids.has(id)) + throw new Error(`Access denied! SID '${id}' is already used` + FALLBACK_SUBS_POST_ERROR) + + id && status.used_ids.add(id) + let data = id && db.read(['state', id]) + let sanitized_data, updated_status = status + if (status.fallback_check) { + if (!data && !status.root_instance) { + ({sanitized_data, updated_status} = find_super({ xtype: 'instance', fallback, fun_status: status })) + } else { + ({sanitized_data, updated_status} = validate_and_preprocess({ + fun_status: status, + fallback, + xtype: 'instance', + pre_data: data || {id: get_instance_path(modulepath)} + })) + updated_status.root_instance = false + } + data = sanitized_data.entry + } + else if (status.root_instance) { + data = db.read(['state', id || get_instance_path(modulepath)]) + updated_status.tree = JSON.parse(JSON.stringify(status.tree)) + updated_status.root_instance = false + } + + if (!data && local_status.orphan) { + data = db.read(['state', get_instance_path(modulepath)]) + } + return { + statedata: data, + state_entries: sanitized_data?.entries, + newstatus: updated_status, + } + } + function find_super ({ xtype, fallback, fun_status, local_status }) { + let modulepath_super = modulepath.split(/\>(?=[^>]*$)/)[0] + let modulepath_grand = modulepath_super.split(/\>(?=[^>]*$)/)[0] + if(status.modulepaths[modulepath_super] !== undefined){ + throw new Error(`Node "${modulepath}" is not defined in the fallback of "${modulepath_super}"` + FALLBACK_SUBS_POST_ERROR) + } + const split = modulepath.split('>') + let data + const entries = {} + if(xtype === 'module'){ + let name = split.at(-1) + while(!data && modulepath_grand.includes('>')){ + data = db.read(['state', modulepath_super]) + const split = modulepath_super.split(/\>(?=[^>]*$)/) + modulepath_super = split[0] + name = split[1] + '>' + name + } + console.log(data) + data.path = data.id = modulepath_super + '>' + name + modulepath = modulepath_super + '>' + name + local_status.name = name + + const super_data = db.read(['state', modulepath_super]) + super_data.subs.forEach((sub_id, i) => { + if(sub_id === modulepath_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + else{ + //@TODO: Make the :0 dynamic + let instance_path_super = modulepath_super + ':0' + let temp + while(!data && temp !== modulepath_super){ + data = db.read(['state', instance_path_super]) + temp = modulepath_super + modulepath_grand = modulepath_super = modulepath_super.split(/\>(?=[^>]*$)/)[0] + instance_path_super = modulepath_super + ':0' + } + data.path = data.id = get_instance_path(modulepath) + temp = null + let super_data + let instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + + while(!super_data?.subs && temp !== modulepath_grand){ + super_data = db.read(['state', instance_path_grand]) + temp = modulepath_grand + modulepath_grand = modulepath_grand.split(/\>(?=[^>]*$)/)[0] + instance_path_grand = modulepath_grand.includes('>') ? modulepath_grand + ':0' : modulepath_grand + } + + super_data.subs.forEach((sub_id, i) => { + if(sub_id === instance_path_super){ + super_data.subs.splice(i, 1) + return + } + }) + super_data.subs.push(data.id) + entries[super_data.id] = super_data + } + data.name = split.at(-1) + return { updated_local_status: local_status, + ...validate_and_preprocess({ + fun_status, + fallback, xtype, + pre_data: data, + orphan_check: true, entries }) } + } + function validate_and_preprocess ({ fallback, xtype, pre_data = {}, orphan_check, fun_status, entries }) { + const used_keys = new Set() + let {id: pre_id, hubs: pre_hubs, mapping} = pre_data + let fallback_data + try { + validate(fallback(status.args[pre_id], { listify: tree => listify(tree, modulepath), tree: status.tree_pointers[modulepath] }), xtype) + } catch (error) { + throw new Error(`in fallback function of ${pre_id} ${xtype}\n${error.stack}`) + } + if(fun_status.overrides[pre_id]){ + fallback_data = fun_status.overrides[pre_id].fun[0](status.args[pre_id], { listify: tree => listify(tree, modulepath), tree: status.tree_pointers[modulepath] }, get_fallbacks({ fallback, modulename: local_status.name, modulepath, instance_path: pre_id })) + console.log('Override used: ', pre_id) + fun_status.overrides[pre_id].by.splice(0, 1) + fun_status.overrides[pre_id].fun.splice(0, 1) + } + else + fallback_data = fallback(status.args[pre_id], { listify: tree => listify(tree, modulepath), tree: status.tree_pointers[modulepath] }) + + // console.log('fallback_data: ', fallback) + fun_status.overrides = register_overrides({ overrides: fun_status.overrides, tree: fallback_data, path: modulepath, id: pre_id }) + console.log('overrides: ', Object.keys(fun_status.overrides)) + orphan_check && (fallback_data.orphan = orphan_check) + //This function makes changes in fun_status (side effect) + return { + sanitized_data: sanitize_state({ local_id: '', entry: fallback_data, path: pre_id, xtype, mapping, entries }), + updated_status: fun_status + } + + function sanitize_state ({ local_id, entry, path, hub_entry, local_tree, entries = {}, xtype, mapping, xkey }) { + [path, entry, local_tree] = extract_data({ local_id, entry, path, hub_entry, local_tree, xtype, xkey }) + + entry.id = path + entry.name = entry.name || local_id.split(':')[0] || local_status.name + mapping && (entry.mapping = mapping) + + entries = {...entries, ...sanitize_subs({ local_id, entry, path, local_tree, xtype, mapping })} + delete entry._ + entries[entry.id] = entry + // console.log('Entry: ', entry) + return {entries, entry} + } + function extract_data ({ local_id, entry, path, hub_entry, xtype, xkey }) { + if (local_id) { + entry.hubs = [hub_entry.id] + if (xtype === 'instance') { + let temp_path = path.split(':')[0] + temp_path = temp_path ? temp_path + '>' : temp_path + const module_id = temp_path + local_id + entry.type = module_id + path = module_id + ':' + xkey + temp = Number(xkey)+1 + temp2 = db.read(['state', path]) + while(temp2 || used_keys.has(path)){ + path = module_id + ':' + temp + temp2 = db.read(['state', path]) + temp++ + } + } + else { + entry.type = local_id + path = path ? path + '>' : '' + path = path + local_id + } + } + else { + if (xtype === 'instance') { + entry.type = local_status.module_id + } else { + local_tree = JSON.parse(JSON.stringify(entry)) + // @TODO Handle JS file entry + // console.log('pre_id:', pre_id) + // const file_id = local_status.name + '.js' + // entry.drive || (entry.drive = {}) + // entry.drive[file_id] = { $ref: address } + entry.type = local_status.name + } + pre_hubs && (entry.hubs = pre_hubs) + } + return [path, entry, local_tree] + } + function sanitize_subs ({ local_id, entry, path, local_tree, xtype, mapping }) { + const entries = {} + if (!local_id) { + entry.subs = [] + if(entry._){ + //@TODO refactor when fallback structure improves + Object.entries(entry._).forEach(([local_id, value]) => { + Object.entries(value).forEach(([key, override]) => { + if(key === 'mapping' || (key === '$' && xtype === 'instance')) + return + const sub_instance = sanitize_state({ local_id, entry: value, path, hub_entry: entry, local_tree, xtype: key === '$' ? 'module' : 'instance', mapping: value['mapping'], xkey: key }).entry + entries[sub_instance.id] = JSON.parse(JSON.stringify(sub_instance)) + entry.subs.push(sub_instance.id) + used_keys.add(sub_instance.id) + }) + })} + if (entry.drive) { + // entry.drive.theme && (entry.theme = entry.drive.theme) + // entry.drive.lang && (entry.lang = entry.drive.lang) + entry.inputs = [] + const new_drive = [] + Object.entries(entry.drive).forEach(([dataset_type, dataset]) => { + dataset_type = dataset_type.split('/')[0] + + const new_dataset = { files: [], mapping: {} } + Object.entries(dataset).forEach(([key, value]) => { + const sanitized_file = sanitize_file(key, value, entry, entries) + entries[sanitized_file.id] = sanitized_file + new_dataset.files.push(sanitized_file.id) + }) + new_dataset.id = entry.id + '.' + dataset_type + '.dataset' + new_dataset.type = dataset_type + new_dataset.name = 'default' + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + entries[new_dataset.id] = new_dataset + let check_name = true + entry.inputs.forEach(dataset_id => { + const ds = entries[dataset_id] + if(ds.type === new_dataset.type) + check_name = false + }) + check_name && entry.inputs.push(new_dataset.id) + new_drive.push(new_dataset.id) + + + if(!status.root_module){ + const hub_entry = db.read(['state', entry.hubs[0]]) + console.log(hub_entry, entry) + if(!hub_entry.inputs) + throw new Error(`Node "${hub_entry.id}" has no "drive" defined in its fallback` + FALLBACK_SUBS_POST_ERROR) + if(!mapping?.[dataset_type]) + throw new Error(`No mapping found for dataset "${dataset_type}" of subnode "${entry.id}" in node "${hub_entry.id}"\nTip: Add a mapping prop for "${dataset_type}" dataset in "${hub_entry.id}"'s fallback for "${entry.id}"` + FALLBACK_POST_ERROR) + const mapped_file_type = mapping[dataset_type] + hub_entry.inputs.some(input_id => { + const input = db.read(['state', input_id]) + if(mapped_file_type === input.type){ + input.mapping[entry.id] = new_dataset.id + entries[input_id] = input + return + } + }) + } + }) + entry.drive = new_drive + } + } + return entries + } + function sanitize_file (file_id, file, entry, entries) { + const type = file_id.split('.').at(-1) + + if (!isNaN(Number(file_id))) return file_id + + const raw_id = local_status.name + '.' + type + file.id = raw_id + file.name = file.name || file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [entry.id] + if(file.$ref){ + file.$ref = address.substring(0, address.lastIndexOf("/")) + '/' + file.$ref + } + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + while(entries[file.id]){ + const no = file.id.split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + return file + } + } +} + +// External Function (helper) +function validate (data, xtype) { + /** Expected structure and types + * Sample : "key1|key2:*:type1|type2" + * ":" : separator + * "|" : OR + * "*" : Required key + * */ + const expected_structure = { + 'api::function': () => {}, + '_::object': { + ":*:object|number": { + ":*:function|string|object": '', + "mapping::": {} + } + }, + 'drive::object': { + "::object": { + "::object": { // Required key, any name allowed + "raw|$ref:*:object|string": {}, // data or $ref are names, required, object or string are types + "$ref": "string" + } + }, + }, + 'net::object': {} + } + + validate_shape(data, expected_structure) + + function validate_shape (obj, expected, super_node = 'root', path = '') { + const keys = Object.keys(obj) + const values = Object.values(obj) + let strict = Object.keys(expected).length + + const all_keys = [] + Object.entries(expected).forEach(([expected_key, expected_value]) => { + let [expected_key_names, required, expected_types] = expected_key.split(':') + expected_types = expected_types ? expected_types.split('|') : [typeof(expected_value)] + let absent = true + if(expected_key_names) + expected_key_names.split('|').forEach(expected_key_name => { + const value = obj[expected_key_name] + if(value !== undefined){ + all_keys.push(expected_key_name) + const type = typeof(value) + absent = false + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, expected_key_name, path + '/' + expected_key_name) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${expected_key_name}" at:` + path + FALLBACK_POST_ERROR) + } + }) + else{ + strict = false + values.forEach((value, index) => { + absent = false + const type = typeof(value) + + if(expected_types.includes(type)) + type === 'object' && validate_shape(value, expected_value, keys[index], path + '/' + keys[index]) + else + throw new Error(`Type mismatch: Expected "${expected_types.join(' or ')}" got "${type}" for key "${keys[index]}" at: ` + path + FALLBACK_POST_ERROR) + }) + } + if(absent && required){ + if(expected_key_names) + throw new Error(`Can't find required key "${expected_key_names.replace('|', ' or ')}" at: ` + path + FALLBACK_POST_ERROR) + else + throw new Error(`No sub-nodes found for super key "${super_node}" at sub: ` + path + FALLBACK_POST_ERROR) + } + }) + + strict && keys.forEach(key => { + if(!all_keys.includes(key)){ + throw new Error(`Unknown key detected: '${key}' is an unknown property at: ${path || 'root'}` + FALLBACK_POST_ERROR) + } + }) + } +} +function extract_filename (address) { + const parts = address.split('/node_modules/') + const last = parts.at(-1).split('/') + if(last.at(-1) === 'index.js') + return last.at(-2) + return last.at(-1).slice(0, -3) +} +function get_instance_path (modulepath, modulepaths = status.modulepaths) { + return modulepath + ':' + modulepaths[modulepath]++ +} +async function get_input ({ id, name, $ref, type, raw }) { + const xtype = (typeof(id) === "number" ? name : id).split('.').at(-1) + let result = db.read([type, id]) + + if (!result) { + if (raw === undefined){ + let ref_url = $ref + // Patch: Prepend GitHub project name if running on GitHub Pages + if (typeof window !== 'undefined' && window.location.hostname.endsWith('github.io')) { + const path_parts = window.location.pathname.split('/').filter(Boolean) + if (path_parts.length > 0 && !$ref.startsWith('/' + path_parts[0])) { + ref_url = '/' + path_parts[0] + ($ref.startsWith('/') ? '' : '/') + $ref + } + } + const response = await fetch(ref_url) + if (!response.ok) + throw new Error(`Failed to fetch data from '${ref_url}' for '${id}'` + FALLBACK_SYNTAX_POST_ERROR) + else + result = await response[xtype === 'json' ? 'json' : 'text']() + } + else + result = raw + } + return result +} +//Unavoidable side effect +function add_source_code (hubs) { + hubs.forEach(async id => { + const data = db.read(['state', id]) + if (data.type === 'js') { + data.data = await get_input(data) + db.add(['state', data.id], data) + return + } + }) +} +function verify_imports (id, imports, data) { + const state_address = imports.find(imp => imp.includes('STATE')) + HELPER_MODULES.push(state_address) + imports = imports.filter(imp => !HELPER_MODULES.includes(imp)) + if(!data._){ + if(imports.length > 1){ + imports.splice(imports.indexOf(state_address), 1) + throw new Error(`No sub-nodes found for required modules "${imports.join(', ')}" in the fallback of "${status.local_statuses[id].module_id}"` + FALLBACK_POST_ERROR) + } + else return + } + const fallback_imports = Object.keys(data._) + + imports.forEach(imp => { + let check = true + fallback_imports.forEach(fallimp => { + if(imp === fallimp) + check = false + }) + + if(check) + throw new Error('Required module "'+imp+'" is not defined in the fallback of '+status.local_statuses[id].module_id + FALLBACK_POST_ERROR) + }) + + fallback_imports.forEach(fallimp => { + let check = true + imports.forEach(imp => { + if(imp === fallimp) + check = false + }) + + if(check) + throw new Error('Module "'+fallimp+'" defined in the fallback of '+status.local_statuses[id].module_id+' is not required') + }) + +} +function symbolfy (data) { + const i2s = {} + const s2i = {} + const i2a = {} + const a2i = {} + const subs = [] + data.subs && data.subs.forEach(sub => { + const substate = db.read(['state', sub]) + i2a[a2i[sub] = encode(sub)] = sub + s2i[i2s[sub] = Symbol(a2i[sub])] = sub + subs.push({ sid: i2s[sub], type: substate.type }) + }) + return [subs, s2i, i2s, a2i, i2a] +} +function encode(text) { + let code = '' + while (code.length < 10) { + for (let i = 0; i < text.length && code.length < 10; i++) { + code += Math.floor(10 + Math.random() * 90) + } + } + return code +} +function listify(tree, prefix = '') { + if (!tree) + return [] + + const result = [] + + function walk(current, prefix = '') { + for (const key in current) { + if (key === '$' && current[key]._ && typeof current[key]._ === 'object') { + walk(current[key]._, prefix) + } else { + const path = prefix ? `${prefix}>${key}` : key + result.push(path) + if (current[key]?.$?._ && typeof current[key].$._ === 'object') { + walk(current[key].$._, path) + } + } + } + } + + if (tree._ && typeof tree._ === 'object') { + walk(tree._, prefix) + } + + return result +} +function register_overrides ({overrides, ...args}) { + recurse(args) + return overrides + function recurse ({ tree, path = '', id, xtype = 'instance', local_modulepaths = {} }) { + + tree._ && Object.entries(tree._).forEach(([type, instances]) => { + const sub_path = path + '>' + type + Object.entries(instances).forEach(([id, override]) => { + const resultant_path = id === '$' ? sub_path : sub_path + ':' + id + if(typeof(override) === 'function'){ + if(overrides[resultant_path]){ + overrides[resultant_path].fun.push(override) + overrides[resultant_path].by.push(id) + } + else + overrides[resultant_path] = {fun: [override], by: [id]} + } + else if ( ['object', 'string'].includes(typeof(override)) && id !== 'mapping' && override._ === undefined) + status.args[resultant_path] = structuredClone(override) + else + recurse({ tree: override, path: sub_path, id, xtype, local_modulepaths }) + }) + }) + } +} +function get_fallbacks ({ fallback, modulename, modulepath, instance_path }) { + return [mutated_fallback, ...status.overrides[instance_path].fun] + + function mutated_fallback () { + const data = fallback(status.args[instance_path], { listify: tree => listify(tree, modulepath), tree: status.tree_pointers[modulepath] }) + + data.overrider = status.overrides[instance_path].by[0] + merge_trees(data, modulepath) + return data + + function merge_trees (data, path) { + if (data._) { + Object.entries(data._).forEach(([type, data]) => merge_trees(data, path + '>' + type.split('$')[0].replace('.', '>'))) + } else { + data.$ = { _: status.tree_pointers[path]?._ } + } + } + } +} +function check_version () { + if (db.read(['playproject_version']) != VERSION) { + localStorage.clear() + return true + } +} + +// Public Function +function create_statedb_interface (local_status, node_id, xtype) { + const drive = { + get, has, put, list + } + const api = { + public_api: { + watch, get_sub, drive + }, + private_api: { + drive, + xget: (id) => db.read(['state', id]), + get_all: () => db.read_all(['state']), + get_db, + register, + load: (snapshot) => { + localStorage.clear() + Object.entries(snapshot).forEach(([key, value]) => { + db.add([key], JSON.parse(value), true) + }) + window.location.reload() + }, + swtch, + unregister, + status, + } + } + node_id === status.ROOT_ID && (api.public_api.admin = api.private_api) + return api + + async function watch (listener, on) { + if(on) + status.services[node_id] = Object.keys(on) + const data = db.read(['state', node_id]) + if(listener){ + status.listeners[data.id] = listener + await listener(await make_input_map(data.inputs)) + } + return xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + } + function get_sub (type) { + const subs = xtype === 'module' ? local_status.sub_modules : local_status.sub_instances[node_id] + return subs.filter(sub => sub.type === type) + } + function get_db ({ type: dataset_type, name: dataset_name } = {}) { + const node = db.read(['state', status.ROOT_ID]) + if(dataset_type){ + const dataset_list = [] + node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.type === dataset_type) + dataset_list.push(dataset.name) + }) + if(dataset_name){ + return recurse(status.ROOT_ID, dataset_type) + } + return dataset_list + } + const datasets = [] + node.inputs && node.inputs.forEach(dataset_id => { + datasets.push(db.read(['state', dataset_id]).type) + }) + return datasets + + function recurse (node_id, dataset_type){ + const node_list = [] + const entry = db.read(['state', node_id]) + const temp = entry.mapping ? Object.keys(entry.mapping).find(key => entry.mapping[key] === dataset_type) : null + const mapped_type = temp || dataset_type + entry.drive && entry.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === mapped_type){ + node_list.push(node_id) + return + } + }) + entry.subs && entry.subs.forEach(sub_id => node_list.push(...recurse(sub_id, mapped_type))) + return node_list + } + } + function register ({ type: dataset_type, name: dataset_name, dataset}) { + Object.entries(dataset).forEach(([node_id, files]) => { + const new_dataset = { files: [] } + Object.entries(files).forEach(([file_id, file]) => { + const type = file_id.split('.').at(-1) + + file.id = local_status.name + '.' + type + file.local_name = file_id + file.type = type + file[file.type === 'js' ? 'subs' : 'hubs'] = [node_id] + + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = file.id + ':' + (Number(no || 0) + 1) + } + db.add(['state', file.id], file) + new_dataset.files.push(file.id) + }) + + const node = db.read(['state', node_id]) + new_dataset.id = node.name + '.' + dataset_type + '.dataset' + new_dataset.name = dataset_name + new_dataset.type = dataset_type + const copies = Object.keys(db.read_all(['state', new_dataset.id])) + if (copies.length) { + const id = copies.sort().at(-1).split(':')[1] + new_dataset.id = new_dataset.id + ':' + (Number(id || 0) + 1) + } + db.push(['state', node_id, 'drive'], new_dataset.id) + db.add(['state', new_dataset.id], new_dataset) + }) + console.log(' registered ' + dataset_name + '.' + dataset_type) + } + function unregister ({ type: dataset_type, name: dataset_name } = {}) { + return recurse(status.ROOT_ID) + + function recurse (node_id){ + const node = db.read(['state', node_id]) + node.drive && node.drive.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.drive.splice(node.drive.indexOf(dataset_id), 1) + return true + } + }) + node.inputs && node.inputs.some(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(dataset.name === dataset_name && dataset.type === dataset_type){ + node.inputs.splice(node.inputs.indexOf(dataset_id), 1) + swtch(dataset_type) + return true + } + }) + db.add(['state', node_id], node) + node.subs.forEach(sub_id => recurse(sub_id)) + } + } + function swtch ({ type: dataset_type, name: dataset_name = 'default'}) { + recurse(dataset_type, dataset_name, status.ROOT_ID) + + async function recurse (target_type, target_name, id) { + const node = db.read(['state', id]) + + let target_dataset + node.drive && node.drive.forEach(dataset_id => { + const dataset = db.read(['state', dataset_id]) + if(target_name === dataset.name && target_type === dataset.type){ + target_dataset = dataset + return + } + }) + if(target_dataset){ + node.inputs.forEach((dataset_id, i) => { + const dataset = db.read(['state', dataset_id]) + if(target_type === dataset.type){ + node.inputs.splice(i, 1) + return + } + }) + node.inputs.push(target_dataset.id) + } + db.add(['state', id], node) + status.listeners[id] && status.listeners[id](await make_input_map(node.inputs)) + node.subs && node.subs.forEach(sub_id => { + const subdataset_id = target_dataset?.mapping?.[sub_id] + recurse(target_type, db.read(['state', subdataset_id])?.name || target_name, sub_id) + }) + } + } + + function list (path, id = node_id) { + const node = db.read(['state', id]) + if(!node.drive) + throw new Error(`Node "${id}" has no drive`) + const dataset_names = node.drive.map(dataset_id => { + return dataset_id.split('.').at(-2) + '/' + }) + + if (path) { + let index + dataset_names.some((dataset_name, i) => { + if (path.includes(dataset_name)) { + index = i + return true + } + }) + if (index === undefined) + throw new Error(`Dataset "${path}" not found in node "${node.name}"`) + const dataset = db.read(['state', node.drive[index]]) + return dataset.files.map(fileId => { + const file = db.read(['state', fileId]) + return file.name + }) + } + return dataset_names + } + async function get (path, id = node_id) { + const [dataset_name, file_name] = path.split('/') + const node = db.read(['state', id]) + let dataset + if(!node.drive) + throw new Error(`Node ${node.id} has no drive defined in its fallback` + FALLBACK_POST_ERROR) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(-2)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + + let target_file + for (const file_id of dataset.files) { + const file = db.read(['state', file_id]) + if (file.name === file_name) { + target_file = { id: file.id, name: file.name, type: file.type, raw: await get_input(file)} + break + } + } + if (!target_file) + throw new Error(`File "${path}" not found`) + return target_file + } + async function put (path, buffer, id = node_id) { + const [dataset_name, filename] = path.split('/') + let dataset + const node = db.read(['state', id]) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(-2)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + const type = filename.split('.').pop() + const raw_id = node.name + '.' + type + const file = { + id: raw_id, + name: filename, + type, + raw: buffer + } + for (const file_id of dataset.files) { + const temp_file = db.read(['state', file_id]) + if(file.name === filename){ + file.id = file_id + break + } + } + if(!dataset.files.includes(file.id)){ + const copies = Object.keys(db.read_all(['state', file.id])) + if (copies.length) { + const no = copies.sort().at(-1).split(':')[1] + file.id = raw_id + ':' + (Number(no || 0) + 1) + } + dataset.files.push(file.id) + db.add(['state', dataset.id], dataset) + } + db.add(['state', file.id], file) + await status.listeners[node.id](await make_input_map(node.inputs)) + + return { id: file.id, name: filename, type, raw: buffer } + } + function has (path) { + const [dataset_name, filename] = path.split('/') + let dataset + const node = db.read(['state', node_id]) + node.drive.some(dataset_id => { + if (dataset_name === dataset_id.split('.').at(-2)) { + dataset = db.read(['state', dataset_id]) + return true + } + }) + if (!dataset) + throw new Error(`Dataset "${dataset_name}" not found in node "${node.name}"`) + return dataset.files.some(file_id => { + const file = db.read(['state', file_id]) + return file && file.name === filename + }) + } +} +async function make_input_map (inputs) { + const input_map = [] + if (inputs) { + await Promise.all(inputs.map(async input => { + let files = [] + const dataset = db.read(['state', input]) + await Promise.all(dataset.files.map(async file_id => { + const input_state = db.read(['state', file_id]) + files.push(dataset.id.split('.').at(-2) + '/' + input_state.name) + })) + input_map.push({ type: dataset.type, paths: files }) + })) + } + return input_map +} + + +module.exports = STATE \ No newline at end of file diff --git a/src/node_modules/content.js b/src/node_modules/content.js deleted file mode 100755 index 9eb0afb..0000000 --- a/src/node_modules/content.js +++ /dev/null @@ -1,63 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') - -function content(data, theme) { - const css = Object.assign({}, styles, theme) - let el = bel` -
-

${data.title}

-
${data.article}
- ${data.action} -
- ` - return el -} - -let styles = csjs` -.content { - -} -.subTitle { - font-family: var(--titleFont); - font-size: var(--subTitleSize); - margin-bottom: 2.5rem; -} -.subTitleColor { - color: var(--section2TitleColor); -} -.article { - -} -.button { - display: inline-block; - outline: none; - border: none; - font-family: var(--titleFont); - font-size: var(--sectionButtonSize); - color: var(--titleColor); - border-radius: 2rem; - padding: 1.2rem 3.8rem; - cursor: pointer; -} -.buttonBg { - -} -@media screen and (min-width: 2561px) { - .subTitle { - font-size: calc(var(--subTitleSize) * 1.5); - } -} -} -@media screen and (min-width: 4096px) { - .subTitle { - font-size: calc(var(--subTitleSize) * 2.25); - } -} -@media screen and (max-width: 414px) { - .subTitle { - font-size: var(--titlesSizeS); - margin-bottom: 1.5rem; - } -} -` -module.exports = content \ No newline at end of file diff --git a/src/node_modules/content/content.js b/src/node_modules/content/content.js new file mode 100755 index 0000000..b54ff12 --- /dev/null +++ b/src/node_modules/content/content.js @@ -0,0 +1,88 @@ +const IO = require('io') +const statedb = require('STATE') +/****************************************************************************** + CONTENT COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = content + +async function content (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'content' + const status = {} + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const style = document.createElement('style') + el.classList.add('content') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+

${data.title}

+
${data.article}
+ ${data.url ? `${data.action}` : ''} +
+ ` + shadow.append(style) + + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} diff --git a/src/node_modules/content/data.json b/src/node_modules/content/data.json new file mode 100644 index 0000000..afe9b86 --- /dev/null +++ b/src/node_modules/content/data.json @@ -0,0 +1,9 @@ +{ + "0": { + "comp": "content", + "title": "Play Editor", + "article": "Web based IDE with interactive UI generator for easy writing, deploying and interacting with Solidity smart contracts.", + "action": "Learn more", + "url": "https://smartcontract-codes.github.io/play-ed/" + } +} \ No newline at end of file diff --git a/src/node_modules/content/package.json b/src/node_modules/content/package.json new file mode 100644 index 0000000..6c34a33 --- /dev/null +++ b/src/node_modules/content/package.json @@ -0,0 +1,3 @@ +{ + "main": "content.js" +} \ No newline at end of file diff --git a/src/node_modules/contributor.js b/src/node_modules/contributor.js deleted file mode 100755 index 9517baa..0000000 --- a/src/node_modules/contributor.js +++ /dev/null @@ -1,122 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const Graphic = require('graphic') - -module.exports = contributor - -async function contributor(person, className, theme) { - let css = Object.assign({}, styles, theme) - let lifeIsland = await Graphic(css.lifeIsland,'./src/node_modules/assets/svg/life-island.svg') - let el = bel` -
-
- ${person.name} -
-

${person.name}

- ${person.careers && - person.careers.map( career => - bel`${career}` - ) - } -
-
- ${lifeIsland} -
- ` - return el -} - -const styles = csjs` -.member { - position: absolute; - z-index: 1; - display: grid; - grid-template: 1fr / 40% 60%; - width: 70%; - top: 20%; -} -.avatar { - position: relative; - z-index: 2; -} -.info { - display: flex; - flex-direction: column; - justify-content: center; - font-size: var(--contributorsTextSize); - text-align: center; - background-color: var(--contributorsBg); - padding: 0% 2% 4% 20%; - margin-left: -20%; -} -.name { - color: var(--section5TitleColor); - margin-top: 0; - margin-bottom: 3%; -} -.career { - display: block; - color: var(--contributorsCareerColor); -} -.lifeIsland { - width: 100%; -} -@media only screen and (max-width: 1550px) { - .member { - width: 280px; - top: 15%; - left: -2vw; - } -} -@media only screen and (max-width: 1200px) { - .lifeIsland { - width: 115%; - } -} -@media only screen and (max-width: 1280px) { - .member { - top: 12%; - left: -4vw; - } -} - -@media only screen and (max-width: 1130px) { - .member { - top: 1vw; - left: -6vw; - } -} -@media only screen and (max-width: 1024px) { - .lifeIsland { - width: 100%; - } - .member { - width: 32vw; - top: 6vw; - left: -2vw; - } -} -@media only screen and (max-width: 768px) { - .member { - width: 85%; - top: 5vw; - left: -4vw; - } -} -@media only screen and (max-width: 640px) { - .member { - width: 75%; - top: 9vw; - } -} -@media only screen and (max-width: 414px) { - .member { - width: 90%; - top: 5vw; - left: -10vw; - } -} -` - - diff --git a/src/node_modules/contributor/contributor.js b/src/node_modules/contributor/contributor.js new file mode 100755 index 0000000..b7027b5 --- /dev/null +++ b/src/node_modules/contributor/contributor.js @@ -0,0 +1,97 @@ +const Graphic = require('graphic') +const IO = require('io') +const statedb = require('STATE') +/****************************************************************************** + CONTRIBUTOR COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = contributor + +async function contributor (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'contributor' + const status = {} + const lifeIsland = await Graphic('lifeIsland','./src/node_modules/assets/svg/life-island.svg') + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ${data.name} +
+

${data.name}

+ ${data.careers && + data.careers.map( career => + `${career}` + ) + } +
+
+ ${lifeIsland.outerHTML} +
+ ` + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name + '.css' }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(...shadow.adoptedStyleSheets, sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} + + diff --git a/src/node_modules/contributor/data.json b/src/node_modules/contributor/data.json new file mode 100644 index 0000000..a213c5d --- /dev/null +++ b/src/node_modules/contributor/data.json @@ -0,0 +1,14 @@ +{ + "0": { + "name": "Nina", + "comp": "contributor", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "css": [{ "id": "contributor.css"}, { "id": "contributor_1.css" }], + "avatar": "./src/node_modules/assets/images/avatar-nina.png" + } +} \ No newline at end of file diff --git a/src/node_modules/contributor/package.json b/src/node_modules/contributor/package.json new file mode 100644 index 0000000..d8f80a7 --- /dev/null +++ b/src/node_modules/contributor/package.json @@ -0,0 +1,3 @@ +{ + "main": "contributor.js" +} \ No newline at end of file diff --git a/src/node_modules/crystalIsland.js b/src/node_modules/crystalIsland.js deleted file mode 100755 index 2d4051b..0000000 --- a/src/node_modules/crystalIsland.js +++ /dev/null @@ -1,20 +0,0 @@ -const bel = require('bel') - -function crystalIsland({date, info}, deco, island, css, title) { - let el = bel` -
-
-
-

${date}

- ${ info === 'Coming soon' ? bel`

${info}

` : bel`

${info}

` } -
- ${deco.map(item => item)} -
- ${title} - ${island} -
- ` - return el -} - -module.exports = crystalIsland \ No newline at end of file diff --git a/src/node_modules/crystal_island/crystal_island.js b/src/node_modules/crystal_island/crystal_island.js new file mode 100755 index 0000000..5a82127 --- /dev/null +++ b/src/node_modules/crystal_island/crystal_island.js @@ -0,0 +1,109 @@ +const IO = require('io') +const graphic = require('graphic') +const statedb = require('STATE') +/****************************************************************************** + CRYSTAL ISLAND COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = crystal_island + +async function crystal_island(opts) { + // {date, info}, deco, island, title) + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'crystal_island' + const status = {} + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + const paths = { + island: './src/node_modules/assets/svg/floating-island3.svg', + tree: './src/node_modules/assets/svg/big-tree.svg', + tree1: './src/node_modules/assets/svg/single-tree1.svg', + tree2: './src/node_modules/assets/svg/single-tree2.svg', + tree3: './src/node_modules/assets/svg/single-tree3.svg', + yellowCrystal: './src/node_modules/assets/svg/crystal-yellow.svg', + purpleCrystal: './src/node_modules/assets/svg/crystal-purple.svg', + blueCrystal: './src/node_modules/assets/svg/crystal-blue.svg', + stone: './src/node_modules/assets/svg/stone1.svg', + card: './src/node_modules/assets/svg/card2.svg' + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + // el.classList.add('scene') + shadow.innerHTML = ` +
+
+

${data.date}

+ ${ data.info === 'Coming soon' ? `

${data.info}

` : `

${data.info}

` } +
+ ${data.title || ''} +
+ ` + // ---------------------------------------- + const deco_el = shadow.querySelector('.deco') + shadow.append(await graphic('island', paths['island'])) + deco_el.append(...await Promise.all(data.deco.map(async v => await graphic(v.includes('tree') ? 'tree' : v, paths[v])))) + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} + diff --git a/src/node_modules/crystal_island/data.json b/src/node_modules/crystal_island/data.json new file mode 100644 index 0000000..dc7bc2b --- /dev/null +++ b/src/node_modules/crystal_island/data.json @@ -0,0 +1,8 @@ +{ + "0": { + "comp": "crystal_island", + "date": "2018", + "info": "$48.000 / Ethereum Foundation", + "deco" : ["stone", "card", "tree1"] + } +} \ No newline at end of file diff --git a/src/node_modules/crystal_island/package.json b/src/node_modules/crystal_island/package.json new file mode 100644 index 0000000..dfb1f11 --- /dev/null +++ b/src/node_modules/crystal_island/package.json @@ -0,0 +1,3 @@ +{ + "main": "./crystal_island.js" +} \ No newline at end of file diff --git a/src/node_modules/css/dark/our_contributors.css b/src/node_modules/css/dark/our_contributors.css new file mode 100644 index 0000000..1d2dab9 --- /dev/null +++ b/src/node_modules/css/dark/our_contributors.css @@ -0,0 +1,3 @@ +section{ + display: none; +} \ No newline at end of file diff --git a/src/node_modules/css/default/app.css b/src/node_modules/css/default/app.css new file mode 100644 index 0000000..65f4059 --- /dev/null +++ b/src/node_modules/css/default/app.css @@ -0,0 +1,6 @@ +.wrap { + background: var(--bodyBg); +} +[class^="cloud"] { + transition: left 0.6s, bottom 0.5s, top 0.5s linear; +} \ No newline at end of file diff --git a/src/node_modules/css/default/content.css b/src/node_modules/css/default/content.css new file mode 100644 index 0000000..cb6e22d --- /dev/null +++ b/src/node_modules/css/default/content.css @@ -0,0 +1,47 @@ +.main { + text-align: center; +} +.subTitle { + font-family: var(--titleFont); + font-size: var(--subTitleSize); + margin-bottom: 2.5rem; +} +.subTitleColor { + color: var(--section2TitleColor); +} +.article { + font-size: var(--articleSize); + color: var(--articleColor); + line-height: 2.5rem; + padding-bottom: 4rem; +} +.button { + display: inline-block; + outline: none; + border: none; + font-family: var(--titleFont); + font-size: var(--sectionButtonSize); + color: var(--titleColor); + border-radius: 2rem; + padding: 1.2rem 3.8rem; + cursor: pointer; +} +a { + text-decoration: none; +} +@media screen and (min-width: 2561px) { + .subTitle { + font-size: calc(var(--subTitleSize) * 1.5); + } +} +@media screen and (min-width: 4096px) { + .subTitle { + font-size: calc(var(--subTitleSize) * 2.25); + } +} +@media screen and (max-width: 414px) { + .subTitle { + font-size: var(--titlesSizeS); + margin-bottom: 1.5rem; + } +} diff --git a/src/node_modules/css/default/contributor.css b/src/node_modules/css/default/contributor.css new file mode 100644 index 0000000..0fc9949 --- /dev/null +++ b/src/node_modules/css/default/contributor.css @@ -0,0 +1,91 @@ +.member { + position: absolute; + z-index: 1; + display: grid; + grid-template: 1fr / 40% 60%; + width: 70%; + top: 20%; +} +.avatar { + position: relative; + z-index: 2; + width: 100%; + height: auto; +} +.info { + display: flex; + flex-direction: column; + justify-content: center; + font-size: var(--contributorsTextSize); + text-align: center; + background-color: var(--contributorsBg); + padding: 0% 2% 4% 20%; + margin-left: -20%; +} +.name { + color: var(--section5TitleColor); + margin-top: 0; + margin-bottom: 3%; +} +.career { + display: block; + color: var(--contributorsCareerColor); +} +.lifeIsland { + width: 100%; +} +@media only screen and (max-width: 1550px) { + .member { + width: 280px; + top: 15%; + left: -2vw; + } +} +@media only screen and (max-width: 1200px) { + .lifeIsland { + width: 115%; + } +} +@media only screen and (max-width: 1280px) { + .member { + top: 12%; + left: -4vw; + } +} + +@media only screen and (max-width: 1130px) { + .member { + top: 1vw; + left: -6vw; + } +} +@media only screen and (max-width: 1024px) { + .lifeIsland { + width: 100%; + } + .member { + width: 32vw; + top: 6vw; + left: -2vw; + } +} +@media only screen and (max-width: 768px) { + .member { + width: 85%; + top: 5vw; + left: -4vw; + } +} +@media only screen and (max-width: 640px) { + .member { + width: 75%; + top: 9vw; + } +} +@media only screen and (max-width: 414px) { + .member { + width: 90%; + top: 5vw; + left: -10vw; + } +} \ No newline at end of file diff --git a/src/node_modules/css/default/contributor_1.css b/src/node_modules/css/default/contributor_1.css new file mode 100644 index 0000000..e69de29 diff --git a/src/node_modules/css/default/contributor_2.css b/src/node_modules/css/default/contributor_2.css new file mode 100644 index 0000000..a9899ad --- /dev/null +++ b/src/node_modules/css/default/contributor_2.css @@ -0,0 +1,3 @@ +div{ + display: none; +} \ No newline at end of file diff --git a/src/node_modules/css/default/crystal_island.css b/src/node_modules/css/default/crystal_island.css new file mode 100644 index 0000000..2d9ac08 --- /dev/null +++ b/src/node_modules/css/default/crystal_island.css @@ -0,0 +1,63 @@ +.deco { + position: relative; +} +.title { + position: absolute; + z-index: 5; + bottom: -18%; + right: 23%; + font-family: var(--titleFont); + font-size: var(--supportersHeadlline); + color: var(--section4TitleColor); +} +.content { + position: absolute; + display: flex; + flex-direction: column; + justify-content: center; + bottom: 24%; + left: 19%; + z-index: 2; + width: 35%; +} +.content h3 { + font-family: var(--titleFont); + font-size: var(--supportersTitleSize); + text-align: center; + color: var(--supportersTitleColor); + margin-top: 0; +} +.content p { + font-size: var(--supportersTextSize); + text-align: center; + margin: 0; +} +.tree, .treeGold{ + position: relative; + width: 36%; + margin: 0 0 -12% 64%; + z-index: 1; +} +.yellowCrystal, .blueCrystal, .purpleCrystal, .stone{ + position: absolute; + width: 5vw; + bottom: 8px; + z-index: 2; +} +.purpleCrystal{ + width: 7vw; +} +.stone{ + width: 8vw; +} +.card{ + position: absolute; + width: 64%; + left: 17px; + bottom: 15px; +} +.deco > .card:last-child { + position: relative; + margin-bottom: -9%; + bottom: 0; +} \ No newline at end of file diff --git a/src/node_modules/css/default/datdot.css b/src/node_modules/css/default/datdot.css new file mode 100644 index 0000000..db68d80 --- /dev/null +++ b/src/node_modules/css/default/datdot.css @@ -0,0 +1,185 @@ + +.section { + position: relative; + display: grid; + grid-template-rows: auto 1fr; + grid-template-columns: 60% 40%; + background-image: linear-gradient(0deg, var(--section1BgGEnd), var(--section1BgGStart)); + padding: 0 2vw; +} +.content { + position: relative; + z-index: 9; + grid-row-start: 1; + grid-column-start: 2; + grid-column-end: 3; + text-align: center; + padding: 0 5%; +} +.subTitleColor { + color: var(--section1TitleColor); +} +.buttonBg { + background-image: linear-gradient(0deg, #ed6e87, #e9627e); +} +.blockchainIsland { + position: relative; + z-index: 2; + grid-row-start: 1; + grid-row-end: 3; + grid-column-start: 1; +} +.blossomIsland { + position: relative; + z-index: 2; + grid-column-start: 2; + grid-row-start: 2; + grid-row-end: 3; + padding-left: 2rem; + align-self: end; + width: 90%; +} +.cloud1 { + position: absolute; + z-index: 4; + width: 10vw; + bottom: 10vh; + left: 5vw; +} +.cloud2 { + position: absolute; + z-index: 4; + width: 14vw; + bottom: -8vh; + left: 42vw; +} +.cloud3 { + position: absolute; + z-index: 1; + width: 8vw; + bottom: 15vh; + left: 52vw; +} +.cloud4 { + position: absolute; + width: 6vw; + bottom: 60%; + right: 5vw; +} +.cloud5 { + position: absolute; + z-index: 1; + width: 18vw; + bottom: -10vh; + right: 2vw; +} +@media only screen and (max-width: 1560px) { + .content { + padding: 0; + } + .blossomIsland { + margin-top: 30px; + width: 35vw; + } +} +@media only screen and (max-width: 1024px) { + .section1 { + grid-template-columns: 55% 45%; + } + .content { + grid-column-start: 1; + padding: 0 15vw; + } + .blockchainIsland { + grid-row-start: 2; + } + .blossomIsland { + width: 90%; + margin-left: 2vw; + align-self: center; + } + .cloud1 { + bottom: 0vh; + } + .cloud2 { + bottom: -5vh; + } + .cloud3 { + bottom: 10%; + } + .cloud4 { + bottom: 60%; + width: 12vw; + } + .cloud5 { + bottom: -4vh; + } +} +@media only screen and (max-width: 812px) { + .cloud3 { + bottom: 10%; + } + .cloud4 { + bottom: 50%; + } +} +@media only screen and (max-width: 768px) { + .cloud3 { + bottom: 12%; + } +} +@media only screen and (max-width: 640px) { + .section1 { + grid-template-rows: repeat(3, auto); + grid-template-columns: 100%; + } + .content { + padding-bottom: 10%; + } + .blockchainIsland { + grid-column-end: 3; + } + .blossomIsland { + grid-row-start: 3; + grid-column-start: 1; + width: 100%; + justify-self: end; + } + .cloud1 { + width: 15vw; + } + .cloud2 { + width: 30vw; + left: 50vw; + bottom: -50vw; + } + .cloud3 { + width: 20vw; + bottom: 5vw; + } + .cloud4 { + top: 30vw; + } +} +@media only screen and (max-width: 414px) { + .content { + padding: 0 5vw 5vh 5vw; + } + .article { + padding-bottom: 2rem; + } + .section { + margin-top: 0; + } + .blossomIsland { + width: 60vw; + margin-left: 35vw; + } + .cloud3 { + bottom: 5vh; + } + .cloud4 { + bottom: 35%; + width: 15vw; + } +} diff --git a/src/node_modules/css/default/demo.css b/src/node_modules/css/default/demo.css new file mode 100644 index 0000000..740104b --- /dev/null +++ b/src/node_modules/css/default/demo.css @@ -0,0 +1,57 @@ +:host{ + --bodyFont: 'Nunito', Arial, sans-serif;--bodyColor: #333333;--bodyBg: #b4e4fd;--menuSize: 1.4rem;--titleFont: 'Slackey', Arial, sans-serif;--titleSize: 5rem;--titleSizeM: 3.6rem;--titlesSizeS: 2.8rem;--titleColor: #fff;--playBgGStart: #b3e2ff;--playBgGEnd: #aae6ed;--subTitleSize: 4.2rem;--section1TitleColor: #e14365;--section2TitleColor: #00a6ad;--section3TitleColor: #b337fb;--section4TitleColor: #b06d56;--section5TitleColor: #4aa95b;--articleSize: 1.4rem;--articleColor: #333333;--section1BgGStart: #aae6ed;--section1BgGEnd: #a1e9da;--section2BgGStart: #a1e9da;--section2BgGEnd: #9db9ee;--section3BgGStart: #9db9ee;--section3BgGEnd: #9a91ff;--section4BgGStart: #9a91ff;--section4BgGEnd: #beb2d7;--section5BgGStart: #beb2d7;--section5BgGMiddle: #eddca4;--section5BgGEnd: #b4e4fd;--sectionButtonSize: 1.4rem;--roadmapHeadlline: 4rem;--roadmapHeadllineM: 3rem;--roadmapHeadllineS: 1.6rem;--roadmapTitleSize: 2rem;--roadmapTitleSizeM: 1.6rem;--roadmapTitleColor: #00a6ad;--roadmapTextSize: 1.6rem;--roadmapTextSizeM: 1.3rem;--contributorsBg: #fdfbee;--contributorsTextSize: 1.4rem;--contributorsTextSizeS: 1.2rem;--contributorsCareerColor: #999999;--footerTextColor: #333333;--footerBg: #b4e4fd; +} +html { + font-size: 82.5%; + scroll-behavior: smooth; +} +body { + font-family: var(--bodyFont); + font-size: 1.4rem; + color: var(--bodyColor); + margin: 0; + padding: 0; + background-color: var(--bodyBg); + overflow-x: hidden; +} +a { + text-decoration: none; +} +button { + outline: none; + border: none; + font-family: var(--titleFont); + font-size: var(--sectionButtonSize); + color: var(--titleColor); + border-radius: 2rem; + padding: 1.2rem 3.8rem; + cursor: pointer; +} +img { + width: 100%; + height: auto; +} +article { + font-size: var(--articleSize); + color: var(--articleColor); + line-height: 2.5rem; + padding-bottom: 4rem; +} +@media only screen and (min-width: 2561px) { + article { + font-size: calc(var(--articleSize) * 1.5 ); + line-height: calc(2.5rem * 1.5); + } + button { + font-size: calc(var(--sectionButtonSize) * 1.5 ); +} +} +@media only screen and (min-width: 4096px) { + article { + font-size: calc(var(--articleSize) * 2.25 ); + line-height: calc(2.5rem * 2.25); + } + button { + font-size: calc(var(--sectionButtonSize) * 2.25 ); + } +} \ No newline at end of file diff --git a/src/node_modules/css/default/editor.css b/src/node_modules/css/default/editor.css new file mode 100644 index 0000000..908e7b3 --- /dev/null +++ b/src/node_modules/css/default/editor.css @@ -0,0 +1,151 @@ + +.section { + position: relative; + display: grid; + grid-template-rows: auto 1fr; + grid-template-columns: 40% 60%; + background-image: linear-gradient(0deg, var(--section2BgGEnd), var(--section2BgGStart)); + padding: 5vw 2vw; +} +.content { + position: relative; + z-index: 9; + grid-row-start: 1; + grid-column-start: 1; + grid-column-end: 2; + text-align: center; + padding: 0 5%; + margin-bottom: 86px; +} +.subTitleColor { + color: var(--section2TitleColor); +} +.buttonBg { + background-image: linear-gradient(0deg, #4dc7be, #35bdb9); +} +.scene { + position: relative; + grid-row-start: span 2; + grid-column-start: 2; +} +.objects { + position: relative; +} +.screenshot { + width: 80%; + margin-bottom: -5.5%; + margin-left: 10%; +} +.logo { + position: absolute; + left: 0%; + bottom: -20%; + width: 20%; +} +.deco { + position: absolute; + right: 0; + bottom: -18.5%; + width: 100%; + display: flex; + align-items: flex-end; + justify-content: flex-end; +} +.tree { + width: 13%; +} +.stone { + position: relative; + width: 10%; + right: -3%; +} +.island { +} +.energyIsland { + grid-row-start: 2; + grid-column-start: 1; + grid-column-end: 2; + width: 80%; + justify-self: center; +} +.cloud1 { + position: absolute; + width: 10vw; + left: 2vw; + bottom: 0; + z-index: 3; +} +.cloud2 { + position: absolute; + width: 15vw; + left: 38vw; + bottom: -35vw; + z-index: 2; +} +.cloud3 { + position: absolute; + width: 8vw; + right: 30vw; + bottom: -34vw; + z-index: 3; +} +.cloud4 { + position: absolute; + width: 14vw; + right: 6vw; + bottom: -40vw; + z-index: 3; +} +.cloud5 { + position: absolute; + width: 8vw; + right: 2vw; + bottom: -10vw; + z-index: 2; +} +@media only screen and (max-width: 1024px) { + .content { + grid-row-start: 1; + grid-column-end: 3; + } + .scene { + grid-row-start: 2; + } + .energyIsland { + align-self: end; + } +} + +@media only screen and (max-width: 640px) { + .scene { + grid-column-start: 1; + grid-column-end: 3; + } + .energyIsland { + grid-row-start: 3; + grid-column-start: 1; + grid-column-end: 3; + width: 60%; + justify-self: start; + } + .cloud1 { + width: 16vw; + } + .cloud2 { + width: 20vw; + left: 50vw; + bottom: 10vw; + } + .cloud3 { + width: 15vw; + bottom: 50vw; + } + .cloud4 { + width: 25vw; + bottom: -85vw; + } + .cloud5 { + width: 15vw; + bottom: 30vw; + } +} diff --git a/src/node_modules/css/default/footer.css b/src/node_modules/css/default/footer.css new file mode 100644 index 0000000..a7add8c --- /dev/null +++ b/src/node_modules/css/default/footer.css @@ -0,0 +1,62 @@ + +.footer { + display: grid; + grid-template-rows: auto; + grid-template-columns: 1fr; + color: var(--footerTextColor); + padding-top: 4vw; + padding-bottom: 0.5%; + background-color: var(--footerBg); +} +.copyright { + text-align: center; + align-self: center; +} +.scene { + position: relative; + width: 60%; + max-width: 1200px; + margin: 0 auto; + display: grid; + grid-template-rows: auto; + grid-template-columns: repeat(4, 25%); +} +.contacts { + display: flex; + justify-content: center; + align-items: center; + grid-row-start: 2; + grid-column-start: 2; + grid-column-end: 4; + margin-top: -2%; +} +.contacts a { + margin: 0 2rem; +} +.icon { + width: 6vw; +} +.island { + grid-row-start: 1; + grid-row-end: 6; + grid-column-start: 1; + grid-column-end: 5; +} +@media only screen and (min-width: 1440px) { + .icon { + max-width: 10rem; + } +} +@media only screen and (max-width: 1200px) { + .contacts a { + margin: 0 1.5vw; + } +} +@media only screen and (max-width: 1024px) { + .scene { + width: 80%; + } + .icon { + width: 8vw; + } +} diff --git a/src/node_modules/css/default/graph_explorer.css b/src/node_modules/css/default/graph_explorer.css new file mode 100644 index 0000000..429662c --- /dev/null +++ b/src/node_modules/css/default/graph_explorer.css @@ -0,0 +1,174 @@ +main{ + max-height: 50vh; + min-height: 20vh; + overflow-y: scroll; + font-family: monospace; +} +.entry > .slot_list{ + cursor: pointer; + transition: color 1s ease, background-color 1s ease; +} +.entry > .slot_list > span:hover{ + background: #ada1c6; + padding: 2px 3px; +} +.entry > .entries{ + display: none; +} +.entries.show{ + display: block; +} +.entry.focus > .slot_list{ + background: #ada1c6; +} +.slot_list > span:not(.odd){ + display: none; +} +.slot_list.on > span, +.slot_list > span.on{ + display: inline; +} +/* .entry > .slot_list > .before::before{ + content: '❔'; +} */ +.comp > .slot_list > .type_emo{ + color: green; +} +.comp > .slot_list > .type_emo::before{ + content: '🧩'; +} +.theme > .slot_list > .type_emo::before{ + content: '🎨'; +} +.action > .slot_list > .type_emo::before{ + content: '🔧'; +} +.slot_list{ + white-space: nowrap; + width: 100%; +} +.entries .hi_emo > span{ + margin-left: -0.9px; +} +.entries .lo_emo > span{ + margin-left: -0.9px; +} +.entries .hi_emo::before{ + content: '🔼'; +} +.entries .lo_emo::before{ + content: '🔽'; +} +.slot_list .hub::before{ + content: '➕'; +} +.slot_list .sub::before{ + content: '➕'; +} +.slot_list .input::before{ + content: '🗃'; +} +.slot_list .output::before{ + content: '🗃'; +} +.slot_list .menu_emo::before{ + content: '🛠️'; +} +.slot_list .hub.on::before{ + content: '➖'; +} +.slot_list .sub.on::before{ + content: '➖'; +} +.slot_list .input.on::before{ + content: '🗂'; +} +.slot_list .output.on::before{ + content: '🗂'; +} +.slot_list .menu_emo.on::before{ + content: '🔨'; +} +.entries .lo_emo.on::before{ + content: '🔼'; +} +.entries .hi_emo.on::before{ + content: '🔽'; +} +.slot_list > span.space { + display: inline-block; + width: fit-content; +} +.space > .on{ + display: none; +} +.entry.on > .slot_list > .space > span{ + display: none; +} +.entry.on > .slot_list > .space > .on{ + display: inline; +} +.slot_list > .last{ + display: none; + position: absolute; + right: 3px; + padding: 0 2px; + background-color: black; + color: white; + box-shadow: 0 0 20px 1px rgba(255, 255, 255, 0.5); +} +.slot_list:hover > .last, +.slot_list > .last.show{ + display: inline; +} +.task.chat_active > .slot_list{ + color: green; +} + + + +/*toggle*/ +.toggle_switch { + position: relative; + display: inline-block; + width: 48px; + height: 22px; +} + +.toggle_switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.4s; + border-radius: 22px; +} + +.slider:before { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 4px; + bottom: 4px; + background-color: white; + transition: 0.4s; + border-radius: 50%; +} + +input:checked + .slider { + background-color: #4CAF50; +} + +input:checked + .slider:before { + transform: translateX(26px); +} diff --git a/src/node_modules/css/default/header.css b/src/node_modules/css/default/header.css new file mode 100644 index 0000000..3f14d58 --- /dev/null +++ b/src/node_modules/css/default/header.css @@ -0,0 +1,178 @@ + +.header { + position: relative; + padding-top: 0vw; + background-image: linear-gradient(0deg, var(--playBgGEnd), var(--playBgGStart)); + overflow: hidden; +} +.scene { + position: relative; + margin-top: 5vw; +} +.playIsland { + position: relative; + width: 90%; + margin-top: 0; + margin-left: 5vw; + z-index: 2; +} +.sunCloud { + position: absolute; + top: -4%; + width: 12%; + margin-left: 8vw; + z-index: 1; +} +.sun { + width: 100%; +} +[class^="cloud"] { + transition: left 0.6s, bottom 0.5s, top 0.5s linear; +} +.cloud1 { + position: absolute; + z-index: 2; + width: 7vw; + left: -3vw; + bottom: 0; +} +.cloud2 { + position: absolute; + z-index: 1; + width: 7vw; + left: 10vw; + top: 25%; +} +.cloud3 { + position: absolute; + z-index: 2; + width: 7vw; + height: auto; + top: -2.5%; + right: 14vw; +} +.cloud4 { + position: absolute; + z-index: 1; + width: 5vw; + height: auto; + top: 8%; + right: 6vw; +} +.cloud5 { + position: absolute; + z-index: 1; + width: 12vw; + height: auto; + top: 50%; + left: 2vw; +} +.cloud6 { + position: absolute; + z-index: 3; + width: 12vw; + height: auto; + bottom: 15%; + left: 15vw; +} +.cloud7 { + position: absolute; + z-index: 4; + width: 18vw; + height: auto; + bottom: 25%; + right: 5vw; +} +.title { + position: relative; + z-index: 4; + font-size: var(--titleSize); + font-family: var(--titleFont); + color: var(--titleColor); + text-align: center; + margin: 0; + padding: 2% 2%; +} +.sun { + will-change: transform; +} +.cloud1, .cloud2, .cloud3, .cloud4, .cloud5, .cloud6, .cloud7 { + will-change: transform; +} +@media only screen and (min-width: 1680px) { + .scrollUp .header { + padding-top: 2.5%; + } +} +@media only screen and (min-width: 2561px) { + .scene { + max-width: 90%; + margin-left: auto; + margin-right: auto; + } + .title { + font-size: calc(var(--titleSize) * 1.5); + margin-bottom: 6vh; + } +} +@media only screen and (min-width: 4096px) { + .title { + font-size: calc(var(--titleSize) * 2.25); + } +} +@media only screen and (max-width: 1680px) { + .header { + padding-top: 2vw; + } +} +@media only screen and (max-width: 1280px) { + .header { + padding-top: 3vw; + } + .scrollUp .header { + padding-top: 6.5vh; + } +} +@media only screen and (max-width: 1024px) { + .header { + padding-top: 0%; + } +} +@media only screen and (max-width: 812px) { + .header { + padding-top: 5vh; + } + .title { + padding: 0 5%; + font-size: var(--titleSizeM); + } +} +@media only screen and (max-width: 414px) { + .header { + padding-top: 8vh; + } + .title { + font-size: var(--titlesSizeS); + } + .playIsland { + width: 150%; + margin-left: -26vw; + } + .sunCloud { + top: -2vh; + left: -3vw; + } + .cloud5 { + width: 12vw; + left: -4vw; + top: 64%; + } + .cloud6 { + width: 15vw; + left: 5vw; + } + .cloud7 { + width: 20vw; + right: -5vw; + } +} diff --git a/src/node_modules/css/default/our_contributors.css b/src/node_modules/css/default/our_contributors.css new file mode 100644 index 0000000..dbe27f7 --- /dev/null +++ b/src/node_modules/css/default/our_contributors.css @@ -0,0 +1,274 @@ + +.section { + position: relative; + background-image: linear-gradient(0deg, var(--section5BgGEnd), var(--section5BgGMiddle), var(--section5BgGStart)); + display: grid; + grid-template-rows: auto; + grid-template-columns: repeat(3, 1fr); + padding: 5vw 2vw 10vw 2vw; +} +.content { + position: relative; + z-index: 9; + grid-row-start: 1; + grid-row-end: 2; + grid-column-start: 3; + text-align: center; + padding: 0; +} +.subTitleColor { + color: var(--section5TitleColor); + margin: 0; + padding: 2.5rem 0; +} +.inner { + position: relative; + grid-row-start: 1; + grid-row-end: 3; + grid-column-start: 1; + grid-column-end: 4; +} +.island { + position: relative; + z-index: 10; + width: 62%; +} +.groups { + z-index: 9; + grid-row-start: 2; + grid-row-end: 3; + grid-column-start: 2; + grid-column-end: 4; + width: 100%; + display: grid; + grid-template-rows: auto; + grid-template-columns: repeat(12, 12.5%); + justify-self: end; + margin-top: 20%; +} +.group { + position: relative; + z-index: 4; + width: 100%; +} +.group:nth-child(4n) { +grid-column-start: 1; +grid-column-end: 4; +} +.group:nth-child(4n + 1) { +grid-column-start: 5; +grid-column-end: 8; +} +.group:nth-child(4n + 2) { +grid-column-start: 2; +grid-column-end: 5; +} +.group:nth-child(4n + 3) { +grid-column-start: 6; +grid-column-end: 9; +} + +.group:nth-child(1) { +grid-column-start: 4; +grid-column-end: 7; +} +@media only screen and (max-width: 1024px) { + .section { + grid-template-columns: 1fr; + } + .content { + grid-column-start: 1; + grid-row-start: 1; + } + .inner { + grid-column-start: 1; + grid-row-start: 2; + } + .inner .island { + width: 98%; + } + .groups { + position: relative; + grid-column-start: 1; + grid-row-start: 3; + grid-template-columns: 1fr 1fr; + margin-top: 0; + } + .group{ + margin-top: 5%; + } + .group:nth-child(2n + 1) { + grid-column-start: 1; + grid-column-end: 1; + margin-top: -35%; + } + .group:nth-child(2n) { + grid-column-start: 2; + grid-column-end: 2; + } +} +@media only screen and (max-width: 640px) { + .groups { + grid-template-columns: 1fr; + } + .group { + grid-column-end: 1 !important; + width: 82%; + margin-top: 2% !important; + margin-left: 5%; + grid-column-start: 1 !important; + grid-column-end: 1 !important; + } + .group:nth-child(2n) { + margin-left: 15%; + } +} +.avatar { + position: relative; + z-index: 2; +} +.info { + display: flex; + flex-direction: column; + justify-content: center; + font-size: var(--contributorsTextSize); + text-align: center; + background-color: var(--contributorsBg); + padding: 0% 2% 4% 20%; + margin-left: -20%; +} +.name { + color: var(--section5TitleColor); + margin-top: 0; + margin-bottom: 3%; +} +.career { + display: block; + color: var(--contributorsCareerColor); +} +.cloud1 { + position: absolute; + z-index: 2; + width: 8vw; + top: 10vw; + left: 5vw; +} +.cloud2 { + position: absolute; + z-index: 3; + width: 12vw; + top: 5vw; + left: 20vw; +} +.cloud3 { + position: absolute; + z-index: 4; + width: 6vw; + top: 15vw; + left: 50vw; +} +.cloud4 { + position: absolute; + z-index: 5; + width: 12vw; + bottom: 12vw; + left: 5vw; +} +.cloud5 { + position: absolute; + z-index: 5; + width: 8vw; + bottom: 5vw; + left: 30vw; +} +.cloud6 { + position: absolute; + z-index: 4; + width: 14vw; + bottom: 0; + right: 25vw; +} +.cloud7 { + position: absolute; + z-index: 3; + width: 6vw; + bottom: 5vw; + right: 10vw; +} +@media only screen and (min-width: 2561px) { + .info { + font-size: var(--contributorsTextSizeS); + } + .cloud1 { + width: 12vw; + top: 30vw; + } + .cloud2 { + top: 22vw; + } + .cloud3 { + width: 12vw; + top: 35vw; + left: 75vw; + } + .cloud4 { + z-index: 1; + width: 20vw; + bottom: 40vw; + } + .cloud5 { + width: 15vw; + left: 10vw; + bottom: 20vw; + } + .cloud6 { + width: 30vw; + bottom: 5vw; + right: 35vw; + } + .cloud7 { + width: 15vw; + bottom: 20vw; + } +} +@media only screen and (max-width: 414px) { + .groups { + width: 100%; + } + .cloud1 { + top: 63vw; + } + .cloud2 { + top: 56vw; + } + .cloud3 { + top: 65vw; + } + .cloud4 { + bottom: 30vw; + } + .cloud5 { + bottom: 10vw; + } + .cloud6 { + bottom: 5vw; + } + .cloud7 { + bottom: 8vw; + } +} +@media only screen and (min-width: 414px) +and (max-width: 736px) and (orientation: landscape) { + .section { + margin-top: -1px; + } + .cloud1 { + top: 50vw; + } + .cloud2 { + top: 48vw; + } + .cloud3 { + top: 55vw; + } +} diff --git a/src/node_modules/css/default/smartcontract_codes.css b/src/node_modules/css/default/smartcontract_codes.css new file mode 100644 index 0000000..ae9d27e --- /dev/null +++ b/src/node_modules/css/default/smartcontract_codes.css @@ -0,0 +1,135 @@ + +.section { + position: relative; + display: grid; + grid-template-rows: auto 1fr; + grid-template-columns: 60% 40%; + background-image: linear-gradient(0deg, var(--section3BgGEnd), var(--section3BgGStart)); + padding: 3vw 2vw 0 2vw; +} +.content { + position: relative; + z-index: 9; + grid-row-start: 1; + grid-column-start: 2; + grid-column-end: 3; + text-align: center; + padding: 0 5%; +} +.subTitleColor { + color: var(--section3TitleColor); + margin-top: 0; +} +.buttonBg { + background-image: linear-gradient(0deg, #900df8, #ac1cf6); +} +.scene { + grid-row-start: span 2; + grid-column-start: 1; +} +.deco { + position: relative; +} +.screenshot { + width: 65%; + margin-left: 15%; + margin-bottom: -6%; +} +.trees { + position: absolute; + right: 10%; + bottom: -20%; + width: 15%; +} +.logo { + position: absolute; + left:6%; + bottom: -20%; + width: 15%; +} +.island { +} +.sceneMedium { + grid-row-start: 2; + grid-column-start: 2; + display: grid; + grid-template: 1fr / 65% 35%; + align-items: center; +} +.container { + position: relative; +} +.sceneMedium .deco:nth-child(1) { + width: 80%; + justify-self: center; +} +.sceneMedium .deco:nth-child(2) { + +} +.blossom { + width: 55%; + margin: 0 0 -10% 12%; +} +.islandMiddle { + +} +.tree { + position: relative; + width: 50%; + margin: 0 auto; + margin-bottom: -11%; + z-index: 2; +} +.islandRight { + +} +.stone { + position: absolute; + right: 12%; + bottom: 3%; + width: 22%; +} +.smallStone { + position: absolute; + left: 7%; + bottom: 5%; + width: 14%; +} +@media screen and (min-width: 2561px) { + .tree { + margin-bottom: -10.5%; + } +} +@media screen and (min-width: 1025px) and (max-width: 1200px) { + .sceneMedium { + margin-top: 4.5rem; + } +} +@media screen and (max-width: 1024px) { + .content { + grid-column-start: 1; + margin-bottom: 60px; + } +} +@media screen and (max-width: 640px) { + .scene { + grid-row-start: 2; + grid-column-end: 3; + } + .sceneMedium { + grid-row-start: 3; + grid-column-start: 1; + grid-column-end: 3; + } + .sceneMedium .deco:nth-child(1) { + width: 90%; + } + .sceneMedium .deco:nth-child(2) { + width: 80%; + justify-self: center; + align-self: center; + } + .tree { + bottom: -5.5%; + } +} diff --git a/src/node_modules/css/default/supporters.css b/src/node_modules/css/default/supporters.css new file mode 100644 index 0000000..a9f4c57 --- /dev/null +++ b/src/node_modules/css/default/supporters.css @@ -0,0 +1,157 @@ + +.section { + position: relative; + background-image: linear-gradient(0deg, var(--section4BgGEnd), var(--section4BgGStart)); + display: grid; + grid-template-rows: repeat(2, auto); + grid-template-columns: 33% 34% 33%; + padding-top: 10vw; + z-index: 1; +} +.cloud1 { + position: absolute; + width: 8vw; + top: 25vw; + left: 8vw; + z-index: 5; +} +.cloud2 { + position: absolute; + width: 15vw; + top: 10vw; + left: 50vw; + z-index: 6; +} +.cloud3 { + position: absolute; + width: 15vw; + top: 30vw; + right: 10vw; + z-index: 5; +} +.cloud4 { + position: absolute; + width: 8vw; + bottom: 28vw; + right: 5vw; + z-index: 4; +} +.cloud5 { + position: absolute; + width: 12vw; + bottom: -3vw; + right: 6vw; + z-index: 5; +} +.cloud6 { + position: absolute; + width: 8vw; + bottom: -10vw; + right: 2vw; + z-index: 6; +} +.scene { + position: relative; + width: 30vw; + margin-top: 6em; +} +.scene:nth-child(3n) { + grid-column-start: 2; + transform: translateY(20px); +} +.scene:nth-child(3n + 1) { + grid-column-start: 3; +} +.scene:nth-child(3n + 2) { + grid-column-start: 1; + transform: translateY(-170px); +} +.scene:first-child { + width: 50vw; + grid-column-start: 2; + grid-column-end: 4; +} +.scene:first-child .tree{ + width: 50%; + margin: 0 0 -11% -12%; +} +.scene:first-child .content{ + left: 38%; +} +.scene:first-child .yellowCrystal{ + width: 20%; + left: 14%; + bottom: 0px; +} +.scene:first-child .card{ + left: 20%; + bottom: 12px; + width: 73%; +} + +@media only screen and (min-width: 3840px) { + .info h3 { + margin-bottom: 6px; + font-size: calc( var(--supportersTitleSizeM) * 2); + } + .info p { + font-size: calc( var(--supportersTextSizeM) * 2); + } +} + +@media only screen and (max-width: 1024px) { + .section{ + grid-template-columns: 50% 50%; + } + .scene{ + width: 40vw; + margin-left: 14%; + } + .scene:nth-child(2n) { + grid-column-start: 1; + transform: translateY(0); + } + .scene:nth-child(2n + 1) { + grid-column-start: 2; + transform: translateY(-35%); + } + .scene:first-child { + width: 70vw; + grid-column-start: 1; + grid-column-end: 3; + transform: translateY(0); + } + .content { + width: 42%; + } + .content p { + font-size: 15px; + } +} +@media only screen and (max-width: 812px) { + .info h3 { + margin-bottom: 6px; + font-size: var(--supportersTitleSizeM); + } + .info p { + font-size: var(--supportersTextSizeM); + } +} +@media only screen and (max-width: 640px) { + .scene:nth-child(2n) { + grid-column-end: 2; + } + .scene:nth-child(2n + 1) { + transform: translateY(-60%); + } + .scene{ + width: 50vw; + margin-left: 1%; + margin-top: 9em; + } + .scene:first-child { + margin-left: 14%; + transform: translateY(0%); + } +} + diff --git a/src/node_modules/css/default/theme_editor.css b/src/node_modules/css/default/theme_editor.css new file mode 100644 index 0000000..c574860 --- /dev/null +++ b/src/node_modules/css/default/theme_editor.css @@ -0,0 +1,63 @@ +main{ + background: #beb2d7; + position: relative; + border-radius: 5px; + padding: 10px; + max-width: 50vw; +} +.content textarea{ + display: none; + min-height: 44vh; + min-width: 100%; +} +.content textarea.active{ + display: block; +} +.tabs{ + display: flex; + overflow-x: scroll; +} +.tabs > .box > span, +.tabs > .plus{ + padding: 0 5px; + margin: 0 5px; + cursor: pointer; +} +.tabs > .box > span.active{ + background: #ada1c6; +} +.tabs > .box > span:hover, +.tabs > .plus:hover{ + background: #ae9cd4; +} +.tabs > .box > span > span:first-child{ + padding-right: 5px; +} +main.select > .single{ + display: none; +} +div.theme { + position: absolute; + background: white; + display: none; + bottom: 21px; + border: 1px solid black; + cursor: pointer; +} +div.theme.active { + display: block; +} +div.theme > .cat { + font-size: 14px; +} +div.theme > .cat > div{ + font-size: 16px; + padding: 2px 7px; +} +div.theme > .cat > div:hover{ + background: grey; +} +.relative{ + position: relative; + display: inline; +} \ No newline at end of file diff --git a/src/node_modules/css/default/theme_widget.css b/src/node_modules/css/default/theme_widget.css new file mode 100644 index 0000000..610ece2 --- /dev/null +++ b/src/node_modules/css/default/theme_widget.css @@ -0,0 +1,39 @@ + +*{ + box-sizing: border-box; +} +section{ + position: fixed; + bottom: 20px; + left: 20px; + z-index: 50; + display: flex; + align-items: end; +} +.btn{ + font-size: 30px; + cursor: pointer; +} +.popup{ + display: none; + position: relative; + bottom: 44px; + margin-left: -42px; + gap: 10px; + align-items: end; + opacity: 75%; +} +.popup.active{ + display: flex; +} +.popup > .box{ + background: #beb2d7; + border-radius: 5px; + padding: 10px; +} +.popup .editor{ + display: none; +} +.popup .editor.active{ + display: block; +} diff --git a/src/node_modules/css/default/topnav.css b/src/node_modules/css/default/topnav.css new file mode 100644 index 0000000..82dee5c --- /dev/null +++ b/src/node_modules/css/default/topnav.css @@ -0,0 +1,92 @@ + +.topnav { + position: relative; + width: 100%; + z-index: 20; + display: grid; + grid-template: 1fr / auto; + background-color: var(--playBgGStart); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + transition: background-color .6s, -webkit-transform .4s, transform .4s, opacity .3s linear; +} +.playLogo { + position: absolute; + top: 10px; + left: 0; + width: 15rem; + z-index: 99; + transition: width .6s ease-in-out; +} +.menu { + padding: 2.5rem; + text-align: right; +} +.menu a { + font-size: var(--menuSize); + margin-left: 1.75%; + color: #575551; + text-transform: uppercase; + transition: color .6s linear; + text-decoration: none; +} +.menu a:hover { + color: #00acff; +} +.topnav.scrollUp { + position: fixed; + background-color: white; + -webkit-transform: none; + transform: none; +} +.topnav.scrollDown { + position: fixed; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + opacity: 0; +} +.scrollUp .playLogo { + width: 10rem; +} +.scrollDown .playLogo { + width: 10rem; + top: 0; +} +@media only screen and (min-width: 4096px) { + .menu a { + font-size: calc(var(--menuSize) * 1.5); + } +} +@media only screen and (max-width: 1024px) { + .playLogo { + width: 9vw; + min-width: 100px; + } +} +@media only screen and (max-width: 960px) { + .topnav { + position: relative; + } + .menu { + padding-top: 3%; + padding-right: 2.5vw; + } + .menu a { + margin-left: 1.5%; + } +} +@media only screen and (max-width: 812px) { + .menu { + display: none; + } + .playLogo { + top: 20px; + min-width: 12vw; + } +} +@media only screen and (max-width: 414px) { + .playLogo { + min-width: 20vw; + } +} diff --git a/src/node_modules/css/index.json b/src/node_modules/css/index.json new file mode 100644 index 0000000..bc6457b --- /dev/null +++ b/src/node_modules/css/index.json @@ -0,0 +1,22 @@ +{ + "default": [ + "index.css", + "contributor.css", + "contributor_1.css", + "contributor_2.css", + "content.css", + "theme_editor.css", + "theme_widget.css", + "datdot.css", + "editor.css", + "smartcontract_codes.css", + "supporters.css", + "our_contributors.css", + "footer.css", + "header.css", + "topnav.css", + "graph_explorer.css", + "crystal_island.css" + ], + "dark": [] +} diff --git a/src/node_modules/datdot.js b/src/node_modules/datdot.js deleted file mode 100755 index f5d8921..0000000 --- a/src/node_modules/datdot.js +++ /dev/null @@ -1,230 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const content = require('content') - -async function datdot(data) { - const css = styles - var graphics = [ - graphic(css.blockchainIsland, './src/node_modules/assets/svg/blockchian-island.svg'), - graphic(css.blossomIsland, './src/node_modules/assets/svg/blossom-island.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [blockchainIsland, blossomIsland, cloud1, cloud2, cloud3, cloud4, cloud5] = await Promise.all(graphics) - // Parallax effects - let cloud1Rellax = new Rellax( cloud1, { speed: 4}) - let cloud2Rellax = new Rellax( cloud2, { speed: 2}) - let cloud3Rellax = new Rellax( cloud3, { speed: 5}) - let cloud4Rellax = new Rellax( cloud4, { speed: 2}) - let cloud5Rellax = new Rellax( cloud5, { speed: 4}) - - let el = bel` -
- ${content(data, css)} - ${blockchainIsland} - ${blossomIsland} - ${cloud1} - ${cloud2} - ${cloud3} - ${cloud4} - ${cloud5} -
- ` - return el -} - -const styles = csjs` -.section { - position: relative; - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 60% 40%; - background-image: linear-gradient(0deg, var(--section1BgGEnd), var(--section1BgGStart)); - padding: 0 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-column-start: 2; - grid-column-end: 3; - text-align: center; - padding: 0 5%; -} -.subTitleColor { - color: var(--section1TitleColor); -} -.buttonBg { - background-image: linear-gradient(0deg, #ed6e87, #e9627e); -} -.blockchainIsland { - position: relative; - z-index: 2; - grid-row-start: 1; - grid-row-end: 3; - grid-column-start: 1; -} -.blossomIsland { - position: relative; - z-index: 2; - grid-column-start: 2; - grid-row-start: 2; - grid-row-end: 3; - padding-left: 2rem; - align-self: end; - width: 90%; -} -.cloud1 { - position: absolute; - z-index: 4; - width: 10vw; - bottom: 10vh; - left: 5vw; -} -.cloud2 { - position: absolute; - z-index: 4; - width: 14vw; - bottom: -8vh; - left: 42vw; -} -.cloud3 { - position: absolute; - z-index: 1; - width: 8vw; - bottom: 15vh; - left: 52vw; -} -.cloud4 { - position: absolute; - width: 6vw; - bottom: 60%; - right: 5vw; -} -.cloud5 { - position: absolute; - z-index: 1; - width: 18vw; - bottom: -10vh; - right: 2vw; -} -@media only screen and (max-width: 1560px) { - .content { - padding: 0; - } - .blossomIsland { - margin-top: 30px; - width: 35vw; - } -} -@media only screen and (max-width: 1024px) { - .section1 { - grid-template-columns: 55% 45%; - } - .content { - grid-column-start: 1; - padding: 0 15vw; - } - .blockchainIsland { - grid-row-start: 2; - } - .blossomIsland { - width: 90%; - margin-left: 2vw; - align-self: center; - } - .cloud1 { - bottom: 0vh; - } - .cloud2 { - bottom: -5vh; - } - .cloud3 { - bottom: 10%; - } - .cloud4 { - bottom: 60%; - width: 12vw; - } - .cloud5 { - bottom: -4vh; - } -} -@media only screen and (max-width: 812px) { - .cloud3 { - bottom: 10%; - } - .cloud4 { - bottom: 50%; - } -} -@media only screen and (max-width: 768px) { - .cloud3 { - bottom: 12%; - } -} -@media only screen and (max-width: 640px) { - .section1 { - grid-template-rows: repeat(3, auto); - grid-template-columns: 100%; - } - .content { - padding-bottom: 10%; - } - .blockchainIsland { - grid-column-end: 3; - } - .blossomIsland { - grid-row-start: 3; - grid-column-start: 1; - width: 100%; - justify-self: end; - } - .cloud1 { - width: 15vw; - } - .cloud2 { - width: 30vw; - left: 50vw; - bottom: -50vw; - } - .cloud3 { - width: 20vw; - bottom: 5vw; - } - .cloud4 { - top: 30vw; - } -} -@media only screen and (max-width: 414px) { - .content { - padding: 0 5vw 5vh 5vw; - } - .article { - padding-bottom: 2rem; - } - .section { - margin-top: 0; - } - .blossomIsland { - width: 60vw; - margin-left: 35vw; - } - .cloud3 { - bottom: 5vh; - } - .cloud4 { - bottom: 35%; - width: 15vw; - } -} -` - -module.exports = datdot \ No newline at end of file diff --git a/src/node_modules/datdot/data.json b/src/node_modules/datdot/data.json new file mode 100644 index 0000000..6443ead --- /dev/null +++ b/src/node_modules/datdot/data.json @@ -0,0 +1,12 @@ +{ + "0": { + "comp": "datdot", + "logo": "", + "image": "", + "sub": { + "content": [ + "x" + ] + } + } +} \ No newline at end of file diff --git a/src/node_modules/datdot/datdot.js b/src/node_modules/datdot/datdot.js new file mode 100755 index 0000000..d42970f --- /dev/null +++ b/src/node_modules/datdot/datdot.js @@ -0,0 +1,108 @@ +const graphic = require('graphic') +const Rellax = require('rellax') +const content = require('content') +const IO = require('io') +const statedb = require('STATE') +/****************************************************************************** + DATDOT COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = datdot + +async function datdot (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'datdot' + const status = {} + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + var graphics = [ + graphic('blockchainIsland', './src/node_modules/assets/svg/blockchian-island.svg'), + graphic('blossomIsland', './src/node_modules/assets/svg/blossom-island.svg'), + graphic('cloud1', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud2', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud3', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud4', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud5', './src/node_modules/assets/svg/cloud.svg'), + ] + + const [blockchainIsland, blossomIsland, cloud1, cloud2, cloud3, cloud4, cloud5] = await Promise.all(graphics) + // Parallax effects + let cloud1Rellax = new Rellax( cloud1, { speed: 4}) + let cloud2Rellax = new Rellax( cloud2, { speed: 2}) + let cloud3Rellax = new Rellax( cloud3, { speed: 5}) + let cloud4Rellax = new Rellax( cloud4, { speed: 2}) + let cloud5Rellax = new Rellax( cloud5, { speed: 4}) + + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ` + const main = shadow.querySelector('section') + main.append(await content({ sid: data.sub?.content?.[0], hub: [css_id] }), blockchainIsland, blossomIsland, cloud1, cloud2, cloud3, cloud4, cloud5) + + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} \ No newline at end of file diff --git a/src/node_modules/datdot/package.json b/src/node_modules/datdot/package.json new file mode 100644 index 0000000..12ee4ba --- /dev/null +++ b/src/node_modules/datdot/package.json @@ -0,0 +1,3 @@ +{ + "main": "datdot.js" +} \ No newline at end of file diff --git a/src/node_modules/editor.js b/src/node_modules/editor.js deleted file mode 100755 index 883e24d..0000000 --- a/src/node_modules/editor.js +++ /dev/null @@ -1,210 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const Content = require('content') - -async function editor (data) { - const css = styles - const graphics = [ - graphic(css.island, './src/node_modules/assets/svg/floating-island.svg'), - graphic(css.energyIsland, './src/node_modules/assets/svg/energy-island.svg'), - graphic(css.tree, './src/node_modules/assets/svg/single-tree.svg'), - graphic(css.stone, './src/node_modules/assets/svg/stone.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [island, energyIsland, tree, stone, cloud1, cloud2, cloud3, cloud4, cloud5] = await Promise.all(graphics) - - // Parallax effects - let cloud1Rellax = new Rellax( cloud1, { speed: 2}) - let cloud2Rellax = new Rellax( cloud2, { speed: 3}) - let cloud3Rellax = new Rellax( cloud3, { speed: 4}) - let cloud4Rellax = new Rellax( cloud4, { speed: 4}) - let cloud5Rellax = new Rellax( cloud5, { speed: 3}) - - let el = bel` -
- ${Content(data, css)} - -
-
- ${data.title} logo - ${data.title} -
- ${stone} - ${tree} -
-
- ${island} -
- ${energyIsland} - ${cloud1} - ${cloud2} - ${cloud3} - ${cloud4} - ${cloud5} -
- ` - return el -} - -const styles = csjs` -.section { - position: relative; - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 40% 60%; - background-image: linear-gradient(0deg, var(--section2BgGEnd), var(--section2BgGStart)); - padding: 5vw 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-column-start: 1; - grid-column-end: 2; - text-align: center; - padding: 0 5%; - margin-bottom: 86px; -} -.subTitleColor { - color: var(--section2TitleColor); -} -.buttonBg { - background-image: linear-gradient(0deg, #4dc7be, #35bdb9); -} -.scene { - position: relative; - grid-row-start: span 2; - grid-column-start: 2; -} -.objects { - position: relative; -} -.screenshot { - width: 80%; - margin-bottom: -5.5%; - margin-left: 10%; -} -.logo { - position: absolute; - left: 0%; - bottom: -20%; - width: 20%; -} -.deco { - position: absolute; - right: 0; - bottom: -18.5%; - width: 100%; - display: flex; - align-items: flex-end; - justify-content: flex-end; -} -.tree { - width: 13%; -} -.stone { - position: relative; - width: 10%; - right: -3%; -} -.island { -} -.energyIsland { - grid-row-start: 2; - grid-column-start: 1; - grid-column-end: 2; - width: 80%; - justify-self: center; -} -.cloud1 { - position: absolute; - width: 10vw; - left: 2vw; - bottom: 0; - z-index: 3; -} -.cloud2 { - position: absolute; - width: 15vw; - left: 38vw; - bottom: -35vw; - z-index: 2; -} -.cloud3 { - position: absolute; - width: 8vw; - right: 30vw; - bottom: -34vw; - z-index: 3; -} -.cloud4 { - position: absolute; - width: 14vw; - right: 6vw; - bottom: -40vw; - z-index: 3; -} -.cloud5 { - position: absolute; - width: 8vw; - right: 2vw; - bottom: -10vw; - z-index: 2; -} -@media only screen and (max-width: 1024px) { - .content { - grid-row-start: 1; - grid-column-end: 3; - } - .scene { - grid-row-start: 2; - } - .energyIsland { - align-self: end; - } -} - -@media only screen and (max-width: 640px) { - .scene { - grid-column-start: 1; - grid-column-end: 3; - } - .energyIsland { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 3; - width: 60%; - justify-self: start; - } - .cloud1 { - width: 16vw; - } - .cloud2 { - width: 20vw; - left: 50vw; - bottom: 10vw; - } - .cloud3 { - width: 15vw; - bottom: 50vw; - } - .cloud4 { - width: 25vw; - bottom: -85vw; - } - .cloud5 { - width: 15vw; - bottom: 30vw; - } -} -` - -module.exports = editor \ No newline at end of file diff --git a/src/node_modules/editor/data.json b/src/node_modules/editor/data.json new file mode 100644 index 0000000..dc2b891 --- /dev/null +++ b/src/node_modules/editor/data.json @@ -0,0 +1,12 @@ +{ + "0": { + "comp": "editor", + "logo": "https://smartcontract-codes.github.io/play-ed/assets/logo.png", + "image": "./src/node_modules/assets/images/smart-contract-ui.jpg", + "sub": { + "content": [ + "x" + ] + } + } +} \ No newline at end of file diff --git a/src/node_modules/editor/editor.js b/src/node_modules/editor/editor.js new file mode 100755 index 0000000..05488f1 --- /dev/null +++ b/src/node_modules/editor/editor.js @@ -0,0 +1,124 @@ +const graphic = require('graphic') +const Rellax = require('rellax') +const Content = require('content') +const IO = require('io') +const statedb = require('STATE') +/****************************************************************************** + EDITOR COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = editor + +async function editor (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'editor' + const status = {} + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + const graphics = [ + graphic('island', './src/node_modules/assets/svg/floating-island.svg'), + graphic('energyIsland', './src/node_modules/assets/svg/energy-island.svg'), + graphic('tree', './src/node_modules/assets/svg/single-tree.svg'), + graphic('stone', './src/node_modules/assets/svg/stone.svg'), + graphic('cloud1', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud2', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud3', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud4', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud5', './src/node_modules/assets/svg/cloud.svg'), + ] + + const [island, energyIsland, tree, stone, cloud1, cloud2, cloud3, cloud4, cloud5] = await Promise.all(graphics) + + // Parallax effects + let cloud1Rellax = new Rellax( cloud1, { speed: 2}) + let cloud2Rellax = new Rellax( cloud2, { speed: 3}) + let cloud3Rellax = new Rellax( cloud3, { speed: 4}) + let cloud4Rellax = new Rellax( cloud4, { speed: 4}) + let cloud5Rellax = new Rellax( cloud5, { speed: 3}) + + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+
+ + ${data.title} +
+ ${stone.outerHTML} + ${tree.outerHTML} +
+
+ ${island.outerHTML} +
+
+ ` + // ---------------------------------------- + const main = shadow.querySelector('section') + main.append(energyIsland, cloud1, cloud2, cloud3, cloud4, cloud5) + main.prepend(await Content({ sid: data.sub?.content?.[0], hub: [css_id] })) + + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} diff --git a/src/node_modules/editor/package.json b/src/node_modules/editor/package.json new file mode 100644 index 0000000..7e6f418 --- /dev/null +++ b/src/node_modules/editor/package.json @@ -0,0 +1,3 @@ +{ + "main": "editor.js" +} \ No newline at end of file diff --git a/src/node_modules/footer.js b/src/node_modules/footer.js deleted file mode 100755 index ed0e192..0000000 --- a/src/node_modules/footer.js +++ /dev/null @@ -1,97 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') - -async function footer(footer) { - const css = styles - let island = await graphic(css.island, './src/node_modules/assets/svg/deco-island.svg') - const graphics = footer.icons.map(icon => graphic(css.icon, icon.imgURL)) - const icons = await Promise.all(graphics) - - let el = bel` -
-
- ${island} - -
- -

${footer.copyright}

-
- ` - return el -} - -let styles = csjs` -.footer { - display: grid; - grid-template-rows: auto; - grid-template-columns: 1fr; - color: var(--footerTextColor); - padding-top: 4vw; - padding-bottom: 0.5%; - background-color: var(--footerBg); -} -.copyright { - text-align: center; - align-self: center; -} -.scene { - position: relative; - width: 60%; - max-width: 1200px; - margin: 0 auto; - display: grid; - grid-template-rows: auto; - grid-template-columns: repeat(4, 25%); -} -.contacts { - display: flex; - justify-content: center; - align-items: center; - grid-row-start: 2; - grid-column-start: 2; - grid-column-end: 4; - margin-top: -2%; -} -.contacts a { - margin: 0 2rem; -} -.icon { - width: 6vw; -} -.island { - grid-row-start: 1; - grid-row-end: 6; - grid-column-start: 1; - grid-column-end: 5; -} -@media only screen and (min-width: 1440px) { - .icon { - max-width: 10rem; - } -} -@media only screen and (max-width: 1200px) { - .contacts a { - margin: 0 1.5vw; - } -} -@media only screen and (max-width: 1024px) { - .scene { - width: 80%; - } - .icon { - width: 8vw; - } -} -` - -module.exports = footer \ No newline at end of file diff --git a/src/node_modules/footer/footer.js b/src/node_modules/footer/footer.js new file mode 100755 index 0000000..0fd3f62 --- /dev/null +++ b/src/node_modules/footer/footer.js @@ -0,0 +1,93 @@ +const graphic = require('graphic') +const IO = require('io') +const STATE = require('STATE') +const name = 'footer' +const statedb = STATE(__filename) +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + const data = require('./instance.json') + data.inputs['footer.css'] = { + $ref: new URL('src/node_modules/css/default/footer.css', location).href + } + return data +} +/****************************************************************************** + APP FOOTER COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = footer + +async function footer (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const on = { + css: inject, + scroll, + json: fill + } + + const send = await IO(id, name, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + let island = await graphic('island', './src/node_modules/assets/svg/deco-island.svg') + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ` + // ---------------------------------------- + const style = shadow.querySelector('style') + const footer = shadow.querySelector('footer') + + sdb.watch(onbatch) + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill ([ opts ]) { + const graphics = opts.icons.map(icon => graphic('icon', icon.imgURL)) + const icons = await Promise.all(graphics) + footer.innerHTML = ` +
+ ${island.outerHTML} + +
+ + ` + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } +} diff --git a/src/node_modules/footer/instance.json b/src/node_modules/footer/instance.json new file mode 100644 index 0000000..d2dde40 --- /dev/null +++ b/src/node_modules/footer/instance.json @@ -0,0 +1,35 @@ +{ + "inputs": { + "footer.json": { + "data": { + "copyright": " PlayProject", + "icons": [ + { + "id": "1", + "name": "email", + "imgURL": "./src/node_modules/assets/svg/email.svg", + "url": "mailto:ninabreznik@gmail.com" + }, + { + "id": "2", + "name": "twitter", + "imgURL": "./src/node_modules/assets/svg/twitter.svg", + "url": "https://twitter.com/playproject_io" + }, + { + "id": "3", + "name": "Github", + "imgURL": "./src/node_modules/assets/svg/github.svg", + "url": "https://github.com/playproject-io" + }, + { + "id": "4", + "name": "Gitter", + "imgURL": "./src/node_modules/assets/svg/gitter.svg", + "url": "https://gitter.im/playproject-io/community" + } + ] + } + } + } +} \ No newline at end of file diff --git a/src/node_modules/footer/package.json b/src/node_modules/footer/package.json new file mode 100644 index 0000000..58b458d --- /dev/null +++ b/src/node_modules/footer/package.json @@ -0,0 +1,3 @@ +{ + "main": "footer.js" +} \ No newline at end of file diff --git a/src/node_modules/graph_explorer/graph_explorer.js b/src/node_modules/graph_explorer/graph_explorer.js new file mode 100644 index 0000000..d82973c --- /dev/null +++ b/src/node_modules/graph_explorer/graph_explorer.js @@ -0,0 +1,729 @@ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const localdb = require('localdb') +const name = 'graph_explorer' +const statedb = STATE(__filename) +const default_slots = [['hubs', 'subs'], ['inputs', 'outputs']] +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + return { + inputs: { + 'graph_explorer.css': { + $ref: new URL('src/node_modules/css/default/graph_explorer.css', location).href + } + } + } +} + +const IO = require('io') +const {copy, get_color, download_json} = require('helper') +/****************************************************************************** + GRAPH COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- + +module.exports = graph_explorer + +async function graph_explorer (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const db = localdb() + const { id, sdb } = await get(opts.sid) + const hub_id = opts.hub[0] + const status = { tab_id: 0, count: 0, entry_types: {}, menu_ids: [] } + const on = { + init, + css: inject, + scroll + } + + const on_add = { + entry: add_entry, + entry_compact: add_entry_compact, + menu: add_action + } + const admin = sdb.req_access(opts.sid) + const send = await IO(id, name, on) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + const style = document.createElement('style') + await sdb.watch(onbatch) + shadow.innerHTML = ` +
+ + + +
+ Compact mode + +
+ +
` + const main = shadow.querySelector('main') + const compact_switch = shadow.querySelector('.toggle_switch > input') + const upload = shadow.querySelector('.upload') + const import_btn = shadow.querySelector('.import') + const export_btn = shadow.querySelector('.export') + shadow.append(style) + shadow.addEventListener('copy', oncopy) + /************************************ + Listeners + *************************************/ + compact_switch.onchange = e => add_root(e.target.checked) + export_btn.onclick = export_fn + import_btn.onclick = () => upload.click() + upload.onchange = import_fn + return el + + /****************************************** + Mix + ******************************************/ + function onbatch (batch) { + for (const {type, data} of batch) { + on[type](data) + } + } + async function oncopy (e) { + const selection = shadow.getSelection() + e.clipboardData.setData('text/plain', copy(selection)) + e.preventDefault() + } + function export_fn () { + const blob = new Blob([JSON.stringify(localStorage, null, 2)], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = 'snapshot.json' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + function import_fn () { + const file = upload.files[0] + const reader = new FileReader() + reader.onload = e => { + const blob = JSON.parse(e.target.result) + admin.load(blob) + } + reader.readAsText(file) + } + async function init ({ data }) { + let id = Object.keys(data).length + 1 + + add({ id, name: 'edit', type: 'action', hubs: [] }) + add({ id, name: 'link', type: 'action', hubs: [] }) + add({ id, name: 'unlink', type: 'action', hubs: [] }) + add({ id, name: 'drop', type: 'action', hubs: [] }) + + status.graph = data + add_root(false) + + function add (args){ + status.menu_ids.push(args.id) + data[id++] = args + } + } + async function add_root(compact) { + status.xentry = null + status.entry_types = {} + status.count = 0 + status.tab_id = 0 + main.innerHTML = '' + const root_entries = Object.values(status.graph).filter(entry => !entry.hubs) + if(compact) + root_entries.forEach((data, i) => add_entry_compact({hub_el: main, data, last: i === root_entries.length - 1, ancestry:[] })) + else + root_entries.forEach((data, i) => add_entry({hub_el: main, data, last: i === root_entries.length - 1, ancestry:[] })) + } + function html_template (data, space, pos){ + const element = document.createElement('div') + element.classList.add(data.type, 'entry', 'a'+data.id) + element.tabIndex = '0' + element.dataset.space = space + element.dataset.pos = pos + return element + } + /****************************************** + Addition Operation + ******************************************/ + // function add_el ({ data, parent, space, grand_last, type }){ + // const is_single = parent.children.length ? false : true + // if(data.root){ + // parent.prepend(add_root({ data, last: false})) + // return + // } + // //hub or sub node check + // if(type === 'inputs') + // parent.append(on_add[type]({ data, space, grand_last, first: is_single})) + // else + // parent.prepend(on_add[type]({ data, space, grand_last, last: is_single})) + // } + + function add_action ({ hub_el, data, last, space = '' }) { + const element = html_template(data, last, space) + hub_el.append(element) + !status.entry_types[data.type] && (status.entry_types[data.type] = Object.keys(status.entry_types).length) + + element.innerHTML = ` +
+ ${space} + + ${data.name} +
` + const name = element.querySelector('.slot_list > .name') + name.onclick = () => send({ type: 'click', to: hub_id, data }) + + } + function add_entry_compact ({ hub_el, data, first, last, space = '', pos, ancestry }) { + //Init + const element = html_template(data, last, space, pos) + !status.entry_types[data.type] && (status.entry_types[data.type] = Object.keys(status.entry_types).length) + ancestry = [...ancestry] + let lo_space = space + (last ? ' ' : '│') + let hi_space = space + (first ? ' ' : '│') + const space_handle = [], els = [] + let slot_no = 0, slot_on + + //HTML + element.innerHTML = ` +
+ ${space}${last ? '└' : first ? "┌" : '├'}${last ? '┗' : first ? "┏" : '┠'} + ${data.name} +
+ + ` + + //Unavoidable mix + const slot_list = element.querySelector('.slot_list') + const name = element.querySelector('.slot_list > .name') + hub_el.append(element) + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + if(copies.length > 1){ + copies.forEach(copy => !copy.previousElementSibling && (copy.style.color = '#000000')) + } + if(ancestry.includes(data.id)){ + name.onclick = () => { + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + copies.forEach((copy, i) => { + if(copy === slot_list) + return + const temp1 = copy.style.color + const temp2 = copy.style.backgroundColor + copy.style.color = '#fff' + copy.style.backgroundColor = '#000000' + setTimeout(() => { + copy.style.color = temp1 + copy.style.backgroundColor = temp2 + }, 1000) + }) + } + return + } + ancestry.push(data.id) + + //Elements + const menu_emo = element.querySelector('.slot_list > .menu_emo') + const type_emo = element.querySelector('.slot_list > .type_emo') + const menu = element.querySelector('.menu') + + //Listeners + type_emo.onclick = type_click + name.onclick = () => send({ type: 'click', to: hub_id, data }) + const slotmap = [] + const data_keys = Object.keys(data) + const new_pair = [[], []] + const slot_handle = [] + let check = false + default_slots.forEach(pair => { + pair.forEach((slot, i) => { + if(data_keys.includes(slot)){ + new_pair[i].push(slot) + check = true + } + }) + }) + check && slotmap.push(new_pair) + slotmap.forEach(handle_slot) + menu_click({el: menu, emo: menu_emo, data: status.menu_ids, pos: 0, type: 'menu'}) + if(getComputedStyle(type_emo, '::before').content === 'none') + type_emo.innerHTML = `[${status.entry_types[data.type]}]` + + //Procedures + async function handle_slot (pair, i) { + const slot_check = [false, false] + const slot_emo = document.createElement('span') + slot_emo.innerHTML = '' + menu_emo.before(slot_emo) + slot_no++ + + pair.forEach((x, j) => { + let gap, mode, emo_on + const pos = !j + const count = status.count++ + const style = document.createElement('style') + const entries = document.createElement('div') + entries.classList.add('entries') + + element.append(style) + if(pos){ + slot_list.before(entries) + mode= 'hi' + gap = hi_space + hi_space += ` ${x.length ? '' : ''}  ` + } + else{ + menu.after(entries) + mode = 'lo' + gap = lo_space + lo_space += ` ${x.length ? '' : ''}  ` + } + style.innerHTML = `.space${count} > .x${mode}{display: none;}` + els.push(slot_emo) + space_handle.push(() => style.innerHTML = `.space${count}${slot_on ? ` > .x${mode}` : ''}{display: none;}`) + if(!x.length){ + const space = document.createElement('span') + space.innerHTML = '   ' + return + } + slot_emo.classList.add('compact') + + slot_handle.push(() => { + slot_emo.classList.add('on') + style.innerHTML = `.space${count} > .${emo_on ? 'x' : ''}${mode}{display: none;}` + // emo_on && space_handle[i]() + slot_check[j] = emo_on = !emo_on + if(slot_check[0] && slot_check[1]) + slot_emo.children[0].innerHTML = '┼' + else if(slot_check[0] && !slot_check[1]) + slot_emo.children[0].innerHTML = '┴' + else if(!slot_check[0] && slot_check[1]) + slot_emo.children[0].innerHTML = '┬' + else{ + slot_emo.children[0].innerHTML = '─' + slot_emo.classList.remove('on') + } + const ids = [] + x.forEach(slot => ids.push(...data[slot])) + handle_click({space: gap, pos, el: entries, data: ids, ancestry, type: 'entry_compact' }) + }) + }) + if(getComputedStyle(slot_emo, '::before').content === 'none') + slot_emo.innerHTML = `${slot_no}─` + } + async function type_click() { + slot_on = !slot_on + // if(status.xentry === type_emo) + // status.xentry = null + // else{ + // status.xentry?.click() + // status.xentry = type_emo + // } + slot_list.classList.toggle('on') + let temp = element + //Find path to root + while(temp.tagName !== 'MAIN'){ + if(temp.classList.contains('entry')){ + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + while(temp.previousElementSibling){ + temp = temp.previousElementSibling + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + } + } + temp = temp.parentElement + } + els.forEach((emo, i) => { + if(!emo.classList.contains('on')){ + space_handle[i]() + } + }) + slot_handle[0] && slot_handle[0]() + slot_handle[1] && slot_handle[1]() + } + async function menu_click({ emo, emo_on, ...rest }, i) { + emo.onclick = () => { + emo.classList.toggle('on') + emo_on = !emo_on + handle_click({space: lo_space, ...rest }) + } + } + } + function add_entry ({ hub_el, data, first, last, space = '', pos, ancestry }) { + //Init + const element = html_template(data, last, space, pos) + !status.entry_types[data.type] && (status.entry_types[data.type] = Object.keys(status.entry_types).length) + ancestry = [...ancestry] + let lo_space = space + (last ? '   ' : '│  ') + let hi_space = space + (first ? '   ' : '│  ') + const space_handle = [], els = [] + let slot_no = 0, slot_on + + //HTML + element.innerHTML = ` +
+ ${space}${last ? '└' : first ? "┌" : '├'}${last ? '┗' : first ? "┏" : '┠'} + ${data.name} +
+ + ` + + //Unavoidable mix + const slot_list = element.querySelector('.slot_list') + const name = element.querySelector('.slot_list > .name') + hub_el.append(element) + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + if(copies.length > 1){ + copies.forEach(copy => !copy.previousElementSibling && (copy.style.color = '#000000')) + } + if(ancestry.includes(data.id)){ + name.onclick = () => { + const copies = main.querySelectorAll('.a'+data.id + '> .slot_list') + copies.forEach((copy, i) => { + if(copy === slot_list) + return + const temp1 = copy.style.color + const temp2 = copy.style.backgroundColor + copy.style.color = '#fff' + copy.style.backgroundColor = '#000000' + setTimeout(() => { + copy.style.color = temp1 + copy.style.backgroundColor = temp2 + }, 1000) + }) + } + return + } + ancestry.push(data.id) + + //Elements + const menu_emo = element.querySelector('.slot_list > .menu_emo') + const type_emo = element.querySelector('.slot_list > .type_emo') + const space_emo = element.querySelector('.slot_list > .space') + const menu = element.querySelector('.menu') + + //Listeners + space_emo.onclick = () => type_click(0) + type_emo.onclick = () => type_click(1) + name.onclick = () => send({ type: 'click', to: hub_id, data }) + const slotmap = [] + const data_keys = Object.keys(data) + const new_pair = [[], []] + const slot_handle = [] + let check = false + default_slots.forEach(pair => { + pair.forEach((slot, i) => { + if(data_keys.includes(slot)){ + new_pair[i].push(slot) + check = true + } + }) + }) + check && slotmap.push(new_pair) + slotmap.forEach(handle_slot) + menu_click({el: menu, emo: menu_emo, data: status.menu_ids, pos: 0, type: 'menu'}) + if(getComputedStyle(type_emo, '::before').content === 'none') + type_emo.innerHTML = `[${status.entry_types[data.type]}]` + + //Procedures + async function handle_slot (pair, i) { + const slot_check = [false, false] + const slot_emo = document.createElement('span') + slot_emo.innerHTML = '' + menu_emo.before(slot_emo) + slot_no++ + + pair.forEach((x, j) => { + let gap, mode, emo_on + const pos = !j + const count = status.count++ + const style = document.createElement('style') + const entries = document.createElement('div') + entries.classList.add('entries') + + element.append(style) + if(pos){ + slot_list.before(entries) + mode= 'hi' + gap = hi_space + hi_space += ` ${x.length ? '' : ''}  ` + } + else{ + menu.after(entries) + mode = 'lo' + gap = lo_space + lo_space += ` ${x.length ? '' : ''}  ` + } + style.innerHTML = `.space${count} > .x${mode}{display: none;}` + els.push(slot_emo) + space_handle.push(() => style.innerHTML = `.space${count}${slot_on ? ` > .x${mode}` : ''}{display: none;}`) + if(!x.length){ + const space = document.createElement('span') + space.innerHTML = '   ' + return + } + slot_emo.classList.add('compact') + + slot_handle.push(() => { + slot_emo.classList.add('on') + style.innerHTML = `.space${count} > .${emo_on ? 'x' : ''}${mode}{display: none;}` + // emo_on && space_handle[i]() + slot_check[j] = emo_on = !emo_on + if(slot_check[0] && slot_check[1]) + slot_emo.children[1].innerHTML = '┼' + else if(slot_check[0] && !slot_check[1]) + slot_emo.children[1].innerHTML = '┴' + else if(!slot_check[0] && slot_check[1]) + slot_emo.children[1].innerHTML = '┬' + else{ + slot_emo.children[1].innerHTML = '─' + slot_emo.classList.remove('on') + } + const ids = [] + x.forEach(slot => ids.push(...data[slot])) + handle_click({space: gap, pos, el: entries, data: ids, ancestry }) + }) + }) + if(getComputedStyle(slot_emo, '::before').content === 'none') + slot_emo.innerHTML = `${slot_no}─` + } + async function type_click(i) { + slot_on = !slot_on + // if(status.xentry === type_emo) + // status.xentry = null + // else{ + // status.xentry?.click() + // status.xentry = type_emo + // } + slot_list.classList.toggle('on') + let temp = element + //Find path to root + while(temp.tagName !== 'MAIN'){ + if(temp.classList.contains('entry')){ + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + while(temp.previousElementSibling){ + temp = temp.previousElementSibling + slot_on ? temp.classList.add('on') : temp.classList.remove('on') + } + } + temp = temp.parentElement + } + els.forEach((emo, i) => { + if(!emo.classList.contains('on')){ + space_handle[i]() + } + }) + // slot_handle[0] && slot_handle[0]() + slot_handle[i] && slot_handle[i]() + } + + async function menu_click({ emo, emo_on, ...rest }, i) { + emo.onclick = () => { + emo.classList.toggle('on') + emo_on = !emo_on + handle_click({space: lo_space, ...rest }) + } + } + } + // async function add_node_data (name, type, parent_id, users, author){ + // const node_id = status.graph.length + // status.graph.push({ id: node_id, name, type: state.code_words[type], room: {}, users }) + // if(parent_id){ + // save_msg({ + // head: [id], + // type: 'save_msg', + // data: {username: 'system', content: author + ' added ' + type.slice(0,-1)+': '+name, chat_id: parent_id} + // }) + // //Add a message in the chat + // if(state.chat_task && parent_id === state.chat_task.id.slice(1)) + // channel_up.send({ + // head: [id, channel_up.send.id, channel_up.mid++], + // type: 'render_msg', + // data: {username: 'system', content: author+' added '+type.slice(0,-1)+': '+name} + // }) + // const sub_nodes = graph[parent_id][state.add_words[type]] + // sub_nodes ? sub_nodes.push(node_id) : graph[parent_id][state.add_words[type]] = [node_id] + // } + // else{ + // graph[node_id].root = true + // graph[node_id].users = [opts.host] + // } + // save_msg({ + // head: [id], + // type: 'save_msg', + // data: {username: 'system', content: author + ' created ' + type.slice(0,-1)+': '+name, chat_id: node_id} + // }) + // const channel = state.net[state.aka.taskdb] + // channel.send({ + // head: [id, channel.send.id, channel.mid++], + // type: 'set', + // data: graph + // }) + + // } + // async function on_add_node (data) { + // const node = data.id ? shadow.querySelector('#a' + data.id + ' > .'+data.type) : main + // node && node.children.length && add_el({ data: { name: data.name, id: status.graph.length, type: state.code_words[data.type] }, parent: node, grand_last: data.grand_last, type: data.type, space: data.space }) + // add_node_data(data.name, data.type, data.id, data.users, data.user) + // } + /****************************************** + Event handlers + ******************************************/ + function handle_focus (e) { + state.xtask = e.target + state.xtask.classList.add('focus') + state.xtask.addEventListener('blur', e => { + if(e.relatedTarget && e.relatedTarget.classList.contains('noblur')) + return + state.xtask.classList.remove('focus') + state.xtask = undefined + }, { once: true }) + } + function handle_popup (e) { + const el = e.target + el.classList.add('show') + popup.style.top = el.offsetTop - 20 + 'px' + popup.style.left = el.offsetLeft - 56 + 'px' + popup.focus() + popup.addEventListener('blur', () => { + el.classList.remove('show') + }, { once: true }) + } + function handle_click ({ el, data, pos, hub_id, type = 'entry', ...rest }) { + el.classList.toggle('show') + if(data && el.children.length < 1){ + length = data.length - 1 + data.forEach((value, i) => on_add[type]({ hub_el: el, data: {...status.graph[value], hub_id}, first: pos ? 0 === i : false, last: pos ? false : length === i, pos, ...rest })) + } + } + async function handle_export () { + const data = await traverse( state.xtask.id.slice(1) ) + download_json(data) + } + async function handle_add (data) { + data = data.slice(2).trim().toLowerCase() + 's' + const input = document.createElement('input') + let node, task_id, space = '', grand_last = true, root = true + //expand other siblings + if(state.xtask){ + node = state.xtask.querySelector('.' + data) + task_id = state.xtask.id.slice(1) + const before = state.xtask.querySelector('.' + data.slice(0,3)) + before.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable: true, view: window})) + node.classList.add('show') + grand_last = state.xtask.dataset.grand_last + space = state.xtask.dataset.space + state.xtask.classList.remove('focus') + state.xtask = undefined + root = false + } + else{ + node = main + task_id = '' + } + node.prepend(input) + input.onkeydown = async (event) => { + if (event.key === 'Enter') { + input.blur() + add_el({ data : { name: input.value, id: status.graph.length, type: state.code_words[data], root }, space, grand_last, type: data, parent: node }) + const users = task_id ? graph[task_id].users : [host] + add_node_data(input.value, data, task_id, users, host) + //sync with other users + if(users.length > 1) + channel_up.send({ + head: [id, channel_up.send.id, channel_up.mid++], + type: 'send', + data: {to: 'task_explorer', route: ['up', 'task_explorer'], users: graph[task_id].users.filter(user => user !== host), type: 'on_add_node', data: {name: input.value, id: task_id, type: data, users, grand_last, space, user: host} } + }) + } + } + input.focus() + input.onblur = () => input.remove() + } + /****************************************** + Tree traversal + ******************************************/ + async function jump (e){ + let target_id = e.currentTarget.dataset.id + const el = main.querySelector('#a'+target_id) + if(el) + el.focus() + else{ + const path = [] + let temp + for(temp = status.graph[target_id]; temp.hub; temp = status.graph[temp.hub[0]]) + path.push(temp.id) + temp = main.querySelector('#a'+temp.id) + target_id = 'a'+target_id + while(temp.id !== target_id){ + const sub_emo = temp.querySelector('.sub_emo') + sub_emo.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable: true, view: window})) + temp.classList.add('show') + temp = temp.querySelector('#a'+path.pop()) + } + temp.focus() + } + + } + async function traverse (id) { + state.result = [] + state.track = [] + recurse(id) + return state.result + } + function recurse (id){ + if(state.track.includes(id)) + return + state.result.push(graph[id]) + state.track.push(id) + for(temp = 0; graph[id].sub && temp < graph[id].sub.length; temp++) + recurse(graph[id].sub[temp]) + for(temp = 0; graph[id].inputs && temp < graph[id].inputs.length; temp++) + recurse(graph[id].inputs[temp]) + for(temp = 0; graph[id].outputs && temp < graph[id].outputs.length; temp++) + recurse(graph[id].outputs[temp]) + } + /****************************************** + Communication + ******************************************/ + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } +} \ No newline at end of file diff --git a/src/node_modules/graph_explorer/node_modules/helper.js b/src/node_modules/graph_explorer/node_modules/helper.js new file mode 100644 index 0000000..8611794 --- /dev/null +++ b/src/node_modules/graph_explorer/node_modules/helper.js @@ -0,0 +1,42 @@ +function copy (selection) { + const range = selection.getRangeAt(0) + const selectedElements = [] + const walker = document.createTreeWalker( + range.commonAncestorContainer, + NodeFilter.SHOW_ELEMENT, + { + acceptNode: function(node) { + return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT + } + }, + false + ) + + while (walker.nextNode()) { + walker.currentNode.tagName === 'SPAN' && selectedElements.push(walker.currentNode) + } + let text = '' + selectedElements.forEach(el => { + const before = getComputedStyle(el, '::before').content + text += (before === 'none' ? '' : before.slice(1, -1)) + el.textContent + text += el.classList.contains('name') ? '\n' : '' + }) + return text +} +function get_color () { + const letters = 'CDEF89' + let color = '#' + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * letters.length)] + } + return color; +} +function download_json (data) { + const json_string = JSON.stringify(data, null, 2); + const blob = new Blob([json_string], { type: 'application/json' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'data.json'; + link.click(); +} +module.exports = {copy, get_color, download_json} \ No newline at end of file diff --git a/src/node_modules/graph_explorer/package.json b/src/node_modules/graph_explorer/package.json new file mode 100644 index 0000000..b3e053a --- /dev/null +++ b/src/node_modules/graph_explorer/package.json @@ -0,0 +1,3 @@ +{ + "main": "graph_explorer.js" +} \ No newline at end of file diff --git a/src/node_modules/graphic.js b/src/node_modules/graphic.js index af90f74..a2cc618 100755 --- a/src/node_modules/graphic.js +++ b/src/node_modules/graphic.js @@ -3,7 +3,7 @@ const loadSVG = require('loadSVG') function graphic(className, url) { return new Promise((resolve, reject) => { - let el = document.createElement('div') + const el = document.createElement('div') el.classList.add(className) loadSVG(url, (err, svg) => { if (err) return console.error(err) diff --git a/src/node_modules/header.js b/src/node_modules/header.js deleted file mode 100755 index ac324bb..0000000 --- a/src/node_modules/header.js +++ /dev/null @@ -1,235 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -const Rellax = require('rellax') - -module.exports = header - -async function header(data) { - const css = styles - var graphics = [ - graphic(css.playIsland, './src/node_modules/assets/svg/play-island.svg'), - graphic(css.sun, './src/node_modules/assets/svg/sun.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud6, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud7, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [playIsland, sun, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) - - // Parallax effects - // let playRellax = new Rellax(playIsland, { speed: 2 }) - let sunRellax = new Rellax(sun, { speed: 2 }) - let cloud1Rellax = new Rellax(cloud1, { speed: 4 }) - let cloud2Rellax = new Rellax(cloud2, { speed: 2 }) - let cloud3Rellax = new Rellax(cloud3, { speed: 4 }) - let cloud4Rellax = new Rellax(cloud4, { speed: 2 }) - let cloud5Rellax = new Rellax(cloud5, { speed: 4 }) - let cloud6Rellax = new Rellax(cloud6, { speed: 3 }) - let cloud7Rellax = new Rellax(cloud7, { speed: 3 }) - - let el = bel` -
-

${data.title}

-
-
- ${cloud1} - ${sun} - ${cloud2} -
- ${cloud3} - ${cloud4} - ${cloud5} - ${cloud6} - ${cloud7} - ${playIsland} -
-
- ` - return el -} - -let styles = csjs` -.header { - position: relative; - padding-top: 0vw; - background-image: linear-gradient(0deg, var(--playBgGEnd), var(--playBgGStart)); - overflow: hidden; -} -.scene { - position: relative; - margin-top: 5vw; -} -.playIsland { - position: relative; - width: 90%; - margin-top: 0; - margin-left: 5vw; - z-index: 2; -} -.sunCloud { - position: absolute; - top: -4%; - width: 12%; - margin-left: 8vw; - z-index: 1; -} -.sun { - width: 100%; -} -[class^="cloud"] { - transition: left 0.6s, bottom 0.5s, top 0.5s linear; -} -.cloud1 { - position: absolute; - z-index: 2; - width: 7vw; - left: -3vw; - bottom: 0; -} -.cloud2 { - position: absolute; - z-index: 1; - width: 7vw; - left: 10vw; - top: 25%; -} -.cloud3 { - position: absolute; - z-index: 2; - width: 7vw; - height: auto; - top: -2.5%; - right: 14vw; -} -.cloud4 { - position: absolute; - z-index: 1; - width: 5vw; - height: auto; - top: 8%; - right: 6vw; -} -.cloud5 { - position: absolute; - z-index: 1; - width: 12vw; - height: auto; - top: 50%; - left: 2vw; -} -.cloud6 { - position: absolute; - z-index: 3; - width: 12vw; - height: auto; - bottom: 15%; - left: 15vw; -} -.cloud7 { - position: absolute; - z-index: 4; - width: 18vw; - height: auto; - bottom: 25%; - right: 5vw; -} -.title { - position: relative; - z-index: 4; - font-size: var(--titleSize); - font-family: var(--titleFont); - color: var(--titleColor); - text-align: center; - margin: 0; - padding: 2% 2%; -} -.sun { - will-change: transform; -} -.cloud1, .cloud2, .cloud3, .cloud4, .cloud5, .cloud6, .cloud7 { - will-change: transform; -} -@media only screen and (min-width: 1680px) { - .scrollUp .header { - padding-top: 2.5%; - } -} -@media only screen and (min-width: 2561px) { - .scene { - max-width: 90%; - margin-left: auto; - margin-right: auto; - } - .title { - font-size: calc(var(--titleSize) * 1.5); - margin-bottom: 6vh; - } -} -@media only screen and (min-width: 4096px) { - .title { - font-size: calc(var(--titleSize) * 2.25); - } -} -@media only screen and (max-width: 1680px) { - .header { - padding-top: 2vw; - } -} -@media only screen and (max-width: 1280px) { - .header { - padding-top: 3vw; - } - .scrollUp .header { - padding-top: 6.5vh; - } -} -@media only screen and (max-width: 1024px) { - .header { - padding-top: 0%; - } -} -@media only screen and (max-width: 812px) { - .header { - padding-top: 5vh; - } - .title { - padding: 0 5%; - font-size: var(--titleSizeM); - } -} -@media only screen and (max-width: 414px) { - .header { - padding-top: 8vh; - } - .title { - font-size: var(--titlesSizeS); - } - .playIsland { - width: 150%; - margin-left: -26vw; - } - .sunCloud { - top: -2vh; - left: -3vw; - } - .cloud5 { - width: 12vw; - left: -4vw; - top: 64%; - } - .cloud6 { - width: 15vw; - left: 5vw; - } - .cloud7 { - width: 20vw; - right: -5vw; - } -} -` diff --git a/src/node_modules/header/data.json b/src/node_modules/header/data.json new file mode 100644 index 0000000..367e462 --- /dev/null +++ b/src/node_modules/header/data.json @@ -0,0 +1,6 @@ +{ + "0": { + "comp": "header", + "title": "Infrastructure for the next-generation Internet" + } +} \ No newline at end of file diff --git a/src/node_modules/header/header.js b/src/node_modules/header/header.js new file mode 100755 index 0000000..6e2df2f --- /dev/null +++ b/src/node_modules/header/header.js @@ -0,0 +1,117 @@ +const graphic = require('graphic') +const Rellax = require('rellax') +const IO = require('io') +const STATE = require('STATE') +const name = 'header' +const statedb = STATE(__filename) +const shopts = { mode: 'closed' } +/****************************************************************************** + HEADER COMPONENT +******************************************************************************/ + +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + return { + inputs: { + 'header.css': { + $ref: new URL('src/node_modules/css/default/header.css', location).href + }, + "header.json": { + data: { + "title": "Infrastructure for the next-generation Internet" + } + } + } + } +} +module.exports = header + +async function header (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const on = { + css: inject, + json: fill, + scroll + } + const send = await IO(id, name, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + var graphics = [ + graphic('playIsland', './src/node_modules/assets/svg/play-island.svg'), + graphic('sun', './src/node_modules/assets/svg/sun.svg'), + graphic('cloud1', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud2', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud3', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud4', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud5', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud6', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud7', './src/node_modules/assets/svg/cloud.svg'), + ] + + const [playIsland, sun, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) + + // Parallax effects + // let playRellax = new Rellax(playIsland, { speed: 2 }) + let sunRellax = new Rellax(sun, { speed: 2 }) + let cloud1Rellax = new Rellax(cloud1, { speed: 4 }) + let cloud2Rellax = new Rellax(cloud2, { speed: 2 }) + let cloud3Rellax = new Rellax(cloud3, { speed: 4 }) + let cloud4Rellax = new Rellax(cloud4, { speed: 2 }) + let cloud5Rellax = new Rellax(cloud5, { speed: 4 }) + let cloud6Rellax = new Rellax(cloud6, { speed: 3 }) + let cloud7Rellax = new Rellax(cloud7, { speed: 3 }) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+

+
+
+
+
+
+ ` + // ---------------------------------------- + const style = shadow.querySelector('style') + const scene = shadow.querySelector('.scene') + const sunCloud = shadow.querySelector('.sunCloud') + const title = shadow.querySelector('.title') + await sdb.watch(onbatch) + scene.append(cloud3, cloud4, cloud5, cloud6, cloud7, playIsland) + sunCloud.append(cloud1, sun, cloud2) + + return el + + function onbatch(batch) { + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + async function fill ([ opts ]) { + title.innerHTML = opts.title + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } +} + diff --git a/src/node_modules/header/package.json b/src/node_modules/header/package.json new file mode 100644 index 0000000..b2524a7 --- /dev/null +++ b/src/node_modules/header/package.json @@ -0,0 +1,3 @@ +{ + "main": "header.js" +} \ No newline at end of file diff --git a/src/node_modules/init.js b/src/node_modules/init.js new file mode 100644 index 0000000..c3b0e0d --- /dev/null +++ b/src/node_modules/init.js @@ -0,0 +1,96 @@ +const default_prefix = 'https://raw.githubusercontent.com/alyhxn/playproject/refs/heads/main/' +const USE_LOCAL = location.hash === '#dev' +const HELPER_MODULES = ['io', 'STATE'] + +module.exports = init +async function init (args, prefix = default_prefix) { + clear_db_on_file_change() + await patch_cache_in_browser(args[4], args[5], prefix) +} + + +function clear_db_on_file_change () { + const last_item = sessionStorage.getItem('last_item') + const now = Date.now() + + const is_reload = performance.getEntriesByType("navigation")[0]?.type === "reload" + + if (is_reload && !(last_item && (now - last_item) < 200)) { + localStorage.clear() + } + + sessionStorage.removeItem('last_item') +} + +document.addEventListener('visibilitychange', () => { + if (document.hidden) { + sessionStorage.setItem('last_item', Date.now()) + } +}) + + +async function patch_cache_in_browser (source_cache, module_cache, prefix) { + let STATE_JS + const state_url = USE_LOCAL ? '/src/node_modules/STATE.js' : prefix + 'src/node_modules/STATE.js' + const localdb_url = USE_LOCAL ? '/src/node_modules/localdb.js' : prefix + 'src/node_modules/localdb.js' + const io_url = USE_LOCAL ? '/src/node_modules/io.js' : prefix + 'src/node_modules/io.js' + + STATE_JS = await Promise.all([ + fetch(state_url, { cache: 'no-store' }).then(res => res.text()), + fetch(localdb_url, { cache: 'no-store' }).then(res => res.text()), + fetch(io_url, { cache: 'no-store' }).then(res => res.text()) + ]).then(([state_source, localdb_source, io_source]) => { + const dependencies = { + localdb: load(localdb_source), + io: load(io_source) + } + const STATE_JS = load(state_source, (dependency) => dependencies[dependency]) + return STATE_JS + function load (source, require) { + const module = { exports: {} } + const f = new Function('module', 'require', source) + f(module, require) + return module.exports + } + }) + + const meta = { modulepath: ['page'], paths: {} } + for (const key of Object.keys(source_cache)) { + const [module, names] = source_cache[key] + const dependencies = names || {} + source_cache[key][0] = patch(module, dependencies, meta) + } + function patch (module, dependencies, meta) { + const MAP = {} + for (const [name, number] of Object.entries(dependencies)) MAP[name] = number + return (...args) => { + const original = args[0] + require.cache = module_cache + require.resolve = resolve + args[0] = require + return module(...args) + function require (name) { + const identifier = resolve(name) + if (HELPER_MODULES.some(suffix => name.endsWith(suffix))) { + const modulepath = meta.modulepath.join('>') + let original_export + if (name.endsWith('STATE')) { original_export = STATE_JS } else { original_export = require.cache[identifier] || (require.cache[identifier] = original(name)) } + const exports = (...args) => original_export(...args, modulepath, Object.keys(dependencies)) + return exports + } else { + // Clear cache for non-STATE and non-io modules + delete require.cache[identifier] + const counter = meta.modulepath.concat(name).join('>') + if (!meta.paths[counter]) meta.paths[counter] = 0 + const localid = `${name}${meta.paths[counter] ? '#' + meta.paths[counter] : ''}` + meta.paths[counter]++ + meta.modulepath.push(localid.replace(/^\.\+/, '').replace('>', ',')) + const exports = original(name) + meta.modulepath.pop(name) + return exports + } + } + } + function resolve (name) { return MAP[name] } + } +} diff --git a/src/node_modules/io.js b/src/node_modules/io.js new file mode 100644 index 0000000..717cfdb --- /dev/null +++ b/src/node_modules/io.js @@ -0,0 +1,46 @@ +const taken = {} + +module.exports = io +function io(seed, alias) { + if (taken[seed]) throw new Error(`seed "${seed}" already taken`) + // const pk = seed.slice(0, seed.length / 2) + // const sk = seed.slice(seed.length / 2, seed.length) + const self = taken[seed] = { id: seed, alias, peer: {} } + const io = { at, on } + return io + + async function at (id, signal = AbortSignal.timeout(1000)) { + if (id === seed) throw new Error('cannot connect to loopback address') + if (!self.online) throw new Error('network must be online') + const peer = taken[id] || {} + // if (self.peer[id] && peer.peer[pk]) { + // self.peer[id].close() || delete self.peer[id] + // peer.peer[pk].close() || delete peer.peer[pk] + // return console.log('disconnect') + // } + // self.peer[id] = peer + if (!peer.online) return wait() // peer with id is offline or doesnt exist + return connect() + function wait () { + const { resolve, reject, promise } = Promise.withResolvers() + signal.onabort = () => reject(`timeout connecting to "${id}"`) + peer.online = { resolve } + return promise.then(connect) + } + function connect () { + signal.onabort = null + const { port1, port2 } = new MessageChannel() + port2.by = port1.to = id + port2.to = port1.by = seed + self.online(self.peer[id] = port1) + peer.online(peer.peer[seed] = port2) + return port1 + } + } + function on (online) { + if (!online) return self.online = null + const resolve = self.online?.resolve + self.online = online + if (resolve) resolve(online) + } +} \ No newline at end of file diff --git a/src/node_modules/lang/en-us.json b/src/node_modules/lang/en-us.json index a73d013..2654b03 100755 --- a/src/node_modules/lang/en-us.json +++ b/src/node_modules/lang/en-us.json @@ -1,279 +1,335 @@ { - "pages": { - "menu": [ + "theme_widget": { + "theme_editor": { + }, + "graph_explorer": { + } + }, + "topnav": { + "links": [ + { + "id": "datdot", + "text": "DatDot", + "url": "datdot" + }, + { + "id": "editor", + "text": "Play Editor", + "url": "editor" + }, + { + "id": "smartcontract_codes", + "text": "Smart Contract Codes", + "url": "smartcontract_codes" + }, + { + "id": "supporters", + "text": "Supporters", + "url": "supporters" + }, + { + "id": "our_contributors", + "text": "Contributors", + "url": "our_contributors" + } + ] + }, + "header": { + "title": "Infrastructure for the next-generation Internet" + }, + "datdot": { + "content": { + "title": "DatDot", + "article": "A system that enables peer-to-peer sharing of storage space and data seeding, eliminating the need for users to rely on renting servers for data hosting or accept the potential unreliability of P2P data sharing. To achieve this goal, our protocol is designed to automate the matchmaking process and conduct periodic checks to ensure reliable hosting and serving of data to readers.", + "action": "Learn more", + "url": "https://datdot.org/" + }, + "logo": "", + "image": "" + }, + "editor": { + "content": { + "title": "Play Editor", + "article": "Web based IDE with interactive UI generator for easy writing, deploying and interacting with Solidity smart contracts.", + "action": "Learn more", + "url": "https://smartcontract-codes.github.io/play-ed/" + }, + "logo": "https://smartcontract-codes.github.io/play-ed/assets/logo.png", + "image": "./src/node_modules/assets/images/smart-contract-ui.jpg" + }, + "smartcontract_codes": { + "content": { + "title": "Smart contract codes", + "article": "Experimental peer-to peer search engine for smart contract source codes. The project aims to make smart contracts verifiable and more transparent.", + "action": "Learn more", + "url": "https://smartcontract.codes/" + }, + "logo": "https://smartcontract.codes/src/assets/images/logo-1.png", + "image": "./src/node_modules/assets/images/smart-contract-codes.jpg" + }, + "supporters": { + "title": "Supporters", + "supporters": [ + { + "date": "2015 - today", + "info": "Various private donations & volunteering", + "deco" : ["yellowCrystal", "card", "tree"] + }, + { + "date": "2018", + "info": "$48.000 / Ethereum Foundation", + "deco" : ["stone", "card", "tree1"] + }, + { + "date": "2020", + "info": "€30.000 / Web3 Foundation", + "deco" : ["purpleCrystal", "card", "tree2"] + }, + { + "date": "2022", + "info": "DOT 5530 / Polkadot Treasury Fund", + "deco" : ["blueCrystal", "card", "tree3"] + }, + { + "date": "2022", + "info": "DOT 5530 / Polkadot Treasury Fund", + "deco" : ["blueCrystal", "card", "tree3"] + } + ] + }, + "our_contributors": { + "content": { + "title": "Contributors", + "article": "We're a community of developers researching how to improve and sustain the Web. We're currently exploring the role that peer-to-peer protocols might play in making the Web more open, equitable and playful." + }, + "contributors": [ { - "id": "datdot", - "text": "DatDot", - "url": "datdot" + "name": "Nina", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-nina.png" }, { - "id": "smartcontractUI", - "text": "Play Editor", - "url": "smartcontractUI" + "name": "Serapath", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "" + }, + "uniq": [{ "id": "contributor_2" }], + "avatar": "./src/node_modules/assets/images/avatar-alex.png" }, { - "id": "smartcontractCodes", - "text": "Smart Contract Codes", - "url": "smartcontractCodes" + "name": "Jam", + "careers": ["Substrate"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-joshua.png" }, { - "id": "supporters", - "text": "Supporters", - "url": "supporters" + "name": "Mauve", + "careers": ["Decentralized tech"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-mauve.png" }, { - "id": "ourContributors", - "text": "Contributors", - "url": "ourContributors" - } - ], - "header": { - "title": "Infrastructure for the next-generation Internet" - }, - "section1": { - "title": "DatDot", - "article": "A system that enables peer-to-peer sharing of storage space and data seeding, eliminating the need for users to rely on renting servers for data hosting or accept the potential unreliability of P2P data sharing. To achieve this goal, our protocol is designed to automate the matchmaking process and conduct periodic checks to ensure reliable hosting and serving of data to readers.", - "action": "Learn more", - "url": "https://datdot.org/", - "logo": "", - "image": "" - }, - "section2": { - "title": "Play Editor", - "article": "Web based IDE with interactive UI generator for easy writing, deploying and interacting with Solidity smart contracts.", - "action": "Learn more", - "url": "https://smartcontract-codes.github.io/play-ed/", - "logo": "https://smartcontract-codes.github.io/play-ed/assets/logo.png", - "image": "./src/node_modules/assets/images/smart-contract-ui.jpg" - }, - "section3": { - "title": "Smart contract codes", - "article": "Experimental peer-to peer search engine for smart contract source codes. The project aims to make smart contracts verifiable and more transparent.", - "action": "Learn more", - "url": "https://smartcontract.codes/", - "logo": "https://smartcontract.codes/src/assets/images/logo-1.png", - "image": "./src/node_modules/assets/images/smart-contract-codes.jpg" - }, - "section4": { - "title": "Supporters", - "supporters": [ - { - "date": "2015 - today", - "info": "Various private donations & volunteering" + "name": "Fiona", + "careers": ["UI/UX & Frontend"], + "contact": { + "twitter": "", + "github": "", + "website": "" }, - { - "date": "2018", - "info": "$48.000 / Ethereum Foundation" + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-fiona.png" + }, + { + "name": "Toshi", + "careers": ["UI/UX & Frontend"], + "contact": { + "twitter": "", + "github": "", + "website": "" }, - { - "date": "2020", - "info": "€30.000 / Web3 Foundation" + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-toshi.png" + }, + { + "name": "Ailin", + "careers": ["NodeJS & Web3"], + "contact": { + "twitter": "", + "github": "", + "website": "" }, - { - "date": "2022", - "info": "DOT 5530 / Polkadot Treasury Fund" - } - ] - }, - "section5": { - "title": "Contributors", - "article": "We're a community of developers researching how to improve and sustain the Web. We're currently exploring the role that peer-to-peer protocols might play in making the Web more open, equitable and playful.", - "contributors": [ - { - "id": "1", - "name": "Nina", - "careers": ["Decentralized tech"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-nina.png" - }, - { - "id": "2", - "name": "Serapath", - "careers": ["Decentralized tech"], - "contact": { - "twitter": "", - "github": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-alex.png" - }, - { - "id": "3", - "name": "Jam", - "careers": ["Substrate"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-joshua.png" - }, - { - "id": "4", - "name": "Mauve", - "careers": ["Decentralized tech"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-mauve.png" - }, - { - "id": "5", - "name": "Fiona", - "careers": ["UI/UX & Frontend"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-fiona.png" - }, - { - "id": "6", - "name": "Toshi", - "careers": ["UI/UX & Frontend"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-toshi.png" - }, - { - "id": "7", - "name": "Ailin", - "careers": ["NodeJS & Web3"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-ailin.png" - }, - - { - "id": "8", - "name": "Kayla", - "careers": ["UX/UI"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-kayla.png" - }, - { - "id": "9", - "name": "Tommings", - "careers": ["Illustration & Design"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-tommings.png" - }, - { - "id": "10", - "name": "Santies", - "careers": ["3D design"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-santies.png" - }, - { - "id": "11", - "name": "Pepe", - "careers": ["Fullstack & 3D"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-pepe.png" - }, - { - "id": "12", - "name": "Jannis", - "careers": ["NodeJS"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-jannis.png" - }, - { - "id": "13", - "name": "Nora", - "careers": ["Frontend & Web3"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-nora.png" - }, - { - "id": "14", - "name": "Mimi", - "careers": ["UI/UX & Frontend"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-mimi.png" - }, - { - "id": "15", - "name": "Helen", - "careers": ["UX/UI and graphic design"], - "contact": { - "twitter": "", - "github": "", - "website": "" - }, - "avatar": "./src/node_modules/assets/images/avatar-helen.png" - } - ] - }, - "footer": { - "copyright": "2023 PlayProject", - "icons": [ - { - "id": "1", - "name": "email", - "imgURL": "./src/node_modules/assets/svg/email.svg", - "url": "mailto:ninabreznik@gmail.com" + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-ailin.png" + }, + + { + "name": "Kayla", + "careers": ["UX/UI"], + "contact": { + "twitter": "", + "github": "", + "website": "" }, - { - "id": "2", - "name": "twitter", - "imgURL": "./src/node_modules/assets/svg/twitter.svg", - "url": "https://twitter.com/playproject_io" + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-kayla.png" + }, + { + "name": "Tommings", + "careers": ["Illustration & Design"], + "contact": { + "twitter": "", + "github": "", + "website": "" }, - { - "id": "3", - "name": "Github", - "imgURL": "./src/node_modules/assets/svg/github.svg", - "url": "https://github.com/playproject-io" + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-tommings.png" + }, + { + "name": "Santies", + "careers": ["3D design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-santies.png" + }, + { + "name": "Pepe", + "careers": ["Fullstack & 3D"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-pepe.png" + }, + { + "name": "Jannis", + "careers": ["NodeJS"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-jannis.png" + }, + { + "name": "Nora", + "careers": ["Frontend & Web3"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-nora.png" + }, + { + "name": "Mimi", + "careers": ["UI/UX & Frontend"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-mimi.png" + }, + { + "name": "Helen", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + { + "name": "Ali", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" }, - { - "id": "4", - "name": "Gitter", - "imgURL": "./src/node_modules/assets/svg/gitter.svg", - "url": "https://gitter.im/playproject-io/community" - } - ] + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + { + "name": "Ibrar", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + }, + { + "name": "Cypher", + "careers": ["UX/UI and graphic design"], + "contact": { + "twitter": "", + "github": "", + "website": "" + }, + "uniq": [{ "id": "contributor_1" }], + "avatar": "./src/node_modules/assets/images/avatar-helen.png" + } + ] + }, + "footer": { + "copyright": " PlayProject", + "icons": [ + { + "id": "1", + "name": "email", + "imgURL": "./src/node_modules/assets/svg/email.svg", + "url": "mailto:ninabreznik@gmail.com" + }, + { + "id": "2", + "name": "twitter", + "imgURL": "./src/node_modules/assets/svg/twitter.svg", + "url": "https://twitter.com/playproject_io" + }, + { + "id": "3", + "name": "Github", + "imgURL": "./src/node_modules/assets/svg/github.svg", + "url": "https://github.com/playproject-io" + }, + { + "id": "4", + "name": "Gitter", + "imgURL": "./src/node_modules/assets/svg/gitter.svg", + "url": "https://gitter.im/playproject-io/community" } - } + ] } +} \ No newline at end of file diff --git a/src/node_modules/localdb.js b/src/node_modules/localdb.js new file mode 100644 index 0000000..8565fc5 --- /dev/null +++ b/src/node_modules/localdb.js @@ -0,0 +1,104 @@ +/****************************************************************************** + LOCALDB COMPONENT +******************************************************************************/ +module.exports = localdb + +function localdb () { + const prefix = '153/' + return { add, read_all, read, drop, push, length, append, find } + + function length (keys) { + const address = prefix + keys.join('/') + return Object.keys(localStorage).filter(key => key.includes(address)).length + } + /** + * Assigns value to the key of an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function add (keys, value, precheck) { + localStorage[(precheck ? '' : prefix) + keys.join('/')] = JSON.stringify(value) + } + /** + * Appends values into an object already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function append (keys, data) { + const pre = keys.join('/') + Object.entries(data).forEach(([key, value]) => { + localStorage[prefix + pre+'/'+key] = JSON.stringify(value) + }) + } + /** + * Pushes value to an array already present in the DB + * + * @param {String[]} keys + * @param {any} value + */ + function push (keys, value) { + const independent_key = keys.slice(0, -1) + const data = JSON.parse(localStorage[prefix + independent_key.join('/')]) + data[keys.at(-1)].push(value) + localStorage[prefix + independent_key.join('/')] = JSON.stringify(data) + } + function read (keys) { + const result = localStorage[prefix + keys.join('/')] + return result && JSON.parse(result) + } + function read_all (addresses) { + let result = localStorage + addresses.forEach(address => { + const temp = {} + Object.entries(result).forEach(([key, value]) => { + if(key.includes(address)) + temp[key] = value + }) + result = temp + }) + const temp = {} + Object.entries(result).forEach(([key, value]) => { + temp[key.replace(/^([^/]+\/){2}/, '')] = JSON.parse(value) + }) + return temp + } + function drop (keys) { + if(keys.length > 1){ + const data = JSON.parse(localStorage[keys[0]]) + let temp = data + keys.slice(1, -1).forEach(key => { + temp = temp[key] + }) + if(Array.isArray(temp)) + temp.splice(keys[keys.length - 1], 1) + else + delete(temp[keys[keys.length - 1]]) + localStorage[keys[0]] = JSON.stringify(data) + } + else + delete(localStorage[keys[0]]) + } + function find (keys, filters, index = 0) { + let index_count = 0 + const address = prefix + keys.join('/') + const target_key = Object.keys(localStorage).find(key => { + if(key.includes(address)){ + const entry = JSON.parse(localStorage[key]) + let count = 0 + Object.entries(filters).some(([search_key, value]) => { + if(entry[search_key] !== value) + return + count++ + }) + if(count === Object.keys(filters).length){ + if(index_count === index) + return key + index_count++ + } + } + }, undefined) + return target_key && JSON.parse(localStorage[target_key]) + } +} \ No newline at end of file diff --git a/src/node_modules/our-contributors.js b/src/node_modules/our-contributors.js deleted file mode 100755 index 41abcec..0000000 --- a/src/node_modules/our-contributors.js +++ /dev/null @@ -1,757 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const Content = require('content') -const Contributor = require('contributor') - -async function our_contributors (data) { - const css = styles - const graphics = [ - graphic(css.island,'./src/node_modules/assets/svg/waterfall-island.svg'), - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud6, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud7, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [island, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) - const contributors = await Promise.all(data.contributors.map(person => Contributor( person, css.group, css))) - - let cloud1Rellax = new Rellax( cloud1, { speed: 0.3}) - let cloud2Rellax = new Rellax( cloud2, { speed: 0.4}) - let cloud3Rellax = new Rellax( cloud3, { speed: 0.3}) - - let el = bel` -
- ${Content(data, css)} - -
- ${island} - ${cloud1} - ${cloud2} - ${cloud3} -
- -
- ${contributors} -
- - ${cloud4} - ${cloud5} - ${cloud6} - ${cloud7} -
- ` - - return el - - function spacing() { - let subTitle = content.querySelector(`.${css.subTitle}`) - let article = content.querySelector(`.${css.article}`) - let contentH = content.offsetTop + subTitle.clientHeight + article.clientHeight - let groups = document.querySelector(`.${css.groups}`) - let screen = window.innerWidth - - } -} - -let styles = csjs` -.section { - position: relative; - background-image: linear-gradient(0deg, var(--section5BgGEnd), var(--section5BgGMiddle), var(--section5BgGStart)); - display: grid; - grid-template-rows: auto; - grid-template-columns: repeat(3, 1fr); - padding: 5vw 2vw 10vw 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-row-end: 2; - grid-column-start: 3; - text-align: center; - padding: 0; -} -.subTitleColor { - color: var(--section5TitleColor); - margin: 0; - padding: 2.5rem 0; -} -.inner { - position: relative; - grid-row-start: 1; - grid-row-end: 3; - grid-column-start: 1; - grid-column-end: 4; -} -.island { - position: relative; - z-index: 7; - width: 62%; -} -.groups { - position: relative; - z-index: 9; - grid-row-start: 2; - grid-row-end: 3; - grid-column-start: 2; - grid-column-end: 4; - width: 100%; - display: grid; - grid-template-rows: auto; - grid-template-columns: repeat(12, 12.5%); - justify-self: end; -} -.group { - position: relative; - z-index: 4; - width: 100%; -} -.group:nth-child(1) { - grid-row-start: 1; - grid-column-start: 4; - grid-column-end: 7; -} -.group:nth-child(2) { - grid-row-start: 2; - grid-column-start: 2; - grid-column-end: 5; - margin-top: -10%; - margin-left: -5%; -} -.group:nth-child(3) { - grid-row-start: 2; - grid-column-start: 6; - grid-column-end: 9; - margin-left: 0vw; - margin-top: -10%; -} -.group:nth-child(4) { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 4; - margin-left: -10%; -} -.group:nth-child(5) { - grid-row-start: 3; - grid-column-start: 5; - grid-column-end: 8; - margin-left: -20%; -} -.group:nth-child(6) { - grid-row-start: 4; - grid-column-start: 3; - grid-column-end: 6; -} -.group:nth-child(7) { - grid-row-start: 4; - grid-column-start: 8; - grid-column-end: 11; -} -.group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 1; - grid-column-end: 4; -} -.group:nth-child(9) { - grid-row-start: 5; - grid-column-start: 5; - grid-column-end: 8; -} -.group:nth-child(10) { - grid-row-start: 5; - grid-column-start: 9; - grid-column-end: 12; -} -.group:nth-child(11) { - grid-row-start: 6; - grid-column-start: 2; - grid-column-end:5; -} -.group:nth-child(12) { - grid-row-start: 6; - grid-column-start: 7; - grid-column-end: 10; -} -.group:nth-child(13) { - grid-row-start: 7; - grid-column-start: 1; - grid-column-end: 4; -} -.group:nth-child(14) { - grid-row-start: 7; - grid-column-start: 5; - grid-column-end: 8; -} -.avatar { - position: relative; - z-index: 2; -} -.info { - display: flex; - flex-direction: column; - justify-content: center; - font-size: var(--contributorsTextSize); - text-align: center; - background-color: var(--contributorsBg); - padding: 0% 2% 4% 20%; - margin-left: -20%; -} -.name { - color: var(--section5TitleColor); - margin-top: 0; - margin-bottom: 3%; -} -.career { - display: block; - color: var(--contributorsCareerColor); -} -.cloud1 { - position: absolute; - z-index: 2; - width: 8vw; - top: 10vw; - left: 5vw; -} -.cloud2 { - position: absolute; - z-index: 3; - width: 12vw; - top: 5vw; - left: 20vw; -} -.cloud3 { - position: absolute; - z-index: 4; - width: 6vw; - top: 15vw; - left: 50vw; -} -.cloud4 { - position: absolute; - z-index: 5; - width: 12vw; - bottom: 12vw; - left: 5vw; -} -.cloud5 { - position: absolute; - z-index: 5; - width: 8vw; - bottom: 5vw; - left: 30vw; -} -.cloud6 { - position: absolute; - z-index: 4; - width: 14vw; - bottom: 0; - right: 25vw; -} -.cloud7 { - position: absolute; - z-index: 3; - width: 6vw; - bottom: 5vw; - right: 10vw; -} -@media only screen and (min-width: 2561px) { - .info { - font-size: calc(var(--contributorsTextSize) * 1.35); - } -} -@media only screen and (min-width: 1920px) { - .groups { - grid-template-columns: repeat(12, 8.33%); - margin-top: 2vw; - } - .group:nth-child(1) { - grid-row-start: 1; - grid-column-start: 7; - grid-column-end: 11; - margin-left: 0; - } - .group:nth-child(2) { - grid-row-start: 2; - grid-column-start: 4; - grid-column-end: 8; - } - .group:nth-child(3) { - grid-row-start: 2; - grid-column-start: 9; - grid-column-end: 13; - } - .group:nth-child(4) { - grid-row-start: 3; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(5) { - grid-row-start: 3; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(6) { - grid-row-start: 4; - grid-column-start: 4; - grid-column-end: 8; - margin-left: 0; - } - .group:nth-child(7) { - grid-row-start: 4; - grid-column-start: 9; - grid-column-end: 13; - margin-left: 0; - } - .group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(9) { - grid-row-start: 5; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(9) { - grid-row-start: 5; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(10) { - grid-row-start: 6; - grid-column-start: 4; - grid-column-end: 8; - margin-left: 0; - } - .group:nth-child(11) { - grid-row-start: 6; - grid-column-start: 9; - grid-column-end: 13; - margin-left: 0; - } - .group:nth-child(12) { - grid-row-start: 7; - grid-column-start: 3; - grid-column-end: 7; - margin-left: 0; - } - .group:nth-child(13) { - grid-row-start: 7; - grid-column-start: 8; - grid-column-end: 12; - margin-left: 0; - } - .group:nth-child(14) { - grid-row-start: 8; - grid-column-start: 4; - grid-column-end: 8; - } - .group:nth-child(15) { - grid-row-start: 8; - grid-column-start: 9; - grid-column-end: 13; - } -} -@media only screen and (max-width: 1900px) { - .group:nth-child(1) { - margin-left: 15%; - } - .group:nth-child(2) { - grid-column-start: 2; - grid-column-end: 5; - } - .group:nth-child(3) { - grid-column-start: 6; - grid-column-end: 9; - } - .group:nth-child(4) { - grid-column-start: 1; - grid-column-end: 4; - } - .group:nth-child(5) { - grid-column-start: 5; - grid-column-end: 8; - } - .group:nth-child(6) { - grid-row-start: 4; - grid-column-start: 3; - grid-column-end: 6; - } - .group:nth-child(7) { - grid-row-start: 5; - grid-column-start: 1; - grid-column-end: 4; - } - .group:nth-child(8) { - grid-row-start: 5; - grid-column-start: 5; - grid-column-end: 8; - } - .group:nth-child(9) { - grid-row-start: 6; - grid-column-start: 2; - grid-column-end: 5; - } - .group:nth-child(10) { - grid-row-start: 6; - grid-column-start: 6; - grid-column-end: 9; - } - .group:nth-child(11) { - grid-row-start: 7; - grid-column-start: 3; - grid-column-end: 6; - } - .group:nth-child(12) { - grid-row-start: 8; - grid-column-start: 1; - grid-column-end: 4; - } - .group:nth-child(13) { - grid-row-start: 8; - grid-column-start: 5; - grid-column-end: 8; - } - .group:nth-child(14) { - grid-row-start: 9; - grid-column-start: 2; - grid-column-end: 5; - } - .group:nth-child(15) { - grid-row-start: 9; - grid-column-start: 6; - grid-column-end: 9; - } -} -@media only screen and (max-width: 1024px) { - .section { - grid-template-columns: 1fr; - } - .content { - grid-row-start: 1; - grid-row-end: 2; - grid-column-start: 1; - padding: 0 5vw; - } - .inner { - justify-content: center; - grid-row-start: 2; - grid-row-end: 3; - padding-top: 10%; - } - .island { - width: 90%; - } - .groups { - grid-row-start: 3; - grid-row-end: 4; - grid-column-start: 1; - width: 90%; - grid-template-columns: 1fr 1fr; - margin: 0 auto; - } - .group:nth-child(1) { - grid-column-start: 1; - grid-column-end: 1; - margin-left: 0; - } - .group:nth-child(2) { - grid-row-start: 2; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(3) { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(4) { - grid-row-start: 4; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(5) { - grid-row-start: 5; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(6) { - grid-row-start: 6; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(7) { - grid-row-start: 7; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(8) { - grid-row-start: 8; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(9) { - grid-row-start: 9; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(10) { - grid-row-start: 10; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(11) { - grid-row-start: 11; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(12) { - grid-row-start: 12; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(13) { - grid-row-start: 13; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .group:nth-child(14) { - grid-row-start: 14; - grid-column-start: 2; - margin-top: -30%; - } - .group:nth-child(15) { - grid-row-start: 15; - grid-column-start: 1; - grid-column-end: 1; - margin-top: -30%; - margin-left: 0; - } - .cloud1 { - width: 10vw; - top: 35vw; - left: 8vw; - } - .cloud2 { - width: 20vw; - top : 22vw; - left: 30vw; - } - .cloud3 { - width: 10vw; - top: 38vw; - left: 70vw; - } -} - -@media only screen and (max-width: 960px) { - .cloud1 { - top: 22vw; - } - .cloud2 { - top: 12vw; - } - .cloud3 { - top: 30vw; - } -} -@media only screen and (max-width: 640px) { - .groups { - position: relative; - z-index: 3; - grid-template-columns: 1fr; - } - .groups > div { - width: 80%; - margin-bottom: 5vw; - } - .group:nth-child(1) { - margin-left: 8vw; - } - .group:nth-child(2) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(3) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -3vw; - } - .group:nth-child(4) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .group:nth-child(5) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -5vw; - } - .group:nth-child(6) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(7) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -3vw; - } - .group:nth-child(8) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .group:nth-child(9) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -5vw; - } - .group:nth-child(10) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(11) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -3vw; - } - .group:nth-child(12) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .group:nth-child(13) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -5vw; - } - .group:nth-child(14) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -10vw; - } - .group:nth-child(15) { - grid-column-end: 1; - margin-left: 8vw; - margin-top: -10vw; - } - .group:nth-child(16) { - grid-column-end: 1; - margin-left: 20vw; - margin-top: -5vw; - } - .info { - font-size: var(--contributorsTextSizeS); - } - .island { - width: 98%; - } - .cloud1 { - width: 12vw; - top: 30vw; - } - .cloud2 { - top: 22vw; - } - .cloud3 { - width: 12vw; - top: 35vw; - left: 75vw; - } - .cloud4 { - z-index: 1; - width: 20vw; - bottom: 40vw; - } - .cloud5 { - width: 15vw; - left: 10vw; - bottom: 20vw; - } - .cloud6 { - width: 30vw; - bottom: 5vw; - right: 35vw; - } - .cloud7 { - width: 15vw; - bottom: 20vw; - } -} -@media only screen and (max-width: 414px) { - .groups { - width: 100%; - } - .cloud1 { - top: 63vw; - } - .cloud2 { - top: 56vw; - } - .cloud3 { - top: 65vw; - } - .cloud4 { - bottom: 30vw; - } - .cloud5 { - bottom: 10vw; - } - .cloud6 { - bottom: 5vw; - } - .cloud7 { - bottom: 8vw; - } -} -@media only screen and (min-width: 414px) -and (max-width: 736px) and (orientation: landscape) { - .section { - margin-top: -1px; - } - .cloud1 { - top: 50vw; - } - .cloud2 { - top: 48vw; - } - .cloud3 { - top: 55vw; - } -} -` - -module.exports = our_contributors diff --git a/src/node_modules/our_contributors/data.json b/src/node_modules/our_contributors/data.json new file mode 100644 index 0000000..b42f4e0 --- /dev/null +++ b/src/node_modules/our_contributors/data.json @@ -0,0 +1,13 @@ +{ + "0": { + "comp": "our_contributors", + "sub": { + "contributor": [ + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x" + ], + "content": [ + "x" + ] + } + } +} \ No newline at end of file diff --git a/src/node_modules/our_contributors/our_contributors.js b/src/node_modules/our_contributors/our_contributors.js new file mode 100755 index 0000000..2d1cc71 --- /dev/null +++ b/src/node_modules/our_contributors/our_contributors.js @@ -0,0 +1,130 @@ +const graphic = require('graphic') +const Rellax = require('rellax') +const Content = require('content') +const Contributor = require('contributor') +const IO = require('io') +const statedb = require('STATE') +/****************************************************************************** + OUR CONTRIBUTORS COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = our_contributors + +async function our_contributors (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'our_contributors' + const status = {} + const on = { + inject, + inject_all, + scroll, + refresh + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + + // ---------------------------------------- + // OPTS + // ---------------------------------------- + const graphics = [ + graphic('island','./src/node_modules/assets/svg/waterfall-island.svg'), + graphic('cloud1', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud2', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud3', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud4', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud5', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud6', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud7', './src/node_modules/assets/svg/cloud.svg'), + ] + + const [island, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6, cloud7] = await Promise.all(graphics) + + let cloud1Rellax = new Rellax( cloud1, { speed: 0.3}) + let cloud2Rellax = new Rellax( cloud2, { speed: 0.4}) + let cloud3Rellax = new Rellax( cloud3, { speed: 0.3}) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + await refresh() + return el + + async function fallback() { + return require('./data.json') + } + async function refresh() { + const data = await sdb.get(opts.sid, fallback) + const temp = [] + for (const sid of data.sub.contributor){ + temp.push(await Contributor({sid, hub: [css_id]})) + } + const contributors = await Promise.all(temp) + shadow.innerHTML = ` +
+
+
+
+
+ ${cloud4.outerHTML} + ${cloud5.outerHTML} + ${cloud6.outerHTML} + ${cloud7.outerHTML} +
+ ` + // ---------------------------------------- + const inner = shadow.querySelector('.inner') + const groups = shadow.querySelector('.groups') + const main = shadow.querySelector('section') + groups.append(...contributors.map(el => el.classList.add('group') || el)) + main.prepend(await Content({ sid: data.sub?.content?.[0], hub: [css_id] })) + inner.append(island, cloud1, cloud2, cloud3) + init_css() + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(...shadow.adoptedStyleSheets, sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} \ No newline at end of file diff --git a/src/node_modules/our_contributors/package.json b/src/node_modules/our_contributors/package.json new file mode 100644 index 0000000..7968aa8 --- /dev/null +++ b/src/node_modules/our_contributors/package.json @@ -0,0 +1,3 @@ +{ + "main": "our_contributors.js" +} \ No newline at end of file diff --git a/src/node_modules/smartcontract-codes.js b/src/node_modules/smartcontract-codes.js deleted file mode 100755 index 01c6182..0000000 --- a/src/node_modules/smartcontract-codes.js +++ /dev/null @@ -1,193 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// Widgets -const graphic = require('graphic') -const Content = require('content') - -module.exports = smartcontract_codes - -async function smartcontract_codes (data) { - const css = styles - const graphics = [ - graphic(css.island, './src/node_modules/assets/svg/floating-island1.svg'), - graphic(css.islandMiddle, './src/node_modules/assets/svg/floating-island2.svg'), - graphic(css.islandRight, './src/node_modules/assets/svg/floating-island2.svg'), - graphic(css.blossom, './src/node_modules/assets/svg/blossom-tree.svg'), - graphic(css.tree, './src/node_modules/assets/svg/single-tree.svg'), - graphic(css.trees, './src/node_modules/assets/svg/two-trees.svg'), - graphic(css.stone, './src/node_modules/assets/svg/stone.svg'), - graphic(css.smallStone, './src/node_modules/assets/svg/small-stone.svg'), - ] - - const [island, islandMiddle, islandRight, blossom, tree, trees, stone, smallStone] = await Promise.all(graphics) - - let el = bel` -
- - ${Content(data, css)} - -
-
- ${data.title} logo - ${data.title} - ${trees} -
- ${island} -
-
-
-
- ${smallStone} - ${stone} - ${blossom} -
- ${islandMiddle} -
-
- ${tree} - ${islandRight} -
-
- -
- ` - - return el -} - -const styles = csjs` -.section { - position: relative; - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 60% 40%; - background-image: linear-gradient(0deg, var(--section3BgGEnd), var(--section3BgGStart)); - padding: 3vw 2vw 0 2vw; -} -.content { - position: relative; - z-index: 9; - grid-row-start: 1; - grid-column-start: 2; - grid-column-end: 3; - text-align: center; - padding: 0 5%; -} -.subTitleColor { - color: var(--section3TitleColor); - margin-top: 0; -} -.buttonBg { - background-image: linear-gradient(0deg, #900df8, #ac1cf6); -} -.scene { - grid-row-start: span 2; - grid-column-start: 1; -} -.deco { - position: relative; -} -.screenshot { - width: 65%; - margin-left: 15%; - margin-bottom: -6%; -} -.trees { - position: absolute; - right: 10%; - bottom: -20%; - width: 15%; -} -.logo { - position: absolute; - left:6%; - bottom: -20%; - width: 15%; -} -.island { -} -.sceneMedium { - grid-row-start: 2; - grid-column-start: 2; - display: grid; - grid-template: 1fr / 65% 35%; - align-items: center; -} -.container { - position: relative; -} -.sceneMedium .deco:nth-child(1) { - width: 80%; - justify-self: center; -} -.sceneMedium .deco:nth-child(2) { - -} -.blossom { - width: 55%; - margin: 0 0 -10% 12%; -} -.islandMiddle { - -} -.tree { - position: relative; - width: 50%; - margin: 0 auto; - margin-bottom: -11%; - z-index: 2; -} -.islandRight { - -} -.stone { - position: absolute; - right: 12%; - bottom: 3%; - width: 22%; -} -.smallStone { - position: absolute; - left: 7%; - bottom: 5%; - width: 14%; -} -@media screen and (min-width: 2561px) { - .tree { - margin-bottom: -10.5%; - } -} -@media screen and (min-width: 1025px) and (max-width: 1200px) { - .sceneMedium { - margin-top: 4.5rem; - } -} -@media screen and (max-width: 1024px) { - .content { - grid-column-start: 1; - margin-bottom: 60px; - } -} -@media screen and (max-width: 640px) { - .scene { - grid-row-start: 2; - grid-column-end: 3; - } - .sceneMedium { - grid-row-start: 3; - grid-column-start: 1; - grid-column-end: 3; - } - .sceneMedium .deco:nth-child(1) { - width: 90%; - } - .sceneMedium .deco:nth-child(2) { - width: 80%; - justify-self: center; - align-self: center; - } - .tree { - bottom: -5.5%; - } -} -` \ No newline at end of file diff --git a/src/node_modules/smartcontract_codes/data.json b/src/node_modules/smartcontract_codes/data.json new file mode 100644 index 0000000..dd6bb78 --- /dev/null +++ b/src/node_modules/smartcontract_codes/data.json @@ -0,0 +1,12 @@ +{ + "0": { + "comp": "smartcontract_codes", + "logo": "https://smartcontract.codes/src/assets/images/logo-1.png", + "image": "./src/node_modules/assets/images/smart-contract-codes.jpg", + "sub": { + "content": [ + "x" + ] + } + } +} \ No newline at end of file diff --git a/src/node_modules/smartcontract_codes/package.json b/src/node_modules/smartcontract_codes/package.json new file mode 100644 index 0000000..91fb572 --- /dev/null +++ b/src/node_modules/smartcontract_codes/package.json @@ -0,0 +1,3 @@ +{ + "main": "smartcontract_codes.js" +} \ No newline at end of file diff --git a/src/node_modules/smartcontract_codes/smartcontract_codes.js b/src/node_modules/smartcontract_codes/smartcontract_codes.js new file mode 100755 index 0000000..cd4008b --- /dev/null +++ b/src/node_modules/smartcontract_codes/smartcontract_codes.js @@ -0,0 +1,127 @@ +const graphic = require('graphic') +const Content = require('content') +const IO = require('io') +const statedb = require('STATE') +/****************************************************************************** + SMARTCONTRACT-CODES COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = smartcontract_codes + +async function smartcontract_codes (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'smartcontract_codes' + const status = {} + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + + // ---------------------------------------- + // OPTS + // ---------------------------------------- + const graphics = [ + graphic('island', './src/node_modules/assets/svg/floating-island1.svg'), + graphic('islandMiddle', './src/node_modules/assets/svg/floating-island2.svg'), + graphic('islandRight', './src/node_modules/assets/svg/floating-island2.svg'), + graphic('blossom', './src/node_modules/assets/svg/blossom-tree.svg'), + graphic('tree', './src/node_modules/assets/svg/single-tree.svg'), + graphic('trees', './src/node_modules/assets/svg/two-trees.svg'), + graphic('stone', './src/node_modules/assets/svg/stone.svg'), + graphic('smallStone', './src/node_modules/assets/svg/small-stone.svg'), + ] + + const [island, islandMiddle, islandRight, blossom, tree, trees, stone, smallStone] = await Promise.all(graphics) + + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+ +
+
+ + ${data.title} + ${trees.outerHTML} +
+ ${island.outerHTML} +
+
+
+
+ ${smallStone.outerHTML} + ${stone.outerHTML} + ${blossom.outerHTML} +
+ ${islandMiddle.outerHTML} +
+
+ ${tree.outerHTML} + ${islandRight.outerHTML} +
+
+ +
+ ` + const main = shadow.querySelector('section') + main.prepend(await Content({ sid: data.sub?.content?.[0], hub: [css_id] })) + + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} diff --git a/src/node_modules/supporters.js b/src/node_modules/supporters.js deleted file mode 100755 index cf053a8..0000000 --- a/src/node_modules/supporters.js +++ /dev/null @@ -1,522 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -const Rellax = require('rellax') -const crystalIsland = require('crystalIsland') - -async function supporters (data) { - const css = styles - let pageTitle = bel`
${data.title}
` - const graphics = [ - // crystals - graphic(css.yellowCrystal,'./src/node_modules/assets/svg/crystal-yellow.svg'), - graphic(css.purpleCrystal,'./src/node_modules/assets/svg/crystal-purple.svg'), - graphic(css.blueCrystal,'./src/node_modules/assets/svg/crystal-blue.svg'), - // stone - graphic(css.stone,'./src/node_modules/assets/svg/stone1.svg'), - // trees - graphic(css.tree,'./src/node_modules/assets/svg/big-tree.svg'), - graphic(css.tree,'./src/node_modules/assets/svg/single-tree1.svg'), - graphic(css.tree,'./src/node_modules/assets/svg/single-tree3.svg'), - graphic(css.tree,'./src/node_modules/assets/svg/single-tree2.svg'), - // islands - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - graphic(css.island,'./src/node_modules/assets/svg/floating-island3.svg'), - // clouds - graphic(css.cloud1, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud2, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud3, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud4, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud5, './src/node_modules/assets/svg/cloud.svg'), - graphic(css.cloud6, './src/node_modules/assets/svg/cloud.svg'), - ] - - const [yellowCrystal, purpleCrystal, blueCrystal, stone, tree, tree1, tree2, tree3, - island, island1, island2, island3, island4, cloud1, cloud2, cloud3, cloud4, cloud5, cloud6] = await Promise.all(graphics) - - // Parallax effects - let cloud1Rellax = new Rellax( cloud1, { speed: 1.5}) - let cloud2Rellax = new Rellax( cloud2, { speed: 1}) - let cloud3Rellax = new Rellax( cloud3, { speed: 1.5}) - let cloud4Rellax = new Rellax( cloud4, { speed: 4}) - let cloud5Rellax = new Rellax( cloud5, { speed: 1.5}) - let cloud6Rellax = new Rellax( cloud6, { speed: 3}) - - let el = bel` -
- - ${crystalIsland(data.supporters[0], [yellowCrystal, tree], island, css, pageTitle)} - ${crystalIsland(data.supporters[1], [stone, tree1], island1, css)} - ${crystalIsland(data.supporters[2], [purpleCrystal], island2, css)} - ${crystalIsland(data.supporters[3], [blueCrystal, tree2], island3, css)} - -
- ${tree3} - ${island4} -
- - ${cloud1} - ${cloud2} - ${cloud3} - ${cloud4} - ${cloud5} - ${cloud6} - -
- ` - return el -} - -let styles = csjs` -.section { - position: relative; - background-image: linear-gradient(0deg, var(--section4BgGEnd), var(--section4BgGStart)); - display: grid; - grid-template-rows: repeat(2, auto); - grid-template-columns: 15% 35% 35% 15%; - padding-top: 10vw; -} -.scene { -} -.scene:nth-child(1) { - position: relative; - z-index: 3; - width: 50vw; - grid-row-start: 1; - grid-row-end: 2; - grid-column-start: 1; - grid-column-end: 4; - margin-left: 30vw; -} -.scene:nth-child(2) { - position: relative; - z-index: 4; - width: 24vw; - grid-row-start: 2; - grid-column-start: 1; - grid-column-end: 3; - margin-top: -15vw; - margin-left: 5vw; -} -.scene:nth-child(3) { - position: relative; - z-index: 4; - width: 24vw; - grid-row-start: 2; - grid-column-start: 2; - margin-top: 3vw; - margin-left: 8vw; -} -.scene:nth-child(4) { - position: relative; - z-index: 4; - width: 30vw; - grid-row-start: 2; - grid-column-start: 3; - margin-top: 5vw; - margin-left: 7vw; -} -.scene:nth-child(5) { - position: relative; - z-index: 2; - grid-row-start: 1; - grid-column-start: 4; - width: 10vw; - align-self: end; - margin-bottom: 12vw; -} -.scene:nth-child(5) .tree { - width: 80%; - margin: 0 auto -1.5vw auto; -} -.deco { - position: relative; -} -.tree { - position: relative; - width: 50%; - margin: 0 0 -11% -9%; - z-index: 2; -} -.yellowCrystal { - position: absolute; - width: 25%; - left: 20%; - bottom: 1%; - z-index: 3; -} -.title { - position: absolute; - bottom: 35%; - right: 18%; - z-index: 5; - font-family: var(--titleFont); - font-size: var(--supportersHeadlline); - color: var(--section4TitleColor); -} -.island { - -} -.content { - position: absolute; - display: flex; - flex-direction: column; - justify-content: center -} -.scene:nth-child(1) .content { - width: 35%; - left: 21vw; - bottom: 2%; - background: url('./src/node_modules/assets/svg/card1.svg') no-repeat; - background-size: cover; - padding: 8% 5% 8% 8%; -} -.content h3 { - font-family: var(--titleFont); - font-size: var(--supportersTitleSize); - text-align: center; - color: var(--supportersTitleColor); - margin-top: 0; -} -.content p { - font-size: var(--supportersTextSize); - text-align: center; - margin: 0; -} -.scene:nth-child(2) .content { - width: 45%; - left: 35%; - bottom: -2vw; - background: url('./src/node_modules/assets/svg/card2.svg') no-repeat; - background-size: cover; - padding: 10% 5% 10% 8%; -} -.scene:nth-child(2) .tree { - position: absolute; - width: 45%; - left: 2vw; - bottom: -0.5vw; -} -.scene:nth-child(2) .stone { - position: absolute; - width: 24%; - right: 5%; - bottom: -2.5vw; - z-index: 2; -} -.scene:nth-child(3) .content { - width: 60%; - left: 5vw; - bottom: -2.2vw; - background: url('./src/node_modules/assets/svg/card3.svg') no-repeat; - background-size: cover; - padding: 10% 5% 10% 8%; -} -.purpleCrystal { - position: absolute; - width: 40%; - left: -6%; - bottom: -2.5vw; - z-index: 3; -} -.scene:nth-child(4) .content { - width: 50%; - left: 6vw; - bottom: -2.5vw; - background: url('./src/node_modules/assets/svg/card4.svg') no-repeat; - background-size: cover; - padding: 10% 5% 10% 8%; -} -.scene:nth-child(4) h3 { - margin-bottom: 10px; -} -.scene:nth-child(4) h3:last-child { - margin-bottom: 0; -} -.scene:nth-child(4) .tree { - position: absolute; - width: 24%; - right: -0.5vw; - bottom: 0vw; -} -.blueCrystal { - position: absolute; - width: 22%; - left: 1vw; - bottom: -3vw; - z-index: 3; -} -.cloud1 { - position: absolute; - width: 8vw; - top: 25vw; - left: 8vw; - z-index: 5; -} -.cloud2 { - position: absolute; - width: 15vw; - top: 10vw; - left: 50vw; - z-index: 6; -} -.cloud3 { - position: absolute; - width: 15vw; - top: 30vw; - right: 10vw; - z-index: 5; -} -.cloud4 { - position: absolute; - width: 8vw; - bottom: 28vw; - right: 5vw; - z-index: 4; -} -.cloud5 { - position: absolute; - width: 12vw; - bottom: -3vw; - right: 6vw; - z-index: 5; -} -.cloud6 { - position: absolute; - width: 8vw; - bottom: -10vw; - right: 2vw; - z-index: 6; -} -@media only screen and (min-width: 3840px) { - .info h3 { - margin-bottom: 6px; - font-size: calc( var(--supportersTitleSizeM) * 2); - } - .info p { - font-size: calc( var(--supportersTextSizeM) * 2); - } -} -@media only screen and (max-width: 1366px) { - .title { - bottom: 17vw; - } -} -@media only screen and (max-width: 1024px) { - .section { - grid-template-columns: repeat(2, 50vw); - } - .scene:nth-child(1) { - grid-row-start: 1; - width: 70vw; - margin-left: 10vw; - } - .scene:nth-child(1) .content { - width: 35%; - left: 28vw; - } - .scene:nth-child(2) { - grid-row-start: 2; - grid-column-start: 1; - width: 40vw; - margin-top: 15vw; - margin-left: 5vw; - } - .scene:nth-child(2) .content { - bottom: -3vw; - left: 28%; - } - .scene:nth-child(2) .stone { - bottom: -4vw; - right: 9%; - } - .scene:nth-child(3) { - grid-row-start: 2; - grid-column-start: 2; - width: 40vw; - margin-top: 20vw; - margin-left: 5vw; - } - .scene:nth-child(3) .content { - left: 8vw; - bottom: -3vw; - } - .purpleCrystal { - bottom: -3.5vw; - } - .scene:nth-child(4) { - grid-row-start: 3; - grid-column-start: 2; - width: 50vw; - margin: 10vw 0 0 -25vw; - } - .scene:nth-child(4) .content { - bottom: -4.5vw; - width: 50%; - left: 8vw; - } - .blueCrystal { - bottom: -5vw; - } - .scene:nth-child(5) { - grid-row-start: 1; - grid-column-start: 3; - width: 15vw; - align-self: start; - margin-top: 15vw; - margin-left: -20vw; - } - .scene:nth-child(5) .tree { - width: 65%; - margin: 0 auto -2vw auto; - } - .title { - font-size: var(--supportersHeadllineM); - bottom: 25vw; - right: 11vw; - } - .cloud1 { - width: 15vw; - top: 30vw; - } - .cloud2 { - width: 25vw; - } - .cloud3 { - width: 18vw; - top: 40vw; - right: 5vw; - } - .cloud4 { - width: 15vw; - bottom: -10vw; - } - .cloud5 { - width: 15vw; - right: 15vw; - bottom: -30vw; - } - .cloud6 { - width: 10vw; - } -} -@media only screen and (max-width: 960px) { - .scene:nth-child(2) .tree { - width: 30%; - } - .scene:nth-child(2) .content { - width: 60%; - left: 18%; - } - .scene:nth-child(5) .tree { - bottom: -0.5vw; - } -} -@media only screen and (max-width: 812px) { - .info h3 { - margin-bottom: 6px; - font-size: var(--supportersTitleSizeM); - } - .info p { - font-size: var(--supportersTextSizeM); - } -} -@media only screen and (max-width: 640px) { - .scene:nth-child(2) { - width: 50vw; - margin-top: 20vw; - } - .scene:nth-child(3) { - width: 50vw; - grid-row-start: 3; - grid-column-start: 2; - margin-left: 0; - margin-top: 5vw; - } - .scene:nth-child(4) { - width: 66vw; - grid-row-start: 4; - grid-column-start: 1; - grid-column-end: 2; - margin-left: 10vw; - margin-top: 20vw; - } - .scene:nth-child(5) { - margin-left: -18vw; - } - .title { - font-size: var(--supportersHeadllineS); - bottom: 25vw; - right: 12vw; - } -} -@media only screen and (max-width: 480px) { - .scene:nth-child(1) { - width: 90vw; - margin-left: 10vw; - } - .scene:nth-child(1) .content { - width: 40%; - left: 36%; - } - .scene:nth-child(2) { - width: 70vw; - margin-top: 20vw; - margin-left: 10vw; - } - .scene:nth-child(2) .content { - width: 60%; - padding: 10% 5% 10% 12%; - bottom: -6vw; - } - .scene:nth-child(2) .stone { - bottom: -8vw; - right: 0; - } - .scene:nth-child(2) .tree { - left: 5vw; - } - .scene:nth-child(3) { - width: 70vw; - margin-left: 20vw; - margin-top: 25vw; - grid-column-start: 1; - } - .scene:nth-child(3) .content { - left: 12vw; - bottom: -6vw; - } - .purpleCrystal { - bottom: -7vw; - } - .scene:nth-child(4) { - width: 66vw; - margin-left: 10vw; - margin-top: 30vw; - } - .scene:nth-child(4) .content { - width: 56%; - } - .blueCrystal { - bottom: -6vw; - } - .scene:nth-child(5) { - grid-row-start: 4; - margin-top: -5vw; - margin-left: -22vw; - } - .scene:nth-child(5) .tree { - bottom: -1vw; - } - .title { - bottom: 32vw; - right: 15vw; - } -} -` - -module.exports = supporters \ No newline at end of file diff --git a/src/node_modules/supporters/data.json b/src/node_modules/supporters/data.json new file mode 100644 index 0000000..d9f6b53 --- /dev/null +++ b/src/node_modules/supporters/data.json @@ -0,0 +1,11 @@ +{ + "0": { + "comp": "supporters", + "title": "Supporters", + "sub": { + "crystal_island": [ + "x", "x", "x", "x", "x" + ] + } + } +} \ No newline at end of file diff --git a/src/node_modules/supporters/package.json b/src/node_modules/supporters/package.json new file mode 100644 index 0000000..51ab392 --- /dev/null +++ b/src/node_modules/supporters/package.json @@ -0,0 +1,3 @@ +{ + "main": "./supporters.js" +} \ No newline at end of file diff --git a/src/node_modules/supporters/supporters.js b/src/node_modules/supporters/supporters.js new file mode 100755 index 0000000..9cbe6c9 --- /dev/null +++ b/src/node_modules/supporters/supporters.js @@ -0,0 +1,121 @@ +const graphic = require('graphic') +const Rellax = require('rellax') +const crystal_island = require('crystal_island') +const IO = require('io') +const statedb = require('STATE') +const default_data = require('./data.json') +/****************************************************************************** + SUPPORTERS COMPONENT +******************************************************************************/ +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = supporters + +async function supporters (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const name = 'supporters' + const status = {} + const on = { + inject, + inject_all, + scroll + } + const sdb = statedb() + const data = await sdb.get(opts.sid, fallback) + console.log(data) + const {send, css_id} = await IO({ + id: data.id, + name, + type: 'comp', + comp: name, + hub: opts.hub, + css: data.css + }, on) + + // ---------------------------------------- + // OPTS + // ---------------------------------------- + let pageTitle = `
${data.title}
` + + + const graphics = [ + graphic('cloud1', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud2', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud3', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud4', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud5', './src/node_modules/assets/svg/cloud.svg'), + graphic('cloud6', './src/node_modules/assets/svg/cloud.svg'), + ] + + const [cloud1, cloud2, cloud3, cloud4, cloud5, cloud6] = await Promise.all(graphics) + + // Parallax effects + let cloud1Rellax = new Rellax( cloud1, { speed: 1.5}) + let cloud2Rellax = new Rellax( cloud2, { speed: 1}) + let cloud3Rellax = new Rellax( cloud3, { speed: 1.5}) + let cloud4Rellax = new Rellax( cloud4, { speed: 4}) + let cloud5Rellax = new Rellax( cloud5, { speed: 1.5}) + let cloud6Rellax = new Rellax( cloud6, { speed: 3}) + + const scene = [] + for (const sid of data.sub.crystal_island){ + scene.push(await crystal_island({sid, hub: [css_id]})) + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ` + // ---------------------------------------- + const main = shadow.querySelector('section') + main.append(...(await Promise.all(scene)).map(v => v), cloud1, cloud2, cloud3, cloud4, cloud5, cloud6) + + // port.onmessage = onmessage + init_css() + return el + + async function fallback() { + return require('./data.json') + } + async function init_css () { + const pref = JSON.parse(localStorage.pref) + const pref_shared = pref[name] || data.shared || [{ id: name }] + const pref_uniq = pref[css_id] || data.uniq || [] + pref_shared.forEach(async v => inject_all({ data: await get_theme(v)})) + pref_uniq.forEach(async v => inject({ data: await get_theme(v)})) + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject_all ({ data }) { + const sheet = new CSSStyleSheet + sheet.replaceSync(data) + shadow.adoptedStyleSheets.push(sheet) + } + async function inject ({ data }){ + const style = document.createElement('style') + style.innerHTML = data + shadow.append(style) + } + async function get_theme ({local = true, theme = 'default', id}) { + let theme_css + if(local) + theme_css = await (await fetch(`./src/node_modules/css/${theme}/${id}.css`)).text() + else + theme_css = JSON.parse(localStorage[theme])[id] + return theme_css + } +} diff --git a/src/node_modules/theme_editor/package.json b/src/node_modules/theme_editor/package.json new file mode 100644 index 0000000..9064305 --- /dev/null +++ b/src/node_modules/theme_editor/package.json @@ -0,0 +1,3 @@ +{ + "main": "./theme_editor.js" +} \ No newline at end of file diff --git a/src/node_modules/theme_editor/theme_editor.js b/src/node_modules/theme_editor/theme_editor.js new file mode 100644 index 0000000..215d9fa --- /dev/null +++ b/src/node_modules/theme_editor/theme_editor.js @@ -0,0 +1,411 @@ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'theme_editor' +const statedb = STATE(__filename) +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + return { + inputs: { + 'theme_editor.css': { + $ref: new URL('src/node_modules/css/default/theme_editor.css', location).href + } + } + } +} +/****************************************************************************** + THEME_EDITOR COMPONENT +******************************************************************************/ +const DB = require('localdb') +const IO = require('io') +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = theme_editor +async function theme_editor (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const status = { tab_id: 0 } + const db = DB() + const on = { + init, + hide, + css: inject + } + const {xget} = sdb.req_access(opts.sid) + const send = await IO(id, name, on) + + status.themes = { + builtin: Object.keys(opts.paths), + saved: Object.keys(JSON.parse(localStorage.index || (localStorage.index = '{}'))) + } + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + const style = document.createElement('style') + await sdb.watch(onbatch) + + shadow.innerHTML = ` +
+
+
+
+ +
+
+ + + + + + + + + + + +

+

+
+
+ + +
+
` + const main = shadow.querySelector('main') + const inject_btn = shadow.querySelector('.inject') + const load_btn = shadow.querySelector('.load') + const save_file_btn = shadow.querySelector('.save_file') + const save_pref_btn = shadow.querySelector('.save_pref') + const add_btn = shadow.querySelector('.add') + const drop_theme_btn = shadow.querySelector('.drop_theme') + const drop_file_btn = shadow.querySelector('.drop_file') + const reset_btn = shadow.querySelector('.reset') + const upload = shadow.querySelector('.upload') + const import_btn = shadow.querySelector('.import') + const export_btn = shadow.querySelector('.export') + const title = shadow.querySelector('h3') + const content = shadow.querySelector('.content') + const tabs = shadow.querySelector('.tabs > .box') + const plus = shadow.querySelector('.plus') + const select_theme = shadow.querySelector('div.theme') + const input = shadow.querySelector('input.theme') + + input.onfocus = () => select_theme.classList.add('active') + input.onblur = () => setTimeout(() => select_theme.classList.remove('active'), 200) + input.oninput = update_select_theme + inject_btn.onclick = on_inject + load_btn.onclick = () => load(input.value, false) + save_file_btn.onclick = save_file + save_pref_btn.onclick = save_pref + add_btn.onclick = () => add(input.value) + drop_theme_btn.onclick = drop_theme + drop_file_btn.onclick = drop_file + export_btn.onclick = export_fn + import_btn.onclick = () => upload.click() + upload.onchange = import_fn + reset_btn.onclick = () => {localStorage.clear(), location.reload()} + plus.onclick = () => add_tab('New') + shadow.append(style) + update_select_theme() + + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function hide () { + main.classList.toggle('select') + status.select = !status.select + } + async function export_fn () { + const theme = db.read([ input.value ]) + const index = db.read([ 'index', input.value ]) + const blob = new Blob([JSON.stringify({theme, index}, null, 2)], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = input.value + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + async function import_fn () { + const file = upload.files[0] + const name = file.name.split('.')[0] + await add(name) + const reader = new FileReader() + reader.onload = e => { + const blob = JSON.parse(e.target.result) + db.add([name], blob.theme) + db.add(['index', name], blob.index) + load(name) + } + reader.readAsText(file) + } + async function add (theme) { + db.add([theme], []) + status.themes.saved.push(theme) + db.add(['index', theme], []) + update_select_theme() + } + async function drop_theme () { + db.drop([input.value]) + db.drop(['index', input.value]) + status.themes.saved = status.themes.saved.filter(v => v != input.value) + update_select_theme() + input.value = 'default' + load('default') + } + async function drop_file () { + db.drop([status.active_tab.dataset.theme, status.active_tab.dataset.id]) + db.drop(['index', status.active_tab.dataset.theme, status.active_tab.dataset.id]) + close_tab(status.active_tab) + } + async function forget_changes () { + status.active_el.classList.remove('dirty') + const dirt = JSON.parse(localStorage.dirt) + delete(dirt[status.title]) + localStorage.dirt = JSON.stringify(dirt) + } + async function save_file () { + // forget_changes() + if(db.read([input.value])){ + db.push(['index', input.value], status.active_tab.dataset.name) + db.push([input.value], status.textarea.value) + } + } + async function save_pref () { + const pref = db.read(['pref']) + if(status.select){ + var ids = await get_select() + ids.forEach(id => pref[id] = []) + } + pref[status.instance_id] = [] + pref[status.title] = [] + Array.from(tabs.children).forEach(tab => { + if(tab.dataset.access === "uniq"){ + if(ids) + ids.forEach(id => + pref[id].push({theme: tab.dataset.theme, id: tab.dataset.id, local: status.themes.builtin.includes(tab.dataset.theme)}) + ) + else + pref[status.instance_id].push({theme: tab.dataset.theme, id: tab.dataset.id, local: status.themes.builtin.includes(tab.dataset.theme)}) + } + else + pref[status.title].push({theme: tab.dataset.theme, id: tab.dataset.id, local: status.themes.builtin.includes(tab.dataset.theme) }) + }) + db.add(['pref'], pref) + } + async function unsave () { + status.active_el.classList.add('dirty') + let theme = localStorage[input.value] && JSON.parse(localStorage[input.value]) + if(theme){ + theme.css[status.title] = textarea.value + localStorage[input.value] = JSON.stringify(theme) + const dirt = JSON.parse(localStorage.dirt) + dirt[status.title] = input.value + localStorage.dirt = JSON.stringify(dirt) + } + else{ + const name = input.value + '*' + theme = localStorage[name] && JSON.parse(localStorage[name]) + if(theme){ + theme.css[status.title] = textarea.value + localStorage[name] = JSON.stringify(theme) + const dirt = JSON.parse(localStorage.dirt) + dirt[status.title] = name + localStorage.dirt = JSON.stringify(dirt) + } + else{ + theme = { theme: true, css: {} } + theme.css[status.title] = textarea.value + localStorage[name] = JSON.stringify(theme) + status.themes.saved.push(name) + const dirt = JSON.parse(localStorage.dirt) + dirt[status.title] = name + localStorage.dirt = JSON.stringify(dirt) + update_select_theme() + input.value = name + } + } + } + async function on_inject () { + if(status.active_tab.dataset.type === 'json'){ + const id = add_data(status.textarea.value) + const hub = xget(xget(id).hub).id + send({type: 'refresh', to: hub}) + } + else{ + if(status.select){ + const ids = await get_select() + ids.forEach(id => { + send({ type: 'inject', to: id, data: status.textarea.value }) + }) + } + else + send({ type: 'inject', to: status.node_data.hub_id, data: status.textarea.value }) + } + } + async function get_select () { + return await send({ type: 'get_select', to: 'theme_widget'}) + } + async function load (theme, clear = true) { + if(clear){ + content.innerHTML = '' + tabs.innerHTML = '' + } + if(status.themes.builtin.includes(theme)){ + const index = opts.paths[theme].length + for(let i = 0; i < index; i++){ + const temp = await fetch(`./src/node_modules/css/${theme}/${i}.css`) + add_tab(i, await temp.text(), '', theme, status.title) + } + } + else{ + const temp = db.read([theme]) + temp.forEach((file, i) => { + add_tab(i, file, '', theme, status.title) + }) + } + // forget_changes() + } + async function init ({ data }) { + title.innerHTML = data.id + status.title = data.type + status.instance_id = data.id + let value = data.file ? db.read([data.xtype, data.id]) : data + if(data.type === 'json' || !data.file) + value = JSON.stringify(value, null, 2) + add_tab(data.name, value) + } + async function add_tab (id, value = '', access = 'uniq', theme = 'default') { + if(id === 'New' && status.themes.builtin.includes(theme)){ + theme += '*' + add(theme) + } + const tab = document.createElement('span') + const tab_id = '_' + status.tab_id++ + tab.id = tab_id + const index = opts.paths[theme] || db.read(['index', theme]) + tabs.append(tab) + const btn = document.createElement('span') + btn.innerHTML = index[id] || id + tab.dataset.id = id + tab.dataset.name = btn.innerHTML + tab.dataset.theme = theme + tab.dataset.access = access + btn.onclick = () => switch_tab(tab.id) + btn.ondblclick = rename + const btn_x = document.createElement('span') + btn_x.innerHTML = 'x' + tab.append(btn, btn_x) + tab.tabIndex = '0' + tab.onkeydown = e => { + if(e.key === 'ArrowRight' && tab.nextElementSibling) + tab.nextElementSibling.after(tab) + else if(e.key === 'ArrowLeft' && tab.previousElementSibling) + tab.previousElementSibling.before(tab) + tab.focus() + } + const textarea = document.createElement('textarea') + textarea.value = value + textarea.id = tab_id + content.append(textarea) + btn_x.onclick = () => close_tab(tab) + switch_tab(tab_id) + } + async function close_tab (tab) { + content.querySelector('#' + tab.id).remove() + tab.remove() + if(tabs.children.length) + switch_tab(tabs.children[tabs.children.length - 1].id) + else + add_tab('New') + } + async function switch_tab (tab_id) { + status.textarea && status.textarea.classList.remove('active') + status.textarea = content.querySelector('#' + tab_id) + status.textarea.classList.add('active') + status.active_tab && status.active_tab.classList.remove('active') + status.active_tab = tabs.querySelector('#' + tab_id) + status.active_tab.classList.add('active') + status.active_tab.focus() + input.value = status.active_tab.dataset.theme + } + async function rename (e) { + const btn = e.target + const hub = btn.parentElement + const input = document.createElement('input') + input.value = btn.innerHTML + btn.innerHTML = '' + btn.append(input) + input.onkeydown = e => { + if(e.key === 'Enter'){ + btn.innerHTML = input.value + db.add([hub.dataset.theme, hub.dataset.id], input.value) + } + } + input.onblur = e => { + if(e.relatedTarget) + btn.innerHTML = hub.dataset.name + } + input.focus() + } + async function update_select_theme () { + const builtin = document.createElement('div') + builtin.classList.add('cat') + status.themes.builtin.forEach(theme => { + const el = document.createElement('div') + el.innerHTML = theme + el.onclick = () => input.value = theme + theme.includes(input.value) && builtin.append(el) + }) + builtin.innerHTML && builtin.insertAdjacentHTML('afterbegin', 'builtin') + const saved = document.createElement('div') + saved.classList.add('cat') + status.themes.saved.forEach(theme => { + const el = document.createElement('div') + el.innerHTML = theme + el.onclick = () => input.value = theme + theme.includes(input.value) && saved.append(el) + }) + saved.innerHTML && saved.insertAdjacentHTML('afterbegin', 'saved') + select_theme.innerHTML = '' + select_theme.append(builtin, saved) + } + async function inject (data){ + style.innerHTML = data.join('\n') + } +} diff --git a/src/node_modules/theme_widget/package.json b/src/node_modules/theme_widget/package.json new file mode 100644 index 0000000..bdb3691 --- /dev/null +++ b/src/node_modules/theme_widget/package.json @@ -0,0 +1,3 @@ +{ + "main": "./theme_widget.js" +} \ No newline at end of file diff --git a/src/node_modules/theme_widget/theme_widget.js b/src/node_modules/theme_widget/theme_widget.js new file mode 100644 index 0000000..9cc8239 --- /dev/null +++ b/src/node_modules/theme_widget/theme_widget.js @@ -0,0 +1,155 @@ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'theme_widget' +const statedb = STATE(__filename) +const shopts = { mode: 'closed' } +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return { + _: { + 'theme_editor': {}, + 'graph_explorer': {} + } + } +} +function fallback_instance () { + return { + _: { + 'theme_editor': {}, + 'graph_explorer': {} + }, + inputs: { + 'theme_widget.css': { + $ref: new URL('src/node_modules/css/default/theme_widget.css', location).href + } + } + } +} +/****************************************************************************** + THEME_WIDGET COMPONENT +******************************************************************************/ +const theme_editor = require('theme_editor') +const graph_explorer = require('graph_explorer') +const IO = require('io') +// ---------------------------------------- +module.exports = theme_widget + +async function theme_widget (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) // hub is "parent's" io "id" to send/receive messages + const status = { tab_id: 0, init_check: true } + const on = { + refresh, + get_select, + css: inject, + scroll, + click + } + const {get_all} = sdb.req_access(opts.sid) + const send = await IO(id, name, on) + + status.clickables = ['css', 'json', 'js'] + status.dirts = JSON.parse(localStorage.dirt || (localStorage.dirt = '{}')) + localStorage.pref || (localStorage.pref = '{}') + const paths = JSON.parse(await(await fetch('./src/node_modules/css/index.json')).text()) + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+
+ ⚙️ +
+ +
+ ` + const style = shadow.querySelector('style') + const btn = shadow.querySelector('.btn') + const popup = shadow.querySelector('.popup') + const box = popup.querySelector('.box') + const list = box.querySelector('.list') + const editor = popup.querySelector('.editor') + const stats = box.querySelector('.stats') + const select = box.querySelector('.select') + const slider = box.querySelector('input') + + const theme_editor_sub = sdb.get_sub('theme_editor') + const graph_explorer_sub = sdb.get_sub('graph_explorer') + await sdb.watch(onbatch) + + editor.append(await theme_editor({ sid: theme_editor_sub[0].sid, hub: [id], paths })) + box.prepend(await graph_explorer({ sid: graph_explorer_sub[0].sid, hub: [id] })) + select.onclick = on_select + slider.oninput = blur + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function blur(e) { + popup.style.opacity = e.target.value/100 + } + async function on_select () { + list.classList.toggle('active') + send({to: 'theme_editor', type: 'hide'}) + } + async function get_select () { + const inputs = list.querySelectorAll('input') + const output = [] + inputs.forEach(el => el.checked && output.push(el.nextElementSibling.id)) + send({type: 'send', to: 'theme_editor', data: output}) + } + async function refresh () { + const data = get_all() + status.tree = data + stats.innerHTML = `Entries: ${Object.keys(data).length}` + btn.onclick = () => { + popup.classList.toggle('active') + status.init_check && send({type: 'init', to: 'graph_explorer' , data:status.tree}) + status.init_check = false + } + } + async function click ({ data }) { + send({ to: 'theme_editor', type: 'init', data}) + status.active_el && status.active_el.classList.remove('active') + if(status.instance_id === data.id) + editor.classList.toggle('active') + else{ + editor.classList.add('active') + el.classList.add('active') + } + status.instance_id = data.id + status.active_el = el + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } +} diff --git a/src/node_modules/topnav.js b/src/node_modules/topnav.js deleted file mode 100755 index 8848823..0000000 --- a/src/node_modules/topnav.js +++ /dev/null @@ -1,159 +0,0 @@ -const bel = require('bel') -const csjs = require('csjs-inject') -// widgets -const graphic = require('graphic') -// plugins -const zenscroll = require('zenscroll') - -module.exports = topnav - -async function topnav(data) { - const playLogo = await graphic(css.playLogo, './src/node_modules/assets/svg/logo.svg') - - function click(url) { - let id = document.querySelector(`#${url}`) - zenscroll.to(id, 20000) - } - - const body = document.body - const scrollUp = css.scrollUp - const scrollDown = css.scrollDown - let lastScroll = 0 - - window.addEventListener('scroll', ()=> { - if (window.innerWidth >= 1024) { - let currentScroll = window.pageYOffset - if (currentScroll < 1) { - body.classList.remove(scrollUp) - body.classList.remove(scrollDown) - return - } - if (currentScroll > lastScroll && !body.classList.contains(scrollDown)) { - body.classList.add(scrollDown) - body.classList.remove(scrollUp) - } else if (currentScroll < lastScroll) { - body.classList.add(scrollUp) - body.classList.remove(scrollDown) - } - lastScroll = currentScroll - } - }) - - window.addEventListener('resize', ()=> { - if (window.innerWidth <= 1024) { - body.classList.remove(scrollUp) - body.classList.remove(scrollDown) - } - }) - - return bel` -
- ${playLogo} - -
- ` -} - -let css = csjs` -.topnav { - position: relative; - width: 100%; - z-index: 20; - display: grid; - grid-template: 1fr / auto; - background-color: var(--playBgGStart); - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - opacity: 1; - transition: background-color .6s, -webkit-transform .4s, transform .4s, opacity .3s linear; -} -.playLogo { - position: absolute; - top: 10px; - left: 0; - width: 15rem; - z-index: 99; - transition: width .6s ease-in-out; -} -.menu { - padding: 2.5rem; - text-align: right; -} -.menu a { - font-size: var(--menuSize); - margin-left: 1.75%; - color: #575551; - text-transform: uppercase; - transition: color .6s linear; -} -.menu a:hover { - color: #00acff; -} -.scrollUp .topnav { - position: fixed; - background-color: white; - -webkit-transform: none; - transform: none; -} -.scrollDown .topnav { - position: fixed; - -webkit-transform: translate3d(0, -100%, 0); - transform: translate3d(0, -100%, 0); - opacity: 0; -} -.scrollUp .playLogo { - width: 10rem; -} - .scrollDown .playLogo { - width: 10rem; - top: 0; -} -@media only screen and (min-width: 4096px) { - .menu a { - font-size: calc(var(--menuSize) * 1.5); - } -} -@media only screen and (max-width: 1024px) { - .playLogo { - width: 9vw; - min-width: 100px; - } -} -@media only screen and (max-width: 960px) { - .topnav { - position: relative; - } - .menu { - padding-top: 3%; - padding-right: 2.5vw; - } - .menu a { - margin-left: 1.5%; - } -} -@media only screen and (max-width: 812px) { - .menu { - display: none; - } - .playLogo { - top: 20px; - min-width: 12vw; - } -} -@media only screen and (max-width: 414px) { - .playLogo { - min-width: 20vw; - } -} -` \ No newline at end of file diff --git a/src/node_modules/topnav/instance.json b/src/node_modules/topnav/instance.json new file mode 100644 index 0000000..7f9bc6f --- /dev/null +++ b/src/node_modules/topnav/instance.json @@ -0,0 +1,37 @@ +{ + "inputs": { + "topnav.json": { + "type": "content", + "data": { + "links": [ + { + "id": "datdot", + "text": "DatDot", + "url": "datdot" + }, + { + "id": "editor", + "text": "Play Editor", + "url": "editor" + }, + { + "id": "smartcontract_codes", + "text": "Smart Contract Codes", + "url": "smartcontract_codes" + }, + { + "id": "supporters", + "text": "Supporters", + "url": "supporters" + }, + { + "id": "our_contributors", + "text": "Contributors", + "url": "our_contributors" + } + ] + } + } + } + +} \ No newline at end of file diff --git a/src/node_modules/topnav/package.json b/src/node_modules/topnav/package.json new file mode 100644 index 0000000..bc239ec --- /dev/null +++ b/src/node_modules/topnav/package.json @@ -0,0 +1,3 @@ +{ + "main": "./topnav.js" +} \ No newline at end of file diff --git a/src/node_modules/topnav/topnav.js b/src/node_modules/topnav/topnav.js new file mode 100755 index 0000000..a345560 --- /dev/null +++ b/src/node_modules/topnav/topnav.js @@ -0,0 +1,126 @@ +/****************************************************************************** + STATE +******************************************************************************/ +const STATE = require('STATE') +const name = 'topnav' +const statedb = STATE(__filename) +// ---------------------------------------- +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) +function fallback_module () { + return {} +} +function fallback_instance () { + const data = require('./instance.json') + data.inputs['topnav.css'] = { + $ref: new URL('src/node_modules/css/default/topnav.css', location).href + } + return data +} + +/****************************************************************************** + OUR CONTRIBUTORS COMPONENT +******************************************************************************/ +const graphic = require('graphic') +const IO = require('io') +// ---------------------------------------- +const shopts = { mode: 'closed' } +// ---------------------------------------- +module.exports = topnav + +async function topnav (opts) { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get(opts.sid) + const status = {} + const on = { + css: inject, + scroll, + content: fill + } + + const send = await IO(id, name, on) + // ---------------------------------------- + // OPTS + // ---------------------------------------- + + const playLogo = await graphic('playLogo', './src/node_modules/assets/svg/logo.svg') + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.createElement('div') + const shadow = el.attachShadow(shopts) + shadow.innerHTML = ` +
+ ${playLogo.outerHTML} + +
+ ` + const style = shadow.querySelector('style') + const menu = shadow.querySelector('.menu') + const body = shadow.querySelector('section') + const scrollUp = 'scrollUp' + const scrollDown = 'scrollDown' + let lastScroll = 0 + + window.addEventListener('scroll', ()=> { + if (window.innerWidth >= 1024) { + let currentScroll = window.pageYOffset + if (currentScroll < 1) { + body.classList.remove(scrollUp) + body.classList.remove(scrollDown) + return + } + if (currentScroll > lastScroll && !body.classList.contains(scrollDown)) { + body.classList.add(scrollDown) + body.classList.remove(scrollUp) + } else if (currentScroll < lastScroll) { + body.classList.add(scrollUp) + body.classList.remove(scrollDown) + } + lastScroll = currentScroll + } + }) + + window.addEventListener('resize', ()=> { + if (window.innerWidth <= 1024) { + body.classList.remove(scrollUp) + body.classList.remove(scrollDown) + } + }) + sdb.watch(onbatch) + + return el + + function onbatch(batch){ + for (const {type, data} of batch) { + on[type](data) + } + } + async function inject (data){ + style.innerHTML = data.join('\n') + } + function fill ([ opts ]) { + menu.replaceChildren(...opts.links.map(make_link)) + } + function click(url) { + send({to:'index', type: 'jump', data: url }) + } + function make_link(link){ + const a = document.createElement('a') + a.href = `#${link.url}` + a.textContent = link.text + a.onclick = () => click(link.url) + return a + } + async function scroll () { + el.scrollIntoView({behavior: 'smooth'}) + el.tabIndex = '0' + el.focus() + el.onblur = () => { + el.tabIndex = '-1' + el.onblur = null + } + } +} diff --git a/web/boot.js b/web/boot.js new file mode 100644 index 0000000..e41a69d --- /dev/null +++ b/web/boot.js @@ -0,0 +1,42 @@ +patch_cache_in_browser(arguments[4], arguments[5]) + +function patch_cache_in_browser (source_cache, module_cache) { + const meta = { modulepath: [], paths: {} } + for (const key of Object.keys(source_cache)) { + const [module, names] = source_cache[key] + const dependencies = names || {} + source_cache[key][0] = patch(module, dependencies, meta) + } + function patch (module, dependencies, meta) { + const MAP = {} + for (const [name, number] of Object.entries(dependencies)) MAP[name] = number + return (...args) => { + const original = args[0] + require.cache = module_cache + require.resolve = resolve + args[0] = require + return module(...args) + function require (name) { + const identifier = resolve(name) + if (name.endsWith('node_modules/STATE')) { + const modulepath = meta.modulepath.join('/') + const original_export = require.cache[identifier] || (require.cache[identifier] = original(name)) + const exports = (...args) => original_export(...args, modulepath) + return exports + } else if (require.cache[identifier]) return require.cache[identifier] + else { + const counter = meta.modulepath.concat(name).join('/') + if (!meta.paths[counter]) meta.paths[counter] = 0 + const localid = `${name}${meta.paths[counter] ? '#' + meta.paths[counter] : ''}` + meta.paths[counter]++ + meta.modulepath.push(localid) + } + const exports = require.cache[identifier] = original(name) + if (!name.endsWith('node_modules/STATE')) meta.modulepath.pop(name) + return exports + } + } + function resolve (name) { return MAP[name] } + } +} +require('./demo') // or whatever is otherwise the main entry of our project diff --git a/web/content/graph_explorer.json b/web/content/graph_explorer.json new file mode 100644 index 0000000..cebeef4 --- /dev/null +++ b/web/content/graph_explorer.json @@ -0,0 +1,10 @@ +{ + "hub_off": "📪", + "hub_on": "📭", + "sub_off": "📪", + "sub_on": "📭", + "in_off": "🗃", + "in_on": "🗂", + "out_off": "🗃", + "out_on": "🗂" +} \ No newline at end of file diff --git a/web/content/topnav.json b/web/content/topnav.json new file mode 100644 index 0000000..cec7f1e --- /dev/null +++ b/web/content/topnav.json @@ -0,0 +1,29 @@ +{ + "links": [ + { + "id": "datdot", + "text": "DatDot", + "url": "datdot" + }, + { + "id": "editor", + "text": "Play Editor", + "url": "editor" + }, + { + "id": "smartcontract_files", + "text": "Smart Contract files", + "url": "smartcontract_files" + }, + { + "id": "supporters", + "text": "Supporters", + "url": "supporters" + }, + { + "id": "our_contributors", + "text": "Contributors", + "url": "our_contributors" + } + ] +} \ No newline at end of file diff --git a/web/demo.js b/web/demo.js new file mode 100755 index 0000000..080f150 --- /dev/null +++ b/web/demo.js @@ -0,0 +1,102 @@ +const STATE = require('../src/node_modules/STATE') +/****************************************************************************** + INITIALIZE PAGE +******************************************************************************/ +const statedb = STATE(__filename) +const { sdb, subs: [get] } = statedb(fallback_module, fallback_instance) + +const make_page = require('../src/app') + +function fallback_module () { // -> set database defaults or load from database + return { + admins: ['theme_editor', 'theme_widget', 'graph_explorer'], + _: { + app: {} + } + } +} +function fallback_instance () { + return { + _: { + app: { + 0: override + } + }, + inputs: { + 'demo.css': { + $ref: new URL('src/node_modules/css/default/demo.css', location).href + } + } + } +} +function override ([app], path) { + const data = app() + console.log(path._.app._.topnav) + return data +} +/****************************************************************************** + CSS & HTML Defaults +******************************************************************************/ +const sheet = new CSSStyleSheet() +config().then(() => boot({ })) + +async function config () { + const path = path => new URL(`../src/node_modules/${path}`, `file://${__dirname}`).href.slice(8) + const html = document.documentElement + const meta = document.createElement('meta') + const appleTouch = '' + const icon32 = '' + const icon16 = '' + const webmanifest = '' + const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' + const loadFont = `` + html.setAttribute('lang', 'en') + meta.setAttribute('name', 'viewport') + meta.setAttribute('content', 'width=device-width,initial-scale=1.0') + // @TODO: use font api and cache to avoid re-downloading the font data every time + document.head.append(meta) + document.head.innerHTML += appleTouch + icon16 + icon32 + webmanifest + loadFont + document.adoptedStyleSheets = [sheet] + await document.fonts.ready // @TODO: investigate why there is a FOUC +} +/****************************************************************************** + PAGE BOOT +******************************************************************************/ +async function boot () { + // ---------------------------------------- + // ID + JSON STATE + // ---------------------------------------- + const { id, sdb } = await get('') // hub is "parent's" io "id" to send/receive messages + const [opts] = sdb.get_sub('app') + const on = { + css: inject + } + sdb.watch(onbatch) + const status = {} + // ---------------------------------------- + // TEMPLATE + // ---------------------------------------- + const el = document.body + const shopts = { mode: 'closed' } + const shadow = el.attachShadow(shopts) + shadow.adoptedStyleSheets = [sheet] + // ---------------------------------------- + // ELEMENTS + // ---------------------------------------- + { // desktop + const element = await make_page(opts) + shadow.append(element) + } + // ---------------------------------------- + // INIT + // ---------------------------------------- + + function onbatch (batch) { + for (const { type, data } of batch) { + on[type](data) + } + } +} +async function inject (data) { + sheet.replaceSync(data.join('\n')) +} diff --git a/demo/node_modules/theme.js b/web/node_modules/theme.js similarity index 93% rename from demo/node_modules/theme.js rename to web/node_modules/theme.js index 4f31ad1..11cc794 100755 --- a/demo/node_modules/theme.js +++ b/web/node_modules/theme.js @@ -1,7 +1,3 @@ -const bel = require('bel') -const font = 'https://fonts.googleapis.com/css?family=Nunito:300,400,700,900|Slackey&display=swap' -const loadFont = bel`` -document.head.appendChild(loadFont) const defines = { fonts: {