Last active
December 23, 2024 11:42
-
-
Save 2called-chaos/aea0ca4fec45d185ee2016b024ba22e3 to your computer and use it in GitHub Desktop.
xray-rails (crude edition) / screenshot below
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Note: make sure your editor in step 2 is non-blocking (i.e. not "subl -w") | |
// | |
// 1. Enable in your config/environment/development.rb | |
// config.action_view.annotate_rendered_view_with_filenames = true | |
// | |
// 2. Add this to your routes.rb | |
// post "__xray/open", to: ->(env) { | |
// editor = ENV["GEM_EDITOR"] || ENV["VISUAL"] || ENV["EDITOR"] || "/usr/local/bin/subl" | |
// params = JSON.parse(Rack::Request.new(env).body.read) | |
// path = Rails.root.join(params["path"].to_s) | |
// `#{editor} #{path.to_s.shellescape}` if path.exist? | |
// [200, { "Content-Type" => "text/plain" }, [""]] | |
// } if Rails.env.development? | |
// | |
// 3. Import openXray, closeXray or registerXrayShortcut function | |
// import { registerXrayShortcut } from "./xray_rails" | |
// registerXrayShortcut((ev, mac) => (mac && ev.metaKey || !mac && ev.ctrlKey) && ev.shiftKey && ev.code == "KeyX") | |
export class Xray { | |
static CSS = ` | |
#rails-xray-container { | |
position: fixed; | |
top: 0; | |
bottom: 0; | |
right: 0; | |
left: 0; | |
//background: rgba(255, 255, 0, 0.1); | |
z-index: 99999; | |
} | |
#rails-xray-container .rails-xray-template { | |
position: absolute; | |
background: rgba(255, 0, 0, 0.1); | |
} | |
#rails-xray-container .rails-xray-template span { | |
position: absolute; | |
opacity: 0.6; | |
padding: 0.1rem 0.5rem; | |
background: rgba(33, 33, 33, 0.6); | |
z-index: 9; | |
} | |
#rails-xray-container .rails-xray-template:hover { | |
background: rgba(255, 0, 0, 0.5); | |
} | |
#rails-xray-container .rails-xray-template:hover span { | |
background: rgba(33, 33, 33, 0.9); | |
opacity: 1; | |
z-index: 10; | |
} | |
` | |
static get isMac() { return navigator.platform.toUpperCase().indexOf('MAC') >= 0 } | |
constructor() { | |
this.addStyles() | |
} | |
addStyles() { | |
if(document.getElementById("rails-xray-styles")) return false | |
const style = document.createElement("style") | |
document.head.appendChild(style) | |
style.id = "rails-xray-styles" | |
style.innerHTML = this.constructor.CSS | |
} | |
computeBoundingBox(contents) { | |
// Edge case: the container may not physically wrap its children, for | |
// example if they are floated and no clearfix is present. | |
if(contents.length == 1 && contents[0].offsetHeight <= 0) { | |
return this.computeBoundingBox(Array.from(contents[0].children)) | |
} | |
const boxFrame = { | |
top: Number.POSITIVE_INFINITY, | |
left: Number.POSITIVE_INFINITY, | |
right: Number.NEGATIVE_INFINITY, | |
bottom: Number.NEGATIVE_INFINITY, | |
} | |
contents.forEach(el => { | |
if (!el || !( el.offsetWidth || el.offsetHeight || el.getClientRects().length )) return | |
const frame = el.getBoundingClientRect() | |
const frame_right = frame.left + frame.width | |
const frame_bottom = frame.top + frame.height | |
boxFrame.top = Math.min(frame.top, boxFrame.top) | |
boxFrame.left = Math.min(frame.left, boxFrame.left) | |
boxFrame.right = Math.max(frame_right, boxFrame.right) | |
boxFrame.bottom = Math.max(frame_bottom, boxFrame.bottom) | |
}) | |
return { | |
left: boxFrame.left, | |
top: boxFrame.top, | |
width: boxFrame.right - boxFrame.left, | |
height: boxFrame.bottom - boxFrame.top, | |
} | |
} | |
findRenderedViews() { | |
const result = [] | |
document.querySelectorAll('*:not(iframe):not(script)').forEach(el => { | |
el.childNodes.forEach(node => { | |
if (node.nodeType !== 8 || !node.data.startsWith(' BEGIN')) return false | |
const id = node.data.match(/^\sBEGIN\s(.+)\s$/)[1] | |
let el = node.nextSibling | |
const res = [] | |
while (el && !(el.nodeType === 8 && el.data === ` END ${id} `)) { | |
if (el.nodeType === 1 && el.tagName !== 'SCRIPT') { | |
res.push(el) | |
} | |
el = el.nextSibling | |
} | |
result.push([id, res]) | |
}) | |
}) | |
return result | |
} | |
closeOverlay() { | |
document.getElementById("rails-xray-container")?.remove() | |
} | |
renderOverlay() { | |
this.closeOverlay() | |
const ctn = document.createElement("div") | |
ctn.id = "rails-xray-container" | |
this.findRenderedViews().forEach(([id, contents]) => { | |
const box = this.computeBoundingBox(contents) | |
// console.log(id, contents, box) | |
if(!Number.isFinite(box.left)) return | |
const tel = document.createElement("div") | |
ctn.append(tel) | |
tel.classList.add("rails-xray-template") | |
tel.setAttribute("data-id", id) | |
tel.style.left = box.left + "px" | |
tel.style.top = box.top + "px" | |
tel.style.width = box.width + "px" | |
tel.style.height = box.height + "px" | |
tel.addEventListener("click", ev => { | |
fetch("/__xray/open", { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify({ path: id }), | |
}).then(_ => this.closeOverlay()) | |
}) | |
const label = document.createElement("span") | |
tel.append(label) | |
label.innerText = id | |
}) | |
document.body.append(ctn) | |
} | |
} | |
export function openXray() { | |
new Xray().renderOverlay() | |
} | |
export function closeXray() { | |
document.getElementById("rails-xray-container").remove() | |
} | |
export function registerXrayShortcut(callback) { | |
document.addEventListener("keydown", ev => { | |
if(callback(ev, Xray.isMac)) { | |
new Xray().renderOverlay() | |
} else if (ev.code == "Escape" && document.getElementById("rails-xray-container")) { | |
closeXray() | |
} | |
}) | |
} |
Author
2called-chaos
commented
Dec 23, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment