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.
- GameSentenceMiner/anki.py +31 -3
- GameSentenceMiner/config_gui.py +26 -2
- GameSentenceMiner/gametext.py +4 -3
- GameSentenceMiner/gsm.py +19 -23
- GameSentenceMiner/obs.py +17 -7
- GameSentenceMiner/ocr/owocr_helper.py +11 -8
- GameSentenceMiner/owocr/owocr/run.py +11 -5
- GameSentenceMiner/util/configuration.py +7 -5
- GameSentenceMiner/util/db.py +176 -8
- GameSentenceMiner/util/downloader/download_tools.py +57 -24
- GameSentenceMiner/util/ffmpeg.py +5 -2
- GameSentenceMiner/util/get_overlay_coords.py +3 -0
- GameSentenceMiner/util/gsm_utils.py +0 -54
- GameSentenceMiner/vad.py +5 -2
- GameSentenceMiner/web/database_api.py +12 -1
- GameSentenceMiner/web/gsm_websocket.py +1 -1
- GameSentenceMiner/web/static/css/shared.css +20 -0
- GameSentenceMiner/web/static/css/stats.css +496 -1
- GameSentenceMiner/web/static/js/anki_stats.js +87 -3
- GameSentenceMiner/web/static/js/shared.js +2 -49
- GameSentenceMiner/web/static/js/stats.js +274 -39
- GameSentenceMiner/web/templates/anki_stats.html +36 -0
- GameSentenceMiner/web/templates/index.html +1 -1
- GameSentenceMiner/web/templates/stats.html +35 -15
- GameSentenceMiner/web/texthooking_page.py +31 -8
- {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/RECORD +31 -31
- {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.17.0.dist-info → gamesentenceminer-2.17.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
333
|
+
await loadStatsData(start_timestamp = null, end_timestamp = null);
|
|
381
334
|
}
|
|
382
335
|
} catch (error) {
|
|
383
336
|
console.error('Error refreshing heatmap data:', error);
|