@xcpcio/core 0.5.2 → 0.6.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
@@ -177,6 +177,14 @@ class TeamProblemStatistics {
177
177
  }
178
178
  }
179
179
 
180
+ class Group {
181
+ constructor() {
182
+ this.names = /* @__PURE__ */ new Map();
183
+ this.defaultLang = "zh-CN";
184
+ this.isDefault = false;
185
+ }
186
+ }
187
+
180
188
  class Contest {
181
189
  constructor() {
182
190
  this.name = "";
@@ -195,6 +203,8 @@ class Contest {
195
203
  incorrect: true,
196
204
  pending: true
197
205
  };
206
+ this.group = /* @__PURE__ */ new Map();
207
+ this.tag = /* @__PURE__ */ new Map();
198
208
  }
199
209
  getContestDuration(timeFormat = "HH:mm:ss") {
200
210
  return dayjs__default.duration(this.endTime.diff(this.startTime)).format(timeFormat);
@@ -292,8 +302,29 @@ function createContest(contestJSON) {
292
302
  c.badge = contestJSON.badge;
293
303
  c.medal = contestJSON.medal;
294
304
  c.organization = contestJSON.organization;
295
- c.group = contestJSON.group;
296
- c.tag = contestJSON.tag;
305
+ {
306
+ const g = new Group();
307
+ g.names.set("en", "All");
308
+ g.names.set("zh-CN", "\u6240\u6709\u961F\u4F0D");
309
+ g.isDefault = true;
310
+ c.group.set("all", g);
311
+ }
312
+ for (const [k, v] of Object.entries(contestJSON?.group ?? {})) {
313
+ let key = k;
314
+ const g = new Group();
315
+ g.names.set("zh-CN", v);
316
+ if (k === "official") {
317
+ g.names.set("en", "Official");
318
+ }
319
+ if (k === "unofficial") {
320
+ g.names.set("en", "Unofficial");
321
+ }
322
+ if (k === "girl" || k === "girls") {
323
+ g.names.set("en", "Girls");
324
+ key = "girl";
325
+ }
326
+ c.group.set(key, g);
327
+ }
297
328
  c.banner = contestJSON.banner;
298
329
  c.logo = contestJSON.logo;
299
330
  c.boardLink = contestJSON.board_link;
@@ -373,9 +404,11 @@ function getImageSource(image) {
373
404
  class RankStatistics {
374
405
  constructor() {
375
406
  this.teamSolvedNum = [];
407
+ this.maxSolvedProblems = 0;
376
408
  }
377
409
  reset() {
378
410
  this.teamSolvedNum = [];
411
+ this.maxSolvedProblems = 0;
379
412
  }
380
413
  }
381
414
 
@@ -394,6 +427,7 @@ class Team {
394
427
  this.group = [];
395
428
  this.tag = [];
396
429
  this.rank = 0;
430
+ this.originalRank = 0;
397
431
  this.organizationRank = -1;
398
432
  this.solvedProblemNum = 0;
399
433
  this.attemptedProblemNum = 0;
@@ -407,6 +441,7 @@ class Team {
407
441
  }
408
442
  reset() {
409
443
  this.rank = 0;
444
+ this.originalRank = 0;
410
445
  this.organizationRank = -1;
411
446
  this.solvedProblemNum = 0;
412
447
  this.attemptedProblemNum = 0;
@@ -693,6 +728,12 @@ class RankOptions {
693
728
  this.enableFilterSubmissionsByTimestamp = false;
694
729
  this.width = 0;
695
730
  this.timestamp = 0;
731
+ this.enableFilterTeamsByGroup = false;
732
+ this.group = "";
733
+ this.filterOrganizations = [];
734
+ this.filterOrganizationMap = /* @__PURE__ */ new Map();
735
+ this.filterTeams = [];
736
+ this.filterTeamMap = /* @__PURE__ */ new Map();
696
737
  }
697
738
  setWidth(width, contest) {
698
739
  this.width = width;
@@ -702,6 +743,32 @@ class RankOptions {
702
743
  disableFilterSubmissionByTimestamp() {
703
744
  this.enableFilterSubmissionsByTimestamp = false;
704
745
  }
746
+ setGroup(group) {
747
+ this.group = group;
748
+ this.enableFilterTeamsByGroup = true;
749
+ if (this.group === "all") {
750
+ this.disableFilterTeamsByGroup();
751
+ }
752
+ }
753
+ disableFilterTeamsByGroup() {
754
+ this.enableFilterTeamsByGroup = false;
755
+ }
756
+ setFilterOrganizations(filterOrganizations) {
757
+ const m = /* @__PURE__ */ new Map();
758
+ filterOrganizations.forEach((item) => {
759
+ m.set(item.value, item);
760
+ });
761
+ this.filterOrganizations = filterOrganizations;
762
+ this.filterOrganizationMap = m;
763
+ }
764
+ setFilterTeams(filterTeams) {
765
+ const m = /* @__PURE__ */ new Map();
766
+ filterTeams.forEach((item) => {
767
+ m.set(item.value, item);
768
+ });
769
+ this.filterTeams = filterTeams;
770
+ this.filterTeamMap = m;
771
+ }
705
772
  }
706
773
  class Rank {
707
774
  constructor(contest, teams, submissions) {
@@ -710,11 +777,23 @@ class Rank {
710
777
  this.teamsMap = new Map(this.teams.map((t) => [t.id, t]));
711
778
  this.submissions = ___default.cloneDeep(submissions).sort(Submission.compare);
712
779
  this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
780
+ this.organizations = this.buildOrganizations();
781
+ this.originTeams = this.teams.map((t) => t);
782
+ this.originTeams.sort(Team.compare);
713
783
  this.rankStatistics = new RankStatistics();
714
784
  this.options = new RankOptions();
715
785
  }
716
786
  buildRank() {
717
787
  (() => {
788
+ (() => {
789
+ this.teams = [];
790
+ for (const [_k, v] of this.teamsMap) {
791
+ if (this.filterTeamByOrg(v)) {
792
+ continue;
793
+ }
794
+ this.teams.push(v);
795
+ }
796
+ })();
718
797
  for (const t of this.teams) {
719
798
  t.reset();
720
799
  t.problemStatistics = this.contest.problems.map((p) => {
@@ -744,7 +823,7 @@ class Rank {
744
823
  const team = this.teamsMap.get(teamId);
745
824
  const problem = this.contest.problemsMap.get(problemId);
746
825
  (() => {
747
- if (team === void 0 || problem === void 0) {
826
+ if (team === void 0 || this.filterTeamByOrg(team) || problem === void 0) {
748
827
  return;
749
828
  }
750
829
  const problemStatistics = team.problemStatisticsMap.get(problemId);
@@ -804,8 +883,11 @@ class Rank {
804
883
  }
805
884
  preSubmissionTimestampToMinute = s.timestampToMinute;
806
885
  }
807
- this.teams.forEach((t) => t.postProcessPlaceChartPoints());
886
+ this.teams.forEach((t) => t.calcSolvedData());
887
+ this.teams.sort(Team.compare);
888
+ this.buildTeamRank();
808
889
  this.buildOrgRank();
890
+ this.teams.forEach((t) => t.postProcessPlaceChartPoints());
809
891
  })();
810
892
  (() => {
811
893
  this.rankStatistics.reset();
@@ -813,14 +895,19 @@ class Rank {
813
895
  for (const t of this.teams) {
814
896
  this.rankStatistics.teamSolvedNum[t.solvedProblemNum]++;
815
897
  }
898
+ if (this.teams.length > 0) {
899
+ this.rankStatistics.maxSolvedProblems = this.teams[0].solvedProblemNum;
900
+ }
816
901
  })();
817
902
  return this;
818
903
  }
819
904
  buildTeamRank() {
820
905
  let rank = 1;
906
+ let originalRank = 1;
821
907
  let preTeam = null;
822
908
  for (const t of this.teams) {
823
909
  t.rank = rank++;
910
+ t.originalRank = originalRank++;
824
911
  if (preTeam !== null) {
825
912
  if (t.isEqualRank(preTeam)) {
826
913
  t.rank = preTeam.rank;
@@ -851,6 +938,32 @@ class Rank {
851
938
  preTeam = t;
852
939
  }
853
940
  }
941
+ buildOrganizations() {
942
+ if (!this.contest.organization) {
943
+ return [];
944
+ }
945
+ const res = new Array();
946
+ const se = /* @__PURE__ */ new Set();
947
+ this.teams.forEach((t) => {
948
+ const org = t.organization;
949
+ if (se.has(org)) {
950
+ return;
951
+ }
952
+ res.push(org);
953
+ se.add(org);
954
+ });
955
+ res.sort();
956
+ return res;
957
+ }
958
+ filterTeamByOrg(team) {
959
+ const o = this.options;
960
+ if (o.enableFilterTeamsByGroup) {
961
+ if (!team.group?.includes(o.group)) {
962
+ return true;
963
+ }
964
+ }
965
+ return false;
966
+ }
854
967
  getSubmissions() {
855
968
  if (this.options.enableFilterSubmissionsByTimestamp === false) {
856
969
  return this.submissions;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import dayjs from 'dayjs';
2
2
  export { default as dayjs } from 'dayjs';
3
- import { SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, StatusTimeDisplay, Image, ContestState, Contest as Contest$1, ContestIndex as ContestIndex$1, Team as Team$1, Teams as Teams$1 } from '@xcpcio/types';
3
+ import { SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Lang, StatusTimeDisplay, Image, ContestState, Contest as Contest$1, ContestIndex as ContestIndex$1, Team as Team$1, Teams as Teams$1 } from '@xcpcio/types';
4
4
 
5
5
  declare class Submission {
6
6
  id: string;
@@ -77,6 +77,13 @@ declare function createDayJS(time?: Date | string | number | undefined): dayjs.D
77
77
  declare function getTimestamp(time: number | dayjs.Dayjs): number;
78
78
  declare function getTimeDiff(seconds: number): string;
79
79
 
80
+ declare class Group {
81
+ names: Map<Lang, string>;
82
+ defaultLang: Lang;
83
+ isDefault: boolean;
84
+ constructor();
85
+ }
86
+
80
87
  declare class Contest {
81
88
  name: string;
82
89
  startTime: dayjs.Dayjs;
@@ -92,8 +99,8 @@ declare class Contest {
92
99
  badge?: string;
93
100
  medal?: Record<string, Record<string, number>>;
94
101
  organization?: string;
95
- group?: Record<string, string>;
96
- tag?: Record<string, string>;
102
+ group: Map<string, Group>;
103
+ tag: Map<string, string>;
97
104
  logo?: Image;
98
105
  banner?: Image;
99
106
  boardLink?: string;
@@ -132,6 +139,7 @@ declare function getImageSource(image: Image): string;
132
139
 
133
140
  declare class RankStatistics {
134
141
  teamSolvedNum: Array<number>;
142
+ maxSolvedProblems: number;
135
143
  constructor();
136
144
  reset(): void;
137
145
  }
@@ -152,6 +160,7 @@ declare class Team {
152
160
  coach?: string | Array<string>;
153
161
  members?: string | Array<string>;
154
162
  rank: number;
163
+ originalRank: number;
155
164
  organizationRank: number;
156
165
  solvedProblemNum: number;
157
166
  attemptedProblemNum: number;
@@ -175,13 +184,27 @@ type Teams = Array<Team>;
175
184
  declare function createTeam(teamJSON: Team$1): Team;
176
185
  declare function createTeams(teamsJSON: Teams$1): Teams;
177
186
 
187
+ interface SelectOptionItem {
188
+ value: string;
189
+ text: string;
190
+ }
178
191
  declare class RankOptions {
179
192
  enableFilterSubmissionsByTimestamp: boolean;
180
193
  width: number;
181
194
  timestamp: number;
195
+ enableFilterTeamsByGroup: boolean;
196
+ group: string;
197
+ filterOrganizations: Array<SelectOptionItem>;
198
+ filterOrganizationMap: Map<string, SelectOptionItem>;
199
+ filterTeams: Array<SelectOptionItem>;
200
+ filterTeamMap: Map<string, SelectOptionItem>;
182
201
  constructor();
183
202
  setWidth(width: number, contest: Contest): void;
184
203
  disableFilterSubmissionByTimestamp(): void;
204
+ setGroup(group: string): void;
205
+ disableFilterTeamsByGroup(): void;
206
+ setFilterOrganizations(filterOrganizations: Array<SelectOptionItem>): void;
207
+ setFilterTeams(filterTeams: Array<SelectOptionItem>): void;
185
208
  }
186
209
  declare class Rank {
187
210
  readonly contest: Contest;
@@ -189,12 +212,16 @@ declare class Rank {
189
212
  teamsMap: Map<string, Team>;
190
213
  submissions: Submissions;
191
214
  submissionsMap: Map<string, Submission>;
215
+ organizations: Array<string>;
216
+ originTeams: Teams;
192
217
  rankStatistics: RankStatistics;
193
218
  options: RankOptions;
194
219
  constructor(contest: Contest, teams: Teams, submissions: Submissions);
195
220
  buildRank(): this;
196
221
  buildTeamRank(): void;
197
222
  buildOrgRank(): void;
223
+ buildOrganizations(): string[];
224
+ filterTeamByOrg(team: Team): boolean;
198
225
  getSubmissions(): Submissions;
199
226
  }
200
227
 
@@ -222,4 +249,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
222
249
  declare function isPending(status: SubmissionStatus): boolean;
223
250
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
224
251
 
225
- export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Resolver, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
252
+ export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Resolver, SelectOptionItem, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
package/dist/index.mjs CHANGED
@@ -161,6 +161,14 @@ class TeamProblemStatistics {
161
161
  }
162
162
  }
163
163
 
164
+ class Group {
165
+ constructor() {
166
+ this.names = /* @__PURE__ */ new Map();
167
+ this.defaultLang = "zh-CN";
168
+ this.isDefault = false;
169
+ }
170
+ }
171
+
164
172
  class Contest {
165
173
  constructor() {
166
174
  this.name = "";
@@ -179,6 +187,8 @@ class Contest {
179
187
  incorrect: true,
180
188
  pending: true
181
189
  };
190
+ this.group = /* @__PURE__ */ new Map();
191
+ this.tag = /* @__PURE__ */ new Map();
182
192
  }
183
193
  getContestDuration(timeFormat = "HH:mm:ss") {
184
194
  return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
@@ -276,8 +286,29 @@ function createContest(contestJSON) {
276
286
  c.badge = contestJSON.badge;
277
287
  c.medal = contestJSON.medal;
278
288
  c.organization = contestJSON.organization;
279
- c.group = contestJSON.group;
280
- c.tag = contestJSON.tag;
289
+ {
290
+ const g = new Group();
291
+ g.names.set("en", "All");
292
+ g.names.set("zh-CN", "\u6240\u6709\u961F\u4F0D");
293
+ g.isDefault = true;
294
+ c.group.set("all", g);
295
+ }
296
+ for (const [k, v] of Object.entries(contestJSON?.group ?? {})) {
297
+ let key = k;
298
+ const g = new Group();
299
+ g.names.set("zh-CN", v);
300
+ if (k === "official") {
301
+ g.names.set("en", "Official");
302
+ }
303
+ if (k === "unofficial") {
304
+ g.names.set("en", "Unofficial");
305
+ }
306
+ if (k === "girl" || k === "girls") {
307
+ g.names.set("en", "Girls");
308
+ key = "girl";
309
+ }
310
+ c.group.set(key, g);
311
+ }
281
312
  c.banner = contestJSON.banner;
282
313
  c.logo = contestJSON.logo;
283
314
  c.boardLink = contestJSON.board_link;
@@ -357,9 +388,11 @@ function getImageSource(image) {
357
388
  class RankStatistics {
358
389
  constructor() {
359
390
  this.teamSolvedNum = [];
391
+ this.maxSolvedProblems = 0;
360
392
  }
361
393
  reset() {
362
394
  this.teamSolvedNum = [];
395
+ this.maxSolvedProblems = 0;
363
396
  }
364
397
  }
365
398
 
@@ -378,6 +411,7 @@ class Team {
378
411
  this.group = [];
379
412
  this.tag = [];
380
413
  this.rank = 0;
414
+ this.originalRank = 0;
381
415
  this.organizationRank = -1;
382
416
  this.solvedProblemNum = 0;
383
417
  this.attemptedProblemNum = 0;
@@ -391,6 +425,7 @@ class Team {
391
425
  }
392
426
  reset() {
393
427
  this.rank = 0;
428
+ this.originalRank = 0;
394
429
  this.organizationRank = -1;
395
430
  this.solvedProblemNum = 0;
396
431
  this.attemptedProblemNum = 0;
@@ -677,6 +712,12 @@ class RankOptions {
677
712
  this.enableFilterSubmissionsByTimestamp = false;
678
713
  this.width = 0;
679
714
  this.timestamp = 0;
715
+ this.enableFilterTeamsByGroup = false;
716
+ this.group = "";
717
+ this.filterOrganizations = [];
718
+ this.filterOrganizationMap = /* @__PURE__ */ new Map();
719
+ this.filterTeams = [];
720
+ this.filterTeamMap = /* @__PURE__ */ new Map();
680
721
  }
681
722
  setWidth(width, contest) {
682
723
  this.width = width;
@@ -686,6 +727,32 @@ class RankOptions {
686
727
  disableFilterSubmissionByTimestamp() {
687
728
  this.enableFilterSubmissionsByTimestamp = false;
688
729
  }
730
+ setGroup(group) {
731
+ this.group = group;
732
+ this.enableFilterTeamsByGroup = true;
733
+ if (this.group === "all") {
734
+ this.disableFilterTeamsByGroup();
735
+ }
736
+ }
737
+ disableFilterTeamsByGroup() {
738
+ this.enableFilterTeamsByGroup = false;
739
+ }
740
+ setFilterOrganizations(filterOrganizations) {
741
+ const m = /* @__PURE__ */ new Map();
742
+ filterOrganizations.forEach((item) => {
743
+ m.set(item.value, item);
744
+ });
745
+ this.filterOrganizations = filterOrganizations;
746
+ this.filterOrganizationMap = m;
747
+ }
748
+ setFilterTeams(filterTeams) {
749
+ const m = /* @__PURE__ */ new Map();
750
+ filterTeams.forEach((item) => {
751
+ m.set(item.value, item);
752
+ });
753
+ this.filterTeams = filterTeams;
754
+ this.filterTeamMap = m;
755
+ }
689
756
  }
690
757
  class Rank {
691
758
  constructor(contest, teams, submissions) {
@@ -694,11 +761,23 @@ class Rank {
694
761
  this.teamsMap = new Map(this.teams.map((t) => [t.id, t]));
695
762
  this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
696
763
  this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
764
+ this.organizations = this.buildOrganizations();
765
+ this.originTeams = this.teams.map((t) => t);
766
+ this.originTeams.sort(Team.compare);
697
767
  this.rankStatistics = new RankStatistics();
698
768
  this.options = new RankOptions();
699
769
  }
700
770
  buildRank() {
701
771
  (() => {
772
+ (() => {
773
+ this.teams = [];
774
+ for (const [_k, v] of this.teamsMap) {
775
+ if (this.filterTeamByOrg(v)) {
776
+ continue;
777
+ }
778
+ this.teams.push(v);
779
+ }
780
+ })();
702
781
  for (const t of this.teams) {
703
782
  t.reset();
704
783
  t.problemStatistics = this.contest.problems.map((p) => {
@@ -728,7 +807,7 @@ class Rank {
728
807
  const team = this.teamsMap.get(teamId);
729
808
  const problem = this.contest.problemsMap.get(problemId);
730
809
  (() => {
731
- if (team === void 0 || problem === void 0) {
810
+ if (team === void 0 || this.filterTeamByOrg(team) || problem === void 0) {
732
811
  return;
733
812
  }
734
813
  const problemStatistics = team.problemStatisticsMap.get(problemId);
@@ -788,8 +867,11 @@ class Rank {
788
867
  }
789
868
  preSubmissionTimestampToMinute = s.timestampToMinute;
790
869
  }
791
- this.teams.forEach((t) => t.postProcessPlaceChartPoints());
870
+ this.teams.forEach((t) => t.calcSolvedData());
871
+ this.teams.sort(Team.compare);
872
+ this.buildTeamRank();
792
873
  this.buildOrgRank();
874
+ this.teams.forEach((t) => t.postProcessPlaceChartPoints());
793
875
  })();
794
876
  (() => {
795
877
  this.rankStatistics.reset();
@@ -797,14 +879,19 @@ class Rank {
797
879
  for (const t of this.teams) {
798
880
  this.rankStatistics.teamSolvedNum[t.solvedProblemNum]++;
799
881
  }
882
+ if (this.teams.length > 0) {
883
+ this.rankStatistics.maxSolvedProblems = this.teams[0].solvedProblemNum;
884
+ }
800
885
  })();
801
886
  return this;
802
887
  }
803
888
  buildTeamRank() {
804
889
  let rank = 1;
890
+ let originalRank = 1;
805
891
  let preTeam = null;
806
892
  for (const t of this.teams) {
807
893
  t.rank = rank++;
894
+ t.originalRank = originalRank++;
808
895
  if (preTeam !== null) {
809
896
  if (t.isEqualRank(preTeam)) {
810
897
  t.rank = preTeam.rank;
@@ -835,6 +922,32 @@ class Rank {
835
922
  preTeam = t;
836
923
  }
837
924
  }
925
+ buildOrganizations() {
926
+ if (!this.contest.organization) {
927
+ return [];
928
+ }
929
+ const res = new Array();
930
+ const se = /* @__PURE__ */ new Set();
931
+ this.teams.forEach((t) => {
932
+ const org = t.organization;
933
+ if (se.has(org)) {
934
+ return;
935
+ }
936
+ res.push(org);
937
+ se.add(org);
938
+ });
939
+ res.sort();
940
+ return res;
941
+ }
942
+ filterTeamByOrg(team) {
943
+ const o = this.options;
944
+ if (o.enableFilterTeamsByGroup) {
945
+ if (!team.group?.includes(o.group)) {
946
+ return true;
947
+ }
948
+ }
949
+ return false;
950
+ }
838
951
  getSubmissions() {
839
952
  if (this.options.enableFilterSubmissionsByTimestamp === false) {
840
953
  return this.submissions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcpcio/core",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "XCPCIO Core",
5
5
  "author": "Dup4 <lyuzhi.pan@gmail.com>",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "dayjs": "^1.11.8",
44
44
  "lodash": "^4.17.21",
45
- "@xcpcio/types": "0.5.2"
45
+ "@xcpcio/types": "0.6.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@babel/types": "^7.22.4",
package/src/contest.ts CHANGED
@@ -4,6 +4,7 @@ import { ContestState, VERSION } from "@xcpcio/types";
4
4
  import type { Problem, Problems } from "./problem";
5
5
  import { createProblems, createProblemsByProblemIds } from "./problem";
6
6
  import { createDayJS, dayjs, getTimeDiff } from "./utils";
7
+ import { Group } from "./group";
7
8
 
8
9
  export class Contest {
9
10
  name = "";
@@ -27,8 +28,8 @@ export class Contest {
27
28
  medal?: Record<string, Record<string, number>>;
28
29
  organization?: string;
29
30
 
30
- group?: Record<string, string>;
31
- tag?: Record<string, string>;
31
+ group: Map<string, Group>;
32
+ tag: Map<string, string>;
32
33
 
33
34
  logo?: Image;
34
35
  banner?: Image;
@@ -56,6 +57,9 @@ export class Contest {
56
57
  incorrect: true,
57
58
  pending: true,
58
59
  };
60
+
61
+ this.group = new Map<string, Group>();
62
+ this.tag = new Map<string, string>();
59
63
  }
60
64
 
61
65
  getContestDuration(timeFormat = "HH:mm:ss"): string {
@@ -189,8 +193,36 @@ export function createContest(contestJSON: IContest): Contest {
189
193
  c.medal = contestJSON.medal;
190
194
  c.organization = contestJSON.organization;
191
195
 
192
- c.group = contestJSON.group;
193
- c.tag = contestJSON.tag;
196
+ {
197
+ const g = new Group();
198
+ g.names.set("en", "All");
199
+ g.names.set("zh-CN", "所有队伍");
200
+ g.isDefault = true;
201
+
202
+ c.group.set("all", g);
203
+ }
204
+
205
+ for (const [k, v] of Object.entries(contestJSON?.group ?? {})) {
206
+ let key = k;
207
+
208
+ const g = new Group();
209
+ g.names.set("zh-CN", v);
210
+
211
+ if (k === "official") {
212
+ g.names.set("en", "Official");
213
+ }
214
+
215
+ if (k === "unofficial") {
216
+ g.names.set("en", "Unofficial");
217
+ }
218
+
219
+ if (k === "girl" || k === "girls") {
220
+ g.names.set("en", "Girls");
221
+ key = "girl";
222
+ }
223
+
224
+ c.group.set(key, g);
225
+ }
194
226
 
195
227
  c.banner = contestJSON.banner;
196
228
 
package/src/group.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { Lang } from "@xcpcio/types";
2
+
3
+ export class Group {
4
+ names: Map<Lang, string>;
5
+ defaultLang: Lang;
6
+ isDefault: boolean;
7
+
8
+ constructor() {
9
+ this.names = new Map<Lang, string>();
10
+ this.defaultLang = "zh-CN";
11
+ this.isDefault = false;
12
+ }
13
+ }
@@ -1,11 +1,14 @@
1
1
  export class RankStatistics {
2
2
  teamSolvedNum: Array<number>;
3
+ maxSolvedProblems: number;
3
4
 
4
5
  constructor() {
5
6
  this.teamSolvedNum = [];
7
+ this.maxSolvedProblems = 0;
6
8
  }
7
9
 
8
10
  reset() {
9
11
  this.teamSolvedNum = [];
12
+ this.maxSolvedProblems = 0;
10
13
  }
11
14
  }
package/src/rank.ts CHANGED
@@ -8,15 +8,37 @@ import { Submission } from "./submission";
8
8
  import { TeamProblemStatistics } from "./problem";
9
9
  import { RankStatistics } from "./rank-statistics";
10
10
 
11
+ export interface SelectOptionItem {
12
+ value: string;
13
+ text: string;
14
+ }
15
+
11
16
  export class RankOptions {
12
17
  enableFilterSubmissionsByTimestamp: boolean;
13
18
  width: number;
14
19
  timestamp: number;
15
20
 
21
+ enableFilterTeamsByGroup: boolean;
22
+ group: string;
23
+
24
+ filterOrganizations: Array<SelectOptionItem>;
25
+ filterOrganizationMap: Map<string, SelectOptionItem>;
26
+ filterTeams: Array<SelectOptionItem>;
27
+ filterTeamMap: Map<string, SelectOptionItem>;
28
+
16
29
  constructor() {
17
30
  this.enableFilterSubmissionsByTimestamp = false;
18
31
  this.width = 0;
19
32
  this.timestamp = 0;
33
+
34
+ this.enableFilterTeamsByGroup = false;
35
+ this.group = "";
36
+
37
+ this.filterOrganizations = [];
38
+ this.filterOrganizationMap = new Map<string, SelectOptionItem>();
39
+
40
+ this.filterTeams = [];
41
+ this.filterTeamMap = new Map<string, SelectOptionItem>();
20
42
  }
21
43
 
22
44
  setWidth(width: number, contest: Contest) {
@@ -28,6 +50,39 @@ export class RankOptions {
28
50
  disableFilterSubmissionByTimestamp() {
29
51
  this.enableFilterSubmissionsByTimestamp = false;
30
52
  }
53
+
54
+ setGroup(group: string) {
55
+ this.group = group;
56
+ this.enableFilterTeamsByGroup = true;
57
+
58
+ if (this.group === "all") {
59
+ this.disableFilterTeamsByGroup();
60
+ }
61
+ }
62
+
63
+ disableFilterTeamsByGroup() {
64
+ this.enableFilterTeamsByGroup = false;
65
+ }
66
+
67
+ setFilterOrganizations(filterOrganizations: Array<SelectOptionItem>) {
68
+ const m = new Map<string, SelectOptionItem>();
69
+ filterOrganizations.forEach((item) => {
70
+ m.set(item.value, item);
71
+ });
72
+
73
+ this.filterOrganizations = filterOrganizations;
74
+ this.filterOrganizationMap = m;
75
+ }
76
+
77
+ setFilterTeams(filterTeams: Array<SelectOptionItem>) {
78
+ const m = new Map<string, SelectOptionItem>();
79
+ filterTeams.forEach((item) => {
80
+ m.set(item.value, item);
81
+ });
82
+
83
+ this.filterTeams = filterTeams;
84
+ this.filterTeamMap = m;
85
+ }
31
86
  }
32
87
 
33
88
  export class Rank {
@@ -39,6 +94,9 @@ export class Rank {
39
94
  submissions: Submissions;
40
95
  submissionsMap: Map<string, Submission>;
41
96
 
97
+ organizations: Array<string>;
98
+ originTeams: Teams;
99
+
42
100
  rankStatistics: RankStatistics;
43
101
 
44
102
  options: RankOptions;
@@ -52,6 +110,10 @@ export class Rank {
52
110
  this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
53
111
  this.submissionsMap = new Map(this.submissions.map(s => [s.id, s]));
54
112
 
113
+ this.organizations = this.buildOrganizations();
114
+ this.originTeams = this.teams.map(t => t);
115
+ this.originTeams.sort(Team.compare);
116
+
55
117
  this.rankStatistics = new RankStatistics();
56
118
 
57
119
  this.options = new RankOptions();
@@ -59,6 +121,18 @@ export class Rank {
59
121
 
60
122
  buildRank() {
61
123
  (() => {
124
+ (() => {
125
+ this.teams = [];
126
+
127
+ for (const [_k, v] of this.teamsMap) {
128
+ if (this.filterTeamByOrg(v)) {
129
+ continue;
130
+ }
131
+
132
+ this.teams.push(v);
133
+ }
134
+ })();
135
+
62
136
  for (const t of this.teams) {
63
137
  t.reset();
64
138
 
@@ -97,7 +171,7 @@ export class Rank {
97
171
  const problem = this.contest.problemsMap.get(problemId);
98
172
 
99
173
  (() => {
100
- if (team === undefined || problem === undefined) {
174
+ if (team === undefined || this.filterTeamByOrg(team) || problem === undefined) {
101
175
  return;
102
176
  }
103
177
 
@@ -177,8 +251,12 @@ export class Rank {
177
251
  preSubmissionTimestampToMinute = s.timestampToMinute;
178
252
  }
179
253
 
180
- this.teams.forEach(t => t.postProcessPlaceChartPoints());
254
+ this.teams.forEach(t => t.calcSolvedData());
255
+ this.teams.sort(Team.compare);
256
+ this.buildTeamRank();
181
257
  this.buildOrgRank();
258
+
259
+ this.teams.forEach(t => t.postProcessPlaceChartPoints());
182
260
  })();
183
261
 
184
262
  (() => {
@@ -188,6 +266,10 @@ export class Rank {
188
266
  for (const t of this.teams) {
189
267
  this.rankStatistics.teamSolvedNum[t.solvedProblemNum]++;
190
268
  }
269
+
270
+ if (this.teams.length > 0) {
271
+ this.rankStatistics.maxSolvedProblems = this.teams[0].solvedProblemNum;
272
+ }
191
273
  })();
192
274
 
193
275
  return this;
@@ -195,9 +277,11 @@ export class Rank {
195
277
 
196
278
  buildTeamRank() {
197
279
  let rank = 1;
280
+ let originalRank = 1;
198
281
  let preTeam = null;
199
282
  for (const t of this.teams) {
200
283
  t.rank = rank++;
284
+ t.originalRank = originalRank++;
201
285
 
202
286
  if (preTeam !== null) {
203
287
  if (t.isEqualRank(preTeam)) {
@@ -238,6 +322,41 @@ export class Rank {
238
322
  }
239
323
  }
240
324
 
325
+ buildOrganizations() {
326
+ if (!this.contest.organization) {
327
+ return [];
328
+ }
329
+
330
+ const res = new Array<string>();
331
+ const se = new Set<string>();
332
+
333
+ this.teams.forEach((t) => {
334
+ const org = t.organization;
335
+ if (se.has(org)) {
336
+ return;
337
+ }
338
+
339
+ res.push(org);
340
+ se.add(org);
341
+ });
342
+
343
+ res.sort();
344
+
345
+ return res;
346
+ }
347
+
348
+ filterTeamByOrg(team: Team) {
349
+ const o = this.options;
350
+
351
+ if (o.enableFilterTeamsByGroup) {
352
+ if (!team.group?.includes(o.group)) {
353
+ return true;
354
+ }
355
+ }
356
+
357
+ return false;
358
+ }
359
+
241
360
  getSubmissions() {
242
361
  if (this.options.enableFilterSubmissionsByTimestamp === false) {
243
362
  return this.submissions;
package/src/team.ts CHANGED
@@ -30,6 +30,7 @@ export class Team {
30
30
  members?: string | Array<string>;
31
31
 
32
32
  rank: number;
33
+ originalRank: number;
33
34
  organizationRank: number;
34
35
 
35
36
  solvedProblemNum: number;
@@ -57,6 +58,7 @@ export class Team {
57
58
  this.tag = [];
58
59
 
59
60
  this.rank = 0;
61
+ this.originalRank = 0;
60
62
  this.organizationRank = -1;
61
63
 
62
64
  this.solvedProblemNum = 0;
@@ -77,6 +79,7 @@ export class Team {
77
79
 
78
80
  reset() {
79
81
  this.rank = 0;
82
+ this.originalRank = 0;
80
83
  this.organizationRank = -1;
81
84
 
82
85
  this.solvedProblemNum = 0;