diff --git a/build.js b/build.js index d7fe374..b581989 100644 --- a/build.js +++ b/build.js @@ -8,6 +8,7 @@ const { encode } = require('html-entities'); const { JSDOM } = require('jsdom'); const yaml = require('js-yaml'); const esbuild = require('esbuild'); +const { generatePlayground } = require('./src/playground') const site = "https://docs.puter.com"; @@ -45,8 +46,10 @@ marked.use({ // toolbar html += '
'; // "Run" - if (exampleID) - html += ``; + if (exampleID == "intro-chatgpt") + html += ``; + else + html += ``; // "Copy" html += '
'; @@ -689,6 +692,7 @@ generateDocumentation('./src'); generateRedirects(); generateSitemap(); generateLLMs(); +generatePlayground(); if (anyErrors) { process.exit(1); diff --git a/src/examples.js b/src/examples.js new file mode 100644 index 0000000..b0afcf3 --- /dev/null +++ b/src/examples.js @@ -0,0 +1,529 @@ +const examples = [ + { + title: 'Introduction', + children: [ + { + title: 'Hello World', + description: 'Try Puter.js instantly with interactive examples in your browser. Run, edit, and experiment with code - no installation or setup required.', + slug: '', + source: '/playground/examples/intro-chatgpt.html' + }, + { + title: 'Image Analysis', + description: 'Analyze images with AI using Puter.js. Run and experiment with this GPT Vision example directly in the playground.', + slug: 'intro-gpt-vision', + source: '/playground/examples/intro-gpt-vision.html' + }, + { + title: 'Cloud Storage', + description: 'Write files to cloud storage with Puter.js filesystem API. Run and modify this code example instantly in your browser.', + slug: 'intro-fs-write', + source: '/playground/examples/intro-fs-write.html' + }, + { + title: 'Key-Value Store', + description: 'Store and retrieve data with Puter.js key-value API. Run and experiment with this working code in the playground.', + slug: 'intro-kv-set', + source: '/playground/examples/intro-kv-set.html' + }, + { + title: 'Publish a Website', + description: 'Deploy websites instantly with Puter.js hosting API. Run and modify this example to publish your own site directly in the playground.', + slug: 'intro-hosting', + source: '/playground/examples/intro-hosting.html' + }, + { + title: 'Authentication', + description: 'Implement user authentication with Puter.js auth API. Run and experiment with this sign-in example in the playground.', + slug: 'intro-auth', + source: '/playground/examples/intro-auth.html' + } + ] + }, + { + title: 'AI', + children: [ + { + title: 'Chat with GPT-5 nano', + description: 'Chat with GPT-5 nano using Puter.js AI API. Run and experiment with this chatbot example directly in the playground.', + slug: 'ai-chatgpt', + source: '/playground/examples/ai-chatgpt.html' + }, + { + title: 'Image Analysis', + description: 'Analyze images with AI using Puter.js GPT Vision API. Run and modify this code example instantly in your browser.', + slug: 'ai-gpt-vision', + source: '/playground/examples/ai-gpt-vision.html' + }, + { + title: 'Stream the response', + description: 'Stream AI chat responses in real-time with Puter.js. Run and experiment with this streaming example in the playground.', + slug: 'ai-chat-stream', + source: '/playground/examples/ai-chat-stream.html' + }, + { + title: 'Function Calling', + description: 'Try AI function calling with Puter.js. Run and experiment with this advanced example directly in the playground.', + slug: 'ai-function-calling', + source: '/playground/examples/ai-function-calling.html' + }, + { + title: 'AI Resume Analyzer (File handling)', + description: 'Try an AI resume analyzer with file handling and GPT integration. Run and experiment with this Puter.js example directly in your browser.', + slug: 'ai-resume-analyzer', + source: '/playground/examples/ai-resume-analyzer.html' + }, + { + title: 'Chat with OpenAI o3-mini', + description: 'Chat with OpenAI o3-mini using Puter.js AI API. Run and experiment with this example directly in the playground.', + slug: 'ai-chat-openai-o3-mini', + source: '/playground/examples/ai-chat-openai-o3-mini.html' + }, + { + title: 'Chat with Claude Sonnet', + description: 'Chat with Claude Sonnet using Puter.js AI API. Run and experiment with this example directly in the playground.', + slug: 'ai-chat-claude', + source: '/playground/examples/ai-chat-claude.html' + }, + { + title: 'Chat with DeepSeek', + description: 'Chat with DeepSeek using Puter.js AI API. Run and experiment with this example directly in the playground.', + slug: 'ai-chat-deepseek', + source: '/playground/examples/ai-chat-deepseek.html' + }, + { + title: 'Chat with Gemini', + description: 'Chat with Google Gemini using Puter.js AI API. Run and experiment with this example directly in the playground.', + slug: 'ai-chat-gemini', + source: '/playground/examples/ai-chat-gemini.html' + }, + { + title: 'Chat with xAI (Grok)', + description: 'Chat with xAI Grok using Puter.js AI API. Run and experiment with this example directly in the playground.', + slug: 'ai-xai', + source: '/playground/examples/ai-xai.html' + }, + { + title: 'Extract Text from Image', + description: 'Extract text from images using Puter.js AI API. Run and modify this OCR example instantly in your browser.', + slug: 'ai-img2txt', + source: '/playground/examples/ai-img2txt.html' + }, + { + title: 'Text to Image', + description: 'Generate images from text with Puter.js AI API. Run and experiment with this text-to-image example in the playground.', + slug: 'ai-txt2img', + source: '/playground/examples/ai-txt2img.html' + }, + { + title: 'Text to Image with options', + description: 'Generate images with custom options using Puter.js AI API. Run and experiment with advanced text-to-image parameters in the playground.', + slug: 'ai-txt2img-options', + source: '/playground/examples/ai-txt2img-options.html' + }, + { + title: 'Text to Image with image-to-image generation', + description: 'Transform images with AI using Puter.js image-to-image generation. Run and experiment with this example directly in the playground.', + slug: 'ai-txt2img-image-to-image', + source: '/playground/examples/ai-txt2img-image-to-image.html' + }, + { + title: 'Text to Speech', + description: 'Convert text to speech with Puter.js AI API. Run and experiment with this TTS example directly in the playground.', + slug: 'ai-txt2speech', + source: '/playground/examples/ai-txt2speech.html' + }, + { + title: 'Text to Speech with options', + description: 'Generate speech with custom voice options using Puter.js AI API. Run and experiment with advanced TTS parameters in the playground.', + slug: 'ai-txt2speech-options', + source: '/playground/examples/ai-txt2speech-options.html' + }, + { + title: 'Text to Speech with engines', + description: 'Try different TTS engines with Puter.js AI API. Run and compare speech synthesis options directly in the playground.', + slug: 'ai-txt2speech-engines', + source: '/playground/examples/ai-txt2speech-engines.html' + }, + { + title: 'Text to Speech with OpenAI', + description: 'Generate speech with OpenAI voices using Puter.js AI API. Run and experiment with this TTS example in the playground.', + slug: 'ai-txt2speech-openai', + source: '/playground/examples/ai-txt2speech-openai.html' + }, + { + title: 'Text to Video', + description: 'Generate videos from text with Puter.js AI API. Run and experiment with this text-to-video example in the playground.', + slug: 'ai-txt2vid', + source: '/playground/examples/ai-txt2vid.html' + }, + { + title: 'Text to Video with options', + description: 'Generate videos with custom options using Puter.js AI API. Run and experiment with advanced text-to-video parameters in the playground.', + slug: 'ai-txt2vid-options', + source: '/playground/examples/ai-txt2vid-options.html' + } + ] + }, + { + title: 'FileSystem', + children: [ + { + title: 'Write File', + description: 'Write files to cloud storage with Puter.js filesystem API. Run and modify this code example instantly in your browser.', + slug: 'fs-write', + source: '/playground/examples/fs-write.html' + }, + { + title: 'Read File', + description: 'Read files from cloud storage with Puter.js filesystem API. Run and experiment with this code example in the playground.', + slug: 'fs-read', + source: '/playground/examples/fs-read.html' + }, + { + title: 'Make a Directory', + description: 'Create directories with Puter.js filesystem API. Run and modify this code example instantly in your browser.', + slug: 'fs-mkdir', + source: '/playground/examples/fs-mkdir.html' + }, + { + title: 'Delete', + description: 'Delete files with Puter.js filesystem API. Run and experiment with this code example directly in the playground.', + slug: 'fs-delete', + source: '/playground/examples/fs-delete.html' + }, + { + title: 'Read Directory', + description: 'List directory contents with Puter.js filesystem API. Run and modify this code example instantly in your browser.', + slug: 'fs-readdir', + source: '/playground/examples/fs-readdir.html' + }, + { + title: 'Rename', + description: 'Rename files and directories with Puter.js filesystem API. Run and experiment with this code example in the playground.', + slug: 'fs-rename', + source: '/playground/examples/fs-rename.html' + }, + { + title: 'Get File/Directory Info', + description: 'Get file metadata with Puter.js filesystem API. Run and modify this stat example instantly in your browser.', + slug: 'fs-stat', + source: '/playground/examples/fs-stat.html' + }, + { + title: 'Copy File/Directory', + description: 'Copy files and directories with Puter.js filesystem API. Run and experiment with this code example in the playground.', + slug: 'fs-copy', + source: '/playground/examples/fs-copy.html' + }, + { + title: 'Move File/Directory', + description: 'Move files and directories with Puter.js filesystem API. Run and modify this code example instantly in your browser.', + slug: 'fs-move', + source: '/playground/examples/fs-move.html' + }, + { + title: 'Upload', + description: 'Upload files with Puter.js filesystem API. Run and experiment with this file upload example directly in the playground.', + slug: 'fs-upload', + source: '/playground/examples/fs-upload.html' + }, + { + title: 'Write a file with deduplication', + description: 'Write files with automatic deduplication using Puter.js. Run and experiment with this example directly in the playground.', + slug: 'fs-write-dedupe', + source: '/playground/examples/fs-write-dedupe.html' + }, + { + title: 'Create a new file with input coming from a file input', + description: 'Create files from file input elements with Puter.js. Run and experiment with this example directly in the playground.', + slug: 'fs-write-from-input', + source: '/playground/examples/fs-write-from-input.html' + }, + { + title: 'Create a file in a directory that does not exist', + description: 'Write files with automatic parent directory creation using Puter.js. Run and experiment with this example in the playground.', + slug: 'fs-write-create-missing-parents', + source: '/playground/examples/fs-write-create-missing-parents.html' + }, + { + title: 'Create a directory with deduplication', + description: 'Create directories with automatic deduplication using Puter.js. Run and modify this code example instantly in your browser.', + slug: 'fs-mkdir-dedupe', + source: '/playground/examples/fs-mkdir-dedupe.html' + }, + { + title: 'Create a directory with missing parent directories', + description: 'Create nested directories automatically with Puter.js. Run and experiment with this example directly in the playground.', + slug: 'fs-mkdir-create-missing-parents', + source: '/playground/examples/fs-mkdir-create-missing-parents.html' + }, + { + title: 'Move a file with missing parent directories', + description: 'Move files with automatic parent directory creation using Puter.js. Run and experiment with this example in the playground.', + slug: 'fs-move-create-missing-parents', + source: '/playground/examples/fs-move-create-missing-parents.html' + }, + { + title: 'Delete a directory', + description: 'Delete directories with Puter.js filesystem API. Run and modify this code example instantly in your browser.', + slug: 'fs-delete-directory', + source: '/playground/examples/fs-delete-directory.html' + } + ] + }, + { + title: 'Key-Value Store', + children: [ + { + title: 'Set', + description: 'Store data with Puter.js key-value API. Run and experiment with this set example directly in the playground.', + slug: 'kv-set', + source: '/playground/examples/kv-set.html' + }, + { + title: 'Get', + description: 'Retrieve data with Puter.js key-value API. Run and modify this get example instantly in your browser.', + slug: 'kv-get', + source: '/playground/examples/kv-get.html' + }, + { + title: 'Increment', + description: 'Increment numeric values with Puter.js key-value API. Run and experiment with this example in the playground.', + slug: 'kv-incr', + source: '/playground/examples/kv-incr.html' + }, + { + title: 'Increment (Object value)', + description: 'Increment nested values in objects with Puter.js key-value API. Run and experiment with this example in the playground.', + slug: 'kv-incr-nested', + source: '/playground/examples/kv-incr-nested.html' + }, + { + title: 'Decrement', + description: 'Decrement numeric values with Puter.js key-value API. Run and modify this code example instantly in your browser.', + slug: 'kv-decr', + source: '/playground/examples/kv-decr.html' + }, + { + title: 'Decrement (Object value)', + description: 'Decrement nested values in objects with Puter.js key-value API. Run and experiment with this example in the playground.', + slug: 'kv-decr-nested', + source: '/playground/examples/kv-decr-nested.html' + }, + { + title: 'Delete', + description: 'Delete key-value pairs with Puter.js API. Run and experiment with this delete example directly in the playground.', + slug: 'kv-del', + source: '/playground/examples/kv-del.html' + }, + { + title: 'List', + description: 'List all keys with Puter.js key-value API. Run and modify this code example instantly in your browser.', + slug: 'kv-list', + source: '/playground/examples/kv-list.html' + }, + { + title: 'Flush', + description: 'Clear all data with Puter.js key-value API. Run and experiment with this flush example in the playground.', + slug: 'kv-flush', + source: '/playground/examples/kv-flush.html' + }, + { + title: "What's your name?", + description: 'Try a simple name storage app with Puter.js key-value API. Run and experiment with this interactive example in the playground.', + slug: 'kv-name', + source: '/playground/examples/kv-name.html' + } + ] + }, + { + title: 'Networking', + children: [ + { + title: 'Basic TCP Socket', + description: 'Create TCP socket connections with Puter.js networking API. Run and experiment with this example directly in the playground.', + slug: 'net-basic', + source: '/playground/examples/net-basic.html' + }, + { + title: 'TLS Socket', + description: 'Create secure TLS connections with Puter.js networking API. Run and modify this code example instantly in your browser.', + slug: 'net-tls', + source: '/playground/examples/net-tls.html' + }, + { + title: 'Fetch', + description: 'Make HTTP requests with Puter.js fetch API. Run and experiment with this example directly in the playground.', + slug: 'net-fetch', + source: '/playground/examples/net-fetch.html' + } + ] + }, + { + title: 'Hosting', + children: [ + { + title: 'Create a simple website displaying "Hello world!"', + description: 'Deploy a simple website instantly with Puter.js hosting API. Run and experiment with this example directly in the playground.', + slug: 'hosting-create', + source: '/playground/examples/hosting-create.html' + }, + { + title: 'Create 3 random websites and then list them', + description: 'Create and list multiple websites with Puter.js hosting API. Run and modify this code example instantly in your browser.', + slug: 'hosting-list', + source: '/playground/examples/hosting-list.html' + }, + { + title: 'Create a random website then delete it', + description: 'Deploy and delete websites with Puter.js hosting API. Run and experiment with this example in the playground.', + slug: 'hosting-delete', + source: '/playground/examples/hosting-delete.html' + }, + { + title: 'Update a subdomain to point to a new directory', + description: 'Update website subdomains with Puter.js hosting API. Run and modify this code example instantly in your browser.', + slug: 'hosting-update', + source: '/playground/examples/hosting-update.html' + }, + { + title: 'Retrieve information about a subdomain', + description: 'Get website information with Puter.js hosting API. Run and experiment with this example directly in the playground.', + slug: 'hosting-get', + source: '/playground/examples/hosting-get.html' + } + ] + }, + { + title: 'Authentication', + children: [ + { + title: 'Sign in', + description: 'Implement user sign-in with Puter.js auth API. Run and experiment with this authentication example in the playground.', + slug: 'auth-sign-in', + source: '/playground/examples/auth-sign-in.html' + }, + { + title: 'Sign out', + description: 'Sign out users with Puter.js auth API. Run and modify this code example instantly in your browser.', + slug: 'auth-sign-out', + source: '/playground/examples/auth-sign-out.html' + }, + { + title: 'Check sign in', + description: 'Check authentication status with Puter.js auth API. Run and experiment with this example directly in the playground.', + slug: 'auth-is-signed-in', + source: '/playground/examples/auth-is-signed-in.html' + }, + { + title: 'Get user', + description: 'Retrieve user information with Puter.js auth API. Run and modify this code example instantly in your browser.', + slug: 'auth-get-user', + source: '/playground/examples/auth-get-user.html' + }, + { + title: "Get user's monthly usage", + description: 'Get user usage statistics with Puter.js auth API. Run and experiment with this example directly in the playground.', + slug: 'auth-get-monthly-usage', + source: '/playground/examples/auth-get-monthly-usage.html' + } + ] + }, + { + title: 'Apps', + children: [ + { + title: 'To-Do List', + description: 'Try a complete to-do list app built with Puter.js. Run, edit, and experiment with this working example in the playground.', + slug: 'app-todo', + source: '/playground/examples/app-todo.html' + }, + { + title: 'AI Chat', + description: 'Try a complete AI chat app built with Puter.js. Run, edit, and experiment with this working example in the playground.', + slug: 'app-ai-chat', + source: '/playground/examples/app-ai-chat.html' + }, + { + title: 'Camera Photo Describer', + description: 'Try a camera app with AI image analysis built with Puter.js. Run and experiment with this working example in the playground.', + slug: 'app-camera', + source: '/playground/examples/app-camera.html' + }, + { + title: 'Text Summarizer', + description: 'Try an AI text summarizer app built with Puter.js. Run, edit, and experiment with this working example in the playground.', + slug: 'app-summarizer', + source: '/playground/examples/app-summarizer.html' + }, + { + title: 'Create an app pointing to example.com', + description: 'Create app registrations with Puter.js apps API. Run and experiment with this example directly in the playground.', + slug: 'app-create', + source: '/playground/examples/app-create.html' + }, + { + title: 'Create 3 random apps and then list them', + description: 'Create and list app registrations with Puter.js apps API. Run and modify this code example instantly in your browser.', + slug: 'app-list', + source: '/playground/examples/app-list.html' + }, + { + title: 'Create a random app then delete it', + description: 'Create and delete app registrations with Puter.js apps API. Run and experiment with this example in the playground.', + slug: 'app-delete', + source: '/playground/examples/app-delete.html' + }, + { + title: 'Create a random app then change its title', + description: 'Update app registrations with Puter.js apps API. Run and modify this code example instantly in your browser.', + slug: 'app-update', + source: '/playground/examples/app-update.html' + }, + { + title: 'Create a random app then get it', + description: 'Get app information with Puter.js apps API. Run and experiment with this example directly in the playground.', + slug: 'app-get', + source: '/playground/examples/app-get.html' + } + ] + }, + { + title: 'Workers', + children: [ + { + title: 'Create a worker', + description: 'Deploy serverless workers with Puter.js workers API. Run and experiment with this example directly in the playground.', + slug: 'workers-create', + source: '/playground/examples/workers-create.html' + }, + { + title: 'List workers', + description: 'List all workers with Puter.js workers API. Run and modify this code example instantly in your browser.', + slug: 'workers-list', + source: '/playground/examples/workers-list.html' + }, + { + title: 'Get a worker', + description: 'Get worker information with Puter.js workers API. Run and experiment with this example in the playground.', + slug: 'workers-get', + source: '/playground/examples/workers-get.html' + }, + { + title: 'Workers Management', + description: 'Manage workers with Puter.js workers API. Run and modify this complete management example instantly in your browser.', + slug: 'workers-management', + source: '/playground/examples/workers-management.html' + }, + { + title: 'Authenticated Worker Requests', + description: 'Execute authenticated worker requests with Puter.js workers API. Run and experiment with this example in the playground.', + slug: 'workers-exec', + source: '/playground/examples/workers-exec.html' + } + ] + } +]; + +module.exports = examples; \ No newline at end of file diff --git a/src/playground.js b/src/playground.js new file mode 100644 index 0000000..f88233a --- /dev/null +++ b/src/playground.js @@ -0,0 +1,173 @@ +const fs = require('fs'); +const path = require('path'); +const examples = require('./examples') + +// Function to generate sidebar HTML +const generateSidebarHtml = (sections) => { + let sidebarHtml = ''; + return sidebarHtml; +}; + +const playgroundHtml = ` + + + + + + + {{TITLE}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Puter.js Playground

+ +
+ +
+ + + +
+ +
+
+ Code +
+
+
+ + +
+ + +
+
+ Preview + +
+
+
+
+
+ + + + +` + +const generatePlayground = () => { + // Generate sidebar HTML once for all examples + const sidebarHtml = generateSidebarHtml(examples); + + let totalExamples = 0; + + examples.forEach(section => { + section.children.forEach(example => { + // Read source file from src/ directory + const sourcePath = path.join('src', example.source); + const sourceContent = fs.readFileSync(sourcePath, 'utf8'); + + // Copy playgroundHtml to avoid tainting the original + let htmlTemplate = playgroundHtml.slice(); + + htmlTemplate = htmlTemplate.replace('{{SIDEBAR}}', sidebarHtml); + const pageTitle = example.slug === '' ? 'Puter.js Playground' : `${example.title} | Puter.js Playground`; + htmlTemplate = htmlTemplate.replaceAll('{{TITLE}}', pageTitle); + const pageDescription = example.description || 'Try Puter.js instantly with interactive examples in your browser. Run, edit, and experiment with code - no installation or setup required.'; + htmlTemplate = htmlTemplate.replaceAll('{{DESCRIPTION}}', pageDescription); + const canonicalUrl = `https://docs.puter.com/playground/${example.slug ? example.slug + '/' : ''}`; + htmlTemplate = htmlTemplate.replaceAll('{{CANONICAL}}', canonicalUrl); + const finalHtml = htmlTemplate.replace('{{CODE}}', sourceContent); + + // Create output directory + const outputDir = path.join('dist', 'playground', example.slug); + fs.mkdirSync(outputDir, { recursive: true }); + + // Write the file + const outputPath = path.join(outputDir, 'index.html'); + fs.writeFileSync(outputPath, finalHtml, 'utf8'); + + totalExamples++; + }); + }); + console.log(`Generated ${totalExamples} playground examples.`); +} + +module.exports = { generatePlayground }; diff --git a/src/playground/assets/css/style.css b/src/playground/assets/css/style.css new file mode 100644 index 0000000..f9dac99 --- /dev/null +++ b/src/playground/assets/css/style.css @@ -0,0 +1,328 @@ +body { + margin: 0; + padding: 0; + height: 100vh; + -webkit-font-smoothing: antialiased; +} + +* { + box-sizing: border-box; +} + +body { + font-family: "Roboto", Arial, Helvetica, sans-serif; +} + +#output-iframe { + width: 100%; + height: 100%; + border: none; +} + +#code, +#output { + overflow: hidden; +} + +#run { + float: right; + margin: 10px; + padding: 7px 20px; + background-color: #2563eb; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-weight: bold; + user-select: none; +} + +#run:hover { + background-color: #1d4ed8; +} + +#run:active { + background-color: #1e40af; +} + +#select-example { + padding: 5px 10px; + min-width: 300px; + font-size: 14px; + margin-right: 10px; +} + +.go-to-docs { + margin-right: 5px; + color: white; + text-decoration: none; + border: 2px solid white; + padding: 5px 7px; + box-sizing: border-box; + border-radius: 4px; + font-size: 15px; + float: right; +} + +.main-container { + display: flex; + height: calc(100vh - 50px); + width: 100%; +} + +#sidebar-container { + width: 280px; + background: #f8f9fa; + border-right: 1px solid #ccc; + overflow: hidden; + flex-shrink: 0; + display: flex; + flex-direction: column; +} + +#sidebar-container.collapsed { + margin-left: 0; + width: auto; +} + +#sidebar-container.collapsed .sidebar-title, +#sidebar-container.collapsed .sidebar { + display: none; +} + +#code-container { + width: 50%; + height: 100%; + position: relative; + min-width: 0; + display: flex; + flex-direction: column; +} + +#output-container { + width: 50%; + height: 100%; + position: relative; + min-width: 0; + display: flex; + flex-direction: column; +} + +#run span { + background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%23fff%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20class%3D%22lucide%20lucide-play-icon%20lucide-play%22%3E%3Cpath%20d%3D%22M5%205a2%202%200%200%201%203.008-1.728l11.997%206.998a2%202%200%200%201%20.003%203.458l-12%207A2%202%200%200%201%205%2019z%22%2F%3E%3C%2Fsvg%3E"); + width: 20px; + display: block; + height: 20px; + background-size: 15px; + background-repeat: no-repeat; + float: left; + margin-top: 2px; + margin-right: 5px; +} + +.navbar { + float: right; + display: flex; + flex-direction: row; + align-items: center; +} + +.navbar a { + color: white; + text-decoration: none; + margin-right: 20px; +} + +.navbar a:hover { + text-decoration: underline; +} + +.logo { + margin: 0; + font-size: 25px; + color: white; + font-weight: 400; + flex-grow: 1; + font-weight: 300; + font-size: 21px; + display: flex; + align-items: center; +} + +.logo a { + color: white; + text-decoration: none; +} + +@media (max-width: 500px) { + #select-example { + max-width: 300px; + font-size: 13px; + width: 200px; + margin-right: 0; + } + + .logo { + font-size: 16px; + } + + .navbar a { + font-size: 14px; + margin-right: 10px; + } +} + +@media (max-width: 768px) { + .main-container { + flex-direction: column; + } + + #sidebar-container { + position: fixed; + top: 50px; + left: 0; + right: 0; + width: 100%; + height: 50px; + border-right: none; + z-index: 1000; + background: #f8f9fa; + } + + /* Always show the sidebar title on mobile */ + #sidebar-container .sidebar-title { + display: block !important; + } + + /* When sidebar is open on mobile, it overlays the content */ + #sidebar-container:not(.collapsed) { + height: calc(100vh - 50px); + } + + /* When sidebar is collapsed, only the header is shown */ + #sidebar-container.collapsed .sidebar { + display: none; + } + + /* Wrapper for code and output - stack vertically on mobile */ + .main-container > div:last-child { + flex-direction: column !important; + flex: 1; + min-height: 0; + margin-top: 50px; + } + + #code-container { + width: 100% !important; + height: 50%; + flex: 1; + min-height: 0; + } + + #output-container { + width: 100% !important; + height: 50%; + flex: 1; + min-height: 0; + } + + #select-example { + max-width: 300px; + } + + .resizer { + display: none; + } +} + +.resizer { + width: 6px; + background: #e1e1e1; + cursor: col-resize; + z-index: 100; + transition: background 0.2s ease; + user-select: none; + flex-shrink: 0; +} + +.resizer:hover, +.resizer.dragging { + background: #ccc; +} + +/* Sidebar styles */ +.sidebar { + flex: 1; + overflow-y: auto; + padding-left: 20px; + padding-right: 20px; +} + +.sidebar-header { + height: 50px; + display: flex; + align-items: center; + padding: 0 10px; + border-bottom: 1px solid #ccc; + background: #fff; +} + +.sidebar-toggle { + background: transparent; + border: none; + cursor: pointer; + font-size: 20px; + padding: 5px; + color: #333; + display: flex; + align-items: center; + justify-content: center; +} + +.sidebar-toggle:hover { + background: #e9ecef; +} + +.sidebar-title { + font-size: 16px; + font-weight: 500; + color: #555; + margin-left: 10px; +} + +.sidebar-content { + padding-top: 20px; +} + +.sidebar-category { + margin-bottom: 30px; +} + +.sidebar-category-title { + font-weight: 500; + font-size: 15px; + color: #012238; + margin-bottom: 20px; +} + +.sidebar-category:first-child .sidebar-category-title { + padding-top: 0; +} + +.sidebar-item { + display: block; + color: #5f5f5f; + text-decoration: none; + margin-right: 15px; + margin-bottom: 15px; + font-size: 15px; +} + +.sidebar-item:hover { + text-decoration: underline; +} + +.sidebar-item.active { + text-decoration: underline; + font-weight: 500; +} diff --git a/src/playground/assets/js/app.js b/src/playground/assets/js/app.js new file mode 100644 index 0000000..1a4bd84 --- /dev/null +++ b/src/playground/assets/js/app.js @@ -0,0 +1,340 @@ +let editor; +// on document load +document.addEventListener('DOMContentLoaded', function () { + // load monaco editor + require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' } }); + require(['vs/editor/editor.main'], function () { + // get editor element + var editorElement = document.getElementById('code'); + // create editor + editor = monaco.editor.create(editorElement, { + language: 'html', + fontFamily: 'monospace', + minimap: { + enabled: false + } + }); + editor.updateOptions({ fontFamily: 'monospace' }); + + // Load initial code from iframe + editor.setValue(document.getElementById('initial-code').textContent); + // auto run? + var urlParams = new URLSearchParams(window.location.search); + var autoRun = urlParams.get('autorun'); + if (autoRun) { + loadStringInIframe(editor.getValue()); + } + }); + + function fetchGitHubData() { + // GitHub API fetching and handling + + const url = "https://api.github.com/repos/HeyPuter/puter"; + + function formatNumber(num) { + if (num < 1000) { + return num; // return the same number if less than 1000 + } else if (num < 1000000) { + return (num / 1000).toFixed(1) + 'K'; // convert to K for thousands + } else { + return (num / 1000000).toFixed(1) + 'M'; // convert to M for millions + } + } + + $.getJSON(url, function (data) { + $('.github-stars').text(formatNumber(data.stargazers_count) + ""); + }).fail(function (jqxhr, textStatus, error) { + let err = textStatus + ", " + error; + console.error("Request Failed: " + err); + $('.github-stars').text('Heyputer/Puter'); + }); + } + + fetchGitHubData(); +}); + +// Attach the resize event listener to the window +window.addEventListener('resize', () => { + editor.layout(); +}); + +function loadStringInIframe(str) { + // Create a new iframe element + var iframe = document.createElement('iframe'); + + // set iframe id + iframe.id = 'output-iframe'; + + // append to output + var output = document.getElementById('output'); + output.innerHTML = ''; + output.appendChild(iframe); + + // Get the document of the iframe + var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; + + // Write the string content into the iframe + iframeDoc.open(); + iframeDoc.write(str); + iframeDoc.close(); +} + +// ctrl + enter to run +document.addEventListener('keydown', function (e) { + if (e.ctrlKey && e.key === 'Enter') { + loadStringInIframe(editor.getValue()); + } +}); + +var code = document.getElementById('code'); +var run = document.getElementById('run'); +run.addEventListener('click', function () { + loadStringInIframe(editor.getValue()); +}); + +// Resizer functionality +const resizer = document.querySelector('.resizer'); +const codeContainer = document.getElementById('code-container'); +const outputContainer = document.getElementById('output-container'); +let isResizing = false; +let startX; +let startWidthCode; +let startWidthOutput; + +resizer.addEventListener('mousedown', (e) => { + isResizing = true; + resizer.classList.add('dragging'); + startX = e.pageX; + startWidthCode = codeContainer.offsetWidth; + startWidthOutput = outputContainer.offsetWidth; + + // Disable pointer events on iframe during resize + const iframe = document.getElementById('output-iframe'); + if (iframe) { + iframe.style.pointerEvents = 'none'; + } +}); + +document.addEventListener('mousemove', (e) => { + if (!isResizing) return; + + const parentWidth = codeContainer.parentElement.offsetWidth; + const diffX = e.pageX - startX; + + const newCodeWidth = ((startWidthCode + diffX) / parentWidth * 100); + const newOutputWidth = ((startWidthOutput - diffX) / parentWidth * 100); + + // Set minimum width to 20% + if (newCodeWidth >= 20 && newOutputWidth >= 20) { + codeContainer.style.width = `${newCodeWidth}%`; + outputContainer.style.width = `${newOutputWidth}%`; + editor.layout(); // Resize Monaco editor + } +}); + +document.addEventListener('mouseup', () => { + if (isResizing) { + isResizing = false; + resizer.classList.remove('dragging'); + + // Re-enable pointer events on iframe after resize + const iframe = document.getElementById('output-iframe'); + if (iframe) { + iframe.style.pointerEvents = 'auto'; + } + } +}); + +// Sidebar toggle functionality +const sidebarToggle = document.getElementById('sidebar-toggle'); +const sidebarContainer = document.getElementById('sidebar-container'); + +// Collapse sidebar by default on mobile +if (window.innerWidth <= 768) { + sidebarContainer.classList.add('collapsed'); +} + +sidebarToggle.addEventListener('click', () => { + sidebarContainer.classList.toggle('collapsed'); + // Re-layout editor + if (editor) { + editor.layout(); + } +}); + +// Highlight active example in sidebar +function updateActiveSidebarItem() { + const currentPath = window.location.pathname; + const sidebarItems = document.querySelectorAll('.sidebar-item'); + sidebarItems.forEach(item => { + if (item.getAttribute('href') === currentPath) { + item.classList.add('active'); + } else { + item.classList.remove('active'); + } + }); +} +updateActiveSidebarItem(); + +// Scroll sidebar to center the active item on first load +const sidebar = document.querySelector('.sidebar'); +const activeItem = document.querySelector('.sidebar-item.active'); +if (sidebar && activeItem) { + const sidebarRect = sidebar.getBoundingClientRect(); + const activeItemRect = activeItem.getBoundingClientRect(); + const scrollOffset = activeItemRect.top - sidebarRect.top + sidebar.scrollTop + - sidebar.clientHeight / 2 + activeItem.clientHeight / 2; + sidebar.scrollTop = scrollOffset; +} + +// Client-side routing for sidebar links +document.addEventListener('click', function (e) { + // Check if clicked element is a sidebar item + const sidebarItem = e.target.closest('.sidebar-item'); + if (!sidebarItem) return; + + // Don't intercept if modifier keys are pressed + if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; + + const href = sidebarItem.getAttribute('href'); + if (!href) return; + + // Don't intercept external links or current page + try { + const url = new URL(href, window.location.href); + if (url.origin !== window.location.origin) return; + if (url.pathname === window.location.pathname) return; + } catch (err) { + return; + } + + e.preventDefault(); + + // Update history + window.history.pushState({ reload: true }, '', href); + + // Clear the preview/output + const output = document.getElementById('output'); + if (output) { + output.innerHTML = ''; + } + + // Fetch the new page + $.ajax({ + url: href, + method: 'GET' + }).done(function (data) { + // Parse the HTML response + const parser = new DOMParser(); + const doc = parser.parseFromString(data, 'text/html'); + + // Extract code content from the initial-code iframe + const initialCodeIframe = doc.getElementById('initial-code'); + if (initialCodeIframe && editor) { + const newCode = initialCodeIframe.textContent; + editor.setValue(newCode); + } + + // Update page title + const newTitle = doc.querySelector('title'); + if (newTitle) { + document.title = newTitle.textContent; + } + + // Update meta description + const newDescription = doc.querySelector('meta[name="description"]'); + if (newDescription) { + let descriptionMeta = document.querySelector('meta[name="description"]'); + if (!descriptionMeta) { + descriptionMeta = document.createElement('meta'); + descriptionMeta.setAttribute('name', 'description'); + document.head.appendChild(descriptionMeta); + } + descriptionMeta.setAttribute('content', newDescription.getAttribute('content')); + } + + // Update canonical URL + const newCanonical = doc.querySelector('link[rel="canonical"]'); + if (newCanonical) { + let canonical = document.querySelector('link[rel="canonical"]'); + if (!canonical) { + canonical = document.createElement('link'); + canonical.setAttribute('rel', 'canonical'); + document.head.appendChild(canonical); + } + canonical.setAttribute('href', newCanonical.getAttribute('href')); + } + + // Update Open Graph tags + const ogTitle = doc.querySelector('meta[property="og:title"]'); + if (ogTitle) { + let ogTitleMeta = document.querySelector('meta[property="og:title"]'); + if (!ogTitleMeta) { + ogTitleMeta = document.createElement('meta'); + ogTitleMeta.setAttribute('property', 'og:title'); + document.head.appendChild(ogTitleMeta); + } + ogTitleMeta.setAttribute('content', ogTitle.getAttribute('content')); + } + + const ogDescription = doc.querySelector('meta[property="og:description"]'); + if (ogDescription) { + let ogDescriptionMeta = document.querySelector('meta[property="og:description"]'); + if (!ogDescriptionMeta) { + ogDescriptionMeta = document.createElement('meta'); + ogDescriptionMeta.setAttribute('property', 'og:description'); + document.head.appendChild(ogDescriptionMeta); + } + ogDescriptionMeta.setAttribute('content', ogDescription.getAttribute('content')); + } + + const ogUrl = doc.querySelector('meta[name="og:url"]'); + if (ogUrl) { + let ogUrlMeta = document.querySelector('meta[name="og:url"]'); + if (!ogUrlMeta) { + ogUrlMeta = document.createElement('meta'); + ogUrlMeta.setAttribute('name', 'og:url'); + document.head.appendChild(ogUrlMeta); + } + ogUrlMeta.setAttribute('content', ogUrl.getAttribute('content')); + } + + // Update Twitter Card tags + const twitterTitle = doc.querySelector('meta[name="twitter:title"]'); + if (twitterTitle) { + let twitterTitleMeta = document.querySelector('meta[name="twitter:title"]'); + if (!twitterTitleMeta) { + twitterTitleMeta = document.createElement('meta'); + twitterTitleMeta.setAttribute('name', 'twitter:title'); + document.head.appendChild(twitterTitleMeta); + } + twitterTitleMeta.setAttribute('content', twitterTitle.getAttribute('content')); + } + + const twitterDescription = doc.querySelector('meta[name="twitter:description"]'); + if (twitterDescription) { + let twitterDescriptionMeta = document.querySelector('meta[name="twitter:description"]'); + if (!twitterDescriptionMeta) { + twitterDescriptionMeta = document.createElement('meta'); + twitterDescriptionMeta.setAttribute('name', 'twitter:description'); + document.head.appendChild(twitterDescriptionMeta); + } + twitterDescriptionMeta.setAttribute('content', twitterDescription.getAttribute('content')); + } + + // Update active sidebar item + updateActiveSidebarItem(); + + }).fail(function (error) { + console.error('Failed to load page:', error); + // On error, do a full page load + window.location.href = href; + }); +}); + +// Handle popstate (back/forward navigation) with reload +window.addEventListener('popstate', function () { + if (window.history.state && window.history.state.reload) { + window.location.href = window.location.href; + } +}); \ No newline at end of file diff --git a/src/playground/index.html b/src/playground/index.html deleted file mode 100755 index 21cdc93..0000000 --- a/src/playground/index.html +++ /dev/null @@ -1,478 +0,0 @@ - - - - - - Puter.js Playground - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Puter.js Playground

- -
-
-
-
-

Code:

-
- Example: - -
-
-
-
-
-
-
-

Preview:

- -
-
-
-
- - - - diff --git a/src/playground/sandbox.svg b/src/playground/sandbox.svg deleted file mode 100644 index aacf192..0000000 --- a/src/playground/sandbox.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file