Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WarpDrive | Commit By Commit #2

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
/.node_modules.ember-try/

/tmp/
generate.mjs
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
/DEBUG/

/tmp/
/public/api/
5 changes: 5 additions & 0 deletions app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@
font-family: "Pixelify Sans", system-ui, sans-serif;
}
}

div.data-pokemon-thumbnail-loading {
width: 100%;
aspect-ratio: 1 / 1;
}
6 changes: 6 additions & 0 deletions app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from 'ember-polaris-pokedex/config/environment';
import './app.css';
import { setBuildURLConfig } from '@ember-data/request-utils';

setBuildURLConfig({
host: window.location.origin,
namespace: 'api',
});

// @ts-expect-error: no types for define
let d = window.define;
Expand Down
40 changes: 32 additions & 8 deletions app/components/pokemon-details.gts
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import Component from '@glimmer/component';
import type PokemonModel from 'ember-polaris-pokedex/models/pokemon';
import type { Pokemon } from 'ember-polaris-pokedex/schemas/pokemon';
import PokemonTypeBadge from 'ember-polaris-pokedex/components/pokemon-type-badge';
import PokemonEvolutionNav from 'ember-polaris-pokedex/components/pokemon-evolution-nav';
import { service } from '@ember/service';
import type { ImageFetch } from '@warp-drive/experiments/image-fetch';
import { cached } from '@glimmer/tracking';
import { getPromiseState } from '@warp-drive/ember';

export default class PokemonDetails extends Component<{
Args: { pokemon: PokemonModel; allPokemon: PokemonModel[] };
Args: { pokemon: Pokemon };
}> {
@service declare images: ImageFetch;

@cached
get detailImageRequest() {
return this.images.load(this.args.pokemon.image.hires);
}

@cached
get detailImageUrl() {
const state = getPromiseState(this.detailImage);
if (state.isError || state.isPending) {
return null;
}
return state.result;
}

<template>
<div
class='pokemon-details flex flex-col justify-center gap-16 md:flex-row'
>
<img
class='max-size-96 full-embed aspect-square size-96 animate-wiggle drop-shadow-2xl [animation-delay:_0.2s]'
src={{@pokemon.image.hires}}
alt={{@pokemon.name.english}}
/>
{{#if this.detailImageUrl}}
<img
class='max-size-96 full-embed aspect-square size-96 animate-wiggle drop-shadow-2xl [animation-delay:_0.2s]'
src={{this.detailImageUrl}}
alt={{@pokemon.name.english}}
/>
{{else}}
<div class='data-pokemon-thumbnail-loading'></div>
{{/if}}
<div class='max-w-96'>
<h2 class='text-4xl font-medium'>{{@pokemon.name.english}}</h2>
<p class='my-2 text-lg italic text-slate-700'>
Expand All @@ -36,7 +60,7 @@ export default class PokemonDetails extends Component<{
</div>
</div>

<PokemonEvolutionNav @pokemon={{@pokemon}} @allPokemon={{@allPokemon}} />
<PokemonEvolutionNav @pokemon={{@pokemon}} />

{{! prettier-ignore }}
<style>
Expand Down
21 changes: 12 additions & 9 deletions app/components/pokemon-evolution-nav.gts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import Component from '@glimmer/component';
import { get } from '@ember/helper';
import type PokemonModel from 'ember-polaris-pokedex/models/pokemon';
import type { Pokemon } from 'ember-polaris-pokedex/schemas/pokemon';
import type RouterService from '@ember/routing/router-service';
import { service } from '@ember/service';
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import { preloadImage } from 'ember-polaris-pokedex/components/pokemon-grid-item';
import type { ImageFetch } from '@warp-drive/experiments/image-fetch';

export function getPokemonById(pokemons: PokemonModel[], id: string) {
return pokemons.find((pokemon) => pokemon.id!.toString() === id);
// https://raw.githubusercontent.com/IgnaceMaes/pokemon-data.json/master/images/pokedex/hires/005.png
function getHiresImageForId(id: string) {
return `https://raw.githubusercontent.com/IgnaceMaes/pokemon-data.json/master/images/pokedex/hires/${id.padStart(
3,
'0',
)}.png`;
}

export default class PokemonEvolutionNav extends Component<{
Args: { pokemon: PokemonModel; allPokemon: PokemonModel[] };
Args: { pokemon: Pokemon };
}> {
@service declare router: RouterService;
@service declare images: ImageFetch;

transitionToPokemonDetails = (
pokemonId: string,
Expand All @@ -37,10 +42,8 @@ export default class PokemonEvolutionNav extends Component<{
};

preloadImageForPokemonId = (pokemonId: string) => {
const pokemon = getPokemonById(this.args.allPokemon, pokemonId);
if (pokemon) {
preloadImage(pokemon.image.hires);
}
const url = getHiresImageForId(pokemonId);
this.images.load(url);
};

<template>
Expand Down
53 changes: 37 additions & 16 deletions app/components/pokemon-grid-item.gts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import Component from '@glimmer/component';
import type PokemonModel from 'ember-polaris-pokedex/models/pokemon';
import type { Pokemon } from 'ember-polaris-pokedex/schemas/pokemon';
import { service } from '@ember/service';
import type RouterService from '@ember/routing/router-service';

export function preloadImage(imageUrl: string) {
const img = new Image();
img.src = imageUrl;
}
import type { ImageFetch } from '@warp-drive/experiments/image-fetch';
import { cached } from '@glimmer/tracking';
import { getPromiseState } from '@warp-drive/ember';

interface PokemonSignature {
Args: { pokemon: PokemonModel };
Args: { pokemon: Pokemon };
}

export default class PokemonGridItem extends Component<PokemonSignature> {
@service declare router: RouterService;
@service declare images: ImageFetch;

transitionToPokemonDetails = (pokemon: PokemonModel, event: MouseEvent) => {
transitionToPokemonDetails = (pokemon: Pokemon, event: MouseEvent) => {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
this.router.transitionTo('pokemon.pokemon', pokemon.id?.toString());
Expand All @@ -38,20 +37,42 @@ export default class PokemonGridItem extends Component<PokemonSignature> {
});
};

preloadImage = (imageUrl: string) => {
this.images.load(imageUrl);
};

@cached
get thumbnailRequest() {
return this.images.load(this.args.pokemon.image.thumbnail);
}

@cached
get thumbnailUrl() {
const state = getPromiseState(this.thumbnailRequest);
if (state.isError || state.isPending) {
return null;
}
return state.result;
}

<template>
<button
type='button'
class='revealing-image group flex cursor-pointer flex-col items-center rounded-xl bg-gradient-to-br from-pink-100 to-yellow-100 p-4 shadow transition-shadow hover:shadow-md'
{{on 'pointerenter' (fn preloadImage @pokemon.image.hires)}}
{{on 'pointerenter' (fn this.preloadImage @pokemon.image.hires)}}
{{on 'click' (fn this.transitionToPokemonDetails @pokemon)}}
>
<img
data-pokemon-thumbnail
class='block aspect-square w-full p-4 transition-transform group-hover:scale-125 group-hover:drop-shadow-xl'
loading='lazy'
src={{@pokemon.image.thumbnail}}
alt='{{@pokemon.name.english}} thumbnail'
/>
{{#if this.thumbnailUrl}}
<img
data-pokemon-thumbnail
class='block aspect-square w-full p-4 transition-transform group-hover:scale-125 group-hover:drop-shadow-xl'
loading='lazy'
src={{this.thumbnailUrl}}
alt='{{@pokemon.name.english}} thumbnail'
/>
{{else}}
<div class='data-pokemon-thumbnail-loading'></div>
{{/if}}
<span class='mt-4 text-lg font-medium'>{{@pokemon.name.english}}</span>
</button>
</template>
Expand Down
2 changes: 1 addition & 1 deletion app/components/pokemon-type-badge.gts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PokemonType } from 'ember-polaris-pokedex/models/pokemon';
import type { PokemonType } from 'ember-polaris-pokedex/schemas/pokemon';
import { get } from '@ember/helper';
import type { TOC } from '@ember/component/template-only';
import {
Expand Down
34 changes: 34 additions & 0 deletions app/data-worker/data-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Store from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import JSONAPICache from '@ember-data/json-api';

import { DataWorker, CacheHandler } from '@warp-drive/experiments/data-worker';
import type { CacheCapabilitiesManager } from '@ember-data/store/types';
import { CachePolicy } from '@ember-data/request-utils';
import { SchemaService } from '@warp-drive/schema-record/schema';
import { register } from '../schemas/pokemon';
import { PokemonHandler } from '../utils/pokemon-api-handler';

class WorkerStore extends Store {
requestManager = new RequestManager()
.use([PokemonHandler, Fetch])
.useCache(CacheHandler);

lifetimes = new CachePolicy({
apiCacheHardExpires: 1000 * 60 * 60 * 48, // 48 hours
apiCacheSoftExpires: 1000 * 60 * 60, // 1 hour
});

createCache(capabilities: CacheCapabilitiesManager) {
return new JSONAPICache(capabilities);
}

createSchemaService() {
const schema = new SchemaService();
register(schema);
return schema;
}
}

new DataWorker(WorkerStore, { persisted: true });
8 changes: 8 additions & 0 deletions app/data-worker/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { WorkerFetch } from '@warp-drive/experiments/worker-fetch';

export const Fetch = new WorkerFetch(
new SharedWorker(new URL('./data-worker.ts', import.meta.url), {
name: 'DataWorker',
type: 'module',
}),
);
3 changes: 3 additions & 0 deletions app/data-worker/image-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ImageWorker } from '@warp-drive/experiments/image-worker';

new ImageWorker();
68 changes: 0 additions & 68 deletions app/models/pokemon.ts

This file was deleted.

8 changes: 5 additions & 3 deletions app/routes/application.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { query } from '@ember-data/rest/request';
import { query } from '@ember-data/json-api/request';
import type StoreService from '@ember-data/store';
import PokemonModel from 'ember-polaris-pokedex/models/pokemon';
import type { Pokemon } from 'ember-polaris-pokedex/schemas/pokemon';

export default class ApplicationRoute extends Route {
@service declare store: StoreService;

model() {
return {
pokemonRequest: this.store.request(query<PokemonModel>('pokemon')),
pokemonRequest: this.store.request(
query<Pokemon>('pokemon', {}, { resourcePath: 'pokemon/list.json' }),
),
};
}
}
16 changes: 11 additions & 5 deletions app/routes/pokemon/pokemon.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import Route from '@ember/routing/route';
import type { ModelFrom } from 'ember-polaris-pokedex/utils/ember-route-template';
import type ApplicationRoute from 'ember-polaris-pokedex/routes/application';
import { service } from '@ember/service';
import type Store from '@ember-data/store';
import { findRecord } from '@ember-data/json-api/request';

export default class PokemonRoute extends Route {
@service declare store: Store;

model(params: { pokemon_id: string }) {
const req = findRecord('pokemon', params.pokemon_id, {
resourcePath: `pokemon/single`,
});
req.url += '.json';

return {
pokemonRequest: (
this.modelFor('application') as ModelFrom<ApplicationRoute>
).pokemonRequest,
pokemonRequest: this.store.request(req),
id: params.pokemon_id,
};
}
Expand Down
Loading
Loading