-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.json
1 lines (1 loc) · 10.6 KB
/
app.json
1
[{"name": "app.py", "content": "from shiny import App, ui, render, reactive\nimport asyncio\n\napp_ui = ui.page_fluid(\n ui.div(\n {\"class\": \"header-container\"},\n ui.div(\n {\"class\": \"header-content\"},\n ui.h2(\"Biomechanics Tutor\", class_=\"main-header\"),\n ui.div(\n {\"class\": \"selector-container\"},\n ui.input_select(\n \"swf_file\",\n None,\n {\n \"\": \"Select Topic\",\n \"BM05.swf\": \"Basic Math\",\n \"CALC05.swf\": \"Calculus\",\n \"TRIG05.swf\": \"Trigonometry\",\n \"LK05.swf\": \"Linear Kinematics\",\n \"FR05.swf\": \"Force Resolution\",\n \"IM05.swf\": \"Impulse Momentum\",\n \"FRIC05.swf\": \"Friction\",\n \"PM.swf\": \"Projectile Motion\",\n \"AK05.swf\": \"Angular Kinematics\",\n \"GK05.swf\": \"General Kinematics\",\n \"TOR05.swf\": \"Torque/Moment of Force\",\n \"MI05.swf\": \"Moment of Inertia\",\n \"SE05.swf\": \"Static Equilibrium\",\n \"DE05.swf\": \"Dynamics\",\n },\n selected=\"\"\n ),\n ),\n ),\n ),\n ui.row(\n ui.column(12,\n ui.output_ui(\"swf_container\"),\n )\n ),\n ui.tags.head(\n ui.tags.meta({\"http-equiv\": \"Cache-Control\", \"content\": \"no-cache, no-store, must-revalidate\"}),\n ui.tags.meta({\"http-equiv\": \"Pragma\", \"content\": \"no-cache\"}),\n ui.tags.meta({\"http-equiv\": \"Expires\", \"content\": \"0\"}),\n # Load Ruffle from jsDelivr CDN instead\n ui.tags.script({\"src\": \"https://cdn.jsdelivr.net/npm/@ruffle-rs/ruffle\", \"crossorigin\": \"anonymous\"}),\n ui.tags.style(\"\"\"\n /* Styles remain the same */\n .header-container {\n background: linear-gradient(135deg, #f5f5f5, #e0e0e0);\n padding: 8px 16px;\n margin-bottom: 10px;\n border-radius: 6px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n height: 60px;\n max-width: 1600px;\n margin: 0 auto;\n width: calc(100% - 32px);\n }\n \n .header-content {\n display: flex;\n align-items: center;\n height: 100%;\n gap: 30px;\n }\n \n .main-header {\n color: #333333;\n margin: 0;\n font-size: 1.75em;\n font-weight: 600;\n text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n }\n \n .selector-container {\n flex-grow: 1;\n max-width: 300px;\n }\n \n .form-group {\n margin-bottom: 0 !important;\n }\n \n .swf-container {\n width: calc(100% - 32px);\n max-width: 1600px;\n margin: 20px auto;\n background-color: white;\n border: 1px solid #ddd;\n border-radius: 6px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n \n .swf-aspect-ratio {\n position: relative;\n padding-bottom: 75%;\n height: 0;\n overflow: hidden;\n border-radius: 6px;\n }\n \n #swf-player {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: white;\n }\n \n #swf-player ruffle-player {\n width: 100%;\n height: 100%;\n display: block;\n }\n \n @media (max-width: 768px) {\n .header-container {\n height: auto;\n padding: 10px;\n }\n \n .header-content {\n flex-direction: column;\n gap: 10px;\n align-items: stretch;\n }\n \n .selector-container {\n max-width: none;\n }\n }\n \"\"\"),\n ui.tags.script(\"\"\"\n window.loadSWF = async function(swf_url) {\n console.log(\"loadSWF called with URL:\", swf_url);\n \n try {\n const container = document.getElementById(\"swf-player\");\n if (!container) {\n throw new Error(\"SWF container not found\");\n }\n \n container.innerHTML = '';\n \n if (!swf_url) {\n container.innerHTML = '<div style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">Select a topic to load content</div>';\n return;\n }\n \n // Wait for Ruffle with a timeout\n const waitForRuffle = async (timeout = 5000) => {\n const start = Date.now();\n \n while (!window.RufflePlayer && (Date.now() - start < timeout)) {\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n \n if (!window.RufflePlayer) {\n throw new Error(\"Ruffle failed to load within \" + timeout + \"ms\");\n }\n \n return window.RufflePlayer.newest();\n };\n \n const ruffle = await waitForRuffle();\n const player = ruffle.createPlayer();\n \n Object.assign(player.style, {\n position: 'absolute',\n width: '100%',\n height: '100%',\n top: '0',\n left: '0',\n visibility: 'visible'\n });\n \n container.appendChild(player);\n \n // Add load and error handlers\n player.addEventListener('loadeddata', () => {\n console.log('SWF loaded data');\n player.style.visibility = 'visible';\n });\n \n player.addEventListener('error', (e) => {\n console.error('SWF error:', e);\n throw e;\n });\n \n // Test URL availability before loading\n try {\n const response = await fetch(swf_url, { method: 'HEAD' });\n if (!response.ok) {\n throw new Error(`SWF file not found (${response.status})`);\n }\n } catch (error) {\n throw new Error(`Failed to access SWF file: ${error.message}`);\n }\n \n // Load the SWF with cache busting\n const cacheBustUrl = `${swf_url}?_=${Date.now()}`;\n await player.load({\n url: cacheBustUrl,\n allowScriptAccess: true,\n backgroundColor: \"#FFFFFF\",\n scale: \"showall\",\n quality: \"high\",\n wmode: \"direct\",\n menu: true,\n allowNetworking: \"all\"\n });\n \n console.log(\"SWF loaded successfully\");\n \n } catch (error) {\n console.error(\"Error in loadSWF:\", error);\n const container = document.getElementById(\"swf-player\");\n if (container) {\n container.innerHTML = `\n <div style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center;\">\n <div style=\"color: red; padding: 20px;\">Error loading content: ${error.message}</div>\n <div style=\"color: #666; font-size: 0.9em;\">Please verify that the content exists and try refreshing the page.</div>\n </div>`;\n }\n }\n };\n\n // Add custom message handler\n Shiny.addCustomMessageHandler(\"loadSWF\", function(swf_url) {\n window.loadSWF(swf_url);\n });\n \"\"\")\n )\n)\n\ndef server(input, output, session):\n @render.ui\n def swf_container():\n return ui.HTML(\"\"\"\n <div class=\"swf-container\">\n <div class=\"swf-aspect-ratio\">\n <div id=\"swf-player\">\n <div style=\"position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\">\n Select a topic to load content\n </div>\n </div>\n </div>\n </div>\n \"\"\")\n\n @reactive.effect\n @reactive.event(input.swf_file)\n async def _():\n swf_file = input.swf_file()\n if swf_file:\n from urllib.parse import quote\n encoded_file = quote(swf_file)\n # Change to raw.githubusercontent.com URL\n swf_url = f\"https://raw.githubusercontent.com/mklimstra/Biomech-tutor/main/swf/{encoded_file}\"\n print(f\"Loading SWF from URL: {swf_url}\")\n await session.send_custom_message(\"loadSWF\", swf_url)\n else:\n await session.send_custom_message(\"loadSWF\", \"\")\n\napp = App(app_ui, server)\n", "type": "text"}]