|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8" /> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 6 | + <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'"> |
| 7 | + <title>FileCC</title> |
| 8 | + <link rel="stylesheet" href="style.css" /> |
| 9 | +</head> |
| 10 | +<body> |
| 11 | + |
| 12 | +<div class="titlebar"> |
| 13 | + <div class="titlebar-title"> |
| 14 | + <span></span> |
| 15 | + FileCC |
| 16 | + </div> |
| 17 | + <div class="titlebar-controls"> |
| 18 | + <button id="btnMinimize">–</button> |
| 19 | + <button id="btnMaximize">□</button> |
| 20 | + <button id="btnClose" class="close">✕</button> |
| 21 | + </div> |
| 22 | +</div> |
| 23 | + |
| 24 | +<div class="layout"> |
| 25 | + |
| 26 | + <aside class="sidebar"> |
| 27 | + <div class="sidebar-label">Type</div> |
| 28 | + <button class="nav-btn active" data-type="image"> |
| 29 | + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg> |
| 30 | + Image |
| 31 | + </button> |
| 32 | + <button class="nav-btn" data-type="document"> |
| 33 | + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg> |
| 34 | + Document |
| 35 | + </button> |
| 36 | + <button class="nav-btn" data-type="audio"> |
| 37 | + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg> |
| 38 | + Audio |
| 39 | + </button> |
| 40 | + <button class="nav-btn" data-type="video"> |
| 41 | + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg> |
| 42 | + Video |
| 43 | + </button> |
| 44 | + </aside> |
| 45 | + |
| 46 | + <main class="main"> |
| 47 | + |
| 48 | + <div class="dropzone" id="dropzone"> |
| 49 | + <div class="dropzone-icon"> |
| 50 | + <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg> |
| 51 | + </div> |
| 52 | + <div class="dropzone-text"> |
| 53 | + <strong>Drop file here</strong> |
| 54 | + or click to browse |
| 55 | + </div> |
| 56 | + </div> |
| 57 | + |
| 58 | + <div class="file-info" id="fileInfo"> |
| 59 | + <div class="file-icon" id="fileExt">—</div> |
| 60 | + <div class="file-meta"> |
| 61 | + <div class="file-name" id="fileName">—</div> |
| 62 | + <div class="file-size" id="fileSize">—</div> |
| 63 | + </div> |
| 64 | + <button class="clear-btn" id="clearBtn"> |
| 65 | + <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> |
| 66 | + </button> |
| 67 | + </div> |
| 68 | + |
| 69 | + <div class="format-section"> |
| 70 | + <div class="section-label">Convert to</div> |
| 71 | + <div class="format-grid" id="formatGrid"></div> |
| 72 | + </div> |
| 73 | + |
| 74 | + <div class="progress-bar" id="progressBar"> |
| 75 | + <div class="progress-fill indeterminate" id="progressFill"></div> |
| 76 | + </div> |
| 77 | + |
| 78 | + <button class="convert-btn" id="convertBtn" disabled> |
| 79 | + <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg> |
| 80 | + Convert |
| 81 | + </button> |
| 82 | + |
| 83 | + <div class="status" id="statusBox"> |
| 84 | + <span id="statusText"></span> |
| 85 | + <button class="status-open" id="statusOpen" style="display:none">Open folder</button> |
| 86 | + </div> |
| 87 | + |
| 88 | + </main> |
| 89 | +</div> |
| 90 | + |
| 91 | +<script> |
| 92 | + const FORMATS = { |
| 93 | + image: ['jpg', 'png', 'webp', 'avif', 'tiff', 'bmp', 'gif'], |
| 94 | + document: ['pdf', 'txt', 'html', 'docx'], |
| 95 | + audio: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a'], |
| 96 | + video: ['mp4', 'avi', 'mkv', 'mov', 'webm'], |
| 97 | + }; |
| 98 | + |
| 99 | + let currentType = 'image'; |
| 100 | + let selectedFile = null; |
| 101 | + let targetFormat = null; |
| 102 | + let lastOutput = null; |
| 103 | + |
| 104 | + const dropzone = document.getElementById('dropzone'); |
| 105 | + const fileInfo = document.getElementById('fileInfo'); |
| 106 | + const fileExt = document.getElementById('fileExt'); |
| 107 | + const fileName = document.getElementById('fileName'); |
| 108 | + const fileSize = document.getElementById('fileSize'); |
| 109 | + const clearBtn = document.getElementById('clearBtn'); |
| 110 | + const formatGrid = document.getElementById('formatGrid'); |
| 111 | + const convertBtn = document.getElementById('convertBtn'); |
| 112 | + const progressBar = document.getElementById('progressBar'); |
| 113 | + const progressFill= document.getElementById('progressFill'); |
| 114 | + const statusBox = document.getElementById('statusBox'); |
| 115 | + const statusText = document.getElementById('statusText'); |
| 116 | + const statusOpen = document.getElementById('statusOpen'); |
| 117 | + |
| 118 | + function formatBytes(bytes) { |
| 119 | + if (bytes < 1024) return bytes + ' B'; |
| 120 | + if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; |
| 121 | + return (bytes / 1048576).toFixed(2) + ' MB'; |
| 122 | + } |
| 123 | + |
| 124 | + function renderFormats() { |
| 125 | + formatGrid.innerHTML = ''; |
| 126 | + targetFormat = null; |
| 127 | + FORMATS[currentType].forEach(fmt => { |
| 128 | + const btn = document.createElement('button'); |
| 129 | + btn.className = 'fmt-btn'; |
| 130 | + btn.textContent = fmt; |
| 131 | + btn.addEventListener('click', () => { |
| 132 | + document.querySelectorAll('.fmt-btn').forEach(b => b.classList.remove('active')); |
| 133 | + btn.classList.add('active'); |
| 134 | + targetFormat = fmt; |
| 135 | + updateConvertBtn(); |
| 136 | + }); |
| 137 | + formatGrid.appendChild(btn); |
| 138 | + }); |
| 139 | + } |
| 140 | + |
| 141 | + function updateConvertBtn() { |
| 142 | + convertBtn.disabled = !(selectedFile && targetFormat); |
| 143 | + } |
| 144 | + |
| 145 | + function setFile(file) { |
| 146 | + selectedFile = file; |
| 147 | + fileExt.textContent = file.ext.toUpperCase(); |
| 148 | + fileName.textContent = file.name; |
| 149 | + fileSize.textContent = formatBytes(file.size); |
| 150 | + dropzone.style.display = 'none'; |
| 151 | + fileInfo.classList.add('visible'); |
| 152 | + hideStatus(); |
| 153 | + updateConvertBtn(); |
| 154 | + } |
| 155 | + |
| 156 | + function clearFile() { |
| 157 | + selectedFile = null; |
| 158 | + lastOutput = null; |
| 159 | + dropzone.style.display = ''; |
| 160 | + fileInfo.classList.remove('visible'); |
| 161 | + hideStatus(); |
| 162 | + updateConvertBtn(); |
| 163 | + } |
| 164 | + |
| 165 | + function showStatus(type, message, outputPath) { |
| 166 | + statusBox.className = 'status visible ' + type; |
| 167 | + statusText.textContent = message; |
| 168 | + if (outputPath) { |
| 169 | + lastOutput = outputPath; |
| 170 | + statusOpen.style.display = 'inline-block'; |
| 171 | + } else { |
| 172 | + statusOpen.style.display = 'none'; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + function hideStatus() { |
| 177 | + statusBox.className = 'status'; |
| 178 | + } |
| 179 | + |
| 180 | + document.querySelectorAll('.nav-btn').forEach(btn => { |
| 181 | + btn.addEventListener('click', () => { |
| 182 | + document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active')); |
| 183 | + btn.classList.add('active'); |
| 184 | + currentType = btn.dataset.type; |
| 185 | + renderFormats(); |
| 186 | + clearFile(); |
| 187 | + }); |
| 188 | + }); |
| 189 | + |
| 190 | + dropzone.addEventListener('click', async () => { |
| 191 | + const file = await window.electronAPI.openFile(); |
| 192 | + if (file) setFile(file); |
| 193 | + }); |
| 194 | + |
| 195 | + dropzone.addEventListener('dragover', e => { |
| 196 | + e.preventDefault(); |
| 197 | + dropzone.classList.add('dragover'); |
| 198 | + }); |
| 199 | + |
| 200 | + dropzone.addEventListener('dragleave', () => { |
| 201 | + dropzone.classList.remove('dragover'); |
| 202 | + }); |
| 203 | + |
| 204 | + dropzone.addEventListener('drop', async e => { |
| 205 | + e.preventDefault(); |
| 206 | + dropzone.classList.remove('dragover'); |
| 207 | + const file = await window.electronAPI.openFile(); |
| 208 | + if (file) setFile(file); |
| 209 | + }); |
| 210 | + |
| 211 | + clearBtn.addEventListener('click', clearFile); |
| 212 | + |
| 213 | + convertBtn.addEventListener('click', async () => { |
| 214 | + if (!selectedFile || !targetFormat) return; |
| 215 | + |
| 216 | + const baseName = selectedFile.name.replace(/\.[^/.]+$/, '') + '.' + targetFormat; |
| 217 | + const outputPath = await window.electronAPI.saveFile(baseName, targetFormat); |
| 218 | + if (!outputPath) return; |
| 219 | + |
| 220 | + convertBtn.disabled = true; |
| 221 | + progressBar.classList.add('visible'); |
| 222 | + hideStatus(); |
| 223 | + |
| 224 | + const result = await window.electronAPI.convertFile( |
| 225 | + selectedFile.path, |
| 226 | + outputPath, |
| 227 | + targetFormat |
| 228 | + ); |
| 229 | + |
| 230 | + progressBar.classList.remove('visible'); |
| 231 | + convertBtn.disabled = false; |
| 232 | + |
| 233 | + if (result.success) { |
| 234 | + showStatus('success', 'Conversion complete — ' + baseName, result.outputPath); |
| 235 | + } else { |
| 236 | + showStatus('error', 'Error: ' + result.error, null); |
| 237 | + } |
| 238 | + }); |
| 239 | + |
| 240 | + statusOpen.addEventListener('click', () => { |
| 241 | + if (lastOutput) window.electronAPI.showInFolder(lastOutput); |
| 242 | + }); |
| 243 | + |
| 244 | + document.getElementById('btnMinimize').addEventListener('click', () => window.electronAPI.minimize()); |
| 245 | + document.getElementById('btnMaximize').addEventListener('click', () => window.electronAPI.maximize()); |
| 246 | + document.getElementById('btnClose').addEventListener('click', () => window.electronAPI.close()); |
| 247 | + |
| 248 | + renderFormats(); |
| 249 | +</script> |
| 250 | +</body> |
| 251 | +</html> |
0 commit comments