this repo has no description
1import { UndoManager } from './undoManager';
2import { VALUES } from './values';
3
4// Define interfaces for our value cards and overall app state.
5export interface ValueCard {
6 id: number;
7 name: string;
8 column: string; // Part1: "unassigned", "notImportant", "important", "veryImportant"
9 // Part2: same as Part1
10 // Part3: "core" or "additional" (for cards carried over from veryImportant)
11 order: number;
12 description?: string; // Add optional description for custom cards
13 isCustom?: boolean; // Flag for custom cards
14}
15
16export interface AppState {
17 currentPart: 'part1' | 'part2' | 'part3' | 'part4' | 'review';
18 cards: ValueCard[];
19 // In part 4, user can add final statements for each core value (by card id)
20 finalStatements: Record<number, string>;
21 valueSet: 'limited' | 'all'; // Add state for current value set
22 editingDescriptionCardId: number | null; // ID of card whose description is being edited
23}
24
25// Use type-only import
26
27// Define the structure based on the imported VALUES
28interface ValueDefinition {
29 name: string;
30 description: string;
31}
32
33// Recreate ALL_VALUES and LIMITED_VALUES based on the import
34const ALL_VALUE_DEFINITIONS: ValueDefinition[] = VALUES;
35const LIMITED_VALUE_DEFINITIONS = ALL_VALUE_DEFINITIONS.slice(0, 10);
36
37// Create a global map for easy description lookup
38const valueDefinitionsMap = new Map<string, string>(
39 ALL_VALUE_DEFINITIONS.map((def: ValueDefinition) => [def.name, def.description]),
40);
41
42// Main application class
43export class App {
44 private state: AppState;
45 public undoManager: UndoManager<AppState>;
46 private storageKey = 'valuesExerciseState';
47 private nextCustomCardId = -1; // Counter for unique negative IDs
48
49 constructor() {
50 // Load state from localStorage or initialize default state.
51 const saved = localStorage.getItem(this.storageKey);
52 let initialState: AppState;
53 if (saved) {
54 try {
55 initialState = JSON.parse(saved) as AppState;
56 // Ensure editing state is null initially
57 initialState.editingDescriptionCardId = null;
58 const minId = Math.min(0, ...initialState.cards.filter((c) => c.isCustom).map((c) => c.id));
59 this.nextCustomCardId = minId - 1;
60 } catch {
61 initialState = this.defaultState('limited');
62 this.nextCustomCardId = -1; // Reset on error
63 }
64 } else {
65 initialState = this.defaultState('limited');
66 this.nextCustomCardId = -1; // Reset if no saved state
67 }
68 this.state = initialState;
69 this.undoManager = new UndoManager<AppState>(this.state);
70
71 this.bindEventListeners();
72 this.render();
73 this.updateUndoRedoButtons();
74 }
75
76 // Default state with sample value cards based on the selected set
77 public defaultState(valueSet: 'limited' | 'all' = 'limited'): AppState {
78 const definitionsToUse = valueSet === 'all' ? ALL_VALUE_DEFINITIONS : LIMITED_VALUE_DEFINITIONS;
79 const sampleCards: ValueCard[] = definitionsToUse.map((definition: ValueDefinition, index: number) => ({
80 id: index + 1,
81 name: definition.name, // Use name from definition
82 column: 'unassigned',
83 order: index,
84 description: undefined, // Ensure built-in cards start with no override
85 isCustom: false,
86 }));
87 return {
88 currentPart: 'part1',
89 cards: sampleCards,
90 finalStatements: {},
91 valueSet: valueSet, // Store the set used
92 editingDescriptionCardId: null, // Start with no editing
93 };
94 }
95
96 // Save the state to localStorage.
97 private saveState() {
98 localStorage.setItem(this.storageKey, JSON.stringify(this.state));
99 }
100
101 // Update state via the undoManager then re-render.
102 public updateState(newState: AppState) {
103 this.undoManager.execute(newState);
104 this.state = this.undoManager.getState();
105 this.saveState();
106 this.render();
107 this.updateUndoRedoButtons();
108 }
109
110 // Method to toggle between limited and all values
111 public toggleValueSet() {
112 const currentState = this.undoManager.getState();
113 const nextSet = currentState.valueSet === 'limited' ? 'all' : 'limited';
114 // Generate a fresh default state for the *new* set, resetting progress
115 const newState = this.defaultState(nextSet);
116 // Execute this change through the undo manager
117 this.updateState(newState);
118 }
119
120 // --- Add Value Form Logic ---
121 public showAddValueForm() {
122 const form = document.getElementById('addValueForm') as HTMLDivElement | null;
123 const nameInput = document.getElementById('newValueName') as HTMLInputElement | null;
124 const descInput = document.getElementById('newValueDesc') as HTMLTextAreaElement | null;
125 if (form && nameInput && descInput) {
126 nameInput.value = ''; // Clear previous input
127 descInput.value = '';
128 form.style.display = 'flex'; // Show the modal
129 nameInput.focus(); // Focus the name input
130 }
131 }
132
133 public hideAddValueForm() {
134 const form = document.getElementById('addValueForm') as HTMLDivElement | null;
135 if (form) {
136 form.style.display = 'none'; // Hide the modal
137 }
138 }
139
140 public saveNewValue() {
141 const nameInput = document.getElementById('newValueName') as HTMLInputElement | null;
142 const descInput = document.getElementById('newValueDesc') as HTMLTextAreaElement | null;
143 const name = nameInput?.value.trim().toUpperCase(); // Normalize name
144 const description = descInput?.value.trim();
145
146 if (!name) {
147 alert('Please enter a name for the new value.');
148 nameInput?.focus();
149 return;
150 }
151 if (!description) {
152 alert('Please enter a description for the new value.');
153 descInput?.focus();
154 return;
155 }
156
157 const newState = this.undoManager.getState();
158
159 // Check for duplicates (case-insensitive)
160 if (newState.cards.some((card) => card.name.toUpperCase() === name)) {
161 alert(`Value "${name}" already exists.`);
162 nameInput?.focus();
163 return;
164 }
165
166 // Create new card
167 const newCard: ValueCard = {
168 id: this.nextCustomCardId,
169 name: name,
170 description: description, // Store description directly on card
171 column: 'unassigned', // Add to unassigned in current view
172 order: newState.cards.length, // Add to end
173 isCustom: true,
174 };
175
176 this.nextCustomCardId--; // Decrement for next custom card
177
178 newState.cards.push(newCard);
179 this.updateState(newState);
180 this.hideAddValueForm();
181 }
182
183 // --- Description Edit Logic ---
184 public startEditingDescription(cardId: number) {
185 const newState = this.undoManager.getState();
186 newState.editingDescriptionCardId = cardId;
187 this.updateState(newState); // Re-render to show the textarea
188 }
189
190 public saveDescriptionEdit(cardId: number, newDescription: string) {
191 const newState = this.undoManager.getState();
192 const cardToUpdate = newState.cards.find((c) => c.id === cardId);
193 if (cardToUpdate) {
194 cardToUpdate.description = newDescription.trim(); // Save trimmed description
195 }
196 newState.editingDescriptionCardId = null; // Stop editing
197 this.updateState(newState);
198 }
199
200 public cancelDescriptionEdit() {
201 const newState = this.undoManager.getState();
202 newState.editingDescriptionCardId = null; // Just stop editing, don't save
203 this.updateState(newState);
204 }
205
206 // Bind event listeners for UI interactions.
207 private bindEventListeners() {
208 // Remove alias: Arrow functions capture 'this' correctly
209 // const appInstance = this;
210
211 // --- Add Value Form Listeners ---
212 document.getElementById('showAddValueFormBtn')?.addEventListener('click', () => {
213 this.showAddValueForm();
214 });
215 document.getElementById('saveNewValueBtn')?.addEventListener('click', () => {
216 this.saveNewValue();
217 });
218 document.getElementById('cancelNewValueBtn')?.addEventListener('click', () => {
219 this.hideAddValueForm();
220 });
221 // Close modal if clicking outside the content
222 document.getElementById('addValueForm')?.addEventListener('click', (e) => {
223 if (e.target === e.currentTarget) {
224 this.hideAddValueForm();
225 }
226 });
227
228 // Navigation buttons
229 document.getElementById('toPart2')?.addEventListener('click', () => {
230 const newState = this.undoManager.getState();
231 // Check for unassigned cards before proceeding
232 const unassignedCount = newState.cards.filter((card) => card.column === 'unassigned').length;
233 if (unassignedCount > 0) {
234 alert(`Please sort all ${unassignedCount} unassigned value(s) before proceeding to Part 2.`);
235 return;
236 }
237 newState.currentPart = 'part2';
238 newState.cards = newState.cards
239 .filter((card) => card.column === 'veryImportant')
240 .map((card) => ({ ...card, column: 'unassigned' }));
241 this.updateState(newState); // Call updateState directly
242 });
243 document.getElementById('toPart3')?.addEventListener('click', () => {
244 const newState = this.undoManager.getState();
245 // Check for unassigned cards before proceeding
246 const unassignedCount = newState.cards.filter((card) => card.column === 'unassigned').length;
247 if (unassignedCount > 0) {
248 alert(`Please sort all ${unassignedCount} unassigned value(s) before proceeding.`);
249 return;
250 }
251 const veryImportantCards = newState.cards.filter((c) => c.column === 'veryImportant');
252 const veryImportantCount = veryImportantCards.length;
253
254 if (veryImportantCount <= 5) {
255 newState.currentPart = 'part4';
256 } else {
257 newState.currentPart = 'part3';
258 }
259 // Move all "veryImportant" cards to "core" regardless of the next part
260 newState.cards = veryImportantCards.map((c, idx) => ({ ...c, column: 'core', order: idx }));
261
262 this.updateState(newState); // Call updateState directly
263 });
264 document.getElementById('toPart4')?.addEventListener('click', () => {
265 const newState = this.undoManager.getState();
266 const coreCount = newState.cards.filter((c) => c.column === 'core').length;
267 if (coreCount > 5) {
268 alert("You can only have 5 core values! Please move some values to 'Also Something I Want' before continuing.");
269 return; // Don't update state if validation fails
270 }
271 newState.currentPart = 'part4';
272 this.updateState(newState); // Call updateState directly
273 });
274 document.getElementById('finish')?.addEventListener('click', () => {
275 const newState = this.undoManager.getState();
276 // Check if all core values have statements
277 const coreCards = newState.cards.filter((c) => c.column === 'core');
278 const missingStatements = coreCards.filter((card) => !newState.finalStatements[card.id]?.trim());
279
280 if (missingStatements.length > 0) {
281 alert(
282 `Please provide a statement for all core values. Missing: ${missingStatements.map((c) => c.name).join(', ')}`,
283 );
284 return; // Prevent transition
285 }
286
287 // Original transition logic
288 newState.currentPart = 'review';
289 this.updateState(newState); // Call updateState directly
290 });
291 document.getElementById('restart')?.addEventListener('click', () => {
292 const newState = this.defaultState();
293 this.updateState(newState); // Call updateState directly
294 });
295
296 // --- Add listeners for the toggle buttons ---
297 document.getElementById('useLimitedValuesBtn')?.addEventListener('click', () => {
298 // Use captured instance
299 const currentSet = this.undoManager.getState().valueSet;
300 if (currentSet !== 'limited') {
301 if (confirm('Switching value sets will reset your current progress. Are you sure?')) {
302 this.toggleValueSet();
303 }
304 }
305 });
306 document.getElementById('useAllValuesBtn')?.addEventListener('click', () => {
307 // Use captured instance
308 const currentSet = this.undoManager.getState().valueSet;
309 if (currentSet !== 'all') {
310 if (confirm('Switching value sets will reset your current progress. Are you sure?')) {
311 this.toggleValueSet();
312 }
313 }
314 });
315
316 // Undo/Redo buttons
317 document.getElementById('undoBtn')?.addEventListener('click', () => {
318 const prev = this.undoManager.undo();
319 if (prev) {
320 this.state = prev;
321 this.saveState();
322 this.render();
323 this.updateUndoRedoButtons();
324 }
325 });
326 document.getElementById('redoBtn')?.addEventListener('click', () => {
327 const next = this.undoManager.redo();
328 if (next) {
329 this.state = next;
330 this.saveState();
331 this.render();
332 this.updateUndoRedoButtons();
333 }
334 });
335
336 // Clear storage button (renamed to Restart exercise)
337 document.getElementById('clearStorageBtn')?.addEventListener('click', () => {
338 // Update confirmation message
339 if (
340 confirm(
341 'Are you sure you want to restart the exercise? All progress will be lost. This action cannot be undone.',
342 )
343 ) {
344 localStorage.removeItem(this.storageKey);
345 // Reset to the default state using the *current* value set preference
346 const newState = this.defaultState(this.state.valueSet);
347 this.updateState(newState);
348 }
349 });
350
351 // Set up drag and drop for card movement in all card-container elements.
352 const containers = document.querySelectorAll('.card-container');
353 containers.forEach((container) => {
354 container.addEventListener('dragover', (e) => {
355 e.preventDefault();
356 });
357 container.addEventListener('drop', (e) => {
358 e.preventDefault();
359 const dragEvent = e as DragEvent;
360 const cardId = Number(dragEvent.dataTransfer?.getData('text/plain'));
361 const targetColumn = container.parentElement!.getAttribute('data-column');
362 if (targetColumn) {
363 this.moveCard(cardId, targetColumn);
364 }
365 });
366 });
367 }
368
369 // Moves a card (by id) to a new column.
370 private moveCard(cardId: number, newColumn: string) {
371 const newState = this.undoManager.getState();
372 const card = newState.cards.find((c) => c.id === cardId);
373 if (card) {
374 // If in Part3 and moving to the 'core' column, enforce a maximum of 5 core cards.
375 if (newState.currentPart === 'part3' && newColumn === 'core') {
376 const coreCount = newState.cards.filter((c) => c.column === 'core').length;
377 if (coreCount >= 5 && card.column !== 'core') {
378 alert('You can only have 5 core values!');
379 return;
380 }
381 }
382 card.column = newColumn;
383 // Optionally, update order if needed (here we simply set order to current timestamp).
384 card.order = Date.now();
385 this.updateState(newState);
386 }
387 }
388
389 // Creates a draggable card element, handles description editing UI.
390 private createCardElement(card: ValueCard): HTMLElement {
391 const cardElem = document.createElement('div');
392 cardElem.className = 'card';
393 cardElem.draggable = true;
394 cardElem.dataset.cardId = card.id.toString();
395
396 const nameElem = document.createElement('span');
397 nameElem.className = 'card-name';
398 nameElem.textContent = card.name;
399 cardElem.appendChild(nameElem);
400
401 // Container for description/edit area
402 const descriptionContainer = document.createElement('div');
403 descriptionContainer.className = 'card-description-container';
404
405 if (this.state.editingDescriptionCardId === card.id) {
406 // RENDER EDIT TEXTAREA
407 const textarea = document.createElement('textarea');
408 textarea.className = 'card-description-edit';
409 textarea.value = card.description ?? valueDefinitionsMap.get(card.name) ?? '';
410 textarea.rows = 3;
411
412 textarea.addEventListener('blur', () => {
413 this.saveDescriptionEdit(card.id, textarea.value);
414 });
415
416 textarea.addEventListener('keydown', (e) => {
417 if (e.key === 'Enter' && !e.shiftKey) {
418 e.preventDefault(); // Prevent newline
419 this.saveDescriptionEdit(card.id, textarea.value);
420 } else if (e.key === 'Escape') {
421 this.cancelDescriptionEdit();
422 }
423 });
424
425 descriptionContainer.appendChild(textarea);
426 // Auto-focus the textarea after it's rendered
427 setTimeout(() => {
428 textarea.focus();
429 }, 0);
430 } else {
431 // RENDER DESCRIPTION TEXT (Clickable)
432 const descriptionElem = document.createElement('span');
433 descriptionElem.className = 'card-description clickable'; // Add clickable class
434 descriptionElem.textContent =
435 card.description ?? valueDefinitionsMap.get(card.name) ?? '(Click to add description)';
436 descriptionElem.title = 'Click to edit description'; // Tooltip
437
438 descriptionElem.addEventListener('click', (e) => {
439 e.stopPropagation(); // Prevent card drag start if clicking text
440 this.startEditingDescription(card.id);
441 });
442 descriptionContainer.appendChild(descriptionElem);
443 }
444
445 cardElem.appendChild(descriptionContainer);
446
447 cardElem.addEventListener('dragstart', (e) => {
448 // Only allow drag if not editing description
449 if (this.state.editingDescriptionCardId === null) {
450 e.dataTransfer?.setData('text/plain', card.id.toString());
451 } else {
452 e.preventDefault(); // Prevent drag while editing
453 }
454 });
455
456 return cardElem;
457 }
458
459 // Render the UI based on the current state.
460 private render() {
461 // Manage tabindex for global controls based on current part
462 const isPart4Active = this.state.currentPart === 'part4';
463 const globalControls = document.querySelectorAll('#global-controls button');
464 globalControls.forEach((btn) => {
465 if (isPart4Active) {
466 (btn as HTMLElement).tabIndex = -1; // Make header buttons non-tabbable during Part 4
467 } else {
468 (btn as HTMLElement).removeAttribute('tabindex'); // Restore default tabbability
469 }
470 });
471
472 // Hide all parts first.
473 document.querySelectorAll('.exercise-part').forEach((section) => {
474 (section as HTMLElement).style.display = 'none';
475 });
476 // Show the current part.
477 const partElem = document.getElementById(this.state.currentPart);
478 if (partElem) {
479 partElem.style.display = 'block';
480 }
481 // Render cards for the current part.
482 if (this.state.currentPart === 'part1') {
483 // Clear containers for Part 1
484 [
485 'part1-unassignedContainer',
486 'part1-veryImportantContainer',
487 'part1-importantContainer',
488 'part1-notImportantContainer',
489 ].forEach((id) => {
490 const container = document.getElementById(id);
491 if (container) container.innerHTML = '';
492 });
493 // Render each card into its Part 1 container.
494 this.state.cards.forEach((card) => {
495 const containerId = 'part1-' + card.column + 'Container'; // Use Part 1 prefix
496 const container = document.getElementById(containerId);
497 if (container) {
498 const cardElem = this.createCardElement(card); // Use helper
499 container.appendChild(cardElem);
500 }
501 });
502 } else if (this.state.currentPart === 'part2') {
503 // Clear containers for Part 2
504 [
505 'part2-unassignedContainer',
506 'part2-veryImportantContainer',
507 'part2-importantContainer',
508 'part2-notImportantContainer',
509 ].forEach((id) => {
510 const container = document.getElementById(id);
511 if (container) container.innerHTML = '';
512 });
513
514 // Log the state for debugging (can be removed later)
515 // console.log("Rendering Part 2:", {
516 // totalCards: this.state.cards.length,
517 // cards: this.state.cards.map(c => ({ name: c.name, column: c.column }))
518 // });
519
520 // In Part 2, show all cards in their current Part 2 columns
521 this.state.cards.forEach((card) => {
522 const containerId = 'part2-' + card.column + 'Container'; // Use Part 2 prefix
523 const container = document.getElementById(containerId);
524 if (container) {
525 const cardElem = this.createCardElement(card); // Use helper
526 container.appendChild(cardElem);
527 } else {
528 // Log error if container not found (can be removed later)
529 // console.error(`Container not found for card ${card.name} in column ${card.column}`);
530 }
531 });
532 } else if (this.state.currentPart === 'part3') {
533 // Clear containers for part3
534 ['coreContainer', 'additionalContainer'].forEach((id) => {
535 const container = document.getElementById(id);
536 if (container) container.innerHTML = '';
537 });
538 this.state.cards.forEach((card) => {
539 if (card.column === 'core' || card.column === 'additional') {
540 const containerId = card.column + 'Container';
541 const container = document.getElementById(containerId);
542 if (container) {
543 const cardElem = this.createCardElement(card); // Use helper
544 container.appendChild(cardElem);
545 }
546 }
547 });
548 } else if (this.state.currentPart === 'part4') {
549 // Render text inputs for each core value.
550 const finalStatementsContainer = document.getElementById('finalStatements');
551 if (finalStatementsContainer) {
552 const coreCards = this.state.cards.filter((c) => c.column === 'core');
553 coreCards.sort((a, b) => a.name.localeCompare(b.name)); // Keep sorting for consistent order
554
555 // Check if inputs matching core cards already exist
556 const existingInputs = finalStatementsContainer.querySelectorAll<HTMLInputElement>('input[type="text"]');
557 let inputsMatch = existingInputs.length === coreCards.length;
558 if (inputsMatch) {
559 existingInputs.forEach((input, index) => {
560 // Verify the input corresponds to the correct card (using id or other attribute if needed)
561 // For simplicity, we assume order matches due to sorting if length is correct
562 if (coreCards[index]?.id.toString() !== input.id.replace('statement-', '')) {
563 inputsMatch = false;
564 }
565 });
566 }
567
568 if (inputsMatch) {
569 // Inputs exist and match: Just update their values
570 existingInputs.forEach((input) => {
571 const cardId = Number(input.id.replace('statement-', ''));
572 const currentValue = this.state.finalStatements[cardId] ?? '';
573 if (input.value !== currentValue) {
574 input.value = currentValue;
575 }
576 });
577 } else {
578 // Inputs don't exist or don't match: Clear and recreate them
579 finalStatementsContainer.innerHTML = '';
580 coreCards.forEach((card) => {
581 const wrapper = document.createElement('div');
582 wrapper.className = 'final-statement';
583 const label = document.createElement('label');
584 label.htmlFor = `statement-${card.id}`;
585 label.textContent = `Describe what "${card.name}" means to you:`;
586 const input = document.createElement('input');
587 input.type = 'text';
588 input.id = `statement-${card.id}`;
589 input.value = this.state.finalStatements[card.id] ?? '';
590 // Use 'blur' event to update state directly when field loses focus
591 input.addEventListener('blur', () => {
592 const currentState = this.undoManager.getState(); // Get latest state
593 // Avoid modifying the state if it hasn't actually changed
594 if (currentState.finalStatements[card.id] !== input.value) {
595 const newState = this.undoManager.getState(); // Get a fresh copy to modify
596 newState.finalStatements[card.id] = input.value;
597 this.updateState(newState); // Update authoritative state
598 }
599 });
600 wrapper.appendChild(label);
601 wrapper.appendChild(input);
602 finalStatementsContainer.appendChild(wrapper);
603 });
604 }
605 }
606 }
607 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
608 else if (this.state.currentPart === 'review') {
609 const reviewContent = document.getElementById('reviewContent');
610 if (reviewContent) {
611 reviewContent.innerHTML = '';
612
613 // Add the visualization grid
614 const grid = document.createElement('div');
615 grid.className = 'values-grid';
616
617 // Create sections for core values and additional values
618 const categories = [
619 { title: 'Core Values (F*CK YEAH)', column: 'core' },
620 { title: 'Also Something I Want', column: 'additional' },
621 ];
622
623 categories.forEach((category) => {
624 const section = document.createElement('div');
625 section.className = 'grid-section';
626 const title = document.createElement('h3');
627 title.textContent = category.title;
628 section.appendChild(title);
629
630 // Get the actual card objects, not just names
631 const cardsInCategory = this.state.cards.filter((c) => c.column === category.column);
632 // Sort them alphabetically by name for consistent display
633 cardsInCategory.sort((a, b) => a.name.localeCompare(b.name));
634
635 if (cardsInCategory.length > 0) {
636 const list = document.createElement('ul');
637 // Iterate over the card objects
638 cardsInCategory.forEach((card) => {
639 const li = document.createElement('li');
640 const nameSpan = document.createElement('span');
641 nameSpan.className = 'review-value-name';
642 nameSpan.textContent = card.name; // Use name from card object
643
644 const descSpan = document.createElement('span');
645 descSpan.className = 'review-value-description';
646 // Prioritize card.description, fall back to map
647 descSpan.textContent = card.description ?? valueDefinitionsMap.get(card.name) ?? '(Description missing)';
648
649 li.appendChild(nameSpan);
650 li.appendChild(descSpan);
651 list.appendChild(li);
652 });
653 section.appendChild(list);
654 }
655
656 grid.appendChild(section);
657 });
658
659 reviewContent.appendChild(grid);
660
661 // Add the final statements
662 const title = document.createElement('h3');
663 title.textContent = 'Your Core Values & Statements:';
664 reviewContent.appendChild(title);
665 const list = document.createElement('ul');
666 const coreCards = this.state.cards.filter((c) => c.column === 'core');
667 coreCards.forEach((card) => {
668 const li = document.createElement('li');
669 const statement = this.state.finalStatements[card.id] ?? '(No statement written)';
670 li.textContent = `${statement} (${card.name})`;
671 list.appendChild(li);
672 });
673 reviewContent.appendChild(list);
674 }
675 }
676
677 // -- Update toggle button appearance based on current state --
678 const limitedBtn = document.getElementById('useLimitedValuesBtn') as HTMLButtonElement | null;
679 const allBtn = document.getElementById('useAllValuesBtn') as HTMLButtonElement | null;
680 if (limitedBtn) {
681 limitedBtn.classList.toggle('active', this.state.valueSet === 'limited');
682 limitedBtn.disabled = this.state.valueSet === 'limited'; // Disable active button
683 }
684 if (allBtn) {
685 allBtn.classList.toggle('active', this.state.valueSet === 'all');
686 allBtn.disabled = this.state.valueSet === 'all'; // Disable active button
687 }
688 }
689
690 // Update the disabled state of the undo/redo buttons.
691 private updateUndoRedoButtons() {
692 const undoBtn = document.getElementById('undoBtn') as HTMLButtonElement;
693 const redoBtn = document.getElementById('redoBtn') as HTMLButtonElement;
694 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
695 if (undoBtn) undoBtn.disabled = !this.undoManager.canUndo();
696 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
697 if (redoBtn) redoBtn.disabled = !this.undoManager.canRedo();
698 }
699}
700
701// Initialize the app normally, only if in a browser environment
702if (typeof window !== 'undefined' && typeof document !== 'undefined') {
703 window.addEventListener('DOMContentLoaded', () => {
704 new App();
705 });
706}