@algoux/standard-ranklist-renderer-component-angular 0.6.2 → 0.6.4

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.
Files changed (26) hide show
  1. package/README.md +13 -0
  2. package/dist/README.md +13 -0
  3. package/dist/esm2022/lib/index.mjs +2 -1
  4. package/dist/esm2022/lib/modal/default-solution-modal.component.mjs +6 -3
  5. package/dist/esm2022/lib/modal/default-user-modal.component.mjs +23 -15
  6. package/dist/esm2022/lib/progress/progress-utils.mjs +2 -45
  7. package/dist/esm2022/lib/ranklist/problem-header-cell-template.directive.mjs +18 -0
  8. package/dist/esm2022/lib/ranklist/ranklist-utils.mjs +2 -110
  9. package/dist/esm2022/lib/ranklist/ranklist.component.mjs +161 -65
  10. package/dist/esm2022/lib/ranklist/status-cell-template.directive.mjs +1 -1
  11. package/dist/esm2022/lib/ranklist/user-cell-template.directive.mjs +1 -1
  12. package/dist/esm2022/lib/types.mjs +1 -1
  13. package/dist/fesm2022/algoux-standard-ranklist-renderer-component-angular.mjs +208 -238
  14. package/dist/fesm2022/algoux-standard-ranklist-renderer-component-angular.mjs.map +1 -1
  15. package/dist/lib/index.d.ts +2 -1
  16. package/dist/lib/modal/default-solution-modal.component.d.ts +2 -1
  17. package/dist/lib/modal/default-user-modal.component.d.ts +5 -3
  18. package/dist/lib/progress/progress-bar.component.d.ts +1 -1
  19. package/dist/lib/progress/progress-utils.d.ts +1 -15
  20. package/dist/lib/ranklist/problem-header-cell-template.directive.d.ts +20 -0
  21. package/dist/lib/ranklist/ranklist-utils.d.ts +1 -22
  22. package/dist/lib/ranklist/ranklist.component.d.ts +12 -2
  23. package/dist/lib/ranklist/status-cell-template.directive.d.ts +1 -0
  24. package/dist/lib/ranklist/user-cell-template.directive.d.ts +1 -0
  25. package/dist/lib/types.d.ts +1 -29
  26. package/package.json +3 -3
@@ -2,116 +2,8 @@ import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { EventEmitter, ViewChild, Output, Input, Component, inject, TemplateRef, Directive, ContentChild, ChangeDetectionStrategy } from '@angular/core';
5
- import { EnumTheme, formatTimeDuration, numberToAlphabet, resolveText, secToTimeStr, resolveStyle, resolveUserMarkers, canRegenerateRanklist } from '@algoux/standard-ranklist-utils';
6
- import { ensureModalInteractionTracker, unlockModalBodyScroll, SRK_ANIMATED_MODAL_ROOT_CLASS, resolveModalTransformOrigin, MODAL_ANIMATION_DURATION_MS, lockModalBodyScroll, registerModalFocusScope, unregisterModalFocusScope, srkSupportedVersions, calculateProblemStatisticsFooter, caniuse, shouldShowTimeColumn as shouldShowTimeColumn$1, getProblemHeaderBackgroundImage as getProblemHeaderBackgroundImage$1, getMarkerPresentation as getMarkerPresentation$1, resolveSrkAssetUrl as resolveSrkAssetUrl$1, calculateDirtPercentage, calculateSEValue, getRankProblemStatusCellPresentation, captureModalTriggerPointFromMouseEvent, formatProblemStatisticsAcceptedMinute, formatProblemStatisticsAverageHardness, formatProblemStatisticsPercent } from '@algoux/standard-ranklist-renderer-component-core';
7
-
8
- const defaultBackgroundColor = {
9
- [EnumTheme.light]: '#ffffff',
10
- [EnumTheme.dark]: '#191919',
11
- };
12
- function resolveSrkAssetUrl(url, field, formatter) {
13
- if (typeof formatter === 'function') {
14
- return formatter(url, field);
15
- }
16
- if (url.startsWith('//')) {
17
- return url;
18
- }
19
- const protocolMatch = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
20
- if (protocolMatch) {
21
- const protocol = protocolMatch[1].toLowerCase();
22
- if (protocol === 'http' || protocol === 'https' || protocol === 'data') {
23
- return url;
24
- }
25
- console.warn(`unsupported protocol "${protocol}" in srk asset url:`, url);
26
- return '';
27
- }
28
- console.warn('unsupported srk asset url:', url);
29
- return '';
30
- }
31
- function getAcceptedStatusDetails(status) {
32
- return `${status.tries}/${status.time ? formatTimeDuration(status.time, 'min', Math.floor) : '-'}`;
33
- }
34
- function getSolutionResultMeta(result) {
35
- switch (result) {
36
- case 'FB':
37
- return { label: 'First Blood', className: 'srk-preset-result-fb' };
38
- case 'AC':
39
- return { label: 'Accepted', className: 'srk-preset-result-ac' };
40
- case 'RJ':
41
- return { label: 'Rejected', className: 'srk-preset-result-rj' };
42
- case '?':
43
- return { label: 'Frozen', className: 'srk-preset-result-fz' };
44
- case 'WA':
45
- return { label: 'Wrong Answer', className: 'srk-preset-result-rj' };
46
- case 'PE':
47
- return { label: 'Presentation Error', className: 'srk-preset-result-rj' };
48
- case 'TLE':
49
- return { label: 'Time Limit Exceeded', className: 'srk-preset-result-rj' };
50
- case 'MLE':
51
- return { label: 'Memory Limit Exceeded', className: 'srk-preset-result-rj' };
52
- case 'OLE':
53
- return { label: 'Output Limit Exceeded', className: 'srk-preset-result-rj' };
54
- case 'RTE':
55
- return { label: 'Runtime Error', className: 'srk-preset-result-rj' };
56
- case 'NOUT':
57
- return { label: 'No Output' };
58
- case 'CE':
59
- return { label: 'Compile Error' };
60
- case 'UKE':
61
- return { label: 'Unknown Error' };
62
- case null:
63
- return { label: '--' };
64
- default:
65
- return { label: result };
66
- }
67
- }
68
- function getSolutionModalTitle(problemIndex, user) {
69
- return `Solutions of ${numberToAlphabet(problemIndex)} (${resolveText(user.name)})`;
70
- }
71
- function formatSolutionTimestamp(solution) {
72
- return secToTimeStr(formatTimeDuration(solution.time, 's'));
73
- }
74
- function getProblemHeaderBackgroundImage(style, theme) {
75
- const { backgroundColor } = resolveStyle(style || {});
76
- const resolvedColor = backgroundColor[theme] || defaultBackgroundColor[theme];
77
- const bgColorAlphaStr = withAlpha(resolvedColor, 0.27);
78
- return `linear-gradient(180deg, ${resolvedColor} 0%, ${resolvedColor} 10%, ${bgColorAlphaStr} 10%, transparent 100%)`;
79
- }
80
- function getMarkerPresentation(marker, theme) {
81
- if (typeof marker.style === 'string') {
82
- return { className: `srk-preset-marker-${marker.style}` };
83
- }
84
- if (marker.style) {
85
- return {
86
- style: {
87
- backgroundColor: resolveStyle(marker.style).backgroundColor[theme],
88
- },
89
- };
90
- }
91
- return {};
92
- }
93
- function shouldShowTimeColumn(rows) {
94
- return rows.some((row) => Boolean(row.score?.time));
95
- }
96
- function withAlpha(color, alpha) {
97
- const hexMatch = color.match(/^#([0-9a-f]{6})$/i);
98
- if (hexMatch) {
99
- const value = hexMatch[1];
100
- const red = parseInt(value.slice(0, 2), 16);
101
- const green = parseInt(value.slice(2, 4), 16);
102
- const blue = parseInt(value.slice(4, 6), 16);
103
- return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
104
- }
105
- const rgbMatch = color.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
106
- if (rgbMatch) {
107
- return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${alpha})`;
108
- }
109
- const rgbaMatch = color.match(/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)$/i);
110
- if (rgbaMatch) {
111
- return `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, ${alpha})`;
112
- }
113
- return color;
114
- }
5
+ import { ensureModalInteractionTracker, unlockModalBodyScroll, SRK_ANIMATED_MODAL_ROOT_CLASS, resolveModalTransformOrigin, MODAL_ANIMATION_DURATION_MS, lockModalBodyScroll, registerModalFocusScope, unregisterModalFocusScope, formatSolutionTimestamp, getSolutionModalTitle, getSolutionResultMeta, getMarkerPresentation, resolveSrkAssetUrl, formatTeamMemberName, getProgressDurationMinutes, getProgressMaxAvailableMinutes, isProgressEnded, getProgressMetrics, srkSupportedVersions, calculateProblemStatisticsFooter, caniuse, shouldShowTimeColumn, getProblemHeaderBackgroundImage, calculateDirtPercentage, calculateSEValue, getRankProblemStatusCellPresentation, captureModalTriggerPointFromMouseEvent, formatProblemStatisticsAcceptedMinute, formatProblemStatisticsAverageHardness, formatProblemStatisticsPercent } from '@algoux/standard-ranklist-renderer-component-core';
6
+ import { EnumTheme, resolveUserMarkers, resolveText, secToTimeStr, numberToAlphabet, formatTimeDuration, resolveStyle } from '@algoux/standard-ranklist-utils';
115
7
 
116
8
  class ModalComponent {
117
9
  open = false;
@@ -382,6 +274,7 @@ class DefaultSolutionModalComponent {
382
274
  rootClassName = 'srk-general-modal-root';
383
275
  wrapClassName = 'srk-solutions-modal';
384
276
  panelStyle = {};
277
+ languages;
385
278
  openChange = new EventEmitter();
386
279
  close = new EventEmitter();
387
280
  formatSolutionTimestamp = formatSolutionTimestamp;
@@ -398,7 +291,7 @@ class DefaultSolutionModalComponent {
398
291
  }
399
292
  }
400
293
  resolvedTitle() {
401
- return this.title || (this.cachedUser ? getSolutionModalTitle(this.cachedProblemIndex, this.cachedUser) : '');
294
+ return this.title || (this.cachedUser ? getSolutionModalTitle(this.cachedProblemIndex, this.cachedUser, this.languages) : '');
402
295
  }
403
296
  solutionMeta(solution) {
404
297
  return getSolutionResultMeta(solution.result);
@@ -407,7 +300,7 @@ class DefaultSolutionModalComponent {
407
300
  return `${solution.result}_${solution.time?.[0]}_${index}`;
408
301
  }
409
302
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DefaultSolutionModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
410
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: DefaultSolutionModalComponent, isStandalone: true, selector: "srk-default-solution-modal", inputs: { open: "open", user: "user", problem: "problem", problemIndex: "problemIndex", solutions: "solutions", title: "title", width: "width", rootClassName: "rootClassName", wrapClassName: "wrapClassName", panelStyle: "panelStyle" }, outputs: { openChange: "openChange", close: "close" }, usesOnChanges: true, ngImport: i0, template: `
303
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: DefaultSolutionModalComponent, isStandalone: true, selector: "srk-default-solution-modal", inputs: { open: "open", user: "user", problem: "problem", problemIndex: "problemIndex", solutions: "solutions", title: "title", width: "width", rootClassName: "rootClassName", wrapClassName: "wrapClassName", panelStyle: "panelStyle", languages: "languages" }, outputs: { openChange: "openChange", close: "close" }, usesOnChanges: true, ngImport: i0, template: `
411
304
  <srk-modal
412
305
  *ngIf="cachedUser"
413
306
  [open]="open"
@@ -505,6 +398,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
505
398
  type: Input
506
399
  }], panelStyle: [{
507
400
  type: Input
401
+ }], languages: [{
402
+ type: Input
508
403
  }], openChange: [{
509
404
  type: Output
510
405
  }], close: [{
@@ -522,9 +417,9 @@ class DefaultUserModalComponent {
522
417
  wrapClassName = 'srk-user-modal';
523
418
  panelStyle = {};
524
419
  formatSrkAssetUrl;
420
+ languages;
525
421
  openChange = new EventEmitter();
526
422
  close = new EventEmitter();
527
- resolveText = resolveText;
528
423
  cachedUser = null;
529
424
  ngOnChanges(_changes) {
530
425
  if (this.user) {
@@ -543,14 +438,20 @@ class DefaultUserModalComponent {
543
438
  formatAssetUrl(url, field) {
544
439
  return resolveSrkAssetUrl(url, field, this.formatSrkAssetUrl);
545
440
  }
441
+ resolveDisplayText(text) {
442
+ return resolveText(text, this.languages);
443
+ }
444
+ formatMemberName(member) {
445
+ return formatTeamMemberName(member, this.languages);
446
+ }
546
447
  trackByMarkerId(_index, entry) {
547
448
  return entry.marker.id;
548
449
  }
549
- trackByMemberName(_index, member) {
550
- return resolveText(member.name);
551
- }
450
+ trackByMemberName = (_index, member) => {
451
+ return this.resolveDisplayText(member.name);
452
+ };
552
453
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DefaultUserModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
553
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: DefaultUserModalComponent, isStandalone: true, selector: "srk-default-user-modal", inputs: { open: "open", user: "user", markers: "markers", theme: "theme", title: "title", width: "width", rootClassName: "rootClassName", wrapClassName: "wrapClassName", panelStyle: "panelStyle", formatSrkAssetUrl: "formatSrkAssetUrl" }, outputs: { openChange: "openChange", close: "close" }, usesOnChanges: true, ngImport: i0, template: `
454
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: DefaultUserModalComponent, isStandalone: true, selector: "srk-default-user-modal", inputs: { open: "open", user: "user", markers: "markers", theme: "theme", title: "title", width: "width", rootClassName: "rootClassName", wrapClassName: "wrapClassName", panelStyle: "panelStyle", formatSrkAssetUrl: "formatSrkAssetUrl", languages: "languages" }, outputs: { openChange: "openChange", close: "close" }, usesOnChanges: true, ngImport: i0, template: `
554
455
  <srk-modal
555
456
  *ngIf="cachedUser as modalUser"
556
457
  [open]="open"
@@ -563,9 +464,9 @@ class DefaultUserModalComponent {
563
464
  (openChange)="openChange.emit($event)"
564
465
  >
565
466
  <div class="srk-user-modal-info">
566
- <h3 class="srk-user-modal-info-user-name">{{ resolveText(modalUser.name) }}</h3>
467
+ <h3 class="srk-user-modal-info-user-name">{{ resolveDisplayText(modalUser.name) }}</h3>
567
468
  <p *ngIf="modalUser.organization" class="srk-user-modal-info-user-second-name">
568
- {{ resolveText(modalUser.organization) }}
469
+ {{ resolveDisplayText(modalUser.organization) }}
569
470
  </p>
570
471
  <div class="srk-user-modal-info-labels">
571
472
  <span class="srk-user-modal-info-labels-label srk-user-modal-info-labels-label-preset-general">
@@ -577,13 +478,13 @@ class DefaultUserModalComponent {
577
478
  [ngClass]="entry.presentation.className"
578
479
  [ngStyle]="entry.presentation.style"
579
480
  >
580
- {{ resolveText(entry.marker.label) }}
481
+ {{ resolveDisplayText(entry.marker.label) }}
581
482
  </span>
582
483
  </div>
583
484
  <div *ngIf="modalUser.teamMembers?.length" class="srk-user-modal-info-team-members">
584
485
  <ng-container *ngFor="let member of modalUser.teamMembers; let index = index; trackBy: trackByMemberName">
585
486
  <span *ngIf="index > 0" class="srk-user-modal-info-team-members-slash"> / </span>
586
- <span>{{ resolveText(member.name) }}</span>
487
+ <span>{{ formatMemberName(member) }}</span>
587
488
  </ng-container>
588
489
  </div>
589
490
  <div *ngIf="modalUser.photo" class="srk-user-modal-info-photo">
@@ -616,9 +517,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
616
517
  (openChange)="openChange.emit($event)"
617
518
  >
618
519
  <div class="srk-user-modal-info">
619
- <h3 class="srk-user-modal-info-user-name">{{ resolveText(modalUser.name) }}</h3>
520
+ <h3 class="srk-user-modal-info-user-name">{{ resolveDisplayText(modalUser.name) }}</h3>
620
521
  <p *ngIf="modalUser.organization" class="srk-user-modal-info-user-second-name">
621
- {{ resolveText(modalUser.organization) }}
522
+ {{ resolveDisplayText(modalUser.organization) }}
622
523
  </p>
623
524
  <div class="srk-user-modal-info-labels">
624
525
  <span class="srk-user-modal-info-labels-label srk-user-modal-info-labels-label-preset-general">
@@ -630,13 +531,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
630
531
  [ngClass]="entry.presentation.className"
631
532
  [ngStyle]="entry.presentation.style"
632
533
  >
633
- {{ resolveText(entry.marker.label) }}
534
+ {{ resolveDisplayText(entry.marker.label) }}
634
535
  </span>
635
536
  </div>
636
537
  <div *ngIf="modalUser.teamMembers?.length" class="srk-user-modal-info-team-members">
637
538
  <ng-container *ngFor="let member of modalUser.teamMembers; let index = index; trackBy: trackByMemberName">
638
539
  <span *ngIf="index > 0" class="srk-user-modal-info-team-members-slash"> / </span>
639
- <span>{{ resolveText(member.name) }}</span>
540
+ <span>{{ formatMemberName(member) }}</span>
640
541
  </ng-container>
641
542
  </div>
642
543
  <div *ngIf="modalUser.photo" class="srk-user-modal-info-photo">
@@ -670,56 +571,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
670
571
  type: Input
671
572
  }], formatSrkAssetUrl: [{
672
573
  type: Input
574
+ }], languages: [{
575
+ type: Input
673
576
  }], openChange: [{
674
577
  type: Output
675
578
  }], close: [{
676
579
  type: Output
677
580
  }] } });
678
581
 
679
- function getProgressDurationMinutes(contest) {
680
- return formatTimeDuration(contest.duration, 'min');
681
- }
682
- function getProgressMaxAvailableMinutes(contest, localTime, td = 0) {
683
- const startAt = new Date(contest.startAt).getTime();
684
- const currentTime = localTime - td;
685
- return Math.min(Math.max(Math.floor((currentTime - startAt) / 60000), 0), getProgressDurationMinutes(contest));
686
- }
687
- function isProgressEnded(contest, localTime, td = 0) {
688
- const startAt = new Date(contest.startAt).getTime();
689
- const endAt = startAt + formatTimeDuration(contest.duration, 'ms');
690
- const currentTime = localTime - td;
691
- return currentTime >= endAt;
692
- }
693
- function getProgressMetrics(data, localTime, td, timeTravelCurrentValue, inTimeMachine) {
694
- const { contest } = data;
695
- const startAt = new Date(contest.startAt).getTime();
696
- const endAt = startAt + formatTimeDuration(contest.duration, 'ms');
697
- const frozenLength = contest.frozenDuration ? formatTimeDuration(contest.frozenDuration, 'ms') : 0;
698
- const currentTime = localTime - td;
699
- const length = endAt - startAt;
700
- const frozenBreakpoint = length ? 1 - frozenLength / length : 1;
701
- const elapsed = Math.min(Math.max(currentTime - startAt, 0), length);
702
- const remaining = length - elapsed;
703
- const frozenAt = endAt - frozenLength;
704
- const percent = length ? (elapsed / length) * 100 : 0;
705
- const normalPercent = length ? (Math.max(Math.min(currentTime, frozenAt) - startAt, 0) / length) * 100 : 0;
706
- const normalInnerPercent = Math.min(100, frozenBreakpoint ? normalPercent / frozenBreakpoint : 0);
707
- const frozenPercent = percent - normalPercent;
708
- const frozenInnerPercent = Math.min(100, 1 - frozenBreakpoint ? frozenPercent / (1 - frozenBreakpoint) : 0);
709
- const timeTravelElapsed = timeTravelCurrentValue * 60 * 1000;
710
- const timeTravelRemaining = length - timeTravelElapsed;
711
- return {
712
- elapsed: inTimeMachine ? timeTravelElapsed : elapsed,
713
- remaining: inTimeMachine ? timeTravelRemaining : remaining,
714
- frozenBreakpoint,
715
- normalInnerPercent,
716
- frozenInnerPercent,
717
- timeTravelElapsed,
718
- timeTravelRemaining,
719
- supportRegen: canRegenerateRanklist(data),
720
- };
721
- }
722
-
723
582
  class ProgressBarComponent {
724
583
  data;
725
584
  enableTimeTravel = false;
@@ -961,6 +820,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
961
820
  type: Output
962
821
  }] } });
963
822
 
823
+ class SrkProblemHeaderCellTemplateDirective {
824
+ templateRef = inject(TemplateRef);
825
+ static ngTemplateContextGuard(_directive, context) {
826
+ return true;
827
+ }
828
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SrkProblemHeaderCellTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
829
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: SrkProblemHeaderCellTemplateDirective, isStandalone: true, selector: "ng-template[srkProblemHeaderCell]", ngImport: i0 });
830
+ }
831
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SrkProblemHeaderCellTemplateDirective, decorators: [{
832
+ type: Directive,
833
+ args: [{
834
+ selector: 'ng-template[srkProblemHeaderCell]',
835
+ standalone: true,
836
+ }]
837
+ }] });
838
+
964
839
  class SrkStatusCellTemplateDirective {
965
840
  templateRef = inject(TemplateRef);
966
841
  static ngTemplateContextGuard(_directive, context) {
@@ -1009,8 +884,11 @@ class RanklistComponent {
1009
884
  showSEColumn = false;
1010
885
  emptyStatusPlaceholder = null;
1011
886
  userAvatarPlacement = 'user';
887
+ languages;
1012
888
  userClick = new EventEmitter();
889
+ problemClick = new EventEmitter();
1013
890
  solutionClick = new EventEmitter();
891
+ problemHeaderCellTemplate;
1014
892
  statusCellTemplate;
1015
893
  userCellTemplate;
1016
894
  supportedVersions = srkSupportedVersions;
@@ -1023,8 +901,8 @@ class RanklistComponent {
1023
901
  tooltip: 'Number of participants who solved this problem',
1024
902
  },
1025
903
  {
1026
- key: 'tried',
1027
- label: 'Tried',
904
+ key: 'attempted',
905
+ label: 'Attempted',
1028
906
  tooltip: 'Number of participants who attempted this problem',
1029
907
  },
1030
908
  {
@@ -1069,7 +947,7 @@ class RanklistComponent {
1069
947
  return caniuse(this.data.version);
1070
948
  }
1071
949
  showTimeColumn() {
1072
- return shouldShowTimeColumn$1(this.data.rows);
950
+ return shouldShowTimeColumn(this.data.rows);
1073
951
  }
1074
952
  showAvatarInOrganization() {
1075
953
  return this.splitOrganization && this.userAvatarPlacement === 'organization';
@@ -1084,7 +962,7 @@ class RanklistComponent {
1084
962
  return rankValue.rank ? rankValue.rank : row.user.official === false ? '*' : '';
1085
963
  }
1086
964
  resolveDisplayText(text) {
1087
- return resolveText(text);
965
+ return resolveText(text, this.languages);
1088
966
  }
1089
967
  resolveSeriesColumnTitle(series, index) {
1090
968
  const seriesTitles = this.columnTitles?.series;
@@ -1107,16 +985,16 @@ class RanklistComponent {
1107
985
  return `${statistics.accepted} / ${statistics.submitted} (${ratio}%)`;
1108
986
  }
1109
987
  problemHeaderBackgroundImage(problem, gradientDirection = 180) {
1110
- return getProblemHeaderBackgroundImage$1(problem.style, this.theme, gradientDirection);
988
+ return getProblemHeaderBackgroundImage(problem.style, this.theme, gradientDirection);
1111
989
  }
1112
990
  resolvedUserMarkers(user) {
1113
991
  return resolveUserMarkers(user, this.data.markers).map((marker) => ({
1114
992
  marker,
1115
- presentation: getMarkerPresentation$1(marker, this.theme),
993
+ presentation: getMarkerPresentation(marker, this.theme),
1116
994
  }));
1117
995
  }
1118
996
  formatAssetUrl(url, field) {
1119
- return resolveSrkAssetUrl$1(url, field, this.formatSrkAssetUrl);
997
+ return resolveSrkAssetUrl(url, field, this.formatSrkAssetUrl);
1120
998
  }
1121
999
  formatTime(time) {
1122
1000
  return formatTimeDuration(time, 'min', Math.floor);
@@ -1167,6 +1045,27 @@ class RanklistComponent {
1167
1045
  statusCellTabIndex(status) {
1168
1046
  return this.isStatusClickable(status) ? 0 : null;
1169
1047
  }
1048
+ hasProblemClickListener() {
1049
+ const emitter = this.problemClick;
1050
+ return emitter.observed === true || Boolean(emitter.observers?.length);
1051
+ }
1052
+ problemHeaderRole() {
1053
+ return this.hasProblemClickListener() ? 'button' : null;
1054
+ }
1055
+ problemHeaderTabIndex() {
1056
+ return this.hasProblemClickListener() ? 0 : null;
1057
+ }
1058
+ buildProblemHeaderCellContext(problem, problemIndex) {
1059
+ return {
1060
+ $implicit: problem,
1061
+ problem,
1062
+ problemIndex,
1063
+ index: problemIndex,
1064
+ ranklist: this.data,
1065
+ languages: this.languages,
1066
+ onClick: (event) => this.emitProblemClick(event, problem, problemIndex),
1067
+ };
1068
+ }
1170
1069
  buildUserCellContext(row, rowIndex) {
1171
1070
  return {
1172
1071
  $implicit: row.user,
@@ -1177,6 +1076,7 @@ class RanklistComponent {
1177
1076
  markers: this.data.markers,
1178
1077
  hideOrganization: this.splitOrganization,
1179
1078
  hideAvatar: this.showAvatarInOrganization(),
1079
+ languages: this.languages,
1180
1080
  onClick: (event) => this.emitUserClick(event, row, rowIndex),
1181
1081
  };
1182
1082
  }
@@ -1194,6 +1094,7 @@ class RanklistComponent {
1194
1094
  statusCellPreset: this.statusCellPreset,
1195
1095
  statusColorAsText: this.statusColorAsText,
1196
1096
  emptyStatusPlaceholder: this.emptyStatusPlaceholder,
1097
+ languages: this.languages,
1197
1098
  onClick: (event) => this.emitSolutionClick(event, row, rowIndex, status, problemIndex),
1198
1099
  };
1199
1100
  }
@@ -1201,6 +1102,13 @@ class RanklistComponent {
1201
1102
  event.preventDefault();
1202
1103
  this.emitUserClick(undefined, row, rowIndex);
1203
1104
  }
1105
+ activateProblemHeaderFromKeyboard(event, problem, problemIndex) {
1106
+ if (!this.hasProblemClickListener()) {
1107
+ return;
1108
+ }
1109
+ event.preventDefault();
1110
+ this.emitProblemClick(undefined, problem, problemIndex);
1111
+ }
1204
1112
  activateStatusCellFromKeyboard(event, row, rowIndex, status, problemIndex) {
1205
1113
  event.preventDefault();
1206
1114
  this.emitSolutionClick(undefined, row, rowIndex, status, problemIndex);
@@ -1223,6 +1131,27 @@ class RanklistComponent {
1223
1131
  ranklist: this.data,
1224
1132
  });
1225
1133
  }
1134
+ emitProblemClick(event, problem, problemIndex) {
1135
+ if (!this.hasProblemClickListener()) {
1136
+ return;
1137
+ }
1138
+ if (event) {
1139
+ event.preventDefault();
1140
+ captureModalTriggerPointFromMouseEvent(event, {
1141
+ source: 'problem-header',
1142
+ context: {
1143
+ problemIndex,
1144
+ problemAlias: problem.alias || null,
1145
+ problemTitle: this.resolveDisplayText(problem.title) || null,
1146
+ },
1147
+ });
1148
+ }
1149
+ this.problemClick.emit({
1150
+ problem,
1151
+ problemIndex,
1152
+ ranklist: this.data,
1153
+ });
1154
+ }
1226
1155
  emitSolutionClick(event, row, rowIndex, status, problemIndex) {
1227
1156
  const solutions = this.getStatusSolutions(status);
1228
1157
  if (!solutions.length) {
@@ -1283,8 +1212,8 @@ class RanklistComponent {
1283
1212
  switch (key) {
1284
1213
  case 'accepted':
1285
1214
  return stat.accepted;
1286
- case 'tried':
1287
- return stat.tried;
1215
+ case 'attempted':
1216
+ return stat.attempted;
1288
1217
  case 'submitted':
1289
1218
  return stat.submitted;
1290
1219
  case 'dirt':
@@ -1303,8 +1232,8 @@ class RanklistComponent {
1303
1232
  switch (key) {
1304
1233
  case 'accepted':
1305
1234
  return formatProblemStatisticsPercent(stat.accepted, stat.participantCount);
1306
- case 'tried':
1307
- return formatProblemStatisticsPercent(stat.tried, stat.participantCount);
1235
+ case 'attempted':
1236
+ return formatProblemStatisticsPercent(stat.attempted, stat.participantCount);
1308
1237
  case 'dirt':
1309
1238
  return formatProblemStatisticsPercent(stat.dirt, stat.dirtSubmitted);
1310
1239
  default:
@@ -1312,7 +1241,7 @@ class RanklistComponent {
1312
1241
  }
1313
1242
  }
1314
1243
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RanklistComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1315
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: RanklistComponent, isStandalone: true, selector: "srk-ranklist", inputs: { data: "data", theme: "theme", rowBordered: "rowBordered", columnBordered: "columnBordered", rowStriped: "rowStriped", formatSrkAssetUrl: "formatSrkAssetUrl", splitOrganization: "splitOrganization", columnTitles: "columnTitles", statusCellPreset: "statusCellPreset", statusColorAsText: "statusColorAsText", showProblemStatisticsFooter: "showProblemStatisticsFooter", showDirtColumn: "showDirtColumn", showSEColumn: "showSEColumn", emptyStatusPlaceholder: "emptyStatusPlaceholder", userAvatarPlacement: "userAvatarPlacement" }, outputs: { userClick: "userClick", solutionClick: "solutionClick" }, queries: [{ propertyName: "statusCellTemplate", first: true, predicate: SrkStatusCellTemplateDirective, descendants: true }, { propertyName: "userCellTemplate", first: true, predicate: SrkUserCellTemplateDirective, descendants: true }], ngImport: i0, template: `
1244
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: RanklistComponent, isStandalone: true, selector: "srk-ranklist", inputs: { data: "data", theme: "theme", rowBordered: "rowBordered", columnBordered: "columnBordered", rowStriped: "rowStriped", formatSrkAssetUrl: "formatSrkAssetUrl", splitOrganization: "splitOrganization", columnTitles: "columnTitles", statusCellPreset: "statusCellPreset", statusColorAsText: "statusColorAsText", showProblemStatisticsFooter: "showProblemStatisticsFooter", showDirtColumn: "showDirtColumn", showSEColumn: "showSEColumn", emptyStatusPlaceholder: "emptyStatusPlaceholder", userAvatarPlacement: "userAvatarPlacement", languages: "languages" }, outputs: { userClick: "userClick", problemClick: "problemClick", solutionClick: "solutionClick" }, queries: [{ propertyName: "problemHeaderCellTemplate", first: true, predicate: SrkProblemHeaderCellTemplateDirective, descendants: true }, { propertyName: "statusCellTemplate", first: true, predicate: SrkStatusCellTemplateDirective, descendants: true }, { propertyName: "userCellTemplate", first: true, predicate: SrkUserCellTemplateDirective, descendants: true }], ngImport: i0, template: `
1316
1245
  <div *ngIf="data.type !== 'general'; else versionState">
1317
1246
  srk type "{{ data.type }}" is not supported
1318
1247
  </div>
@@ -1350,38 +1279,55 @@ class RanklistComponent {
1350
1279
  <th *ngIf="showTimeColumn()" class="srk--text-right srk--nowrap">
1351
1280
  {{ resolveColumnTitle('time', 'Time') }}
1352
1281
  </th>
1353
- <th
1282
+ <ng-container
1354
1283
  *ngFor="let problem of data.problems; let problemIndex = index"
1355
- class="srk--nowrap srk-problem-header"
1356
- [style.background-image]="problemHeaderBackgroundImage(problem)"
1357
1284
  >
1358
- <a
1359
- *ngIf="problem.link; else unlinkedProblemHeader"
1360
- [href]="problem.link"
1361
- target="_blank"
1362
- rel="noopener noreferrer"
1363
- style="color: unset"
1364
- >
1365
- <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1366
- <span
1367
- *ngIf="problem.statistics"
1368
- class="srk--display-block srk-problem-stats"
1369
- [title]="problemStatsTitle(problem.statistics)"
1370
- >
1371
- {{ problem.statistics.accepted }}
1372
- </span>
1373
- </a>
1374
- <ng-template #unlinkedProblemHeader>
1375
- <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1376
- <span
1377
- *ngIf="problem.statistics"
1378
- class="srk--display-block srk-problem-stats"
1379
- [title]="problemStatsTitle(problem.statistics)"
1285
+ <ng-container *ngIf="problemHeaderCellTemplate; else defaultProblemHeader">
1286
+ <ng-container
1287
+ [ngTemplateOutlet]="problemHeaderCellTemplate.templateRef"
1288
+ [ngTemplateOutletContext]="buildProblemHeaderCellContext(problem, problemIndex)"
1289
+ />
1290
+ </ng-container>
1291
+ <ng-template #defaultProblemHeader>
1292
+ <th
1293
+ class="srk--nowrap srk-problem-header"
1294
+ [class.srk--cursor-pointer]="hasProblemClickListener()"
1295
+ [attr.role]="problemHeaderRole()"
1296
+ [attr.tabindex]="problemHeaderTabIndex()"
1297
+ [style.background-image]="problemHeaderBackgroundImage(problem)"
1298
+ (click)="emitProblemClick($event, problem, problemIndex)"
1299
+ (keydown.enter)="activateProblemHeaderFromKeyboard($event, problem, problemIndex)"
1300
+ (keydown.space)="activateProblemHeaderFromKeyboard($event, problem, problemIndex)"
1380
1301
  >
1381
- {{ problem.statistics.accepted }}
1382
- </span>
1302
+ <a
1303
+ *ngIf="problem.link && !hasProblemClickListener(); else unlinkedProblemHeader"
1304
+ [href]="problem.link"
1305
+ target="_blank"
1306
+ rel="noopener noreferrer"
1307
+ style="color: unset"
1308
+ >
1309
+ <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1310
+ <span
1311
+ *ngIf="problem.statistics"
1312
+ class="srk--display-block srk-problem-stats"
1313
+ [title]="problemStatsTitle(problem.statistics)"
1314
+ >
1315
+ {{ problem.statistics.accepted }}
1316
+ </span>
1317
+ </a>
1318
+ <ng-template #unlinkedProblemHeader>
1319
+ <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1320
+ <span
1321
+ *ngIf="problem.statistics"
1322
+ class="srk--display-block srk-problem-stats"
1323
+ [title]="problemStatsTitle(problem.statistics)"
1324
+ >
1325
+ {{ problem.statistics.accepted }}
1326
+ </span>
1327
+ </ng-template>
1328
+ </th>
1383
1329
  </ng-template>
1384
- </th>
1330
+ </ng-container>
1385
1331
  <th *ngIf="showDirtColumn" class="srk-dirt-header srk--text-right srk--nowrap">
1386
1332
  {{ resolveColumnTitle('dirt', 'Dirt') }}
1387
1333
  </th>
@@ -1673,38 +1619,55 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1673
1619
  <th *ngIf="showTimeColumn()" class="srk--text-right srk--nowrap">
1674
1620
  {{ resolveColumnTitle('time', 'Time') }}
1675
1621
  </th>
1676
- <th
1622
+ <ng-container
1677
1623
  *ngFor="let problem of data.problems; let problemIndex = index"
1678
- class="srk--nowrap srk-problem-header"
1679
- [style.background-image]="problemHeaderBackgroundImage(problem)"
1680
1624
  >
1681
- <a
1682
- *ngIf="problem.link; else unlinkedProblemHeader"
1683
- [href]="problem.link"
1684
- target="_blank"
1685
- rel="noopener noreferrer"
1686
- style="color: unset"
1687
- >
1688
- <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1689
- <span
1690
- *ngIf="problem.statistics"
1691
- class="srk--display-block srk-problem-stats"
1692
- [title]="problemStatsTitle(problem.statistics)"
1693
- >
1694
- {{ problem.statistics.accepted }}
1695
- </span>
1696
- </a>
1697
- <ng-template #unlinkedProblemHeader>
1698
- <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1699
- <span
1700
- *ngIf="problem.statistics"
1701
- class="srk--display-block srk-problem-stats"
1702
- [title]="problemStatsTitle(problem.statistics)"
1625
+ <ng-container *ngIf="problemHeaderCellTemplate; else defaultProblemHeader">
1626
+ <ng-container
1627
+ [ngTemplateOutlet]="problemHeaderCellTemplate.templateRef"
1628
+ [ngTemplateOutletContext]="buildProblemHeaderCellContext(problem, problemIndex)"
1629
+ />
1630
+ </ng-container>
1631
+ <ng-template #defaultProblemHeader>
1632
+ <th
1633
+ class="srk--nowrap srk-problem-header"
1634
+ [class.srk--cursor-pointer]="hasProblemClickListener()"
1635
+ [attr.role]="problemHeaderRole()"
1636
+ [attr.tabindex]="problemHeaderTabIndex()"
1637
+ [style.background-image]="problemHeaderBackgroundImage(problem)"
1638
+ (click)="emitProblemClick($event, problem, problemIndex)"
1639
+ (keydown.enter)="activateProblemHeaderFromKeyboard($event, problem, problemIndex)"
1640
+ (keydown.space)="activateProblemHeaderFromKeyboard($event, problem, problemIndex)"
1703
1641
  >
1704
- {{ problem.statistics.accepted }}
1705
- </span>
1642
+ <a
1643
+ *ngIf="problem.link && !hasProblemClickListener(); else unlinkedProblemHeader"
1644
+ [href]="problem.link"
1645
+ target="_blank"
1646
+ rel="noopener noreferrer"
1647
+ style="color: unset"
1648
+ >
1649
+ <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1650
+ <span
1651
+ *ngIf="problem.statistics"
1652
+ class="srk--display-block srk-problem-stats"
1653
+ [title]="problemStatsTitle(problem.statistics)"
1654
+ >
1655
+ {{ problem.statistics.accepted }}
1656
+ </span>
1657
+ </a>
1658
+ <ng-template #unlinkedProblemHeader>
1659
+ <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1660
+ <span
1661
+ *ngIf="problem.statistics"
1662
+ class="srk--display-block srk-problem-stats"
1663
+ [title]="problemStatsTitle(problem.statistics)"
1664
+ >
1665
+ {{ problem.statistics.accepted }}
1666
+ </span>
1667
+ </ng-template>
1668
+ </th>
1706
1669
  </ng-template>
1707
- </th>
1670
+ </ng-container>
1708
1671
  <th *ngIf="showDirtColumn" class="srk-dirt-header srk--text-right srk--nowrap">
1709
1672
  {{ resolveColumnTitle('dirt', 'Dirt') }}
1710
1673
  </th>
@@ -1982,10 +1945,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1982
1945
  type: Input
1983
1946
  }], userAvatarPlacement: [{
1984
1947
  type: Input
1948
+ }], languages: [{
1949
+ type: Input
1985
1950
  }], userClick: [{
1986
1951
  type: Output
1952
+ }], problemClick: [{
1953
+ type: Output
1987
1954
  }], solutionClick: [{
1988
1955
  type: Output
1956
+ }], problemHeaderCellTemplate: [{
1957
+ type: ContentChild,
1958
+ args: [SrkProblemHeaderCellTemplateDirective]
1989
1959
  }], statusCellTemplate: [{
1990
1960
  type: ContentChild,
1991
1961
  args: [SrkStatusCellTemplateDirective]
@@ -1998,5 +1968,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1998
1968
  * Generated bundle index. Do not edit.
1999
1969
  */
2000
1970
 
2001
- export { DefaultSolutionModalComponent, DefaultUserModalComponent, ModalComponent, ProgressBarComponent, RanklistComponent, SrkStatusCellTemplateDirective, SrkUserCellTemplateDirective };
1971
+ export { DefaultSolutionModalComponent, DefaultUserModalComponent, ModalComponent, ProgressBarComponent, RanklistComponent, SrkProblemHeaderCellTemplateDirective, SrkStatusCellTemplateDirective, SrkUserCellTemplateDirective };
2002
1972
  //# sourceMappingURL=algoux-standard-ranklist-renderer-component-angular.mjs.map