GameSentenceMiner 2.16.5__py3-none-any.whl → 2.16.7__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.
@@ -468,6 +468,249 @@
468
468
  }
469
469
  }
470
470
 
471
+ /* Goal Progress Chart Styles */
472
+ .goal-progress-chart {
473
+ background: var(--bg-secondary);
474
+ border-radius: 12px;
475
+ box-shadow: 0 4px 16px var(--shadow-color);
476
+ border: 1px solid var(--border-color);
477
+ padding: 24px;
478
+ transition: all 0.3s ease;
479
+ position: relative;
480
+ overflow: hidden;
481
+ }
482
+
483
+ .goal-progress-chart::before {
484
+ content: '';
485
+ position: absolute;
486
+ top: 0;
487
+ left: 0;
488
+ right: 0;
489
+ height: 4px;
490
+ background: linear-gradient(90deg, #2ee6e0, #3be62f, #e6dc2e);
491
+ border-radius: 12px 12px 0 0;
492
+ }
493
+
494
+ .goal-progress-chart:hover {
495
+ transform: translateY(-2px);
496
+ box-shadow: 0 8px 24px var(--shadow-color);
497
+ }
498
+
499
+ .goal-progress-grid {
500
+ display: grid;
501
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
502
+ gap: 24px;
503
+ margin-top: 20px;
504
+ }
505
+
506
+ .goal-progress-item {
507
+ background: var(--bg-tertiary);
508
+ border-radius: 8px;
509
+ padding: 20px;
510
+ border: 1px solid var(--border-color);
511
+ transition: all 0.3s ease;
512
+ }
513
+
514
+ .goal-progress-item:hover {
515
+ background: var(--border-color);
516
+ transform: scale(1.02);
517
+ }
518
+
519
+ .goal-progress-header {
520
+ display: flex;
521
+ justify-content: space-between;
522
+ align-items: center;
523
+ margin-bottom: 12px;
524
+ }
525
+
526
+ .goal-progress-label {
527
+ display: flex;
528
+ align-items: center;
529
+ gap: 8px;
530
+ font-weight: 600;
531
+ color: var(--text-primary);
532
+ font-size: 16px;
533
+ }
534
+
535
+ .goal-icon {
536
+ font-size: 20px;
537
+ opacity: 0.8;
538
+ }
539
+
540
+ .goal-progress-values {
541
+ display: flex;
542
+ align-items: center;
543
+ gap: 4px;
544
+ font-size: 14px;
545
+ font-weight: 600;
546
+ }
547
+
548
+ .goal-current {
549
+ color: var(--text-primary);
550
+ }
551
+
552
+ .goal-separator {
553
+ color: var(--text-tertiary);
554
+ margin: 0 4px;
555
+ }
556
+
557
+ .goal-target {
558
+ color: var(--text-secondary);
559
+ }
560
+
561
+ .goal-progress-bar {
562
+ height: 12px;
563
+ background: var(--bg-primary);
564
+ border-radius: 6px;
565
+ overflow: hidden;
566
+ margin-bottom: 12px;
567
+ border: 1px solid var(--border-color);
568
+ }
569
+
570
+ .goal-progress-fill {
571
+ height: 100%;
572
+ border-radius: 6px;
573
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
574
+ background: var(--text-tertiary); /* Default color for 0% */
575
+ position: relative;
576
+ overflow: hidden;
577
+ }
578
+
579
+ .goal-progress-fill[data-percentage="100"] {
580
+ background: #2ee6e0; /* 100% completion - cyan */
581
+ }
582
+
583
+ .goal-progress-fill[data-percentage="75"] {
584
+ background: #3be62f; /* 75% completion - green */
585
+ }
586
+
587
+ .goal-progress-fill[data-percentage="50"] {
588
+ background: #3be62f; /* 50% completion - green */
589
+ }
590
+
591
+ .goal-progress-fill[data-percentage="25"] {
592
+ background: #e6dc2e; /* 25% completion - yellow */
593
+ }
594
+
595
+ /* Dynamic progress fill colors based on completion percentage */
596
+ .goal-progress-fill.completion-100 { background: #2ee6e0; }
597
+ .goal-progress-fill.completion-75 { background: #3be62f; }
598
+ .goal-progress-fill.completion-50 { background: #3be62f; }
599
+ .goal-progress-fill.completion-25 { background: #e6dc2e; }
600
+ .goal-progress-fill.completion-0 {
601
+ background: var(--text-tertiary);
602
+ }
603
+
604
+ [data-theme="dark"] .goal-progress-fill.completion-0 {
605
+ background: #333;
606
+ }
607
+
608
+ [data-theme="light"] .goal-progress-fill.completion-0 {
609
+ background: #fff;
610
+ border: 1px solid var(--border-color);
611
+ }
612
+
613
+ .goal-progress-info {
614
+ display: flex;
615
+ justify-content: space-between;
616
+ align-items: center;
617
+ font-size: 12px;
618
+ }
619
+
620
+ .goal-percentage {
621
+ font-weight: 600;
622
+ color: var(--text-primary);
623
+ }
624
+
625
+ .goal-projection {
626
+ color: var(--text-secondary);
627
+ font-style: italic;
628
+ }
629
+
630
+ /* Loading and Error States */
631
+ .goal-progress-loading {
632
+ display: flex;
633
+ align-items: center;
634
+ justify-content: center;
635
+ padding: 40px;
636
+ color: var(--text-tertiary);
637
+ }
638
+
639
+ .goal-progress-loading .spinner {
640
+ margin-right: 10px;
641
+ }
642
+
643
+ .goal-progress-error {
644
+ text-align: center;
645
+ padding: 40px 20px;
646
+ color: var(--danger-color);
647
+ }
648
+
649
+ .goal-progress-error-icon {
650
+ font-size: 48px;
651
+ margin-bottom: 16px;
652
+ opacity: 0.7;
653
+ }
654
+
655
+ .goal-progress-error-message {
656
+ font-size: 16px;
657
+ margin-bottom: 16px;
658
+ }
659
+
660
+ /* Responsive Design for Goal Progress Chart */
661
+ @media (max-width: 768px) {
662
+ .goal-progress-grid {
663
+ grid-template-columns: 1fr;
664
+ gap: 16px;
665
+ }
666
+
667
+ .goal-progress-item {
668
+ padding: 16px;
669
+ }
670
+
671
+ .goal-progress-header {
672
+ flex-direction: column;
673
+ align-items: flex-start;
674
+ gap: 8px;
675
+ }
676
+
677
+ .goal-progress-values {
678
+ font-size: 12px;
679
+ }
680
+
681
+ .goal-progress-label {
682
+ font-size: 14px;
683
+ }
684
+
685
+ .goal-progress-info {
686
+ flex-direction: column;
687
+ align-items: flex-start;
688
+ gap: 4px;
689
+ }
690
+ }
691
+
692
+ @media (max-width: 480px) {
693
+ .goal-progress-chart {
694
+ padding: 16px;
695
+ }
696
+
697
+ .goal-progress-item {
698
+ padding: 12px;
699
+ }
700
+
701
+ .goal-icon {
702
+ font-size: 16px;
703
+ }
704
+
705
+ .goal-progress-label {
706
+ font-size: 13px;
707
+ }
708
+
709
+ .goal-progress-bar {
710
+ height: 10px;
711
+ }
712
+ }
713
+
471
714
  /* Navigation styles */
472
715
  .navigation .nav-link:hover {
473
716
  background-color: var(--accent-color) !important;
@@ -13,12 +13,14 @@ class SentenceSearchApp {
13
13
  this.errorMessage = document.getElementById('errorMessage');
14
14
  this.searchStats = document.getElementById('searchStats');
15
15
  this.searchTime = document.getElementById('searchTime');
16
+ this.regexCheckbox = document.getElementById('regexCheckbox');
16
17
 
17
18
  this.currentPage = 1;
18
19
  this.pageSize = 20;
19
20
  this.searchTimeout = null;
20
21
  this.currentQuery = '';
21
22
  this.totalResults = 0;
23
+ this.currentUseRegex = false;
22
24
 
23
25
  // Move initialization logic to async method
24
26
  this.initialize();
@@ -66,6 +68,13 @@ class SentenceSearchApp {
66
68
  this.currentPage++;
67
69
  this.performSearch();
68
70
  });
71
+
72
+ // Regex checkbox toggle triggers search
73
+ if (this.regexCheckbox) {
74
+ this.regexCheckbox.addEventListener('change', () => {
75
+ this.performSearch();
76
+ });
77
+ }
69
78
  }
70
79
 
71
80
  async loadGamesList() {
@@ -94,22 +103,24 @@ class SentenceSearchApp {
94
103
  const query = this.searchInput.value.trim();
95
104
  const gameFilter = this.gameFilter.value;
96
105
  const sortBy = this.sortFilter.value;
97
-
98
- // Reset to first page for new searches
99
- if (query !== this.currentQuery) {
106
+ const useRegex = this.regexCheckbox && this.regexCheckbox.checked;
107
+
108
+ // Reset to first page for new searches or regex toggle
109
+ if (query !== this.currentQuery || useRegex !== this.currentUseRegex) {
100
110
  this.currentPage = 1;
101
111
  }
102
112
  this.currentQuery = query;
103
-
113
+ this.currentUseRegex = useRegex;
114
+
104
115
  // Show appropriate state
105
116
  if (!query) {
106
117
  this.showEmptyState();
107
118
  return;
108
119
  }
109
-
120
+
110
121
  this.showLoadingState();
111
122
  const startTime = Date.now();
112
-
123
+
113
124
  try {
114
125
  const params = new URLSearchParams({
115
126
  q: query,
@@ -117,22 +128,25 @@ class SentenceSearchApp {
117
128
  page_size: this.pageSize,
118
129
  sort: sortBy
119
130
  });
120
-
131
+
121
132
  if (gameFilter) {
122
133
  params.append('game', gameFilter);
123
134
  }
124
-
135
+ if (useRegex) {
136
+ params.append('use_regex', 'true');
137
+ }
138
+
125
139
  const response = await fetch(`/api/search-sentences?${params}`);
126
140
  const data = await response.json();
127
-
141
+
128
142
  const searchTime = Date.now() - startTime;
129
-
143
+
130
144
  if (!response.ok) {
131
145
  throw new Error(data.error || 'Search failed');
132
146
  }
133
-
147
+
134
148
  this.displayResults(data, searchTime);
135
-
149
+
136
150
  } catch (error) {
137
151
  this.showErrorState(error.message);
138
152
  }
@@ -198,17 +212,27 @@ class SentenceSearchApp {
198
212
 
199
213
  highlightSearchTerms(text, query) {
200
214
  if (!query) return escapeHtml(text);
201
-
215
+
216
+ const useRegex = this.regexCheckbox && this.regexCheckbox.checked;
202
217
  const escapedText = escapeHtml(text);
203
- const searchTerms = query.split(' ').filter(term => term.length > 0);
204
-
205
- let result = escapedText;
206
- searchTerms.forEach(term => {
207
- const regex = new RegExp(`(${escapeRegex(term)})`, 'gi');
208
- result = result.replace(regex, '<span class="search-highlight">$1</span>');
209
- });
210
-
211
- return result;
218
+
219
+ if (useRegex) {
220
+ try {
221
+ const pattern = new RegExp(query, 'gi');
222
+ return escapedText.replace(pattern, '<span class="search-highlight">$&</span>');
223
+ } catch (e) {
224
+ // If invalid regex, just return escaped text
225
+ return escapedText;
226
+ }
227
+ } else {
228
+ const searchTerms = query.split(' ').filter(term => term.length > 0);
229
+ let result = escapedText;
230
+ searchTerms.forEach(term => {
231
+ const regex = new RegExp(`(${escapeRegex(term)})`, 'gi');
232
+ result = result.replace(regex, '<span class="search-highlight">$1</span>');
233
+ });
234
+ return result;
235
+ }
212
236
  }
213
237
 
214
238
  updatePagination(data) {
@@ -19,12 +19,17 @@ function closeModal(modalId) {
19
19
 
20
20
  // Initialize modal close functionality (backdrop clicks and ESC key)
21
21
  function initializeModalHandlers() {
22
- // Close modals when clicking outside (backdrop)
22
+ // Close modals only if both mousedown and mouseup are on the backdrop
23
23
  document.querySelectorAll('.modal').forEach(modal => {
24
- modal.addEventListener('click', (e) => {
25
- if (e.target === modal) {
24
+ let backdropMouseDown = false;
25
+ modal.addEventListener('mousedown', (e) => {
26
+ backdropMouseDown = (e.target === modal);
27
+ });
28
+ modal.addEventListener('mouseup', (e) => {
29
+ if (backdropMouseDown && e.target === modal) {
26
30
  closeModal(modal.id);
27
31
  }
32
+ backdropMouseDown = false;
28
33
  });
29
34
  });
30
35
 
@@ -224,6 +229,9 @@ class SettingsManager {
224
229
  this.sessionGapInput = document.getElementById('sessionGap');
225
230
  this.heatmapYearSelect = document.getElementById('heatmapYear');
226
231
  this.streakRequirementInput = document.getElementById('streakRequirement');
232
+ this.readingHoursTargetInput = document.getElementById('readingHoursTarget');
233
+ this.characterCountTargetInput = document.getElementById('characterCountTarget');
234
+ this.gamesTargetInput = document.getElementById('gamesTarget');
227
235
  }
228
236
 
229
237
  attachEventListeners() {
@@ -245,17 +253,18 @@ class SettingsManager {
245
253
  this.saveSettingsBtn.addEventListener('click', () => this.saveSettings());
246
254
  }
247
255
 
248
- // Close modal when clicking outside
249
- if (this.settingsModal) {
250
- this.settingsModal.addEventListener('click', (e) => {
251
- if (e.target === this.settingsModal) {
252
- this.closeModal();
253
- }
254
- });
255
- }
256
+ // // Close modal when clicking outside
257
+ // if (this.settingsModal) {
258
+ // this.settingsModal.addEventListener('click', (e) => {
259
+ // if (e.target === this.settingsModal) {
260
+ // this.closeModal();
261
+ // }
262
+ // });
263
+ // }
256
264
 
257
265
  // Clear messages when user starts typing
258
- [this.afkTimerInput, this.sessionGapInput, this.heatmapYearSelect, this.streakRequirementInput]
266
+ [this.afkTimerInput, this.sessionGapInput, this.heatmapYearSelect, this.streakRequirementInput,
267
+ this.readingHoursTargetInput, this.characterCountTargetInput, this.gamesTargetInput]
259
268
  .filter(Boolean)
260
269
  .forEach(input => {
261
270
  input.addEventListener('input', () => this.clearMessages());
@@ -318,6 +327,15 @@ class SettingsManager {
318
327
  if (this.streakRequirementInput) {
319
328
  this.streakRequirementInput.value = settings.streak_requirement_hours || 1;
320
329
  }
330
+ if (this.readingHoursTargetInput) {
331
+ this.readingHoursTargetInput.value = settings.reading_hours_target || 1500;
332
+ }
333
+ if (this.characterCountTargetInput) {
334
+ this.characterCountTargetInput.value = settings.character_count_target || 25000000;
335
+ }
336
+ if (this.gamesTargetInput) {
337
+ this.gamesTargetInput.value = settings.games_target || 100;
338
+ }
321
339
 
322
340
  // Load saved year preference
323
341
  const savedYear = localStorage.getItem('selectedHeatmapYear') || 'all';
@@ -399,6 +417,33 @@ class SettingsManager {
399
417
  settings.streak_requirement_hours = streakRequirement;
400
418
  }
401
419
 
420
+ if (this.readingHoursTargetInput) {
421
+ const readingHoursTarget = parseInt(this.readingHoursTargetInput.value);
422
+ if (isNaN(readingHoursTarget) || readingHoursTarget < 1 || readingHoursTarget > 10000) {
423
+ this.showError('Reading hours target must be between 1 and 10,000 hours');
424
+ return;
425
+ }
426
+ settings.reading_hours_target = readingHoursTarget;
427
+ }
428
+
429
+ if (this.characterCountTargetInput) {
430
+ const characterCountTarget = parseInt(this.characterCountTargetInput.value);
431
+ if (isNaN(characterCountTarget) || characterCountTarget < 1000 || characterCountTarget > 1000000000) {
432
+ this.showError('Character count target must be between 1,000 and 1,000,000,000 characters');
433
+ return;
434
+ }
435
+ settings.character_count_target = characterCountTarget;
436
+ }
437
+
438
+ if (this.gamesTargetInput) {
439
+ const gamesTarget = parseInt(this.gamesTargetInput.value);
440
+ if (isNaN(gamesTarget) || gamesTarget < 1 || gamesTarget > 1000) {
441
+ this.showError('Games target must be between 1 and 1,000');
442
+ return;
443
+ }
444
+ settings.games_target = gamesTarget;
445
+ }
446
+
402
447
  // Show loading state
403
448
  if (this.saveSettingsBtn) {
404
449
  this.saveSettingsBtn.disabled = true;
@@ -421,6 +466,9 @@ class SettingsManager {
421
466
 
422
467
  this.showSuccess('Settings saved successfully! Changes will apply to new calculations.');
423
468
 
469
+ // Dispatch event to notify other components that settings were updated
470
+ window.dispatchEvent(new CustomEvent('settingsUpdated'));
471
+
424
472
  // Auto-close modal after 2 seconds
425
473
  setTimeout(() => {
426
474
  this.closeModal();