@algoux/standard-ranklist-renderer-component-angular 0.5.1 → 0.6.1

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.
@@ -3,7 +3,7 @@ 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
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, caniuse, captureModalTriggerPointFromMouseEvent } from '@algoux/standard-ranklist-renderer-component-core';
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
7
 
8
8
  const defaultBackgroundColor = {
9
9
  [EnumTheme.light]: '#ffffff',
@@ -997,18 +997,86 @@ class RanklistComponent {
997
997
  data;
998
998
  theme = EnumTheme.light;
999
999
  borderedRows = false;
1000
+ rowBordered = false;
1001
+ columnBordered = false;
1000
1002
  stripedRows = false;
1001
1003
  formatSrkAssetUrl;
1004
+ splitOrganization = false;
1005
+ columnTitles;
1006
+ statusCellPreset = 'classic';
1007
+ statusColorAsText = false;
1008
+ showProblemStatisticsFooter = false;
1009
+ showDirtColumn = false;
1010
+ showSEColumn = false;
1011
+ emptyStatusPlaceholder = null;
1012
+ userAvatarPlacement = 'user';
1002
1013
  userClick = new EventEmitter();
1003
1014
  solutionClick = new EventEmitter();
1004
1015
  statusCellTemplate;
1005
1016
  userCellTemplate;
1006
1017
  supportedVersions = srkSupportedVersions;
1018
+ firstBloodStar = '\u2605';
1019
+ statusSeparator = ' ';
1020
+ problemStatisticsFooterRows = [
1021
+ {
1022
+ key: 'accepted',
1023
+ label: 'Accepted',
1024
+ tooltip: 'Number of participants who solved this problem',
1025
+ },
1026
+ {
1027
+ key: 'tried',
1028
+ label: 'Tried',
1029
+ tooltip: 'Number of participants who attempted this problem',
1030
+ },
1031
+ {
1032
+ key: 'submitted',
1033
+ label: 'Submitted',
1034
+ tooltip: 'Total number of valid submissions for this problem',
1035
+ },
1036
+ {
1037
+ key: 'dirt',
1038
+ label: 'Dirt',
1039
+ tooltip: 'Wrong submissions among participants who solved this problem',
1040
+ },
1041
+ {
1042
+ key: 'se',
1043
+ label: 'SE',
1044
+ tooltip: 'Average hardness, calculated as (participants - accepted) / participants',
1045
+ },
1046
+ {
1047
+ key: 'firstAccepted',
1048
+ label: 'FB at',
1049
+ tooltip: 'First Blood at, also known as first solve time, in minutes',
1050
+ },
1051
+ {
1052
+ key: 'lastAccepted',
1053
+ label: 'LB at',
1054
+ tooltip: 'Last Blood at, also known as last solve time, in minutes',
1055
+ },
1056
+ ];
1057
+ problemStatisticsSource;
1058
+ problemStatisticsCache;
1059
+ get problemStatistics() {
1060
+ if (!this.showProblemStatisticsFooter && !this.showSEColumn) {
1061
+ return [];
1062
+ }
1063
+ if (this.problemStatisticsSource !== this.data || !this.problemStatisticsCache) {
1064
+ this.problemStatisticsSource = this.data;
1065
+ this.problemStatisticsCache = calculateProblemStatisticsFooter(this.data);
1066
+ }
1067
+ return this.problemStatisticsCache;
1068
+ }
1007
1069
  isSupportedVersion() {
1008
1070
  return caniuse(this.data.version);
1009
1071
  }
1010
1072
  showTimeColumn() {
1011
- return shouldShowTimeColumn(this.data.rows);
1073
+ return shouldShowTimeColumn$1(this.data.rows);
1074
+ }
1075
+ showAvatarInOrganization() {
1076
+ return this.splitOrganization && this.userAvatarPlacement === 'organization';
1077
+ }
1078
+ leftFooterColumnCount() {
1079
+ return this.data.series.length + 1 + 1 + (this.showTimeColumn() ? 1 : 0) + (this.splitOrganization ? 1 : 0);
1012
1080
  }
1013
1081
  getRankValues(row) {
1014
1082
  return row.rankValues || this.data.series.map(() => ({ rank: null, segmentIndex: null }));
@@ -1019,6 +1087,19 @@ class RanklistComponent {
1019
1087
  resolveDisplayText(text) {
1020
1088
  return resolveText(text);
1021
1089
  }
1090
+ resolveSeriesColumnTitle(series, index) {
1091
+ const seriesTitles = this.columnTitles?.series;
1092
+ if (typeof seriesTitles === 'function') {
1093
+ return seriesTitles(series, index) ?? series.title;
1094
+ }
1095
+ if (Array.isArray(seriesTitles)) {
1096
+ return seriesTitles[index] ?? series.title;
1097
+ }
1098
+ return series.title;
1099
+ }
1100
+ resolveColumnTitle(key, fallback) {
1101
+ return this.columnTitles?.[key] ?? fallback;
1102
+ }
1022
1103
  problemAlias(problem, problemIndex) {
1023
1104
  return problem.alias || numberToAlphabet(problemIndex);
1024
1105
  }
@@ -1026,23 +1107,29 @@ class RanklistComponent {
1026
1107
  const ratio = statistics.submitted ? ((statistics.accepted / statistics.submitted) * 100).toFixed(1) : 0;
1027
1108
  return `${statistics.accepted} / ${statistics.submitted} (${ratio}%)`;
1028
1109
  }
1029
- problemHeaderBackgroundImage(problem) {
1030
- return getProblemHeaderBackgroundImage(problem.style, this.theme);
1110
+ problemHeaderBackgroundImage(problem, gradientDirection = 180) {
1111
+ return getProblemHeaderBackgroundImage$1(problem.style, this.theme, gradientDirection);
1031
1112
  }
1032
1113
  resolvedUserMarkers(user) {
1033
1114
  return resolveUserMarkers(user, this.data.markers).map((marker) => ({
1034
1115
  marker,
1035
- presentation: getMarkerPresentation(marker, this.theme),
1116
+ presentation: getMarkerPresentation$1(marker, this.theme),
1036
1117
  }));
1037
1118
  }
1038
1119
  formatAssetUrl(url, field) {
1039
- return resolveSrkAssetUrl(url, field, this.formatSrkAssetUrl);
1120
+ return resolveSrkAssetUrl$1(url, field, this.formatSrkAssetUrl);
1040
1121
  }
1041
1122
  formatTime(time) {
1042
1123
  return formatTimeDuration(time, 'min', Math.floor);
1043
1124
  }
1044
- acceptedStatusDetails(status) {
1045
- return getAcceptedStatusDetails(status);
1125
+ calculateDirtPercentage(row) {
1126
+ return calculateDirtPercentage(row);
1127
+ }
1128
+ calculateSEValue(row, problemStatistics) {
1129
+ return calculateSEValue(row, problemStatistics);
1130
+ }
1131
+ statusPresentation(status) {
1132
+ return getRankProblemStatusCellPresentation(status, this.data, this.statusCellPreset);
1046
1133
  }
1047
1134
  isNumber(value) {
1048
1135
  return typeof value === 'number';
@@ -1055,6 +1142,9 @@ class RanklistComponent {
1055
1142
  if (this.isStatusClickable(status)) {
1056
1143
  classNames.push('srk--cursor-pointer');
1057
1144
  }
1145
+ if (this.statusColorAsText) {
1146
+ classNames.push('srk-prest-status-block-color-text');
1147
+ }
1058
1148
  if (status.result === 'FB') {
1059
1149
  classNames.push('srk-prest-status-block-fb');
1060
1150
  }
@@ -1086,6 +1176,8 @@ class RanklistComponent {
1086
1176
  rowIndex,
1087
1177
  ranklist: this.data,
1088
1178
  markers: this.data.markers,
1179
+ hideOrganization: this.splitOrganization,
1180
+ hideAvatar: this.showAvatarInOrganization(),
1089
1181
  onClick: (event) => this.emitUserClick(event, row, rowIndex),
1090
1182
  };
1091
1183
  }
@@ -1100,6 +1192,9 @@ class RanklistComponent {
1100
1192
  rowIndex,
1101
1193
  ranklist: this.data,
1102
1194
  solutions: this.getStatusSolutions(status),
1195
+ statusCellPreset: this.statusCellPreset,
1196
+ statusColorAsText: this.statusColorAsText,
1197
+ emptyStatusPlaceholder: this.emptyStatusPlaceholder,
1103
1198
  onClick: (event) => this.emitSolutionClick(event, row, rowIndex, status, problemIndex),
1104
1199
  };
1105
1200
  }
@@ -1141,7 +1236,7 @@ class RanklistComponent {
1141
1236
  rowIndex,
1142
1237
  problemIndex,
1143
1238
  problemAlias: this.data.problems[problemIndex]?.alias || null,
1144
- problemTitle: this.data.problems[problemIndex]?.title || null,
1239
+ problemTitle: this.data.problems[problemIndex] ? this.resolveDisplayText(this.data.problems[problemIndex].title) : null,
1145
1240
  userId: row.user.id || null,
1146
1241
  },
1147
1242
  });
@@ -1161,6 +1256,9 @@ class RanklistComponent {
1161
1256
  const index = rankValue.segmentIndex || rankValue.segmentIndex === 0 ? rankValue.segmentIndex : -1;
1162
1257
  return (series?.segments || [])[index] || {};
1163
1258
  }
1259
+ isSeriesSegmentedColumn(series) {
1260
+ return (series?.segments || []).some((segment) => typeof segment.style === 'string');
1261
+ }
1164
1262
  getSeriesSegmentClass(rankValue, series) {
1165
1263
  const segmentStyle = this.resolveSeriesSegment(rankValue, series).style;
1166
1264
  return typeof segmentStyle === 'string' ? `srk-preset-series-segment-${segmentStyle}` : '';
@@ -1182,8 +1280,40 @@ class RanklistComponent {
1182
1280
  backgroundColor: backgroundColor[this.theme],
1183
1281
  };
1184
1282
  }
1283
+ getProblemStatisticsFooterCellPrimary(key, stat) {
1284
+ switch (key) {
1285
+ case 'accepted':
1286
+ return stat.accepted;
1287
+ case 'tried':
1288
+ return stat.tried;
1289
+ case 'submitted':
1290
+ return stat.submitted;
1291
+ case 'dirt':
1292
+ return stat.dirt;
1293
+ case 'se':
1294
+ return formatProblemStatisticsAverageHardness(stat);
1295
+ case 'firstAccepted':
1296
+ return formatProblemStatisticsAcceptedMinute(stat.firstAcceptedTime);
1297
+ case 'lastAccepted':
1298
+ return formatProblemStatisticsAcceptedMinute(stat.lastAcceptedTime);
1299
+ default:
1300
+ return '';
1301
+ }
1302
+ }
1303
+ getProblemStatisticsFooterCellSecondary(key, stat) {
1304
+ switch (key) {
1305
+ case 'accepted':
1306
+ return formatProblemStatisticsPercent(stat.accepted, stat.participantCount);
1307
+ case 'tried':
1308
+ return formatProblemStatisticsPercent(stat.tried, stat.participantCount);
1309
+ case 'dirt':
1310
+ return formatProblemStatisticsPercent(stat.dirt, stat.dirtSubmitted);
1311
+ default:
1312
+ return undefined;
1313
+ }
1314
+ }
1185
1315
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RanklistComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1186
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: RanklistComponent, isStandalone: true, selector: "srk-ranklist", inputs: { data: "data", theme: "theme", borderedRows: "borderedRows", stripedRows: "stripedRows", formatSrkAssetUrl: "formatSrkAssetUrl" }, 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: `
1316
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: RanklistComponent, isStandalone: true, selector: "srk-ranklist", inputs: { data: "data", theme: "theme", borderedRows: "borderedRows", rowBordered: "rowBordered", columnBordered: "columnBordered", stripedRows: "stripedRows", 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: `
1187
1317
  <div *ngIf="data.type !== 'general'; else versionState">
1188
1318
  srk type "{{ data.type }}" is not supported
1189
1319
  </div>
@@ -1197,20 +1327,30 @@ class RanklistComponent {
1197
1327
  <ng-template #ranklistTable>
1198
1328
  <div class="srk-common-table srk-main">
1199
1329
  <table
1200
- [class.srk-table-row-bordered]="borderedRows"
1330
+ [class.srk-table-row-bordered]="borderedRows || rowBordered"
1331
+ [class.srk-table-column-bordered]="columnBordered"
1201
1332
  [class.srk-table-row-striped]="stripedRows"
1202
1333
  >
1203
1334
  <thead>
1204
1335
  <tr>
1205
1336
  <th
1206
- *ngFor="let seriesItem of data.series"
1337
+ *ngFor="let seriesItem of data.series; let seriesIndex = index"
1207
1338
  class="srk-series-header srk--text-right srk--nowrap"
1339
+ [class.srk-series-segmented-column]="isSeriesSegmentedColumn(seriesItem)"
1208
1340
  >
1209
- {{ seriesItem.title }}
1341
+ {{ resolveSeriesColumnTitle(seriesItem, seriesIndex) }}
1342
+ </th>
1343
+ <th
1344
+ *ngIf="splitOrganization"
1345
+ class="srk-organization-header srk--text-left srk--nowrap"
1346
+ >
1347
+ {{ resolveColumnTitle('organization', 'Organization') }}
1348
+ </th>
1349
+ <th class="srk--text-left srk--nowrap">{{ resolveColumnTitle('user', 'Name') }}</th>
1350
+ <th class="srk--text-right srk--nowrap">{{ resolveColumnTitle('score', 'Score') }}</th>
1351
+ <th *ngIf="showTimeColumn()" class="srk--text-right srk--nowrap">
1352
+ {{ resolveColumnTitle('time', 'Time') }}
1210
1353
  </th>
1211
- <th class="srk--text-left srk--nowrap">Name</th>
1212
- <th class="srk--nowrap">Score</th>
1213
- <th *ngIf="showTimeColumn()" class="srk--nowrap">Time</th>
1214
1354
  <th
1215
1355
  *ngFor="let problem of data.problems; let problemIndex = index"
1216
1356
  class="srk--nowrap srk-problem-header"
@@ -1243,6 +1383,12 @@ class RanklistComponent {
1243
1383
  </span>
1244
1384
  </ng-template>
1245
1385
  </th>
1386
+ <th *ngIf="showDirtColumn" class="srk-dirt-header srk--text-right srk--nowrap">
1387
+ {{ resolveColumnTitle('dirt', 'Dirt') }}
1388
+ </th>
1389
+ <th *ngIf="showSEColumn" class="srk-se-header srk--text-right srk--nowrap">
1390
+ {{ resolveColumnTitle('se', 'SE') }}
1391
+ </th>
1246
1392
  </tr>
1247
1393
  </thead>
1248
1394
  <tbody>
@@ -1251,11 +1397,32 @@ class RanklistComponent {
1251
1397
  *ngFor="let rankValue of getRankValues(row); let seriesIndex = index"
1252
1398
  class="srk--text-right srk--nowrap"
1253
1399
  [ngClass]="getSeriesSegmentClass(rankValue, data.series[seriesIndex])"
1400
+ [class.srk-series-segmented-column]="isSeriesSegmentedColumn(data.series[seriesIndex])"
1254
1401
  [ngStyle]="getSeriesSegmentStyle(rankValue, data.series[seriesIndex])"
1255
1402
  >
1256
1403
  {{ getRankText(rankValue, row) }}
1257
1404
  </td>
1258
1405
 
1406
+ <td
1407
+ *ngIf="splitOrganization"
1408
+ class="srk-organization-cell srk--text-left srk--nowrap"
1409
+ [class.srk-organization-cell-avatar]="showAvatarInOrganization() && !!row.user.avatar"
1410
+ >
1411
+ <div class="srk-organization-cell-content">
1412
+ <div *ngIf="showAvatarInOrganization() && row.user.avatar" class="srk-user-avatar">
1413
+ <img
1414
+ [src]="formatAssetUrl(row.user.avatar, 'user.avatar')"
1415
+ alt="User Avatar"
1416
+ />
1417
+ </div>
1418
+ <span
1419
+ class="srk-organization-name-text"
1420
+ [title]="row.user.organization ? resolveDisplayText(row.user.organization) : ''"
1421
+ [textContent]="row.user.organization ? resolveDisplayText(row.user.organization) : ''"
1422
+ ></span>
1423
+ </div>
1424
+ </td>
1425
+
1259
1426
  <ng-container *ngIf="userCellTemplate; else defaultUserCell">
1260
1427
  <ng-container
1261
1428
  [ngTemplateOutlet]="userCellTemplate.templateRef"
@@ -1272,7 +1439,7 @@ class RanklistComponent {
1272
1439
  (keydown.space)="activateUserCellFromKeyboard($event, row, rowIndex)"
1273
1440
  >
1274
1441
  <div class="srk-user-cell-content">
1275
- <div *ngIf="row.user.avatar" class="srk-user-avatar">
1442
+ <div *ngIf="row.user.avatar && !showAvatarInOrganization()" class="srk-user-avatar">
1276
1443
  <img
1277
1444
  [src]="formatAssetUrl(row.user.avatar, 'user.avatar')"
1278
1445
  alt="User Avatar"
@@ -1297,7 +1464,7 @@ class RanklistComponent {
1297
1464
  </span>
1298
1465
  </div>
1299
1466
  <p
1300
- *ngIf="row.user.organization"
1467
+ *ngIf="row.user.organization && !splitOrganization"
1301
1468
  class="srk-user-secondary-text srk--text-ellipsis"
1302
1469
  title=""
1303
1470
  >
@@ -1330,15 +1497,27 @@ class RanklistComponent {
1330
1497
  (keydown.enter)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1331
1498
  (keydown.space)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1332
1499
  >
1333
- <ng-container *ngIf="isNumber(status.score); else acceptedDetails">
1334
- <span class="srk-prest-status-block-score">{{ status.score }}</span>
1335
- <span class="srk-prest-status-block-score-details">
1336
- {{ acceptedStatusDetails(status) }}
1337
- </span>
1500
+ <span
1501
+ *ngIf="statusColorAsText && status.result === 'FB'"
1502
+ class="srk-prest-status-block-fb-star"
1503
+ [textContent]="firstBloodStar"
1504
+ ></span>
1505
+ <ng-container *ngIf="statusPresentation(status) as presentation">
1506
+ <ng-container *ngIf="isNumber(presentation.score); else statusText">
1507
+ <span class="srk-prest-status-block-score">{{ presentation.score }}</span>
1508
+ <span class="srk-prest-status-block-score-details">
1509
+ {{ presentation.scoreDetails }}
1510
+ </span>
1511
+ </ng-container>
1512
+ <ng-template #statusText>
1513
+ <ng-container *ngIf="presentation.secondary !== undefined; else singleStatus">
1514
+ <span class="srk-prest-status-block-primary">{{ presentation.primary || '' }}</span>
1515
+ <span [textContent]="statusSeparator"></span>
1516
+ <span class="srk-prest-status-block-secondary">{{ presentation.secondary }}</span>
1517
+ </ng-container>
1518
+ <ng-template #singleStatus>{{ presentation.primary }}</ng-template>
1519
+ </ng-template>
1338
1520
  </ng-container>
1339
- <ng-template #acceptedDetails>
1340
- {{ acceptedStatusDetails(status) }}
1341
- </ng-template>
1342
1521
  </td>
1343
1522
  <ng-template #failedOrFrozenStatus>
1344
1523
  <td
@@ -1350,16 +1529,101 @@ class RanklistComponent {
1350
1529
  (keydown.enter)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1351
1530
  (keydown.space)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1352
1531
  >
1353
- {{ status.tries }}
1532
+ <ng-container *ngIf="statusPresentation(status) as presentation">
1533
+ <ng-container *ngIf="presentation.secondary !== undefined; else singleStatus">
1534
+ <span class="srk-prest-status-block-primary">{{ presentation.primary || '' }}</span>
1535
+ <span [textContent]="statusSeparator"></span>
1536
+ <span class="srk-prest-status-block-secondary">{{ presentation.secondary }}</span>
1537
+ </ng-container>
1538
+ <ng-template #singleStatus>{{ presentation.primary }}</ng-template>
1539
+ </ng-container>
1354
1540
  </td>
1355
1541
  </ng-template>
1356
1542
  <ng-template #emptyStatus>
1357
- <td></td>
1543
+ <td class="srk-status-placeholder-cell srk--text-center srk--nowrap">
1544
+ {{ emptyStatusPlaceholder }}
1545
+ </td>
1358
1546
  </ng-template>
1359
1547
  </ng-template>
1360
1548
  </ng-container>
1549
+
1550
+ <td *ngIf="showDirtColumn" class="srk-dirt-cell srk--text-right srk--nowrap">
1551
+ {{ calculateDirtPercentage(row) }}
1552
+ </td>
1553
+ <td *ngIf="showSEColumn" class="srk-se-cell srk--text-right srk--nowrap">
1554
+ {{ calculateSEValue(row, problemStatistics) }}
1555
+ </td>
1361
1556
  </tr>
1362
1557
  </tbody>
1558
+ <tfoot *ngIf="showProblemStatisticsFooter">
1559
+ <tr
1560
+ *ngFor="let footerRow of problemStatisticsFooterRows"
1561
+ class="srk-problem-statistics-footer-row"
1562
+ >
1563
+ <td
1564
+ class="srk-problem-statistics-footer-labels srk--text-right srk--nowrap"
1565
+ [attr.colspan]="leftFooterColumnCount()"
1566
+ >
1567
+ <span
1568
+ class="srk-problem-statistics-footer-label srk--c-tooltip"
1569
+ [attr.data-tooltip]="footerRow.tooltip"
1570
+ >
1571
+ {{ footerRow.label }}
1572
+ </span>
1573
+ </td>
1574
+ <td
1575
+ *ngFor="let stat of problemStatistics"
1576
+ class="srk-problem-statistics-footer-cell srk--text-center srk--nowrap"
1577
+ >
1578
+ <span class="srk-problem-statistics-footer-primary">
1579
+ {{ getProblemStatisticsFooterCellPrimary(footerRow.key, stat) }}
1580
+ </span>
1581
+ <ng-container *ngIf="getProblemStatisticsFooterCellSecondary(footerRow.key, stat) !== undefined">
1582
+ <span> </span>
1583
+ <span class="srk-problem-statistics-footer-secondary">
1584
+ {{ getProblemStatisticsFooterCellSecondary(footerRow.key, stat) }}
1585
+ </span>
1586
+ </ng-container>
1587
+ </td>
1588
+ <td
1589
+ *ngIf="showDirtColumn"
1590
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-dirt-footer-cell srk--nowrap"
1591
+ >
1592
+ <span class="srk-problem-statistics-footer-primary"></span>
1593
+ </td>
1594
+ <td
1595
+ *ngIf="showSEColumn"
1596
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-se-footer-cell srk--nowrap"
1597
+ >
1598
+ <span class="srk-problem-statistics-footer-primary"></span>
1599
+ </td>
1600
+ </tr>
1601
+ <tr class="srk-problem-statistics-footer-row srk-problem-statistics-footer-problem-label-row">
1602
+ <td
1603
+ class="srk-problem-statistics-footer-labels srk--text-right srk--nowrap"
1604
+ [attr.colspan]="leftFooterColumnCount()"
1605
+ ></td>
1606
+ <td
1607
+ *ngFor="let problem of data.problems; let problemIndex = index"
1608
+ class="srk-problem-statistics-footer-cell srk-problem-statistics-footer-problem-header srk-problem-header srk--text-center srk--nowrap"
1609
+ [style.background-image]="problemHeaderBackgroundImage(problem, 0)"
1610
+ >
1611
+ <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1612
+ </td>
1613
+ <td
1614
+ *ngIf="showDirtColumn"
1615
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-dirt-footer-cell srk--nowrap"
1616
+ >
1617
+ <span class="srk-problem-statistics-footer-primary"></span>
1618
+ </td>
1619
+ <td
1620
+ *ngIf="showSEColumn"
1621
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-se-footer-cell srk--nowrap"
1622
+ >
1623
+ <span class="srk-problem-statistics-footer-primary"></span>
1624
+ </td>
1625
+ </tr>
1626
+ </tfoot>
1363
1627
  </table>
1364
1628
  </div>
1365
1629
  </ng-template>
@@ -1386,20 +1650,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1386
1650
  <ng-template #ranklistTable>
1387
1651
  <div class="srk-common-table srk-main">
1388
1652
  <table
1389
- [class.srk-table-row-bordered]="borderedRows"
1653
+ [class.srk-table-row-bordered]="borderedRows || rowBordered"
1654
+ [class.srk-table-column-bordered]="columnBordered"
1390
1655
  [class.srk-table-row-striped]="stripedRows"
1391
1656
  >
1392
1657
  <thead>
1393
1658
  <tr>
1394
1659
  <th
1395
- *ngFor="let seriesItem of data.series"
1660
+ *ngFor="let seriesItem of data.series; let seriesIndex = index"
1396
1661
  class="srk-series-header srk--text-right srk--nowrap"
1662
+ [class.srk-series-segmented-column]="isSeriesSegmentedColumn(seriesItem)"
1397
1663
  >
1398
- {{ seriesItem.title }}
1664
+ {{ resolveSeriesColumnTitle(seriesItem, seriesIndex) }}
1665
+ </th>
1666
+ <th
1667
+ *ngIf="splitOrganization"
1668
+ class="srk-organization-header srk--text-left srk--nowrap"
1669
+ >
1670
+ {{ resolveColumnTitle('organization', 'Organization') }}
1671
+ </th>
1672
+ <th class="srk--text-left srk--nowrap">{{ resolveColumnTitle('user', 'Name') }}</th>
1673
+ <th class="srk--text-right srk--nowrap">{{ resolveColumnTitle('score', 'Score') }}</th>
1674
+ <th *ngIf="showTimeColumn()" class="srk--text-right srk--nowrap">
1675
+ {{ resolveColumnTitle('time', 'Time') }}
1399
1676
  </th>
1400
- <th class="srk--text-left srk--nowrap">Name</th>
1401
- <th class="srk--nowrap">Score</th>
1402
- <th *ngIf="showTimeColumn()" class="srk--nowrap">Time</th>
1403
1677
  <th
1404
1678
  *ngFor="let problem of data.problems; let problemIndex = index"
1405
1679
  class="srk--nowrap srk-problem-header"
@@ -1432,6 +1706,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1432
1706
  </span>
1433
1707
  </ng-template>
1434
1708
  </th>
1709
+ <th *ngIf="showDirtColumn" class="srk-dirt-header srk--text-right srk--nowrap">
1710
+ {{ resolveColumnTitle('dirt', 'Dirt') }}
1711
+ </th>
1712
+ <th *ngIf="showSEColumn" class="srk-se-header srk--text-right srk--nowrap">
1713
+ {{ resolveColumnTitle('se', 'SE') }}
1714
+ </th>
1435
1715
  </tr>
1436
1716
  </thead>
1437
1717
  <tbody>
@@ -1440,11 +1720,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1440
1720
  *ngFor="let rankValue of getRankValues(row); let seriesIndex = index"
1441
1721
  class="srk--text-right srk--nowrap"
1442
1722
  [ngClass]="getSeriesSegmentClass(rankValue, data.series[seriesIndex])"
1723
+ [class.srk-series-segmented-column]="isSeriesSegmentedColumn(data.series[seriesIndex])"
1443
1724
  [ngStyle]="getSeriesSegmentStyle(rankValue, data.series[seriesIndex])"
1444
1725
  >
1445
1726
  {{ getRankText(rankValue, row) }}
1446
1727
  </td>
1447
1728
 
1729
+ <td
1730
+ *ngIf="splitOrganization"
1731
+ class="srk-organization-cell srk--text-left srk--nowrap"
1732
+ [class.srk-organization-cell-avatar]="showAvatarInOrganization() && !!row.user.avatar"
1733
+ >
1734
+ <div class="srk-organization-cell-content">
1735
+ <div *ngIf="showAvatarInOrganization() && row.user.avatar" class="srk-user-avatar">
1736
+ <img
1737
+ [src]="formatAssetUrl(row.user.avatar, 'user.avatar')"
1738
+ alt="User Avatar"
1739
+ />
1740
+ </div>
1741
+ <span
1742
+ class="srk-organization-name-text"
1743
+ [title]="row.user.organization ? resolveDisplayText(row.user.organization) : ''"
1744
+ [textContent]="row.user.organization ? resolveDisplayText(row.user.organization) : ''"
1745
+ ></span>
1746
+ </div>
1747
+ </td>
1748
+
1448
1749
  <ng-container *ngIf="userCellTemplate; else defaultUserCell">
1449
1750
  <ng-container
1450
1751
  [ngTemplateOutlet]="userCellTemplate.templateRef"
@@ -1461,7 +1762,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1461
1762
  (keydown.space)="activateUserCellFromKeyboard($event, row, rowIndex)"
1462
1763
  >
1463
1764
  <div class="srk-user-cell-content">
1464
- <div *ngIf="row.user.avatar" class="srk-user-avatar">
1765
+ <div *ngIf="row.user.avatar && !showAvatarInOrganization()" class="srk-user-avatar">
1465
1766
  <img
1466
1767
  [src]="formatAssetUrl(row.user.avatar, 'user.avatar')"
1467
1768
  alt="User Avatar"
@@ -1486,7 +1787,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1486
1787
  </span>
1487
1788
  </div>
1488
1789
  <p
1489
- *ngIf="row.user.organization"
1790
+ *ngIf="row.user.organization && !splitOrganization"
1490
1791
  class="srk-user-secondary-text srk--text-ellipsis"
1491
1792
  title=""
1492
1793
  >
@@ -1519,15 +1820,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1519
1820
  (keydown.enter)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1520
1821
  (keydown.space)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1521
1822
  >
1522
- <ng-container *ngIf="isNumber(status.score); else acceptedDetails">
1523
- <span class="srk-prest-status-block-score">{{ status.score }}</span>
1524
- <span class="srk-prest-status-block-score-details">
1525
- {{ acceptedStatusDetails(status) }}
1526
- </span>
1823
+ <span
1824
+ *ngIf="statusColorAsText && status.result === 'FB'"
1825
+ class="srk-prest-status-block-fb-star"
1826
+ [textContent]="firstBloodStar"
1827
+ ></span>
1828
+ <ng-container *ngIf="statusPresentation(status) as presentation">
1829
+ <ng-container *ngIf="isNumber(presentation.score); else statusText">
1830
+ <span class="srk-prest-status-block-score">{{ presentation.score }}</span>
1831
+ <span class="srk-prest-status-block-score-details">
1832
+ {{ presentation.scoreDetails }}
1833
+ </span>
1834
+ </ng-container>
1835
+ <ng-template #statusText>
1836
+ <ng-container *ngIf="presentation.secondary !== undefined; else singleStatus">
1837
+ <span class="srk-prest-status-block-primary">{{ presentation.primary || '' }}</span>
1838
+ <span [textContent]="statusSeparator"></span>
1839
+ <span class="srk-prest-status-block-secondary">{{ presentation.secondary }}</span>
1840
+ </ng-container>
1841
+ <ng-template #singleStatus>{{ presentation.primary }}</ng-template>
1842
+ </ng-template>
1527
1843
  </ng-container>
1528
- <ng-template #acceptedDetails>
1529
- {{ acceptedStatusDetails(status) }}
1530
- </ng-template>
1531
1844
  </td>
1532
1845
  <ng-template #failedOrFrozenStatus>
1533
1846
  <td
@@ -1539,16 +1852,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1539
1852
  (keydown.enter)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1540
1853
  (keydown.space)="activateStatusCellFromKeyboard($event, row, rowIndex, status, problemIndex)"
1541
1854
  >
1542
- {{ status.tries }}
1855
+ <ng-container *ngIf="statusPresentation(status) as presentation">
1856
+ <ng-container *ngIf="presentation.secondary !== undefined; else singleStatus">
1857
+ <span class="srk-prest-status-block-primary">{{ presentation.primary || '' }}</span>
1858
+ <span [textContent]="statusSeparator"></span>
1859
+ <span class="srk-prest-status-block-secondary">{{ presentation.secondary }}</span>
1860
+ </ng-container>
1861
+ <ng-template #singleStatus>{{ presentation.primary }}</ng-template>
1862
+ </ng-container>
1543
1863
  </td>
1544
1864
  </ng-template>
1545
1865
  <ng-template #emptyStatus>
1546
- <td></td>
1866
+ <td class="srk-status-placeholder-cell srk--text-center srk--nowrap">
1867
+ {{ emptyStatusPlaceholder }}
1868
+ </td>
1547
1869
  </ng-template>
1548
1870
  </ng-template>
1549
1871
  </ng-container>
1872
+
1873
+ <td *ngIf="showDirtColumn" class="srk-dirt-cell srk--text-right srk--nowrap">
1874
+ {{ calculateDirtPercentage(row) }}
1875
+ </td>
1876
+ <td *ngIf="showSEColumn" class="srk-se-cell srk--text-right srk--nowrap">
1877
+ {{ calculateSEValue(row, problemStatistics) }}
1878
+ </td>
1550
1879
  </tr>
1551
1880
  </tbody>
1881
+ <tfoot *ngIf="showProblemStatisticsFooter">
1882
+ <tr
1883
+ *ngFor="let footerRow of problemStatisticsFooterRows"
1884
+ class="srk-problem-statistics-footer-row"
1885
+ >
1886
+ <td
1887
+ class="srk-problem-statistics-footer-labels srk--text-right srk--nowrap"
1888
+ [attr.colspan]="leftFooterColumnCount()"
1889
+ >
1890
+ <span
1891
+ class="srk-problem-statistics-footer-label srk--c-tooltip"
1892
+ [attr.data-tooltip]="footerRow.tooltip"
1893
+ >
1894
+ {{ footerRow.label }}
1895
+ </span>
1896
+ </td>
1897
+ <td
1898
+ *ngFor="let stat of problemStatistics"
1899
+ class="srk-problem-statistics-footer-cell srk--text-center srk--nowrap"
1900
+ >
1901
+ <span class="srk-problem-statistics-footer-primary">
1902
+ {{ getProblemStatisticsFooterCellPrimary(footerRow.key, stat) }}
1903
+ </span>
1904
+ <ng-container *ngIf="getProblemStatisticsFooterCellSecondary(footerRow.key, stat) !== undefined">
1905
+ <span> </span>
1906
+ <span class="srk-problem-statistics-footer-secondary">
1907
+ {{ getProblemStatisticsFooterCellSecondary(footerRow.key, stat) }}
1908
+ </span>
1909
+ </ng-container>
1910
+ </td>
1911
+ <td
1912
+ *ngIf="showDirtColumn"
1913
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-dirt-footer-cell srk--nowrap"
1914
+ >
1915
+ <span class="srk-problem-statistics-footer-primary"></span>
1916
+ </td>
1917
+ <td
1918
+ *ngIf="showSEColumn"
1919
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-se-footer-cell srk--nowrap"
1920
+ >
1921
+ <span class="srk-problem-statistics-footer-primary"></span>
1922
+ </td>
1923
+ </tr>
1924
+ <tr class="srk-problem-statistics-footer-row srk-problem-statistics-footer-problem-label-row">
1925
+ <td
1926
+ class="srk-problem-statistics-footer-labels srk--text-right srk--nowrap"
1927
+ [attr.colspan]="leftFooterColumnCount()"
1928
+ ></td>
1929
+ <td
1930
+ *ngFor="let problem of data.problems; let problemIndex = index"
1931
+ class="srk-problem-statistics-footer-cell srk-problem-statistics-footer-problem-header srk-problem-header srk--text-center srk--nowrap"
1932
+ [style.background-image]="problemHeaderBackgroundImage(problem, 0)"
1933
+ >
1934
+ <span class="srk--display-block">{{ problemAlias(problem, problemIndex) }}</span>
1935
+ </td>
1936
+ <td
1937
+ *ngIf="showDirtColumn"
1938
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-dirt-footer-cell srk--nowrap"
1939
+ >
1940
+ <span class="srk-problem-statistics-footer-primary"></span>
1941
+ </td>
1942
+ <td
1943
+ *ngIf="showSEColumn"
1944
+ class="srk-problem-statistics-footer-cell srk-extra-statistics-footer-cell srk-se-footer-cell srk--nowrap"
1945
+ >
1946
+ <span class="srk-problem-statistics-footer-primary"></span>
1947
+ </td>
1948
+ </tr>
1949
+ </tfoot>
1552
1950
  </table>
1553
1951
  </div>
1554
1952
  </ng-template>
@@ -1561,10 +1959,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1561
1959
  type: Input
1562
1960
  }], borderedRows: [{
1563
1961
  type: Input
1962
+ }], rowBordered: [{
1963
+ type: Input
1964
+ }], columnBordered: [{
1965
+ type: Input
1564
1966
  }], stripedRows: [{
1565
1967
  type: Input
1566
1968
  }], formatSrkAssetUrl: [{
1567
1969
  type: Input
1970
+ }], splitOrganization: [{
1971
+ type: Input
1972
+ }], columnTitles: [{
1973
+ type: Input
1974
+ }], statusCellPreset: [{
1975
+ type: Input
1976
+ }], statusColorAsText: [{
1977
+ type: Input
1978
+ }], showProblemStatisticsFooter: [{
1979
+ type: Input
1980
+ }], showDirtColumn: [{
1981
+ type: Input
1982
+ }], showSEColumn: [{
1983
+ type: Input
1984
+ }], emptyStatusPlaceholder: [{
1985
+ type: Input
1986
+ }], userAvatarPlacement: [{
1987
+ type: Input
1568
1988
  }], userClick: [{
1569
1989
  type: Output
1570
1990
  }], solutionClick: [{