@decentnetwork/beagle 0.1.96 → 0.1.98
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/dist/desktop/app.js +308 -81
- package/dist/desktop/index.html +1 -1
- package/dist/ipc.d.ts +7 -3
- package/dist/ipc.js +7 -3
- package/dist/lan-handoff.d.ts +10 -0
- package/dist/lan-handoff.js +43 -5
- package/dist/server.d.ts +3 -1
- package/dist/server.js +41 -5
- package/package.json +2 -2
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.98";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -516,7 +516,7 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
516
516
|
whiteSpace: "nowrap",
|
|
517
517
|
overflow: "hidden",
|
|
518
518
|
textOverflow: "ellipsis"
|
|
519
|
-
} }, peer.alias || shortKey(peer.userId, 8, 5)), peer.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 13, color: "var(--faint)", stroke: 2 }), muted && /* @__PURE__ */ React.createElement(Icon, { name: "bellOff", size: 12, color: "var(--faint)", stroke: 2, title: T && T.mutedTag || "sound muted" }), peer.wire === "64" && /* @__PURE__ */ React.createElement(Tag, { tone: "off", style: { height: 15, fontSize: 9.5, padding: "0 4px" } }, "w64")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 2 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, peer.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)", fontSize: 11 } }, "\xB7"), /* @__PURE__ */ React.createElement("span", { style: {
|
|
519
|
+
} }, peer.alias || shortKey(peer.userId, 8, 5)), peer.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 13, color: "var(--faint)", stroke: 2 }), muted && /* @__PURE__ */ React.createElement(Icon, { name: "bellOff", size: 12, color: "var(--faint)", stroke: 2, title: T && T.mutedTag || "sound muted" }), peer.wire === "64" && /* @__PURE__ */ React.createElement(Tag, { tone: "off", style: { height: 15, fontSize: 9.5, padding: "0 4px" } }, "w64")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 2 } }, peer.ip ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, peer.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)", fontSize: 11 } }, "\xB7")) : null, /* @__PURE__ */ React.createElement("span", { style: {
|
|
520
520
|
fontSize: 11,
|
|
521
521
|
color: "var(--dim)",
|
|
522
522
|
whiteSpace: "nowrap",
|
|
@@ -524,12 +524,12 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
524
524
|
textOverflow: "ellipsis",
|
|
525
525
|
flex: 1,
|
|
526
526
|
minWidth: 0
|
|
527
|
-
} },
|
|
527
|
+
} }, dkRowPreview(peer.lastMsg)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, peer.lastTime), peer.unread ? /* @__PURE__ */ React.createElement(Unread, { n: peer.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
|
|
528
528
|
}
|
|
529
529
|
function dkActivityTs(p) {
|
|
530
530
|
return Math.max(p.lastTs || 0, p.acceptedAt || 0, p.requestedAt || 0);
|
|
531
531
|
}
|
|
532
|
-
function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }) {
|
|
532
|
+
function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide }) {
|
|
533
533
|
const [q, setQ] = React.useState("");
|
|
534
534
|
const [addr, setAddr] = React.useState("");
|
|
535
535
|
const [addState, setAddState] = React.useState(null);
|
|
@@ -578,7 +578,7 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
578
578
|
return String(a.alias || a.userId || "").localeCompare(String(b.alias || b.userId || ""));
|
|
579
579
|
});
|
|
580
580
|
const online = peers.filter((p) => p.online).length;
|
|
581
|
-
return /* @__PURE__ */ React.createElement("div", { style: { width: 320, flexShrink: 0, borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { padding: "12px 12px 10px", borderBottom: "1px solid var(--line)", display: "flex", flexDirection: "column", gap: 8 } }, fromDeepLink && /* @__PURE__ */ React.createElement("div", { style: {
|
|
581
|
+
return /* @__PURE__ */ React.createElement("div", { style: wide ? { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column", background: "var(--panel)" } : { width: 320, flexShrink: 0, borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { padding: "12px 12px 10px", borderBottom: "1px solid var(--line)", display: "flex", flexDirection: "column", gap: 8 } }, fromDeepLink && /* @__PURE__ */ React.createElement("div", { style: {
|
|
582
582
|
fontFamily: "var(--ui)",
|
|
583
583
|
fontSize: 12,
|
|
584
584
|
lineHeight: 1.4,
|
|
@@ -771,6 +771,17 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
771
771
|
const link = "\n\n[Open name card](https://app.beagle.chat/#/chat?address=" + encodeURIComponent(p.address || "") + ")";
|
|
772
772
|
return head + bio + link + "\n\n```beagle-contact\n" + JSON.stringify(j) + "\n```\n";
|
|
773
773
|
}
|
|
774
|
+
function dkRowPreview(text) {
|
|
775
|
+
const card = dkContactPreview(text);
|
|
776
|
+
if (card)
|
|
777
|
+
return card;
|
|
778
|
+
const s = String(text == null ? "" : text);
|
|
779
|
+
const mine = /^you:\s*/i.test(s);
|
|
780
|
+
const cgp = dkParseCGP1(s.replace(/^you:\s*/i, ""));
|
|
781
|
+
if (cgp)
|
|
782
|
+
return (mine ? "you: " : "") + cgp.sender + ": " + cgp.body.replace(/\s+/g, " ");
|
|
783
|
+
return s;
|
|
784
|
+
}
|
|
774
785
|
function dkContactPreview(text) {
|
|
775
786
|
const s = String(text == null ? "" : text);
|
|
776
787
|
if (!/beagle-contact|BCR1\s*\{/i.test(s))
|
|
@@ -1413,6 +1424,8 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
1413
1424
|
background: mine ? "var(--bub-me)" : "var(--bub-them)",
|
|
1414
1425
|
border: "1px solid " + (mine ? "transparent" : "var(--line)"),
|
|
1415
1426
|
minWidth: 200,
|
|
1427
|
+
maxWidth: "100%",
|
|
1428
|
+
boxSizing: "border-box",
|
|
1416
1429
|
textDecoration: "none",
|
|
1417
1430
|
cursor: mine ? "default" : "pointer"
|
|
1418
1431
|
};
|
|
@@ -1596,7 +1609,7 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
1596
1609
|
) : mine && m.status === "sent" && m.confirmed === false ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "var(--faint)", title: T.sentUnconfirmed || "sent \u2014 not confirmed by the peer" }) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
|
|
1597
1610
|
);
|
|
1598
1611
|
}
|
|
1599
|
-
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, muted, onToggleMute }) {
|
|
1612
|
+
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, muted, onToggleMute, phone, onBack }) {
|
|
1600
1613
|
const scrollRef = React.useRef(null);
|
|
1601
1614
|
const isGroup = React.useMemo(() => dkThreadIsGroup(threadProp), [threadProp]);
|
|
1602
1615
|
const followScrollRef = React.useRef(true);
|
|
@@ -1869,7 +1882,16 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
1869
1882
|
onDrop
|
|
1870
1883
|
},
|
|
1871
1884
|
dragOver && /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", inset: 0, zIndex: 30, background: "rgba(91,140,255,0.10)", border: "2px dashed var(--accent)", borderRadius: 12, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none", fontFamily: "var(--mono)", fontSize: 14, color: "var(--accent)" } }, lang === "zh" ? "\u677E\u5F00\u53D1\u9001\u6587\u4EF6" : "Drop to send file"),
|
|
1872
|
-
/* @__PURE__ */ React.createElement("div", { style: { height:
|
|
1885
|
+
/* @__PURE__ */ React.createElement("div", { style: { height: phone ? 56 : 60, boxSizing: "content-box", paddingTop: phone ? "env(safe-area-inset-top, 0px)" : 0, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: phone ? 8 : 12, paddingLeft: phone ? 4 : 16, paddingRight: phone ? 8 : 16, background: "var(--panel)" } }, phone && onBack && /* @__PURE__ */ React.createElement(
|
|
1886
|
+
"button",
|
|
1887
|
+
{
|
|
1888
|
+
onClick: onBack,
|
|
1889
|
+
title: T && T.back || "Back",
|
|
1890
|
+
"aria-label": T && T.back || "Back",
|
|
1891
|
+
style: { width: 40, height: 44, flexShrink: 0, border: "none", background: "transparent", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", padding: 0 }
|
|
1892
|
+
},
|
|
1893
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "chevronLeft", size: 26, stroke: 2.2, color: "var(--accent)" })
|
|
1894
|
+
), /* @__PURE__ */ React.createElement("div", { onClick: () => setPubProfile(true), title: T && T.pubOpen || "View public profile", style: { cursor: "pointer", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: phone ? 1 : void 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { onClick: () => setPubProfile(true), style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)", cursor: "pointer", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 } }, peer.alias || shortKey(peer.userId, phone ? 6 : 10, phone ? 4 : 6)), peer.agent && !phone && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && !phone && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), !phone && /* @__PURE__ */ React.createElement(RouteTag, { peer })), phone ? /* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, display: "flex" } }, /* @__PURE__ */ React.createElement(RouteTag, { peer })) : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
|
|
1873
1895
|
Mono,
|
|
1874
1896
|
{
|
|
1875
1897
|
size: 11.5,
|
|
@@ -1880,7 +1902,7 @@ window.__DK_UI_VERSION="0.1.96";
|
|
|
1880
1902
|
${peer.address}`
|
|
1881
1903
|
},
|
|
1882
1904
|
shortKey(peer.address, 8, 6)
|
|
1883
|
-
)))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), onToggleMute && /* @__PURE__ */ React.createElement(
|
|
1905
|
+
)))), !phone && /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), onToggleMute && !phone && /* @__PURE__ */ React.createElement(
|
|
1884
1906
|
Btn,
|
|
1885
1907
|
{
|
|
1886
1908
|
icon: muted ? "bellOff" : "bell",
|
|
@@ -1898,7 +1920,16 @@ ${peer.address}`
|
|
|
1898
1920
|
e.target.value = "";
|
|
1899
1921
|
}
|
|
1900
1922
|
}
|
|
1901
|
-
), /* @__PURE__ */ React.createElement(Btn, { icon: "file", title: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => rtcFileRef.current && rtcFileRef.current.click() })), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, /* @__PURE__ */ React.createElement(MenuItem, { icon: "
|
|
1923
|
+
), !phone && /* @__PURE__ */ React.createElement(Btn, { icon: "file", title: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => rtcFileRef.current && rtcFileRef.current.click() })), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, phone && onToggleMute && /* @__PURE__ */ React.createElement(MenuItem, { icon: muted ? "bell" : "bellOff", label: muted ? T && T.unmuteSound || "Turn sound back on" : T && T.muteSound || "Mute sound", onClick: () => {
|
|
1924
|
+
setMenu(false);
|
|
1925
|
+
onToggleMute(peer.id);
|
|
1926
|
+
} }), phone && onSendRtcFile && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
|
|
1927
|
+
setMenu(false);
|
|
1928
|
+
rtcFileRef.current && rtcFileRef.current.click();
|
|
1929
|
+
} }), phone && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
|
|
1930
|
+
setMenu(false);
|
|
1931
|
+
onOpenNet(peer);
|
|
1932
|
+
} }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
|
|
1902
1933
|
setMenu(false);
|
|
1903
1934
|
onAlias(peer);
|
|
1904
1935
|
} }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "check", label: T.selectDelete || "Select & delete", onClick: () => {
|
|
@@ -1919,7 +1950,7 @@ ${peer.address}`
|
|
|
1919
1950
|
fontSize: 12.5,
|
|
1920
1951
|
color: "var(--text)"
|
|
1921
1952
|
} }, /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 13, stroke: 2.2, color: "#d29922" }), /* @__PURE__ */ React.createElement("span", null, T.pendingFriend || "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.")),
|
|
1922
|
-
/* @__PURE__ */ React.createElement("div", { ref: scrollRef, onScroll: updateFollowScroll, style: { flex: 1, overflow: "auto", padding: "18px 22px" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto" } }, thread.filter((m) => m.day || !hidden.has(m.id)).map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(
|
|
1953
|
+
/* @__PURE__ */ React.createElement("div", { ref: scrollRef, onScroll: updateFollowScroll, style: { flex: 1, overflow: "auto", padding: phone ? "12px 10px" : "18px 22px", WebkitOverflowScrolling: "touch" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto" } }, thread.filter((m) => m.day || !hidden.has(m.id)).map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(
|
|
1923
1954
|
Msg,
|
|
1924
1955
|
{
|
|
1925
1956
|
key: m.id || i,
|
|
@@ -1948,7 +1979,7 @@ ${peer.address}`
|
|
|
1948
1979
|
setSelMode(false);
|
|
1949
1980
|
setSel(/* @__PURE__ */ new Set());
|
|
1950
1981
|
} }, T.cancel || "Cancel"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", icon: "trash", onClick: () => doDelete([...sel]) }, T.delete || "Delete")),
|
|
1951
|
-
/* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, borderTop: "1px solid var(--line)", padding: "12px 16px", background: "var(--panel)" } }, flash && /* @__PURE__ */ React.createElement("div", { style: {
|
|
1982
|
+
/* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, borderTop: "1px solid var(--line)", padding: phone ? "8px 8px calc(8px + env(safe-area-inset-bottom, 0px))" : "12px 16px", background: "var(--panel)" } }, flash && /* @__PURE__ */ React.createElement("div", { style: {
|
|
1952
1983
|
maxWidth: 1200,
|
|
1953
1984
|
margin: "0 auto 8px",
|
|
1954
1985
|
display: "flex",
|
|
@@ -1965,7 +1996,7 @@ ${peer.address}`
|
|
|
1965
1996
|
stroke: 2.2,
|
|
1966
1997
|
color: flash.kind === "err" ? "var(--danger)" : "var(--accent)"
|
|
1967
1998
|
}
|
|
1968
|
-
), flash.msg), sending && /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto 8px", display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "paperclip", size: 13, stroke: 2, color: "var(--accent)" }), (lang === "zh" ? "\u53D1\u9001\u4E2D: " : "Sending: ") + (sending.via === "webrtc" ? "WebRTC \xB7 " : "") + sending.name + (sending.size ? " \xB7 " + dkFileSize(sending.sent || 0) + " / " + dkFileSize(sending.size) : "") + (sending.pct != null ? " \xB7 " + Math.min(100, Math.max(0, sending.pct)).toFixed(1) + "%" : "") + (sending.kbps != null ? " \xB7 " + dkSpeed(sending.kbps) : "")), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement(
|
|
1999
|
+
), flash.msg), sending && /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto 8px", display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "paperclip", size: 13, stroke: 2, color: "var(--accent)" }), (lang === "zh" ? "\u53D1\u9001\u4E2D: " : "Sending: ") + (sending.via === "webrtc" ? "WebRTC \xB7 " : "") + sending.name + (sending.size ? " \xB7 " + dkFileSize(sending.sent || 0) + " / " + dkFileSize(sending.size) : "") + (sending.pct != null ? " \xB7 " + Math.min(100, Math.max(0, sending.pct)).toFixed(1) + "%" : "") + (sending.kbps != null ? " \xB7 " + dkSpeed(sending.kbps) : "")), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1200, margin: "0 auto", display: "flex", alignItems: "center", gap: phone ? 6 : 10 } }, /* @__PURE__ */ React.createElement(
|
|
1969
2000
|
"input",
|
|
1970
2001
|
{
|
|
1971
2002
|
ref: fileRef,
|
|
@@ -1987,7 +2018,7 @@ ${peer.address}`
|
|
|
1987
2018
|
sendDraft();
|
|
1988
2019
|
}
|
|
1989
2020
|
},
|
|
1990
|
-
placeholder: `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
|
|
2021
|
+
placeholder: phone ? `${T.message}\u2026` : `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
|
|
1991
2022
|
style: { flex: 1, height: 38, borderRadius: 9, border: "1px solid var(--line)", background: "var(--panel-2)", color: "var(--text)", fontFamily: "var(--ui)", fontSize: 13.5, padding: "0 14px", outline: "none", minWidth: 0 }
|
|
1992
2023
|
}
|
|
1993
2024
|
), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "arrowUp", title: T.send, onClick: sendDraft }))),
|
|
@@ -2079,8 +2110,11 @@ ${peer.address}`
|
|
|
2079
2110
|
function ChatEmpty({ T }) {
|
|
2080
2111
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--faint)", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "message", size: 40, stroke: 1.4, color: "var(--line)" }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13 } }, T.pickPeer));
|
|
2081
2112
|
}
|
|
2082
|
-
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, prefillAddr, onPrefillConsumed, muted, onToggleMute }) {
|
|
2113
|
+
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
2083
2114
|
const peer = peers.find((p) => p.id === activeId);
|
|
2115
|
+
if (phone) {
|
|
2116
|
+
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, muted: !!muted && muted.includes(peer.id), onToggleMute, phone: true, onBack }) : /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, wide: true });
|
|
2117
|
+
}
|
|
2084
2118
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
2085
2119
|
}
|
|
2086
2120
|
Object.assign(window, { ChatTab });
|
|
@@ -2117,14 +2151,14 @@ ${peer.address}`
|
|
|
2117
2151
|
));
|
|
2118
2152
|
}
|
|
2119
2153
|
function FieldRow({ T, label, value, mono = true, copy, qr, onQr, last }) {
|
|
2120
|
-
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px", borderBottom: last ? "none" : "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, label), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: mono ? "var(--mono)" : "var(--ui)", fontSize: 13.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, value), copy && /* @__PURE__ */ React.createElement(CopyBtn, { value, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), qr && /* @__PURE__ */ React.createElement(Btn, { icon: "qr", size: "sm", onClick: () => onQr && onQr(value, label) }));
|
|
2154
|
+
return /* @__PURE__ */ React.createElement("div", { className: "dk-row", style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px", borderBottom: last ? "none" : "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, label), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: mono ? "var(--mono)" : "var(--ui)", fontSize: 13.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, value), copy && /* @__PURE__ */ React.createElement(CopyBtn, { value, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), qr && /* @__PURE__ */ React.createElement(Btn, { icon: "qr", size: "sm", onClick: () => onQr && onQr(value, label) }));
|
|
2121
2155
|
}
|
|
2122
2156
|
function DkVersionRow({ T, me }) {
|
|
2123
2157
|
const unsure = me.verFromBackend === false;
|
|
2124
2158
|
const pill = (name, ver, mark) => /* @__PURE__ */ React.createElement("span", { key: name, style: { display: "inline-flex", alignItems: "baseline", gap: 5 } }, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, name), /* @__PURE__ */ React.createElement("span", { title: mark && unsure ? T.verInstalledHint : void 0 }, ver || "\u2014", mark && unsure && ver ? /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, " ?") : null));
|
|
2125
2159
|
const line = (label, items) => /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 10, flexWrap: "wrap" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 52, flexShrink: 0, fontSize: 11, color: "var(--faint)" } }, label), /* @__PURE__ */ React.createElement("span", { style: { display: "flex", gap: 12, flexWrap: "wrap" } }, items));
|
|
2126
2160
|
const embedded = me.backend === "embedded";
|
|
2127
|
-
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "flex-start", gap: 14, padding: "13px 16px", borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.version), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 6, fontFamily: "var(--mono)", fontSize: 13, color: "var(--text)" } }, line(T.verApp || "app", [
|
|
2161
|
+
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "flex-start", gap: 14, padding: "13px 16px", borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.version), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 6, fontFamily: "var(--mono)", fontSize: 13, color: "var(--text)" } }, line(T.verApp || "app", [
|
|
2128
2162
|
pill("beagle", me.beagleVer),
|
|
2129
2163
|
pill("peer-webrtc", me.webrtcVer),
|
|
2130
2164
|
...embedded ? [pill("peer", me.peerVer)] : []
|
|
@@ -2428,6 +2462,47 @@ ${peer.address}`
|
|
|
2428
2462
|
recState[p.id] === "sent" ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--online)" } }, T.pubRecSent || "sent \u2713") : recState[p.id] === "busy" ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, "\u2026") : recState[p.id] ? /* @__PURE__ */ React.createElement("span", { title: recState[p.id], style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--warn, #f59e0b)" } }, T.addFailed || "failed") : /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--accent)" } }, T.send || "Send")
|
|
2429
2463
|
))))));
|
|
2430
2464
|
}
|
|
2465
|
+
function dkAvatarDataUrl(file) {
|
|
2466
|
+
return new Promise((resolve, reject) => {
|
|
2467
|
+
const img = new Image();
|
|
2468
|
+
const url = URL.createObjectURL(file);
|
|
2469
|
+
img.onload = () => {
|
|
2470
|
+
URL.revokeObjectURL(url);
|
|
2471
|
+
try {
|
|
2472
|
+
const S = 256;
|
|
2473
|
+
const c = document.createElement("canvas");
|
|
2474
|
+
c.width = S;
|
|
2475
|
+
c.height = S;
|
|
2476
|
+
const ctx = c.getContext("2d");
|
|
2477
|
+
const side = Math.min(img.width, img.height);
|
|
2478
|
+
ctx.drawImage(img, (img.width - side) / 2, (img.height - side) / 2, side, side, 0, 0, S, S);
|
|
2479
|
+
const enc = (q) => {
|
|
2480
|
+
let d = c.toDataURL("image/webp", q);
|
|
2481
|
+
if (!d.startsWith("data:image/webp"))
|
|
2482
|
+
d = c.toDataURL("image/jpeg", q);
|
|
2483
|
+
return d;
|
|
2484
|
+
};
|
|
2485
|
+
let dataUrl = null;
|
|
2486
|
+
for (const q of [0.9, 0.75, 0.55, 0.4]) {
|
|
2487
|
+
dataUrl = enc(q);
|
|
2488
|
+
if (dataUrl.length < 13e4)
|
|
2489
|
+
break;
|
|
2490
|
+
}
|
|
2491
|
+
if (!dataUrl || dataUrl.length >= 13e4)
|
|
2492
|
+
reject(new Error("too-big"));
|
|
2493
|
+
else
|
|
2494
|
+
resolve(dataUrl);
|
|
2495
|
+
} catch (err) {
|
|
2496
|
+
reject(err);
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
img.onerror = () => {
|
|
2500
|
+
URL.revokeObjectURL(url);
|
|
2501
|
+
reject(new Error("bad-image"));
|
|
2502
|
+
};
|
|
2503
|
+
img.src = url;
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2431
2506
|
function DkEnsCard({ T, me, onRecord }) {
|
|
2432
2507
|
const [st, setSt] = React.useState({ loading: true });
|
|
2433
2508
|
const [label, setLabel] = React.useState("");
|
|
@@ -2510,7 +2585,7 @@ ${peer.address}`
|
|
|
2510
2585
|
var _a;
|
|
2511
2586
|
const saved = (st.record && st.record.texts || {})["com." + k] || "";
|
|
2512
2587
|
const cur = (_a = draft[k]) != null ? _a : "";
|
|
2513
|
-
return /* @__PURE__ */ React.createElement("div", { key: k, style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { style: rowLbl }, k), /* @__PURE__ */ React.createElement(
|
|
2588
|
+
return /* @__PURE__ */ React.createElement("div", { key: k, className: "dk-row", style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: rowLbl }, k), /* @__PURE__ */ React.createElement(
|
|
2514
2589
|
"input",
|
|
2515
2590
|
{
|
|
2516
2591
|
value: cur,
|
|
@@ -2553,51 +2628,14 @@ ${peer.address}`
|
|
|
2553
2628
|
return;
|
|
2554
2629
|
setBusy(true);
|
|
2555
2630
|
setMsg(null);
|
|
2556
|
-
|
|
2557
|
-
const url = URL.createObjectURL(f);
|
|
2558
|
-
const fail = (text) => {
|
|
2631
|
+
dkAvatarDataUrl(f).then((dataUrl) => post("/api/ens-avatar-upload", { dataUrl })).catch((err) => {
|
|
2559
2632
|
setBusy(false);
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
URL.revokeObjectURL(url);
|
|
2564
|
-
try {
|
|
2565
|
-
const S = 256;
|
|
2566
|
-
const c = document.createElement("canvas");
|
|
2567
|
-
c.width = S;
|
|
2568
|
-
c.height = S;
|
|
2569
|
-
const ctx = c.getContext("2d");
|
|
2570
|
-
const side = Math.min(img.width, img.height);
|
|
2571
|
-
ctx.drawImage(img, (img.width - side) / 2, (img.height - side) / 2, side, side, 0, 0, S, S);
|
|
2572
|
-
const enc = (q) => {
|
|
2573
|
-
let d = c.toDataURL("image/webp", q);
|
|
2574
|
-
if (!d.startsWith("data:image/webp"))
|
|
2575
|
-
d = c.toDataURL("image/jpeg", q);
|
|
2576
|
-
return d;
|
|
2577
|
-
};
|
|
2578
|
-
let dataUrl = null;
|
|
2579
|
-
for (const q of [0.9, 0.75, 0.55, 0.4]) {
|
|
2580
|
-
dataUrl = enc(q);
|
|
2581
|
-
if (dataUrl.length < 13e4)
|
|
2582
|
-
break;
|
|
2583
|
-
}
|
|
2584
|
-
if (!dataUrl || dataUrl.length >= 13e4) {
|
|
2585
|
-
fail(T.ensTooBig || "image too large even after scaling");
|
|
2586
|
-
return;
|
|
2587
|
-
}
|
|
2588
|
-
post("/api/ens-avatar-upload", { dataUrl });
|
|
2589
|
-
} catch (err) {
|
|
2590
|
-
fail(String(err && err.message || err));
|
|
2591
|
-
}
|
|
2592
|
-
};
|
|
2593
|
-
img.onerror = () => {
|
|
2594
|
-
URL.revokeObjectURL(url);
|
|
2595
|
-
fail(T.ensBadImage || "cannot read that image");
|
|
2596
|
-
};
|
|
2597
|
-
img.src = url;
|
|
2633
|
+
const m = err && err.message;
|
|
2634
|
+
setMsg({ tone: "err", text: m === "too-big" ? T.ensTooBig || "image too large even after scaling" : m === "bad-image" ? T.ensBadImage || "cannot read that image" : String(m || err) });
|
|
2635
|
+
});
|
|
2598
2636
|
};
|
|
2599
|
-
const walletRow = (lbl, chain, bound, onBind, last) => /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, lbl), bound ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, bound), /* @__PURE__ */ React.createElement(CopyBtn, { value: bound, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => bind(chain, "") }, T.ensUnbind || "unbind")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotBound || "not bound"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: onBind }, T.ensBind || "bind")));
|
|
2600
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { label: T.ensCard || "Name \xB7 beagles.eth" }, st.loading ? /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensName || "name"), /* @__PURE__ */ React.createElement(
|
|
2637
|
+
const walletRow = (lbl, chain, bound, onBind, last) => /* @__PURE__ */ React.createElement("div", { className: "dk-row", style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, lbl), bound ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, bound), /* @__PURE__ */ React.createElement(CopyBtn, { value: bound, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => bind(chain, "") }, T.ensUnbind || "unbind")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotBound || "not bound"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: onBind }, T.ensBind || "bind")));
|
|
2638
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { label: T.ensCard || "Name \xB7 beagles.eth" }, st.loading ? /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensName || "name"), /* @__PURE__ */ React.createElement(
|
|
2601
2639
|
"input",
|
|
2602
2640
|
{
|
|
2603
2641
|
value: label,
|
|
@@ -2608,9 +2646,9 @@ ${peer.address}`
|
|
|
2608
2646
|
if (e.key === "Enter")
|
|
2609
2647
|
register();
|
|
2610
2648
|
},
|
|
2611
|
-
style: field
|
|
2649
|
+
style: { ...field, minWidth: 110 }
|
|
2612
2650
|
}
|
|
2613
|
-
), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
|
|
2651
|
+
), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
|
|
2614
2652
|
}
|
|
2615
2653
|
function DkUpdateCheck({ T }) {
|
|
2616
2654
|
const [st, setSt] = React.useState("idle");
|
|
@@ -2626,7 +2664,7 @@ ${peer.address}`
|
|
|
2626
2664
|
setSt("latest");
|
|
2627
2665
|
}).catch(() => setSt("idle"));
|
|
2628
2666
|
};
|
|
2629
|
-
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.updCheckLabel || "updates"), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: st === "latest" ? "var(--online)" : "var(--faint)" } }, st === "latest" ? T.updLatest || "up to date \u2713" : ""), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: st === "busy", onClick: check }, st === "busy" ? "\u2026" : T.updCheck || "check for updates"), info && /* @__PURE__ */ React.createElement(DkUpdateModal, { T, info, onClose: () => setInfo(null) }));
|
|
2667
|
+
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px" } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.updCheckLabel || "updates"), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: st === "latest" ? "var(--online)" : "var(--faint)" } }, st === "latest" ? T.updLatest || "up to date \u2713" : ""), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: st === "busy", onClick: check }, st === "busy" ? "\u2026" : T.updCheck || "check for updates"), info && /* @__PURE__ */ React.createElement(DkUpdateModal, { T, info, onClose: () => setInfo(null) }));
|
|
2630
2668
|
}
|
|
2631
2669
|
function DkKeyBackup({ t }) {
|
|
2632
2670
|
const zh = t && t.lang === "zh";
|
|
@@ -2739,7 +2777,7 @@ ${peer.address}`
|
|
|
2739
2777
|
},
|
|
2740
2778
|
label
|
|
2741
2779
|
)));
|
|
2742
|
-
return /* @__PURE__ */ React.createElement(Card, { label: zh ? "\u663E\u793A" : "Display" }, /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u8BED\u8A00" : "Language"), seg("lang", t.lang, (v) => setTweak("lang", v), [["en", "EN"], ["zh", "\u4E2D\u6587"]])), /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u4E3B\u9898" : "Theme"), seg("theme", t.theme, (v) => setTweak("theme", v), [["dark", zh ? "\u6DF1\u8272" : "Dark"], ["light", zh ? "\u6D45\u8272" : "Light"]])));
|
|
2780
|
+
return /* @__PURE__ */ React.createElement(Card, { label: zh ? "\u663E\u793A" : "Display" }, /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u8BED\u8A00" : "Language"), seg("lang", t.lang, (v) => setTweak("lang", v), [["en", "EN"], ["zh", "\u4E2D\u6587"]])), /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u4E3B\u9898" : "Theme"), seg("theme", t.theme, (v) => setTweak("theme", v), [["dark", zh ? "\u6DF1\u8272" : "Dark"], ["light", zh ? "\u6D45\u8272" : "Light"]])));
|
|
2743
2781
|
}
|
|
2744
2782
|
function DkAlertSettings({ t, setTweak }) {
|
|
2745
2783
|
const zh = t.lang === "zh";
|
|
@@ -2770,8 +2808,67 @@ ${peer.address}`
|
|
|
2770
2808
|
true
|
|
2771
2809
|
));
|
|
2772
2810
|
}
|
|
2773
|
-
function
|
|
2811
|
+
function DkAvatarSheet({ T, me, zh, onSet, onClose }) {
|
|
2812
|
+
const [picking, setPicking] = React.useState(false);
|
|
2813
|
+
const [busy, setBusy] = React.useState(false);
|
|
2814
|
+
const [err, setErr] = React.useState(null);
|
|
2815
|
+
const fileRef = React.useRef(null);
|
|
2816
|
+
const canUpload = Object.prototype.hasOwnProperty.call(me, "avatarDataUrl");
|
|
2817
|
+
const save = (p) => {
|
|
2818
|
+
setBusy(true);
|
|
2819
|
+
setErr(null);
|
|
2820
|
+
return Promise.resolve(onSet(p)).then((r) => {
|
|
2821
|
+
setBusy(false);
|
|
2822
|
+
if (r && r.ok === false)
|
|
2823
|
+
setErr(r.error || "failed");
|
|
2824
|
+
else
|
|
2825
|
+
onClose();
|
|
2826
|
+
}).catch((e) => {
|
|
2827
|
+
setBusy(false);
|
|
2828
|
+
setErr(String(e && e.message || e));
|
|
2829
|
+
});
|
|
2830
|
+
};
|
|
2831
|
+
const pickPunk = (id) => {
|
|
2832
|
+
setPicking(false);
|
|
2833
|
+
save(canUpload ? { punkId: id, avatarDataUrl: null } : { punkId: id });
|
|
2834
|
+
};
|
|
2835
|
+
const onFile = (e) => {
|
|
2836
|
+
const f = e.target.files && e.target.files[0];
|
|
2837
|
+
e.target.value = "";
|
|
2838
|
+
if (!f)
|
|
2839
|
+
return;
|
|
2840
|
+
setBusy(true);
|
|
2841
|
+
setErr(null);
|
|
2842
|
+
dkAvatarDataUrl(f).then((dataUrl) => save({ avatarDataUrl: dataUrl })).catch((x) => {
|
|
2843
|
+
setBusy(false);
|
|
2844
|
+
const m = x && x.message;
|
|
2845
|
+
setErr(m === "too-big" ? T.ensTooBig || "image too large even after scaling" : m === "bad-image" ? T.ensBadImage || "cannot read that image" : String(m || x));
|
|
2846
|
+
});
|
|
2847
|
+
};
|
|
2848
|
+
const has = me.punkId != null || !!(canUpload && me.avatarDataUrl);
|
|
2849
|
+
const item = (icon, label, onClick, danger) => /* @__PURE__ */ React.createElement("button", { onClick, disabled: busy, style: {
|
|
2850
|
+
display: "flex",
|
|
2851
|
+
alignItems: "center",
|
|
2852
|
+
gap: 12,
|
|
2853
|
+
width: "100%",
|
|
2854
|
+
padding: "13px 14px",
|
|
2855
|
+
border: "none",
|
|
2856
|
+
borderRadius: 10,
|
|
2857
|
+
background: "var(--panel-2)",
|
|
2858
|
+
cursor: busy ? "default" : "pointer",
|
|
2859
|
+
opacity: busy ? 0.6 : 1,
|
|
2860
|
+
fontFamily: "var(--ui)",
|
|
2861
|
+
fontSize: 14,
|
|
2862
|
+
color: danger ? "var(--danger)" : "var(--text)",
|
|
2863
|
+
textAlign: "left"
|
|
2864
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 18, stroke: 2, color: danger ? "var(--danger)" : "var(--accent)" }), label);
|
|
2865
|
+
if (picking)
|
|
2866
|
+
return /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) });
|
|
2867
|
+
return /* @__PURE__ */ React.createElement("div", { onClick: onClose, style: { position: "fixed", inset: 0, zIndex: 90, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 16 } }, /* @__PURE__ */ React.createElement("div", { onClick: (e) => e.stopPropagation(), style: { width: 360, maxWidth: "100%", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: 16, display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 4 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 40, radius: 10, dot: false }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, zh ? "\u66F4\u6362\u5934\u50CF" : "Change avatar"), /* @__PURE__ */ React.createElement(Btn, { icon: "x", size: "sm", onClick: onClose })), item("sparkles", zh ? "\u9009\u4E00\u4E2A CryptoPunk" : "Choose a CryptoPunk", () => setPicking(true)), canUpload && item("image", zh ? "\u4E0A\u4F20\u7167\u7247" : "Upload a photo", () => fileRef.current && fileRef.current.click()), has && item("trash", zh ? "\u79FB\u9664\u5934\u50CF" : "Remove avatar", () => save(canUpload ? { punkId: null, avatarDataUrl: null } : { punkId: null }), true), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onFile, style: { display: "none" } }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)", lineHeight: 1.5, marginTop: 2 } }, zh ? "\u4E0D\u9700\u8981\u6CE8\u518C beagles.eth \u540D\u5B57\u3002\u597D\u53CB\u4F1A\u5728\u4E0B\u6B21\u6536\u5230\u4F60\u7684\u8D44\u6599\u65F6\u770B\u5230\u65B0\u5934\u50CF\u3002" : "No beagles.eth name needed. Friends see it the next time your profile reaches them."), err && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--danger)" } }, err)));
|
|
2868
|
+
}
|
|
2869
|
+
function ProfileTab({ T, me, onEdit, t, setTweak, onSetAvatar }) {
|
|
2774
2870
|
var _a;
|
|
2871
|
+
const [avatarSheet, setAvatarSheet] = React.useState(false);
|
|
2775
2872
|
const [qr, setQr] = React.useState(null);
|
|
2776
2873
|
const [editing, setEditing] = React.useState(false);
|
|
2777
2874
|
const [ensRec, setEnsRec] = React.useState(null);
|
|
@@ -2779,7 +2876,17 @@ ${peer.address}`
|
|
|
2779
2876
|
const pubAvatar = ensRec && ensRec.texts && ensRec.texts.avatar || null;
|
|
2780
2877
|
const punkId = (_a = me.punkId) != null ? _a : pubPunk;
|
|
2781
2878
|
const upAvatar = me.avatarUrl || pubAvatar;
|
|
2782
|
-
|
|
2879
|
+
const canSetAvatar = !!onSetAvatar && !ensRec;
|
|
2880
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 18, rowGap: 12, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement(
|
|
2881
|
+
"div",
|
|
2882
|
+
{
|
|
2883
|
+
style: { position: "relative", cursor: canSetAvatar ? "pointer" : "default" },
|
|
2884
|
+
onClick: canSetAvatar ? () => setAvatarSheet(true) : void 0,
|
|
2885
|
+
title: canSetAvatar ? t && t.lang === "zh" ? "\u66F4\u6362\u5934\u50CF" : "Change avatar" : void 0
|
|
2886
|
+
},
|
|
2887
|
+
upAvatar ? /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 68, height: 68, style: { width: 68, height: 68, borderRadius: 16, objectFit: "cover", background: "var(--panel-2)" } }) : punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 68, radius: 16, fallbackSeed: me.userId }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }),
|
|
2888
|
+
canSetAvatar ? /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -5, bottom: -5, width: 24, height: 24, borderRadius: 999, background: "var(--accent)", border: "3px solid var(--panel)", display: "flex", alignItems: "center", justifyContent: "center", boxSizing: "border-box" } }, /* @__PURE__ */ React.createElement(Icon, { name: "camera", size: 12, stroke: 2.2, color: "#fff" })) : /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })
|
|
2889
|
+
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 10, rowGap: 4 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0, maxWidth: "100%" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })), /* @__PURE__ */ React.createElement(DkEnsCard, { T, me, onRecord: setEnsRec }), typeof me.autoAccept === "boolean" && /* @__PURE__ */ React.createElement(Card, { label: T && T.friendsSettings || "Friends" }, /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
|
|
2783
2890
|
"input",
|
|
2784
2891
|
{
|
|
2785
2892
|
type: "checkbox",
|
|
@@ -2791,13 +2898,50 @@ ${peer.address}`
|
|
|
2791
2898
|
},
|
|
2792
2899
|
style: { width: 16, height: 16, accentColor: "var(--accent)" }
|
|
2793
2900
|
}
|
|
2794
|
-
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), hostHasKeyBackup && /* @__PURE__ */ React.createElement(DkKeyBackup, { t: t || {} }), t && setTweak && /* @__PURE__ */ React.createElement(DkAlertSettings, { t, setTweak }), t && setTweak && /* @__PURE__ */ React.createElement(DkDisplaySettings, { t, setTweak }), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(DkVersionRow, { T, me }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
|
|
2901
|
+
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), hostHasKeyBackup && /* @__PURE__ */ React.createElement(DkKeyBackup, { t: t || {} }), t && setTweak && /* @__PURE__ */ React.createElement(DkAlertSettings, { t, setTweak }), t && setTweak && /* @__PURE__ */ React.createElement(DkDisplaySettings, { t, setTweak }), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(DkVersionRow, { T, me }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), avatarSheet && /* @__PURE__ */ React.createElement(DkAvatarSheet, { T, me, zh: t && t.lang === "zh", onSet: onSetAvatar, onClose: () => setAvatarSheet(false) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
|
|
2795
2902
|
if (onEdit)
|
|
2796
2903
|
onEdit(name, description);
|
|
2797
2904
|
setEditing(false);
|
|
2798
2905
|
} }));
|
|
2799
2906
|
}
|
|
2800
2907
|
Object.assign(window, { ProfileTab, DkPunkAvatar, DkEnsAvatar, DkPublicProfile });
|
|
2908
|
+
var DK_PHONE_MQ = "(max-width: 700px)";
|
|
2909
|
+
function useIsPhone() {
|
|
2910
|
+
const mq = React.useMemo(() => typeof window !== "undefined" && window.matchMedia ? window.matchMedia(DK_PHONE_MQ) : null, []);
|
|
2911
|
+
const [phone, setPhone] = React.useState(() => !!(mq && mq.matches));
|
|
2912
|
+
React.useEffect(() => {
|
|
2913
|
+
if (!mq)
|
|
2914
|
+
return void 0;
|
|
2915
|
+
const on = () => setPhone(mq.matches);
|
|
2916
|
+
on();
|
|
2917
|
+
if (mq.addEventListener)
|
|
2918
|
+
mq.addEventListener("change", on);
|
|
2919
|
+
else
|
|
2920
|
+
mq.addListener(on);
|
|
2921
|
+
return () => {
|
|
2922
|
+
if (mq.removeEventListener)
|
|
2923
|
+
mq.removeEventListener("change", on);
|
|
2924
|
+
else
|
|
2925
|
+
mq.removeListener(on);
|
|
2926
|
+
};
|
|
2927
|
+
}, [mq]);
|
|
2928
|
+
return phone;
|
|
2929
|
+
}
|
|
2930
|
+
var DK_PHONE_CSS = `
|
|
2931
|
+
@media ${DK_PHONE_MQ} {
|
|
2932
|
+
.dk-page { padding: 14px 12px 40px !important; gap: 18px !important; }
|
|
2933
|
+
.dk-lbl { width: 78px !important; font-size: 10px !important; letter-spacing: 0.3px !important; }
|
|
2934
|
+
.dk-row { padding: 11px 12px !important; gap: 9px !important; }
|
|
2935
|
+
.dk-wrap { flex-wrap: wrap !important; }
|
|
2936
|
+
.dk-grid2 { grid-template-columns: 1fr 1fr !important; }
|
|
2937
|
+
.dk-grid1 { grid-template-columns: 1fr !important; }
|
|
2938
|
+
.dk-no-phone { display: none !important; }
|
|
2939
|
+
/* iOS zooms the page into any focused field under 16px and never zooms back. */
|
|
2940
|
+
input:not([type=checkbox]):not([type=radio]), textarea, select { font-size: 16px !important; }
|
|
2941
|
+
}`;
|
|
2942
|
+
function DkPhoneStyles() {
|
|
2943
|
+
return /* @__PURE__ */ React.createElement("style", null, DK_PHONE_CSS);
|
|
2944
|
+
}
|
|
2801
2945
|
function DkIdenticon({ seed, size = 30, radius = 7 }) {
|
|
2802
2946
|
const { cells, hue } = dkIdenticon(seed);
|
|
2803
2947
|
const cell = size / 5;
|
|
@@ -2971,7 +3115,7 @@ ${peer.address}`
|
|
|
2971
3115
|
function Section({ label, count, trailing, style }) {
|
|
2972
3116
|
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px", ...style } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), count != null && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "var(--faint)" } }, count), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, height: 1, background: "var(--line)" } }), trailing);
|
|
2973
3117
|
}
|
|
2974
|
-
Object.assign(window, { DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
|
|
3118
|
+
Object.assign(window, { useIsPhone, DkPhoneStyles, DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
|
|
2975
3119
|
var APPS = [
|
|
2976
3120
|
{
|
|
2977
3121
|
id: "meet",
|
|
@@ -3813,9 +3957,9 @@ ${peer.address}`
|
|
|
3813
3957
|
value: q,
|
|
3814
3958
|
onChange: (e) => setQ(e.target.value),
|
|
3815
3959
|
placeholder: T.dirSearch || "search name / userid",
|
|
3816
|
-
style: { flex: 1, background: "transparent", border: "none", outline: "none", color: "var(--text)", fontFamily: "var(--ui)", fontSize: 13 }
|
|
3960
|
+
style: { flex: 1, minWidth: 0, background: "transparent", border: "none", outline: "none", color: "var(--text)", fontFamily: "var(--ui)", fontSize: 13 }
|
|
3817
3961
|
}
|
|
3818
|
-
), kind === "bridge" && (meta == null ? void 0 : meta.bridge) && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, String(meta.bridge).replace(/^https?:\/\//, ""), meta.online ? ` \xB7 ${meta.online} online` : ""), list && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, shown.length, "/", list.length)), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minHeight: 0, overflowY: "auto" } }, err ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, (T.dirError || "Could not load the directory:") + " " + err) : !list ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : shown.length === 0 ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, kind === "bridge" && (meta == null ? void 0 : meta.pending) ? T.bridgePending || "Not verified on this bridge yet \u2014 it may still be running the older protocol." : kind === "bridge" ? T.bridgeEmpty || "Nobody else is listed on this bridge yet." : T.dirEmpty || "nobody here") : shown.map((p) => /* @__PURE__ */ React.createElement(
|
|
3962
|
+
), kind === "bridge" && (meta == null ? void 0 : meta.bridge) && /* @__PURE__ */ React.createElement("span", { className: "dk-no-phone", style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)", whiteSpace: "nowrap" } }, String(meta.bridge).replace(/^https?:\/\//, ""), meta.online ? ` \xB7 ${meta.online} online` : ""), list && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)", whiteSpace: "nowrap", flexShrink: 0 } }, shown.length, "/", list.length)), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minHeight: 0, overflowY: "auto" } }, err ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, (T.dirError || "Could not load the directory:") + " " + err) : !list ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : shown.length === 0 ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, kind === "bridge" && (meta == null ? void 0 : meta.pending) ? T.bridgePending || "Not verified on this bridge yet \u2014 it may still be running the older protocol." : kind === "bridge" ? T.bridgeEmpty || "Nobody else is listed on this bridge yet." : T.dirEmpty || "nobody here") : shown.map((p) => /* @__PURE__ */ React.createElement(
|
|
3819
3963
|
DkDirRow,
|
|
3820
3964
|
{
|
|
3821
3965
|
key: p.userid,
|
|
@@ -4204,14 +4348,20 @@ ${peer.address}`
|
|
|
4204
4348
|
}
|
|
4205
4349
|
Object.assign(window, { useRtcFileController, RtcFileInbox });
|
|
4206
4350
|
function StatTile({ label, value, sub, tone }) {
|
|
4207
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, padding: "13px 15px", borderRadius: 10, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 21, fontWeight: 700, letterSpacing: -0.5, marginTop: 5, color: tone || "var(--text)" } }, value), sub && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", marginTop: 2 } }, sub));
|
|
4351
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, padding: "13px 15px", borderRadius: 10, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, label), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 21, fontWeight: 700, letterSpacing: -0.5, marginTop: 5, color: tone || "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, value), sub && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", marginTop: 2 } }, sub));
|
|
4208
4352
|
}
|
|
4209
4353
|
function MyNode({ T, me, activeExit, peers, reqCount = 0 }) {
|
|
4210
4354
|
const online = peers.filter((p) => p.online).length;
|
|
4211
4355
|
const direct = peers.filter((p) => p.online && p.via === "direct").length;
|
|
4212
|
-
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", borderRadius: 12, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement(
|
|
4356
|
+
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement("div", { className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", borderRadius: 12, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 46, radius: 11 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React.createElement("div", { className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 9 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 17, fontWeight: 700, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: me.online ? "ok" : "off" }, me.online ? "online" : "offline"), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 8, marginTop: 5, rowGap: 2 } }, me.ip ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, T.myIp), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: me.ip }, me.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7")) : null, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, "wire ", me.wire), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, "lan ", me.lanVer, " / peer ", me.peerVer))), /* @__PURE__ */ React.createElement(Btn, { icon: "copy", onClick: () => dkCopy(me.carrier) }, T.copyAddr)), /* @__PURE__ */ React.createElement("div", { className: "dk-grid2", style: { display: "grid", gridTemplateColumns: "repeat(4, minmax(0, 1fr))", gap: 12 } }, /* @__PURE__ */ React.createElement(StatTile, { label: T.peersOnline, value: `${online}/${peers.length}`, sub: `${direct} ${T.direct}`, tone: "var(--online)" }), /* @__PURE__ */ React.createElement(StatTile, { label: T.activeEgress, value: activeExit ? activeExit : T.directEgress, sub: activeExit ? T.routing : T.noProxy, tone: activeExit ? "var(--warn)" : "var(--text)" }), /* @__PURE__ */ React.createElement(StatTile, { label: T.wireLabel, value: me.wire, sub: `${me.channel} \xB7 ${T.lossless}` }), /* @__PURE__ */ React.createElement(StatTile, { label: T.reqs, value: String(reqCount), sub: T.pending, tone: reqCount ? "var(--accent)" : "var(--text)" })));
|
|
4213
4357
|
}
|
|
4214
4358
|
function PeerTable({ T, peers, onOpenChat }) {
|
|
4359
|
+
const phone = useIsPhone();
|
|
4360
|
+
if (!peers.length)
|
|
4361
|
+
return null;
|
|
4362
|
+
if (phone) {
|
|
4363
|
+
return /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, peers.map((p, i) => /* @__PURE__ */ React.createElement("div", { key: p.id, onClick: () => onOpenChat(p.id), style: { display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", cursor: "pointer", borderBottom: i < peers.length - 1 ? "1px solid var(--line)" : "none" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: p, size: 30, radius: 7 }), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 3 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: p.alias ? "var(--ui)" : "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 } }, p.alias || shortKey(p.userId, 8, 5)), p.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 12, color: "var(--faint)", stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, minWidth: 0 } }, /* @__PURE__ */ React.createElement(RouteTag, { peer: p }), p.ip ? /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: p.ip }, p.ip) : null)), /* @__PURE__ */ React.createElement(Icon, { name: "chevronRight", size: 16, color: "var(--faint)", stroke: 2 }))));
|
|
4364
|
+
}
|
|
4215
4365
|
return /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "1.4fr 1fr 1fr 0.8fr 90px", gap: 0, padding: "9px 16px", borderBottom: "1px solid var(--line)", background: "var(--panel-2)" } }, [T.colPeer, T.colVip, T.colPath, T.colWire, ""].map((h, i) => /* @__PURE__ */ React.createElement("span", { key: i, style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 700, letterSpacing: 0.8, textTransform: "uppercase", color: "var(--faint)", textAlign: i === 4 ? "right" : "left" } }, h))), peers.map((p, i) => /* @__PURE__ */ React.createElement("div", { key: p.id, style: {
|
|
4216
4366
|
display: "grid",
|
|
4217
4367
|
gridTemplateColumns: "1.4fr 1fr 1fr 0.8fr 90px",
|
|
@@ -4229,7 +4379,7 @@ ${peer.address}`
|
|
|
4229
4379
|
})()), /* @__PURE__ */ React.createElement("div", null, region.nodes.map((n, i) => {
|
|
4230
4380
|
const active = n.ip === activeExit;
|
|
4231
4381
|
const stuck = n.online && !n.reachable;
|
|
4232
|
-
return /* @__PURE__ */ React.createElement("div", { key: n.ip, style: { display: "flex", alignItems: "center", gap: 12, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.reachable }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.reachable ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : stuck ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "Announces online but IP won't pass \u2014 session desync or NAT-blocked" }, "online \xB7 no route") : /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline"), active ? /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onSetExit(null) }, T.routing) : /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "route", onClick: () => n.reachable && onSetExit(n.ip), style: { opacity: n.reachable ? 1 : 0.4 } }, T.routeThru));
|
|
4382
|
+
return /* @__PURE__ */ React.createElement("div", { key: n.ip, className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 12, rowGap: 6, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.reachable }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.reachable ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : stuck ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "Announces online but IP won't pass \u2014 session desync or NAT-blocked" }, "online \xB7 no route") : /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline"), active ? /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onSetExit(null) }, T.routing) : /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "route", onClick: () => n.reachable && onSetExit(n.ip), style: { opacity: n.reachable ? 1 : 0.4 } }, T.routeThru));
|
|
4233
4383
|
})));
|
|
4234
4384
|
}
|
|
4235
4385
|
function LanEnableCard({ backend, onArm, onCancel }) {
|
|
@@ -4335,9 +4485,10 @@ ${peer.address}`
|
|
|
4335
4485
|
}
|
|
4336
4486
|
function NetworkTab({ T, me, peers, exits, activeExit, reqCount, onSetExit, onOpenChat, backend, onArmLan, onCancelLan }) {
|
|
4337
4487
|
if (backend && backend.switchable && !backend.hasVirtualLan) {
|
|
4338
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement(LanEnableCard, { backend, onArm: onArmLan, onCancel: onCancelLan })));
|
|
4488
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement(LanEnableCard, { backend, onArm: onArmLan, onCancel: onCancelLan })));
|
|
4339
4489
|
}
|
|
4340
|
-
|
|
4490
|
+
const lanless = !!backend && backend.hasVirtualLan === false;
|
|
4491
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat }), !peers.length && /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", borderRadius: 11, border: "1px dashed var(--line)", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.noPeersYet || "No friends yet \u2014 add one from Chat and it shows up here with its route.")), lanless ? null : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { className: "dk-grid1", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit }))))));
|
|
4341
4492
|
}
|
|
4342
4493
|
Object.assign(window, { NetworkTab });
|
|
4343
4494
|
var dkAudioCtx = null;
|
|
@@ -4658,6 +4809,8 @@ ${peer.address}`
|
|
|
4658
4809
|
recommended: "Discover",
|
|
4659
4810
|
registered: "Names",
|
|
4660
4811
|
here: "Here",
|
|
4812
|
+
people: "People",
|
|
4813
|
+
back: "Back",
|
|
4661
4814
|
dirAdd: "Add",
|
|
4662
4815
|
dirSent: "requested",
|
|
4663
4816
|
dirOpenChat: "chat",
|
|
@@ -4843,6 +4996,8 @@ ${peer.address}`
|
|
|
4843
4996
|
recommended: "\u63A8\u8350",
|
|
4844
4997
|
registered: "\u540D\u5F55",
|
|
4845
4998
|
here: "\u5728\u573A",
|
|
4999
|
+
people: "\u53D1\u73B0",
|
|
5000
|
+
back: "\u8FD4\u56DE",
|
|
4846
5001
|
dirAdd: "\u52A0\u597D\u53CB",
|
|
4847
5002
|
dirSent: "\u5DF2\u8BF7\u6C42",
|
|
4848
5003
|
dirOpenChat: "\u4F1A\u8BDD",
|
|
@@ -5033,6 +5188,24 @@ ${peer.address}`
|
|
|
5033
5188
|
gap: 3
|
|
5034
5189
|
} }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 20, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, fontWeight: 600, letterSpacing: 0.2, color: active ? "var(--accent)" : "var(--faint)" } }, label), soon && /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 5, right: 6, width: 6, height: 6, borderRadius: 999, background: "var(--warn)" } }));
|
|
5035
5190
|
}
|
|
5191
|
+
function TabBarBtn({ icon, label, active, badge, onClick }) {
|
|
5192
|
+
return /* @__PURE__ */ React.createElement("button", { onClick, style: {
|
|
5193
|
+
flex: 1,
|
|
5194
|
+
minWidth: 0,
|
|
5195
|
+
height: 54,
|
|
5196
|
+
border: "none",
|
|
5197
|
+
background: "transparent",
|
|
5198
|
+
cursor: "pointer",
|
|
5199
|
+
position: "relative",
|
|
5200
|
+
display: "flex",
|
|
5201
|
+
flexDirection: "column",
|
|
5202
|
+
alignItems: "center",
|
|
5203
|
+
justifyContent: "center",
|
|
5204
|
+
gap: 3,
|
|
5205
|
+
fontFamily: "var(--mono)"
|
|
5206
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 22, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 10, fontWeight: 600, color: active ? "var(--accent)" : "var(--faint)", whiteSpace: "nowrap" } }, label), badge ? /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 4, left: "50%", marginLeft: 6 } }, /* @__PURE__ */ React.createElement(Unread, { n: badge })) : null);
|
|
5207
|
+
}
|
|
5208
|
+
var DK_PEOPLE_TABS = ["here", "recommended", "registered"];
|
|
5036
5209
|
function DkUpdateModal({ T, info, onClose }) {
|
|
5037
5210
|
const [phase, setPhase] = React.useState("idle");
|
|
5038
5211
|
const [err, setErr] = React.useState(null);
|
|
@@ -5270,10 +5443,34 @@ ${peer.address}`
|
|
|
5270
5443
|
data.refresh();
|
|
5271
5444
|
});
|
|
5272
5445
|
});
|
|
5446
|
+
const phone = useIsPhone();
|
|
5273
5447
|
React.useEffect(() => {
|
|
5274
|
-
if (!activeId && peers.length)
|
|
5448
|
+
if (!phone && !activeId && peers.length)
|
|
5275
5449
|
setActiveId(peers[0].id);
|
|
5276
|
-
}, [peers, activeId]);
|
|
5450
|
+
}, [peers, activeId, phone]);
|
|
5451
|
+
React.useEffect(() => {
|
|
5452
|
+
if (!phone)
|
|
5453
|
+
return void 0;
|
|
5454
|
+
const onPop = () => setActiveId(null);
|
|
5455
|
+
window.addEventListener("popstate", onPop);
|
|
5456
|
+
return () => window.removeEventListener("popstate", onPop);
|
|
5457
|
+
}, [phone]);
|
|
5458
|
+
const openConversation = (id) => {
|
|
5459
|
+
setActiveId(id);
|
|
5460
|
+
if (phone && id) {
|
|
5461
|
+
try {
|
|
5462
|
+
window.history.pushState({ dkConv: id }, "");
|
|
5463
|
+
} catch (e) {
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
};
|
|
5467
|
+
const closeConversation = () => {
|
|
5468
|
+
const st = window.history.state;
|
|
5469
|
+
if (st && st.dkConv)
|
|
5470
|
+
window.history.back();
|
|
5471
|
+
else
|
|
5472
|
+
setActiveId(null);
|
|
5473
|
+
};
|
|
5277
5474
|
React.useEffect(() => {
|
|
5278
5475
|
if (!activeId)
|
|
5279
5476
|
return;
|
|
@@ -5285,7 +5482,7 @@ ${peer.address}`
|
|
|
5285
5482
|
}, 2500);
|
|
5286
5483
|
return () => clearInterval(iv);
|
|
5287
5484
|
}, [activeId]);
|
|
5288
|
-
const onSelect = (id) =>
|
|
5485
|
+
const onSelect = (id) => openConversation(id);
|
|
5289
5486
|
const onAct = (id, kind) => {
|
|
5290
5487
|
const r = requests.find((x) => x.id === id);
|
|
5291
5488
|
const uid = r && r.userid || id;
|
|
@@ -5381,8 +5578,8 @@ ${peer.address}`
|
|
|
5381
5578
|
const onSetExit = () => {
|
|
5382
5579
|
};
|
|
5383
5580
|
const onOpenChat = (id) => {
|
|
5384
|
-
setActiveId(id);
|
|
5385
5581
|
setTab("chat");
|
|
5582
|
+
openConversation(id);
|
|
5386
5583
|
};
|
|
5387
5584
|
const onOpenNet = () => setTab("network");
|
|
5388
5585
|
const nav = [
|
|
@@ -5393,14 +5590,44 @@ ${peer.address}`
|
|
|
5393
5590
|
{ id: "network", icon: "network", label: T.network },
|
|
5394
5591
|
{ id: "apps", icon: "grid", label: T.apps }
|
|
5395
5592
|
];
|
|
5396
|
-
|
|
5593
|
+
const peopleTab = DK_PEOPLE_TABS.includes(tab);
|
|
5594
|
+
const lastPeople = React.useRef("here");
|
|
5595
|
+
if (peopleTab)
|
|
5596
|
+
lastPeople.current = tab;
|
|
5597
|
+
const unreadTotal = (peers || []).reduce((n, p) => n + (Number(p.unread) || 0), 0) + (requests || []).length;
|
|
5598
|
+
const phoneNav = [
|
|
5599
|
+
{ id: "chat", icon: "message", label: T.chat, active: tab === "chat", badge: unreadTotal, go: () => setTab("chat") },
|
|
5600
|
+
{ id: "people", icon: "users", label: T.people, active: peopleTab, go: () => setTab(lastPeople.current) },
|
|
5601
|
+
{ id: "network", icon: "network", label: T.network, active: tab === "network", go: () => setTab("network") },
|
|
5602
|
+
{ id: "apps", icon: "grid", label: T.apps, active: tab === "apps", go: () => setTab("apps") },
|
|
5603
|
+
{ id: "profile", icon: "userRound", label: T.profile, active: tab === "profile", go: () => setTab("profile") }
|
|
5604
|
+
];
|
|
5605
|
+
const inConversation = phone && tab === "chat" && !!activeId && (peers || []).some((p) => p.id === activeId);
|
|
5606
|
+
return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", flexDirection: phone ? "column" : "row", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement(DkPhoneStyles, null), !phone && /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: {
|
|
5397
5607
|
padding: 4,
|
|
5398
5608
|
borderRadius: 12,
|
|
5399
5609
|
cursor: "pointer",
|
|
5400
5610
|
display: "flex",
|
|
5401
5611
|
border: "1px solid " + (tab === "profile" ? "var(--line)" : "transparent"),
|
|
5402
5612
|
background: tab === "profile" ? "var(--panel-2)" : "transparent"
|
|
5403
|
-
} }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12,
|
|
5613
|
+
} }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, !inConversation && /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), !inConversation && /* @__PURE__ */ React.createElement("div", { style: { height: 46, boxSizing: "content-box", paddingTop: phone ? "env(safe-area-inset-top, 0px)" : 0, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: phone ? 8 : 12, paddingLeft: phone ? 12 : 16, paddingRight: phone ? 12 : 16 } }, /* @__PURE__ */ React.createElement("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "var(--accent)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block", flexShrink: 0 } }, /* @__PURE__ */ React.createElement("path", { d: "m4.5 17 6-6-6-6" }), /* @__PURE__ */ React.createElement("path", { d: "M12 18.5h7.5" })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", whiteSpace: "nowrap" } }, "\xB7 ", (nav.find((n) => n.id === tab) || { label: T.profile }).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), phone ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: { padding: 0, border: "none", background: "transparent", cursor: "pointer", display: "flex" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 28, radius: 7, dot: false }))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name)))), phone && peopleTab && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", gap: 6, padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--panel)", overflowX: "auto" } }, nav.filter((n) => DK_PEOPLE_TABS.includes(n.id)).map((n) => /* @__PURE__ */ React.createElement("button", { key: n.id, onClick: () => setTab(n.id), style: {
|
|
5614
|
+
display: "inline-flex",
|
|
5615
|
+
alignItems: "center",
|
|
5616
|
+
gap: 6,
|
|
5617
|
+
padding: "6px 12px",
|
|
5618
|
+
borderRadius: 999,
|
|
5619
|
+
cursor: "pointer",
|
|
5620
|
+
whiteSpace: "nowrap",
|
|
5621
|
+
border: "1px solid var(--line)",
|
|
5622
|
+
fontFamily: "var(--mono)",
|
|
5623
|
+
fontSize: 12,
|
|
5624
|
+
fontWeight: 600,
|
|
5625
|
+
background: tab === n.id ? "var(--accent)" : "transparent",
|
|
5626
|
+
color: tab === n.id ? "#fff" : "var(--dim)"
|
|
5627
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, onBack: closeConversation, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak, onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
|
|
5628
|
+
data.refresh();
|
|
5629
|
+
return r;
|
|
5630
|
+
}) })), phone && !inConversation && /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", borderTop: "1px solid var(--line)", background: "var(--rail)", paddingBottom: "env(safe-area-inset-bottom, 0px)" } }, phoneNav.map((n) => /* @__PURE__ */ React.createElement(TabBarBtn, { key: n.id, icon: n.icon, label: n.label, active: n.active, badge: n.badge, onClick: n.go }))), data.locked && /* @__PURE__ */ React.createElement(
|
|
5404
5631
|
DkLockedOut,
|
|
5405
5632
|
{
|
|
5406
5633
|
compact: true,
|
package/dist/desktop/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<title>Beagle Chat</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect width='24' height='24' rx='5' fill='%230d1117'/%3E%3Cg fill='none' stroke='%233fb950' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4.5 17 6-6-6-6'/%3E%3Cpath d='M12 18.5h7.5'/%3E%3C/g%3E%3C/svg%3E" />
|
|
8
8
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
package/dist/ipc.d.ts
CHANGED
|
@@ -44,9 +44,13 @@ export declare function ipcSocketPath(dataDir: string, platform?: NodeJS.Platfor
|
|
|
44
44
|
* Windows named pipes, so on Windows we connect and let net report. */
|
|
45
45
|
export declare function ipcSocketSupportsFsExistenceCheck(platform?: NodeJS.Platform): boolean;
|
|
46
46
|
/** How long the liveness probe waits for a connect before calling it dead.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
47
|
+
* A listening local socket accepts in well under a millisecond, so this
|
|
48
|
+
* budget never measured the daemon — it measured how long OUR event loop took
|
|
49
|
+
* to run the connect callback. At 400 ms a GC pause or a big JSON body in
|
|
50
|
+
* beagle read as "daemon gone": 814 false flaps on mac-dev while the daemon's
|
|
51
|
+
* pid never changed. One miss is not believed either; see DAEMON_MISS_STREAK
|
|
52
|
+
* in lan-handoff.ts. */
|
|
53
|
+
export declare const DAEMON_PROBE_TIMEOUT_MS = 2000;
|
|
50
54
|
/**
|
|
51
55
|
* Is a decentlan daemon actually LISTENING for this identity?
|
|
52
56
|
*
|
package/dist/ipc.js
CHANGED
|
@@ -28,9 +28,13 @@ export function ipcSocketSupportsFsExistenceCheck(platform = process.platform) {
|
|
|
28
28
|
return platform !== "win32";
|
|
29
29
|
}
|
|
30
30
|
/** How long the liveness probe waits for a connect before calling it dead.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
31
|
+
* A listening local socket accepts in well under a millisecond, so this
|
|
32
|
+
* budget never measured the daemon — it measured how long OUR event loop took
|
|
33
|
+
* to run the connect callback. At 400 ms a GC pause or a big JSON body in
|
|
34
|
+
* beagle read as "daemon gone": 814 false flaps on mac-dev while the daemon's
|
|
35
|
+
* pid never changed. One miss is not believed either; see DAEMON_MISS_STREAK
|
|
36
|
+
* in lan-handoff.ts. */
|
|
37
|
+
export const DAEMON_PROBE_TIMEOUT_MS = 2000;
|
|
34
38
|
/**
|
|
35
39
|
* Is a decentlan daemon actually LISTENING for this identity?
|
|
36
40
|
*
|
package/dist/lan-handoff.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export type HandoffState = "embedded" | "releasing" | "daemon" | "waiting";
|
|
|
6
6
|
* gives up on it and takes the identity back. `agentnet restart` and a
|
|
7
7
|
* launchd/systemd kickstart are both back well inside this. */
|
|
8
8
|
export declare const DAEMON_GRACE_MS: number;
|
|
9
|
+
/** One missed probe is not a verdict. The probe runs on beagle's own event
|
|
10
|
+
* loop, so any stall here (GC, a large /api/desktop body, an npm child) fires
|
|
11
|
+
* its timer before the queued `connect` callback runs, and a healthy daemon
|
|
12
|
+
* reads as gone — 814 times on mac-dev while daemon pid 75338 never changed.
|
|
13
|
+
* Only a streak of misses that also spans real time is believed. */
|
|
14
|
+
export declare const DAEMON_MISS_STREAK = 3;
|
|
15
|
+
export declare const DAEMON_MISS_WINDOW_MS = 5000;
|
|
9
16
|
export interface SwitchableHostOptions {
|
|
10
17
|
dataDir: string;
|
|
11
18
|
/** Build a fresh embedded host. Called on first start and on every fallback,
|
|
@@ -37,6 +44,9 @@ export declare class SwitchablePeerHost implements PeerHost {
|
|
|
37
44
|
get kind(): PeerHost["kind"];
|
|
38
45
|
get hasVirtualLan(): boolean;
|
|
39
46
|
get state(): HandoffState;
|
|
47
|
+
/** The daemon's data dir (socket and pidfile), for callers that need to
|
|
48
|
+
* watch a daemon restart through. */
|
|
49
|
+
get dataDir(): string;
|
|
40
50
|
/** Seconds left before `releasing` gives up; 0 when not releasing. */
|
|
41
51
|
get releaseSecondsLeft(): number;
|
|
42
52
|
call(req: IpcRequest): Promise<IpcResponse>;
|
package/dist/lan-handoff.js
CHANGED
|
@@ -23,6 +23,13 @@ const RELEASE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
|
23
23
|
* gives up on it and takes the identity back. `agentnet restart` and a
|
|
24
24
|
* launchd/systemd kickstart are both back well inside this. */
|
|
25
25
|
export const DAEMON_GRACE_MS = 60 * 1000;
|
|
26
|
+
/** One missed probe is not a verdict. The probe runs on beagle's own event
|
|
27
|
+
* loop, so any stall here (GC, a large /api/desktop body, an npm child) fires
|
|
28
|
+
* its timer before the queued `connect` callback runs, and a healthy daemon
|
|
29
|
+
* reads as gone — 814 times on mac-dev while daemon pid 75338 never changed.
|
|
30
|
+
* Only a streak of misses that also spans real time is believed. */
|
|
31
|
+
export const DAEMON_MISS_STREAK = 3;
|
|
32
|
+
export const DAEMON_MISS_WINDOW_MS = 5000;
|
|
26
33
|
/**
|
|
27
34
|
* A PeerHost whose backend can change under it.
|
|
28
35
|
*
|
|
@@ -59,6 +66,11 @@ export class SwitchablePeerHost {
|
|
|
59
66
|
get state() {
|
|
60
67
|
return this.#state;
|
|
61
68
|
}
|
|
69
|
+
/** The daemon's data dir (socket and pidfile), for callers that need to
|
|
70
|
+
* watch a daemon restart through. */
|
|
71
|
+
get dataDir() {
|
|
72
|
+
return this.#opts.dataDir;
|
|
73
|
+
}
|
|
62
74
|
/** Seconds left before `releasing` gives up; 0 when not releasing. */
|
|
63
75
|
get releaseSecondsLeft() {
|
|
64
76
|
if (this.#state !== "releasing")
|
|
@@ -130,14 +142,33 @@ export class SwitchablePeerHost {
|
|
|
130
142
|
}, WATCH_INTERVAL_MS);
|
|
131
143
|
this.#timer.unref?.();
|
|
132
144
|
}
|
|
145
|
+
/** Consecutive missed probes; 0 the moment the daemon answers. */
|
|
146
|
+
#missStreak = 0;
|
|
147
|
+
/** A probe can take its full 2 s timeout against a 1.5 s watch interval.
|
|
148
|
+
* Overlapping ticks would each act on the other's half-made decision. */
|
|
149
|
+
#ticking = false;
|
|
133
150
|
async #tick() {
|
|
134
|
-
if (this.#busy)
|
|
151
|
+
if (this.#busy || this.#ticking)
|
|
135
152
|
return;
|
|
153
|
+
this.#ticking = true;
|
|
154
|
+
try {
|
|
155
|
+
await this.#step();
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
this.#ticking = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async #step() {
|
|
136
162
|
const daemonUp = await this.#probe(this.#opts.dataDir);
|
|
137
|
-
if (daemonUp)
|
|
163
|
+
if (daemonUp) {
|
|
138
164
|
this.#daemonMissingSince = 0;
|
|
139
|
-
|
|
140
|
-
|
|
165
|
+
this.#missStreak = 0;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
if (this.#daemonMissingSince === 0)
|
|
169
|
+
this.#daemonMissingSince = Date.now();
|
|
170
|
+
this.#missStreak++;
|
|
171
|
+
}
|
|
141
172
|
// A tick can await; make sure nothing started while we were probing.
|
|
142
173
|
if (this.#busy)
|
|
143
174
|
return;
|
|
@@ -152,7 +183,14 @@ export class SwitchablePeerHost {
|
|
|
152
183
|
// and rm the pidfile.
|
|
153
184
|
if (this.#opts.force === "daemon") {
|
|
154
185
|
if (this.#state === "daemon" && !daemonUp) {
|
|
155
|
-
|
|
186
|
+
// Keep serving until the absence is confirmed. Every call made during
|
|
187
|
+
// a false `waiting` failed with "the daemon is restarting", which the
|
|
188
|
+
// UI showed as an empty friend list and a hung /api/desktop.
|
|
189
|
+
const missedMs = Date.now() - this.#daemonMissingSince;
|
|
190
|
+
if (this.#missStreak < DAEMON_MISS_STREAK || missedMs < DAEMON_MISS_WINDOW_MS)
|
|
191
|
+
return;
|
|
192
|
+
this.#log.info(`the daemon went away — waiting for it to come back (forced daemon backend; ` +
|
|
193
|
+
`${this.#missStreak} probes missed over ${(missedMs / 1000).toFixed(1)} s)`);
|
|
156
194
|
const old = this.#inner;
|
|
157
195
|
this.#inner = null;
|
|
158
196
|
this.#setState("waiting");
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IpcRequest, type IpcResponse } from "./ipc.js";
|
|
2
2
|
/** How this copy of beagle got onto disk — decides what "update" can do.
|
|
3
3
|
* npx cache paths contain _npx AND node_modules, so test _npx first. */
|
|
4
4
|
export declare function installMode(): "global" | "npx" | "dev" | "unknown";
|
|
@@ -32,6 +32,8 @@ export interface BeagleServerOptions {
|
|
|
32
32
|
readonly hasVirtualLan: boolean;
|
|
33
33
|
readonly state: string;
|
|
34
34
|
readonly releaseSecondsLeft: number;
|
|
35
|
+
/** The daemon's socket/pidfile dir, so "Update lan" can wait for a new pid. */
|
|
36
|
+
readonly dataDir?: string;
|
|
35
37
|
armForDaemon: () => Promise<{
|
|
36
38
|
state: string;
|
|
37
39
|
command: string;
|
package/dist/server.js
CHANGED
|
@@ -19,6 +19,7 @@ import { fileURLToPath } from "node:url";
|
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { INSTALL_COMMAND as LAN_INSTALL_COMMAND } from "./lan-handoff.js";
|
|
21
21
|
import yaml from "js-yaml";
|
|
22
|
+
import { probeDaemon } from "./ipc.js";
|
|
22
23
|
import { DEFAULT_EXITS } from "./exits.js";
|
|
23
24
|
import * as peerAddr from "@decentnetwork/peer";
|
|
24
25
|
import { bridgeRoster } from "./bridge-roster.js";
|
|
@@ -513,10 +514,45 @@ async function npmQuery(args) {
|
|
|
513
514
|
}
|
|
514
515
|
});
|
|
515
516
|
}
|
|
516
|
-
/** Ask the
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
|
|
517
|
+
/** Ask the agentnet daemon to restart onto the lan that was just installed,
|
|
518
|
+
* and report success only when a NEW daemon answers. `agentnet restart`
|
|
519
|
+
* exiting 0 means the request was accepted, nothing more: under launchd a
|
|
520
|
+
* lan ≥ 0.1.302 daemon exits and launchd starts the new code, while an older
|
|
521
|
+
* one re-execs outside launchd — which left mac-dev's KeepAlive job failing
|
|
522
|
+
* every ~10 s. Either way the proof is a different pid that accepts a
|
|
523
|
+
* connection. Without a data dir there is nothing to watch, so the old
|
|
524
|
+
* answer stands. */
|
|
525
|
+
async function agentnetRestart(dataDir) {
|
|
526
|
+
const pidBefore = dataDir ? readDaemonPid(dataDir) : "";
|
|
527
|
+
const r = await requestAgentnetRestart();
|
|
528
|
+
if (!r.ok || !dataDir)
|
|
529
|
+
return r;
|
|
530
|
+
return waitForNewDaemon(dataDir, pidBefore);
|
|
531
|
+
}
|
|
532
|
+
const readDaemonPid = (dataDir) => {
|
|
533
|
+
try {
|
|
534
|
+
return readFileSync(join(dataDir, "daemon.pid"), "utf-8").trim();
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
async function waitForNewDaemon(dataDir, pidBefore) {
|
|
541
|
+
const deadline = Date.now() + 60_000;
|
|
542
|
+
while (Date.now() < deadline) {
|
|
543
|
+
const pid = readDaemonPid(dataDir);
|
|
544
|
+
if (pid && pid !== pidBefore && (await probeDaemon(dataDir)))
|
|
545
|
+
return { ok: true };
|
|
546
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
ok: false,
|
|
550
|
+
error: `agentnet accepted the restart, but no new daemon answered within 60 s (pid still ${readDaemonPid(dataDir) || "none"})`,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/** Run `agentnet restart`: the PATH binary, then the global install's CLI entry
|
|
554
|
+
* directly (pm2's PATH often lacks the nvm shims). */
|
|
555
|
+
async function requestAgentnetRestart() {
|
|
520
556
|
const { spawn } = await import("node:child_process");
|
|
521
557
|
const cliJs = join(dirname(process.execPath), "..", "lib", "node_modules", "@decentnetwork", "lan", "dist", "cli", "index.js");
|
|
522
558
|
const attempts = [
|
|
@@ -1826,7 +1862,7 @@ export function startBeagleServer(opts) {
|
|
|
1826
1862
|
sendJson(res, 502, inst);
|
|
1827
1863
|
return;
|
|
1828
1864
|
}
|
|
1829
|
-
const rr = await agentnetRestart();
|
|
1865
|
+
const rr = await agentnetRestart(opts.lanHost?.dataDir);
|
|
1830
1866
|
updateCache = null;
|
|
1831
1867
|
sendJson(res, 200, { ok: true, restarted: rr.ok, ...(rr.ok ? {} : { note: rr.error }) });
|
|
1832
1868
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.98",
|
|
4
4
|
"description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"yargs": "^17.7.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
38
|
+
"@decentnetwork/beagle-ui": "^0.2.14",
|
|
39
39
|
"@types/js-yaml": "^4.0.9",
|
|
40
40
|
"@types/node": "^20.11.0",
|
|
41
41
|
"@types/yargs": "^17.0.32",
|