GameSentenceMiner 2.18.15__py3-none-any.whl → 2.18.16__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/anki.py +8 -53
- GameSentenceMiner/ui/anki_confirmation.py +16 -2
- GameSentenceMiner/util/db.py +11 -7
- GameSentenceMiner/util/games_table.py +320 -0
- GameSentenceMiner/web/anki_api_endpoints.py +506 -0
- GameSentenceMiner/web/database_api.py +239 -117
- GameSentenceMiner/web/static/css/loading-skeleton.css +41 -0
- GameSentenceMiner/web/static/css/search.css +54 -0
- GameSentenceMiner/web/static/css/stats.css +76 -0
- GameSentenceMiner/web/static/js/anki_stats.js +304 -50
- GameSentenceMiner/web/static/js/database.js +44 -7
- GameSentenceMiner/web/static/js/heatmap.js +326 -0
- GameSentenceMiner/web/static/js/overview.js +20 -224
- GameSentenceMiner/web/static/js/search.js +190 -23
- GameSentenceMiner/web/static/js/stats.js +371 -1
- GameSentenceMiner/web/stats.py +188 -0
- GameSentenceMiner/web/templates/anki_stats.html +145 -58
- GameSentenceMiner/web/templates/components/date-range.html +19 -0
- GameSentenceMiner/web/templates/components/html-head.html +45 -0
- GameSentenceMiner/web/templates/components/js-config.html +37 -0
- GameSentenceMiner/web/templates/components/popups.html +15 -0
- GameSentenceMiner/web/templates/components/settings-modal.html +233 -0
- GameSentenceMiner/web/templates/database.html +13 -3
- GameSentenceMiner/web/templates/goals.html +9 -31
- GameSentenceMiner/web/templates/overview.html +16 -223
- GameSentenceMiner/web/templates/search.html +46 -0
- GameSentenceMiner/web/templates/stats.html +49 -311
- GameSentenceMiner/web/texthooking_page.py +4 -66
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/RECORD +34 -25
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/top_level.txt +0 -0
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<!-- Include Chart.js from a CDN -->
|
|
12
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
13
|
-
|
|
14
|
-
<!-- Include shared theme styles -->
|
|
15
|
-
{% include 'components/theme-styles.html' %}
|
|
16
|
-
|
|
17
|
-
<!-- Include shared CSS -->
|
|
18
|
-
<link rel="stylesheet" href="/static/css/shared.css">
|
|
19
|
-
|
|
20
|
-
<!-- Include shared dashboard and popup CSS -->
|
|
21
|
-
<link rel="stylesheet" href="/static/css/dashboard-shared.css">
|
|
22
|
-
<link rel="stylesheet" href="/static/css/popups-shared.css">
|
|
23
|
-
|
|
24
|
-
<!-- Include stats-specific CSS -->
|
|
25
|
-
<link rel="stylesheet" href="/static/css/stats.css">
|
|
26
|
-
|
|
27
|
-
<!-- Include shared kanji grid CSS -->
|
|
28
|
-
<link rel="stylesheet" href="/static/css/kanji-grid.css">
|
|
29
|
-
</head>
|
|
3
|
+
{% set page_title = 'Anki and GSM Kanji Stats' %}
|
|
4
|
+
{% set page_specific_css = 'stats.css' %}
|
|
5
|
+
{% set include_kanji_grid_css = true %}
|
|
6
|
+
{% set include_chart_js = true %}
|
|
7
|
+
{% set include_html2canvas = true %}
|
|
8
|
+
{% include 'components/html-head.html' %}
|
|
9
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/loading-skeleton.css') }}">
|
|
30
10
|
<body>
|
|
31
11
|
|
|
32
12
|
<div class="container">
|
|
@@ -38,39 +18,137 @@
|
|
|
38
18
|
<!-- Include shared navigation -->
|
|
39
19
|
{% include 'components/navigation.html' %}
|
|
40
20
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
21
|
+
<!-- Include shared date range component -->
|
|
22
|
+
{% include 'components/date-range.html' %}
|
|
23
|
+
|
|
24
|
+
<!-- Include shared popup components -->
|
|
25
|
+
{% include 'components/popups.html' %}
|
|
26
|
+
|
|
27
|
+
<!-- AnkiConnect Warning Banner -->
|
|
28
|
+
<div id="ankiConnectWarning" class="dashboard-card" style="display: none; background: var(--warning-bg, #fff3cd); border-left: 4px solid var(--warning-color, #f39c12); margin-bottom: 20px;">
|
|
29
|
+
<div style="padding: 16px; display: flex; align-items: center; gap: 12px;">
|
|
30
|
+
<span style="font-size: 24px;">⚠️</span>
|
|
31
|
+
<div style="flex: 1;">
|
|
32
|
+
<strong style="color: var(--warning-color, #f39c12); display: block; margin-bottom: 4px;">AnkiConnect Not Detected</strong>
|
|
33
|
+
<p style="margin: 0; color: var(--text-primary); font-size: 14px;">
|
|
34
|
+
Unable to connect to Anki. Please ensure Anki is running and <a href="https://ankiweb.net/shared/info/2055492159" target="_blank" style="color: var(--primary-color); text-decoration: underline;">AnkiConnect</a> is installed.
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
47
37
|
</div>
|
|
38
|
+
</div>
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
<!-- Anki Game Stats Section -->
|
|
41
|
+
<div class="dashboard-container">
|
|
42
|
+
<div class="dashboard-card current-game" id="gameStatsCard">
|
|
43
|
+
<div class="dashboard-card-header">
|
|
44
|
+
<div>
|
|
45
|
+
<h3 class="dashboard-card-title">
|
|
46
|
+
<span class="dashboard-card-icon">🎮</span>
|
|
47
|
+
Anki Game Stats
|
|
48
|
+
</h3>
|
|
49
|
+
<p class="dashboard-card-subtitle">Review Anki statistics by game</p>
|
|
50
|
+
</div>
|
|
53
51
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<
|
|
52
|
+
|
|
53
|
+
<div class="dashboard-progress-section">
|
|
54
|
+
<div id="gameStatsTableContainer">
|
|
55
|
+
<div id="gameStatsLoading" class="dashboard-loading" style="display: none;">
|
|
56
|
+
<div class="spinner"></div>
|
|
57
|
+
<span>Loading game statistics...</span>
|
|
58
|
+
</div>
|
|
59
|
+
<table id="gameStatsTable" class="stats-table">
|
|
60
|
+
<thead>
|
|
61
|
+
<tr>
|
|
62
|
+
<th>Game Name</th>
|
|
63
|
+
<th>Avg Time/Card</th>
|
|
64
|
+
<th>Retention</th>
|
|
65
|
+
<!-- <th>Mined Lines</th> -->
|
|
66
|
+
</tr>
|
|
67
|
+
</thead>
|
|
68
|
+
<tbody id="gameStatsTableBody">
|
|
69
|
+
<!-- Data will be populated by JavaScript -->
|
|
70
|
+
</tbody>
|
|
71
|
+
</table>
|
|
72
|
+
<div id="gameStatsEmpty" class="empty-message" style="display: none;">
|
|
73
|
+
No game statistics available for the selected date range.
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
57
76
|
</div>
|
|
58
77
|
</div>
|
|
59
78
|
</div>
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<div class="dashboard-
|
|
66
|
-
|
|
80
|
+
<!-- NSFW vs SFW Retention Section -->
|
|
81
|
+
<!--
|
|
82
|
+
<div class="dashboard-container">
|
|
83
|
+
<div class="dashboard-card current-game" id="nsfwSfwRetentionCard">
|
|
84
|
+
<div class="dashboard-card-header">
|
|
85
|
+
<div>
|
|
86
|
+
<h3 class="dashboard-card-title">
|
|
87
|
+
<span class="dashboard-card-icon">🔞</span>
|
|
88
|
+
Retention of NSFW vs SFW
|
|
89
|
+
</h3>
|
|
90
|
+
<p class="dashboard-card-subtitle">Compare retention rates between NSFW and SFW game content</p>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="dashboard-stats-grid" id="nsfwSfwRetentionStats">
|
|
95
|
+
<div class="dashboard-stat-item tooltip" data-tooltip="Retention rate for NSFW tagged cards">
|
|
96
|
+
<span class="dashboard-stat-value" id="nsfwRetention" style="color: var(--text-secondary);">
|
|
97
|
+
<div class="loading-skeleton" style="width: 50px; height: 24px; display: inline-block;"></div>
|
|
98
|
+
</span>
|
|
99
|
+
<span class="dashboard-stat-label">NSFW Retention</span>
|
|
100
|
+
<span class="dashboard-stat-sublabel" id="nsfwReviews" style="font-size: 0.75rem; color: var(--text-tertiary);"></span>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="dashboard-stat-item tooltip" data-tooltip="Retention rate for SFW tagged cards">
|
|
103
|
+
<span class="dashboard-stat-value" id="sfwRetention" style="color: var(--text-secondary);">
|
|
104
|
+
<div class="loading-skeleton" style="width: 50px; height: 24px; display: inline-block;"></div>
|
|
105
|
+
</span>
|
|
106
|
+
<span class="dashboard-stat-label">SFW Retention</span>
|
|
107
|
+
<span class="dashboard-stat-sublabel" id="sfwReviews" style="font-size: 0.75rem; color: var(--text-tertiary);"></span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="dashboard-stats-grid" style="margin-top: 20px;">
|
|
112
|
+
<div class="dashboard-stat-item tooltip" data-tooltip="Average time per card for NSFW tagged cards">
|
|
113
|
+
<span class="dashboard-stat-value" id="nsfwAvgTime" style="color: var(--text-secondary);">
|
|
114
|
+
<div class="loading-skeleton" style="width: 40px; height: 24px; display: inline-block;"></div>
|
|
115
|
+
</span>
|
|
116
|
+
<span class="dashboard-stat-label">NSFW Avg Time</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="dashboard-stat-item tooltip" data-tooltip="Average time per card for SFW tagged cards">
|
|
119
|
+
<span class="dashboard-stat-value" id="sfwAvgTime" style="color: var(--text-secondary);">
|
|
120
|
+
<div class="loading-skeleton" style="width: 40px; height: 24px; display: inline-block;"></div>
|
|
121
|
+
</span>
|
|
122
|
+
<span class="dashboard-stat-label">SFW Avg Time</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<div id="nsfwSfwRetentionLoading" class="dashboard-loading" style="display: none;">
|
|
127
|
+
<div class="spinner"></div>
|
|
128
|
+
<span>Loading retention statistics...</span>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div id="nsfwSfwRetentionEmpty" class="empty-message" style="display: none;">
|
|
132
|
+
No NSFW/SFW retention data available for the selected date range.
|
|
133
|
+
</div>
|
|
67
134
|
</div>
|
|
68
135
|
</div>
|
|
136
|
+
-->
|
|
69
137
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
138
|
+
<!-- Missing High-Frequency Kanji Section -->
|
|
139
|
+
<!-- Mining Heatmap Card (Full Width) -->
|
|
140
|
+
<div class="dashboard-card all-games" id="miningHeatmapCard" style="margin-bottom: 24px;">
|
|
141
|
+
<div class="dashboard-card-header">
|
|
142
|
+
<h3 class="dashboard-card-title">
|
|
143
|
+
<span class="dashboard-card-icon">📊</span>
|
|
144
|
+
Mining Heatmap
|
|
145
|
+
</h3>
|
|
146
|
+
<p class="dashboard-card-subtitle">Track your daily sentence mining progress</p>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="dashboard-progress-section">
|
|
150
|
+
<div class="dashboard-progress-title">Activity Heatmap</div>
|
|
151
|
+
<div id="miningHeatmapContainer"></div>
|
|
74
152
|
</div>
|
|
75
153
|
</div>
|
|
76
154
|
|
|
@@ -94,19 +172,27 @@
|
|
|
94
172
|
|
|
95
173
|
<div class="dashboard-stats-grid" id="missingKanjiStats">
|
|
96
174
|
<div class="dashboard-stat-item tooltip" data-tooltip="Number of high-frequency kanji missing from Anki">
|
|
97
|
-
<span class="dashboard-stat-value" id="missingKanjiCount"
|
|
175
|
+
<span class="dashboard-stat-value" id="missingKanjiCount">
|
|
176
|
+
<div class="loading-skeleton" style="width: 40px; height: 24px; display: inline-block;"></div>
|
|
177
|
+
</span>
|
|
98
178
|
<span class="dashboard-stat-label">Missing Kanji</span>
|
|
99
179
|
</div>
|
|
100
180
|
<div class="dashboard-stat-item tooltip" data-tooltip="Total kanji in your Anki collection">
|
|
101
|
-
<span class="dashboard-stat-value" id="ankiTotalKanji"
|
|
181
|
+
<span class="dashboard-stat-value" id="ankiTotalKanji">
|
|
182
|
+
<div class="loading-skeleton" style="width: 40px; height: 24px; display: inline-block;"></div>
|
|
183
|
+
</span>
|
|
102
184
|
<span class="dashboard-stat-label">Kanji in Anki</span>
|
|
103
185
|
</div>
|
|
104
186
|
<div class="dashboard-stat-item tooltip" data-tooltip="Total unique kanji seen in GSM">
|
|
105
|
-
<span class="dashboard-stat-value" id="gsmTotalKanji"
|
|
187
|
+
<span class="dashboard-stat-value" id="gsmTotalKanji">
|
|
188
|
+
<div class="loading-skeleton" style="width: 40px; height: 24px; display: inline-block;"></div>
|
|
189
|
+
</span>
|
|
106
190
|
<span class="dashboard-stat-label">Kanji in GSM</span>
|
|
107
191
|
</div>
|
|
108
192
|
<div class="dashboard-stat-item tooltip" data-tooltip="Percentage of GSM kanji covered by Anki">
|
|
109
|
-
<span class="dashboard-stat-value" id="ankiCoverage"
|
|
193
|
+
<span class="dashboard-stat-value" id="ankiCoverage">
|
|
194
|
+
<div class="loading-skeleton" style="width: 50px; height: 24px; display: inline-block;"></div>
|
|
195
|
+
</span>
|
|
110
196
|
<span class="dashboard-stat-label">Coverage %</span>
|
|
111
197
|
</div>
|
|
112
198
|
</div>
|
|
@@ -241,10 +327,11 @@
|
|
|
241
327
|
</div>
|
|
242
328
|
</div>
|
|
243
329
|
|
|
244
|
-
<!-- Include shared JavaScript
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
330
|
+
<!-- Include shared JavaScript configuration -->
|
|
331
|
+
{% set include_kanji_grid_js = true %}
|
|
332
|
+
{% set include_heatmap_js = true %}
|
|
333
|
+
{% set page_specific_js = 'anki_stats.js' %}
|
|
334
|
+
{% include 'components/js-config.html' %}
|
|
248
335
|
|
|
249
336
|
</body>
|
|
250
337
|
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- Date Range Component -->
|
|
2
|
+
<div class="dashboard-card date-range">
|
|
3
|
+
<div class="dashboard-card-header">
|
|
4
|
+
<h3 class="dashboard-card-title">
|
|
5
|
+
<span class="dashboard-card-icon">📅</span>
|
|
6
|
+
Date Range
|
|
7
|
+
</h3>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="dashboard-date-range">
|
|
10
|
+
<div class="dashboard-date-item tooltip" data-tooltip="{{ from_tooltip | default('Select the start date for your stats') }}">
|
|
11
|
+
<label for="{{ from_id | default('fromDate') }}">From</label>
|
|
12
|
+
<input type="date" id="{{ from_id | default('fromDate') }}" class="dashboard-date-input">
|
|
13
|
+
</div>
|
|
14
|
+
<div class="dashboard-date-item tooltip" data-tooltip="{{ to_tooltip | default('Select the end date for your stats') }}">
|
|
15
|
+
<label for="{{ to_id | default('toDate') }}">To</label>
|
|
16
|
+
<input type="date" id="{{ to_id | default('toDate') }}" class="dashboard-date-input">
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!-- HTML Head Component -->
|
|
2
|
+
<head>
|
|
3
|
+
<meta charset="UTF-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5
|
+
<title>{{ page_title | default('GSM Dashboard') }}</title>
|
|
6
|
+
|
|
7
|
+
{% if include_chartjs | default(true) %}
|
|
8
|
+
<!-- Include Chart.js from a CDN -->
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
10
|
+
{% endif %}
|
|
11
|
+
|
|
12
|
+
{% if include_html2canvas | default(true) %}
|
|
13
|
+
<!-- Include html2canvas for screenshot functionality -->
|
|
14
|
+
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
15
|
+
{% endif %}
|
|
16
|
+
|
|
17
|
+
<!-- Include shared theme styles -->
|
|
18
|
+
{% include 'components/theme-styles.html' %}
|
|
19
|
+
|
|
20
|
+
<!-- Include shared CSS -->
|
|
21
|
+
<link rel="stylesheet" href="/static/css/shared.css">
|
|
22
|
+
|
|
23
|
+
{% if include_dashboard_css | default(true) %}
|
|
24
|
+
<!-- Include shared dashboard and popup CSS -->
|
|
25
|
+
<link rel="stylesheet" href="/static/css/dashboard-shared.css">
|
|
26
|
+
<link rel="stylesheet" href="/static/css/popups-shared.css">
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
{% if page_specific_css %}
|
|
30
|
+
<!-- Include page-specific CSS -->
|
|
31
|
+
<link rel="stylesheet" href="/static/css/{{ page_specific_css }}">
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
34
|
+
{% if include_kanji_grid_css | default(false) %}
|
|
35
|
+
<!-- Include shared kanji grid CSS -->
|
|
36
|
+
<link rel="stylesheet" href="/static/css/kanji-grid.css">
|
|
37
|
+
{% endif %}
|
|
38
|
+
|
|
39
|
+
{% if additional_css %}
|
|
40
|
+
<!-- Additional CSS files -->
|
|
41
|
+
{% for css_file in additional_css %}
|
|
42
|
+
<link rel="stylesheet" href="/static/css/{{ css_file }}">
|
|
43
|
+
{% endfor %}
|
|
44
|
+
{% endif %}
|
|
45
|
+
</head>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!-- JavaScript Configuration Component -->
|
|
2
|
+
{% if config_vars %}
|
|
3
|
+
<!-- Inject config values for frontend -->
|
|
4
|
+
<script>
|
|
5
|
+
window.{{ config_object_name | default('statsConfig') }} = {{ config_vars | tojson }};
|
|
6
|
+
</script>
|
|
7
|
+
{% endif %}
|
|
8
|
+
|
|
9
|
+
<!-- Include shared JavaScript first (required dependency) -->
|
|
10
|
+
<script src="/static/js/shared.js"></script>
|
|
11
|
+
|
|
12
|
+
{% if include_kanji_grid_js | default(false) %}
|
|
13
|
+
<script src="/static/js/kanji-grid.js"></script>
|
|
14
|
+
{% endif %}
|
|
15
|
+
|
|
16
|
+
{% if include_heatmap_js | default(false) %}
|
|
17
|
+
<script src="/static/js/heatmap.js"></script>
|
|
18
|
+
{% endif %}
|
|
19
|
+
|
|
20
|
+
{% if page_specific_js %}
|
|
21
|
+
<!-- Include page-specific JavaScript -->
|
|
22
|
+
<script src="/static/js/{{ page_specific_js }}"></script>
|
|
23
|
+
{% endif %}
|
|
24
|
+
|
|
25
|
+
{% if additional_js %}
|
|
26
|
+
<!-- Additional JavaScript files -->
|
|
27
|
+
{% for js_file in additional_js %}
|
|
28
|
+
<script src="/static/js/{{ js_file }}"></script>
|
|
29
|
+
{% endfor %}
|
|
30
|
+
{% endif %}
|
|
31
|
+
|
|
32
|
+
{% if inline_js %}
|
|
33
|
+
<!-- Inline JavaScript -->
|
|
34
|
+
<script>
|
|
35
|
+
{{ inline_js | safe }}
|
|
36
|
+
</script>
|
|
37
|
+
{% endif %}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- Shared Popup Components -->
|
|
2
|
+
<div id="{{ date_error_id | default('dateErrorPopup') }}" class="dashboard-popup hidden">
|
|
3
|
+
<div class="dashboard-popup-content">
|
|
4
|
+
<div class="dashboard-popup-icon">⚠️</div>
|
|
5
|
+
<div class="dashboard-popup-message">{{ date_error_message | default('"From" date cannot be later than "To" date.') }}</div>
|
|
6
|
+
<button id="{{ date_error_btn_id | default('closePopupBtn') }}" class="dashboard-popup-btn">OK</button>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div id="{{ no_data_id | default('noDataPopup') }}" class="no-data-popup hidden">
|
|
11
|
+
<div class="no-data-content">
|
|
12
|
+
<p>{{ no_data_message | default('No data found for the selected range.') }}</p>
|
|
13
|
+
<button id="{{ no_data_btn_id | default('closeNoDataPopup') }}">OK</button>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<!-- Settings Modal Component -->
|
|
2
|
+
<div id="{{ modal_id | default('settingsModal') }}" class="modal">
|
|
3
|
+
<div class="modal-content">
|
|
4
|
+
<div class="modal-header">
|
|
5
|
+
<h3>{{ title | default('Statistics Settings') }}</h3>
|
|
6
|
+
<span class="close-btn" id="{{ close_btn_id | default('closeSettingsModal') }}">×</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="modal-body">
|
|
9
|
+
<p style="color: var(--text-secondary); margin-bottom: 20px;">
|
|
10
|
+
{{ description | default('Configure how reading time and sessions are calculated for your statistics.') }}
|
|
11
|
+
</p>
|
|
12
|
+
<form id="{{ form_id | default('settingsForm') }}">
|
|
13
|
+
{% if show_timezone_section | default(false) %}
|
|
14
|
+
<!-- Timezone Section (commented out in original) -->
|
|
15
|
+
<!-- <div style="margin-bottom: 20px;">
|
|
16
|
+
<label for="timezone-select"
|
|
17
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
18
|
+
Timezone
|
|
19
|
+
</label>
|
|
20
|
+
<div style="display: flex; gap: 8px;">
|
|
21
|
+
<select id="timezone-select" name="timezone"
|
|
22
|
+
style="flex-grow: 1; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;">
|
|
23
|
+
</select>
|
|
24
|
+
<button id="set-local-timezone-btn"
|
|
25
|
+
style="padding: 10px; border: none; border-radius: 5px; background: var(--primary-color); color: white; font-size: 14px; cursor: pointer;">
|
|
26
|
+
Set to Local
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
<small style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
30
|
+
Select the timezone to base your statistics on
|
|
31
|
+
</small>
|
|
32
|
+
</div> -->
|
|
33
|
+
{% endif %}
|
|
34
|
+
|
|
35
|
+
{% if show_basic_settings | default(true) %}
|
|
36
|
+
<div style="margin-bottom: 20px;">
|
|
37
|
+
<label for="{{ afk_timer_id | default('afkTimer') }}"
|
|
38
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
39
|
+
AFK Timer (seconds)
|
|
40
|
+
</label>
|
|
41
|
+
<input type="number" id="{{ afk_timer_id | default('afkTimer') }}" name="afk_timer_seconds"
|
|
42
|
+
style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
43
|
+
placeholder="120">
|
|
44
|
+
<small
|
|
45
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
46
|
+
Maximum time between activities that still counts as active reading
|
|
47
|
+
</small>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div style="margin-bottom: 20px;">
|
|
51
|
+
<label for="{{ session_gap_id | default('sessionGap') }}"
|
|
52
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
53
|
+
Session Gap (seconds)
|
|
54
|
+
</label>
|
|
55
|
+
<input type="number" id="{{ session_gap_id | default('sessionGap') }}" name="session_gap_seconds"
|
|
56
|
+
style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
57
|
+
placeholder="3600">
|
|
58
|
+
<small
|
|
59
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
60
|
+
Time gap that triggers a new reading session
|
|
61
|
+
</small>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div style="margin-bottom: 20px;">
|
|
65
|
+
<label for="{{ streak_req_id | default('streakRequirement') }}"
|
|
66
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
67
|
+
Streak Requirement (hours)
|
|
68
|
+
</label>
|
|
69
|
+
<input type="number" id="{{ streak_req_id | default('streakRequirement') }}" name="streak_requirement_hours" min="0.01"
|
|
70
|
+
max="24" step="0.01"
|
|
71
|
+
style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
72
|
+
placeholder="1.0">
|
|
73
|
+
<small
|
|
74
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
75
|
+
Minimum hours of reading activity required to maintain streak (0.01-24 hours)
|
|
76
|
+
</small>
|
|
77
|
+
</div>
|
|
78
|
+
{% endif %}
|
|
79
|
+
|
|
80
|
+
{% if show_goals_section | default(true) %}
|
|
81
|
+
<!-- Reading Goals Section -->
|
|
82
|
+
<div style="margin-bottom: 20px; padding-top: 20px; border-top: 1px solid var(--border-color);">
|
|
83
|
+
<label
|
|
84
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
85
|
+
Reading Goals Configuration
|
|
86
|
+
</label>
|
|
87
|
+
<p style="color: var(--text-secondary); margin-bottom: 20px; font-size: 14px;">
|
|
88
|
+
Set your long-term reading targets for tracking progress and projections.
|
|
89
|
+
</p>
|
|
90
|
+
|
|
91
|
+
<div style="margin-bottom: 15px;">
|
|
92
|
+
<label for="{{ hours_target_id | default('readingHoursTarget') }}"
|
|
93
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
94
|
+
Reading Hours Target
|
|
95
|
+
</label>
|
|
96
|
+
<input type="number" id="{{ hours_target_id | default('readingHoursTarget') }}" name="reading_hours_target" min="1"
|
|
97
|
+
max="10000"
|
|
98
|
+
style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
99
|
+
placeholder="1500">
|
|
100
|
+
<small
|
|
101
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
102
|
+
Total reading hours goal (1-10,000 hours) - Default: 1500 hours based on TMW N1
|
|
103
|
+
achievement
|
|
104
|
+
</small>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div style="margin-bottom: 15px;">
|
|
108
|
+
<label for="{{ chars_target_id | default('characterCountTarget') }}"
|
|
109
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
110
|
+
Character Count Target
|
|
111
|
+
</label>
|
|
112
|
+
<input type="number" id="{{ chars_target_id | default('characterCountTarget') }}" name="character_count_target" min="1000"
|
|
113
|
+
max="1000000000"
|
|
114
|
+
style="padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
115
|
+
placeholder="25000000">
|
|
116
|
+
<small
|
|
117
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
118
|
+
Total characters read goal (1,000-1,000,000,000) - Default: 25 million characters
|
|
119
|
+
</small>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{% if show_target_dates | default(true) %}
|
|
123
|
+
<div style="margin-bottom: 15px;">
|
|
124
|
+
<label for="{{ hours_date_id | default('readingHoursTargetDate') }}"
|
|
125
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
126
|
+
Reading Hours Target Date
|
|
127
|
+
</label>
|
|
128
|
+
<input type="date" id="{{ hours_date_id | default('readingHoursTargetDate') }}" name="reading_hours_target_date"
|
|
129
|
+
style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
130
|
+
data-date-format="yyyy-mm-dd">
|
|
131
|
+
<small
|
|
132
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
133
|
+
Target date to achieve your reading hours goal
|
|
134
|
+
</small>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div style="margin-bottom: 15px;">
|
|
138
|
+
<label for="{{ chars_date_id | default('characterCountTargetDate') }}"
|
|
139
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
140
|
+
Character Count Target Date
|
|
141
|
+
</label>
|
|
142
|
+
<input type="date" id="{{ chars_date_id | default('characterCountTargetDate') }}" name="character_count_target_date"
|
|
143
|
+
style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
144
|
+
data-date-format="yyyy-mm-dd">
|
|
145
|
+
<small
|
|
146
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
147
|
+
Target date to achieve your character count goal
|
|
148
|
+
</small>
|
|
149
|
+
</div>
|
|
150
|
+
{% endif %}
|
|
151
|
+
|
|
152
|
+
<div style="margin-bottom: 15px;">
|
|
153
|
+
<label for="{{ games_target_id | default('gamesTarget') }}"
|
|
154
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
155
|
+
Games/Visual Novels Target
|
|
156
|
+
</label>
|
|
157
|
+
<input type="number" id="{{ games_target_id | default('gamesTarget') }}" name="games_target" min="1" max="1000"
|
|
158
|
+
style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
159
|
+
placeholder="100">
|
|
160
|
+
<small
|
|
161
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
162
|
+
Number of games/visual novels to complete (1-1,000) - Default: 100 based on Refold
|
|
163
|
+
standards
|
|
164
|
+
</small>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{% if show_target_dates | default(true) %}
|
|
168
|
+
<div style="margin-bottom: 15px;">
|
|
169
|
+
<label for="{{ games_date_id | default('gamesTargetDate') }}"
|
|
170
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
171
|
+
Games/Visual Novels Target Date
|
|
172
|
+
</label>
|
|
173
|
+
<input type="date" id="{{ games_date_id | default('gamesTargetDate') }}" name="games_target_date"
|
|
174
|
+
style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;"
|
|
175
|
+
data-date-format="yyyy-mm-dd">
|
|
176
|
+
<small
|
|
177
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
178
|
+
Target date to achieve your games goal
|
|
179
|
+
</small>
|
|
180
|
+
</div>
|
|
181
|
+
{% endif %}
|
|
182
|
+
</div>
|
|
183
|
+
{% endif %}
|
|
184
|
+
|
|
185
|
+
{% if show_exstatic_section | default(true) %}
|
|
186
|
+
<!-- Import ExStatic Lines Section -->
|
|
187
|
+
<div style="margin-bottom: 20px; padding-top: 20px; border-top: 1px solid var(--border-color);">
|
|
188
|
+
<label
|
|
189
|
+
style="display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary);">
|
|
190
|
+
Import ExStatic Lines
|
|
191
|
+
</label>
|
|
192
|
+
<div style="margin-bottom: 10px;">
|
|
193
|
+
<input type="file" id="{{ exstatic_file_id | default('exstaticFile') }}" accept=".csv"
|
|
194
|
+
style="width: 90%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;">
|
|
195
|
+
<small
|
|
196
|
+
style="color: var(--text-tertiary); font-size: 12px; margin-top: 4px; display: block;">
|
|
197
|
+
Select an ExStatic CSV file to import reading data into GSM
|
|
198
|
+
</small>
|
|
199
|
+
</div>
|
|
200
|
+
<button type="button" id="{{ import_btn_id | default('importExstaticBtn') }}"
|
|
201
|
+
style="width: 90%; padding: 10px; background: #666; color: white; border: none; border-radius: 5px; font-size: 14px; cursor: not-allowed; margin-bottom: 10px;"
|
|
202
|
+
disabled>
|
|
203
|
+
Import ExStatic Lines
|
|
204
|
+
</button>
|
|
205
|
+
<div id="{{ import_progress_id | default('importProgress') }}" style="display: none; margin-bottom: 10px;">
|
|
206
|
+
<div
|
|
207
|
+
style="background: var(--bg-tertiary); border-radius: 5px; overflow: hidden; height: 20px; position: relative;">
|
|
208
|
+
<div id="{{ import_progress_bar_id | default('importProgressBar') }}"
|
|
209
|
+
style="background: var(--primary-color); height: 100%; width: 0%; transition: width 0.3s ease;">
|
|
210
|
+
</div>
|
|
211
|
+
<span id="{{ import_progress_text_id | default('importProgressText') }}"
|
|
212
|
+
style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; color: var(--text-primary);">0%</span>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
<div id="{{ import_status_id | default('importStatus') }}"
|
|
216
|
+
style="display: none; padding: 10px; border-radius: 5px; font-size: 14px;"></div>
|
|
217
|
+
</div>
|
|
218
|
+
{% endif %}
|
|
219
|
+
</form>
|
|
220
|
+
|
|
221
|
+
<div id="{{ error_id | default('settingsError') }}"
|
|
222
|
+
style="display: none; background: var(--danger-color); color: white; padding: 10px; border-radius: 5px; margin-bottom: 15px; font-size: 14px;">
|
|
223
|
+
</div>
|
|
224
|
+
<div id="{{ success_id | default('settingsSuccess') }}"
|
|
225
|
+
style="display: none; background: var(--success-color); color: white; padding: 10px; border-radius: 5px; margin-bottom: 15px; font-size: 14px;">
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
<div class="modal-footer">
|
|
229
|
+
<button id="{{ cancel_btn_id | default('cancelSettingsBtn') }}" class="cancel-btn">Cancel</button>
|
|
230
|
+
<button id="{{ save_btn_id | default('saveSettingsBtn') }}" class="confirm-delete-btn">Save Settings</button>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
@@ -231,11 +231,21 @@
|
|
|
231
231
|
</small>
|
|
232
232
|
</div>
|
|
233
233
|
|
|
234
|
-
<div class="form-group">
|
|
234
|
+
<div class="form-group" id="timeWindowGroup">
|
|
235
235
|
<label class="form-label">Time Window (minutes):</label>
|
|
236
|
-
<input type="number" id="timeWindow" class="form-input" min="1"
|
|
236
|
+
<input type="number" id="timeWindow" class="form-input" min="1" value="5" placeholder="5">
|
|
237
237
|
<small style="color: var(--text-tertiary); font-size: 12px;">
|
|
238
|
-
Only remove duplicates that appear within this many minutes of each other (1
|
|
238
|
+
Only remove duplicates that appear within this many minutes of each other (minimum 1 minute, no maximum limit).
|
|
239
|
+
</small>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="form-group">
|
|
243
|
+
<div class="checkbox-container">
|
|
244
|
+
<input type="checkbox" id="ignoreTimeWindow" class="checkbox-input">
|
|
245
|
+
<label for="ignoreTimeWindow" class="checkbox-label">Entire Game (ignore time window)</label>
|
|
246
|
+
</div>
|
|
247
|
+
<small style="color: var(--text-tertiary); font-size: 12px; margin-left: 25px; display: block;">
|
|
248
|
+
Remove ALL duplicates within each game regardless of when they occurred.
|
|
239
249
|
</small>
|
|
240
250
|
</div>
|
|
241
251
|
|