Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect, useRef } from 'react'; | |
| function Customise() { | |
| const [sensitivity, setSensitivity] = useState(6); | |
| const [frameRate, setFrameRate] = useState(30); | |
| const [notificationsEnabled, setNotificationsEnabled] = useState(true); | |
| const [threshold, setThreshold] = useState(30); | |
| // 引用隐藏的文件输入框 | |
| const fileInputRef = useRef(null); | |
| // 1. 加载设置 | |
| useEffect(() => { | |
| fetch('/api/settings') | |
| .then(res => res.json()) | |
| .then(data => { | |
| if (data) { | |
| if (data.sensitivity) setSensitivity(data.sensitivity); | |
| if (data.frame_rate) setFrameRate(data.frame_rate); | |
| if (data.notification_threshold) setThreshold(data.notification_threshold); | |
| if (data.notification_enabled !== undefined) setNotificationsEnabled(data.notification_enabled); | |
| } | |
| }) | |
| .catch(err => console.error("Failed to load settings", err)); | |
| }, []); | |
| // 2. 保存设置 | |
| const handleSave = async () => { | |
| const settings = { | |
| sensitivity: parseInt(sensitivity), | |
| frame_rate: parseInt(frameRate), | |
| notification_enabled: notificationsEnabled, | |
| notification_threshold: parseInt(threshold) | |
| }; | |
| try { | |
| const response = await fetch('/api/settings', { | |
| method: 'PUT', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(settings) | |
| }); | |
| if (response.ok) alert("Settings saved successfully!"); | |
| else alert("Failed to save settings."); | |
| } catch (error) { | |
| alert("Error saving settings: " + error.message); | |
| } | |
| }; | |
| // 3. 导出数据 (Export) | |
| const handleExport = async () => { | |
| try { | |
| // 请求获取所有历史记录 | |
| const response = await fetch('/api/sessions?filter=all'); | |
| if (!response.ok) throw new Error("Failed to fetch data"); | |
| const data = await response.json(); | |
| // 创建 JSON Blob | |
| const jsonString = JSON.stringify(data, null, 2); | |
| // 在浏览器缓存里存一份 | |
| localStorage.setItem('focus_magic_backup', jsonString); | |
| const blob = new Blob([jsonString], { type: 'application/json' }); | |
| // 创建临时下载链接 | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| // 文件名包含当前日期 | |
| link.download = `focus-guard-backup-${new Date().toISOString().slice(0, 10)}.json`; | |
| // 触发下载 | |
| document.body.appendChild(link); | |
| link.click(); | |
| // 清理 | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } catch (error) { | |
| console.error(error); | |
| alert("Export failed: " + error.message); | |
| } | |
| }; | |
| // 4. 触发导入文件选择 | |
| const triggerImport = () => { | |
| fileInputRef.current.click(); | |
| }; | |
| // 5. 处理文件导入 (Import) | |
| const handleFileChange = async (event) => { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = async (e) => { | |
| try { | |
| const content = e.target.result; | |
| const sessions = JSON.parse(content); | |
| // 简单的验证:确保它是一个数组 | |
| if (!Array.isArray(sessions)) { | |
| throw new Error("Invalid file format: Expected a list of sessions."); | |
| } | |
| // 发送给后端进行存储 | |
| const response = await fetch('/api/import', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(sessions) | |
| }); | |
| if (response.ok) { | |
| const result = await response.json(); | |
| alert(`Success! Imported ${result.count} sessions.`); | |
| } else { | |
| alert("Import failed on server side."); | |
| } | |
| } catch (err) { | |
| alert("Error parsing file: " + err.message); | |
| } | |
| // 清空 input,允许重复上传同一个文件 | |
| event.target.value = ''; | |
| }; | |
| reader.readAsText(file); | |
| }; | |
| // 6. 清除历史 (Clear History) | |
| const handleClearHistory = async () => { | |
| if (!window.confirm("Are you sure? This will delete ALL your session history permanently.")) { | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/history', { method: 'DELETE' }); | |
| if (response.ok) { | |
| alert("All history has been cleared."); | |
| } else { | |
| alert("Failed to clear history."); | |
| } | |
| } catch (err) { | |
| alert("Error: " + err.message); | |
| } | |
| }; | |
| return ( | |
| <main id="page-e" className="page"> | |
| <h1 className="page-title">Customise</h1> | |
| <div className="settings-container"> | |
| {/* Detection Settings */} | |
| <div className="setting-group"> | |
| <h2>Detection Settings</h2> | |
| <div className="setting-item"> | |
| <label htmlFor="sensitivity-slider">Detection Sensitivity</label> | |
| <div className="slider-group"> | |
| <input type="range" id="sensitivity-slider" min="1" max="10" value={sensitivity} onChange={(e) => setSensitivity(e.target.value)} /> | |
| <span id="sensitivity-value">{sensitivity}</span> | |
| </div> | |
| <p className="setting-description">Higher values require stricter focus criteria</p> | |
| </div> | |
| <div className="setting-item"> | |
| <label htmlFor="default-framerate">Default Frame Rate</label> | |
| <div className="slider-group"> | |
| <input type="range" id="default-framerate" min="5" max="60" value={frameRate} onChange={(e) => setFrameRate(e.target.value)} /> | |
| <span id="framerate-value">{frameRate}</span> FPS | |
| </div> | |
| </div> | |
| </div> | |
| {/* Notifications */} | |
| <div className="setting-group"> | |
| <h2>Notifications</h2> | |
| <div className="setting-item"> | |
| <label> | |
| <input type="checkbox" id="enable-notifications" checked={notificationsEnabled} onChange={(e) => setNotificationsEnabled(e.target.checked)} /> | |
| Enable distraction notifications | |
| </label> | |
| </div> | |
| <div className="setting-item"> | |
| <label htmlFor="notification-threshold">Alert after (seconds)</label> | |
| <input type="number" id="notification-threshold" value={threshold} onChange={(e) => setThreshold(e.target.value)} min="5" max="300" /> | |
| </div> | |
| </div> | |
| {/* Data Management */} | |
| <div className="setting-group"> | |
| <h2>Data Management</h2> | |
| {/* 隐藏的文件输入框,只接受 json */} | |
| <input | |
| type="file" | |
| ref={fileInputRef} | |
| style={{ display: 'none' }} | |
| accept=".json" | |
| onChange={handleFileChange} | |
| /> | |
| <div style={{ display: 'flex', gap: '10px', justifyContent: 'center', flexWrap: 'wrap' }}> | |
| {/* Export 按钮 */} | |
| <button id="export-data" className="action-btn blue" onClick={handleExport} style={{ width: '30%', minWidth: '120px' }}> | |
| Export Data | |
| </button> | |
| {/* Import 按钮 */} | |
| <button id="import-data" className="action-btn yellow" onClick={triggerImport} style={{ width: '30%', minWidth: '120px' }}> | |
| Import Data | |
| </button> | |
| {/* Clear 按钮 */} | |
| <button id="clear-history" className="action-btn red" onClick={handleClearHistory} style={{ width: '30%', minWidth: '120px' }}> | |
| Clear History | |
| </button> | |
| </div> | |
| </div> | |
| <button id="save-settings" className="btn-main" onClick={handleSave}>Save Settings</button> | |
| </div> | |
| </main> | |
| ); | |
| } | |
| export default Customise; | |