Skip to content

Commit

Permalink
19 - DEMO4 issues sans ImageWorker
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Sep 12, 2024
1 parent eb7c912 commit 1449795
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 20 deletions.
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;
}
34 changes: 29 additions & 5 deletions app/components/pokemon-details.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,43 @@ import Component from '@glimmer/component';
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: 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 Down
5 changes: 3 additions & 2 deletions app/components/pokemon-evolution-nav.gts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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';

// https://raw.githubusercontent.com/IgnaceMaes/pokemon-data.json/master/images/pokedex/hires/005.png
function getHiresImageForId(id: string) {
Expand All @@ -19,6 +19,7 @@ export default class PokemonEvolutionNav extends Component<{
Args: { pokemon: Pokemon };
}> {
@service declare router: RouterService;
@service declare images: ImageFetch;

transitionToPokemonDetails = (
pokemonId: string,
Expand All @@ -42,7 +43,7 @@ export default class PokemonEvolutionNav extends Component<{

preloadImageForPokemonId = (pokemonId: string) => {
const url = getHiresImageForId(pokemonId);
preloadImage(url);
this.images.load(url);
};

<template>
Expand Down
48 changes: 35 additions & 13 deletions app/components/pokemon-grid-item.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ import Component from '@glimmer/component';
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: Pokemon };
}

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

transitionToPokemonDetails = (pokemon: Pokemon, event: MouseEvent) => {
// Fallback for browsers that don't support this API:
Expand All @@ -38,20 +37,43 @@ 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() {
return this.args.pokemon.image.thumbnail;
// 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
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();
156 changes: 156 additions & 0 deletions app/services/images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { createDeferred } from '@ember-data/request';
import type { Deferred } from '@ember-data/request/-private/types';

export type SuccessResponseEventData = {
type: 'success-response';
thread: string;
url: string;
};
export type ErrorResponseEventData = {
type: 'error-response';
thread: string;
url: string;
};

export type RequestEventData = {
type: 'load';
thread: string;
url: string;
};

export type ThreadInitEventData = {
type: 'connect';
thread: string;
};

export type MainThreadEvent = MessageEvent<
SuccessResponseEventData | ErrorResponseEventData
>;
export type WorkerThreadEvent =
| MessageEvent<RequestEventData>
| MessageEvent<ThreadInitEventData>;

import { assert } from '@ember/debug';

export interface FastBoot {
// eslint-disable-next-line no-unused-vars
require(_moduleName: string): unknown;
isFastBoot: boolean;
request: Request;
}

const isServerEnv = typeof FastBoot !== 'undefined';

// This is a copy of WarpDrive's ImageFetch class with
// a temporary fix for an issue with which url is returned.
export class ImageFetch {
declare worker: Worker | SharedWorker;
declare threadId: string;
declare pending: Map<string, Deferred<string>>;
declare channel: MessageChannel;
declare cache: Map<string, string>;
declare queue: MainThreadEvent[];

constructor(worker: Worker | SharedWorker | null) {
this.threadId = isServerEnv ? '' : crypto.randomUUID();
this.pending = new Map();
this.cache = new Map();
this.queue = [];

const isTesting = false;
assert(
`Expected a SharedWorker instance`,
isTesting || isServerEnv || worker instanceof SharedWorker,
);
this.worker = worker as SharedWorker;

if (!isServerEnv) {
const fn = (event: MainThreadEvent) => {
this.queue.push(event);
if (this.queue.length === 1) {
setTimeout(() => {
this.processQueue();
}, 0);
}
};

if (worker instanceof SharedWorker) {
worker.port.postMessage({ type: 'connect', thread: this.threadId });
worker.port.onmessage = fn;
} else if (worker) {
this.channel = new MessageChannel();
worker.postMessage({ type: 'connect', thread: this.threadId }, [
this.channel.port2,
]);

this.channel.port1.onmessage = fn;
}
}
}

processQueue() {
const queue = this.queue;
this.queue = [];

queue.forEach((event) => {
const { type, url, objectUrl } = event.data;
const deferred = this.cleanupRequest(url);
if (!deferred) {
return;
}

if (type === 'success-response') {
deferred.resolve(objectUrl);
return;
}

if (type === 'error-response') {
deferred.reject(null);
return;
}
});
}

cleanupRequest(url: string) {
const deferred = this.pending.get(url);
this.pending.delete(url);

return deferred;
}

_send(event: RequestEventData) {
this.worker instanceof SharedWorker
? this.worker.port.postMessage(event)
: this.channel.port1.postMessage(event);
}

load(url: string) {
if (isServerEnv) {
return Promise.resolve(url);
}

const objectUrl = this.cache.get(url);
if (objectUrl) {
return Promise.resolve(objectUrl);
}

const deferred = createDeferred<string>();
this.pending.set(url, deferred);
this._send({ type: 'load', thread: this.threadId, url });
return deferred.promise;
}
}

export default {
create() {
return new ImageFetch(
new SharedWorker(
new URL('../data-worker/image-worker.ts', import.meta.url),
{
name: 'ImageWorker',
type: 'module',
},
),
);
},
};

0 comments on commit 1449795

Please sign in to comment.