Skip to content

Commit

Permalink
feat: token items load bitmap (#358)
Browse files Browse the repository at this point in the history
* feat: tokens now display but only until something happens
  • Loading branch information
micahg authored Jan 18, 2025
1 parent d8366d5 commit 9a550a6
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 58 deletions.
12 changes: 6 additions & 6 deletions packages/mui/src/components/ContentEditor/ContentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ const ContentEditor = ({
const handleDrawables = useCallback(() => {
if (!scene) return;
if (!worker) return;
if (!apiUrl) return;
if (!bearer) return;

// we cannot pre-translate these into drawables because properties
// that are methods do not survive the transfer to the worker
Expand All @@ -347,8 +349,8 @@ const ContentEditor = ({

// scene.tokens?.forEach((token) => things.push(token));
// need to delay this until we know we're in a good state.
worker.postMessage({ cmd: "things", values: { things } });
}, [worker, scene]);
worker.postMessage({ cmd: "things", values: { apiUrl, bearer, things } });
}, [apiUrl, bearer, worker, scene]);

useEffect(() => {
if (!internalState || !toolbarPopulated) return;
Expand Down Expand Up @@ -801,10 +803,6 @@ const ContentEditor = ({

// draw the viewport (and possibly tokens) to the canvas
handleDrawables();

// scene.tokens?.forEach((token) => things.push(token));
// // need to delay this until we know we're in a good state.
// worker.postMessage({ cmd: "things", values: { things } });
}, [displayViewport, scene, sceneId, worker, sceneUpdated, handleDrawables]);

/**
Expand All @@ -817,6 +815,8 @@ const ContentEditor = ({

// draw the viewport (and possibly tokens) to the canvas
if (scene.tokens && handleDrawables) {
// TODO MICAH HYDRATE SCENE TOKENS HERE WHENEVER THEY CHANGE
console.log(scene.tokens);
handleDrawables();
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { debounce } from "lodash";
import { Rect, TableState } from "@micahg/tbltp-common";

// TODO UNION MICAH DON"T SKIP NOW
// BEFORE MERGING THIS BRANCH - this doesn't need to be *here* but it does need to be somewhere.
// Its used exclusively in the worker so it probably should be in the worker file? things could
// also be typed a little better (HydratedTokenInstance | Rect)
export type TableUpdate = TableState & {
apiUrl: string;
bearer: string;
things?: unknown[];
};
Expand Down
31 changes: 27 additions & 4 deletions packages/mui/src/reducers/ContentReducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { PayloadAction } from "@reduxjs/toolkit";
import { Asset, Scene, Token, TokenInstance } from "@micahg/tbltp-common";
import {
Asset,
HydratedTokenInstance,
Scene,
Token,
TokenInstance,
} from "@micahg/tbltp-common";

// copied from the api
interface TableTop {
Expand Down Expand Up @@ -144,13 +150,30 @@ export const ContentReducer = (state = initialState, action: PayloadAction) => {
if (!scene) return state;

const tokens = action.payload as unknown as TokenInstance[];
const hydrated: HydratedTokenInstance[] = [];

const scenes = state.scenes;
const idx = state.scenes.findIndex((s) => s._id === scene._id);

scenes[idx] = { ...scenes[idx], tokens };
const newScene = { ...scene, tokens };
console.log(`MICAH updating current scene ${action.type}`);
for (const instance of tokens) {
const token = state.tokens?.find((t) => t._id === instance.token);
if (!token) {
console.error(`Unable to find token for instance ${instance._id}`);
continue;
}
console.log(token.asset);
const asset = state.assets?.find((a) => a._id === token.asset);
if (!asset || !asset.location) {
console.error(
`Unable to find asset for token instance ${instance._id}, asset ${token.asset}`,
);
continue;
}
hydrated.push({ ...instance, token: asset.location });
}

scenes[idx] = { ...scenes[idx], tokens: hydrated };
const newScene = { ...scene, tokens: hydrated };
return { ...state, scenes: [...scenes], currentScene: newScene };
break;
}
Expand Down
29 changes: 21 additions & 8 deletions packages/mui/src/utils/contentworker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,17 +543,30 @@ function adjustZoom(zoom: number, x: number, y: number) {
renderAllCanvasses(backgroundImage);
}

function updateThings(things?: unknown[], render = false) {
async function updateThings(
apiUrl: string,
bearer: string,
things?: unknown[],
render = false,
) {
// clear the existing thing list
_things.length = 0;

// cheese it if there are no things to render
if (!things) return;

things
.filter((thing) => thing)
.map((thing) => createDrawable(thing))
.forEach((thing) => (thing ? _things.push(thing) : null));
const promises: Promise<Drawable>[] = [];
for (const thing of things.filter((thing) => thing)) {
promises.push(createDrawable(thing, apiUrl, bearer));
}
let drawables: Drawable[];
try {
drawables = await Promise.all(promises);
} catch (err) {
console.error(`Unable to load things: ${JSON.stringify(err)}`);
return;
}
drawables.forEach((d) => _things.push(d));

// render if we're asked (avoided in cases of subsequent full renders)
if (!render) return;
Expand All @@ -562,14 +575,14 @@ function updateThings(things?: unknown[], render = false) {
}

async function update(values: TableUpdate) {
const { angle, background, viewport, things } = values;
const { apiUrl, bearer, angle, background, viewport, things } = values;
if (!background) {
if (things) return updateThings(things, true);
if (things) return updateThings(apiUrl, bearer, things, true);
console.error(`Ignoring update without background`);
return;
}
_angle = angle;
updateThings(things);
updateThings(apiUrl, bearer, things);

if (viewport) {
copyRect(viewport, _img_orig);
Expand Down
117 changes: 77 additions & 40 deletions packages/mui/src/utils/drawing.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Rect, Token, TokenInstance } from "@micahg/tbltp-common";
import { Rect, HydratedTokenInstance } from "@micahg/tbltp-common";
import { loadImage } from "./content";

export type DrawContext = CanvasDrawPath &
CanvasPathDrawingStyles &
CanvasFillStrokeStyles &
CanvasTransform &
CanvasDrawImage &
CanvasPath;

export interface Drawable {
Expand All @@ -11,14 +14,32 @@ export interface Drawable {

export type Thing = SelectedRegion | Marker;

export function isRect(r: unknown): r is Rect {
type BitmapCache = {
[key: string]: ImageBitmap;
};

const cache: BitmapCache = {};

export function isRect(d: unknown): d is Rect {
return (
!!d &&
typeof d === "object" &&
typeof (d as Rect).x === "number" &&
typeof (d as Rect).y === "number" &&
typeof (d as Rect).width === "number" &&
typeof (d as Rect).height === "number"
);
}

export function isHydratedTokenInstnace(
d: unknown,
): d is HydratedTokenInstance {
return (
!!r &&
typeof r === "object" &&
typeof (r as Rect).x === "number" &&
typeof (r as Rect).y === "number" &&
typeof (r as Rect).width === "number" &&
typeof (r as Rect).height === "number"
!!d &&
typeof d === "object" &&
typeof (d as HydratedTokenInstance).x === "number" &&
typeof (d as HydratedTokenInstance).y === "number" &&
typeof (d as HydratedTokenInstance).token === "string"
);
}

Expand All @@ -32,10 +53,16 @@ export type Marker = {
value: "hi";
};

export function createDrawable<T = Rect>(d: T): Drawable {
// if (isPoint(d)) return new DrawablePoint(d);
// if (isToken(d)) return new DrawableToken(d);
export async function createDrawable<T = Rect>(
d: T,
apiUrl: string,
bearer: string,
): Promise<Drawable> {
if (isRect(d)) return new DrawableSelectedRegion(d);
if (isHydratedTokenInstnace(d)) {
const img = await cacheTokenImage(apiUrl, d.token, bearer);
return new DrawableToken(d, img);
}
throw new TypeError("Invalid Drawable");
}

Expand Down Expand Up @@ -68,32 +95,42 @@ class DrawableSelectedRegion implements Drawable {
}
}

// MICAH: NEED HYDRATED INSTANCE TOKEN THAT INCLUDES IMAGE + A TOKEN CACHE
// class DrawableToken implements Drawable {
// token: TokenInstance;
// constructor(token: TokenInstance) {
// this.token = token;
// }
// draw(ctx: DrawContext) {
// this.token.token
// // ctx.beginPath();
// // ctx.arc(this.token.x, this.token.y, 10, 0, 2 * Math.PI);
// // ctx.fillStyle = "black";
// // ctx.fill();
// const [_token_dw, _token_dh] = [this.token.width, this.token.height];
// ctx.translate(-_token_dw / 2, -_token_dh / 2);
// ctx.drawImage(
// vamp,
// // source (should always just be source dimensions)
// 0,
// 0,
// vamp.width,
// vamp.height,
// // destination (adjust according to scale)
// x,
// y,
// _token_dw,
// _token_dh,
// );
// }
// }
// TODO try with bad link and see what happens before merging - ideally fallack to X
async function cacheTokenImage(
apiUrl: string,
location: string,
bearer: string,
) {
if (location in cache) return cache[location];
console.warn(`Cache miss for token ${location}`);
const url = `${apiUrl}/${location}`;
const img = await loadImage(url, bearer);
cache[location] = img;
return img;
}

class DrawableToken implements Drawable {
token: HydratedTokenInstance;
img: ImageBitmap;
constructor(token: HydratedTokenInstance, img: ImageBitmap) {
this.token = token;
this.img = img;
}
draw(ctx: DrawContext) {
const [_token_dw, _token_dh] = [this.img.width, this.img.height];
ctx.translate(-_token_dw / 2, -_token_dh / 2);
ctx.drawImage(
this.img,
// source (should always just be source dimensions)
0,
0,
this.img.width,
this.img.height,
// destination (adjust according to scale)
this.token.x,
this.token.y,
_token_dw,
_token_dh,
);
}
}

0 comments on commit 9a550a6

Please sign in to comment.