Skip to content

Commit 5fb2d2a

Browse files
Add a Remix dropdown (CodeSandbox/Krabbel) to the gh-pages examples (#5842)
Inject examples/js/remix-dropdown.js into the published examples that reference the local A-Frame build. The script adds a REMIX button above the AR/VR enter buttons with a dropdown to open the current example on CodeSandbox or Krabbel, deriving the example path from the page URL. It also repositions the CodeSandbox "Open Sandbox" button and hides itself when already running inside a remix preview (*.csb.app, *.krabbel.fun). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e9f3f48 commit 5fb2d2a

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

examples/js/remix-dropdown.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/* Adds a "Remix" dropdown to the gh-pages examples letting visitors open the
2+
current example on CodeSandbox or Krabbel. Injected into the published
3+
examples by scripts/preghpages.js. */
4+
(function () {
5+
// Derive the example location (relative to the examples directory) from the
6+
// current URL, splitting it into the directory the remix services open and
7+
// the html filename. e.g. ".../examples/test/text/msdf2.html" ->
8+
// { dir: "test/text", file: "msdf2.html" }.
9+
// In prod the examples live under /examples/ (e.g. aframe.io/examples/ or
10+
// aframe.io/aframe/examples/); the local dev server serves the examples
11+
// directory as the web root, so fall back to the whole pathname.
12+
function getExampleLocation () {
13+
var pathname = window.location.pathname;
14+
var marker = '/examples/';
15+
var index = pathname.indexOf(marker);
16+
var rel = (index === -1 ? pathname : pathname.slice(index + marker.length)).replace(/^\//, '');
17+
var match = rel.match(/([^/]+\.html)$/);
18+
return {
19+
dir: rel.replace(/[^/]+\.html$/, '').replace(/\/$/, ''),
20+
file: match ? match[1] : ''
21+
};
22+
}
23+
24+
function createMenuItem (label, href) {
25+
var item = document.createElement('a');
26+
item.classList.add('a-remix-menu-item');
27+
item.setAttribute('href', href);
28+
item.setAttribute('target', '_blank');
29+
item.setAttribute('rel', 'noopener');
30+
item.textContent = label;
31+
return item;
32+
}
33+
34+
function addStyles () {
35+
var css =
36+
// Reposition the CodeSandbox "Open Sandbox" button to the top left so it
37+
// does not overlap the VR/AR enter buttons.
38+
'iframe[id^="sb__open-sandbox"]{inset: 16px auto auto 16px;}' +
39+
40+
// Sit above the AR/VR enter buttons in the bottom right corner.
41+
'.a-remix-container{position: absolute; right: 20px; bottom: 70px; z-index: 9999;' +
42+
'font-size: 20px; line-height: 20px;' +
43+
'font-family: \'Helvetica Neue\', Helvetica, Arial, sans-serif;}' +
44+
45+
'.a-remix-button{cursor: pointer; padding: 0 10px; font-weight: bold; color: #666;' +
46+
'font-size: large; border: 3px solid #666; box-sizing: border-box; min-width: 58px; min-height: 38px;' +
47+
'border-radius: 8px; background-color: white;}' +
48+
49+
'.a-remix-button:active, .a-remix-button:hover{border-color: #ef2d5e; color: #ef2d5e;}' +
50+
51+
'.a-remix-menu{position: absolute; right: 0; bottom: 40px; min-width: 200px;' +
52+
'background-color: white; border: 3px solid #666; border-radius: 8px;' +
53+
'overflow: hidden;}' +
54+
55+
'.a-remix-menu-item{display: block; padding: 10px 15px; color: rgb(51, 51, 51);' +
56+
'text-decoration: none; font-size: 11pt; line-height: 20pt;}' +
57+
58+
'.a-remix-menu-item:hover{background-color: #ef2d5e; color: white;}';
59+
var style = document.createElement('style');
60+
61+
style.appendChild(document.createTextNode(css));
62+
document.getElementsByTagName('head')[0].appendChild(style);
63+
}
64+
65+
function init () {
66+
// Don't show the button when the example is already running inside a remix
67+
// service preview (CodeSandbox *.csb.app, Krabbel *.krabbel.fun).
68+
if (/\.(csb\.app|krabbel\.fun)$/.test(window.location.hostname)) { return; }
69+
70+
var loc = getExampleLocation();
71+
if (!loc) { return; }
72+
73+
// Point at the directory, adding a file parameter for non-index.html pages
74+
// so the specific example file is opened. The leading slash is URL-encoded
75+
// (CodeSandbox expects %2F).
76+
var fileParam = (loc.file && loc.file !== 'index.html') ? '?file=' + encodeURIComponent('/' + loc.file) : '';
77+
var treePath = 'tree/gh-pages/examples/' + loc.dir + fileParam;
78+
var codesandboxUrl = 'https://codesandbox.io/p/sandbox/github/aframevr/aframe/' + treePath;
79+
var krabbelUrl = 'https://krabbel.dev/github/aframevr/aframe/' + treePath;
80+
81+
addStyles();
82+
83+
var menu = document.createElement('div');
84+
menu.classList.add('a-remix-menu');
85+
menu.style.display = 'none';
86+
menu.appendChild(createMenuItem('Remix on CodeSandbox', codesandboxUrl));
87+
menu.appendChild(createMenuItem('Remix on Krabbel', krabbelUrl));
88+
89+
var button = document.createElement('button');
90+
button.classList.add('a-remix-button');
91+
button.setAttribute('title', 'Remix this example');
92+
button.textContent = 'REMIX';
93+
button.addEventListener('click', function (evt) {
94+
evt.stopPropagation();
95+
menu.style.display = menu.style.display === 'none' ? '' : 'none';
96+
});
97+
98+
// Close the menu when clicking elsewhere.
99+
document.addEventListener('click', function () { menu.style.display = 'none'; });
100+
101+
var container = document.createElement('div');
102+
container.classList.add('a-remix-container');
103+
container.appendChild(menu);
104+
container.appendChild(button);
105+
document.body.appendChild(container);
106+
}
107+
108+
if (document.readyState === 'loading') {
109+
document.addEventListener('DOMContentLoaded', init);
110+
} else {
111+
init();
112+
}
113+
})();

scripts/preghpages.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ shell.cp('-r', [
2727
'*.md'
2828
], 'gh-pages');
2929

30-
function replaceInFileSync ({ files, processor }) {
30+
function replaceInFileSync ({ files, processor, processorIfChanged = [] }) {
3131
for (const file of fs.globSync(files)) {
3232
const input = fs.readFileSync(file, 'utf8');
33-
const output = processor.reduce((content, process) => process(content), input);
33+
let output = processor.reduce((content, process) => process(content), input);
34+
// processorIfChanged only runs on files the processors above modified, i.e.
35+
// the examples that reference the local A-Frame build.
36+
if (output !== input) {
37+
output = processorIfChanged.reduce((content, process) => process(content), output);
38+
}
3439
if (output !== input) {
3540
fs.writeFileSync(file, output);
3641
}
@@ -49,5 +54,9 @@ replaceInFileSync({
4954
htmlReplace(/"\.\.\/\.\.\/\.\.\/super-three-package/g, `"https://cdn.jsdelivr.net/npm/super-three@${threeVersion}`),
5055
htmlReplace(/"\.\.\/\.\.\/js\//g, '"https://cdn.jsdelivr.net/gh/aframevr/aframe@gh-pages/examples/js/'),
5156
htmlReplace(/"\.\.\/\.\.\/assets\//g, '"https://cdn.jsdelivr.net/gh/aframevr/aframe@gh-pages/examples/assets/')
57+
],
58+
// Add the "Remix" dropdown (CodeSandbox/Krabbel) to the published examples.
59+
processorIfChanged: [
60+
htmlReplace('</head>', ' <script src="https://cdn.jsdelivr.net/gh/aframevr/aframe@gh-pages/examples/js/remix-dropdown.js"></script>\n </head>')
5261
]
5362
});

0 commit comments

Comments
 (0)