palmier 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +28 -13
  2. package/dist/agents/agent.d.ts +0 -1
  3. package/dist/agents/agent.js +0 -1
  4. package/dist/agents/aider.d.ts +0 -1
  5. package/dist/agents/aider.js +0 -1
  6. package/dist/agents/claude.d.ts +0 -1
  7. package/dist/agents/claude.js +0 -1
  8. package/dist/agents/cline.d.ts +0 -1
  9. package/dist/agents/cline.js +0 -1
  10. package/dist/agents/codex.d.ts +0 -1
  11. package/dist/agents/codex.js +0 -1
  12. package/dist/agents/copilot.d.ts +0 -1
  13. package/dist/agents/copilot.js +0 -1
  14. package/dist/agents/cursor.d.ts +0 -1
  15. package/dist/agents/cursor.js +0 -1
  16. package/dist/agents/deepagents.d.ts +0 -1
  17. package/dist/agents/deepagents.js +0 -1
  18. package/dist/agents/droid.d.ts +0 -1
  19. package/dist/agents/droid.js +0 -1
  20. package/dist/agents/gemini.d.ts +0 -1
  21. package/dist/agents/gemini.js +0 -1
  22. package/dist/agents/goose.d.ts +0 -1
  23. package/dist/agents/goose.js +0 -1
  24. package/dist/agents/hermes.d.ts +0 -1
  25. package/dist/agents/hermes.js +0 -1
  26. package/dist/agents/kimi.d.ts +0 -1
  27. package/dist/agents/kimi.js +0 -1
  28. package/dist/agents/kiro.d.ts +0 -1
  29. package/dist/agents/kiro.js +0 -1
  30. package/dist/agents/openclaw.d.ts +0 -1
  31. package/dist/agents/openclaw.js +0 -1
  32. package/dist/agents/opencode.d.ts +0 -1
  33. package/dist/agents/opencode.js +0 -1
  34. package/dist/agents/qoder.d.ts +0 -1
  35. package/dist/agents/qoder.js +0 -1
  36. package/dist/agents/qwen.d.ts +0 -1
  37. package/dist/agents/qwen.js +0 -1
  38. package/dist/agents/shared-prompt.d.ts +0 -1
  39. package/dist/agents/shared-prompt.js +0 -1
  40. package/dist/client-store.d.ts +0 -1
  41. package/dist/client-store.js +0 -1
  42. package/dist/commands/clients.d.ts +0 -1
  43. package/dist/commands/clients.js +0 -1
  44. package/dist/commands/info.d.ts +0 -1
  45. package/dist/commands/info.js +0 -1
  46. package/dist/commands/init.d.ts +0 -1
  47. package/dist/commands/init.js +1 -2
  48. package/dist/commands/pair.d.ts +0 -1
  49. package/dist/commands/pair.js +0 -1
  50. package/dist/commands/restart.d.ts +0 -1
  51. package/dist/commands/restart.js +0 -1
  52. package/dist/commands/run.d.ts +0 -1
  53. package/dist/commands/run.js +19 -3
  54. package/dist/commands/serve.d.ts +0 -1
  55. package/dist/commands/serve.js +0 -1
  56. package/dist/commands/uninstall.d.ts +0 -1
  57. package/dist/commands/uninstall.js +0 -1
  58. package/dist/config.d.ts +0 -1
  59. package/dist/config.js +0 -1
  60. package/dist/event-queues.d.ts +0 -1
  61. package/dist/event-queues.js +0 -1
  62. package/dist/events.d.ts +0 -1
  63. package/dist/events.js +0 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.js +0 -1
  66. package/dist/linked-device.d.ts +0 -1
  67. package/dist/linked-device.js +0 -1
  68. package/dist/mcp-handler.d.ts +0 -1
  69. package/dist/mcp-handler.js +0 -1
  70. package/dist/mcp-tools.d.ts +0 -1
  71. package/dist/mcp-tools.js +0 -1
  72. package/dist/nats-client.d.ts +0 -1
  73. package/dist/nats-client.js +0 -1
  74. package/dist/network.d.ts +0 -1
  75. package/dist/network.js +0 -1
  76. package/dist/notification-store.d.ts +0 -1
  77. package/dist/notification-store.js +0 -1
  78. package/dist/pending-requests.d.ts +0 -1
  79. package/dist/pending-requests.js +0 -1
  80. package/dist/platform/index.d.ts +0 -1
  81. package/dist/platform/index.js +0 -1
  82. package/dist/platform/linux.d.ts +0 -1
  83. package/dist/platform/linux.js +0 -1
  84. package/dist/platform/macos.d.ts +0 -1
  85. package/dist/platform/macos.js +0 -1
  86. package/dist/platform/platform.d.ts +0 -1
  87. package/dist/platform/platform.js +0 -1
  88. package/dist/platform/windows.d.ts +0 -1
  89. package/dist/platform/windows.js +0 -1
  90. package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
  91. package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
  92. package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
  93. package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
  94. package/dist/pwa/index.html +2 -2
  95. package/dist/rpc-handler.d.ts +0 -1
  96. package/dist/rpc-handler.js +0 -1
  97. package/dist/sms-store.d.ts +0 -1
  98. package/dist/sms-store.js +0 -1
  99. package/dist/spawn-command.d.ts +0 -1
  100. package/dist/spawn-command.js +0 -1
  101. package/dist/task.d.ts +0 -1
  102. package/dist/task.js +0 -1
  103. package/dist/transports/http-transport.d.ts +0 -1
  104. package/dist/transports/http-transport.js +0 -1
  105. package/dist/transports/nats-transport.d.ts +0 -1
  106. package/dist/transports/nats-transport.js +0 -1
  107. package/dist/types.d.ts +0 -1
  108. package/dist/types.js +0 -1
  109. package/dist/update-checker.d.ts +0 -1
  110. package/dist/update-checker.js +0 -1
  111. package/package.json +11 -1
  112. package/.github/workflows/ci.yml +0 -16
  113. package/.github/workflows/publish.yml +0 -37
  114. package/CLAUDE.md +0 -22
  115. package/dist/pwa/apple-touch-icon.png +0 -0
  116. package/dist/pwa/manifest.webmanifest +0 -1
  117. package/dist/pwa/pwa-192x192.png +0 -0
  118. package/dist/pwa/pwa-512x512.png +0 -0
  119. package/dist/pwa/registerSW.js +0 -1
  120. package/dist/pwa/service-worker.js +0 -2
  121. package/palmier-server/.github/workflows/ci.yml +0 -21
  122. package/palmier-server/.github/workflows/deploy.yml +0 -38
  123. package/palmier-server/CLAUDE.md +0 -17
  124. package/palmier-server/PRODUCTION.md +0 -358
  125. package/palmier-server/README.md +0 -231
  126. package/palmier-server/nats.conf +0 -19
  127. package/palmier-server/package.json +0 -15
  128. package/palmier-server/pnpm-lock.yaml +0 -7639
  129. package/palmier-server/pnpm-workspace.yaml +0 -3
  130. package/palmier-server/pwa/index.html +0 -16
  131. package/palmier-server/pwa/logo/logo_20260421.png +0 -0
  132. package/palmier-server/pwa/package.json +0 -34
  133. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  134. package/palmier-server/pwa/public/favicon.ico +0 -0
  135. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  136. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  137. package/palmier-server/pwa/src/App.css +0 -3012
  138. package/palmier-server/pwa/src/App.tsx +0 -59
  139. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  140. package/palmier-server/pwa/src/api.ts +0 -67
  141. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  142. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
  143. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  144. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  145. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  146. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  147. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  148. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  149. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  150. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  151. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  152. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
  153. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  154. package/palmier-server/pwa/src/constants.ts +0 -2
  155. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  156. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  157. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  158. package/palmier-server/pwa/src/formatTime.ts +0 -44
  159. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  160. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  161. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  162. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  163. package/palmier-server/pwa/src/main.tsx +0 -14
  164. package/palmier-server/pwa/src/native/Device.ts +0 -49
  165. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  166. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  167. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  168. package/palmier-server/pwa/src/service-worker.ts +0 -142
  169. package/palmier-server/pwa/src/types.ts +0 -75
  170. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  171. package/palmier-server/pwa/tsconfig.json +0 -21
  172. package/palmier-server/pwa/tsconfig.node.json +0 -19
  173. package/palmier-server/pwa/vite.config.ts +0 -47
  174. package/palmier-server/server/.env.example +0 -20
  175. package/palmier-server/server/package.json +0 -36
  176. package/palmier-server/server/src/db.ts +0 -44
  177. package/palmier-server/server/src/fcm.ts +0 -74
  178. package/palmier-server/server/src/index.ts +0 -688
  179. package/palmier-server/server/src/nats-jwt.ts +0 -299
  180. package/palmier-server/server/src/nats-setup.ts +0 -48
  181. package/palmier-server/server/src/nats.ts +0 -33
  182. package/palmier-server/server/src/notify.ts +0 -34
  183. package/palmier-server/server/src/push.ts +0 -68
  184. package/palmier-server/server/src/routes/device.ts +0 -224
  185. package/palmier-server/server/src/routes/fcm.ts +0 -64
  186. package/palmier-server/server/src/routes/hosts.ts +0 -56
  187. package/palmier-server/server/src/routes/push.ts +0 -101
  188. package/palmier-server/server/tsconfig.json +0 -20
  189. package/palmier-server/spec.md +0 -533
  190. package/src/agents/agent-instructions.md +0 -28
  191. package/src/agents/agent.ts +0 -114
  192. package/src/agents/aider.ts +0 -35
  193. package/src/agents/claude.ts +0 -39
  194. package/src/agents/cline.ts +0 -35
  195. package/src/agents/codex.ts +0 -40
  196. package/src/agents/copilot.ts +0 -37
  197. package/src/agents/cursor.ts +0 -36
  198. package/src/agents/deepagents.ts +0 -36
  199. package/src/agents/droid.ts +0 -35
  200. package/src/agents/gemini.ts +0 -43
  201. package/src/agents/goose.ts +0 -33
  202. package/src/agents/hermes.ts +0 -36
  203. package/src/agents/kimi.ts +0 -35
  204. package/src/agents/kiro.ts +0 -36
  205. package/src/agents/openclaw.ts +0 -29
  206. package/src/agents/opencode.ts +0 -36
  207. package/src/agents/qoder.ts +0 -36
  208. package/src/agents/qwen.ts +0 -32
  209. package/src/agents/shared-prompt.ts +0 -30
  210. package/src/client-store.ts +0 -68
  211. package/src/commands/clients.ts +0 -29
  212. package/src/commands/info.ts +0 -29
  213. package/src/commands/init.ts +0 -165
  214. package/src/commands/pair.ts +0 -137
  215. package/src/commands/restart.ts +0 -6
  216. package/src/commands/run.ts +0 -608
  217. package/src/commands/serve.ts +0 -211
  218. package/src/commands/uninstall.ts +0 -9
  219. package/src/config.ts +0 -36
  220. package/src/cross-spawn.d.ts +0 -5
  221. package/src/event-queues.ts +0 -41
  222. package/src/events.ts +0 -29
  223. package/src/index.ts +0 -111
  224. package/src/linked-device.ts +0 -52
  225. package/src/mcp-handler.ts +0 -200
  226. package/src/mcp-tools.ts +0 -839
  227. package/src/nats-client.ts +0 -19
  228. package/src/network.ts +0 -96
  229. package/src/notification-store.ts +0 -30
  230. package/src/pending-requests.ts +0 -73
  231. package/src/platform/index.ts +0 -20
  232. package/src/platform/linux.ts +0 -296
  233. package/src/platform/macos.ts +0 -329
  234. package/src/platform/platform.ts +0 -31
  235. package/src/platform/windows.ts +0 -299
  236. package/src/rpc-handler.ts +0 -691
  237. package/src/sms-store.ts +0 -28
  238. package/src/spawn-command.ts +0 -123
  239. package/src/task.ts +0 -343
  240. package/src/transports/http-transport.ts +0 -478
  241. package/src/transports/nats-transport.ts +0 -76
  242. package/src/types.ts +0 -89
  243. package/src/update-checker.ts +0 -40
  244. package/test/agent-instructions.test.ts +0 -209
  245. package/test/agent-output-parsing.test.ts +0 -74
  246. package/test/linux-cron.test.ts +0 -41
  247. package/test/macos-plist.test.ts +0 -112
  248. package/test/notification-store.test.ts +0 -57
  249. package/test/pairing.test.ts +0 -35
  250. package/test/result-state.test.ts +0 -110
  251. package/test/task-parsing.test.ts +0 -82
  252. package/test/taskrun-messages.test.ts +0 -224
  253. package/test/tsconfig.json +0 -9
  254. package/test/windows-xml.test.ts +0 -89
  255. package/tsconfig.json +0 -19
@@ -1,59 +0,0 @@
1
- import { useEffect } from "react";
2
- import { Routes, Route, useNavigate, useParams, Navigate } from "react-router-dom";
3
- import { HostStoreProvider, useHostStore } from "./contexts/HostStoreContext";
4
- import { HostConnectionProvider } from "./contexts/HostConnectionContext";
5
- import { Device } from "./native/Device";
6
- import Dashboard from "./pages/Dashboard";
7
- import PairHost from "./pages/PairHost";
8
- import PairSetup from "./pages/PairSetup";
9
-
10
- function DeepLinkRouter() {
11
- const navigate = useNavigate();
12
- useEffect(() => {
13
- if (!Device) return;
14
- const handle = Device.addListener("deepLink", ({ path }) => navigate(path));
15
- return () => { handle.then((h) => h.remove()); };
16
- }, [navigate]);
17
- return null;
18
- }
19
-
20
- function RootRedirect() {
21
- const { pairedHosts } = useHostStore();
22
- if (pairedHosts.length === 0) return <Navigate to="/pair" replace />;
23
- return <Navigate to={`/hosts/${encodeURIComponent(pairedHosts[0].hostId)}`} replace />;
24
- }
25
-
26
- function HostScope() {
27
- const { hostId } = useParams<{ hostId: string }>();
28
- const { pairedHosts } = useHostStore();
29
- const host = pairedHosts.find((h) => h.hostId === hostId) ?? null;
30
-
31
- if (!host) return <Navigate to="/" replace />;
32
-
33
- return (
34
- <HostConnectionProvider activeHost={host}>
35
- <Routes>
36
- <Route index element={<Dashboard />} />
37
- <Route path="tasks" element={<Dashboard />} />
38
- <Route path="runs/:taskId" element={<Dashboard />} />
39
- <Route path="runs/:taskId/:runId" element={<Dashboard />} />
40
- <Route path="pair/setup" element={<PairSetup />} />
41
- <Route path="*" element={<Navigate to="." replace />} />
42
- </Routes>
43
- </HostConnectionProvider>
44
- );
45
- }
46
-
47
- export default function App() {
48
- return (
49
- <HostStoreProvider>
50
- <DeepLinkRouter />
51
- <Routes>
52
- <Route path="/" element={<RootRedirect />} />
53
- <Route path="/pair" element={<PairHost />} />
54
- <Route path="/hosts/:hostId/*" element={<HostScope />} />
55
- <Route path="*" element={<Navigate to="/" replace />} />
56
- </Routes>
57
- </HostStoreProvider>
58
- );
59
- }
@@ -1,11 +0,0 @@
1
- import type { AgentInfo } from "./types";
2
-
3
- const labels: Record<string, string> = {};
4
-
5
- export function setAgentLabels(agents: AgentInfo[]): void {
6
- for (const a of agents) labels[a.key] = a.label;
7
- }
8
-
9
- export function getAgentLabel(key: string): string {
10
- return labels[key] ?? key;
11
- }
@@ -1,67 +0,0 @@
1
- import { Capacitor } from "@capacitor/core";
2
-
3
- /** On native platforms, API calls go to the production server. On web, they're relative (same-origin). */
4
- export const SERVER_URL = Capacitor.isNativePlatform() ? "https://app.palmier.me" : "";
5
-
6
- async function request<T>(
7
- method: string,
8
- path: string,
9
- body?: unknown,
10
- token?: string
11
- ): Promise<T> {
12
- const url = `${SERVER_URL}${path}`;
13
- console.log(`[API] ${method} ${url}`);
14
- const headers: Record<string, string> = {
15
- "Content-Type": "application/json",
16
- };
17
- if (token) {
18
- headers["Authorization"] = `Bearer ${token}`;
19
- }
20
- const res = await fetch(url, {
21
- method,
22
- headers,
23
- body: body != null ? JSON.stringify(body) : undefined,
24
- });
25
- if (!res.ok) {
26
- const text = await res.text();
27
- let message: string;
28
- try {
29
- const json = JSON.parse(text);
30
- message = json.error ?? json.message ?? text;
31
- } catch {
32
- message = text;
33
- }
34
- console.error(`[API] ${method} ${path} failed:`, res.status, message);
35
- throw new Error(message || `Request failed with status ${res.status}`);
36
- }
37
- console.log(`[API] ${method} ${path} ->`, res.status);
38
- return res.json() as Promise<T>;
39
- }
40
-
41
- export function apiPost<T = unknown>(
42
- path: string,
43
- body: unknown,
44
- token?: string
45
- ): Promise<T> {
46
- return request<T>("POST", path, body, token);
47
- }
48
-
49
- export function apiGet<T = unknown>(path: string, token?: string): Promise<T> {
50
- return request<T>("GET", path, undefined, token);
51
- }
52
-
53
- export function apiPatch<T = unknown>(
54
- path: string,
55
- body: unknown,
56
- token?: string
57
- ): Promise<T> {
58
- return request<T>("PATCH", path, body, token);
59
- }
60
-
61
- export function apiDelete<T = unknown>(
62
- path: string,
63
- body?: unknown,
64
- token?: string
65
- ): Promise<T> {
66
- return request<T>("DELETE", path, body, token);
67
- }
@@ -1,170 +0,0 @@
1
- import { useState, useEffect, useCallback } from "react";
2
- import { createPortal } from "react-dom";
3
- import { Capacitor } from "@capacitor/core";
4
- import { App as CapacitorApp } from "@capacitor/app";
5
- import { Device, type CapabilityStatus } from "../native/Device";
6
-
7
- const isNative = Capacitor.isNativePlatform();
8
-
9
- type CapabilityGroup = "Messaging" | "Data" | "Device";
10
-
11
- const CAPABILITY_GROUPS: CapabilityGroup[] = ["Device", "Data", "Messaging"];
12
-
13
- interface CapabilityDefinition {
14
- capability: string;
15
- label: string;
16
- group: CapabilityGroup;
17
- }
18
-
19
- const CAPABILITIES: CapabilityDefinition[] = [
20
- { capability: "sms-read", label: "Read SMS", group: "Messaging" },
21
- { capability: "sms-send", label: "Send SMS", group: "Messaging" },
22
- { capability: "send-email", label: "Prompt Email to Send", group: "Messaging" },
23
- { capability: "notifications", label: "Notifications from Other Apps", group: "Data" },
24
- { capability: "contacts", label: "Manage Contacts", group: "Data" },
25
- { capability: "calendar", label: "Manage Calendar", group: "Data" },
26
- { capability: "location", label: "Get Location", group: "Device" },
27
- { capability: "dnd", label: "Set Ringer Mode", group: "Device" },
28
- { capability: "alarm", label: "Trigger Alarms", group: "Device" },
29
- ];
30
-
31
- export async function loadEnabledCapabilities(): Promise<Set<string>> {
32
- if (!isNative || !Device) return new Set();
33
- try {
34
- const { capabilities } = await Device.getCapabilityStatus();
35
- return new Set(capabilities.filter((c) => c.enabled).map((c) => c.name));
36
- } catch {
37
- return new Set();
38
- }
39
- }
40
-
41
- interface CapabilityTogglesProps {
42
- onChange?(enabled: Set<string>): void;
43
- /** When true, disabling a currently-on capability prompts a confirmation
44
- * mentioning that all linked hosts will lose it. */
45
- confirmDisable?: boolean;
46
- }
47
-
48
- export default function CapabilityToggles({ onChange, confirmDisable = false }: CapabilityTogglesProps) {
49
- const [statuses, setStatuses] = useState<Map<string, CapabilityStatus>>(new Map());
50
- const [busyCapability, setBusyCapability] = useState<string | null>(null);
51
- const [confirmingDisable, setConfirmingDisable] = useState<CapabilityDefinition | null>(null);
52
-
53
- const refresh = useCallback(async () => {
54
- if (!isNative || !Device) return;
55
- try {
56
- const { capabilities } = await Device.getCapabilityStatus();
57
- const next = new Map<string, CapabilityStatus>();
58
- for (const c of capabilities) next.set(c.name, c);
59
- setStatuses(next);
60
- onChange?.(new Set(capabilities.filter((c) => c.enabled).map((c) => c.name)));
61
- } catch (err) {
62
- console.error("Failed to read capability status:", err);
63
- }
64
- }, [onChange]);
65
-
66
- useEffect(() => { refresh(); }, [refresh]);
67
-
68
- useEffect(() => {
69
- if (!isNative) return;
70
- const listener = CapacitorApp.addListener("resume", refresh);
71
- return () => { listener.then((h) => h.remove()); };
72
- }, [refresh]);
73
-
74
- async function applyToggle(definition: CapabilityDefinition, enabled: boolean) {
75
- if (!Device) return;
76
- setBusyCapability(definition.capability);
77
- try {
78
- const result = await Device.setCapabilityEnabled({ capability: definition.capability, enabled });
79
- if (!result.enabled && result.reason === "no-email-client") {
80
- alert("No email app is installed on this device. Install one (e.g. Gmail) before enabling Send Email.");
81
- }
82
- await refresh();
83
- } catch (err) {
84
- console.error(`Failed to toggle ${definition.capability}:`, err);
85
- } finally {
86
- setBusyCapability(null);
87
- }
88
- }
89
-
90
- function toggleCapability(definition: CapabilityDefinition) {
91
- const status = statuses.get(definition.capability);
92
- if (!status) return;
93
- if (status.enabled && confirmDisable) {
94
- setConfirmingDisable(definition);
95
- return;
96
- }
97
- applyToggle(definition, !status.enabled);
98
- }
99
-
100
- function confirmAndDisable() {
101
- const definition = confirmingDisable;
102
- if (!definition) return;
103
- setConfirmingDisable(null);
104
- applyToggle(definition, false);
105
- }
106
-
107
- if (!isNative) return null;
108
-
109
- const visibleGroups = CAPABILITY_GROUPS
110
- .map((group) => ({
111
- group,
112
- items: CAPABILITIES.filter((d) => {
113
- const s = statuses.get(d.capability);
114
- return d.group === group && s?.supported;
115
- }),
116
- }))
117
- .filter((g) => g.items.length > 0);
118
-
119
- if (visibleGroups.length === 0) {
120
- return (
121
- <div className="capability-toggles-loading">
122
- <span className="spinner" />
123
- </div>
124
- );
125
- }
126
-
127
- const disableModal = confirmingDisable && createPortal(
128
- <div className="confirm-modal-overlay" onClick={() => setConfirmingDisable(null)}>
129
- <div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
130
- <h2 className="confirm-modal-title">Disable {confirmingDisable.label}?</h2>
131
- <p className="confirm-modal-message">
132
- All hosts linked to this device will no longer be able to use {confirmingDisable.label}.
133
- </p>
134
- <div className="confirm-modal-actions">
135
- <button className="btn btn-secondary" onClick={() => setConfirmingDisable(null)}>Cancel</button>
136
- <button className="btn btn-danger" onClick={confirmAndDisable}>Disable</button>
137
- </div>
138
- </div>
139
- </div>,
140
- document.body,
141
- );
142
-
143
- return (
144
- <>
145
- {visibleGroups.map(({ group, items }, index) => (
146
- <div key={group} className={index > 0 ? "drawer-toggle-group drawer-toggle-group-divided" : "drawer-toggle-group"}>
147
- {items.map((definition) => {
148
- const status = statuses.get(definition.capability);
149
- const on = !!status?.enabled;
150
- return (
151
- <label key={definition.capability} className="drawer-toggle">
152
- <span className="drawer-toggle-label">{definition.label}</span>
153
- <button
154
- className={`toggle-switch ${on ? "toggle-switch-on" : ""}`}
155
- onClick={() => toggleCapability(definition)}
156
- disabled={busyCapability === definition.capability}
157
- role="switch"
158
- aria-checked={on}
159
- >
160
- <span className="toggle-switch-thumb" />
161
- </button>
162
- </label>
163
- );
164
- })}
165
- </div>
166
- ))}
167
- {disableModal}
168
- </>
169
- );
170
- }
@@ -1,113 +0,0 @@
1
- import { useState, useRef, useEffect } from "react";
2
- import { Capacitor } from "@capacitor/core";
3
- import { useHostConnection } from "../contexts/HostConnectionContext";
4
-
5
- const isNative = Capacitor.isNativePlatform();
6
-
7
- const SVG_PROPS = {
8
- width: 16,
9
- height: 16,
10
- viewBox: "0 0 24 24",
11
- fill: "none",
12
- stroke: "currentColor",
13
- strokeWidth: 2,
14
- strokeLinecap: "round" as const,
15
- strokeLinejoin: "round" as const,
16
- };
17
-
18
- function LinkedDevicesIcon() {
19
- return (
20
- <svg {...SVG_PROPS} aria-hidden="true">
21
- <rect x="3" y="6" width="5" height="12" rx="1" />
22
- <rect x="16" y="6" width="5" height="12" rx="1" />
23
- <line x1="8" y1="12" x2="16" y2="12" />
24
- </svg>
25
- );
26
- }
27
-
28
- function GlobeIcon() {
29
- return (
30
- <svg {...SVG_PROPS} aria-hidden="true">
31
- <circle cx="12" cy="12" r="10" />
32
- <line x1="2" y1="12" x2="22" y2="12" />
33
- <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
34
- </svg>
35
- );
36
- }
37
-
38
- function WarningIcon() {
39
- return (
40
- <svg {...SVG_PROPS} aria-hidden="true">
41
- <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
42
- <line x1="12" y1="9" x2="12" y2="13" />
43
- <line x1="12" y1="17" x2="12.01" y2="17" />
44
- </svg>
45
- );
46
- }
47
-
48
- export default function ConnectionStatusIcon() {
49
- const { mode } = useHostConnection();
50
- const [popoverOpen, setPopoverOpen] = useState(false);
51
- const containerRef = useRef<HTMLDivElement>(null);
52
-
53
- useEffect(() => {
54
- if (!popoverOpen) return;
55
- function onPointerDown(e: PointerEvent) {
56
- if (!containerRef.current?.contains(e.target as Node)) setPopoverOpen(false);
57
- }
58
- document.addEventListener("pointerdown", onPointerDown);
59
- const timer = window.setTimeout(() => setPopoverOpen(false), 3000);
60
- return () => {
61
- document.removeEventListener("pointerdown", onPointerDown);
62
- clearTimeout(timer);
63
- };
64
- }, [popoverOpen]);
65
-
66
- if (mode === "direct") return null;
67
-
68
- let icon: React.ReactNode;
69
- let label: string;
70
- let modifier: string;
71
- switch (mode) {
72
- case "lan":
73
- icon = <LinkedDevicesIcon />;
74
- label = "Connected via LAN";
75
- modifier = "lan";
76
- break;
77
- case "nats":
78
- icon = <GlobeIcon />;
79
- label = isNative ? "Connected via relay" : "Connected";
80
- modifier = "relay";
81
- break;
82
- case "disconnected":
83
- icon = <WarningIcon />;
84
- label = "Disconnected";
85
- modifier = "disconnected";
86
- break;
87
- case "connecting":
88
- default:
89
- icon = <GlobeIcon />;
90
- label = "Connecting\u2026";
91
- modifier = "connecting";
92
- break;
93
- }
94
-
95
- return (
96
- <div ref={containerRef} className={`conn-status conn-status--${modifier}`}>
97
- <button
98
- type="button"
99
- className="conn-status-btn"
100
- aria-label={label}
101
- onClick={() => setPopoverOpen((v) => !v)}
102
- >
103
- {icon}
104
- </button>
105
- <div
106
- className={`conn-status-popover ${popoverOpen ? "conn-status-popover--open" : ""}`}
107
- role="tooltip"
108
- >
109
- {label}
110
- </div>
111
- </div>
112
- );
113
- }