/
Muvz82
/
openbox-linux
Обзор
Документация
Войти
/
Muvz82
/
openbox-linux
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
apps/desktop/src/components/AddAppForm.jsx
213 строк
7 KB
Mikhail Sviridov
init
18 май 2026, 17:54
18 май 2026, 17:54
482974b
Код
Авторство
О чём код?
import React, { useState } from 'react'; import { appAdd, importInstaller, browseISO } from '../api/client'; function AddAppForm({ onAdded, onCancel }) { const [name, setName] = useState(''); const [icon, setIcon] = useState('📦'); const [exePath, setExePath] = useState(''); const [installerSource, setInstallerSource] = useState(''); const [browserDir, setBrowserDir] = useState('/home'); const [browserFiles, setBrowserFiles] = useState(null); const [selectedFile, setSelectedFile] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [importing, setImporting] = useState(false); const [importResult, setImportResult] = useState(null); const ICON_OPTIONS = ['📦', '🎮', '🔧', '📊', '🎨', '📝', '🌐', '🎵', '📷', '🖥️', '⚙️', '💼']; const browseForInstaller = async (dir) => { try { const result = await browseISO(dir); setBrowserFiles(result); setBrowserDir(result.current_dir || dir); } catch (e) { setError(e.message); } }; const handleImport = async () => { if (!selectedFile && !installerSource) { setError('Выберите файл установщика'); return; } const source = selectedFile || installerSource; try { setImporting(true); setError(null); const result = await importInstaller(source, name || 'App'); setImportResult(result); setInstallerSource(result.installer_path || ''); } catch (e) { setError(e.message); } finally { setImporting(false); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!name.trim()) { setError('Укажите название приложения'); return; } try { setLoading(true); setError(null); await appAdd({ name: name.trim(), icon, exe_path: exePath.trim(), installer_path: importResult?.installer_path || installerSource.trim(), installed: false, }); onAdded(); } catch (e) { setError(e.message); } finally { setLoading(false); } }; return ( <> <div className="wizard-header"> <div className="wizard-title">Добавить приложение</div> <button className="btn btn-close" onClick={onCancel}>✕</button> </div> <div className="wizard-content"> {error && ( <div className="vm-error">{error}</div> )} <form className="wizard-form" onSubmit={handleSubmit}> <div> <label className="wizard-label">Название</label> <input className="wizard-input" value={name} onChange={(e) => setName(e.target.value)} placeholder="Например: Notepad++" autoFocus /> </div> <div> <label className="wizard-label">Иконка</label> <div className="icon-picker"> {ICON_OPTIONS.map((ic) => ( <button key={ic} type="button" className={`icon-option ${icon === ic ? 'icon-selected' : ''}`} onClick={() => setIcon(ic)} > {ic} </button> ))} </div> </div> <div> <label className="wizard-label">Путь к EXE (внутри VM)</label> <input className="wizard-input" value={exePath} onChange={(e) => setExePath(e.target.value)} placeholder="C:\Program Files\App\app.exe" /> </div> <div> <label className="wizard-label">Импорт установщика (.exe / .msi)</label> <div className="wizard-input-row"> <input className="wizard-input" value={installerSource || selectedFile} onChange={(e) => { setInstallerSource(e.target.value); setSelectedFile(''); }} placeholder="/путь/к/setup.exe" /> <button type="button" className="btn" onClick={() => browseForInstaller(browserDir)} > 📂 Обзор </button> </div> {browserFiles && ( <div className="wizard-file-browser" style={{ marginTop: '10px' }}> <div className="wizard-browser-header"> <span className="wizard-browser-path">{browserDir}</span> <button type="button" className="btn btn-sm" onClick={() => { const parent = browserDir.split('/').slice(0, -1).join('/') || '/'; browseForInstaller(parent); }} > ↑ </button> </div> <div className="wizard-browser-list"> {browserFiles.entries && browserFiles.entries .filter((f) => f.is_dir || /\.(exe|msi)$/i.test(f.name)) .map((f) => ( <div key={f.name} className={`wizard-browser-item ${selectedFile === f.path ? 'item-selected' : ''}`} onClick={() => { if (f.is_dir) { browseForInstaller(f.path); } else { setSelectedFile(f.path); setInstallerSource(''); } }} > <span className="item-icon">{f.is_dir ? '📁' : '📄'}</span> <span className="item-name">{f.name}</span> {!f.is_dir && <span className="item-size">{f.size}</span>} </div> ))} {(!browserFiles.entries || browserFiles.entries.length === 0) && ( <div className="wizard-browser-empty">Пусто</div> )} </div> </div> )} <button type="button" className="btn btn-import" onClick={handleImport} disabled={importing || (!installerSource && !selectedFile)} style={{ marginTop: '8px' }} > {importing ? '⏳ Копирование…' : '📥 Импортировать в shared/installers'} </button> {importResult && ( <div className="wizard-disk-info" style={{ marginTop: '8px' }}> ✅ Скопировано: <code>{importResult.installer_path}</code> </div> )} </div> </form> </div> <div className="wizard-footer"> <button className="btn" onClick={onCancel}>Отмена</button> <button className="btn btn-next" onClick={handleSubmit} disabled={loading || !name.trim()}> {loading ? '⏳…' : 'Добавить'} </button> </div> </> ); } export default AddAppForm;