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.
- package/README.md +28 -13
- package/dist/agents/agent.d.ts +0 -1
- package/dist/agents/agent.js +0 -1
- package/dist/agents/aider.d.ts +0 -1
- package/dist/agents/aider.js +0 -1
- package/dist/agents/claude.d.ts +0 -1
- package/dist/agents/claude.js +0 -1
- package/dist/agents/cline.d.ts +0 -1
- package/dist/agents/cline.js +0 -1
- package/dist/agents/codex.d.ts +0 -1
- package/dist/agents/codex.js +0 -1
- package/dist/agents/copilot.d.ts +0 -1
- package/dist/agents/copilot.js +0 -1
- package/dist/agents/cursor.d.ts +0 -1
- package/dist/agents/cursor.js +0 -1
- package/dist/agents/deepagents.d.ts +0 -1
- package/dist/agents/deepagents.js +0 -1
- package/dist/agents/droid.d.ts +0 -1
- package/dist/agents/droid.js +0 -1
- package/dist/agents/gemini.d.ts +0 -1
- package/dist/agents/gemini.js +0 -1
- package/dist/agents/goose.d.ts +0 -1
- package/dist/agents/goose.js +0 -1
- package/dist/agents/hermes.d.ts +0 -1
- package/dist/agents/hermes.js +0 -1
- package/dist/agents/kimi.d.ts +0 -1
- package/dist/agents/kimi.js +0 -1
- package/dist/agents/kiro.d.ts +0 -1
- package/dist/agents/kiro.js +0 -1
- package/dist/agents/openclaw.d.ts +0 -1
- package/dist/agents/openclaw.js +0 -1
- package/dist/agents/opencode.d.ts +0 -1
- package/dist/agents/opencode.js +0 -1
- package/dist/agents/qoder.d.ts +0 -1
- package/dist/agents/qoder.js +0 -1
- package/dist/agents/qwen.d.ts +0 -1
- package/dist/agents/qwen.js +0 -1
- package/dist/agents/shared-prompt.d.ts +0 -1
- package/dist/agents/shared-prompt.js +0 -1
- package/dist/client-store.d.ts +0 -1
- package/dist/client-store.js +0 -1
- package/dist/commands/clients.d.ts +0 -1
- package/dist/commands/clients.js +0 -1
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +0 -1
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/pair.d.ts +0 -1
- package/dist/commands/pair.js +0 -1
- package/dist/commands/restart.d.ts +0 -1
- package/dist/commands/restart.js +0 -1
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +19 -3
- package/dist/commands/serve.d.ts +0 -1
- package/dist/commands/serve.js +0 -1
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/event-queues.d.ts +0 -1
- package/dist/event-queues.js +0 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/linked-device.d.ts +0 -1
- package/dist/linked-device.js +0 -1
- package/dist/mcp-handler.d.ts +0 -1
- package/dist/mcp-handler.js +0 -1
- package/dist/mcp-tools.d.ts +0 -1
- package/dist/mcp-tools.js +0 -1
- package/dist/nats-client.d.ts +0 -1
- package/dist/nats-client.js +0 -1
- package/dist/network.d.ts +0 -1
- package/dist/network.js +0 -1
- package/dist/notification-store.d.ts +0 -1
- package/dist/notification-store.js +0 -1
- package/dist/pending-requests.d.ts +0 -1
- package/dist/pending-requests.js +0 -1
- package/dist/platform/index.d.ts +0 -1
- package/dist/platform/index.js +0 -1
- package/dist/platform/linux.d.ts +0 -1
- package/dist/platform/linux.js +0 -1
- package/dist/platform/macos.d.ts +0 -1
- package/dist/platform/macos.js +0 -1
- package/dist/platform/platform.d.ts +0 -1
- package/dist/platform/platform.js +0 -1
- package/dist/platform/windows.d.ts +0 -1
- package/dist/platform/windows.js +0 -1
- package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
- package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
- package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
- package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/rpc-handler.d.ts +0 -1
- package/dist/rpc-handler.js +0 -1
- package/dist/sms-store.d.ts +0 -1
- package/dist/sms-store.js +0 -1
- package/dist/spawn-command.d.ts +0 -1
- package/dist/spawn-command.js +0 -1
- package/dist/task.d.ts +0 -1
- package/dist/task.js +0 -1
- package/dist/transports/http-transport.d.ts +0 -1
- package/dist/transports/http-transport.js +0 -1
- package/dist/transports/nats-transport.d.ts +0 -1
- package/dist/transports/nats-transport.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/update-checker.d.ts +0 -1
- package/dist/update-checker.js +0 -1
- package/package.json +11 -1
- package/.github/workflows/ci.yml +0 -16
- package/.github/workflows/publish.yml +0 -37
- package/CLAUDE.md +0 -22
- package/dist/pwa/apple-touch-icon.png +0 -0
- package/dist/pwa/manifest.webmanifest +0 -1
- package/dist/pwa/pwa-192x192.png +0 -0
- package/dist/pwa/pwa-512x512.png +0 -0
- package/dist/pwa/registerSW.js +0 -1
- package/dist/pwa/service-worker.js +0 -2
- package/palmier-server/.github/workflows/ci.yml +0 -21
- package/palmier-server/.github/workflows/deploy.yml +0 -38
- package/palmier-server/CLAUDE.md +0 -17
- package/palmier-server/PRODUCTION.md +0 -358
- package/palmier-server/README.md +0 -231
- package/palmier-server/nats.conf +0 -19
- package/palmier-server/package.json +0 -15
- package/palmier-server/pnpm-lock.yaml +0 -7639
- package/palmier-server/pnpm-workspace.yaml +0 -3
- package/palmier-server/pwa/index.html +0 -16
- package/palmier-server/pwa/logo/logo_20260421.png +0 -0
- package/palmier-server/pwa/package.json +0 -34
- package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
- package/palmier-server/pwa/public/favicon.ico +0 -0
- package/palmier-server/pwa/public/pwa-192x192.png +0 -0
- package/palmier-server/pwa/public/pwa-512x512.png +0 -0
- package/palmier-server/pwa/src/App.css +0 -3012
- package/palmier-server/pwa/src/App.tsx +0 -59
- package/palmier-server/pwa/src/agentLabels.ts +0 -11
- package/palmier-server/pwa/src/api.ts +0 -67
- package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
- package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
- package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
- package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
- package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
- package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
- package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
- package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
- package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
- package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
- package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
- package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
- package/palmier-server/pwa/src/constants.ts +0 -2
- package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
- package/palmier-server/pwa/src/draftGuard.ts +0 -24
- package/palmier-server/pwa/src/formatTime.ts +0 -44
- package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
- package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
- package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
- package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
- package/palmier-server/pwa/src/main.tsx +0 -14
- package/palmier-server/pwa/src/native/Device.ts +0 -49
- package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
- package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
- package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
- package/palmier-server/pwa/src/service-worker.ts +0 -142
- package/palmier-server/pwa/src/types.ts +0 -75
- package/palmier-server/pwa/src/vite-env.d.ts +0 -11
- package/palmier-server/pwa/tsconfig.json +0 -21
- package/palmier-server/pwa/tsconfig.node.json +0 -19
- package/palmier-server/pwa/vite.config.ts +0 -47
- package/palmier-server/server/.env.example +0 -20
- package/palmier-server/server/package.json +0 -36
- package/palmier-server/server/src/db.ts +0 -44
- package/palmier-server/server/src/fcm.ts +0 -74
- package/palmier-server/server/src/index.ts +0 -688
- package/palmier-server/server/src/nats-jwt.ts +0 -299
- package/palmier-server/server/src/nats-setup.ts +0 -48
- package/palmier-server/server/src/nats.ts +0 -33
- package/palmier-server/server/src/notify.ts +0 -34
- package/palmier-server/server/src/push.ts +0 -68
- package/palmier-server/server/src/routes/device.ts +0 -224
- package/palmier-server/server/src/routes/fcm.ts +0 -64
- package/palmier-server/server/src/routes/hosts.ts +0 -56
- package/palmier-server/server/src/routes/push.ts +0 -101
- package/palmier-server/server/tsconfig.json +0 -20
- package/palmier-server/spec.md +0 -533
- package/src/agents/agent-instructions.md +0 -28
- package/src/agents/agent.ts +0 -114
- package/src/agents/aider.ts +0 -35
- package/src/agents/claude.ts +0 -39
- package/src/agents/cline.ts +0 -35
- package/src/agents/codex.ts +0 -40
- package/src/agents/copilot.ts +0 -37
- package/src/agents/cursor.ts +0 -36
- package/src/agents/deepagents.ts +0 -36
- package/src/agents/droid.ts +0 -35
- package/src/agents/gemini.ts +0 -43
- package/src/agents/goose.ts +0 -33
- package/src/agents/hermes.ts +0 -36
- package/src/agents/kimi.ts +0 -35
- package/src/agents/kiro.ts +0 -36
- package/src/agents/openclaw.ts +0 -29
- package/src/agents/opencode.ts +0 -36
- package/src/agents/qoder.ts +0 -36
- package/src/agents/qwen.ts +0 -32
- package/src/agents/shared-prompt.ts +0 -30
- package/src/client-store.ts +0 -68
- package/src/commands/clients.ts +0 -29
- package/src/commands/info.ts +0 -29
- package/src/commands/init.ts +0 -165
- package/src/commands/pair.ts +0 -137
- package/src/commands/restart.ts +0 -6
- package/src/commands/run.ts +0 -608
- package/src/commands/serve.ts +0 -211
- package/src/commands/uninstall.ts +0 -9
- package/src/config.ts +0 -36
- package/src/cross-spawn.d.ts +0 -5
- package/src/event-queues.ts +0 -41
- package/src/events.ts +0 -29
- package/src/index.ts +0 -111
- package/src/linked-device.ts +0 -52
- package/src/mcp-handler.ts +0 -200
- package/src/mcp-tools.ts +0 -839
- package/src/nats-client.ts +0 -19
- package/src/network.ts +0 -96
- package/src/notification-store.ts +0 -30
- package/src/pending-requests.ts +0 -73
- package/src/platform/index.ts +0 -20
- package/src/platform/linux.ts +0 -296
- package/src/platform/macos.ts +0 -329
- package/src/platform/platform.ts +0 -31
- package/src/platform/windows.ts +0 -299
- package/src/rpc-handler.ts +0 -691
- package/src/sms-store.ts +0 -28
- package/src/spawn-command.ts +0 -123
- package/src/task.ts +0 -343
- package/src/transports/http-transport.ts +0 -478
- package/src/transports/nats-transport.ts +0 -76
- package/src/types.ts +0 -89
- package/src/update-checker.ts +0 -40
- package/test/agent-instructions.test.ts +0 -209
- package/test/agent-output-parsing.test.ts +0 -74
- package/test/linux-cron.test.ts +0 -41
- package/test/macos-plist.test.ts +0 -112
- package/test/notification-store.test.ts +0 -57
- package/test/pairing.test.ts +0 -35
- package/test/result-state.test.ts +0 -110
- package/test/task-parsing.test.ts +0 -82
- package/test/taskrun-messages.test.ts +0 -224
- package/test/tsconfig.json +0 -9
- package/test/windows-xml.test.ts +0 -89
- package/tsconfig.json +0 -19
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useCallback } from "react";
|
|
2
|
-
import { createPortal } from "react-dom";
|
|
3
|
-
import { useNavigate, useParams, useLocation } from "react-router-dom";
|
|
4
|
-
import { Capacitor } from "@capacitor/core";
|
|
5
|
-
import { useHostStore } from "../contexts/HostStoreContext";
|
|
6
|
-
import { useHostConnection } from "../contexts/HostConnectionContext";
|
|
7
|
-
import { useMediaQuery } from "../hooks/useMediaQuery";
|
|
8
|
-
import { confirmLeaveDraft } from "../draftGuard";
|
|
9
|
-
import { Device } from "../native/Device";
|
|
10
|
-
import CapabilityToggles from "./CapabilityToggles";
|
|
11
|
-
|
|
12
|
-
/** Local mode: PWA is served by palmier serve on loopback. */
|
|
13
|
-
const isLoopback = !!(window as any).__PALMIER_SERVE__
|
|
14
|
-
&& (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
|
|
15
|
-
const isNative = Capacitor.isNativePlatform();
|
|
16
|
-
|
|
17
|
-
interface HostMenuProps {
|
|
18
|
-
daemonVersion?: string | null;
|
|
19
|
-
linkedClientToken?: string | null;
|
|
20
|
-
request?<T = unknown>(method: string, params?: unknown): Promise<T>;
|
|
21
|
-
onEnabledCapabilitiesChange?(next: Set<string>): void;
|
|
22
|
-
onLinkedClientTokenChange?(next: string | null): void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function HostMenu({ daemonVersion, linkedClientToken, request, onEnabledCapabilitiesChange, onLinkedClientTokenChange }: HostMenuProps) {
|
|
26
|
-
const { pairedHosts, removePairedHost, renamePairedHost } = useHostStore();
|
|
27
|
-
const { activeHost } = useHostConnection();
|
|
28
|
-
const navigate = useNavigate();
|
|
29
|
-
const location = useLocation();
|
|
30
|
-
const params = useParams<{ hostId?: string }>();
|
|
31
|
-
const activeHostId = params.hostId ?? null;
|
|
32
|
-
const activeClientToken = activeHost.clientToken || null;
|
|
33
|
-
const isLinkedDevice = !!activeClientToken && linkedClientToken === activeClientToken;
|
|
34
|
-
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
35
|
-
const [linkingBusy, setLinkingBusy] = useState(false);
|
|
36
|
-
|
|
37
|
-
const [now, setNow] = useState(() => Date.now());
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const tick = () => setNow(Date.now());
|
|
40
|
-
const msUntilNextMinute = 60_000 - (Date.now() % 60_000);
|
|
41
|
-
const first = setTimeout(() => {
|
|
42
|
-
tick();
|
|
43
|
-
const iv = setInterval(tick, 60_000);
|
|
44
|
-
(first as unknown as { _iv: ReturnType<typeof setInterval> })._iv = iv;
|
|
45
|
-
}, msUntilNextMinute);
|
|
46
|
-
return () => {
|
|
47
|
-
const iv = (first as unknown as { _iv?: ReturnType<typeof setInterval> })._iv;
|
|
48
|
-
if (iv) clearInterval(iv);
|
|
49
|
-
clearTimeout(first);
|
|
50
|
-
};
|
|
51
|
-
}, []);
|
|
52
|
-
const hostClock = activeHost.timezone
|
|
53
|
-
? new Date(now).toLocaleString(undefined, {
|
|
54
|
-
month: "short", day: "numeric", hour: "numeric", minute: "2-digit",
|
|
55
|
-
timeZone: activeHost.timezone,
|
|
56
|
-
})
|
|
57
|
-
: "";
|
|
58
|
-
|
|
59
|
-
async function makeThisLinkedDevice() {
|
|
60
|
-
if (!Device || !request || !activeClientToken) return;
|
|
61
|
-
setLinkingBusy(true);
|
|
62
|
-
try {
|
|
63
|
-
const { token: fcmToken } = await Device.getFcmToken();
|
|
64
|
-
if (!fcmToken) return;
|
|
65
|
-
await request("device.link", { fcmToken });
|
|
66
|
-
onLinkedClientTokenChange?.(activeClientToken);
|
|
67
|
-
} catch (err) {
|
|
68
|
-
console.error("Failed to make this the linked device:", err);
|
|
69
|
-
} finally {
|
|
70
|
-
setLinkingBusy(false);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function handleDelete(hostId: string) {
|
|
75
|
-
const wasActive = hostId === activeHostId;
|
|
76
|
-
// Only revoke against the currently-active host — `request` uses its client
|
|
77
|
-
// token. Non-active unpair is local-only; the host keeps a dangling token
|
|
78
|
-
// the user can clear via `palmier clients revoke`.
|
|
79
|
-
if (wasActive && request) {
|
|
80
|
-
try { await request("clients.revoke_self"); } catch { /* best effort */ }
|
|
81
|
-
}
|
|
82
|
-
removePairedHost(hostId);
|
|
83
|
-
setConfirmingDeleteId(null);
|
|
84
|
-
if (wasActive) navigate("/", { replace: true });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function switchHost(newHostId: string) {
|
|
88
|
-
const onTasks = location.pathname.endsWith("/tasks");
|
|
89
|
-
const tabSuffix = onTasks ? "/tasks" : "";
|
|
90
|
-
navigate(`/hosts/${encodeURIComponent(newHostId)}${tabSuffix}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const [visible, setVisible] = useState(false);
|
|
94
|
-
const [closing, setClosing] = useState(false);
|
|
95
|
-
const [renamingId, setRenamingId] = useState<string | null>(null);
|
|
96
|
-
const [renameValue, setRenameValue] = useState("");
|
|
97
|
-
const [confirmingDeleteId, setConfirmingDeleteId] = useState<string | null>(null);
|
|
98
|
-
const [confirmingLink, setConfirmingLink] = useState(false);
|
|
99
|
-
|
|
100
|
-
const drawerRef = useRef<HTMLDivElement>(null);
|
|
101
|
-
const renameInputRef = useRef<HTMLInputElement>(null);
|
|
102
|
-
|
|
103
|
-
const close = useCallback(() => {
|
|
104
|
-
setClosing(true);
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
function handleAnimationEnd() {
|
|
108
|
-
if (closing) {
|
|
109
|
-
setVisible(false);
|
|
110
|
-
setClosing(false);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function openDrawer() {
|
|
115
|
-
setClosing(false);
|
|
116
|
-
setVisible(true);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function startRename(hostId: string, currentName: string) {
|
|
120
|
-
setRenamingId(hostId);
|
|
121
|
-
setRenameValue(currentName);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function submitRename() {
|
|
125
|
-
if (renamingId && renameValue.trim()) {
|
|
126
|
-
renamePairedHost(renamingId, renameValue.trim());
|
|
127
|
-
}
|
|
128
|
-
setRenamingId(null);
|
|
129
|
-
setRenameValue("");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function cancelRename() {
|
|
133
|
-
setRenamingId(null);
|
|
134
|
-
setRenameValue("");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
if (renamingId && renameInputRef.current) {
|
|
139
|
-
renameInputRef.current.focus();
|
|
140
|
-
renameInputRef.current.select();
|
|
141
|
-
}
|
|
142
|
-
}, [renamingId]);
|
|
143
|
-
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (!visible || closing) return;
|
|
146
|
-
function handleKey(e: KeyboardEvent) {
|
|
147
|
-
if (e.key === "Escape") close();
|
|
148
|
-
}
|
|
149
|
-
document.addEventListener("keydown", handleKey);
|
|
150
|
-
return () => {
|
|
151
|
-
document.removeEventListener("keydown", handleKey);
|
|
152
|
-
};
|
|
153
|
-
}, [visible, closing, close]);
|
|
154
|
-
|
|
155
|
-
const drawerContent = (
|
|
156
|
-
<>
|
|
157
|
-
{!isDesktop && (
|
|
158
|
-
<button
|
|
159
|
-
className="drawer-close-btn"
|
|
160
|
-
onClick={close}
|
|
161
|
-
aria-label="Close menu"
|
|
162
|
-
>
|
|
163
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
164
|
-
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
165
|
-
</svg>
|
|
166
|
-
</button>
|
|
167
|
-
)}
|
|
168
|
-
|
|
169
|
-
{!isLoopback && pairedHosts.length > 0 && (
|
|
170
|
-
<div className="drawer-section">
|
|
171
|
-
<h3 className="drawer-section-label">Hosts</h3>
|
|
172
|
-
<div className="host-picker-inline">
|
|
173
|
-
<div className="host-picker-list" role="listbox">
|
|
174
|
-
{pairedHosts.map((host) => {
|
|
175
|
-
const isActive = host.hostId === activeHostId;
|
|
176
|
-
const isRenaming = renamingId === host.hostId;
|
|
177
|
-
const displayName = host.name || host.hostId.slice(0, 8);
|
|
178
|
-
return (
|
|
179
|
-
<div key={host.hostId} className="host-picker-item-wrapper">
|
|
180
|
-
<div
|
|
181
|
-
className={`host-picker-item ${isActive ? "host-picker-item-active" : ""}`}
|
|
182
|
-
onClick={() => {
|
|
183
|
-
if (isRenaming) return;
|
|
184
|
-
if (!isActive) {
|
|
185
|
-
if (!confirmLeaveDraft()) return;
|
|
186
|
-
switchHost(host.hostId);
|
|
187
|
-
if (!isDesktop) close();
|
|
188
|
-
}
|
|
189
|
-
}}
|
|
190
|
-
role="option"
|
|
191
|
-
aria-selected={isActive}
|
|
192
|
-
>
|
|
193
|
-
{isRenaming ? (
|
|
194
|
-
<input
|
|
195
|
-
ref={renameInputRef}
|
|
196
|
-
className="form-input host-picker-rename-input"
|
|
197
|
-
type="text"
|
|
198
|
-
value={renameValue}
|
|
199
|
-
onChange={(e) => setRenameValue(e.target.value)}
|
|
200
|
-
onKeyDown={(e) => {
|
|
201
|
-
if (e.key === "Enter") submitRename();
|
|
202
|
-
if (e.key === "Escape") cancelRename();
|
|
203
|
-
}}
|
|
204
|
-
onBlur={submitRename}
|
|
205
|
-
onClick={(e) => e.stopPropagation()}
|
|
206
|
-
/>
|
|
207
|
-
) : (
|
|
208
|
-
<span className="host-picker-item-name">
|
|
209
|
-
{displayName}
|
|
210
|
-
</span>
|
|
211
|
-
)}
|
|
212
|
-
<div className="host-picker-item-actions">
|
|
213
|
-
{isActive && !isRenaming && (
|
|
214
|
-
<button
|
|
215
|
-
className="host-picker-edit-btn"
|
|
216
|
-
onClick={(e) => {
|
|
217
|
-
e.stopPropagation();
|
|
218
|
-
startRename(host.hostId, displayName);
|
|
219
|
-
}}
|
|
220
|
-
aria-label="Rename host"
|
|
221
|
-
>
|
|
222
|
-
<svg width="13" height="13" viewBox="0 0 13 13" fill="none">
|
|
223
|
-
<path
|
|
224
|
-
d="M9.5 1.5L11.5 3.5M1.5 11.5L2 9L9 2L11 4L4 11L1.5 11.5Z"
|
|
225
|
-
stroke="currentColor"
|
|
226
|
-
strokeWidth="1.2"
|
|
227
|
-
strokeLinecap="round"
|
|
228
|
-
strokeLinejoin="round"
|
|
229
|
-
/>
|
|
230
|
-
</svg>
|
|
231
|
-
</button>
|
|
232
|
-
)}
|
|
233
|
-
{!isActive && (
|
|
234
|
-
<button
|
|
235
|
-
className="host-picker-delete-btn"
|
|
236
|
-
onClick={(e) => {
|
|
237
|
-
e.stopPropagation();
|
|
238
|
-
setConfirmingDeleteId(host.hostId);
|
|
239
|
-
}}
|
|
240
|
-
aria-label={`Unpair ${displayName}`}
|
|
241
|
-
>
|
|
242
|
-
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
243
|
-
<path
|
|
244
|
-
d="M4 5.5V11C4 11.2761 4.22386 11.5 4.5 11.5H9.5C9.77614 11.5 10 11.2761 10 11V5.5M3 4H11M5.5 4V3C5.5 2.72386 5.72386 2.5 6 2.5H8C8.27614 2.5 8.5 2.72386 8.5 3V4M6.5 6.5V9.5M7.5 6.5V9.5"
|
|
245
|
-
stroke="currentColor"
|
|
246
|
-
strokeWidth="1.2"
|
|
247
|
-
strokeLinecap="round"
|
|
248
|
-
strokeLinejoin="round"
|
|
249
|
-
/>
|
|
250
|
-
</svg>
|
|
251
|
-
</button>
|
|
252
|
-
)}
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
);
|
|
257
|
-
})}
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
)}
|
|
262
|
-
|
|
263
|
-
{!isLoopback && (<>
|
|
264
|
-
<div className="drawer-divider" />
|
|
265
|
-
<div className="drawer-section">
|
|
266
|
-
<button
|
|
267
|
-
className="btn btn-primary btn-full"
|
|
268
|
-
onClick={() => { if (!confirmLeaveDraft()) return; navigate("/pair"); if (!isDesktop) close(); }}
|
|
269
|
-
>
|
|
270
|
-
Add New Host
|
|
271
|
-
</button>
|
|
272
|
-
</div>
|
|
273
|
-
</>)}
|
|
274
|
-
|
|
275
|
-
{isNative && request && (
|
|
276
|
-
<>
|
|
277
|
-
<div className="drawer-divider" />
|
|
278
|
-
<div className="drawer-section">
|
|
279
|
-
<h3 className="drawer-section-label">Device Capabilities</h3>
|
|
280
|
-
{isLinkedDevice ? (
|
|
281
|
-
<CapabilityToggles onChange={onEnabledCapabilitiesChange} confirmDisable />
|
|
282
|
-
) : (
|
|
283
|
-
<>
|
|
284
|
-
<p className="drawer-section-hint">
|
|
285
|
-
This device isn't the linked device for the host, so it can't provide capabilities (SMS, contacts, location, etc.).
|
|
286
|
-
</p>
|
|
287
|
-
<button
|
|
288
|
-
className="btn btn-secondary btn-full"
|
|
289
|
-
onClick={() => {
|
|
290
|
-
if (linkedClientToken && linkedClientToken !== activeClientToken) {
|
|
291
|
-
setConfirmingLink(true);
|
|
292
|
-
} else {
|
|
293
|
-
makeThisLinkedDevice();
|
|
294
|
-
}
|
|
295
|
-
}}
|
|
296
|
-
disabled={linkingBusy}
|
|
297
|
-
>
|
|
298
|
-
{linkingBusy ? "Linking…" : "Link the host to this device"}
|
|
299
|
-
</button>
|
|
300
|
-
</>
|
|
301
|
-
)}
|
|
302
|
-
</div>
|
|
303
|
-
</>
|
|
304
|
-
)}
|
|
305
|
-
|
|
306
|
-
<div className="drawer-footer">
|
|
307
|
-
{activeHost.timezone && (
|
|
308
|
-
<div className="drawer-host-time">
|
|
309
|
-
Host time: {hostClock} · {activeHost.timezone}
|
|
310
|
-
</div>
|
|
311
|
-
)}
|
|
312
|
-
{daemonVersion && (
|
|
313
|
-
<div className="drawer-version">
|
|
314
|
-
Host version: v{daemonVersion}
|
|
315
|
-
</div>
|
|
316
|
-
)}
|
|
317
|
-
<div className="drawer-legal">
|
|
318
|
-
<a href="https://www.palmier.me/terms" target="_blank" rel="noopener noreferrer">Terms</a>
|
|
319
|
-
<span className="drawer-legal-sep">·</span>
|
|
320
|
-
<a href="https://www.palmier.me/privacy" target="_blank" rel="noopener noreferrer">Privacy</a>
|
|
321
|
-
</div>
|
|
322
|
-
</div>
|
|
323
|
-
</>
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
const deletingIsLinked = !!confirmingDeleteId && confirmingDeleteId === activeHostId && isLinkedDevice;
|
|
327
|
-
const deleteModal = confirmingDeleteId && createPortal(
|
|
328
|
-
<div className="confirm-modal-overlay" onClick={() => setConfirmingDeleteId(null)}>
|
|
329
|
-
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
|
|
330
|
-
<h2 className="confirm-modal-title">Remove host?</h2>
|
|
331
|
-
<p className="confirm-modal-message">
|
|
332
|
-
"{pairedHosts.find((h) => h.hostId === confirmingDeleteId)?.name || confirmingDeleteId.slice(0, 8)}" will be {deletingIsLinked ? "removed and unlinked" : "removed"}.
|
|
333
|
-
{deletingIsLinked && (
|
|
334
|
-
<> This device is currently linked to the host — unlinking will revoke its access to all device capabilities (SMS, contacts, location, etc.) until another device is linked.</>
|
|
335
|
-
)}
|
|
336
|
-
</p>
|
|
337
|
-
<div className="confirm-modal-actions">
|
|
338
|
-
<button
|
|
339
|
-
className="btn btn-secondary"
|
|
340
|
-
onClick={() => setConfirmingDeleteId(null)}
|
|
341
|
-
>
|
|
342
|
-
Cancel
|
|
343
|
-
</button>
|
|
344
|
-
<button
|
|
345
|
-
className="btn btn-danger"
|
|
346
|
-
onClick={() => handleDelete(confirmingDeleteId)}
|
|
347
|
-
>
|
|
348
|
-
Remove
|
|
349
|
-
</button>
|
|
350
|
-
</div>
|
|
351
|
-
</div>
|
|
352
|
-
</div>,
|
|
353
|
-
document.body
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
const linkModal = confirmingLink && createPortal(
|
|
357
|
-
<div className="confirm-modal-overlay" onClick={() => setConfirmingLink(false)}>
|
|
358
|
-
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
|
|
359
|
-
<h2 className="confirm-modal-title">Link the host to this device?</h2>
|
|
360
|
-
<p className="confirm-modal-message">
|
|
361
|
-
Another device is already linked to this host. Only one device can be linked to the host — switching will disable those capabilities on the currently linked device.
|
|
362
|
-
</p>
|
|
363
|
-
<div className="confirm-modal-actions">
|
|
364
|
-
<button
|
|
365
|
-
className="btn btn-secondary"
|
|
366
|
-
onClick={() => setConfirmingLink(false)}
|
|
367
|
-
>
|
|
368
|
-
Cancel
|
|
369
|
-
</button>
|
|
370
|
-
<button
|
|
371
|
-
className="btn btn-primary"
|
|
372
|
-
onClick={() => { setConfirmingLink(false); makeThisLinkedDevice(); }}
|
|
373
|
-
>
|
|
374
|
-
Link
|
|
375
|
-
</button>
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
</div>,
|
|
379
|
-
document.body
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
// Desktop: persistent inline sidebar
|
|
383
|
-
if (isDesktop) {
|
|
384
|
-
return (
|
|
385
|
-
<>
|
|
386
|
-
<div className="drawer-panel drawer-panel-desktop" ref={drawerRef}>
|
|
387
|
-
{drawerContent}
|
|
388
|
-
</div>
|
|
389
|
-
{deleteModal}
|
|
390
|
-
{linkModal}
|
|
391
|
-
</>
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Mobile: hamburger + slide-out drawer
|
|
396
|
-
return (
|
|
397
|
-
<>
|
|
398
|
-
<button
|
|
399
|
-
className="hamburger-btn"
|
|
400
|
-
onClick={openDrawer}
|
|
401
|
-
aria-label="Open menu"
|
|
402
|
-
>
|
|
403
|
-
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
404
|
-
<path d="M3 5H17M3 10H17M3 15H17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
405
|
-
</svg>
|
|
406
|
-
</button>
|
|
407
|
-
|
|
408
|
-
{visible && createPortal(
|
|
409
|
-
<div
|
|
410
|
-
className={`drawer-overlay ${closing ? "drawer-overlay-closing" : ""}`}
|
|
411
|
-
onClick={close}
|
|
412
|
-
onAnimationEnd={handleAnimationEnd}
|
|
413
|
-
>
|
|
414
|
-
<div
|
|
415
|
-
className={`drawer-panel ${closing ? "drawer-panel-closing" : ""}`}
|
|
416
|
-
ref={drawerRef}
|
|
417
|
-
onClick={(e) => e.stopPropagation()}
|
|
418
|
-
>
|
|
419
|
-
{drawerContent}
|
|
420
|
-
</div>
|
|
421
|
-
</div>,
|
|
422
|
-
document.body
|
|
423
|
-
)}
|
|
424
|
-
|
|
425
|
-
{deleteModal}
|
|
426
|
-
{linkModal}
|
|
427
|
-
</>
|
|
428
|
-
);
|
|
429
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { RequiredPermission } from "../types";
|
|
2
|
-
|
|
3
|
-
interface PermissionsDialogProps {
|
|
4
|
-
permissions?: RequiredPermission[];
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function PermissionsDialog({ permissions }: PermissionsDialogProps) {
|
|
8
|
-
return (
|
|
9
|
-
<div className="permissions-dialog">
|
|
10
|
-
<h2>Granted Permissions</h2>
|
|
11
|
-
<div className="permissions-dialog-scroll">
|
|
12
|
-
{permissions && permissions.length > 0 ? (
|
|
13
|
-
<div className="permissions-section">
|
|
14
|
-
<ul className="permissions-list">
|
|
15
|
-
{permissions.map((p, i) => (
|
|
16
|
-
<li key={i} className="permission-item">
|
|
17
|
-
<span className="permission-tool">{p.name}</span>
|
|
18
|
-
<span className="permission-desc">{p.description}</span>
|
|
19
|
-
</li>
|
|
20
|
-
))}
|
|
21
|
-
</ul>
|
|
22
|
-
</div>
|
|
23
|
-
) : (
|
|
24
|
-
<p className="permissions-empty">No permissions have been granted for this task.</p>
|
|
25
|
-
)}
|
|
26
|
-
</div>
|
|
27
|
-
<div className="permissions-dialog-actions">
|
|
28
|
-
<button className="btn btn-secondary" onClick={() => history.back()}>
|
|
29
|
-
Back
|
|
30
|
-
</button>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
interface Props {
|
|
2
|
-
pullDistance: number;
|
|
3
|
-
refreshing: boolean;
|
|
4
|
-
threshold: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Fixed-position indicator parked just above the viewport that slides down as
|
|
9
|
-
* the user pulls. Opacity ramps up quickly so the badge peeks in immediately;
|
|
10
|
-
* the arrow flips upward once the pull has crossed the release threshold.
|
|
11
|
-
*/
|
|
12
|
-
export default function PullToRefreshIndicator({ pullDistance, refreshing, threshold }: Props) {
|
|
13
|
-
const visible = pullDistance > 0 || refreshing;
|
|
14
|
-
const ready = pullDistance >= threshold;
|
|
15
|
-
return (
|
|
16
|
-
<div
|
|
17
|
-
className="pull-to-refresh"
|
|
18
|
-
style={{
|
|
19
|
-
transform: `translateY(${pullDistance}px)`,
|
|
20
|
-
opacity: visible ? Math.min(1, pullDistance / 30) : 0,
|
|
21
|
-
}}
|
|
22
|
-
aria-hidden={!visible}
|
|
23
|
-
>
|
|
24
|
-
<div className="pull-to-refresh-badge">
|
|
25
|
-
{refreshing ? (
|
|
26
|
-
<div className="spinner" />
|
|
27
|
-
) : (
|
|
28
|
-
<svg
|
|
29
|
-
width="18"
|
|
30
|
-
height="18"
|
|
31
|
-
viewBox="0 0 24 24"
|
|
32
|
-
fill="none"
|
|
33
|
-
stroke="currentColor"
|
|
34
|
-
strokeWidth="2.25"
|
|
35
|
-
strokeLinecap="round"
|
|
36
|
-
strokeLinejoin="round"
|
|
37
|
-
style={{ transform: ready ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.15s" }}
|
|
38
|
-
>
|
|
39
|
-
<line x1="12" y1="5" x2="12" y2="19" />
|
|
40
|
-
<polyline points="19 12 12 19 5 12" />
|
|
41
|
-
</svg>
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
}
|