diff --git a/apps/palette-generator/palette-generator.ts b/apps/palette-generator/palette-generator.ts
index e76f210..fe02404 100644
--- a/apps/palette-generator/palette-generator.ts
+++ b/apps/palette-generator/palette-generator.ts
@@ -13,7 +13,7 @@ import { render } from "preact";
import { html } from "htm/preact";
import { Palette } from "../../src/client/Palette";
import { BlissSymbol } from "../../src/client/BlissSymbol";
-import { processPaletteLabels, fetchBlissGlossJson } from "./paletteJsonGenerator";
+import { processPaletteLabels, loadBciAvSymbolsDict } from "./paletteJsonGenerator";
import "../../src/client/index.scss";
import {
initAdaptivePaletteGlobals, adaptivePaletteGlobals, cellTypeRegistry
@@ -22,7 +22,7 @@ import {
// Initialize any globals used elsewhere in the code.
await initAdaptivePaletteGlobals("paletteDisplay");
-await fetchBlissGlossJson();
+await loadBciAvSymbolsDict();
let currentPaletteName = "";
const MAX_MATCHES_OUTPUT = 7;
@@ -32,7 +32,7 @@ const MAX_MATCHES_OUTPUT = 7;
* set up a handler to adjust the palette for changes in the cell type.
*/
function initCellTypesSelect () {
- const cellTypesSelect = document.getElementById("cellTypes");
+ const cellTypesSelect = document.getElementById("cellTypes") as HTMLSelectElement;
Object.keys(cellTypeRegistry).forEach ((cellType) => {
// The "cell" type `ContentBmwEncoding` is for an array of symbols within
// a content area, not for cells within a palette. Avoid for now.
@@ -58,7 +58,7 @@ function renderExamples() {
isPresentation=false
labelledBy="slashExampleLabel"
/>
- `, document.getElementById("slashExample"));
+ `, document.getElementById("slashExample") as HTMLElement);
// Semi-colon example
render(html`
@@ -68,7 +68,7 @@ function renderExamples() {
isPresentation=false
labelledBy="semicolonExampleLabel"
/>
- `, document.getElementById("semicolonExample"));
+ `, document.getElementById("semicolonExample") as HTMLElement);
// Kerning example (relative kerning)
render(html`
@@ -78,7 +78,7 @@ function renderExamples() {
isPresentation=false
labelledBy="kerningExampleLabel"
/>
- `, document.getElementById("kerningExample"));
+ `, document.getElementById("kerningExample") as HTMLElement);
// X example
render(html`
@@ -88,7 +88,7 @@ function renderExamples() {
isPresentation=false
labelledBy="XExampleLabel"
/>
- `, document.getElementById("XExample"));
+ `, document.getElementById("XExample") as HTMLElement);
}
/**
@@ -123,7 +123,7 @@ function handleGenerateDisplayButton () {
if (glossesArray.length === 0) {
paletteDisplay.innerText = "
Missing glosses ?
";
}
- const lookupResults = processPaletteLabels(
+ const lookupResults = await processPaletteLabels(
glossesArray,
paletteName.value,
parseInt(rowStart.value),
diff --git a/apps/palette-generator/paletteJsonGenerator.ts b/apps/palette-generator/paletteJsonGenerator.ts
index 0f4b879..fb641e2 100644
--- a/apps/palette-generator/paletteJsonGenerator.ts
+++ b/apps/palette-generator/paletteJsonGenerator.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2024-2025 Inclusive Design Research Centre, OCAD University
+ * Copyright 2024-2026 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
@@ -9,25 +9,55 @@
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/
import { v4 as uuidv4 } from "uuid";
+import { bciAvSymbolsDictUrl, loadDataFromUrl } from "../../src/client/GlobalData";
import {
makeBciAvIdType, BCIAV_PATTERN_KEY, BLISSARY_PATTERN_KEY, decomposeBciAvId
} from "../../src/client/SvgUtils";
+import { BciAvSymbolsDict } from "../../src/client";
const BLANK_CELL = "BLANK";
const SVG_PREFIX = "SVG:";
const SVG_SUFFIX = ":SVG";
const LABEL_MARKER = "LABEL:";
-let bliss_gloss;
-export async function fetchBlissGlossJson () {
- // Read and parse the Bliss gloss JSON file
- try {
- const fetchResponse = await fetch("/data/bliss_symbol_explanations.json");
- bliss_gloss = await fetchResponse.json();
- } catch (error) {
- console.error(`Error fetching 'bliss_symbol_explanations.json': ${error.message}`);
- }
- return bliss_gloss;
+let bciAvSymbolsDict: BciAvSymbolsDict = [];
+
+// Load the BCI AV symbols dictionary from the URL
+export async function loadBciAvSymbolsDict() {
+ bciAvSymbolsDict = await loadDataFromUrl(bciAvSymbolsDictUrl);
+}
+
+// Define types used in the functions below
+interface BlissGloss {
+ id: string | number;
+ description: string;
+ composition?: (string | number)[]; // Optional
+}
+
+interface BciAvMatch {
+ bciAvId: number;
+ label: string;
+ composition?: (string | number)[]; // Optional
+ fullComposition?: (string | number)[]; // Optional
+}
+
+type MatchByInfo = Record;
+
+interface Cell {
+ type: string;
+ options: {
+ label: string;
+ bciAvId?: number | (string | number)[];
+ rowStart: number;
+ rowSpan: number;
+ columnStart: number;
+ columnSpan: number;
+ };
+}
+
+interface Palette {
+ name: string;
+ cells: Record;
}
/**
@@ -36,7 +66,7 @@ export async function fetchBlissGlossJson () {
* @param {string} - the string to test.
* @returns {boolean}
*/
-function isSvgBuilderString (theString) {
+function isSvgBuilderString (theString: string) {
return theString.startsWith(SVG_PREFIX) && theString.endsWith(SVG_SUFFIX);
}
@@ -51,7 +81,7 @@ function isSvgBuilderString (theString) {
* @return {Array} - An array of the specifiers required by the SvgUtils.
* @throws {Error} - If the encoding is not well formed.
*/
-function convertSvgBuilderString (theString) {
+function convertSvgBuilderString (theString: string) {
let result;
// Three forms, one with commas and one without:
// - commas:
@@ -96,43 +126,62 @@ function convertSvgBuilderString (theString) {
* { id: {number}, description: {string}, ... }
* @throws {Error} If no BCI AV ID is found for the label.
*/
-function findBciAvId(label, blissGlosses) {
- const matches = [];
- // Search for the label in the Bliss gloss
+
+/**
+ * Helper function: Normalizes polymorphic return from decomposeBciAvId
+ * (value | array | undefined) into (array | undefined)
+ */
+const normalizeToOptionalArray = (val: unknown): (string | number)[] | undefined => {
+ if (val === undefined || val === null) return undefined;
+ if (Array.isArray(val)) {
+ return val as (string | number)[];
+ }
+ return [val as string | number];
+};
+
+/**
+ * Helper function: Compares two arrays for equality safely
+ */
+const areArraysEqual = (a?: (string | number)[], b?: (string | number)[]): boolean => {
+ if (!a || !b) return a === b; // If both undefined, they are equal
+ if (a.length !== b.length) return false;
+ return a.every((val, index) => String(val) === String(b[index]));
+};
+
+function findBciAvId(label: string, blissGlosses: BlissGloss[]): BciAvMatch[] {
+ const matches: BciAvMatch[] = [];
console.log(`For label ${label}:`);
+
for (const gloss of blissGlosses) {
- // Try an exact match or a word match
- const wordMatch = new RegExp("\\b" + `${label}` + "\\b");
- if ((label === gloss.description) || wordMatch.test(gloss.description)) {
- // Get the composition of all the parts of the symbol's compostion or
- // its ID. But if the `fullComposition` is the same as the original
- // ignore it.
- const glossId = parseInt(gloss.id);
- let fullComposition = undefined;
- let equalCompositions = false;
- if (gloss.composition) {
- fullComposition = decomposeBciAvId(gloss.composition);
- equalCompositions = fullComposition.join("") === gloss.composition.join("");
- }
- else {
- fullComposition = decomposeBciAvId(glossId);
- if (fullComposition && fullComposition.length === 1) {
- equalCompositions = fullComposition[0] === glossId;
- }
- }
+ const wordMatch = new RegExp("\\b" + label + "\\b");
+
+ if (label === gloss.description || wordMatch.test(gloss.description)) {
+ const glossId = typeof gloss.id === "number" ? gloss.id : parseInt(gloss.id);
+
+ // Determine the source for decomposition and what "original" state to compare against
+ const source = gloss.composition ?? glossId;
+ const originalAsArray = gloss.composition ?? [glossId];
+
+ const fullComp = normalizeToOptionalArray(decomposeBciAvId(source));
+
+ // Check if the decomposition resulted in exactly what we started with
+ const equalCompositions = areArraysEqual(fullComp, originalAsArray);
+
matches.push({
bciAvId: glossId,
label: gloss.description,
- composition: gloss.composition,
- fullComposition: ( equalCompositions ? undefined : fullComposition )
+ composition: gloss.composition, // Might be undefined
+ fullComposition: equalCompositions ? undefined : fullComp
});
- console.log(`\tFound match: ${gloss.description}, bci-av-id: ${gloss.id}`);
+
+ console.log(`\tFound match: ${gloss.description}, bci-av-id: ${glossId}`);
}
}
- // If no BCI AV ID is found, throw an error
+
if (matches.length === 0) {
throw new Error(`BciAvId not found for label: ${label}`);
}
+
return matches;
}
@@ -145,7 +194,7 @@ function findBciAvId(label, blissGlosses) {
* @returns {Object} The object that matches the given BCI AV ID
* @throws {Error} If the given BCI AV ID is invalid (not in the gloss)
*/
-function findByBciAvId (bciAvId: string, blissGlosses: array) {
+function findByBciAvId (bciAvId: string, blissGlosses: BlissGloss[]) {
const theEntry = blissGlosses.find((entry) => (entry.id === bciAvId));
if (theEntry === undefined) {
throw new Error(`BciAvId not found for BCI AV ID: ${bciAvId}`);
@@ -197,17 +246,18 @@ function findByBciAvId (bciAvId: string, blissGlosses: array) {
* was no match in the gloss
* }
*/
-export function processPaletteLabels (paletteLabels, paletteName, startRow, startColumn, cellType) {
+
+export async function processPaletteLabels (paletteLabels: string[][], paletteName: string, startRow: number, startColumn: number, cellType: string) {
// Initialize palette to return, the matches, and the error list
- const finalJson = {
+ const finalJson: Palette = {
"name": paletteName,
"cells": {}
};
- const matchByInfoArray = [];
- const errors = [];
+ const matchByInfoArray: MatchByInfo[] = [];
+ const errors: string[] = [];
- paletteLabels.forEach((row, rowIndex) => {
- row.forEach((infoString, colIndex) => {
+ paletteLabels.forEach((row: string[], rowIndex: number) => {
+ row.forEach((infoString: string, colIndex: number) => {
const current_row = startRow + rowIndex;
const current_column = startColumn + colIndex;
@@ -217,7 +267,7 @@ export function processPaletteLabels (paletteLabels, paletteName, startRow, star
}
// Create a cell object for the current `infoString`, leaving the
// `bciAvId` field undefined for now.
- const cell = {
+ const cell: Cell = {
type: cellType,
options: {
label: infoString,
@@ -243,27 +293,28 @@ export function processPaletteLabels (paletteLabels, paletteName, startRow, star
}
else {
// If the `infoString` is a BCI AV ID (a number), just use it for the
- // `bciAvId`. Even so, find its description from the `bliss_gloss`
+ // `bciAvId`. Even so, find its description from the `bciAvSymbolsDict`
cell.options.bciAvId = parseInt(infoString);
if (!isNaN(cell.options.bciAvId)) {
- const glossEntry = findByBciAvId(infoString, bliss_gloss);
+ const glossEntry = findByBciAvId(infoString, bciAvSymbolsDict);
cell.options.label = actualLabel || glossEntry["description"];
}
else {
// Find the BCI AV IDs for the current infoString. Use the first
// match for the palette
- const matches = findBciAvId(infoString, bliss_gloss);
+ const matches = findBciAvId(infoString, bciAvSymbolsDict);
cell.options.bciAvId = matches[0].bciAvId;
cell.options.label = actualLabel || infoString;
- const inputMatches = {};
+ const inputMatches: MatchByInfo = {};
inputMatches[infoString] = matches;
matchByInfoArray.push(inputMatches);
}
}
}
- catch (error) {
+ catch (error: unknown) {
// If an error occurs, add it to the errors array
- errors.push(error.message);
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ errors.push(errorMessage);
// Change the cell label to indicate that this cell is not right yet.
// The `bciAvId` encoding means "not found".
diff --git a/src/client/GlobalData.ts b/src/client/GlobalData.ts
index ed3250d..227130b 100644
--- a/src/client/GlobalData.ts
+++ b/src/client/GlobalData.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2023-2025 Inclusive Design Research Centre, OCAD University
+ * Copyright 2023-2026 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
@@ -13,18 +13,7 @@
* Populate and export global data
*/
import { signal } from "@preact/signals";
-
-// NOTE: this import causes a warning serving the application using the `vite`
-// server. The warning suggests to *not* us the `public` folder but to use
-// the `src` folder instead. However, this code is also served using node
-// express and it is in the proper location for that envionment. A copy of the
-// warning follows:
-// "Assets in public directory cannot be imported from JavaScript.
-// If you intend to import that asset, put the file in the src directory, and use /src/data/bliss_symbol_explanations.json instead of /public/data/bliss_symbol_explanations.json.
-// If you intend to use the URL of that asset, use /data/bliss_symbol_explanations.json?url.
-// Files in the public directory are served at the root path.
-// Instead of /public/data/bliss_symbol_explanations.json, use /data/bliss_symbol_explanations.json."
-import bliss_symbols from "../../public/data/bliss_symbol_explanations.json";
+import { BlissaryIdMap, BciAvSymbolsDict } from "./index.d";
/**
* The map between cell types (string) and actual components that render corresponding cells
@@ -65,12 +54,22 @@ export const cellTypeRegistry = {
* Load the map between the BCI-AV IDs and the code consumed by the Bliss SVG
* and create the PaletterStore and NavigationStack objects.
*/
-export const adaptivePaletteGlobals = {
+interface AdaptivePaletteGlobals {
+ blissaryIdMap: BlissaryIdMap | null;
+ bciAvSymbols: BciAvSymbolsDict | null;
+ paletteStore: PaletteStore;
+ navigationStack: NavigationStack;
+ mainPaletteContainerId: string;
+};
+
+const blissaryIdMapUrl: string = "https://raw.githubusercontent.com/hlridge/Bliss-Blissary-BCI-ID-Map/main/blissary_to_bci_mapping.json";
+export const bciAvSymbolsDictUrl: string = "https://raw.githubusercontent.com/inclusive-design/adaptive-palette/main/public/data/bliss_symbol_explanations.json";
+
+export const adaptivePaletteGlobals: AdaptivePaletteGlobals = {
// The map between the BCI-AV IDs and the code consumed by the Bliss SVG
// builder. The map itself is set asynchronously.
- blissaryIdMapUrl: "https://raw.githubusercontent.com/hlridge/Bliss-Blissary-BCI-ID-Map/main/blissary_to_bci_mapping.json",
blissaryIdMap: null,
- bciAvSymbols: bliss_symbols,
+ bciAvSymbols: null,
paletteStore: new PaletteStore(),
navigationStack: new NavigationStack(),
@@ -81,8 +80,8 @@ export const adaptivePaletteGlobals = {
mainPaletteContainerId: ""
};
-export async function loadBlissaryIdMap (): Promise