Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import {
isPluginAvailable,
} from "@/lib/capacitorBridge";
import { useDeepLinks } from "@/lib/deepLinks";
import { DatabaseIcon, PlayIcon } from "@/lib/images";
import {
ensurePurchasesUser,
getPurchaseAccess,
resetPurchasesUser,
} from "@/lib/purchasesSetup";
import { preloadZapLogo } from "@/lib/zapLogo";
import { routeTree } from "./routeTree.gen";
import { useStatusStore } from "./lib/store";
import { DatabaseIcon, PlayIcon, preloadZapLogo } from "./lib/images";
import { ConnectionProvider } from "./components/ConnectionProvider";
import { ReconnectingIndicator } from "./components/ReconnectingIndicator";
import { MediaFinishedToast } from "./components/MediaFinishedToast.tsx";
Expand Down
12 changes: 2 additions & 10 deletions src/__tests__/integration/create-mappings-edit.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,9 @@ vi.mock("react-hot-toast", () => ({
},
}));

vi.mock("@/lib/writeNfcHook", () => ({
vi.mock("@/lib/writeNfcHook", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/writeNfcHook")>()),
useNfcWriter: () => mockNfcWriter,
WriteMethod: {
Auto: "auto",
LocalNFC: "local",
RemoteReader: "remote",
},
WriteAction: {
Write: "write",
Read: "read",
},
}));

vi.mock("@capacitor-mlkit/barcode-scanning", () => ({
Expand Down
15 changes: 2 additions & 13 deletions src/__tests__/integration/index-route.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,9 @@ vi.mock("@/hooks/useScanOperations", () => ({
}));

// Mock useNfcWriter
vi.mock("@/lib/writeNfcHook", () => ({
vi.mock("@/lib/writeNfcHook", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/writeNfcHook")>()),
useNfcWriter: vi.fn(() => mockNfcWriterState),
WriteMethod: {
Auto: "auto",
LocalNFC: "local",
RemoteReader: "remote",
},
WriteAction: {
Write: "write",
Read: "read",
Format: "format",
Erase: "erase",
MakeReadOnly: "makeReadOnly",
},
}));

// Use vi.hoisted for pro purchase mock
Expand Down
7 changes: 4 additions & 3 deletions src/__tests__/unit/lib/images.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("Zap logo preload", () => {
}

vi.stubGlobal("Image", MockImage);
const { preloadZapLogo, ZAP_LOGO_URL } = await import("@/lib/images");
const { preloadZapLogo, ZAP_LOGO_URL } = await import("@/lib/zapLogo");

const first = preloadZapLogo();
const second = preloadZapLogo();
Expand Down Expand Up @@ -84,7 +84,8 @@ describe("Zap logo preload", () => {
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(
context as unknown as CanvasRenderingContext2D,
);
const { preloadZapLogo, ZapLogo } = await import("@/lib/images");
const { preloadZapLogo } = await import("@/lib/zapLogo");
const { ZapLogo } = await import("@/lib/images");
await preloadZapLogo();

render(createElement(ZapLogo));
Expand Down Expand Up @@ -140,7 +141,7 @@ describe("Zap logo preload", () => {
}

vi.stubGlobal("Image", MockImage);
const { preloadZapLogo } = await import("@/lib/images");
const { preloadZapLogo } = await import("@/lib/zapLogo");

await expect(preloadZapLogo()).resolves.toBeUndefined();
});
Expand Down
18 changes: 3 additions & 15 deletions src/__tests__/unit/routes/create.custom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,10 @@ vi.mock("@/lib/preferencesStore", () => ({
}),
}));

// Mock NFC writer
vi.mock("@/lib/writeNfcHook", () => ({
// Mock NFC writer, keeping the real enums and isWriteModalOpen
vi.mock("@/lib/writeNfcHook", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/writeNfcHook")>()),
useNfcWriter: () => mockNfcWriter,
WriteMethod: {
Auto: "auto",
LocalNFC: "local",
RemoteReader: "remote",
},
WriteAction: {
Write: "write",
Read: "read",
},
}));

// Mock hooks
Expand Down Expand Up @@ -102,10 +94,6 @@ vi.mock("@/components/ZapScriptInput.tsx", () => ({

// Mock WriteModal to simplify testing
vi.mock("@/components/WriteModal", () => ({
isWriteModalOpen: (
writeIntent: boolean,
writer: { status: unknown; verifyError: unknown },
) => writeIntent && (writer.status === null || writer.verifyError !== null),
WriteModal: ({ isOpen }: { isOpen: boolean }) =>
isOpen ? <div data-testid="write-modal">Write Modal</div> : null,
}));
Expand Down
18 changes: 3 additions & 15 deletions src/__tests__/unit/routes/create.index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,10 @@ vi.mock("@/lib/toastUtils", () => ({
showRateLimitedErrorToast: mockShowRateLimitedErrorToast,
}));

// Mock NFC writer
vi.mock("@/lib/writeNfcHook", () => ({
// Mock NFC writer, keeping the real enums and isWriteModalOpen
vi.mock("@/lib/writeNfcHook", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/writeNfcHook")>()),
useNfcWriter: () => mockNfcWriter,
WriteMethod: {
Auto: "auto",
LocalNFC: "local",
RemoteReader: "remote",
},
WriteAction: {
Write: "write",
Read: "read",
},
}));

// Mock Capacitor
Expand All @@ -143,10 +135,6 @@ vi.mock("@/hooks/usePageHeadingFocus", () => ({

// Mock WriteModal to simplify testing
vi.mock("@/components/WriteModal", () => ({
isWriteModalOpen: (
writeIntent: boolean,
writer: { status: unknown; verifyError: unknown },
) => writeIntent && (writer.status === null || writer.verifyError !== null),
WriteModal: ({ isOpen, close }: { isOpen: boolean; close: () => void }) =>
isOpen ? (
<div data-testid="write-modal">
Expand Down
14 changes: 2 additions & 12 deletions src/__tests__/unit/routes/create.nfc.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,9 @@ vi.mock("@tanstack/react-router", async (importOriginal) => {
});

// Mock NFC writer
vi.mock("@/lib/writeNfcHook", () => ({
vi.mock("@/lib/writeNfcHook", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/writeNfcHook")>()),
useNfcWriter: () => ({ ...mockNfcWriter }),
WriteMethod: {
Auto: "auto",
LocalNFC: "local",
RemoteReader: "remote",
},
WriteAction: {
Write: "write",
Read: "read",
Format: "format",
Erase: "erase",
},
}));

// Mock hooks
Expand Down
8 changes: 0 additions & 8 deletions src/components/WriteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,10 @@ import { useSmartSwipe } from "@/hooks/useSmartSwipe";
import { useBackButtonHandler } from "@/hooks/useBackButtonHandler";
import { useFocusTrap } from "@/hooks/useFocusTrap";
import { ScanResult } from "@/lib/models";
import type { WriteNfcHook } from "@/lib/writeNfcHook";
import { ScanSpinner } from "./ScanSpinner";
import { Button } from "./wui/Button";
import { useAnnouncer } from "./A11yAnnouncer";

export function isWriteModalOpen(
writeIntent: boolean,
writer: Pick<WriteNfcHook, "status" | "verifyError">,
): boolean {
return writeIntent && (writer.status === null || writer.verifyError !== null);
}

export function WriteModal(props: {
isOpen: boolean;
close: () => void;
Expand Down
50 changes: 6 additions & 44 deletions src/lib/images.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,11 @@
import { useLayoutEffect, useRef } from "react";
import { useTranslation } from "react-i18next";

export const ZAP_LOGO_URL = `${__APP_BASE_PATH__}lockup.webp`;
export const ZAP_LOGO_WIDTH = 160;
export const ZAP_LOGO_HEIGHT = 36;

let zapLogoImage: HTMLImageElement | null = null;
let zapLogoReady: Promise<void> | null = null;

export function preloadZapLogo(): Promise<void> {
if (typeof Image === "undefined") return Promise.resolve();

if (!zapLogoReady) {
zapLogoImage = new Image(ZAP_LOGO_WIDTH, ZAP_LOGO_HEIGHT);
zapLogoImage.decoding = "sync";
zapLogoImage.fetchPriority = "high";
zapLogoImage.src = ZAP_LOGO_URL;
zapLogoReady =
typeof zapLogoImage.decode === "function"
? zapLogoImage.decode().catch(() => undefined)
: Promise.resolve();
}

return zapLogoReady;
}

function drawPreloadedZapLogo(
canvas: HTMLCanvasElement,
pixelRatio = window.devicePixelRatio || 1,
): boolean {
if (!zapLogoImage?.complete || zapLogoImage.naturalWidth === 0) return false;

const context = canvas.getContext("2d");
if (!context) return false;

const scale = Math.max(1, pixelRatio);
canvas.width = Math.round(ZAP_LOGO_WIDTH * scale);
canvas.height = Math.round(ZAP_LOGO_HEIGHT * scale);
context.setTransform(scale, 0, 0, scale, 0, 0);
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = "high";
context.clearRect(0, 0, ZAP_LOGO_WIDTH, ZAP_LOGO_HEIGHT);
context.drawImage(zapLogoImage, 0, 0, ZAP_LOGO_WIDTH, ZAP_LOGO_HEIGHT);
return true;
}
import {
drawPreloadedZapLogo,
preloadZapLogo,
ZAP_LOGO_HEIGHT,
ZAP_LOGO_WIDTH,
} from "@/lib/zapLogo";

export function ZapLogo() {
const { t } = useTranslation();
Expand Down
8 changes: 8 additions & 0 deletions src/lib/writeNfcHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export interface WriteNfcHook {
getVerifyError: () => NfcVerificationError | null;
}

/** The write modal stays open until a write settles, or while a verify failed. */
export function isWriteModalOpen(
writeIntent: boolean,
writer: Pick<WriteNfcHook, "status" | "verifyError">,
): boolean {
return writeIntent && (writer.status === null || writer.verifyError !== null);
}

export enum WriteMethod {
Auto = "auto",
LocalNFC = "local",
Expand Down
43 changes: 43 additions & 0 deletions src/lib/zapLogo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export const ZAP_LOGO_URL = `${__APP_BASE_PATH__}lockup.webp`;
export const ZAP_LOGO_WIDTH = 160;
export const ZAP_LOGO_HEIGHT = 36;

let zapLogoImage: HTMLImageElement | null = null;
let zapLogoReady: Promise<void> | null = null;

export function preloadZapLogo(): Promise<void> {
if (typeof Image === "undefined") return Promise.resolve();

if (!zapLogoReady) {
zapLogoImage = new Image(ZAP_LOGO_WIDTH, ZAP_LOGO_HEIGHT);
zapLogoImage.decoding = "sync";
zapLogoImage.fetchPriority = "high";
zapLogoImage.src = ZAP_LOGO_URL;
zapLogoReady =
typeof zapLogoImage.decode === "function"
? zapLogoImage.decode().catch(() => undefined)
: Promise.resolve();
}

return zapLogoReady;
}

export function drawPreloadedZapLogo(
canvas: HTMLCanvasElement,
pixelRatio = window.devicePixelRatio || 1,
): boolean {
if (!zapLogoImage?.complete || zapLogoImage.naturalWidth === 0) return false;

const context = canvas.getContext("2d");
if (!context) return false;

const scale = Math.max(1, pixelRatio);
canvas.width = Math.round(ZAP_LOGO_WIDTH * scale);
canvas.height = Math.round(ZAP_LOGO_HEIGHT * scale);
context.setTransform(scale, 0, 0, scale, 0, 0);
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = "high";
context.clearRect(0, 0, ZAP_LOGO_WIDTH, ZAP_LOGO_HEIGHT);
context.drawImage(zapLogoImage, 0, 0, ZAP_LOGO_WIDTH, ZAP_LOGO_HEIGHT);
return true;
}
8 changes: 6 additions & 2 deletions src/routes/-pages/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import { useTranslation } from "react-i18next";
import { Capacitor } from "@capacitor/core";
import { logger } from "@/lib/logger";
import { showRateLimitedErrorToast } from "@/lib/toastUtils";
import { useNfcWriter, WriteMethod } from "@/lib/writeNfcHook.tsx";
import {
isWriteModalOpen,
useNfcWriter,
WriteMethod,
} from "@/lib/writeNfcHook.tsx";
import { useProPurchase } from "@/components/ProPurchase.tsx";
import { isWriteModalOpen, WriteModal } from "@/components/WriteModal.tsx";
import { WriteModal } from "@/components/WriteModal.tsx";
import { useAnnouncer } from "@/components/A11yAnnouncer";
import { cancelSession } from "@/lib/nfc";
import { CoreAPI } from "@/lib/coreApi";
Expand Down
9 changes: 7 additions & 2 deletions src/routes/-pages/MappingEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ import {
import { Button } from "@/components/wui/Button";
import { PageFrame } from "@/components/PageFrame";
import { SlideModal } from "@/components/SlideModal";
import { useNfcWriter, WriteAction, WriteMethod } from "@/lib/writeNfcHook";
import {
isWriteModalOpen,
useNfcWriter,
WriteAction,
WriteMethod,
} from "@/lib/writeNfcHook";
import { usePreferencesStore } from "@/lib/preferencesStore";
import { isWriteModalOpen, WriteModal } from "@/components/WriteModal";
import { WriteModal } from "@/components/WriteModal";
import { useSmartSwipe } from "@/hooks/useSmartSwipe";
import { usePageHeadingFocus } from "@/hooks/usePageHeadingFocus";
import { appBackNavigationOptions } from "@/lib/tabSessionStore";
Expand Down
9 changes: 7 additions & 2 deletions src/routes/create.custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import { BackIcon, CreateIcon } from "@/lib/images";
import { HeaderButton } from "@/components/wui/HeaderButton";
import { Button } from "@/components/wui/Button";
import { useSmartSwipe } from "@/hooks/useSmartSwipe";
import { isWriteModalOpen, WriteModal } from "@/components/WriteModal";
import { useNfcWriter, WriteAction, WriteMethod } from "@/lib/writeNfcHook";
import { WriteModal } from "@/components/WriteModal";
import {
isWriteModalOpen,
useNfcWriter,
WriteAction,
WriteMethod,
} from "@/lib/writeNfcHook";
import { logger } from "@/lib/logger";
import { PageFrame } from "@/components/PageFrame";
import { usePreferencesStore, selectCustomText } from "@/lib/preferencesStore";
Expand Down
9 changes: 7 additions & 2 deletions src/routes/create.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import { ListPlusIcon, NfcIcon } from "lucide-react";
import { usePageHeadingFocus } from "@/hooks/usePageHeadingFocus";
import { NextIcon, PlayIcon, SearchIcon, TextIcon } from "@/lib/images";
import { useStatusStore } from "@/lib/store";
import { useNfcWriter, WriteAction, WriteMethod } from "@/lib/writeNfcHook";
import {
isWriteModalOpen,
useNfcWriter,
WriteAction,
WriteMethod,
} from "@/lib/writeNfcHook";
import { CoreAPI } from "@/lib/coreApi";
import { isCoreFeatureAvailable } from "@/lib/featureGates";
import { logger } from "@/lib/logger";
import { showRateLimitedErrorToast } from "@/lib/toastUtils";
import type { PlayingResponse, SearchResultGame } from "@/lib/models";
import { MediaDetailsModal } from "@/components/MediaDetailsModal";
import { Card } from "@/components/wui/Card";
import { isWriteModalOpen, WriteModal } from "@/components/WriteModal";
import { WriteModal } from "@/components/WriteModal";
import { PageFrame } from "@/components/PageFrame";
import { usePreferencesStore } from "@/lib/preferencesStore";

Expand Down
9 changes: 7 additions & 2 deletions src/routes/create.nfc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import { logger } from "@/lib/logger";
// } from "lucide-react";
import { useSmartSwipe } from "@/hooks/useSmartSwipe";
import { useHaptics } from "@/hooks/useHaptics";
import { isWriteModalOpen, WriteModal } from "@/components/WriteModal";
import { useNfcWriter, WriteAction, WriteMethod } from "@/lib/writeNfcHook";
import { WriteModal } from "@/components/WriteModal";
import {
isWriteModalOpen,
useNfcWriter,
WriteAction,
WriteMethod,
} from "@/lib/writeNfcHook";
import { usePreferencesStore } from "@/lib/preferencesStore";
import { PageFrame } from "@/components/PageFrame";
import { HeaderButton } from "@/components/wui/HeaderButton";
Expand Down
Loading