Ando / script.js
proti0070's picture
Upload 7 files
22be007 verified
// Android Studio Web - Complete JavaScript with Real Build
// ==================== Global Variables ====================
let currentProject = null;
let currentFile = null;
let currentFilePath = null;
let terminal = null;
let ws = null;
let activeBuildId = null;
let files = [];
let projects = [];
// ==================== Initialize on Load ====================
document.addEventListener('DOMContentLoaded', function() {
// Load Lucide icons
lucide.createIcons();
// Initialize terminal
initTerminal();
// Load projects
loadProjects();
// Setup event listeners
setupEventListeners();
// Update status
updateStatus('Ready', 'success');
});
// ==================== Terminal Functions ====================
function initTerminal() {
const terminalContainer = document.getElementById('terminal-panel');
terminal = new Terminal({
theme: {
background: '#1E1E1E',
foreground: '#A9B7C6',
cursor: '#3DDC84',
selection: '#214283'
},
fontFamily: 'JetBrains Mono, monospace',
fontSize: 12,
cursorBlink: true,
scrollback: 5000
});
terminal.open(terminalContainer);
// Connect WebSocket
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}`;
ws = new WebSocket(wsUrl);
ws.onopen = () => {
terminal.write('\x1b[32mAndroid Studio Web Terminal\x1b[0m\r\n');
terminal.write('\x1b[32mProject workspace: /workspace\x1b[0m\r\n');
terminal.write('$ ');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'data') {
terminal.write(data.data);
}
};
ws.onerror = (error) => {
terminal.write('\x1b[31mTerminal connection error\x1b[0m\r\n');
};
terminal.onData((data) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'input', data }));
}
});
terminal.onResize((size) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'resize', data: size }));
}
});
}
// ==================== Project Functions ====================
async function loadProjects() {
try {
const response = await fetch('/api/projects');
const data = await response.json();
projects = data.projects || [];
renderProjectList();
} catch (error) {
console.error('Failed to load projects:', error);
}
}
function renderProjectList() {
const projectList = document.getElementById('project-list');
if (!projectList) return;
if (projects.length === 0) {
projectList.innerHTML = '<div class="p-2 text-gray-400">No projects found</div>';
return;
}
let html = '';
projects.forEach(project => {
html += `
<div class="project-item" onclick="openProject('${project}')">
<i data-lucide="folder" class="w-4 h-4 text-yellow-500"></i>
<span>${project}</span>
</div>
`;
});
projectList.innerHTML = html;
lucide.createIcons();
}
function toggleProjectDropdown() {
const dropdown = document.getElementById('project-dropdown');
dropdown.classList.toggle('hidden');
}
async function createNewProject() {
const name = prompt('Enter project name:');
if (!name) return;
updateStatus('Creating project...', 'building');
try {
const response = await fetch('/api/project/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
const data = await response.json();
if (data.success) {
await loadProjects();
await openProject(name);
updateStatus('Project created', 'success');
} else {
updateStatus('Failed to create project', 'error');
}
} catch (error) {
updateStatus('Error creating project', 'error');
}
toggleProjectDropdown();
}
async function openProject(projectName) {
currentProject = projectName;
document.getElementById('current-project').textContent = projectName;
document.getElementById('project-name-display').textContent = projectName;
document.getElementById('preview-app-name').textContent = projectName;
toggleProjectDropdown();
await loadProjectFiles();
}
async function loadProjectFiles() {
if (!currentProject) return;
try {
const response = await fetch(`/api/files/${currentProject}/`);
const data = await response.json();
if (data.type === 'directory') {
files = data.files || [];
renderFileTree();
}
} catch (error) {
console.error('Failed to load files:', error);
}
}
function renderFileTree() {
const treeElement = document.getElementById('file-tree');
if (files.length === 0) {
treeElement.innerHTML = '<div class="px-3 py-1 text-xs text-gray-500">No files found</div>';
return;
}
let html = '<div class="px-3 py-1 text-xs text-gray-500 uppercase">app/src/main</div>';
// Simple file tree - you can make this recursive for full structure
files.forEach(file => {
if (file.includes('.')) {
const icon = file.endsWith('.kt') ? 'file-code' :
file.endsWith('.xml') ? 'file-text' : 'file';
const color = file.endsWith('.kt') ? 'text-blue-300' :
file.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400';
html += `
<div class="file-item px-3 py-1.5 flex items-center space-x-2 cursor-pointer text-sm"
onclick="openFile('${file}')">
<i data-lucide="${icon}" class="w-4 h-4 ${color}"></i>
<span>${file}</span>
</div>
`;
}
});
treeElement.innerHTML = html;
lucide.createIcons();
}
// ==================== File Functions ====================
async function openFile(filename) {
if (!currentProject) {
alert('Please open a project first');
return;
}
currentFile = filename;
currentFilePath = filename;
try {
const response = await fetch(`/api/files/${currentProject}/${filename}`);
const data = await response.json();
if (data.type === 'file') {
document.getElementById('code-editor').innerHTML = data.content;
updateLineNumbers();
addTab(filename);
}
} catch (error) {
console.error('Failed to open file:', error);
}
// Update active state
document.querySelectorAll('.file-item').forEach(el => {
el.classList.remove('active');
if (el.textContent.trim() === filename) {
el.classList.add('active');
}
});
}
function addTab(filename) {
const tabs = document.getElementById('editor-tabs');
// Check if tab already exists
const existingTab = Array.from(tabs.children).find(
tab => tab.dataset.file === filename
);
if (existingTab) return;
const icon = filename.endsWith('.kt') ? 'file-code' :
filename.endsWith('.xml') ? 'file-text' : 'file';
const color = filename.endsWith('.kt') ? 'text-blue-300' :
filename.endsWith('.xml') ? 'text-orange-300' : 'text-gray-400';
const tabHtml = `
<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"
data-file="${filename}"
onclick="switchToTab('${filename}')">
<i data-lucide="${icon}" class="w-4 h-4 ${color}"></i>
<span>${filename}</span>
<i data-lucide="x" class="w-3 h-3 hover:text-red-400 cursor-pointer ml-2"
onclick="closeTab(event, '${filename}')"></i>
</div>
`;
tabs.insertAdjacentHTML('beforeend', tabHtml);
lucide.createIcons();
}
function switchToTab(filename) {
openFile(filename);
}
function closeTab(event, filename) {
event.stopPropagation();
const tab = event.target.closest('[data-file]');
if (tab) {
tab.remove();
if (currentFile === filename) {
const firstTab = document.querySelector('[data-file]');
if (firstTab) {
openFile(firstTab.dataset.file);
} else {
currentFile = null;
document.getElementById('code-editor').innerHTML = '// No file open';
updateLineNumbers();
}
}
}
}
async function saveCurrentFile() {
if (!currentProject || !currentFile) {
alert('No file open');
return;
}
const content = document.getElementById('code-editor').innerText;
try {
const response = await fetch(`/api/file/${currentProject}/${currentFile}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
const data = await response.json();
if (data.success) {
updateStatus('File saved', 'success');
}
} catch (error) {
updateStatus('Save failed', 'error');
}
}
function createNewFile() {
if (!currentProject) {
alert('Please open a project first');
return;
}
const filename = prompt('Enter file name (e.g., NewFile.kt):');
if (filename) {
currentFile = filename;
document.getElementById('code-editor').innerHTML = getDefaultContent(filename);
updateLineNumbers();
addTab(filename);
saveCurrentFile();
}
}
function getDefaultContent(filename) {
if (filename.endsWith('.kt')) {
return `package com.example.app
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}`;
} else if (filename.endsWith('.xml')) {
return `<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout>`;
}
return '';
}
function refreshFiles() {
if (currentProject) {
loadProjectFiles();
}
}
// ==================== Build Functions ====================
async function startBuild() {
if (!currentProject) {
alert('Please open a project first');
return;
}
switchPanel('build');
const buildPanel = document.getElementById('build-panel');
buildPanel.innerHTML = '<div class="building">🔨 Building APK...</div>';
updateStatus('Building...', 'building');
try {
const response = await fetch(`/api/build/${currentProject}`, {
method: 'POST'
});
const data = await response.json();
activeBuildId = data.buildId;
// Poll for build status
checkBuildStatus(activeBuildId);
} catch (error) {
buildPanel.innerHTML = '<div class="build-error">❌ Build failed to start</div>';
updateStatus('Build failed', 'error');
}
}
async function checkBuildStatus(buildId) {
const buildPanel = document.getElementById('build-panel');
try {
const response = await fetch(`/api/build/${buildId}`);
const data = await response.json();
if (data.output) {
let html = data.output.replace(/\n/g, '<br>')
.replace(/SUCCESS/g, '<span class="build-success">SUCCESS</span>')
.replace(/FAILED/g, '<span class="build-error">FAILED</span>')
.replace(/warning/g, '<span class="build-warning">warning</span>');
buildPanel.innerHTML = html;
}
if (data.success !== undefined) {
if (data.success) {
buildPanel.innerHTML += '<br><br><div class="build-success">✅ Build successful! APK ready.</div>';
updateStatus('Build successful', 'success');
} else {
buildPanel.innerHTML += '<br><br><div class="build-error">❌ Build failed</div>';
updateStatus('Build failed', 'error');
}
} else {
// Still building, check again
setTimeout(() => checkBuildStatus(buildId), 1000);
}
} catch (error) {
buildPanel.innerHTML = '<div class="build-error">Error checking build status</div>';
}
}
async function downloadAPK() {
if (!currentProject) {
alert('Please open a project first');
return;
}
window.location.href = `/api/apk/${currentProject}`;
}
// ==================== Preview Functions ====================
async function uploadLogo() {
if (!currentProject) {
alert('Please open a project first');
return;
}
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = async (e) => {
const file = e.target.files[0];
const formData = new FormData();
formData.append('logo', file);
updateStatus('Uploading logo...', 'building');
try {
const response = await fetch(`/api/logo/upload/${currentProject}`, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
// Update preview logo
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('preview-logo').src = e.target.result;
};
reader.readAsDataURL(file);
updateStatus('Logo updated', 'success');
}
} catch (error) {
updateStatus('Logo upload failed', 'error');
}
};
input.click();
}
function installOnPreview() {
const appScreen = document.getElementById('app-screen');
const logo = document.getElementById('preview-logo');
const appName = document.getElementById('preview-app-name');
// Simulate app installation
updateStatus('Installing on Android 10...', 'building');
setTimeout(() => {
appScreen.innerHTML = `
<div class="logo-container">
<img id="preview-logo" src="${logo.src}" alt="App Logo" style="width: 96px; height: 96px; border-radius: 20px;">
<h3 id="preview-app-name" style="font-size: 20px; font-weight: 600; color: #333; margin-top: 16px;">${appName.textContent}</h3>
<p style="font-size: 12px; color: #666; margin-top: 4px;">Running on Android 10</p>
<div style="margin-top: 20px; padding: 8px 16px; background: #3DDC84; color: white; border-radius: 20px; font-size: 12px;">
App installed
</div>
</div>
`;
updateStatus('App installed on preview', 'success');
}, 2000);
}
function rotatePreview() {
const phone = document.getElementById('phone-preview');
phone.classList.toggle('rotated');
}
function toggleTheme() {
const statusBar = document.getElementById('status-bar');
const appScreen = document.getElementById('app-screen');
const navBar = document.getElementById('nav-bar');
statusBar.classList.toggle('light');
appScreen.classList.toggle('dark');
navBar.classList.toggle('light');
}
function simulateBack() {
updateStatus('Back button pressed', 'info');
}
function simulateHome() {
updateStatus('Home button pressed', 'info');
// Reset to home screen
const appScreen = document.getElementById('app-screen');
appScreen.innerHTML = `
<div class="logo-container">
<img id="preview-logo" src="${document.getElementById('preview-logo').src}" alt="App Logo" style="width: 96px; height: 96px; border-radius: 20px;">
<h3 id="preview-app-name" style="font-size: 20px; font-weight: 600; color: #333; margin-top: 16px;">${document.getElementById('preview-app-name').textContent}</h3>
<p class="app-version">Android 10 (API 29)</p>
</div>
`;
}
function simulateRecent() {
updateStatus('Recent apps button pressed', 'info');
}
// ==================== UI Functions ====================
function updateLineNumbers() {
const editor = document.getElementById('code-editor');
const lines = editor.innerText.split('\n').length;
const lineNumbers = document.getElementById('line-numbers');
let html = '';
for (let i = 1; i <= lines; i++) {
html += `<div class="leading-6">${i}</div>`;
}
lineNumbers.innerHTML = html;
}
function syncScroll() {
const editor = document.getElementById('code-editor');
const lineNumbers = document.getElementById('line-numbers');
lineNumbers.scrollTop = editor.scrollTop;
}
function switchPanel(panel) {
// Hide all panels
document.getElementById('terminal-panel').classList.add('hidden');
document.getElementById('build-panel').classList.add('hidden');
// Show selected panel
document.getElementById(`${panel}-panel`).classList.remove('hidden');
// Update tab styles
document.querySelectorAll('.panel-tab').forEach(tab => {
tab.classList.remove('border-[#3DDC84]', 'text-white');
tab.classList.add('text-gray-400');
});
event.currentTarget.classList.add('border-[#3DDC84]', 'text-white');
event.currentTarget.classList.remove('text-gray-400');
}
function updateStatus(message, type) {
const statusEl = document.getElementById('status-message');
statusEl.textContent = message;
const colors = {
success: '#3DDC84',
error: '#CF6679',
building: '#FBC02D',
info: '#64B5F6'
};
statusEl.style.color = colors[type] || '#000000';
}
function openSettings() {
alert('Settings dialog would open here');
}
function setupEventListeners() {
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
saveCurrentFile();
}
if (e.key === 'F5') {
e.preventDefault();
startBuild();
}
});
// Click outside to close dropdown
document.addEventListener('click', (e) => {
if (!e.target.closest('.relative')) {
document.getElementById('project-dropdown').classList.add('hidden');
}
});
}
// Export functions for HTML
window.toggleProjectDropdown = toggleProjectDropdown;
window.createNewProject = createNewProject;
window.openProject = openProject;
window.openFile = openFile;
window.saveCurrentFile = saveCurrentFile;
window.createNewFile = createNewFile;
window.refreshFiles = refreshFiles;
window.startBuild = startBuild;
window.downloadAPK = downloadAPK;
window.installOnPreview = installOnPreview;
window.rotatePreview = rotatePreview;
window.toggleTheme = toggleTheme;
window.uploadLogo = uploadLogo;
window.simulateBack = simulateBack;
window.simulateHome = simulateHome;
window.simulateRecent = simulateRecent;
window.switchPanel = switchPanel;
window.updateLineNumbers = updateLineNumbers;
window.syncScroll = syncScroll;
window.openSettings = openSettings;
window.switchToTab = switchToTab;
window.closeTab = closeTab;