|
4 | 4 | // Prerequisites: node-sonos-http-api running on port 5005 |
5 | 5 | // https://github.com/jishi/node-sonos-http-api |
6 | 6 |
|
7 | | -import { css } from "uebersicht"; |
| 7 | +import { css, run } from "uebersicht"; |
8 | 8 |
|
9 | 9 | // ============ CONFIGURATION ============ |
10 | 10 | const SONOS_API_URL = "http://localhost:5005"; |
11 | 11 | // ======================================= |
12 | 12 |
|
13 | 13 | export const command = ` |
14 | | - echo '{"zones":' && curl -s "${SONOS_API_URL}/zones" 2>/dev/null && \ |
15 | | - echo ',"favorites":' && curl -s "${SONOS_API_URL}/favorites/detailed" 2>/dev/null && \ |
16 | | - echo ',"playlists":' && curl -s "${SONOS_API_URL}/playlists" 2>/dev/null && \ |
17 | | - echo '}' |
| 14 | + zones=$(curl -s --connect-timeout 1 "${SONOS_API_URL}/zones" 2>/dev/null) |
| 15 | + curlrc=$? |
| 16 | + if [ $curlrc -ne 0 ]; then |
| 17 | + echo '{"error":"offline"}' |
| 18 | + elif [ -z "$zones" ] || [ "$zones" = "[]" ]; then |
| 19 | + echo '{"error":"no-speakers"}' |
| 20 | + else |
| 21 | + favs=$(curl -s --connect-timeout 2 "${SONOS_API_URL}/favorites/detailed" 2>/dev/null || echo '[]') |
| 22 | + lists=$(curl -s --connect-timeout 2 "${SONOS_API_URL}/playlists" 2>/dev/null || echo '[]') |
| 23 | + echo "{\\"zones\\":$zones,\\"favorites\\":$favs,\\"playlists\\":$lists}" |
| 24 | + fi |
18 | 25 | `; |
19 | 26 |
|
20 | 27 | export const refreshFrequency = 3000; |
@@ -1078,29 +1085,85 @@ function ZoneControl({ zone, allZones, favorites, expanded, onToggleExpand, onOp |
1078 | 1085 |
|
1079 | 1086 | let expandedZones = new Set(); |
1080 | 1087 |
|
| 1088 | +const connectingStyle = { |
| 1089 | + background: "rgba(0, 0, 0, 0.80)", |
| 1090 | + backdropFilter: "blur(24px)", |
| 1091 | + WebkitBackdropFilter: "blur(24px)", |
| 1092 | + borderRadius: "16px", |
| 1093 | + padding: "16px 20px", |
| 1094 | + fontSize: "12px", |
| 1095 | + color: "rgba(255, 255, 255, 0.8)", |
| 1096 | + textAlign: "center", |
| 1097 | + position: "absolute", |
| 1098 | + top: "20px", |
| 1099 | + right: "20px", |
| 1100 | + pointerEvents: "auto", |
| 1101 | + maxWidth: "200px", |
| 1102 | +}; |
| 1103 | + |
| 1104 | +const startServer = () => { |
| 1105 | + run(`open ~/Desktop/Start\\ Sonos\\ Server.command`); |
| 1106 | +}; |
| 1107 | + |
1081 | 1108 | export const render = ({ output, error }) => { |
1082 | | - if (error || !output) { |
| 1109 | + if (error || !output || output.trim() === "") { |
1083 | 1110 | return ( |
1084 | | - <div style={errorStyle}> |
1085 | | - Cannot reach Sonos API<br/> |
1086 | | - <span style={{fontSize: "10px", opacity: 0.7}}> |
1087 | | - Ensure node-sonos-http-api is running on port 5005 |
| 1111 | + <div |
| 1112 | + style={{...connectingStyle, cursor: "pointer"}} |
| 1113 | + onClick={startServer} |
| 1114 | + title="Click to start server" |
| 1115 | + > |
| 1116 | + 🔊 Sonos Offline<br/> |
| 1117 | + <span style={{fontSize: "10px", opacity: 0.6, lineHeight: 1.4, display: "block", marginTop: "8px"}}> |
| 1118 | + Click to start |
1088 | 1119 | </span> |
1089 | 1120 | </div> |
1090 | 1121 | ); |
1091 | 1122 | } |
1092 | 1123 |
|
1093 | 1124 | try { |
1094 | | - const cleanOutput = output.replace(/\n/g, ''); |
| 1125 | + const cleanOutput = output.replace(/\n/g, '').trim(); |
1095 | 1126 | const data = JSON.parse(cleanOutput); |
| 1127 | + |
| 1128 | + // Handle offline state - server not running |
| 1129 | + if (data.error === "offline") { |
| 1130 | + return ( |
| 1131 | + <div |
| 1132 | + style={{...connectingStyle, cursor: "pointer"}} |
| 1133 | + onClick={startServer} |
| 1134 | + title="Click to start server" |
| 1135 | + > |
| 1136 | + 🔊 Sonos Offline<br/> |
| 1137 | + <span style={{fontSize: "10px", opacity: 0.6, lineHeight: 1.4, display: "block", marginTop: "8px"}}> |
| 1138 | + Click to start |
| 1139 | + </span> |
| 1140 | + </div> |
| 1141 | + ); |
| 1142 | + } |
| 1143 | + |
| 1144 | + // Handle no speakers state |
| 1145 | + if (data.error === "no-speakers") { |
| 1146 | + return ( |
| 1147 | + <div style={connectingStyle}> |
| 1148 | + 🔊 Sonos<br/> |
| 1149 | + <span style={{fontSize: "10px", opacity: 0.6, lineHeight: 1.4, display: "block", marginTop: "6px"}}> |
| 1150 | + No speakers found |
| 1151 | + </span> |
| 1152 | + </div> |
| 1153 | + ); |
| 1154 | + } |
| 1155 | + |
1096 | 1156 | const zones = data.zones || []; |
1097 | 1157 | const favorites = data.favorites || []; |
1098 | 1158 | const playlists = data.playlists || []; |
1099 | 1159 |
|
1100 | 1160 | if (!zones || zones.length === 0) { |
1101 | 1161 | return ( |
1102 | | - <div style={errorStyle}> |
1103 | | - No Sonos speakers found |
| 1162 | + <div style={connectingStyle}> |
| 1163 | + 🔊 Sonos<br/> |
| 1164 | + <span style={{fontSize: "10px", opacity: 0.6, lineHeight: 1.4, display: "block", marginTop: "6px"}}> |
| 1165 | + No speakers found |
| 1166 | + </span> |
1104 | 1167 | </div> |
1105 | 1168 | ); |
1106 | 1169 | } |
@@ -1275,9 +1338,11 @@ export const render = ({ output, error }) => { |
1275 | 1338 | ); |
1276 | 1339 | } catch (e) { |
1277 | 1340 | return ( |
1278 | | - <div style={errorStyle}> |
1279 | | - Error: {e.message}<br/> |
1280 | | - <span style={{fontSize: "9px", opacity: 0.5}}>{output?.substring(0, 100)}</span> |
| 1341 | + <div style={connectingStyle}> |
| 1342 | + 🔊 Sonos<br/> |
| 1343 | + <span style={{fontSize: "10px", opacity: 0.6, lineHeight: 1.4, display: "block", marginTop: "6px"}}> |
| 1344 | + Starting up... |
| 1345 | + </span> |
1281 | 1346 | </div> |
1282 | 1347 | ); |
1283 | 1348 | } |
|
0 commit comments