Skip to content

Commit

Permalink
Merge pull request #101 from otacke/setCurrentState
Browse files Browse the repository at this point in the history
Add setCurrentState function
  • Loading branch information
otacke authored Sep 12, 2024
2 parents 05520bd + 1b5786c commit 755bbb2
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 46 deletions.
4 changes: 2 additions & 2 deletions library.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"title": "Crossword",
"description": "Crossword puzzle for H5P",
"majorVersion": 0,
"minorVersion": 5,
"patchVersion": 14,
"minorVersion": 6,
"patchVersion": 0,
"runnable": 1,
"author": "Language Training Center GmbH, Oliver Tacke",
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "h5p-crossword",
"version": "0.5.14",
"version": "0.6.0",
"description": "Example H5P Content Type for getting started",
"private": true,
"scripts": {
Expand Down
127 changes: 105 additions & 22 deletions src/scripts/components/h5p-crossword-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CrosswordClueAnnouncer from '@components/h5p-crossword-clue-announcer.js'
import CrosswordInput from '@components/h5p-crossword-input.js';
import CrosswordTable from '@components/h5p-crossword-table.js';
import CrosswordSolutionWord from '@components/h5p-crossword-solution-word.js';
import Util from '@services/util.js';
import CrosswordGenerator from '@services/h5p-crossword-generator.js';
import './h5p-crossword-content.scss';

Expand Down Expand Up @@ -33,37 +34,63 @@ export default class CrosswordContent {

this.answerGiven = false;

this.setCurrentState(this.params.previousState);
}

/**
* Set current state.
* @param {object} state State to set, must match return value from getCurrentState.
*/
setCurrentState(state = {}) {
state = this.sanitizeState(state);

this.content.innerHTML = ''; // Clean state

// Only support uppercase
this.params.words = (this.params.words || [])
.filter((word) => {
return (
typeof word.answer !== 'undefined' &&
typeof word.clue !== 'undefined'
);
})
.map((word) => {
word.answer = Util.stripHTML(Util.htmlDecode(Util.toUpperCase(word.answer, Util.UPPERCASE_EXCEPTIONS)));
word.clue = Util.stripHTML(Util.htmlDecode(word.clue));
return word;
});

// Restore previous cells or create crossword
if (this.params.previousState && this.params.previousState.crosswordLayout) {
this.crosswordLayout = this.params.previousState.crosswordLayout;
if (state?.crosswordLayout) {
this.crosswordLayout = state.crosswordLayout;
}
else {
const errorMessages = [];
let crosswordGenerator;
let grid;

if (params.words.length < MIN_WORDS_FOR_CROSSWORD) {
if (this.params.words.length < MIN_WORDS_FOR_CROSSWORD) {
errorMessages.push(params.l10n.couldNotGenerateCrosswordTooFewWords);
}
else {
crosswordGenerator = new CrosswordGenerator({
words: params.words,
words: this.params.words,
config: {
poolSize: params.poolSize
poolSize: this.params.poolSize
}
});
grid = crosswordGenerator.getSquareGrid(MAXIMUM_TRIES);

if (!grid) {
errorMessages.push(params.l10n.couldNotGenerateCrossword);
errorMessages.push(this.params.l10n.couldNotGenerateCrossword);
}
}

let badWords = crosswordGenerator?.getBadWords();
if (badWords?.length) {
badWords = badWords.map((badWord) => `${badWord.answer}`).join(', ');

errorMessages.push(params.l10n.problematicWords.replace(/@words/g, badWords));
errorMessages.push(this.params.l10n.problematicWords.replace(/@words/g, badWords));
}

if (errorMessages.length) {
Expand Down Expand Up @@ -91,6 +118,14 @@ export default class CrosswordContent {
this.clueAnnouncer = new CrosswordClueAnnouncer();
tableWrapper.appendChild(this.clueAnnouncer.getDOM());

this.crosswordLayout.result = this.crosswordLayout.result.map((word, index) => {
word.clue = this.params.words[index].clue;
word.answer = this.params.words[index].answer;
word.extraClue = this.params.words[index].extraClue;

return word;
});

// Table
this.table = new CrosswordTable(
{
Expand Down Expand Up @@ -126,7 +161,7 @@ export default class CrosswordContent {
tableWrapper.appendChild(this.table.getDOM());
this.content.appendChild(tableWrapper);

const canHaveSolutionWord = this.table.addSolutionWord(this.params.solutionWord);
const canHaveSolutionWord = this.table.addSolutionWord(this.params.solutionWord, state.solutionCells);
if (this.params.solutionWord !== '') {
if (canHaveSolutionWord) {
this.solutionWord = new CrosswordSolutionWord({
Expand Down Expand Up @@ -169,28 +204,64 @@ export default class CrosswordContent {
this.content.appendChild(this.inputarea.getDOM());

// Restore previous cells
if (this.params.previousState.cells) {
this.table.setAnswers(this.params.previousState.cells);
if (state.cells) {
this.table.setAnswers(state.cells);
this.answerGiven = true;
}

// Restore previous focus
if (this.params.previousState.focus) {
if (this.params.previousState.focus.position && this.params.previousState.focus.position.row) {
this.table.setcurrentOrientation(
this.params.previousState.focus.orientation,
this.params.previousState.focus.position
);
if (typeof state.focus?.position?.row === 'number') {
this.table.setcurrentOrientation(
state.focus.orientation,
state.focus.position
);

this.table.focusCell(this.params.previousState.focus.position);
}
this.table.focusCell(state.focus.position);
}

this.overrideCSS(this.params.theme);

this.resize();
this.callbacks.onInitialized(true);
}

/**
* Sanitize state.
* TODO: This is just rudimentary, should be improved.
* @param {object} state Previous state object.
* @returns {object} Sanitized state object.
*/
sanitizeState(state = {}) {
if (state.crosswordLayout) {
if (typeof state.crosswordLayout?.cols !== 'number') {
delete state.crosswordLayout.cols;
}

if (typeof state.crosswordLayout?.rows !== 'number') {
delete state.crosswordLayout.rows;
}
}

if (
!Array.isArray(state.cells) ||
state.cells.some((char) => typeof char !== 'string' && char !== undefined && char !== null)
) {
delete state.cells;
}

if (typeof state.focus?.position?.row !== 'number' || typeof state.focus?.position?.column !== 'number') {
delete state.focus;
}

if (state.focus) {
if (state.focus.orientation !== 'across' && state.focus.orientation !== 'down') {
delete state.focus.orientation;
}
}

return state;
}

/**
* Return the DOM for this class.
* @returns {HTMLElement} DOM for this class.
Expand Down Expand Up @@ -317,11 +388,23 @@ export default class CrosswordContent {
return;
}

return {
crosswordLayout: this.crosswordLayout,
cells,
focus
// Position of the solution word cells in the crossword
const solutionCells = this.table?.getSolutionWordCellPositions();

const crosswordLayout = {
cols: this.crosswordLayout.cols,
rows: this.crosswordLayout.rows,
result: this.crosswordLayout.result.map((word) => {
return {
clueId: word.clueId,
orientation: word.orientation,
startx: word.startx,
starty: word.starty,
};
})
};

return { crosswordLayout, cells, focus, solutionCells };
}

/**
Expand Down
34 changes: 29 additions & 5 deletions src/scripts/components/h5p-crossword-table.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CrosswordCell from '@components/h5p-crossword-cell.js';
import Util from '@services/util.js';
import './h5p-crossword-table.scss';
import { XAPI_PLACEHOLDER } from '@mixins/xapi.js';

/** @constant {number} CELL_FONT_SIZE_DIVIDER Divisor found by testing */
export const CELL_FONT_SIZE_DIVIDER = 2;
Expand Down Expand Up @@ -313,13 +314,21 @@ export default class CrosswordTable {
/**
* Mark cells with solution word ids and circles if possible.
* @param {string} solutionWord Solution word.
* @param {number[]} positions Positions of solution word.
* @returns {boolean} True, if possibe, else false.
*/
addSolutionWord(solutionWord) {
addSolutionWord(solutionWord, positions = []) {
if (!solutionWord || solutionWord === '') {
return false;
}

if (positions.length) {
positions.forEach((position) => {
this.cells[position.row][position.column].addSolutionWordIdMarker(position.solutionWordId);
});
return true;
}

const solutionWordCells = this.findSolutionWordCells(solutionWord);
solutionWordCells.forEach((cell, index) => {
cell.addSolutionWordIdMarker(index + 1);
Expand Down Expand Up @@ -1091,18 +1100,33 @@ export default class CrosswordTable {

const placeholders = [];
if (this.params.scoreWords) {
placeholders.push(CrosswordTable.XAPI_PLACEHOLDER);
placeholders.push(XAPI_PLACEHOLDER);
}
else {
while (placeholders.length < word.answer.length) {
placeholders.push(CrosswordTable.XAPI_PLACEHOLDER);
placeholders.push(XAPI_PLACEHOLDER);
}
}

return `<p>${clue}</ br>${placeholders.join(' ')}</p>`;
})
.join('');
}
}

CrosswordTable.XAPI_PLACEHOLDER = '__________';
/**
* Get solution word cell positions.
* @returns {object[]} Solution word cell positions.
*/
getSolutionWordCellPositions() {
return this.cells
.flat()
.filter(((cell) => cell.getInformation().solutionWordId !== null))
.map((cell) => {
return {
row: cell.getPosition().row,
column: cell.getPosition().column,
solutionWordId: cell.getInformation().solutionWordId
};
});
}
}
14 changes: 0 additions & 14 deletions src/scripts/h5p-crossword.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,6 @@ export default class Crossword extends H5P.Question {
this.previousState = {};
}

// Only support uppercase
this.params.words = (this.params.words || [])
.filter((word) => {
return (
typeof word.answer !== 'undefined' &&
typeof word.clue !== 'undefined'
);
})
.map((word) => {
word.answer = Util.stripHTML(Util.htmlDecode(Util.toUpperCase(word.answer, Util.UPPERCASE_EXCEPTIONS)));
word.clue = Util.stripHTML(Util.htmlDecode(word.clue));
return word;
});

this.content = new CrosswordContent(
{
scoreWords: this.params.behaviour.scoreWords,
Expand Down
9 changes: 9 additions & 0 deletions src/scripts/mixins/question-type-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,13 @@ export default class QuestionTypeContract {

return this.content.getCurrentState();
}

/**
* Set current state.
* Candidate for question type contract in H5P core.
* @param {object} state State to set, must match return value from getCurrentState.
*/
setCurrentState(state = {}) {
this.content.setCurrentState(state);
}
}
3 changes: 3 additions & 0 deletions src/scripts/mixins/xapi.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Util from '@services/util.js';
import { DEFAULT_DESCRIPTION } from '@scripts/h5p-crossword.js';

/** @constant {string} XAPI_PLACEHOLDER Placeholder for a gap. */
export const XAPI_PLACEHOLDER = '__________';

/**
* Mixin containing methods for xapi stuff.
*/
Expand Down

0 comments on commit 755bbb2

Please sign in to comment.