Kexin-251202's picture
update user/admin, update data import options
9def1ee verified
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;