From 0ae23900abc8969128dac7c81cef9333832613a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Feb 2026 00:05:45 +0000 Subject: [PATCH 1/2] Add Tinder-like swipe interface with card stack, drag gestures, and action buttons Implements a full Tinder-style UI using Symbols framework: - ProfileCard: displays user photo, name, age, job, school, distance with gradient overlay and LIKE/NOPE/SUPER LIKE stamps - SwipeCard: handles pointer drag gestures with rotation, threshold-based swipe detection, and animated card dismissal - TinderStack: manages card stack state, profile cycling, action handling (like/nope/superlike/rewind), and empty state - ActionButtons: rewind, nope, super like, like, and boost buttons with themed colors - TinderHeader: app header with logo and navigation icons - Mock profile data with 6 sample profiles in snippets - Swipe animations (swipeLeft, swipeRight, swipeUp, cardStackIn, pulseGlow) in design system - Tinder color palette (tinderPink, tinderGold, tinderBlue, tinderGreen, tinderRed, tinderPurple) - New icons: heart, heartOutline, tinderClose, star, rewind, bolt, mapPin, briefcase, graduationCap, info https://claude.ai/code/session_019CMCq1QWDzmJbrtf6hoRUp --- smbls/components/ActionButton.js | 24 ++++ smbls/components/ActionButtons.js | 65 +++++++++++ smbls/components/ProfileCard.js | 176 ++++++++++++++++++++++++++++++ smbls/components/SwipeCard.js | 148 +++++++++++++++++++++++++ smbls/components/TinderHeader.js | 49 +++++++++ smbls/components/TinderStack.js | 140 ++++++++++++++++++++++++ smbls/components/index.js | 6 + smbls/designSystem/animation.js | 54 +++++++++ smbls/designSystem/color.js | 6 + smbls/designSystem/icons.js | 20 ++++ smbls/pages/index.js | 2 + smbls/pages/tinder.js | 13 +++ smbls/snippets/index.js | 2 +- smbls/snippets/profilesData.js | 68 ++++++++++++ 14 files changed, 772 insertions(+), 1 deletion(-) create mode 100644 smbls/components/ActionButton.js create mode 100644 smbls/components/ActionButtons.js create mode 100644 smbls/components/ProfileCard.js create mode 100644 smbls/components/SwipeCard.js create mode 100644 smbls/components/TinderHeader.js create mode 100644 smbls/components/TinderStack.js create mode 100644 smbls/pages/tinder.js create mode 100644 smbls/snippets/profilesData.js diff --git a/smbls/components/ActionButton.js b/smbls/components/ActionButton.js new file mode 100644 index 0000000..d067183 --- /dev/null +++ b/smbls/components/ActionButton.js @@ -0,0 +1,24 @@ +export const ActionButton = { + extends: 'Flex', + flexAlign: 'center center', + round: '100px', + cursor: 'pointer', + transition: 'transform 0.2s ease, box-shadow 0.2s ease', + style: { + userSelect: 'none' + }, + ':hover': { + style: { + transform: 'scale(1.12)' + } + }, + ':active': { + style: { + transform: 'scale(0.95)' + } + }, + + Icon: { + name: (el, s) => s.icon || 'heart' + } +} diff --git a/smbls/components/ActionButtons.js b/smbls/components/ActionButtons.js new file mode 100644 index 0000000..dd55963 --- /dev/null +++ b/smbls/components/ActionButtons.js @@ -0,0 +1,65 @@ +export const ActionButtons = { + extends: 'Flex', + flow: 'x', + flexAlign: 'center center', + gap: 'A2', + padding: 'A2 0', + + RewindBtn: { + extends: 'ActionButton', + boxSize: 'C2', + background: 'gray3 0.85', + ':hover': { background: 'gray3' }, + Icon: { name: 'rewind', boxSize: 'A', color: 'tinderGold' }, + onClick: (ev, el, s) => { + s.update({ action: 'rewind' }) + } + }, + + NopeBtn: { + extends: 'ActionButton', + boxSize: 'D', + background: 'gray3 0.85', + border: 'tinderRed 2px solid', + ':hover': { background: 'tinderRed 0.15' }, + Icon: { name: 'tinderClose', boxSize: 'A2', color: 'tinderRed' }, + onClick: (ev, el, s) => { + s.update({ action: 'nope' }) + } + }, + + SuperLikeBtn: { + extends: 'ActionButton', + boxSize: 'C2', + background: 'gray3 0.85', + border: 'tinderBlue 2px solid', + ':hover': { background: 'tinderBlue 0.15' }, + Icon: { name: 'star', boxSize: 'A', color: 'tinderBlue' }, + onClick: (ev, el, s) => { + s.update({ action: 'superlike' }) + } + }, + + LikeBtn: { + extends: 'ActionButton', + boxSize: 'D', + background: 'gray3 0.85', + border: 'tinderGreen 2px solid', + ':hover': { background: 'tinderGreen 0.15' }, + Icon: { name: 'heart', boxSize: 'A2', color: 'tinderGreen' }, + onClick: (ev, el, s) => { + s.update({ action: 'like' }) + } + }, + + BoostBtn: { + extends: 'ActionButton', + boxSize: 'C2', + background: 'gray3 0.85', + ':hover': { background: 'tinderPurple 0.15' }, + Icon: { name: 'bolt', boxSize: 'A', color: 'tinderPurple' }, + onClick: (ev, el, s) => { + s.update({ action: 'boost' }) + } + } +} diff --git a/smbls/components/ProfileCard.js b/smbls/components/ProfileCard.js new file mode 100644 index 0000000..ee3e499 --- /dev/null +++ b/smbls/components/ProfileCard.js @@ -0,0 +1,176 @@ +export const ProfileCard = { + extends: 'Flex', + flow: 'y', + width: '100%', + height: '100%', + position: 'relative', + overflow: 'hidden', + round: 'B', + + Photo: { + width: '100%', + height: '100%', + position: 'absolute', + top: '0', + left: '0', + tag: 'img', + style: { objectFit: 'cover' }, + src: (el, s) => s.image || '', + draggable: 'false' + }, + + Gradient: { + position: 'absolute', + bottom: '0', + left: '0', + width: '100%', + height: '55%', + style: { + background: 'linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.4) 50%, transparent 100%)', + pointerEvents: 'none' + } + }, + + Info: { + extends: 'Flex', + flow: 'y', + position: 'absolute', + bottom: '0', + left: '0', + width: '100%', + padding: 'A2', + gap: 'Z', + color: 'white', + + NameRow: { + extends: 'Flex', + flow: 'x', + align: 'center flex-start', + gap: 'Z', + Name: { + tag: 'h2', + fontSize: 'C', + fontWeight: '700', + text: (el, s) => s.name || '' + }, + Age: { + tag: 'span', + fontSize: 'B2', + fontWeight: '300', + text: (el, s) => s.age || '' + } + }, + + JobRow: { + extends: 'Flex', + flow: 'x', + align: 'center flex-start', + gap: 'Z', + if: (el, s) => s.job, + Icon: { name: 'briefcase', boxSize: 'Z2', color: 'white 0.85' }, + Text: { + tag: 'span', + fontSize: 'A', + color: 'white 0.9', + text: (el, s) => s.job || '' + } + }, + + SchoolRow: { + extends: 'Flex', + flow: 'x', + align: 'center flex-start', + gap: 'Z', + if: (el, s) => s.school, + Icon: { name: 'graduationCap', boxSize: 'Z2', color: 'white 0.85' }, + Text: { + tag: 'span', + fontSize: 'A', + color: 'white 0.9', + text: (el, s) => s.school || '' + } + }, + + LocationRow: { + extends: 'Flex', + flow: 'x', + align: 'center flex-start', + gap: 'Z', + if: (el, s) => s.distance, + Icon: { name: 'mapPin', boxSize: 'Z2', color: 'white 0.85' }, + Text: { + tag: 'span', + fontSize: 'Z2', + color: 'white 0.75', + text: (el, s) => s.distance || '' + } + } + }, + + LikeStamp: { + extends: 'Flex', + flexAlign: 'center center', + position: 'absolute', + top: 'B', + left: 'A2', + padding: 'Z A', + border: 'tinderGreen 4px solid', + round: 'Z2', + color: 'tinderGreen', + fontSize: 'C', + fontWeight: '800', + letterSpacing: '2px', + text: 'LIKE', + opacity: '0', + style: { + transform: 'rotate(-25deg)', + pointerEvents: 'none', + textTransform: 'uppercase' + } + }, + + NopeStamp: { + extends: 'Flex', + flexAlign: 'center center', + position: 'absolute', + top: 'B', + right: 'A2', + padding: 'Z A', + border: 'tinderRed 4px solid', + round: 'Z2', + color: 'tinderRed', + fontSize: 'C', + fontWeight: '800', + letterSpacing: '2px', + text: 'NOPE', + opacity: '0', + style: { + transform: 'rotate(25deg)', + pointerEvents: 'none', + textTransform: 'uppercase' + } + }, + + SuperLikeStamp: { + extends: 'Flex', + flexAlign: 'center center', + position: 'absolute', + bottom: 'E', + left: '50%', + padding: 'Z A', + border: 'tinderBlue 4px solid', + round: 'Z2', + color: 'tinderBlue', + fontSize: 'C', + fontWeight: '800', + letterSpacing: '2px', + text: 'SUPER LIKE', + opacity: '0', + style: { + transform: 'translateX(-50%)', + pointerEvents: 'none', + textTransform: 'uppercase', + whiteSpace: 'nowrap' + } + } +} diff --git a/smbls/components/SwipeCard.js b/smbls/components/SwipeCard.js new file mode 100644 index 0000000..2b35943 --- /dev/null +++ b/smbls/components/SwipeCard.js @@ -0,0 +1,148 @@ +export const SwipeCard = { + extends: 'Flex', + position: 'absolute', + width: '100%', + height: '100%', + cursor: 'grab', + style: { + userSelect: 'none', + touchAction: 'none', + willChange: 'transform' + }, + + scope: { + startX: 0, + startY: 0, + currentX: 0, + currentY: 0, + isDragging: false, + + onPointerDown: (el, s, e) => { + el.scope.isDragging = true + el.scope.startX = e.clientX + el.scope.startY = e.clientY + el.scope.currentX = 0 + el.scope.currentY = 0 + el.node.style.cursor = 'grabbing' + el.node.style.transition = 'none' + }, + + onPointerMove: (el, s, e) => { + if (!el.scope.isDragging) return + el.scope.currentX = e.clientX - el.scope.startX + el.scope.currentY = e.clientY - el.scope.startY + var rotation = el.scope.currentX * 0.1 + if (rotation > 15) rotation = 15 + if (rotation < -15) rotation = -15 + el.node.style.transform = 'translate(' + el.scope.currentX + 'px, ' + el.scope.currentY + 'px) rotate(' + rotation + 'deg)' + + var card = el.node.querySelector('[data-profile-card]') + if (!card) return + var likeStamp = card.querySelectorAll('[data-stamp]')[0] + var nopeStamp = card.querySelectorAll('[data-stamp]')[1] + var superLikeStamp = card.querySelectorAll('[data-stamp]')[2] + + var absX = Math.abs(el.scope.currentX) + var absY = Math.abs(el.scope.currentY) + + if (el.scope.currentX > 0 && absX > absY) { + var likeOpacity = Math.min(absX / 100, 1) + if (likeStamp) likeStamp.style.opacity = likeOpacity + if (nopeStamp) nopeStamp.style.opacity = 0 + if (superLikeStamp) superLikeStamp.style.opacity = 0 + } else if (el.scope.currentX < 0 && absX > absY) { + var nopeOpacity = Math.min(absX / 100, 1) + if (nopeStamp) nopeStamp.style.opacity = nopeOpacity + if (likeStamp) likeStamp.style.opacity = 0 + if (superLikeStamp) superLikeStamp.style.opacity = 0 + } else if (el.scope.currentY < -50) { + var superOpacity = Math.min(absY / 100, 1) + if (superLikeStamp) superLikeStamp.style.opacity = superOpacity + if (likeStamp) likeStamp.style.opacity = 0 + if (nopeStamp) nopeStamp.style.opacity = 0 + } else { + if (likeStamp) likeStamp.style.opacity = 0 + if (nopeStamp) nopeStamp.style.opacity = 0 + if (superLikeStamp) superLikeStamp.style.opacity = 0 + } + }, + + onPointerUp: (el, s) => { + if (!el.scope.isDragging) return + el.scope.isDragging = false + el.node.style.cursor = 'grab' + + var threshold = 120 + var dx = el.scope.currentX + var dy = el.scope.currentY + + if (dx > threshold) { + el.scope.animateOut(el, s, 'right') + } else if (dx < -threshold) { + el.scope.animateOut(el, s, 'left') + } else if (dy < -threshold) { + el.scope.animateOut(el, s, 'up') + } else { + el.scope.snapBack(el) + } + }, + + snapBack: (el) => { + el.node.style.transition = 'transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)' + el.node.style.transform = 'translate(0, 0) rotate(0deg)' + var card = el.node.querySelector('[data-profile-card]') + if (card) { + var stamps = card.querySelectorAll('[data-stamp]') + for (var i = 0; i < stamps.length; i++) { + stamps[i].style.transition = 'opacity 0.3s' + stamps[i].style.opacity = 0 + } + } + }, + + animateOut: (el, s, direction) => { + var tx = 0 + var ty = 0 + var rot = 0 + if (direction === 'right') { + tx = window.innerWidth + 200 + rot = 30 + } else if (direction === 'left') { + tx = -(window.innerWidth + 200) + rot = -30 + } else if (direction === 'up') { + ty = -(window.innerHeight + 200) + } + el.node.style.transition = 'transform 0.5s ease-out, opacity 0.5s ease-out' + el.node.style.transform = 'translate(' + tx + 'px, ' + ty + 'px) rotate(' + rot + 'deg)' + el.node.style.opacity = '0' + + setTimeout(function () { + s.update({ swiped: direction }) + }, 500) + } + }, + + onRender: (el, s) => { + var pd = function (e) { el.scope.onPointerDown(el, s, e) } + var pm = function (e) { el.scope.onPointerMove(el, s, e) } + var pu = function () { el.scope.onPointerUp(el, s) } + + el.node.addEventListener('pointerdown', pd) + window.addEventListener('pointermove', pm) + window.addEventListener('pointerup', pu) + + return function () { + el.node.removeEventListener('pointerdown', pd) + window.removeEventListener('pointermove', pm) + window.removeEventListener('pointerup', pu) + } + }, + + ProfileCard: { + attr: { 'data-profile-card': true }, + LikeStamp: { attr: { 'data-stamp': 'like' } }, + NopeStamp: { attr: { 'data-stamp': 'nope' } }, + SuperLikeStamp: { attr: { 'data-stamp': 'superlike' } } + } +} diff --git a/smbls/components/TinderHeader.js b/smbls/components/TinderHeader.js new file mode 100644 index 0000000..11c936e --- /dev/null +++ b/smbls/components/TinderHeader.js @@ -0,0 +1,49 @@ +export const TinderHeader = { + extends: 'Flex', + flow: 'x', + flexAlign: 'center space-between', + padding: 'Z2 A2', + width: '100%', + maxWidth: 'G', + margin: '0 auto', + + ProfileBtn: { + extends: 'Flex', + flexAlign: 'center center', + boxSize: 'B2', + round: '100px', + cursor: 'pointer', + color: 'caption', + ':hover': { color: 'title' }, + Icon: { name: 'accessibility', boxSize: 'A2' } + }, + + Logo: { + extends: 'Flex', + flexAlign: 'center center', + gap: 'Z', + Icon: { name: 'heart', boxSize: 'A2', color: 'tinderPink' }, + Title: { + tag: 'h1', + fontSize: 'B', + fontWeight: '700', + style: { + background: 'linear-gradient(135deg, #FF4B6E, #FF8E6E)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent' + }, + text: 'tinder' + } + }, + + ChatBtn: { + extends: 'Flex', + flexAlign: 'center center', + boxSize: 'B2', + round: '100px', + cursor: 'pointer', + color: 'caption', + ':hover': { color: 'title' }, + Icon: { name: 'menu', boxSize: 'A2' } + } +} diff --git a/smbls/components/TinderStack.js b/smbls/components/TinderStack.js new file mode 100644 index 0000000..b1f6aa1 --- /dev/null +++ b/smbls/components/TinderStack.js @@ -0,0 +1,140 @@ +export const TinderStack = { + extends: 'Flex', + flow: 'y', + flexAlign: 'center center', + width: '100%', + maxWidth: 'G', + margin: 'auto', + height: '100%', + + state: { + currentIndex: 0, + action: null, + swiped: null, + liked: [], + noped: [], + superliked: [] + }, + + scope: { + getProfiles: (el, s, ctx) => { + return ctx.snippets.profilesData || [] + }, + + handleAction: (el, s, action) => { + var profiles = el.scope.getProfiles(el, s, el.context) + if (s.currentIndex >= profiles.length) return + + var profile = profiles[s.currentIndex] + + if (action === 'like') { + s.apply(function (st) { st.liked.push(profile) }) + } else if (action === 'nope') { + s.apply(function (st) { st.noped.push(profile) }) + } else if (action === 'superlike') { + s.apply(function (st) { st.superliked.push(profile) }) + } else if (action === 'rewind' && s.currentIndex > 0) { + s.update({ currentIndex: s.currentIndex - 1, action: null, swiped: null }) + return + } + + s.update({ currentIndex: s.currentIndex + 1, action: null, swiped: null }) + } + }, + + onStateUpdate: (changes, el, s) => { + if (changes.action && changes.action !== 'boost') { + el.scope.handleAction(el, s, changes.action) + } + if (changes.swiped) { + var direction = changes.swiped + var action = direction === 'right' ? 'like' : direction === 'left' ? 'nope' : 'superlike' + el.scope.handleAction(el, s, action) + } + }, + + CardArea: { + extends: 'Flex', + position: 'relative', + width: '100%', + flex: '1', + minHeight: 'G', + maxHeight: '500px', + flexAlign: 'center center', + + content: (el, s, ctx) => { + var profiles = ctx.snippets.profilesData || [] + var idx = s.currentIndex + + if (idx >= profiles.length) { + return { + EmptyState: { + extends: 'Flex', + flow: 'y', + flexAlign: 'center center', + gap: 'A', + padding: 'C', + animation: 'fadeIn 0.5s ease', + Icon: { name: 'heartOutline', boxSize: 'D', color: 'tinderPink 0.5' }, + H3: { + fontSize: 'B', + fontWeight: '600', + color: 'title', + text: 'No more profiles' + }, + P: { + fontSize: 'A', + color: 'caption', + text: 'Check back later for more people' + } + } + } + } + + var cards = {} + + var nextIdx = idx + 1 + if (nextIdx < profiles.length) { + cards.BackCard = { + extends: 'Flex', + position: 'absolute', + width: '95%', + height: '95%', + round: 'B', + overflow: 'hidden', + style: { + transform: 'scale(0.95) translateY(10px)', + pointerEvents: 'none' + }, + Photo: { + width: '100%', + height: '100%', + tag: 'img', + style: { objectFit: 'cover', filter: 'brightness(0.7)' }, + src: profiles[nextIdx].image, + draggable: 'false' + } + } + } + + cards.SwipeCard = { + state: { + name: profiles[idx].name, + age: profiles[idx].age, + job: profiles[idx].job, + school: profiles[idx].school, + distance: profiles[idx].distance, + bio: profiles[idx].bio, + image: profiles[idx].image, + interests: profiles[idx].interests, + swiped: null, + action: null + } + } + + return cards + } + }, + + ActionButtons: {} +} diff --git a/smbls/components/index.js b/smbls/components/index.js index 5f1cc05..3d787e9 100644 --- a/smbls/components/index.js +++ b/smbls/components/index.js @@ -59,3 +59,9 @@ export * from './NetListeningChart.js'; export * from './PeerCountChart.js'; export * from './SyncingChart.js'; export * from './UpChart.js'; +export * from './ProfileCard.js'; +export * from './SwipeCard.js'; +export * from './ActionButton.js'; +export * from './ActionButtons.js'; +export * from './TinderStack.js'; +export * from './TinderHeader.js'; diff --git a/smbls/designSystem/animation.js b/smbls/designSystem/animation.js index ddb5591..5fac97d 100644 --- a/smbls/designSystem/animation.js +++ b/smbls/designSystem/animation.js @@ -252,4 +252,58 @@ export default { opacity: 1, }, }, + swipeRight: { + from: { + transform: "translateX(0) rotate(0deg)", + opacity: 1, + }, + to: { + transform: "translateX(150%) rotate(30deg)", + opacity: 0, + }, + }, + swipeLeft: { + from: { + transform: "translateX(0) rotate(0deg)", + opacity: 1, + }, + to: { + transform: "translateX(-150%) rotate(-30deg)", + opacity: 0, + }, + }, + swipeUp: { + from: { + transform: "translateY(0) scale(1)", + opacity: 1, + }, + to: { + transform: "translateY(-150%) scale(0.8)", + opacity: 0, + }, + }, + cardStackIn: { + from: { + transform: "scale(0.9) translateY(30px)", + opacity: 0, + }, + to: { + transform: "scale(1) translateY(0)", + opacity: 1, + }, + }, + pulseGlow: { + "0%": { + transform: "scale(1)", + boxShadow: "0 0 0 0 rgba(255, 75, 110, 0.4)", + }, + "70%": { + transform: "scale(1.05)", + boxShadow: "0 0 0 15px rgba(255, 75, 110, 0)", + }, + "100%": { + transform: "scale(1)", + boxShadow: "0 0 0 0 rgba(255, 75, 110, 0)", + }, + }, }; diff --git a/smbls/designSystem/color.js b/smbls/designSystem/color.js index 824cedb..567fa43 100644 --- a/smbls/designSystem/color.js +++ b/smbls/designSystem/color.js @@ -69,4 +69,10 @@ export default { "line-highlight": ["--gray13 1 +4", "--gray2 1 +16"], blue2: "#4676EC", lightBlue: "#6899D1", + tinderPink: "#FF4B6E", + tinderGold: "#FFD700", + tinderBlue: "#3B82F6", + tinderGreen: "#10B981", + tinderRed: "#EF4444", + tinderPurple: "#8B5CF6", }; diff --git a/smbls/designSystem/icons.js b/smbls/designSystem/icons.js index 796130d..08032ff 100644 --- a/smbls/designSystem/icons.js +++ b/smbls/designSystem/icons.js @@ -1039,4 +1039,24 @@ export default { '', sidebarLeftFill: '', + heart: + '', + heartOutline: + '', + tinderClose: + '', + star: + '', + rewind: + '', + bolt: + '', + mapPin: + '', + briefcase: + '', + graduationCap: + '', + info: + '', } diff --git a/smbls/pages/index.js b/smbls/pages/index.js index ba21313..48925df 100644 --- a/smbls/pages/index.js +++ b/smbls/pages/index.js @@ -8,6 +8,7 @@ import { editNetwork } from './edit-network'; import { editNode } from './edit-node'; import { dashboard } from './dashboard'; import { addNetworkCopy } from './add-network-copy'; +import { tinder } from './tinder'; export default { '/': main, '/network': network, @@ -19,5 +20,6 @@ export default { '/edit-node': editNode, '/dashboard': dashboard, '/add-network-copy': addNetworkCopy, +'/tinder': tinder, } \ No newline at end of file diff --git a/smbls/pages/tinder.js b/smbls/pages/tinder.js new file mode 100644 index 0000000..e9191ff --- /dev/null +++ b/smbls/pages/tinder.js @@ -0,0 +1,13 @@ +export const tinder = { + extends: 'Page', + width: '100%', + height: '100%', + maxWidth: '500px', + margin: '0 auto', + flow: 'y', + overflow: 'hidden', + theme: 'document', + + TinderHeader: {}, + TinderStack: {} +} diff --git a/smbls/snippets/index.js b/smbls/snippets/index.js index 8b13789..fe36bbf 100644 --- a/smbls/snippets/index.js +++ b/smbls/snippets/index.js @@ -1 +1 @@ - +export * from './profilesData.js' diff --git a/smbls/snippets/profilesData.js b/smbls/snippets/profilesData.js new file mode 100644 index 0000000..7e38387 --- /dev/null +++ b/smbls/snippets/profilesData.js @@ -0,0 +1,68 @@ +export const profilesData = [ + { + id: 1, + name: 'Sophia', + age: 26, + distance: '3 miles away', + job: 'UX Designer', + school: 'Stanford University', + bio: 'Adventure seeker. Coffee addict. Dog mom. Let\'s explore the city together!', + image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=900&fit=crop&q=80', + interests: ['Travel', 'Photography', 'Yoga', 'Coffee'] + }, + { + id: 2, + name: 'Emma', + age: 24, + distance: '5 miles away', + job: 'Software Engineer', + school: 'MIT', + bio: 'Code by day, cook by night. Looking for someone to taste-test my recipes.', + image: 'https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?w=600&h=900&fit=crop&q=80', + interests: ['Coding', 'Cooking', 'Gaming', 'Music'] + }, + { + id: 3, + name: 'Olivia', + age: 28, + distance: '8 miles away', + job: 'Photographer', + school: 'Parsons School of Design', + bio: 'Capturing moments one click at a time. Nature lover. Art enthusiast.', + image: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=600&h=900&fit=crop&q=80', + interests: ['Art', 'Nature', 'Film', 'Travel'] + }, + { + id: 4, + name: 'Ava', + age: 25, + distance: '2 miles away', + job: 'Marketing Manager', + school: 'NYU', + bio: 'Foodie on a mission. Weekend hiker. Always down for brunch.', + image: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=600&h=900&fit=crop&q=80', + interests: ['Food', 'Hiking', 'Wine', 'Reading'] + }, + { + id: 5, + name: 'Isabella', + age: 27, + distance: '6 miles away', + job: 'Architect', + school: 'Columbia University', + bio: 'Building dreams, one blueprint at a time. Jazz, museums, and rooftop sunsets.', + image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=600&h=900&fit=crop&q=80', + interests: ['Design', 'Jazz', 'Museums', 'Architecture'] + }, + { + id: 6, + name: 'Mia', + age: 23, + distance: '4 miles away', + job: 'Dance Instructor', + school: 'Juilliard', + bio: 'Life is better when you dance. Salsa, ballet, or just freestyle in the kitchen.', + image: 'https://images.unsplash.com/photo-1488716820095-cbe80883c496?w=600&h=900&fit=crop&q=80', + interests: ['Dance', 'Fitness', 'Music', 'Meditation'] + } +] From b1193435ff282aedf594fc25beba8edae7d74d41 Mon Sep 17 00:00:00 2001 From: nikoloza Date: Sat, 14 Feb 2026 18:52:23 +0400 Subject: [PATCH 2/2] cleanup --- .symbols/config.json | 3 - .symbols/lock.json | 7 - .symbols/project.json | 7461 --------------------------------- DOMQL_v2-v3_MIGRATION.md | 236 -- SYMBOLS_LOCAL_INSTRUCTIONS.md | 1399 ------- 5 files changed, 9106 deletions(-) delete mode 100644 .symbols/config.json delete mode 100644 .symbols/lock.json delete mode 100644 .symbols/project.json delete mode 100644 DOMQL_v2-v3_MIGRATION.md delete mode 100644 SYMBOLS_LOCAL_INSTRUCTIONS.md diff --git a/.symbols/config.json b/.symbols/config.json deleted file mode 100644 index 997206e..0000000 --- a/.symbols/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "apiBaseUrl": "https://api.symbols.app" -} \ No newline at end of file diff --git a/.symbols/lock.json b/.symbols/lock.json deleted file mode 100644 index 34ab3a4..0000000 --- a/.symbols/lock.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "etag": null, - "version": "1.7.387", - "branch": "main", - "projectId": "6874baae0769df64f1a4484d", - "pulledAt": "2026-01-08T22:50:42.678Z" -} \ No newline at end of file diff --git a/.symbols/project.json b/.symbols/project.json deleted file mode 100644 index 079b9ec..0000000 --- a/.symbols/project.json +++ /dev/null @@ -1,7461 +0,0 @@ -{ - "_migrationMeta": { - "hasBasedData": false, - "hasRichData": false, - "sourceVersion": "1.0", - "migratedAt": "2025-07-14T08:07:10.346Z" - }, - "status": "active", - "designTool": "none", - "environments": { - "dev": { - "mode": "latest", - "branch": "main", - "enabled": true, - "label": "Development" - }, - "testing": { - "mode": "version", - "enabled": false, - "label": "Testing" - }, - "staging": { - "mode": "version", - "enabled": false, - "label": "Staging" - }, - "prod": { - "mode": "published", - "enabled": true, - "label": "Production" - } - }, - "ownerType": "user", - "_id": "6874baae0769df64f1a4484d", - "name": "Bigbrother", - "key": "big-brother.symbo.ls", - "tier": "free", - "projectType": "web", - "settings": { - "remote": false, - "async": false, - "inspect": true, - "liveSync": true, - "syncCanvas": true - }, - "designSystem": { - "COLOR": { - "black": "#000", - "white": "#fff", - "softBlack": "#1d1d1d", - "lightGreen": "#9cbc8f", - "codGray": "#131313", - "caption": "#b1b1b1", - "deepFir": "#222222", - "cloudProvider": "#986565", - "SLA": "#007081", - "nodeType": "#8cba83", - "env": "#7081d8" - }, - "GRADIENT": {}, - "THEME": { - "document": { - "@light": { - "color": "black", - "background": "white" - }, - "@dark": { - "color": "white", - "background": "softBlack" - } - }, - "none": { - "color": "none", - "background": "none" - }, - "transparent": { - "color": "currentColor", - "background": "transparent" - }, - "dialog": { - "@dark": { - "color": "white", - "background": "codGray", - "colorKey": "white", - "colorType": null, - "backgroundKey": "codGray", - "backgroundType": null - }, - "@light": { - "color": "white", - "background": "white 0.065" - } - }, - "button": { - "@dark": { - "color": "white", - "background": "transparent", - ":hover": { - "background": "deepFir" - }, - ":active": { - "background": "deepFir +15" - } - } - } - }, - "FONT": {}, - "FONT_FAMILY": {}, - "TYPOGRAPHY": { - "base": 16, - "ratio": 1.25, - "subSequence": true, - "templates": {} - }, - "SPACING": { - "base": 16, - "ratio": 1.618, - "subSequence": true - }, - "TIMING": { - "defaultBezier": "cubic-bezier(.29, .67, .51, .97)" - }, - "CLASS": {}, - "GRID": {}, - "ICONS": { - "github": "", - "gitlab": "", - "logo": "", - "logo2": "", - "logo_optimized": "", - "eyes": "", - "eye": "", - "copy": "", - "edit": "", - "trash": "", - "cloudProvider": "#986565" - }, - "SHAPE": {}, - "RESET": {}, - "ANIMATION": {}, - "MEDIA": { - "mobileSm": "(min-width: 500px)" - }, - "CASES": {}, - "useReset": true, - "useVariable": true, - "useFontImport": true, - "useIconSprite": true, - "useSvgSprite": true, - "useDefaultConfig": true, - "useDocumentTheme": true, - "verbose": false, - "globalTheme": "dark" - }, - "package": "1", - "functions": { - "stringToHexColor": "function(str = '') {\n let hash = 0;\n // Compute a hash value for the string\n for (let i = 0; i < str.length; i++) {\n hash = str.charCodeAt(i) + ((hash << 5) - hash);\n }\n // Convert hash to a hexadecimal color code\n let color = '#';\n for (let i = 0; i < 3; i++) {\n const value = (hash >> (i * 8)) & 0xFF;\n color += ('00' + value.toString(16)).slice(-2);\n }\n return color;\n }", - "filterByEnv": "function filterByEnv(array, value) {\n return this.call('isArray', array) && array.filter(item => item['env'] === value).length\n }", - "calculateWeek": "function calculateWeek(index) {\n\n // Generate date ranges based on index\n // Starting from April 1st and creating week ranges\n if (index === 0) return ''; // First child has no date\n\n const startApril = new Date(2025, 3, 1); // April 1, 2025\n const startDay = new Date(startApril);\n startDay.setDate(startDay.getDate() + (index - 1) * 7); // Add weeks\n\n const endDay = new Date(startDay);\n endDay.setDate(endDay.getDate() + 6); // End is 6 days later\n\n // Format the dates\n const startDate = startDay.getDate();\n const endDate = endDay.getDate();\n const startMonth = startDay.toLocaleString('en-US', {\n month: 'short'\n });\n const endMonth = endDay.toLocaleString('en-US', {\n month: 'short'\n });\n\n if (startMonth === endMonth) {\n return `${startDate}-${endDate} ${startMonth}`;\n } else {\n return `${startDate} ${startMonth}-${endDate} ${endMonth}`;\n }\n }", - "calculateCosts": "function calculateCosts(data) {\n let totalCurrentCost = 0;\n let totalProjectedCost = 0;\n\n data.forEach(item => {\n const allValidators = item.validator_info || [];\n const allRpcs = item.rpc_info || [];\n\n allValidators.forEach(val => {\n if (typeof val.current_spend === 'number') {\n totalCurrentCost += val.current_spend;\n }\n if (typeof val.projected_cost === 'number') {\n totalProjectedCost += val.projected_cost;\n }\n });\n\n allRpcs.forEach(rpc => {\n if (typeof rpc.current_spend === 'number') {\n totalCurrentCost += rpc.current_spend;\n }\n if (typeof rpc.projected_cost === 'number') {\n totalProjectedCost += rpc.projected_cost;\n }\n });\n });\n\n const formatter = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n });\n\n return {\n totalCurrentCost: formatter.format(totalCurrentCost),\n totalProjectedCost: formatter.format(totalProjectedCost)\n };\n }", - "getCostsPerProtocol": "function getCostsPerProtocol(data) {\n return data.map(item => {\n let currentCost = 0;\n let projectedCost = 0;\n\n const allValidators = item.validator_info || [];\n const allRpcs = item.rpc_info || [];\n\n allValidators.forEach(val => {\n if (typeof val.current_spend === 'number') {\n currentCost += val.current_spend;\n }\n if (typeof val.projected_cost === 'number') {\n projectedCost += val.projected_cost;\n }\n });\n\n allRpcs.forEach(rpc => {\n if (typeof rpc.current_spend === 'number') {\n currentCost += rpc.current_spend;\n }\n if (typeof rpc.projected_cost === 'number') {\n projectedCost += rpc.projected_cost;\n }\n });\n\n const formatter = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n });\n\n return {\n protocol: item.protocol,\n validatorCount: allValidators.length,\n rpcCount: allRpcs.length,\n currentCost: formatter.format(currentCost),\n projectedCost: formatter.format(projectedCost)\n };\n });\n }", - "giveMeAnswer": "async function giveMeAnswer(text) {\n const apiUrl = 'https://bigbrother.symbo.ls/api/mcp/query'\n const requestBody = {\n prompt: text\n }\n let responseHtml =\n '

Apologies, something went wrong while contacting the assistant.

'\n\n try {\n console.log('Sending request to API:', JSON.stringify(requestBody, null, 2))\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(requestBody)\n })\n\n const responseText = await response.text() // Get raw response text for debugging\n console.log('Raw API Response:', responseText)\n\n if (!response.ok) {\n let errorDetail = 'Unknown error'\n try {\n const errorData = JSON.parse(responseText)\n errorDetail = errorData.detail || errorData.error || response.statusText\n } catch (e) {\n errorDetail = response.statusText || 'Failed to parse error response'\n }\n console.error('API Error:', response.status, errorDetail)\n responseHtml = `

Error from assistant: ${errorDetail}

`\n return responseHtml\n }\n\n try {\n const data = JSON.parse(responseText)\n return data\n } catch (e) {\n console.error('Error parsing API response:', e)\n return responseText // Return raw response if parsing fails\n }\n } catch (error) {\n console.error('Failed to fetch from API or process response:', error)\n return `

Failed to connect to the assistant: ${error.message}

`\n }\n }", - "getMe": "async (path = '/api/fleet', opts = {}) => {\n const endpointUrl = 'https://bigbrother.symbo.ls/auth/me'\n const options = {\n method: 'GET',\n headers: {\n Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImVtYWlsIjoibmlrYUBzeW1ib2xzLmFwcCIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTc1MjAxMjU3MiwiZXhwIjoxNzUyNjE3MzcyfQ.jw2-gNSFdOL3qpHBgRzSSlV6Kas0p15UTCJhDwRNvCo'\n },\n params: {},\n auth: {}\n }\n\n return await window.fetch(endpointUrl, options).then(r => r.json())\n }", - "getTagsOfNodes": "function(networkObj, tag) {\n\n }", - "edit": "async function edit(item = 'network', protocol, opts = {}) {\n const formData = new FormData(this.node)\n let data = Object.fromEntries(formData)\n\n // For nodes, send flat object directly (not nested)\n if (item === 'node') {\n // Remove nodeType from data since it's in the URL\n delete data.nodeType\n data.projected_cost = parseInt(data.projected_cost)\n // Send the form data directly as flat object\n // data is already correct: { moniker: \"...\", env: \"...\", etc. }\n }\n\n const ROUTE = {\n network: `/${protocol}`,\n node: `/node/${this.state.nodeType}/${this.state.uid}`\n }\n\n console.log('Route:', ROUTE[item])\n console.log('Data being sent:', data)\n\n const res = await this.call('fetch', 'PUT', ROUTE[item], data, opts)\n console.log('Response:', res)\n\n this.state.root.quietUpdate({\n modal: null\n })\n\n const redirectUrl = {\n network: '/network/' + this.state.protocol,\n node: '/node/' + this.state.protocol + '/' + this.state.nodeType + '/' + this.state.uid\n }\n\n this.call('router', redirectUrl[item] || '/', this.__ref.root)\n this.node.reset()\n }", - "remove": "async function remove(item = 'network', protocol, opts = {}) {\n let [, _, urlProtocol, nodeType, uid] = window.location.pathname.split('/')\n\n const ROUTE = {\n network: '/' + protocol,\n node: '/node/' + nodeType + '/' + uid\n }\n\n console.log('/node/' + nodeType + '/' + uid)\n\n const res = await this.call('fetch', 'DELETE', ROUTE[item])\n if (!res) return\n\n this.state.root.quietUpdate({\n modal: null\n })\n\n const REDIRECT = {\n network: '/dashboard',\n node: '/network/' + protocol\n }\n this.call('router', REDIRECT[item], this.__ref.root)\n }", - "fetch": "async function fetch(method = 'GET', path = '', data, opts = {}) {\n // const ENDPOINT = 'https://small-sound-18b4.nika-980.workers.dev/api/fleet'\n // const ENDPOINT = 'https://bigbrother.symbo.ls' + (opts.route || '/api/fleet') + path\n\n const options = {\n method: method || 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n ...opts\n }\n\n const isCanvas = location.href === 'srcdoc'\n const isSymbols = location.host.includes('symbo.ls')\n const isProd = location.host.includes('nodeops.ninja') && !location.host.includes('dev')\n // const isProd = false // location.host.includes('nodeops.ninja') && !location.host.includes('dev')\n\n const URL = `https://${isProd ? '' : 'dev.'}api.nodeops.ninja`\n const ENDPOINT = URL + (opts.route || '/api/fleet') + path\n\n if (isCanvas || isSymbols || !isProd) {\n const API_TOKEN = 'bb_ff64921b7b6dae704a352681c26ae5ed35c8143e18e13f2682cc2be1ab4ebb74'\n options.headers.Authorization = 'Bearer ' + API_TOKEN\n } else {\n // const SESSION_ID = 's%3AwAm91jUNz7Bv3ihqvY9o4AJ_xQDTA6x3.VcEZyPFSdClTokzsu9n3gXULU1qp7pxSNCSEQBUhoIQ'\n // options.headers.credentials = true\n // options.headers.Authorization = 'Bearer ' + SESSION_ID\n options.credentials = 'include'\n options.mode = 'cors'\n }\n\n if (data && (options.method === 'POST' || options.method === 'PUT')) {\n options.body = JSON.stringify(data)\n }\n\n console.log('Fetch request:', ENDPOINT, options)\n const res = await window.fetch(ENDPOINT, options)\n\n if (!res.ok) {\n const errorText = await res.text()\n console.error('Failed to submit:', res.status, errorText)\n throw new Error(`HTTP ${res.status}: ${errorText}`)\n }\n\n // Check if response has content before parsing JSON\n const contentType = res.headers.get('content-type')\n if (contentType && contentType.includes('application/json')) {\n return res.json()\n }\n\n return res.text()\n }", - "add": "async function addNew(item = 'network') {\n const ROUTE = {\n network: '',\n node: '/' + this.state.protocol + '/node'\n }\n\n const formData = new FormData(this.node)\n let data = Object.fromEntries(formData)\n if (item === 'node') {\n data.projected_cost = parseInt(data.projected_cost)\n console.log(data.projected_cost)\n data = {\n nodeType: data.nodeType,\n nodeData: data\n }\n }\n console.log(data)\n\n const res = await this.call('fetch', 'POST', ROUTE[item], data)\n if (!res) return\n\n this.state.root.quietUpdate({\n modal: null\n })\n\n // console.log('here')\n // console.log(this.state)\n // console.log(res)\n const ROUTES = {\n network: '/network/' + data.protocol,\n node: '/node/' + this.state.protocol + '/' + data.nodeType + '/' + res.uid\n }\n this.call('router', ROUTES[item] || '/', this.__ref.root)\n this.node.reset()\n }", - "read": "async function read(path) {\n return await this.call('fetch', 'GET', path)\n }", - "parseNetworkRow": "function parseNetworkRow(data) {\n const result = {\n cloud_provider: new Set(),\n env: new Set(),\n node_types: new Set(),\n status: new Set()\n };\n\n // Parse validators\n if (Array.isArray(data.validators) && data.validators.length > 0) {\n result.node_types.add('Validator');\n data.validators.forEach(validator => {\n if (validator.cloud_provider) result.cloud_provider.add(validator.cloud_provider);\n if (validator.env) result.env.add(validator.env);\n if (validator.status) result.status.add(validator.status);\n });\n }\n\n // Parse rpc_nodes\n if (Array.isArray(data.rpc_nodes) && data.rpc_nodes.length > 0) {\n result.node_types.add('RPC');\n data.rpc_nodes.forEach(node => {\n if (node.cloud_provider) result.cloud_provider.add(node.cloud_provider);\n if (node.env) result.env.add(node.env);\n if (node.status) result.status.add(node.status);\n });\n }\n\n // Convert Sets to Arrays\n return {\n cloud_provider: Array.from(result.cloud_provider),\n env: Array.from(result.env),\n node_types: Array.from(result.node_types),\n status: Array.from(result.status)\n };\n }", - "getFleet": "function getFleet() {\n // console.log(this, this.state, this.context)\n }", - "filterFleet": "function filterFleet() {\n // Search filter\n if (root.search) {\n fleet = fleet.filter(item =>\n item.protocol.toLowerCase().includes(root.search.toLowerCase()) ||\n item.repo_url.includes(root.search)\n )\n }\n\n // Validators filter\n if (root.validators) {\n if (root.validators === 'Mainnet') {\n fleet = fleet.filter(item =>\n item.validator_info?.some(validator => validator.env === 'Mainnet')\n )\n } else if (root.validators === 'Testnet') {\n fleet = fleet.filter(item =>\n item.validator_info?.some(validator => validator.env === 'Testnet')\n )\n } else if (root.validators === 'None') {\n fleet = fleet.filter(item =>\n !item.validator_info?.length\n )\n }\n // 'All' option doesn't need additional filtering\n }\n\n // RPC filter\n if (root.rpc) {\n if (root.rpc === 'Mainnet') {\n fleet = fleet.filter(item =>\n item.rpc_info?.some(rpc => rpc.env === 'Mainnet')\n )\n } else if (root.rpc === 'Testnet') {\n fleet = fleet.filter(item =>\n item.rpc_info?.some(rpc => rpc.env === 'Testnet')\n )\n } else if (root.rpc === 'None') {\n fleet = fleet.filter(item =>\n !item.rpc_info?.length\n )\n }\n // 'All' option doesn't need additional filtering\n }\n\n // Status filter\n if (root.status) {\n fleet = fleet.filter(item => item.status === root.status)\n }\n\n // Last Updated filter\n if (root.lastUpdate && root.lastUpdate !== 'All Time') {\n const now = new Date()\n let targetDate = new Date()\n\n if (root.lastUpdate === 'Today') {\n targetDate.setHours(0, 0, 0, 0) // Start of today\n } else if (root.lastUpdate === 'Last 2 Days') {\n targetDate.setDate(now.getDate() - 2)\n targetDate.setHours(0, 0, 0, 0)\n } else if (root.lastUpdate === 'Last 3 Days') {\n targetDate.setDate(now.getDate() - 3)\n targetDate.setHours(0, 0, 0, 0)\n } else if (root.lastUpdate === 'Last Week') {\n targetDate.setDate(now.getDate() - 7)\n targetDate.setHours(0, 0, 0, 0)\n } else if (root.lastUpdate === 'Last Month') {\n targetDate.setMonth(now.getMonth() - 1)\n targetDate.setHours(0, 0, 0, 0)\n }\n\n fleet = fleet.filter(item => {\n // Check logs in validator_info\n const validatorLogs = item.validator_info?.flatMap(validator =>\n validator.logs?.map(log => new Date(log.created_at)) || []\n ) || []\n\n // Check logs in rpc_info\n const rpcLogs = item.rpc_info?.flatMap(rpc =>\n rpc.logs?.map(log => new Date(log.created_at)) || []\n ) || []\n\n // Combine all logs\n const allLogs = [...validatorLogs, ...rpcLogs]\n\n // Check if any log is newer than the target date\n return allLogs.some(logDate => logDate >= targetDate)\n })\n }\n }", - "setInitialData": "function setInitialData(data = {}) {\n this.state.replace(data, {\n preventUpdate: true,\n preventUpdateListener: true\n })\n\n this.update({}, {\n preventUpdateListener: true\n })\n }", - "getStatusColor": "function getStatusColor(status) {\n const MAP = {\n 'Live': 'green',\n 'Stable/Maintenance': 'green',\n 'Onboarding': 'yellow',\n 'Off': 'gray'\n }\n\n return MAP[status]\n }", - "auth": "async function auth() {\n if (this.state.root.success) {\n if (window.location.pathname === '/') {\n this.call('router', '/dashboard', this.__ref.root)\n }\n } else {\n if (window.location.pathname === '/') {\n const res = await this.call('fetch', 'GET', '', null, {\n route: '/auth/me',\n })\n\n if (res.success) {\n this.state.root.update(res)\n this.call('router', '/dashboard', this.__ref.root)\n }\n return res\n } else {\n this.call('router', '/', this.__ref.root)\n }\n }\n }", - "loadIntercom": "function loadIntercom() {\n // Prevent multiple loads\n if (window.intercomScriptLoaded) {\n Intercom(\"show\");\n return;\n }\n window.intercomScriptLoaded = true;\n\n // Load Intercom widget\n (function () {\n var w = window;\n var ic = w.Intercom;\n if (typeof ic === \"function\") {\n ic('reattach_activator');\n ic('update', {});\n } else {\n var d = document;\n var i = function () {\n i.c(arguments);\n };\n i.q = [];\n i.c = function (args) {\n i.q.push(args);\n };\n w.Intercom = i;\n\n var l = function () {\n var s = d.createElement('script');\n s.type = 'text/javascript';\n s.async = true;\n s.src = 'https://widget.intercom.io/widget/YOUR_APP_ID';\n var x = d.getElementsByTagName('script')[0];\n x.parentNode.insertBefore(s, x);\n };\n l();\n }\n })();\n\n // After load → open automatically\n window.Intercom('boot', {\n app_id: 'YOUR_APP_ID'\n });\n\n // Optional: ensure it opens after load\n setTimeout(() => Intercom('show'), 500);\n}" - }, - "methods": { - "default": {} - }, - "version": "1.7.378", - "versionscount": 849, - "seats": 1, - "dependencies": { - "chart.js": "4.4.9", - "fuse.js": "7.1.0" - }, - "files": { - "Arbitrum.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/689afdd763c13d9548d7710e/download" - }, - "code": "", - "key": "Arbitrum.png", - "type": "file", - "format": "png" - }, - "CRO.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/689afde063c13d9548d77129/download" - }, - "code": "", - "key": "CRO.png", - "type": "file", - "format": "png" - }, - "Aptos.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/689afde863c13d9548d771b1/download" - }, - "code": "", - "key": "Aptos.png", - "type": "file", - "format": "png" - }, - "Allora.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30df9f65883b039b9dbea/download" - }, - "code": "", - "key": "Allora.png", - "type": "file", - "format": "jpg" - }, - "Eth2.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30edbf65883b039b9de92/download" - }, - "code": "", - "key": "Eth2.png", - "type": "file", - "format": "png" - }, - "Chromia.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30eaff65883b039b9dd79/download" - }, - "code": "", - "key": "Chromia.png", - "type": "file", - "format": "png" - }, - "Canton.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30e38f65883b039b9dc64/download" - }, - "code": "", - "key": "Canton.png", - "type": "file", - "format": "jpg" - }, - "Polygon.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a310bef65883b039b9e3be/download" - }, - "code": "", - "key": "Polygon.png", - "type": "file", - "format": "svg" - }, - "Haven1.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30efbf65883b039b9deff/download" - }, - "code": "", - "key": "Haven1.png", - "type": "file", - "format": "jpg" - }, - "Pharos.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a310a8f65883b039b9e365/download" - }, - "code": "", - "key": "Pharos.png", - "type": "file", - "format": "jpg" - }, - "Nillion.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30fcbf65883b039b9e13d/download" - }, - "code": "", - "key": "Nillion.png", - "type": "file", - "format": "jpg" - }, - "Near.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30fb6f65883b039b9e0e8/download" - }, - "code": "", - "key": "Near.png", - "type": "file", - "format": "webp" - }, - "Story.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a311abf65883b039b9e5eb/download" - }, - "code": "", - "key": "Story.png", - "type": "file", - "format": "png" - }, - "Solana.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a31112f65883b039b9e4d9/download" - }, - "code": "", - "key": "Solana.png", - "type": "file", - "format": "jpg" - }, - "SXT.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a311dbf65883b039b9e6a5/download" - }, - "code": "", - "key": "SXT.png", - "type": "file", - "format": "jpg" - }, - "Radix.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a310f4f65883b039b9e46e/download" - }, - "code": "", - "key": "Radix.png", - "type": "file", - "format": "jpg" - }, - "Polymesh.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a310e2f65883b039b9e419/download" - }, - "code": "", - "key": "Polymesh.png", - "type": "file", - "format": "png" - }, - "Supra.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a311c2f65883b039b9e642/download" - }, - "code": "", - "key": "Supra.png", - "type": "file", - "format": "webp" - }, - "Tezos.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a311f2f65883b039b9e6fa/download" - }, - "code": "", - "key": "Tezos.png", - "type": "file", - "format": "png" - }, - "Vanar.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a31200f65883b039b9e74f/download" - }, - "code": "", - "key": "Vanar.png", - "type": "file", - "format": "png" - }, - "Wemix.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a31268f65883b039b9e843/download" - }, - "code": "", - "key": "Wemix.png", - "type": "file", - "format": "png" - }, - "Zenrock.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a31279f65883b039b9e8a0/download" - }, - "code": "", - "key": "Zenrock.png", - "type": "file", - "format": "jpg" - }, - "Zetachain.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a3128af65883b039b9e903/download" - }, - "code": "", - "key": "Zetachain.png", - "type": "file", - "format": "png" - }, - "Aleo.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30dbaf65883b039b9db1b/download" - }, - "code": "", - "key": "Aleo.png", - "type": "file", - "format": "jpg" - }, - "Cardano.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30e7af65883b039b9dd06/download" - }, - "code": "", - "key": "Cardano.png", - "type": "file", - "format": "png" - }, - "Ika.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30f3af65883b039b9df7d/download" - }, - "code": "", - "key": "Ika.png", - "type": "file", - "format": "jpg" - }, - "Kaia.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30f64f65883b039b9dfd7/download" - }, - "code": "", - "key": "Kaia.png", - "type": "file", - "format": "png" - }, - "Mantra.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30f7af65883b039b9e034/download" - }, - "code": "", - "key": "Mantra.png", - "type": "file", - "format": "jpg" - }, - "Midnight.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a30f9bf65883b039b9e08f/download" - }, - "code": "", - "key": "Midnight.png", - "type": "file", - "format": "jpg" - }, - "Somnia.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a31127f65883b039b9e566/download" - }, - "code": "", - "key": "Somnia.png", - "type": "file", - "format": "png" - }, - "Warden.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a31216f65883b039b9e79e/download" - }, - "code": "", - "key": "Warden.png", - "type": "file", - "format": "png" - }, - "ZkCloud.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68a3129ef65883b039b9e958/download" - }, - "code": "", - "key": "ZkCloud.png", - "type": "file", - "format": "png" - }, - "google.svg": { - "content": { - "src": "https://api.symbols.app/core/files/public/687723c00769df64f1a749e3/download" - }, - "code": "", - "key": "google.svg", - "type": "file", - "format": "svg" - }, - "Autonity.png": { - "content": { - "src": "https://api.symbols.app/core/files/public/68959a0763c13d9548d20d48/download" - }, - "code": "", - "key": "autonity.png", - "type": "file", - "format": "png" - }, - "walrus": { - "content": { - "filename": "KC6luiJ-_wNASRFXds4Zc.png", - "originalName": "apple-touch-icon.png", - "mimeType": "image/png", - "size": 3380, - "storageUrl": "https://storage.googleapis.com/smbls-api-media/media/projects/6874baae0769df64f1a4484d/users/6868484c0cf470c5890933d5/KC6luiJ-_wNASRFXds4Zc.png", - "bucket": "smbls-api-media", - "category": "image", - "tags": [], - "uploadedBy": "6868484c0cf470c5890933d5", - "project": "6874baae0769df64f1a4484d", - "visibility": "public", - "status": "active", - "version": 1, - "downloadCount": 0, - "_id": "691722cceff651dd5dde3586", - "previousVersions": [], - "createdAt": "2025-11-14T12:38:36.273Z", - "updatedAt": "2025-11-14T12:38:36.273Z", - "__v": 0, - "extension": "png", - "humanSize": "3.3 KB", - "age": 55, - "id": "691722cceff651dd5dde3586", - "urls": { - "api": { - "base": "/core/files", - "file": "/core/files/691722cceff651dd5dde3586", - "download": "/core/files/public/691722cceff651dd5dde3586/download", - "publicDownload": "/core/files/public/691722cceff651dd5dde3586/download" - }, - "absolute": { - "base": "https://api.symbols.app/core/files", - "file": "https://api.symbols.app/core/files/691722cceff651dd5dde3586", - "download": "https://api.symbols.app/core/files/public/691722cceff651dd5dde3586/download", - "publicDownload": "https://api.symbols.app/core/files/public/691722cceff651dd5dde3586/download" - }, - "storage": "https://storage.googleapis.com/smbls-api-media/media/projects/6874baae0769df64f1a4484d/users/6868484c0cf470c5890933d5/KC6luiJ-_wNASRFXds4Zc.png" - }, - "src": "https://api.symbols.app/core/files/public/691722cceff651dd5dde3586/download" - }, - "code": "", - "key": "walrus", - "type": "files", - "format": "walrus" - } - }, - "snippets": { - "default": {} - }, - "access": "account", - "isSharedLibrary": false, - "framework": "platform", - "language": "javascript", - "visibility": "public", - "versions": [ - "6874baae0769df64f1a44852" - ], - "pullRequests": [], - "members": [ - { - "_id": "6874baae0769df64f1a4484f", - "user": { - "_id": "6868484c0cf470c5890933d5", - "email": "toko@symbols.app", - "name": "Tornike", - "avatar": "https://lh3.googleusercontent.com/a/ACg8ocIJUaJjaHnDukKYkIZ_0POhuyuiPQv_HxQ9InABK4W9nOYukr8=s96-c", - "username": "toko", - "id": "6868484c0cf470c5890933d5" - }, - "project": "6874baae0769df64f1a4484d", - "role": "owner", - "joinedAt": "2025-07-14T08:07:10.366Z", - "createdAt": "2025-07-14T08:07:10.366Z", - "updatedAt": "2025-07-14T08:07:10.366Z", - "__v": 0, - "id": "6874baae0769df64f1a4484f" - }, - { - "_id": "6874baff0769df64f1a44a4e", - "user": { - "_id": "6868484c0cf470c5890933cc", - "email": "nika@symbols.app", - "name": "Nika", - "username": "nikoloza", - "id": "6868484c0cf470c5890933cc" - }, - "project": "6874baae0769df64f1a4484d", - "role": "admin", - "joinedAt": "2025-07-14T08:08:31.086Z", - "createdAt": "2025-07-14T08:08:31.086Z", - "updatedAt": "2025-07-14T08:08:31.086Z", - "__v": 0, - "id": "6874baff0769df64f1a44a4e" - }, - { - "_id": "6946a10c4c0341ca0852b1ac", - "user": { - "_id": "6868484c0cf470c5890933cf", - "email": "thomas@symbols.app", - "name": "Thomas", - "avatar": "https://lh3.googleusercontent.com/a/ACg8ocLzOMpMtLVoWtCdx2aabhbbK1jKL7EkbAC2UDStgMmw2t1x7tg=s96-c", - "username": "thomas", - "id": "6868484c0cf470c5890933cf" - }, - "project": "6874baae0769df64f1a4484d", - "role": "editor", - "joinedAt": "2025-12-20T13:13:48.773Z", - "createdAt": "2025-12-20T13:13:48.773Z", - "updatedAt": "2025-12-20T13:13:48.773Z", - "__v": 0, - "id": "6946a10c4c0341ca0852b1ac" - } - ], - "payments": [], - "useLibraries": [], - "usedByProjects": [], - "createdAt": "2025-07-14T08:07:10.346Z", - "updatedAt": "2025-12-21T20:11:56.172Z", - "__v": 2, - "publishedVersion": "69171bb503fef75cff661362", - "canvas": { - "pages": { - "Symbols": { - "title": "Symbols", - "freeform": true, - "pages": [], - "components": [ - "FilterStatus", - "LabelTag", - "FormModal", - "ValidatorFooter", - "ValidatorRow", - "FieldCaption", - "CopyButton", - "Column", - "FilterSidebar", - "PreviewItem", - "Dropdown", - "Row", - "ActiveRow", - "ValidatorsList", - "Search", - "NetworkRow", - "Page", - "NavButton", - "ModalWindow", - "MetricsSection", - "NetworkRowLabel", - "Header", - "MetaSectionCopy", - "Select", - "ValidatorInfo", - "ValidatorContent", - "Modal", - "ModalForm", - "MetaSection", - "ValidatorLogs", - "Aside", - "OldNotes", - "Graphs", - "Chart", - "Chart.Block", - "Prompt", - "AIThread", - "AIMessage", - "Uptime", - "LogItem", - "MetaSectionBars", - "Logs", - "Table", - "SearchItem", - "SearchDropdown", - "Content", - "Layout" - ], - "functions": [ - "parseNetworkRow", - "getStatusColor" - ], - "methods": [], - "snippets": [], - "atom": [], - "files": [], - "dependencies": [], - "secrets": [] - } - }, - "clients": {} - }, - "colors": {}, - "components": { - "Row": { - "Flex": { - "Status": { - "props": "(el, s) => ({\n order: '-1',\n background: el.call('getStatusColor', s.status),\n round: 'C',\n boxSize: 'Y2'\n })" - }, - "Title": { - "tag": "strong", - "flexFlow": "x", - "gap": "X2", - "text": "(el, s) => s.protocol" - }, - "Validator": { - "extends": "NetworkRowLabel", - "text": "Validator", - "color": "white", - "background": "white .1", - "theme": null, - "fontSize": "Z2", - "hide": "(el, s) => !s.validator_info?.length" - }, - "Rpc": { - "extends": "NetworkRowLabel", - "text": "RPC", - "color": "white", - "background": "white .1", - "theme": null, - "fontSize": "Z2", - "hide": "(el, s) => !s.rpc_info?.length" - }, - "Mainnet": { - "extends": "NetworkRowLabel", - "text": "Mainnet", - "color": "white", - "background": "white .1", - "theme": null, - "fontSize": "Z2", - "hide": "(el, s) => !el.call('filterByEnv', s.validator_info, 'Mainnet') && !el.call('filterByEnv', s.rpc_info, 'Mainnet')", - "order": 12 - }, - "Testnet": { - "extends": "NetworkRowLabel", - "text": "Testnet", - "color": "white", - "background": "white .1", - "theme": null, - "fontSize": "Z2", - "hide": "(el, s) => !el.call('filterByEnv', s.validator_info, 'Testnet') && !el.call('filterByEnv', s.rpc_info, 'Testnet')", - "order": 12 - }, - "gap": "B", - "align": "center", - "padding": "A", - "width": "100%", - "Icon": { - "order": 13, - "icon": "(el, s) => 'chevron' + (s.isActive ? 'Up' : 'Down')" - }, - "Spacer": { - "flex": 1, - "order": 10 - } - }, - "props": { - "href": "(el, s) => '/network/' + s.protocol", - "align": "center", - "gap": "Z2", - "childProps": {}, - "padding": "A A2", - "width": "100%", - "flexFlow": "y", - "userSelect": "none", - "cursor": "pointer" - } - }, - "Column": { - "text": "{{ value }}", - "props": { - "padding": "Z" - } - }, - "LabelTag": { - "tag": "label", - "props": { - "text": "-2.901", - "theme": "warning", - "round": "B", - "padding": "X Y", - "lineHeight": "1em" - } - }, - "ValidatorInfo": { - "extend": "Flex", - "props": "(el, s) => ({\n flexFlow: 'y',\n gap: 'C1',\n padding: 'A A2',\n round: 'A2 A2 B+X B+X',\n transition: 'B1 defaultBezier opacity',\n })", - "Header": { - "extends": "Flex", - "align": "center", - "gap": "Z", - "Avatar": { - "src": "{{ protocol }}.png", - "boxSize": "B1" - }, - "H5": { - "lineHeight": 1, - "text": "{{ protocol }}" - } - }, - "Flex": { - "if": "() => false", - "Add": { - "extends": "Button", - "theme": "transparent", - "padding": "Z A", - ":hover": { - "theme": "dialog" - }, - "icon": "plus", - "text": "Add Node", - "gap": "Y1", - "onClick": "(ev, el, s) => {\n ev.stopPropagation()\n s.root.update({\n editMode: true\n })\n }" - }, - "align": "center space-between", - "gap": "B2", - "Repo": { - "extends": [ - "IconText", - "Link" - ], - "fontWeight": "300", - "order": 10, - "icon": "(el, s) => s.repo_url?.includes('gitlab') ? 'gitlab' : 'github'", - "gap": "X2", - "href": "{{repo_url}}", - "text": "(el, s) => s.repo_url?.split('.com/')[1] || '...'", - ":hover": { - "textDecoration": "underline" - }, - "margin": "- - - auto", - "target": "_blank" - }, - "padding": "- A - -" - }, - "Grid": { - "childExtends": "Hgroup", - "templateColumns": "repeat(4, 1fr)", - "gap": "C", - "childProps": { - "H": { - "tag": "h6", - "order": 2 - }, - "P": { - "color": "caption", - "fontSize": "Z1" - } - }, - "children": [ - { - "H": { - "text": "{{ network_type }}" - }, - "P": { - "text": "Network Type" - } - }, - { - "H": { - "text": "{{ network_layer }}" - }, - "P": { - "text": "Network Layer" - } - }, - { - "H": { - "text": "{{ participation }}" - }, - "P": { - "text": "Network access" - } - }, - { - "gridColumn": "span 3", - "H": { - "text": null, - "Link": { - "href": "{{repo_url}}", - "text": "(el, s) => s.repo_url?.split('.com/')[1] || '...'", - "target": "_blank", - ":hover": { - "textDecoration": "underline" - } - } - }, - "P": { - "text": "Repo URL" - } - } - ] - }, - "NoContent": { - "show": "(el, s) => !s.validators?.length && !s.rpc_nodes?.length", - "padding": "A A2", - "textAlign": "center", - "text": "Network is offline" - }, - "Both": { - "margin": "B -Z2 A", - "flexFlow": "y", - "gap": "Z", - "Title": { - "fontSize": "Z2", - "margin": "- - - Z2", - "text": "Nodes ", - "Span": { - "fontWeight": "100", - "text": "(el, s) => `(${(s.validators?.length + s.rpc_nodes?.length) || 0})`" - } - }, - "Validators": { - "show": "(el, s) => s.validators?.length", - "children": "(el, s) => s.validators", - "extends": "ValidatorsList" - }, - "RPC": { - "show": "(el, s) => s.rpc_nodes?.length", - "children": "(el, s) => s.rpc_nodes", - "extends": "ValidatorsList" - } - }, - "Communication": { - "flexFlow": "y", - "gap": "Z", - "Title": { - "fontSize": "Z2", - "text": "Communication Channels" - }, - "Grid": { - "margin": "A - - -", - "hide": "(el, s) => !s.communication_channels", - "templateColumns": "repeat(4, 1fr)", - "childProps": { - "Link": { - "href": "{{ value }}", - "text": "{{ key }}", - "target": "_blank", - ":hover": { - "textDecoration": "underline" - } - } - }, - "childrenAs": "state", - "children": "(el, s) => {\n if (s.communication_channels) {\n const channels = JSON.parse(s.communication_channels)[0]\n const keyVal = Object.entries(channels).filter(v => v[1])\n return keyVal.map(v => ({\n key: v[0],\n value: v[1]\n }))\n }\n }" - }, - "NoContent": { - "hide": "(el, s) => s.communication_channels", - "text": "n/a", - "tag": "h6", - "margin": "0", - "fontWeight": "300", - "color": "caption" - } - } - }, - "ValidatorContent": { - "Grid": { - "childExtends": "PreviewItem", - "gap": "C", - "columns": "repeat(4, 1fr)", - "Monkier": { - "P": { - "text": "Moniker" - }, - "H": { - "isNan": "(el, s) => !s.moniker", - "text": "(el, s) => s.moniker || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "Env": { - "P": { - "text": "Environment" - }, - "H": { - "isNan": "(el, s) => !s.env", - "text": "(el, s) => s.env || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "Status": { - "P": { - "text": "Status" - }, - "H": { - "flexFlow": "x", - "gap": "X2", - "flexAlign": "center start", - "alignSelf": "start", - "text": null, - "Status": { - "props": "(el, s) => ({\n hide: (el, s) => !s.status,\n order: '-1',\n background: el.call('getStatusColor', s.status),\n round: 'C',\n boxSize: 'Y2'\n })" - }, - "Text": { - "isNan": "(el, s) => !s.status", - "text": "(el, s) => s.status || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - } - }, - "Owner": { - "P": { - "text": "Node Operator" - }, - "H": { - "hide": "(el, s) => s.owner", - "text": "n/a", - "fontWeight": "300", - "color": "caption" - }, - "LabelTag": { - "hide": "(el, s) => !s.owner", - "flexFlow": "x", - "gap": "X2", - "flexAlign": "center start", - "alignSelf": "start", - "background": "white", - "color": "black", - "fontSize": "Z2", - "Avatar": { - "boxSize": "A" - }, - "Text": { - "text": "(el, s) => s.owner" - }, - "text": null - } - }, - "Version": { - "P": { - "text": "Client version" - }, - "H": { - "text": "(el, s) => s.client_version || 'n/a'", - "isNan": "(el, s) => !s.owner", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "RewardClaim": { - "P": { - "text": "Reward Claim" - }, - "H": { - "isNan": "(el, s) => !s.reward_claim", - "text": "(el, s) => s.reward_claim || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "Category": { - "P": { - "text": "Category" - }, - "H": { - "isNan": "(el, s) => !s.category", - "text": "(el, s) => s.category || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "CloudProvider": { - "P": { - "text": "Cloud Provider" - }, - "H": { - "isNan": "(el, s) => !s.cloud_provider", - "text": "(el, s) => s.cloud_provider || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "Reward_address": { - "gridColumn": "span 2", - "P": { - "text": "Reward Address" - }, - "H": { - "extends": "Flex", - "flexAlign": "center start", - "Text": { - "overflow": "hidden", - "maxWidth": "95%", - "textOverflow": "ellipsis", - "text": "{{reward_address}}" - }, - "Text_no": { - "color": "caption", - "hide": "(el, s) => s.reward_address", - "fontWeight": "300", - "text": "n/a" - }, - "CopyButton": { - "hide": "(el, s) => !s.reward_address", - "value": "(el, s) => s.reward_address" - }, - "text": null - } - }, - "PublicKey_address": { - "gridColumn": "span 2", - "P": { - "text": "Public key" - }, - "H": { - "extends": "Flex", - "flexAlign": "center start", - "Text": { - "overflow": "hidden", - "maxWidth": "95%", - "textOverflow": "ellipsis", - "text": "{{public_key}}" - }, - "Text_no": { - "color": "caption", - "hide": "(el, s) => s.public_key", - "fontWeight": "300", - "text": "n/a" - }, - "CopyButton": { - "hide": "(el, s) => !s.public_key", - "value": "(el, s) => s.public_key" - }, - "text": null - } - }, - "Hr": { - "ignoreChildExtend": true, - "margin": "A 0", - "opacity": ".05", - "gridColumn": "span 4" - }, - "OurProposal": { - "P": { - "text": "Proposals managed by us" - }, - "H": { - "isNan": "(el, s) => !s.do_we_manage_proposals", - "text": "(el, s) => s.do_we_manage_proposals || 'n/a'", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "CurrentSpent": { - "P": { - "text": "Current Spent" - }, - "H": { - "text": "(el, s) => s.projected_cost ? `$${ s.current_spend?.toFixed(2) }` : 'n/a'" - } - }, - "ProjectedCost": { - "P": { - "text": "Projected Cost" - }, - "H": { - "text": "(el, s) => s.projected_cost ? `$${ s.projected_cost?.toFixed(2) }` : 'n/a'", - "isNan": "(el, s) => !s.owner", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "Consensus": { - "P": { - "text": "Consensus" - }, - "H": { - "text": "(el, s) => s.consensus || 'n/a'", - "isNan": "(el, s) => !s.consensus", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "SLA": { - "P": { - "text": "SLA" - }, - "H": { - "text": "(el, s) => s.sla || 'n/a'", - "isNan": "(el, s) => !s.sla", - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "WarehouseSupport": { - "if": "(el, s) => s.nodeType === 'rpc'", - "P": { - "text": "Data warehouse support" - }, - "H": { - "text": "(el, s) => s.data_warehouse_support ? 'Yes' : 'No'" - } - }, - "WarehouseUrl": { - "if": "(el, s) => s.nodeType === 'rpc'", - "gridColumn": "span 2", - "P": { - "text": "Data warehouse URL" - }, - "H": null, - "Link": { - "isNan": "(el, s) => !s.data_warehouse_url", - "fontSize": "B", - "target": "_blank", - "href": "(el, s) => s.data_warehouse_url", - "text": "(el, s) => s.data_warehouse_url ? s.data_warehouse_url.slice(0, 56) + '...' : 'n/a'", - "fontWeight": "400", - "[href]:hover": { - "textDecoration": "underline" - }, - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "KnowledgeBase": { - "gridColumn": "span 2", - "P": { - "text": "Knowledge base" - }, - "H": null, - "Link": { - "isNan": "(el, s) => !s.knowledge_base", - "fontSize": "B", - "target": "_blank", - "href": "(el, s) => s.knowledge_base", - "text": "(el, s) => s.knowledge_base ? s.knowledge_base.slice(0, 56) + '...' : 'n/a'", - "fontWeight": "400", - "[href]:hover": { - "textDecoration": "underline" - }, - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - }, - "Explorer": { - "gridColumn": "span 2", - "P": { - "text": "Explorer link" - }, - "H": null, - "Link": { - "isNan": "(el, s) => !s.explorer_link", - "fontSize": "B", - "target": "_blank", - "href": "(el, s) => s.explorer_link", - "text": "(el, s) => s.explorer_link ? s.explorer_link.slice(0, 56) + '...' : 'n/a'", - "fontWeight": "400", - "[href]:hover": { - "textDecoration": "underline" - }, - ".isNan": { - "fontWeight": "300", - "color": "caption" - } - } - } - }, - "Hr": { - "ignoreChildExtend": true, - "margin": "-B2 0", - "opacity": ".05", - "gridColumn": "span 4" - }, - "Uptime": {}, - "Graphs": {}, - "props": { - "width": "100%", - "flow": "y", - "gap": "E", - "padding": "A2", - "onClick": "(ev, el, s) => {\n ev.stopPropagation()\n ev.preventDefault()\n }" - }, - "extend": "Flex" - }, - "PreviewItem": { - "props": { - "flexFlow": "y", - "align": "start start" - }, - "P": { - "text": "RAM", - "fontSize": "Z1", - "color": "caption" - }, - "H": { - "text": "100%", - "margin": "0", - "lineHeight": 1, - "tag": "h6", - "order": 2 - } - }, - "ValidatorRow": { - "extend": "Link", - "props": { - "flexFlow": "x", - "gap": "A2", - "padding": "A1 B", - "flexAlign": "center start", - "cursor": "pointer", - "background": "deepFir", - "href": "(el, s) => '/node/' + s.parent.protocol + '/' + (el.__ref.path.includes('RPC') ? 'rpc' : 'validator') + '/{{ uid }}'", - ":hover": { - "background": "deepFir 1 +5" - }, - "transition": "B defaultBezier background", - "round": "C1" - }, - "Status": { - "props": "(el, s) => ({\n order: '-1',\n background: el.call('getStatusColor', s.status),\n round: 'C',\n boxSize: 'Y2'\n })" - }, - "Strong": { - "text": "{{moniker}}" - }, - "Version": { - "text": "({{ client_version }})", - "color": "white", - "fontSize": "Z2", - "fontWeight": "200" - }, - "PublicKey": { - "extends": "Flex", - "flexAlign": "center start", - "hide": "(el, s) => !s.public_key", - "Text": { - "text": "{{public_key}}", - "overflow": "hidden", - "maxWidth": "95%", - "textOverflow": "ellipsis" - }, - "CopyButton": { - "value": "(el, s) => s.public_key" - } - }, - "Env": { - "extends": "NetworkRowLabel", - "fontWeight": "500", - "fontSize": "Z2", - "text": "(el, s) => s.env", - "background": "white .25", - "color": "white" - } - }, - "ValidatorFooter": { - "props": { - "childProps": { - "theme": "transparent", - "padding": "Z A", - ":hover": { - "theme": "dialog" - } - }, - "padding": "A", - "gap": "C1", - "margin": "0 -Y", - "onClick": "(ev, el, s) => {\n ev.stopPropagation()\n ev.preventDefault()\n }" - }, - "extend": "Flex", - "Edit": { - "icon": "check", - "text": "Edit", - "gap": "Y1", - "onClick": "(ev, el, s) => {\n s.root.update({\n editMode: true\n })\n }" - }, - "childExtend": "Button" - }, - "FormModal": { - "extend": "ModalWindow", - "props": { - "maxHeight": "95dvh", - "overflow": "hidden auto", - "widthRange": "H1", - "@mobileS": { - "fontSize": "Z2" - }, - "@mobileXS": { - "fontSize": "Z1" - } - }, - "Hgroup": { - "margin": "- W B+V2 W", - "H": {}, - "P": {} - }, - "XBtn": {}, - "Form": { - "extends": "ModalForm" - } - }, - "EditForm": { - "extend": "FormModal", - "props": { - "gap": "C", - "width": "80%", - "maxWidth": "H3", - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n\n await el.call('addNew')\n }" - }, - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Update Network" - }, - "P": { - "text": "Edit properties for existing Network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Protocol',\n },\n Field: {\n Input: {\n name: 'protocol',\n placeholder: 'E.g. Polygon',\n type: 'text',\n value: '{{ protocol }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Network Layer',\n },\n Field: {\n Input: {\n placeholder: 'v0.1.2',\n value: '{{ network_layer }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Network Type',\n },\n Field: {\n Input: {\n placeholder: 'v0.1.2',\n value: '{{ network_type }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Repository',\n },\n Field: {\n Input: {\n placeholder: 'https://github.com',\n type: 'url',\n value: '{{ repo_url }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - }, - "tag": "form" - }, - "FieldCaption": { - "extend": "Flex", - "props": { - "flow": "column", - "boxSize": "fit-content fit-content" - }, - "Caption": { - "tag": "caption", - "text": "Caption", - "lineHeight": "1em", - "fontSize": "A", - "fontWeight": "400", - "padding": "- Y2 Z X", - "alignSelf": "flex-start", - "whiteSpace": "nowrap", - "textAlign": "left" - }, - "Field": { - "width": "100%", - "Input": { - "width": "100%" - }, - "Icon": {} - } - }, - "FilterSidebar": { - "props": { - "gap": "A", - "flexFlow": "x", - "flexAlign": "center start" - }, - "extend": "DropdownParent", - "Button": { - "text": "(el, s) => s.isUpdate ? 'Updates' : 'All validators'", - "theme": "transparent", - "gap": "Z2", - "Icon": { - "name": "chevronDown", - "order": 2, - "minWidth": "Z2" - }, - "fontWeight": "300", - "paddingInline": "0", - "width": "E3", - "flexAlign": "center start" - }, - "Dropdown": { - "left": "-X", - "backdropFilter": "blur(3px)", - "background": "softBlack .9 +65", - "childExtends": "Button", - "childProps": { - "theme": "transparent", - "align": "start", - "padding": "Z2 A" - }, - "flexFlow": "y" - } - }, - "FilterStatus": { - "extend": "Flex", - "Stable": { - "Box": { - "background": "green", - "round": "C", - "boxSize": "Y2" - }, - "padding": "Z2", - ":hover": { - "theme": "field" - }, - "round": "C1", - "onClick": "(ev, el, s) => s.update({\n status: s.status === 'Stable/Maintenance' ? null : 'Stable/Maintenance'\n })", - ".isInactive": { - "opacity": "0.35" - }, - "isInactive": "(el, s) => s.status && s.status !== 'Stable/Maintenance'", - "isActive": "(el, s) => s.status === 'Stable/Maintenance'", - ".isActive": { - "theme": "field" - } - }, - "Off": { - "Box": { - "background": "gray", - "round": "C", - "boxSize": "Y2" - }, - "padding": "Z2", - ":hover": { - "theme": "field" - }, - "round": "C1", - "onClick": "(ev, el, s) => s.update({\n status: s.status === 'Off' ? null : 'Off'\n })", - ".isInactive": { - "opacity": "0.35" - }, - "isInactive": "(el, s) => s.status && s.status !== 'Off'", - "isActive": "(el, s) => s.status === 'Off'", - ".isActive": { - "theme": "field" - } - } - }, - "Dropdown": { - "props": { - "isDropdownRoot": true, - "theme": "dialog", - "round": "Z2", - "position": "absolute", - "left": "50%", - "transform": "translate3d(0, -10px, 0)", - "top": "102%", - "minWidth": "F", - "maxHeight": "H", - "transition": "A defaultBezier", - "transitionProperty": "visibility, transform, opacity", - "overflow": "hidden auto", - "opacity": "0", - "visibility": "hidden", - "boxSizing": "border-box", - "zIndex": 1000, - "@dark": { - "boxShadow": "black .20, 0px, 5px, 10px, 5px" - }, - "@light": { - "boxShadow": "gray5 .20, 0px, 5px, 10px, 5px" - } - }, - "attr": { - "dropdown": true - }, - "tag": "section" - }, - "CopyButton": { - "extend": "IconButton", - "props": { - "icon": "copy outline", - "background": "transparent", - "color": "currentColor", - "isActive": false, - "padding": "Y1", - "fontSize": "Z", - "--spacing-ratio": 1.2, - "onClick": "async (ev, el, s, ctx) => {\n ev.preventDefault()\n ev.stopPropagation()\n el.setProps({\n Icon: {\n name: 'check outline'\n }\n })\n const t = setTimeout(() => {\n el.setProps({\n Icon: {\n name: 'copy outline'\n }\n })\n clearTimeout(t)\n }, 1000)\n\n await el.call(\n 'copyStringToClipboard',\n el.call('exec', el.props.value, el) ||\n el.call('exec', s.key, el) ||\n el.call('exec', s.value, el)\n )\n }" - } - }, - "ValidatorLogs": { - "props": { - "gap": "B2", - "columns": "repeat(3, 1fr)", - "margin": "0 -Y", - "padding": "A2 Z2+X", - "onClick": "(ev, el, s) => {\n ev.stopPropagation()\n ev.preventDefault()\n }" - }, - "extend": "Grid", - "childExtend": "LogItem", - "RAM": { - "Title": { - "text": "Client Version" - }, - "Strong": { - "text": "{{ client_version }}" - } - }, - "Reward_address": { - "Title": { - "text": "Reward Address" - }, - "Strong": { - "text": "{{ reward_address }}" - } - }, - "CloudProvider": { - "Title": { - "text": "Cloud Provider" - }, - "Strong": { - "text": "{{ cloud_provider }}" - } - }, - "Owner": { - "Title": { - "text": "Owner" - }, - "Strong": { - "text": "{{ owner }}" - } - }, - "Monkier": { - "Title": { - "text": "Monkier" - }, - "Strong": { - "text": "{{ moniker }}" - } - } - }, - "LogItem": { - "props": { - "padding": "A2", - "flexFlow": "y", - "gap": "Z2", - "theme": "dialog", - "round": "A" - }, - "Title": { - "text": "RAM" - }, - "H3": { - "text": "100%", - "margin": "0", - "lineHeight": 1 - } - }, - "Chart": { - "props": { - "onRender": "async (el, s) => {\n const Chart = await import('chart.js')\n const ctx = el.node.getContext('2d');\n\n const maxPoints = 50;\n const dataPoints = Array(maxPoints).fill(0);\n\n const labels = Array.from({\n length: maxPoints\n }, (_, i) => i.toString());\n\n // Initialize with some starting data\n for (let i = 0; i < dataPoints.length; i++) {\n dataPoints[i] = 30 + Math.sin(i * 0.2) * 20;\n }\n\n const data = {\n labels: labels,\n datasets: [{\n label: 'CPU',\n data: dataPoints.map((value, index) => ({\n x: index,\n y: value\n })),\n borderColor: 'rgb(86, 154, 67)',\n fill: false,\n tension: 0.4,\n pointRadius: 0,\n borderWidth: 2,\n cubicInterpolationMode: 'monotone'\n }]\n };\n\n const options = {\n responsive: true,\n animation: false,\n interaction: {\n intersect: false,\n mode: 'index'\n },\n plugins: {\n legend: {\n display: false\n },\n tooltip: {\n enabled: false\n }\n },\n scales: {\n x: {\n display: false,\n type: 'linear',\n min: 0,\n max: maxPoints - 1\n },\n y: {\n display: false,\n min: 0,\n max: 100\n }\n },\n elements: {\n line: {\n borderJoinStyle: 'round'\n }\n },\n maintainAspectRatio: false\n };\n\n const chart = new Chart(ctx, {\n type: 'line',\n data: data,\n options: options\n });\n\n // Color functions\n function getColor(value) {\n if (value < 60) return 'rgb(86, 154, 67)';\n if (value < 80) return 'rgb(245, 158, 11)';\n return 'rgb(220, 38, 38)';\n }\n\n function interpolateColor(color1, color2, factor) {\n const c1 = color1.match(/\\d+/g).map(Number);\n const c2 = color2.match(/\\d+/g).map(Number);\n const r = Math.round(c1[0] + (c2[0] - c1[0]) * factor);\n const g = Math.round(c1[1] + (c2[1] - c1[1]) * factor);\n const b = Math.round(c1[2] + (c2[2] - c1[2]) * factor);\n return `rgb(${r}, ${g}, ${b})`;\n }\n\n let animationFrame = null;\n let isAnimating = false;\n\n function updateCPUChart(newValue) {\n if (isAnimating) return;\n\n isAnimating = true;\n const dataset = chart.data.datasets[0];\n const originalData = [...dataset.data];\n const lastPoint = originalData[originalData.length - 1];\n const startTime = performance.now();\n const duration = 1000;\n\n // Get colors for transition\n const startColor = dataset.borderColor;\n const endColor = getColor(newValue);\n\n function animate(currentTime) {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / duration, 1);\n const easeProgress = easeInOutCubic(progress);\n\n // Create new animated data\n const animatedData = [];\n\n // Animate all existing points shifting left (keep their y values)\n for (let i = 0; i < originalData.length; i++) {\n animatedData.push({\n x: originalData[i].x - easeProgress,\n y: originalData[i].y // Keep original y value unchanged\n });\n }\n\n // Add new point entering from the right, transitioning from lastPoint.y to newValue\n animatedData.push({\n x: maxPoints - 1 + (1 - easeProgress),\n y: lastPoint.y + (newValue - lastPoint.y) * easeProgress\n });\n\n dataset.data = animatedData;\n\n // Animate color if crossing threshold\n if (getColor(lastPoint.y) !== getColor(newValue)) {\n dataset.borderColor = interpolateColor(startColor, endColor, easeProgress);\n }\n\n chart.update('none');\n\n if (progress < 1) {\n animationFrame = requestAnimationFrame(animate);\n } else {\n // Final state: shift all data and add new point\n dataset.data = [\n ...originalData.slice(1).map((point, i) => ({\n x: i,\n y: point.y\n })),\n {\n x: maxPoints - 1,\n y: newValue\n }\n ];\n dataset.borderColor = endColor;\n chart.update('none');\n isAnimating = false;\n }\n }\n\n animationFrame = requestAnimationFrame(animate);\n }\n\n function easeInOutCubic(t) {\n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n }\n\n // Simulate CPU data updates\n const interval = setInterval(() => {\n const fakeCPU = Math.floor(Math.random() * 100);\n updateCPUChart(fakeCPU);\n }, 1100);\n\n // Cleanup function\n return () => {\n clearInterval(interval);\n if (animationFrame) {\n cancelAnimationFrame(animationFrame);\n }\n chart.destroy();\n };\n }", - "tag": "canvas", - "minWidth": "G", - "minHeight": "D" - }, - "data": {}, - "Block": { - "data": {}, - "Number": { - "text": "5,104,599", - "fontSize": "D", - "position": "absolute", - "top": "50%", - "left": "50%", - "transform": "translate(-50%, -50%)", - "textShadow": "0 0 10px rgba(0, 0, 0, 0.5)", - "fontWeight": "500" - }, - "Box": { - "tag": "canvas", - "minWidth": "G", - "minHeight": "D", - "onRender": "async (el, s) => {\n const Chart = window.Chart;\n const ctx = el.node.getContext('2d');\n const canvas = el.node;\n\n const maxPoints = 100;\n let currentHeight = 5104596;\n\n // Create initial data with a slight upward trend\n const data = {\n labels: Array(maxPoints).fill(''),\n datasets: [{\n label: 'Chain height',\n data: Array.from({\n length: maxPoints\n }, (_, i) =>\n currentHeight - (maxPoints - i) * 10\n ),\n borderColor: 'rgb(86, 154, 67)',\n backgroundColor: 'transparent',\n borderWidth: 2,\n fill: false,\n tension: 0.4,\n pointRadius: 0,\n }]\n };\n\n // Chart configuration\n const config = {\n type: 'line',\n data: data,\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: false,\n scales: {\n x: {\n display: false,\n grid: {\n display: false\n }\n },\n y: {\n display: false,\n grid: {\n display: false\n }\n }\n },\n plugins: {\n legend: {\n display: false\n },\n tooltip: {\n enabled: false\n }\n },\n elements: {\n line: {\n borderCapStyle: 'round',\n borderJoinStyle: 'round'\n }\n }\n }\n };\n\n // Create the chart\n const chart = new Chart(ctx, config);\n\n // Update function\n const updateChart = () => {\n // Simulate block height increase\n currentHeight += Math.floor(Math.random() * 3) + 1;\n\n // Update the data\n const newData = [...chart.data.datasets[0].data];\n newData.shift();\n newData.push(currentHeight);\n chart.data.datasets[0].data = newData;\n\n // Update the chart\n chart.update('none');\n\n // Update the overlay\n const Number = el.parent.Number\n Number.setProps({\n text: currentHeight.toLocaleString()\n })\n };\n\n // Start updating every second\n const interval = setInterval(updateChart, 3000);\n\n // Clean up\n const cleanup = () => {\n clearInterval(interval);\n chart.destroy();\n };\n\n // Handle unmount\n canvas.addEventListener('unmount', cleanup);\n\n // Also clean up if the canvas is removed from DOM\n const observer = new MutationObserver((mutations) => {\n if (!document.contains(canvas)) {\n cleanup();\n observer.disconnect();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true\n });\n }" - }, - "props": { - "position": "relative" - } - } - }, - "Uptime": { - "extend": "Flex", - "Flex": { - "maxWidth": "100%", - "childProps": { - "minWidth": "Z1", - "boxSize": "Z1", - "background": "green .3", - "border": "1px, solid, green", - "round": "W", - "style": { - "&:hover": { - "opacity": "1 !important" - } - }, - "transition": "A defaultBezier opacity" - }, - "gap": "X", - "children": "(el, s) => new Array(300).fill({})", - "flow": "row wrap", - "height": "2.8em", - "overflow": "hidden", - ":hover > div": { - "opacity": 0.5 - } - }, - "props": { - "flow": "y", - "gap": "Z" - }, - "Title": { - "fontSize": "Z", - "text": "Uptime", - "order": "-1" - } - }, - "Logs": { - "extend": "Flex", - "props": { - "flexFlow": "y", - "borderWidth": "0 0 0 2px", - "borderStyle": "solid", - "borderColor": "--theme-document-dark-background", - "minWidth": "G3", - "padding": "A A2", - "gap": "A2", - "onRender": "(el, s) => {\n window.requestAnimationFrame(async () => {\n let [, _, nodeType, uid] = window.location.pathname.split('/')\n if (window.location.pathname === 'srcdoc') {\n uid = '02841d62-e018-45e7-92f4-7928f832cd30'\n }\n\n const logs = await el.call('read', `/node/${uid}/logs`)\n\n el.call('setInitialData', {\n logs\n })\n })\n }", - "@tabletM": { - "hide": true - } - }, - "H6": { - "fontSize": "Z2", - "text": "Logs", - "fontWeight": "bold" - }, - "Flex": { - "flow": "y", - "children": "(el, s) => s.logs", - "childrenAs": "state", - "padding": "Z2", - "gap": "B2", - "childProps": { - "flexFlow": "y", - "gap": "Y", - "width": "100%", - "position": "relative", - ":hover .buttons": { - "opacity": 1 - }, - ":not(:last-child)": { - "border": "0 0 1px 0, dashed, gray" - }, - "Date": { - "fontWeight": "300", - "fontSize": "Z1", - "color": "caption", - "text": "{{ created_at }}", - "position": "relative", - "Status": { - "round": "C1", - "boxSize": "Z", - "border": "1px, solid, caption", - "position": "absolute", - "top": "Y1", - "left": "-A2" - } - }, - "Notes": { - "text": "{{ status_notes }}" - }, - "Title": { - "text": "{{ action_items }}" - } - } - } - }, - "Graphs": { - "extend": "Flex", - "Flex": { - "children": "(el, s) => ['RAM', 'CPU']", - "childrenAs": "state", - "childExtends": "Flex", - "gap": "D1", - "childProps": { - "flex": 1, - "flow": "y", - "gap": "Z", - "Title": { - "fontSize": "Z", - "text": "{{ value }}", - "textTransform": "uppercase", - "whiteSpace": "nowrap" - }, - "Chart": { - "heightRange": "D3" - } - }, - "flex": 2, - "order": 2 - }, - "props": { - "gap": "D1" - }, - "BlockHeight": { - "extends": "Flex", - "flex": 1, - "flow": "y", - "gap": "Z", - "Title": { - "fontSize": "Z", - "whiteSpace": "nowrap", - "textTransform": "uppercase", - "text": "Block Height" - }, - "Chart.Block": { - "heightRange": "D3", - "variant": "Block", - "position": "relative" - }, - "position": "relative" - } - }, - "Chart.Block": { - "data": {}, - "props": { - "position": "relative" - }, - "Number": { - "text": "5,104,599", - "fontSize": "D", - "position": "absolute", - "top": "50%", - "left": "50%", - "transform": "translate(-50%, -50%)", - "textShadow": "0 0 10px rgba(0, 0, 0, 0.5)", - "fontWeight": "500" - }, - "Box": { - "tag": "canvas", - "minWidth": "G", - "minHeight": "D", - "onRender": "async (el, s) => {\n const Chart = window.Chart;\n const ctx = el.node.getContext('2d');\n const canvas = el.node;\n\n const maxPoints = 100;\n let currentHeight = 5104596;\n\n // Create initial data with a slight upward trend\n const data = {\n labels: Array(maxPoints).fill(''),\n datasets: [{\n label: 'Chain height',\n data: Array.from({\n length: maxPoints\n }, (_, i) =>\n currentHeight - (maxPoints - i) * 10\n ),\n borderColor: 'rgb(86, 154, 67)',\n backgroundColor: 'transparent',\n borderWidth: 2,\n fill: false,\n tension: 0.4,\n pointRadius: 0,\n }]\n };\n\n // Chart configuration\n const config = {\n type: 'line',\n data: data,\n options: {\n responsive: true,\n maintainAspectRatio: false,\n animation: false,\n scales: {\n x: {\n display: false,\n grid: {\n display: false\n }\n },\n y: {\n display: false,\n grid: {\n display: false\n }\n }\n },\n plugins: {\n legend: {\n display: false\n },\n tooltip: {\n enabled: false\n }\n },\n elements: {\n line: {\n borderCapStyle: 'round',\n borderJoinStyle: 'round'\n }\n }\n }\n };\n\n // Create the chart\n const chart = new Chart(ctx, config);\n\n // Update function\n const updateChart = () => {\n // Simulate block height increase\n currentHeight += Math.floor(Math.random() * 3) + 1;\n\n // Update the data\n const newData = [...chart.data.datasets[0].data];\n newData.shift();\n newData.push(currentHeight);\n chart.data.datasets[0].data = newData;\n\n // Update the chart\n chart.update('none');\n\n // Update the overlay\n const Number = el.parent.Number\n Number.setProps({\n text: currentHeight.toLocaleString()\n })\n };\n\n // Start updating every second\n const interval = setInterval(updateChart, 3000);\n\n // Clean up\n const cleanup = () => {\n clearInterval(interval);\n chart.destroy();\n };\n\n // Handle unmount\n canvas.addEventListener('unmount', cleanup);\n\n // Also clean up if the canvas is removed from DOM\n const observer = new MutationObserver((mutations) => {\n if (!document.contains(canvas)) {\n cleanup();\n observer.disconnect();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true\n });\n }" - } - }, - "ValidatorsList": { - "props": { - "childrenAs": "state", - "childProps": { - "flexFlow": "y", - "ValidatorRow": {}, - "ValidatorContent": { - "padding": "B C3 C3", - "hide": "(el, s) => !s.isActive" - } - }, - "gap": "Z", - "flexFlow": "y" - } - }, - "Aside": { - "InputField": { - "Icon": { - "name": "search", - "fontSize": "Z", - "minWidth": "A", - "margin": "- -B2 - Z2", - "opacity": ".35" - }, - "Input": { - "theme": "transparent", - "placeholder": "Filter...", - "fontWeight": "300", - "value": "{{ search }}", - "onInput": "(ev, el, s) => s.update({\n search: el.node.value,\n activeIndex: null\n })", - "padding": "Y Z Y B2", - "width": "F3", - "flex": 1 - }, - "flexFlow": "x", - "flexAlign": "center", - "margin": "- - - -Z2" - }, - "FilterSidebar_validators": { - "Button": { - "text": "(el, s) => s.validators || 'All Validators'" - }, - "Dropdown": { - "childProps": { - ".isActive": { - "background": "white .1" - }, - "isActive": "(el, s) => s.validators === el.text", - "onClick": "(ev, el, s) => {\n s.update({\n validators: el.text,\n activeIndex: null\n })\n }" - }, - "All": { - "text": "All Validators" - }, - "Mainnet": { - "text": "Mainnet" - }, - "Testnet": { - "text": "Testnet" - }, - "None": { - "text": "None" - } - } - }, - "FilterSidebar_rpc": { - "Button": { - "text": "(el, s) => s.rpc || 'All RPC'" - }, - "Dropdown": { - "childProps": { - ".isActive": { - "background": "white .1" - }, - "isActive": "(el, s) => s.rpc === el.text", - "onClick": "(ev, el, s) => {\n s.update({\n rpc: el.text,\n activeIndex: null\n })\n }" - }, - "All": { - "text": "All RPC" - }, - "Mainnet": { - "text": "Mainnet" - }, - "Testnet": { - "text": "Testnet" - }, - "None": { - "text": "None" - } - } - }, - "FilterSidebar_update": { - "Button": { - "text": "(el, s) => s.lastUpdate || 'Last update'" - }, - "Dropdown": { - "childProps": { - ".isActive": { - "background": "white .1" - }, - "isActive": "(el, s) => s.lastUpdate === el.text", - "onClick": "(ev, el, s) => {\n s.update({\n lastUpdate: el.text,\n activeIndex: null\n })\n }" - }, - "All": { - "text": "All Time" - }, - "Today": { - "text": "Today" - }, - "Last2Days": { - "text": "Last 2 Days" - }, - "Last3Days": { - "text": "Last 3 Days" - }, - "LastWeek": { - "text": "Last Week" - }, - "LastMonth": { - "text": "Last Month" - } - } - }, - "FilterSidebar_status": { - "Button": { - "text": "(el, s) => s.status || 'Status'" - }, - "Dropdown": { - "childProps": { - ".isActive": { - "background": "white .1" - }, - "isActive": "(el, s) => s.status === el.text", - "onClick": "(ev, el, s) => {\n s.update({\n status: el.text,\n activeIndex: null\n })\n }" - }, - "All": { - "text": "Stable/Maintenance" - }, - "Today": { - "text": "Off" - } - } - }, - "props": { - "flexFlow": "y", - "gap": "B2", - "fontSize": "Z2+V", - "margin": "Z2 - -" - } - }, - "AIMessage": { - "extend": [ - "LabelTag", - "Focusable" - ], - "props": { - "theme": "field", - "tag": "div", - "padding": "Z1 B1 Z1 A2", - "contentEditable": true, - "color": "caption", - "lineHeight": "1.55em", - "fontWeight": "400", - "text": null, - "html": "(el, s) => s.prompt", - "whiteSpace": "wrap", - "wordBreaking": "break-all", - ":empty:before": { - "opacity": 0.35, - "color": "disabled", - "content": "\"Ask me . . .\"" - } - }, - "tag": "div", - "text": null, - "html": "(el, s) => s.prompt" - }, - "AIThread": { - "extend": "Flex", - "props": { - "padding": "A1", - "width": "100%", - "maxWidth": "100%", - "margin": "- auto", - "height": "100%", - "transition": "A defaultBezier height", - "maxHeight": "50dvh", - "flow": "y", - "gap": "A2", - "overflow": "auto", - "::-webkit-scrollbar": { - "display": "none" - }, - "children": "(el, s) => s.thread", - "childrenAs": "state" - }, - "childExtend": { - "props": "(el, s) => ({\n alignSelf: s.role === 'user' ? 'start' : 'end',\n ':first-child': {\n margin: 'auto - -'\n },\n onRender: el => {\n // prevent scrolling in platform\n // if (location.host === 'symbols.app') return\n\n // run setTimout to run it after everything else\n const t = setTimeout(() => {\n el.log('Scrolling', el.node, el)\n // el.node.scrollIntoView()\n window.scrollBy(0, 80)\n clearTimeout(t)\n }, 35)\n }\n })", - "content": "(el, s) => {\n if (s.role === 'user') {\n return {\n extend: 'AIMessage',\n props: {\n contentEditable: false,\n shape: 'bubble',\n shapeDirection: 'top left',\n round: 'X C C C',\n padding: 'Y2 A',\n margin: '- -Z',\n // round: 'C',\n maxWidth: 'fit-content'\n },\n Message: {\n props: {},\n html: (el, s) => s.message\n },\n }\n }\n\n return {\n extend: 'P',\n props: {\n color: 'paragraph',\n maxWidth: 'I1'\n },\n html: (el, s) => s.message\n }\n }" - } - }, - "Prompt": { - "state": { - "keyword": "", - "thread": [], - "images": [], - "selectedOption": 0 - }, - "tag": "form", - "props": { - "position": "relative", - "margin": "Z 0 -", - "zIndex": "10", - "transition": "Z defaultBezier margin", - "fontSize": "Z2", - "round": 0, - "theme": "transparent", - "onSubmit": "(ev, el, s) => {\n // javascript prevent default to prevent default behaviour\n ev.preventDefault()\n console.log(1)\n\n // get value from state (that we kept in keyup)\n const value = s.keyword\n\n // if value is empty, do nothing (break the function)\n if (!value) return\n\n // how state.apply works https://symbols.app/docs/api/state#methods\n // - instead of object, you can use function and make custom logic\n const makeUserChatData = s => {\n s.thread.push({\n role: 'user', // differenciate if message is by user or agent\n message: value,\n })\n s.keyword = ''\n }\n\n // when 'makeUserChatData' is done, run s.update\n s.apply(makeUserChatData)\n\n // make textarea empty again\n // e.g. reset form\n el.Relative.Textarea.value = ''\n\n // fake functionity that chat is answering\n // reuse\n const answer = setTimeout(async () => {\n const res = await el.call('giveMeAnswer', value)\n console.log(res)\n\n s.apply(s => {\n s.thread.push({\n role: 'agent',\n message: res.summary\n })\n })\n\n const promptedData = res.data[0].data\n s.root.replace({\n promptedData: el.call('isArray', promptedData) ? promptedData : [promptedData]\n })\n\n clearTimeout(answer)\n }, 1000)\n }" - }, - "Relative": { - "position": "relative", - "Textarea": { - "id": "prompt", - "display": "flex", - "padding": "Z2 A", - "maxWidth": "none", - "maxHeight": "E3", - "minHeight": "C3", - "overflow": "hidden auto", - "outline": "none", - "width": "100%", - "borderWidth": "0 0 1px", - "whiteSpace": "pre-wrap", - "borderStyle": "solid", - "borderColor": "line", - "color": "title", - "transition": "Z defaultBezier padding", - "placeholder": "\"Ask, Search, Prompt…\"", - "theme": null, - "background": "transparent", - "round": "0", - "onInput": "(ev, el, s) => {\n let prompt = el.node.value.trim()\n if (prompt === '\\n') prompt = ''\n // s.replace({ keyword: prompt, thread: [] }) // Todo: maybe keep the thread\n s.replace({\n keyword: prompt\n })\n }", - "onKeydown": "(ev, el, s) => {\n if (ev.key === 'Enter' && !ev.shiftKey) {\n ev.preventDefault()\n // console.log(el.parent.parent.node)\n // console.log(s)\n // el.parent.node.submit(ev, el, s)\n }\n // TODO: I think this does not work\n if (ev.key === 'Escape') {\n ev.stopPropagation()\n el.node.blur()\n }\n }" - }, - "Submit": { - "extends": "SquareButton", - "theme": "transparent", - "icon": "check", - "type": "submit", - "position": "absolute", - "bottom": "Z2", - "right": "Z2", - "zIndex": 2 - } - }, - "AIThread": { - "hide": "(el, s) => !s.thread.length" - } - }, - "Search": { - "extend": [ - "Flex" - ], - "tag": "search", - "state": {}, - "props": { - "minWidth": "G2", - "gap": "Z", - "icon": "search", - "align": "center flex-start", - "position": "relative", - "theme": "field", - "round": "D2", - "@mobileS": { - "minWidth": "G1" - } - }, - "Icon": { - "icon": "search", - "right": "A+V2" - }, - "Input": { - "type": "search", - "placeholder": "Type a command or search", - "width": "100%", - "padding": "Z2 C Z2 A2+W", - "theme": "transparent", - ":focus ~ button": { - "opacity": "1" - }, - "onInput": "(ev, el, s) => {\n console.log(el.node.value)\n s.update({\n searchTerm: el.node.value\n })\n }" - } - }, - "NetworkRow": { - "extend": "Grid", - "props": { - "templateColumns": "3fr 3fr 3fr 2fr 2fr", - "gap": "Z2", - "href": "(el, s) => '/network/' + s.protocol", - "align": "center", - "padding": "A1 A2", - "width": "100%", - "poisition": "relative", - "childProps": { - "gap": "Z2", - "flexAlign": "center" - }, - "borderWidth": "0 0 1px 0", - "borderStyle": "solid", - "borderColor": "--theme-document-dark-background", - "userSelect": "none", - "cursor": "pointer", - "transition": "background, defaultBezier, A", - ":hover": { - "background": "deepFir" - }, - ":active": { - "background": "deepFir 1 +5" - }, - "onInit": "(el, s) => {\n const parsed = el.call('parseNetworkRow', s)\n s.parsed = parsed\n }" - }, - "Name": { - "Avatar": { - "src": "{{ protocol }}.png", - "boxSize": "B1" - }, - "Title": { - "tag": "strong", - "flexFlow": "x", - "gap": "X2", - "text": "(el, s) => s.protocol" - } - }, - "Env": { - "childExtends": "NetworkRowLabel", - "childProps": { - "background": "env .25" - }, - "children": "(el, s) => s.parsed?.env" - }, - "NodeTypes": { - "childExtends": "NetworkRowLabel", - "childProps": { - "color": "white", - "theme": null, - "fontSize": "Z2", - "background": "nodeType .25" - }, - "children": "(el, s) => s.parsed?.node_types" - }, - "CloudProvider": { - "childExtends": "NetworkRowLabel", - "childProps": { - "color": "white", - "theme": null, - "fontSize": "Z2", - "background": "cloudProvider .25" - }, - "children": "(el, s) => s.parsed?.cloud_provider" - }, - "Status": { - "childExtends": "NetworkRowLabel", - "childProps": { - "color": "white", - "theme": null, - "fontSize": "Z2", - "background": "SLA .25" - }, - "children": "(el, s) => s.parsed?.status" - }, - "on": { - "init": "(el, s) => {\n const parsed = el.call('parseNetworkRow', s)\n s.parsed = parsed\n }" - } - }, - "Page": { - "props": { - "flexFlow": "y", - "position": "relative", - "heightRange": "100dvh", - "overflow": "hidden" - } - }, - "NavButton": { - "extend": [ - "Link", - "Button" - ], - "props": { - "theme": "button", - "padding": "Z1 A1", - "fontSize": "Z1", - "gap": "Y", - "fontWeight": "300", - "transition": "background, defaultBezier, A", - "text": "300", - "@dark": { - "color": "white", - "background": "transparent", - ":hover": { - "background": "deepFir" - }, - ":active": { - "background": "deepFir 1 +5" - } - } - } - }, - "ModalWindow": { - "extend": "Flex", - "tag": "field", - "props": { - "boxSize": "fit-content", - "align": "stretch flex-start", - "minWidth": "G3", - "position": "relative", - "round": "B1", - "theme": "dialog", - "flow": "y", - "padding": "A2 A2 A1 A2", - "borderStyle": "none", - "@mobileS": { - "fontSize": "Z2", - "minWidth": "G2" - } - }, - "Hgroup": { - "gap": "Y", - "H": { - "tag": "h5", - "margin": "0", - "fontWeight": "700" - }, - "P": { - "margin": "0" - } - }, - "XBtn": { - "position": "absolute", - "right": "Z1+W", - "top": "Z+W", - "round": "100%", - "fontSize": "A1", - "$isSafari": { - "top": "Z2", - "right": "Z2" - }, - "Icon": { - "name": "x" - }, - "onClick": "(ev, el, s) => {\n s.root.update({\n editMode: false\n })\n el.lookup('Modal').removeContent()\n }" - } - }, - "Modal": { - "props": "(el, s) => ({\n position: 'absolute',\n inset: '0',\n background: 'black 0.95 +15',\n backdropFilter: 'blur(3px)',\n flexAlign: 'center center',\n zIndex: 99,\n transition: 'all defaultBezier B',\n pointerEvents: 'auto',\n ...(s.root?.modal ? {\n opacity: 1,\n visibility: 'visible',\n } : {\n opacity: 0,\n visibility: 'hidden',\n }),\n ':empty': {\n opacity: 0,\n visibility: 'hidden',\n pointerEvents: 'none',\n },\n onClick: (ev, el, s) => {\n console.log(1)\n s.root.update({\n modal: false\n })\n\n if (el.key === 'Modal') el.removeContent()\n else el.lookup('Modal').removeContent()\n },\n })", - "content": "(el, s) => ({\n Box: s.root.modal && {\n extend: s.root.modal,\n props: {\n onClick: (ev) => ev.stopPropagation()\n }\n } || {}\n }) || {}" - }, - "MetaSection": { - "state": { - "data": [], - "metrics": [ - { - "title": "Status", - "items": [ - { - "caption": "Live", - "value": "14" - }, - { - "caption": "Onboarding", - "value": "4" - }, - { - "caption": "Degraded", - "value": "0" - } - ] - }, - { - "title": "Fleet cloud distribution", - "items": [ - { - "caption": "GCP", - "value": "4" - }, - { - "caption": "AWS", - "value": "4" - }, - { - "caption": "Rame", - "value": "0" - }, - { - "caption": "OVH", - "value": "14" - } - ] - } - ] - }, - "props": { - "flexFlow": "y", - "borderWidth": "0 0 0 2px", - "borderStyle": "solid", - "borderColor": "--theme-document-dark-background", - "minWidth": "G3", - "padding": "A A2", - "gap": "B2", - "@tabletM": { - "hide": true - } - }, - "H6": { - "fontSize": "Z2", - "text": "Fleet Summary", - "fontWeight": "bold" - }, - "Flex": { - "flow": "y", - "gap": "C1", - "children": "(el, s) => s.metrics", - "childrenAs": "state", - "childProps": { - "flexFlow": "y", - "gap": "A2", - "H6": { - "fontWeight": "700", - "fontSize": "Y1", - "textTransform": "uppercase", - "text": "{{ title }}" - }, - "Grid": { - "templateColumns": "repeat(3, 1fr)", - "gap": "B", - "childExtends": "Hgroup", - "childProps": { - "gap": "X", - "H": { - "tag": "h5", - "text": "{{ value }}" - }, - "P": { - "order": "-1", - "text": "{{ caption }}" - } - }, - "childrenAs": "state", - "children": "(el, s) => s.items" - } - } - } - }, - "NetworkRowLabel": { - "extend": "LabelTag", - "props": { - "color": "white", - "theme": null, - "fontSize": "Z2", - "fontWeight": 300, - "padding": "X1 Y1", - "background": "env .25" - } - }, - "Header": { - "extend": "Flex", - "props": { - "minWidth": "G2", - "icon": "search", - "align": "center flex-start", - "position": "relative", - "theme": "dialog", - "round": "D2", - ":focus-visible": { - "outline": "solid, X, blue .3" - }, - ":focus-within ~ .content": { - "opacity": 0.3, - "pointerEvents": "none" - }, - ":focus-within ~ .search-results": { - "opacity": 0.3 - }, - ":focus-within": { - "@dark": { - "background": "softBlack .95 +4" - }, - "@light": { - "background": "gray1 .95" - } - } - }, - "Link": { - "extends": [ - "Link", - "IconButton" - ], - "paddingInline": "A Z1", - "icon": "chevron left", - "href": "/", - "theme": null, - "hide": "() => window.location.pathname === '/'", - "borderRadius": "0", - "border": null, - "borderWidth": "0 1px 0 0", - "borderStyle": "solid", - "borderColor": "deepFir", - "style": null - }, - "Search": { - "flex": 1, - "theme": "dialog", - "paddingInline": "Z Z1", - "Input": { - "paddingInline": "Z1", - ":focus-visible": null - } - }, - "Avatar": { - "boxSize": "B", - "margin": "Z", - "src": "(el, s) => s.root.user?.picture || `https://avatars.symbo.ls/initials/png?seed=${s.root.user?.name}`" - } - }, - "OldNotes": { - "extend": "Flex", - "Flex": { - "flow": "y", - "children": "(el, s) => s.logs", - "childrenAs": "state", - "padding": "Z2", - "childExtends": "Flex", - "childProps": { - "flow": "y", - "padding": "Z2", - "gap": "Z", - ":not(:last-child)": { - "border": "0 0 1px 0, dashed, gray" - }, - "Notes": { - "order": 0, - "text": "{{ status_notes }}" - }, - "Date": { - "order": 1, - "fontWeight": "300", - "fontSize": "Z1", - "opacity": "0.5", - "margin": "X2 - -", - "text": "{{ created_at }}" - }, - "width": "100%", - ":hover .buttons": { - "opacity": 1 - }, - "Flex": { - "class": "buttons", - "position": "absolute", - "top": "X2", - "right": "X2", - "childExtends": "IconButton", - "Add": { - "icon": "plus", - "onClick": "(ev, el, s) => s.parent.toggle('adding')" - }, - "Edit": { - "icon": "moreHorizontal", - "onClick": "(ev, el, s) => window.alert('Edit soon')" - }, - "Remove": { - "icon": "x", - "onClick": "(ev, el, s) => window.confirm('You sure want to remove this log?')" - }, - "opacity": 0, - "childProps": { - "theme": "transparent" - }, - "gap": "X2" - }, - "position": "relative", - "Title": { - "order": "-1", - "text": "{{ action_items }}" - } - } - }, - "props": { - "flow": "y", - "hide": "(el, s) => !s.logs?.length", - "background": "black .15", - "round": "B", - "margin": "0 -A" - }, - "Addnew": { - "Notes": { - "placeholder": "Status notes", - "value": "(el, s) => s.status_notes" - }, - "childExtends": "Textarea", - "extends": "Flex", - "align": "stretch start", - "childProps": { - "width": "100%", - "maxWidth": "none", - "heightRange": "D1" - }, - "Actions": { - "placeholder": "Action items", - "value": "(el, s) => s.action_items" - }, - "flow": "y", - "gap": "Z2", - "padding": "Z2", - "hide": "(el, s) => !s.adding" - } - }, - "MetaSectionBars": { - "props": { - "flexFlow": "y", - "borderWidth": "0 0 0 2px", - "borderStyle": "solid", - "borderColor": "--theme-document-dark-background", - "minWidth": "G3", - "padding": "A A2", - "gap": "B2", - "@tabletM": { - "hide": true - } - }, - "H6": { - "fontSize": "Z2", - "text": "Fleet Summary", - "fontWeight": "bold" - }, - "Flex": { - "flow": "y", - "gap": "C1", - "children": "(el, s) => s.metrics", - "childrenAs": "state", - "childProps": { - "flexFlow": "y", - "gap": "A", - "H6": { - "fontWeight": "700", - "fontSize": "Y1", - "textTransform": "uppercase", - "text": "{{ title }}" - }, - "Flex": { - "gap": "X", - "style": { - "mixBlendMode": "luminosity" - }, - "childExtends": { - "extend": "TooltipParent", - "TooltipHidden": { - "fontSize": "Z", - "whiteSpace": "nowrap", - "top": "150%", - "padding": "0 A X2", - "shapeDirection": "top", - "text": "{{ caption }} ({{ value }})" - } - }, - "childProps": "(el, s) => ({\n height: 'X2',\n position: 'relative',\n flex: s.value,\n background: el.call('stringToHexColor', s.caption),\n ':after': {\n content: '\"\"',\n position: 'absolute',\n zIndex: '2',\n inset: '-Y2 0',\n }\n })", - "childrenAs": "state", - "children": "(el, s) => s.items" - } - } - } - }, - "Select": { - "tag": "label", - "extend": "Flex", - "props": { - "theme": "transparent", - "position": "relative", - "round": "0", - "align": "center flex-start" - }, - "Selects": { - "tag": "select", - "extends": "Flex", - "fontSize": "A", - "fontWeight": "500", - "boxSize": "100%", - "border": "none", - "padding": "- B - -", - "cursor": "pointer", - "outline": "none", - "pointerEvents": "All", - "appearance": "none", - "height": "100%", - "background": "none", - "color": "title", - "lineHeight": 1, - "zIndex": "2", - "flex": "1", - ":focus-visible": { - "outline": "none" - }, - "childExtends": { - "tag": "option" - }, - "onInit": "(el, s) => {\n el.attr.name = el.call('exec', el.props.name)\n el.attr.value = el.call('exec', el.props.value)\n }", - "childProps": { - "onInit": "(el, s) => {\n el.attr.disabled = el.call('exec', el.props.disabled)\n el.attr.selected = el.call('exec', el.props.selected)\n el.attr.value = el.call('exec', el.props.value)\n }" - }, - "children": "() => [{\n text: 'Please select',\n disabled: 'disabled',\n selected: true\n }, {\n text: 'One',\n value: 'One',\n },\n {\n text: 'Two',\n value: 'Two',\n },\n {\n text: 'Three',\n value: 'Three',\n },\n ]" - }, - "Icon": { - "name": "chevronDown", - "position": "absolute", - "fontSize": "B", - "right": "0" - } - }, - "ModalForm": { - "tag": "form", - "extend": "Grid", - "childExtend": "FieldCaption", - "props": { - "gap": "B", - "justifyItems": "stretch", - "alignItems": "stretch", - "childProps": { - "minWidth": "100%", - "maxWidth": "100%", - "align": "flex-start flex-start", - "> label": { - "width": "100%" - }, - "> *": { - "width": "100%" - } - }, - "children": [ - {} - ], - "childrenAs": "props" - } - }, - "Table": { - "props": { - "extends": "Flex", - "childExtends": [ - "NetworkRow", - "Link" - ], - "width": "100%", - "children": "(el, s) => s.fleet", - "childrenAs": "state", - "flow": "y", - "position": "relative", - "zIndex": 2, - "text": null - } - }, - "Asdasd": { - "state": { - "keyword": "", - "thread": [], - "images": [], - "selectedOption": 0 - }, - "tag": "form", - "props": { - "position": "relative", - "margin": "Z 0 -", - "zIndex": "10", - "transition": "Z defaultBezier margin", - "fontSize": "Z2", - "round": 0, - "theme": "transparent" - }, - "Label": { - "position": "relative", - "zIndex": "1", - "attr": { - "for": "prompt" - }, - "Textarea": { - "id": "prompt", - "display": "flex", - "padding": "Z2 A", - "position": "relative", - "maxWidth": "none", - "maxHeight": "E3", - "overflow": "hidden auto", - "outline": "none", - "width": "100%", - "contentEditable": true, - "borderWidth": "0 0 1px", - "whiteSpace": "pre-wrap", - "borderStyle": "solid", - "borderColor": "line", - "color": "title", - "transition": "Z defaultBezier padding", - "background": "0", - "round": "0", - "placeholder": "\"Ask, Search, Prompt…\"", - "onInput": "(ev, el, s) => {\n let prompt = el.node.innerText.trim()\n if (prompt === '\\n') prompt = ''\n // s.replace({ keyword: prompt, thread: [] }) // Todo: maybe keep the thread\n s.replace({\n keyword: prompt\n }) // Todo: maybe keep the thread\n }", - "onKeydown": "(ev, el, s) => {\n if (ev.key === 'Enter' && !ev.shiftKey) {\n ev.preventDefault()\n }\n // TODO: I think this does not work\n if (ev.key === 'Escape') {\n ev.stopPropagation()\n el.node.blur()\n }\n }" - }, - "SearchOverlay": { - "class": "overlay", - "pointerEvents": "none", - "position": "absolute", - "top": "100%", - "left": "0", - "zIndex": "10", - "right": "0", - "opacity": "0", - "backdropFilter": "blur(10px)", - "transition": "A, defaultBezier", - "transitionProperty": "opacity, visibility", - "theme": "dialog-elevated", - "round": "0 0 --canvas-round --canvas-round", - "boxShadow": "0, 10px, 26px, -4px, document", - "visibility": "hidden", - "childProps": { - "transition": "Z2 defaultBezier", - "transitionProp": "opacity, transform", - ".isVisible": { - "pointerEvents": "auto", - "opacity": "1", - "transform": "translateY(0)" - }, - "!isVisible": { - "transform": "translateY(6px)", - "pointerEvents": "none", - "opacity": "0" - } - }, - "AIThread": { - "pointerEvents": "auto", - "hide": "(el, s) => !s.thread.length", - "isVisible": true - }, - "Flex": { - "flow": "y", - "padding": "X X A", - "gap": "X", - "theme": "transparent", - "hide": "(el, s) => s.images.length || s.thread.length", - "isVisible": "(el, s) =>\n !s.images.length &&\n s.keyword &&\n s.keyword.length < 10 &&\n !s.thread.length", - "childExtends": { - "extend": "Button", - "props": { - "theme": "tertiary" - } - }, - "childProps": { - "flow": "x", - "padding": "Z2", - "margin": "0", - "gap": "A", - "color": "placeholder", - "fontWeight": "200", - "width": "100%", - "align": "center start", - "isActive": "(el, s) => {\n if (s.selectedOption === Number(el.key)) {\n s.update({\n selectedElement: el.node\n }, {\n preventUpdate: true\n })\n return true\n }\n return false\n }", - ".isActive": { - "theme": "secondary-highlight", - "@dark": { - "background": "--color-line-highlight-dark" - }, - "@light": { - "background": "--color-line-highlight-light" - }, - "& .return": { - "opacity": "1" - } - }, - "style": { - "border": "none" - }, - "Icon": { - "widthRange": "1em", - "fontSize": "Z2" - }, - "Icon_enter": { - "class": "return", - "opacity": "0", - "order": 10, - "fontSize": "Z", - "margin": "- X - auto", - "name": "return" - } - }, - "children": [ - { - "icon": "magicstar outline", - "text": "Prompt", - "type": "submit" - }, - { - "icon": "search", - "text": null, - "children": [ - "Search for “", - { - "extends": "Strong", - "margin": "- -Z2", - "fontWeight": "500", - "color": "title", - "text": "{{ keyword }}" - }, - "”" - ], - "onClick": "(ev, el, s) => {\n el.node.blur()\n\n el.call('openNotification', {\n title: 'Sorry',\n message: 'Search is not working yet:)',\n type: 'transparentPattern',\n duration: 500\n })\n\n // el.lookup('Overflow')?.state.update({ search: s.keyword })\n }" - }, - { - "icon": "arrow angle right", - "text": "el => 'Command “...”'", - "onClick": "(ev, el, s) => {\n el.node.blur()\n\n el.call('openNotification', {\n title: 'Uh, oh',\n message: 'Command runner too...',\n type: 'transparentPattern',\n duration: 500\n })\n }" - } - ] - }, - "P": { - "isVisible": "(el, s) =>\n !s.images.length && !s.keyword && !s.thread.length", - "position": "absolute", - "inset": "0", - "flexAlign": "center", - "textAlign": "center", - "margin": "B2 auto", - "color": "placeholder", - "maxWidth": "F1", - "opacity": "1", - "text": "Try asking questions and requests...", - "onRender": "el => window.requestAnimationFrame(() => el.update())" - }, - "Suggestions": { - "position": "absolute", - "inset": "0", - "flexAlign": "center", - "isVisible": "(el, s) =>\n s.images.length ||\n (s.keyword && s.keyword?.length >= 10 && !s.thread.length)", - "P": { - "textAlign": "center", - "color": "placeholder", - "margin": "B2 auto", - "maxWidth": "F1", - "text": "Ask me anything..." - } - } - } - }, - "on": { - "submit": "(ev, el, s) => {\n // javascript prevent default to prevent default behaviour\n ev.preventDefault()\n\n // get value from state (that we kept in keyup)\n const value = s.keyword\n\n // if value is empty, do nothing (break the function)\n if (!value) return\n\n // how state.apply works https://symbols.app/docs/api/state#methods\n // - instead of object, you can use function and make custom logic\n const images = s.images\n\n const makeUserChatData = s => {\n s.thread.push({\n role: 'user', // differenciate if message is by user or agent\n message: value,\n images: [...images]\n })\n s.images = []\n s.keyword = ''\n }\n\n // when 'makeUserChatData' is done, run s.update\n s.apply(makeUserChatData)\n\n // make textarea empty again\n // e.g. reset form\n el.Label.Span.node.innerText = ''\n\n // fake functionity that chat is answering\n // reuse\n const answer = setTimeout(async () => {\n const res = await el.call('giveMeAnswer', value, images)\n console.log(res)\n\n s.apply(s => {\n s.thread.push({\n role: 'agent',\n message: res\n })\n })\n\n clearTimeout(answer)\n }, 1000)\n }" - } - }, - "EditFormCopy": { - "extend": "FormModal", - "props": { - "gap": "C", - "width": "80%", - "maxWidth": "H3", - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n\n const URL = 'https://bigbrother.symbo.ls/api/fleet'\n\n const formData = new FormData(el.node);\n const data = Object.fromEntries(formData)\n // output as an object\n\n console.log(data)\n await window.fetch(URL, {\n body: JSON.stringify(data)\n })\n\n // s.root.update({\n // editMode: false\n // })\n // el.lookup('ModalFade').removeContent()\n }" - }, - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Update Network" - }, - "P": { - "text": "Edit properties for existing Network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Protocol',\n },\n Field: {\n Input: {\n name: 'protocol',\n placeholder: 'E.g. Polygon',\n type: 'text',\n value: '{{ protocol }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Status',\n },\n Field: {\n Input: null,\n SelectField: {\n width: '100%',\n Selects: {\n name: 'status',\n value: '{{ status }}',\n children: [{\n value: 'Onboarding',\n text: 'Onboarding'\n },\n {\n value: 'Maintenance',\n text: 'Maintenance'\n },\n {\n value: 'Off',\n text: 'Off'\n }\n ]\n },\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Environment',\n },\n Field: {\n Input: {\n name: 'env',\n placeholder: 'mainnet',\n value: '{{ env }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Version',\n },\n Field: {\n Input: {\n placeholder: 'v0.1.2',\n value: '{{ version }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Repository',\n },\n Field: {\n Input: {\n placeholder: 'https://github.com',\n type: 'url',\n value: '{{ repo_url }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - }, - "tag": "form" - }, - "SearchDropdown": { - "extend": [ - "Flex", - "Dropdown" - ], - "attr": { - "dropdown": true - }, - "props": { - "flow": "y", - "gap": "A", - "padding": "Z1 A", - "fontSize": "Z2", - "theme": null, - "@dark": { - "backdropFilter": "blur(8px)", - "boxShadow": "black .35, 0px, 15px, 150px, 280px", - "background": "#1D1D1D .95" - }, - "@light": { - "background": "gray1 .95", - "backdropFilter": "blur(8px)", - "boxShadow": "white .68, 0px, 15px, 150px, 280px" - }, - "onMousedown": "(ev, el, s) => {\n ev.preventDefault()\n }", - "onClick": "(ev, el, s) => {\n ev.stopPropagation()\n el.parent.Header.Search.Input.node.focus()\n }" - }, - "CaptionTitle": { - "Text": { - "text": "Search results" - } - }, - "Flex": { - "gap": "Z", - "flow": "y", - "margin": "- -Z2", - "children": "(el, s) => {\n const fuse = el.props.fuse\n\n function searchAll(query) {\n const results = fuse.search(query);\n\n return results.map(r => ({\n key: r.item.key,\n title: r.item.title,\n code: r.item.code,\n type: r.item.type, // component / page / function\n score: r.score,\n }));\n }\n\n const result = searchAll(s.searchTerm)\n console.log(result, s.searchTerm)\n return result\n }", - "childrenAs": "state", - "childExtends": "SearchItem", - "onInit": "async (el, s) => {\n const fuse = await import('fuse.js')\n const Fuse = fuse.default\n\n const fuseOptions = {\n isCaseSensitive: false,\n shouldSort: true,\n findAllMatches: true,\n includeScore: true,\n threshold: 0.4, // Adjust for fuzzy matching sensitivity\n keys: [{\n name: \"title\",\n weight: 2\n }, // Higher weight for titles\n {\n name: \"networkName\",\n weight: 1.5\n },\n {\n name: \"moniker\",\n weight: 1.5\n },\n {\n name: \"owner\",\n weight: 1\n },\n {\n name: \"env\",\n weight: 1\n },\n {\n name: \"cloudProvider\",\n weight: 0.5\n },\n {\n name: \"publicKey\",\n weight: 0.3\n },\n ]\n }\n\n // Function to create searchable items from fleet data\n function flattenFleetData(fleet) {\n const searchItems = []\n\n fleet.forEach(network => {\n // Add network itself as searchable\n searchItems.push({\n id: network.id,\n key: `network-${network.id}`,\n title: network.protocol,\n subtitle: `${network.network_type} • ${network.network_layer}`,\n type: 'network',\n networkName: network.protocol,\n networkType: network.network_type,\n networkLayer: network.network_layer,\n participation: network.participation,\n original: network\n })\n\n // Add validators\n network.validators?.forEach(validator => {\n searchItems.push({\n id: validator.uid,\n key: `validator-${validator.uid}`,\n title: validator.moniker || 'Unknown',\n subtitle: `Validator • ${network.protocol} • ${validator.env}`,\n type: 'validator',\n networkName: network.protocol,\n moniker: validator.moniker,\n owner: validator.owner,\n env: validator.env,\n cloudProvider: validator.cloud_provider,\n publicKey: validator.public_key,\n clientVersion: validator.client_version,\n original: validator,\n networkId: network.id\n })\n })\n\n // Add RPC nodes\n network.rpc_nodes?.forEach(rpc => {\n searchItems.push({\n id: rpc.uid,\n key: `rpc-${rpc.uid}`,\n title: rpc.moniker || 'RPC Node',\n subtitle: `RPC • ${network.protocol} • ${rpc.env}`,\n type: 'rpc',\n networkName: network.protocol,\n moniker: rpc.moniker,\n env: rpc.env,\n clientVersion: rpc.client_version,\n original: rpc,\n networkId: network.id\n })\n })\n })\n\n return searchItems\n }\n\n // Get fleet data from root state\n const fleet = window.fleet || s.parent.fleet || s.root.fleet || []\n const searchItems = flattenFleetData(fleet)\n\n console.log('Indexed items:', searchItems.length)\n el.props.fuse = new Fuse(searchItems, fuseOptions)\n }" - }, - "Filters": { - "hide": "(el, s) => s.searchTerm" - }, - "Results": { - "CaptionTitle": { - "margin": "- - Z", - "Text": { - "text": "(el, s) => s.searchTerm ? 'Search results' : 'Recent searches'" - } - }, - "Flex": { - "gap": "Z", - "flow": "y", - "margin": "- -Z2", - "fontSize": "Z2", - "children": "(el, s) => {\n if (!s.searchTerm && !s.filters?.length) {\n return s.recents\n }\n\n const fuse = el.props.fuse\n\n function searchAll(query) {\n const results = fuse.search(query);\n\n return results.map(r => ({\n id: r.item.id,\n title: r.item.title,\n type: r.item.type, // component / page / function\n score: r.score,\n }));\n }\n\n let term = s.searchTerm || ''\n if (s.filters) term = s.searchTerm + ' ' + s.filters.join(' ')\n\n const result = searchAll(term)\n return result\n }", - "childrenAs": "state", - "childExtends": "SearchItem", - "onInit": "async (el, s) => {\n const fuse = await import('fuse.js')\n const Fuse = fuse.default\n\n const fuseOptions = {\n isCaseSensitive: false,\n shouldSort: true,\n findAllMatches: true,\n includeScore: true,\n threshold: 0.4, // Adjust for fuzzy matching sensitivity\n keys: [{\n name: \"title\",\n weight: 2\n }, // Higher weight for titles\n {\n name: \"networkName\",\n weight: 1.5\n },\n {\n name: \"moniker\",\n weight: 1.5\n },\n {\n name: \"owner\",\n weight: 1\n },\n {\n name: \"env\",\n weight: 1\n },\n {\n name: \"cloudProvider\",\n weight: 0.5\n },\n {\n name: \"publicKey\",\n weight: 0.3\n },\n ]\n }\n\n // Function to create searchable items from fleet data\n function flattenFleetData(fleet) {\n const searchItems = []\n\n fleet.forEach(network => {\n // Add network itself as searchable\n searchItems.push({\n id: network.id,\n key: `network-${network.id}`,\n title: network.protocol,\n subtitle: `${network.network_type} • ${network.network_layer}`,\n type: 'network',\n networkName: network.protocol,\n networkType: network.network_type,\n networkLayer: network.network_layer,\n participation: network.participation,\n original: network\n })\n\n // Add validators\n network.validators?.forEach(validator => {\n searchItems.push({\n id: validator.uid,\n key: `validator-${validator.uid}`,\n title: validator.moniker || 'Unknown',\n subtitle: `Validator • ${network.protocol} • ${validator.env}`,\n type: 'validator',\n networkName: network.protocol,\n moniker: validator.moniker,\n owner: validator.owner,\n env: validator.env,\n cloudProvider: validator.cloud_provider,\n publicKey: validator.public_key,\n clientVersion: validator.client_version,\n original: validator,\n networkId: network.id\n })\n })\n\n // Add RPC nodes\n network.rpc_nodes?.forEach(rpc => {\n searchItems.push({\n id: rpc.uid,\n key: `rpc-${rpc.uid}`,\n title: rpc.moniker || 'RPC Node',\n subtitle: `RPC • ${network.protocol} • ${rpc.env}`,\n type: 'rpc',\n networkName: network.protocol,\n moniker: rpc.moniker,\n env: rpc.env,\n clientVersion: rpc.client_version,\n original: rpc,\n networkId: network.id\n })\n })\n })\n\n return searchItems\n }\n\n // Get fleet data from root state\n const fleet = window.fleet || s.parent.fleet || s.root.fleet || []\n const searchItems = flattenFleetData(fleet)\n\n el.props.fuse = new Fuse(searchItems, fuseOptions)\n }" - } - } - }, - "SearchItem": { - "extend": [ - "Flex" - ], - "props": { - "gap": "Z1", - "align": "start", - "padding": "Z A X", - "theme": "transparent", - ":hover": { - "style": { - "svg": { - "opacity": 1 - } - }, - "theme": "tertiary" - }, - "onClick": "(ev, el, s) => {\n ev.stopPropagation()\n\n if (!s.parent.recents) s.parent.recents = []\n s.parent.recents.unshift(s.parse())\n\n if (s.type === 'network')\n el.call('router', `/network/${s.title}`, el.__ref.root)\n }" - }, - "Avatar": { - "if": "(el, s) => s.type === 'network'", - "margin": "-W2 - -", - "src": "{{ title }}.png", - "boxSize": "B1" - }, - "Hgroup": { - "gap": "W", - "H": { - "text": "{{ title }}", - "margin": "- C - -", - "tag": "h6" - }, - "P": { - "text": "{{ type }}", - "textAlign": "start" - } - }, - "Flex": { - "flow": "y", - "gap": "W", - "Strong": { - "tag": "", - "text": "{{ title }}", - "margin": "- C - -" - }, - "P": { - "extends": "Flex", - "gap": "X1", - "align": "center", - "margin": "0", - "textAlign": "start", - "color": "caption", - "Span_network": { - "if": "(el, s) => s.type !== 'network'", - "extends": "Flex", - "align": "center", - "gap": "Y", - "Avatar": { - "margin": "-W2 - -", - "src": "{{ network }}.png", - "boxSize": "Z2" - }, - "Strong": { - "text": "{{ network }}" - }, - "Span_sep": { - "text": "・" - } - }, - "Span_type": { - "text": "{{ type }}" - }, - "lineHeight": 1.3 - } - } - }, - "Content": { - "extend": "Flex", - "props": { - "class": "content", - "position": "relative", - "width": "100%", - "round": "A A2", - "theme": "dialog", - "flow": "y", - "flex": 1 - } - }, - "Layout": { - "props": { - "position": "relative" - }, - "Header": {}, - "SearchDropdown": { - "width": "100%", - "left": "0", - "extends": [ - "SearchDropdown", - "Dropdown" - ] - }, - "Content": {} - }, - "DropdownSiblingFocus": { - "props": { - "position": "relative", - "tabindex": "0", - "style": { - "&:focus-within": { - "zIndex": 1000, - "& ~ [dropdown]": { - "transform": "translate3d(0,0,0)", - "opacity": 1, - "visibility": "visible" - } - } - } - }, - "Input_trigger": { - "type": "checkbox", - "opacity": "0", - "visibility": "hidden", - "position": "absolute", - "inset": "0", - "onUpdate": "(el) => el.node.blur()" - }, - "style": { - "&:focus-within": { - "zIndex": 1000, - "& ~ [dropdown]": { - "transform": "translate3d(0,0,0)", - "opacity": 1, - "visibility": "visible" - } - } - } - }, - "CaptionTitle": { - "extend": "Flex", - "props": { - "align": "center", - "color": "disabled", - "fontSize": "Y", - "textTransform": "uppercase" - }, - "Text": { - "text": "Active Project" - } - }, - "FiltersSection": { - "props": { - "key": "Environment", - "options": [] - }, - "CaptionTitle": { - "margin": "- - B", - "Text": { - "text": "el => el.parent.parent.props.key" - } - }, - "Flex": { - "flow": "y", - "gap": "Z", - "align": "start", - "childExtends": "NetworkRowLabel", - "childProps": { - "color": "white", - "background": "white .1", - "theme": null, - "fontSize": "Z2", - "order": 12, - "onClick": "(ev, el, s) => {\n if (!s.filters) s.filters = []\n s.apply(() => {\n s.filters.push(el.props.text)\n })\n }", - "isDisabled": "(el, s) => s.filters?.includes(el.props.text)", - ".isDisabled": { - "pointerEvents": "none", - "opacity": 0.35 - } - }, - "children": "el => el.parent.props.options" - }, - "key": "Environment" - }, - "Filters": { - "extend": "Flex", - "props": { - "gap": "D1" - }, - "childExtend": "FiltersSection", - "Env": { - "key": "Environment", - "options": [ - "Mainnet", - "Testnet" - ], - "Flex": { - "childProps": { - "background": "env .25" - } - } - }, - "NodeType": { - "key": "Node Type", - "options": [ - "Validator", - "RPC" - ], - "Flex": { - "childProps": { - "color": "white", - "theme": null, - "fontSize": "Z2", - "background": "nodeType .25" - } - } - }, - "Cloud": { - "key": "Cloud Provider", - "options": [ - "GCP", - "AWS" - ], - "Flex": { - "childProps": { - "color": "white", - "theme": null, - "fontSize": "Z2", - "background": "cloudProvider .25", - "style": { - "justifySelf": "start" - } - }, - "extends": [ - "Grid" - ], - "display": "grid", - "style": { - "gridTemplateColumns": "repeat(2, 1fr)" - } - } - }, - "NodeOp": { - "key": "Node Operator", - "options": [ - "Tornike", - "Peter", - "Yan", - "Patrick", - "Prashant", - "Ankit", - "Raja", - "Reza", - "Tommy" - ], - "Flex": { - "extends": [ - "Grid" - ], - "childProps": { - "style": { - "justifySelf": "start" - } - }, - "style": { - "gridTemplateColumns": "repeat(3, 1fr)" - }, - "display": "grid" - } - } - } - }, - "icons": {}, - "integrations": {}, - "packages": {}, - "pages": { - "/": { - "extend": "Page", - "props": { - "width": "100%", - "margin": "0", - "flexFlow": "x", - "align": "stretch", - "onRender": "async (el, s) => {\n await el.call('auth')\n }" - }, - "Cover": { - "backgroundImage": "https://images.unsplash.com/photo-1751601454754-68dce3c26795?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHx0b3BpYy1mZWVkfDI2fGJvOGpRS1RhRTBZfHxlbnwwfHx8fHw%3D", - "backgroundSize": "cover", - "flex": 2 - }, - "Login": { - "flex": 5, - "flexAlign": "center center", - "Window": { - "theme": "dialog", - "padding": "A A2", - "margin": "auto", - "round": "A2", - "H3": { - "maxWidth": "F", - "lineHeight": "1.2", - "fontWeight": "300", - "children": [ - "Welcome to", - "Tech dashboard" - ] - }, - "P": { - "fontWeight": "300", - "color": "caption", - "margin": "X2 - B2", - "text": "Sign in using Google to continue" - }, - "Link": { - "margin": "0 -W", - "href": "https://api.nodeops.ninja/auth/google", - "Img": { - "src": "google.svg" - } - } - } - } - }, - "/network": { - "extend": [ - "Page", - "Layout" - ], - "props": { - "width": "100%", - "padding": "X", - "flexFlow": "y", - "gap": "X", - "onRender": "(el, s) => {\n window.requestAnimationFrame(async () => {\n let [, _, protocol] = window.location.pathname.split('/')\n if (window.location.pathname === 'srcdoc') {\n protocol = 'Eth2'\n }\n\n const network = s.fleet?.filter(v => v.protocol === protocol)[0]\n if (network) {\n network.protocol = protocol\n }\n\n el.call('setInitialData', {\n network\n })\n\n window.requestAnimationFrame(async () => {\n const network = await el.call('read', `/${protocol}`)\n console.log({\n network\n })\n network.protocol = protocol\n\n el.call('setInitialData', {\n protocol,\n network\n })\n })\n })\n }" - }, - "Header": { - "Link": { - "href": "/" - } - }, - "Content": { - "align": "stretch start", - "state": "network", - "Box": { - "flex": 1, - "position": "relative", - "Flex": { - "padding": "Z", - "gap": "A", - "NavButton": { - "theme": "button", - "icon": "chevron left", - "href": "/", - "fontWeight": "bold", - "text": "All Networks" - }, - "NavButton_add": { - "theme": "button", - "flow": "row-reverse", - "margin": "- - - auto", - "icon": "plus", - "text": "Add node", - "onClick": "(ev, el, s) => {\n s.root.update({\n modal: '/add-node'\n })\n }" - }, - "DropdownParentFocus": { - "Input_trigger": { - "visibility": "hidden" - }, - "IconButton_add": { - "theme": "button", - "icon": "moreVertical", - "Icon": { - "pointerEvents": "none" - } - }, - "Dropdown": { - "left": "auto", - "right": "0", - "padding": "X2 -", - "DropdownList": { - "childExtends": "Button", - "childProps": { - "theme": "button", - "align": "start", - "gap": "Y" - }, - "CopyURL": { - "icon": "copy", - "text": "Copy", - "onClick": "(ev, el, s) => {\n\n }" - }, - "Update": { - "icon": "edit", - "text": "Edit", - "onClick": "(ev, el, s) => {\n s.root.update({\n modal: '/edit-network'\n })\n }" - }, - "Delete": { - "icon": "trash", - "text": "Delete", - "onClick": "(ev, el, s, ctx) => {\n const y = window.confirm('You sure you want to delete this network?')\n if (y) el.call('remove', 'network', s.protocol)\n }" - } - } - } - } - }, - "Hr": { - "opacity": "0.05", - "margin": "0 0 B" - }, - "ValidatorInfo": {} - }, - "Modal": {} - } - }, - "/node": { - "extend": [ - "Page", - "Layout" - ], - "props": { - "width": "100%", - "padding": "X", - "flexFlow": "y", - "gap": "X", - "onRender": "(el, s) => {\n window.requestAnimationFrame(async () => {\n let [, _, protocol, nodeType, uid] = window.location.pathname.split('/')\n if (window.location.pathname === 'srcdoc') {\n protocol = 'Eth2'\n nodeType = 'validator'\n uid = '6a596043-2744-4d36-b10a-c8c2a35f6a7c'\n }\n\n s.protocol = protocol\n if (!s.parent.network) {\n s.parent.network = {\n protocol\n }\n } else {\n s.parent.network.protocol = protocol\n }\n\n const TYPE_MAP = {\n validator: 'validators',\n rpc: 'rpc_nodes'\n }\n const node = s.network?.[TYPE_MAP[nodeType]]?.filter(v => v.uid === uid)[0]\n if (node) {\n node.nodeType = nodeType\n node.protocol = protocol\n }\n\n el.call('setInitialData', {\n node\n })\n\n window.requestAnimationFrame(async () => {\n const node = await el.call('read', `/node/${nodeType}/${uid}`)\n node.nodeType = nodeType\n node.protocol = protocol\n el.call('setInitialData', {\n node\n })\n })\n })\n }" - }, - "Header": { - "Link": { - "href": "/" - } - }, - "Content": { - "state": "node", - "PageHead": { - "state": "../network", - "extends": "Flex", - "gap": "A", - "flexFlow": "x", - "padding": "Z A", - "width": "100%", - "Protocol": { - "extends": [ - "Link", - "Flex" - ], - "align": "center", - "href": "/network/{{ protocol }}", - "gap": "Z", - "Avatar": { - "src": "{{ protocol }}.png", - "boxSize": "A2" - }, - "Strong": { - "lineHeight": 1, - "text": "{{ protocol }}" - } - }, - "NavButton": { - "theme": "button", - "icon": "chevron down", - "flow": "row-reverse", - "href": "/network/{{ protocol }}", - "fontWeight": "bold", - "text": "Show All Nodes" - }, - "NavButton_edit": { - "theme": "button", - "flow": "row-reverse", - "margin": "- - - auto", - "icon": "edit", - "text": "Edit node", - "onClick": "(ev, el, s) => {\n s.root.update({\n modal: '/edit-node'\n })\n }" - }, - "DropdownParentFocus": { - "Input_trigger": { - "visibility": "hidden" - }, - "IconButton_add": { - "theme": "button", - "icon": "moreVertical", - "Icon": { - "pointerEvents": "none" - } - }, - "Dropdown": { - "left": "auto", - "right": "0", - "padding": "X2 -", - "DropdownList": { - "childExtends": "Button", - "childProps": { - "theme": "button", - "align": "start", - "gap": "Y2" - }, - "CopyURL": { - "icon": "copy", - "text": "Copy", - "onClick": "(ev, el, s) => {\n\n }" - }, - "Delete": { - "icon": "trash", - "text": "Remove node", - "onClick": "(ev, el, s, ctx) => {\n const y = window.confirm('You sure you want to delete this node?')\n if (y) el.call('remove', 'node', s.protocol, s.uid)\n }" - } - } - } - } - }, - "Modal": {}, - "Hr": { - "opacity": "0.05", - "margin": "0" - }, - "Flex": { - "align": "stretch start", - "flex": 1, - "Box": { - "flex": 1, - "position": "relative", - "Overflow": { - "overflow": "hidden auto", - "position": "absolute", - "inset": "0", - "ValidatorContent": {} - } - }, - "Logs": { - "minWidth": "G3" - } - } - } - }, - "/add-network": { - "extend": "FormModal", - "props": { - "extends": "FormModal", - "tag": "form", - "gap": "C", - "width": "80%", - "maxWidth": "I", - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n await el.call('add', 'network')\n }", - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Add Network" - }, - "P": { - "text": "Add new network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Protocol',\n },\n Field: {\n Input: {\n name: 'protocol',\n required: true,\n placeholder: 'E.g. Polygon',\n type: 'text',\n value: '{{ protocol }}'\n },\n },\n },\n {\n Caption: {\n text: 'Network Layer',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'network_layer',\n value: '{{ network_layer }}',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'L1',\n value: 'L1',\n },\n {\n text: 'L2',\n value: 'L2',\n },\n {\n text: 'L3',\n value: 'L3',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.network_layer === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Network type',\n },\n Field: {\n Input: {\n name: 'network_type',\n placeholder: 'E.g. Costmos SDK',\n required: true,\n type: 'text',\n value: '{{ network_type }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Network access',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'participation',\n value: '{{ participation }}',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Permissioned',\n value: 'Permissioned',\n },\n {\n text: 'Semi-permissioned',\n value: 'Semi-permissioned'\n },\n {\n text: 'Permissionless',\n value: 'Permissionless',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.participation === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Repository',\n },\n Field: {\n Input: {\n name: 'repo_url',\n placeholder: 'https://github.com',\n type: 'url',\n value: '{{ repo_url }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - } - }, - "tag": "form", - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Add Network" - }, - "P": { - "text": "Add new network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Protocol',\n },\n Field: {\n Input: {\n name: 'protocol',\n required: true,\n placeholder: 'E.g. Polygon',\n type: 'text',\n value: '{{ protocol }}'\n },\n },\n },\n {\n Caption: {\n text: 'Network Layer',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'network_layer',\n value: '{{ network_layer }}',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'L1',\n value: 'L1',\n },\n {\n text: 'L2',\n value: 'L2',\n },\n {\n text: 'L3',\n value: 'L3',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.network_layer === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Network type',\n },\n Field: {\n Input: {\n name: 'network_type',\n placeholder: 'E.g. Costmos SDK',\n required: true,\n type: 'text',\n value: '{{ network_type }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Network access',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'participation',\n value: '{{ participation }}',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Permissioned',\n value: 'Permissioned',\n },\n {\n text: 'Semi-permissioned',\n value: 'Semi-permissioned'\n },\n {\n text: 'Permissionless',\n value: 'Permissionless',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.participation === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Repository',\n },\n Field: {\n Input: {\n name: 'repo_url',\n placeholder: 'https://github.com',\n type: 'url',\n value: '{{ repo_url }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - } - }, - "/add-node": { - "extend": "FormModal", - "tag": "form", - "props": { - "gap": "C", - "width": "80%", - "maxWidth": "I", - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n await el.call('add', 'node')\n }", - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Add node" - }, - "P": { - "text": "Add node in {{ protocol }} network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Moniker',\n },\n Field: {\n Input: {\n name: 'moniker',\n value: '{{ moniker }}',\n placeholder: 'E.g. Bcw-Technologies',\n type: 'text',\n required: true,\n value: '{{ moniker }}'\n },\n },\n },\n {\n Caption: {\n text: 'Node type',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n value: '{{ nodeType }}',\n name: 'nodeType',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Validator',\n value: 'validator',\n },\n {\n text: 'RPC',\n value: 'rpc',\n },\n {\n text: 'Archival-RPC',\n value: 'archival-RPC',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.nodeType === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Cloud Provider',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n value: '{{ cloud_provider }}',\n name: 'cloud_provider',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'AWS',\n value: 'AWS',\n },\n {\n text: 'GCP',\n value: 'GCP',\n },\n {\n text: 'Latitude',\n value: 'Latitude',\n },\n {\n text: 'OVH',\n value: 'OVH',\n },\n {\n text: 'Nirvana',\n value: 'Nirvana',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.cloud_provider === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Priority',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'category',\n value: '{{ category }}',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'P1',\n value: 'P1',\n },\n {\n text: 'P2',\n value: 'P2',\n },\n {\n text: 'P3',\n value: 'P3',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.category === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Do we manage proposals?',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'do_we_manage_proposals',\n value: '{{ do_we_manage_proposals }}',\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Yes',\n value: 'Yes',\n },\n {\n text: 'No',\n value: 'No',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.do_we_manage_proposals === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'SLA',\n },\n Field: {\n Input: {\n name: 'sla',\n value: '{{ sla }}',\n placeholder: '99.99%',\n value: '{{ sla }}'\n },\n },\n },\n {\n Caption: {\n text: 'Projected Cost',\n },\n Field: {\n Input: {\n name: 'projected_cost',\n value: '{{ projected_cost }}',\n placeholder: '$2,911.00',\n value: '{{ projected_cost }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - } - }, - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Add node" - }, - "P": { - "text": "Add node in {{ protocol }} network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Moniker',\n },\n Field: {\n Input: {\n name: 'moniker',\n value: '{{ moniker }}',\n placeholder: 'E.g. Bcw-Technologies',\n type: 'text',\n required: true,\n value: '{{ moniker }}'\n },\n },\n },\n {\n Caption: {\n text: 'Node type',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n value: '{{ nodeType }}',\n name: 'nodeType',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Validator',\n value: 'validator',\n },\n {\n text: 'RPC',\n value: 'rpc',\n },\n {\n text: 'Archival-RPC',\n value: 'archival-RPC',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.nodeType === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Cloud Provider',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n value: '{{ cloud_provider }}',\n name: 'cloud_provider',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'AWS',\n value: 'AWS',\n },\n {\n text: 'GCP',\n value: 'GCP',\n },\n {\n text: 'Latitude',\n value: 'Latitude',\n },\n {\n text: 'OVH',\n value: 'OVH',\n },\n {\n text: 'Nirvana',\n value: 'Nirvana',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.cloud_provider === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Priority',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'category',\n value: '{{ category }}',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'P1',\n value: 'P1',\n },\n {\n text: 'P2',\n value: 'P2',\n },\n {\n text: 'P3',\n value: 'P3',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.category === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Do we manage proposals?',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'do_we_manage_proposals',\n value: '{{ do_we_manage_proposals }}',\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Yes',\n value: 'Yes',\n },\n {\n text: 'No',\n value: 'No',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.do_we_manage_proposals === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'SLA',\n },\n Field: {\n Input: {\n name: 'sla',\n value: '{{ sla }}',\n placeholder: '99.99%',\n value: '{{ sla }}'\n },\n },\n },\n {\n Caption: {\n text: 'Projected Cost',\n },\n Field: {\n Input: {\n name: 'projected_cost',\n value: '{{ projected_cost }}',\n placeholder: '$2,911.00',\n value: '{{ projected_cost }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - } - }, - "/edit-network": { - "extend": "/add-network", - "props": { - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n\n await el.call('edit', 'network', s.root.protocol)\n }", - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Edit Network" - }, - "P": { - "text": "Edit properties for existing {{ protocol }} network" - } - } - }, - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Edit Network" - }, - "P": { - "text": "Edit properties for existing {{ protocol }} network" - } - } - }, - "/edit-node": { - "Hgroup": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Edit Node" - }, - "P": { - "text": "Edit properties for existing node" - } - }, - "Form": {}, - "Hr": { - "margin": "-A1 0 X", - "opacity": "0.05" - }, - "Form_2": { - "extends": "ModalForm", - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Status',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n required: true,\n Selects: {\n name: 'status',\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Off',\n value: 'Off',\n }, {\n text: 'Onboarding',\n value: 'Onboarding',\n },\n {\n text: 'Stable/Mainterance',\n value: 'Stable/Mainterance',\n },\n {\n text: 'Live',\n value: 'Live',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.status === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n }, {\n Caption: {\n text: 'Client version',\n },\n Field: {\n Input: {\n name: 'client_version',\n placeholder: '1.2.3...',\n required: true,\n type: 'text',\n value: '{{ client_version }}'\n },\n },\n }, {\n Caption: {\n text: 'Reward claim',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'reward_claim',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Automatic',\n value: 'Automatic',\n },\n {\n text: 'Manual',\n value: 'Manual',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.reward_claim === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n }, {\n Caption: {\n text: 'Node Operator',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'owner',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Tornike',\n value: 'Tornike',\n }, {\n text: 'Peter',\n value: 'Peter',\n }, {\n text: 'Yan',\n value: 'Yan',\n }, {\n text: 'Patrick',\n value: 'Patrick',\n }, {\n text: 'Prashant',\n value: 'Prashant',\n }, {\n text: 'Ankit',\n value: 'Ankit',\n }, {\n text: 'Raja',\n value: 'Raja',\n }, {\n text: 'Reza',\n value: 'Reza',\n }, {\n text: 'Tommy',\n value: 'Tommy',\n }, ],\n childProps: {\n selected: (el, s) => {\n return s.node_operator === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n }, {\n Caption: {\n text: 'Env',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n\n Selects: {\n name: 'env',\n required: true,\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Testnet',\n value: 'Testnet',\n }, {\n text: 'Mainnet',\n value: 'Mainnet',\n }, {\n text: 'Devnet',\n value: 'Devnet',\n }, ],\n childProps: {\n selected: (el, s) => {\n return s.env === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Public key',\n },\n Field: {\n Input: {\n name: 'public_key',\n placeholder: 'Public key',\n value: '{{ public_key }}'\n },\n },\n }, {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Reward address',\n },\n Field: {\n Input: {\n name: 'reward_address',\n placeholder: 'Reward address',\n value: '{{ reward_address }}'\n },\n },\n }, {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Explorer Link',\n },\n Field: {\n Input: {\n name: 'explorer_link',\n placeholder: 'Explorer link',\n value: '{{ explorer_link }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "extend": "/add-node", - "props": { - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n await el.call('edit', 'node', s.protocol)\n }" - } - }, - "/dashboard": { - "extend": [ - "Page" - ], - "props": { - "width": "100%", - "padding": "X", - "flexFlow": "y", - "gap": "X", - "onRender": "(el, s) => {\n window.requestAnimationFrame(async () => {\n const fleet = await el.call('read')\n el.call('setInitialData', {\n fleet\n })\n })\n }" - }, - "Header": {}, - "Flex": { - "width": "100%", - "align": "stretch start", - "theme": "dialog", - "round": "A", - "flex": 1, - "position": "relative", - "Box": { - "position": "relative", - "flex": 1, - "Overflow": { - "overflow": "hidden auto", - "position": "absolute", - "inset": "0", - "PageHead": { - "flexFlow": "x", - "padding": "A A2 Z", - "width": "100%", - "Title": { - "fontSize": "Z2", - "Strong": { - "fontWeight": "700", - "text": "Network " - }, - "Span": { - "fontWeight": "100" - } - }, - "NavButton": { - "theme": "button", - "flow": "row-reverse", - "margin": "-Y1 -Z2 - auto", - "icon": "plus", - "text": "Add network" - } - }, - "Tr": { - "extends": "Grid", - "zIndex": 3, - "position": "sticky", - "background": "black .001", - "backdropFilter": "blur(10px)", - "top": "0", - "padding": "A A2", - "templateColumns": "3fr 3fr 3fr 2fr 2fr", - "color": "caption", - "childProps": { - "fontSize": "Z1" - }, - "children": [ - "Network", - "Environment", - "Node types", - "Cloud provider", - "" - ] - }, - "Hr": { - "margin": "0", - "opacity": ".035" - }, - "Table": { - "round": "C1" - } - }, - "Modal": {} - }, - "MetaSectionBars": { - "minWidth": "G3" - } - } - }, - "/add-network-copy": { - "extend": "FormModal", - "props": { - "tag": "form", - "gap": "C", - "width": "80%", - "maxWidth": "I", - "onSubmit": "async (ev, el, s) => {\n ev.preventDefault()\n await el.call('give', ...rest)\n await el.give()\n }" - }, - "Section": { - "margin": "0", - "H": { - "tag": "strong", - "text": "Add Network" - }, - "P": { - "text": "Add new network" - } - }, - "Form": { - "columns": "repeat(2, 1fr)", - "@mobileM": { - "columns": "repeat(1, 1fr)" - }, - "children": "() => [{\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Protocol',\n },\n Field: {\n Input: {\n name: 'protocol',\n placeholder: 'E.g. Polygon',\n type: 'text',\n value: '{{ protocol }}'\n },\n },\n },\n {\n Caption: {\n text: 'Network Layer',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'network_layer',\n value: '{{ network_layer }}',\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'L1',\n value: 'L1',\n },\n {\n text: 'L2',\n value: 'L2',\n },\n {\n text: 'L3',\n value: 'L3',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.network_layer === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n Caption: {\n text: 'Network type',\n },\n Field: {\n Input: {\n name: 'network_type',\n placeholder: 'E.g. Costmos SDK',\n type: 'text',\n value: '{{ network_type }}'\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Network access',\n },\n Field: {\n Input: null,\n Select: {\n padding: 'A A2',\n round: 'C1',\n theme: 'field',\n Selects: {\n name: 'participation',\n value: '{{ participation }}',\n children: [{\n text: 'Please select',\n selected: true,\n disabled: 'disabled'\n }, {\n text: 'Permissioned',\n value: 'Permissioned',\n },\n {\n text: 'Semi-permissioned',\n value: 'Semi-permissioned'\n },\n {\n text: 'Permissionless',\n value: 'Permissionless',\n },\n ],\n childProps: {\n selected: (el, s) => {\n return s.participation === el.props.value\n }\n }\n },\n Icon: {\n color: 'caption',\n right: 'Z'\n }\n },\n },\n },\n {\n gridColumn: '1 / span 2',\n Caption: {\n text: 'Repository',\n },\n Field: {\n Input: {\n name: 'repo_url',\n placeholder: 'https://github.com',\n type: 'url',\n value: '{{ repo_url }}'\n },\n },\n },\n ]", - "tag": "div" - }, - "Button": { - "text": "Save", - "theme": "primary", - "type": "submit" - } - } - }, - "state": { - "metrics": [ - { - "title": "Status", - "items": [ - { - "caption": "Live", - "value": "14" - }, - { - "caption": "Onboarding", - "value": "4" - }, - { - "caption": "Degraded", - "value": "0" - } - ] - }, - { - "title": "Fleet cloud distribution", - "items": [ - { - "caption": "GCP", - "value": "4" - }, - { - "caption": "AWS", - "value": "14" - }, - { - "caption": "Latitude", - "value": "1" - }, - { - "caption": "OVH", - "value": "4" - } - ] - }, - { - "title": "Nodes by Layer", - "items": [ - { - "caption": "Live", - "value": "4" - } - ] - }, - { - "title": "Nodes by type", - "items": [ - { - "caption": "Live", - "value": "4" - } - ] - } - ] - }, - "domains": { - "custom": [ - "www.bb.nodeops.ninja", - "bb.nodeops.ninja" - ] - }, - "system": {}, - "define": {}, - "project": {}, - "environmentHosts": [], - "multiEnvironment": { - "active": false, - "wildcard": null, - "totalTls": null - }, - "id": "6874baae0769df64f1a4484d", - "schema": { - "components": { - "Row": { - "key": "Row", - "type": "component", - "settings": { - "gridOptions": { - "x": 11, - "y": 0, - "w": 3, - "h": 1 - } - }, - "uses": [ - "Flex", - "Status", - "Title", - "Validator", - "NetworkRowLabel", - "Rpc", - "Mainnet", - "Testnet", - "Icon", - "Spacer" - ], - "state": "data/0", - "title": "Row", - "description": "", - "category": "", - "tags": [], - "props": { - "demoComponent": { - "state": { - "protocol": "kaia", - "env": "Mainnet", - "role": "Validator", - "status": "Stable/Maintenance", - "repo_url": "https://github.com/kaiachain/kaia/releases/", - "version": "v1.2.3", - "validator_info": [ - { - "moniker": "BCW Technologies", - "public_key": "mantravaloper1r3s4pefpz69fk4rq8sn0zt2wxnv09uffz06nxu" - } - ], - "validator_count": 1 - } - } - }, - "interactivity": [], - "dataTypes": [], - "advanced": true, - "error": null - }, - "Column": { - "key": "Column", - "type": "component", - "settings": { - "gridOptions": { - "x": 15, - "y": 1, - "w": 1, - "h": 1 - } - }, - "uses": [] - }, - "LabelTag": { - "type": "component", - "key": "LabelTag", - "title": "LabelTag", - "description": "", - "category": "", - "tags": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "x": 7, - "y": 2, - "w": 1, - "h": 1 - } - }, - "interactivity": [], - "dataTypes": [], - "touched": true, - "uses": [] - }, - "ValidatorInfo": { - "key": "ValidatorInfo", - "type": "component", - "title": "ValidatorInfo", - "description": "", - "category": "", - "tags": [], - "state": null, - "props": { - "demoComponent": {} - }, - "settings": { - "gridOptions": { - "x": 0, - "y": 0, - "w": 2, - "h": 2 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [ - "Flex", - "Header", - "Avatar", - "H5", - "Add", - "Button", - "Repo", - "IconText", - "Link", - "Grid", - "H", - "P", - "NoContent", - "Both", - "Title", - "Span", - "Validators", - "ValidatorsList", - "RPC", - "Communication" - ], - "advanced": true, - "error": null - }, - "ValidatorContent": { - "key": "ValidatorContent", - "type": "component", - "title": "Validator Content", - "description": "", - "category": "", - "tags": [], - "state": {}, - "props": { - "demoComponent": {} - }, - "settings": { - "gridOptions": { - "x": 3, - "y": 0, - "w": 3, - "h": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [ - "Grid", - "Monkier", - "P", - "H", - "Env", - "Status", - "Text", - "Owner", - "LabelTag", - "Avatar", - "Version", - "RewardClaim", - "Category", - "CloudProvider", - "Reward", - "Flex", - "CopyButton", - "PublicKey", - "Hr", - "OurProposal", - "CurrentSpent", - "ProjectedCost", - "Consensus", - "SLA", - "WarehouseSupport", - "WarehouseUrl", - "Link", - "KnowledgeBase", - "Explorer", - "Uptime", - "Graphs" - ], - "advanced": true, - "error": null, - "code": "export default {/////n Grid: {/////n childExtends: 'PreviewItem',/////n gap: 'C',/////n columns: 'repeat(4, 1fr)',/////n Monkier: {/////n P: {/////n text: 'Moniker',/////n },/////n H: {/////n isNan: (el, s) => !s.moniker,/////n text: (el, s) => s.moniker || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n Env: {/////n P: {/////n text: 'Environment',/////n },/////n H: {/////n isNan: (el, s) => !s.env,/////n text: (el, s) => s.env || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n Status: {/////n P: {/////n text: 'Status',/////n },/////n H: {/////n flexFlow: 'x',/////n gap: 'X2',/////n flexAlign: 'center start',/////n alignSelf: 'start',/////n text: null,/////n Status: {/////n props: (el, s) => ({/////n hide: (el, s) => !s.status,/////n order: '-1',/////n background: el.call('getStatusColor', s.status),/////n round: 'C',/////n boxSize: 'Y2'/////n }),/////n },/////n Text: {/////n isNan: (el, s) => !s.status,/////n text: (el, s) => s.status || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n },/////n Owner: {/////n P: {/////n text: 'Node Operator',/////n },/////n H: {/////n hide: (el, s) => s.owner,/////n text: 'n/a',/////n fontWeight: '300',/////n color: 'caption',/////n },/////n LabelTag: {/////n hide: (el, s) => !s.owner,/////n flexFlow: 'x',/////n gap: 'X2',/////n flexAlign: 'center start',/////n alignSelf: 'start',/////n background: 'white',/////n color: 'black',/////n fontSize: 'Z2',/////n Avatar: {/////n boxSize: 'A',/////n },/////n Text: {/////n text: (el, s) => s.owner,/////n },/////n text: null,/////n },/////n },/////n Version: {/////n P: {/////n text: 'Client version',/////n },/////n H: {/////n text: (el, s) => s.client_version || 'n/a',/////n isNan: (el, s) => !s.owner,/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n RewardClaim: {/////n P: {/////n text: 'Reward Claim',/////n },/////n H: {/////n isNan: (el, s) => !s.reward_claim,/////n text: (el, s) => s.reward_claim || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n Category: {/////n P: {/////n text: 'Category',/////n },/////n H: {/////n isNan: (el, s) => !s.category,/////n text: (el, s) => s.category || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n CloudProvider: {/////n P: {/////n text: 'Cloud Provider',/////n },/////n H: {/////n isNan: (el, s) => !s.cloud_provider,/////n text: (el, s) => s.cloud_provider || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n Reward_address: {/////n gridColumn: 'span 2',/////n P: {/////n text: 'Reward Address',/////n },/////n H: {/////n extends: 'Flex',/////n flexAlign: 'center start',/////n Text: {/////n overflow: 'hidden',/////n maxWidth: '95%',/////n textOverflow: 'ellipsis',/////n text: '{{reward_address}}',/////n },/////n Text_no: {/////n color: 'caption',/////n hide: (el, s) => s.reward_address,/////n fontWeight: '300',/////n text: 'n/a',/////n },/////n CopyButton: {/////n hide: (el, s) => !s.reward_address,/////n value: (el, s) => s.reward_address,/////n },/////n text: null,/////n },/////n },/////n PublicKey_address: {/////n gridColumn: 'span 2',/////n P: {/////n text: 'Public key',/////n },/////n H: {/////n extends: 'Flex',/////n flexAlign: 'center start',/////n Text: {/////n overflow: 'hidden',/////n maxWidth: '95%',/////n textOverflow: 'ellipsis',/////n text: '{{public_key}}',/////n },/////n Text_no: {/////n color: 'caption',/////n hide: (el, s) => s.public_key,/////n fontWeight: '300',/////n text: 'n/a',/////n },/////n CopyButton: {/////n hide: (el, s) => !s.public_key,/////n value: (el, s) => s.public_key,/////n },/////n text: null,/////n },/////n },/////n Hr: {/////n ignoreChildExtend: true,/////n margin: 'A 0',/////n opacity: '.05',/////n gridColumn: 'span 4',/////n },/////n OurProposal: {/////n P: {/////n text: 'Proposals managed by us',/////n },/////n H: {/////n isNan: (el, s) => !s.do_we_manage_proposals,/////n text: (el, s) => s.do_we_manage_proposals || 'n/a',/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n CurrentSpent: {/////n P: {/////n text: 'Current Spent',/////n },/////n H: {/////n text: (el, s) => s.projected_cost ? /////tilde/////dlrsgn/////dlrsgn{ s.current_spend?.toFixed(2) }/////tilde : 'n/a',/////n },/////n },/////n ProjectedCost: {/////n P: {/////n text: 'Projected Cost',/////n },/////n H: {/////n text: (el, s) => s.projected_cost ? /////tilde/////dlrsgn/////dlrsgn{ s.projected_cost?.toFixed(2) }/////tilde : 'n/a',/////n isNan: (el, s) => !s.owner,/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n Consensus: {/////n P: {/////n text: 'Consensus',/////n },/////n H: {/////n text: (el, s) => s.consensus || 'n/a',/////n isNan: (el, s) => !s.consensus,/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n SLA: {/////n P: {/////n text: 'SLA',/////n },/////n H: {/////n text: (el, s) => s.sla || 'n/a',/////n isNan: (el, s) => !s.sla,/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n WarehouseSupport: {/////n if: (el, s) => s.nodeType === 'rpc',/////n P: {/////n text: 'Data warehouse support',/////n },/////n H: {/////n text: (el, s) => s.data_warehouse_support ? 'Yes' : 'No',/////n },/////n },/////n WarehouseUrl: {/////n if: (el, s) => s.nodeType === 'rpc',/////n gridColumn: 'span 2',/////n P: {/////n text: 'Data warehouse URL',/////n },/////n H: null,/////n Link: {/////n isNan: (el, s) => !s.data_warehouse_url,/////n fontSize: 'B',/////n target: '_blank',/////n href: (el, s) => s.data_warehouse_url,/////n text: (el, s) => s.data_warehouse_url ? s.data_warehouse_url.slice(0, 56) + '...' : 'n/a',/////n fontWeight: '400',/////n '[href]:hover': {/////n textDecoration: 'underline',/////n },/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n KnowledgeBase: {/////n gridColumn: 'span 2',/////n P: {/////n text: 'Knowledge base',/////n },/////n H: null,/////n Link: {/////n isNan: (el, s) => !s.knowledge_base,/////n fontSize: 'B',/////n target: '_blank',/////n href: (el, s) => s.knowledge_base,/////n text: (el, s) => s.knowledge_base ? s.knowledge_base.slice(0, 56) + '...' : 'n/a',/////n fontWeight: '400',/////n '[href]:hover': {/////n textDecoration: 'underline',/////n },/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n Explorer: {/////n gridColumn: 'span 2',/////n P: {/////n text: 'Explorer link',/////n },/////n H: null,/////n Link: {/////n isNan: (el, s) => !s.explorer_link,/////n fontSize: 'B',/////n target: '_blank',/////n href: (el, s) => s.explorer_link,/////n text: (el, s) => s.explorer_link ? s.explorer_link.slice(0, 56) + '...' : 'n/a',/////n fontWeight: '400',/////n '[href]:hover': {/////n textDecoration: 'underline',/////n },/////n '.isNan': {/////n fontWeight: '300',/////n color: 'caption',/////n },/////n },/////n },/////n },/////n Hr: {/////n ignoreChildExtend: true,/////n margin: '-B2 0',/////n opacity: '.05',/////n gridColumn: 'span 4',/////n },/////n Uptime: {},/////n Graphs: {},/////n props: {/////n width: '100%',/////n flow: 'y',/////n gap: 'E',/////n padding: 'A2',/////n onClick: (ev, el, s) => {/////n ev.stopPropagation()/////n ev.preventDefault()/////n },/////n },/////n extend: 'Flex',/////n}" - }, - "PreviewItem": { - "key": "PreviewItem", - "type": "component", - "settings": { - "gridOptions": { - "x": 17, - "y": 1, - "w": 1, - "h": 1 - } - }, - "uses": [ - "P", - "H" - ], - "error": null - }, - "ValidatorRow": { - "key": "ValidatorRow", - "type": "component", - "settings": { - "gridOptions": { - "x": 11, - "y": 1, - "w": 3, - "h": 1 - } - }, - "uses": [ - "Link", - "Status", - "Strong", - "Version", - "PublicKey", - "Flex", - "Text", - "CopyButton", - "Env", - "NetworkRowLabel" - ], - "error": null - }, - "ValidatorFooter": { - "key": "ValidatorFooter", - "type": "component", - "settings": { - "gridOptions": { - "x": 11, - "y": 2, - "w": 3, - "h": 1 - } - }, - "uses": [ - "Flex", - "Edit" - ] - }, - "FormModal": { - "key": "FormModal", - "title": "FormModal", - "description": "", - "category": "comp", - "extends": [ - "Modal", - "Modal" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "x": 19, - "y": 2, - "w": 2, - "h": 1 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [ - "ModalWindow", - "Hgroup", - "H", - "P", - "XBtn", - "Form", - "ModalForm" - ], - "type": "component", - "error": null, - "code": "export default {/////n extend: 'ModalWindow',/////n props: {/////n maxHeight: '95dvh',/////n overflow: 'hidden auto',/////n widthRange: 'H1',/////n '@mobileS': {/////n fontSize: 'Z2',/////n },/////n '@mobileXS': {/////n fontSize: 'Z1',/////n },/////n },/////n Hgroup: {/////n margin: '- W B+V2 W',/////n H: {},/////n P: {},/////n },/////n XBtn: {},/////n Form: {/////n extends: 'ModalForm',/////n },/////n}" - }, - "EditForm": { - "key": "EditForm", - "title": "ContactSection", - "description": "", - "category": "block", - "extends": [ - "Flex", - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "x": 0, - "y": 7, - "w": 2, - "h": 2 - } - }, - "interactivity": [], - "dataTypes": [], - "variants": [ - { - "key": "ContactSection.Two", - "title": "ContactSection.Two", - "description": "", - "category": "block", - "code": "", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 5 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "align": "flex-start flex-start", - "theme": "dialog", - "boxSize": "content-box", - "padding": "C B2 B B2", - "round": "A", - "alignSelf": "flex-start", - "@mobileL": { - "fontSize": "Z2", - "minWidth": "100%" - } - }, - "SectionHgroup": { - "props": { - "align": "flex-start", - "gap": "A" - }, - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "Form": { - "0": { - "props": { - "gridColumn": "1", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "1" - } - }, - "Caption": { - "text": "First name" - }, - "Field": { - "Input": { - "placeholder": "First name" - } - } - }, - "1": { - "props": { - "gridColumn": "2", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "2" - } - }, - "Caption": { - "text": "Last name" - }, - "Field": { - "Input": { - "placeholder": "Last name" - } - } - }, - "2": { - "props": { - "gridColumn": "1", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "3" - } - }, - "Caption": { - "text": "Email" - }, - "Field": { - "Input": { - "placeholder": "Email" - } - } - }, - "3": { - "props": { - "gridColumn": "2", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "4" - } - }, - "Caption": { - "text": "Phone number" - }, - "Field": { - "Input": { - "type": "number", - "placeholder": "000000000", - "::-webkit-inner-spin-button": { - "appearance": "none" - } - } - } - }, - "4": { - "props": { - "gridColumn": "1 / span 2" - }, - "Caption": { - "text": "Choose a topic" - }, - "SelectField": { - "minWidth": "100%", - "margin": "- -Y1", - "@mobileL": { - "minWidth": "100%" - } - }, - "Field": null - }, - "5": { - "props": { - "gap": "A", - "gridColumn": "1 / span 2" - }, - "Caption": { - "text": "Which best describes you?" - }, - "Field": null, - "RadioAnsList": {} - }, - "6": { - "props": { - "gridColumn": "1 / span 2" - }, - "Caption": { - "text": "Message" - }, - "TextAreaField": { - "minWidth": "100%", - "margin": "- -Y1", - "minHeight": "E" - }, - "Field": null - }, - "extend": "Grid", - "props": { - "gap": "B2", - "width": "100%", - "margin": "C1 - A2 -", - "columns": "repeat(2, 1fr)" - }, - "childExtend": { - "extend": "FieldCaption", - "props": { - "minWidth": "100%" - }, - "Caption": {}, - "Field": { - "theme": "field", - "minWidth": "100%" - } - } - }, - "PBtnCheck": { - "props": {}, - "Check": {}, - "PBtn": { - "P": { - "text": "I accept the" - }, - "Btn": { - "text": "terms and conditions" - } - } - }, - "Btn": { - "props": { - "padding": "A C1", - "margin": "C1 - - -", - "@mobileS": { - "minWidth": "100%" - } - }, - "text": "Submit" - } - } - }, - { - "key": "ContactSection.Three", - "title": "ContactSection.Three", - "description": "", - "category": "block", - "code": "", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "align": "center flex-start", - "gap": "D2", - "padding": "B1 B1 B1 B2", - "theme": "dialog", - "round": "A", - "alignSelf": "flex-start", - "@tabletM": { - "flow": "y" - } - }, - "ContactSection": { - "theme": "transparent", - "padding": "0", - "align": "flex-start", - "flex": "1", - "@mobileL": { - "minWidth": "100%" - }, - "SectionHgroup": { - "align": "flex-start", - "textAlign": "left" - }, - "Form": {}, - "PBtnCheck": {}, - "Btn": { - "padding": "A C1", - "@mobileM": { - "alignSelf": "center", - "minWidth": "100%" - } - } - }, - "BannerImg": { - "flex": "1", - "boxSize": "I H1", - "@tabletS": { - "boxSize": "I 100%" - } - } - } - }, - { - "key": "ContactSection.Four", - "title": "ContactSection.Four", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 5 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "padding": "B1", - "theme": "dialog", - "textAlign": "left", - "round": "A", - "align": "center flex-start", - "gap": "A2", - "flex": "1", - "@tabletM": { - "flow": "y", - "reverse": true - } - }, - "BannerImg": { - "props": { - "boxSize": "I2+D1 H2", - "gridColumn": "1", - "gridRow": "span 3", - "flex": "1", - "@tabletM": { - "boxSize": "I 100%" - } - } - }, - "ContactSection.Two": { - "theme": "transparent", - "flex": "1", - "@tabletS": { - "minWidth": "100%" - }, - "@mobileM": { - "padding": "0" - }, - "SectionHgroup": { - "align": "flex-start", - "minWidth": "100%", - "textAlign": "left" - }, - "Form": { - "@mobileL": { - "columns": "repeat(1, 1fr)" - } - }, - "PBtnCheck": { - "alignSelf": "flex-start" - }, - "Btn": { - "alignSelf": "flex-start", - "padding": "A C2" - } - } - } - }, - { - "key": "ContactSection.Five", - "title": "ContactSection.Five", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "C C1 C C", - "round": "A", - "gap": "F", - "@screenS": { - "minWidth": "100%", - "gap": "0", - "align": "flex-start space-between" - }, - "@tabletS": { - "flow": "y", - "gap": "D", - "padding": "C" - }, - "@mobileM": { - "padding": "B2" - } - }, - "Article": { - "extend": "Flex", - "props": { - "flow": "y", - "gap": "D" - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You", - "@tabletM": { - "maxWidth": "G2" - } - } - }, - "ContactInfos": {} - }, - "ContactSection": { - "props": { - "padding": "0", - "theme": "transparent" - }, - "SectionHgroup": null, - "Form": { - "props": { - "margin": "- - - -", - "childProps": { - "Caption": {}, - "Field": { - "minWidth": "G3", - "@mobileL": { - "minWidth": "100%" - } - }, - "TextAreaField": { - "minWidth": "G3", - "@mobileL": { - "minWidth": "100%" - } - } - } - } - }, - "PBtnCheck": { - "props": { - "alignSelf": "flex-start", - "padding": "Y - - -" - } - }, - "Btn": { - "props": { - "alignSelf": "flex-start", - "padding": "A C" - } - } - } - } - }, - { - "key": "ContactSection.Seven", - "title": "ContactSection.Seven", - "description": "", - "category": "block", - "code": "", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "C B2 B2 B2", - "round": "A", - "flow": "y", - "gap": "D", - "@mobileM": { - "padding": "B2 B1 B1 B1" - }, - "@mobileS": { - "padding": "B1 B B B" - } - }, - "Flex": { - "props": { - "align": "flex-end space-between", - "padding": "- Y - W", - "gap": "C", - "@tabletM": { - "flow": "y", - "align": "flex-start", - "gap": "D" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You", - "maxWidth": "G1" - } - }, - "ContactInfos.Two": { - "gap": "C2" - } - }, - "BannerImg": { - "boxSize": "H2 J", - "flex": "1", - "@tabletM": { - "boxSize": "H2 100%" - } - } - } - }, - { - "key": "ContactSection.Eight", - "title": "ContactSection.Eight", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "C B1 B1 B1", - "round": "A", - "flow": "y", - "gap": "D" - }, - "Flex": { - "props": { - "align": "flex-end space-between", - "padding": "- Y - W", - "gap": "C", - "@tabletS": { - "flow": "y", - "align": "flex-start", - "gap": "C2" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You", - "maxWidth": "G1" - } - }, - "ContactInfos": {} - }, - "BannerImg": { - "boxSize": "H2 J", - "flex": "1", - "@tabletM": { - "boxSize": "H2 100%" - } - } - } - }, - { - "key": "ContactSection.Nine", - "title": "ContactSection.Nine", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "B2", - "round": "A", - "gap": "E", - "@tabletM": { - "flow": "y", - "gap": "D" - }, - "@mobileM": { - "padding": "B1" - } - }, - "Article": { - "extend": "Flex", - "props": { - "flow": "y", - "gap": "D2", - "margin": "B - - -", - "@tabletM": { - "margin": "0", - "gap": "D" - } - }, - "SectionHgroup": { - "gap": "A1", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "ContactInfos": {} - }, - "BannerImg": { - "@tabletM": { - "flex": "1", - "width": "100%" - } - } - } - }, - { - "key": "ContactSection.Ten", - "title": "ContactSection.Ten", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "B2", - "round": "A", - "gap": "E", - "@tabletM": { - "flow": "y", - "minWidth": "100%" - }, - "@mobileM": { - "gap": "D", - "padding": "B2 B1 B1 B1" - } - }, - "Article": { - "extend": "Flex", - "props": { - "flow": "y", - "gap": "D2", - "margin": "B - - -", - "@mobileM": { - "gap": "D", - "margin": "0" - } - }, - "SectionHgroup": { - "gap": "A1", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "ContactInfos.Two": { - "flow": "y", - "gap": "C", - "@tabletM": { - "flow": "x" - } - } - }, - "BannerImg": { - "flex": "1", - "@tabletM": { - "width": "100%" - } - } - } - }, - { - "key": "ContactSection.Eleven", - "title": "ContactSection.Eleven", - "description": "", - "category": "block", - "code": "", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "gap": "D1", - "padding": "B2 B2 C B2", - "theme": "dialog", - "round": "A" - }, - "SectionHgroup": { - "gap": "A1", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "ContacInfos.Three": {} - } - }, - { - "key": "ContactSection.Thirteen", - "title": "ContactSection.Thirteen", - "description": "", - "category": "block", - "code": "", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "theme": "dialog", - "padding": "B2 C", - "gap": "C2", - "round": "A", - "@mobileS": { - "fontSize": "Z2" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "Flex": { - "0": {}, - "1": {}, - "props": { - "gap": "D", - "@tabletS": { - "flow": "y", - "minWidth": "100%", - "align": "center flex-start", - "gap": "B" - }, - "childProps": { - "@tabletS": { - "minWidth": "100%" - }, - "Map": { - "theme": "field" - }, - "HgroupLink": { - "@tabletS": { - "alignSelf": "flex-start" - } - } - } - }, - "childExtend": "LocationInfo" - } - } - }, - { - "key": "ContactSection.Fourteen", - "title": "ContactSection.Fourteen", - "description": "", - "category": "block", - "code": "", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "theme": "dialog", - "padding": "B2 B2 B2 C1", - "round": "A", - "gap": "C1", - "@screenS": { - "minWidth": "100%" - }, - "@tabletM": { - "padding": "B2 C B C" - }, - "@mobileM": { - "padding": "B2 B1 A B1" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "Flex": { - "props": { - "gap": "F", - "@tabletM": { - "flow": "y", - "gap": "C2" - } - }, - "Aside": { - "0": {}, - "1": {}, - "2": {}, - "3": {}, - "extend": "Flex", - "props": { - "flow": "y", - "gap": "C2", - "padding": "B - - -", - "@tabletM": { - "gap": "C", - "padding": "0 - - -" - } - }, - "childExtend": { - "extend": "HgroupLink", - "props": {} - } - }, - "Map": { - "extend": "Flex", - "props": { - "theme": "field", - "minWidth": "I", - "minHeight": "H2", - "align": "center center", - "round": "A", - "@screenS": { - "flex": "1" - }, - "@tabletM": { - "minWidth": "100%", - "minHeight": "G2", - "margin": "- -A" - }, - "@mobileM": { - "minHeight": "G" - } - }, - "Icon": { - "props": { - "name": "send", - "fontSize": "E2" - } - } - } - } - } - }, - { - "key": "ContactSection.Two", - "title": "ContactSection.Two", - "description": "", - "category": "block", - "code": "", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 5 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "align": "flex-start flex-start", - "theme": "dialog", - "boxSize": "content-box", - "padding": "C B2 B B2", - "round": "A", - "alignSelf": "flex-start", - "@mobileL": { - "fontSize": "Z2", - "minWidth": "100%" - } - }, - "SectionHgroup": { - "props": { - "align": "flex-start", - "gap": "A" - }, - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "Form": { - "0": { - "props": { - "gridColumn": "1", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "1" - } - }, - "Caption": { - "text": "First name" - }, - "Field": { - "Input": { - "placeholder": "First name" - } - } - }, - "1": { - "props": { - "gridColumn": "2", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "2" - } - }, - "Caption": { - "text": "Last name" - }, - "Field": { - "Input": { - "placeholder": "Last name" - } - } - }, - "2": { - "props": { - "gridColumn": "1", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "3" - } - }, - "Caption": { - "text": "Email" - }, - "Field": { - "Input": { - "placeholder": "Email" - } - } - }, - "3": { - "props": { - "gridColumn": "2", - "@mobileL": { - "gridColumn": "1 / span 2", - "gridRow": "4" - } - }, - "Caption": { - "text": "Phone number" - }, - "Field": { - "Input": { - "type": "number", - "placeholder": "000000000", - "::-webkit-inner-spin-button": { - "appearance": "none" - } - } - } - }, - "4": { - "props": { - "gridColumn": "1 / span 2" - }, - "Caption": { - "text": "Choose a topic" - }, - "SelectField": { - "minWidth": "100%", - "margin": "- -Y1", - "@mobileL": { - "minWidth": "100%" - } - }, - "Field": null - }, - "5": { - "props": { - "gap": "A", - "gridColumn": "1 / span 2" - }, - "Caption": { - "text": "Which best describes you?" - }, - "Field": null, - "RadioAnsList": {} - }, - "6": { - "props": { - "gridColumn": "1 / span 2" - }, - "Caption": { - "text": "Message" - }, - "TextAreaField": { - "minWidth": "100%", - "margin": "- -Y1", - "minHeight": "E" - }, - "Field": null - }, - "extend": "Grid", - "props": { - "gap": "B2", - "width": "100%", - "margin": "C1 - A2 -", - "columns": "repeat(2, 1fr)" - }, - "childExtend": { - "extend": "FieldCaption", - "props": { - "minWidth": "100%" - }, - "Caption": {}, - "Field": { - "theme": "field", - "minWidth": "100%" - } - } - }, - "PBtnCheck": { - "props": {}, - "Check": {}, - "PBtn": { - "P": { - "text": "I accept the" - }, - "Btn": { - "text": "terms and conditions" - } - } - }, - "Btn": { - "props": { - "padding": "A C1", - "margin": "C1 - - -", - "@mobileS": { - "minWidth": "100%" - } - }, - "text": "Submit" - } - } - }, - { - "key": "ContactSection.Three", - "title": "ContactSection.Three", - "description": "", - "category": "block", - "code": "", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "align": "center flex-start", - "gap": "D2", - "padding": "B1 B1 B1 B2", - "theme": "dialog", - "round": "A", - "alignSelf": "flex-start", - "@tabletM": { - "flow": "y" - } - }, - "ContactSection": { - "theme": "transparent", - "padding": "0", - "align": "flex-start", - "flex": "1", - "@mobileL": { - "minWidth": "100%" - }, - "SectionHgroup": { - "align": "flex-start", - "textAlign": "left" - }, - "Form": {}, - "PBtnCheck": {}, - "Btn": { - "padding": "A C1", - "@mobileM": { - "alignSelf": "center", - "minWidth": "100%" - } - } - }, - "BannerImg": { - "flex": "1", - "boxSize": "I H1", - "@tabletS": { - "boxSize": "I 100%" - } - } - } - }, - { - "key": "ContactSection.Four", - "title": "ContactSection.Four", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 5 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "padding": "B1", - "theme": "dialog", - "textAlign": "left", - "round": "A", - "align": "center flex-start", - "gap": "A2", - "flex": "1", - "@tabletM": { - "flow": "y", - "reverse": true - } - }, - "BannerImg": { - "props": { - "boxSize": "I2+D1 H2", - "gridColumn": "1", - "gridRow": "span 3", - "flex": "1", - "@tabletM": { - "boxSize": "I 100%" - } - } - }, - "ContactSection.Two": { - "theme": "transparent", - "flex": "1", - "@tabletS": { - "minWidth": "100%" - }, - "@mobileM": { - "padding": "0" - }, - "SectionHgroup": { - "align": "flex-start", - "minWidth": "100%", - "textAlign": "left" - }, - "Form": { - "@mobileL": { - "columns": "repeat(1, 1fr)" - } - }, - "PBtnCheck": { - "alignSelf": "flex-start" - }, - "Btn": { - "alignSelf": "flex-start", - "padding": "A C2" - } - } - } - }, - { - "key": "ContactSection.Five", - "title": "ContactSection.Five", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "C C1 C C", - "round": "A", - "gap": "F", - "@screenS": { - "minWidth": "100%", - "gap": "0", - "align": "flex-start space-between" - }, - "@tabletS": { - "flow": "y", - "gap": "D", - "padding": "C" - }, - "@mobileM": { - "padding": "B2" - } - }, - "Article": { - "extend": "Flex", - "props": { - "flow": "y", - "gap": "D" - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You", - "@tabletM": { - "maxWidth": "G2" - } - } - }, - "ContactInfos": {} - }, - "ContactSection": { - "props": { - "padding": "0", - "theme": "transparent" - }, - "SectionHgroup": null, - "Form": { - "props": { - "margin": "- - - -", - "childProps": { - "Caption": {}, - "Field": { - "minWidth": "G3", - "@mobileL": { - "minWidth": "100%" - } - }, - "TextAreaField": { - "minWidth": "G3", - "@mobileL": { - "minWidth": "100%" - } - } - } - } - }, - "PBtnCheck": { - "props": { - "alignSelf": "flex-start", - "padding": "Y - - -" - } - }, - "Btn": { - "props": { - "alignSelf": "flex-start", - "padding": "A C" - } - } - } - } - }, - { - "key": "ContactSection.Seven", - "title": "ContactSection.Seven", - "description": "", - "category": "block", - "code": "", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "C B2 B2 B2", - "round": "A", - "flow": "y", - "gap": "D", - "@mobileM": { - "padding": "B2 B1 B1 B1" - }, - "@mobileS": { - "padding": "B1 B B B" - } - }, - "Flex": { - "props": { - "align": "flex-end space-between", - "padding": "- Y - W", - "gap": "C", - "@tabletM": { - "flow": "y", - "align": "flex-start", - "gap": "D" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You", - "maxWidth": "G1" - } - }, - "ContactInfos.Two": { - "gap": "C2" - } - }, - "BannerImg": { - "boxSize": "H2 J", - "flex": "1", - "@tabletM": { - "boxSize": "H2 100%" - } - } - } - }, - { - "key": "ContactSection.Eight", - "title": "ContactSection.Eight", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "C B1 B1 B1", - "round": "A", - "flow": "y", - "gap": "D" - }, - "Flex": { - "props": { - "align": "flex-end space-between", - "padding": "- Y - W", - "gap": "C", - "@tabletS": { - "flow": "y", - "align": "flex-start", - "gap": "C2" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You", - "maxWidth": "G1" - } - }, - "ContactInfos": {} - }, - "BannerImg": { - "boxSize": "H2 J", - "flex": "1", - "@tabletM": { - "boxSize": "H2 100%" - } - } - } - }, - { - "key": "ContactSection.Nine", - "title": "ContactSection.Nine", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "B2", - "round": "A", - "gap": "E", - "@tabletM": { - "flow": "y", - "gap": "D" - }, - "@mobileM": { - "padding": "B1" - } - }, - "Article": { - "extend": "Flex", - "props": { - "flow": "y", - "gap": "D2", - "margin": "B - - -", - "@tabletM": { - "margin": "0", - "gap": "D" - } - }, - "SectionHgroup": { - "gap": "A1", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "ContactInfos": {} - }, - "BannerImg": { - "@tabletM": { - "flex": "1", - "width": "100%" - } - } - } - }, - { - "key": "ContactSection.Ten", - "title": "ContactSection.Ten", - "description": "", - "category": "block", - "code": "", - "extends": [ - "ContactSection" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "theme": "dialog", - "padding": "B2", - "round": "A", - "gap": "E", - "@tabletM": { - "flow": "y", - "minWidth": "100%" - }, - "@mobileM": { - "gap": "D", - "padding": "B2 B1 B1 B1" - } - }, - "Article": { - "extend": "Flex", - "props": { - "flow": "y", - "gap": "D2", - "margin": "B - - -", - "@mobileM": { - "gap": "D", - "margin": "0" - } - }, - "SectionHgroup": { - "gap": "A1", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "ContactInfos.Two": { - "flow": "y", - "gap": "C", - "@tabletM": { - "flow": "x" - } - } - }, - "BannerImg": { - "flex": "1", - "@tabletM": { - "width": "100%" - } - } - } - }, - { - "key": "ContactSection.Eleven", - "title": "ContactSection.Eleven", - "description": "", - "category": "block", - "code": "", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 3 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "gap": "D1", - "padding": "B2 B2 C B2", - "theme": "dialog", - "round": "A" - }, - "SectionHgroup": { - "gap": "A1", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "ContacInfos.Three": {} - } - }, - { - "key": "ContactSection.Thirteen", - "title": "ContactSection.Thirteen", - "description": "", - "category": "block", - "code": "", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "theme": "dialog", - "padding": "B2 C", - "gap": "C2", - "round": "A", - "@mobileS": { - "fontSize": "Z2" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "Flex": { - "0": {}, - "1": {}, - "props": { - "gap": "D", - "@tabletS": { - "flow": "y", - "minWidth": "100%", - "align": "center flex-start", - "gap": "B" - }, - "childProps": { - "@tabletS": { - "minWidth": "100%" - }, - "Map": { - "theme": "field" - }, - "HgroupLink": { - "@tabletS": { - "alignSelf": "flex-start" - } - } - } - }, - "childExtend": "LocationInfo" - } - } - }, - { - "key": "ContactSection.Fourteen", - "title": "ContactSection.Fourteen", - "description": "", - "category": "block", - "code": "", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "colspan": 12, - "rowspan": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "component": { - "extend": [ - "Flex" - ], - "props": { - "flow": "y", - "theme": "dialog", - "padding": "B2 B2 B2 C1", - "round": "A", - "gap": "C1", - "@screenS": { - "minWidth": "100%" - }, - "@tabletM": { - "padding": "B2 C B C" - }, - "@mobileM": { - "padding": "B2 B1 A B1" - } - }, - "SectionHgroup": { - "gap": "A", - "H": { - "text": "Contact us" - }, - "P": { - "text": "We Value Your Connection and Look Forward to Hearing from You" - } - }, - "Flex": { - "props": { - "gap": "F", - "@tabletM": { - "flow": "y", - "gap": "C2" - } - }, - "Aside": { - "0": {}, - "1": {}, - "2": {}, - "3": {}, - "extend": "Flex", - "props": { - "flow": "y", - "gap": "C2", - "padding": "B - - -", - "@tabletM": { - "gap": "C", - "padding": "0 - - -" - } - }, - "childExtend": { - "extend": "HgroupLink", - "props": {} - } - }, - "Map": { - "extend": "Flex", - "props": { - "theme": "field", - "minWidth": "I", - "minHeight": "H2", - "align": "center center", - "round": "A", - "@screenS": { - "flex": "1" - }, - "@tabletM": { - "minWidth": "100%", - "minHeight": "G2", - "margin": "- -A" - }, - "@mobileM": { - "minHeight": "G" - } - }, - "Icon": { - "props": { - "name": "send", - "fontSize": "E2" - } - } - } - } - } - } - ], - "thumbnail": { - "screenshot": "https://storage.screenshotapi.net/default_symbo_ls_component_key_contactsection_widt_51480f8953fd.webp", - "url": "https://default.symbo.ls/component?key=ContactSection&width=1096&height=1096&hide_ui=true", - "created_at": "2024-08-31T12:13:17.567Z", - "is_fresh": true, - "token": "SGG5Q3D-WZN437D-NCC3ZZ2-Q15ZMZG", - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - "width": "1096", - "height": "1096", - "dark_mode": "true", - "fresh": "true", - "file_type": "webp", - "retina": "true", - "wait_for_event": "domcontentloaded", - "ttl": "2024-09-30T12:13:12.387Z" - }, - "uses": [ - "FormModal", - "Hgroup", - "H", - "P", - "Form", - "Button" - ], - "type": "component", - "error": null - }, - "FieldCaption": { - "key": "FieldCaption", - "title": "FieldCaption", - "description": "", - "category": "comp", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "x": 15, - "y": 0, - "w": 1, - "h": 1 - } - }, - "interactivity": [], - "dataTypes": [], - "variants": [], - "uses": [ - "Flex", - "Caption", - "Field", - "Input", - "Icon" - ], - "type": "component" - }, - "FilterSidebar": { - "key": "FilterSidebar", - "type": "component", - "settings": { - "gridOptions": { - "x": 16, - "y": 1, - "w": 1, - "h": 1 - } - }, - "uses": [ - "DropdownParent", - "Button", - "Icon", - "Dropdown" - ] - }, - "FilterStatus": { - "key": "FilterStatus", - "type": "component", - "settings": { - "gridOptions": { - "x": 11, - "y": 6, - "w": 1, - "h": 1 - } - }, - "uses": [ - "Flex", - "Stable", - "Box", - "Off" - ] - }, - "Dropdown": { - "key": "Dropdown", - "type": "component", - "settings": { - "gridOptions": { - "x": 16, - "y": 0, - "w": 1, - "h": 1 - } - }, - "uses": [], - "code": "export default {/////n props: {/////n isDropdownRoot: true,/////n theme: 'dialog',/////n round: 'Z2',/////n position: 'absolute',/////n left: '50%',/////n transform: 'translate3d(0, -10px, 0)',/////n top: '102%',/////n minWidth: 'F',/////n maxHeight: 'H',/////n transition: 'A defaultBezier',/////n transitionProperty: 'visibility, transform, opacity',/////n overflow: 'hidden auto',/////n opacity: '0',/////n visibility: 'hidden',/////n boxSizing: 'border-box',/////n zIndex: 1000,/////n '@dark': {/////n boxShadow: 'black .20, 0px, 5px, 10px, 5px',/////n },/////n '@light': {/////n boxShadow: 'gray5 .20, 0px, 5px, 10px, 5px',/////n },/////n },/////n attr: {/////n dropdown: true/////n },/////n tag: 'section',/////n}" - }, - "CopyButton": { - "key": "CopyButton", - "type": "component", - "settings": { - "gridOptions": { - "x": 15, - "y": 2, - "w": 1, - "h": 1 - } - }, - "uses": [ - "IconButton" - ] - }, - "ValidatorLogs": { - "key": "ValidatorLogs", - "type": "component", - "settings": { - "gridOptions": { - "x": 22, - "y": 0, - "w": 2, - "h": 2 - } - }, - "uses": [ - "Grid", - "Monkier", - "Title", - "Strong", - "Owner", - "CloudProvider", - "Reward", - "RAM" - ] - }, - "LogItem": { - "key": "LogItem", - "type": "component", - "settings": { - "gridOptions": { - "x": 23, - "y": 2, - "w": 1, - "h": 1 - } - }, - "uses": [ - "Title", - "H3" - ] - }, - "Chart": { - "key": "Chart", - "type": "component", - "settings": { - "gridOptions": { - "x": 3, - "y": 5, - "w": 2, - "h": 1 - } - }, - "uses": [], - "error": null, - "code": "export default {/////n props: {/////n onRender: async (el, s) => {/////n const Chart = await import('chart.js')/////n const ctx = el.node.getContext('2d');/////n/////n const maxPoints = 50;/////n const dataPoints = Array(maxPoints).fill(0);/////n/////n const labels = Array.from({/////n length: maxPoints/////n }, (_, i) => i.toString());/////n/////n // Initialize with some starting data/////n for (let i = 0; i < dataPoints.length; i++) {/////n dataPoints[i] = 30 + Math.sin(i * 0.2) * 20;/////n }/////n/////n const data = {/////n labels: labels,/////n datasets: [{/////n label: 'CPU',/////n data: dataPoints.map((value, index) => ({/////n x: index,/////n y: value/////n })),/////n borderColor: 'rgb(86, 154, 67)',/////n fill: false,/////n tension: 0.4,/////n pointRadius: 0,/////n borderWidth: 2,/////n cubicInterpolationMode: 'monotone'/////n }]/////n };/////n/////n const options = {/////n responsive: true,/////n animation: false,/////n interaction: {/////n intersect: false,/////n mode: 'index'/////n },/////n plugins: {/////n legend: {/////n display: false/////n },/////n tooltip: {/////n enabled: false/////n }/////n },/////n scales: {/////n x: {/////n display: false,/////n type: 'linear',/////n min: 0,/////n max: maxPoints - 1/////n },/////n y: {/////n display: false,/////n min: 0,/////n max: 100/////n }/////n },/////n elements: {/////n line: {/////n borderJoinStyle: 'round'/////n }/////n },/////n maintainAspectRatio: false/////n };/////n/////n const chart = new Chart(ctx, {/////n type: 'line',/////n data: data,/////n options: options/////n });/////n/////n // Color functions/////n function getColor(value) {/////n if (value < 60) return 'rgb(86, 154, 67)';/////n if (value < 80) return 'rgb(245, 158, 11)';/////n return 'rgb(220, 38, 38)';/////n }/////n/////n function interpolateColor(color1, color2, factor) {/////n const c1 = color1.match(/\\d+/g).map(Number);/////n const c2 = color2.match(/\\d+/g).map(Number);/////n const r = Math.round(c1[0] + (c2[0] - c1[0]) * factor);/////n const g = Math.round(c1[1] + (c2[1] - c1[1]) * factor);/////n const b = Math.round(c1[2] + (c2[2] - c1[2]) * factor);/////n return /////tildergb(/////dlrsgn{r}, /////dlrsgn{g}, /////dlrsgn{b})/////tilde;/////n }/////n/////n let animationFrame = null;/////n let isAnimating = false;/////n/////n function updateCPUChart(newValue) {/////n if (isAnimating) return;/////n/////n isAnimating = true;/////n const dataset = chart.data.datasets[0];/////n const originalData = [...dataset.data];/////n const lastPoint = originalData[originalData.length - 1];/////n const startTime = performance.now();/////n const duration = 1000;/////n/////n // Get colors for transition/////n const startColor = dataset.borderColor;/////n const endColor = getColor(newValue);/////n/////n function animate(currentTime) {/////n const elapsed = currentTime - startTime;/////n const progress = Math.min(elapsed / duration, 1);/////n const easeProgress = easeInOutCubic(progress);/////n/////n // Create new animated data/////n const animatedData = [];/////n/////n // Animate all existing points shifting left (keep their y values)/////n for (let i = 0; i < originalData.length; i++) {/////n animatedData.push({/////n x: originalData[i].x - easeProgress,/////n y: originalData[i].y // Keep original y value unchanged/////n });/////n }/////n/////n // Add new point entering from the right, transitioning from lastPoint.y to newValue/////n animatedData.push({/////n x: maxPoints - 1 + (1 - easeProgress),/////n y: lastPoint.y + (newValue - lastPoint.y) * easeProgress/////n });/////n/////n dataset.data = animatedData;/////n/////n // Animate color if crossing threshold/////n if (getColor(lastPoint.y) !== getColor(newValue)) {/////n dataset.borderColor = interpolateColor(startColor, endColor, easeProgress);/////n }/////n/////n chart.update('none');/////n/////n if (progress < 1) {/////n animationFrame = requestAnimationFrame(animate);/////n } else {/////n // Final state: shift all data and add new point/////n dataset.data = [/////n ...originalData.slice(1).map((point, i) => ({/////n x: i,/////n y: point.y/////n })),/////n {/////n x: maxPoints - 1,/////n y: newValue/////n }/////n ];/////n dataset.borderColor = endColor;/////n chart.update('none');/////n isAnimating = false;/////n }/////n }/////n/////n animationFrame = requestAnimationFrame(animate);/////n }/////n/////n function easeInOutCubic(t) {/////n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;/////n }/////n/////n // Simulate CPU data updates/////n const interval = setInterval(() => {/////n const fakeCPU = Math.floor(Math.random() * 100);/////n updateCPUChart(fakeCPU);/////n }, 1100);/////n/////n // Cleanup function/////n return () => {/////n clearInterval(interval);/////n if (animationFrame) {/////n cancelAnimationFrame(animationFrame);/////n }/////n chart.destroy();/////n };/////n },/////n tag: 'canvas',/////n minWidth: 'G',/////n minHeight: 'D',/////n },/////n data: {},/////n}" - }, - "Uptime": { - "key": "Uptime", - "type": "component", - "settings": { - "gridOptions": { - "x": 3, - "y": 4, - "w": 3, - "h": 1 - } - }, - "uses": [ - "Flex", - "Title" - ], - "title": "Uptime", - "description": "", - "category": "", - "tags": [], - "state": { - "isActive": true - }, - "props": {}, - "interactivity": [], - "dataTypes": [], - "advanced": true, - "error": null - }, - "Logs": { - "key": "Logs", - "type": "component", - "settings": { - "gridOptions": { - "x": 8, - "y": 0, - "w": 2, - "h": 1 - } - }, - "uses": [ - "Flex", - "H6", - "Date", - "Status", - "Notes", - "Title" - ], - "title": "Logs", - "description": "", - "category": "", - "tags": [], - "state": { - "isActive": true, - "logs": [ - { - "action_items": "Check disk space usage; Implement log rotation", - "created_at": "2025-05-04T00:05:40.283Z", - "status_notes": "Node operating normally with stable performance" - } - ] - }, - "props": {}, - "interactivity": [], - "dataTypes": [], - "advanced": true, - "error": null - }, - "Graphs": { - "key": "Graphs", - "type": "component", - "settings": { - "gridOptions": { - "x": 3, - "y": 3, - "w": 3, - "h": 1 - } - }, - "uses": [ - "Flex", - "Title", - "Chart", - "BlockHeight" - ], - "title": "Graphs", - "description": "", - "category": "", - "tags": [], - "state": { - "isActive": true - }, - "props": {}, - "interactivity": [], - "dataTypes": [], - "advanced": true, - "error": null - }, - "Chart.Block": { - "key": "Chart.Block", - "type": "component", - "settings": { - "gridOptions": { - "x": 3, - "y": 6, - "w": 2, - "h": 1 - } - }, - "uses": [ - "Number", - "Box" - ] - }, - "ValidatorsList": { - "key": "ValidatorsList", - "type": "component", - "settings": { - "gridOptions": { - "x": 11, - "y": 3, - "w": 3, - "h": 2 - } - }, - "uses": [ - "ValidatorRow", - "ValidatorContent" - ], - "title": "ValidatorsList", - "description": "", - "category": "", - "tags": [], - "state": "", - "props": { - "demoComponent": { - "children": [ - {}, - {} - ] - } - }, - "interactivity": [], - "dataTypes": [], - "advanced": true, - "error": null - }, - "Aside": { - "key": "Aside", - "type": "component", - "settings": { - "gridOptions": { - "x": 21, - "y": 0, - "w": 1, - "h": 2 - } - }, - "uses": [ - "InputField", - "Icon", - "Input", - "FilterSidebar", - "Button", - "Dropdown", - "All", - "Mainnet", - "Testnet", - "None", - "Today", - "Last2Days", - "Last3Days", - "LastWeek", - "LastMonth" - ] - }, - "AIMessage": { - "key": "AIMessage", - "type": "component", - "settings": { - "gridOptions": { - "x": 25, - "y": 2, - "w": 1, - "h": 1 - } - }, - "error": null, - "uses": [ - "LabelTag", - "Focusable" - ] - }, - "AIThread": { - "key": "AIThread", - "type": "component", - "settings": { - "gridOptions": { - "x": 26, - "y": 2, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [ - "Flex" - ] - }, - "Prompt": { - "key": "Prompt", - "type": "component", - "settings": { - "gridOptions": { - "x": 25, - "y": 0, - "w": 3, - "h": 2 - } - }, - "error": null, - "uses": [ - "Relative", - "Textarea", - "Submit", - "SquareButton", - "AIThread" - ] - }, - "Search": { - "key": "Search", - "title": "Search", - "description": "", - "category": "comp", - "extends": [], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "x": 15, - "y": 9, - "w": 2, - "h": 2 - } - }, - "interactivity": [], - "dataTypes": [], - "pdf": { - "screenshot": "https://storage.screenshotapi.net/default_symbo_ls_component_key_search_width_1007_h_dcb2d641c14a.pdf", - "url": "https://default.symbo.ls/component?key=Search&width=1007&height=504&hide_ui=true", - "created_at": "2024-10-03T06:56:38.591Z", - "is_fresh": true, - "token": "SGG5Q3D-WZN437D-NCC3ZZ2-Q15ZMZG", - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - "width": "1007", - "height": "504", - "dark_mode": "true", - "fresh": "true", - "file_type": "pdf", - "pdf_options": { - "format": "A4", - "media": "print", - "landscape": false, - "print_background": false - }, - "wait_for_event": "domcontentloaded", - "ttl": "2024-11-02T06:56:34.246Z", - "sizes": null, - "smblsUrl": "https://screenshots.symbo.ls/default_symbo_ls_component_key_search_width_1007_h_dcb2d641c14a.pdf" - }, - "uses": [ - "Flex", - "Icon", - "Input" - ], - "type": "component", - "error": null, - "code": "export default {/////n extend: [/////n 'Flex',/////n ],/////n tag: 'search',/////n state: {},/////n/////n props: {/////n minWidth: 'G2',/////n gap: 'Z',/////n icon: 'search',/////n align: 'center flex-start',/////n position: 'relative',/////n theme: 'field',/////n round: 'D2',/////n '@mobileS': {/////n minWidth: 'G1',/////n },/////n },/////n Icon: {/////n icon: 'search',/////n right: 'A+V2',/////n },/////n Input: {/////n type: 'search',/////n placeholder: 'Type a command or search',/////n width: '100%',/////n padding: 'Z2 C Z2 A2+W',/////n theme: 'transparent',/////n ':focus ~ button': {/////n opacity: '1',/////n },/////n onInput: (ev, el, s) => {/////n console.log(el.node.value)/////n s.update({/////n searchTerm: el.node.value/////n })/////n },/////n },/////n}" - }, - "NetworkRow": { - "key": "NetworkRow", - "type": "component", - "title": "Row", - "description": "", - "category": "", - "tags": [], - "state": "data/0", - "props": { - "demoComponent": { - "state": { - "protocol": "kaia", - "env": "Mainnet", - "role": "Validator", - "status": "Stable/Maintenance", - "repo_url": "https://github.com/kaiachain/kaia/releases/", - "version": "v1.2.3", - "validator_info": [ - { - "moniker": "BCW Technologies", - "public_key": "mantravaloper1r3s4pefpz69fk4rq8sn0zt2wxnv09uffz06nxu" - } - ], - "validator_count": 1 - } - } - }, - "settings": { - "gridOptions": { - "x": 11, - "y": 5, - "w": 3, - "h": 1 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [ - "Grid", - "Name", - "Avatar", - "Title", - "Env", - "NodeTypes", - "CloudProvider", - "Status" - ], - "advanced": true, - "error": null - }, - "Page": { - "key": "Page", - "type": "component", - "settings": { - "gridOptions": { - "x": 0, - "y": 4, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [], - "code": "export default {/////n props: {/////n flexFlow: 'y',/////n position: 'relative',/////n heightRange: '100dvh',/////n overflow: 'hidden',/////n },/////n}" - }, - "NavButton": { - "key": "NavButton", - "type": "component", - "settings": { - "gridOptions": { - "x": 7, - "y": 1, - "w": 1, - "h": 1 - } - }, - "error": null, - "uses": [ - "Link", - "Button" - ] - }, - "ModalWindow": { - "key": "ModalWindow", - "title": "Modal", - "description": "", - "category": "comp", - "extends": [ - "Flex" - ], - "state": "", - "props": {}, - "settings": { - "gridOptions": { - "x": 19, - "y": 3, - "w": 2, - "h": 1 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [ - "Flex", - "Hgroup", - "H", - "P", - "XBtn", - "Icon" - ], - "type": "component", - "error": null - }, - "Modal": { - "key": "Modal", - "type": "component", - "title": "Modal Fade", - "description": "", - "category": "", - "tags": [], - "state": { - "root": { - "modal": "/add-network" - } - }, - "props": { - "demoComponent": { - "editMode": true - } - }, - "settings": { - "gridOptions": { - "x": 19, - "y": 0, - "w": 2, - "h": 2 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [], - "error": null, - "advanced": true - }, - "MetaSection": { - "description": "A section aggregating multiple MetricItem components.", - "key": "MetaSection", - "title": "MetricsSection Component", - "type": "component", - "settings": { - "gridOptions": { - "x": 0, - "y": 2, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [ - "H6", - "Flex", - "Grid", - "H", - "P" - ] - }, - "NetworkRowLabel": { - "key": "NetworkRowLabel", - "type": "component", - "settings": { - "gridOptions": { - "x": 7, - "y": 0, - "w": 1, - "h": 1 - } - }, - "error": null, - "uses": [ - "LabelTag" - ] - }, - "Header": { - "key": "Header", - "type": "component", - "settings": { - "gridOptions": { - "x": 15, - "y": 8, - "w": 4, - "h": 1 - } - }, - "error": null, - "uses": [ - "Flex", - "Link", - "IconButton", - "Search", - "Input", - "Avatar" - ], - "code": "export default {/////n extend: 'Flex',/////n props: {/////n minWidth: 'G2',/////n icon: 'search',/////n align: 'center flex-start',/////n position: 'relative',/////n theme: 'dialog',/////n round: 'D2',/////n ':focus-visible': {/////n outline: 'solid, X, blue .3',/////n },/////n ':focus-within ~ .search-results': {/////n opacity: 0.3/////n },/////n ':focus-within ~ .content': {/////n opacity: 0.3/////n }/////n },/////n Link: {/////n extends: [/////n 'Link',/////n 'IconButton',/////n ],/////n paddingInline: 'A Z1',/////n icon: 'chevron left',/////n href: '/',/////n theme: null,/////n hide: () => window.location.pathname === '/',/////n borderRadius: '0',/////n border: null,/////n borderWidth: '0 1px 0 0',/////n borderStyle: 'solid',/////n borderColor: 'deepFir',/////n style: null,/////n },/////n Search: {/////n flex: 1,/////n theme: 'dialog',/////n paddingInline: 'Z Z1',/////n Input: {/////n paddingInline: 'Z1',/////n ':focus-visible': null,/////n },/////n },/////n Avatar: {/////n boxSize: 'B',/////n margin: 'Z',/////n src: (el, s) => s.root.user?.picture || /////tildehttps://avatars.symbo.ls/initials/png?seed=/////dlrsgn{s.root.user?.name}/////tilde,/////n },/////n}" - }, - "OldNotes": { - "key": "OldNotes", - "type": "component", - "settings": { - "gridOptions": { - "x": 3, - "y": 7, - "w": 2, - "h": 1 - } - }, - "uses": [ - "Flex", - "Title", - "Notes", - "Date", - "Add", - "Edit", - "Remove", - "Addnew", - "Actions" - ], - "title": "Logs", - "description": "", - "category": "", - "tags": [], - "state": { - "isActive": true, - "logs": [ - { - "action_items": "Check disk space usage; Implement log rotation", - "created_at": "2025-05-04T00:05:40.283Z", - "status_notes": "Node operating normally with stable performance" - } - ] - }, - "props": {}, - "interactivity": [], - "dataTypes": [], - "advanced": true - }, - "MetaSectionBars": { - "description": "A section aggregating multiple MetricItem components.", - "key": "MetaSectionBars", - "title": "MetricsSection Component", - "type": "component", - "settings": { - "gridOptions": { - "x": 8, - "y": 1, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [ - "H6", - "Flex", - "TooltipParent", - "TooltipHidden" - ] - }, - "Select": { - "key": "Select", - "type": "component", - "settings": { - "gridOptions": { - "x": 17, - "y": 0, - "w": 1, - "h": 1 - } - }, - "error": null, - "uses": [ - "Flex", - "Selects", - "Icon" - ] - }, - "ModalForm": { - "key": "ModalForm", - "type": "component", - "settings": { - "gridOptions": { - "x": 19, - "y": 4, - "w": 1, - "h": 1 - } - }, - "error": null, - "uses": [ - "Grid" - ] - }, - "Test": { - "settings": { - "gridOptions": { - "w": "3", - "h": "2", - "x": "12", - "y": "0" - } - } - }, - "Table": { - "key": "Table", - "type": "component", - "title": "Table", - "description": "", - "category": "", - "tags": [], - "state": "", - "props": { - "demoComponent": {} - }, - "settings": { - "gridOptions": { - "x": 7, - "y": 4, - "w": 3, - "h": 4 - } - }, - "interactivity": [], - "dataTypes": [], - "uses": [ - "Flex", - "NetworkRow", - "Link" - ], - "error": null, - "advanced": true, - "metadata": "copiedFromSymbols", - "code": "export default {/////n props: {/////n extends: 'Flex',/////n childExtends: [/////n 'NetworkRow',/////n 'Link',/////n ],/////n width: '100%',/////n children: (el, s) => s.fleet,/////n childrenAs: 'state',/////n flow: 'y',/////n position: 'relative',/////n zIndex: 2,/////n text: null/////n }/////n}" - }, - "SearchDropdown": { - "key": "SearchDropdown", - "type": "components", - "settings": { - "gridOptions": { - "w": 2, - "h": 2, - "x": 17, - "y": 9 - } - }, - "error": null, - "uses": [ - "Flex", - "CaptionTitle", - "Text", - "SearchItem" - ], - "title": "", - "description": "", - "category": "", - "tags": [], - "state": { - "searchTerm": "Header" - }, - "props": {}, - "interactivity": [], - "dataTypes": [], - "metadata": "copiedFromSymbols", - "code": "export default {/////n extend: 'Flex',/////n attr: {/////n dropdown: true,/////n },/////n props: {/////n flow: 'y',/////n gap: 'A',/////n padding: 'Z1 A',/////n fontSize: 'Z2',/////n theme: null,/////n class: 'search-results',/////n '@dark': {/////n background: '#1D1D1D .95',/////n backdropFilter: 'blur(8px)',/////n boxShadow: 'black .35, 0px, 15px, 150px, 280px',/////n },/////n '@light': {/////n background: 'gray1 .95',/////n backdropFilter: 'blur(8px)',/////n boxShadow: 'white .68, 0px, 15px, 150px, 280px',/////n },/////n },/////n CaptionTitle: {/////n Text: {/////n text: 'Search results',/////n },/////n },/////n Flex: {/////n gap: 'Z',/////n flow: 'y',/////n margin: '- -Z2',/////n children: (el, s) => {/////n const fuse = el.props.fuse/////n/////n function searchAll(query) {/////n const results = fuse.search(query);/////n/////n return results.map(r => ({/////n key: r.item.key,/////n title: r.item.title,/////n code: r.item.code,/////n type: r.item.type, // component / page / function/////n score: r.score,/////n }));/////n }/////n/////n const result = searchAll(s.searchTerm)/////n console.log(result, s.searchTerm)/////n return result/////n },/////n childrenAs: 'state',/////n childExtends: 'SearchItem',/////n onInit: async (el, s) => {/////n const fuse = await import('fuse.js')/////n const Fuse = fuse.default/////n/////n const fuseOptions = {/////n isCaseSensitive: false,/////n shouldSort: true,/////n findAllMatches: true,/////n includeScore: true,/////n threshold: 0.4, // Adjust for fuzzy matching sensitivity/////n keys: [{/////n name: \"title\",/////n weight: 2/////n }, // Higher weight for titles/////n {/////n name: \"networkName\",/////n weight: 1.5/////n },/////n {/////n name: \"moniker\",/////n weight: 1.5/////n },/////n {/////n name: \"owner\",/////n weight: 1/////n },/////n {/////n name: \"env\",/////n weight: 1/////n },/////n {/////n name: \"cloudProvider\",/////n weight: 0.5/////n },/////n {/////n name: \"publicKey\",/////n weight: 0.3/////n },/////n ]/////n }/////n/////n // Function to create searchable items from fleet data/////n function flattenFleetData(fleet) {/////n const searchItems = []/////n/////n fleet.forEach(network => {/////n // Add network itself as searchable/////n searchItems.push({/////n id: network.id,/////n key: /////tildenetwork-/////dlrsgn{network.id}/////tilde,/////n title: network.protocol,/////n subtitle: /////tilde/////dlrsgn{network.network_type} • /////dlrsgn{network.network_layer}/////tilde,/////n type: 'network',/////n networkName: network.protocol,/////n networkType: network.network_type,/////n networkLayer: network.network_layer,/////n participation: network.participation,/////n original: network/////n })/////n/////n // Add validators/////n network.validators?.forEach(validator => {/////n searchItems.push({/////n id: validator.uid,/////n key: /////tildevalidator-/////dlrsgn{validator.uid}/////tilde,/////n title: validator.moniker || 'Unknown',/////n subtitle: /////tildeValidator • /////dlrsgn{network.protocol} • /////dlrsgn{validator.env}/////tilde,/////n type: 'validator',/////n networkName: network.protocol,/////n moniker: validator.moniker,/////n owner: validator.owner,/////n env: validator.env,/////n cloudProvider: validator.cloud_provider,/////n publicKey: validator.public_key,/////n clientVersion: validator.client_version,/////n original: validator,/////n networkId: network.id/////n })/////n })/////n/////n // Add RPC nodes/////n network.rpc_nodes?.forEach(rpc => {/////n searchItems.push({/////n id: rpc.uid,/////n key: /////tilderpc-/////dlrsgn{rpc.uid}/////tilde,/////n title: rpc.moniker || 'RPC Node',/////n subtitle: /////tildeRPC • /////dlrsgn{network.protocol} • /////dlrsgn{rpc.env}/////tilde,/////n type: 'rpc',/////n networkName: network.protocol,/////n moniker: rpc.moniker,/////n env: rpc.env,/////n clientVersion: rpc.client_version,/////n original: rpc,/////n networkId: network.id/////n })/////n })/////n })/////n/////n return searchItems/////n }/////n/////n // Get fleet data from root state/////n const fleet = window.fleet || s.parent.fleet || s.root.fleet || []/////n const searchItems = flattenFleetData(fleet)/////n/////n console.log('Indexed items:', searchItems.length)/////n el.props.fuse = new Fuse(searchItems, fuseOptions)/////n },/////n },/////n}" - }, - "SearchItem": { - "key": "SearchItem", - "type": "components", - "settings": { - "gridOptions": { - "w": 3, - "h": 1, - "x": 15, - "y": 11 - } - }, - "error": null, - "uses": [ - "Button", - "Avatar", - "Hgroup", - "H", - "P" - ], - "title": "", - "description": "", - "category": "", - "tags": [], - "state": { - "type": "network", - "title": "Solana" - }, - "props": {}, - "interactivity": [], - "dataTypes": [], - "code": "export default {/////n extend: [/////n 'Flex',/////n ],/////n props: {/////n gap: 'Z1',/////n align: 'start',/////n padding: 'Z A X',/////n theme: 'transparent',/////n ':hover': {/////n theme: 'tertiary',/////n style: {/////n svg: {/////n opacity: 1,/////n },/////n },/////n },/////n },/////n Avatar: {/////n if: (el, s) => s.type === 'network',/////n margin: '-W2 - -',/////n src: '{{ title }}.png',/////n boxSize: 'B1',/////n },/////n Hgroup: {/////n gap: 'W',/////n H: {/////n tag: 'h6',/////n text: '{{ title }}',/////n margin: '- C - -',/////n },/////n P: {/////n textAlign: 'start',/////n text: '{{ type }}'/////n }/////n }/////n}", - "metadata": "copiedFromSymbols" - }, - "Content": { - "key": "Content", - "type": "components", - "settings": { - "gridOptions": { - "w": 4, - "h": 2, - "x": 15, - "y": 12 - } - }, - "code": "export default {/////n extend: 'Flex',/////n props: {/////n class: 'content',/////n flex: 1,/////n position: 'relative',/////n width: '100%',/////n round: 'A A2',/////n theme: 'dialog',/////n flow: 'y',/////n }/////n}", - "uses": [ - "Flex" - ] - }, - "Layout": { - "key": "Layout", - "type": "components", - "settings": { - "gridOptions": { - "x": 15, - "y": 6, - "w": 4, - "h": 2 - } - }, - "error": null, - "uses": [ - "Header", - "SearchDropdown", - "Dropdown", - "Content" - ], - "code": "export default {/////n props: {/////n position: 'relative',/////n },/////n Header: {},/////n SearchDropdown: {/////n extends: [/////n 'SearchDropdown',/////n 'Dropdown',/////n ],/////n width: '100%',/////n left: '0', /////n },/////n Content: {}/////n}" - } - }, - "integrations": {}, - "snippets": {}, - "pages": { - "/network": { - "key": "/network", - "type": "page", - "settings": { - "gridOptions": { - "x": 6, - "y": 0, - "w": 3, - "h": 2 - } - }, - "error": null, - "uses": [ - "Page", - "Layout", - "Header", - "Link", - "Content", - "Box", - "Flex", - "NavButton", - "DropdownParentFocus", - "Input", - "IconButton", - "Icon", - "Dropdown", - "DropdownList", - "Button", - "CopyURL", - "Update", - "Delete", - "Hr", - "ValidatorInfo", - "Modal" - ] - }, - "/node": { - "key": "/node", - "type": "page", - "settings": { - "gridOptions": { - "x": 9, - "y": 0, - "w": 3, - "h": 2 - } - }, - "error": null, - "uses": [ - "Page", - "Layout", - "Header", - "Link", - "Content", - "PageHead", - "Flex", - "Protocol", - "Avatar", - "Strong", - "NavButton", - "DropdownParentFocus", - "Input", - "IconButton", - "Icon", - "Dropdown", - "DropdownList", - "Button", - "CopyURL", - "Delete", - "Modal", - "Hr", - "Box", - "Overflow", - "ValidatorContent", - "Logs" - ], - "network": { - "protocol": "Eth2" - } - }, - "/add-network": { - "key": "/add-network", - "type": "page", - "settings": { - "gridOptions": { - "x": 0, - "y": 3, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [ - "FormModal", - "Hgroup", - "H", - "P", - "Form", - "Button" - ] - }, - "/add-node": { - "key": "/add-node", - "type": "page", - "settings": { - "gridOptions": { - "x": 3, - "y": 3, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [ - "FormModal", - "Hgroup", - "H", - "P", - "Form", - "Button" - ] - }, - "/edit-network": { - "key": "/edit-network", - "type": "page", - "settings": { - "gridOptions": { - "x": 0, - "y": 5, - "w": 2, - "h": 2 - } - }, - "error": null, - "uses": [ - "/add-network", - "Hgroup", - "H", - "P" - ], - "title": "Edit network", - "advanced": true, - "description": "", - "category": "", - "tags": [], - "state": "", - "props": {}, - "interactivity": [], - "dataTypes": [], - "touched": true - }, - "/edit-node": { - "key": "/edit-node", - "type": "page", - "settings": { - "gridOptions": { - "x": 3, - "y": 5, - "w": 2, - "h": 3 - } - }, - "error": null, - "uses": [ - "/add-node", - "Hgroup", - "H", - "P", - "Form", - "Hr", - "ModalForm" - ], - "title": "Edit network", - "advanced": true, - "description": "", - "category": "", - "tags": [], - "state": "", - "props": {}, - "interactivity": [], - "dataTypes": [], - "touched": true - }, - "/dashboard": { - "key": "/dashboard", - "type": "page", - "settings": { - "gridOptions": { - "x": 3, - "y": 0, - "w": 3, - "h": 2 - } - }, - "uses": [ - "Page", - "TestItem", - "Header", - "Flex", - "Box", - "Overflow", - "PageHead", - "Title", - "Strong", - "Span", - "NavButton", - "Tr", - "Grid", - "Hr", - "Table", - "Modal", - "MetaSectionBars" - ], - "error": null, - "hover": false - }, - "/": { - "title": "/", - "key": "/", - "type": "page", - "settings": { - "gridOptions": { - "x": 0, - "y": 0, - "w": 3, - "h": 2 - } - }, - "props": {}, - "interactivity": [], - "dataTypes": [] - } - }, - "state": { - "mockData": { - "method": "GET", - "type": "state", - "headers": {}, - "headersArr": [], - "params": {}, - "paramsArr": [], - "auth": {}, - "authArr": [], - "body": {}, - "runtime": false, - "title": "Mock data", - "key": "mockData", - "endpointUrl": "https://tim-sodium-pets-owen.trycloudflare.com/api/fleet" - }, - "mockedData": { - "method": "GET", - "type": "state", - "headers": {}, - "headersArr": [], - "params": {}, - "paramsArr": [], - "auth": {}, - "authArr": [], - "body": {}, - "runtime": false, - "title": "mockedData", - "key": "mockedData", - "endpointUrl": "http://localhost:3000/api/fleet" - }, - "fleetCopy": { - "key": "fleetCopy", - "type": "function", - "editing": false - } - }, - "files": { - "Autonity.png": { - "key": "Autonity.png", - "type": "file" - } - }, - "dependencies": { - "chart.js": { - "key": "chart.js", - "resolvedVersion": "4.4.9", - "type": "dependency", - "version": "4.4.9", - "status": "done" - }, - "fuse.js": { - "key": "fuse.js", - "resolvedVersion": "7.1.0", - "type": "dependencies", - "version": "latest" - } - }, - "methods": {}, - "functions": { - "stringToHexColor": { - "key": "stringToHexColor", - "type": "function" - }, - "filterByEnv": { - "key": "filterByEnv", - "type": "function" - }, - "calculateWeek": { - "key": "calculateWeek", - "type": "function" - }, - "calculateCosts": { - "key": "calculateCosts", - "type": "function" - }, - "getCostsPerProtocol": { - "key": "getCostsPerProtocol", - "type": "function" - }, - "giveMeAnswer": { - "key": "giveMeAnswer", - "type": "function" - }, - "getMe": { - "key": "getMe", - "type": "function" - }, - "getTagsOfNodes": { - "key": "getTagsOfNodes", - "type": "function" - }, - "edit": { - "key": "edit", - "code": "export default async function edit(item = 'network', protocol, opts = {}) {/////n const formData = new FormData(this.node)/////n let data = Object.fromEntries(formData)/////n/////n // For nodes, send flat object directly (not nested)/////n if (item === 'node') {/////n // Remove nodeType from data since it's in the URL/////n delete data.nodeType/////n data.projected_cost = parseInt(data.projected_cost)/////n // Send the form data directly as flat object/////n // data is already correct: { moniker: \"...\", env: \"...\", etc. }/////n }/////n/////n const ROUTE = {/////n network: /////tilde//////dlrsgn{protocol}/////tilde,/////n node: /////tilde/node//////dlrsgn{this.state.nodeType}//////dlrsgn{this.state.uid}/////tilde/////n }/////n/////n console.log('Route:', ROUTE[item])/////n console.log('Data being sent:', data)/////n/////n const res = await this.call('fetch', 'PUT', ROUTE[item], data, opts)/////n console.log('Response:', res)/////n/////n this.state.root.quietUpdate({/////n modal: null/////n })/////n/////n const redirectUrl = {/////n network: '/network/' + this.state.protocol,/////n node: '/node/' + this.state.protocol + '/' + this.state.nodeType + '/' + this.state.uid/////n }/////n/////n this.call('router', redirectUrl[item] || '/', this.__ref.root)/////n this.node.reset()/////n}", - "type": "function", - "error": "SyntaxError: Unexpected end of input", - "err": {} - }, - "remove": { - "key": "remove", - "code": "export default async function remove(item = 'network', protocol, opts = {}) {/////n let [, _, urlProtocol, nodeType, uid] = window.location.pathname.split('/')/////n/////n const ROUTE = {/////n network: '/' + protocol,/////n node: '/node/' + nodeType + '/' + uid/////n }/////n/////n console.log('/node/' + nodeType + '/' + uid)/////n/////n const res = await this.call('fetch', 'DELETE', ROUTE[item])/////n if (!res) return/////n/////n this.state.root.quietUpdate({/////n modal: null/////n })/////n/////n const REDIRECT = {/////n network: '/dashboard',/////n node: '/network/' + protocol/////n }/////n this.call('router', REDIRECT[item], this.__ref.root)/////n}", - "type": "function", - "error": "SyntaxError: Unexpected end of input", - "err": {} - }, - "fetch": { - "key": "fetch", - "code": "export default async function fetch(method = 'GET', path = '', data, opts = {}) {/////n // const ENDPOINT = 'https://small-sound-18b4.nika-980.workers.dev/api/fleet'/////n // const ENDPOINT = 'https://bigbrother.symbo.ls' + (opts.route || '/api/fleet') + path/////n/////n const options = {/////n method: method || 'POST',/////n headers: {/////n 'Content-Type': 'application/json'/////n },/////n ...opts/////n }/////n/////n const isCanvas = location.href === 'srcdoc'/////n const isSymbols = location.host.includes('symbo.ls')/////n const isProd = location.host.includes('nodeops.ninja') && !location.host.includes('dev')/////n // const isProd = false // location.host.includes('nodeops.ninja') && !location.host.includes('dev')/////n/////n const URL = /////tildehttps:///////dlrsgn{isProd ? '' : 'dev.'}api.nodeops.ninja/////tilde/////n const ENDPOINT = URL + (opts.route || '/api/fleet') + path/////n/////n if (isCanvas || isSymbols || !isProd) {/////n const API_TOKEN = 'bb_ff64921b7b6dae704a352681c26ae5ed35c8143e18e13f2682cc2be1ab4ebb74'/////n options.headers.Authorization = 'Bearer ' + API_TOKEN/////n } else {/////n // const SESSION_ID = 's%3AwAm91jUNz7Bv3ihqvY9o4AJ_xQDTA6x3.VcEZyPFSdClTokzsu9n3gXULU1qp7pxSNCSEQBUhoIQ'/////n // options.headers.credentials = true/////n // options.headers.Authorization = 'Bearer ' + SESSION_ID/////n options.credentials = 'include'/////n options.mode = 'cors'/////n }/////n/////n if (data && (options.method === 'POST' || options.method === 'PUT')) {/////n options.body = JSON.stringify(data)/////n }/////n/////n console.log('Fetch request:', ENDPOINT, options)/////n const res = await window.fetch(ENDPOINT, options)/////n/////n if (!res.ok) {/////n const errorText = await res.text()/////n console.error('Failed to submit:', res.status, errorText)/////n throw new Error(/////tildeHTTP /////dlrsgn{res.status}: /////dlrsgn{errorText}/////tilde)/////n }/////n/////n // Check if response has content before parsing JSON/////n const contentType = res.headers.get('content-type')/////n if (contentType && contentType.includes('application/json')) {/////n return res.json()/////n }/////n/////n return res.text()/////n}", - "type": "function", - "err": {}, - "error": "SyntaxError: Unexpected end of input" - }, - "add": { - "key": "add", - "code": "export default async function addNew(item = 'network') {/////n const ROUTE = {/////n network: '',/////n node: '/' + this.state.protocol + '/node'/////n }/////n/////n const formData = new FormData(this.node)/////n let data = Object.fromEntries(formData)/////n if (item === 'node') {/////n data.projected_cost = parseInt(data.projected_cost)/////n console.log(data.projected_cost)/////n data = {/////n nodeType: data.nodeType,/////n nodeData: data/////n }/////n }/////n console.log(data)/////n/////n const res = await this.call('fetch', 'POST', ROUTE[item], data)/////n if (!res) return/////n/////n this.state.root.quietUpdate({/////n modal: null/////n })/////n/////n // console.log('here')/////n // console.log(this.state)/////n // console.log(res)/////n const ROUTES = {/////n network: '/network/' + data.protocol,/////n node: '/node/' + this.state.protocol + '/' + data.nodeType + '/' + res.uid/////n }/////n this.call('router', ROUTES[item] || '/', this.__ref.root)/////n this.node.reset()/////n}", - "type": "function", - "err": {}, - "error": "SyntaxError: Unexpected end of input" - }, - "read": { - "key": "read", - "code": "export default async function read(path) {/////n return await this.call('fetch', 'GET', path)/////n}", - "type": "function", - "error": null, - "err": {} - }, - "parseNetworkRow": { - "key": "parseNetworkRow", - "code": "export default function parseNetworkRow(data) {/////n const result = {/////n cloud_provider: new Set(),/////n env: new Set(),/////n node_types: new Set(),/////n status: new Set()/////n };/////n/////n // Parse validators/////n if (Array.isArray(data.validators) && data.validators.length > 0) {/////n result.node_types.add('Validator');/////n data.validators.forEach(validator => {/////n if (validator.cloud_provider) result.cloud_provider.add(validator.cloud_provider);/////n if (validator.env) result.env.add(validator.env);/////n if (validator.status) result.status.add(validator.status);/////n });/////n }/////n/////n // Parse rpc_nodes/////n if (Array.isArray(data.rpc_nodes) && data.rpc_nodes.length > 0) {/////n result.node_types.add('RPC');/////n data.rpc_nodes.forEach(node => {/////n if (node.cloud_provider) result.cloud_provider.add(node.cloud_provider);/////n if (node.env) result.env.add(node.env);/////n if (node.status) result.status.add(node.status);/////n });/////n }/////n/////n // Convert Sets to Arrays/////n return {/////n cloud_provider: Array.from(result.cloud_provider),/////n env: Array.from(result.env),/////n node_types: Array.from(result.node_types),/////n status: Array.from(result.status)/////n };/////n}", - "type": "function", - "error": "SyntaxError: Unexpected end of input" - }, - "getFleet": { - "key": "getFleet", - "code": "export default function getFleet () {\n // console.log(this, this.state, this.context)\n }", - "type": "function" - }, - "filterFleet": { - "key": "filterFleet", - "code": "export default function filterFleet() {/////n // Search filter/////n if (root.search) {/////n fleet = fleet.filter(item =>/////n item.protocol.toLowerCase().includes(root.search.toLowerCase()) ||/////n item.repo_url.includes(root.search)/////n )/////n }/////n/////n // Validators filter/////n if (root.validators) {/////n if (root.validators === 'Mainnet') {/////n fleet = fleet.filter(item =>/////n item.validator_info?.some(validator => validator.env === 'Mainnet')/////n )/////n } else if (root.validators === 'Testnet') {/////n fleet = fleet.filter(item =>/////n item.validator_info?.some(validator => validator.env === 'Testnet')/////n )/////n } else if (root.validators === 'None') {/////n fleet = fleet.filter(item =>/////n !item.validator_info?.length/////n )/////n }/////n // 'All' option doesn't need additional filtering/////n }/////n/////n // RPC filter/////n if (root.rpc) {/////n if (root.rpc === 'Mainnet') {/////n fleet = fleet.filter(item =>/////n item.rpc_info?.some(rpc => rpc.env === 'Mainnet')/////n )/////n } else if (root.rpc === 'Testnet') {/////n fleet = fleet.filter(item =>/////n item.rpc_info?.some(rpc => rpc.env === 'Testnet')/////n )/////n } else if (root.rpc === 'None') {/////n fleet = fleet.filter(item =>/////n !item.rpc_info?.length/////n )/////n }/////n // 'All' option doesn't need additional filtering/////n }/////n/////n // Status filter/////n if (root.status) {/////n fleet = fleet.filter(item => item.status === root.status)/////n }/////n/////n // Last Updated filter/////n if (root.lastUpdate && root.lastUpdate !== 'All Time') {/////n const now = new Date()/////n let targetDate = new Date()/////n/////n if (root.lastUpdate === 'Today') {/////n targetDate.setHours(0, 0, 0, 0) // Start of today/////n } else if (root.lastUpdate === 'Last 2 Days') {/////n targetDate.setDate(now.getDate() - 2)/////n targetDate.setHours(0, 0, 0, 0)/////n } else if (root.lastUpdate === 'Last 3 Days') {/////n targetDate.setDate(now.getDate() - 3)/////n targetDate.setHours(0, 0, 0, 0)/////n } else if (root.lastUpdate === 'Last Week') {/////n targetDate.setDate(now.getDate() - 7)/////n targetDate.setHours(0, 0, 0, 0)/////n } else if (root.lastUpdate === 'Last Month') {/////n targetDate.setMonth(now.getMonth() - 1)/////n targetDate.setHours(0, 0, 0, 0)/////n }/////n/////n fleet = fleet.filter(item => {/////n // Check logs in validator_info/////n const validatorLogs = item.validator_info?.flatMap(validator =>/////n validator.logs?.map(log => new Date(log.created_at)) || []/////n ) || []/////n/////n // Check logs in rpc_info/////n const rpcLogs = item.rpc_info?.flatMap(rpc =>/////n rpc.logs?.map(log => new Date(log.created_at)) || []/////n ) || []/////n/////n // Combine all logs/////n const allLogs = [...validatorLogs, ...rpcLogs]/////n/////n // Check if any log is newer than the target date/////n return allLogs.some(logDate => logDate >= targetDate)/////n })/////n }/////n}", - "type": "function", - "error": null - }, - "setInitialData": { - "key": "setInitialData", - "code": "export default function setInitialData(data = {}) {/////n this.state.replace(data, {/////n preventUpdate: true,/////n preventUpdateListener: true/////n })/////n/////n this.update({}, {/////n preventUpdateListener: true/////n })/////n}", - "type": "function", - "error": "SyntaxError: Unexpected end of input" - }, - "getStatusColor": { - "key": "getStatusColor", - "code": "export default function getStatusColor(status) {/////n const MAP = {/////n 'Live': 'green',/////n 'Stable/Maintenance': 'green',/////n 'Onboarding': 'yellow',/////n 'Off': 'gray'/////n }/////n/////n return MAP[status]/////n}", - "type": "function", - "error": "SyntaxError: Unexpected end of input", - "err": {} - }, - "auth": { - "key": "auth", - "code": "export default async function auth() {/////n if (this.state.root.success) {/////n if (window.location.pathname === '/') {/////n this.call('router', '/dashboard', this.__ref.root)/////n }/////n } else {/////n if (window.location.pathname === '/') {/////n const res = await this.call('fetch', 'GET', '', null, {/////n route: '/auth/me',/////n })/////n/////n if (res.success) {/////n this.state.root.update(res)/////n this.call('router', '/dashboard', this.__ref.root)/////n }/////n return res/////n } else {/////n this.call('router', '/', this.__ref.root)/////n }/////n }/////n}", - "type": "function", - "err": {}, - "error": "SecurityError: Failed to read a named property 'eval' from 'Window': Blocked a frame with origin \"https://symbols.app\" from accessing a cross-origin frame." - } - }, - "secrets": {}, - "designSystem": { - "COLOR": { - "black": "#000", - "white": "#fff" - }, - "GRADIENT": {}, - "THEME": { - "document": { - "@light": { - "color": "black", - "background": "white" - }, - "@dark": { - "color": "white", - "background": "black" - } - }, - "none": { - "color": "none", - "background": "none" - }, - "transparent": { - "color": "currentColor", - "background": "transparent" - } - }, - "FONT": {}, - "FONT_FAMILY": {}, - "TYPOGRAPHY": { - "base": 16, - "ratio": 1.25, - "subSequence": true, - "templates": {} - }, - "SPACING": { - "base": 16, - "ratio": 1.618, - "subSequence": true - }, - "TIMING": {}, - "CLASS": {}, - "GRID": {}, - "ICONS": {}, - "SHAPE": {}, - "RESET": {}, - "ANIMATION": {}, - "MEDIA": {}, - "CASES": {}, - "useReset": true, - "useVariable": true, - "useFontImport": true, - "useIconSprite": true, - "useSvgSprite": true, - "useDefaultConfig": true, - "useDocumentTheme": true, - "verbose": false - } - }, - "secrets": {}, - "__pending": { - "count": 0, - "uncommitted": false, - "etag": "1.7.378:0" - }, - "sharedLibraries": [], - "icon": null, - "branch": "main", - "latestVersion": "1.7.378", - "isLatest": true, - "projectInfo": { - "id": "6874baae0769df64f1a4484d", - "key": "big-brother.symbo.ls", - "name": "Bigbrother", - "updatedAt": "2025-12-21T20:11:56.172Z" - }, - "projectMeta": { - "id": "6874baae0769df64f1a4484d", - "key": "big-brother.symbo.ls", - "name": "Bigbrother", - "tier": "free", - "visibility": "public", - "access": "account", - "framework": "platform", - "language": "javascript", - "status": "active", - "isSharedLibrary": false, - "icon": null, - "createdAt": "2025-07-14T08:07:10.346Z", - "updatedAt": "2025-12-21T20:11:56.172Z" - }, - "owner": { - "id": "6868484c0cf470c5890933d5", - "username": "toko" - } -} \ No newline at end of file diff --git a/DOMQL_v2-v3_MIGRATION.md b/DOMQL_v2-v3_MIGRATION.md deleted file mode 100644 index 9375883..0000000 --- a/DOMQL_v2-v3_MIGRATION.md +++ /dev/null @@ -1,236 +0,0 @@ -# DOMQL v2 → v3 Migration Guide - -This guide covers the key changes when migrating from DOMQL v2 to v3. The main updates focus on flattening object structures, renaming properties, and simplifying the API while maintaining backwards compatibility for most use cases. - ---- - -## Inheritance changes - -### Flatten `props` and `on` - -**Before:** - -```js -{ - props: { - position: 'absolute', - }, - on: { - frame: (e, t) => {}, - render: e => {}, - wheel: (e, t) => {}, - dblclick: (e, t) => {}, - }, -} -``` - -**After:** - -```js -{ - position: 'absolute', - onFrame: (e, t) => {}, - onRender: e => {}, - onWheel: (e, t) => {}, - onDblclick: e => {}, -} -``` - -**Rules:** - -- Move all `props` entries one level up into the component object -- Applies to root and nested elements -- Remove the `on` wrapper -- Prefix each event with `on` + `CapitalizedEventName` - ---- - -### Merging `props` inside nested elements - -**Before:** - -```js -Box: { - props: { - '--section-background': '#7a5e0e55', - id: 'editorjs' - } -} -``` - -**After:** - -```js -Box: { - '--section-background': '#7a5e0e55', - id: 'editorjs' -} -``` - -- Remove the `props` wrapper and move contents up - ---- - -### All nested objects within `props` stay inline - -**Before:** - -```js -props: { - style: { - height: "100%"; - } -} -``` - -**After:** - -```js -style: { - height: "100%"; -} -``` - ---- - -## Naming changes - -### Rename `extend` → `extends` - -### Rename `childExtend` → `childExtends` - -**Before:** - -```js -extend: SomeComponent, -childExtend: AnotherComponent -``` - -**After:** - -```js -extends: SomeComponent, -childExtends: AnotherComponent -``` - -- Property rename only -- No behavioral changes - ---- - -## Functional changes - -Child elements are created **only if**: - -- `children` array is present, **or** -- Child component keys start with `CapitalCase` - -Event logic and all other JavaScript behavior remain unchanged. - -### Element creation rules - -**Before (v2):** - -```js -{ - div: {}, // creates
- Div: {}, // creates
-} -``` - -**After (v3):** - -```js -{ - div: {}, // treated as a plain property, no rendering - Div: {}, // only way to create keyed children -} -``` - -- `Div` is equivalent to: - -```js -Div: { extends: 'Div' } -``` - -- Rendering behavior: - - React → `
` - - HTML → `
` - ---- - -## Summary - -DOMQL v3 removes DOMQL-specific wrappers and relies on flat, explicit object structures: - -- Removed: `props`, `on` -- Events are flattened and prefixed with `onX` -- `extend` → `extends` -- `childExtend` → `childExtends` -- Only `CapitalCase` keys create child elements - ---- - -## More examples - -### From (v2) - -```js -{ - extend: 'Flex', - childExtend: 'ListItem', - props: { - position: 'absolute', - }, - attr: { - 'gs-w': 1, - 'gs-h': 1, - }, - SectionTitle: { - text: 'Notes', - }, - Box: { - props: { - '--section-background': '#7a5e0e55', - id: 'editorjs', - '& a': { - color: 'blue', - }, - }, - on: { - frame: (e, t) => {}, - render: e => {}, - wheel: (e, t) => {}, - dblclick: (e, t) => { e.stopPropagation() }, - }, - }, -} -``` - -### To (v3) - -```js -{ - extends: 'Flex', - childExtends: 'ListItem', - position: 'absolute', - attr: { - 'gs-w': 1, - 'gs-h': 1, - }, - SectionTitle: { - text: 'Notes', - }, - Box: { - '--section-background': '#7a5e0e55', - id: 'editorjs', - '& a': { - color: 'blue', - }, - onFrame: (e, t) => {}, - onRender: e => {}, - onWheel: (e, t) => {}, - onDblclick: e => { e.stopPropagation() }, - }, -} -``` diff --git a/SYMBOLS_LOCAL_INSTRUCTIONS.md b/SYMBOLS_LOCAL_INSTRUCTIONS.md deleted file mode 100644 index 48b00e9..0000000 --- a/SYMBOLS_LOCAL_INSTRUCTIONS.md +++ /dev/null @@ -1,1399 +0,0 @@ -# DOMQL/Symbols Project Structure - AI Instructions - -## Overview - -This is a strict, environment-agnostic folder structure designed for the Symbols platform. It generates independent files without JavaScript module preloading, enabling seamless rendering across all environments: VSCode, file structure, Symbols platform, server rendering, and web browsers. - -## Core Principle - -**NO JavaScript imports/exports for component usage.** Components are registered once in their respective folders and reused through a declarative configuration tree using object notation. - ---- - -## Folder Structure & Export Patterns - -### 1. **Components** (`/smbls/components/`) - -**File Pattern:** - -```javascript -// components/Header.js -export const Header = { - extends: "Flex", // Base component type - minWidth: "G2", - padding: "A", - // Props and styling applied directly - no 'props' wrapper - - // Nested children as properties - Search: { - extends: "Input", - flex: 1, - }, - Avatar: { - extends: "Image", - boxSize: "B", - }, -}; -``` - -**Key Rules:** - -- Each component is a named export: `export const ComponentName = { ... }` -- Component name MUST match filename (PascalCase) -- Contains declarative object structure with nested child components -- Can reference other components by name in the tree without imports -- Props use design system tokens (e.g., `minWidth: 'G2'`, `padding: 'A'`) - -**Usage in Components Tree:** - -```javascript -{ - Header: {}, // No import needed, referenced by name - Content: { - Article: {}, - } -} -``` - ---- - -### 2. **Pages** (`/smbls/pages/`) - -**File Pattern:** - -```javascript -// pages/add-network.js (dash-case filename) -export const addNetwork = { - extends: "Page", // camelCase export name - width: "100%", - padding: "A", - - // Event handlers applied directly to properties - onRender: async (el, state) => { - await el.call("auth"); - }, - - Form: { - extends: "Box", - Input: { - extends: "Input", - placeholder: "Network name", - }, - }, -}; -``` - -**Key Rules:** - -- Filenames use dash-case (kebab-case): `add-network.js`, `edit-node.js`, `dashboard.js` -- Exports use camelCase: `export const addNetwork = { ... }`, `export const editNode = { ... }` -- Pages extend from 'Page' component -- Contain complete page structure with nested components -- Can call functions using `el.call('functionName')` - -**Usage:** -Pages are registered in [pages/index.js](smbls/pages/index.js) as a default export object mapping routes to pages. - ---- - -### 3. **Functions** (`/smbls/functions/`) - -**File Pattern:** - -```javascript -// functions/parseNetworkRow.js -export const parseNetworkRow = function parseNetworkRow(data) { - // Function logic - return processedData; -}; -``` - -**Key Rules:** - -- Each function is a named export matching filename (camelCase) -- Functions are pure, standalone utilities -- NO imports of other functions; reuse only through composition - -**Usage in Components:** - -```javascript -{ - Button: { - onClick: (el) => el.call("parseNetworkRow", data); - } -} -``` - -Functions are called via `element.call('functionName', ...args)` from within component handlers. - ---- - -### 4. **Methods** (`/smbls/methods/`) - -**File Pattern:** - -```javascript -// methods/formatDate.js -export const formatDate = function (date) { - return new Intl.DateTimeFormat().format(date); -}; -``` - -**Key Rules:** - -- Utility methods that extend element behavior -- Registered once, reused across all components - -**Usage in Components:** - -```javascript -{ - Button: { - onClick: (el) => el.methodName(args); - } -} -``` - -Methods are called directly on element instances: `element.methodName()` - ---- - -### 5. **Design System** (`/smbls/designSystem/`) - -**Structure:** - -Flat folder with individual files for each design aspect: - -``` -designSystem/ -├── index.js # Registry with namespaces -├── color.js # Color tokens -├── spacing.js # Spacing/sizing scale -├── typography.js # Typography definitions -├── grid.js # Grid system -├── theme.js # Theme definitions -├── font.js # Font definitions -├── icons.js # SVG icons (flat) -├── animation.js # Animation definitions -├── timing.js # Timing/duration values -└── ... # Other design tokens -``` - -**File Pattern:** - -```javascript -// designSystem/color.js -export default { - black: '#000', - white: '#fff', - primary: '#0066cc', - secondary: '#666666', -}; - -// designSystem/spacing.js -export default { - base: 16, - ratio: 1.618, -}; -``` - -**Key Rules:** - -- Each file exports a single design aspect as default object -- Organized by design concern: color, spacing, typography, grid, etc. -- Used in component properties through shorthand (e.g., `padding: 'A'`, `color: 'primary'`) - ---- - -### 6. **State** (`/smbls/state/`) - -**Structure:** - -Flat folder for state and data files: - -``` -state/ -├── index.js # Registry exporting all state -├── metrics.js # Metrics data -└── ... # Other state files -``` - -**File Pattern:** - -```javascript -// state/metrics.js -export default [ - { - title: "Status", - items: [{ caption: "Live", value: 14 }], - }, - // ... -]; -``` - -**state/index.js:** - -```javascript -import metrics from "./metrics.js"; - -export default { - metrics, - // ... -}; -``` - -**Key Rules:** - -- State organized in folder with separate files by concern -- Each file exports default object with related state -- No logic or methods - only data structures -- Used as `state` parameter in component event handlers -- Accessed and modified through: `state.propertyName` - -**Usage in Components:** - -```javascript -{ - Button: { - onClick: (el, state) => { - state.user.authenticated = true; - console.log(state.metrics.status); - }, - }, -} -``` - ---- - -### 7. **Variables** (`/smbls/vars.js`) - -**File Pattern:** - -```javascript -// vars.js -export default { - // Global constants and settings - APP_VERSION: "1.0.0", - API_BASE_URL: "https://api.example.com", -}; -``` - -**Key Rules:** - -- Global constants and application-wide settings -- Read-only values -- Reference in components: `onClick: (el, state) => { const url = state.root.vars.API_BASE_URL; }` - ---- - -### 8. **Config** (`/smbls/config.js`) - -**File Pattern:** - -```javascript -// config.js -export default { - useReset: true, - useVariable: true, - useFontImport: true, - useIconSprite: true, - useSvgSprite: true, - useDefaultConfig: true, - useDocumentTheme: true, - verbose: false, -}; -``` - -**Key Rules:** - -- Single default export with platform/application configuration -- Boolean flags for feature toggles -- Settings that control runtime behavior -- Used internally by the DOMQL runtime -- Affects how components are rendered and styled - ---- - -### 9. **Dependencies** (`/smbls/dependencies.js`) - -**File Pattern:** - -```javascript -// dependencies.js - Fixed version numbers -export default { - "chart.js": "4.4.9", - "fuse.js": "7.1.0", - lit: "3.1.0", - "ninja-keys": "1.2.2", -}; -``` - -**Key Rules:** - -- Maps external npm package names to **fixed version numbers** -- **NO module preloading** - packages defined here, imported on-demand -- Dynamic imports only within event handlers and functions via `await import()` -- Keeps structure resolvable without build step or package installation -- Version numbers must be exact and locked (no ranges like `^` or `~`) - -**Dynamic Import Pattern:** - -```javascript -{ - Button: { - onClick: async (element, state) => { - // Import at runtime when needed - const { Chart } = await import("chart.js"); - const chart = new Chart(element, { - /* config */ - }); - }; - } -} -``` - -This approach ensures: - -- No top-level imports clutter the file -- Packages loaded only when actually used -- Works in any environment (browser, server, file system) -- Declarative tree remains environment-agnostic -- Version consistency across environments - ---- - -### 10. **Files** (`/smbls/files/`) - -**File Pattern:** - -```javascript -// files/index.js - Asset manifest managed via Symbols SDK -// DO NOT manually edit this file -// To add files: Use Symbols SDK to upload, then place response data here - -export default { - // ... file data managed by SDK -}; -``` - -**Key Rules:** - -- Single default export mapping file keys to file metadata -- **Managed entirely via Symbols SDK** - do NOT manually create entries -- Use strict folder structure - -**Adding Files:** - -When the AI adds assets or files, it should use Symbols SDK commands to upload files and return related data to place in `files/`. - -**Usage in Components:** - -```javascript -{ - Image: { - src: 'Arbitrum.png', - // or rare occasions - src: (element, state, context) => context.files['Arbitrum.png'].content.src, - } -} -``` - ---- - -### 11. **Fonts** (`/smbls/designSystem/fonts.js`) - -**File Pattern:** - -```javascript -// designSystem/fonts.js - Fonts managed via Symbols SDK -// To add fonts: Upload via SDK, place response in this file - -export default { - // ... font data managed by SDK -}; -``` - -**Key Rules:** - -- Single default export with font definitions -- **Managed via Symbols SDK file upload** - don't add manually - -**Adding Fonts:** - -When adding fonts, use the SDK to upload and place the returned metadata in `designSystem/fonts.js`. - ---- - -### 12. **Icons** (`/smbls/designSystem/icons.js`) - -**File Pattern:** - -```javascript -// designSystem/icons.js - Flat SVG icon collection -export default { - logo: '', - - chevronLeft: - '', - - chevronRight: - '', - - search: - '', - - menu: '', - - close: - '', -}; -``` - -**Key Rules:** - -- **Completely flat structure** - no nesting or categorization -- Each icon is a named property with inline SVG string -- Icon names in camelCase: `chevronLeft`, `arrowUp`, `checkmark` -- SVG uses `stroke="currentColor"` or `fill="currentColor"` for styling -- No folder structure or category prefixes - -**Usage in Components:** - -```javascript -{ - Icon: { - name: 'chevronLeft', - }, - - Button: { - extends: 'Button', - icon: 'search', - }, -} -``` - ---- - -### 13. **Snippets** (`/smbls/snippets/`) - -**File Pattern:** - -```javascript -// snippets/index.js -export * from "./oneSnippet.js"; -export * from "./dataSnippet.js"; -``` - -**Key Rules:** - -- Reusable component or layout snippets -- Named exports of common patterns -- Used to reduce repetition of complex structures -- Can contain random snippets, JSON data, classes, functions, or any other reusable code -- **NOT** a component definition itself, but can be used within one - -**Example Snippet:** - -```javascript -// snippets/dataMockSnippet.js -export const dataMockSnippet = { - data: [], -}; -``` - -**Usage in Components:** - -```javascript -{ - List: { - // Reuse snippet pattern - children: (el, s, ctx) => el.getSnippet('dataMockSnippet').data - // or in rare occasions - children: (el, s, ctx) => ctx.snippets['dataMockSnippet'].data - }, - Title: { - text: 'Hello' - } -} -``` - ---- - -## Root Index Export (`/smbls/index.js`) - -**Pattern:** - -```javascript -// smbls/index.js -export { default as config } from "./config.js"; -export { default as vars } from "./vars.js"; -export { default as dependencies } from "./dependencies.js"; -export { default as designSystem } from "./designSystem/index.js"; -export { default as state } from "./state/index.js"; -export { default as files } from "./files/index.js"; -export { default as pages } from "./pages/index.js"; -export * as components from "./components/index.js"; -export * as snippets from "./snippets/index.js"; -export * as functions from "./functions/index.js"; -export * as methods from "./methods/index.js"; -``` - -**Key Rules:** - -- Central export point for entire project -- Exposes all modules with consistent naming -- Used by platform/framework to load the application -- Maintains structure for environment-agnostic loading - ---- - -## Import Restrictions by Environment - -### ✅ ALLOWED Imports - -1. **Within same folder** (component to component via tree) - - ```javascript - // No imports needed - reference by name - { - Header: { }, - Content: { Search: { } } - } - ``` - -2. **Function calls** - - ```javascript - el.call("functionName", args); - ``` - -3. **Method calls** - ```javascript - el.methodName(); - ``` - -### ❌ FORBIDDEN Imports - -```javascript -// DON'T DO THIS: -import { Header } from "./Header.js"; -import { parseNetworkRow } from "../functions/parseNetworkRow.js"; -import styles from "./style.css"; -``` - -No JavaScript `import`/`require` statements for components, functions, or methods within the project structure. - ---- - -## File Structure Rules - -### Naming Conventions - -| Location | Filename | Export | Type | -| --------------- | -------------------- | ----------------------------------------------- | --------------------- | -| `components/` | `Header.js` | `export const Header = { }` | PascalCase | -| `pages/` | `add-network.js` | `export const addNetwork = { }` | dash-case / camelCase | -| `functions/` | `parseNetworkRow.js` | `export const parseNetworkRow = function() { }` | camelCase | -| `methods/` | `formatDate.js` | `export const formatDate = function() { }` | camelCase | -| `designSystem/` | `color.js` | `export default { }` | camelCase | -| `snippets/` | `cardSnippet.js` | `export const cardSnippet = { }` | camelCase | -| `state/` | `metrics.js` | `export default [ ]` | camelCase | -| Root | `vars.js` | `export default { }` | camelCase | -| Root | `config.js` | `export default { }` | camelCase | -| Root | `dependencies.js` | `export default { }` | camelCase | -| Root | `files.js` | `export default { }` | camelCase | - -### Index Files & Root Exports - -**[components/index.js](smbls/components/index.js):** - -```javascript -export * as ComponentName from "./ComponentName.js"; -``` - -**[functions/index.js](smbls/functions/index.js):** - -```javascript -export * from "./functionName.js"; -``` - -**[pages/index.js](smbls/pages/index.js):** - -```javascript -import { main } from "./main"; -import { addNetwork } from "./add-network"; -import { editNode } from "./edit-node"; - -export default { - "/": main, - "/add-network": addNetwork, - "/edit-node": editNode, - // ... route mappings -}; -``` - -**[snippets/index.js](smbls/snippets/index.js):** - -```javascript -export * from "./snippet1.js"; -export * from "./snippet2.js"; -``` - -**[designSystem/index.js](smbls/designSystem/index.js):** - -```javascript -import ANIMATION from "./animation.js"; -import COLOR from "./color.js"; -import SPACING from "./spacing.js"; -// ... all design system modules - -export { ANIMATION, COLOR, SPACING }; - -export default { - ANIMATION, - COLOR, - SPACING, - // ... -}; -``` - ---- - -## Component Definition Template - -```javascript -export const ComponentName = { - // Extend from base component type - extends: "Flex", // or 'Box', 'Page', etc. - - // Props: styling, behavior, event handlers - padding: "A", - theme: "dialog", - onClick: (el, s) => el.call("handleClick"), - onRender: async (el, s) => await el.call("fetchData"), - - // Nested child components (no imports) - Header: {}, - Content: { - Article: { - Title: { text: "Hello" }, - }, - }, - Footer: {}, -}; -``` - ---- - -## Component References: PascalCase Keys - -**PascalCase keys automatically create components of that type.** You don't need to import them or use `extends:` when the key name matches the component name. - -### ❌ INCORRECT: Verbose with imports and extends - -```javascript -import { UpChart } from "./UpChart.js"; -import { PeerCountChart } from "./PeerCountChart.js"; - -export const Graphs = { - UpChart: { extends: UpChart, props: { order: "1" } }, - PeerCountChart: { extends: PeerCountChart, props: { flex: "1" } }, -}; -``` - -**Problems:** - -- Unnecessary imports clutter the file -- Verbose `extends:` and `props:` wrapper -- Redundant when key name matches component name - -### ✅ CORRECT: Clean PascalCase references - -```javascript -export const Graphs = { - UpChart: { order: "1" }, - PeerCountChart: { flex: "1" }, -}; -``` - -**Key Rules:** - -- **Key name determines component type:** `UpChart:` automatically creates an `UpChart` component -- **No imports needed:** Component is referenced by name, not imported -- **Flatten props directly:** Put props inline, not in a `props:` wrapper -- **No `extends:` needed:** The key name is implicit `extends: ComponentName` -- **Works in all contexts:** Top-level, nested, or inside functions - -### Example: Rows of Charts - -```javascript -export const Graphs = { - extends: "Flex", - - Row1: { - extends: "Flex", - flow: "x", - gap: "A", - - // Each chart component is created by its PascalCase key - LatestBlockChart: { flex: "1" }, - SyncingChart: { flex: "1" }, - BlocksToSyncChart: { flex: "1" }, - }, - - Row2: { - extends: "Flex", - flow: "x", - gap: "A", - - PeerCountChart: { flex: "1" }, - NetListeningChart: { flex: "1" }, - }, -}; -``` - -This approach keeps component definitions clean and readable while maintaining the declarative architecture principle. - ---- - -## Event Handlers - -```javascript -{ - Button: { - onClick: (element, state) => { - // Call function: el.call('functionName', ...args) - element.call("parseData", rawData); - - // Call method: el.methodName() - element.format(); - - // Update state: state.property = value - state.count++; - - // Use design system tokens in inline styles - element.style.padding = "A"; // From designSystem/spacing - }; - } -} -``` - ---- - -## Component Scope: Local Functions - -When you need to define local helper functions within a component that are not meant for global reuse, use the `scope` property instead of importing external functions. This keeps the component self-contained and avoids creating unnecessary top-level imports. - -### ❌ INCORRECT: Importing Local Functions - -```javascript -// functions/fetchMetrics.js - If only used in one component! -const fetchMetrics = (timeRange) => { - // fetch logic -}; - -// components/Graphs.js -import { fetchMetrics } from "../functions/fetchMetrics.js"; // WRONG! - -export const Graphs = { - extends: "Box", - onInit: (el) => { - fetchMetrics("week"); - }, -}; -``` - -**Problems:** - -- Creates unnecessary separate files for component-specific logic -- Mixes component-specific and globally-reusable code -- Adds imports where they should be avoided - -### ✅ CORRECT: Use Component Scope - -```javascript -// components/Graphs.js -export const Graphs = { - extends: "Box", - - // Define local functions in scope property - scope: { - fetchMetrics: (timeRange) => { - // fetch logic here - not exported, only used locally - return fetch(`/api/metrics?range=${timeRange}`).then((res) => res.json()); - }, - - calculateAverage: (data) => { - return data.reduce((a, b) => a + b, 0) / data.length; - }, - }, - - // Use scope functions in event handlers - onInit: (el) => { - const metrics = el.scope.fetchMetrics("week"); - const avg = el.scope.calculateAverage(metrics); - }, - - Button: { - onClick: (el) => { - el.scope.fetchMetrics("month"); - }, - }, -}; -``` - -**Key Rules for Scope:** - -- `scope` property contains **local helper functions** for this component only -- Functions in scope are **never exported** and **never imported elsewhere** -- Access scope functions via `el.scope.functionName()` -- Use `scope` only for component-specific logic; use `functions/` for reusable utilities -- Scope functions receive only the data they need (element context is available via `el`) - -### Scope vs Functions Folder - -**Use `scope` when:** - -- Function is only used within one component -- Function is a helper for that specific component's logic -- Keeping code co-located improves readability - -**Use `functions/` when:** - -- Function is reused across multiple components -- Function is a general utility (parsing, calculations, data fetching patterns) -- Function can be tested independently - -### Example: Multiple Scope Functions - -```javascript -export const MetricsPage = { - extends: "Page", - - scope: { - // Local helpers - fetchMetrics: async (type) => { - const res = await fetch(`/api/metrics/${type}`); - return res.json(); - }, - - formatMetric: (value) => { - return new Intl.NumberFormat().format(value); - }, - - filterByEnvironment: (data, env) => { - return data.filter((item) => item.environment === env); - }, - }, - - onInit: async (el, state) => { - const data = await el.scope.fetchMetrics("daily"); - state.metrics = el.scope.filterByEnvironment(data, "production"); - }, - - MetricsChart: { - onRender: (el, state) => { - const formatted = state.metrics.map((m) => - el.scope.formatMetric(m.value), - ); - el.data = formatted; - }, - }, -}; -``` - -### Calling Global Functions from Scope - -When a scope function needs to call a global function from the `functions/` folder, use `el.call()` instead of importing. **Pass `el` as the first parameter to the scope function so it has access to element methods.** - -**Pattern:** - -```javascript -export const Graphs = { - extends: "Flex", - - scope: { - // Scope function receives el as first parameter - fetchMetrics: (el, s, timeRange) => { - const networkName = (s.protocol || "").toLowerCase(); - - s.update({ metricsLoading: true }); - - // Call global function via el.call() - no import needed - el.call("apiFetch", "POST", "/api/metrics", { - networkName, - timeRangeMinutes: timeRange || 5, - }) - .then((data) => { - s.update({ metricsData: data, metricsLoading: false }); - }) - .catch((err) => { - console.error("Failed to fetch:", err); - s.update({ metricsLoading: false }); - }); - }, - }, - - onInit: (el, s) => { - // Pass el as first argument when calling scope function - el.scope.fetchMetrics(el, s, 5); - }, -}; -``` - -**Key Rules:** - -- **No imports needed** - Call global functions via `el.call('functionName', ...args)` -- **Pass `el` as first parameter** - Scope functions need element access to call `el.call()` -- **Update all call sites** - When calling the scope function, pass `el` as first argument -- **Keep scope for local logic only** - Use this pattern only in scope functions that need global utilities -- **No async/await in el.call()** - Handle promises with `.then()` and `.catch()` - ---- - -## Environment Compatibility - -This structure works seamlessly in: - -- **VSCode** - Individual files can be edited independently -- **Symbols Platform** - Declarative trees render directly -- **File Structure** - Pure JavaScript, no build step required -- **Server Rendering** - Stateless object definitions -- **Web Browser** - DOMQL runtime interprets the tree - -**No special handling needed** - the same files work everywhere. - ---- - -## DO's and DON'Ts - -### ✅ DO: - -- Name files exactly as their export names -- Use declarative object syntax for all components and pages -- Components MUST be plain JavaScript objects, never functions -- Reference components by name within the tree -- Use `el.call('functionName')` for utilities -- Use design system shorthand in props -- Keep components stateless (state managed externally) -- Place all component logic into props handlers -- Keep all folders flat - no subfolders within components/, functions/, etc. - -### ❌ DON'T: - -- Import components as JavaScript modules -- Use `import/require` for project files -- Create class-based components -- **Use component functions** - components must be objects, never callable functions -- Mix framework-specific code (React, Vue, etc.) -- Mutate element directly (use state instead) -- Create circular dependencies between files -- Use default exports for components (use named exports) -- **Create subfolders** - Anti-pattern to have `components/charts/`, `functions/api/`, etc. All files must be flat - ---- - -## Flat Folder Structure (No Subfolders) - -All folders must remain completely flat. Creating subfolders is an anti-pattern that breaks the architecture. - -### ❌ INCORRECT: Organizing with Subfolders - -```javascript -// WRONG - Don't do this! -components/ - ├── charts/ - │ ├── LineChart.js - │ ├── BarChart.js - │ └── PieChart.js - ├── forms/ - │ ├── LoginForm.js - │ ├── RegisterForm.js - │ └── ContactForm.js - └── Button.js - -functions/ - ├── api/ - │ ├── fetchUser.js - │ ├── fetchPosts.js - │ └── deleteUser.js - ├── math/ - │ └── calculateCosts.js - └── ... -``` - -**Problems:** - -- Import paths become complex and variable -- File registry in `index.js` becomes harder to maintain -- Path resolution differs between environments -- Contradicts flat, declarative principle -- Makes search and discovery harder - -### ✅ CORRECT: Completely Flat Structure - -```javascript -// RIGHT - Always do this -components/ - ├── index.js - ├── Header.js - ├── Button.js - ├── LineChart.js - ├── BarChart.js - ├── PieChart.js - ├── LoginForm.js - ├── RegisterForm.js - ├── ContactForm.js - └── ... - -functions/ - ├── index.js - ├── parseNetworkRow.js - ├── calculateCosts.js - ├── fetchUser.js - ├── fetchPosts.js - ├── deleteUser.js - └── ... -``` - -**Advantages:** - -- Simple, predictable file structure -- Consistent import paths: `./ComponentName.js` -- Easy to maintain and search -- Works identically in all environments -- Clear naming eliminates need for folders -- All files registered at the same level in `index.js` - -### Naming Strategy Instead of Folders: - -Use **descriptive filenames** instead of subfolders to organize conceptually-related files: - -```javascript -// components/ - Chart-related components -ChartContainer.js; -LineChart.js; -BarChart.js; -PieChart.js; -ChartTooltip.js; -ChartLegend.js; - -// functions/ - Function-related APIs -fetchUserProfile.js; -fetchUserPosts.js; -deleteUserAccount.js; -createNewPost.js; -``` - -Each filename clearly indicates its purpose without needing folder organization. - ---- - -## Components are Objects, Not Functions - -This is a critical architectural principle. Components must ALWAYS be plain JavaScript objects, never functions. - -### ❌ INCORRECT: Component as a Function - -```javascript -// WRONG - Don't do this! -export const Header = (element, state) => ({ - border: "1px solid black", - padding: "A", - Title: { - text: "Hello", - }, -}); -``` - -**Problems:** - -- Runtime recalculation on every access -- Stateful behavior contradicts declarative principle -- Cannot be properly registered and cached -- Breaks environment-agnostic loading - -### ✅ CORRECT: Component as a Plain Object - -```javascript -// RIGHT - Always do this -export const Header = { - border: "1px solid black", - padding: "A", - Title: { - text: "Hello", - }, - onClick: (element, state) => { - // Logic goes in handlers, not in the object definition - state.count++; - }, -}; -``` - -**Why:** - -- Static, declarative definition -- Resolvable without runtime execution -- Cacheable and registrable -- Works in all environments (VSCode, server, browser, Symbols platform) -- Handlers can contain logic at runtime - -### Key Point: - -**Only property values can be functions (event handlers, getters), never the component itself.** - ---- - -## Example: Building a Feature - -### Task: Create a User Profile Card Component - -**1. Create** `components/UserCard.js`: - -```javascript -export const UserCard = { - extends: "Box", - padding: "A2", - round: "A", - background: "white", - boxShadow: "0 2px 8px rgba(0,0,0,0.1)", - Avatar: { - boxSize: "C", - }, - Name: { - fontSize: "L", - fontWeight: "600", - }, - Email: { - fontSize: "S", - color: "caption", - }, - Actions: { - Edit: { - onClick: (el) => el.call("editUser"), - }, - Delete: { - onClick: (el) => el.call("deleteUser"), - }, - }, -}; -``` - -**2. Create** `functions/editUser.js`: - -```javascript -export const editUser = function (userId) { - return fetch(`/api/users/${userId}`, { method: "GET" }).then((res) => - res.json(), - ); -}; -``` - -**3. Use in page** `pages/profile.js`: - -```javascript -export const profile = { - extends: "Page", - UserCard: { - // No import needed! - }, -}; -``` - -**4. Register in** `components/index.js`: - -```javascript -export * as UserCard from "./UserCard.js"; -``` - ---- - -## Summary - -This is a **strict, declarative architecture** where: - -1. **Each file exports one independent object** (component, function, or method) -2. **No JavaScript imports between project files** - Everything is registered in index files -3. **Components reference each other by name** in the object tree -4. **Functions/methods are called at runtime** via `element.call()` and `element.method()` -5. **Design tokens are available globally** through shorthand notation -6. **Works everywhere** - VSCode, Symbols platform, servers, browsers - without modification - -The design enables **framework-agnostic, environment-independent code** while maintaining **strict structure and conventions**. - ---- - -## Complete Project Structure Overview - -``` -smbls/ -├── index.js # Root export (central loader) -├── vars.js # Global variables/constants -├── config.js # Platform configuration -├── dependencies.js # External npm packages (fixed versions) -│ -├── components/ # UI Components (PascalCase files) -│ ├── index.js # Component registry -│ ├── Header.js -│ ├── Button.js -│ ├── Modal.js -│ └── ... -│ -├── pages/ # Page layouts (dash-case files) -│ ├── index.js # Route mapping -│ ├── main.js -│ ├── add-network.js # dash-case filename, camelCase export -│ ├── edit-node.js -│ ├── dashboard.js -│ └── ... -│ -├── functions/ # Utility functions (camelCase) -│ ├── index.js # Function exports -│ ├── parseNetworkRow.js -│ ├── calculateCosts.js -│ └── ... -│ -├── methods/ # Element methods (camelCase) -│ ├── index.js -│ └── ... -│ -├── state/ # State and data (flat folder) -│ ├── index.js # State registry -│ ├── metrics.js -│ ├── user.js -│ ├── fleet.js -│ └── ... -│ -├── files/ # File assets (flat folder) -│ ├── index.js # Files registry -│ ├── images.js -│ ├── logos.js -│ └── ... -│ -├── designSystem/ # Design tokens (flat folder) -│ ├── index.js # Token registry with namespaces -│ ├── color.js # Color tokens -│ ├── spacing.js # Spacing/sizing scale -│ ├── typography.js # Typography definitions -│ ├── grid.js # Grid system -│ ├── theme.js # Theme definitions -│ ├── fonts.js # Font definitions -│ ├── icons.js # SVG icons (flat) -│ ├── animation.js # Animation definitions -│ ├── timing.js # Timing values -│ └── ... -│ -└── snippets/ # Reusable code/data (any type) - ├── index.js # Snippet registry - ├── cardSnippet.js # Component snippets - ├── mockData.js # Mock data - ├── constants.js # Constants/enums - ├── utils.js # Utility functions - ├── response.json # Response examples - └── ... -``` - -**Key Structural Points:** - -- `state/` and `files/` are **folders with index.js registry**, not single files -- All folders are **completely flat** - no subfolders within any folder -- `designSystem/` is completely flat with separate files for each design aspect -- `snippets/` can contain ANY type of JavaScript: components, functions, data, JSON -- Clear naming conventions eliminate need for folder organization - ---- - -## Zero Compilation Principle - -**This structure is completely resolvable without any build step, compilation, or bundling.** - -### Why It Works Everywhere: - -1. **Pure JavaScript Objects** - No JSX, no preprocessing, just plain object literals -2. **Dynamic Imports Only in Handlers** - NPM packages imported via `await import()` inside event listeners, never at module level -3. **No Circular Dependencies** - Declarative tree structure prevents cyclic imports -4. **Stateless by Design** - Each file is independent; state managed externally -5. **Direct File Resolution** - No alias resolution needed; files imported directly by path - -### Environment Loading: - -- **VSCode:** Each `.js` file is a standalone module; syntax check works immediately -- **Symbols Platform:** Parses the entire tree; renders components declaratively -- **Browser:** DOMQL runtime loads and interprets the tree; imports handled by browser `import()` -- **Server:** Node.js executes the tree; dynamic imports work natively -- **File System:** Can be read and processed as plain JavaScript objects - -**Result:** The exact same files work in all environments without modification. - ---- - -## Best Practices Summary - -### ✅ Best Practices: - -- **One export per file** - Keeps structure clear and resolvable -- **Declarative over imperative** - Use object structures, not code execution -- **Import dependencies dynamically** - Only when needed in event handlers -- **Reference components by name** - The tree handles all resolution -- **Keep functions pure** - No side effects, only transformations -- **Store state separately** - Never mutate component definitions -- **Use design tokens** - Never hardcode styles or values -- **Keep files small** - Easy to read, test, and maintain - -### ❌ Anti-Patterns: - -- Top-level `import`/`require` of project files -- Class-based components or inheritance -- Framework-specific syntax (React hooks, Vue composables) -- Circular module dependencies -- Hardcoded values in component definitions -- Dynamic property names in object keys -- Side effects in function definitions - ---- - -## Migration Guide: Converting Old Projects - -If converting from a standard module-based architecture: - -1. **Remove all imports** of local project modules -2. **Convert all components to plain objects** - No function wrappers, no factory functions -3. **Convert props to declarative objects** - No function closures, just data -4. **Move state to `state/` folder** - Centralize all mutable data -5. **Replace function calls with `el.call()`** - Dynamic dispatch instead of imports -6. **Move dynamic dependencies to handlers** - Use `await import()` inside events -7. **Rename components to PascalCase** - Consistent with component convention -8. **Update index files** - Register all exports centrally - -Example transformation: - -```javascript -// OLD (Framework style) -import Button from "./Button.js"; -import { handleClick } from "./handlers.js"; - -export const Header = () => { - return