@decentnetwork/beagle-ui 0.2.25 → 0.2.27
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/beagle-ui.js +349 -57
- package/package.json +1 -1
package/dist/beagle-ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const BEAGLE_UI_VERSION = "0.2.
|
|
1
|
+
export const BEAGLE_UI_VERSION = "0.2.27";
|
|
2
2
|
// src/icons.jsx
|
|
3
3
|
var ICON_PATHS = {
|
|
4
4
|
// ---- tab bar (the four must feel like one set) ----
|
|
@@ -66,8 +66,8 @@ var ICON_PATHS = {
|
|
|
66
66
|
speaker: '<path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M16 9a3 3 0 0 1 0 6"/><path d="M19 7a6 6 0 0 1 0 10"/>',
|
|
67
67
|
micFill: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>'
|
|
68
68
|
};
|
|
69
|
-
function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
|
|
70
|
-
const d = ICON_PATHS[name];
|
|
69
|
+
function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style, svgPath }) {
|
|
70
|
+
const d = svgPath || ICON_PATHS[name];
|
|
71
71
|
if (!d)
|
|
72
72
|
return null;
|
|
73
73
|
return /* @__PURE__ */ React.createElement(
|
|
@@ -111,7 +111,10 @@ Object.assign(ICON_PATHS, {
|
|
|
111
111
|
zap: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
|
|
112
112
|
clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',
|
|
113
113
|
bellOff: '<path d="M8.7 3.4A6 6 0 0 1 18 8.5c0 3 .5 5 1.1 6.3M6.1 6.1A6 6 0 0 0 6 8.5c0 6.5-2.5 8-2.5 8H16"/><path d="M13.7 20.5a2 2 0 0 1-3.4 0"/><path d="m3 3 18 18"/>',
|
|
114
|
-
plug: '<path d="M9 2v6M15 2v6"/><path d="M7 8h10v3a5 5 0 0 1-10 0V8Z"/><path d="M12 16v6"/>'
|
|
114
|
+
plug: '<path d="M9 2v6M15 2v6"/><path d="M7 8h10v3a5 5 0 0 1-10 0V8Z"/><path d="M12 16v6"/>',
|
|
115
|
+
// A form with a filled-in field: the glyph for "Fill with Beagle" and any
|
|
116
|
+
// other host app that puts your identity into someone else's page.
|
|
117
|
+
form: '<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h5"/><path d="M8 16h3"/><path d="m14.5 17.5 1.6 1.6 3.4-3.6"/>'
|
|
115
118
|
});
|
|
116
119
|
|
|
117
120
|
// src/ui-host.jsx
|
|
@@ -121,8 +124,16 @@ var hostPrefersRtcFile = false;
|
|
|
121
124
|
var hostHasKeyBackup = false;
|
|
122
125
|
var hostEnsWrite = true;
|
|
123
126
|
var hostBrandHref = "";
|
|
127
|
+
var hostTabs = null;
|
|
128
|
+
var hostHideApps = null;
|
|
129
|
+
var hostApps = null;
|
|
130
|
+
var hostProfileSections = null;
|
|
124
131
|
function setUiHost(host) {
|
|
125
132
|
H = host || {};
|
|
133
|
+
hostTabs = Array.isArray(H.hostTabs) ? H.hostTabs : null;
|
|
134
|
+
hostHideApps = Array.isArray(H.hideApps) ? H.hideApps : null;
|
|
135
|
+
hostApps = Array.isArray(H.hostApps) ? H.hostApps : null;
|
|
136
|
+
hostProfileSections = Array.isArray(H.hostProfileSections) ? H.hostProfileSections : null;
|
|
126
137
|
hostNeedsWelcome = !!H.hostNeedsWelcome;
|
|
127
138
|
hostPrefersRtcFile = !!H.hostPrefersRtcFile;
|
|
128
139
|
hostHasKeyBackup = !!H.hostHasKeyBackup;
|
|
@@ -650,6 +661,18 @@ var actions_default = {
|
|
|
650
661
|
confirm: "list",
|
|
651
662
|
scope: "assistant"
|
|
652
663
|
},
|
|
664
|
+
{
|
|
665
|
+
name: "nodes-refresh",
|
|
666
|
+
usage: "/nodes refresh",
|
|
667
|
+
summary: "Look for friends that are provably service peers (the shipped exit table, this machine's dora roster) and offer to reclassify them. Nothing changes until you tick and confirm.",
|
|
668
|
+
params: {
|
|
669
|
+
type: "object",
|
|
670
|
+
properties: {},
|
|
671
|
+
required: []
|
|
672
|
+
},
|
|
673
|
+
confirm: "list",
|
|
674
|
+
scope: "assistant"
|
|
675
|
+
},
|
|
653
676
|
{
|
|
654
677
|
name: "find",
|
|
655
678
|
usage: "/find <text>",
|
|
@@ -824,7 +847,8 @@ var actions_default = {
|
|
|
824
847
|
file: "service-peers.json",
|
|
825
848
|
loader: "loadServicePeers()",
|
|
826
849
|
rule: "A peer is a service peer if and only if its userid is in this list. The Assistant never infers it from a name or from having no messages. /friends excludes them entirely; /nodes is only them.",
|
|
827
|
-
later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1."
|
|
850
|
+
later: "A dora / ENS / bridge / directory service returns the same shape; the bundled file is v1.",
|
|
851
|
+
refresh: "/nodes refresh asks GET /api/assistant/service-candidates for evidence-backed candidates and writes the ticked ones to this machine's overlay (POST /api/assistant/service-peers, stored at <configDir>/service-peers-local.json). The shipped list is never edited at runtime; the overlay wins on the same userid."
|
|
828
852
|
}
|
|
829
853
|
};
|
|
830
854
|
|
|
@@ -983,6 +1007,8 @@ function parseCommand(line) {
|
|
|
983
1007
|
const sub = (parts[1] || "").toLowerCase();
|
|
984
1008
|
if (sub === "list" || sub === "remove")
|
|
985
1009
|
return { ok: true, name: `${head}-${sub}`, args: parts.slice(2) };
|
|
1010
|
+
if (head === "nodes" && sub === "refresh")
|
|
1011
|
+
return { ok: true, name: "nodes-refresh", args: parts.slice(2) };
|
|
986
1012
|
return { ok: false, name: head, args: parts.slice(1), error: `${head}-subcommand` };
|
|
987
1013
|
}
|
|
988
1014
|
return { ok: true, name: head, args: parts.slice(1) };
|
|
@@ -1020,6 +1046,19 @@ function servicePeerMap(list) {
|
|
|
1020
1046
|
}
|
|
1021
1047
|
return map;
|
|
1022
1048
|
}
|
|
1049
|
+
function mergeServicePeers(bundled, local) {
|
|
1050
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1051
|
+
for (const p of bundled && bundled.peers || [])
|
|
1052
|
+
if (p && p.userid)
|
|
1053
|
+
byId.set(p.userid, { ...p, source: "app" });
|
|
1054
|
+
for (const p of local && local.peers || [])
|
|
1055
|
+
if (p && p.userid)
|
|
1056
|
+
byId.set(p.userid, { ...p, source: "local" });
|
|
1057
|
+
return { version: 1, source: "merged", peers: [...byId.values()] };
|
|
1058
|
+
}
|
|
1059
|
+
function newCandidates(candidates, svcMap) {
|
|
1060
|
+
return (candidates != null ? candidates : []).filter((c) => c && c.userid && !(svcMap && svcMap.has(c.userid)));
|
|
1061
|
+
}
|
|
1023
1062
|
function partitionPeers(friends, svcMap) {
|
|
1024
1063
|
const people = [];
|
|
1025
1064
|
const nodes = [];
|
|
@@ -1104,8 +1143,13 @@ function resolveFriend(friends, token) {
|
|
|
1104
1143
|
}
|
|
1105
1144
|
|
|
1106
1145
|
// src/assistant.jsx
|
|
1107
|
-
function loadServicePeers() {
|
|
1108
|
-
|
|
1146
|
+
async function loadServicePeers() {
|
|
1147
|
+
try {
|
|
1148
|
+
const local = await fetch("/api/assistant/service-peers").then((r) => r.json());
|
|
1149
|
+
return mergeServicePeers(service_peers_default, local);
|
|
1150
|
+
} catch (e) {
|
|
1151
|
+
return service_peers_default;
|
|
1152
|
+
}
|
|
1109
1153
|
}
|
|
1110
1154
|
var ASSISTANT_PEER = {
|
|
1111
1155
|
id: ASSISTANT_ID,
|
|
@@ -1161,10 +1205,12 @@ function helpText(lang) {
|
|
|
1161
1205
|
const tail = lang === "zh" ? `\u7B5B\u9009\u6761\u4EF6\uFF08\u53EF\u7EC4\u5408\uFF0C\u53D6\u5E76\u96C6\uFF09\uFF1A
|
|
1162
1206
|
${filters}
|
|
1163
1207
|
|
|
1164
|
-
|
|
1208
|
+
\`/rename\`\u3001\`/mute\`\u3001\`/find\` \u5BF9\u597D\u53CB\u548C\u670D\u52A1\u8282\u70B9\u90FD\u9002\u7528\uFF1B\u53EA\u6709\u5217\u8868\u548C\u5220\u9664\u5206\u6210\u4E24\u8FB9\u3002
|
|
1209
|
+
\u6700\u8FD1 ${RECENT_DAYS} \u5929\u5185\u6DFB\u52A0\u7684\u597D\u53CB\u4E0D\u4F1A\u88AB"\u4ECE\u672A/\u6C89\u9ED8"\u7C7B\u7B5B\u9009\u9009\u4E2D\uFF0C\u5E76\u4F1A\u5217\u51FA\u88AB\u8DF3\u8FC7\u7684\u540D\u5B57\u3002\u5220\u9664\u6C38\u8FDC\u9700\u8981\u5148\u786E\u8BA4\uFF0C\u53D6\u6D88\u52FE\u9009\u5373\u53EF\u4FDD\u7559\u3002` : `Filters (combine them; a friend matching any one is listed):
|
|
1165
1210
|
${filters}
|
|
1166
1211
|
|
|
1167
|
-
|
|
1212
|
+
\`/rename\`, \`/mute\` and \`/find\` work on friends and service peers alike \u2014 only listing and removing are split in two.
|
|
1213
|
+
Friends added in the last ${RECENT_DAYS} days are never matched by the "never/silent" filters, and the ones held back are named. Removal always asks first: untick anyone you want to keep.`;
|
|
1168
1214
|
return `${intro}
|
|
1169
1215
|
${cmds}
|
|
1170
1216
|
|
|
@@ -1178,8 +1224,9 @@ var asMsg = (from, text, extra) => Object.assign({
|
|
|
1178
1224
|
time: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
|
1179
1225
|
text
|
|
1180
1226
|
}, extra);
|
|
1181
|
-
function AssistantPanel({ m, T, lang, onRemove }) {
|
|
1227
|
+
function AssistantPanel({ m, T, lang, onRemove, onAdopt }) {
|
|
1182
1228
|
const rows = m.assistant.rows || [];
|
|
1229
|
+
const adopt = m.assistant.kind === "adopt";
|
|
1183
1230
|
const [sel, setSel] = React.useState(() => new Set(rows.map((r) => r.userid)));
|
|
1184
1231
|
const [busy, setBusy] = React.useState(false);
|
|
1185
1232
|
const [done, setDone] = React.useState(null);
|
|
@@ -1190,7 +1237,7 @@ function AssistantPanel({ m, T, lang, onRemove }) {
|
|
|
1190
1237
|
n.has(id) ? n.delete(id) : n.add(id);
|
|
1191
1238
|
return n;
|
|
1192
1239
|
});
|
|
1193
|
-
const label = lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
1240
|
+
const label = adopt ? lang === "zh" ? `\u52A0\u5165 ${sel.size} \u4E2A` : `Add ${sel.size} as ${sel.size === 1 ? "a node" : "nodes"}` : lang === "zh" ? `\u5220\u9664 ${sel.size} \u4F4D` : `Remove ${sel.size}`;
|
|
1194
1241
|
return /* @__PURE__ */ React.createElement("div", { style: { marginTop: 8, border: "1px solid var(--line)", borderRadius: 10, overflow: "hidden" } }, rows.map((r) => /* @__PURE__ */ React.createElement("label", { key: r.userid, style: {
|
|
1195
1242
|
display: "flex",
|
|
1196
1243
|
alignItems: "center",
|
|
@@ -1207,12 +1254,14 @@ function AssistantPanel({ m, T, lang, onRemove }) {
|
|
|
1207
1254
|
disabled: !!done || busy,
|
|
1208
1255
|
onChange: () => toggle(r.userid)
|
|
1209
1256
|
}
|
|
1210
|
-
), /* @__PURE__ */ React.createElement("span", { style: { flex: 1 } }, nameOf(r)), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, r.userid.slice(0, 8), "\u2026"))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 10px" } }, done ? /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12.5, color: "var(--faint)" } }, done) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !sel.size, onClick: () => {
|
|
1257
|
+
), /* @__PURE__ */ React.createElement("span", { style: { flex: 1 } }, nameOf(r), r.why && /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)", fontSize: 11.5 } }, ` \u2014 ${r.why}`)), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, r.userid.slice(0, 8), "\u2026"))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 10px" } }, done ? /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12.5, color: "var(--faint)" } }, done) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: adopt ? "solid" : "danger", disabled: busy || !sel.size, onClick: () => {
|
|
1211
1258
|
setBusy(true);
|
|
1212
|
-
|
|
1213
|
-
|
|
1259
|
+
const picked = rows.filter((r) => sel.has(r.userid));
|
|
1260
|
+
const run = adopt ? onAdopt(picked) : onRemove([...sel]);
|
|
1261
|
+
run.then((r) => {
|
|
1262
|
+
setDone(adopt ? lang === "zh" ? `\u5DF2\u52A0\u5165 ${r.added} \u4E2A\u670D\u52A1\u8282\u70B9` : `Added ${r.added} as service peers` : lang === "zh" ? `\u5DF2\u5220\u9664 ${r.removed} \u4F4D${r.failed ? `\uFF0C${r.failed} \u4F4D\u5931\u8D25` : ""}` : `Removed ${r.removed}${r.failed ? `, ${r.failed} failed` : ""}`);
|
|
1214
1263
|
}).finally(() => setBusy(false));
|
|
1215
|
-
} }, busy ? T.working || "Working\u2026" : label), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "var(--faint)" } }, lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u53EF\u4FDD\u7559" : "Untick anyone you want to keep"))));
|
|
1264
|
+
} }, busy ? T.working || "Working\u2026" : label), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 12, color: "var(--faint)" } }, adopt ? lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u5373\u4FDD\u6301\u4E3A\u597D\u53CB" : "Untick any you want to keep as a friend" : lang === "zh" ? "\u53D6\u6D88\u52FE\u9009\u53EF\u4FDD\u7559" : "Untick anyone you want to keep"))));
|
|
1216
1265
|
}
|
|
1217
1266
|
function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, onReloadFriends }) {
|
|
1218
1267
|
const [thread, setThread] = React.useState(() => [{ day: (/* @__PURE__ */ new Date()).toLocaleDateString() }, asMsg("them", helpText(lang))]);
|
|
@@ -1222,11 +1271,13 @@ function useAssistant({ lang, T, muted, onToggleMute, sound, flash, setAlert, on
|
|
|
1222
1271
|
}, [lang]);
|
|
1223
1272
|
const fetchFriends = async () => {
|
|
1224
1273
|
const r = await fetch("/api/assistant/friends").then((x) => x.json());
|
|
1225
|
-
const
|
|
1274
|
+
const svcMap = servicePeerMap(await loadServicePeers());
|
|
1275
|
+
const { people, nodes } = partitionPeers(r.friends || [], svcMap);
|
|
1226
1276
|
return {
|
|
1227
1277
|
friends: people,
|
|
1228
1278
|
nodes,
|
|
1229
1279
|
all: r.friends || [],
|
|
1280
|
+
svcMap,
|
|
1230
1281
|
historySince: r.historySince || null,
|
|
1231
1282
|
now: r.now || Date.now(),
|
|
1232
1283
|
ver: r.beagleVer || ""
|
|
@@ -1293,6 +1344,33 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1293
1344
|
- Waiting to be accepted: ${pending}
|
|
1294
1345
|
- History on this device since: ${fmtDate(d.historySince, lang)}
|
|
1295
1346
|
- Version: ${d.ver}`));
|
|
1347
|
+
}
|
|
1348
|
+
case "nodes-refresh": {
|
|
1349
|
+
const d = await fetchFriends();
|
|
1350
|
+
const r = await fetch("/api/assistant/service-candidates").then((x) => x.json());
|
|
1351
|
+
const fresh = newCandidates(r.candidates || [], d.svcMap);
|
|
1352
|
+
const src = r.sources || {};
|
|
1353
|
+
const where = lang === "zh" ? `\u67E5\u8BE2\u6765\u6E90\uFF1A\u5185\u7F6E\u51FA\u53E3\u8868\uFF08${src.exits || 0} \u6761\uFF09${src.dora == null ? "\uFF0C\u672C\u673A\u4E0D\u662F dora" : `\uFF0C\u672C\u673A dora \u540D\u518C\uFF08${src.dora} \u6761\uFF09`}` : `Looked at: the shipped exit table (${src.exits || 0})${src.dora == null ? ", and this machine is not a dora" : `, and this machine's dora roster (${src.dora})`}`;
|
|
1354
|
+
if (!fresh.length) {
|
|
1355
|
+
return append(asMsg("them", lang === "zh" ? `**\u6CA1\u6709\u65B0\u7684\u670D\u52A1\u8282\u70B9**
|
|
1356
|
+
|
|
1357
|
+
\u73B0\u6709 ${d.nodes.length} \u4E2A\u5DF2\u8BC6\u522B\u3002${where}\u3002
|
|
1358
|
+
|
|
1359
|
+
*\u53EA\u91C7\u7528\u8BC1\u636E\uFF0C\u4E0D\u9760\u540D\u5B57\u731C\u6D4B\u3002*` : `**Nothing new**
|
|
1360
|
+
|
|
1361
|
+
${d.nodes.length} service peers are already classified. ${where}.
|
|
1362
|
+
|
|
1363
|
+
*Evidence only \u2014 names are never guessed from.*`));
|
|
1364
|
+
}
|
|
1365
|
+
const title = lang === "zh" ? `**\u53D1\u73B0 ${fresh.length} \u4E2A\u53EF\u4EE5\u6807\u4E3A\u670D\u52A1\u8282\u70B9\u7684\u597D\u53CB** \u2014 \u52FE\u9009\u540E\u624D\u4F1A\u751F\u6548
|
|
1366
|
+
|
|
1367
|
+
${where}\u3002` : `**${fresh.length === 1 ? "1 friend looks" : `${fresh.length} friends look`} like service peers** \u2014 nothing changes until you tick and confirm
|
|
1368
|
+
|
|
1369
|
+
${where}.`;
|
|
1370
|
+
const lines = fresh.map((c) => `- **${c.name}** \`${c.kind}${c.role ? " \xB7 " + c.role : ""}\` \u2014 ${c.why}`).join("\n");
|
|
1371
|
+
return append(asMsg("them", `${title}
|
|
1372
|
+
|
|
1373
|
+
${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
1296
1374
|
}
|
|
1297
1375
|
case "nodes-list":
|
|
1298
1376
|
case "friends-list": {
|
|
@@ -1382,6 +1460,24 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1382
1460
|
return append(asMsg("them", lang === "zh" ? `\u51FA\u9519\u4E86\uFF1A${e && e.message || e}` : `That failed: ${e && e.message || e}`));
|
|
1383
1461
|
}
|
|
1384
1462
|
}, [append, lang, muted, onToggleMute, setAlert, onReloadFriends]);
|
|
1463
|
+
const adoptServicePeers = React.useCallback(async (rows) => {
|
|
1464
|
+
const add = rows.map((r) => ({ userid: r.userid, name: r.name, kind: r.kind, role: r.role }));
|
|
1465
|
+
let added = 0;
|
|
1466
|
+
try {
|
|
1467
|
+
const r = await fetch("/api/assistant/service-peers", {
|
|
1468
|
+
method: "POST",
|
|
1469
|
+
headers: { "content-type": "application/json" },
|
|
1470
|
+
body: JSON.stringify({ add })
|
|
1471
|
+
}).then((x) => x.json());
|
|
1472
|
+
added = r && r.ok ? add.length : 0;
|
|
1473
|
+
} catch (e) {
|
|
1474
|
+
added = 0;
|
|
1475
|
+
}
|
|
1476
|
+
if (onReloadFriends)
|
|
1477
|
+
onReloadFriends();
|
|
1478
|
+
append(asMsg("them", added ? lang === "zh" ? `\u5DF2\u628A ${added} \u4F4D\u6807\u4E3A\u670D\u52A1\u8282\u70B9\uFF0C\u5B83\u4EEC\u4E0D\u518D\u51FA\u73B0\u5728 \`/friends\` \u4E2D\u3002` : `${added} now count as service peers \u2014 they no longer appear in \`/friends\`.` : lang === "zh" ? "\u5199\u5165\u5931\u8D25\uFF0C\u6CA1\u6709\u4EFB\u4F55\u6539\u52A8\u3002" : "That write failed; nothing changed."));
|
|
1479
|
+
return { added };
|
|
1480
|
+
}, [append, lang, onReloadFriends]);
|
|
1385
1481
|
const removeFriends = React.useCallback(async (ids) => {
|
|
1386
1482
|
let removed = 0;
|
|
1387
1483
|
const failed = [];
|
|
@@ -1401,7 +1497,7 @@ ${cands.map((c) => `- ${nameOf(c)}`).join("\n")}`;
|
|
|
1401
1497
|
append(asMsg("them", failed.length ? lang === "zh" ? `\u5DF2\u5220\u9664 ${removed} \u4F4D\uFF0C${failed.length} \u4F4D\u5931\u8D25\u3002` : `Removed ${removed}, ${failed.length} failed.` : lang === "zh" ? `\u5DF2\u5220\u9664 ${removed} \u4F4D\u597D\u53CB\u3002` : `Removed ${removed} friend${removed === 1 ? "" : "s"}.`));
|
|
1402
1498
|
return { removed, failed: failed.length };
|
|
1403
1499
|
}, [append, lang, onReloadFriends]);
|
|
1404
|
-
return { thread, handle, removeFriends };
|
|
1500
|
+
return { thread, handle, removeFriends, adoptServicePeers };
|
|
1405
1501
|
}
|
|
1406
1502
|
|
|
1407
1503
|
// src/chat.jsx
|
|
@@ -2210,7 +2306,7 @@ function DkChatForm({ form, submitted, peer, onSubmit }) {
|
|
|
2210
2306
|
return null;
|
|
2211
2307
|
}), err && /* @__PURE__ */ React.createElement("div", { style: { color: "var(--danger, #ff6b6b)", fontSize: 11.5, fontFamily: "var(--mono)" } }, err));
|
|
2212
2308
|
}
|
|
2213
|
-
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
2309
|
+
function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetryMsg, onReveal, onOpenFile, onCall, onFormSubmit, onNameCard, onAssistantRemove, onAssistantAdopt, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
2214
2310
|
const mine = m.from === "me";
|
|
2215
2311
|
const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
|
|
2216
2312
|
const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
|
|
@@ -2539,7 +2635,7 @@ function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetry
|
|
|
2539
2635
|
letterSpacing: -0.1,
|
|
2540
2636
|
wordBreak: "break-word"
|
|
2541
2637
|
} }, /* @__PURE__ */ React.createElement(MarkdownText, { text: read.text, onNameCard })));
|
|
2542
|
-
})(), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), m.assistant && onAssistantRemove && /* @__PURE__ */ React.createElement(AssistantPanel, { m, T, lang, onRemove: onAssistantRemove }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), (() => {
|
|
2638
|
+
})(), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), m.assistant && onAssistantRemove && /* @__PURE__ */ React.createElement(AssistantPanel, { m, T, lang, onRemove: onAssistantRemove, onAdopt: onAssistantAdopt }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), (() => {
|
|
2543
2639
|
const sentDone = mine && m.status && m.status !== "queued" && m.status !== "sending" && m.status !== "failed";
|
|
2544
2640
|
const via = !mine ? m.via : sentDone ? m.via === "offline" ? "offline" : "online" : null;
|
|
2545
2641
|
if (!via)
|
|
@@ -2584,7 +2680,7 @@ function Msg({ m, peer, T, lang, onTheater, onDelete, onCancel, onRetry, onRetry
|
|
|
2584
2680
|
) : mine && m.via === "offline" && m.status !== "queued" && m.status !== "sending" ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "#f59e0b", title: T.viaOutOffline || "posted to the offline relay \u2014 they get it when they come online" }) : 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)" })))
|
|
2585
2681
|
);
|
|
2586
2682
|
}
|
|
2587
|
-
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, muted, onToggleMute, phone, onBack }) {
|
|
2683
|
+
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted, onToggleMute, phone, onBack }) {
|
|
2588
2684
|
const scrollRef = React.useRef(null);
|
|
2589
2685
|
const prependRef = React.useRef(null);
|
|
2590
2686
|
const [earlierBusy, setEarlierBusy] = React.useState(false);
|
|
@@ -2947,7 +3043,7 @@ function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, on
|
|
|
2947
3043
|
// The internal id is not an address anyone can use and not a name
|
|
2948
3044
|
// anyone chose; saying what this row IS is more use than showing it.
|
|
2949
3045
|
/* @__PURE__ */ React.createElement("div", { style: { marginTop: 2, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, T && T.assistantSub || "Local \xB7 APP")
|
|
2950
|
-
) : 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(
|
|
3046
|
+
) : 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)), onOpenNet && /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), onOpenNet && /* @__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(
|
|
2951
3047
|
Mono,
|
|
2952
3048
|
{
|
|
2953
3049
|
size: 11.5,
|
|
@@ -2982,7 +3078,7 @@ ${peer.address}`
|
|
|
2982
3078
|
} }), phone && onSendRtcFile && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => {
|
|
2983
3079
|
setMenu(false);
|
|
2984
3080
|
rtcFileRef.current && rtcFileRef.current.click();
|
|
2985
|
-
} }), phone && !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
|
|
3081
|
+
} }), phone && !peer.app && onOpenNet && /* @__PURE__ */ React.createElement(MenuItem, { icon: "network", label: T && T.openNet || "Network view", onClick: () => {
|
|
2986
3082
|
setMenu(false);
|
|
2987
3083
|
onOpenNet(peer);
|
|
2988
3084
|
} }), !peer.app && /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
|
|
@@ -3017,6 +3113,7 @@ ${peer.address}`
|
|
|
3017
3113
|
isGroup,
|
|
3018
3114
|
onTheater: setTheater,
|
|
3019
3115
|
onAssistantRemove,
|
|
3116
|
+
onAssistantAdopt,
|
|
3020
3117
|
onDelete: (id) => doDelete([id]),
|
|
3021
3118
|
onCancel: doCancel,
|
|
3022
3119
|
onRetry: doRetry,
|
|
@@ -3176,12 +3273,12 @@ function MenuItem({ icon, label, onClick, danger }) {
|
|
|
3176
3273
|
function ChatEmpty({ T }) {
|
|
3177
3274
|
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));
|
|
3178
3275
|
}
|
|
3179
|
-
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
3276
|
+
function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, prefillAddr, onPrefillConsumed, muted, onToggleMute, phone, onBack }) {
|
|
3180
3277
|
const peer = peers.find((p) => p.id === activeId);
|
|
3181
3278
|
if (phone) {
|
|
3182
|
-
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, 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 });
|
|
3279
|
+
return peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, 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 });
|
|
3183
3280
|
}
|
|
3184
|
-
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, hasEarlier, onLoadEarlier, onAssistantRemove, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
3281
|
+
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, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
3185
3282
|
}
|
|
3186
3283
|
Object.assign(window, { ChatTab });
|
|
3187
3284
|
|
|
@@ -3956,7 +4053,7 @@ function DkAvatarSheet({ T, me, zh, onSet, onClose }) {
|
|
|
3956
4053
|
return /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) });
|
|
3957
4054
|
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)));
|
|
3958
4055
|
}
|
|
3959
|
-
function ProfileTab({ T, me, onEdit, t, setTweak, onSetAvatar }) {
|
|
4056
|
+
function ProfileTab({ T, me, onEdit, t, setTweak, onSetAvatar, sections, sectionProps }) {
|
|
3960
4057
|
var _a;
|
|
3961
4058
|
const [avatarSheet, setAvatarSheet] = React.useState(false);
|
|
3962
4059
|
const [qr, setQr] = React.useState(null);
|
|
@@ -3988,12 +4085,33 @@ function ProfileTab({ T, me, onEdit, t, setTweak, onSetAvatar }) {
|
|
|
3988
4085
|
},
|
|
3989
4086
|
style: { width: 16, height: 16, accentColor: "var(--accent)" }
|
|
3990
4087
|
}
|
|
3991
|
-
), /* @__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) => {
|
|
4088
|
+
), /* @__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)))), Array.isArray(sections) && sections.length > 0 && /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "0 20px 40px", display: "flex", flexDirection: "column", gap: 16 } }, sections.map((sec) => sec && typeof sec.render === "function" ? /* @__PURE__ */ React.createElement(DkHostSection, { key: sec.id, sec, props: sectionProps, lang: t && t.lang }) : null)), 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) => {
|
|
3992
4089
|
if (onEdit)
|
|
3993
4090
|
onEdit(name, description);
|
|
3994
4091
|
setEditing(false);
|
|
3995
4092
|
} }));
|
|
3996
4093
|
}
|
|
4094
|
+
var DkHostSection = class extends React.Component {
|
|
4095
|
+
constructor(props) {
|
|
4096
|
+
super(props);
|
|
4097
|
+
this.state = { err: null };
|
|
4098
|
+
}
|
|
4099
|
+
static getDerivedStateFromError(err) {
|
|
4100
|
+
return { err };
|
|
4101
|
+
}
|
|
4102
|
+
render() {
|
|
4103
|
+
var _a;
|
|
4104
|
+
const { sec, props, lang } = this.props;
|
|
4105
|
+
if (this.state.err) {
|
|
4106
|
+
return /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, lang === "zh" ? "\u8FD9\u4E2A\u9644\u52A0\u677F\u5757\u51FA\u9519\u4E86\u3002" : "This host section failed to render.");
|
|
4107
|
+
}
|
|
4108
|
+
try {
|
|
4109
|
+
return (_a = sec.render(props)) != null ? _a : null;
|
|
4110
|
+
} catch (e) {
|
|
4111
|
+
return null;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
};
|
|
3997
4115
|
Object.assign(window, { DkShareSheet, ProfileTab, DkPunkAvatar, DkEnsAvatar, DkPublicProfile });
|
|
3998
4116
|
|
|
3999
4117
|
// src/ui.jsx
|
|
@@ -4210,6 +4328,84 @@ function Section({ label, count, trailing, style }) {
|
|
|
4210
4328
|
}
|
|
4211
4329
|
Object.assign(window, { useIsPhone, DkPhoneStyles, DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
|
|
4212
4330
|
|
|
4331
|
+
// src/host-tabs.js
|
|
4332
|
+
var BUILTIN_TABS = ["chat", "people", "network", "apps", "profile"];
|
|
4333
|
+
var warned = /* @__PURE__ */ new Set();
|
|
4334
|
+
function warnOnce(what) {
|
|
4335
|
+
if (warned.has(what))
|
|
4336
|
+
return;
|
|
4337
|
+
warned.add(what);
|
|
4338
|
+
try {
|
|
4339
|
+
console.warn(`[beagle-ui] ignoring unknown host tab or app: ${what}`);
|
|
4340
|
+
} catch (e) {
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
function pickText(value, lang, fallback = "") {
|
|
4344
|
+
if (typeof value === "string")
|
|
4345
|
+
return value;
|
|
4346
|
+
if (value && typeof value === "object")
|
|
4347
|
+
return value[lang] || value.en || value.zh || fallback;
|
|
4348
|
+
return fallback;
|
|
4349
|
+
}
|
|
4350
|
+
function resolveTabs(hostTabs2) {
|
|
4351
|
+
if (!Array.isArray(hostTabs2) || !hostTabs2.length) {
|
|
4352
|
+
return BUILTIN_TABS.map((id) => ({ id, kind: "builtin" }));
|
|
4353
|
+
}
|
|
4354
|
+
const out = [];
|
|
4355
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4356
|
+
for (const entry of hostTabs2) {
|
|
4357
|
+
if (typeof entry === "string") {
|
|
4358
|
+
if (!BUILTIN_TABS.includes(entry)) {
|
|
4359
|
+
warnOnce(entry);
|
|
4360
|
+
continue;
|
|
4361
|
+
}
|
|
4362
|
+
if (seen.has(entry))
|
|
4363
|
+
continue;
|
|
4364
|
+
seen.add(entry);
|
|
4365
|
+
out.push({ id: entry, kind: "builtin" });
|
|
4366
|
+
continue;
|
|
4367
|
+
}
|
|
4368
|
+
if (entry && typeof entry === "object" && typeof entry.id === "string" && entry.id) {
|
|
4369
|
+
if (typeof entry.render !== "function") {
|
|
4370
|
+
warnOnce(`${entry.id} (no render function)`);
|
|
4371
|
+
continue;
|
|
4372
|
+
}
|
|
4373
|
+
if (BUILTIN_TABS.includes(entry.id)) {
|
|
4374
|
+
warnOnce(`${entry.id} (a built-in id cannot be redefined)`);
|
|
4375
|
+
continue;
|
|
4376
|
+
}
|
|
4377
|
+
if (seen.has(entry.id))
|
|
4378
|
+
continue;
|
|
4379
|
+
seen.add(entry.id);
|
|
4380
|
+
out.push({
|
|
4381
|
+
id: entry.id,
|
|
4382
|
+
kind: "host",
|
|
4383
|
+
label: entry.label,
|
|
4384
|
+
icon: entry.icon,
|
|
4385
|
+
render: entry.render,
|
|
4386
|
+
badge: typeof entry.badge === "function" ? entry.badge : void 0
|
|
4387
|
+
});
|
|
4388
|
+
continue;
|
|
4389
|
+
}
|
|
4390
|
+
warnOnce(String(entry && entry.id ? entry.id : entry));
|
|
4391
|
+
}
|
|
4392
|
+
return out.length ? out : BUILTIN_TABS.map((id) => ({ id, kind: "builtin" }));
|
|
4393
|
+
}
|
|
4394
|
+
function hasTab(tabs, id) {
|
|
4395
|
+
return (tabs != null ? tabs : []).some((t) => t.id === id);
|
|
4396
|
+
}
|
|
4397
|
+
function resolveApps(builtins, hideApps, hostApps2) {
|
|
4398
|
+
const hidden = new Set(Array.isArray(hideApps) ? hideApps : []);
|
|
4399
|
+
const host = (Array.isArray(hostApps2) ? hostApps2 : []).filter((a) => {
|
|
4400
|
+
if (a && typeof a.id === "string" && a.id && typeof a.open === "function")
|
|
4401
|
+
return true;
|
|
4402
|
+
warnOnce(`app ${a && a.id ? a.id : String(a)}`);
|
|
4403
|
+
return false;
|
|
4404
|
+
}).map((a) => ({ ...a, host: true }));
|
|
4405
|
+
const kept = (builtins != null ? builtins : []).filter((a) => !hidden.has(a.id));
|
|
4406
|
+
return host.concat(kept);
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4213
4409
|
// src/apps.jsx
|
|
4214
4410
|
var APPS = [
|
|
4215
4411
|
{
|
|
@@ -4250,11 +4446,24 @@ function dkB64Url(obj) {
|
|
|
4250
4446
|
const b64 = btoa(unescape(encodeURIComponent(json)));
|
|
4251
4447
|
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
4252
4448
|
}
|
|
4253
|
-
function AppsTab({ T, t }) {
|
|
4449
|
+
function AppsTab({ T, t, me, peers, phone, setTab }) {
|
|
4254
4450
|
const zh = t && t.lang === "zh";
|
|
4451
|
+
const apps = React.useMemo(
|
|
4452
|
+
() => resolveApps(APPS, hostHideApps, hostApps),
|
|
4453
|
+
[]
|
|
4454
|
+
);
|
|
4255
4455
|
const [busy, setBusy] = React.useState(null);
|
|
4256
4456
|
const [err, setErr] = React.useState(null);
|
|
4257
4457
|
const open = async (app) => {
|
|
4458
|
+
if (app.host) {
|
|
4459
|
+
setErr(null);
|
|
4460
|
+
try {
|
|
4461
|
+
app.open({ T, t, me, peers, phone, setTab });
|
|
4462
|
+
} catch (e) {
|
|
4463
|
+
setErr(String(e && e.message || e));
|
|
4464
|
+
}
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4258
4467
|
setBusy(app.id);
|
|
4259
4468
|
setErr(null);
|
|
4260
4469
|
const win = window.open("", "_blank");
|
|
@@ -4276,7 +4485,7 @@ function AppsTab({ T, t }) {
|
|
|
4276
4485
|
setBusy(null);
|
|
4277
4486
|
}
|
|
4278
4487
|
};
|
|
4279
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 16 } }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", color: "var(--faint)" } }, zh ? "\u5E94\u7528" : "Apps"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 6, lineHeight: 1.6 } }, zh ? "\u8FD9\u4E9B\u5E94\u7528\u63A5\u53D7 Beagle \u8EAB\u4EFD\u3002\u4ECE\u8FD9\u91CC\u6253\u5F00\u4E0D\u9700\u8981\u518D\u767B\u5F55\u4E00\u6B21 \u2014\u2014 \u4F1A\u5E26\u4E0A\u4E00\u4E2A\u4E24\u5206\u949F\u5185\u6709\u6548\u7684\u7B7E\u540D\u3002" : "These accept a Beagle identity. Opening one from here does not ask you to sign in again \u2014 it carries a signature that is good for two minutes.")), err && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "#f85149" } }, err),
|
|
4488
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 16 } }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", color: "var(--faint)" } }, zh ? "\u5E94\u7528" : "Apps"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 6, lineHeight: 1.6 } }, zh ? "\u8FD9\u4E9B\u5E94\u7528\u63A5\u53D7 Beagle \u8EAB\u4EFD\u3002\u4ECE\u8FD9\u91CC\u6253\u5F00\u4E0D\u9700\u8981\u518D\u767B\u5F55\u4E00\u6B21 \u2014\u2014 \u4F1A\u5E26\u4E0A\u4E00\u4E2A\u4E24\u5206\u949F\u5185\u6709\u6548\u7684\u7B7E\u540D\u3002" : "These accept a Beagle identity. Opening one from here does not ask you to sign in again \u2014 it carries a signature that is good for two minutes.")), err && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "#f85149" } }, err), apps.map((app) => /* @__PURE__ */ React.createElement("div", { key: app.id, style: {
|
|
4280
4489
|
display: "flex",
|
|
4281
4490
|
alignItems: "center",
|
|
4282
4491
|
gap: 14,
|
|
@@ -4293,7 +4502,7 @@ function AppsTab({ T, t }) {
|
|
|
4293
4502
|
display: "flex",
|
|
4294
4503
|
alignItems: "center",
|
|
4295
4504
|
justifyContent: "center"
|
|
4296
|
-
} }, /* @__PURE__ */ React.createElement(Icon, { name: app.icon, size: 19, color: "var(--accent)", stroke: 1.8 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 14, fontWeight: 600, color: "var(--text)" } }, app.name), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 2 } },
|
|
4505
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: app.icon, size: 19, color: "var(--accent)", stroke: 1.8 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 14, fontWeight: 600, color: "var(--text)" } }, pickText(app.name, t && t.lang, app.id)), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 2 } }, pickText(app.blurb, t && t.lang, "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", marginTop: 3 } }, app.host ? zh ? "\u672C\u673A\u5E94\u7528" : "runs in this app" : String(app.origin || "").replace(/^https?:\/\//, ""))), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", onClick: busy ? void 0 : () => open(app) }, busy === app.id ? zh ? "\u6253\u5F00\u4E2D\u2026" : "opening\u2026" : zh ? "\u6253\u5F00" : "Open")))));
|
|
4297
4506
|
}
|
|
4298
4507
|
|
|
4299
4508
|
// src/rtc-signaling.jsx
|
|
@@ -6298,7 +6507,7 @@ var STR = {
|
|
|
6298
6507
|
meetingDesc: "\u8DE8\u670D\u52A1\u5668\u521B\u5EFA\u97F3\u89C6\u9891\u623F\u95F4\uFF0C\u901A\u8FC7\u865A\u62DF IP \u9080\u8BF7\u597D\u53CB\u3002\u7FA4\u7EC4\u901A\u8BDD\u3001\u5C4F\u5E55\u5171\u4EAB\u4E0E\u5171\u4EAB\u51FA\u53E3\u8DEF\u7531\u5C06\u5728\u4E0B\u4E2A\u7248\u672C\u4E0A\u7EBF\u3002"
|
|
6299
6508
|
}
|
|
6300
6509
|
};
|
|
6301
|
-
function RailBtn({ icon, label, active, soon, onClick }) {
|
|
6510
|
+
function RailBtn({ icon, label, active, soon, onClick, svgPath }) {
|
|
6302
6511
|
return /* @__PURE__ */ React.createElement("button", { onClick, title: label, style: {
|
|
6303
6512
|
width: 52,
|
|
6304
6513
|
height: 52,
|
|
@@ -6313,9 +6522,9 @@ function RailBtn({ icon, label, active, soon, onClick }) {
|
|
|
6313
6522
|
alignItems: "center",
|
|
6314
6523
|
justifyContent: "center",
|
|
6315
6524
|
gap: 3
|
|
6316
|
-
} }, /* @__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)" } }));
|
|
6525
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: icon, svgPath, 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)" } }));
|
|
6317
6526
|
}
|
|
6318
|
-
function TabBarBtn({ icon, label, active, badge, onClick }) {
|
|
6527
|
+
function TabBarBtn({ icon, label, active, badge, onClick, svgPath }) {
|
|
6319
6528
|
return /* @__PURE__ */ React.createElement("button", { onClick, style: {
|
|
6320
6529
|
flex: 1,
|
|
6321
6530
|
minWidth: 0,
|
|
@@ -6330,9 +6539,36 @@ function TabBarBtn({ icon, label, active, badge, onClick }) {
|
|
|
6330
6539
|
justifyContent: "center",
|
|
6331
6540
|
gap: 3,
|
|
6332
6541
|
fontFamily: "var(--mono)"
|
|
6333
|
-
} }, /* @__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);
|
|
6542
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: icon, svgPath, 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);
|
|
6334
6543
|
}
|
|
6335
6544
|
var DK_PEOPLE_TABS = ["here", "recommended", "registered"];
|
|
6545
|
+
var HostTabPane = class extends React.Component {
|
|
6546
|
+
constructor(props) {
|
|
6547
|
+
super(props);
|
|
6548
|
+
this.state = { err: null };
|
|
6549
|
+
}
|
|
6550
|
+
static getDerivedStateFromError(err) {
|
|
6551
|
+
return { err };
|
|
6552
|
+
}
|
|
6553
|
+
componentDidCatch(err) {
|
|
6554
|
+
try {
|
|
6555
|
+
console.error("[beagle-ui] host tab failed", err);
|
|
6556
|
+
} catch (e) {
|
|
6557
|
+
}
|
|
6558
|
+
}
|
|
6559
|
+
render() {
|
|
6560
|
+
var _a;
|
|
6561
|
+
const { tab, props, lang } = this.props;
|
|
6562
|
+
if (this.state.err) {
|
|
6563
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: 24, textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--faint)", maxWidth: 420 } }, lang === "zh" ? `"${pickText(tab.label, lang, tab.id)}" \u8FD9\u4E2A\u6807\u7B7E\u9875\u51FA\u9519\u4E86\u3002\u5176\u5B83\u6807\u7B7E\u9875\u4E0D\u53D7\u5F71\u54CD\u3002` : `The "${pickText(tab.label, lang, tab.id)}" tab failed to render. The rest of the app is unaffected.`));
|
|
6564
|
+
}
|
|
6565
|
+
try {
|
|
6566
|
+
return (_a = tab.render(props)) != null ? _a : null;
|
|
6567
|
+
} catch (e) {
|
|
6568
|
+
return null;
|
|
6569
|
+
}
|
|
6570
|
+
}
|
|
6571
|
+
};
|
|
6336
6572
|
function Brand({ T, children, style }) {
|
|
6337
6573
|
if (!hostBrandHref)
|
|
6338
6574
|
return /* @__PURE__ */ React.createElement("span", { style }, children);
|
|
@@ -6449,7 +6685,17 @@ function DkUpdateModal({ T, info, onClose }) {
|
|
|
6449
6685
|
}
|
|
6450
6686
|
function DkApp() {
|
|
6451
6687
|
const [t, setTweak] = useTweaks({ ...DK_DEFAULTS, lang: dkDetectLang() });
|
|
6452
|
-
const
|
|
6688
|
+
const tabs = React.useMemo(() => resolveTabs(hostTabs), []);
|
|
6689
|
+
const showPeople = hasTab(tabs, "people");
|
|
6690
|
+
const showNetwork = hasTab(tabs, "network");
|
|
6691
|
+
const showProfile = hasTab(tabs, "profile");
|
|
6692
|
+
const firstTab = tabs[0] ? tabs[0].id === "people" ? "here" : tabs[0].id : "chat";
|
|
6693
|
+
const [tab, setTab] = React.useState(() => {
|
|
6694
|
+
const want = t.startTab || "chat";
|
|
6695
|
+
if (want === "chat" && hasTab(tabs, "chat"))
|
|
6696
|
+
return "chat";
|
|
6697
|
+
return hasTab(tabs, want) || DK_PEOPLE_TABS.includes(want) && showPeople ? want : firstTab;
|
|
6698
|
+
});
|
|
6453
6699
|
const [activeId, setActiveId] = React.useState(null);
|
|
6454
6700
|
const data = useDaemonData();
|
|
6455
6701
|
const me = data.me;
|
|
@@ -6484,11 +6730,13 @@ function DkApp() {
|
|
|
6484
6730
|
});
|
|
6485
6731
|
}, []);
|
|
6486
6732
|
React.useEffect(() => {
|
|
6733
|
+
if (!showNetwork)
|
|
6734
|
+
return void 0;
|
|
6487
6735
|
refreshBackend();
|
|
6488
6736
|
const ms = backend && backend.state === "releasing" ? 1500 : 8e3;
|
|
6489
6737
|
const t2 = setInterval(refreshBackend, ms);
|
|
6490
6738
|
return () => clearInterval(t2);
|
|
6491
|
-
}, [refreshBackend, backend && backend.state]);
|
|
6739
|
+
}, [refreshBackend, backend && backend.state, showNetwork]);
|
|
6492
6740
|
const armLan = React.useCallback(async () => {
|
|
6493
6741
|
await dkPost("/api/lan-arm", {});
|
|
6494
6742
|
refreshBackend();
|
|
@@ -6757,29 +7005,61 @@ function DkApp() {
|
|
|
6757
7005
|
setTab("chat");
|
|
6758
7006
|
openConversation(id);
|
|
6759
7007
|
};
|
|
6760
|
-
const onOpenNet = () => setTab("network");
|
|
6761
|
-
const
|
|
6762
|
-
{ id: "chat", icon: "message", label: T.chat },
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
7008
|
+
const onOpenNet = showNetwork ? () => setTab("network") : null;
|
|
7009
|
+
const RAIL_FOR = {
|
|
7010
|
+
chat: [{ id: "chat", icon: "message", label: T.chat }],
|
|
7011
|
+
people: [
|
|
7012
|
+
{ id: "here", icon: "users", label: T.here },
|
|
7013
|
+
{ id: "recommended", icon: "sparkles", label: T.recommended },
|
|
7014
|
+
{ id: "registered", icon: "at", label: T.registered }
|
|
7015
|
+
],
|
|
7016
|
+
network: [{ id: "network", icon: "network", label: T.network }],
|
|
7017
|
+
apps: [{ id: "apps", icon: "grid", label: T.apps }],
|
|
7018
|
+
profile: []
|
|
7019
|
+
};
|
|
7020
|
+
const nav = tabs.flatMap((x) => x.kind === "builtin" ? RAIL_FOR[x.id] || [] : [{ id: x.id, icon: x.icon, label: pickText(x.label, t.lang, x.id) }]);
|
|
6769
7021
|
const peopleTab = DK_PEOPLE_TABS.includes(tab);
|
|
6770
7022
|
const lastPeople = React.useRef("here");
|
|
6771
7023
|
if (peopleTab)
|
|
6772
7024
|
lastPeople.current = tab;
|
|
6773
7025
|
const unreadTotal = (peers || []).reduce((n, p) => n + (Number(p.unread) || 0), 0) + (requests || []).length;
|
|
6774
|
-
const
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
7026
|
+
const NAV_FOR = {
|
|
7027
|
+
chat: { icon: "message", label: T.chat, active: tab === "chat", badge: unreadTotal, go: () => setTab("chat") },
|
|
7028
|
+
people: { icon: "users", label: T.people, active: peopleTab, go: () => setTab(lastPeople.current) },
|
|
7029
|
+
network: { icon: "network", label: T.network, active: tab === "network", go: () => setTab("network") },
|
|
7030
|
+
apps: { icon: "grid", label: T.apps, active: tab === "apps", go: () => setTab("apps") },
|
|
7031
|
+
profile: { icon: "userRound", label: T.profile, active: tab === "profile", go: () => setTab("profile") }
|
|
7032
|
+
};
|
|
7033
|
+
const phoneNav = tabs.map((x) => x.kind === "builtin" ? { id: x.id, ...NAV_FOR[x.id] } : {
|
|
7034
|
+
id: x.id,
|
|
7035
|
+
icon: typeof x.icon === "string" ? x.icon : void 0,
|
|
7036
|
+
svgPath: x.icon && typeof x.icon === "object" ? x.icon.svgPath : void 0,
|
|
7037
|
+
label: pickText(x.label, t.lang, x.id),
|
|
7038
|
+
active: tab === x.id,
|
|
7039
|
+
// A host badge is called on every render, like the unread count beside
|
|
7040
|
+
// it; a throwing one must not take the tab bar down with it.
|
|
7041
|
+
badge: (() => {
|
|
7042
|
+
try {
|
|
7043
|
+
return x.badge ? x.badge() : 0;
|
|
7044
|
+
} catch (e) {
|
|
7045
|
+
return 0;
|
|
7046
|
+
}
|
|
7047
|
+
})(),
|
|
7048
|
+
go: () => setTab(x.id)
|
|
7049
|
+
});
|
|
6781
7050
|
const inConversation = phone && tab === "chat" && !!activeId && (peers || []).some((p) => p.id === activeId);
|
|
6782
|
-
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(Brand, { T, style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8, flexShrink: 0 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(
|
|
7051
|
+
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(Brand, { T, style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8, flexShrink: 0 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(
|
|
7052
|
+
RailBtn,
|
|
7053
|
+
{
|
|
7054
|
+
key: n.id,
|
|
7055
|
+
icon: typeof n.icon === "string" ? n.icon : void 0,
|
|
7056
|
+
svgPath: n.icon && typeof n.icon === "object" ? n.icon.svgPath : void 0,
|
|
7057
|
+
label: n.label,
|
|
7058
|
+
active: tab === n.id,
|
|
7059
|
+
soon: n.soon,
|
|
7060
|
+
onClick: () => setTab(n.id)
|
|
7061
|
+
}
|
|
7062
|
+
)), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), showProfile && /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: {
|
|
6783
7063
|
padding: 4,
|
|
6784
7064
|
borderRadius: 12,
|
|
6785
7065
|
cursor: "pointer",
|
|
@@ -6800,10 +7080,22 @@ function DkApp() {
|
|
|
6800
7080
|
fontWeight: 600,
|
|
6801
7081
|
background: tab === n.id ? "var(--accent)" : "transparent",
|
|
6802
7082
|
color: tab === n.id ? "#fff" : "var(--dim)"
|
|
6803
|
-
} }, /* @__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: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onLoadEarlier: () => activeId && data.loadEarlier(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 }),
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
7083
|
+
} }, /* @__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: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(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, me, peers, phone, setTab }), tabs.map((x) => x.kind === "host" && tab === x.id ? /* @__PURE__ */ React.createElement(HostTabPane, { key: x.id, tab: x, props: { T, t, me, peers, phone, setTab }, lang: t.lang }) : null), tab === "profile" && /* @__PURE__ */ React.createElement(
|
|
7084
|
+
ProfileTab,
|
|
7085
|
+
{
|
|
7086
|
+
T,
|
|
7087
|
+
me,
|
|
7088
|
+
onEdit,
|
|
7089
|
+
t,
|
|
7090
|
+
setTweak,
|
|
7091
|
+
onSetAvatar: (p) => dkApi.setProfileFull(p).then((r) => {
|
|
7092
|
+
data.refresh();
|
|
7093
|
+
return r;
|
|
7094
|
+
}),
|
|
7095
|
+
sections: hostProfileSections,
|
|
7096
|
+
sectionProps: { T, t, me, peers, phone, setTab }
|
|
7097
|
+
}
|
|
7098
|
+
)), 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, svgPath: n.svgPath, label: n.label, active: n.active, badge: n.badge, onClick: n.go }))), data.locked && /* @__PURE__ */ React.createElement(
|
|
6807
7099
|
DkLockedOut,
|
|
6808
7100
|
{
|
|
6809
7101
|
compact: true,
|
package/package.json
CHANGED