Render grouped mobile sensor sections

This commit is contained in:
seo
2026-06-12 15:30:53 +09:00
parent 4559301fa6
commit b8778427f7
4 changed files with 378 additions and 358 deletions
+298 -68
View File
@@ -47,9 +47,118 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
dozeMode: "Doze 모드",
powerSave: "절전 모드",
deviceLocked: "잠금 상태",
network: "네트워크",
device: "기기",
displayAudio: "화면 및 오디오",
health: "건강",
car: "차량",
storageData: "저장공간 및 데이터",
notifications: "알림",
environment: "센서",
wifiBssid: "Wi-Fi BSSID",
mobileDataRoaming: "데이터 로밍",
publicIpAddress: "공인 IP",
hotspotState: "핫스팟",
ipv6Addresses: "IPv6 주소",
nfcState: "NFC",
sim1: "SIM 1",
sim2: "SIM 2",
signalStrengthSim1: "SIM 1 신호",
signalStrengthSim2: "SIM 2 신호",
dataNetworkTypeSim1: "SIM 1 데이터망",
dataNetworkTypeSim2: "SIM 2 데이터망",
osVersion: "OS 버전",
securityPatch: "보안 패치",
currentVersion: "앱 버전",
lastReboot: "마지막 재부팅",
currentTimeZone: "시간대",
deviceSecure: "보안 설정",
keyguardLocked: "키가드 잠김",
keyguardSecure: "키가드 보안",
workProfile: "업무 프로필",
lastUsedApp: "마지막 사용 앱",
appInactive: "앱 비활성",
appStandbyBucket: "앱 대기 버킷",
appImportance: "앱 중요도",
appMemory: "앱 메모리",
screenBrightness: "화면 밝기",
screenOffTimeout: "화면 꺼짐 시간",
screenOrientation: "화면 방향",
screenRotation: "화면 회전",
doNotDisturb: "방해 금지",
ringerMode: "벨소리 모드",
audioMode: "오디오 모드",
headphones: "헤드폰",
micMuted: "마이크 음소거",
speakerphone: "스피커폰",
musicActive: "미디어 재생",
volumeAlarm: "알람 볼륨",
volumeCall: "통화 볼륨",
volumeMusic: "미디어 볼륨",
volumeRinger: "벨소리 볼륨",
volumeNotification: "알림 볼륨",
volumeSystem: "시스템 볼륨",
volumeDtmf: "DTMF 볼륨",
volumeAccessibility: "접근성 볼륨",
sleepConfidence: "수면 신뢰도",
sleepSegment: "수면 구간",
sleepDuration: "수면 시간",
activeCaloriesBurned: "활동 칼로리",
totalCaloriesBurned: "총 칼로리",
basalBodyTemperature: "기초 체온",
basalMetabolicRate: "기초대사량",
bloodGlucose: "혈당",
bodyFat: "체지방",
bodyTemperature: "체온",
bodyWaterMass: "체수분",
boneMass: "골량",
diastolicBloodPressure: "이완기 혈압",
systolicBloodPressure: "수축기 혈압",
heartRate: "심박수",
heartRateVariability: "심박 변이도",
restingHeartRate: "안정 심박수",
oxygenSaturation: "산소포화도",
respiratoryRate: "호흡수",
vo2Max: "VO2 Max",
weight: "체중",
height: "키",
dailyHydration: "일일 수분",
dailyElevationGained: "일일 고도 상승",
stepsSensor: "걸음 센서",
androidAuto: "Android Auto",
carName: "차량 이름",
carBattery: "차량 배터리",
carChargingStatus: "차량 충전 상태",
carEvConnectorType: "차량 충전 커넥터",
carFuel: "차량 연료",
carFuelType: "차량 연료 종류",
carOdometer: "차량 주행거리",
carSpeed: "차량 속도",
carRangeRemaining: "차량 잔여 주행거리",
internalStorage: "내부 저장공간",
externalStorage: "외부 저장공간",
appRxGb: "앱 수신 데이터",
appTxGb: "앱 송신 데이터",
mobileRxGb: "모바일 수신 데이터",
mobileTxGb: "모바일 송신 데이터",
totalRxGb: "전체 수신 데이터",
totalTxGb: "전체 송신 데이터",
nextAlarm: "다음 알람",
lastNotification: "마지막 알림",
lastRemovedNotification: "마지막 제거 알림",
activeNotificationCount: "활성 알림 수",
mediaSession: "미디어 세션",
lightSensor: "조도 센서",
pressureSensor: "기압 센서",
proximitySensor: "근접 센서",
bluetoothConnection: "블루투스 연결",
bluetoothState: "블루투스",
bleTransmitter: "BLE 송신기",
beaconMonitor: "비콘 모니터",
accentColor: "강조 색상",
editPage: "페이지 수정",
pwa: "눌러 홈 화면에 추가해 바로 보기",
history: "기록",
pwa: "눌러 홈 화면에 추가해 바로 보기",
history: "기록",
startTime: "시작",
endTime: "종료",
},
@@ -100,8 +209,117 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts
dozeMode: "Doze Mode",
powerSave: "Power Save",
deviceLocked: "Device Locked",
network: "Network",
device: "Device",
displayAudio: "Display & Audio",
health: "Health",
car: "Car",
storageData: "Storage & Data",
notifications: "Notifications",
environment: "Sensors",
wifiBssid: "Wi-Fi BSSID",
mobileDataRoaming: "Data Roaming",
publicIpAddress: "Public IP",
hotspotState: "Hotspot",
ipv6Addresses: "IPv6 Addresses",
nfcState: "NFC",
sim1: "SIM 1",
sim2: "SIM 2",
signalStrengthSim1: "SIM 1 Signal",
signalStrengthSim2: "SIM 2 Signal",
dataNetworkTypeSim1: "SIM 1 Data Network",
dataNetworkTypeSim2: "SIM 2 Data Network",
osVersion: "OS Version",
securityPatch: "Security Patch",
currentVersion: "App Version",
lastReboot: "Last Reboot",
currentTimeZone: "Time Zone",
deviceSecure: "Device Secure",
keyguardLocked: "Keyguard Locked",
keyguardSecure: "Keyguard Secure",
workProfile: "Work Profile",
lastUsedApp: "Last Used App",
appInactive: "App Inactive",
appStandbyBucket: "App Standby Bucket",
appImportance: "App Importance",
appMemory: "App Memory",
screenBrightness: "Screen Brightness",
screenOffTimeout: "Screen Timeout",
screenOrientation: "Screen Orientation",
screenRotation: "Screen Rotation",
doNotDisturb: "Do Not Disturb",
ringerMode: "Ringer Mode",
audioMode: "Audio Mode",
headphones: "Headphones",
micMuted: "Mic Muted",
speakerphone: "Speakerphone",
musicActive: "Music Active",
volumeAlarm: "Alarm Volume",
volumeCall: "Call Volume",
volumeMusic: "Music Volume",
volumeRinger: "Ringer Volume",
volumeNotification: "Notification Volume",
volumeSystem: "System Volume",
volumeDtmf: "DTMF Volume",
volumeAccessibility: "Accessibility Volume",
sleepConfidence: "Sleep Confidence",
sleepSegment: "Sleep Segment",
sleepDuration: "Sleep Duration",
activeCaloriesBurned: "Active Calories Burned",
totalCaloriesBurned: "Total Calories Burned",
basalBodyTemperature: "Basal Body Temperature",
basalMetabolicRate: "Basal Metabolic Rate",
bloodGlucose: "Blood Glucose",
bodyFat: "Body Fat",
bodyTemperature: "Body Temperature",
bodyWaterMass: "Body Water Mass",
boneMass: "Bone Mass",
diastolicBloodPressure: "Diastolic Blood Pressure",
systolicBloodPressure: "Systolic Blood Pressure",
heartRate: "Heart Rate",
heartRateVariability: "Heart Rate Variability",
restingHeartRate: "Resting Heart Rate",
oxygenSaturation: "Oxygen Saturation",
respiratoryRate: "Respiratory Rate",
vo2Max: "VO2 Max",
weight: "Weight",
height: "Height",
dailyHydration: "Daily Hydration",
dailyElevationGained: "Daily Elevation Gained",
stepsSensor: "Step Sensor",
androidAuto: "Android Auto",
carName: "Car Name",
carBattery: "Car Battery",
carChargingStatus: "Car Charging Status",
carEvConnectorType: "Car EV Connector",
carFuel: "Car Fuel",
carFuelType: "Car Fuel Type",
carOdometer: "Car Odometer",
carSpeed: "Car Speed",
carRangeRemaining: "Car Range Remaining",
internalStorage: "Internal Storage",
externalStorage: "External Storage",
appRxGb: "App RX Data",
appTxGb: "App TX Data",
mobileRxGb: "Mobile RX Data",
mobileTxGb: "Mobile TX Data",
totalRxGb: "Total RX Data",
totalTxGb: "Total TX Data",
nextAlarm: "Next Alarm",
lastNotification: "Last Notification",
lastRemovedNotification: "Last Removed Notification",
activeNotificationCount: "Active Notifications",
mediaSession: "Media Session",
lightSensor: "Light Sensor",
pressureSensor: "Pressure Sensor",
proximitySensor: "Proximity Sensor",
bluetoothConnection: "Bluetooth Connection",
bluetoothState: "Bluetooth",
bleTransmitter: "BLE Transmitter",
beaconMonitor: "Beacon Monitor",
accentColor: "Accent Color",
editPage: "Edit Page",
pwa: "Add to Home Screen for quick access!",
pwa: "Add to Home Screen for quick access!",
history: "History",
startTime: "Start",
endTime: "End",
@@ -562,11 +780,23 @@ function applyLanguage(langCode) {
if (el) el.textContent = titleMap[id];
}
for (const id in labelMap) {
const el = document.getElementById(id);
if (el) el.textContent = labelMap[id];
}
}
for (const id in labelMap) {
const el = document.getElementById(id);
if (el) el.textContent = labelMap[id];
}
document.querySelectorAll('[data-sensor-title="1"]').forEach(title => {
const sectionKey = title.dataset.sensorSection;
const label = title.querySelector('span');
if (label) label.textContent = t[sectionKey] || sectionKey;
});
document.querySelectorAll('[data-sensor-row="1"]').forEach(row => {
const key = row.dataset.sensorKey;
const label = row.querySelector('span');
if (label && key) label.textContent = t[key] || key;
});
}
function setCookieHours(name, value, hours) {
const d = new Date();
@@ -599,49 +829,67 @@ function updateDOM(data) {
toggleOptionalRow('WbatteryStatus', data.wstate);
toggleOptionalRow('WBattery', data.wlevel);
updateGalaxyRows(data.galaxy || {});
updateSensorSections(data.sensors || {});
}
function updateGalaxyRows(galaxy) {
function updateSensorSections(sections) {
const t = translations[langCode] || translations.ko;
const rows = [
['isCharging', t.isCharging, galaxy.isCharging],
['chargerType', t.chargerType, galaxy.chargerType],
['batteryHealth', t.batteryHealth, galaxy.batteryHealth],
['batteryTemperature', t.batteryTemperature, formatWithUnit(galaxy.batteryTemperature, '°C')],
['batteryPower', t.batteryPower, formatWithUnit(galaxy.batteryPower, 'W')],
['remainingChargeTime', t.remainingChargeTime, formatDuration(galaxy.remainingChargeTime)],
['batteryCycleCount', t.batteryCycleCount, galaxy.batteryCycleCount],
['wifiConnection', t.wifiConnection, galaxy.wifiConnection],
['wifiIp', t.wifiIp, galaxy.wifiIp],
['wifiLinkSpeed', t.wifiLinkSpeed, formatWithUnit(galaxy.wifiLinkSpeed, 'Mbps')],
['wifiFrequency', t.wifiFrequency, formatWithUnit(galaxy.wifiFrequency, 'MHz')],
['wifiSignalStrength', t.wifiSignalStrength, formatWithUnit(galaxy.wifiSignalStrength, 'dBm')],
['mobileData', t.mobileData, galaxy.mobileData],
['phoneState', t.phoneState, galaxy.phoneState],
['interactive', t.interactive, galaxy.interactive],
['dozeMode', t.dozeMode, galaxy.dozeMode],
['powerSave', t.powerSave, galaxy.powerSave],
['deviceLocked', t.deviceLocked, galaxy.deviceLocked],
];
const activeSectionIds = new Set();
const activeRowIds = new Set();
rows.forEach(([key, label, value]) => {
const targetSection = ['isCharging', 'chargerType', 'batteryHealth', 'batteryTemperature', 'batteryPower', 'remainingChargeTime', 'batteryCycleCount'].includes(key)
? findSectionByTitle(langCode === 'ko' ? '배터리' : 'Battery')
: findSectionByTitle(langCode === 'ko' ? '기타' : 'Others');
upsertOptionalRow(targetSection, `galaxy_${key}`, label, value);
Object.entries(sections || {}).forEach(([sectionKey, rows]) => {
const entries = Object.entries(rows || {}).filter(([, value]) => normalizeDisplayValue(value));
if (!entries.length) return;
activeSectionIds.add(sectionKey);
const section = ensureSensorSection(sectionKey);
if (!section) return;
const title = document.getElementById(`sensorTitleText_${sectionKey}`);
if (title) title.textContent = t[sectionKey] || sectionKey;
entries.forEach(([key, value]) => {
activeRowIds.add(`sensor_${sectionKey}_${key}`);
upsertOptionalRow(section, `sensor_${sectionKey}_${key}`, t[key] || key, value);
});
});
document.querySelectorAll('[data-sensor-row="1"]').forEach(row => {
if (!activeRowIds.has(row.id)) row.remove();
});
document.querySelectorAll('[data-sensor-title="1"]').forEach(title => {
const sectionKey = title.dataset.sensorSection;
const section = document.getElementById(`sensorSection_${sectionKey}`);
if (!activeSectionIds.has(sectionKey) || !section?.querySelector('[data-sensor-row="1"]')) {
section?.remove();
title.remove();
}
});
}
function findSectionByTitle(titleText) {
const titles = document.querySelectorAll('.settingTitle');
for (const title of titles) {
if (title.textContent.trim() === titleText) {
const section = title.nextElementSibling;
if (section && section.classList.contains('settingSection')) return section;
}
}
return null;
function ensureSensorSection(sectionKey) {
let section = document.getElementById(`sensorSection_${sectionKey}`);
if (section) return section;
const t = translations[langCode] || translations.ko;
const toggleContent = document.getElementById('toggleContent') || document.body;
const title = document.createElement('div');
title.className = 'settingTitle';
title.id = `sensorTitle_${sectionKey}`;
title.dataset.sensorTitle = '1';
title.dataset.sensorSection = sectionKey;
title.innerHTML = `<span id="sensorTitleText_${sectionKey}">${t[sectionKey] || sectionKey}</span>`;
section = document.createElement('div');
section.className = 'settingSection';
section.id = `sensorSection_${sectionKey}`;
section.dataset.sensorSection = sectionKey;
toggleContent.appendChild(title);
toggleContent.appendChild(section);
return section;
}
function upsertOptionalRow(section, id, label, value) {
@@ -658,10 +906,14 @@ function upsertOptionalRow(section, id, label, value) {
row = document.createElement('div');
row.className = 'settingRow';
row.id = id;
row.dataset.sensorRow = id.startsWith('sensor_') ? '1' : '0';
row.innerHTML = `<span></span><span></span>`;
section.appendChild(row);
}
if (id.startsWith('sensor_')) {
row.dataset.sensorKey = id.split('_').slice(2).join('_');
}
row.children[0].textContent = label;
row.children[1].textContent = displayValue;
}
@@ -680,29 +932,7 @@ function normalizeDisplayValue(value) {
return String(value);
}
function formatWithUnit(value, unit) {
const normalized = normalizeDisplayValue(value);
if (!normalized) return '';
return /[a-zA-Z%°]/.test(normalized) ? normalized : `${normalized}${unit}`;
}
function formatDuration(value) {
const normalized = normalizeDisplayValue(value);
if (!normalized) return '';
const seconds = Number(normalized);
if (!Number.isFinite(seconds)) return normalized;
if (seconds <= 0) return '0s';
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
return [
h ? `${h}h` : '',
m ? `${m}m` : '',
s && !h ? `${s}s` : '',
].filter(Boolean).join(' ');
}
function updateActivitySection(data) {
function updateActivitySection(data) {
const validActivities = ["Stationary", "Walking", "Running", "In Vehicle", "Cycling", "가만히 있음", "걷는 중", "달리는 중", "차량 이동 중", "자전거 이동 중"];
const validAccuracies = ["높음", "중간", "낮음", "High", "Middle", "Low"];
@@ -1157,7 +1387,7 @@ function resetDisplayValues() {
const el = document.getElementById(id);
if (el) el.textContent = '-';
});
document.querySelectorAll('[id^="galaxy_"]').forEach(row => row.remove());
document.querySelectorAll('[data-sensor-row="1"], [data-sensor-title="1"], [id^="sensorSection_"]').forEach(el => el.remove());
}
function triggerForceUpdate() {