From c38386ad420e56419bd4e62f5bd57b04500a9c99 Mon Sep 17 00:00:00 2001 From: tomasvana10 Date: Thu, 9 May 2024 19:59:30 +1000 Subject: [PATCH] Make small bug fixes and add minor ergonomic features for web app Fixed a plethora of small bugs, such as the program not removing a compound input element when selecting a definitions list item. Some ergonomic features that were introduced include: - Removing a compound input element if you click anywhere other than the compound input element - Preventing your focus on a compound input element being removed if you clicked on a definitions list item that would set your focus to the current compound input element - Removing small gaps between definitions list items and dropdown buttons to prevent misclicks - Performing an appropriate cell shift (shift or skip) when completing a compound input via pressing enter or escape (not through a dropdown button or mouse click) - Adding a vertical divider between the across and down definitions and increasing the font size of the 'Across' and 'Down' headings --- .../cword_webapp/static/interaction.js | 135 +++++++++++------- .../cword_webapp/static/styles.css | 36 +++-- 2 files changed, 108 insertions(+), 63 deletions(-) diff --git a/crossword_puzzle/cword_webapp/static/interaction.js b/crossword_puzzle/cword_webapp/static/interaction.js index a30667048..f6bad1cd8 100644 --- a/crossword_puzzle/cword_webapp/static/interaction.js +++ b/crossword_puzzle/cword_webapp/static/interaction.js @@ -1,29 +1,20 @@ class Interaction { /* Class to handle all forms of interaction with the web app. - - Also contains utility functions to perform cell-related calculations. + + Also contains utility functions to perform cell-related calculations. */ + static arrowKeys = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"]; static spacebarKeys = ["Spacebar", " "]; static backspaceKeys = ["Backspace", "Delete"]; static compoundInputPlaceholders = [ - // Cycle through these periodically - "ㅇ", - "+", - "ㅏ", - "=", - "아", - "क​", - "+", - "इ", - "=", - "कै", + "ㅇ", "+", "ㅏ", "=", "아", "क​", "+", "इ", "=", "कै", ]; static onlyLangRegex = /\p{L}/u; // Ensure user only types language characters constructor() { this.direction = "ACROSS"; // Default direction when first clicking (if the - // first click is at an intersection). + // first click is at an intersection). this.currentWord = null; // e.x. "HELLO" this.cellCoords = null; // e.x. [0, 5] this.staticIndex = null; // e.x. 5 @@ -211,8 +202,9 @@ class Interaction { // If the cell is wrong/red in colour, it must be reverted as the user // has just typed in it currentCell.classList.remove("wrong"); + } else if (mode === "del") { - // The focused cell has content, just delete it + // The focused cell has content, just delete it and do nothing if ( !Interaction.isEmpty(currentCell) && !currentCell.classList.contains("lock_in") @@ -241,6 +233,8 @@ class Interaction { } handleCellShift(mode) { + /* Determines how the focus of a cell within a word is shifted. */ + this.changeCellFocus(false); // User has the "smart skip" button toggled, so perform a cell skip if (mode === "enter" && document.getElementById("ts").checked) { @@ -249,6 +243,7 @@ class Interaction { this.direction, mode ); + } else { // Just do a normal cell shift this.cellCoords = this.shiftCellCoords( @@ -260,7 +255,7 @@ class Interaction { // Refocus the entire word, then set the focus of the cell that has just // been shifted/skipped to. No need to update the current word as the current - // word can never change with a standard keyboard input + // word can never change with a standard keyboard input. this.changeWordFocus(true); this.changeCellFocus(true); } @@ -268,7 +263,8 @@ class Interaction { skipCellCoords(coords, direction) { /* Skip to the next empty cell if the current cell is empty and there is an empty cell in front of the current cell somewhere along the current word - (that is separated by filled cells) */ + (that is separated by filled cells). + */ let newCellCoords = this.shiftCellCoords(coords, direction, "enter"); // The next cell is a void/empty cell, so just return the shifted coordinates. @@ -303,11 +299,13 @@ class Interaction { false), the original coordinates are returned. The aforementioned force parameter allows the cell coordinates to be shifted - into a cell that may be a void cell. */ + into a cell that may be a void cell. + + */ let offset = mode == "enter" ? 1 : -1; - let newCellCoords = - dir == this.directions[1] + let newCellCoords = + dir === this.directions[1] ? [coords[0] + offset, coords[1]] : [coords[0], coords[1] + offset]; let newCell = Interaction.getCellElement(newCellCoords); @@ -322,15 +320,22 @@ class Interaction { onDefinitionsListItemClick(event, numLabel, dir) { /* Set user input to the start of a word when they click its definition/clue. */ + // Retrieve cell from parent element of number label list item + let currentCell = document.querySelector( + `[data-num_label="${numLabel}"]`).parentElement + Interaction.preventZoomIfRequired(event); - this.setFocusMode(false); + this.removeCompoundInputIfRequired(currentCell); + this.setFocusMode(false); document.activeElement.blur(); + // User has compound input set at, say, 26 down, and they have just clicked + // on the definition for 26 down, so refocus their compound input + if (this.compoundInputActive) { + document.getElementsByClassName("compound_input")[0].focus(); + } this.direction = dir; - // Retrieve cell from parent element of number label list item - this.cellCoords = Interaction.updateCellCoords( - document.querySelector(`[data-num_label="${numLabel}"]`).parentElement - ); + this.cellCoords = Interaction.updateCellCoords(currentCell); this.currentWord = this.updateCurrentWord(); this.setFocusMode(true); } @@ -339,18 +344,13 @@ class Interaction { /* Handles how the grid responds to a user clicking on the cell. Ensures the appropriate display of the current cell and word focus on cell click, as well as alternating input directions if clicking at an intersecting point - between two words. */ - // User is performing compound input - if ( - this.compoundInputActive && - cell !== Interaction.getCellElement(this.cellCoords) - ) { - this.removeCompoundInput(); - } + between two words. + */ Interaction.preventZoomIfRequired(event); - this.setFocusMode(false); + this.removeCompoundInputIfRequired(cell) + this.setFocusMode(false); let newCellCoords = Interaction.updateCellCoords(cell); // User is clicking on an intersection for the second time, so alternate // the direction @@ -373,7 +373,8 @@ class Interaction { /* Determine how the program responds to the user pressing an arrow. First, see if a "enter" or "del" type shift is performed and in what direction. Then, ensure the user is not shifting into a ``.empty`` cell. Finally, - alternate the direction if necessary and refocus. */ + alternate the direction if necessary and refocus. + */ event.preventDefault(); let mode = key === "ArrowDown" || key === "ArrowRight" ? "enter" : "del"; @@ -433,7 +434,7 @@ class Interaction { handleEnterKeybindPress(event) { /* Allow the user to check the current word with "Enter" or reveal it with - "Shift + Enter". */ + [Shift + Enter]. */ Interaction.unfocusActiveElement(); this.hideDropdowns(); @@ -454,6 +455,7 @@ class Interaction { handleEscapePress(event) { /* Remove focus from everything. */ + event.preventDefault(); Interaction.unfocusActiveElement(); this.hideDropdowns(); @@ -472,13 +474,14 @@ class Interaction { } } - doSpecialButtonAction(magnitude, mode, via_button = true) { + doSpecialButtonAction(magnitude, mode, viaButton = true) { /* Perform reveal/check/clear operations on a selected cell, word, or, the - grid. */ + grid. + */ // Since the user is running the function from a dropdown button, close the // dropdown that the button belongs to - if (via_button) { + if (viaButton) { this.onDropdownClick(mode + "_dropdown"); } @@ -507,13 +510,13 @@ class Interaction { } doGridOperation(cell, mode) { - /* Perform either a reveal, check or clear action on a cell. */ + /* Perform either a reveal, check or clear action on a single cell. */ if (mode === "reveal") { cell.classList.remove("wrong"); Interaction.setValue(cell, cell.getAttribute("data-value")); cell.classList.add("lock_in"); // This cell must now be correct, so lock - // it in + // it in } else if (mode === "check") { if (!Interaction.isEmpty(cell)) { if (cell.hasCorrectValue()) { @@ -531,6 +534,13 @@ class Interaction { } shouldDirectionBeAlternated(coords) { + /* Return true if shifting the cell coords in both enter and delete mode + returns values that are identical to the current cell coordinates (passed + as the ``coords`` parameter). This likely means the user clicked on a new + word that is a different direction to the current one, or they skipped over + void cells as a result of ``this.handleArrowPress``. + */ + return ( this.shiftCellCoords(coords, this.direction, "enter").isEqualTo(coords) && this.shiftCellCoords(coords, this.direction, "del").isEqualTo(coords) @@ -547,7 +557,9 @@ class Interaction { changeWordFocus(focus) { /* Retrieve the starting and ending coordinates of a word and change the - colour of the cell elements that make up that word to a different colour. */ + colour of the cell elements that make up that word to either blue (focused) + or white/black (unfocused). + */ for (const element of this.getWordElements()) { element.style.backgroundColor = focus @@ -659,9 +671,13 @@ class Interaction { } setCompoundInput(priorValue) { + /* Remove the value of the current cell and add an input element to its + children. + */ this.compoundInputActive = true; + if (!priorValue) { this.wasEmpty = true; } let currentCell = Interaction.getCellElement(this.cellCoords); - currentCell.onclick = event => dummyCellClick(event); + currentCell.onclick = event => Interaction.dummyCellClick(event); Interaction.setValue(currentCell, ""); let compoundInput = document.createElement("input"); @@ -673,9 +689,10 @@ class Interaction { } handleSetCompoundInput() { - if (this.cellCoords === null) { + if (this.cellCoords === null) { // User must select a cell return alert(this.errMsgs[0]); } + // User already has a compound input selected, so they want to remove it if (document.getElementsByClassName("compound_input")[0]) { return this.removeCompoundInput(); } @@ -684,10 +701,8 @@ class Interaction { this.setCompoundInput(priorValue); } - removeCompoundInput() { - if (!this.compoundInputActive) { - return; - } // failsafe + removeCompoundInput(andShift=true) { + if (!this.compoundInputActive) { return; } // failsafe let compoundInput = document.getElementsByClassName("compound_input")[0]; let cellOfCompoundInput = compoundInput.parentElement; let enteredText = compoundInput.value; @@ -705,9 +720,13 @@ class Interaction { cellOfCompoundInput.classList.remove("lock_in", "wrong"); this.compoundInputActive = false; this.currentPlaceholder = 0; + if (andShift) { + this.handleCellShift("enter"); // Shift focus for ease of use + } } cycleCompoundInputPlaceholderText() { + /* Cycle placeholder text whenever a compound input element is active. */ let compoundInput = document.getElementsByClassName("compound_input")[0]; if (compoundInput === undefined) { return; @@ -724,10 +743,25 @@ class Interaction { } } + removeCompoundInputIfRequired(cell) { + /* Remove the compound input if it is already active and the ``cell`` element + is not equal to the current cell. + */ + if ( + this.compoundInputActive && + cell !== Interaction.getCellElement(this.cellCoords) + ) { + this.removeCompoundInput(); + } + } + handleClickForDropdowns(event) { - /* Close dropdowns if clicking outside of the dropdown area. */ + /* Close dropdowns and remove compound input (if possible) when clicking + outside of the dropdown area. + */ if (!event.target.closest(".special_button, .dropdown, .dropdown_button")) { this.hideDropdowns(); + this.removeCompoundInput(false); } } @@ -772,7 +806,7 @@ class Interaction { onDropdownClick(id) { /* Opens the dropdown a user clicks on or closes it if they already have it open. */ - this.removeCompoundInput(); + this.removeCompoundInput(false); let dropdown = document.getElementById(id); if (id === this.currentDropdown) { @@ -876,6 +910,7 @@ class Interaction { } } + Array.prototype.isEqualTo = function (arr) { return JSON.stringify(this) === JSON.stringify(arr); }; diff --git a/crossword_puzzle/cword_webapp/static/styles.css b/crossword_puzzle/cword_webapp/static/styles.css index eae9c9c57..74c8e1680 100644 --- a/crossword_puzzle/cword_webapp/static/styles.css +++ b/crossword_puzzle/cword_webapp/static/styles.css @@ -23,8 +23,8 @@ body { var(--c); } +/* Integration of scaling from the tkinter GUI. */ li, -.def_heading, .toggle_label, .special_button, .dropdown_button, @@ -33,6 +33,10 @@ button { font-size: calc(1em * var(--scaling)); } +.def_heading { + font-size: calc(1.1em * var(--scaling)); +} + h2 { font-size: calc(24px * var(--scaling)); } @@ -52,6 +56,7 @@ p { } button { + /* Standard button design. */ -webkit-border-radius: 5; -moz-border-radius: 5; border-radius: 5px; @@ -164,6 +169,10 @@ button:hover { z-index: 100; } +#return_def_zoom { + padding: 3% 0 3%; +} + .wrapper_def { display: flex; flex: 1; @@ -179,18 +188,22 @@ button:hover { overflow-y: hidden; } -ul { +.wrapper_def ul { /* Remove bullet points from the unordered list */ list-style-type: none; } -li { +.wrapper_def li { /* Each definition/clue */ margin-bottom: 10px; white-space: pre-wrap; /* Helps html recognise more whitespace for the definitions list */ - padding: 4.5%; + padding: 5.5%; cursor: pointer; + /* Prevent the user from missing their click by clicking in the small area + between definitions list items. */ + margin-top: 0; + margin-bottom: 0; } .def_heading { @@ -201,13 +214,10 @@ li { resizing equally distributed. */ .definitions_a, .definitions_d { + border-right: 0.1px solid var(--text_colour); flex-grow: 1; flex: 1; } - -.definitions_d { - padding-right: 7.75%; -} /* ---------- */ /* ----- Styling for the popups ----- */ @@ -248,10 +258,10 @@ resizing equally distributed. */ display: none; top: 100%; left: 0; - width: 18.73vmin; + width: 20vmin; background-color: var(--sub_colour); box-shadow: 6px 6px 10px 1px var(--main_colour); - padding: 5px; + padding: 0; } .dropdown button { @@ -300,11 +310,11 @@ resizing equally distributed. */ } #check_dropdown { - left: 24.4vmin; + left: 35.522%; } #clear_dropdown { - left: 49.1vmin; + left: 71.21%; } /* ---------- */ @@ -333,7 +343,7 @@ are from https://codepen.io/morgoe/pen/VvzWQg ----- */ position: absolute; display: flex; justify-content: space-around; - top: 8vmin; + top: 8.225vmin; } .toggle {