refactor: remove dead Python bridge and unused packages
Removed abandoned SVG generation code that was never used in production: **Deleted Files:** - packages/core/src/bridge.py (302 lines) - Python-shell bridge for SVG generation - packages/core/client/node/src/soroban-generator-bridge.ts - TypeScript wrapper - packages/core/client/typescript/ - Entire unused @soroban/client package - packages/core/client/browser/ - Empty package **Dependencies Removed:** - python-shell - Only used by abandoned bridge code - @types/minimatch - Only needed by removed TypeScript packages - @soroban/client from apps/web **Code Cleanup:** - Simplified packages/core/client/node/src/index.ts exports - Removed SorobanGeneratorBridge, BridgeFlashcardConfig, BridgeFlashcardResult exports **Impact:** - ~800 lines of dead TypeScript code removed - 302 lines of unused Python code removed - 2 npm dependencies removed - Build verified successful - no functionality affected **What Remains Active:** - generate.py - PDF generation via Typst CLI (actively used by /api/generate) - soroban-generator.ts - CLI wrapper for PDF generation - api.py - Optional FastAPI server - generate_examples.py - Documentation image generator - Web app uses @soroban/abacus-react for all SVG rendering 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cf997b9cbc
commit
22426f677f
|
|
@ -92,7 +92,9 @@
|
|||
"Bash(ls:*)",
|
||||
"Bash(do if [ -f \"$file\" ])",
|
||||
"Bash(! echo \"$file\")",
|
||||
"Bash(then sed -i '' \"s|from ''''../context/ComplementRaceContext''''|from ''''@/arcade-games/complement-race/Provider''''|g\" \"$file\" sed -i '' \"s|from ''''../../context/ComplementRaceContext''''|from ''''@/arcade-games/complement-race/Provider''''|g\" \"$file\" fi done)"
|
||||
"Bash(then sed -i '' \"s|from ''''../context/ComplementRaceContext''''|from ''''@/arcade-games/complement-race/Provider''''|g\" \"$file\" sed -i '' \"s|from ''''../../context/ComplementRaceContext''''|from ''''@/arcade-games/complement-race/Provider''''|g\" \"$file\" fi done)",
|
||||
"Bash(pnpm install)",
|
||||
"Bash(pnpm exec turbo build --filter=@soroban/web)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@
|
|||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@react-spring/web": "^10.0.2",
|
||||
"@soroban/abacus-react": "workspace:*",
|
||||
"@soroban/client": "workspace:*",
|
||||
"@soroban/core": "workspace:*",
|
||||
"@soroban/templates": "workspace:*",
|
||||
"@tanstack/react-form": "^0.19.0",
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||
fi
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||
fi
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules/vite/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules/vite/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules/vite/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules/vite/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../vite/bin/vite.js" "$@"
|
||||
fi
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../node_modules/.pnpm/@myriaddreamin+typst-ts-renderer@0.6.0/node_modules/@myriaddreamin/typst-ts-renderer
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../node_modules/.pnpm/@myriaddreamin+typst-ts-web-compiler@0.6.0/node_modules/@myriaddreamin/typst-ts-web-compiler
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../node_modules/.pnpm/vite@5.4.20_@types+node@20.19.19_terser@5.44.0/node_modules/vite
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
* Node.js TypeScript wrapper for Soroban Flashcard Generator
|
||||
* Calls Python functions via child_process
|
||||
*/
|
||||
interface FlashcardConfig$1 {
|
||||
interface FlashcardConfig {
|
||||
range: string;
|
||||
step?: number;
|
||||
cardsPerPage?: number;
|
||||
paperSize?: 'us-letter' | 'a4' | 'a3' | 'a5';
|
||||
orientation?: 'portrait' | 'landscape';
|
||||
paperSize?: "us-letter" | "a4" | "a3" | "a5";
|
||||
orientation?: "portrait" | "landscape";
|
||||
margins?: {
|
||||
top?: string;
|
||||
bottom?: string;
|
||||
|
|
@ -24,12 +24,12 @@ interface FlashcardConfig$1 {
|
|||
columns?: string | number;
|
||||
showEmptyColumns?: boolean;
|
||||
hideInactiveBeads?: boolean;
|
||||
beadShape?: 'diamond' | 'circle' | 'square';
|
||||
colorScheme?: 'monochrome' | 'place-value' | 'heaven-earth' | 'alternating';
|
||||
beadShape?: "diamond" | "circle" | "square";
|
||||
colorScheme?: "monochrome" | "place-value" | "heaven-earth" | "alternating";
|
||||
coloredNumerals?: boolean;
|
||||
scaleFactor?: number;
|
||||
}
|
||||
declare class SorobanGenerator$1 {
|
||||
declare class SorobanGenerator {
|
||||
private pythonPath;
|
||||
private generatorPath;
|
||||
private projectRoot;
|
||||
|
|
@ -38,15 +38,15 @@ declare class SorobanGenerator$1 {
|
|||
/**
|
||||
* Generate flashcards and return PDF as Buffer
|
||||
*/
|
||||
generate(config: FlashcardConfig$1): Promise<Buffer>;
|
||||
generate(config: FlashcardConfig): Promise<Buffer>;
|
||||
/**
|
||||
* Generate flashcards and save to file
|
||||
*/
|
||||
generateToFile(config: FlashcardConfig$1, outputPath: string): Promise<void>;
|
||||
generateToFile(config: FlashcardConfig, outputPath: string): Promise<void>;
|
||||
/**
|
||||
* Generate flashcards and return as base64 string
|
||||
*/
|
||||
generateBase64(config: FlashcardConfig$1): Promise<string>;
|
||||
generateBase64(config: FlashcardConfig): Promise<string>;
|
||||
private executePython;
|
||||
/**
|
||||
* Check if all dependencies are installed
|
||||
|
|
@ -59,65 +59,4 @@ declare class SorobanGenerator$1 {
|
|||
}
|
||||
declare function expressExample(): Promise<void>;
|
||||
|
||||
/**
|
||||
* TypeScript wrapper using python-shell for clean function interface
|
||||
* No CLI arguments - just function calls with objects
|
||||
*/
|
||||
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;
|
||||
format?: 'pdf' | 'svg';
|
||||
mode?: 'single-card' | 'flashcards';
|
||||
number?: number;
|
||||
}
|
||||
interface FlashcardResult {
|
||||
pdf: string;
|
||||
count: number;
|
||||
numbers: number[];
|
||||
}
|
||||
declare class SorobanGenerator {
|
||||
private pythonShell;
|
||||
private projectRoot;
|
||||
constructor(projectRoot?: string);
|
||||
/**
|
||||
* Initialize persistent Python process for better performance
|
||||
*/
|
||||
initialize(): Promise<void>;
|
||||
/**
|
||||
* Generate flashcards - clean function interface
|
||||
*/
|
||||
generate(config: FlashcardConfig): Promise<FlashcardResult>;
|
||||
/**
|
||||
* Generate and return as Buffer
|
||||
*/
|
||||
generateBuffer(config: FlashcardConfig): Promise<Buffer>;
|
||||
/**
|
||||
* Clean up Python process
|
||||
*/
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export { FlashcardConfig as BridgeFlashcardConfig, FlashcardResult as BridgeFlashcardResult, FlashcardConfig$1 as FlashcardConfig, SorobanGenerator$1 as SorobanGenerator, SorobanGenerator as SorobanGeneratorBridge, SorobanGenerator$1 as default, expressExample };
|
||||
export { type FlashcardConfig, SorobanGenerator, SorobanGenerator as default, expressExample };
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
SorobanGenerator: () => SorobanGenerator,
|
||||
SorobanGeneratorBridge: () => SorobanGenerator2,
|
||||
default: () => SorobanGenerator,
|
||||
expressExample: () => expressExample
|
||||
});
|
||||
|
|
@ -194,96 +193,8 @@ var SorobanGenerator = class {
|
|||
async function expressExample() {
|
||||
const generator = new SorobanGenerator();
|
||||
}
|
||||
|
||||
// src/soroban-generator-bridge.ts
|
||||
var import_python_shell = require("python-shell");
|
||||
var path2 = __toESM(require("path"));
|
||||
var SorobanGenerator2 = class {
|
||||
pythonShell = null;
|
||||
projectRoot;
|
||||
constructor(projectRoot) {
|
||||
this.projectRoot = projectRoot || path2.join(__dirname, "../../");
|
||||
}
|
||||
/**
|
||||
* Initialize persistent Python process for better performance
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.pythonShell)
|
||||
return;
|
||||
this.pythonShell = new import_python_shell.PythonShell(path2.join("src", "bridge.py"), {
|
||||
mode: "json",
|
||||
pythonPath: "python3",
|
||||
pythonOptions: ["-u"],
|
||||
// Unbuffered
|
||||
scriptPath: this.projectRoot
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generate flashcards - clean function interface
|
||||
*/
|
||||
async generate(config) {
|
||||
if (!this.pythonShell) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const shell = new import_python_shell.PythonShell(path2.join("src", "bridge.py"), {
|
||||
mode: "json",
|
||||
pythonPath: "python3",
|
||||
scriptPath: this.projectRoot
|
||||
});
|
||||
shell.on("message", (message) => {
|
||||
if (message.error) {
|
||||
reject(new Error(message.error));
|
||||
} else {
|
||||
resolve(message);
|
||||
}
|
||||
});
|
||||
shell.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
shell.send(config);
|
||||
shell.end((err, code, signal) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.pythonShell) {
|
||||
reject(new Error("Not initialized"));
|
||||
return;
|
||||
}
|
||||
const handler = (message) => {
|
||||
if (message.error) {
|
||||
reject(new Error(message.error));
|
||||
} else {
|
||||
resolve(message);
|
||||
}
|
||||
this.pythonShell?.removeListener("message", handler);
|
||||
};
|
||||
this.pythonShell.on("message", handler);
|
||||
this.pythonShell.send(config);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generate and return as Buffer
|
||||
*/
|
||||
async generateBuffer(config) {
|
||||
const result = await this.generate(config);
|
||||
return Buffer.from(result.pdf, "base64");
|
||||
}
|
||||
/**
|
||||
* Clean up Python process
|
||||
*/
|
||||
async close() {
|
||||
if (this.pythonShell) {
|
||||
this.pythonShell.end(() => {
|
||||
});
|
||||
this.pythonShell = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
SorobanGenerator,
|
||||
SorobanGeneratorBridge,
|
||||
expressExample
|
||||
});
|
||||
|
|
|
|||
|
|
@ -155,96 +155,8 @@ var SorobanGenerator = class {
|
|||
async function expressExample() {
|
||||
const generator = new SorobanGenerator();
|
||||
}
|
||||
|
||||
// src/soroban-generator-bridge.ts
|
||||
import { PythonShell } from "python-shell";
|
||||
import * as path2 from "path";
|
||||
var SorobanGenerator2 = class {
|
||||
pythonShell = null;
|
||||
projectRoot;
|
||||
constructor(projectRoot) {
|
||||
this.projectRoot = projectRoot || path2.join(__dirname, "../../");
|
||||
}
|
||||
/**
|
||||
* Initialize persistent Python process for better performance
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.pythonShell)
|
||||
return;
|
||||
this.pythonShell = new PythonShell(path2.join("src", "bridge.py"), {
|
||||
mode: "json",
|
||||
pythonPath: "python3",
|
||||
pythonOptions: ["-u"],
|
||||
// Unbuffered
|
||||
scriptPath: this.projectRoot
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generate flashcards - clean function interface
|
||||
*/
|
||||
async generate(config) {
|
||||
if (!this.pythonShell) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const shell = new PythonShell(path2.join("src", "bridge.py"), {
|
||||
mode: "json",
|
||||
pythonPath: "python3",
|
||||
scriptPath: this.projectRoot
|
||||
});
|
||||
shell.on("message", (message) => {
|
||||
if (message.error) {
|
||||
reject(new Error(message.error));
|
||||
} else {
|
||||
resolve(message);
|
||||
}
|
||||
});
|
||||
shell.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
shell.send(config);
|
||||
shell.end((err, code, signal) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.pythonShell) {
|
||||
reject(new Error("Not initialized"));
|
||||
return;
|
||||
}
|
||||
const handler = (message) => {
|
||||
if (message.error) {
|
||||
reject(new Error(message.error));
|
||||
} else {
|
||||
resolve(message);
|
||||
}
|
||||
this.pythonShell?.removeListener("message", handler);
|
||||
};
|
||||
this.pythonShell.on("message", handler);
|
||||
this.pythonShell.send(config);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generate and return as Buffer
|
||||
*/
|
||||
async generateBuffer(config) {
|
||||
const result = await this.generate(config);
|
||||
return Buffer.from(result.pdf, "base64");
|
||||
}
|
||||
/**
|
||||
* Clean up Python process
|
||||
*/
|
||||
async close() {
|
||||
if (this.pythonShell) {
|
||||
this.pythonShell.end(() => {
|
||||
});
|
||||
this.pythonShell = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
export {
|
||||
SorobanGenerator,
|
||||
SorobanGenerator2 as SorobanGeneratorBridge,
|
||||
SorobanGenerator as default,
|
||||
expressExample
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ case `uname` in
|
|||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.19.12/node_modules/esbuild/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.19.12/node_modules/esbuild/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.19.12/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.19.12/node_modules/esbuild/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.19.12/node_modules/esbuild/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.19.12/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
"$basedir/../../../../../../node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/bin/esbuild" "$@"
|
||||
"$basedir/../../../../../../node_modules/.pnpm/esbuild@0.19.12/node_modules/esbuild/bin/esbuild" "$@"
|
||||
exit $?
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
../../../../../node_modules/.pnpm/python-shell@5.0.0/node_modules/python-shell
|
||||
|
|
@ -18,11 +18,7 @@
|
|||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"python-shell": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/minimatch": "^6.0.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"tsup": "^7.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -5,12 +5,5 @@
|
|||
|
||||
export * from "./soroban-generator";
|
||||
|
||||
// Export bridge generator with different name to avoid conflicts
|
||||
export {
|
||||
SorobanGenerator as SorobanGeneratorBridge,
|
||||
FlashcardConfig as BridgeFlashcardConfig,
|
||||
FlashcardResult as BridgeFlashcardResult,
|
||||
} from "./soroban-generator-bridge";
|
||||
|
||||
// Default export for convenience
|
||||
export { SorobanGenerator as default } from "./soroban-generator";
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
/**
|
||||
* TypeScript wrapper using python-shell for clean function interface
|
||||
* No CLI arguments - just function calls with objects
|
||||
*/
|
||||
|
||||
import { PythonShell } from "python-shell";
|
||||
import * as path from "path";
|
||||
|
||||
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;
|
||||
format?: "pdf" | "svg";
|
||||
mode?: "single-card" | "flashcards";
|
||||
number?: number;
|
||||
}
|
||||
|
||||
export interface FlashcardResult {
|
||||
pdf: string; // base64 encoded PDF or SVG content (depending on format)
|
||||
count: number;
|
||||
numbers: number[];
|
||||
}
|
||||
|
||||
export class SorobanGenerator {
|
||||
private pythonShell: PythonShell | null = null;
|
||||
private projectRoot: string;
|
||||
|
||||
constructor(projectRoot?: string) {
|
||||
this.projectRoot = projectRoot || path.join(__dirname, "../../");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize persistent Python process for better performance
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.pythonShell) return;
|
||||
|
||||
this.pythonShell = new PythonShell(path.join("src", "bridge.py"), {
|
||||
mode: "json",
|
||||
pythonPath: "python3",
|
||||
pythonOptions: ["-u"], // Unbuffered
|
||||
scriptPath: this.projectRoot,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate flashcards - clean function interface
|
||||
*/
|
||||
async generate(config: FlashcardConfig): Promise<FlashcardResult> {
|
||||
// One-shot mode if not initialized
|
||||
if (!this.pythonShell) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const shell = new PythonShell(path.join("src", "bridge.py"), {
|
||||
mode: "json",
|
||||
pythonPath: "python3",
|
||||
scriptPath: this.projectRoot,
|
||||
});
|
||||
|
||||
shell.on("message", (message: any) => {
|
||||
if (message.error) {
|
||||
reject(new Error(message.error));
|
||||
} else {
|
||||
resolve(message as FlashcardResult);
|
||||
}
|
||||
});
|
||||
|
||||
shell.on("error", (err: any) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
shell.send(config);
|
||||
shell.end((err: any, code: any, signal: any) => {
|
||||
if (err) reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Persistent mode
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.pythonShell) {
|
||||
reject(new Error("Not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = (message: any) => {
|
||||
if (message.error) {
|
||||
reject(new Error(message.error));
|
||||
} else {
|
||||
resolve(message as FlashcardResult);
|
||||
}
|
||||
this.pythonShell?.removeListener("message", handler);
|
||||
};
|
||||
|
||||
this.pythonShell.on("message", handler);
|
||||
this.pythonShell.send(config);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return as Buffer
|
||||
*/
|
||||
async generateBuffer(config: FlashcardConfig): Promise<Buffer> {
|
||||
const result = await this.generate(config);
|
||||
return Buffer.from(result.pdf, "base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up Python process
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
if (this.pythonShell) {
|
||||
this.pythonShell.end(() => {});
|
||||
this.pythonShell = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage - just like calling a regular TypeScript function
|
||||
async function example() {
|
||||
const generator = new SorobanGenerator();
|
||||
|
||||
// Just call it like a function!
|
||||
const result = await generator.generate({
|
||||
range: "0-99",
|
||||
cardsPerPage: 6,
|
||||
colorScheme: "place-value",
|
||||
coloredNumerals: true,
|
||||
showCutMarks: true,
|
||||
});
|
||||
|
||||
// You get back a clean result object
|
||||
console.log(`Generated ${result.count} flashcards`);
|
||||
|
||||
// Convert to Buffer if needed
|
||||
const pdfBuffer = Buffer.from(result.pdf, "base64");
|
||||
|
||||
// Or use persistent mode for better performance
|
||||
await generator.initialize();
|
||||
|
||||
// Now calls are faster
|
||||
const result2 = await generator.generate({ range: "0-9" });
|
||||
const result3 = await generator.generate({ range: "10-19" });
|
||||
|
||||
await generator.close();
|
||||
}
|
||||
|
||||
// Express example - clean function calls
|
||||
export function expressRoute(app: any) {
|
||||
const generator = new SorobanGenerator();
|
||||
|
||||
app.post("/api/flashcards", async (req: any, res: any) => {
|
||||
try {
|
||||
// Just pass the config object directly!
|
||||
const result = await generator.generate(req.body);
|
||||
|
||||
// Send back JSON or PDF
|
||||
if (req.query.format === "json") {
|
||||
res.json(result);
|
||||
} else {
|
||||
const pdfBuffer = Buffer.from(result.pdf, "base64");
|
||||
res.contentType("application/pdf");
|
||||
res.send(pdfBuffer);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* TypeScript client for Soroban Flashcard Generator API
|
||||
*/
|
||||
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;
|
||||
}
|
||||
interface FlashcardResponse {
|
||||
pdf: string;
|
||||
count: number;
|
||||
numbers: number[];
|
||||
}
|
||||
declare class SorobanFlashcardClient {
|
||||
private apiUrl;
|
||||
constructor(apiUrl?: string);
|
||||
/**
|
||||
* Generate flashcards and return as base64 PDF
|
||||
*/
|
||||
generate(config: FlashcardConfig): Promise<FlashcardResponse>;
|
||||
/**
|
||||
* Generate flashcards and download as PDF file
|
||||
*/
|
||||
generateAndDownload(config: FlashcardConfig, filename?: string): Promise<void>;
|
||||
/**
|
||||
* Generate flashcards and open in new tab
|
||||
*/
|
||||
generateAndOpen(config: FlashcardConfig): Promise<void>;
|
||||
/**
|
||||
* Check API health
|
||||
*/
|
||||
health(): Promise<boolean>;
|
||||
}
|
||||
declare function example(): Promise<void>;
|
||||
|
||||
export { FlashcardConfig, FlashcardResponse, SorobanFlashcardClient, SorobanFlashcardClient as default, example };
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
SorobanFlashcardClient: () => SorobanFlashcardClient,
|
||||
default: () => SorobanFlashcardClient,
|
||||
example: () => example
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
|
||||
// src/soroban-flashcards.ts
|
||||
var SorobanFlashcardClient = class {
|
||||
apiUrl;
|
||||
constructor(apiUrl = "http://localhost:8000") {
|
||||
this.apiUrl = apiUrl;
|
||||
}
|
||||
/**
|
||||
* Generate flashcards and return as base64 PDF
|
||||
*/
|
||||
async generate(config) {
|
||||
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, filename = "flashcards.pdf") {
|
||||
const result = await this.generate(config);
|
||||
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" });
|
||||
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) {
|
||||
const result = await this.generate(config);
|
||||
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" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
/**
|
||||
* Check API health
|
||||
*/
|
||||
async health() {
|
||||
try {
|
||||
const response = await fetch(`${this.apiUrl}/health`);
|
||||
const data = await response.json();
|
||||
return data.status === "healthy";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
async function example() {
|
||||
const client = new SorobanFlashcardClient();
|
||||
await client.generateAndDownload({
|
||||
range: "0-99",
|
||||
cardsPerPage: 6,
|
||||
colorScheme: "place-value",
|
||||
coloredNumerals: true,
|
||||
showCutMarks: true
|
||||
});
|
||||
await client.generateAndDownload(
|
||||
{
|
||||
range: "0-100",
|
||||
step: 5,
|
||||
cardsPerPage: 6
|
||||
},
|
||||
"counting-by-5s.pdf"
|
||||
);
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
SorobanFlashcardClient,
|
||||
example
|
||||
});
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
// src/soroban-flashcards.ts
|
||||
var SorobanFlashcardClient = class {
|
||||
apiUrl;
|
||||
constructor(apiUrl = "http://localhost:8000") {
|
||||
this.apiUrl = apiUrl;
|
||||
}
|
||||
/**
|
||||
* Generate flashcards and return as base64 PDF
|
||||
*/
|
||||
async generate(config) {
|
||||
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, filename = "flashcards.pdf") {
|
||||
const result = await this.generate(config);
|
||||
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" });
|
||||
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) {
|
||||
const result = await this.generate(config);
|
||||
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" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
/**
|
||||
* Check API health
|
||||
*/
|
||||
async health() {
|
||||
try {
|
||||
const response = await fetch(`${this.apiUrl}/health`);
|
||||
const data = await response.json();
|
||||
return data.status === "healthy";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
async function example() {
|
||||
const client = new SorobanFlashcardClient();
|
||||
await client.generateAndDownload({
|
||||
range: "0-99",
|
||||
cardsPerPage: 6,
|
||||
colorScheme: "place-value",
|
||||
coloredNumerals: true,
|
||||
showCutMarks: true
|
||||
});
|
||||
await client.generateAndDownload(
|
||||
{
|
||||
range: "0-100",
|
||||
step: 5,
|
||||
cardsPerPage: 6
|
||||
},
|
||||
"counting-by-5s.pdf"
|
||||
);
|
||||
}
|
||||
export {
|
||||
SorobanFlashcardClient,
|
||||
SorobanFlashcardClient as default,
|
||||
example
|
||||
};
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/esbuild@0.18.20/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
"$basedir/../../../../../../node_modules/.pnpm/esbuild@0.18.20/node_modules/esbuild/bin/esbuild" "$@"
|
||||
exit $?
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||
fi
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||
fi
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../tsup/dist/cli-default.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../tsup/dist/cli-default.js" "$@"
|
||||
fi
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/dist/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../tsup/dist/cli-node.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../tsup/dist/cli-node.js" "$@"
|
||||
fi
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.19_@vitest+ui@3.2.4_happy-dom@18.0.1_jsdom@27.0.0_postcss@8.5.6__terser@5.44.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.19_@vitest+ui@3.2.4_happy-dom@18.0.1_jsdom@27.0.0_postcss@8.5.6__terser@5.44.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.19_@vitest+ui@3.2.4_happy-dom@18.0.1_jsdom@27.0.0_postcss@8.5.6__terser@5.44.0/node_modules/vitest/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.19_@vitest+ui@3.2.4_happy-dom@18.0.1_jsdom@27.0.0_postcss@8.5.6__terser@5.44.0/node_modules:/Users/antialias/projects/soroban-abacus-flashcards/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
|
||||
else
|
||||
exec node "$basedir/../vitest/vitest.mjs" "$@"
|
||||
fi
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../node_modules/.pnpm/tsup@7.3.0_postcss@8.5.6_typescript@5.9.3/node_modules/tsup
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../node_modules/.pnpm/vitest@1.6.1_@types+node@20.19.19_@vitest+ui@3.2.4_happy-dom@18.0.1_jsdom@27.0.0_postcss@8.5.6__terser@5.44.0/node_modules/vitest
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"name": "@soroban/client",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript client for Soroban Flashcard Generator",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.js",
|
||||
"import": "./dist/index.esm.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
||||
"test": "vitest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"keywords": [
|
||||
"soroban",
|
||||
"abacus",
|
||||
"flashcards",
|
||||
"education",
|
||||
"math"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/minimatch": "^6.0.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"tsup": "^7.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
/**
|
||||
* Soroban Flashcard Generator - TypeScript Client
|
||||
* Re-export main client functionality
|
||||
*/
|
||||
|
||||
export * from "./soroban-flashcards";
|
||||
export { SorobanFlashcardClient as default } from "./soroban-flashcards";
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
/**
|
||||
* 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",
|
||||
);
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"skipLibCheck": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
|
@ -1,303 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Python bridge for Node.js integration
|
||||
Provides a clean function interface instead of CLI
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import base64
|
||||
import tempfile
|
||||
import os
|
||||
import glob
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
# Import our existing functions
|
||||
from generate import parse_range, generate_typst_file, generate_single_card_typst
|
||||
|
||||
def generate_flashcards_json(config_json):
|
||||
"""
|
||||
Generate flashcards from JSON config
|
||||
Returns base64 encoded PDF
|
||||
"""
|
||||
config = json.loads(config_json)
|
||||
|
||||
# Parse numbers
|
||||
numbers = parse_range(
|
||||
config.get('range', '0-9'),
|
||||
config.get('step', 1)
|
||||
)
|
||||
|
||||
# Handle shuffle
|
||||
if config.get('shuffle', False):
|
||||
import random
|
||||
if 'seed' in config:
|
||||
random.seed(config['seed'])
|
||||
random.shuffle(numbers)
|
||||
|
||||
# Build Typst config
|
||||
typst_config = {
|
||||
'cards_per_page': config.get('cardsPerPage', 6),
|
||||
'paper_size': config.get('paperSize', 'us-letter'),
|
||||
'orientation': config.get('orientation', 'portrait'),
|
||||
'margins': config.get('margins', {
|
||||
'top': '0.5in',
|
||||
'bottom': '0.5in',
|
||||
'left': '0.5in',
|
||||
'right': '0.5in'
|
||||
}),
|
||||
'gutter': config.get('gutter', '5mm'),
|
||||
'show_cut_marks': config.get('showCutMarks', False),
|
||||
'show_registration': config.get('showRegistration', False),
|
||||
'font_family': config.get('fontFamily', 'DejaVu Sans'),
|
||||
'font_size': config.get('fontSize', '48pt'),
|
||||
'columns': config.get('columns', 'auto'),
|
||||
'show_empty_columns': config.get('showEmptyColumns', False),
|
||||
'hide_inactive_beads': config.get('hideInactiveBeads', False),
|
||||
'bead_shape': config.get('beadShape', 'diamond'),
|
||||
'color_scheme': config.get('colorScheme', 'monochrome'),
|
||||
'colored_numerals': config.get('coloredNumerals', False),
|
||||
'scale_factor': config.get('scaleFactor', 0.9),
|
||||
}
|
||||
|
||||
# Generate in core package directory to match main generator behavior
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmpdir_path = Path(tmpdir)
|
||||
|
||||
# Generate Typst file - same setup as main generate.py
|
||||
core_package_root = Path(__file__).parent.parent # packages/core directory
|
||||
|
||||
# Create temp files in core package root, not temp directory
|
||||
temp_typst = core_package_root / f'temp_flashcards_{os.getpid()}.typ'
|
||||
temp_pdf = core_package_root / f'temp_flashcards_{os.getpid()}.pdf'
|
||||
|
||||
# Convert Python list to Typst array syntax (same as main generate.py)
|
||||
if numbers:
|
||||
numbers_str = '(' + ', '.join(str(n) for n in numbers) + ',)'
|
||||
else:
|
||||
numbers_str = '()'
|
||||
|
||||
# Create temp Typst with relative imports (works when run from core package root)
|
||||
typst_content = f'''
|
||||
#import "templates/flashcards.typ": generate-flashcards
|
||||
|
||||
#generate-flashcards(
|
||||
{numbers_str},
|
||||
cards-per-page: {typst_config['cards_per_page']},
|
||||
paper-size: "{typst_config['paper_size']}",
|
||||
orientation: "{typst_config['orientation']}",
|
||||
margins: (
|
||||
top: {typst_config['margins'].get('top', '0.5in')},
|
||||
bottom: {typst_config['margins'].get('bottom', '0.5in')},
|
||||
left: {typst_config['margins'].get('left', '0.5in')},
|
||||
right: {typst_config['margins'].get('right', '0.5in')}
|
||||
),
|
||||
gutter: {typst_config['gutter']},
|
||||
show-cut-marks: {str(typst_config['show_cut_marks']).lower()},
|
||||
show-registration: {str(typst_config['show_registration']).lower()},
|
||||
font-family: "{typst_config['font_family']}",
|
||||
font-size: {typst_config['font_size']},
|
||||
columns: {typst_config['columns']},
|
||||
show-empty-columns: {str(typst_config['show_empty_columns']).lower()},
|
||||
hide-inactive-beads: {str(typst_config['hide_inactive_beads']).lower()},
|
||||
bead-shape: "{typst_config['bead_shape']}",
|
||||
color-scheme: "{typst_config['color_scheme']}",
|
||||
colored-numerals: {str(typst_config['colored_numerals']).lower()},
|
||||
scale-factor: {typst_config['scale_factor']}
|
||||
)
|
||||
'''
|
||||
|
||||
with open(temp_typst, 'w') as f:
|
||||
f.write(typst_content)
|
||||
|
||||
# Get format preference
|
||||
output_format = config.get('format', 'pdf').lower()
|
||||
temp_svg = None
|
||||
|
||||
try:
|
||||
if output_format == 'svg':
|
||||
# Generate SVG using Typst with page template for multi-page support
|
||||
temp_svg = core_package_root / f'temp_flashcards_{os.getpid()}_{{p}}.svg'
|
||||
result = subprocess.run(
|
||||
['typst', 'compile', str(temp_typst), str(temp_svg), '--format', 'svg'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(core_package_root)
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return json.dumps({
|
||||
'error': f'Typst SVG compilation failed: {result.stderr}'
|
||||
})
|
||||
|
||||
# Read SVG content - find the first generated page
|
||||
svg_pattern = core_package_root / f'temp_flashcards_{os.getpid()}_*.svg'
|
||||
import glob
|
||||
svg_files = glob.glob(str(svg_pattern))
|
||||
|
||||
if not svg_files:
|
||||
return json.dumps({
|
||||
'error': 'No SVG files were generated'
|
||||
})
|
||||
|
||||
# Read the first SVG file (page 1)
|
||||
svg_file = Path(svg_files[0])
|
||||
with open(svg_file, 'r', encoding='utf-8') as f:
|
||||
svg_content = f.read()
|
||||
|
||||
# Clean up all generated SVG files
|
||||
for svg_path in svg_files:
|
||||
Path(svg_path).unlink()
|
||||
|
||||
result_data = {
|
||||
'pdf': svg_content, # Keep field name for compatibility
|
||||
'count': len(numbers),
|
||||
'numbers': numbers[:100]
|
||||
}
|
||||
else:
|
||||
# Generate PDF (default)
|
||||
result = subprocess.run(
|
||||
['typst', 'compile', str(temp_typst), str(temp_pdf)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(core_package_root)
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return json.dumps({
|
||||
'error': f'Typst compilation failed: {result.stderr}'
|
||||
})
|
||||
|
||||
# Read and encode PDF
|
||||
with open(temp_pdf, 'rb') as f:
|
||||
pdf_bytes = f.read()
|
||||
|
||||
result_data = {
|
||||
'pdf': base64.b64encode(pdf_bytes).decode('utf-8'),
|
||||
'count': len(numbers),
|
||||
'numbers': numbers[:100] # Limit preview
|
||||
}
|
||||
finally:
|
||||
# Clean up temp files
|
||||
for temp_file in [temp_typst, temp_pdf, temp_svg if output_format == 'svg' else None]:
|
||||
if temp_file and temp_file.exists():
|
||||
temp_file.unlink()
|
||||
|
||||
return json.dumps(result_data)
|
||||
|
||||
def generate_single_card_json(config_json):
|
||||
"""
|
||||
Generate a single card SVG from JSON config
|
||||
Specifically for preview - always returns front side (abacus)
|
||||
"""
|
||||
config = json.loads(config_json)
|
||||
|
||||
# Extract the single number
|
||||
number = config.get('number')
|
||||
if number is None:
|
||||
return json.dumps({'error': 'Missing number parameter'})
|
||||
|
||||
# Build Typst config optimized for preview display
|
||||
typst_config = {
|
||||
'bead_shape': config.get('beadShape', 'diamond'),
|
||||
'color_scheme': config.get('colorScheme', 'monochrome'),
|
||||
'color_palette': config.get('colorPalette', 'default'),
|
||||
'colored_numerals': config.get('coloredNumerals', False),
|
||||
'hide_inactive_beads': config.get('hideInactiveBeads', False),
|
||||
'show_empty_columns': config.get('showEmptyColumns', False),
|
||||
'columns': config.get('columns', 'auto'),
|
||||
'transparent': config.get('transparent', False),
|
||||
'card_width': '120pt', # Smaller card for larger abacus
|
||||
'card_height': '160pt', # Smaller card for larger abacus
|
||||
'font_size': config.get('fontSize', '48pt'),
|
||||
'font_family': config.get('fontFamily', 'DejaVu Sans'),
|
||||
'scale_factor': config.get('scaleFactor', 4.0), # Much larger scale for preview visibility
|
||||
}
|
||||
|
||||
# Generate in core package directory
|
||||
core_package_root = Path(__file__).parent.parent
|
||||
temp_typst = core_package_root / f'temp_single_{number}_{os.getpid()}.typ'
|
||||
temp_svg = core_package_root / f'temp_single_{number}_{os.getpid()}.svg'
|
||||
|
||||
try:
|
||||
# Create single card content directly with correct template path
|
||||
typst_content = f'''
|
||||
#import "templates/single-card.typ": generate-single-card
|
||||
|
||||
#generate-single-card(
|
||||
{number},
|
||||
side: "front",
|
||||
bead-shape: "{typst_config['bead_shape']}",
|
||||
color-scheme: "{typst_config['color_scheme']}",
|
||||
color-palette: "{typst_config['color_palette']}",
|
||||
colored-numerals: {str(typst_config['colored_numerals']).lower()},
|
||||
hide-inactive-beads: {str(typst_config['hide_inactive_beads']).lower()},
|
||||
show-empty-columns: {str(typst_config['show_empty_columns']).lower()},
|
||||
columns: {typst_config['columns']},
|
||||
transparent: {str(typst_config['transparent']).lower()},
|
||||
width: {typst_config['card_width']},
|
||||
height: {typst_config['card_height']},
|
||||
font-size: {typst_config['font_size']},
|
||||
font-family: "{typst_config['font_family']}",
|
||||
scale-factor: {typst_config['scale_factor']}
|
||||
)
|
||||
'''
|
||||
|
||||
with open(temp_typst, 'w') as f:
|
||||
f.write(typst_content)
|
||||
|
||||
# Generate SVG
|
||||
result = subprocess.run(
|
||||
['typst', 'compile', str(temp_typst), str(temp_svg), '--format', 'svg'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(core_package_root)
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return json.dumps({
|
||||
'error': f'Typst SVG compilation failed: {result.stderr}'
|
||||
})
|
||||
|
||||
# Read SVG content
|
||||
if not temp_svg.exists():
|
||||
return json.dumps({
|
||||
'error': 'SVG file was not generated'
|
||||
})
|
||||
|
||||
with open(temp_svg, 'r', encoding='utf-8') as f:
|
||||
svg_content = f.read()
|
||||
|
||||
return json.dumps({
|
||||
'pdf': svg_content, # Keep field name for compatibility
|
||||
'count': 1,
|
||||
'numbers': [number]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({'error': f'Single card generation failed: {str(e)}'})
|
||||
finally:
|
||||
# Clean up temp files
|
||||
for temp_file in [temp_typst, temp_svg]:
|
||||
if temp_file and temp_file.exists():
|
||||
temp_file.unlink()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Read JSON from stdin, write JSON to stdout
|
||||
# This allows clean function-like communication
|
||||
for line in sys.stdin:
|
||||
try:
|
||||
config = json.loads(line.strip())
|
||||
|
||||
# Check if this is a single-card generation request
|
||||
if config.get('mode') == 'single-card':
|
||||
result = generate_single_card_json(line.strip())
|
||||
else:
|
||||
result = generate_flashcards_json(line.strip())
|
||||
|
||||
print(result)
|
||||
sys.stdout.flush()
|
||||
except Exception as e:
|
||||
print(json.dumps({'error': str(e)}))
|
||||
sys.stdout.flush()
|
||||
|
|
@ -119,9 +119,6 @@ importers:
|
|||
'@soroban/abacus-react':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/abacus-react
|
||||
'@soroban/client':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core/client/typescript
|
||||
'@soroban/core':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core/client/node
|
||||
|
|
@ -380,49 +377,8 @@ importers:
|
|||
specifier: ^1.0.0
|
||||
version: 1.6.1(@types/node@20.19.19)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jsdom@27.0.0(postcss@8.5.6))(terser@5.44.0)
|
||||
|
||||
packages/core/client/browser:
|
||||
dependencies:
|
||||
'@myriaddreamin/typst-ts-renderer':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
'@myriaddreamin/typst-ts-web-compiler':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
devDependencies:
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.4.20(@types/node@20.19.19)(terser@5.44.0)
|
||||
|
||||
packages/core/client/node:
|
||||
dependencies:
|
||||
python-shell:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
devDependencies:
|
||||
'@types/minimatch':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.19.19
|
||||
tsup:
|
||||
specifier: ^7.0.0
|
||||
version: 7.3.0(postcss@8.5.6)(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^1.0.0
|
||||
version: 1.6.1(@types/node@20.19.19)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jsdom@27.0.0(postcss@8.5.6))(terser@5.44.0)
|
||||
|
||||
packages/core/client/typescript:
|
||||
devDependencies:
|
||||
'@types/minimatch':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.19.19
|
||||
|
|
@ -1957,12 +1913,6 @@ packages:
|
|||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
|
||||
'@myriaddreamin/typst-ts-renderer@0.6.0':
|
||||
resolution: {integrity: sha512-56Mids4E5Ob6LeEeXDedvmsVnEWnLmc1qeUOeUSruL/zI3S9QXleF/c3Os1FXwJmLuCFbWTEIq8Quh2cXlnxKw==}
|
||||
|
||||
'@myriaddreamin/typst-ts-web-compiler@0.6.0':
|
||||
resolution: {integrity: sha512-P/eIJ5RnfElj0NYzn5PI296t/IwWtgqUyyTMi5Jm5X3V5kZfskkH+LI7mSQe8tEyxwgCvxbxvFe5adinA3K8Gg==}
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
|
|
@ -7773,10 +7723,6 @@ packages:
|
|||
python-bridge@1.1.0:
|
||||
resolution: {integrity: sha512-qjQ0QB8p9cn/XDeILQH0aP307hV58lrmv0Opjyub68Um7FHdF+ZXlTqyxNkKaXOFk2QSkScoPWwn7U9GGnrkeQ==}
|
||||
|
||||
python-shell@5.0.0:
|
||||
resolution: {integrity: sha512-RUOOOjHLhgR1MIQrCtnEqz/HJ1RMZBIN+REnpSUrfft2bXqXy69fwJASVziWExfFXsR1bCY0TznnHooNsCo0/w==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
qs@6.13.0:
|
||||
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
|
@ -10814,10 +10760,6 @@ snapshots:
|
|||
'@types/react': 18.3.26
|
||||
react: 18.3.1
|
||||
|
||||
'@myriaddreamin/typst-ts-renderer@0.6.0': {}
|
||||
|
||||
'@myriaddreamin/typst-ts-web-compiler@0.6.0': {}
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.5.0
|
||||
|
|
@ -17642,8 +17584,6 @@ snapshots:
|
|||
dependencies:
|
||||
bluebird: 3.7.2
|
||||
|
||||
python-shell@5.0.0: {}
|
||||
|
||||
qs@6.13.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
|
|
|||
Loading…
Reference in New Issue