Skip to content

Commit 349a676

Browse files
committed
fix: refactor locker to use utils showCustomPrompt with keyboard navigation
Main Fix: - Replaced custom showCustomPrompt with utils version for consistency - Added full keyboard navigation support (arrows, tab, enter, space) - Added velocity reset and delay to prevent stuck movement - Maintained pagination functionality (5 characters per page) Improvements: - Removed duplicate prompt implementation code - Removed inline CSS styles for cleaner code - Benefits from stuck key fix in utils closeCustomPrompt - Reduced total code lines - Improved accessibility for keyboard-only users Closes #278
1 parent 4d43c14 commit 349a676

1 file changed

Lines changed: 80 additions & 148 deletions

File tree

Lines changed: 80 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,98 @@
11
import { time } from '../../kplayCtx';
22
import { characters } from '../../constants';
33
import { changePlayerSprite } from '../../utils/changePlayer';
4-
import { closeCustomPrompt } from '../../utils';
4+
import { showCustomPrompt, closeCustomPrompt } from '../../utils';
55

6-
const slightPause = () => new Promise((res) => setTimeout(res, 500));
76
let abort;
87

9-
export const interactionWithLocker = (player, k, map) => {
10-
player.onCollide('cabin_edge_room_1', () => {
11-
const characterOptions = characters.map(
12-
(character) =>
13-
character.name.charAt(0).toUpperCase() + character.name.slice(1)
14-
);
15-
time.paused = true;
16-
player.state.isInDialog = true;
17-
abort = new AbortController();
18-
// Trigger the custom prompt when the player collides with the drinks machine
19-
showCustomPrompt(
20-
'What character would you like to play?', // Prompt message
21-
characterOptions, // Dynamic options based on characters
22-
(selectedOption) => {
23-
// Find the selected character from the array
24-
const selectedCharacter = characters.find(
25-
(character) =>
26-
character.name.toLowerCase() ===
27-
selectedOption.toLowerCase()
28-
);
29-
changePlayerSprite(
30-
selectedCharacter.name,
31-
player.curAnim(),
32-
k,
33-
player
34-
);
35-
k.canvas.focus();
36-
},
37-
player,
38-
k
39-
);
8+
/**
9+
* Show paginated character list (5 per page)
10+
*/
11+
const showCharacterList = async (player, k, page = 0) => {
12+
const characterOptions = characters.map(
13+
(character) =>
14+
character.name.charAt(0).toUpperCase() + character.name.slice(1)
15+
);
16+
17+
const itemsPerPage = 5;
18+
const totalPages = Math.ceil(characterOptions.length / itemsPerPage);
19+
const startIndex = page * itemsPerPage;
20+
const endIndex = Math.min(startIndex + itemsPerPage, characterOptions.length);
21+
const currentCharacters = characterOptions.slice(startIndex, endIndex);
22+
23+
const message = `<strong>Character Selection (Page ${page + 1}/${totalPages})</strong>`;
24+
25+
// Create options for characters on this page
26+
const options = currentCharacters.map((characterName, index) => {
27+
const actualIndex = startIndex + index;
28+
return {
29+
value: actualIndex,
30+
text: characterName,
31+
};
4032
});
41-
};
42-
43-
async function showCustomPrompt(message, options, callback, player, k) {
44-
const statsUI = document.getElementById('stats-container');
45-
const miniMapUI = document.getElementById('minimap');
46-
statsUI.style.display = 'none';
47-
miniMapUI.style.display = 'none';
48-
49-
// Set the prompt message
50-
document.getElementById('prompt-message').textContent = message;
51-
52-
// Clear any existing options in the container
53-
const optionsContainer = document.getElementById('options-container');
54-
optionsContainer.innerHTML = '';
55-
56-
const itemsPerPage = 5; // Number of options per page
57-
let currentPage = 1;
58-
const totalPages = Math.ceil(options.length / itemsPerPage);
59-
60-
async function renderOptions() {
61-
await slightPause();
62-
// Calculate the start and end indices for the current page
63-
const start = (currentPage - 1) * itemsPerPage;
64-
const end = start + itemsPerPage;
65-
const currentOptions = options.slice(start, end);
6633

67-
// Clear existing options
68-
optionsContainer.innerHTML = '';
69-
70-
// Create buttons for each option
71-
currentOptions.forEach((option) => {
72-
const button = document.createElement('button');
73-
button.textContent = option;
74-
button.classList.add('option-btn');
75-
button.setAttribute('tabindex', '0'); // Make the button focusable
76-
77-
// Add click event for mouse interactions
78-
button.onclick = function () {
79-
callback(option);
80-
closeCustomPrompt(player, k, abort);
81-
};
82-
83-
// Add keyboard event listener for accessibility
84-
button.addEventListener('keydown', function (e) {
85-
if (e.key === 'Enter' || e.key === ' ') {
86-
// Enter or Space key
87-
e.preventDefault(); // Prevent the default behavior (e.g., form submission)
88-
callback(option);
89-
closeCustomPrompt(player, k, abort);
90-
}
91-
});
92-
93-
optionsContainer.appendChild(button);
34+
// Add navigation buttons
35+
if (page > 0) {
36+
options.push({
37+
value: 'prev',
38+
text: '← Previous',
9439
});
9540
}
9641

97-
async function renderPagination() {
98-
await slightPause();
99-
// Create Previous button
100-
const prevButton = document.createElement('button');
101-
prevButton.classList.add('option-btn');
102-
prevButton.textContent = 'Previous';
103-
prevButton.disabled = currentPage === 1;
104-
prevButton.onclick = async () => {
105-
if (currentPage > 1) {
106-
currentPage--;
107-
await renderOptions();
108-
await renderPagination();
109-
110-
// Set focus on the first button
111-
if (optionsContainer.children.length > 0) {
112-
optionsContainer.children[0].focus();
113-
}
114-
}
115-
};
42+
if (page < totalPages - 1) {
43+
options.push({
44+
value: 'next',
45+
text: 'Next →',
46+
});
47+
}
11648

117-
// Create Next button
118-
const nextButton = document.createElement('button');
119-
nextButton.classList.add('option-btn');
120-
nextButton.textContent = 'Next';
121-
nextButton.disabled = currentPage === totalPages;
122-
nextButton.onclick = async () => {
123-
if (currentPage < totalPages) {
124-
currentPage++;
125-
await renderOptions();
126-
await renderPagination();
49+
options.push({
50+
value: 'close',
51+
text: '✕ Close',
52+
});
12753

128-
// Set focus on the first button
129-
if (optionsContainer.children.length > 0) {
130-
optionsContainer.children[0].focus();
131-
}
54+
showCustomPrompt(
55+
message,
56+
options,
57+
async (selectedValue) => {
58+
if (selectedValue === 'prev' || selectedValue === 'next') {
59+
// Reopen immediately for navigation
60+
setTimeout(async () => {
61+
player.state.isInDialog = true;
62+
time.paused = true;
63+
64+
if (selectedValue === 'prev') {
65+
await showCharacterList(player, k, page - 1);
66+
} else if (selectedValue === 'next') {
67+
await showCharacterList(player, k, page + 1);
68+
}
69+
}, 50);
70+
} else if (typeof selectedValue === 'number') {
71+
const selectedCharacter = characters[selectedValue];
72+
changePlayerSprite(
73+
selectedCharacter.name,
74+
player.curAnim(),
75+
k,
76+
player
77+
);
13278
}
133-
};
79+
},
80+
player,
81+
k,
82+
abort
83+
);
84+
};
13485

135-
// Create Close button
136-
const closeButton = document.createElement('button');
137-
closeButton.classList.add('option-btn');
138-
closeButton.textContent = 'Close';
139-
closeButton.onclick = () => {
140-
closeCustomPrompt(player, k, abort);
141-
};
142-
closeButton.style.width = '35%';
143-
prevButton.style.width = '20%';
144-
nextButton.style.width = '20%';
145-
closeButton.style.backgroundColor = 'crimson';
146-
prevButton.style.backgroundColor = 'lightblue';
147-
nextButton.style.backgroundColor = 'lightgreen';
148-
optionsContainer.appendChild(closeButton);
149-
optionsContainer.appendChild(prevButton);
150-
optionsContainer.appendChild(nextButton);
151-
}
86+
export const interactionWithLocker = (player, k, map) => {
87+
player.onCollide('cabin_edge_room_1', async () => {
88+
player.vel = k.vec2(0, 0);
15289

153-
// Display the custom prompt
154-
document.getElementById('custom-prompt').style.display = 'flex';
155-
optionsContainer.style.display = 'flex';
156-
optionsContainer.style.flexWrap = 'wrap';
90+
time.paused = true;
91+
player.state.isInDialog = true;
92+
abort = new AbortController();
15793

158-
// Initial render of options and pagination
159-
await renderOptions();
160-
await renderPagination();
94+
await new Promise(resolve => setTimeout(resolve, 300));
16195

162-
// Set focus on the first button
163-
if (optionsContainer.children.length > 0) {
164-
optionsContainer.children[0].focus();
165-
}
166-
}
96+
await showCharacterList(player, k);
97+
});
98+
};

0 commit comments

Comments
 (0)