2026-06-26 20:49:17 +02:00
|
|
|
import { useState, useRef } from 'react'
|
2026-06-26 20:47:11 +02:00
|
|
|
import { useWaste } from '../store'
|
|
|
|
|
|
|
|
|
|
interface ShareRecord {
|
|
|
|
|
name: string // display name (folder name picked by user)
|
|
|
|
|
global: boolean // true = all networks
|
|
|
|
|
networkId?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const STORAGE_KEY = 'waste_shares'
|
|
|
|
|
|
|
|
|
|
function loadShares(): ShareRecord[] {
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]')
|
|
|
|
|
} catch {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveShares(shares: ShareRecord[]) {
|
|
|
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(shares))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ShareManager() {
|
|
|
|
|
const { activeNetworkId, setSharedFiles, sharedFilesByNetwork } = useWaste()
|
|
|
|
|
const [shares, setShares] = useState<ShareRecord[]>(loadShares)
|
|
|
|
|
const [includeSubfolders, setIncludeSubfolders] = useState(true)
|
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
|
|
|
|
// Count files currently shared on active network
|
|
|
|
|
const current = activeNetworkId ? sharedFilesByNetwork[activeNetworkId] : undefined
|
|
|
|
|
const fileCount = current?.size ?? 0
|
|
|
|
|
|
|
|
|
|
function persist(next: ShareRecord[]) {
|
|
|
|
|
setShares(next)
|
|
|
|
|
saveShares(next)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addShare(files: FileList, folderName: string) {
|
|
|
|
|
const map = new Map<string, File>()
|
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
|
const f = files[i]
|
|
|
|
|
const rel = (f as File & { webkitRelativePath?: string }).webkitRelativePath
|
|
|
|
|
const path = rel ? rel.split('/').slice(1).join('/') : f.name
|
|
|
|
|
if (!includeSubfolders && path.includes('/')) continue
|
|
|
|
|
map.set(path, f)
|
|
|
|
|
}
|
|
|
|
|
setSharedFiles(map)
|
|
|
|
|
|
|
|
|
|
const record: ShareRecord = {
|
|
|
|
|
name: folderName,
|
|
|
|
|
global: true,
|
|
|
|
|
networkId: activeNetworkId ?? undefined,
|
|
|
|
|
}
|
|
|
|
|
persist([...shares.filter(s => s.name !== folderName), record])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeShare(name: string) {
|
|
|
|
|
persist(shares.filter(s => s.name !== name))
|
|
|
|
|
// Clear the in-memory share if it matches
|
|
|
|
|
setSharedFiles(new Map())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
|
|
|
const fileList = e.target.files
|
|
|
|
|
if (!fileList || fileList.length === 0) return
|
|
|
|
|
// Get folder name from first file's path
|
|
|
|
|
const first = fileList[0] as File & { webkitRelativePath?: string }
|
|
|
|
|
const folderName = first.webkitRelativePath?.split('/')[0] ?? 'folder'
|
|
|
|
|
addShare(fileList, folderName)
|
|
|
|
|
e.target.value = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="share-manager">
|
|
|
|
|
<input
|
|
|
|
|
ref={inputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
// @ts-expect-error webkitdirectory is non-standard
|
|
|
|
|
webkitdirectory=""
|
|
|
|
|
multiple
|
|
|
|
|
style={{ display: 'none' }}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{shares.length > 0 && (
|
|
|
|
|
<ul className="share-list">
|
|
|
|
|
{shares.map(s => (
|
|
|
|
|
<li key={s.name} className="share-item">
|
|
|
|
|
<span className="share-icon">📁</span>
|
|
|
|
|
<span className="share-name" title={s.name}>{s.name}</span>
|
|
|
|
|
<span className="share-scope">{s.global ? 'all nets' : 'this net'}</span>
|
|
|
|
|
<button className="share-repick" onClick={() => inputRef.current?.click()} title="Re-pick folder">↺</button>
|
|
|
|
|
<button className="share-remove" onClick={() => removeShare(s.name)} title="Remove share">✕</button>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
className="folder-picker-btn"
|
|
|
|
|
onClick={() => inputRef.current?.click()}
|
|
|
|
|
title="Share a folder with peers on this network"
|
|
|
|
|
>
|
|
|
|
|
{fileCount > 0 ? `${fileCount} file${fileCount !== 1 ? 's' : ''} shared` : '+ Share folder'}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<label className="folder-picker-subfolders">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={includeSubfolders}
|
|
|
|
|
onChange={e => setIncludeSubfolders(e.target.checked)}
|
|
|
|
|
/>
|
|
|
|
|
include subfolders
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|