Upload 7 files
Browse files- .hf-space.yaml +8 -0
- index.html +266 -0
- package.json +21 -0
- script.js +648 -0
- server.js +473 -0
- style.css +265 -0
.hf-space.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Android Studio Web - Real Build with Android 10 Preview
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
index.html
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="bn">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Android Studio Web - Real Build + Android 10 Preview</title>
|
| 7 |
+
|
| 8 |
+
<!-- Tailwind CSS -->
|
| 9 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
+
|
| 11 |
+
<!-- Icons -->
|
| 12 |
+
<script src="https://unpkg.com/lucide@latest"></script>
|
| 13 |
+
|
| 14 |
+
<!-- Fonts -->
|
| 15 |
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 16 |
+
|
| 17 |
+
<!-- xterm.js for terminal -->
|
| 18 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" />
|
| 19 |
+
|
| 20 |
+
<!-- Custom CSS -->
|
| 21 |
+
<link rel="stylesheet" href="style.css">
|
| 22 |
+
</head>
|
| 23 |
+
<body class="bg-[#2B2B2B] text-gray-300 h-screen flex flex-col overflow-hidden">
|
| 24 |
+
<!-- Top Toolbar -->
|
| 25 |
+
<div class="h-12 bg-[#3C3F41] border-b border-[#4E4E4E] flex items-center px-4 justify-between select-none">
|
| 26 |
+
<div class="flex items-center space-x-4">
|
| 27 |
+
<div class="flex items-center space-x-2">
|
| 28 |
+
<i data-lucide="smartphone" class="w-5 h-5 text-[#3DDC84]"></i>
|
| 29 |
+
<span class="font-semibold text-white text-sm">Android Studio Web</span>
|
| 30 |
+
<span class="text-xs text-gray-500">Real Build + Android 10</span>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<!-- Project Dropdown -->
|
| 34 |
+
<div class="relative">
|
| 35 |
+
<button onclick="toggleProjectDropdown()" class="flex items-center space-x-1 bg-[#2B2B2B] px-3 py-1 rounded text-sm">
|
| 36 |
+
<i data-lucide="folder" class="w-4 h-4 text-yellow-500"></i>
|
| 37 |
+
<span id="current-project">Select Project</span>
|
| 38 |
+
<i data-lucide="chevron-down" class="w-4 h-4"></i>
|
| 39 |
+
</button>
|
| 40 |
+
<div id="project-dropdown" class="absolute top-full left-0 mt-1 bg-[#3C3F41] border border-[#4E4E4E] rounded shadow-lg hidden z-50">
|
| 41 |
+
<div class="p-2 min-w-[200px]" id="project-list">
|
| 42 |
+
<!-- Projects will load here -->
|
| 43 |
+
</div>
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<!-- File Operations -->
|
| 48 |
+
<div class="h-4 w-px bg-gray-600"></div>
|
| 49 |
+
<div class="flex items-center space-x-1">
|
| 50 |
+
<button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="createNewFile()" title="New File">
|
| 51 |
+
<i data-lucide="file-plus" class="w-4 h-4"></i>
|
| 52 |
+
</button>
|
| 53 |
+
<button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="saveCurrentFile()" title="Save (Ctrl+S)">
|
| 54 |
+
<i data-lucide="save" class="w-4 h-4"></i>
|
| 55 |
+
</button>
|
| 56 |
+
<button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="refreshFiles()" title="Refresh">
|
| 57 |
+
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
| 58 |
+
</button>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<!-- Build & Run -->
|
| 62 |
+
<div class="h-4 w-px bg-gray-600"></div>
|
| 63 |
+
<div class="flex items-center space-x-1">
|
| 64 |
+
<button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="startBuild()" title="Build APK">
|
| 65 |
+
<i data-lucide="play" class="w-4 h-4 text-[#3DDC84]"></i>
|
| 66 |
+
</button>
|
| 67 |
+
<button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="downloadAPK()" title="Download APK">
|
| 68 |
+
<i data-lucide="download" class="w-4 h-4 text-blue-400"></i>
|
| 69 |
+
</button>
|
| 70 |
+
<button class="p-1.5 hover:bg-gray-600 rounded text-gray-400 hover:text-white" onclick="installOnPreview()" title="Install on Preview">
|
| 71 |
+
<i data-lucide="smartphone" class="w-4 h-4 text-green-400"></i>
|
| 72 |
+
</button>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Right side -->
|
| 77 |
+
<div class="flex items-center space-x-3">
|
| 78 |
+
<div class="flex items-center space-x-2 px-3 py-1 bg-[#2B2B2B] rounded-full text-xs">
|
| 79 |
+
<i data-lucide="git-branch" class="w-3 h-3"></i>
|
| 80 |
+
<span>main</span>
|
| 81 |
+
</div>
|
| 82 |
+
<button class="p-1.5 hover:bg-gray-600 rounded" onclick="openSettings()">
|
| 83 |
+
<i data-lucide="settings" class="w-4 h-4"></i>
|
| 84 |
+
</button>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<!-- Main Content -->
|
| 89 |
+
<div class="flex-1 flex overflow-hidden">
|
| 90 |
+
<!-- Left Sidebar - Project Files -->
|
| 91 |
+
<div class="w-64 bg-[#3C3F41] border-r border-[#4E4E4E] flex flex-col">
|
| 92 |
+
<!-- Project Header -->
|
| 93 |
+
<div class="p-3 border-b border-[#4E4E4E]">
|
| 94 |
+
<div class="flex items-center justify-between mb-2">
|
| 95 |
+
<span class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Project</span>
|
| 96 |
+
<button onclick="createNewProject()" class="hover:bg-gray-600 p-1 rounded" title="New Project">
|
| 97 |
+
<i data-lucide="folder-plus" class="w-4 h-4"></i>
|
| 98 |
+
</button>
|
| 99 |
+
</div>
|
| 100 |
+
<div class="flex items-center space-x-2 text-sm">
|
| 101 |
+
<i data-lucide="folder" class="w-4 h-4 text-yellow-600"></i>
|
| 102 |
+
<span class="font-medium text-white" id="project-name-display">No Project</span>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
<!-- File Tree -->
|
| 107 |
+
<div class="flex-1 overflow-y-auto py-2" id="file-tree">
|
| 108 |
+
<div class="px-3 py-1 text-xs text-gray-500">Open a project to see files</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<!-- Bottom Tools -->
|
| 112 |
+
<div class="border-t border-[#4E4E4E] p-2">
|
| 113 |
+
<div class="flex items-center justify-around">
|
| 114 |
+
<button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Project Structure">
|
| 115 |
+
<i data-lucide="structure" class="w-4 h-4"></i>
|
| 116 |
+
</button>
|
| 117 |
+
<button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Search">
|
| 118 |
+
<i data-lucide="search" class="w-4 h-4"></i>
|
| 119 |
+
</button>
|
| 120 |
+
<button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Version Control">
|
| 121 |
+
<i data-lucide="git-commit" class="w-4 h-4"></i>
|
| 122 |
+
</button>
|
| 123 |
+
<button class="p-2 hover:bg-gray-600 rounded text-gray-400 hover:text-white" title="Logcat">
|
| 124 |
+
<i data-lucide="terminal" class="w-4 h-4"></i>
|
| 125 |
+
</button>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<!-- Center - Editor Area -->
|
| 131 |
+
<div class="flex-1 flex flex-col bg-[#2B2B2B]">
|
| 132 |
+
<!-- Editor Tabs -->
|
| 133 |
+
<div class="flex bg-[#3C3F41] border-b border-[#4E4E4E] overflow-x-auto" id="editor-tabs">
|
| 134 |
+
<div class="px-4 py-2 flex items-center space-x-2 text-sm text-gray-400">
|
| 135 |
+
<i data-lucide="file" class="w-4 h-4"></i>
|
| 136 |
+
<span>No file open</span>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
|
| 140 |
+
<!-- Editor with Line Numbers -->
|
| 141 |
+
<div class="flex-1 flex overflow-hidden">
|
| 142 |
+
<div class="line-numbers code-font text-sm py-4 select-none bg-[#313335]" id="line-numbers">
|
| 143 |
+
<div class="leading-6">1</div>
|
| 144 |
+
</div>
|
| 145 |
+
<div class="flex-1 editor-content code-font text-sm p-4 overflow-auto"
|
| 146 |
+
id="code-editor"
|
| 147 |
+
contenteditable="true"
|
| 148 |
+
spellcheck="false"
|
| 149 |
+
oninput="updateLineNumbers()"
|
| 150 |
+
onscroll="syncScroll()">
|
| 151 |
+
{/* Open a file to start editing */}
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<!-- Bottom Panel with Terminal & Build Output -->
|
| 156 |
+
<div class="h-48 bg-[#2B2B2B] border-t border-[#4E4E4E] flex flex-col">
|
| 157 |
+
<!-- Panel Tabs -->
|
| 158 |
+
<div class="flex items-center border-b border-[#4E4E4E] bg-[#3C3F41]">
|
| 159 |
+
<button class="px-4 py-2 text-sm text-white border-b-2 border-[#3DDC84] panel-tab" onclick="switchPanel('terminal')">
|
| 160 |
+
<div class="flex items-center space-x-2">
|
| 161 |
+
<i data-lucide="terminal" class="w-4 h-4"></i>
|
| 162 |
+
<span>Terminal</span>
|
| 163 |
+
</div>
|
| 164 |
+
</button>
|
| 165 |
+
<button class="px-4 py-2 text-sm text-gray-400 hover:text-white panel-tab" onclick="switchPanel('build')">
|
| 166 |
+
<div class="flex items-center space-x-2">
|
| 167 |
+
<i data-lucide="package" class="w-4 h-4"></i>
|
| 168 |
+
<span>Build Output</span>
|
| 169 |
+
</div>
|
| 170 |
+
</button>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
<!-- Terminal Panel -->
|
| 174 |
+
<div id="terminal-panel" class="flex-1 bg-black"></div>
|
| 175 |
+
|
| 176 |
+
<!-- Build Output Panel (hidden by default) -->
|
| 177 |
+
<div id="build-panel" class="flex-1 p-3 code-font text-sm overflow-auto hidden">
|
| 178 |
+
<div class="text-gray-400">▶ Click Build button to compile your app</div>
|
| 179 |
+
</div>
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
|
| 183 |
+
<!-- Right Panel - Android 10 Preview -->
|
| 184 |
+
<div class="w-80 bg-[#2B2B2B] border-l border-[#4E4E4E] flex flex-col">
|
| 185 |
+
<div class="h-10 border-b border-[#4E4E4E] flex items-center px-4 justify-between bg-[#3C3F41]">
|
| 186 |
+
<span class="text-sm font-medium text-white">Android 10 Preview</span>
|
| 187 |
+
<div class="flex items-center space-x-2">
|
| 188 |
+
<button class="p-1 hover:bg-gray-600 rounded" onclick="rotatePreview()" title="Rotate">
|
| 189 |
+
<i data-lucide="rotate-ccw" class="w-4 h-4"></i>
|
| 190 |
+
</button>
|
| 191 |
+
<button class="p-1 hover:bg-gray-600 rounded" onclick="toggleTheme()" title="Toggle Theme">
|
| 192 |
+
<i data-lucide="moon" class="w-4 h-4"></i>
|
| 193 |
+
</button>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
+
<!-- Android 10 Phone Frame -->
|
| 198 |
+
<div class="flex-1 p-4 flex items-center justify-center bg-[#1E1E1E]">
|
| 199 |
+
<div class="phone-frame android-10" id="phone-preview">
|
| 200 |
+
<!-- Status Bar (Android 10 style) -->
|
| 201 |
+
<div class="status-bar" id="status-bar">
|
| 202 |
+
<div class="time">9:41</div>
|
| 203 |
+
<div class="status-icons">
|
| 204 |
+
<i data-lucide="signal" class="w-3 h-3"></i>
|
| 205 |
+
<i data-lucide="wifi" class="w-3 h-3"></i>
|
| 206 |
+
<i data-lucide="battery-full" class="w-3 h-3"></i>
|
| 207 |
+
</div>
|
| 208 |
+
</div>
|
| 209 |
+
|
| 210 |
+
<!-- App Content -->
|
| 211 |
+
<div class="app-screen" id="app-screen">
|
| 212 |
+
<div class="logo-container">
|
| 213 |
+
<img id="preview-logo" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 24 24' fill='%233DDC84'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z'/%3E%3C/svg%3E" alt="App Logo">
|
| 214 |
+
<h3 id="preview-app-name">My Android App</h3>
|
| 215 |
+
<p class="app-version">Android 10 (API 29)</p>
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
|
| 219 |
+
<!-- Navigation Bar (Android 10 gesture) -->
|
| 220 |
+
<div class="nav-bar" id="nav-bar">
|
| 221 |
+
<div class="nav-handle"></div>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
|
| 226 |
+
<!-- Preview Controls -->
|
| 227 |
+
<div class="h-16 border-t border-[#4E4E4E] bg-[#3C3F41] flex items-center justify-center space-x-4">
|
| 228 |
+
<button class="p-2 rounded-full hover:bg-gray-600" onclick="simulateBack()">
|
| 229 |
+
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
| 230 |
+
</button>
|
| 231 |
+
<button class="p-2 rounded-full hover:bg-gray-600" onclick="simulateHome()">
|
| 232 |
+
<i data-lucide="circle" class="w-5 h-5"></i>
|
| 233 |
+
</button>
|
| 234 |
+
<button class="p-2 rounded-full hover:bg-gray-600" onclick="simulateRecent()">
|
| 235 |
+
<i data-lucide="square" class="w-5 h-5"></i>
|
| 236 |
+
</button>
|
| 237 |
+
<button class="p-2 rounded-full hover:bg-gray-600" onclick="uploadLogo()">
|
| 238 |
+
<i data-lucide="image" class="w-5 h-5"></i>
|
| 239 |
+
</button>
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
|
| 244 |
+
<!-- Status Bar -->
|
| 245 |
+
<div class="h-6 bg-[#3DDC84] text-black text-xs flex items-center px-3 justify-between">
|
| 246 |
+
<div class="flex items-center space-x-4">
|
| 247 |
+
<span id="status-message" class="font-medium">Ready</span>
|
| 248 |
+
<span id="build-status" class="hidden md:inline">● Gradle sync</span>
|
| 249 |
+
</div>
|
| 250 |
+
<div class="flex items-center space-x-4">
|
| 251 |
+
<span class="flex items-center space-x-1">
|
| 252 |
+
<i data-lucide="code" class="w-3 h-3"></i>
|
| 253 |
+
<span>Android 10 (API 29)</span>
|
| 254 |
+
</span>
|
| 255 |
+
<span class="flex items-center space-x-1 cursor-pointer hover:underline">
|
| 256 |
+
<i data-lucide="download" class="w-3 h-3"></i>
|
| 257 |
+
<span>Download APK</span>
|
| 258 |
+
</span>
|
| 259 |
+
</div>
|
| 260 |
+
</div>
|
| 261 |
+
|
| 262 |
+
<!-- Scripts -->
|
| 263 |
+
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
|
| 264 |
+
<script src="script.js"></script>
|
| 265 |
+
</body>
|
| 266 |
+
</html>
|
package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "android-studio-web-real",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Real Android Studio Web with Android 10 Preview",
|
| 5 |
+
"main": "server.js",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"start": "node server.js",
|
| 8 |
+
"dev": "nodemon server.js"
|
| 9 |
+
},
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"express": "^4.18.2",
|
| 12 |
+
"ws": "^8.14.2",
|
| 13 |
+
"node-pty": "^1.0.0",
|
| 14 |
+
"multer": "^1.4.5-lts.1",
|
| 15 |
+
"adm-zip": "^0.5.10",
|
| 16 |
+
"archiver": "^6.0.1"
|
| 17 |
+
},
|
| 18 |
+
"engines": {
|
| 19 |
+
"node": ">=18.0.0"
|
| 20 |
+
}
|
| 21 |
+
}
|
script.js
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Android Studio Web - Complete JavaScript with Real Build
|
| 2 |
+
|
| 3 |
+
// ==================== Global Variables ====================
|
| 4 |
+
let currentProject = null;
|
| 5 |
+
let currentFile = null;
|
| 6 |
+
let currentFilePath = null;
|
| 7 |
+
let terminal = null;
|
| 8 |
+
let ws = null;
|
| 9 |
+
let activeBuildId = null;
|
| 10 |
+
let files = [];
|
| 11 |
+
let projects = [];
|
| 12 |
+
|
| 13 |
+
// ==================== Initialize on Load ====================
|
| 14 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 15 |
+
// Load Lucide icons
|
| 16 |
+
lucide.createIcons();
|
| 17 |
+
|
| 18 |
+
// Initialize terminal
|
| 19 |
+
initTerminal();
|
| 20 |
+
|
| 21 |
+
// Load projects
|
| 22 |
+
loadProjects();
|
| 23 |
+
|
| 24 |
+
// Setup event listeners
|
| 25 |
+
setupEventListeners();
|
| 26 |
+
|
| 27 |
+
// Update status
|
| 28 |
+
updateStatus('Ready', 'success');
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
// ==================== Terminal Functions ====================
|
| 32 |
+
function initTerminal() {
|
| 33 |
+
const terminalContainer = document.getElementById('terminal-panel');
|
| 34 |
+
|
| 35 |
+
terminal = new Terminal({
|
| 36 |
+
theme: {
|
| 37 |
+
background: '#1E1E1E',
|
| 38 |
+
foreground: '#A9B7C6',
|
| 39 |
+
cursor: '#3DDC84',
|
| 40 |
+
selection: '#214283'
|
| 41 |
+
},
|
| 42 |
+
fontFamily: 'JetBrains Mono, monospace',
|
| 43 |
+
fontSize: 12,
|
| 44 |
+
cursorBlink: true,
|
| 45 |
+
scrollback: 5000
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
terminal.open(terminalContainer);
|
| 49 |
+
|
| 50 |
+
// Connect WebSocket
|
| 51 |
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 52 |
+
const wsUrl = `${protocol}//${window.location.host}`;
|
| 53 |
+
|
| 54 |
+
ws = new WebSocket(wsUrl);
|
| 55 |
+
|
| 56 |
+
ws.onopen = () => {
|
| 57 |
+
terminal.write('\x1b[32mAndroid Studio Web Terminal\x1b[0m\r\n');
|
| 58 |
+
terminal.write('\x1b[32mProject workspace: /workspace\x1b[0m\r\n');
|
| 59 |
+
terminal.write('$ ');
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
ws.onmessage = (event) => {
|
| 63 |
+
const data = JSON.parse(event.data);
|
| 64 |
+
if (data.type === 'data') {
|
| 65 |
+
terminal.write(data.data);
|
| 66 |
+
}
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
ws.onerror = (error) => {
|
| 70 |
+
terminal.write('\x1b[31mTerminal connection error\x1b[0m\r\n');
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
terminal.onData((data) => {
|
| 74 |
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
| 75 |
+
ws.send(JSON.stringify({ type: 'input', data }));
|
| 76 |
+
}
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
terminal.onResize((size) => {
|
| 80 |
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
| 81 |
+
ws.send(JSON.stringify({ type: 'resize', data: size }));
|
| 82 |
+
}
|
| 83 |
+
});
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// ==================== Project Functions ====================
|
| 87 |
+
async function loadProjects() {
|
| 88 |
+
try {
|
| 89 |
+
const response = await fetch('/api/projects');
|
| 90 |
+
const data = await response.json();
|
| 91 |
+
projects = data.projects || [];
|
| 92 |
+
renderProjectList();
|
| 93 |
+
} catch (error) {
|
| 94 |
+
console.error('Failed to load projects:', error);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
function renderProjectList() {
|
| 99 |
+
const projectList = document.getElementById('project-list');
|
| 100 |
+
if (!projectList) return;
|
| 101 |
+
|
| 102 |
+
if (projects.length === 0) {
|
| 103 |
+
projectList.innerHTML = '<div class="p-2 text-gray-400">No projects found</div>';
|
| 104 |
+
return;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
let html = '';
|
| 108 |
+
projects.forEach(project => {
|
| 109 |
+
html += `
|
| 110 |
+
<div class="project-item" onclick="openProject('${project}')">
|
| 111 |
+
<i data-lucide="folder" class="w-4 h-4 text-yellow-500"></i>
|
| 112 |
+
<span>${project}</span>
|
| 113 |
+
</div>
|
| 114 |
+
`;
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
projectList.innerHTML = html;
|
| 118 |
+
lucide.createIcons();
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
function toggleProjectDropdown() {
|
| 122 |
+
const dropdown = document.getElementById('project-dropdown');
|
| 123 |
+
dropdown.classList.toggle('hidden');
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
async function createNewProject() {
|
| 127 |
+
const name = prompt('Enter project name:');
|
| 128 |
+
if (!name) return;
|
| 129 |
+
|
| 130 |
+
updateStatus('Creating project...', 'building');
|
| 131 |
+
|
| 132 |
+
try {
|
| 133 |
+
const response = await fetch('/api/project/create', {
|
| 134 |
+
method: 'POST',
|
| 135 |
+
headers: { 'Content-Type': 'application/json' },
|
| 136 |
+
body: JSON.stringify({ name })
|
| 137 |
+
});
|
| 138 |
+
|
| 139 |
+
const data = await response.json();
|
| 140 |
+
|
| 141 |
+
if (data.success) {
|
| 142 |
+
await loadProjects();
|
| 143 |
+
await openProject(name);
|
| 144 |
+
updateStatus('Project created', 'success');
|
| 145 |
+
} else {
|
| 146 |
+
updateStatus('Failed to create project', 'error');
|
| 147 |
+
}
|
| 148 |
+
} catch (error) {
|
| 149 |
+
updateStatus('Error creating project', 'error');
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
toggleProjectDropdown();
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
async function openProject(projectName) {
|
| 156 |
+
currentProject = projectName;
|
| 157 |
+
document.getElementById('current-project').textContent = projectName;
|
| 158 |
+
document.getElementById('project-name-display').textContent = projectName;
|
| 159 |
+
document.getElementById('preview-app-name').textContent = projectName;
|
| 160 |
+
|
| 161 |
+
toggleProjectDropdown();
|
| 162 |
+
await loadProjectFiles();
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
async function loadProjectFiles() {
|
| 166 |
+
if (!currentProject) return;
|
| 167 |
+
|
| 168 |
+
try {
|
| 169 |
+
const response = await fetch(`/api/files/${currentProject}/`);
|
| 170 |
+
const data = await response.json();
|
| 171 |
+
|
| 172 |
+
if (data.type === 'directory') {
|
| 173 |
+
files = data.files || [];
|
| 174 |
+
renderFileTree();
|
| 175 |
+
}
|
| 176 |
+
} catch (error) {
|
| 177 |
+
console.error('Failed to load files:', error);
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
function renderFileTree() {
|
| 182 |
+
const treeElement = document.getElementById('file-tree');
|
| 183 |
+
|
| 184 |
+
if (files.length === 0) {
|
| 185 |
+
treeElement.innerHTML = '<div class="px-3 py-1 text-xs text-gray-500">No files found</div>';
|
| 186 |
+
return;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
let html = '<div class="px-3 py-1 text-xs text-gray-500 uppercase">app/src/main</div>';
|
| 190 |
+
|
| 191 |
+
// Simple file tree - you can make this recursive for full structure
|
| 192 |
+
files.forEach(file => {
|
| 193 |
+
if (file.includes('.')) {
|
| 194 |
+
const icon = file.endsWith('.kt') ? 'file-code' :
|
| 195 |
+
file.endsWith('.xml') ? 'file-text' : 'file';
|
| 196 |
+
const color = file.endsWith('.kt') ? 'text-blue-300' :
|
| 197 |
+
file.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400';
|
| 198 |
+
|
| 199 |
+
html += `
|
| 200 |
+
<div class="file-item px-3 py-1.5 flex items-center space-x-2 cursor-pointer text-sm"
|
| 201 |
+
onclick="openFile('${file}')">
|
| 202 |
+
<i data-lucide="${icon}" class="w-4 h-4 ${color}"></i>
|
| 203 |
+
<span>${file}</span>
|
| 204 |
+
</div>
|
| 205 |
+
`;
|
| 206 |
+
}
|
| 207 |
+
});
|
| 208 |
+
|
| 209 |
+
treeElement.innerHTML = html;
|
| 210 |
+
lucide.createIcons();
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// ==================== File Functions ====================
|
| 214 |
+
async function openFile(filename) {
|
| 215 |
+
if (!currentProject) {
|
| 216 |
+
alert('Please open a project first');
|
| 217 |
+
return;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
currentFile = filename;
|
| 221 |
+
currentFilePath = filename;
|
| 222 |
+
|
| 223 |
+
try {
|
| 224 |
+
const response = await fetch(`/api/files/${currentProject}/${filename}`);
|
| 225 |
+
const data = await response.json();
|
| 226 |
+
|
| 227 |
+
if (data.type === 'file') {
|
| 228 |
+
document.getElementById('code-editor').innerHTML = data.content;
|
| 229 |
+
updateLineNumbers();
|
| 230 |
+
addTab(filename);
|
| 231 |
+
}
|
| 232 |
+
} catch (error) {
|
| 233 |
+
console.error('Failed to open file:', error);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
// Update active state
|
| 237 |
+
document.querySelectorAll('.file-item').forEach(el => {
|
| 238 |
+
el.classList.remove('active');
|
| 239 |
+
if (el.textContent.trim() === filename) {
|
| 240 |
+
el.classList.add('active');
|
| 241 |
+
}
|
| 242 |
+
});
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
function addTab(filename) {
|
| 246 |
+
const tabs = document.getElementById('editor-tabs');
|
| 247 |
+
|
| 248 |
+
// Check if tab already exists
|
| 249 |
+
const existingTab = Array.from(tabs.children).find(
|
| 250 |
+
tab => tab.dataset.file === filename
|
| 251 |
+
);
|
| 252 |
+
|
| 253 |
+
if (existingTab) return;
|
| 254 |
+
|
| 255 |
+
const icon = filename.endsWith('.kt') ? 'file-code' :
|
| 256 |
+
filename.endsWith('.xml') ? 'file-text' : 'file';
|
| 257 |
+
const color = filename.endsWith('.kt') ? 'text-blue-300' :
|
| 258 |
+
filename.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400';
|
| 259 |
+
|
| 260 |
+
const tabHtml = `
|
| 261 |
+
<div class="px-4 py-2 flex items-center space-x-2 text-sm text-white bg-[#2B2B2B] border-t-2 border-[#3DDC84] cursor-pointer"
|
| 262 |
+
data-file="${filename}"
|
| 263 |
+
onclick="switchToTab('${filename}')">
|
| 264 |
+
<i data-lucide="${icon}" class="w-4 h-4 ${color}"></i>
|
| 265 |
+
<span>${filename}</span>
|
| 266 |
+
<i data-lucide="x" class="w-3 h-3 hover:text-red-400 cursor-pointer ml-2"
|
| 267 |
+
onclick="closeTab(event, '${filename}')"></i>
|
| 268 |
+
</div>
|
| 269 |
+
`;
|
| 270 |
+
|
| 271 |
+
tabs.insertAdjacentHTML('beforeend', tabHtml);
|
| 272 |
+
lucide.createIcons();
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
function switchToTab(filename) {
|
| 276 |
+
openFile(filename);
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
function closeTab(event, filename) {
|
| 280 |
+
event.stopPropagation();
|
| 281 |
+
const tab = event.target.closest('[data-file]');
|
| 282 |
+
if (tab) {
|
| 283 |
+
tab.remove();
|
| 284 |
+
if (currentFile === filename) {
|
| 285 |
+
const firstTab = document.querySelector('[data-file]');
|
| 286 |
+
if (firstTab) {
|
| 287 |
+
openFile(firstTab.dataset.file);
|
| 288 |
+
} else {
|
| 289 |
+
currentFile = null;
|
| 290 |
+
document.getElementById('code-editor').innerHTML = '// No file open';
|
| 291 |
+
updateLineNumbers();
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
async function saveCurrentFile() {
|
| 298 |
+
if (!currentProject || !currentFile) {
|
| 299 |
+
alert('No file open');
|
| 300 |
+
return;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
const content = document.getElementById('code-editor').innerText;
|
| 304 |
+
|
| 305 |
+
try {
|
| 306 |
+
const response = await fetch(`/api/file/${currentProject}/${currentFile}`, {
|
| 307 |
+
method: 'POST',
|
| 308 |
+
headers: { 'Content-Type': 'application/json' },
|
| 309 |
+
body: JSON.stringify({ content })
|
| 310 |
+
});
|
| 311 |
+
|
| 312 |
+
const data = await response.json();
|
| 313 |
+
|
| 314 |
+
if (data.success) {
|
| 315 |
+
updateStatus('File saved', 'success');
|
| 316 |
+
}
|
| 317 |
+
} catch (error) {
|
| 318 |
+
updateStatus('Save failed', 'error');
|
| 319 |
+
}
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
function createNewFile() {
|
| 323 |
+
if (!currentProject) {
|
| 324 |
+
alert('Please open a project first');
|
| 325 |
+
return;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
const filename = prompt('Enter file name (e.g., NewFile.kt):');
|
| 329 |
+
if (filename) {
|
| 330 |
+
currentFile = filename;
|
| 331 |
+
document.getElementById('code-editor').innerHTML = getDefaultContent(filename);
|
| 332 |
+
updateLineNumbers();
|
| 333 |
+
addTab(filename);
|
| 334 |
+
saveCurrentFile();
|
| 335 |
+
}
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
function getDefaultContent(filename) {
|
| 339 |
+
if (filename.endsWith('.kt')) {
|
| 340 |
+
return `package com.example.app
|
| 341 |
+
|
| 342 |
+
import android.os.Bundle
|
| 343 |
+
import androidx.appcompat.app.AppCompatActivity
|
| 344 |
+
|
| 345 |
+
class MainActivity : AppCompatActivity() {
|
| 346 |
+
override fun onCreate(savedInstanceState: Bundle?) {
|
| 347 |
+
super.onCreate(savedInstanceState)
|
| 348 |
+
setContentView(R.layout.activity_main)
|
| 349 |
+
}
|
| 350 |
+
}`;
|
| 351 |
+
} else if (filename.endsWith('.xml')) {
|
| 352 |
+
return `<?xml version="1.0" encoding="utf-8"?>
|
| 353 |
+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
| 354 |
+
android:layout_width="match_parent"
|
| 355 |
+
android:layout_height="match_parent"
|
| 356 |
+
android:orientation="vertical">
|
| 357 |
+
|
| 358 |
+
<TextView
|
| 359 |
+
android:layout_width="wrap_content"
|
| 360 |
+
android:layout_height="wrap_content"
|
| 361 |
+
android:text="Hello World!" />
|
| 362 |
+
|
| 363 |
+
</LinearLayout>`;
|
| 364 |
+
}
|
| 365 |
+
return '';
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
function refreshFiles() {
|
| 369 |
+
if (currentProject) {
|
| 370 |
+
loadProjectFiles();
|
| 371 |
+
}
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
// ==================== Build Functions ====================
|
| 375 |
+
async function startBuild() {
|
| 376 |
+
if (!currentProject) {
|
| 377 |
+
alert('Please open a project first');
|
| 378 |
+
return;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
switchPanel('build');
|
| 382 |
+
const buildPanel = document.getElementById('build-panel');
|
| 383 |
+
buildPanel.innerHTML = '<div class="building">🔨 Building APK...</div>';
|
| 384 |
+
updateStatus('Building...', 'building');
|
| 385 |
+
|
| 386 |
+
try {
|
| 387 |
+
const response = await fetch(`/api/build/${currentProject}`, {
|
| 388 |
+
method: 'POST'
|
| 389 |
+
});
|
| 390 |
+
|
| 391 |
+
const data = await response.json();
|
| 392 |
+
activeBuildId = data.buildId;
|
| 393 |
+
|
| 394 |
+
// Poll for build status
|
| 395 |
+
checkBuildStatus(activeBuildId);
|
| 396 |
+
|
| 397 |
+
} catch (error) {
|
| 398 |
+
buildPanel.innerHTML = '<div class="build-error">❌ Build failed to start</div>';
|
| 399 |
+
updateStatus('Build failed', 'error');
|
| 400 |
+
}
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
async function checkBuildStatus(buildId) {
|
| 404 |
+
const buildPanel = document.getElementById('build-panel');
|
| 405 |
+
|
| 406 |
+
try {
|
| 407 |
+
const response = await fetch(`/api/build/${buildId}`);
|
| 408 |
+
const data = await response.json();
|
| 409 |
+
|
| 410 |
+
if (data.output) {
|
| 411 |
+
let html = data.output.replace(/\n/g, '<br>')
|
| 412 |
+
.replace(/SUCCESS/g, '<span class="build-success">SUCCESS</span>')
|
| 413 |
+
.replace(/FAILED/g, '<span class="build-error">FAILED</span>')
|
| 414 |
+
.replace(/warning/g, '<span class="build-warning">warning</span>');
|
| 415 |
+
|
| 416 |
+
buildPanel.innerHTML = html;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
if (data.success !== undefined) {
|
| 420 |
+
if (data.success) {
|
| 421 |
+
buildPanel.innerHTML += '<br><br><div class="build-success">✅ Build successful! APK ready.</div>';
|
| 422 |
+
updateStatus('Build successful', 'success');
|
| 423 |
+
} else {
|
| 424 |
+
buildPanel.innerHTML += '<br><br><div class="build-error">❌ Build failed</div>';
|
| 425 |
+
updateStatus('Build failed', 'error');
|
| 426 |
+
}
|
| 427 |
+
} else {
|
| 428 |
+
// Still building, check again
|
| 429 |
+
setTimeout(() => checkBuildStatus(buildId), 1000);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
} catch (error) {
|
| 433 |
+
buildPanel.innerHTML = '<div class="build-error">Error checking build status</div>';
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
async function downloadAPK() {
|
| 438 |
+
if (!currentProject) {
|
| 439 |
+
alert('Please open a project first');
|
| 440 |
+
return;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
window.location.href = `/api/apk/${currentProject}`;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
// ==================== Preview Functions ====================
|
| 447 |
+
async function uploadLogo() {
|
| 448 |
+
if (!currentProject) {
|
| 449 |
+
alert('Please open a project first');
|
| 450 |
+
return;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
const input = document.createElement('input');
|
| 454 |
+
input.type = 'file';
|
| 455 |
+
input.accept = 'image/*';
|
| 456 |
+
|
| 457 |
+
input.onchange = async (e) => {
|
| 458 |
+
const file = e.target.files[0];
|
| 459 |
+
const formData = new FormData();
|
| 460 |
+
formData.append('logo', file);
|
| 461 |
+
|
| 462 |
+
updateStatus('Uploading logo...', 'building');
|
| 463 |
+
|
| 464 |
+
try {
|
| 465 |
+
const response = await fetch(`/api/logo/upload/${currentProject}`, {
|
| 466 |
+
method: 'POST',
|
| 467 |
+
body: formData
|
| 468 |
+
});
|
| 469 |
+
|
| 470 |
+
const data = await response.json();
|
| 471 |
+
|
| 472 |
+
if (data.success) {
|
| 473 |
+
// Update preview logo
|
| 474 |
+
const reader = new FileReader();
|
| 475 |
+
reader.onload = (e) => {
|
| 476 |
+
document.getElementById('preview-logo').src = e.target.result;
|
| 477 |
+
};
|
| 478 |
+
reader.readAsDataURL(file);
|
| 479 |
+
|
| 480 |
+
updateStatus('Logo updated', 'success');
|
| 481 |
+
}
|
| 482 |
+
} catch (error) {
|
| 483 |
+
updateStatus('Logo upload failed', 'error');
|
| 484 |
+
}
|
| 485 |
+
};
|
| 486 |
+
|
| 487 |
+
input.click();
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
function installOnPreview() {
|
| 491 |
+
const appScreen = document.getElementById('app-screen');
|
| 492 |
+
const logo = document.getElementById('preview-logo');
|
| 493 |
+
const appName = document.getElementById('preview-app-name');
|
| 494 |
+
|
| 495 |
+
// Simulate app installation
|
| 496 |
+
updateStatus('Installing on Android 10...', 'building');
|
| 497 |
+
|
| 498 |
+
setTimeout(() => {
|
| 499 |
+
appScreen.innerHTML = `
|
| 500 |
+
<div class="logo-container">
|
| 501 |
+
<img id="preview-logo" src="${logo.src}" alt="App Logo" style="width: 96px; height: 96px; border-radius: 20px;">
|
| 502 |
+
<h3 id="preview-app-name" style="font-size: 20px; font-weight: 600; color: #333; margin-top: 16px;">${appName.textContent}</h3>
|
| 503 |
+
<p style="font-size: 12px; color: #666; margin-top: 4px;">Running on Android 10</p>
|
| 504 |
+
<div style="margin-top: 20px; padding: 8px 16px; background: #3DDC84; color: white; border-radius: 20px; font-size: 12px;">
|
| 505 |
+
App installed
|
| 506 |
+
</div>
|
| 507 |
+
</div>
|
| 508 |
+
`;
|
| 509 |
+
updateStatus('App installed on preview', 'success');
|
| 510 |
+
}, 2000);
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
function rotatePreview() {
|
| 514 |
+
const phone = document.getElementById('phone-preview');
|
| 515 |
+
phone.classList.toggle('rotated');
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
function toggleTheme() {
|
| 519 |
+
const statusBar = document.getElementById('status-bar');
|
| 520 |
+
const appScreen = document.getElementById('app-screen');
|
| 521 |
+
const navBar = document.getElementById('nav-bar');
|
| 522 |
+
|
| 523 |
+
statusBar.classList.toggle('light');
|
| 524 |
+
appScreen.classList.toggle('dark');
|
| 525 |
+
navBar.classList.toggle('light');
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
function simulateBack() {
|
| 529 |
+
updateStatus('Back button pressed', 'info');
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
function simulateHome() {
|
| 533 |
+
updateStatus('Home button pressed', 'info');
|
| 534 |
+
// Reset to home screen
|
| 535 |
+
const appScreen = document.getElementById('app-screen');
|
| 536 |
+
appScreen.innerHTML = `
|
| 537 |
+
<div class="logo-container">
|
| 538 |
+
<img id="preview-logo" src="${document.getElementById('preview-logo').src}" alt="App Logo" style="width: 96px; height: 96px; border-radius: 20px;">
|
| 539 |
+
<h3 id="preview-app-name" style="font-size: 20px; font-weight: 600; color: #333; margin-top: 16px;">${document.getElementById('preview-app-name').textContent}</h3>
|
| 540 |
+
<p class="app-version">Android 10 (API 29)</p>
|
| 541 |
+
</div>
|
| 542 |
+
`;
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
function simulateRecent() {
|
| 546 |
+
updateStatus('Recent apps button pressed', 'info');
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
// ==================== UI Functions ====================
|
| 550 |
+
function updateLineNumbers() {
|
| 551 |
+
const editor = document.getElementById('code-editor');
|
| 552 |
+
const lines = editor.innerText.split('\n').length;
|
| 553 |
+
const lineNumbers = document.getElementById('line-numbers');
|
| 554 |
+
|
| 555 |
+
let html = '';
|
| 556 |
+
for (let i = 1; i <= lines; i++) {
|
| 557 |
+
html += `<div class="leading-6">${i}</div>`;
|
| 558 |
+
}
|
| 559 |
+
lineNumbers.innerHTML = html;
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
function syncScroll() {
|
| 563 |
+
const editor = document.getElementById('code-editor');
|
| 564 |
+
const lineNumbers = document.getElementById('line-numbers');
|
| 565 |
+
lineNumbers.scrollTop = editor.scrollTop;
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
function switchPanel(panel) {
|
| 569 |
+
// Hide all panels
|
| 570 |
+
document.getElementById('terminal-panel').classList.add('hidden');
|
| 571 |
+
document.getElementById('build-panel').classList.add('hidden');
|
| 572 |
+
|
| 573 |
+
// Show selected panel
|
| 574 |
+
document.getElementById(`${panel}-panel`).classList.remove('hidden');
|
| 575 |
+
|
| 576 |
+
// Update tab styles
|
| 577 |
+
document.querySelectorAll('.panel-tab').forEach(tab => {
|
| 578 |
+
tab.classList.remove('border-[#3DDC84]', 'text-white');
|
| 579 |
+
tab.classList.add('text-gray-400');
|
| 580 |
+
});
|
| 581 |
+
|
| 582 |
+
event.currentTarget.classList.add('border-[#3DDC84]', 'text-white');
|
| 583 |
+
event.currentTarget.classList.remove('text-gray-400');
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
function updateStatus(message, type) {
|
| 587 |
+
const statusEl = document.getElementById('status-message');
|
| 588 |
+
statusEl.textContent = message;
|
| 589 |
+
|
| 590 |
+
const colors = {
|
| 591 |
+
success: '#3DDC84',
|
| 592 |
+
error: '#CF6679',
|
| 593 |
+
building: '#FBC02D',
|
| 594 |
+
info: '#64B5F6'
|
| 595 |
+
};
|
| 596 |
+
|
| 597 |
+
statusEl.style.color = colors[type] || '#000000';
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
function openSettings() {
|
| 601 |
+
alert('Settings dialog would open here');
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
function setupEventListeners() {
|
| 605 |
+
// Keyboard shortcuts
|
| 606 |
+
document.addEventListener('keydown', (e) => {
|
| 607 |
+
if (e.ctrlKey && e.key === 's') {
|
| 608 |
+
e.preventDefault();
|
| 609 |
+
saveCurrentFile();
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
if (e.key === 'F5') {
|
| 613 |
+
e.preventDefault();
|
| 614 |
+
startBuild();
|
| 615 |
+
}
|
| 616 |
+
});
|
| 617 |
+
|
| 618 |
+
// Click outside to close dropdown
|
| 619 |
+
document.addEventListener('click', (e) => {
|
| 620 |
+
if (!e.target.closest('.relative')) {
|
| 621 |
+
document.getElementById('project-dropdown').classList.add('hidden');
|
| 622 |
+
}
|
| 623 |
+
});
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
// Export functions for HTML
|
| 627 |
+
window.toggleProjectDropdown = toggleProjectDropdown;
|
| 628 |
+
window.createNewProject = createNewProject;
|
| 629 |
+
window.openProject = openProject;
|
| 630 |
+
window.openFile = openFile;
|
| 631 |
+
window.saveCurrentFile = saveCurrentFile;
|
| 632 |
+
window.createNewFile = createNewFile;
|
| 633 |
+
window.refreshFiles = refreshFiles;
|
| 634 |
+
window.startBuild = startBuild;
|
| 635 |
+
window.downloadAPK = downloadAPK;
|
| 636 |
+
window.installOnPreview = installOnPreview;
|
| 637 |
+
window.rotatePreview = rotatePreview;
|
| 638 |
+
window.toggleTheme = toggleTheme;
|
| 639 |
+
window.uploadLogo = uploadLogo;
|
| 640 |
+
window.simulateBack = simulateBack;
|
| 641 |
+
window.simulateHome = simulateHome;
|
| 642 |
+
window.simulateRecent = simulateRecent;
|
| 643 |
+
window.switchPanel = switchPanel;
|
| 644 |
+
window.updateLineNumbers = updateLineNumbers;
|
| 645 |
+
window.syncScroll = syncScroll;
|
| 646 |
+
window.openSettings = openSettings;
|
| 647 |
+
window.switchToTab = switchToTab;
|
| 648 |
+
window.closeTab = closeTab;
|
server.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const http = require('http');
|
| 3 |
+
const WebSocket = require('ws');
|
| 4 |
+
const { exec, spawn } = require('child_process');
|
| 5 |
+
const fs = require('fs').promises;
|
| 6 |
+
const path = require('path');
|
| 7 |
+
const multer = require('multer');
|
| 8 |
+
const AdmZip = require('adm-zip');
|
| 9 |
+
const archiver = require('archiver');
|
| 10 |
+
|
| 11 |
+
const app = express();
|
| 12 |
+
const server = http.createServer(app);
|
| 13 |
+
const wss = new WebSocket.Server({ server });
|
| 14 |
+
const upload = multer({ dest: 'uploads/' });
|
| 15 |
+
|
| 16 |
+
// Store active builds and terminals
|
| 17 |
+
const activeBuilds = new Map();
|
| 18 |
+
const activeTerminals = new Map();
|
| 19 |
+
|
| 20 |
+
// Middleware
|
| 21 |
+
app.use(express.json({ limit: '50mb' }));
|
| 22 |
+
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
| 23 |
+
|
| 24 |
+
// Serve all static files from root
|
| 25 |
+
app.use(express.static(__dirname));
|
| 26 |
+
|
| 27 |
+
// ==================== API Routes ====================
|
| 28 |
+
|
| 29 |
+
// 1. Home page
|
| 30 |
+
app.get('/', (req, res) => {
|
| 31 |
+
res.sendFile(path.join(__dirname, 'index.html'));
|
| 32 |
+
});
|
| 33 |
+
|
| 34 |
+
// 2. Get all projects
|
| 35 |
+
app.get('/api/projects', async (req, res) => {
|
| 36 |
+
try {
|
| 37 |
+
const projects = await fs.readdir('/workspace');
|
| 38 |
+
res.json({ projects });
|
| 39 |
+
} catch (error) {
|
| 40 |
+
res.json({ projects: [] });
|
| 41 |
+
}
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
// 3. Create new project
|
| 45 |
+
app.post('/api/project/create', async (req, res) => {
|
| 46 |
+
const { name, template = 'empty' } = req.body;
|
| 47 |
+
const projectPath = path.join('/workspace', name);
|
| 48 |
+
|
| 49 |
+
try {
|
| 50 |
+
await fs.mkdir(projectPath, { recursive: true });
|
| 51 |
+
await createAndroidProject(projectPath, name);
|
| 52 |
+
res.json({ success: true, message: 'Project created', path: projectPath });
|
| 53 |
+
} catch (error) {
|
| 54 |
+
res.status(500).json({ error: error.message });
|
| 55 |
+
}
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
// 4. Get project files
|
| 59 |
+
app.get('/api/files/:project/*', async (req, res) => {
|
| 60 |
+
const { project, '0': filePath } = req.params;
|
| 61 |
+
const fullPath = path.join('/workspace', project, filePath || '');
|
| 62 |
+
|
| 63 |
+
try {
|
| 64 |
+
const stat = await fs.stat(fullPath);
|
| 65 |
+
|
| 66 |
+
if (stat.isDirectory()) {
|
| 67 |
+
const files = await fs.readdir(fullPath);
|
| 68 |
+
res.json({ type: 'directory', files });
|
| 69 |
+
} else {
|
| 70 |
+
const content = await fs.readFile(fullPath, 'utf-8');
|
| 71 |
+
res.json({ type: 'file', content });
|
| 72 |
+
}
|
| 73 |
+
} catch (error) {
|
| 74 |
+
res.status(404).json({ error: 'Not found' });
|
| 75 |
+
}
|
| 76 |
+
});
|
| 77 |
+
|
| 78 |
+
// 5. Save file
|
| 79 |
+
app.post('/api/file/:project/*', async (req, res) => {
|
| 80 |
+
const { project, '0': filePath } = req.params;
|
| 81 |
+
const { content } = req.body;
|
| 82 |
+
const fullPath = path.join('/workspace', project, filePath);
|
| 83 |
+
|
| 84 |
+
try {
|
| 85 |
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
| 86 |
+
await fs.writeFile(fullPath, content);
|
| 87 |
+
res.json({ success: true });
|
| 88 |
+
} catch (error) {
|
| 89 |
+
res.status(500).json({ error: error.message });
|
| 90 |
+
}
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
// 6. Build project
|
| 94 |
+
app.post('/api/build/:project', (req, res) => {
|
| 95 |
+
const { project } = req.params;
|
| 96 |
+
const projectPath = path.join('/workspace', project);
|
| 97 |
+
const buildId = Date.now().toString();
|
| 98 |
+
|
| 99 |
+
const buildProcess = spawn('./gradlew', ['assembleDebug'], {
|
| 100 |
+
cwd: projectPath,
|
| 101 |
+
env: {
|
| 102 |
+
...process.env,
|
| 103 |
+
ANDROID_HOME: '/android-sdk',
|
| 104 |
+
JAVA_HOME: '/usr/lib/jvm/java-17-openjdk-amd64'
|
| 105 |
+
}
|
| 106 |
+
});
|
| 107 |
+
|
| 108 |
+
let output = '';
|
| 109 |
+
|
| 110 |
+
buildProcess.stdout.on('data', (data) => {
|
| 111 |
+
output += data.toString();
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
buildProcess.stderr.on('data', (data) => {
|
| 115 |
+
output += data.toString();
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
buildProcess.on('close', (code) => {
|
| 119 |
+
const success = code === 0;
|
| 120 |
+
const apkPath = path.join(projectPath, 'app/build/outputs/apk/debug/app-debug.apk');
|
| 121 |
+
|
| 122 |
+
activeBuilds.set(buildId, {
|
| 123 |
+
success,
|
| 124 |
+
output,
|
| 125 |
+
apkPath: success ? apkPath : null,
|
| 126 |
+
project
|
| 127 |
+
});
|
| 128 |
+
|
| 129 |
+
res.json({
|
| 130 |
+
buildId,
|
| 131 |
+
success,
|
| 132 |
+
output,
|
| 133 |
+
message: success ? 'Build successful!' : 'Build failed'
|
| 134 |
+
});
|
| 135 |
+
});
|
| 136 |
+
});
|
| 137 |
+
|
| 138 |
+
// 7. Get build status
|
| 139 |
+
app.get('/api/build/:buildId', (req, res) => {
|
| 140 |
+
const { buildId } = req.params;
|
| 141 |
+
const build = activeBuilds.get(buildId);
|
| 142 |
+
|
| 143 |
+
if (build) {
|
| 144 |
+
res.json(build);
|
| 145 |
+
} else {
|
| 146 |
+
res.status(404).json({ error: 'Build not found' });
|
| 147 |
+
}
|
| 148 |
+
});
|
| 149 |
+
|
| 150 |
+
// 8. Download APK
|
| 151 |
+
app.get('/api/apk/:project', async (req, res) => {
|
| 152 |
+
const { project } = req.params;
|
| 153 |
+
const apkPath = path.join('/workspace', project, 'app/build/outputs/apk/debug/app-debug.apk');
|
| 154 |
+
|
| 155 |
+
try {
|
| 156 |
+
await fs.access(apkPath);
|
| 157 |
+
res.download(apkPath, `${project}.apk`);
|
| 158 |
+
} catch (error) {
|
| 159 |
+
res.status(404).json({ error: 'APK not found. Build first!' });
|
| 160 |
+
}
|
| 161 |
+
});
|
| 162 |
+
|
| 163 |
+
// 9. Upload logo
|
| 164 |
+
app.post('/api/logo/upload/:project', upload.single('logo'), async (req, res) => {
|
| 165 |
+
const { project } = req.params;
|
| 166 |
+
const logoPath = req.file.path;
|
| 167 |
+
|
| 168 |
+
try {
|
| 169 |
+
// Convert to Android icon sizes and copy to project
|
| 170 |
+
const resDir = path.join('/workspace', project, 'app/src/main/res');
|
| 171 |
+
|
| 172 |
+
// Create mipmap directories
|
| 173 |
+
const sizes = {
|
| 174 |
+
'mipmap-hdpi': 72,
|
| 175 |
+
'mipmap-mdpi': 48,
|
| 176 |
+
'mipmap-xhdpi': 96,
|
| 177 |
+
'mipmap-xxhdpi': 144,
|
| 178 |
+
'mipmap-xxxhdpi': 192
|
| 179 |
+
};
|
| 180 |
+
|
| 181 |
+
for (const [dir, size] of Object.entries(sizes)) {
|
| 182 |
+
const destDir = path.join(resDir, dir);
|
| 183 |
+
await fs.mkdir(destDir, { recursive: true });
|
| 184 |
+
|
| 185 |
+
// Copy and rename to ic_launcher.png
|
| 186 |
+
const destPath = path.join(destDir, 'ic_launcher.png');
|
| 187 |
+
await fs.copyFile(logoPath, destPath);
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
res.json({ success: true, message: 'Logo updated successfully' });
|
| 191 |
+
} catch (error) {
|
| 192 |
+
res.status(500).json({ error: error.message });
|
| 193 |
+
}
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
// 10. Terminal WebSocket
|
| 197 |
+
wss.on('connection', (ws) => {
|
| 198 |
+
console.log('Terminal connected');
|
| 199 |
+
|
| 200 |
+
const { spawn } = require('node-pty');
|
| 201 |
+
const shell = spawn('bash', [], {
|
| 202 |
+
name: 'xterm-color',
|
| 203 |
+
cols: 80,
|
| 204 |
+
rows: 24,
|
| 205 |
+
cwd: '/workspace',
|
| 206 |
+
env: process.env
|
| 207 |
+
});
|
| 208 |
+
|
| 209 |
+
const terminalId = Date.now().toString();
|
| 210 |
+
activeTerminals.set(terminalId, shell);
|
| 211 |
+
|
| 212 |
+
shell.on('data', (data) => {
|
| 213 |
+
ws.send(JSON.stringify({ type: 'data', data: data.toString() }));
|
| 214 |
+
});
|
| 215 |
+
|
| 216 |
+
ws.on('message', (msg) => {
|
| 217 |
+
try {
|
| 218 |
+
const { type, data } = JSON.parse(msg);
|
| 219 |
+
if (type === 'input') {
|
| 220 |
+
shell.write(data);
|
| 221 |
+
} else if (type === 'resize') {
|
| 222 |
+
shell.resize(data.cols, data.rows);
|
| 223 |
+
}
|
| 224 |
+
} catch (e) {
|
| 225 |
+
console.error('Terminal error:', e);
|
| 226 |
+
}
|
| 227 |
+
});
|
| 228 |
+
|
| 229 |
+
ws.on('close', () => {
|
| 230 |
+
shell.kill();
|
| 231 |
+
activeTerminals.delete(terminalId);
|
| 232 |
+
});
|
| 233 |
+
});
|
| 234 |
+
|
| 235 |
+
// ==================== Helper Functions ====================
|
| 236 |
+
|
| 237 |
+
async function createAndroidProject(projectPath, name) {
|
| 238 |
+
const packageName = `com.${name.toLowerCase().replace(/[^a-z]/g, '')}.app`;
|
| 239 |
+
|
| 240 |
+
// Create directory structure
|
| 241 |
+
const mainPath = path.join(projectPath, 'app/src/main');
|
| 242 |
+
const javaPath = path.join(mainPath, 'java', ...packageName.split('.'));
|
| 243 |
+
const resPath = path.join(mainPath, 'res');
|
| 244 |
+
const layoutPath = path.join(resPath, 'layout');
|
| 245 |
+
const drawablePath = path.join(resPath, 'drawable');
|
| 246 |
+
const valuesPath = path.join(resPath, 'values');
|
| 247 |
+
const mipmapPath = path.join(resPath, 'mipmap-hdpi');
|
| 248 |
+
|
| 249 |
+
await fs.mkdir(javaPath, { recursive: true });
|
| 250 |
+
await fs.mkdir(layoutPath, { recursive: true });
|
| 251 |
+
await fs.mkdir(drawablePath, { recursive: true });
|
| 252 |
+
await fs.mkdir(valuesPath, { recursive: true });
|
| 253 |
+
await fs.mkdir(mipmapPath, { recursive: true });
|
| 254 |
+
|
| 255 |
+
// 1. settings.gradle
|
| 256 |
+
await fs.writeFile(path.join(projectPath, 'settings.gradle'),
|
| 257 |
+
`rootProject.name = "${name}"\ninclude ':app'`);
|
| 258 |
+
|
| 259 |
+
// 2. build.gradle (project)
|
| 260 |
+
await fs.writeFile(path.join(projectPath, 'build.gradle'), `
|
| 261 |
+
// Top-level build file
|
| 262 |
+
buildscript {
|
| 263 |
+
ext.kotlin_version = '1.9.0'
|
| 264 |
+
repositories {
|
| 265 |
+
google()
|
| 266 |
+
mavenCentral()
|
| 267 |
+
}
|
| 268 |
+
dependencies {
|
| 269 |
+
classpath 'com.android.tools.build:gradle:8.0.0'
|
| 270 |
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
allprojects {
|
| 275 |
+
repositories {
|
| 276 |
+
google()
|
| 277 |
+
mavenCentral()
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
task clean(type: Delete) {
|
| 282 |
+
delete rootProject.buildDir
|
| 283 |
+
}
|
| 284 |
+
`);
|
| 285 |
+
|
| 286 |
+
// 3. gradle.properties
|
| 287 |
+
await fs.writeFile(path.join(projectPath, 'gradle.properties'), `
|
| 288 |
+
org.gradle.jvmargs=-Xmx2048m
|
| 289 |
+
android.useAndroidX=true
|
| 290 |
+
kotlin.code.style=official
|
| 291 |
+
`);
|
| 292 |
+
|
| 293 |
+
// 4. gradlew wrapper
|
| 294 |
+
const gradlew = `#!/usr/bin/env bash
|
| 295 |
+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
| 296 |
+
$DIR/gradlew "$@"
|
| 297 |
+
`;
|
| 298 |
+
await fs.writeFile(path.join(projectPath, 'gradlew'), gradlew);
|
| 299 |
+
await fs.chmod(path.join(projectPath, 'gradlew'), 0o755);
|
| 300 |
+
|
| 301 |
+
// 5. app/build.gradle
|
| 302 |
+
await fs.writeFile(path.join(projectPath, 'app/build.gradle'), `
|
| 303 |
+
plugins {
|
| 304 |
+
id 'com.android.application'
|
| 305 |
+
id 'org.jetbrains.kotlin.android'
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
android {
|
| 309 |
+
compileSdk 34
|
| 310 |
+
|
| 311 |
+
defaultConfig {
|
| 312 |
+
applicationId "${packageName}"
|
| 313 |
+
minSdk 29
|
| 314 |
+
targetSdk 34
|
| 315 |
+
versionCode 1
|
| 316 |
+
versionName "1.0"
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
buildTypes {
|
| 320 |
+
release {
|
| 321 |
+
minifyEnabled false
|
| 322 |
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
compileOptions {
|
| 327 |
+
sourceCompatibility JavaVersion.VERSION_17
|
| 328 |
+
targetCompatibility JavaVersion.VERSION_17
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
kotlinOptions {
|
| 332 |
+
jvmTarget = '17'
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
dependencies {
|
| 337 |
+
implementation 'androidx.core:core-ktx:1.12.0'
|
| 338 |
+
implementation 'androidx.appcompat:appcompat:1.6.1'
|
| 339 |
+
implementation 'com.google.android.material:material:1.11.0'
|
| 340 |
+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
| 341 |
+
}
|
| 342 |
+
`);
|
| 343 |
+
|
| 344 |
+
// 6. AndroidManifest.xml
|
| 345 |
+
await fs.writeFile(path.join(mainPath, 'AndroidManifest.xml'), `
|
| 346 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 347 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
| 348 |
+
package="${packageName}">
|
| 349 |
+
|
| 350 |
+
<application
|
| 351 |
+
android:allowBackup="true"
|
| 352 |
+
android:icon="@mipmap/ic_launcher"
|
| 353 |
+
android:label="${name}"
|
| 354 |
+
android:theme="@style/Theme.${name}">
|
| 355 |
+
<activity
|
| 356 |
+
android:name=".MainActivity"
|
| 357 |
+
android:exported="true">
|
| 358 |
+
<intent-filter>
|
| 359 |
+
<action android:name="android.intent.action.MAIN" />
|
| 360 |
+
<category android:name="android.intent.category.LAUNCHER" />
|
| 361 |
+
</intent-filter>
|
| 362 |
+
</activity>
|
| 363 |
+
</application>
|
| 364 |
+
|
| 365 |
+
</manifest>
|
| 366 |
+
`);
|
| 367 |
+
|
| 368 |
+
// 7. MainActivity.kt
|
| 369 |
+
await fs.writeFile(path.join(javaPath, 'MainActivity.kt'), `
|
| 370 |
+
package ${packageName}
|
| 371 |
+
|
| 372 |
+
import android.os.Bundle
|
| 373 |
+
import androidx.appcompat.app.AppCompatActivity
|
| 374 |
+
|
| 375 |
+
class MainActivity : AppCompatActivity() {
|
| 376 |
+
override fun onCreate(savedInstanceState: Bundle?) {
|
| 377 |
+
super.onCreate(savedInstanceState)
|
| 378 |
+
setContentView(R.layout.activity_main)
|
| 379 |
+
|
| 380 |
+
// Your app starts here
|
| 381 |
+
supportActionBar?.title = "${name}"
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
`);
|
| 385 |
+
|
| 386 |
+
// 8. activity_main.xml
|
| 387 |
+
await fs.writeFile(path.join(layoutPath, 'activity_main.xml'), `
|
| 388 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 389 |
+
<LinearLayout
|
| 390 |
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
| 391 |
+
android:layout_width="match_parent"
|
| 392 |
+
android:layout_height="match_parent"
|
| 393 |
+
android:orientation="vertical"
|
| 394 |
+
android:gravity="center"
|
| 395 |
+
android:padding="20dp">
|
| 396 |
+
|
| 397 |
+
<ImageView
|
| 398 |
+
android:id="@+id/app_logo"
|
| 399 |
+
android:layout_width="120dp"
|
| 400 |
+
android:layout_height="120dp"
|
| 401 |
+
android:src="@drawable/ic_launcher_foreground"
|
| 402 |
+
android:layout_marginBottom="20dp" />
|
| 403 |
+
|
| 404 |
+
<TextView
|
| 405 |
+
android:layout_width="wrap_content"
|
| 406 |
+
android:layout_height="wrap_content"
|
| 407 |
+
android:text="${name}"
|
| 408 |
+
android:textSize="28sp"
|
| 409 |
+
android:textStyle="bold"
|
| 410 |
+
android:textColor="#3DDC84"
|
| 411 |
+
android:layout_marginBottom="8dp" />
|
| 412 |
+
|
| 413 |
+
<TextView
|
| 414 |
+
android:layout_width="wrap_content"
|
| 415 |
+
android:layout_height="wrap_content"
|
| 416 |
+
android:text="Android 10 (API 29)"
|
| 417 |
+
android:textSize="16sp"
|
| 418 |
+
android:textColor="#666666"
|
| 419 |
+
android:layout_marginBottom="24dp" />
|
| 420 |
+
|
| 421 |
+
<TextView
|
| 422 |
+
android:layout_width="wrap_content"
|
| 423 |
+
android:layout_height="wrap_content"
|
| 424 |
+
android:text="Welcome to your Android app!"
|
| 425 |
+
android:textSize="14sp"
|
| 426 |
+
android:textColor="#333333" />
|
| 427 |
+
|
| 428 |
+
</LinearLayout>
|
| 429 |
+
`);
|
| 430 |
+
|
| 431 |
+
// 9. strings.xml
|
| 432 |
+
await fs.writeFile(path.join(valuesPath, 'strings.xml'), `
|
| 433 |
+
<resources>
|
| 434 |
+
<string name="app_name">${name}</string>
|
| 435 |
+
</resources>
|
| 436 |
+
`);
|
| 437 |
+
|
| 438 |
+
// 10. colors.xml
|
| 439 |
+
await fs.writeFile(path.join(valuesPath, 'colors.xml'), `
|
| 440 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 441 |
+
<resources>
|
| 442 |
+
<color name="purple_200">#FFBB86FC</color>
|
| 443 |
+
<color name="purple_500">#FF6200EE</color>
|
| 444 |
+
<color name="purple_700">#FF3700B3</color>
|
| 445 |
+
<color name="teal_200">#FF03DAC5</color>
|
| 446 |
+
<color name="teal_700">#FF018786</color>
|
| 447 |
+
<color name="black">#FF000000</color>
|
| 448 |
+
<color name="white">#FFFFFFFF</color>
|
| 449 |
+
</resources>
|
| 450 |
+
`);
|
| 451 |
+
|
| 452 |
+
// 11. themes.xml
|
| 453 |
+
await fs.writeFile(path.join(valuesPath, 'themes.xml'), `
|
| 454 |
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
| 455 |
+
<style name="Theme.${name}" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
| 456 |
+
<item name="colorPrimary">@color/purple_500</item>
|
| 457 |
+
<item name="colorPrimaryVariant">@color/purple_700</item>
|
| 458 |
+
<item name="colorOnPrimary">@color/white</item>
|
| 459 |
+
<item name="colorSecondary">@color/teal_200</item>
|
| 460 |
+
<item name="colorSecondaryVariant">@color/teal_700</item>
|
| 461 |
+
<item name="colorOnSecondary">@color/black</item>
|
| 462 |
+
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
| 463 |
+
</style>
|
| 464 |
+
</resources>
|
| 465 |
+
`);
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
// Start server
|
| 469 |
+
server.listen(7860, () => {
|
| 470 |
+
console.log('🚀 Server running on http://localhost:7860');
|
| 471 |
+
console.log('📱 Android 10 Preview Ready');
|
| 472 |
+
console.log('🔧 Build System Active');
|
| 473 |
+
});
|
style.css
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Android Studio Web - Complete Styles with Android 10 Preview */
|
| 2 |
+
|
| 3 |
+
* {
|
| 4 |
+
margin: 0;
|
| 5 |
+
padding: 0;
|
| 6 |
+
box-sizing: border-box;
|
| 7 |
+
transition: background-color 0.2s ease, border-color 0.2s ease;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
::selection {
|
| 11 |
+
background-color: #214283;
|
| 12 |
+
color: white;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
body {
|
| 16 |
+
font-family: 'Inter', sans-serif;
|
| 17 |
+
overflow: hidden;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.code-font {
|
| 21 |
+
font-family: 'JetBrains Mono', monospace;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
/* Custom Scrollbar */
|
| 25 |
+
::-webkit-scrollbar {
|
| 26 |
+
width: 8px;
|
| 27 |
+
height: 8px;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
::-webkit-scrollbar-track {
|
| 31 |
+
background: #2B2B2B;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
::-webkit-scrollbar-thumb {
|
| 35 |
+
background: #4E4E4E;
|
| 36 |
+
border-radius: 4px;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
::-webkit-scrollbar-thumb:hover {
|
| 40 |
+
background: #5E5E5E;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
::-webkit-scrollbar-corner {
|
| 44 |
+
background: #2B2B2B;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/* Editor Styles */
|
| 48 |
+
.line-numbers {
|
| 49 |
+
background-color: #313335;
|
| 50 |
+
color: #808080;
|
| 51 |
+
text-align: right;
|
| 52 |
+
padding-right: 12px;
|
| 53 |
+
user-select: none;
|
| 54 |
+
min-width: 40px;
|
| 55 |
+
line-height: 1.5rem;
|
| 56 |
+
font-size: 12px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.editor-content {
|
| 60 |
+
background-color: #1E1E1E;
|
| 61 |
+
color: #A9B7C6;
|
| 62 |
+
outline: none;
|
| 63 |
+
white-space: pre;
|
| 64 |
+
tab-size: 4;
|
| 65 |
+
font-size: 12px;
|
| 66 |
+
line-height: 1.5rem;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
/* File Tree */
|
| 70 |
+
.file-item {
|
| 71 |
+
cursor: pointer;
|
| 72 |
+
transition: background-color 0.15s ease;
|
| 73 |
+
font-size: 13px;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.file-item:hover {
|
| 77 |
+
background-color: #4E4E4E;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.file-item.active {
|
| 81 |
+
background-color: #4E4E4E;
|
| 82 |
+
color: white;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/* Android 10 Phone Frame */
|
| 86 |
+
.phone-frame {
|
| 87 |
+
width: 280px;
|
| 88 |
+
height: 560px;
|
| 89 |
+
background: #121212;
|
| 90 |
+
border-radius: 40px;
|
| 91 |
+
position: relative;
|
| 92 |
+
overflow: hidden;
|
| 93 |
+
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
|
| 94 |
+
transition: transform 0.3s ease;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.phone-frame.rotated {
|
| 98 |
+
transform: rotate(90deg);
|
| 99 |
+
width: 560px;
|
| 100 |
+
height: 280px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/* Android 10 Status Bar */
|
| 104 |
+
.status-bar {
|
| 105 |
+
display: flex;
|
| 106 |
+
justify-content: space-between;
|
| 107 |
+
padding: 12px 20px;
|
| 108 |
+
background: #121212;
|
| 109 |
+
color: white;
|
| 110 |
+
font-size: 12px;
|
| 111 |
+
font-weight: 500;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.status-bar.light {
|
| 115 |
+
background: #FFFFFF;
|
| 116 |
+
color: black;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.status-bar .status-icons {
|
| 120 |
+
display: flex;
|
| 121 |
+
gap: 4px;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
/* App Screen */
|
| 125 |
+
.app-screen {
|
| 126 |
+
height: calc(100% - 80px);
|
| 127 |
+
padding: 20px;
|
| 128 |
+
display: flex;
|
| 129 |
+
flex-direction: column;
|
| 130 |
+
align-items: center;
|
| 131 |
+
justify-content: center;
|
| 132 |
+
background: #F5F5F5;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.app-screen.dark {
|
| 136 |
+
background: #1E1E1E;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.logo-container {
|
| 140 |
+
text-align: center;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
#preview-logo {
|
| 144 |
+
width: 96px;
|
| 145 |
+
height: 96px;
|
| 146 |
+
border-radius: 20px;
|
| 147 |
+
margin-bottom: 16px;
|
| 148 |
+
object-fit: cover;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
#preview-app-name {
|
| 152 |
+
font-size: 20px;
|
| 153 |
+
font-weight: 600;
|
| 154 |
+
color: #333;
|
| 155 |
+
margin-bottom: 4px;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.dark #preview-app-name {
|
| 159 |
+
color: white;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.app-version {
|
| 163 |
+
font-size: 12px;
|
| 164 |
+
color: #666;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.dark .app-version {
|
| 168 |
+
color: #999;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
/* Android 10 Navigation */
|
| 172 |
+
.nav-bar {
|
| 173 |
+
position: absolute;
|
| 174 |
+
bottom: 0;
|
| 175 |
+
left: 0;
|
| 176 |
+
right: 0;
|
| 177 |
+
height: 40px;
|
| 178 |
+
display: flex;
|
| 179 |
+
justify-content: center;
|
| 180 |
+
align-items: center;
|
| 181 |
+
background: #121212;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.nav-bar.light {
|
| 185 |
+
background: #FFFFFF;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.nav-handle {
|
| 189 |
+
width: 120px;
|
| 190 |
+
height: 5px;
|
| 191 |
+
background: #4E4E4E;
|
| 192 |
+
border-radius: 3px;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
/* Terminal */
|
| 196 |
+
.xterm {
|
| 197 |
+
padding: 8px;
|
| 198 |
+
height: 100%;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.xterm-viewport {
|
| 202 |
+
overflow-y: auto !important;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
/* Build Output */
|
| 206 |
+
.build-success {
|
| 207 |
+
color: #3DDC84;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.build-error {
|
| 211 |
+
color: #CF6679;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.build-warning {
|
| 215 |
+
color: #FBC02D;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.build-info {
|
| 219 |
+
color: #64B5F6;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/* Animations */
|
| 223 |
+
@keyframes pulse {
|
| 224 |
+
0%, 100% { opacity: 1; }
|
| 225 |
+
50% { opacity: 0.5; }
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.building {
|
| 229 |
+
animation: pulse 1.5s infinite;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
/* Project Dropdown */
|
| 233 |
+
#project-dropdown {
|
| 234 |
+
max-height: 300px;
|
| 235 |
+
overflow-y: auto;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.project-item {
|
| 239 |
+
padding: 8px 12px;
|
| 240 |
+
cursor: pointer;
|
| 241 |
+
border-radius: 4px;
|
| 242 |
+
display: flex;
|
| 243 |
+
align-items: center;
|
| 244 |
+
gap: 8px;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
.project-item:hover {
|
| 248 |
+
background: #4E4E4E;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/* Responsive */
|
| 252 |
+
@media (max-width: 1024px) {
|
| 253 |
+
.w-64 {
|
| 254 |
+
width: 200px;
|
| 255 |
+
}
|
| 256 |
+
.w-80 {
|
| 257 |
+
width: 240px;
|
| 258 |
+
}
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
@media (max-width: 768px) {
|
| 262 |
+
.w-64, .w-80 {
|
| 263 |
+
display: none;
|
| 264 |
+
}
|
| 265 |
+
}
|