Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SplashKitOnline Integration #320

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export default defineConfig({
label: "Usage Examples",
autogenerate: { directory: "usage-examples", collapsed: true },
},
{
label: "SplashKit Online",
autogenerate: { directory: "splashkit-online", collapsed: true },
badge: "New",
},
// {
// label: "Arcade Hackathon Project",
// autogenerate: { directory: "arcade-hackathon-project", collapsed: true },
Expand Down
228 changes: 226 additions & 2 deletions scripts/usage-example-page-generation.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ function getFunctionLink(jsonData, groupNameToCheck, uniqueNameToCheck) {
return functionLink;
}

function findZipFileInTxt(folderPath) {
const txtFiles = fs.readdirSync(folderPath).filter(file => file.endsWith('.txt'));
for (const txtFile of txtFiles) {
const txtFilePath = `${folderPath}/${txtFile}`;
const fileContent = fs.readFileSync(txtFilePath, 'utf-8');

// Regex to match a .zip path or URL
const zipRegex = /\/[^\s]*\.zip/g;
const match = fileContent.match(zipRegex);

if (match && match.length > 0) {
return match[0]; // Return the first match
}
}
return null;
}

// ===============================================================================
// Start of Main Script
// ===============================================================================
Expand Down Expand Up @@ -448,8 +465,215 @@ categories.forEach((categoryKey) => {
}
}

mdxContent += `![${exampleKey} example](${outputFilePath})\n`
mdxContent += "\n---\n";
mdxContent += `![${exampleKey} example](${outputFilePath})\n`

// Find the .zip file path for the folder
const zipFilePath = findZipFileInTxt(`${categoryFilePath}/${functionKey}`);

// Add the toggle button and iframe logic
mdxContent += `
<div style="text-align: center; margin-top: 1rem;">
<button
id="${functionKey}_sko_button"
class="sko-button"
style="margin-top: 1rem;"
onclick="
(function() {
try {
// Check the screen size and restrict functionality for small screens
const screenWidth = window.innerWidth;
if (screenWidth < 768) {
alert('This feature is not available on screens of this size.');
return;
}

// Declare variables for the iframe container, iframe element, message queue, and readiness flag
let globalIframeContainer = document.getElementById('sko_iframe_global_container');
let globalIframe = document.getElementById('sko_iframe_global');
const messageQueue = []; // Queue for storing messages while iframe is not ready
let isIframeReady = false; // Tracks whether iframe is ready to receive messages
let readinessTimeout = null; // Timeout for processing messages in the queue

// Clear the message queue on every button click
const resetMessageQueue = () => {
console.log('Clearing message queue.');
messageQueue.length = 0;
};

// Ensure only one message listener is added
if (!window.skoListenerAdded) {
window.skoListenerAdded = true;

// Add an event listener to process messages from the iframe
window.addEventListener('message', function(event) {
if (event.data.type === 'SplashKitOnlineListening') {
console.log('Iframe is ready to receive messages.');
isIframeReady = true;

// Clear timeout if readiness signal is received
if (readinessTimeout) clearTimeout(readinessTimeout);

// Process all queued messages
while (messageQueue.length > 0) {
const message = messageQueue.shift();
globalIframe.contentWindow.postMessage(message, '*');
}
}
});
}

// Create the iframe and its container if it doesn't already exist
if (!globalIframeContainer) {
// Create a container for the iframe
globalIframeContainer = document.createElement('div');
globalIframeContainer.id = 'sko_iframe_global_container';
globalIframeContainer.style.position = 'fixed';
globalIframeContainer.style.top = '50%';
globalIframeContainer.style.left = '50%';
globalIframeContainer.style.transform = 'translate(-50%, -50%)';
globalIframeContainer.style.width = '75vw';
globalIframeContainer.style.height = '75vh';
globalIframeContainer.style.border = '1px solid #ccc';
globalIframeContainer.style.borderRadius = '8px';
globalIframeContainer.style.backgroundColor = '#fff';
globalIframeContainer.style.zIndex = '9999';
document.body.appendChild(globalIframeContainer);

// Add a draggable bar for moving the iframe container
const dragBar = document.createElement('div');
dragBar.style.width = '100%';
dragBar.style.height = '30px';
dragBar.style.backgroundColor = '#333';
dragBar.style.color = '#fff';
dragBar.style.display = 'flex';
dragBar.style.justifyContent = 'space-between';
dragBar.style.alignItems = 'center';
dragBar.style.padding = '0 10px';
dragBar.style.cursor = 'move';
dragBar.textContent = 'Drag to move';
globalIframeContainer.appendChild(dragBar);

// Add a close button to the drag bar
const closeButton = document.createElement('button');
closeButton.textContent = 'X';
closeButton.style.backgroundColor = '#e74c3c';
closeButton.style.color = '#fff';
closeButton.style.border = 'none';
closeButton.style.cursor = 'pointer';
closeButton.onclick = function () {
globalIframeContainer.style.display = 'none';
};
dragBar.appendChild(closeButton);

// Create the iframe element
globalIframe = document.createElement('iframe');
globalIframe.id = 'sko_iframe_global';
globalIframe.src = 'https://thoth-tech.github.io/SplashkitOnline/?language=C++';
globalIframe.style.width = '100%';
globalIframe.style.height = 'calc(100% - 30px)';
globalIframe.style.border = 'none';
globalIframeContainer.appendChild(globalIframe);

// Add functionality to make the container draggable
let offsetX = 0, offsetY = 0, isDragging = false;
dragBar.addEventListener('mousedown', function(event) {
isDragging = true;
offsetX = event.clientX - globalIframeContainer.getBoundingClientRect().left;
offsetY = event.clientY - globalIframeContainer.getBoundingClientRect().top;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});

// Function to handle mouse movement for dragging
function onMouseMove(event) {
if (isDragging) {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const containerRect = globalIframeContainer.getBoundingClientRect();
let newLeft = event.clientX - offsetX;
let newTop = event.clientY - offsetY;
newLeft = Math.max(0, Math.min(newLeft, viewportWidth - containerRect.width));
newTop = Math.max(0, Math.min(newTop, viewportHeight - containerRect.height));
globalIframeContainer.style.left = newLeft + 'px';
globalIframeContainer.style.top = newTop + 'px';
globalIframeContainer.style.transform = 'translate(0, 0)';
}
}

// Function to stop dragging
function onMouseUp() {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
}

// Clear the message queue and display the iframe container
resetMessageQueue();
globalIframeContainer.style.display = 'block';

// Function to send a message to the iframe, queuing if it's not ready
const sendMessageToIframe = (message) => {
if (isIframeReady) {
globalIframe.contentWindow.postMessage(message, '*');
} else {
console.log('Iframe not ready, queuing message.');
messageQueue.push(message);
}
};

// Paths to the C++ code and ZIP file
const filePath = '/usage-examples/${categoryKey}/${functionKey}/${exampleKey}.cpp';
const zipPath = '${zipFilePath}';

// Reset iframe state before sending new data
sendMessageToIframe({ eventType: 'ResetProjectState' });

// Add a fallback to process messages after a timeout
readinessTimeout = setTimeout(() => {
console.warn('Iframe readiness timeout. Processing queued messages.');
isIframeReady = true;
while (messageQueue.length > 0) {
const message = messageQueue.shift();
globalIframe.contentWindow.postMessage(message, '*');
}
}, 100);

// Fetch the C++ code and ZIP file
fetch(filePath)
.then(response => response.ok ? response.text() : Promise.reject('Failed to fetch C++ file.'))
.then(async codeData => {
let zipBlob = null;
try {
const zipResponse = await fetch(zipPath);
if (zipResponse.ok) zipBlob = await zipResponse.blob();
} catch (e) {
console.warn('ZIP file fetch failed:', zipPath);
}

// Prepare the initialization message
const message = {
eventType: 'InitializeProjectFromOutsideWorld',
files: [{ path: '/code/main.cpp', data: codeData }],
zips: zipBlob ? [{ data: zipBlob }] : []
};

// Send the message to the iframe
sendMessageToIframe(message);
})
.catch(console.error);
} catch (error) {
console.error(error);
}
})();
"
>
Try it in SKO
</button>
</div>`;

mdxContent += "\n---\n";

});
}
functionIndex++;
Expand Down
17 changes: 17 additions & 0 deletions src/content/docs/splashkit-online/splashkit-online.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: SplashKit Online
tableOfContents: false
template: splash
prev: false
next: false
---

SplashKit Online is a web-based IDE that allows students to write and run code directly in their browsers. Initially launched as a prototype in 2023, the 2024 goal is to mature this tool into a comprehensive product, including C# support. Currently supports JavaScript and C++. Try it below or [in a new window](https://thoth-tech.github.io/SplashkitOnline/).


<div style="margin: 0; display: flex; width: 100%; height: 90vh;">
<iframe
src="https://thoth-tech.github.io/SplashkitOnline/"
style="width: 100%; height: 100%; border: 1px solid #ccc; border-radius: 8px;">
</iframe>
</div>
17 changes: 17 additions & 0 deletions src/styles/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,21 @@ code {
vertical-align: middle;
border: 1px solid lightgray;
margin-left: 20px;
}

.sko-button {
padding: 1rem 1.5rem; /* Adjusted padding for better proportions */
border: 3px solid transparent; /* Ensures clean border */
position: relative; /* Keeps the button content in place */
overflow: hidden; /* Handles any child element overflow */
cursor: pointer; /* Makes it clear the button is clickable */
background-color: var(--sk-logo-blue); /* SplashKit's brand color */
color: black; /* Text color */
border-radius: 9999px; /* Fully rounded edges for the stadium shape */
text-align: center; /* Ensures the text is centered */
}

.sko-button:hover {
border: 3px solid var(--sl-color-white);
transform: scale(1.05); /* Slight zoom effect on hover */
}