import { useState, useEffect } from 'react'; import { fetchRegistry, getRawFileUrl } from './api/registry'; import type { RegistryResponse, MetaJson } from './api/types'; import { Key, X, Download, Upload, ExternalLink } from 'lucide-react'; import UploadModal from './UploadModal'; const GITHUB_CLIENT_ID = 'Ov23lioBHF1VmiCOoYui'; function App() { const [registry, setRegistry] = useState(null); const [error, setError] = useState(null); const [pat, setPat] = useState(''); const [showLogin, setShowLogin] = useState(false); const [showUpload, setShowUpload] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const [filter, setFilter] = useState(null); useEffect(() => { const savedPat = localStorage.getItem('piston_pat'); if (savedPat) setPat(savedPat); fetchRegistry() .then(setRegistry) .catch(e => setError(e.message)); }, []); const resolvePath = (path: string) => { if (path === '') return 'Game Root'; let p = path; p = p.replace(/{MediaDir}/g, 'Windows64Media'); p = p.replace(/{GameHDD}/g, 'Windows64/GameHDD'); p = p.replace(/{DLCDir}/g, 'Windows64Media/DLC'); p = p.replace(/{MobDir}/g, 'Common/res/mob'); return p; }; const [userCode, setUserCode] = useState(null); const [verificationUri, setVerificationUri] = useState(null); const [loginStatus, setLoginStatus] = useState(''); const [pollingInterval, setPollingInterval] = useState(null); const initiateDeviceFlow = async () => { try { setLoginStatus('Requesting code...'); const proxyRoute = import.meta.env.DEV ? '/github-proxy' : 'https://corsproxy.io/?url=https://github.com'; const res = await fetch(`${proxyRoute}/login/device/code`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, scope: 'public_repo' }) }); const data = await res.json(); if (data.user_code) { setUserCode(data.user_code); setVerificationUri(data.verification_uri); setLoginStatus('Waiting for authorization...'); pollForToken(data.device_code, data.interval); } else { setLoginStatus('Failed: ' + (data.error || 'Unknown error')); } } catch (e: any) { setLoginStatus('Error: ' + e.message); } }; const pollForToken = (dCode: string, initialIntervalSecs: number) => { let currentInterval = initialIntervalSecs; let timeoutId: number; const poll = async () => { try { const proxyRoute = import.meta.env.DEV ? '/github-proxy' : 'https://corsproxy.io/?url=https://github.com'; const res = await fetch(`${proxyRoute}/login/oauth/access_token`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, device_code: dCode, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }) }); const data = await res.json(); if (data.access_token) { setPat(data.access_token); localStorage.setItem('piston_pat', data.access_token); closeLogin(); return; } else if (data.error === 'authorization_pending') { //neo: still waiting for auth ig } else if (data.error === 'slow_down') { if (data.interval) currentInterval = data.interval; } else { setLoginStatus('Login failed: ' + data.error); return; } } catch (e) { setLoginStatus('Polling error.'); return; } timeoutId = window.setTimeout(poll, currentInterval * 1000); setPollingInterval(timeoutId); }; timeoutId = window.setTimeout(poll, currentInterval * 1000); setPollingInterval(timeoutId); }; const closeLogin = () => { setShowLogin(false); setUserCode(null); setVerificationUri(null); setLoginStatus(''); if (pollingInterval) window.clearTimeout(pollingInterval); }; const handleLogout = () => { setPat(''); localStorage.removeItem('piston_pat'); }; return (

Piston

Emerald Workshop
{pat ? ( <> ) : ( )}
{error && (
Error: {error}
)} {!selectedItem ? ( <>
{Array.from(new Set(registry?.packages.flatMap(pkg => Array.isArray(pkg.category) ? pkg.category : [pkg.category]) || [])).map(c => ( ))}
{registry?.packages.filter(pkg => filter ? (Array.isArray(pkg.category) ? pkg.category.includes(filter) : pkg.category === filter) : true).map((pkg) => (
setSelectedItem(pkg)} style={{ cursor: 'pointer' }}> {pkg.name}

{pkg.name}

by {pkg.author}
{(Array.isArray(pkg.category) ? pkg.category : [pkg.category]).map(c => ( {c} ))}
))}
) : (
{selectedItem.name}
{(Array.isArray(selectedItem.category) ? selectedItem.category : [selectedItem.category]).map(c => ( {c} ))}

{selectedItem.name} v{selectedItem.version}

by {selectedItem.author}

Open in Emerald
{selectedItem.description}

Downloads & Installation

{Object.entries(selectedItem.zips).map(([zipName, extractPath]) => (
{zipName}
Extract to {resolvePath(extractPath)}
))}
)} {showLogin && (

GitHub Login

{!userCode ? ( <>

We use GitHub's Device Flow so you don't need to paste tokens or passwords.

{loginStatus}
) : ( <>

Your verification code is:

{userCode}

{loginStatus}

Open GitHub )}
)} {showUpload && setShowUpload(false)} pat={pat} />}
); } export default App;