diff --git a/150b3a-virtual-piano/music-sheet.gts b/150b3a-virtual-piano/music-sheet.gts index 0d8d582d..54067a35 100644 --- a/150b3a-virtual-piano/music-sheet.gts +++ b/150b3a-virtual-piano/music-sheet.gts @@ -12,6 +12,7 @@ import MusicIcon from '@cardstack/boxel-icons/music-2'; import { eq } from '@cardstack/boxel-ui/helpers'; import { htmlSafe } from '@ember/template'; import { Genre, GENRE_EMOJI } from './genre'; +import { diffLabel, diffClass } from './utils/diff-helpers'; /* ── Difficulty helpers (VP.net scale 1–10) ────────────────────────────── 1 → SUPER EASY @@ -19,21 +20,6 @@ import { Genre, GENRE_EMOJI } from './genre'; 5, 6, 7 → INTERMEDIATE 8, 9, 10 → EXPERT ───────────────────────────────────────────────────────────────────────── */ -function diffLabel(level: number | null | undefined): string { - if (!level) return 'UNKNOWN'; - if (level === 1) return 'SUPER EASY'; - if (level <= 4) return 'EASY'; - if (level <= 7) return 'INTERMEDIATE'; - return 'EXPERT'; -} - -function diffClass(level: number | null | undefined): string { - if (!level) return 'diff-unknown'; - if (level === 1) return 'diff-super-easy'; - if (level <= 4) return 'diff-easy'; - if (level <= 7) return 'diff-intermediate'; - return 'diff-expert'; -} export class MusicSheet extends CardDef { static displayName = 'Music Sheet'; diff --git a/150b3a-virtual-piano/utils/diff-helpers.gts b/150b3a-virtual-piano/utils/diff-helpers.gts new file mode 100644 index 00000000..c902fb61 --- /dev/null +++ b/150b3a-virtual-piano/utils/diff-helpers.gts @@ -0,0 +1,15 @@ +export function diffLabel(level: number | null | undefined): string { + if (!level) return 'UNKNOWN'; + if (level === 1) return 'SUPER EASY'; + if (level <= 4) return 'EASY'; + if (level <= 7) return 'INTERMEDIATE'; + return 'EXPERT'; +} + +export function diffClass(level: number | null | undefined): string { + if (!level) return 'diff-unknown'; + if (level === 1) return 'diff-super-easy'; + if (level <= 4) return 'diff-easy'; + if (level <= 7) return 'diff-intermediate'; + return 'diff-expert'; +} diff --git a/150b3a-virtual-piano/virtual-piano.gts b/150b3a-virtual-piano/virtual-piano.gts index 11927f77..f6a9b877 100644 --- a/150b3a-virtual-piano/virtual-piano.gts +++ b/150b3a-virtual-piano/virtual-piano.gts @@ -32,6 +32,7 @@ import { } from '@cardstack/runtime-common'; import PianoIcon from '@cardstack/boxel-icons/piano'; import type { Genre } from './genre'; +import { diffLabel, diffClass } from './utils/diff-helpers'; /* @ts-expect-error import.meta is valid ESM */ const here: string = import.meta.url; @@ -173,21 +174,6 @@ interface SongData { } /* ── Difficulty helpers (mirrors piano-song.gts) ─────────────────────── */ -function diffLabel(level: number): string { - if (!level) return ''; - if (level === 1) return 'SUPER EASY'; - if (level <= 4) return 'EASY'; - if (level <= 7) return 'INTERMEDIATE'; - return 'EXPERT'; -} - -function diffClass(level: number): string { - if (!level) return 'diff-unknown'; - if (level === 1) return 'diff-super-easy'; - if (level <= 4) return 'diff-easy'; - if (level <= 7) return 'diff-intermediate'; - return 'diff-expert'; -} /* ── Piano key layout data ───────────────────────────────────────────── */ interface KeyData { @@ -757,7 +743,8 @@ class IsolatedVirtualPiano extends Component { } get difficultyLabel(): string { - return diffLabel(this.selectedSong?.difficulty ?? 0); + if (!this.selectedSong?.difficulty) return ''; + return diffLabel(this.selectedSong.difficulty); } get difficultyClass(): string { diff --git a/41e20f-wedding-table-seating-planner/commands/analyze-floor-plan-command.gts b/41e20f-wedding-table-seating-planner/commands/analyze-floor-plan-command.gts index 5f63f68e..74a574f2 100644 --- a/41e20f-wedding-table-seating-planner/commands/analyze-floor-plan-command.gts +++ b/41e20f-wedding-table-seating-planner/commands/analyze-floor-plan-command.gts @@ -2,15 +2,7 @@ import { CardDef, field, contains } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import { Command } from '@cardstack/runtime-common'; import SendRequestViaProxyCommand from '@cardstack/boxel-host/commands/send-request-via-proxy'; - -function arrayBufferToBase64(buffer: ArrayBuffer): string { - let binary = ''; - let bytes = new Uint8Array(buffer); - for (let i = 0; i < bytes.byteLength; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); -} +import { arrayBufferToBase64 } from '../utils/encoding'; async function toDataUrl(url: string): Promise { if (url.startsWith('data:image/')) return url; diff --git a/41e20f-wedding-table-seating-planner/components/tsp.gts b/41e20f-wedding-table-seating-planner/components/tsp.gts index 5cf34d0d..7038617f 100644 --- a/41e20f-wedding-table-seating-planner/components/tsp.gts +++ b/41e20f-wedding-table-seating-planner/components/tsp.gts @@ -23,6 +23,7 @@ import { POSTER_ASPECTS, } from '../commands/invitation-poster-command'; import { debounce } from 'lodash-es'; +import { arrayBufferToBase64 } from '../utils/encoding'; import type { TableSeatingPlanner } from '../table-seating-planner'; import { Guest } from '../guest'; import { Host } from '../host'; @@ -10751,18 +10752,6 @@ function clampNum(v: unknown, min: number, max: number, def: number): number { if (!isFinite(n)) return def; return Math.max(min, Math.min(max, Math.round(n))); } -function arrayBufferToBase64(buffer: ArrayBuffer): string { - let binary = ''; - let bytes = new Uint8Array(buffer); - let chunk = 0x8000; - for (let i = 0; i < bytes.length; i += chunk) { - binary += String.fromCharCode.apply( - null, - Array.from(bytes.subarray(i, i + chunk)) as unknown as number[], - ); - } - return btoa(binary); -} function imageDims(src: string): Promise<{ w: number; h: number }> { return new Promise((resolve) => { let img = new Image(); diff --git a/41e20f-wedding-table-seating-planner/utils/encoding.gts b/41e20f-wedding-table-seating-planner/utils/encoding.gts new file mode 100644 index 00000000..410ef331 --- /dev/null +++ b/41e20f-wedding-table-seating-planner/utils/encoding.gts @@ -0,0 +1,15 @@ +export function arrayBufferToBase64(buffer: ArrayBuffer): string { + let binary = ''; + let bytes = new Uint8Array(buffer); + // Chunk to avoid quadratic string concatenation and the call-stack/argument + // limit of String.fromCharCode.apply on large buffers (floor plan uploads + // allow up to 25MB). + let chunk = 0x8000; + for (let i = 0; i < bytes.length; i += chunk) { + binary += String.fromCharCode.apply( + null, + Array.from(bytes.subarray(i, i + chunk)) as unknown as number[], + ); + } + return btoa(binary); +}