GameSentenceMiner 2.17.0__py3-none-any.whl → 2.17.2__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.

Files changed (31) hide show
  1. GameSentenceMiner/anki.py +31 -3
  2. GameSentenceMiner/config_gui.py +26 -2
  3. GameSentenceMiner/gametext.py +4 -3
  4. GameSentenceMiner/gsm.py +19 -23
  5. GameSentenceMiner/obs.py +17 -7
  6. GameSentenceMiner/ocr/owocr_helper.py +11 -8
  7. GameSentenceMiner/owocr/owocr/run.py +11 -5
  8. GameSentenceMiner/util/configuration.py +7 -5
  9. GameSentenceMiner/util/db.py +176 -8
  10. GameSentenceMiner/util/downloader/download_tools.py +57 -24
  11. GameSentenceMiner/util/ffmpeg.py +5 -2
  12. GameSentenceMiner/util/get_overlay_coords.py +3 -0
  13. GameSentenceMiner/util/gsm_utils.py +0 -54
  14. GameSentenceMiner/vad.py +5 -2
  15. GameSentenceMiner/web/database_api.py +12 -1
  16. GameSentenceMiner/web/gsm_websocket.py +1 -1
  17. GameSentenceMiner/web/static/css/shared.css +20 -0
  18. GameSentenceMiner/web/static/css/stats.css +496 -1
  19. GameSentenceMiner/web/static/js/anki_stats.js +87 -3
  20. GameSentenceMiner/web/static/js/shared.js +2 -49
  21. GameSentenceMiner/web/static/js/stats.js +274 -39
  22. GameSentenceMiner/web/templates/anki_stats.html +36 -0
  23. GameSentenceMiner/web/templates/index.html +1 -1
  24. GameSentenceMiner/web/templates/stats.html +35 -15
  25. GameSentenceMiner/web/texthooking_page.py +31 -8
  26. {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/METADATA +1 -1
  27. {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/RECORD +31 -31
  28. {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/WHEEL +0 -0
  29. {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/entry_points.txt +0 -0
  30. {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/licenses/LICENSE +0 -0
  31. {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/top_level.txt +0 -0
@@ -657,6 +657,440 @@
657
657
  margin-bottom: 16px;
658
658
  }
659
659
 
660
+ /* ================================
661
+ Dashboard Cards Styles
662
+ ================================ */
663
+ .dashboard-container {
664
+ display: grid;
665
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
666
+ gap: 30px;
667
+ margin-bottom: 40px;
668
+ }
669
+
670
+ .dashboard-card {
671
+ background: var(--bg-secondary);
672
+ border-radius: 12px;
673
+ box-shadow: 0 4px 16px var(--shadow-color);
674
+ border: 1px solid var(--border-color);
675
+ padding: 24px;
676
+ transition: all 0.3s ease;
677
+ position: relative;
678
+ overflow: hidden;
679
+ }
680
+
681
+ .dashboard-card:hover {
682
+ transform: translateY(-2px);
683
+ box-shadow: 0 8px 24px var(--shadow-color);
684
+ }
685
+
686
+ .dashboard-card::before {
687
+ content: '';
688
+ position: absolute;
689
+ top: 0;
690
+ left: 0;
691
+ right: 0;
692
+ height: 4px;
693
+ background: linear-gradient(90deg, var(--accent-color), var(--success-color));
694
+ border-radius: 12px 12px 0 0;
695
+ }
696
+
697
+ .dashboard-card.current-game::before {
698
+ background: linear-gradient(90deg, var(--accent-color), var(--info-color));
699
+ }
700
+
701
+ .dashboard-card.all-games::before {
702
+ background: linear-gradient(90deg, var(--success-color), var(--warning-color));
703
+ }
704
+
705
+ .dashboard-card.date-range::before {
706
+ background: linear-gradient(90deg, var(--info-color), var(--accent-color));
707
+ }
708
+
709
+ .dashboard-card.date-range {
710
+ margin-bottom: 30px;
711
+ }
712
+
713
+ .dashboard-card-header {
714
+ display: flex;
715
+ align-items: center;
716
+ justify-content: space-between;
717
+ margin-bottom: 20px;
718
+ }
719
+
720
+ .dashboard-card-title {
721
+ font-size: 20px;
722
+ font-weight: 600;
723
+ color: var(--text-primary);
724
+ margin: 0;
725
+ display: flex;
726
+ align-items: center;
727
+ gap: 10px;
728
+ }
729
+
730
+ .dashboard-card-icon {
731
+ font-size: 24px;
732
+ opacity: 0.8;
733
+ }
734
+
735
+ .dashboard-card-subtitle {
736
+ font-size: 14px;
737
+ color: var(--text-tertiary);
738
+ margin: 4px 0 0 0;
739
+ }
740
+
741
+ /* ================================
742
+ Stats Grid
743
+ ================================ */
744
+ .dashboard-stats-grid {
745
+ display: grid;
746
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
747
+ gap: 16px;
748
+ margin-bottom: 20px;
749
+ }
750
+
751
+ .dashboard-stat-item {
752
+ text-align: center;
753
+ padding: 12px;
754
+ background: var(--bg-tertiary);
755
+ border-radius: 8px;
756
+ transition: all 0.2s ease;
757
+ position: relative;
758
+ cursor: pointer;
759
+ }
760
+
761
+ .dashboard-stat-item:hover {
762
+ background: var(--border-color);
763
+ transform: scale(1.02);
764
+ }
765
+
766
+ .dashboard-stat-value {
767
+ font-size: 24px;
768
+ font-weight: bold;
769
+ color: var(--text-primary);
770
+ margin-bottom: 4px;
771
+ display: block;
772
+ }
773
+
774
+ .dashboard-stat-label {
775
+ font-size: 12px;
776
+ color: var(--text-tertiary);
777
+ text-transform: uppercase;
778
+ letter-spacing: 0.5px;
779
+ font-weight: 500;
780
+ }
781
+
782
+ /* ================================
783
+ Progress Section
784
+ ================================ */
785
+ .dashboard-progress-section {
786
+ margin-top: 20px;
787
+ padding-top: 20px;
788
+ border-top: 1px solid var(--border-color);
789
+ }
790
+
791
+ .dashboard-progress-title {
792
+ font-size: 14px;
793
+ font-weight: 600;
794
+ color: var(--text-secondary);
795
+ margin-bottom: 12px;
796
+ }
797
+
798
+ .dashboard-progress-items {
799
+ display: flex;
800
+ justify-content: space-between;
801
+ gap: 16px;
802
+ }
803
+
804
+ .dashboard-progress-item {
805
+ text-align: center;
806
+ flex: 1;
807
+ }
808
+
809
+ .dashboard-progress-value {
810
+ font-size: 18px;
811
+ font-weight: bold;
812
+ margin-bottom: 4px;
813
+ }
814
+
815
+ .dashboard-progress-value.positive {
816
+ color: var(--success-color);
817
+ }
818
+
819
+ .dashboard-progress-value.neutral {
820
+ color: var(--text-secondary);
821
+ }
822
+
823
+ .dashboard-progress-label {
824
+ font-size: 11px;
825
+ color: var(--text-tertiary);
826
+ text-transform: uppercase;
827
+ letter-spacing: 0.5px;
828
+ }
829
+
830
+ /* ================================
831
+ Streak Indicator
832
+ ================================ */
833
+ .dashboard-streak-indicator {
834
+ display: inline-flex;
835
+ align-items: center;
836
+ gap: 4px;
837
+ font-size: 12px;
838
+ color: var(--success-color);
839
+ background: rgba(40, 167, 69, 0.1);
840
+ padding: 4px 8px;
841
+ border-radius: 12px;
842
+ font-weight: 500;
843
+ }
844
+
845
+ .dashboard-streak-indicator::before {
846
+ content: '🔥';
847
+ font-size: 14px;
848
+ }
849
+
850
+ /* ================================
851
+ Date Range Card
852
+ ================================ */
853
+ .dashboard-date-range {
854
+ display: flex;
855
+ gap: 20px;
856
+ }
857
+
858
+ .dashboard-date-item {
859
+ flex: 1;
860
+ display: flex;
861
+ flex-direction: column;
862
+ gap: 6px;
863
+ }
864
+
865
+ .dashboard-date-item label {
866
+ font-size: 13px;
867
+ color: var(--text-secondary);
868
+ font-weight: 500;
869
+ }
870
+
871
+ .dashboard-date-input {
872
+ padding: 8px 12px;
873
+ border: 1px solid var(--border-color);
874
+ border-radius: 8px;
875
+ background: var(--bg-tertiary);
876
+ color: var(--text-primary);
877
+ font-size: 14px;
878
+ transition: all 0.2s ease;
879
+ }
880
+
881
+ .dashboard-date-input:focus {
882
+ outline: none;
883
+ border-color: var(--accent-color);
884
+ box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.15);
885
+ }
886
+
887
+ /* ================================
888
+ Tooltip Styles
889
+ ================================ */
890
+ .tooltip {
891
+ position: relative;
892
+ cursor: help;
893
+ }
894
+
895
+ .tooltip::before {
896
+ content: attr(data-tooltip);
897
+ position: absolute;
898
+ bottom: 125%;
899
+ left: 50%;
900
+ transform: translateX(-50%);
901
+ background: rgba(0, 0, 0, 0.9);
902
+ color: white;
903
+ padding: 8px 12px;
904
+ border-radius: 6px;
905
+ font-size: 12px;
906
+ white-space: nowrap;
907
+ opacity: 0;
908
+ visibility: hidden;
909
+ transition: all 0.3s ease;
910
+ z-index: 1000;
911
+ pointer-events: none;
912
+ }
913
+
914
+ .tooltip::after {
915
+ content: '';
916
+ position: absolute;
917
+ bottom: 120%;
918
+ left: 50%;
919
+ transform: translateX(-50%);
920
+ border: 4px solid transparent;
921
+ border-top-color: rgba(0, 0, 0, 0.9);
922
+ opacity: 0;
923
+ visibility: hidden;
924
+ transition: all 0.3s ease;
925
+ z-index: 1000;
926
+ }
927
+
928
+ .tooltip:hover::before,
929
+ .tooltip:hover::after {
930
+ opacity: 1;
931
+ visibility: visible;
932
+ }
933
+
934
+ /* ================================
935
+ Loading State
936
+ ================================ */
937
+ .dashboard-loading {
938
+ display: flex;
939
+ align-items: center;
940
+ justify-content: center;
941
+ min-height: 200px;
942
+ color: var(--text-tertiary);
943
+ }
944
+
945
+ .dashboard-loading .spinner {
946
+ margin-right: 10px;
947
+ }
948
+
949
+ /* ================================
950
+ Error State
951
+ ================================ */
952
+ .dashboard-error {
953
+ text-align: center;
954
+ padding: 40px 20px;
955
+ color: var(--danger-color);
956
+ }
957
+
958
+ .dashboard-error-icon {
959
+ font-size: 48px;
960
+ margin-bottom: 16px;
961
+ opacity: 0.7;
962
+ }
963
+
964
+ .dashboard-error-message {
965
+ font-size: 16px;
966
+ margin-bottom: 16px;
967
+ }
968
+
969
+ .dashboard-retry-btn {
970
+ background-color: var(--accent-color);
971
+ color: white;
972
+ border: none;
973
+ padding: 10px 20px;
974
+ border-radius: 6px;
975
+ cursor: pointer;
976
+ font-size: 14px;
977
+ transition: all 0.3s ease;
978
+ }
979
+
980
+ .dashboard-retry-btn:hover {
981
+ background-color: #0056b3;
982
+ transform: translateY(-1px);
983
+ }
984
+
985
+ /* ================================
986
+ Popups
987
+ ================================ */
988
+ .dashboard-popup,
989
+ .no-data-popup {
990
+ position: fixed;
991
+ top: 0;
992
+ left: 0;
993
+ right: 0;
994
+ bottom: 0;
995
+ display: flex;
996
+ align-items: center;
997
+ justify-content: center;
998
+ background: rgba(0, 0, 0, 0.4);
999
+ z-index: 2000;
1000
+ }
1001
+
1002
+ .hidden {
1003
+ display: none;
1004
+ }
1005
+
1006
+ .dashboard-popup-content,
1007
+ .no-data-content {
1008
+ background: var(--bg-secondary);
1009
+ border: 1px solid var(--border-color);
1010
+ border-radius: 12px;
1011
+ padding: 24px;
1012
+ box-shadow: 0 8px 24px var(--shadow-color);
1013
+ text-align: center;
1014
+ max-width: 400px;
1015
+ width: 90%;
1016
+ }
1017
+
1018
+ .dashboard-popup-icon {
1019
+ font-size: 32px;
1020
+ margin-bottom: 12px;
1021
+ }
1022
+
1023
+ .dashboard-popup-message,
1024
+ .no-data-content p {
1025
+ font-size: 14px;
1026
+ color: var(--text-primary);
1027
+ margin-bottom: 20px;
1028
+ }
1029
+
1030
+ .dashboard-popup-btn,
1031
+ #closeNoDataPopup {
1032
+ background: var(--accent-color);
1033
+ color: white;
1034
+ border: none;
1035
+ padding: 10px 18px;
1036
+ border-radius: 8px;
1037
+ font-size: 14px;
1038
+ cursor: pointer;
1039
+ transition: all 0.2s ease;
1040
+ }
1041
+
1042
+ .dashboard-popup-btn:hover,
1043
+ #closeNoDataPopup:hover {
1044
+ background: #0056b3;
1045
+ transform: translateY(-1px);
1046
+ }
1047
+
1048
+ /* Popup box */
1049
+ .dashboard-popup-content {
1050
+ background: var(--bg-secondary);
1051
+ border-radius: 12px;
1052
+ box-shadow: 0 8px 24px var(--shadow-color);
1053
+ border: 1px solid var(--border-color);
1054
+ padding: 24px;
1055
+ max-width: 320px;
1056
+ text-align: center;
1057
+ animation: popupFadeIn 0.3s ease;
1058
+ }
1059
+
1060
+ .dashboard-popup-icon {
1061
+ font-size: 36px;
1062
+ margin-bottom: 12px;
1063
+ }
1064
+
1065
+ .dashboard-popup-message {
1066
+ font-size: 14px;
1067
+ color: var(--danger-color);
1068
+ margin-bottom: 20px;
1069
+ font-weight: 500;
1070
+ }
1071
+
1072
+ .dashboard-popup-btn {
1073
+ padding: 10px 16px;
1074
+ border-radius: 6px;
1075
+ border: none;
1076
+ background: var(--accent-color);
1077
+ color: #fff;
1078
+ font-size: 14px;
1079
+ font-weight: 600;
1080
+ cursor: pointer;
1081
+ transition: all 0.2s ease;
1082
+ }
1083
+
1084
+ .dashboard-popup-btn:hover {
1085
+ background: var(--accent-color-hover, #0056b3);
1086
+ transform: translateY(-1px);
1087
+ }
1088
+
1089
+ @keyframes popupFadeIn {
1090
+ from { transform: scale(0.95); opacity: 0; }
1091
+ to { transform: scale(1); opacity: 1; }
1092
+ }
1093
+
660
1094
  /* Responsive Design for Goal Progress Chart */
661
1095
  @media (max-width: 768px) {
662
1096
  .goal-progress-grid {
@@ -739,4 +1173,65 @@
739
1173
  margin-left: 0;
740
1174
  align-self: center;
741
1175
  }
742
- }
1176
+ }
1177
+
1178
+ /* Responsive adjustments */
1179
+ @media (max-width: 768px) {
1180
+ .dashboard-date-range {
1181
+ flex-direction: column;
1182
+ gap: 12px;
1183
+ padding: 12px 16px;
1184
+ }
1185
+
1186
+ .dashboard-date-item {
1187
+ width: 100%;
1188
+ }
1189
+
1190
+ .dashboard-fetch-btn {
1191
+ width: 100%;
1192
+ }
1193
+ }
1194
+
1195
+
1196
+ .no-data-popup {
1197
+ position: fixed;
1198
+ inset: 0;
1199
+ background: rgba(0,0,0,0.6);
1200
+ display: flex;
1201
+ justify-content: center;
1202
+ align-items: center;
1203
+ z-index: 2000;
1204
+ }
1205
+
1206
+ .no-data-popup.hidden {
1207
+ display: none;
1208
+ }
1209
+
1210
+ .no-data-content {
1211
+ background: var(--bg-secondary);
1212
+ padding: 20px 30px;
1213
+ border-radius: 10px;
1214
+ text-align: center;
1215
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
1216
+ }
1217
+
1218
+ .no-data-content p {
1219
+ margin-bottom: 16px;
1220
+ color: var(--text-primary);
1221
+ font-size: 15px;
1222
+ }
1223
+
1224
+ .no-data-content button {
1225
+ padding: 8px 16px;
1226
+ background: var(--accent-color);
1227
+ border: none;
1228
+ border-radius: 6px;
1229
+ color: #fff;
1230
+ font-weight: 600;
1231
+ cursor: pointer;
1232
+ transition: all 0.2s ease;
1233
+ }
1234
+
1235
+ .no-data-content button:hover {
1236
+ background: #0056b3;
1237
+ }
@@ -10,6 +10,8 @@ document.addEventListener('DOMContentLoaded', function () {
10
10
  const ankiTotalKanji = document.getElementById('ankiTotalKanji');
11
11
  const gsmTotalKanji = document.getElementById('gsmTotalKanji');
12
12
  const ankiCoverage = document.getElementById('ankiCoverage');
13
+ const fromDateInput = document.getElementById('fromDate');
14
+ const toDateInput = document.getElementById('toDate');
13
15
 
14
16
  console.log('Found DOM elements:', {
15
17
  loading, error, missingKanjiGrid, missingKanjiCount,
@@ -62,12 +64,18 @@ document.addEventListener('DOMContentLoaded', function () {
62
64
  renderKanjiGrid(data.missing_kanji);
63
65
  }
64
66
 
65
- async function loadStats() {
67
+ async function loadStats(start_timestamp = null, end_timestamp = null) {
66
68
  console.log('Loading Anki stats...');
67
69
  showLoading(true);
68
70
  showError(false);
69
71
  try {
70
- const resp = await fetch('/api/anki_stats');
72
+ // Build URL with optional query params
73
+ const params = new URLSearchParams();
74
+ if (start_timestamp) params.append('start_timestamp', start_timestamp);
75
+ if (end_timestamp) params.append('end_timestamp', end_timestamp);
76
+ const url = '/api/anki_stats' + (params.toString() ? `?${params.toString()}` : '');
77
+
78
+ const resp = await fetch(url);
71
79
  if (!resp.ok) throw new Error('Failed to load');
72
80
  const data = await resp.json();
73
81
  console.log('Received data:', data);
@@ -80,5 +88,81 @@ document.addEventListener('DOMContentLoaded', function () {
80
88
  }
81
89
  }
82
90
 
83
- loadStats();
91
+ function getUnixTimestampsInMilliseconds(startDate, endDate) {
92
+ // Parse the start date and create a Date object at the beginning of the day
93
+ const start = new Date(startDate + 'T00:00:00');
94
+ const startTimestamp = start.getTime();
95
+
96
+ // Parse the end date and create a Date object at the end of the day
97
+ const end = new Date(endDate + 'T23:59:59.999');
98
+ const endTimestamp = end.getTime();
99
+
100
+ return { startTimestamp, endTimestamp };
101
+ }
102
+
103
+ document.addEventListener("datesSetAnki", () => {
104
+ const fromDate = sessionStorage.getItem("fromDateAnki");
105
+ const toDate = sessionStorage.getItem("toDateAnki");
106
+ const { startTimestamp, endTimestamp } = getUnixTimestampsInMilliseconds(fromDate, toDate);
107
+
108
+ loadStats(startTimestamp, endTimestamp);
109
+ });
110
+
111
+ function initializeDates() {
112
+ const fromDateInput = document.getElementById('fromDate');
113
+ const toDateInput = document.getElementById('toDate');
114
+
115
+ const fromDate = sessionStorage.getItem("fromDateAnki");
116
+ const toDate = sessionStorage.getItem("toDateAnki");
117
+
118
+ if (!(fromDate && toDate)) {
119
+ fetch('/api/anki_earliest_date')
120
+ .then(response => response.json())
121
+ .then(response_json => {
122
+ // Get first date in ms from API
123
+ const firstDateinMs = response_json.earliest_card;
124
+ const firstDateObject = new Date(firstDateinMs);
125
+ fromDateInput.value = firstDateObject.toISOString().split('T')[0];
126
+
127
+ // Get today's date
128
+ const today = new Date();
129
+ toDateInput.value = today.toISOString().split("T")[0];
130
+
131
+ // Save in sessionStorage
132
+ sessionStorage.setItem("fromDateAnki", firstDateObject.toISOString().split('T')[0]);
133
+ sessionStorage.setItem("toDateAnki", today.toISOString().split("T")[0]);
134
+
135
+ document.dispatchEvent(new Event("datesSetAnki"));
136
+ });
137
+ } else {
138
+ // If values already in sessionStorage, set inputs from there
139
+ fromDateInput.value = fromDate;
140
+ toDateInput.value = toDate;
141
+ console.log("already in session storage, dispatching datesSetAnki")
142
+ document.dispatchEvent(new Event("datesSetAnki"));
143
+ }
144
+ }
145
+
146
+ function handleDateChange() {
147
+ const fromDateStr = fromDateInput.value;
148
+ const toDateStr = toDateInput.value;
149
+
150
+ sessionStorage.setItem("fromDateAnki", fromDateStr);
151
+ sessionStorage.setItem("toDateAnki", toDateStr);
152
+
153
+ // Validate date order
154
+ if (fromDateStr && toDateStr && new Date(fromDateStr) > new Date(toDateStr)) {
155
+ popup.classList.remove("hidden");
156
+ return;
157
+ }
158
+
159
+ const { startTimestamp, endTimestamp } = getUnixTimestampsInMilliseconds(fromDateStr, toDateStr);
160
+
161
+ loadStats(startTimestamp, endTimestamp)
162
+ }
163
+
164
+ fromDateInput.addEventListener("change", handleDateChange);
165
+ toDateInput.addEventListener("change", handleDateChange);
166
+
167
+ initializeDates();
84
168
  });
@@ -227,7 +227,6 @@ class SettingsManager {
227
227
  // Optional elements that may not exist on all pages
228
228
  this.afkTimerInput = document.getElementById('afkTimer');
229
229
  this.sessionGapInput = document.getElementById('sessionGap');
230
- this.heatmapYearSelect = document.getElementById('heatmapYear');
231
230
  this.streakRequirementInput = document.getElementById('streakRequirement');
232
231
  this.readingHoursTargetInput = document.getElementById('readingHoursTarget');
233
232
  this.characterCountTargetInput = document.getElementById('characterCountTarget');
@@ -263,27 +262,17 @@ class SettingsManager {
263
262
  // }
264
263
 
265
264
  // Clear messages when user starts typing
266
- [this.afkTimerInput, this.sessionGapInput, this.heatmapYearSelect, this.streakRequirementInput,
265
+ [this.afkTimerInput, this.sessionGapInput, this.streakRequirementInput,
267
266
  this.readingHoursTargetInput, this.characterCountTargetInput, this.gamesTargetInput]
268
267
  .filter(Boolean)
269
268
  .forEach(input => {
270
269
  input.addEventListener('input', () => this.clearMessages());
271
270
  });
272
-
273
- // Handle year selection change
274
- if (this.heatmapYearSelect) {
275
- this.heatmapYearSelect.addEventListener('change', (e) => {
276
- const selectedYear = e.target.value;
277
- localStorage.setItem('selectedHeatmapYear', selectedYear);
278
- this.refreshHeatmapData(selectedYear);
279
- });
280
- }
281
271
  }
282
272
 
283
273
  async openModal() {
284
274
  try {
285
275
  await this.loadCurrentSettings();
286
- await this.loadAvailableYears();
287
276
  this.showModal();
288
277
  } catch (error) {
289
278
  console.error('Error opening settings modal:', error);
@@ -336,48 +325,12 @@ class SettingsManager {
336
325
  if (this.gamesTargetInput) {
337
326
  this.gamesTargetInput.value = settings.games_target || 100;
338
327
  }
339
-
340
- // Load saved year preference
341
- const savedYear = localStorage.getItem('selectedHeatmapYear') || 'all';
342
- if (this.heatmapYearSelect) {
343
- this.heatmapYearSelect.value = savedYear;
344
- }
345
- }
346
-
347
- async loadAvailableYears() {
348
- if (!this.heatmapYearSelect) return;
349
-
350
- try {
351
- const response = await fetch('/api/stats');
352
- if (!response.ok) throw new Error('Failed to fetch stats');
353
-
354
- const data = await response.json();
355
- const availableYears = Object.keys(data.heatmapData || {}).sort().reverse();
356
-
357
- // Clear existing options except "All Years"
358
- this.heatmapYearSelect.innerHTML = '<option value="all">All Years</option>';
359
-
360
- // Add available years
361
- availableYears.forEach(year => {
362
- const option = document.createElement('option');
363
- option.value = year;
364
- option.textContent = year;
365
- this.heatmapYearSelect.appendChild(option);
366
- });
367
-
368
- // Restore saved selection
369
- const savedYear = localStorage.getItem('selectedHeatmapYear') || 'all';
370
- this.heatmapYearSelect.value = savedYear;
371
-
372
- } catch (error) {
373
- console.error('Error loading available years:', error);
374
- }
375
328
  }
376
329
 
377
330
  async refreshHeatmapData(selectedYear) {
378
331
  try {
379
332
  if (typeof loadStatsData === 'function') {
380
- await loadStatsData(selectedYear);
333
+ await loadStatsData(start_timestamp = null, end_timestamp = null);
381
334
  }
382
335
  } catch (error) {
383
336
  console.error('Error refreshing heatmap data:', error);