feat: add TypeScript client libraries for browser integration

- Add TypeScript client for API consumption
- Add browser package setup for Typst.ts WASM
- Support for direct browser PDF generation

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-09-09 15:45:59 -05:00
parent 98263a79a0
commit f21b5e5592
3 changed files with 214 additions and 0 deletions

View File

@ -0,0 +1,19 @@
{
"name": "soroban-flashcards-browser",
"version": "1.0.0",
"description": "Browser-based Soroban Flashcard Generator using Typst.ts",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@myriaddreamin/typst-ts-web-compiler": "^0.6.0",
"@myriaddreamin/typst-ts-renderer": "^0.6.0"
},
"devDependencies": {
"vite": "^5.0.0",
"typescript": "^5.0.0"
}
}

View File

@ -0,0 +1,25 @@
{
"name": "soroban-flashcards-client",
"version": "1.0.0",
"description": "TypeScript client for Soroban Flashcard Generator",
"main": "dist/soroban-flashcards.js",
"module": "dist/soroban-flashcards.esm.js",
"types": "dist/soroban-flashcards.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"keywords": [
"soroban",
"abacus",
"flashcards",
"education",
"math"
],
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}

View File

@ -0,0 +1,170 @@
/**
* TypeScript client for Soroban Flashcard Generator API
*/
export interface FlashcardConfig {
range: string;
step?: number;
cardsPerPage?: number;
paperSize?: 'us-letter' | 'a4' | 'a3' | 'a5';
orientation?: 'portrait' | 'landscape';
margins?: {
top?: string;
bottom?: string;
left?: string;
right?: string;
};
gutter?: string;
shuffle?: boolean;
seed?: number;
showCutMarks?: boolean;
showRegistration?: boolean;
fontFamily?: string;
fontSize?: string;
columns?: string | number;
showEmptyColumns?: boolean;
hideInactiveBeads?: boolean;
beadShape?: 'diamond' | 'circle' | 'square';
colorScheme?: 'monochrome' | 'place-value' | 'heaven-earth' | 'alternating';
coloredNumerals?: boolean;
scaleFactor?: number;
}
export interface FlashcardResponse {
pdf: string; // base64 encoded PDF
count: number;
numbers: number[];
}
export class SorobanFlashcardClient {
private apiUrl: string;
constructor(apiUrl: string = 'http://localhost:8000') {
this.apiUrl = apiUrl;
}
/**
* Generate flashcards and return as base64 PDF
*/
async generate(config: FlashcardConfig): Promise<FlashcardResponse> {
const response = await fetch(`${this.apiUrl}/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
range: config.range,
step: config.step ?? 1,
cards_per_page: config.cardsPerPage ?? 6,
paper_size: config.paperSize ?? 'us-letter',
orientation: config.orientation ?? 'portrait',
margins: config.margins ?? {
top: '0.5in',
bottom: '0.5in',
left: '0.5in',
right: '0.5in',
},
gutter: config.gutter ?? '5mm',
shuffle: config.shuffle ?? false,
seed: config.seed,
show_cut_marks: config.showCutMarks ?? false,
show_registration: config.showRegistration ?? false,
font_family: config.fontFamily ?? 'DejaVu Sans',
font_size: config.fontSize ?? '48pt',
columns: config.columns ?? 'auto',
show_empty_columns: config.showEmptyColumns ?? false,
hide_inactive_beads: config.hideInactiveBeads ?? false,
bead_shape: config.beadShape ?? 'diamond',
color_scheme: config.colorScheme ?? 'monochrome',
colored_numerals: config.coloredNumerals ?? false,
scale_factor: config.scaleFactor ?? 0.9,
format: 'base64',
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to generate flashcards');
}
return response.json();
}
/**
* Generate flashcards and download as PDF file
*/
async generateAndDownload(config: FlashcardConfig, filename: string = 'flashcards.pdf'): Promise<void> {
const result = await this.generate(config);
// Convert base64 to blob
const pdfBytes = atob(result.pdf);
const byteArray = new Uint8Array(pdfBytes.length);
for (let i = 0; i < pdfBytes.length; i++) {
byteArray[i] = pdfBytes.charCodeAt(i);
}
const blob = new Blob([byteArray], { type: 'application/pdf' });
// Create download link
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* Generate flashcards and open in new tab
*/
async generateAndOpen(config: FlashcardConfig): Promise<void> {
const result = await this.generate(config);
// Convert base64 to blob
const pdfBytes = atob(result.pdf);
const byteArray = new Uint8Array(pdfBytes.length);
for (let i = 0; i < pdfBytes.length; i++) {
byteArray[i] = pdfBytes.charCodeAt(i);
}
const blob = new Blob([byteArray], { type: 'application/pdf' });
// Open in new tab
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
}
/**
* Check API health
*/
async health(): Promise<boolean> {
try {
const response = await fetch(`${this.apiUrl}/health`);
const data = await response.json();
return data.status === 'healthy';
} catch {
return false;
}
}
}
// Example usage function
export async function example() {
const client = new SorobanFlashcardClient();
// Generate and download flashcards for 0-99 with place-value coloring
await client.generateAndDownload({
range: '0-99',
cardsPerPage: 6,
colorScheme: 'place-value',
coloredNumerals: true,
showCutMarks: true,
});
// Generate counting by 5s
await client.generateAndDownload({
range: '0-100',
step: 5,
cardsPerPage: 6,
}, 'counting-by-5s.pdf');
}