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
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// Database Bulk Operations Functions
|
|
2
|
+
// Dependencies: shared.js (provides escapeHtml, openModal, closeModal), database-popups.js, database-game-data.js
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Select all games in the bulk operations list
|
|
6
|
+
*/
|
|
7
|
+
function selectAllGames() {
|
|
8
|
+
// Clear current merge target
|
|
9
|
+
if (typeof databaseManager !== 'undefined') {
|
|
10
|
+
databaseManager.mergeTargetGame = null;
|
|
11
|
+
}
|
|
12
|
+
document.querySelectorAll('.checkbox-container').forEach(container => {
|
|
13
|
+
container.classList.remove('merge-target');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const checkboxes = document.querySelectorAll('.game-checkbox');
|
|
17
|
+
checkboxes.forEach((cb, index) => {
|
|
18
|
+
cb.checked = true;
|
|
19
|
+
// Mark the first checkbox as merge target
|
|
20
|
+
if (index === 0 && typeof databaseManager !== 'undefined') {
|
|
21
|
+
databaseManager.mergeTargetGame = cb.dataset.game;
|
|
22
|
+
cb.closest('.checkbox-container').classList.add('merge-target');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
updateGameSelection();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Deselect all games in the bulk operations list
|
|
30
|
+
*/
|
|
31
|
+
function selectNoGames() {
|
|
32
|
+
// Clear merge target
|
|
33
|
+
if (typeof databaseManager !== 'undefined') {
|
|
34
|
+
databaseManager.mergeTargetGame = null;
|
|
35
|
+
}
|
|
36
|
+
document.querySelectorAll('.checkbox-container').forEach(container => {
|
|
37
|
+
container.classList.remove('merge-target');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
document.querySelectorAll('.game-checkbox').forEach(cb => {
|
|
41
|
+
cb.checked = false;
|
|
42
|
+
});
|
|
43
|
+
updateGameSelection();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Handle game selection change events
|
|
48
|
+
* @param {Event} event - Change event from checkbox
|
|
49
|
+
*/
|
|
50
|
+
function handleGameSelectionChange(event) {
|
|
51
|
+
const checkbox = event.target;
|
|
52
|
+
const gameName = checkbox.dataset.game;
|
|
53
|
+
const isChecked = checkbox.checked;
|
|
54
|
+
|
|
55
|
+
// Get current selection count before updating
|
|
56
|
+
const currentSelectedCount = document.querySelectorAll('.game-checkbox:checked').length - (isChecked ? 1 : 0);
|
|
57
|
+
|
|
58
|
+
if (isChecked) {
|
|
59
|
+
// Game is being selected
|
|
60
|
+
if (currentSelectedCount === 0 && typeof databaseManager !== 'undefined') {
|
|
61
|
+
// This is the first game being selected, mark it as merge target
|
|
62
|
+
databaseManager.mergeTargetGame = gameName;
|
|
63
|
+
// Add visual indicator
|
|
64
|
+
checkbox.closest('.checkbox-container').classList.add('merge-target');
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// Game is being deselected
|
|
68
|
+
if (typeof databaseManager !== 'undefined' && gameName === databaseManager.mergeTargetGame) {
|
|
69
|
+
// The merge target is being deselected
|
|
70
|
+
databaseManager.mergeTargetGame = null;
|
|
71
|
+
checkbox.closest('.checkbox-container').classList.remove('merge-target');
|
|
72
|
+
|
|
73
|
+
// If there are still other games selected, make the first one the new target
|
|
74
|
+
const remainingSelected = document.querySelectorAll('.game-checkbox:checked');
|
|
75
|
+
if (remainingSelected.length > 0) {
|
|
76
|
+
const newTargetCheckbox = remainingSelected[0];
|
|
77
|
+
const newTargetGame = newTargetCheckbox.dataset.game;
|
|
78
|
+
databaseManager.mergeTargetGame = newTargetGame;
|
|
79
|
+
newTargetCheckbox.closest('.checkbox-container').classList.add('merge-target');
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// Remove merge target styling if it exists
|
|
83
|
+
checkbox.closest('.checkbox-container').classList.remove('merge-target');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
updateGameSelection();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Update the state of bulk operation buttons based on selection
|
|
92
|
+
*/
|
|
93
|
+
function updateGameSelection() {
|
|
94
|
+
const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
|
|
95
|
+
const deleteBtn = document.getElementById('deleteSelectedGamesBtn');
|
|
96
|
+
const mergeBtn = document.getElementById('mergeSelectedGamesBtn');
|
|
97
|
+
|
|
98
|
+
if (deleteBtn) {
|
|
99
|
+
// Update delete button
|
|
100
|
+
deleteBtn.disabled = selectedCheckboxes.length === 0;
|
|
101
|
+
deleteBtn.textContent = selectedCheckboxes.length > 0 ? `Delete Selected (${selectedCheckboxes.length})` : 'Delete Selected';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (mergeBtn) {
|
|
105
|
+
// Update merge button - only enable when 2 or more games are selected
|
|
106
|
+
mergeBtn.disabled = selectedCheckboxes.length < 2;
|
|
107
|
+
mergeBtn.textContent = selectedCheckboxes.length >= 2 ? `Merge Selected (${selectedCheckboxes.length})` : 'Merge Selected Games';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Delete selected games after confirmation
|
|
113
|
+
*/
|
|
114
|
+
async function deleteSelectedGames() {
|
|
115
|
+
const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
|
|
116
|
+
const gameNames = Array.from(selectedCheckboxes).map(cb => cb.dataset.game);
|
|
117
|
+
|
|
118
|
+
if (gameNames.length === 0) return;
|
|
119
|
+
|
|
120
|
+
showDatabaseConfirmPopup(
|
|
121
|
+
`Are you sure you want to delete ${gameNames.length} game(s)? This action cannot be undone.`,
|
|
122
|
+
async () => {
|
|
123
|
+
try {
|
|
124
|
+
const response = await fetch('/api/delete-games', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({ game_names: gameNames })
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result = await response.json();
|
|
131
|
+
|
|
132
|
+
if (response.ok) {
|
|
133
|
+
showDatabaseSuccessPopup(`Successfully deleted ${result.successful_games.length} games!`);
|
|
134
|
+
closeModal('gamesDeletionModal');
|
|
135
|
+
if (typeof databaseManager !== 'undefined') {
|
|
136
|
+
await databaseManager.loadDashboardStats();
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
showDatabaseErrorPopup(`Error: ${result.error}`);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('Error deleting games:', error);
|
|
143
|
+
showDatabaseErrorPopup('Failed to delete games');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Open game merge modal with selected games
|
|
151
|
+
*/
|
|
152
|
+
async function openGameMergeModal() {
|
|
153
|
+
const selectedCheckboxes = document.querySelectorAll('.game-checkbox:checked');
|
|
154
|
+
const gameNames = Array.from(selectedCheckboxes).map(cb => cb.dataset.game);
|
|
155
|
+
|
|
156
|
+
if (gameNames.length < 2) {
|
|
157
|
+
showDatabaseErrorPopup('Please select at least 2 games to merge.');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Get detailed game information
|
|
163
|
+
const response = await fetch('/api/games-list');
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
|
|
166
|
+
if (response.ok && data.games) {
|
|
167
|
+
const selectedGames = data.games.filter(game => gameNames.includes(game.name));
|
|
168
|
+
|
|
169
|
+
// Use the tracked merge target as primary game, or fall back to first selected
|
|
170
|
+
let primaryGame = selectedGames.find(game =>
|
|
171
|
+
typeof databaseManager !== 'undefined' && game.name === databaseManager.mergeTargetGame
|
|
172
|
+
);
|
|
173
|
+
if (!primaryGame) {
|
|
174
|
+
primaryGame = selectedGames[0];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Secondary games are all selected games except the primary
|
|
178
|
+
const secondaryGames = selectedGames.filter(game => game.name !== primaryGame.name);
|
|
179
|
+
|
|
180
|
+
// Calculate totals
|
|
181
|
+
const totalSentences = selectedGames.reduce((sum, game) => sum + game.sentence_count, 0);
|
|
182
|
+
const totalCharacters = selectedGames.reduce((sum, game) => sum + game.total_characters, 0);
|
|
183
|
+
|
|
184
|
+
// Populate primary game info
|
|
185
|
+
document.getElementById('primaryGameName').textContent = primaryGame.name;
|
|
186
|
+
document.getElementById('primaryGameStats').textContent =
|
|
187
|
+
`${primaryGame.sentence_count} sentences, ${primaryGame.total_characters.toLocaleString()} characters`;
|
|
188
|
+
|
|
189
|
+
// Populate secondary games list
|
|
190
|
+
const secondaryList = document.getElementById('secondaryGamesList');
|
|
191
|
+
secondaryList.innerHTML = '';
|
|
192
|
+
secondaryGames.forEach(game => {
|
|
193
|
+
const gameDiv = document.createElement('div');
|
|
194
|
+
gameDiv.className = 'game-item';
|
|
195
|
+
gameDiv.innerHTML = `
|
|
196
|
+
<div class="game-name">${escapeHtml(game.name)}</div>
|
|
197
|
+
<div class="game-stats">${game.sentence_count} sentences, ${game.total_characters.toLocaleString()} characters</div>
|
|
198
|
+
`;
|
|
199
|
+
secondaryList.appendChild(gameDiv);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Update merge statistics
|
|
203
|
+
document.getElementById('totalSentencesAfterMerge').textContent = totalSentences.toLocaleString();
|
|
204
|
+
document.getElementById('totalCharactersAfterMerge').textContent = totalCharacters.toLocaleString();
|
|
205
|
+
document.getElementById('gamesBeingMerged').textContent = gameNames.length;
|
|
206
|
+
|
|
207
|
+
// Reset modal state
|
|
208
|
+
document.getElementById('mergeError').style.display = 'none';
|
|
209
|
+
document.getElementById('mergeSuccess').style.display = 'none';
|
|
210
|
+
document.getElementById('mergeLoadingIndicator').style.display = 'none';
|
|
211
|
+
document.getElementById('confirmMergeBtn').disabled = false;
|
|
212
|
+
|
|
213
|
+
// Store selected games for the merge operation
|
|
214
|
+
window.selectedGamesForMerge = gameNames;
|
|
215
|
+
|
|
216
|
+
openModal('gameMergeModal');
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Error loading game data for merge:', error);
|
|
220
|
+
showDatabaseErrorPopup('Failed to load game data for merge');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Confirm and execute game merge operation
|
|
226
|
+
*/
|
|
227
|
+
async function confirmGameMerge() {
|
|
228
|
+
const gameNames = window.selectedGamesForMerge;
|
|
229
|
+
|
|
230
|
+
if (!gameNames || gameNames.length < 2) {
|
|
231
|
+
showDatabaseErrorPopup('Invalid game selection for merge');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const errorDiv = document.getElementById('mergeError');
|
|
236
|
+
const successDiv = document.getElementById('mergeSuccess');
|
|
237
|
+
const loadingDiv = document.getElementById('mergeLoadingIndicator');
|
|
238
|
+
const confirmBtn = document.getElementById('confirmMergeBtn');
|
|
239
|
+
|
|
240
|
+
// Reset state
|
|
241
|
+
errorDiv.style.display = 'none';
|
|
242
|
+
successDiv.style.display = 'none';
|
|
243
|
+
|
|
244
|
+
// Show loading state
|
|
245
|
+
loadingDiv.style.display = 'flex';
|
|
246
|
+
confirmBtn.disabled = true;
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const target_game = (typeof databaseManager !== 'undefined' && databaseManager.mergeTargetGame) || gameNames[0];
|
|
250
|
+
const response = await fetch('/api/merge_games', {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: { 'Content-Type': 'application/json' },
|
|
253
|
+
body: JSON.stringify({
|
|
254
|
+
target_game: target_game,
|
|
255
|
+
games_to_merge: gameNames.filter(name => name !== target_game)
|
|
256
|
+
})
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const result = await response.json();
|
|
260
|
+
|
|
261
|
+
if (response.ok) {
|
|
262
|
+
// Show success message
|
|
263
|
+
successDiv.textContent = `Successfully merged ${result.merged_games.length} games into "${result.primary_game}"! Moved ${result.lines_moved} sentences.`;
|
|
264
|
+
successDiv.style.display = 'block';
|
|
265
|
+
|
|
266
|
+
// Auto-close modal after 2 seconds and refresh
|
|
267
|
+
setTimeout(async () => {
|
|
268
|
+
closeModal('gameMergeModal');
|
|
269
|
+
closeModal('gamesDeletionModal');
|
|
270
|
+
if (typeof databaseManager !== 'undefined') {
|
|
271
|
+
await databaseManager.loadDashboardStats();
|
|
272
|
+
}
|
|
273
|
+
}, 2000);
|
|
274
|
+
|
|
275
|
+
} else {
|
|
276
|
+
// Show error message
|
|
277
|
+
errorDiv.textContent = result.error || 'Failed to merge games';
|
|
278
|
+
errorDiv.style.display = 'block';
|
|
279
|
+
confirmBtn.disabled = false;
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('Error merging games:', error);
|
|
283
|
+
errorDiv.textContent = 'Network error occurred while merging games';
|
|
284
|
+
errorDiv.style.display = 'block';
|
|
285
|
+
confirmBtn.disabled = false;
|
|
286
|
+
} finally {
|
|
287
|
+
loadingDiv.style.display = 'none';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Initialize bulk operations event handlers
|
|
293
|
+
*/
|
|
294
|
+
function initializeBulkOperations() {
|
|
295
|
+
// Bulk operations handlers
|
|
296
|
+
const selectAllBtn = document.querySelector('[data-action="selectAllGames"]');
|
|
297
|
+
if (selectAllBtn) {
|
|
298
|
+
selectAllBtn.addEventListener('click', selectAllGames);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const selectNoneBtn = document.querySelector('[data-action="selectNoGames"]');
|
|
302
|
+
if (selectNoneBtn) {
|
|
303
|
+
selectNoneBtn.addEventListener('click', selectNoGames);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const deleteSelectedBtn = document.querySelector('[data-action="deleteSelectedGames"]');
|
|
307
|
+
if (deleteSelectedBtn) {
|
|
308
|
+
deleteSelectedBtn.addEventListener('click', deleteSelectedGames);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const mergeSelectedBtn = document.querySelector('[data-action="mergeSelectedGames"]');
|
|
312
|
+
if (mergeSelectedBtn) {
|
|
313
|
+
mergeSelectedBtn.addEventListener('click', openGameMergeModal);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const confirmMergeBtn = document.querySelector('[data-action="confirmGameMerge"]');
|
|
317
|
+
if (confirmMergeBtn) {
|
|
318
|
+
confirmMergeBtn.addEventListener('click', confirmGameMerge);
|
|
319
|
+
}
|
|
320
|
+
}
|