@alepot55/chessboardjs 2.2.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +227 -0
- package/.github/instructions/copilot-instuctions.md +1671 -0
- package/README.md +125 -401
- package/assets/themes/alepot/theme.json +42 -0
- package/assets/themes/default/theme.json +42 -0
- package/chessboard.bundle.js +708 -58
- package/config/jest.config.js +15 -0
- package/config/rollup.config.js +35 -0
- package/dist/chessboard.cjs.js +10476 -0
- package/dist/chessboard.css +197 -0
- package/dist/chessboard.esm.js +10407 -0
- package/dist/chessboard.iife.js +10481 -0
- package/dist/chessboard.umd.js +10482 -0
- package/jest.config.js +2 -7
- package/package.json +18 -3
- package/rollup.config.js +2 -11
- package/{chessboard.move.js → src/components/Move.js} +3 -3
- package/src/components/Piece.js +273 -0
- package/{chessboard.square.js → src/components/Square.js} +60 -7
- package/src/constants/index.js +15 -0
- package/src/constants/positions.js +62 -0
- package/src/core/Chessboard.js +1930 -0
- package/src/core/ChessboardConfig.js +458 -0
- package/src/core/ChessboardFactory.js +385 -0
- package/src/core/index.js +141 -0
- package/src/errors/ChessboardError.js +133 -0
- package/src/errors/index.js +15 -0
- package/src/errors/messages.js +189 -0
- package/src/index.js +103 -0
- package/src/services/AnimationService.js +180 -0
- package/src/services/BoardService.js +156 -0
- package/src/services/CoordinateService.js +355 -0
- package/src/services/EventService.js +807 -0
- package/src/services/MoveService.js +594 -0
- package/src/services/PieceService.js +303 -0
- package/src/services/PositionService.js +237 -0
- package/src/services/ValidationService.js +673 -0
- package/src/services/index.js +14 -0
- package/src/styles/animations.css +46 -0
- package/{chessboard.css → src/styles/board.css} +3 -0
- package/src/styles/index.css +4 -0
- package/src/styles/pieces.css +66 -0
- package/src/utils/animations.js +37 -0
- package/{chess.js → src/utils/chess.js} +16 -16
- package/src/utils/coordinates.js +62 -0
- package/src/utils/cross-browser.js +150 -0
- package/src/utils/logger.js +422 -0
- package/src/utils/performance.js +311 -0
- package/src/utils/validation.js +458 -0
- package/tests/unit/chessboard-config-animations.test.js +106 -0
- package/tests/unit/chessboard-robust.test.js +163 -0
- package/tests/unit/chessboard.test.js +183 -0
- package/chessboard.config.js +0 -147
- package/chessboard.js +0 -979
- package/chessboard.piece.js +0 -115
- package/test/chessboard.test.js +0 -128
- /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
- /package/{.babelrc → config/.babelrc} +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error messages and error handling utilities
|
|
3
|
+
* @module errors/messages
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Error codes for categorizing different types of errors
|
|
9
|
+
* @type {Object.<string, string>}
|
|
10
|
+
* @readonly
|
|
11
|
+
*/
|
|
12
|
+
export const ERROR_CODES = Object.freeze({
|
|
13
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
14
|
+
CONFIG_ERROR: 'CONFIG_ERROR',
|
|
15
|
+
MOVE_ERROR: 'MOVE_ERROR',
|
|
16
|
+
DOM_ERROR: 'DOM_ERROR',
|
|
17
|
+
ANIMATION_ERROR: 'ANIMATION_ERROR',
|
|
18
|
+
PIECE_ERROR: 'PIECE_ERROR',
|
|
19
|
+
INITIALIZATION_ERROR: 'INITIALIZATION_ERROR',
|
|
20
|
+
POSITION_ERROR: 'POSITION_ERROR',
|
|
21
|
+
FEN_ERROR: 'FEN_ERROR',
|
|
22
|
+
SQUARE_ERROR: 'SQUARE_ERROR',
|
|
23
|
+
THEME_ERROR: 'THEME_ERROR',
|
|
24
|
+
NETWORK_ERROR: 'NETWORK_ERROR'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Standardized error messages for the chessboard application
|
|
29
|
+
* @type {Object.<string, string>}
|
|
30
|
+
* @readonly
|
|
31
|
+
*/
|
|
32
|
+
export const ERROR_MESSAGES = Object.freeze({
|
|
33
|
+
// Position and FEN related
|
|
34
|
+
invalid_position: 'Invalid position: ',
|
|
35
|
+
invalid_fen: 'Invalid FEN string: ',
|
|
36
|
+
position_load_failed: 'Failed to load position: ',
|
|
37
|
+
|
|
38
|
+
// DOM and UI related
|
|
39
|
+
invalid_id_div: 'Board container not found: ',
|
|
40
|
+
invalid_value: 'Invalid value: ',
|
|
41
|
+
dom_operation_failed: 'DOM operation failed: ',
|
|
42
|
+
element_not_found: 'Element not found: ',
|
|
43
|
+
|
|
44
|
+
// Piece related
|
|
45
|
+
invalid_piece: 'Invalid piece notation: ',
|
|
46
|
+
invalid_square: 'Invalid square notation: ',
|
|
47
|
+
invalid_piecesPath: 'Invalid pieces path: ',
|
|
48
|
+
piece_operation_failed: 'Piece operation failed: ',
|
|
49
|
+
|
|
50
|
+
// Board configuration
|
|
51
|
+
invalid_orientation: 'Invalid orientation: ',
|
|
52
|
+
invalid_color: 'Invalid color: ',
|
|
53
|
+
invalid_mode: 'Invalid mode: ',
|
|
54
|
+
invalid_dropOffBoard: 'Invalid dropOffBoard setting: ',
|
|
55
|
+
invalid_size: 'Invalid size: ',
|
|
56
|
+
invalid_movableColors: 'Invalid movableColors setting: ',
|
|
57
|
+
|
|
58
|
+
// Animation related
|
|
59
|
+
invalid_snapbackTime: 'Invalid snapbackTime: ',
|
|
60
|
+
invalid_snapbackAnimation: 'Invalid snapbackAnimation: ',
|
|
61
|
+
invalid_fadeTime: 'Invalid fadeTime: ',
|
|
62
|
+
invalid_fadeAnimation: 'Invalid fadeAnimation: ',
|
|
63
|
+
invalid_ratio: 'Invalid ratio: ',
|
|
64
|
+
invalid_animationStyle: 'Invalid animationStyle: ',
|
|
65
|
+
animation_failed: 'Animation failed: ',
|
|
66
|
+
|
|
67
|
+
// Event handlers
|
|
68
|
+
invalid_onMove: 'Invalid onMove callback: ',
|
|
69
|
+
invalid_onMoveEnd: 'Invalid onMoveEnd callback: ',
|
|
70
|
+
invalid_onChange: 'Invalid onChange callback: ',
|
|
71
|
+
invalid_onDragStart: 'Invalid onDragStart callback: ',
|
|
72
|
+
invalid_onDragMove: 'Invalid onDragMove callback: ',
|
|
73
|
+
invalid_onDrop: 'Invalid onDrop callback: ',
|
|
74
|
+
invalid_onSnapbackEnd: 'Invalid onSnapbackEnd callback: ',
|
|
75
|
+
callback_execution_failed: 'Callback execution failed: ',
|
|
76
|
+
|
|
77
|
+
// Visual styling
|
|
78
|
+
invalid_whiteSquare: 'Invalid whiteSquare color: ',
|
|
79
|
+
invalid_blackSquare: 'Invalid blackSquare color: ',
|
|
80
|
+
invalid_highlight: 'Invalid highlight color: ',
|
|
81
|
+
invalid_selectedSquareWhite: 'Invalid selectedSquareWhite color: ',
|
|
82
|
+
invalid_selectedSquareBlack: 'Invalid selectedSquareBlack color: ',
|
|
83
|
+
invalid_movedSquareWhite: 'Invalid movedSquareWhite color: ',
|
|
84
|
+
invalid_movedSquareBlack: 'Invalid movedSquareBlack color: ',
|
|
85
|
+
invalid_choiceSquare: 'Invalid choiceSquare color: ',
|
|
86
|
+
invalid_coverSquare: 'Invalid coverSquare color: ',
|
|
87
|
+
invalid_hintColor: 'Invalid hintColor: ',
|
|
88
|
+
|
|
89
|
+
// Move related
|
|
90
|
+
invalid_move: 'Invalid move: ',
|
|
91
|
+
invalid_move_format: 'Invalid move format: ',
|
|
92
|
+
move_execution_failed: 'Move execution failed: ',
|
|
93
|
+
illegal_move: 'Illegal move: ',
|
|
94
|
+
square_no_piece: 'No piece found on square: ',
|
|
95
|
+
move_validation_failed: 'Move validation failed: ',
|
|
96
|
+
|
|
97
|
+
// Game state
|
|
98
|
+
game_over: 'Game is over',
|
|
99
|
+
invalid_turn: 'Invalid turn: ',
|
|
100
|
+
position_validation_failed: 'Position validation failed: ',
|
|
101
|
+
|
|
102
|
+
// Theme and assets
|
|
103
|
+
theme_load_failed: 'Theme loading failed: ',
|
|
104
|
+
asset_load_failed: 'Asset loading failed: ',
|
|
105
|
+
invalid_theme: 'Invalid theme: ',
|
|
106
|
+
|
|
107
|
+
// Network and resources
|
|
108
|
+
network_error: 'Network error: ',
|
|
109
|
+
resource_not_found: 'Resource not found: ',
|
|
110
|
+
timeout_error: 'Operation timed out: ',
|
|
111
|
+
|
|
112
|
+
// General errors
|
|
113
|
+
initialization_failed: 'Initialization failed: ',
|
|
114
|
+
operation_failed: 'Operation failed: ',
|
|
115
|
+
invalid_state: 'Invalid state: ',
|
|
116
|
+
memory_limit_exceeded: 'Memory limit exceeded',
|
|
117
|
+
performance_degraded: 'Performance degraded: '
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Error severity levels
|
|
122
|
+
* @type {Object.<string, string>}
|
|
123
|
+
* @readonly
|
|
124
|
+
*/
|
|
125
|
+
export const ERROR_SEVERITY = Object.freeze({
|
|
126
|
+
LOW: 'LOW',
|
|
127
|
+
MEDIUM: 'MEDIUM',
|
|
128
|
+
HIGH: 'HIGH',
|
|
129
|
+
CRITICAL: 'CRITICAL'
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Maps error codes to their severity levels
|
|
134
|
+
* @type {Object.<string, string>}
|
|
135
|
+
* @readonly
|
|
136
|
+
*/
|
|
137
|
+
export const ERROR_SEVERITY_MAP = Object.freeze({
|
|
138
|
+
[ERROR_CODES.VALIDATION_ERROR]: ERROR_SEVERITY.MEDIUM,
|
|
139
|
+
[ERROR_CODES.CONFIG_ERROR]: ERROR_SEVERITY.HIGH,
|
|
140
|
+
[ERROR_CODES.MOVE_ERROR]: ERROR_SEVERITY.LOW,
|
|
141
|
+
[ERROR_CODES.DOM_ERROR]: ERROR_SEVERITY.HIGH,
|
|
142
|
+
[ERROR_CODES.ANIMATION_ERROR]: ERROR_SEVERITY.LOW,
|
|
143
|
+
[ERROR_CODES.PIECE_ERROR]: ERROR_SEVERITY.MEDIUM,
|
|
144
|
+
[ERROR_CODES.INITIALIZATION_ERROR]: ERROR_SEVERITY.CRITICAL,
|
|
145
|
+
[ERROR_CODES.POSITION_ERROR]: ERROR_SEVERITY.MEDIUM,
|
|
146
|
+
[ERROR_CODES.FEN_ERROR]: ERROR_SEVERITY.MEDIUM,
|
|
147
|
+
[ERROR_CODES.SQUARE_ERROR]: ERROR_SEVERITY.MEDIUM,
|
|
148
|
+
[ERROR_CODES.THEME_ERROR]: ERROR_SEVERITY.LOW,
|
|
149
|
+
[ERROR_CODES.NETWORK_ERROR]: ERROR_SEVERITY.MEDIUM
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Utility function to get error severity
|
|
154
|
+
* @param {string} errorCode - Error code
|
|
155
|
+
* @returns {string} Error severity level
|
|
156
|
+
*/
|
|
157
|
+
export function getErrorSeverity(errorCode) {
|
|
158
|
+
return ERROR_SEVERITY_MAP[errorCode] || ERROR_SEVERITY.MEDIUM;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Utility function to format error message
|
|
163
|
+
* @param {string} messageKey - Message key from ERROR_MESSAGES
|
|
164
|
+
* @param {string} [details] - Additional details
|
|
165
|
+
* @returns {string} Formatted error message
|
|
166
|
+
*/
|
|
167
|
+
export function formatErrorMessage(messageKey, details = '') {
|
|
168
|
+
const message = ERROR_MESSAGES[messageKey];
|
|
169
|
+
if (!message) {
|
|
170
|
+
return `Unknown error: ${messageKey}${details ? ` - ${details}` : ''}`;
|
|
171
|
+
}
|
|
172
|
+
return `${message}${details}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Utility function to create error context
|
|
177
|
+
* @param {string} operation - Operation that failed
|
|
178
|
+
* @param {Object} [data] - Additional context data
|
|
179
|
+
* @returns {Object} Error context object
|
|
180
|
+
*/
|
|
181
|
+
export function createErrorContext(operation, data = {}) {
|
|
182
|
+
return {
|
|
183
|
+
operation,
|
|
184
|
+
timestamp: new Date().toISOString(),
|
|
185
|
+
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Unknown',
|
|
186
|
+
url: typeof window !== 'undefined' ? window.location.href : 'Unknown',
|
|
187
|
+
...data
|
|
188
|
+
};
|
|
189
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chessboard.js - A beautiful, customizable chessboard widget
|
|
3
|
+
* Main entry point for the library
|
|
4
|
+
*
|
|
5
|
+
* @version 2.2.1
|
|
6
|
+
* @author alepot55
|
|
7
|
+
* @license ISC
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Core components
|
|
11
|
+
export {
|
|
12
|
+
Chessboard,
|
|
13
|
+
ChessboardConfig,
|
|
14
|
+
ChessboardFactory,
|
|
15
|
+
chessboardFactory,
|
|
16
|
+
createChessboard,
|
|
17
|
+
createChessboardFromTemplate
|
|
18
|
+
} from './core/index.js';
|
|
19
|
+
|
|
20
|
+
// Component exports
|
|
21
|
+
export { default as Square } from './components/Square.js';
|
|
22
|
+
export { default as Piece } from './components/Piece.js';
|
|
23
|
+
export { default as Move } from './components/Move.js';
|
|
24
|
+
|
|
25
|
+
// Service exports for advanced usage
|
|
26
|
+
export {
|
|
27
|
+
AnimationService,
|
|
28
|
+
BoardService,
|
|
29
|
+
CoordinateService,
|
|
30
|
+
EventService,
|
|
31
|
+
MoveService,
|
|
32
|
+
PieceService,
|
|
33
|
+
PositionService,
|
|
34
|
+
ValidationService
|
|
35
|
+
} from './services/index.js';
|
|
36
|
+
|
|
37
|
+
// Constants exports
|
|
38
|
+
export * from './constants/index.js';
|
|
39
|
+
|
|
40
|
+
// Error handling exports
|
|
41
|
+
export {
|
|
42
|
+
ChessboardError,
|
|
43
|
+
ValidationError,
|
|
44
|
+
ConfigurationError,
|
|
45
|
+
MoveError,
|
|
46
|
+
DOMError,
|
|
47
|
+
PieceError
|
|
48
|
+
} from './errors/index.js';
|
|
49
|
+
|
|
50
|
+
// Utility exports
|
|
51
|
+
export { Chess, validateFen } from './utils/chess.js';
|
|
52
|
+
export {
|
|
53
|
+
PerformanceMonitor,
|
|
54
|
+
throttle,
|
|
55
|
+
debounce,
|
|
56
|
+
rafThrottle,
|
|
57
|
+
setTransform,
|
|
58
|
+
resetTransform,
|
|
59
|
+
batchDOMOperations,
|
|
60
|
+
isElementVisible,
|
|
61
|
+
getMemoryUsage
|
|
62
|
+
} from './utils/performance.js';
|
|
63
|
+
export * from './utils/coordinates.js';
|
|
64
|
+
export {
|
|
65
|
+
isValidPiece,
|
|
66
|
+
isValidPosition,
|
|
67
|
+
validateFenFormat,
|
|
68
|
+
validateMove,
|
|
69
|
+
validateConfig,
|
|
70
|
+
batchValidate,
|
|
71
|
+
clearValidationCache,
|
|
72
|
+
getValidationCacheStats
|
|
73
|
+
} from './utils/validation.js';
|
|
74
|
+
export * from './utils/animations.js';
|
|
75
|
+
|
|
76
|
+
// Logging system exports
|
|
77
|
+
export {
|
|
78
|
+
Logger,
|
|
79
|
+
logger,
|
|
80
|
+
createLogger,
|
|
81
|
+
LOG_LEVELS
|
|
82
|
+
} from './utils/logger.js';
|
|
83
|
+
|
|
84
|
+
// Default export for IIFE compatibility
|
|
85
|
+
import { Chessboard as ChessboardClass } from './core/index.js';
|
|
86
|
+
export default ChessboardClass;
|
|
87
|
+
|
|
88
|
+
// Version information
|
|
89
|
+
export const version = '2.2.1';
|
|
90
|
+
export const build = process.env.BUILD_NUMBER || 'dev';
|
|
91
|
+
export const buildDate = process.env.BUILD_DATE || new Date().toISOString();
|
|
92
|
+
|
|
93
|
+
// Library information
|
|
94
|
+
export const info = {
|
|
95
|
+
name: 'Chessboard.js',
|
|
96
|
+
version,
|
|
97
|
+
build,
|
|
98
|
+
buildDate,
|
|
99
|
+
author: 'alepot55',
|
|
100
|
+
license: 'ISC',
|
|
101
|
+
repository: 'https://github.com/alepot55/Chessboardjs',
|
|
102
|
+
homepage: 'https://chessboardjs.com'
|
|
103
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for managing animations and transitions
|
|
3
|
+
* @module services/AnimationService
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Service responsible for coordinating animations and transitions
|
|
9
|
+
* @class
|
|
10
|
+
*/
|
|
11
|
+
export class AnimationService {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new AnimationService instance
|
|
14
|
+
* @param {ChessboardConfig} config - Board configuration
|
|
15
|
+
*/
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.activeAnimations = new Map();
|
|
19
|
+
this.animationId = 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a timing function for animations
|
|
24
|
+
* @param {string} [type='ease'] - Animation type
|
|
25
|
+
* @returns {Function} Timing function
|
|
26
|
+
*/
|
|
27
|
+
createTimingFunction(type = 'ease') {
|
|
28
|
+
return (elapsed, duration) => {
|
|
29
|
+
const x = elapsed / duration;
|
|
30
|
+
|
|
31
|
+
switch (type) {
|
|
32
|
+
case 'linear':
|
|
33
|
+
return x;
|
|
34
|
+
case 'ease':
|
|
35
|
+
return (x ** 2) * (3 - 2 * x);
|
|
36
|
+
case 'ease-in':
|
|
37
|
+
return x ** 2;
|
|
38
|
+
case 'ease-out':
|
|
39
|
+
return -1 * (x - 1) ** 2 + 1;
|
|
40
|
+
case 'ease-in-out':
|
|
41
|
+
return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
|
|
42
|
+
default:
|
|
43
|
+
return x;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Animates an element with given properties
|
|
50
|
+
* @param {HTMLElement} element - Element to animate
|
|
51
|
+
* @param {Object} properties - Properties to animate
|
|
52
|
+
* @param {number} duration - Animation duration in milliseconds
|
|
53
|
+
* @param {string} [easing='ease'] - Easing function
|
|
54
|
+
* @param {Function} [callback] - Callback when animation completes
|
|
55
|
+
* @returns {number} Animation ID
|
|
56
|
+
*/
|
|
57
|
+
animate(element, properties, duration, easing = 'ease', callback) {
|
|
58
|
+
const animationId = ++this.animationId;
|
|
59
|
+
const timingFunction = this.createTimingFunction(easing);
|
|
60
|
+
const startTime = performance.now();
|
|
61
|
+
const startValues = {};
|
|
62
|
+
|
|
63
|
+
// Store initial values
|
|
64
|
+
Object.keys(properties).forEach(prop => {
|
|
65
|
+
startValues[prop] = this._getInitialValue(element, prop);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const animate = (currentTime) => {
|
|
69
|
+
const elapsed = currentTime - startTime;
|
|
70
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
71
|
+
const easedProgress = timingFunction(elapsed, duration);
|
|
72
|
+
|
|
73
|
+
// Apply animated values
|
|
74
|
+
Object.keys(properties).forEach(prop => {
|
|
75
|
+
const startValue = startValues[prop];
|
|
76
|
+
const endValue = properties[prop];
|
|
77
|
+
const currentValue = this._interpolateValue(startValue, endValue, easedProgress);
|
|
78
|
+
this._applyValue(element, prop, currentValue);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (progress < 1 && this.activeAnimations.has(animationId)) {
|
|
82
|
+
requestAnimationFrame(animate);
|
|
83
|
+
} else {
|
|
84
|
+
this.activeAnimations.delete(animationId);
|
|
85
|
+
if (callback) callback();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.activeAnimations.set(animationId, { element, animate, callback });
|
|
90
|
+
requestAnimationFrame(animate);
|
|
91
|
+
|
|
92
|
+
return animationId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Cancels an animation
|
|
97
|
+
* @param {number} animationId - Animation ID to cancel
|
|
98
|
+
*/
|
|
99
|
+
cancel(animationId) {
|
|
100
|
+
if (this.activeAnimations.has(animationId)) {
|
|
101
|
+
this.activeAnimations.delete(animationId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Cancels all animations
|
|
107
|
+
*/
|
|
108
|
+
cancelAll() {
|
|
109
|
+
this.activeAnimations.clear();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gets initial value for a property
|
|
114
|
+
* @private
|
|
115
|
+
* @param {HTMLElement} element - Element
|
|
116
|
+
* @param {string} property - Property name
|
|
117
|
+
* @returns {number} Initial value
|
|
118
|
+
*/
|
|
119
|
+
_getInitialValue(element, property) {
|
|
120
|
+
if (!element || !element.style) return 0;
|
|
121
|
+
switch (property) {
|
|
122
|
+
case 'opacity':
|
|
123
|
+
return parseFloat(getComputedStyle(element).opacity) || 1;
|
|
124
|
+
case 'left':
|
|
125
|
+
return parseFloat(element.style.left) || 0;
|
|
126
|
+
case 'top':
|
|
127
|
+
return parseFloat(element.style.top) || 0;
|
|
128
|
+
case 'scale':
|
|
129
|
+
return 1;
|
|
130
|
+
default:
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Interpolates between two values
|
|
137
|
+
* @private
|
|
138
|
+
* @param {number} start - Start value
|
|
139
|
+
* @param {number} end - End value
|
|
140
|
+
* @param {number} progress - Progress (0-1)
|
|
141
|
+
* @returns {number} Interpolated value
|
|
142
|
+
*/
|
|
143
|
+
_interpolateValue(start, end, progress) {
|
|
144
|
+
return start + (end - start) * progress;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Applies animated value to element
|
|
149
|
+
* @private
|
|
150
|
+
* @param {HTMLElement} element - Element
|
|
151
|
+
* @param {string} property - Property name
|
|
152
|
+
* @param {number} value - Value to apply
|
|
153
|
+
*/
|
|
154
|
+
_applyValue(element, property, value) {
|
|
155
|
+
if (!element || !element.style) return;
|
|
156
|
+
switch (property) {
|
|
157
|
+
case 'opacity':
|
|
158
|
+
element.style.opacity = value;
|
|
159
|
+
break;
|
|
160
|
+
case 'left':
|
|
161
|
+
element.style.left = value + 'px';
|
|
162
|
+
break;
|
|
163
|
+
case 'top':
|
|
164
|
+
element.style.top = value + 'px';
|
|
165
|
+
break;
|
|
166
|
+
case 'scale':
|
|
167
|
+
element.style.transform = `scale(${value})`;
|
|
168
|
+
break;
|
|
169
|
+
default:
|
|
170
|
+
element.style[property] = value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Cleans up resources
|
|
176
|
+
*/
|
|
177
|
+
destroy() {
|
|
178
|
+
this.cancelAll();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for managing board setup and DOM operations
|
|
3
|
+
* @module services/BoardService
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BOARD_SIZE } from '../constants/positions.js';
|
|
8
|
+
import { DOMError, ValidationError } from '../errors/ChessboardError.js';
|
|
9
|
+
import { ERROR_MESSAGES } from '../errors/messages.js';
|
|
10
|
+
import Square from '../components/Square.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Service responsible for board DOM manipulation and setup
|
|
14
|
+
* @class
|
|
15
|
+
*/
|
|
16
|
+
export class BoardService {
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new BoardService instance
|
|
19
|
+
* @param {ChessboardConfig} config - Board configuration
|
|
20
|
+
*/
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.element = null;
|
|
24
|
+
this.squares = {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds the board DOM element and attaches it to the configured container
|
|
29
|
+
* @throws {DOMError} When the container element cannot be found
|
|
30
|
+
*/
|
|
31
|
+
buildBoard() {
|
|
32
|
+
console.log('BoardService.buildBoard: Looking for element with ID:', this.config.id_div);
|
|
33
|
+
|
|
34
|
+
this.element = document.getElementById(this.config.id_div);
|
|
35
|
+
if (!this.element) {
|
|
36
|
+
throw new DOMError(ERROR_MESSAGES.invalid_id_div + this.config.id_div, this.config.id_div);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.resize(this.config.size);
|
|
40
|
+
this.element.className = "board";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates all 64 squares and adds them to the board
|
|
45
|
+
* @param {Function} coordConverter - Function to convert row/col to real coordinates
|
|
46
|
+
*/
|
|
47
|
+
buildSquares(coordConverter) {
|
|
48
|
+
for (let row = 0; row < BOARD_SIZE.ROWS; row++) {
|
|
49
|
+
for (let col = 0; col < BOARD_SIZE.COLS; col++) {
|
|
50
|
+
const [squareRow, squareCol] = coordConverter(row, col);
|
|
51
|
+
const square = new Square(squareRow, squareCol);
|
|
52
|
+
|
|
53
|
+
this.squares[square.getId()] = square;
|
|
54
|
+
this.element.appendChild(square.element);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Removes all squares from the board and cleans up their resources
|
|
61
|
+
* Best practice: always destroy JS objects and DOM nodes, and clear references.
|
|
62
|
+
*/
|
|
63
|
+
removeSquares() {
|
|
64
|
+
for (const square of Object.values(this.squares)) {
|
|
65
|
+
// Always call destroy to remove DOM and clear piece reference
|
|
66
|
+
square.destroy();
|
|
67
|
+
}
|
|
68
|
+
this.squares = {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Removes all content from the board element
|
|
73
|
+
* Best practice: clear DOM and force element to be re-fetched on next build.
|
|
74
|
+
*/
|
|
75
|
+
removeBoard() {
|
|
76
|
+
if (this.element) {
|
|
77
|
+
this.element.innerHTML = '';
|
|
78
|
+
this.element = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resizes the board to the specified size
|
|
84
|
+
* @param {number|string} value - Size in pixels or 'auto'
|
|
85
|
+
* @throws {ValidationError} When size value is invalid
|
|
86
|
+
*/
|
|
87
|
+
resize(value) {
|
|
88
|
+
if (value === 'auto') {
|
|
89
|
+
const size = this._calculateAutoSize();
|
|
90
|
+
this.resize(size);
|
|
91
|
+
} else if (typeof value !== 'number') {
|
|
92
|
+
throw new ValidationError(ERROR_MESSAGES.invalid_value + value, 'size', value);
|
|
93
|
+
} else {
|
|
94
|
+
document.documentElement.style.setProperty('--dimBoard', value + 'px');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Calculates the optimal size when 'auto' is specified
|
|
100
|
+
* @private
|
|
101
|
+
* @returns {number} Calculated size in pixels
|
|
102
|
+
*/
|
|
103
|
+
_calculateAutoSize() {
|
|
104
|
+
if (!this.element) return 400; // Default fallback
|
|
105
|
+
|
|
106
|
+
const { offsetWidth, offsetHeight } = this.element;
|
|
107
|
+
|
|
108
|
+
if (offsetWidth === 0) {
|
|
109
|
+
return offsetHeight || 400;
|
|
110
|
+
} else if (offsetHeight === 0) {
|
|
111
|
+
return offsetWidth;
|
|
112
|
+
} else {
|
|
113
|
+
return Math.min(offsetWidth, offsetHeight);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Gets a square by its ID
|
|
119
|
+
* @param {string} squareId - Square identifier (e.g., 'e4')
|
|
120
|
+
* @returns {Square|null} The square or null if not found
|
|
121
|
+
*/
|
|
122
|
+
getSquare(squareId) {
|
|
123
|
+
return this.squares[squareId] || null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Gets all squares
|
|
128
|
+
* @returns {Object.<string, Square>} All squares indexed by ID
|
|
129
|
+
*/
|
|
130
|
+
getAllSquares() {
|
|
131
|
+
return { ...this.squares };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Applies a method to all squares
|
|
136
|
+
* @param {string} methodName - Name of the method to call on each square
|
|
137
|
+
* @param {...*} args - Arguments to pass to the method
|
|
138
|
+
*/
|
|
139
|
+
applyToAllSquares(methodName, ...args) {
|
|
140
|
+
for (const square of Object.values(this.squares)) {
|
|
141
|
+
if (typeof square[methodName] === 'function') {
|
|
142
|
+
square[methodName](...args);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Cleans up all resources
|
|
149
|
+
*/
|
|
150
|
+
destroy() {
|
|
151
|
+
this.removeSquares();
|
|
152
|
+
this.removeBoard();
|
|
153
|
+
this.element = null;
|
|
154
|
+
this.squares = {};
|
|
155
|
+
}
|
|
156
|
+
}
|