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
5 changes: 1 addition & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@
"parserOptions": {
"project": "./tsconfig.test.json"
},
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
Expand Down
5 changes: 4 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"endOfLine": "auto"
"endOfLine": "auto",
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
}
22 changes: 12 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ await processor.processLLMTranslations(file, translations, output);

```typescript
// Before
const result = analyze(file, format); // Returns { tree }
const result = analyze(file, format); // Returns { tree }

// After
const result = await analyze(file, format); // Returns Promise<{ tree }>
const result = await analyze(file, format); // Returns Promise<{ tree }>
```

### Migration Guide
Expand Down Expand Up @@ -154,6 +154,7 @@ async function processMultipleFiles(files: string[]) {
### Browser Compatibility Progress

This change enables the following browser-compatible processors:

- ✅ DotProcessor
- ✅ OpmlProcessor
- ✅ ObfProcessor (JSZip migration complete!)
Expand All @@ -164,6 +165,7 @@ This change enables the following browser-compatible processors:
**Note:** Gridset `.gridsetx` encrypted files require Node.js for crypto operations. Regular `.gridset` files work in browser.

Still Node-only (deferred):

- ❌ SnapProcessor (sqlite - needs wasm sqlite)
- ❌ TouchChatProcessor (sqlite - needs wasm sqlite)
- ❌ ExcelProcessor (fs dependencies - needs audit)
Expand Down Expand Up @@ -314,34 +316,34 @@ npm install aac-processors@2.x

```javascript
// Old (CommonJS)
const { DotProcessor } = require("aac-processors/dist/processors");
const { DotProcessor } = require('aac-processors/dist/processors');

// New (ES Modules + TypeScript)
import { DotProcessor, getProcessor } from "aac-processors";
import { DotProcessor, getProcessor } from 'aac-processors';
```

### API Changes

```typescript
// Old
const processor = new DotProcessor();
const tree = processor.loadIntoTree("file.dot");
const tree = processor.loadIntoTree('file.dot');

// New (same API, but with TypeScript support)
const processor = new DotProcessor();
const tree: AACTree = processor.loadIntoTree("file.dot");
const tree: AACTree = processor.loadIntoTree('file.dot');

// New factory pattern
const processor = getProcessor("file.dot"); // Auto-detects format
const processor = getProcessor('file.dot'); // Auto-detects format
```

### New Translation Workflow

```typescript
// New in 2.x
const texts = processor.extractTexts("file.dot");
const translations = new Map([["Hello", "Hola"]]);
const result = processor.processTexts("file.dot", translations, "output.dot");
const texts = processor.extractTexts('file.dot');
const translations = new Map([['Hello', 'Hola']]);
const result = processor.processTexts('file.dot', translations, 'output.dot');
```

For detailed migration assistance, see the [Migration Guide](docs/MIGRATION.md).
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ npm install @willwade/aac-processors
## Dual Build Targets

### Node.js (default)

Full feature set, including filesystem access, SQLite-backed formats, and
ZIP/encrypted formats.

Expand All @@ -27,6 +28,7 @@ const texts = await snap.extractTexts('board.sps');
```

### Browser

Browser-safe entry that avoids Node-only dependencies. It expects `Buffer`,
`Uint8Array`, or `ArrayBuffer` inputs rather than file paths.

Expand Down Expand Up @@ -82,7 +84,8 @@ const translations = new Map([

await processor.processTexts('board.dot', translations, 'board-es.dot');
```
NB: Please use [https://aactools.co.uk](https://aactools.co.uk) for a far more comphrensive translation logic - where we do far far more than this...

NB: Please use [https://aactools.co.uk](https://aactools.co.uk) for a far more comphrensive translation logic - where we do far far more than this...

## Documentation

Expand Down
53 changes: 21 additions & 32 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
rootDir: __dirname,
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src", "<rootDir>/test"],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
"**/?(*.)+(spec|test).+(ts|tsx|js)",
],
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/test'],
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
transform: {
"^.+\\.(ts|tsx)$": [
"ts-jest",
'^.+\\.(ts|tsx)$': [
'ts-jest',
{
tsconfig: "<rootDir>/tsconfig.test.json",
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
collectCoverage: true,
coverageDirectory: "coverage",
coverageReporters: [
"text",
"text-summary",
"lcov",
"html",
"json",
"json-summary",
"cobertura",
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'text-summary', 'lcov', 'html', 'json', 'json-summary', 'cobertura'],
collectCoverageFrom: [
"src/**/*.{js,ts}",
"!src/**/*.d.ts",
"!src/**/*.test.{js,ts}",
"!src/**/__tests__/**",
"!src/cli/**",
"!src/utilities/**",
'src/**/*.{js,ts}',
'!src/**/*.d.ts',
'!src/**/*.test.{js,ts}',
'!src/**/__tests__/**',
'!src/cli/**',
'!src/utilities/**',
],
coveragePathIgnorePatterns: ["/node_modules/", "/dist/", "/__tests__/"],
coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '/__tests__/'],
coverageThreshold: {
global: {
branches: 58,
Expand All @@ -45,13 +34,13 @@ module.exports = {
statements: 72,
},
// Per-file thresholds for critical components
"src/core/": {
'src/core/': {
branches: 80,
functions: 88,
lines: 88,
statements: 88,
},
"src/processors/dotProcessor.ts": {
'src/processors/dotProcessor.ts': {
branches: 70,
functions: 80,
lines: 80,
Expand All @@ -60,8 +49,8 @@ module.exports = {
},
// Enable module resolution for both src and dist
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
'^@/(.*)$': '<rootDir>/src/$1',
},
// Ensure Jest can find modules
moduleDirectories: ["node_modules", "<rootDir>/src", "<rootDir>/dist"],
moduleDirectories: ['node_modules', '<rootDir>/src', '<rootDir>/dist'],
};
122 changes: 122 additions & 0 deletions src/processors/gridset/cellHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Grid3 Cell Helpers
*
* Utilities for working with Grid 3 cells, including finding button positions
* and calculating cell spans in grid layouts.
*/

import type { AACPage, AACButton } from '../../core/treeStructure';

/**
* Cell position with span information
*/
export interface CellPosition {
/** X coordinate (column) */
x: number;
/** Y coordinate (row) */
y: number;
/** Number of columns the cell spans */
columnSpan: number;
/** Number of rows the cell spans */
rowSpan: number;
}

/**
* Find button position with span information
*
* Searches the page's grid layout for a button and calculates its position
* and span (how many columns/rows it occupies).
*
* @param page - The AAC page containing the button
* @param button - The button to locate
* @param fallbackIndex - Index to use if button not found in grid
* @returns Position and span information for the button
*
* @example
* const position = findButtonPosition(page, button, 0);
* console.log(`Button at ${position.x},${position.y} spans ${position.columnSpan}x${position.rowSpan}`);
*/
export function findButtonPosition(
page: AACPage,
button: AACButton,
fallbackIndex: number
): CellPosition {
if (page.grid && page.grid.length > 0) {
// Search for button in grid layout and calculate span
for (let y = 0; y < page.grid.length; y++) {
for (let x = 0; x < page.grid[y].length; x++) {
const current = page.grid[y][x];
if (current && current.id === button.id) {
// Calculate span by checking how far the same button extends
let columnSpan = 1;
let rowSpan = 1;

// Check column span (rightward)
while (x + columnSpan < page.grid[y].length) {
const right = page.grid[y][x + columnSpan];
if (right && right.id === button.id) {
columnSpan++;
} else {
break;
}
}

// Check row span (downward)
while (y + rowSpan < page.grid.length) {
const below = page.grid[y + rowSpan][x];
if (below && below.id === button.id) {
rowSpan++;
} else {
break;
}
}

return { x, y, columnSpan, rowSpan };
}
}
}
}

// Fallback positioning
const gridCols = page.grid?.[0]?.length || Math.ceil(Math.sqrt(page.buttons.length));
return {
x: fallbackIndex % gridCols,
y: Math.floor(fallbackIndex / gridCols),
columnSpan: 1,
rowSpan: 1,
};
}

/**
* Calculate cell position key for Maps and Sets
*
* Creates a string key from X and Y coordinates for use as a Map key or Set entry.
*
* @param x - X coordinate
* @param y - Y coordinate
* @returns String key in format "x,y"
*
* @example
* const key = cellPositionKey(5, 3);
* console.log(key); // "5,3"
*/
export function cellPositionKey(x: number, y: number): string {
return `${x},${y}`;
}

/**
* Parse cell position key
*
* Extracts X and Y coordinates from a position key string.
*
* @param key - Position key in format "x,y"
* @returns Object with x and y properties
*
* @example
* const pos = parseCellPositionKey("5,3");
* console.log(pos); // { x: 5, y: 3 }
*/
export function parseCellPositionKey(key: string): { x: number; y: number } {
const [x, y] = key.split(',').map(Number);
return { x, y };
}
Loading
Loading