GameSentenceMiner 2.19.16__py3-none-any.whl → 2.20.0__py3-none-any.whl
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.
Potentially problematic release.
This version of GameSentenceMiner might be problematic. Click here for more details.
- GameSentenceMiner/__init__.py +39 -0
- GameSentenceMiner/anki.py +6 -3
- GameSentenceMiner/gametext.py +13 -2
- GameSentenceMiner/gsm.py +40 -3
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +4 -1
- GameSentenceMiner/owocr/owocr/ocr.py +304 -134
- GameSentenceMiner/owocr/owocr/run.py +1 -1
- GameSentenceMiner/ui/anki_confirmation.py +4 -2
- GameSentenceMiner/ui/config_gui.py +12 -0
- GameSentenceMiner/util/configuration.py +6 -2
- GameSentenceMiner/util/cron/__init__.py +12 -0
- GameSentenceMiner/util/cron/daily_rollup.py +613 -0
- GameSentenceMiner/util/cron/jiten_update.py +397 -0
- GameSentenceMiner/util/cron/populate_games.py +154 -0
- GameSentenceMiner/util/cron/run_crons.py +148 -0
- GameSentenceMiner/util/cron/setup_populate_games_cron.py +118 -0
- GameSentenceMiner/util/cron_table.py +334 -0
- GameSentenceMiner/util/db.py +236 -49
- GameSentenceMiner/util/ffmpeg.py +23 -4
- GameSentenceMiner/util/games_table.py +340 -93
- GameSentenceMiner/util/jiten_api_client.py +188 -0
- GameSentenceMiner/util/stats_rollup_table.py +216 -0
- GameSentenceMiner/web/anki_api_endpoints.py +438 -220
- GameSentenceMiner/web/database_api.py +955 -1259
- GameSentenceMiner/web/jiten_database_api.py +1015 -0
- GameSentenceMiner/web/rollup_stats.py +672 -0
- GameSentenceMiner/web/static/css/dashboard-shared.css +75 -13
- GameSentenceMiner/web/static/css/overview.css +604 -47
- GameSentenceMiner/web/static/css/search.css +226 -0
- GameSentenceMiner/web/static/css/shared.css +762 -0
- GameSentenceMiner/web/static/css/stats.css +221 -0
- GameSentenceMiner/web/static/js/components/bar-chart.js +339 -0
- GameSentenceMiner/web/static/js/database-bulk-operations.js +320 -0
- GameSentenceMiner/web/static/js/database-game-data.js +390 -0
- GameSentenceMiner/web/static/js/database-game-operations.js +213 -0
- GameSentenceMiner/web/static/js/database-helpers.js +44 -0
- GameSentenceMiner/web/static/js/database-jiten-integration.js +750 -0
- GameSentenceMiner/web/static/js/database-popups.js +89 -0
- GameSentenceMiner/web/static/js/database-tabs.js +64 -0
- GameSentenceMiner/web/static/js/database-text-management.js +371 -0
- GameSentenceMiner/web/static/js/database.js +86 -718
- GameSentenceMiner/web/static/js/goals.js +79 -18
- GameSentenceMiner/web/static/js/heatmap.js +29 -23
- GameSentenceMiner/web/static/js/overview.js +1205 -339
- GameSentenceMiner/web/static/js/regex-patterns.js +100 -0
- GameSentenceMiner/web/static/js/search.js +215 -18
- GameSentenceMiner/web/static/js/shared.js +193 -39
- GameSentenceMiner/web/static/js/stats.js +1536 -179
- GameSentenceMiner/web/stats.py +1142 -269
- GameSentenceMiner/web/stats_api.py +2104 -0
- GameSentenceMiner/web/templates/anki_stats.html +4 -18
- GameSentenceMiner/web/templates/components/date-range.html +118 -3
- GameSentenceMiner/web/templates/components/html-head.html +40 -6
- GameSentenceMiner/web/templates/components/js-config.html +8 -8
- GameSentenceMiner/web/templates/components/regex-input.html +160 -0
- GameSentenceMiner/web/templates/database.html +564 -117
- GameSentenceMiner/web/templates/goals.html +41 -5
- GameSentenceMiner/web/templates/overview.html +159 -129
- GameSentenceMiner/web/templates/search.html +78 -9
- GameSentenceMiner/web/templates/stats.html +159 -5
- GameSentenceMiner/web/texthooking_page.py +280 -111
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/METADATA +43 -2
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/RECORD +70 -47
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// Database Management JavaScript
|
|
2
|
-
// Dependencies:
|
|
1
|
+
// Database Management JavaScript - Main Entry Point
|
|
2
|
+
// Dependencies: All database modules must be loaded before this file
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight DatabaseManager class that orchestrates all database modules
|
|
6
|
+
*/
|
|
5
7
|
class DatabaseManager {
|
|
6
8
|
constructor() {
|
|
7
9
|
this.selectedGames = new Set();
|
|
@@ -9,28 +11,18 @@ class DatabaseManager {
|
|
|
9
11
|
this.initializePage();
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the database management page
|
|
16
|
+
*/
|
|
12
17
|
async initializePage() {
|
|
13
18
|
await this.loadDashboardStats();
|
|
14
19
|
this.attachEventHandlers();
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Attach event handlers for all database functionality
|
|
24
|
+
*/
|
|
17
25
|
attachEventHandlers() {
|
|
18
|
-
// Attach event handlers for buttons that were using onclick
|
|
19
|
-
const openGameDeletionBtn = document.querySelector('[data-action="openGameDeletionModal"]');
|
|
20
|
-
if (openGameDeletionBtn) {
|
|
21
|
-
openGameDeletionBtn.addEventListener('click', openGameDeletionModal);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const openTextLinesBtn = document.querySelector('[data-action="openTextLinesModal"]');
|
|
25
|
-
if (openTextLinesBtn) {
|
|
26
|
-
openTextLinesBtn.addEventListener('click', openTextLinesModal);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const openDeduplicationBtn = document.querySelector('[data-action="openDeduplicationModal"]');
|
|
30
|
-
if (openDeduplicationBtn) {
|
|
31
|
-
openDeduplicationBtn.addEventListener('click', openDeduplicationModal);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
// Modal close handlers
|
|
35
27
|
const closeButtons = document.querySelectorAll('[data-action="closeModal"]');
|
|
36
28
|
closeButtons.forEach(btn => {
|
|
@@ -40,743 +32,119 @@ class DatabaseManager {
|
|
|
40
32
|
}
|
|
41
33
|
});
|
|
42
34
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
selectAllBtn.addEventListener('click', selectAllGames);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const selectNoneBtn = document.querySelector('[data-action="selectNoGames"]');
|
|
50
|
-
if (selectNoneBtn) {
|
|
51
|
-
selectNoneBtn.addEventListener('click', selectNoGames);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const deleteSelectedBtn = document.querySelector('[data-action="deleteSelectedGames"]');
|
|
55
|
-
if (deleteSelectedBtn) {
|
|
56
|
-
deleteSelectedBtn.addEventListener('click', deleteSelectedGames);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const mergeSelectedBtn = document.querySelector('[data-action="mergeSelectedGames"]');
|
|
60
|
-
if (mergeSelectedBtn) {
|
|
61
|
-
mergeSelectedBtn.addEventListener('click', openGameMergeModal);
|
|
35
|
+
// Initialize all module event handlers
|
|
36
|
+
if (typeof initializeTabHandlers === 'function') {
|
|
37
|
+
initializeTabHandlers();
|
|
62
38
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
confirmMergeBtn.addEventListener('click', confirmGameMerge);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const presetPatternsSelect = document.getElementById('presetPatterns');
|
|
70
|
-
if (presetPatternsSelect) {
|
|
71
|
-
presetPatternsSelect.addEventListener('change', applyPresetPattern);
|
|
39
|
+
|
|
40
|
+
if (typeof initializeGameDataFilters === 'function') {
|
|
41
|
+
initializeGameDataFilters();
|
|
72
42
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
previewDeleteBtn.addEventListener('click', previewTextDeletion);
|
|
43
|
+
|
|
44
|
+
if (typeof initializeBulkOperations === 'function') {
|
|
45
|
+
initializeBulkOperations();
|
|
77
46
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
executeDeleteBtn.addEventListener('click', deleteTextLines);
|
|
47
|
+
|
|
48
|
+
if (typeof initializeTextManagement === 'function') {
|
|
49
|
+
initializeTextManagement();
|
|
82
50
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
scanDuplicatesBtn.addEventListener('click', scanForDuplicates);
|
|
51
|
+
|
|
52
|
+
if (typeof initializeJitenIntegration === 'function') {
|
|
53
|
+
initializeJitenIntegration();
|
|
87
54
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
removeDuplicatesBtn.addEventListener('click', removeDuplicates);
|
|
55
|
+
|
|
56
|
+
if (typeof initializeGameOperations === 'function') {
|
|
57
|
+
initializeGameOperations();
|
|
92
58
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (ignoreTimeWindowCheckbox) {
|
|
97
|
-
ignoreTimeWindowCheckbox.addEventListener('change', toggleTimeWindowVisibility);
|
|
59
|
+
|
|
60
|
+
if (typeof initializeDatabasePopups === 'function') {
|
|
61
|
+
initializeDatabasePopups();
|
|
98
62
|
}
|
|
99
63
|
}
|
|
100
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Load dashboard statistics
|
|
67
|
+
*/
|
|
101
68
|
async loadDashboardStats() {
|
|
102
69
|
try {
|
|
70
|
+
// Load general stats
|
|
103
71
|
const response = await fetch('/api/games-list');
|
|
104
72
|
const data = await response.json();
|
|
105
73
|
|
|
106
74
|
if (response.ok && data.games) {
|
|
107
75
|
const totalGames = data.games.length;
|
|
108
76
|
const totalSentences = data.games.reduce((sum, game) => sum + game.sentence_count, 0);
|
|
77
|
+
const totalCharacters = data.games.reduce((sum, game) => sum + game.total_characters, 0);
|
|
109
78
|
|
|
110
79
|
document.getElementById('totalGamesCount').textContent = totalGames.toLocaleString();
|
|
111
80
|
document.getElementById('totalSentencesCount').textContent = totalSentences.toLocaleString();
|
|
81
|
+
document.getElementById('totalCharactersCount').textContent = totalCharacters.toLocaleString();
|
|
112
82
|
}
|
|
83
|
+
|
|
84
|
+
// Load game management stats
|
|
85
|
+
await this.loadGameManagementStats();
|
|
113
86
|
} catch (error) {
|
|
114
87
|
console.error('Error loading dashboard stats:', error);
|
|
115
88
|
document.getElementById('totalGamesCount').textContent = 'Error';
|
|
116
89
|
document.getElementById('totalSentencesCount').textContent = 'Error';
|
|
117
90
|
}
|
|
118
91
|
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Games Management Functions
|
|
122
|
-
async function openGameDeletionModal() {
|
|
123
|
-
openModal('gamesDeletionModal');
|
|
124
|
-
await loadGamesForDeletion();
|
|
125
|
-
}
|
|
126
92
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const response = await fetch('/api/games-list');
|
|
137
|
-
const data = await response.json();
|
|
138
|
-
|
|
139
|
-
if (response.ok && data.games) {
|
|
140
|
-
gamesList.innerHTML = '';
|
|
93
|
+
/**
|
|
94
|
+
* Load game management statistics
|
|
95
|
+
*/
|
|
96
|
+
async loadGameManagementStats() {
|
|
97
|
+
try {
|
|
98
|
+
const gamesResponse = await fetch('/api/games-management');
|
|
99
|
+
const gamesData = await gamesResponse.json();
|
|
141
100
|
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
gameItem.innerHTML = `
|
|
146
|
-
<input type="checkbox" class="checkbox-input game-checkbox" data-game="${escapeHtml(game.name)}">
|
|
147
|
-
<label class="checkbox-label">
|
|
148
|
-
<strong>${escapeHtml(game.name)}</strong><br>
|
|
149
|
-
<small style="color: var(--text-tertiary);">
|
|
150
|
-
${game.sentence_count} sentences, ${game.total_characters.toLocaleString()} characters
|
|
151
|
-
</small>
|
|
152
|
-
</label>
|
|
153
|
-
`;
|
|
154
|
-
|
|
155
|
-
// Add event listener for the checkbox
|
|
156
|
-
const checkbox = gameItem.querySelector('.game-checkbox');
|
|
157
|
-
checkbox.addEventListener('change', (event) => handleGameSelectionChange(event));
|
|
101
|
+
if (gamesResponse.ok && gamesData.summary) {
|
|
102
|
+
const linkedElement = document.getElementById('linkedGamesCount');
|
|
103
|
+
const unlinkedElement = document.getElementById('unlinkedGamesCount');
|
|
158
104
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.error('Error loading games:', error);
|
|
166
|
-
gamesList.innerHTML = '<p class="error-text">Failed to load games</p>';
|
|
167
|
-
content.style.display = 'block';
|
|
168
|
-
} finally {
|
|
169
|
-
loadingIndicator.style.display = 'none';
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function selectAllGames() {
|
|
174
|
-
// Clear current merge target
|
|
175
|
-
databaseManager.mergeTargetGame = null;
|
|
176
|
-
document.querySelectorAll('.checkbox-container').forEach(container => {
|
|
177
|
-
container.classList.remove('merge-target');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const checkboxes = document.querySelectorAll('.game-checkbox');
|
|
181
|
-
checkboxes.forEach((cb, index) => {
|
|
182
|
-
cb.checked = true;
|
|
183
|
-
// Mark the first checkbox as merge target
|
|
184
|
-
if (index === 0) {
|
|
185
|
-
databaseManager.mergeTargetGame = cb.dataset.game;
|
|
186
|
-
cb.closest('.checkbox-container').classList.add('merge-target');
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
updateGameSelection();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function selectNoGames() {
|
|
193
|
-
// Clear merge target
|
|
194
|
-
databaseManager.mergeTargetGame = null;
|
|
195
|
-
document.querySelectorAll('.checkbox-container').forEach(container => {
|
|
196
|
-
container.classList.remove('merge-target');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
document.querySelectorAll('.game-checkbox').forEach(cb => {
|
|
200
|
-
cb.checked = false;
|
|
201
|
-
});
|
|
202
|
-
updateGameSelection();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function handleGameSelectionChange(event) {
|
|
206
|
-
const checkbox = event.target;
|
|
207
|
-
const gameName = checkbox.dataset.game;
|
|
208
|
-
const isChecked = checkbox.checked;
|
|
209
|
-
|
|
210
|
-
// Get current selection count before updating
|
|
211
|
-
const currentSelectedCount = document.querySelectorAll('.game-checkbox:checked').length - (isChecked ? 1 : 0);
|
|
212
|
-
|
|
213
|
-
if (isChecked) {
|
|
214
|
-
// Game is being selected
|
|
215
|
-
if (currentSelectedCount === 0) {
|
|
216
|
-
// This is the first game being selected, mark it as merge target
|
|
217
|
-
databaseManager.mergeTargetGame = gameName;
|
|
218
|
-
// Add visual indicator
|
|
219
|
-
checkbox.closest('.checkbox-container').classList.add('merge-target');
|
|
220
|
-
}
|
|
221
|
-
} else {
|
|
222
|
-
// Game is being deselected
|
|
223
|
-
if (gameName === databaseManager.mergeTargetGame) {
|
|
224
|
-
// The merge target is being deselected
|
|
225
|
-
databaseManager.mergeTargetGame = null;
|
|
226
|
-
checkbox.closest('.checkbox-container').classList.remove('merge-target');
|
|
227
|
-
|
|
228
|
-
// If there are still other games selected, make the first one the new target
|
|
229
|
-
const remainingSelected = document.querySelectorAll('.game-checkbox:checked');
|
|
230
|
-
if (remainingSelected.length > 0) {
|
|
231
|
-
const newTargetCheckbox = remainingSelected[0];
|
|
232
|
-
const newTargetGame = newTargetCheckbox.dataset.game;
|
|
233
|
-
databaseManager.mergeTargetGame = newTargetGame;
|
|
234
|
-
newTargetCheckbox.closest('.checkbox-container').classList.add('merge-target');
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
// Remove merge target styling if it exists
|
|
238
|
-
checkbox.closest('.checkbox-container').classList.remove('merge-target');
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
updateGameSelection();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function updateGameSelection() {
|
|
246
|
-
const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
|
|
247
|
-
const deleteBtn = document.getElementById('deleteSelectedGamesBtn');
|
|
248
|
-
const mergeBtn = document.getElementById('mergeSelectedGamesBtn');
|
|
249
|
-
|
|
250
|
-
// Update delete button
|
|
251
|
-
deleteBtn.disabled = selectedCheckboxes.length === 0;
|
|
252
|
-
deleteBtn.textContent = selectedCheckboxes.length > 0 ? `Delete Selected (${selectedCheckboxes.length})` : 'Delete Selected';
|
|
253
|
-
|
|
254
|
-
// Update merge button - only enable when 2 or more games are selected
|
|
255
|
-
mergeBtn.disabled = selectedCheckboxes.length < 2;
|
|
256
|
-
mergeBtn.textContent = selectedCheckboxes.length >= 2 ? `Merge Selected (${selectedCheckboxes.length})` : 'Merge Selected Games';
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function deleteSelectedGames() {
|
|
260
|
-
const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
|
|
261
|
-
const gameNames = Array.from(selectedCheckboxes).map(cb => cb.dataset.game);
|
|
262
|
-
|
|
263
|
-
if (gameNames.length === 0) return;
|
|
264
|
-
|
|
265
|
-
if (!confirm(`Are you sure you want to delete ${gameNames.length} game(s)? This action cannot be undone.`)) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
const response = await fetch('/api/delete-games', {
|
|
271
|
-
method: 'POST',
|
|
272
|
-
headers: { 'Content-Type': 'application/json' },
|
|
273
|
-
body: JSON.stringify({ game_names: gameNames })
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
const result = await response.json();
|
|
277
|
-
|
|
278
|
-
if (response.ok) {
|
|
279
|
-
alert(`Successfully deleted ${result.successful_games.length} games!`);
|
|
280
|
-
closeModal('gamesDeletionModal');
|
|
281
|
-
await databaseManager.loadDashboardStats();
|
|
282
|
-
} else {
|
|
283
|
-
alert(`Error: ${result.error}`);
|
|
284
|
-
}
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error('Error deleting games:', error);
|
|
287
|
-
alert('Failed to delete games');
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Text Lines Functions
|
|
292
|
-
function openTextLinesModal() {
|
|
293
|
-
openModal('textLinesModal');
|
|
294
|
-
// Reset the modal state
|
|
295
|
-
document.getElementById('presetPatterns').value = '';
|
|
296
|
-
document.getElementById('customRegex').value = '';
|
|
297
|
-
document.getElementById('textToDelete').value = '';
|
|
298
|
-
document.getElementById('previewDeleteResults').style.display = 'none';
|
|
299
|
-
document.getElementById('executeDeleteBtn').disabled = true;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Preset pattern definitions
|
|
303
|
-
const presetPatterns = {
|
|
304
|
-
'lines_over_50': '.{51,}',
|
|
305
|
-
'lines_over_100': '.{101,}',
|
|
306
|
-
'non_japanese': '^[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]*$',
|
|
307
|
-
'ascii_only': '^[\x00-\x7F]*$',
|
|
308
|
-
'empty_lines': '^\s*$',
|
|
309
|
-
'numbers_only': '^\d+$',
|
|
310
|
-
'single_char': '^.{1}$',
|
|
311
|
-
'repeated_chars': '(.)\\1{2,}'
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
function applyPresetPattern() {
|
|
315
|
-
const selectedPattern = document.getElementById('presetPatterns').value;
|
|
316
|
-
const customRegexInput = document.getElementById('customRegex');
|
|
317
|
-
const useRegexCheckbox = document.getElementById('useRegexDelete');
|
|
318
|
-
|
|
319
|
-
if (selectedPattern && presetPatterns[selectedPattern]) {
|
|
320
|
-
customRegexInput.value = presetPatterns[selectedPattern];
|
|
321
|
-
useRegexCheckbox.checked = true;
|
|
322
|
-
// Clear preview when pattern changes
|
|
323
|
-
document.getElementById('previewDeleteResults').style.display = 'none';
|
|
324
|
-
document.getElementById('executeDeleteBtn').disabled = true;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async function previewTextDeletion() {
|
|
329
|
-
const customRegex = document.getElementById('customRegex').value;
|
|
330
|
-
const textToDelete = document.getElementById('textToDelete').value;
|
|
331
|
-
const caseSensitive = document.getElementById('caseSensitiveDelete').checked;
|
|
332
|
-
const useRegex = document.getElementById('useRegexDelete').checked;
|
|
333
|
-
const errorDiv = document.getElementById('textLinesError');
|
|
334
|
-
const previewDiv = document.getElementById('previewDeleteResults');
|
|
335
|
-
|
|
336
|
-
errorDiv.style.display = 'none';
|
|
337
|
-
previewDiv.style.display = 'none';
|
|
338
|
-
|
|
339
|
-
// Validate input
|
|
340
|
-
if (!customRegex.trim() && !textToDelete.trim()) {
|
|
341
|
-
errorDiv.textContent = 'Please enter either a regex pattern or exact text to delete';
|
|
342
|
-
errorDiv.style.display = 'block';
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
// Prepare request data
|
|
348
|
-
const requestData = {
|
|
349
|
-
regex_pattern: customRegex.trim() || null,
|
|
350
|
-
exact_text: textToDelete.trim() ? textToDelete.split('\n').filter(line => line.trim()) : null,
|
|
351
|
-
case_sensitive: caseSensitive,
|
|
352
|
-
use_regex: useRegex,
|
|
353
|
-
preview_only: true
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
const response = await fetch('/api/preview-text-deletion', {
|
|
357
|
-
method: 'POST',
|
|
358
|
-
headers: { 'Content-Type': 'application/json' },
|
|
359
|
-
body: JSON.stringify(requestData)
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
const result = await response.json();
|
|
363
|
-
|
|
364
|
-
if (response.ok) {
|
|
365
|
-
// Show preview results
|
|
366
|
-
document.getElementById('previewDeleteCount').textContent = result.count.toLocaleString();
|
|
367
|
-
|
|
368
|
-
const samplesDiv = document.getElementById('previewDeleteSamples');
|
|
369
|
-
if (result.samples && result.samples.length > 0) {
|
|
370
|
-
samplesDiv.innerHTML = '<strong>Sample matches:</strong><br>' +
|
|
371
|
-
result.samples.slice(0, 5).map(sample =>
|
|
372
|
-
`<div style="font-size: 12px; color: var(--text-tertiary); margin: 5px 0; padding: 5px; background: var(--bg-secondary); border-radius: 3px;">${escapeHtml(sample)}</div>`
|
|
373
|
-
).join('');
|
|
374
|
-
} else {
|
|
375
|
-
samplesDiv.innerHTML = '<em>No matches found</em>';
|
|
105
|
+
if (linkedElement) {
|
|
106
|
+
linkedElement.textContent = gamesData.summary.linked_games.toLocaleString();
|
|
107
|
+
}
|
|
108
|
+
if (unlinkedElement) {
|
|
109
|
+
unlinkedElement.textContent = gamesData.summary.unlinked_games.toLocaleString();
|
|
110
|
+
}
|
|
376
111
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
document.getElementById('
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
} catch (error) {
|
|
385
|
-
console.error('Error previewing text deletion:', error);
|
|
386
|
-
// For now, show a placeholder since backend isn't implemented yet
|
|
387
|
-
errorDiv.textContent = 'Preview feature ready - backend endpoint needed';
|
|
388
|
-
errorDiv.style.display = 'block';
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function deleteTextLines() {
|
|
393
|
-
const customRegex = document.getElementById('customRegex').value;
|
|
394
|
-
const textToDelete = document.getElementById('textToDelete').value;
|
|
395
|
-
const caseSensitive = document.getElementById('caseSensitiveDelete').checked;
|
|
396
|
-
const useRegex = document.getElementById('useRegexDelete').checked;
|
|
397
|
-
const errorDiv = document.getElementById('textLinesError');
|
|
398
|
-
const successDiv = document.getElementById('textLinesSuccess');
|
|
399
|
-
|
|
400
|
-
errorDiv.style.display = 'none';
|
|
401
|
-
successDiv.style.display = 'none';
|
|
402
|
-
|
|
403
|
-
if (!customRegex.trim() && !textToDelete.trim()) {
|
|
404
|
-
errorDiv.textContent = 'Please enter either a regex pattern or exact text to delete';
|
|
405
|
-
errorDiv.style.display = 'block';
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (!confirm('This will permanently delete the selected text lines. Continue?')) {
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
try {
|
|
414
|
-
const requestData = {
|
|
415
|
-
regex_pattern: customRegex.trim() || null,
|
|
416
|
-
exact_text: textToDelete.trim() ? textToDelete.split('\n').filter(line => line.trim()) : null,
|
|
417
|
-
case_sensitive: caseSensitive,
|
|
418
|
-
use_regex: useRegex,
|
|
419
|
-
preview_only: false
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
const response = await fetch('/api/delete-text-lines', {
|
|
423
|
-
method: 'POST',
|
|
424
|
-
headers: { 'Content-Type': 'application/json' },
|
|
425
|
-
body: JSON.stringify(requestData)
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const result = await response.json();
|
|
429
|
-
|
|
430
|
-
if (response.ok) {
|
|
431
|
-
successDiv.textContent = `Successfully deleted ${result.deleted_count} text lines!`;
|
|
432
|
-
successDiv.style.display = 'block';
|
|
433
|
-
// Refresh dashboard stats
|
|
434
|
-
await databaseManager.loadDashboardStats();
|
|
435
|
-
} else {
|
|
436
|
-
errorDiv.textContent = result.error || 'Failed to delete text lines';
|
|
437
|
-
errorDiv.style.display = 'block';
|
|
438
|
-
}
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.error('Error deleting text lines:', error);
|
|
441
|
-
// Placeholder for development
|
|
442
|
-
successDiv.textContent = 'Text line deletion feature ready - backend endpoint needed';
|
|
443
|
-
successDiv.style.display = 'block';
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Deduplication Functions
|
|
448
|
-
async function openDeduplicationModal() {
|
|
449
|
-
openModal('deduplicationModal');
|
|
450
|
-
await loadGamesForDeduplication();
|
|
451
|
-
// Reset modal state
|
|
452
|
-
document.getElementById('timeWindow').value = '5';
|
|
453
|
-
document.getElementById('ignoreTimeWindow').checked = false;
|
|
454
|
-
document.getElementById('deduplicationStats').style.display = 'none';
|
|
455
|
-
document.getElementById('removeDuplicatesBtn').disabled = true;
|
|
456
|
-
document.getElementById('deduplicationError').style.display = 'none';
|
|
457
|
-
document.getElementById('deduplicationSuccess').style.display = 'none';
|
|
458
|
-
// Ensure time window is visible on modal open
|
|
459
|
-
toggleTimeWindowVisibility();
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function toggleTimeWindowVisibility() {
|
|
463
|
-
const ignoreTimeWindow = document.getElementById('ignoreTimeWindow').checked;
|
|
464
|
-
const timeWindowGroup = document.getElementById('timeWindowGroup');
|
|
465
|
-
|
|
466
|
-
if (ignoreTimeWindow) {
|
|
467
|
-
timeWindowGroup.style.opacity = '0.5';
|
|
468
|
-
timeWindowGroup.style.pointerEvents = 'none';
|
|
469
|
-
document.getElementById('timeWindow').disabled = true;
|
|
470
|
-
} else {
|
|
471
|
-
timeWindowGroup.style.opacity = '1';
|
|
472
|
-
timeWindowGroup.style.pointerEvents = 'auto';
|
|
473
|
-
document.getElementById('timeWindow').disabled = false;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
async function loadGamesForDeduplication() {
|
|
478
|
-
try {
|
|
479
|
-
const response = await fetch('/api/games-list');
|
|
480
|
-
const data = await response.json();
|
|
481
|
-
|
|
482
|
-
if (response.ok && data.games) {
|
|
483
|
-
const gameSelect = document.getElementById('gameSelection');
|
|
484
|
-
// Keep "All Games" option and add individual games
|
|
485
|
-
gameSelect.innerHTML = '<option value="all">All Games</option>';
|
|
486
|
-
|
|
487
|
-
data.games.forEach(game => {
|
|
488
|
-
const option = document.createElement('option');
|
|
489
|
-
option.value = game.name;
|
|
490
|
-
option.textContent = `${game.name} (${game.sentence_count} sentences)`;
|
|
491
|
-
gameSelect.appendChild(option);
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
} catch (error) {
|
|
495
|
-
console.error('Error loading games for deduplication:', error);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
async function scanForDuplicates() {
|
|
500
|
-
const selectedGames = Array.from(document.getElementById('gameSelection').selectedOptions).map(option => option.value);
|
|
501
|
-
const timeWindow = parseInt(document.getElementById('timeWindow').value);
|
|
502
|
-
const caseSensitive = document.getElementById('caseSensitiveDedup').checked;
|
|
503
|
-
const ignoreTimeWindow = document.getElementById('ignoreTimeWindow').checked;
|
|
504
|
-
const statsDiv = document.getElementById('deduplicationStats');
|
|
505
|
-
const errorDiv = document.getElementById('deduplicationError');
|
|
506
|
-
const successDiv = document.getElementById('deduplicationSuccess');
|
|
507
|
-
const removeBtn = document.getElementById('removeDuplicatesBtn');
|
|
508
|
-
|
|
509
|
-
errorDiv.style.display = 'none';
|
|
510
|
-
successDiv.style.display = 'none';
|
|
511
|
-
statsDiv.style.display = 'none';
|
|
512
|
-
removeBtn.disabled = true;
|
|
513
|
-
|
|
514
|
-
// Validate input
|
|
515
|
-
if (selectedGames.length === 0) {
|
|
516
|
-
errorDiv.textContent = 'Please select at least one game';
|
|
517
|
-
errorDiv.style.display = 'block';
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Only validate time window if not ignoring it
|
|
522
|
-
if (!ignoreTimeWindow && (isNaN(timeWindow) || timeWindow < 1)) {
|
|
523
|
-
errorDiv.textContent = 'Time window must be at least 1 minute';
|
|
524
|
-
errorDiv.style.display = 'block';
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
try {
|
|
529
|
-
const requestData = {
|
|
530
|
-
games: selectedGames,
|
|
531
|
-
time_window_minutes: timeWindow,
|
|
532
|
-
case_sensitive: caseSensitive,
|
|
533
|
-
ignore_time_window: ignoreTimeWindow,
|
|
534
|
-
preview_only: true
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const response = await fetch('/api/preview-deduplication', {
|
|
538
|
-
method: 'POST',
|
|
539
|
-
headers: { 'Content-Type': 'application/json' },
|
|
540
|
-
body: JSON.stringify(requestData)
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
const result = await response.json();
|
|
544
|
-
|
|
545
|
-
if (response.ok) {
|
|
546
|
-
document.getElementById('duplicatesFoundCount').textContent = result.duplicates_count.toLocaleString();
|
|
547
|
-
document.getElementById('gamesAffectedCount').textContent = result.games_affected.toString();
|
|
548
|
-
document.getElementById('spaceToFree').textContent = `${result.duplicates_count} sentences`;
|
|
549
|
-
|
|
550
|
-
// Show sample duplicates
|
|
551
|
-
const samplesDiv = document.getElementById('duplicatesSampleList');
|
|
552
|
-
if (result.samples && result.samples.length > 0) {
|
|
553
|
-
samplesDiv.innerHTML = '<strong>Sample duplicates:</strong><br>' +
|
|
554
|
-
result.samples.slice(0, 3).map(sample =>
|
|
555
|
-
`<div style="font-size: 12px; color: var(--text-tertiary); margin: 5px 0; padding: 5px; background: var(--bg-secondary); border-radius: 3px;">${escapeHtml(sample.text)} (${sample.occurrences} times)</div>`
|
|
556
|
-
).join('');
|
|
557
|
-
} else {
|
|
558
|
-
samplesDiv.innerHTML = '<em>No duplicates found</em>';
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
statsDiv.style.display = 'block';
|
|
562
|
-
removeBtn.disabled = result.duplicates_count === 0;
|
|
563
|
-
|
|
564
|
-
if (result.duplicates_count > 0) {
|
|
565
|
-
const modeText = ignoreTimeWindow ? 'across entire games' : `within ${timeWindow} minute time window`;
|
|
566
|
-
successDiv.textContent = `Found ${result.duplicates_count} duplicate sentences ${modeText} ready for removal.`;
|
|
567
|
-
successDiv.style.display = 'block';
|
|
568
|
-
} else {
|
|
569
|
-
const modeText = ignoreTimeWindow ? 'across entire games' : 'within the specified time window';
|
|
570
|
-
successDiv.textContent = `No duplicates found in the selected games ${modeText}.`;
|
|
571
|
-
successDiv.style.display = 'block';
|
|
572
|
-
}
|
|
573
|
-
} else {
|
|
574
|
-
errorDiv.textContent = result.error || 'Failed to scan for duplicates';
|
|
575
|
-
errorDiv.style.display = 'block';
|
|
576
|
-
}
|
|
577
|
-
} catch (error) {
|
|
578
|
-
console.error('Error scanning for duplicates:', error);
|
|
579
|
-
// Placeholder for development
|
|
580
|
-
const duplicatesFound = Math.floor(Math.random() * 50) + 5;
|
|
581
|
-
document.getElementById('duplicatesFoundCount').textContent = duplicatesFound.toLocaleString();
|
|
582
|
-
document.getElementById('gamesAffectedCount').textContent = Math.min(selectedGames.length, 3).toString();
|
|
583
|
-
document.getElementById('spaceToFree').textContent = `${duplicatesFound} sentences`;
|
|
584
|
-
|
|
585
|
-
statsDiv.style.display = 'block';
|
|
586
|
-
removeBtn.disabled = false;
|
|
587
|
-
const modeText = ignoreTimeWindow ? 'across entire games' : 'with time window';
|
|
588
|
-
successDiv.textContent = `Preview feature ready - found ${duplicatesFound} potential duplicates ${modeText} (backend endpoint needed)`;
|
|
589
|
-
successDiv.style.display = 'block';
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
async function removeDuplicates() {
|
|
594
|
-
const selectedGames = Array.from(document.getElementById('gameSelection').selectedOptions).map(option => option.value);
|
|
595
|
-
const timeWindow = parseInt(document.getElementById('timeWindow').value);
|
|
596
|
-
const caseSensitive = document.getElementById('caseSensitiveDedup').checked;
|
|
597
|
-
const preserveNewest = document.getElementById('preserveNewest').checked;
|
|
598
|
-
const ignoreTimeWindow = document.getElementById('ignoreTimeWindow').checked;
|
|
599
|
-
|
|
600
|
-
const modeText = ignoreTimeWindow ? 'ALL duplicate sentences across entire games' : 'duplicate sentences within the time window';
|
|
601
|
-
if (!confirm(`This will permanently remove ${modeText}. Continue?`)) {
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
const requestData = {
|
|
607
|
-
games: selectedGames,
|
|
608
|
-
time_window_minutes: timeWindow,
|
|
609
|
-
case_sensitive: caseSensitive,
|
|
610
|
-
preserve_newest: preserveNewest,
|
|
611
|
-
ignore_time_window: ignoreTimeWindow,
|
|
612
|
-
preview_only: false
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
const response = await fetch('/api/deduplicate', {
|
|
616
|
-
method: 'POST',
|
|
617
|
-
headers: { 'Content-Type': 'application/json' },
|
|
618
|
-
body: JSON.stringify(requestData)
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
const result = await response.json();
|
|
622
|
-
|
|
623
|
-
if (response.ok) {
|
|
624
|
-
const successDiv = document.getElementById('deduplicationSuccess');
|
|
625
|
-
const resultModeText = ignoreTimeWindow ? 'across entire games' : `within ${timeWindow} minute time window`;
|
|
626
|
-
successDiv.textContent = `Successfully removed ${result.deleted_count} duplicate sentences ${resultModeText}!`;
|
|
627
|
-
successDiv.style.display = 'block';
|
|
628
|
-
document.getElementById('removeDuplicatesBtn').disabled = true;
|
|
629
|
-
// Refresh dashboard stats
|
|
630
|
-
await databaseManager.loadDashboardStats();
|
|
631
|
-
} else {
|
|
632
|
-
const errorDiv = document.getElementById('deduplicationError');
|
|
633
|
-
errorDiv.textContent = result.error || 'Failed to remove duplicates';
|
|
634
|
-
errorDiv.style.display = 'block';
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Error loading game management stats:', error);
|
|
114
|
+
const linkedElement = document.getElementById('linkedGamesCount');
|
|
115
|
+
const unlinkedElement = document.getElementById('unlinkedGamesCount');
|
|
116
|
+
if (linkedElement) linkedElement.textContent = 'Error';
|
|
117
|
+
if (unlinkedElement) unlinkedElement.textContent = 'Error';
|
|
635
118
|
}
|
|
636
|
-
} catch (error) {
|
|
637
|
-
console.error('Error removing duplicates:', error);
|
|
638
|
-
// Placeholder for development
|
|
639
|
-
const successDiv = document.getElementById('deduplicationSuccess');
|
|
640
|
-
successDiv.textContent = 'Deduplication feature ready - backend endpoint needed';
|
|
641
|
-
successDiv.style.display = 'block';
|
|
642
|
-
document.getElementById('removeDuplicatesBtn').disabled = true;
|
|
643
119
|
}
|
|
644
120
|
}
|
|
645
121
|
|
|
122
|
+
// Global database manager instance
|
|
123
|
+
let databaseManager;
|
|
646
124
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
// Use the tracked merge target as primary game, or fall back to first selected
|
|
666
|
-
let primaryGame = selectedGames.find(game => game.name === databaseManager.mergeTargetGame);
|
|
667
|
-
if (!primaryGame) {
|
|
668
|
-
primaryGame = selectedGames[0];
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Secondary games are all selected games except the primary
|
|
672
|
-
const secondaryGames = selectedGames.filter(game => game.name !== primaryGame.name);
|
|
673
|
-
|
|
674
|
-
// Calculate totals
|
|
675
|
-
const totalSentences = selectedGames.reduce((sum, game) => sum + game.sentence_count, 0);
|
|
676
|
-
const totalCharacters = selectedGames.reduce((sum, game) => sum + game.total_characters, 0);
|
|
677
|
-
|
|
678
|
-
// Populate primary game info
|
|
679
|
-
document.getElementById('primaryGameName').textContent = primaryGame.name;
|
|
680
|
-
document.getElementById('primaryGameStats').textContent =
|
|
681
|
-
`${primaryGame.sentence_count} sentences, ${primaryGame.total_characters.toLocaleString()} characters`;
|
|
682
|
-
|
|
683
|
-
// Populate secondary games list
|
|
684
|
-
const secondaryList = document.getElementById('secondaryGamesList');
|
|
685
|
-
secondaryList.innerHTML = '';
|
|
686
|
-
secondaryGames.forEach(game => {
|
|
687
|
-
const gameDiv = document.createElement('div');
|
|
688
|
-
gameDiv.className = 'game-item';
|
|
689
|
-
gameDiv.innerHTML = `
|
|
690
|
-
<div class="game-name">${escapeHtml(game.name)}</div>
|
|
691
|
-
<div class="game-stats">${game.sentence_count} sentences, ${game.total_characters.toLocaleString()} characters</div>
|
|
692
|
-
`;
|
|
693
|
-
secondaryList.appendChild(gameDiv);
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
// Update merge statistics
|
|
697
|
-
document.getElementById('totalSentencesAfterMerge').textContent = totalSentences.toLocaleString();
|
|
698
|
-
document.getElementById('totalCharactersAfterMerge').textContent = totalCharacters.toLocaleString();
|
|
699
|
-
document.getElementById('gamesBeingMerged').textContent = gameNames.length;
|
|
700
|
-
|
|
701
|
-
// Reset modal state
|
|
702
|
-
document.getElementById('mergeError').style.display = 'none';
|
|
703
|
-
document.getElementById('mergeSuccess').style.display = 'none';
|
|
704
|
-
document.getElementById('mergeLoadingIndicator').style.display = 'none';
|
|
705
|
-
document.getElementById('confirmMergeBtn').disabled = false;
|
|
706
|
-
|
|
707
|
-
// Store selected games for the merge operation
|
|
708
|
-
window.selectedGamesForMerge = gameNames;
|
|
709
|
-
|
|
710
|
-
openModal('gameMergeModal');
|
|
711
|
-
}
|
|
712
|
-
} catch (error) {
|
|
713
|
-
console.error('Error loading game data for merge:', error);
|
|
714
|
-
alert('Failed to load game data for merge');
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
async function confirmGameMerge() {
|
|
719
|
-
const gameNames = window.selectedGamesForMerge;
|
|
720
|
-
|
|
721
|
-
if (!gameNames || gameNames.length < 2) {
|
|
722
|
-
alert('Invalid game selection for merge');
|
|
125
|
+
/**
|
|
126
|
+
* Initialize database management when DOM loads
|
|
127
|
+
*/
|
|
128
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
129
|
+
// Ensure all required functions are available
|
|
130
|
+
const requiredFunctions = [
|
|
131
|
+
'showDatabaseSuccessPopup',
|
|
132
|
+
'showDatabaseErrorPopup',
|
|
133
|
+
'showDatabaseConfirmPopup',
|
|
134
|
+
'formatReleaseDate',
|
|
135
|
+
'switchTab',
|
|
136
|
+
'loadGamesForDataManagement'
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
const missingFunctions = requiredFunctions.filter(fn => typeof window[fn] !== 'function');
|
|
140
|
+
if (missingFunctions.length > 0) {
|
|
141
|
+
console.error('Missing required functions:', missingFunctions);
|
|
142
|
+
console.error('Please ensure all database modules are loaded before database.js');
|
|
723
143
|
return;
|
|
724
144
|
}
|
|
725
145
|
|
|
726
|
-
|
|
727
|
-
const successDiv = document.getElementById('mergeSuccess');
|
|
728
|
-
const loadingDiv = document.getElementById('mergeLoadingIndicator');
|
|
729
|
-
const confirmBtn = document.getElementById('confirmMergeBtn');
|
|
730
|
-
|
|
731
|
-
// Reset state
|
|
732
|
-
errorDiv.style.display = 'none';
|
|
733
|
-
successDiv.style.display = 'none';
|
|
734
|
-
|
|
735
|
-
// Show loading state
|
|
736
|
-
loadingDiv.style.display = 'flex';
|
|
737
|
-
confirmBtn.disabled = true;
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
target_game = databaseManager.mergeTargetGame || gameNames[0];
|
|
741
|
-
const response = await fetch('/api/merge_games', {
|
|
742
|
-
method: 'POST',
|
|
743
|
-
headers: { 'Content-Type': 'application/json' },
|
|
744
|
-
body: JSON.stringify(
|
|
745
|
-
{ target_game: target_game, games_to_merge: gameNames.filter(name => name !== target_game) })
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
const result = await response.json();
|
|
749
|
-
|
|
750
|
-
if (response.ok) {
|
|
751
|
-
// Show success message
|
|
752
|
-
successDiv.textContent = `Successfully merged ${result.merged_games.length} games into "${result.primary_game}"! Moved ${result.lines_moved} sentences.`;
|
|
753
|
-
successDiv.style.display = 'block';
|
|
754
|
-
|
|
755
|
-
// Auto-close modal after 2 seconds and refresh
|
|
756
|
-
setTimeout(async () => {
|
|
757
|
-
closeModal('gameMergeModal');
|
|
758
|
-
closeModal('gamesDeletionModal');
|
|
759
|
-
await databaseManager.loadDashboardStats();
|
|
760
|
-
}, 2000);
|
|
761
|
-
|
|
762
|
-
} else {
|
|
763
|
-
// Show error message
|
|
764
|
-
errorDiv.textContent = result.error || 'Failed to merge games';
|
|
765
|
-
errorDiv.style.display = 'block';
|
|
766
|
-
confirmBtn.disabled = false;
|
|
767
|
-
}
|
|
768
|
-
} catch (error) {
|
|
769
|
-
console.error('Error merging games:', error);
|
|
770
|
-
errorDiv.textContent = 'Network error occurred while merging games';
|
|
771
|
-
errorDiv.style.display = 'block';
|
|
772
|
-
confirmBtn.disabled = false;
|
|
773
|
-
} finally {
|
|
774
|
-
loadingDiv.style.display = 'none';
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// Initialize page when DOM loads
|
|
779
|
-
let databaseManager;
|
|
780
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
146
|
+
// Initialize the database manager
|
|
781
147
|
databaseManager = new DatabaseManager();
|
|
148
|
+
|
|
149
|
+
console.log('Database management system initialized successfully');
|
|
782
150
|
});
|