Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: basic implementation for navigation among palette layers (resolves #5) #6

Merged
merged 39 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7105026
feat: select from a set of palettes
klown Oct 30, 2023
6f25a5f
fix: palette json definition files for "people" and "myfamily"
klown Oct 31, 2023
0adab7b
Merge branch 'feat/create-palette-from-json' into MultiPalettes
klown Nov 3, 2023
f7530a8
feat: added main "palette of palettes" for choosing other palettes
klown Nov 13, 2023
f01e7e0
Merge branch 'feat/create-palette-from-json' into MultiPalettes
klown Nov 20, 2023
094f434
fix: actually add the new `ActionBranchToPalette` component
klown Nov 21, 2023
e43465c
Merge branch 'feat/create-palette-from-json' into MultiPalettes
klown Nov 28, 2023
5e3743f
Merge branch 'feat/create-palette-from-json' into MultiPalettes
klown Nov 30, 2023
4752e51
Merge branch 'feat/create-palette-from-json' into MultiPalettes
klown Dec 19, 2023
4305b13
Merge branch 'main' into MultiPalettes
klown Dec 20, 2023
d5df467
feat: load palette json files when clicking on palette selection cells
klown Dec 20, 2023
10a95a8
chore: add note about "singing" palette cell as a test
klown Dec 20, 2023
30b570a
Merge branch 'feat/create-bmw-encoding' into multipaletteAndOutput
klown Jan 17, 2024
4c91168
chore: modify to work with branch feat/create-bmw-encoding
klown Jan 19, 2024
10e33c5
fix: use "isPresentational" for the symbols in the content display area
klown Jan 22, 2024
a73e48a
chore: modfiy palette json to expand to fit the display
klown Jan 24, 2024
7201c22
Merge branch 'feat/create-bmw-encoding' into multipaletteAndOutput
klown Jan 26, 2024
9555c79
feat: implement basic navigation/layer tracking with back button
klown Jan 30, 2024
0357a3c
Merge branch 'feat/create-bmw-encoding' into multipaletteAndOutput
klown Feb 1, 2024
15b899f
fix: change style class of "back button" to be a command style
klown Feb 1, 2024
4594254
Merge branch 'main' into multipaletteAndOutput
klown Feb 1, 2024
a7c6d83
feat: unit tests for basic navigation/layer tracking
klown Feb 5, 2024
8776a1b
feat: implement navigation by layers (demo)
klown Feb 9, 2024
82d7cd6
feat: implement lazy loading of palettes as needed
klown Feb 9, 2024
08d262c
fix: add style file for ActionBranchToPalette component
klown Feb 12, 2024
1ea48b5
fix: make names of cells consistent
klown Feb 12, 2024
de1028a
feat: test that back button can be in verious locations
klown Feb 12, 2024
84ca4f6
fix: replace fetch() with import() for accessing local files.
klown Feb 20, 2024
946ee4e
fix: implement dynamic `import()` based on restrictions
klown Feb 21, 2024
44c960c
feat: implement the ability to peek at the bottom of the NavigationStack
klown Feb 26, 2024
b30c693
feat: add tests of the handlers for "go-to" and "go-back" cells
klown Feb 26, 2024
bdf503a
feat: store id of main palette area in global data for go-back naviga…
klown Feb 29, 2024
3b832fd
fix: move `importPaletteFromJsonFile()` from `GlobalData.ts` to `Glob…
klown Mar 1, 2024
7380960
fix: format a comment properly
klown Mar 1, 2024
4f7cc54
fix: use bliss letters "BMW" for bliss symbol for BWM go-to-palette b…
klown Mar 6, 2024
49a10b0
feat: define return types for all functions
klown Mar 8, 2024
4e3a17f
feat: implement aria-controls for clear and delete cells
klown Mar 11, 2024
13b87f7
feat: rename "keyboards" folder to "palettes"
klown Mar 11, 2024
2e4c520
feat: decompose types into reusable parts and combine as appropriate
klown Mar 12, 2024
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
10 changes: 8 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
<title>Adaptive Palette</title>
</head>
<body>
<h2>Palette Based on JSON</h1>
<div id="bmwKeyCodes">
<h2>Palettes Based on JSON</h1>
<div id="outputArea">
<div id="output_palette"></div>
</div>
<div id="backupArea">
<div id="backup_palette"></div>
</div>
<div id="mainPaletteDisplayArea">
<script type="module">
import "./src/index.js";
</script>
Expand Down
17 changes: 17 additions & 0 deletions src/ActionBranchToPaletteCell.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2024 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

.actionBranchToPaletteCell {
border: 2px solid blue;
background-color: chartreuse;
padding-inline: 0.5rem;
text-align: center;
}
65 changes: 65 additions & 0 deletions src/ActionBranchToPaletteCell.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { render, screen } from "@testing-library/preact";
import "@testing-library/jest-dom";
import { html } from "htm/preact";

import { initAdaptivePaletteGlobals } from "./GlobalData";
import { ActionBranchToPaletteCell } from "./ActionBranchToPaletteCell";

describe("ActionBranchToPaletteCell render tests", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to also test the click event of the ActionBranchToPaletteCell that leads to the rendering of the palette at the next layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I decided that the best place to do that is in Palette.integration.test since the go-to button affects other areas of the screen and involves other objects.


const TEST_CELL_ID = "uuid-of-some-kind";
const goToPaletteCell = {
options: {
"label": "Animals",
"branchTo": "Animals",
"rowStart": "100",
"rowSpan": "12",
"columnStart": "33",
"columnSpan": "11",
"bciAvId": [ 16161, "/", 9011 ]
}
};

beforeAll(async () => {
await initAdaptivePaletteGlobals();
});

test("ActionBranchToPaletteCell rendering", async () => {

render(html`
<${ActionBranchToPaletteCell}
id="${TEST_CELL_ID}"
options=${goToPaletteCell.options}
/>`
);

// Check the rendered cell
const button = await screen.findByRole("button", {name: goToPaletteCell.options.label});

// Check that the ActionBmwCodeCell/button is rendered and has the correct
// attributes and text.
expect(button).toBeVisible();
expect(button).toBeValid();
expect(button.id).toBe(TEST_CELL_ID);
expect(button.getAttribute("class")).toBe("actionBranchToPaletteCell");
expect(button.textContent).toBe(goToPaletteCell.options.label);

// Check the grid cell styles.
expect(button.style["grid-column"]).toBe(`${goToPaletteCell.options.columnStart} / span ${goToPaletteCell.options.columnSpan}`);
expect(button.style["grid-row"]).toBe(`${goToPaletteCell.options.rowStart} / span ${goToPaletteCell.options.rowSpan}`);

// Check disabled state (should be enabled)
expect(button.getAttribute("disabled")).toBe(null);
});
});
66 changes: 66 additions & 0 deletions src/ActionBranchToPaletteCell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2023 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { render } from "preact";
import { html } from "htm/preact";
import { BlissSymbolCellType } from "./index.d";
import { adaptivePaletteGlobals, importPaletteFromJsonFile } from "./GlobalData";
import { Palette } from "./Palette";
import { BlissSymbol } from "./BlissSymbol";
import { speak } from "./GlobalUtils";
import "./ActionBranchToPaletteCell.scss";

// TODO: this is identical to `ActionBmwCodeCellPropsType`. Should it be?
type ActionBranchToPalettePropsType = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking if we should expand the options section for every type component as this part is not defined and verified by typescript. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and will start work on it next. I also noticed that we don't define the return type of many of our functions. Should we?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a great idea to define the return type for functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done them all. The one that was problematic was the return type for the htm() template function used by all of the component Functions. I thought htm returned a string type, but it does not. Its return type is something like TemplateStringsArray, but when I looked into the project's repository, I found issue #73, Question: How well does this work with TypeScript?. The main answer is given in the first response: "there aren't typings for htm / html, but there are extremely basic types for the react and preact integrations." The rest of the discussion is about TypeScript's effort to support a tagged template type. That work is ongoing.

I did some more debugging and it looks like, in the case of Preact, the htm return type is VNode, or "virtual DOM node". I'm currently using that for the return type.

As for the expanding the options, I'm still working on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking if we should expand the options section for every type component as this part is not defined and verified by typescript. What do you think?

For "expanding the options section", I took the full BlissSymbolCellType and created new types from its parts. Since these are not palette cell types, I used "Info" in their type names; for example, a LayoutInfoType that contains only the grid-styles information. Then, one can define cell types by combining the info types as needed.

Let me know what you think.

id: string,
options: BlissSymbolCellType
};

/*
* Event handler for an ActionBranchToPaletteCell button/cell that, when clicked,
* finds and renders the palette referenced by this cell.
*/
const navigateToPalette = async (event) => {
const { paletteStore, navigationStack } = adaptivePaletteGlobals;
const button = event.currentTarget;
speak(button.innerText);

const branchToPaletteName = button.getAttribute("data-branchto");
const paletteDefinition = await paletteStore.getNamedPalette(branchToPaletteName, importPaletteFromJsonFile);
if (paletteDefinition) {
const mainPaletteDisplayArea = document.getElementById("mainPaletteDisplayArea");
navigationStack.push(navigationStack.currentPalette);
render (html`<${Palette} json=${paletteDefinition}/>`, mainPaletteDisplayArea);
navigationStack.currentPalette = paletteDefinition;
}
else {
console.error(`navigateToPalette(): Unable to locate the palette definition for ${branchToPaletteName}`);
}
};

export function ActionBranchToPaletteCell (props: ActionBranchToPalettePropsType) {
const {
columnStart, columnSpan, rowStart, rowSpan, branchTo, bciAvId, label
} = props.options;

const gridStyles = `
grid-column: ${columnStart} / span ${columnSpan};
grid-row: ${rowStart} / span ${rowSpan};
`;

return html`
<button
id="${props.id}" class="actionBranchToPaletteCell" style="${gridStyles}"
data-branchto="${branchTo}" onClick=${navigateToPalette}>
<${BlissSymbol} bciAvId=${bciAvId} label=${label} />
</button>
`;
}
64 changes: 64 additions & 0 deletions src/CommandGoBackCell.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2024 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { render, screen } from "@testing-library/preact";
import "@testing-library/jest-dom";
import { html } from "htm/preact";

import { initAdaptivePaletteGlobals } from "./GlobalData";
import { CommandGoBackCell } from "./CommandGoBackCell";

describe("ActionBmwCodeDell render tests", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to test the click event of the go back button. Since this test involves paletteStore and navigationStack, it may fit better in the integration test file Palette.integration.test.ts?

The same idea applies in the comment above about testing the click event for ActionBranchToPaletteCell.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above with the ActionBranchToPaletteCell, the go-back button test is in Palette.integration.test.


const TEST_CELL_ID = "uuid-of-some-kind";
const goBackCell = {
options: {
"label": "Back Up",
"rowStart": "3",
"rowSpan": "2",
"columnStart": "2",
"columnSpan": "1",
"bciAvId": 12612
}
};

beforeAll(async () => {
await initAdaptivePaletteGlobals();
});

test("CommandGoBackCell rendering", async () => {

render(html`
<${CommandGoBackCell}
id="${TEST_CELL_ID}"
options=${goBackCell.options}
/>`
);

// Check the rendered cell
const button = await screen.findByRole("button", {name: goBackCell.options.label});

// Check that the ActionBmwCodeCell/button is rendered and has the correct
// attributes and text.
expect(button).toBeVisible();
expect(button).toBeValid();
expect(button.id).toBe(TEST_CELL_ID);
expect(button.getAttribute("class")).toBe("btn-command");
expect(button.textContent).toBe(goBackCell.options.label);

// Check the grid cell styles.
expect(button.style["grid-column"]).toBe("2 / span 1");
expect(button.style["grid-row"]).toBe("3 / span 2");

// Check disabled state (should be enabled)
expect(button.getAttribute("disabled")).toBe(null);
});
});
68 changes: 68 additions & 0 deletions src/CommandGoBackCell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2023 Inclusive Design Research Centre, OCAD University
* All rights reserved.
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { render } from "preact";
import { html } from "htm/preact";
import { BlissSymbolCellType } from "./index.d";
import { adaptivePaletteGlobals, importPaletteFromJsonFile } from "./GlobalData";
import { Palette } from "./Palette";
import { BlissSymbol } from "./BlissSymbol";
import { speak } from "./GlobalUtils";
import "./ActionBmwCodeCell.scss";

// TODO: this is identical to `ActionBmwCodeCellPropsType`. Should it be?
type CommandGoBackCellPropsType = {
id: string,
options: BlissSymbolCellType
};

/*
* Event handler for an CommandGoBackCellPropsType button/cell that, when clicked,
* goes back one palette.
*/
const goBackToPalette = async (event) => {
const { paletteStore, navigationStack } = adaptivePaletteGlobals;
const button = event.currentTarget;
speak(button.innerText);

const paletteToGoBackTo = navigationStack.peek();
const paletteDefinition = await paletteStore.getNamedPalette(paletteToGoBackTo.name, importPaletteFromJsonFile);
if (paletteDefinition) {
navigationStack.popAndSetCurrent(paletteDefinition);
render (
html`<${Palette} json=${paletteDefinition}/>`,
document.getElementById("mainPaletteDisplayArea")
);
}
else {
console.error(`goBackToPalette(): Unable to locate the palette definition for ${paletteToGoBackTo}`);
}
};

export function CommandGoBackCell (props: CommandGoBackCellPropsType) {

const {
columnStart, columnSpan, rowStart, rowSpan, bciAvId, label
} = props.options;

const gridStyles = `
grid-column: ${columnStart} / span ${columnSpan};
grid-row: ${rowStart} / span ${rowSpan};
`;

return html`
<button
id="${props.id}" class="btn-command" style="${gridStyles}"
onClick=${goBackToPalette}>
<${BlissSymbol} bciAvId=${bciAvId} label=${label} />
</button>
`;
}
35 changes: 33 additions & 2 deletions src/GlobalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,34 @@ import { EncodingType } from "./index.d";
* The map between cell types (string) and actual components that render corresponding cells
*/
import { ActionBmwCodeCell } from "./ActionBmwCodeCell";
import { ActionBranchToPaletteCell } from "./ActionBranchToPaletteCell";
import { CommandGoBackCell } from "./CommandGoBackCell";
import { ContentBmwEncoding } from "./ContentBmwEncoding";
import { CommandClearEncoding } from "./CommandClearEncoding";
import { CommandDelLastEncoding } from "./CommandDelLastEncoding";
import { PaletteStore } from "./PaletteStore";
import { NavigationStack } from "./NavigationStack";

export const cellTypeRegistry = {
"ActionBmwCodeCell": ActionBmwCodeCell,
"ActionBranchToPaletteCell": ActionBranchToPaletteCell,
"CommandGoBackCell": CommandGoBackCell,
"ContentBmwEncoding": ContentBmwEncoding,
"CommandClearEncoding": CommandClearEncoding,
"CommandDelLastEncoding": CommandDelLastEncoding
};

/**
* Load the map between the BCI-AV IDs and the code consumed by the Bliss SVG builder
* 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 = {
// 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
blissaryIdMap: null,
paletteStore: new PaletteStore(),
navigationStack: new NavigationStack()
};

export async function loadBlissaryIdMap () {
Expand All @@ -52,6 +61,28 @@ export async function initAdaptivePaletteGlobals () {
adaptivePaletteGlobals.blissaryIdMap = await loadBlissaryIdMap();
}

/**
* Import a palette from the given json file using dynamic `import()`.
*
* Note: There are restrictions regarding the arguments to `import()`:
* - the path must start with "./" or "../" and not be part of the argument,
* - the path must end with "/" and not be part of the argument,
* - the file name extension must be added here (not part of the argument)
* See the following for more information:
* https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
*
* @param {String} jsonFile - Name of the JSON file to load, without the
* ".json" extension (added herein).
* @param {String} jsonFile - Path to the file to without any leading nor
* trailing "/".
* @return {JsonPaletteType} - The palette itself, or `null` if it could not be
* loaded.
*/
export async function importPaletteFromJsonFile (jsonFile: string, path: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this function better belong to GlobalUtils.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. I moved it there.

const paletteJson = await import(`./${path}/${jsonFile}.json`);
return paletteJson;
}

/**
* Palette shared states
*/
Expand Down
Loading
Loading