@elefunc/send 0.1.6 → 0.1.7
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/package.json +1 -1
- package/src/core/session.ts +14 -0
- package/src/tui/app.ts +45 -15
- package/src/tui/file-search-protocol.ts +1 -0
- package/src/tui/file-search.ts +4 -0
package/package.json
CHANGED
package/src/core/session.ts
CHANGED
|
@@ -492,6 +492,20 @@ export class SendSession {
|
|
|
492
492
|
return sent
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
shareTurnWithPeers(peerIds: string[]) {
|
|
496
|
+
if (!this.extraTurnServers.length) return 0
|
|
497
|
+
const iceServers = this.sharedTurnServers()
|
|
498
|
+
const sentPeers: string[] = []
|
|
499
|
+
for (const peerId of new Set(peerIds.filter(Boolean))) {
|
|
500
|
+
const peer = this.peers.get(peerId)
|
|
501
|
+
if (!peer || peer.presence !== "active") continue
|
|
502
|
+
if (!this.sendSignal({ kind: "turn-share", to: peer.id, iceServers })) continue
|
|
503
|
+
sentPeers.push(peer.id)
|
|
504
|
+
}
|
|
505
|
+
if (sentPeers.length) this.pushLog("turn:share-sent", { peers: sentPeers.length, scope: "filtered", peerIds: sentPeers }, "info")
|
|
506
|
+
return sentPeers.length
|
|
507
|
+
}
|
|
508
|
+
|
|
495
509
|
shareTurnWithAllPeers() {
|
|
496
510
|
const count = this.activePeers().length
|
|
497
511
|
if (!count || !this.extraTurnServers.length) return 0
|
package/src/tui/app.ts
CHANGED
|
@@ -45,6 +45,7 @@ export interface TuiState {
|
|
|
45
45
|
sessionSeed: SessionSeed
|
|
46
46
|
peerSelectionByRoom: Map<string, Map<string, boolean>>
|
|
47
47
|
snapshot: SessionSnapshot
|
|
48
|
+
peerSearch: string
|
|
48
49
|
focusedId: string | null
|
|
49
50
|
roomInput: string
|
|
50
51
|
nameInput: string
|
|
@@ -72,6 +73,7 @@ export interface TuiActions {
|
|
|
72
73
|
jumpToNewSelf: TuiAction
|
|
73
74
|
commitName: TuiAction
|
|
74
75
|
setNameInput: (value: string) => void
|
|
76
|
+
setPeerSearch: (value: string) => void
|
|
75
77
|
toggleSelectReadyPeers: TuiAction
|
|
76
78
|
clearPeerSelection: TuiAction
|
|
77
79
|
toggleHideTerminalPeers: TuiAction
|
|
@@ -97,6 +99,7 @@ export interface TuiActions {
|
|
|
97
99
|
|
|
98
100
|
const ROOM_INPUT_ID = "room-input"
|
|
99
101
|
const NAME_INPUT_ID = "name-input"
|
|
102
|
+
const PEER_SEARCH_INPUT_ID = "peer-search-input"
|
|
100
103
|
const DRAFT_INPUT_ID = "draft-input"
|
|
101
104
|
const TRANSPARENT_BORDER_STYLE = { fg: rgb(7, 10, 12) } as const
|
|
102
105
|
const METRIC_BORDER_STYLE = { fg: rgb(20, 25, 32) } as const
|
|
@@ -151,6 +154,7 @@ export const createNoopTuiActions = (): TuiActions => ({
|
|
|
151
154
|
jumpToNewSelf: noop,
|
|
152
155
|
commitName: noop,
|
|
153
156
|
setNameInput: noop,
|
|
157
|
+
setPeerSearch: noop,
|
|
154
158
|
toggleSelectReadyPeers: noop,
|
|
155
159
|
clearPeerSelection: noop,
|
|
156
160
|
toggleHideTerminalPeers: noop,
|
|
@@ -206,6 +210,16 @@ const transferStatusKind = (status: TransferSnapshot["status"]) => ({
|
|
|
206
210
|
error: "offline",
|
|
207
211
|
}[status] || "unknown") as "online" | "offline" | "away" | "busy" | "unknown"
|
|
208
212
|
const visiblePeers = (peers: PeerSnapshot[], hideTerminalPeers: boolean) => hideTerminalPeers ? peers.filter(peer => peer.presence === "active") : peers
|
|
213
|
+
const peerSearchNeedle = (value: string) => `${value ?? ""}`.trim().toLowerCase()
|
|
214
|
+
const peerMatchesSearch = (peer: PeerSnapshot, search: string) => !search || peer.displayName.toLowerCase().includes(search)
|
|
215
|
+
export const renderedPeers = (peers: PeerSnapshot[], hideTerminalPeers: boolean, search: string) => {
|
|
216
|
+
const needle = peerSearchNeedle(search)
|
|
217
|
+
return visiblePeers(peers, hideTerminalPeers)
|
|
218
|
+
.filter(peer => peerMatchesSearch(peer, needle))
|
|
219
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
220
|
+
}
|
|
221
|
+
export const renderedReadySelectedPeers = (peers: PeerSnapshot[], hideTerminalPeers: boolean, search: string) =>
|
|
222
|
+
renderedPeers(peers, hideTerminalPeers, search).filter(peer => peer.selected && peer.ready)
|
|
209
223
|
const transferProgress = (transfer: TransferSnapshot) => Math.max(0, Math.min(1, transfer.progress / 100))
|
|
210
224
|
const isPendingOffer = (transfer: TransferSnapshot) => transfer.direction === "out" && (transfer.status === "queued" || transfer.status === "offered")
|
|
211
225
|
const statusVariant = (status: TransferSnapshot["status"]): BadgeVariant => ({
|
|
@@ -479,6 +493,7 @@ export const createInitialTuiState = (initialConfig: SessionConfig, showEvents =
|
|
|
479
493
|
sessionSeed,
|
|
480
494
|
peerSelectionByRoom,
|
|
481
495
|
snapshot: session.snapshot(),
|
|
496
|
+
peerSearch: "",
|
|
482
497
|
focusedId: null,
|
|
483
498
|
roomInput: sessionSeed.room,
|
|
484
499
|
nameInput: visibleNameInput(sessionSeed.name),
|
|
@@ -700,18 +715,18 @@ const renderPeerRow = (peer: PeerSnapshot, turnShareEnabled: boolean, actions: T
|
|
|
700
715
|
])
|
|
701
716
|
|
|
702
717
|
const renderPeersCard = (state: TuiState, actions: TuiActions) => {
|
|
703
|
-
const peers =
|
|
704
|
-
const activeCount =
|
|
705
|
-
const selectedCount =
|
|
718
|
+
const peers = renderedPeers(state.snapshot.peers, state.hideTerminalPeers, state.peerSearch)
|
|
719
|
+
const activeCount = peers.filter(peer => peer.presence === "active").length
|
|
720
|
+
const selectedCount = peers.filter(peer => peer.selectable && peer.selected).length
|
|
706
721
|
const canShareTurn = state.session.canShareTurn()
|
|
707
722
|
return denseSection({
|
|
708
723
|
id: "peers-card",
|
|
709
724
|
titleNode: ui.row({ id: "peers-title-row", gap: 1, items: "center" }, [
|
|
710
725
|
headingTextButton("share-turn-all-peers", "Peers", canShareTurn && !!activeCount ? actions.shareTurnWithAllPeers : undefined, {
|
|
711
726
|
focusable: canShareTurn && !!activeCount,
|
|
712
|
-
accessibleLabel: "share TURN with
|
|
727
|
+
accessibleLabel: "share TURN with matching active peers",
|
|
713
728
|
}),
|
|
714
|
-
ui.text(`${selectedCount}/${
|
|
729
|
+
ui.text(`${selectedCount}/${peers.length}`, { id: "peers-count-text", variant: "heading" }),
|
|
715
730
|
]),
|
|
716
731
|
flex: 1,
|
|
717
732
|
actions: [
|
|
@@ -720,10 +735,16 @@ const renderPeersCard = (state: TuiState, actions: TuiActions) => {
|
|
|
720
735
|
toggleButton("toggle-clean-peers", "Clean", state.hideTerminalPeers, actions.toggleHideTerminalPeers),
|
|
721
736
|
],
|
|
722
737
|
}, [
|
|
738
|
+
ui.input({
|
|
739
|
+
id: PEER_SEARCH_INPUT_ID,
|
|
740
|
+
value: state.peerSearch,
|
|
741
|
+
placeholder: "filter",
|
|
742
|
+
onInput: value => actions.setPeerSearch(value),
|
|
743
|
+
}),
|
|
723
744
|
ui.box({ id: "peers-list", flex: 1, minHeight: 0, overflow: "scroll", border: "none" }, [
|
|
724
745
|
peers.length
|
|
725
746
|
? ui.column({ gap: 0 }, peers.map(peer => renderPeerRow(peer, canShareTurn, actions)))
|
|
726
|
-
: ui.empty(`Waiting for peers in ${state.snapshot.room}...`),
|
|
747
|
+
: ui.empty(state.snapshot.peers.length ? "No peers match current filters." : `Waiting for peers in ${state.snapshot.room}...`),
|
|
727
748
|
]),
|
|
728
749
|
])
|
|
729
750
|
}
|
|
@@ -768,6 +789,7 @@ const renderFilePreviewRow = (match: FileSearchMatch, index: number, selected: b
|
|
|
768
789
|
offsetFileSearchMatchIndices(displayPrefix, match.indices),
|
|
769
790
|
{ id: `file-preview-path-${index}`, flex: 1 },
|
|
770
791
|
),
|
|
792
|
+
match.kind === "file" && typeof match.size === "number" ? ui.text(formatBytes(match.size), { style: { dim: true } }) : null,
|
|
771
793
|
match.kind === "directory" ? tightTag("dir", { variant: "info", bare: true }) : null,
|
|
772
794
|
])
|
|
773
795
|
|
|
@@ -1233,11 +1255,12 @@ export const startTui = async (initialConfig: SessionConfig, showEvents = false)
|
|
|
1233
1255
|
|
|
1234
1256
|
const maybeOfferDrafts = () => {
|
|
1235
1257
|
if (!state.autoOfferOutgoing || !state.drafts.length || state.offeringDrafts) return
|
|
1236
|
-
|
|
1258
|
+
const targetPeerIds = renderedReadySelectedPeers(state.snapshot.peers, state.hideTerminalPeers, state.peerSearch).map(peer => peer.id)
|
|
1259
|
+
if (!targetPeerIds.length) return
|
|
1237
1260
|
const session = state.session
|
|
1238
1261
|
const pendingDrafts = [...state.drafts]
|
|
1239
1262
|
commit(current => ({ ...current, offeringDrafts: true }))
|
|
1240
|
-
void session.
|
|
1263
|
+
void session.queueFiles(pendingDrafts.map(draft => draft.path), targetPeerIds).then(
|
|
1241
1264
|
ids => {
|
|
1242
1265
|
if (state.session !== session) return
|
|
1243
1266
|
const offeredIds = new Set(pendingDrafts.map(draft => draft.id))
|
|
@@ -1277,6 +1300,7 @@ export const startTui = async (initialConfig: SessionConfig, showEvents = false)
|
|
|
1277
1300
|
sessionSeed: nextSeed,
|
|
1278
1301
|
peerSelectionByRoom: current.peerSelectionByRoom,
|
|
1279
1302
|
snapshot: nextSession.snapshot(),
|
|
1303
|
+
peerSearch: "",
|
|
1280
1304
|
roomInput: nextSeed.room,
|
|
1281
1305
|
nameInput: visibleNameInput(nextSeed.name),
|
|
1282
1306
|
draftInput: "",
|
|
@@ -1350,16 +1374,19 @@ export const startTui = async (initialConfig: SessionConfig, showEvents = false)
|
|
|
1350
1374
|
jumpToNewSelf: () => replaceSession({ ...state.sessionSeed, localId: cleanLocalId(uid(8)) }, "Started a fresh self ID.", { reseedBootFocus: true }),
|
|
1351
1375
|
commitName,
|
|
1352
1376
|
setNameInput: value => commit(current => ({ ...current, nameInput: value })),
|
|
1377
|
+
setPeerSearch: value => commit(current => ({ ...current, peerSearch: value })),
|
|
1353
1378
|
toggleSelectReadyPeers: () => {
|
|
1379
|
+
const peers = renderedPeers(state.snapshot.peers, state.hideTerminalPeers, state.peerSearch)
|
|
1354
1380
|
let changed = 0
|
|
1355
|
-
for (const peer of
|
|
1356
|
-
commit(current => withNotice(current, { text: changed ? "Selected ready peers." : "No ready peers to select.", variant: changed ? "success" : "info" }))
|
|
1381
|
+
for (const peer of peers) if (state.session.setPeerSelected(peer.id, peer.presence === "active" && peer.ready)) changed += 1
|
|
1382
|
+
commit(current => withNotice(current, { text: changed ? "Selected matching ready peers." : "No matching ready peers to select.", variant: changed ? "success" : "info" }))
|
|
1357
1383
|
maybeOfferDrafts()
|
|
1358
1384
|
},
|
|
1359
1385
|
clearPeerSelection: () => {
|
|
1386
|
+
const peers = renderedPeers(state.snapshot.peers, state.hideTerminalPeers, state.peerSearch)
|
|
1360
1387
|
let changed = 0
|
|
1361
|
-
for (const peer of
|
|
1362
|
-
commit(current => withNotice(current, { text: changed ? `Cleared ${plural(changed, "peer selection")}.` : "No peer selections to clear.", variant: changed ? "warning" : "info" }))
|
|
1388
|
+
for (const peer of peers) if (state.session.setPeerSelected(peer.id, false)) changed += 1
|
|
1389
|
+
commit(current => withNotice(current, { text: changed ? `Cleared ${plural(changed, "matching peer selection")}.` : "No matching peer selections to clear.", variant: changed ? "warning" : "info" }))
|
|
1363
1390
|
},
|
|
1364
1391
|
toggleHideTerminalPeers: () => commit(current => withNotice({ ...current, hideTerminalPeers: !current.hideTerminalPeers }, { text: current.hideTerminalPeers ? "Terminal peers shown." : "Terminal peers hidden.", variant: "info" })),
|
|
1365
1392
|
togglePeer: peerId => {
|
|
@@ -1379,13 +1406,16 @@ export const startTui = async (initialConfig: SessionConfig, showEvents = false)
|
|
|
1379
1406
|
}))
|
|
1380
1407
|
},
|
|
1381
1408
|
shareTurnWithAllPeers: () => {
|
|
1382
|
-
const
|
|
1409
|
+
const targetPeerIds = renderedPeers(state.snapshot.peers, state.hideTerminalPeers, state.peerSearch)
|
|
1410
|
+
.filter(peer => peer.presence === "active")
|
|
1411
|
+
.map(peer => peer.id)
|
|
1412
|
+
const shared = state.session.shareTurnWithPeers(targetPeerIds)
|
|
1383
1413
|
commit(current => withNotice(current, {
|
|
1384
1414
|
text: !state.session.canShareTurn()
|
|
1385
1415
|
? "TURN is not configured."
|
|
1386
1416
|
: shared
|
|
1387
|
-
? `Shared TURN with ${plural(shared, "
|
|
1388
|
-
: "No active peers to share TURN with.",
|
|
1417
|
+
? `Shared TURN with ${plural(shared, "matching peer")}.`
|
|
1418
|
+
: "No matching active peers to share TURN with.",
|
|
1389
1419
|
variant: !state.session.canShareTurn() ? "info" : shared ? "success" : "info",
|
|
1390
1420
|
}))
|
|
1391
1421
|
},
|
package/src/tui/file-search.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface IndexedEntry {
|
|
|
8
8
|
relativePath: string
|
|
9
9
|
fileName: string
|
|
10
10
|
kind: "file" | "directory"
|
|
11
|
+
size?: number
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface FileSearchScope {
|
|
@@ -187,6 +188,7 @@ const browseEntries = (entries: readonly IndexedEntry[], resultLimit: number) =>
|
|
|
187
188
|
absolutePath: entry.absolutePath,
|
|
188
189
|
fileName: entry.fileName,
|
|
189
190
|
kind: entry.kind,
|
|
191
|
+
size: entry.size,
|
|
190
192
|
score: 0,
|
|
191
193
|
indices: [],
|
|
192
194
|
} satisfies FileSearchMatch))
|
|
@@ -203,6 +205,7 @@ export const searchEntries = (entries: readonly IndexedEntry[], query: string, r
|
|
|
203
205
|
absolutePath: entry.absolutePath,
|
|
204
206
|
fileName: entry.fileName,
|
|
205
207
|
kind: entry.kind,
|
|
208
|
+
size: entry.size,
|
|
206
209
|
score: match.score,
|
|
207
210
|
indices: match.indices,
|
|
208
211
|
})
|
|
@@ -272,6 +275,7 @@ export const crawlWorkspaceEntries = async (workspaceRoot: string, onEntry: (ent
|
|
|
272
275
|
relativePath: normalizeRelativePath(relative(root, absolutePath)),
|
|
273
276
|
fileName: child.name,
|
|
274
277
|
kind: "file",
|
|
278
|
+
size: info.size,
|
|
275
279
|
})
|
|
276
280
|
}
|
|
277
281
|
} catch {
|