11import { useEffect , useMemo , useState } from 'react'
2- import type { ByondStatus } from '../../services/ByondService'
2+ import { ByondStatus } from '../../services/ByondService'
33import { byondService } from '../../services/ByondService'
44
55type StatusMap = Record < string , ByondStatus >
66
7+ export function ByondTitle ( ) {
8+ const refresh = async ( ) => {
9+ window . dispatchEvent ( new CustomEvent ( 'byond:refresh' ) )
10+ }
11+
12+ return (
13+ < div className = "flex flex-1 items-center gap-2" >
14+ < span > BYOND</ span >
15+ < button
16+ type = "button"
17+ onClick = { ( ) => void refresh ( ) }
18+ className = "ml-auto rounded border border-slate-700 px-2 py-1 text-xs text-slate-200 hover:border-slate-500"
19+ title = "Refresh versions"
20+ >
21+ Refresh
22+ </ button >
23+ </ div >
24+ )
25+ }
26+
727export function ByondPanel ( ) {
8- const [ available , setAvailable ] = useState < string [ ] > ( [ ] )
28+ const [ latestVersion , setLatestVersion ] = useState < string | null > ( null )
929 const [ local , setLocal ] = useState < string [ ] > ( [ ] )
1030 const [ activeVersion , setActiveVersion ] = useState < string | null > ( null )
1131 const [ status , setStatus ] = useState < StatusMap > ( { } )
1232 const [ error , setError ] = useState < string | null > ( null )
33+ const [ customMajor , setCustomMajor ] = useState ( '' )
34+ const [ customMinor , setCustomMinor ] = useState ( '' )
1335
14- const topVersions = useMemo ( ( ) => available . slice ( 0 , 20 ) , [ available ] )
36+ const displayVersions = useMemo ( ( ) => {
37+ const list = [ ...local ]
38+ if ( latestVersion && ! list . includes ( latestVersion ) ) {
39+ list . push ( latestVersion )
40+ }
41+ return list
42+ } , [ local , latestVersion ] )
1543
1644 const refresh = async ( ) => {
1745 try {
18- const [ remoteVersions , localVersions ] = await Promise . all ( [
19- byondService . getAvailableVersions ( ) ,
46+ const [ latestVersion , localVersions ] = await Promise . all ( [
47+ byondService . getLatestVersion ( ) ,
2048 byondService . getLocalVersions ( ) ,
2149 ] )
22- setAvailable ( remoteVersions )
50+ setLatestVersion ( latestVersion )
51+ // custom inputs default to latest remote version
52+ if ( latestVersion ) {
53+ const parts = latestVersion . split ( '.' )
54+ setCustomMajor ( ( prev ) => ( prev ? prev : parts [ 0 ] ?? '' ) )
55+ setCustomMinor ( ( prev ) => ( prev ? prev : parts [ 1 ] ?? '' ) )
56+ }
2357 setLocal ( localVersions )
2458 const active = byondService . getActiveVersion ( )
2559 setActiveVersion ( active )
@@ -33,15 +67,15 @@ export function ByondPanel() {
3367
3468 if ( ! active && localVersions . length > 0 ) {
3569 const latest = localVersions [ 0 ]
36- setStatus ( ( prev ) => ( { ...prev , [ latest ] : 'loading' } ) )
70+ setStatus ( ( prev ) => ( { ...prev , [ latest ] : ByondStatus . Loading } ) )
3771 void byondService
3872 . load ( latest , true )
39- . then ( ( ) => {
40- setActiveVersion ( latest )
41- setStatus ( ( prev ) => ( { ...prev , [ latest ] : 'loaded' } ) )
42- } )
73+ . then ( ( ) => {
74+ setActiveVersion ( latest )
75+ setStatus ( ( prev ) => ( { ...prev , [ latest ] : ByondStatus . Installed } ) )
76+ } )
4377 . catch ( ( loadError ) => {
44- setStatus ( ( prev ) => ( { ...prev , [ latest ] : 'error' } ) )
78+ setStatus ( ( prev ) => ( { ...prev , [ latest ] : ByondStatus . Error } ) )
4579 setError ( loadError instanceof Error ? loadError . message : 'Load failed' )
4680 } )
4781 }
@@ -54,7 +88,14 @@ export function ByondPanel() {
5488 const id = setTimeout ( ( ) => {
5589 void refresh ( )
5690 } , 0 )
57- return ( ) => clearTimeout ( id )
91+ const handleRefresh = ( ) => {
92+ void refresh ( )
93+ }
94+ window . addEventListener ( 'byond:refresh' , handleRefresh )
95+ return ( ) => {
96+ clearTimeout ( id )
97+ window . removeEventListener ( 'byond:refresh' , handleRefresh )
98+ }
5899 } , [ ] )
59100
60101 useEffect ( ( ) => {
@@ -76,18 +117,19 @@ export function ByondPanel() {
76117 } , [ ] )
77118
78119 const handleDownload = async ( version : string ) => {
79- setStatus ( ( prev ) => ( { ...prev , [ version ] : 'fetching' } ) )
120+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Fetching } ) )
80121 setError ( null )
81122 try {
82123 await byondService . downloadVersion ( version , ( value ) => {
83124 if ( value >= 1 ) {
84- setStatus ( ( prev ) => ( { ...prev , [ version ] : 'fetched' } ) )
125+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Fetched } ) )
85126 }
86127 } )
87- const localVersions = await byondService . getLocalVersions ( )
88- setLocal ( localVersions )
128+ // Ensure local state reflects the newly downloaded version immediately
129+ setLocal ( ( prev ) => ( prev . includes ( version ) ? prev : [ version , ...prev ] ) )
130+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Fetched } ) )
89131 } catch ( downloadError ) {
90- setStatus ( ( prev ) => ( { ...prev , [ version ] : 'error' } ) )
132+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Error } ) )
91133 setError ( downloadError instanceof Error ? downloadError . message : 'Download failed' )
92134 }
93135 }
@@ -106,29 +148,52 @@ export function ByondPanel() {
106148 }
107149
108150 const handleSetActive = ( version : string ) => {
109- setStatus ( ( prev ) => ( { ...prev , [ version ] : 'loading' } ) )
151+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Loading } ) )
110152 void byondService
111153 . load ( version , true )
112154 . then ( ( ) => {
113155 setActiveVersion ( version )
114- setStatus ( ( prev ) => ( { ...prev , [ version ] : 'loaded' } ) )
156+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Installed } ) )
115157 } )
116158 . catch ( ( loadError ) => {
117- setStatus ( ( prev ) => ( { ...prev , [ version ] : 'error' } ) )
159+ setStatus ( ( prev ) => ( { ...prev , [ version ] : ByondStatus . Error } ) )
118160 setError ( loadError instanceof Error ? loadError . message : 'Load failed' )
119161 } )
120162 }
121163
122164 return (
123165 < div className = "flex h-full flex-col gap-3 text-sm text-slate-300" >
124- < div className = "flex items-center justify-between" >
125- < span className = "text-xs text-slate-400" > BYOND versions</ span >
166+ < div className = "flex items-center gap-2" >
167+ < label className = "text-xs text-slate-400" > BYOND Version:</ label >
168+ < input
169+ type = "number"
170+ min = { 0 }
171+ value = { customMajor }
172+ onChange = { ( e ) => setCustomMajor ( e . target . value ) }
173+ className = "w-20 rounded border border-slate-700 bg-transparent px-2 py-1 text-xs text-slate-200"
174+ placeholder = "major"
175+ />
176+ < input
177+ type = "number"
178+ min = { 0 }
179+ value = { customMinor }
180+ onChange = { ( e ) => setCustomMinor ( e . target . value ) }
181+ className = "w-24 rounded border border-slate-700 bg-transparent px-2 py-1 text-xs text-slate-200"
182+ placeholder = "minor"
183+ />
126184 < button
127185 type = "button"
128- onClick = { ( ) => void refresh ( ) }
129- className = "rounded border border-slate-700 px-2 py-1 text-xs text-slate-200 hover:border-slate-500"
186+ onClick = { ( ) => {
187+ const version = `${ customMajor } .${ customMinor } `
188+ if ( ! customMajor || ! customMinor ) return
189+ if ( local . includes ( version ) ) return
190+ void handleDownload ( version )
191+ } }
192+ disabled = { ! customMajor || ! customMinor || local . includes ( `${ customMajor } .${ customMinor } ` ) }
193+ title = { local . includes ( `${ customMajor } .${ customMinor } ` ) ? 'Version already installed' : undefined }
194+ className = "rounded border border-slate-700 px-2 py-1 text-xs text-slate-200 hover:border-slate-500 disabled:opacity-50"
130195 >
131- Refresh
196+ Fetch
132197 </ button >
133198 </ div >
134199
@@ -144,10 +209,11 @@ export function ByondPanel() {
144209 </ tr >
145210 </ thead >
146211 < tbody >
147- { topVersions . map ( ( version ) => {
212+ { displayVersions . map ( ( version ) => {
148213 const isLocal = local . includes ( version )
149214 const isActive = activeVersion === version
150- const versionStatus = status [ version ] ?? byondService . getStatus ( version )
215+ const isLatest = version === latestVersion
216+ const versionStatus = status [ version ] ?? ( isLocal ? ByondStatus . Installed : ByondStatus . Idle )
151217
152218 return (
153219 < tr key = { version } className = "border-t border-slate-800" >
@@ -180,7 +246,9 @@ export function ByondPanel() {
180246 < button
181247 type = "button"
182248 onClick = { ( ) => void handleDelete ( version ) }
183- className = "rounded border border-slate-700 px-2 py-1 text-[11px] text-slate-200 hover:border-slate-500"
249+ disabled = { isLatest || isActive }
250+ title = { isLatest || isActive ? 'Cannot delete the latest or active version' : undefined }
251+ className = "rounded border border-slate-700 px-2 py-1 text-[11px] text-slate-200 hover:border-slate-500 disabled:opacity-50"
184252 >
185253 Delete
186254 </ button >
0 commit comments