Spotlight... K
border-black border-solid border-2border-black border-solid border-2 Poem Database - Remove + Add 0some text 1some text 2some text 3some text 4some text 5some text 6some text 7some text 8some text 9some text 10some text 11some text Debug blockTree["Ax0"].block.store; Zoom Poems 1 of 12 < Previous Next > Just playing into the future
What Alex asked me to do
How you are describing it is actually counterintuitive
I want it simple
But what does simple mean?
Let’s get rid of all this fluff down here
A user should never have a dead end
Let’s not apologize for anything
And that’s the way it works
I see your point
Some of this stuff doesn’t make sense
Go to developement world 🌎 Go to designer world 🌎 Guest Book Add your name to the guest book if you gave us a visit! Go to debugger world 🌎 Go to the Peoplevine world 🌿 Playground.blocks.world-block.js export default class World extends Block { tagName = "world-block"; render = ` <style> .world { position: relative; width: 100vw; height: 100vh; overflow: hidden; } .world::before { z-index: -1; content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; text-align: center; background: white; /*#22333b*/; } .space { position: absolute; top: 0; left: 0; width: 4096px; height: 4096px; background-size: 32px 32px; background-image: linear-gradient( to right, rgba(40, 126, 255, 0.1) 1px, transparent 1px ), linear-gradient(to bottom, rgba(40, 126, 255, 0.1) 1px, transparent 1px); transform: translate3d(0, 0, 0); } </style> <div class="world"> <div class="space"> <slot> </div> </div> `; connectedCallback() { super.connectedCallback(); this.rootElement = true; this.shadowWorld = this.shadowRoot.querySelector(".world"); this.space = this.shadowRoot.querySelector(".space"); this.buildBlockTree(); this.store.meta = false; const playerId = localStorage.getItem("playerId") || crypto.randomUUID(); localStorage.setItem("playerId", playerId); this.store.playerId = playerId; this.observer = new MutationObserver((record) => { const hasBlocks = (arr) => Array.isArray(arr) && arr.some((el) => el.tagName?.toLowerCase().endsWith("block")); const added = record .map((r) => Array.from(r.addedNodes)) .filter(hasBlocks) .flat(2); const removed = record .map((r) => Array.from(r.removedNodes)) .filter(hasBlocks) .flat(2); if (![...added, ...removed].length) { return; } this.buildBlockTree(added, removed); }); this.observer.observe(this, { childList: true, subtree: true, }); /* this.saveObserver.observe(this, { childList: true, subtree: true, attributes: true, }); */ this.scrollX = Number(this.getAttribute("scroll-x")) || 0; this.scrollY = Number(this.getAttribute("scroll-y")) || 0; this.shadowWorld.addEventListener("wheel", this.handleScroll.bind(this), { passive: false, }); if (!this.hasAttribute("scroll-x") && !this.hasAttribute("scroll-y")) { this.scrollX = (this.space.offsetWidth - this.shadowWorld.offsetWidth) / 2; this.scrollY = (this.space.offsetHeight - this.shadowWorld.offsetHeight) / 2; this.setAttribute("scroll-x", this.scrollX); this.setAttribute("scroll-y", this.scrollY); } this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px)`; this.isDragging = false; /*this.shadowWorld.addEventListener( "touchstart", this.handleTouchStart.bind(this), ); this.shadowWorld.addEventListener( "touchmove", this.handleTouchMove.bind(this), ); this.shadowWorld.addEventListener( "touchend", this.handleTouchEnd.bind(this), );*/ this.shadowWorld.addEventListener("click", this.click); document.addEventListener("keyup", (e) => { this.store.meta = false; }); // Command+K to open spotlight document.addEventListener("keydown", (e) => { if (e.metaKey || e.ctrlKey) { this.store.meta = true; } if ( e.keyCode === 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) { // CMD+S / CTRL+S e.preventDefault(); } if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); if (!document.querySelector("spotlight-block")) { const spotlight = document.createElement("spotlight-block"); spotlight.setAttribute("fixed", ""); document.body.appendChild(spotlight); } else { const spotlight = document.querySelector("spotlight-block"); spotlight.remove(); } } }); } disconnectedCallback() { super.disconnectedCallback && super.disconnectedCallback(); this.observer && this.observer.disconnect(); } isAncestorScrollable(el) { let node = el; while (node && node !== document.body) { const hasScrollableContent = node.scrollHeight > node.clientHeight; const overflowYStyle = window.getComputedStyle(node).overflowY; const isOverflowHidden = overflowYStyle.indexOf("hidden") !== -1; const canScroll = hasScrollableContent && !isOverflowHidden; if (canScroll) { return true; } node = node.parentElement; if (node && node.tagName === "world-block") return false; } return false; } handleScroll(e) { e.preventDefault(); if (this.isAncestorScrollable(e.target)) { //return; } if (e.target.tagName === "code-block") return; // Don't scroll if context menu is open const spotlightBlock = document.querySelector("spotlight-block"); const menuBlocks = document.querySelectorAll("menu-block"); if (menuBlocks.length || spotlightBlock) return; if (e.ctrlKey) { const scale = this.scale || 1; const delta = -e.deltaY * 0.001; const newScale = Math.max(0.1, Math.min(3, scale + delta)); this.scale = newScale; this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px) scale(${newScale})`; return; } const newScrollX = this.scrollX + e.deltaX; const newScrollY = this.scrollY + e.deltaY; const maxScrollX = this.space.offsetWidth - this.shadowWorld.offsetWidth; const maxScrollY = this.space.offsetHeight - this.shadowWorld.offsetHeight; this.scrollX = Math.max(0, Math.min(newScrollX, maxScrollX)); this.scrollY = Math.max(0, Math.min(newScrollY, maxScrollY)); // Update attributes this.setAttribute("scroll-x", this.scrollX); this.setAttribute("scroll-y", this.scrollY); this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px) ${ this.scale ? `scale(${this.scale})` : "" }`; } handleTouchStart(event) { event.preventDefault(); this.isDragging = true; this.lastTouchX = event.touches[0].clientX; this.lastTouchY = event.touches[0].clientY; } handleTouchMove(e) { if (!this.isDragging) return; e.preventDefault(); const touch = e.touches[0]; const deltaX = this.lastTouchX - touch.clientX; const deltaY = this.lastTouchY - touch.clientY; this.updateScroll(deltaX, deltaY); this.lastTouchX = touch.clientX; this.lastTouchY = touch.clientY; } handleTouchEnd() { this.isDragging = false; } updateScroll(deltaX, deltaY) { const newScrollX = this.scrollX + deltaX; const newScrollY = this.scrollY + deltaY; const maxScrollX = this.space.offsetWidth - this.shadowWorld.offsetWidth; const maxScrollY = this.space.offsetHeight - this.shadowWorld.offsetHeight; this.scrollX = Math.max(0, Math.min(newScrollX, maxScrollX)); this.scrollY = Math.max(0, Math.min(newScrollY, maxScrollY)); this.setAttribute("scroll-x", this.scrollX); this.setAttribute("scroll-y", this.scrollY); this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px)`; } click() { const toolbars = document.querySelectorAll("toolbar-block"); toolbars.forEach((toolbar) => toolbar.remove()); } nextSpreadsheetLetter(char) { let carry = 1; let res = ""; for (let i = char.length - 1; i >= 0; i--) { let charCode = char.charCodeAt(i) - 65 + carry; if (charCode === 26) { res = "A" + res; carry = 1; } else { res = String.fromCharCode((charCode % 26) + 65) + res; carry = 0; } } if (carry === 1) res = "A" + res; return res; } buildBlockTree(added, removed) { /* if (window.blockTree) { if (added) { for (let el of added) { const parent = Object.values(window.blockTree).find((node) => node.block === added); console.log(parent); } } return; } */ //console.time("build-block-tree"); let currentNode = null; let rowCounts = { A: 0, }; let todo = [ { parent: null, position: "Ax0", block: this, }, ]; window.blockTree = { Ax0: { block: this }, }; while (todo.length) { currentNode = todo.shift(); let [row] = currentNode.position.split("x"); const childRow = this.nextSpreadsheetLetter(row); for (let child of Array.from(currentNode.block.childNodes || [])) { if (!child.tagName?.toLowerCase().endsWith("block")) { continue; } const childColumn = ++rowCounts[childRow] || (rowCounts[childRow] = 1); const node = { parent: currentNode.position, position: childRow + "x" + childColumn, block: child, }; const { position: _, ...nodeWithoutPosition } = node; window.blockTree[node.position] = nodeWithoutPosition; if (child.childNodes) { todo.push(node); } } } //console.timeEnd("build-block-tree"); } } Kit Browser Zoom PoemsPlaygroundPeoplevineFirst Spin blocksscripts Create Block Create WorldKit BrowserTextareaCursorWindowTextFirst Spin PrototypeFlexSpotlightDropdownSceneRepeatTinaLinkInputMenu ItemSpotlight NavHighlightScript LoggerStage ManagerCodeImageToolbarButtonMenu Playground.blocks.world-block.js export default class World extends Block { tagName = "world-block"; render = ` <style> .world { position: relative; width: 100vw; height: 100vh; overflow: hidden; } .world::before { z-index: -1; content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; text-align: center; background: white; /*#22333b*/; } .space { position: absolute; top: 0; left: 0; width: 4096px; height: 4096px; background-size: 32px 32px; background-image: linear-gradient( to right, rgba(40, 126, 255, 0.1) 1px, transparent 1px ), linear-gradient(to bottom, rgba(40, 126, 255, 0.1) 1px, transparent 1px); transform: translate3d(0, 0, 0); } </style> <div class="world"> <div class="space"> <slot> </div> </div> `; connectedCallback() { super.connectedCallback(); this.rootElement = true; this.shadowWorld = this.shadowRoot.querySelector(".world"); this.space = this.shadowRoot.querySelector(".space"); this.buildBlockTree(); this.store.meta = false; const playerId = localStorage.getItem("playerId") || crypto.randomUUID(); localStorage.setItem("playerId", playerId); this.store.playerId = playerId; this.observer = new MutationObserver((record) => { const hasBlocks = (arr) => Array.isArray(arr) && arr.some((el) => el.tagName?.toLowerCase().endsWith("block")); const added = record .map((r) => Array.from(r.addedNodes)) .filter(hasBlocks) .flat(2); const removed = record .map((r) => Array.from(r.removedNodes)) .filter(hasBlocks) .flat(2); if (![...added, ...removed].length) { return; } this.buildBlockTree(added, removed); }); this.observer.observe(this, { childList: true, subtree: true, }); /* this.saveObserver.observe(this, { childList: true, subtree: true, attributes: true, }); */ this.scrollX = Number(this.getAttribute("scroll-x")) || 0; this.scrollY = Number(this.getAttribute("scroll-y")) || 0; this.shadowWorld.addEventListener("wheel", this.handleScroll.bind(this), { passive: false, }); if (!this.hasAttribute("scroll-x") && !this.hasAttribute("scroll-y")) { this.scrollX = (this.space.offsetWidth - this.shadowWorld.offsetWidth) / 2; this.scrollY = (this.space.offsetHeight - this.shadowWorld.offsetHeight) / 2; this.setAttribute("scroll-x", this.scrollX); this.setAttribute("scroll-y", this.scrollY); } this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px)`; this.isDragging = false; /*this.shadowWorld.addEventListener( "touchstart", this.handleTouchStart.bind(this), ); this.shadowWorld.addEventListener( "touchmove", this.handleTouchMove.bind(this), ); this.shadowWorld.addEventListener( "touchend", this.handleTouchEnd.bind(this), );*/ this.shadowWorld.addEventListener("click", this.click); document.addEventListener("keyup", (e) => { this.store.meta = false; }); // Command+K to open spotlight document.addEventListener("keydown", (e) => { if (e.metaKey || e.ctrlKey) { this.store.meta = true; } if ( e.keyCode === 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) { // CMD+S / CTRL+S e.preventDefault(); } if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); if (!document.querySelector("spotlight-block")) { const spotlight = document.createElement("spotlight-block"); spotlight.setAttribute("fixed", ""); document.body.appendChild(spotlight); } else { const spotlight = document.querySelector("spotlight-block"); spotlight.remove(); } } }); } disconnectedCallback() { super.disconnectedCallback && super.disconnectedCallback(); this.observer && this.observer.disconnect(); } isAncestorScrollable(el) { let node = el; while (node && node !== document.body) { const hasScrollableContent = node.scrollHeight > node.clientHeight; const overflowYStyle = window.getComputedStyle(node).overflowY; const isOverflowHidden = overflowYStyle.indexOf("hidden") !== -1; const canScroll = hasScrollableContent && !isOverflowHidden; if (canScroll) { return true; } node = node.parentElement; if (node && node.tagName === "world-block") return false; } return false; } handleScroll(e) { e.preventDefault(); if (this.isAncestorScrollable(e.target)) { //return; } if (e.target.tagName === "code-block") return; // Don't scroll if context menu is open const spotlightBlock = document.querySelector("spotlight-block"); const menuBlocks = document.querySelectorAll("menu-block"); if (menuBlocks.length || spotlightBlock) return; if (e.ctrlKey) { const scale = this.scale || 1; const delta = -e.deltaY * 0.001; const newScale = Math.max(0.1, Math.min(3, scale + delta)); this.scale = newScale; this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px) scale(${newScale})`; return; } const newScrollX = this.scrollX + e.deltaX; const newScrollY = this.scrollY + e.deltaY; const maxScrollX = this.space.offsetWidth - this.shadowWorld.offsetWidth; const maxScrollY = this.space.offsetHeight - this.shadowWorld.offsetHeight; this.scrollX = Math.max(0, Math.min(newScrollX, maxScrollX)); this.scrollY = Math.max(0, Math.min(newScrollY, maxScrollY)); // Update attributes this.setAttribute("scroll-x", this.scrollX); this.setAttribute("scroll-y", this.scrollY); this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px) ${ this.scale ? `scale(${this.scale})` : "" }`; } handleTouchStart(event) { event.preventDefault(); this.isDragging = true; this.lastTouchX = event.touches[0].clientX; this.lastTouchY = event.touches[0].clientY; } handleTouchMove(e) { if (!this.isDragging) return; e.preventDefault(); const touch = e.touches[0]; const deltaX = this.lastTouchX - touch.clientX; const deltaY = this.lastTouchY - touch.clientY; this.updateScroll(deltaX, deltaY); this.lastTouchX = touch.clientX; this.lastTouchY = touch.clientY; } handleTouchEnd() { this.isDragging = false; } updateScroll(deltaX, deltaY) { const newScrollX = this.scrollX + deltaX; const newScrollY = this.scrollY + deltaY; const maxScrollX = this.space.offsetWidth - this.shadowWorld.offsetWidth; const maxScrollY = this.space.offsetHeight - this.shadowWorld.offsetHeight; this.scrollX = Math.max(0, Math.min(newScrollX, maxScrollX)); this.scrollY = Math.max(0, Math.min(newScrollY, maxScrollY)); this.setAttribute("scroll-x", this.scrollX); this.setAttribute("scroll-y", this.scrollY); this.space.style.transform = `translate(${-this.scrollX}px, ${-this.scrollY}px)`; } click() { const toolbars = document.querySelectorAll("toolbar-block"); toolbars.forEach((toolbar) => toolbar.remove()); } nextSpreadsheetLetter(char) { let carry = 1; let res = ""; for (let i = char.length - 1; i >= 0; i--) { let charCode = char.charCodeAt(i) - 65 + carry; if (charCode === 26) { res = "A" + res; carry = 1; } else { res = String.fromCharCode((charCode % 26) + 65) + res; carry = 0; } } if (carry === 1) res = "A" + res; return res; } buildBlockTree(added, removed) { /* if (window.blockTree) { if (added) { for (let el of added) { const parent = Object.values(window.blockTree).find((node) => node.block === added); console.log(parent); } } return; } */ //console.time("build-block-tree"); let currentNode = null; let rowCounts = { A: 0, }; let todo = [ { parent: null, position: "Ax0", block: this, }, ]; window.blockTree = { Ax0: { block: this }, }; while (todo.length) { currentNode = todo.shift(); let [row] = currentNode.position.split("x"); const childRow = this.nextSpreadsheetLetter(row); for (let child of Array.from(currentNode.block.childNodes || [])) { if (!child.tagName?.toLowerCase().endsWith("block")) { continue; } const childColumn = ++rowCounts[childRow] || (rowCounts[childRow] = 1); const node = { parent: currentNode.position, position: childRow + "x" + childColumn, block: child, }; const { position: _, ...nodeWithoutPosition } = node; window.blockTree[node.position] = nodeWithoutPosition; if (child.childNodes) { todo.push(node); } } } //console.timeEnd("build-block-tree"); } } Playground.blocks.button-block.js export default class Button extends Block { tagName = "button-block"; static defaultAttributes = { class: "bg-gray-100 rounded-sm cursor-pointer px-4 py-2 text-sm text-gray-700 hover:bg-gray-200", }; render = ` <button> <slot> </button> `; connectedCallback() { super.connectedCallback(); this.button = this.shadowRoot.querySelector("button"); const slot = this.shadowRoot.querySelector("slot"); if (slot.assignedNodes().length === 0) { this.button.textContent = "my awesome button"; } } } Playground.scripts.api.ts const handler = async (request: Request): Promise<Response> => { if (request.url.endsWith("/djs")) { const supabaseUrl = "https://kodycvenourfbbbgphhl.supabase.co"; const supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtvZHljdmVub3VyZmJiYmdwaGhsIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTY2NDY5MDg0NywiZXhwIjoxOTgwMjY2ODQ3fQ.kBf-ggVMtblzWPip1uYRomLKWrrmgSSLNaLW2ktB4a8"; const table = "Profile"; const response = await fetch(`${supabaseUrl}/rest/v1/${table}?select=*`, { headers: { apikey: supabaseKey, Authorization: `Bearer ${supabaseKey}`, }, }); if (!response.ok) { console.error(new Error(`Fetch failed: ${response.status} ${response.statusText}`).stack); return new Response("Failed to fetch djs", { status: 500 }); } let data = await response.json(); data = data.map(dj => ({ ...dj, email: dj.email.toUpperCase() })); console.log(`Fetched ${data.length} DJs from supabase`); return new Response(JSON.stringify(data), { headers: { "content-type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }, }); } console.log('Request from...', request.origin); return new Response("Hello from Deno server!", { headers: { "content-type": "text/plain", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }, }); }; console.log("Deno server running on http://localhost:9001"); Deno.serve({ port: 9001 }, handler); Playground.blocks.cursor-block.js export default class Cursor extends Block { tagName = "cursor-block"; static defaultAttributes = { class: "w-4 h-4", }; render = ` <div> <slot></slot> </div> `; constructor() { super(); } connectedCallback() { super.connectedCallback(); const player = localStorage.getItem("playerId"); const cursorUrl = this.getAttribute("cursor-url") || "/assets/img/cursor.png"; const grabbedUrl = this.getAttribute("grabbed-url") || "/assets/img/grabbed.png"; //console.log(player, this.getAttribute("player")); if (player === this.getAttribute("player")) { // Do nothing for current player /* this.shadowRoot.querySelector("div").display = "none"; document.body.style.setProperty("--default-cursor", `url("${cursorUrl}"), default`); document.body.style.setProperty("--grabbed-cursor", `url("${grabbedUrl}"), grabbing`); document.body.style.cursor = "var(--default-cursor)"; document.addEventListener( "mousemove", (e) => { const point = e.touches ? e.touches[0] : e; const maxX = this.space.offsetWidth - this.shadowRoot.querySelector("div").offsetWidth; const maxY = this.space.offsetWidth - this.shadowRoot.querySelector("div").offsetHeight; const newX = Math.max(0, Math.min(point.pageX + Number(this.world.props["scroll-x"]), maxX)); const newY = Math.max(0, Math.min(point.pageY + Number(this.world.props["scroll-y"]), maxY)); this.shadowRoot.querySelector("div").style.transform = `translate(${newX}px, ${newY}px)`; this.setAttribute("x", Math.round(newX)); this.setAttribute("y", Math.round(newY)); }, true ); */ } else { this.classList.add(`bg-[url("${cursorUrl}")]`, "bg-no-repeat", "bg-center", "bg-contain"); } } }