@xcpcio/core 0.55.2 → 0.57.0

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.
package/dist/index.cjs CHANGED
@@ -512,7 +512,7 @@ class Submission {
512
512
  }
513
513
  }
514
514
  }
515
- function createSubmission(submissionJSON) {
515
+ function createSubmission(submissionJSON, contest) {
516
516
  const s = new Submission();
517
517
  s.id = String(submissionJSON.id ?? submissionJSON.submission_id ?? "");
518
518
  s.teamId = String(submissionJSON.team_id);
@@ -528,15 +528,19 @@ function createSubmission(submissionJSON) {
528
528
  }
529
529
  if (submissionJSON.reaction) {
530
530
  s.reaction = submissionJSON.reaction;
531
+ } else if (contest?.options.reactionVideoUrlTemplate) {
532
+ s.reaction = {
533
+ url: contest.options.reactionVideoUrlTemplate.replace(/\$\{submission_id\}/, s.id)
534
+ };
531
535
  }
532
536
  return s;
533
537
  }
534
- function createSubmissions(submissionsJSON) {
538
+ function createSubmissions(submissionsJSON, contest) {
535
539
  if (Array.isArray(submissionsJSON)) {
536
- return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }));
540
+ return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }, contest));
537
541
  } else {
538
542
  const submissions = Object.entries(submissionsJSON).map(
539
- ([submissionId, s]) => createSubmission({ ...s, id: s.submission_id ?? submissionId })
543
+ ([submissionId, s]) => createSubmission({ ...s, id: s.submission_id ?? submissionId }, contest)
540
544
  );
541
545
  return submissions;
542
546
  }
@@ -928,12 +932,16 @@ class ContestOptions {
928
932
  submissionHasTimeField;
929
933
  submissionHasLanguageField;
930
934
  submissionEnableActionField;
935
+ submissionHasReactionField;
936
+ reactionVideoUrlTemplate;
931
937
  constructor() {
932
938
  this.calculationOfPenalty = "in_minutes";
933
939
  this.submissionTimestampUnit = "second";
934
940
  this.submissionHasTimeField = false;
935
941
  this.submissionHasLanguageField = false;
936
942
  this.submissionEnableActionField = false;
943
+ this.submissionHasReactionField = false;
944
+ this.reactionVideoUrlTemplate = void 0;
937
945
  }
938
946
  }
939
947
  function createContestOptions(contestOptionsJSON = {}) {
@@ -945,9 +953,11 @@ function createContestOptions(contestOptionsJSON = {}) {
945
953
  if (j.submission_timestamp_unit) {
946
954
  o.submissionTimestampUnit = j.submission_timestamp_unit;
947
955
  }
948
- if (j.submission_has_reaction) {
949
- o.submissionEnableActionField = j.submission_has_reaction;
956
+ if (j.submission_has_reaction || j.has_reaction_videos) {
957
+ o.submissionHasReactionField = true;
950
958
  }
959
+ o.submissionEnableActionField = o.submissionHasReactionField;
960
+ o.reactionVideoUrlTemplate = j.reaction_video_url_template;
951
961
  return o;
952
962
  }
953
963
 
@@ -1405,13 +1415,13 @@ class GeneralExcelConverter {
1405
1415
  }
1406
1416
  convertToSheet(rank) {
1407
1417
  const aoa = this.convertToAoa(rank);
1408
- const sheet = XLSX__namespace.utils.aoa_to_sheet(aoa);
1418
+ const sheet = XLSX__namespace.utils.aoa_to_sheet(aoa.aoa);
1409
1419
  const cols = [];
1410
- const head = aoa[1];
1420
+ const head = aoa.aoa[1];
1411
1421
  for (let j = 0; j < head.length; j++) {
1412
1422
  let wch = 10;
1413
- for (let i = 1; i < aoa.length; i++) {
1414
- wch = Math.max(wch, stringWidth__default(aoa[i][j]) + 2);
1423
+ for (let i = 1; i < aoa.aoa.length; i++) {
1424
+ wch = Math.max(wch, stringWidth__default(aoa.aoa[i][j]) + 2);
1415
1425
  }
1416
1426
  cols.push({
1417
1427
  wch
@@ -1445,11 +1455,22 @@ class GeneralExcelConverter {
1445
1455
  },
1446
1456
  font
1447
1457
  };
1448
- for (let i = 1; i < aoa.length; i++) {
1449
- for (let j = 0; j < aoa[i].length; j++) {
1458
+ const firstSolvedCellStyle = {
1459
+ ...cellStyle,
1460
+ fill: {
1461
+ fgColor: { rgb: "009900" }
1462
+ }
1463
+ };
1464
+ for (let i = 1; i < aoa.aoa.length; i++) {
1465
+ for (let j = 0; j < aoa.aoa[i].length; j++) {
1450
1466
  const cellAddress = XLSX__namespace.utils.encode_cell({ r: i, c: j });
1451
1467
  const cell = sheet[cellAddress];
1452
- cell.s = cellStyle;
1468
+ const specialCell = aoa.specialCells.find((sc) => sc.row === i && sc.col === j);
1469
+ if (specialCell?.type === "firstSolved" /* FIRST_SOLVED */) {
1470
+ cell.s = firstSolvedCellStyle;
1471
+ } else {
1472
+ cell.s = cellStyle;
1473
+ }
1453
1474
  }
1454
1475
  }
1455
1476
  {
@@ -1464,6 +1485,7 @@ class GeneralExcelConverter {
1464
1485
  }
1465
1486
  convertToAoa(rank) {
1466
1487
  const aoa = [];
1488
+ const specialCells = [];
1467
1489
  const enableAwards = rank.contest.isEnableAwards(rank.options.group);
1468
1490
  const enableMembers = (Array.isArray(rank.teams) && rank.teams[0]?.members) ?? false;
1469
1491
  const enableCoach = rank.teams[0]?.coach ?? false;
@@ -1509,6 +1531,13 @@ class GeneralExcelConverter {
1509
1531
  }
1510
1532
  if (p.isSolved) {
1511
1533
  arr.push(`+${p.totalCount}(${p.solvedTimestampToMinute})`);
1534
+ if (p.isFirstSolved) {
1535
+ specialCells.push({
1536
+ row: aoa.length,
1537
+ col: arr.length - 1,
1538
+ type: "firstSolved" /* FIRST_SOLVED */
1539
+ });
1540
+ }
1512
1541
  }
1513
1542
  if (p.isWrongAnswer) {
1514
1543
  arr.push(`-${p.failedCount}`);
@@ -1543,7 +1572,7 @@ class GeneralExcelConverter {
1543
1572
  arr.push(team.isGirl ? "Y" : "N");
1544
1573
  aoa.push(arr);
1545
1574
  }
1546
- return aoa;
1575
+ return { aoa, specialCells };
1547
1576
  }
1548
1577
  }
1549
1578
 
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { TimeUnit, SubmissionReaction, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, CalculationOfPenalty, Image, Team as Team$1, Teams as Teams$1, Lang, StatusTimeDisplay, MedalPreset, BannerMode, ContestState, Contest as Contest$1, ContestIndex as ContestIndex$1, IPerson, IRatingHistory, IRatingUser, IRating } from '@xcpcio/types';
1
+ import { CalculationOfPenalty, TimeUnit, Lang, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, SubmissionReaction, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Team as Team$1, Teams as Teams$1, ContestIndex as ContestIndex$1, IPerson, IRatingHistory, IRatingUser, IRating } from '@xcpcio/types';
2
2
  import dayjs from 'dayjs';
3
3
  export { default as dayjs } from 'dayjs';
4
4
  import * as XLSX from 'xlsx-js-style';
@@ -19,6 +19,78 @@ declare class Award {
19
19
  declare function isValidMedalType(medal: MedalType): boolean;
20
20
  type Awards = Map<string, Award[]>;
21
21
 
22
+ declare class ContestOptions {
23
+ calculationOfPenalty: CalculationOfPenalty;
24
+ submissionTimestampUnit: TimeUnit;
25
+ submissionHasTimeField: boolean;
26
+ submissionHasLanguageField: boolean;
27
+ submissionEnableActionField: boolean;
28
+ submissionHasReactionField: boolean;
29
+ reactionVideoUrlTemplate?: string;
30
+ constructor();
31
+ }
32
+
33
+ declare class Group {
34
+ names: Map<Lang, string>;
35
+ defaultLang: Lang;
36
+ isDefault: boolean;
37
+ constructor();
38
+ }
39
+
40
+ declare function calcDirt(attemptedNum: number, solvedNum: number): number;
41
+
42
+ declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
43
+ declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
44
+
45
+ declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
46
+ declare function getTimestamp(time: number | dayjs.Dayjs): number;
47
+ declare function getTimeDiff(seconds: number): string;
48
+
49
+ declare class Contest {
50
+ id: string;
51
+ name: string;
52
+ startTime: dayjs.Dayjs;
53
+ endTime: dayjs.Dayjs;
54
+ freezeTime: dayjs.Dayjs;
55
+ replayStartTime?: dayjs.Dayjs;
56
+ replayEndTime?: dayjs.Dayjs;
57
+ replayFreezeTime?: dayjs.Dayjs;
58
+ replayNowTime?: dayjs.Dayjs;
59
+ replayContestStartTimestamp?: number;
60
+ totalDurationTimestamp: number;
61
+ freezeDurationTimestamp: number;
62
+ unFreezeDurationTimestamp: number;
63
+ penalty: number;
64
+ problems: Problems;
65
+ problemsMap: Map<string, Problem>;
66
+ statusTimeDisplay: StatusTimeDisplay;
67
+ badge?: string;
68
+ medal?: Record<string, Record<string, number>> | MedalPreset;
69
+ awards?: Awards;
70
+ organization?: string;
71
+ group: Map<string, Group>;
72
+ tag: Map<string, string>;
73
+ logo?: Image;
74
+ banner?: Image;
75
+ bannerMode?: BannerMode;
76
+ boardLink?: string;
77
+ options: ContestOptions;
78
+ constructor();
79
+ getStartTime(): dayjs.Dayjs;
80
+ getEndTime(): dayjs.Dayjs;
81
+ getFreezeTime(): dayjs.Dayjs;
82
+ getContestDuration(timeFormat?: string): string;
83
+ getContestState(nowTime?: Date): ContestState;
84
+ getContestPendingTime(nowTime?: Date): string;
85
+ getContestElapsedTime(nowTime?: Date): string;
86
+ getContestRemainingTime(nowTime?: Date): string;
87
+ getContestProgressRatio(nowTime?: Date): number;
88
+ isEnableAwards(group: string): boolean;
89
+ resetReplayTime(): void;
90
+ setReplayTime(replayStartTimestamp: number): void;
91
+ }
92
+ declare function createContest(contestJSON: Contest$1): Contest;
93
+
22
94
  declare class Submission {
23
95
  id: string;
24
96
  teamId: string;
@@ -44,8 +116,8 @@ declare class Submission {
44
116
  static compare(lhs: Submission, rhs: Submission): number;
45
117
  }
46
118
  type Submissions = Array<Submission>;
47
- declare function createSubmission(submissionJSON: Submission$1): Submission;
48
- declare function createSubmissions(submissionsJSON: Submissions$1): Submissions;
119
+ declare function createSubmission(submissionJSON: Submission$1, contest?: Contest): Submission;
120
+ declare function createSubmissions(submissionsJSON: Submissions$1, contest?: Contest): Submissions;
49
121
 
50
122
  declare class ProblemStatistics {
51
123
  acceptedNum: number;
@@ -102,15 +174,6 @@ declare class TeamProblemStatistics {
102
174
  get penaltyInSecond(): number;
103
175
  }
104
176
 
105
- declare class ContestOptions {
106
- calculationOfPenalty: CalculationOfPenalty;
107
- submissionTimestampUnit: TimeUnit;
108
- submissionHasTimeField: boolean;
109
- submissionHasLanguageField: boolean;
110
- submissionEnableActionField: boolean;
111
- constructor();
112
- }
113
-
114
177
  declare class PlaceChartPointData {
115
178
  timePoint: number;
116
179
  rank: number;
@@ -215,67 +278,6 @@ declare class BattleOfGiants {
215
278
  FromBase64(base64: string): void;
216
279
  }
217
280
 
218
- declare class Group {
219
- names: Map<Lang, string>;
220
- defaultLang: Lang;
221
- isDefault: boolean;
222
- constructor();
223
- }
224
-
225
- declare function calcDirt(attemptedNum: number, solvedNum: number): number;
226
-
227
- declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
228
- declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
229
-
230
- declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
231
- declare function getTimestamp(time: number | dayjs.Dayjs): number;
232
- declare function getTimeDiff(seconds: number): string;
233
-
234
- declare class Contest {
235
- id: string;
236
- name: string;
237
- startTime: dayjs.Dayjs;
238
- endTime: dayjs.Dayjs;
239
- freezeTime: dayjs.Dayjs;
240
- replayStartTime?: dayjs.Dayjs;
241
- replayEndTime?: dayjs.Dayjs;
242
- replayFreezeTime?: dayjs.Dayjs;
243
- replayNowTime?: dayjs.Dayjs;
244
- replayContestStartTimestamp?: number;
245
- totalDurationTimestamp: number;
246
- freezeDurationTimestamp: number;
247
- unFreezeDurationTimestamp: number;
248
- penalty: number;
249
- problems: Problems;
250
- problemsMap: Map<string, Problem>;
251
- statusTimeDisplay: StatusTimeDisplay;
252
- badge?: string;
253
- medal?: Record<string, Record<string, number>> | MedalPreset;
254
- awards?: Awards;
255
- organization?: string;
256
- group: Map<string, Group>;
257
- tag: Map<string, string>;
258
- logo?: Image;
259
- banner?: Image;
260
- bannerMode?: BannerMode;
261
- boardLink?: string;
262
- options: ContestOptions;
263
- constructor();
264
- getStartTime(): dayjs.Dayjs;
265
- getEndTime(): dayjs.Dayjs;
266
- getFreezeTime(): dayjs.Dayjs;
267
- getContestDuration(timeFormat?: string): string;
268
- getContestState(nowTime?: Date): ContestState;
269
- getContestPendingTime(nowTime?: Date): string;
270
- getContestElapsedTime(nowTime?: Date): string;
271
- getContestRemainingTime(nowTime?: Date): string;
272
- getContestProgressRatio(nowTime?: Date): number;
273
- isEnableAwards(group: string): boolean;
274
- resetReplayTime(): void;
275
- setReplayTime(replayStartTimestamp: number): void;
276
- }
277
- declare function createContest(contestJSON: Contest$1): Contest;
278
-
279
281
  declare class ContestIndexConfig {
280
282
  contestName: string;
281
283
  startTime: dayjs.Dayjs;
@@ -516,4 +518,5 @@ declare function isRejected(status: SubmissionStatus): boolean;
516
518
  declare function isPending(status: SubmissionStatus): boolean;
517
519
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
518
520
 
519
- export { Award, type Awards, Balloon, type Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, type ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, type Persons, PlaceChartPointData, Problem, ProblemStatistics, type Problems, Rank, RankOptions, RankStatistics, type Ranks, Rating, RatingCalculator, type RatingHistories, RatingHistory, RatingLevel, RatingLevelToString, RatingUser, type RatingUserMap, type RatingUsers, RatingUtility, Resolver, ResolverVue, type SelectOptionItem, Submission, type Submissions, Team, TeamProblemStatistics, type Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
521
+ export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Rating, RatingCalculator, RatingHistory, RatingLevel, RatingLevelToString, RatingUser, RatingUtility, Resolver, ResolverVue, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
522
+ export type { Awards, Balloons, ContestIndexList, Persons, Problems, Ranks, RatingHistories, RatingUserMap, RatingUsers, SelectOptionItem, Submissions, Teams };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { TimeUnit, SubmissionReaction, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, CalculationOfPenalty, Image, Team as Team$1, Teams as Teams$1, Lang, StatusTimeDisplay, MedalPreset, BannerMode, ContestState, Contest as Contest$1, ContestIndex as ContestIndex$1, IPerson, IRatingHistory, IRatingUser, IRating } from '@xcpcio/types';
1
+ import { CalculationOfPenalty, TimeUnit, Lang, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, SubmissionReaction, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Team as Team$1, Teams as Teams$1, ContestIndex as ContestIndex$1, IPerson, IRatingHistory, IRatingUser, IRating } from '@xcpcio/types';
2
2
  import dayjs from 'dayjs';
3
3
  export { default as dayjs } from 'dayjs';
4
4
  import * as XLSX from 'xlsx-js-style';
@@ -19,6 +19,78 @@ declare class Award {
19
19
  declare function isValidMedalType(medal: MedalType): boolean;
20
20
  type Awards = Map<string, Award[]>;
21
21
 
22
+ declare class ContestOptions {
23
+ calculationOfPenalty: CalculationOfPenalty;
24
+ submissionTimestampUnit: TimeUnit;
25
+ submissionHasTimeField: boolean;
26
+ submissionHasLanguageField: boolean;
27
+ submissionEnableActionField: boolean;
28
+ submissionHasReactionField: boolean;
29
+ reactionVideoUrlTemplate?: string;
30
+ constructor();
31
+ }
32
+
33
+ declare class Group {
34
+ names: Map<Lang, string>;
35
+ defaultLang: Lang;
36
+ isDefault: boolean;
37
+ constructor();
38
+ }
39
+
40
+ declare function calcDirt(attemptedNum: number, solvedNum: number): number;
41
+
42
+ declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
43
+ declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
44
+
45
+ declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
46
+ declare function getTimestamp(time: number | dayjs.Dayjs): number;
47
+ declare function getTimeDiff(seconds: number): string;
48
+
49
+ declare class Contest {
50
+ id: string;
51
+ name: string;
52
+ startTime: dayjs.Dayjs;
53
+ endTime: dayjs.Dayjs;
54
+ freezeTime: dayjs.Dayjs;
55
+ replayStartTime?: dayjs.Dayjs;
56
+ replayEndTime?: dayjs.Dayjs;
57
+ replayFreezeTime?: dayjs.Dayjs;
58
+ replayNowTime?: dayjs.Dayjs;
59
+ replayContestStartTimestamp?: number;
60
+ totalDurationTimestamp: number;
61
+ freezeDurationTimestamp: number;
62
+ unFreezeDurationTimestamp: number;
63
+ penalty: number;
64
+ problems: Problems;
65
+ problemsMap: Map<string, Problem>;
66
+ statusTimeDisplay: StatusTimeDisplay;
67
+ badge?: string;
68
+ medal?: Record<string, Record<string, number>> | MedalPreset;
69
+ awards?: Awards;
70
+ organization?: string;
71
+ group: Map<string, Group>;
72
+ tag: Map<string, string>;
73
+ logo?: Image;
74
+ banner?: Image;
75
+ bannerMode?: BannerMode;
76
+ boardLink?: string;
77
+ options: ContestOptions;
78
+ constructor();
79
+ getStartTime(): dayjs.Dayjs;
80
+ getEndTime(): dayjs.Dayjs;
81
+ getFreezeTime(): dayjs.Dayjs;
82
+ getContestDuration(timeFormat?: string): string;
83
+ getContestState(nowTime?: Date): ContestState;
84
+ getContestPendingTime(nowTime?: Date): string;
85
+ getContestElapsedTime(nowTime?: Date): string;
86
+ getContestRemainingTime(nowTime?: Date): string;
87
+ getContestProgressRatio(nowTime?: Date): number;
88
+ isEnableAwards(group: string): boolean;
89
+ resetReplayTime(): void;
90
+ setReplayTime(replayStartTimestamp: number): void;
91
+ }
92
+ declare function createContest(contestJSON: Contest$1): Contest;
93
+
22
94
  declare class Submission {
23
95
  id: string;
24
96
  teamId: string;
@@ -44,8 +116,8 @@ declare class Submission {
44
116
  static compare(lhs: Submission, rhs: Submission): number;
45
117
  }
46
118
  type Submissions = Array<Submission>;
47
- declare function createSubmission(submissionJSON: Submission$1): Submission;
48
- declare function createSubmissions(submissionsJSON: Submissions$1): Submissions;
119
+ declare function createSubmission(submissionJSON: Submission$1, contest?: Contest): Submission;
120
+ declare function createSubmissions(submissionsJSON: Submissions$1, contest?: Contest): Submissions;
49
121
 
50
122
  declare class ProblemStatistics {
51
123
  acceptedNum: number;
@@ -102,15 +174,6 @@ declare class TeamProblemStatistics {
102
174
  get penaltyInSecond(): number;
103
175
  }
104
176
 
105
- declare class ContestOptions {
106
- calculationOfPenalty: CalculationOfPenalty;
107
- submissionTimestampUnit: TimeUnit;
108
- submissionHasTimeField: boolean;
109
- submissionHasLanguageField: boolean;
110
- submissionEnableActionField: boolean;
111
- constructor();
112
- }
113
-
114
177
  declare class PlaceChartPointData {
115
178
  timePoint: number;
116
179
  rank: number;
@@ -215,67 +278,6 @@ declare class BattleOfGiants {
215
278
  FromBase64(base64: string): void;
216
279
  }
217
280
 
218
- declare class Group {
219
- names: Map<Lang, string>;
220
- defaultLang: Lang;
221
- isDefault: boolean;
222
- constructor();
223
- }
224
-
225
- declare function calcDirt(attemptedNum: number, solvedNum: number): number;
226
-
227
- declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
228
- declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
229
-
230
- declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
231
- declare function getTimestamp(time: number | dayjs.Dayjs): number;
232
- declare function getTimeDiff(seconds: number): string;
233
-
234
- declare class Contest {
235
- id: string;
236
- name: string;
237
- startTime: dayjs.Dayjs;
238
- endTime: dayjs.Dayjs;
239
- freezeTime: dayjs.Dayjs;
240
- replayStartTime?: dayjs.Dayjs;
241
- replayEndTime?: dayjs.Dayjs;
242
- replayFreezeTime?: dayjs.Dayjs;
243
- replayNowTime?: dayjs.Dayjs;
244
- replayContestStartTimestamp?: number;
245
- totalDurationTimestamp: number;
246
- freezeDurationTimestamp: number;
247
- unFreezeDurationTimestamp: number;
248
- penalty: number;
249
- problems: Problems;
250
- problemsMap: Map<string, Problem>;
251
- statusTimeDisplay: StatusTimeDisplay;
252
- badge?: string;
253
- medal?: Record<string, Record<string, number>> | MedalPreset;
254
- awards?: Awards;
255
- organization?: string;
256
- group: Map<string, Group>;
257
- tag: Map<string, string>;
258
- logo?: Image;
259
- banner?: Image;
260
- bannerMode?: BannerMode;
261
- boardLink?: string;
262
- options: ContestOptions;
263
- constructor();
264
- getStartTime(): dayjs.Dayjs;
265
- getEndTime(): dayjs.Dayjs;
266
- getFreezeTime(): dayjs.Dayjs;
267
- getContestDuration(timeFormat?: string): string;
268
- getContestState(nowTime?: Date): ContestState;
269
- getContestPendingTime(nowTime?: Date): string;
270
- getContestElapsedTime(nowTime?: Date): string;
271
- getContestRemainingTime(nowTime?: Date): string;
272
- getContestProgressRatio(nowTime?: Date): number;
273
- isEnableAwards(group: string): boolean;
274
- resetReplayTime(): void;
275
- setReplayTime(replayStartTimestamp: number): void;
276
- }
277
- declare function createContest(contestJSON: Contest$1): Contest;
278
-
279
281
  declare class ContestIndexConfig {
280
282
  contestName: string;
281
283
  startTime: dayjs.Dayjs;
@@ -516,4 +518,5 @@ declare function isRejected(status: SubmissionStatus): boolean;
516
518
  declare function isPending(status: SubmissionStatus): boolean;
517
519
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
518
520
 
519
- export { Award, type Awards, Balloon, type Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, type ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, type Persons, PlaceChartPointData, Problem, ProblemStatistics, type Problems, Rank, RankOptions, RankStatistics, type Ranks, Rating, RatingCalculator, type RatingHistories, RatingHistory, RatingLevel, RatingLevelToString, RatingUser, type RatingUserMap, type RatingUsers, RatingUtility, Resolver, ResolverVue, type SelectOptionItem, Submission, type Submissions, Team, TeamProblemStatistics, type Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
521
+ export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Rating, RatingCalculator, RatingHistory, RatingLevel, RatingLevelToString, RatingUser, RatingUtility, Resolver, ResolverVue, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
522
+ export type { Awards, Balloons, ContestIndexList, Persons, Problems, Ranks, RatingHistories, RatingUserMap, RatingUsers, SelectOptionItem, Submissions, Teams };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TimeUnit, SubmissionReaction, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, CalculationOfPenalty, Image, Team as Team$1, Teams as Teams$1, Lang, StatusTimeDisplay, MedalPreset, BannerMode, ContestState, Contest as Contest$1, ContestIndex as ContestIndex$1, IPerson, IRatingHistory, IRatingUser, IRating } from '@xcpcio/types';
1
+ import { CalculationOfPenalty, TimeUnit, Lang, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, SubmissionReaction, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Team as Team$1, Teams as Teams$1, ContestIndex as ContestIndex$1, IPerson, IRatingHistory, IRatingUser, IRating } from '@xcpcio/types';
2
2
  import dayjs from 'dayjs';
3
3
  export { default as dayjs } from 'dayjs';
4
4
  import * as XLSX from 'xlsx-js-style';
@@ -19,6 +19,78 @@ declare class Award {
19
19
  declare function isValidMedalType(medal: MedalType): boolean;
20
20
  type Awards = Map<string, Award[]>;
21
21
 
22
+ declare class ContestOptions {
23
+ calculationOfPenalty: CalculationOfPenalty;
24
+ submissionTimestampUnit: TimeUnit;
25
+ submissionHasTimeField: boolean;
26
+ submissionHasLanguageField: boolean;
27
+ submissionEnableActionField: boolean;
28
+ submissionHasReactionField: boolean;
29
+ reactionVideoUrlTemplate?: string;
30
+ constructor();
31
+ }
32
+
33
+ declare class Group {
34
+ names: Map<Lang, string>;
35
+ defaultLang: Lang;
36
+ isDefault: boolean;
37
+ constructor();
38
+ }
39
+
40
+ declare function calcDirt(attemptedNum: number, solvedNum: number): number;
41
+
42
+ declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
43
+ declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
44
+
45
+ declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
46
+ declare function getTimestamp(time: number | dayjs.Dayjs): number;
47
+ declare function getTimeDiff(seconds: number): string;
48
+
49
+ declare class Contest {
50
+ id: string;
51
+ name: string;
52
+ startTime: dayjs.Dayjs;
53
+ endTime: dayjs.Dayjs;
54
+ freezeTime: dayjs.Dayjs;
55
+ replayStartTime?: dayjs.Dayjs;
56
+ replayEndTime?: dayjs.Dayjs;
57
+ replayFreezeTime?: dayjs.Dayjs;
58
+ replayNowTime?: dayjs.Dayjs;
59
+ replayContestStartTimestamp?: number;
60
+ totalDurationTimestamp: number;
61
+ freezeDurationTimestamp: number;
62
+ unFreezeDurationTimestamp: number;
63
+ penalty: number;
64
+ problems: Problems;
65
+ problemsMap: Map<string, Problem>;
66
+ statusTimeDisplay: StatusTimeDisplay;
67
+ badge?: string;
68
+ medal?: Record<string, Record<string, number>> | MedalPreset;
69
+ awards?: Awards;
70
+ organization?: string;
71
+ group: Map<string, Group>;
72
+ tag: Map<string, string>;
73
+ logo?: Image;
74
+ banner?: Image;
75
+ bannerMode?: BannerMode;
76
+ boardLink?: string;
77
+ options: ContestOptions;
78
+ constructor();
79
+ getStartTime(): dayjs.Dayjs;
80
+ getEndTime(): dayjs.Dayjs;
81
+ getFreezeTime(): dayjs.Dayjs;
82
+ getContestDuration(timeFormat?: string): string;
83
+ getContestState(nowTime?: Date): ContestState;
84
+ getContestPendingTime(nowTime?: Date): string;
85
+ getContestElapsedTime(nowTime?: Date): string;
86
+ getContestRemainingTime(nowTime?: Date): string;
87
+ getContestProgressRatio(nowTime?: Date): number;
88
+ isEnableAwards(group: string): boolean;
89
+ resetReplayTime(): void;
90
+ setReplayTime(replayStartTimestamp: number): void;
91
+ }
92
+ declare function createContest(contestJSON: Contest$1): Contest;
93
+
22
94
  declare class Submission {
23
95
  id: string;
24
96
  teamId: string;
@@ -44,8 +116,8 @@ declare class Submission {
44
116
  static compare(lhs: Submission, rhs: Submission): number;
45
117
  }
46
118
  type Submissions = Array<Submission>;
47
- declare function createSubmission(submissionJSON: Submission$1): Submission;
48
- declare function createSubmissions(submissionsJSON: Submissions$1): Submissions;
119
+ declare function createSubmission(submissionJSON: Submission$1, contest?: Contest): Submission;
120
+ declare function createSubmissions(submissionsJSON: Submissions$1, contest?: Contest): Submissions;
49
121
 
50
122
  declare class ProblemStatistics {
51
123
  acceptedNum: number;
@@ -102,15 +174,6 @@ declare class TeamProblemStatistics {
102
174
  get penaltyInSecond(): number;
103
175
  }
104
176
 
105
- declare class ContestOptions {
106
- calculationOfPenalty: CalculationOfPenalty;
107
- submissionTimestampUnit: TimeUnit;
108
- submissionHasTimeField: boolean;
109
- submissionHasLanguageField: boolean;
110
- submissionEnableActionField: boolean;
111
- constructor();
112
- }
113
-
114
177
  declare class PlaceChartPointData {
115
178
  timePoint: number;
116
179
  rank: number;
@@ -215,67 +278,6 @@ declare class BattleOfGiants {
215
278
  FromBase64(base64: string): void;
216
279
  }
217
280
 
218
- declare class Group {
219
- names: Map<Lang, string>;
220
- defaultLang: Lang;
221
- isDefault: boolean;
222
- constructor();
223
- }
224
-
225
- declare function calcDirt(attemptedNum: number, solvedNum: number): number;
226
-
227
- declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
228
- declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
229
-
230
- declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
231
- declare function getTimestamp(time: number | dayjs.Dayjs): number;
232
- declare function getTimeDiff(seconds: number): string;
233
-
234
- declare class Contest {
235
- id: string;
236
- name: string;
237
- startTime: dayjs.Dayjs;
238
- endTime: dayjs.Dayjs;
239
- freezeTime: dayjs.Dayjs;
240
- replayStartTime?: dayjs.Dayjs;
241
- replayEndTime?: dayjs.Dayjs;
242
- replayFreezeTime?: dayjs.Dayjs;
243
- replayNowTime?: dayjs.Dayjs;
244
- replayContestStartTimestamp?: number;
245
- totalDurationTimestamp: number;
246
- freezeDurationTimestamp: number;
247
- unFreezeDurationTimestamp: number;
248
- penalty: number;
249
- problems: Problems;
250
- problemsMap: Map<string, Problem>;
251
- statusTimeDisplay: StatusTimeDisplay;
252
- badge?: string;
253
- medal?: Record<string, Record<string, number>> | MedalPreset;
254
- awards?: Awards;
255
- organization?: string;
256
- group: Map<string, Group>;
257
- tag: Map<string, string>;
258
- logo?: Image;
259
- banner?: Image;
260
- bannerMode?: BannerMode;
261
- boardLink?: string;
262
- options: ContestOptions;
263
- constructor();
264
- getStartTime(): dayjs.Dayjs;
265
- getEndTime(): dayjs.Dayjs;
266
- getFreezeTime(): dayjs.Dayjs;
267
- getContestDuration(timeFormat?: string): string;
268
- getContestState(nowTime?: Date): ContestState;
269
- getContestPendingTime(nowTime?: Date): string;
270
- getContestElapsedTime(nowTime?: Date): string;
271
- getContestRemainingTime(nowTime?: Date): string;
272
- getContestProgressRatio(nowTime?: Date): number;
273
- isEnableAwards(group: string): boolean;
274
- resetReplayTime(): void;
275
- setReplayTime(replayStartTimestamp: number): void;
276
- }
277
- declare function createContest(contestJSON: Contest$1): Contest;
278
-
279
281
  declare class ContestIndexConfig {
280
282
  contestName: string;
281
283
  startTime: dayjs.Dayjs;
@@ -516,4 +518,5 @@ declare function isRejected(status: SubmissionStatus): boolean;
516
518
  declare function isPending(status: SubmissionStatus): boolean;
517
519
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
518
520
 
519
- export { Award, type Awards, Balloon, type Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, type ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, type Persons, PlaceChartPointData, Problem, ProblemStatistics, type Problems, Rank, RankOptions, RankStatistics, type Ranks, Rating, RatingCalculator, type RatingHistories, RatingHistory, RatingLevel, RatingLevelToString, RatingUser, type RatingUserMap, type RatingUsers, RatingUtility, Resolver, ResolverVue, type SelectOptionItem, Submission, type Submissions, Team, TeamProblemStatistics, type Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
521
+ export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, Person, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Rating, RatingCalculator, RatingHistory, RatingLevel, RatingLevelToString, RatingUser, RatingUtility, Resolver, ResolverVue, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createPersons, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
522
+ export type { Awards, Balloons, ContestIndexList, Persons, Problems, Ranks, RatingHistories, RatingUserMap, RatingUsers, SelectOptionItem, Submissions, Teams };
package/dist/index.mjs CHANGED
@@ -481,7 +481,7 @@ class Submission {
481
481
  }
482
482
  }
483
483
  }
484
- function createSubmission(submissionJSON) {
484
+ function createSubmission(submissionJSON, contest) {
485
485
  const s = new Submission();
486
486
  s.id = String(submissionJSON.id ?? submissionJSON.submission_id ?? "");
487
487
  s.teamId = String(submissionJSON.team_id);
@@ -497,15 +497,19 @@ function createSubmission(submissionJSON) {
497
497
  }
498
498
  if (submissionJSON.reaction) {
499
499
  s.reaction = submissionJSON.reaction;
500
+ } else if (contest?.options.reactionVideoUrlTemplate) {
501
+ s.reaction = {
502
+ url: contest.options.reactionVideoUrlTemplate.replace(/\$\{submission_id\}/, s.id)
503
+ };
500
504
  }
501
505
  return s;
502
506
  }
503
- function createSubmissions(submissionsJSON) {
507
+ function createSubmissions(submissionsJSON, contest) {
504
508
  if (Array.isArray(submissionsJSON)) {
505
- return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }));
509
+ return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }, contest));
506
510
  } else {
507
511
  const submissions = Object.entries(submissionsJSON).map(
508
- ([submissionId, s]) => createSubmission({ ...s, id: s.submission_id ?? submissionId })
512
+ ([submissionId, s]) => createSubmission({ ...s, id: s.submission_id ?? submissionId }, contest)
509
513
  );
510
514
  return submissions;
511
515
  }
@@ -897,12 +901,16 @@ class ContestOptions {
897
901
  submissionHasTimeField;
898
902
  submissionHasLanguageField;
899
903
  submissionEnableActionField;
904
+ submissionHasReactionField;
905
+ reactionVideoUrlTemplate;
900
906
  constructor() {
901
907
  this.calculationOfPenalty = "in_minutes";
902
908
  this.submissionTimestampUnit = "second";
903
909
  this.submissionHasTimeField = false;
904
910
  this.submissionHasLanguageField = false;
905
911
  this.submissionEnableActionField = false;
912
+ this.submissionHasReactionField = false;
913
+ this.reactionVideoUrlTemplate = void 0;
906
914
  }
907
915
  }
908
916
  function createContestOptions(contestOptionsJSON = {}) {
@@ -914,9 +922,11 @@ function createContestOptions(contestOptionsJSON = {}) {
914
922
  if (j.submission_timestamp_unit) {
915
923
  o.submissionTimestampUnit = j.submission_timestamp_unit;
916
924
  }
917
- if (j.submission_has_reaction) {
918
- o.submissionEnableActionField = j.submission_has_reaction;
925
+ if (j.submission_has_reaction || j.has_reaction_videos) {
926
+ o.submissionHasReactionField = true;
919
927
  }
928
+ o.submissionEnableActionField = o.submissionHasReactionField;
929
+ o.reactionVideoUrlTemplate = j.reaction_video_url_template;
920
930
  return o;
921
931
  }
922
932
 
@@ -1374,13 +1384,13 @@ class GeneralExcelConverter {
1374
1384
  }
1375
1385
  convertToSheet(rank) {
1376
1386
  const aoa = this.convertToAoa(rank);
1377
- const sheet = XLSX.utils.aoa_to_sheet(aoa);
1387
+ const sheet = XLSX.utils.aoa_to_sheet(aoa.aoa);
1378
1388
  const cols = [];
1379
- const head = aoa[1];
1389
+ const head = aoa.aoa[1];
1380
1390
  for (let j = 0; j < head.length; j++) {
1381
1391
  let wch = 10;
1382
- for (let i = 1; i < aoa.length; i++) {
1383
- wch = Math.max(wch, stringWidth(aoa[i][j]) + 2);
1392
+ for (let i = 1; i < aoa.aoa.length; i++) {
1393
+ wch = Math.max(wch, stringWidth(aoa.aoa[i][j]) + 2);
1384
1394
  }
1385
1395
  cols.push({
1386
1396
  wch
@@ -1414,11 +1424,22 @@ class GeneralExcelConverter {
1414
1424
  },
1415
1425
  font
1416
1426
  };
1417
- for (let i = 1; i < aoa.length; i++) {
1418
- for (let j = 0; j < aoa[i].length; j++) {
1427
+ const firstSolvedCellStyle = {
1428
+ ...cellStyle,
1429
+ fill: {
1430
+ fgColor: { rgb: "009900" }
1431
+ }
1432
+ };
1433
+ for (let i = 1; i < aoa.aoa.length; i++) {
1434
+ for (let j = 0; j < aoa.aoa[i].length; j++) {
1419
1435
  const cellAddress = XLSX.utils.encode_cell({ r: i, c: j });
1420
1436
  const cell = sheet[cellAddress];
1421
- cell.s = cellStyle;
1437
+ const specialCell = aoa.specialCells.find((sc) => sc.row === i && sc.col === j);
1438
+ if (specialCell?.type === "firstSolved" /* FIRST_SOLVED */) {
1439
+ cell.s = firstSolvedCellStyle;
1440
+ } else {
1441
+ cell.s = cellStyle;
1442
+ }
1422
1443
  }
1423
1444
  }
1424
1445
  {
@@ -1433,6 +1454,7 @@ class GeneralExcelConverter {
1433
1454
  }
1434
1455
  convertToAoa(rank) {
1435
1456
  const aoa = [];
1457
+ const specialCells = [];
1436
1458
  const enableAwards = rank.contest.isEnableAwards(rank.options.group);
1437
1459
  const enableMembers = (Array.isArray(rank.teams) && rank.teams[0]?.members) ?? false;
1438
1460
  const enableCoach = rank.teams[0]?.coach ?? false;
@@ -1478,6 +1500,13 @@ class GeneralExcelConverter {
1478
1500
  }
1479
1501
  if (p.isSolved) {
1480
1502
  arr.push(`+${p.totalCount}(${p.solvedTimestampToMinute})`);
1503
+ if (p.isFirstSolved) {
1504
+ specialCells.push({
1505
+ row: aoa.length,
1506
+ col: arr.length - 1,
1507
+ type: "firstSolved" /* FIRST_SOLVED */
1508
+ });
1509
+ }
1481
1510
  }
1482
1511
  if (p.isWrongAnswer) {
1483
1512
  arr.push(`-${p.failedCount}`);
@@ -1512,7 +1541,7 @@ class GeneralExcelConverter {
1512
1541
  arr.push(team.isGirl ? "Y" : "N");
1513
1542
  aoa.push(arr);
1514
1543
  }
1515
- return aoa;
1544
+ return { aoa, specialCells };
1516
1545
  }
1517
1546
  }
1518
1547
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcpcio/core",
3
- "version": "0.55.2",
3
+ "version": "0.57.0",
4
4
  "description": "XCPCIO Core",
5
5
  "author": "Dup4 <lyuzhi.pan@gmail.com>",
6
6
  "license": "MIT",
@@ -42,34 +42,34 @@
42
42
  "dependencies": {
43
43
  "chroma-js": "^3.1.2",
44
44
  "color-diff": "^1.4.0",
45
- "dayjs": "^1.11.13",
46
- "js-base64": "^3.7.7",
45
+ "dayjs": "^1.11.18",
46
+ "js-base64": "^3.7.8",
47
47
  "lodash": "^4.17.21",
48
48
  "ordinal": "^1.0.3",
49
- "papaparse": "^5.5.2",
50
- "string-width": "^7.2.0",
49
+ "papaparse": "^5.5.3",
50
+ "string-width": "^8.1.0",
51
51
  "xlsx-js-style": "^1.2.0",
52
- "@xcpcio/types": "0.55.2"
52
+ "@xcpcio/types": "0.57.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@babel/types": "^7.27.0",
55
+ "@babel/types": "^7.28.4",
56
56
  "@types/chroma-js": "^3.1.1",
57
57
  "@types/color-diff": "^1.2.5",
58
- "@types/lodash": "^4.17.16",
59
- "@types/node": "^18.19.86",
60
- "@types/papaparse": "^5.3.15",
61
- "@typescript-eslint/eslint-plugin": "^8.29.1",
62
- "@typescript-eslint/parser": "^8.29.1",
63
- "bumpp": "^10.1.0",
64
- "eslint": "^9.24.0",
58
+ "@types/lodash": "^4.17.20",
59
+ "@types/node": "^20.19.13",
60
+ "@types/papaparse": "^5.3.16",
61
+ "@typescript-eslint/eslint-plugin": "^8.42.0",
62
+ "@typescript-eslint/parser": "^8.42.0",
63
+ "bumpp": "^10.2.3",
64
+ "eslint": "^9.35.0",
65
65
  "esmo": "^4.8.0",
66
66
  "npm-run-all": "^4.1.5",
67
- "pnpm": "^10.8.0",
68
- "taze": "^19.0.4",
69
- "typescript": "^5.8.3",
70
- "unbuild": "^3.5.0",
71
- "vite": "^6.2.6",
72
- "vitest": "^3.1.1"
67
+ "pnpm": "^10.15.1",
68
+ "taze": "^19.5.0",
69
+ "typescript": "^5.9.2",
70
+ "unbuild": "^3.6.1",
71
+ "vite": "^7.1.4",
72
+ "vitest": "^3.2.4"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "unbuild",
@@ -6,7 +6,11 @@ export class ContestOptions {
6
6
 
7
7
  submissionHasTimeField: boolean;
8
8
  submissionHasLanguageField: boolean;
9
+
9
10
  submissionEnableActionField: boolean;
11
+ submissionHasReactionField: boolean;
12
+
13
+ reactionVideoUrlTemplate?: string;
10
14
 
11
15
  constructor() {
12
16
  this.calculationOfPenalty = "in_minutes";
@@ -14,7 +18,11 @@ export class ContestOptions {
14
18
 
15
19
  this.submissionHasTimeField = false;
16
20
  this.submissionHasLanguageField = false;
21
+
17
22
  this.submissionEnableActionField = false;
23
+ this.submissionHasReactionField = false;
24
+
25
+ this.reactionVideoUrlTemplate = undefined;
18
26
  }
19
27
  }
20
28
 
@@ -30,9 +38,12 @@ export function createContestOptions(contestOptionsJSON: IContestOptions = {}):
30
38
  o.submissionTimestampUnit = j.submission_timestamp_unit;
31
39
  }
32
40
 
33
- if (j.submission_has_reaction) {
34
- o.submissionEnableActionField = j.submission_has_reaction;
41
+ if (j.submission_has_reaction || j.has_reaction_videos) {
42
+ o.submissionHasReactionField = true;
35
43
  }
36
44
 
45
+ o.submissionEnableActionField = o.submissionHasReactionField;
46
+ o.reactionVideoUrlTemplate = j.reaction_video_url_template;
47
+
37
48
  return o;
38
49
  }
@@ -6,8 +6,23 @@ import * as XLSX from "xlsx-js-style";
6
6
 
7
7
  import { isValidMedalType } from "../award";
8
8
 
9
+ enum SpecialCellType {
10
+ FIRST_SOLVED = "firstSolved",
11
+ }
12
+
13
+ interface SpecialCell {
14
+ row: number;
15
+ col: number;
16
+ type: SpecialCellType;
17
+ }
18
+
19
+ interface AoaConvertResult {
20
+ aoa: string[][];
21
+ specialCells: SpecialCell[];
22
+ }
23
+
9
24
  export class GeneralExcelConverter {
10
- constructor() {}
25
+ constructor() { }
11
26
 
12
27
  public convert(oriRank: Rank): XLSX.WorkBook {
13
28
  const rank = _.cloneDeep(oriRank);
@@ -40,14 +55,14 @@ export class GeneralExcelConverter {
40
55
 
41
56
  private convertToSheet(rank: Rank): XLSX.WorkSheet {
42
57
  const aoa = this.convertToAoa(rank);
43
- const sheet = XLSX.utils.aoa_to_sheet(aoa);
58
+ const sheet = XLSX.utils.aoa_to_sheet(aoa.aoa);
44
59
 
45
60
  const cols = [];
46
- const head = aoa[1];
61
+ const head = aoa.aoa[1];
47
62
  for (let j = 0; j < head.length; j++) {
48
63
  let wch = 10;
49
- for (let i = 1; i < aoa.length; i++) {
50
- wch = Math.max(wch, stringWidth(aoa[i][j]) + 2);
64
+ for (let i = 1; i < aoa.aoa.length; i++) {
65
+ wch = Math.max(wch, stringWidth(aoa.aoa[i][j]) + 2);
51
66
  }
52
67
 
53
68
  cols.push({
@@ -88,11 +103,23 @@ export class GeneralExcelConverter {
88
103
  font,
89
104
  };
90
105
 
91
- for (let i = 1; i < aoa.length; i++) {
92
- for (let j = 0; j < aoa[i].length; j++) {
106
+ const firstSolvedCellStyle = {
107
+ ...cellStyle,
108
+ fill: {
109
+ fgColor: { rgb: "009900" },
110
+ },
111
+ };
112
+
113
+ for (let i = 1; i < aoa.aoa.length; i++) {
114
+ for (let j = 0; j < aoa.aoa[i].length; j++) {
93
115
  const cellAddress = XLSX.utils.encode_cell({ r: i, c: j });
94
116
  const cell = sheet[cellAddress];
95
- cell.s = cellStyle;
117
+ const specialCell = aoa.specialCells.find(sc => sc.row === i && sc.col === j);
118
+ if (specialCell?.type === SpecialCellType.FIRST_SOLVED) {
119
+ cell.s = firstSolvedCellStyle;
120
+ } else {
121
+ cell.s = cellStyle;
122
+ }
96
123
  }
97
124
  }
98
125
 
@@ -108,8 +135,9 @@ export class GeneralExcelConverter {
108
135
  return sheet;
109
136
  }
110
137
 
111
- private convertToAoa(rank: Rank): string[][] {
138
+ private convertToAoa(rank: Rank): AoaConvertResult {
112
139
  const aoa: string[][] = [];
140
+ const specialCells: SpecialCell[] = [];
113
141
 
114
142
  const enableAwards = rank.contest.isEnableAwards(rank.options.group);
115
143
  const enableMembers = (Array.isArray(rank.teams) && rank.teams[0]?.members) ?? false;
@@ -152,6 +180,7 @@ export class GeneralExcelConverter {
152
180
  const arr: string[] = [];
153
181
 
154
182
  arr.push(team.rank.toString());
183
+
155
184
  if (team.organization) {
156
185
  if (team.organizationRank !== -1) {
157
186
  arr.push(team.organizationRank.toString());
@@ -171,6 +200,13 @@ export class GeneralExcelConverter {
171
200
 
172
201
  if (p.isSolved) {
173
202
  arr.push(`+${p.totalCount}(${p.solvedTimestampToMinute})`);
203
+ if (p.isFirstSolved) {
204
+ specialCells.push({
205
+ row: aoa.length,
206
+ col: arr.length - 1,
207
+ type: SpecialCellType.FIRST_SOLVED,
208
+ });
209
+ }
174
210
  }
175
211
 
176
212
  if (p.isWrongAnswer) {
@@ -216,6 +252,6 @@ export class GeneralExcelConverter {
216
252
  aoa.push(arr);
217
253
  }
218
254
 
219
- return aoa;
255
+ return { aoa, specialCells };
220
256
  }
221
257
  }
package/src/submission.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { Submission as ISubmission, Submissions as ISubmissions, SubmissionReaction, TimeUnit } from "@xcpcio/types";
2
+ import type { Contest } from "./contest";
3
+
2
4
  import { SubmissionStatus } from "@xcpcio/types";
3
5
 
4
6
  import {
@@ -134,7 +136,7 @@ export class Submission {
134
136
 
135
137
  export type Submissions = Array<Submission>;
136
138
 
137
- export function createSubmission(submissionJSON: ISubmission): Submission {
139
+ export function createSubmission(submissionJSON: ISubmission, contest?: Contest): Submission {
138
140
  const s = new Submission();
139
141
 
140
142
  s.id = String(submissionJSON.id ?? submissionJSON.submission_id ?? "");
@@ -154,19 +156,22 @@ export function createSubmission(submissionJSON: ISubmission): Submission {
154
156
 
155
157
  if (submissionJSON.reaction) {
156
158
  s.reaction = submissionJSON.reaction;
159
+ } else if (contest?.options.reactionVideoUrlTemplate) {
160
+ s.reaction = {
161
+ url: contest.options.reactionVideoUrlTemplate.replace(/\$\{submission_id\}/, s.id),
162
+ };
157
163
  }
158
164
 
159
165
  return s;
160
166
  }
161
167
 
162
- export function createSubmissions(submissionsJSON: ISubmissions): Submissions {
168
+ export function createSubmissions(submissionsJSON: ISubmissions, contest?: Contest): Submissions {
163
169
  if (Array.isArray(submissionsJSON)) {
164
- return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }));
170
+ return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }, contest));
165
171
  } else {
166
172
  const submissions = Object.entries(submissionsJSON).map(([submissionId, s]) =>
167
- createSubmission({ ...s, id: s.submission_id ?? submissionId }),
173
+ createSubmission({ ...s, id: s.submission_id ?? submissionId }, contest),
168
174
  );
169
-
170
175
  return submissions;
171
176
  }
172
177
  }