-
Notifications
You must be signed in to change notification settings - Fork 374
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
091aa3c
commit 47844e7
Showing
54 changed files
with
2,980 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Throwing Promises |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { getShip, searchShips } from '#shared/ship-api-utils.server' | ||
|
||
export type Ship = Awaited<ReturnType<typeof getShip>> | ||
export type ShipSearch = Awaited<ReturnType<typeof searchShips>> | ||
|
||
export async function loader({ | ||
request, | ||
params, | ||
}: { | ||
request: Request | ||
params: Record<string, string> | ||
}) { | ||
const path = params['*'] | ||
switch (path) { | ||
case 'search-ships': { | ||
const result = await searchShips(request) | ||
return new Response(JSON.stringify(result), { | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
} | ||
case 'get-ship': { | ||
const result = await getShip(request) | ||
return new Response(JSON.stringify(result), { | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
} | ||
default: { | ||
return new Response('Not found', { status: 404 }) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
body { | ||
margin: 0; | ||
} | ||
|
||
* { | ||
box-sizing: border-box; | ||
} | ||
|
||
.app-wrapper { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
height: 100vh; | ||
} | ||
|
||
.app { | ||
display: flex; | ||
max-width: 1024px; | ||
border: 1px solid #000; | ||
border-start-end-radius: 0.5rem; | ||
border-start-start-radius: 0.5rem; | ||
border-end-start-radius: 50% 8%; | ||
border-end-end-radius: 50% 8%; | ||
overflow: hidden; | ||
} | ||
|
||
.search { | ||
width: 150px; | ||
max-height: 400px; | ||
overflow: hidden; | ||
display: flex; | ||
flex-direction: column; | ||
|
||
input { | ||
width: 100%; | ||
border: 0; | ||
border-bottom: 1px solid #000; | ||
padding: 8px; | ||
line-height: 1.5; | ||
border-top-left-radius: 0.5rem; | ||
} | ||
|
||
ul { | ||
flex: 1; | ||
list-style: none; | ||
padding: 4px; | ||
padding-bottom: 30px; | ||
margin: 0; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 8px; | ||
overflow-y: auto; | ||
li { | ||
button { | ||
display: flex; | ||
align-items: center; | ||
gap: 4px; | ||
border: none; | ||
background-color: transparent; | ||
&:hover { | ||
text-decoration: underline; | ||
} | ||
img { | ||
width: 20px; | ||
height: 20px; | ||
object-fit: contain; | ||
border-radius: 50%; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
.details { | ||
flex: 1; | ||
height: 400px; | ||
position: relative; | ||
overflow: hidden; | ||
} | ||
|
||
.ship-info { | ||
height: 100%; | ||
width: 300px; | ||
margin: auto; | ||
overflow: auto; | ||
background-color: #eee; | ||
border-radius: 4px; | ||
padding: 20px; | ||
position: relative; | ||
} | ||
|
||
.ship-info.ship-loading { | ||
opacity: 0.6; | ||
} | ||
|
||
.ship-info h2 { | ||
font-weight: bold; | ||
text-align: center; | ||
margin-top: 0.3em; | ||
} | ||
|
||
.ship-info img { | ||
width: 100%; | ||
height: 100%; | ||
aspect-ratio: 1; | ||
object-fit: contain; | ||
} | ||
|
||
.ship-info .ship-info__img-wrapper { | ||
margin-top: 20px; | ||
width: 100%; | ||
height: 200px; | ||
} | ||
|
||
.ship-info .ship-info__fetch-time { | ||
position: absolute; | ||
top: 6px; | ||
right: 10px; | ||
} | ||
|
||
.app-error { | ||
position: relative; | ||
background-image: url('/img/broken-ship.webp'); | ||
background-size: contain; | ||
background-repeat: no-repeat; | ||
background-position: center; | ||
width: 400px; | ||
height: 400px; | ||
p { | ||
position: absolute; | ||
top: 30%; | ||
left: 50%; | ||
transform: translate(-50%, -50%); | ||
background-color: white; | ||
padding: 6px 12px; | ||
border-radius: 1rem; | ||
font-size: 1.5rem; | ||
font-weight: bold; | ||
width: 300px; | ||
text-align: center; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { Suspense } from 'react' | ||
import * as ReactDOM from 'react-dom/client' | ||
import { getImageUrlForShip, getShip, type Ship } from './utils' | ||
|
||
const shipName = 'Dreadnought' | ||
|
||
function App() { | ||
return ( | ||
<div className="app-wrapper"> | ||
<div className="app"> | ||
<div className="details"> | ||
<Suspense fallback={<ShipFallback />}> | ||
<ShipDetails /> | ||
</Suspense> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
let ship: Ship | ||
const shipPromise = getShip(shipName).then(s => (ship = s)) | ||
function ShipDetails() { | ||
if (!ship) throw shipPromise | ||
|
||
return ( | ||
<div className="ship-info"> | ||
<div className="ship-info__img-wrapper"> | ||
<img | ||
src={getImageUrlForShip(ship.name, { size: 200 })} | ||
alt={ship.name} | ||
/> | ||
</div> | ||
<section> | ||
<h2> | ||
{ship.name} | ||
<sup> | ||
{ship.topSpeed} <small>lyh</small> | ||
</sup> | ||
</h2> | ||
</section> | ||
<section> | ||
{ship.weapons.length ? ( | ||
<ul> | ||
{ship.weapons.map(weapon => ( | ||
<li key={weapon.name}> | ||
<label>{weapon.name}</label>:{' '} | ||
<span> | ||
{weapon.damage} <small>({weapon.type})</small> | ||
</span> | ||
</li> | ||
))} | ||
</ul> | ||
) : ( | ||
<p>NOTE: This ship is not equipped with any weapons.</p> | ||
)} | ||
</section> | ||
<small className="ship-info__fetch-time">{ship.fetchedAt}</small> | ||
</div> | ||
) | ||
} | ||
|
||
function ShipFallback() { | ||
return ( | ||
<div className="ship-info"> | ||
<div className="ship-info__img-wrapper"> | ||
<img src="/img/fallback-ship.png" alt={shipName} /> | ||
</div> | ||
<section> | ||
<h2> | ||
{shipName} | ||
<sup> | ||
XX <small>lyh</small> | ||
</sup> | ||
</h2> | ||
</section> | ||
<section> | ||
<ul> | ||
{Array.from({ length: 3 }).map((_, i) => ( | ||
<li key={i}> | ||
<label>loading</label>:{' '} | ||
<span> | ||
XX <small>(loading)</small> | ||
</span> | ||
</li> | ||
))} | ||
</ul> | ||
</section> | ||
</div> | ||
) | ||
} | ||
|
||
const rootEl = document.createElement('div') | ||
document.body.append(rootEl) | ||
ReactDOM.createRoot(rootEl).render(<App />) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { type Ship } from './api.server' | ||
|
||
export type { Ship } | ||
|
||
export async function getShip(name: string, delay?: number) { | ||
const searchParams = new URLSearchParams({ name }) | ||
if (delay) searchParams.set('delay', String(delay)) | ||
const response = await fetch(`api/get-ship?${searchParams.toString()}`) | ||
if (!response.ok) { | ||
return Promise.reject(new Error(await response.text())) | ||
} | ||
const ship = await response.json() | ||
return ship as Ship | ||
} | ||
|
||
export function getImageUrlForShip( | ||
shipName: string, | ||
{ size }: { size: number }, | ||
) { | ||
return `/img/ships/${shipName.toLowerCase().replaceAll(' ', '-')}.webp?size=${size}` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Error Handling |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { getShip, searchShips } from '#shared/ship-api-utils.server' | ||
|
||
export type Ship = Awaited<ReturnType<typeof getShip>> | ||
export type ShipSearch = Awaited<ReturnType<typeof searchShips>> | ||
|
||
export async function loader({ | ||
request, | ||
params, | ||
}: { | ||
request: Request | ||
params: Record<string, string> | ||
}) { | ||
const path = params['*'] | ||
switch (path) { | ||
case 'search-ships': { | ||
const result = await searchShips(request) | ||
return new Response(JSON.stringify(result), { | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
} | ||
case 'get-ship': { | ||
const result = await getShip(request) | ||
return new Response(JSON.stringify(result), { | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
} | ||
default: { | ||
return new Response('Not found', { status: 404 }) | ||
} | ||
} | ||
} |
Oops, something went wrong.