Skip to content

Commit 8b58308

Browse files
committed
thinner class
1 parent 9199a7b commit 8b58308

File tree

7 files changed

+817
-936
lines changed

7 files changed

+817
-936
lines changed

src/client/components/DevToolsPane.js

Lines changed: 155 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,11 @@ function DevToolsContent({ world }) {
272272
const [allApps, setAllApps] = useState([])
273273
const [connectionStatus, setConnectionStatus] = useState('disconnected')
274274
const [isConnecting, setIsConnecting] = useState(false)
275+
const [isLoadingApps, setIsLoadingApps] = useState(false)
275276
const [lastError, setLastError] = useState(null)
277+
const [lastSuccess, setLastSuccess] = useState(null)
276278
const [activeTab, setActiveTab] = useState('linked')
279+
const [actionLoading, setActionLoading] = useState({})
277280

278281
useEffect(() => {
279282
// Initialize server URL from AppServerClient if available
@@ -288,9 +291,11 @@ function DevToolsContent({ world }) {
288291
// Listen for app linked/unlinked events
289292
const onAppLinked = ({ appName, linkInfo }) => {
290293
loadApps()
294+
showSuccess(`${appName} linked successfully`)
291295
}
292296
const onAppUnlinked = ({ appName }) => {
293297
loadApps()
298+
showSuccess(`${appName} unlinked successfully`)
294299
}
295300

296301
world.on('app_linked', onAppLinked)
@@ -302,29 +307,56 @@ function DevToolsContent({ world }) {
302307
}
303308
}, [])
304309

310+
// Utility functions for showing feedback
311+
const showSuccess = (message) => {
312+
setLastSuccess(message)
313+
setLastError(null)
314+
setTimeout(() => setLastSuccess(null), 3000)
315+
}
316+
317+
const showError = (message) => {
318+
setLastError(message)
319+
setLastSuccess(null)
320+
}
321+
322+
const setActionLoadingState = (action, isLoading) => {
323+
setActionLoading(prev => ({ ...prev, [action]: isLoading }))
324+
}
325+
305326
const checkConnection = async () => {
306327
try {
307328
setIsConnecting(true)
308329
setLastError(null)
330+
setLastSuccess(null)
309331

310332
const response = await fetch(`${serverUrl}/health`)
311333
if (response.ok) {
312334
setConnectionStatus('connected')
335+
showSuccess('Connected to development server')
313336
await loadApps()
314337
} else {
315338
setConnectionStatus('error')
316-
setLastError('Server responded with error')
339+
showError('Server responded with error')
317340
}
318341
} catch (error) {
319342
setConnectionStatus('disconnected')
320-
setLastError(error.message)
343+
showError(`Connection failed: ${error.message}`)
321344
} finally {
322345
setIsConnecting(false)
323346
}
324347
}
325348

349+
const disconnect = () => {
350+
setConnectionStatus('disconnected')
351+
setLinkedApps([])
352+
setAllApps([])
353+
showSuccess('Disconnected from development server')
354+
}
355+
326356
const loadApps = async () => {
327357
try {
358+
setIsLoadingApps(true)
359+
328360
// Load all apps
329361
const allAppsResponse = await fetch(`${serverUrl}/api/apps`)
330362
if (allAppsResponse.ok) {
@@ -333,14 +365,17 @@ function DevToolsContent({ world }) {
333365
}
334366

335367
// Load linked apps for current world
336-
const worldUrl = world.network?.ws?.url || 'unknown'
368+
const worldUrl = world.network?.apiUrl.split("/api")[0]
337369
const linkedAppsResponse = await fetch(`${serverUrl}/api/linked-apps?worldUrl=${encodeURIComponent(worldUrl)}`)
338370
if (linkedAppsResponse.ok) {
339371
const { apps } = await linkedAppsResponse.json()
340372
setLinkedApps(apps || [])
341373
}
342374
} catch (error) {
343375
console.warn('Failed to load apps:', error)
376+
showError('Failed to load apps list')
377+
} finally {
378+
setIsLoadingApps(false)
344379
}
345380
}
346381

@@ -356,53 +391,48 @@ function DevToolsContent({ world }) {
356391

357392
const unlinkApp = async (appName) => {
358393
try {
394+
setActionLoadingState(`unlink-${appName}`, true)
395+
359396
const response = await fetch(`${serverUrl}/api/apps/${appName}/unlink`, {
360397
method: 'POST'
361398
})
362399

363400
if (response.ok) {
364-
console.log(`✅ Unlinked ${appName}`)
401+
showSuccess(`${appName} unlinked successfully`)
365402
await loadApps()
366403
} else {
367404
throw new Error('Failed to unlink app')
368405
}
369406
} catch (error) {
370407
console.error(`❌ Failed to unlink ${appName}:`, error)
371-
alert(`Failed to unlink ${appName}: ${error.message}`)
408+
showError(`Failed to unlink ${appName}: ${error.message}`)
409+
} finally {
410+
setActionLoadingState(`unlink-${appName}`, false)
372411
}
373412
}
374413

375414
const pushApp = async (appName) => {
376415
try {
377-
// Find the app in the current world
378-
let targetApp = null
379-
for (const [_, entity] of world.entities.items) {
380-
if (entity.isApp && entity.blueprint?.name === appName) {
381-
targetApp = entity
382-
break
383-
}
384-
}
385-
386-
if (!targetApp) {
387-
throw new Error(`App ${appName} not found in current world`)
388-
}
416+
setActionLoadingState(`push-${appName}`, true)
389417

390418
const response = await fetch(`${serverUrl}/api/apps/${appName}/deploy`, {
391419
method: 'POST',
392420
headers: { 'Content-Type': 'application/json' },
393421
body: JSON.stringify({
394-
position: targetApp.data.position || [0, 0, 0]
422+
position: [0, 0, 0]
395423
})
396424
})
397425

398426
if (response.ok) {
399-
console.log(`🚀 Deployed ${appName} from development server`)
427+
showSuccess(`${appName} deployed successfully`)
400428
} else {
401429
throw new Error('Failed to deploy app')
402430
}
403431
} catch (error) {
404432
console.error(`❌ Failed to deploy ${appName}:`, error)
405-
alert(`Failed to deploy ${appName}: ${error.message}`)
433+
showError(`Failed to deploy ${appName}: ${error.message}`)
434+
} finally {
435+
setActionLoadingState(`push-${appName}`, false)
406436
}
407437
}
408438

@@ -417,33 +447,93 @@ function DevToolsContent({ world }) {
417447

418448
<FieldText
419449
label='Server Port'
420-
hint='Port number for the local development server'
450+
hint={connectionStatus === 'connected' ? 'Disconnect to change port' : 'Port number for the local development server'}
421451
value={customPort}
422-
onChange={setCustomPort}
452+
onChange={connectionStatus === 'connected' ? () => {} : setCustomPort}
423453
/>
424454

425-
<FieldBtn
426-
label='Connect'
427-
hint='Connect to the development server'
428-
onClick={connectWithCustomPort}
429-
disabled={isConnecting}
430-
/>
455+
{connectionStatus !== 'connected' && (
456+
<FieldBtn
457+
label={isConnecting ? 'Connecting...' : 'Connect'}
458+
hint='Connect to the development server'
459+
onClick={connectWithCustomPort}
460+
disabled={isConnecting}
461+
/>
462+
)}
463+
464+
{connectionStatus === 'connected' && (
465+
<FieldBtn
466+
label='Disconnect'
467+
hint='Disconnect from the development server'
468+
onClick={disconnect}
469+
disabled={isConnecting}
470+
/>
471+
)}
431472

432473
<FieldBtn
433-
label='Refresh Status'
474+
label={isConnecting ? 'Checking...' : 'Refresh Status'}
434475
hint='Check the current connection status'
435476
onClick={checkConnection}
436477
disabled={isConnecting}
437478
/>
438479

439-
{connectionStatus === 'connected' && (
440-
<FieldBtn
441-
label='Open Server UI'
442-
hint='Open the development server web interface'
443-
onClick={() => window.open(serverUrl, '_blank')}
444-
/>
480+
{/* Connection Status Display */}
481+
<div
482+
css={css`
483+
margin: 0.5rem 1rem;
484+
padding: 0.75rem 1rem;
485+
border-radius: 0.375rem;
486+
font-size: 0.9rem;
487+
display: flex;
488+
align-items: center;
489+
gap: 0.5rem;
490+
${connectionStatus === 'connected' && `
491+
background: rgba(16, 185, 129, 0.1);
492+
border: 1px solid rgba(16, 185, 129, 0.3);
493+
color: #a7f3d0;
494+
`}
495+
${connectionStatus === 'disconnected' && `
496+
background: rgba(156, 163, 175, 0.1);
497+
border: 1px solid rgba(156, 163, 175, 0.3);
498+
color: #d1d5db;
499+
`}
500+
${connectionStatus === 'error' && `
501+
background: rgba(239, 68, 68, 0.1);
502+
border: 1px solid rgba(239, 68, 68, 0.3);
503+
color: #fca5a5;
504+
`}
505+
`}
506+
>
507+
{connectionStatus === 'connected' && <CheckCircleIcon size={16} />}
508+
{connectionStatus === 'disconnected' && <WifiOffIcon size={16} />}
509+
{connectionStatus === 'error' && <XCircleIcon size={16} />}
510+
{connectionStatus === 'connected' && `Connected to ${serverUrl}`}
511+
{connectionStatus === 'disconnected' && 'Not connected to development server'}
512+
{connectionStatus === 'error' && 'Connection error'}
513+
</div>
514+
515+
{/* Success Message */}
516+
{lastSuccess && (
517+
<div
518+
css={css`
519+
background: rgba(16, 185, 129, 0.1);
520+
border: 1px solid rgba(16, 185, 129, 0.3);
521+
border-radius: 0.375rem;
522+
padding: 0.75rem 1rem;
523+
color: #a7f3d0;
524+
font-size: 0.9rem;
525+
margin: 0.5rem 1rem;
526+
display: flex;
527+
align-items: center;
528+
gap: 0.5rem;
529+
`}
530+
>
531+
<CheckCircleIcon size={16} />
532+
{lastSuccess}
533+
</div>
445534
)}
446535

536+
{/* Error Message */}
447537
{lastError && (
448538
<div
449539
css={css`
@@ -454,8 +544,12 @@ function DevToolsContent({ world }) {
454544
color: #fca5a5;
455545
font-size: 0.9rem;
456546
margin: 0.5rem 1rem;
547+
display: flex;
548+
align-items: center;
549+
gap: 0.5rem;
457550
`}
458551
>
552+
<XCircleIcon size={16} />
459553
{lastError}
460554
</div>
461555
)}
@@ -536,43 +630,36 @@ function DevToolsContent({ world }) {
536630
<div className="app-item-info-name">{app.name}</div>
537631
<div className="app-item-info-details">
538632
{app.assets.length} assets • {app.script ? 'Has script' : 'No script'}
539-
{app.linkInfo && <> • Linked to {(() => {
540-
try {
541-
return new URL(app.linkInfo.worldUrl).pathname
542-
} catch {
543-
return app.linkInfo.worldUrl
544-
}
545-
})()}</>}
546633
</div>
547634
</div>
548635
<div className="app-item-actions">
549636
<div
550-
className="app-item-btn"
551-
onClick={() => pushApp(app.name)}
637+
className={`app-item-btn ${actionLoading[`push-${app.name}`] ? 'loading' : ''}`}
638+
onClick={() => !actionLoading[`push-${app.name}`] && pushApp(app.name)}
552639
title="Deploy local changes to world"
640+
style={{ opacity: actionLoading[`push-${app.name}`] ? 0.5 : 1 }}
553641
>
554-
<UploadIcon size={14} />
642+
{actionLoading[`push-${app.name}`] ? (
643+
<RefreshCwIcon size={14} className="spin" />
644+
) : (
645+
<UploadIcon size={14} />
646+
)}
555647
</div>
556648
<div
557-
className="app-item-btn"
649+
className={`app-item-btn danger ${actionLoading[`unlink-${app.name}`] ? 'loading' : ''}`}
558650
onClick={() => {
559-
console.log(`App folder: tools/apps/${app.name}/`)
560-
alert(`App folder: tools/apps/${app.name}/\n\nEdit index.js to modify the app.`)
561-
}}
562-
title="Show app folder location"
563-
>
564-
<ExternalLinkIcon size={14} />
565-
</div>
566-
<div
567-
className="app-item-btn danger"
568-
onClick={() => {
569-
if (confirm(`Unlink ${app.name}? This will remove the connection but keep the local files.`)) {
651+
if (!actionLoading[`unlink-${app.name}`] && confirm(`Unlink ${app.name}? This will remove the connection but keep the local files.`)) {
570652
unlinkApp(app.name)
571653
}
572654
}}
573655
title="Unlink app from development server"
656+
style={{ opacity: actionLoading[`unlink-${app.name}`] ? 0.5 : 1 }}
574657
>
575-
<TrashIcon size={14} />
658+
{actionLoading[`unlink-${app.name}`] ? (
659+
<RefreshCwIcon size={14} className="spin" />
660+
) : (
661+
<TrashIcon size={14} />
662+
)}
576663
</div>
577664
</div>
578665
</div>
@@ -601,21 +688,16 @@ function DevToolsContent({ world }) {
601688
</div>
602689
<div className="app-item-actions">
603690
<div
604-
className="app-item-btn"
605-
onClick={() => pushApp(app.name)}
691+
className={`app-item-btn ${actionLoading[`push-${app.name}`] ? 'loading' : ''}`}
692+
onClick={() => !actionLoading[`push-${app.name}`] && pushApp(app.name)}
606693
title="Deploy app to world"
694+
style={{ opacity: actionLoading[`push-${app.name}`] ? 0.5 : 1 }}
607695
>
608-
<UploadIcon size={14} />
609-
</div>
610-
<div
611-
className="app-item-btn"
612-
onClick={() => {
613-
console.log(`App folder: tools/apps/${app.name}/`)
614-
alert(`App folder: tools/apps/${app.name}/\n\nEdit index.js to modify the app.`)
615-
}}
616-
title="Show app folder location"
617-
>
618-
<ExternalLinkIcon size={14} />
696+
{actionLoading[`push-${app.name}`] ? (
697+
<RefreshCwIcon size={14} className="spin" />
698+
) : (
699+
<UploadIcon size={14} />
700+
)}
619701
</div>
620702
</div>
621703
</div>
@@ -628,10 +710,10 @@ function DevToolsContent({ world }) {
628710
<Group label='Quick Actions' />
629711

630712
<FieldBtn
631-
label='Refresh Apps'
713+
label={isLoadingApps ? 'Refreshing...' : 'Refresh Apps'}
632714
hint='Manually refresh the list of apps'
633715
onClick={loadApps}
634-
disabled={connectionStatus !== 'connected'}
716+
disabled={connectionStatus !== 'connected' || isLoadingApps}
635717
/>
636718
</div>
637719
)

0 commit comments

Comments
 (0)