From 1889bcb0984b023e38f2009a399edf469a21377b Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 16 Nov 2024 10:07:38 +1100 Subject: [PATCH 1/9] Created SKO page --- astro.config.mjs | 5 +++++ .../docs/splashkit-online/splashkit-online.mdx | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/content/docs/splashkit-online/splashkit-online.mdx diff --git a/astro.config.mjs b/astro.config.mjs index d7d187a4..7bbf8689 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -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 }, diff --git a/src/content/docs/splashkit-online/splashkit-online.mdx b/src/content/docs/splashkit-online/splashkit-online.mdx new file mode 100644 index 00000000..72442993 --- /dev/null +++ b/src/content/docs/splashkit-online/splashkit-online.mdx @@ -0,0 +1,15 @@ +--- +title: SplashKit Online +tableOfContents: false +template: splash +--- + +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++. + + +
+ +
From e5b92156bcb9a3a1aa7cd529c3abb70343eed4d2 Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 23 Nov 2024 11:58:05 +1100 Subject: [PATCH 2/9] Integrate SKO into usage examples --- scripts/usage-example-page-generation.cjs | 86 ++++++++++++++++++- .../splashkit-online/splashkit-online.mdx | 4 +- src/styles/custom.css | 17 ++++ 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index 01051a19..8d1b2dcc 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -448,8 +448,90 @@ categories.forEach((categoryKey) => { } } - mdxContent += `![${exampleKey} example](${outputFilePath})\n` - mdxContent += "\n---\n"; + mdxContent += `![${exampleKey} example](${outputFilePath})\n` + + // Add the toggle button and iframe + mdxContent += ` +
+ +
+ + + `; + + + mdxContent += "\n---\n"; + }); } functionIndex++; diff --git a/src/content/docs/splashkit-online/splashkit-online.mdx b/src/content/docs/splashkit-online/splashkit-online.mdx index 72442993..846d5342 100644 --- a/src/content/docs/splashkit-online/splashkit-online.mdx +++ b/src/content/docs/splashkit-online/splashkit-online.mdx @@ -2,9 +2,11 @@ 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++. +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/).
diff --git a/src/styles/custom.css b/src/styles/custom.css index 8705ec2c..7f51db1d 100644 --- a/src/styles/custom.css +++ b/src/styles/custom.css @@ -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 */ } \ No newline at end of file From 93634dcad4f487657c4894b18c8c6965758d966e Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 30 Nov 2024 10:06:52 +1100 Subject: [PATCH 3/9] Update to SKO integration code --- scripts/usage-example-page-generation.cjs | 200 +++++++++++++--------- 1 file changed, 120 insertions(+), 80 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index 8d1b2dcc..4985dabc 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -450,86 +450,126 @@ categories.forEach((categoryKey) => { mdxContent += `![${exampleKey} example](${outputFilePath})\n` - // Add the toggle button and iframe - mdxContent += ` -
- -
- - - `; - - + // Add the toggle button and iframe logic (to be injected into MDX) + mdxContent += ` +
+ +
+ `; + mdxContent += "\n---\n"; }); From 258906f7d87a57f88b5435f72f3c457ff4c342a7 Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 30 Nov 2024 15:45:25 +1100 Subject: [PATCH 4/9] Clamp iframe to screen --- scripts/usage-example-page-generation.cjs | 255 ++++++++++++---------- 1 file changed, 138 insertions(+), 117 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index 4985dabc..8811c557 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -451,123 +451,144 @@ categories.forEach((categoryKey) => { mdxContent += `![${exampleKey} example](${outputFilePath})\n` // Add the toggle button and iframe logic (to be injected into MDX) - mdxContent += ` -
- -
+ mdxContent += ` +
+ +
+ `; mdxContent += "\n---\n"; From bf582d0edb9ad808cc0a5c5c9bbb3b5deee4309c Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 30 Nov 2024 15:53:58 +1100 Subject: [PATCH 5/9] Add breakpoints for mobile devices --- scripts/usage-example-page-generation.cjs | 186 +++++++++++----------- 1 file changed, 96 insertions(+), 90 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index 8811c557..a8789656 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -453,143 +453,149 @@ categories.forEach((categoryKey) => { // Add the toggle button and iframe logic (to be injected into MDX) mdxContent += `
- +
- - `; + `; mdxContent += "\n---\n"; From bb1bfe02510f025036913d08a48ea419cf18a810 Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sun, 1 Dec 2024 09:50:01 +1100 Subject: [PATCH 6/9] Added ZIP message functionality for resource packages --- scripts/usage-example-page-generation.cjs | 277 ++++++++++++---------- 1 file changed, 156 insertions(+), 121 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index a8789656..a8d4fb59 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -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 // =============================================================================== @@ -449,153 +466,171 @@ categories.forEach((categoryKey) => { } mdxContent += `![${exampleKey} example](${outputFilePath})\n` - - // Add the toggle button and iframe logic (to be injected into MDX) + + // Find the .zip file path for the folder + const zipFilePath = findZipFileInTxt(`${categoryFilePath}/${functionKey}`); + + // Add the toggle button and iframe logic mdxContent += `
- + }) + .catch(error => console.error('Error reading file:', error)); + } catch (error) { + console.error('An error occurred:', error); + } + })();" + > + Try it in SKO +
- `; + `; mdxContent += "\n---\n"; From b7017586318c7503fed6111717af6a03fa74d9c5 Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Tue, 3 Dec 2024 19:51:56 +1100 Subject: [PATCH 7/9] Added message queue --- scripts/usage-example-page-generation.cjs | 322 +++++++++++----------- 1 file changed, 168 insertions(+), 154 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index a8d4fb59..f8ed7fb6 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -473,164 +473,178 @@ categories.forEach((categoryKey) => { // Add the toggle button and iframe logic mdxContent += `
- -
- `; + }; + + // Paths to the C++ code and ZIP file + const filePath = '/usage-examples/${categoryKey}/${functionKey}/${exampleKey}.cpp'; + const zipPath = '${zipFilePath}'; + + // Fetch the C++ code and ZIP file and send them to the iframe + fetch(filePath) + .then(response => { + if (!response.ok) throw new Error('Failed to fetch C++ file.'); + return response.text(); + }) + .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(err => console.error('Error fetching resources:', err)); + } catch (error) { + console.error('Error initializing iframe:', error); + } + })(); + " + > + Try it in SKO + +
`; mdxContent += "\n---\n"; From 1f48851f617ba56f7f2696ea4545f86abd7bfac4 Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 7 Dec 2024 12:35:44 +1100 Subject: [PATCH 8/9] Added small timeout to event listener --- scripts/usage-example-page-generation.cjs | 91 ++++++++++------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index 66782fb1..b4a3cea2 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -493,7 +493,32 @@ categories.forEach((categoryKey) => { const messageQueue = []; // Queue for storing messages while iframe is not ready let isIframeReady = false; // Tracks whether iframe is ready to receive messages - // Create and initialize the iframe if it doesn't exist + // Add a fallback timeout to process messages + let readinessTimeout = null; + + // 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'); @@ -544,58 +569,11 @@ categories.forEach((categoryKey) => { 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); - } } - // Ensure the iframe container is visible + // Display the iframe container globalIframeContainer.style.display = 'block'; - // Listen for a message indicating that the iframe is ready - window.addEventListener('message', function(event) { - if (event.data.type === 'SplashKitOnlineListening') { - console.log('Iframe is ready to receive messages.'); - isIframeReady = true; - - // Send all queued messages to the iframe - while (messageQueue.length > 0) { - const message = messageQueue.shift(); - globalIframe.contentWindow.postMessage(message, '*'); - } - } - }); - // Function to send a message to the iframe, queuing if it's not ready const sendMessageToIframe = (message) => { if (isIframeReady) { @@ -610,7 +588,20 @@ categories.forEach((categoryKey) => { const filePath = '/usage-examples/${categoryKey}/${functionKey}/${exampleKey}.cpp'; const zipPath = '${zipFilePath}'; - // Fetch the C++ code and ZIP file and send them to the iframe + // 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); // 0.1-second timeout + + // Fetch the C++ code and ZIP file fetch(filePath) .then(response => { if (!response.ok) throw new Error('Failed to fetch C++ file.'); From f0ce6161fe5ae2743d89866a3f7246dce880a647 Mon Sep 17 00:00:00 2001 From: ShaunR1991 Date: Sat, 7 Dec 2024 17:48:49 +1100 Subject: [PATCH 9/9] Restored drag functionality and resetMessageQueue function --- scripts/usage-example-page-generation.cjs | 55 ++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/scripts/usage-example-page-generation.cjs b/scripts/usage-example-page-generation.cjs index b4a3cea2..a6b9c9c7 100644 --- a/scripts/usage-example-page-generation.cjs +++ b/scripts/usage-example-page-generation.cjs @@ -492,9 +492,13 @@ categories.forEach((categoryKey) => { 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 - // Add a fallback timeout to process messages - let readinessTimeout = null; + // 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) { @@ -569,9 +573,43 @@ categories.forEach((categoryKey) => { 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); + } } - // Display the iframe container + // 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 @@ -599,14 +637,11 @@ categories.forEach((categoryKey) => { const message = messageQueue.shift(); globalIframe.contentWindow.postMessage(message, '*'); } - }, 100); // 0.1-second timeout + }, 100); // Fetch the C++ code and ZIP file fetch(filePath) - .then(response => { - if (!response.ok) throw new Error('Failed to fetch C++ file.'); - return response.text(); - }) + .then(response => response.ok ? response.text() : Promise.reject('Failed to fetch C++ file.')) .then(async codeData => { let zipBlob = null; try { @@ -626,9 +661,9 @@ categories.forEach((categoryKey) => { // Send the message to the iframe sendMessageToIframe(message); }) - .catch(err => console.error('Error fetching resources:', err)); + .catch(console.error); } catch (error) { - console.error('Error initializing iframe:', error); + console.error(error); } })(); "