Add rooms, per-network file sharing, and file transfer UX

Additional rooms:
- Store tracks customRooms per networkId; createRoom action slugifies and
  deduplicates, switches active room on creation
- Sidebar: + button next to Rooms label opens an inline input; Escape
  dismisses; Enter creates the room

Per-network share directories:
- Store tracks sharedFilesByNetwork keyed by networkId instead of a single
  global map; setSharedFiles(files, networkId?) defaults to activeNetworkId
- FolderPicker reads per-network map to display accurate file count label

File transfer UX:
- Incoming file-offer now emits pending_offer instead of auto-accepting;
  user sees Accept / Reject buttons in the sidebar Transfers section
- Progress events emitted per chunk during receive; Transfers panel shows
  a live progress bar with received/total sizes
- file-cancel protocol message: sender or receiver can cancel an in-flight
  transfer; DataChannel is closed and buffers discarded
- PeerConn: acceptOffer(), rejectOffer(), cancelRecv(), cancelSend()
- BrowserAdapter: acceptOffer(), rejectOffer(), cancelTransfer()
- Store: pendingOffers, fileProgress, acceptOffer, rejectOffer, cancelTransfer
- Transfers component renders in sidebar; auto-hides when no activity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-25 20:49:15 +02:00
parent 5421352e62
commit fb14ca82af
6 changed files with 269 additions and 17 deletions

View File

@@ -1,10 +1,14 @@
import { useRef, useState } from 'react'
import { useRef } from 'react'
import { useWaste } from '../store'
export function FolderPicker() {
const { setSharedFiles } = useWaste()
const { setSharedFiles, activeNetworkId, sharedFilesByNetwork } = useWaste()
const inputRef = useRef<HTMLInputElement>(null)
const [label, setLabel] = useState<string | null>(null)
const current = activeNetworkId ? sharedFilesByNetwork[activeNetworkId] : undefined
const label = current && current.size > 0
? `${current.size} file${current.size !== 1 ? 's' : ''} shared`
: null
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const fileList = e.target.files
@@ -15,9 +19,7 @@ export function FolderPicker() {
map.set(f.name, f)
}
setSharedFiles(map)
const first = fileList[0]
const parts = (first.webkitRelativePath || first.name).split('/')
setLabel(`${parts[0]} (${map.size} file${map.size !== 1 ? 's' : ''})`)
e.target.value = ''
}
return (
@@ -34,7 +36,7 @@ export function FolderPicker() {
<button
className="folder-picker-btn"
onClick={() => inputRef.current?.click()}
title="Share a folder with peers"
title="Share a folder with peers on this network"
>
{label ?? '+ Share folder'}
</button>