@xcpcio/board-app 0.66.2 → 0.68.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.
Files changed (84) hide show
  1. package/dist/404.html +1 -1
  2. package/dist/assets/Balloon.vue_vue_type_script_setup_true_lang-CVSYxE8q.js +1 -0
  3. package/dist/assets/{Board-CG69kAf7.css → Board-C89Lc-cb.css} +1 -1
  4. package/dist/assets/{Board-sX3HmCh3.js → Board-ZYE9iVvT.js} +108 -108
  5. package/dist/assets/{ContestStateBadge-BqhxSQpe.css → ContestStateBadge-D6uf8wH-.css} +1 -1
  6. package/dist/assets/ContestStateBadge-M2tyF2R2.js +1 -0
  7. package/dist/assets/Countdown-fK8Yi5IE.js +1 -0
  8. package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-B--dkpEf.js +1 -0
  9. package/dist/assets/{Footer-BWV6CMUR.js → Footer-5EqvI5Pw.js} +1 -1
  10. package/dist/assets/{NavBar-CVNz1--5.js → NavBar-Dot9PKUw.js} +1 -1
  11. package/dist/assets/{Resolver-Cu-OKY1L.js → Resolver-Yd-uWMur.js} +1 -1
  12. package/dist/assets/{RightArrowIcon-ClzV1Z1J.js → RightArrowIcon-2ctMH32p.js} +1 -1
  13. package/dist/assets/{TheInput.vue_vue_type_script_setup_true_lang-C21qO8yZ.js → TheInput.vue_vue_type_script_setup_true_lang-BqqBHhl3.js} +1 -1
  14. package/dist/assets/{Tooltip.vue_vue_type_script_setup_true_lang-D8Y2xuKM.js → Tooltip.vue_vue_type_script_setup_true_lang-BFSx1PX0.js} +1 -1
  15. package/dist/assets/_...all_-9ILQSzaE.js +1 -0
  16. package/dist/assets/_...all_-B-QynHK6.js +6 -0
  17. package/dist/assets/{_name_-CNK60UTb.js → _name_-RaGieVvR.js} +1 -1
  18. package/dist/assets/{about-Cz2BvibQ.js → about-DNnMqs94.js} +1 -1
  19. package/dist/assets/board-DmrjMzqp.js +1 -0
  20. package/dist/assets/{board-layout-DciCGZEs.js → board-layout-BSxiOc_Q.js} +1 -1
  21. package/dist/assets/{constant-BWnqIcmY.js → constant-D6Nm4zsw.js} +1 -1
  22. package/dist/assets/{dayjs-Dk3pur30.js → dayjs-txUEIdIq.js} +1 -1
  23. package/dist/assets/{default-C691w98Q.js → default-DVYI5rLy.js} +1 -1
  24. package/dist/assets/en-DYzJz2c1.js +1 -0
  25. package/dist/assets/{headless-CORlMDXV.js → headless-DNk-Av30.js} +1 -1
  26. package/dist/assets/{home-hTEr1TJm.js → home-DtZxkw_X.js} +1 -1
  27. package/dist/assets/{index-BiwUz1KO.css → index-BGJAeisZ.css} +1 -1
  28. package/dist/assets/{index-B4yWQlcU.js → index-BW1DT02C.js} +1 -1
  29. package/dist/assets/index-BeHHcwg1.js +1 -0
  30. package/dist/assets/index-BetRF1su.js +1 -0
  31. package/dist/assets/{index-9_oqoLcR.js → index-DA67idY7.js} +5 -5
  32. package/dist/assets/{index-D6pv9dGi.js → index-DSyYcmRD.js} +2 -2
  33. package/dist/assets/index-HzZPY3xn.js +1 -0
  34. package/dist/assets/{index-layout-CnsXfFdQ.js → index-layout-D1p6y6vz.js} +1 -1
  35. package/dist/assets/{pagination-DnhQ5r1u.js → pagination-f8dx5hXg.js} +1 -1
  36. package/dist/assets/{person-U3tcpMKk.js → person-CTZ9LyDi.js} +1 -1
  37. package/dist/assets/query-DSjYhsbY.js +39 -0
  38. package/dist/assets/rank-BbnylyV_.js +1 -0
  39. package/dist/assets/{test-Cflk2EUV.js → test-C0_2hMsN.js} +1 -1
  40. package/dist/assets/{use-vmodel-Bl_BJeQo.js → use-vmodel-BmoIT9Gg.js} +1 -1
  41. package/dist/assets/useQueryBoardData-BfELgSoK.js +1 -0
  42. package/dist/assets/{user-LgM0y5TK.js → user-Dx6zM0gp.js} +1 -1
  43. package/dist/assets/{virtual_pwa-register-72WHXVVX.js → virtual_pwa-register-BfEpgXzG.js} +1 -1
  44. package/dist/assets/zh-CN-Cbd_t3aN.js +1 -0
  45. package/dist/index.html +1 -1
  46. package/dist/sw.js +1 -1
  47. package/package.json +3 -3
  48. package/src/auto-imports.d.ts +19 -4
  49. package/src/components/CustomBalloon.vue +1 -3
  50. package/src/components/CustomBoard.vue +1 -3
  51. package/src/components/CustomCountdown.vue +1 -3
  52. package/src/components/CustomResolver.vue +1 -3
  53. package/src/components/DataSourceInput.vue +1 -3
  54. package/src/components/board/Badge.vue +1 -1
  55. package/src/components/board/Board.vue +5 -20
  56. package/src/components/board/OptionsModal.vue +1 -1
  57. package/src/components/board/Progress.vue +1 -2
  58. package/src/components/board/SecondLevelMenu.vue +1 -2
  59. package/src/components/board/Standings.vue +1 -1
  60. package/src/components/board/TeamInfo.vue +116 -0
  61. package/src/components/board/TeamInfoModal.vue +16 -13
  62. package/src/components/board/TeamUI.vue +2 -1
  63. package/src/components/board/Utility.vue +1 -2
  64. package/src/components.d.ts +1 -0
  65. package/src/composables/query.ts +46 -6
  66. package/src/composables/rating.ts +6 -2
  67. package/src/composables/useRouteQueryWithoutParam.ts +157 -0
  68. package/src/pages/[...all].vue +1 -7
  69. package/src/pages/index.vue +1 -2
  70. package/dist/assets/Balloon.vue_vue_type_script_setup_true_lang-Cd-QNnDt.js +0 -1
  71. package/dist/assets/ContestStateBadge-Cad99MQ4.js +0 -1
  72. package/dist/assets/Countdown-Dhia-u0Y.js +0 -1
  73. package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-DVWnGqx-.js +0 -1
  74. package/dist/assets/_...all_-D0bkV7XM.js +0 -1
  75. package/dist/assets/_...all_-DJwQ4yj4.js +0 -6
  76. package/dist/assets/board-CAZpPFLb.js +0 -1
  77. package/dist/assets/contest-CgNGJetR.js +0 -39
  78. package/dist/assets/en-78xzu5B_.js +0 -1
  79. package/dist/assets/index-BX3T_nMP.js +0 -1
  80. package/dist/assets/index-DncwAlQA.js +0 -1
  81. package/dist/assets/index-mCQ-iQmf.js +0 -1
  82. package/dist/assets/query-CDfDbkZE.js +0 -1
  83. package/dist/assets/rank-B4f6mi_k.js +0 -1
  84. package/dist/assets/zh-CN-32UqCzRy.js +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xcpcio/board-app",
3
3
  "type": "module",
4
- "version": "0.66.2",
4
+ "version": "0.68.0",
5
5
  "description": "The ICPC Series Competition Leaderboard Visualization Engine",
6
6
  "author": "Dup4 <hi@dup4.com>",
7
7
  "license": "MIT",
@@ -53,8 +53,8 @@
53
53
  "vue-router": "^4.5.1",
54
54
  "vue-search-select": "^3.2.0",
55
55
  "vue-toast-notification": "^3.1.3",
56
- "@xcpcio/core": "0.66.2",
57
- "@xcpcio/types": "0.66.2"
56
+ "@xcpcio/core": "0.68.0",
57
+ "@xcpcio/types": "0.68.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@iconify/json": "^2.2.394",
@@ -53,7 +53,6 @@ declare global {
53
53
  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
54
54
  const getCurrentScope: typeof import('vue')['getCurrentScope']
55
55
  const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
56
- const getDataSourceUrl: typeof import('./composables/query')['getDataSourceUrl']
57
56
  const getLocalStorageKeyForFilterOrganizations: typeof import('./composables/useLocalStorage')['getLocalStorageKeyForFilterOrganizations']
58
57
  const getLocalStorageKeyForFilterTeams: typeof import('./composables/useLocalStorage')['getLocalStorageKeyForFilterTeams']
59
58
  const getMedalColor: typeof import('./composables/color')['getMedalColor']
@@ -260,11 +259,18 @@ declare global {
260
259
  const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
261
260
  const usePrevious: typeof import('@vueuse/core')['usePrevious']
262
261
  const useQueryBoardData: typeof import('./composables/useQueryBoardData')['useQueryBoardData']
262
+ const useQueryForBattleOfGiants: typeof import('./composables/query')['useQueryForBattleOfGiants']
263
+ const useQueryForComponent: typeof import('./composables/query')['useQueryForComponent']
264
+ const useQueryForDataSourceUrl: typeof import('./composables/query')['useQueryForDataSourceUrl']
265
+ const useQueryForGroup: typeof import('./composables/query')['useQueryForGroup']
266
+ const useQueryForProgressRatio: typeof import('./composables/query')['useQueryForProgressRatio']
267
+ const useQueryForReplayStartTime: typeof import('./composables/query')['useQueryForReplayStartTime']
268
+ const useQueryForSearch: typeof import('./composables/query')['useQueryForSearch']
263
269
  const useRafFn: typeof import('@vueuse/core')['useRafFn']
264
270
  const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
265
271
  const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
266
272
  const useRoute: typeof import('vue-router')['useRoute']
267
- const useRouteQueryForBattleOfGiants: typeof import('./composables/query')['useRouteQueryForBattleOfGiants']
273
+ const useRouteQueryWithoutParam: typeof import('./composables/useRouteQueryWithoutParam')['useRouteQueryWithoutParam']
268
274
  const useRouter: typeof import('vue-router')['useRouter']
269
275
  const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
270
276
  const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
@@ -353,6 +359,9 @@ declare global {
353
359
  // @ts-ignore
354
360
  export type { BoardData } from './composables/useQueryBoardData'
355
361
  import('./composables/useQueryBoardData')
362
+ // @ts-ignore
363
+ export type { RouteQueryValueRaw, RouteHashValueRaw, ReactiveRouteOptions, ReactiveRouteOptionsWithTransform } from './composables/useRouteQueryWithoutParam'
364
+ import('./composables/useRouteQueryWithoutParam')
356
365
  }
357
366
 
358
367
  // for vue template auto import
@@ -407,7 +416,6 @@ declare module 'vue' {
407
416
  readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
408
417
  readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
409
418
  readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
410
- readonly getDataSourceUrl: UnwrapRef<typeof import('./composables/query')['getDataSourceUrl']>
411
419
  readonly getLocalStorageKeyForFilterOrganizations: UnwrapRef<typeof import('./composables/useLocalStorage')['getLocalStorageKeyForFilterOrganizations']>
412
420
  readonly getLocalStorageKeyForFilterTeams: UnwrapRef<typeof import('./composables/useLocalStorage')['getLocalStorageKeyForFilterTeams']>
413
421
  readonly getMedalColor: UnwrapRef<typeof import('./composables/color')['getMedalColor']>
@@ -614,11 +622,18 @@ declare module 'vue' {
614
622
  readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
615
623
  readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
616
624
  readonly useQueryBoardData: UnwrapRef<typeof import('./composables/useQueryBoardData')['useQueryBoardData']>
625
+ readonly useQueryForBattleOfGiants: UnwrapRef<typeof import('./composables/query')['useQueryForBattleOfGiants']>
626
+ readonly useQueryForComponent: UnwrapRef<typeof import('./composables/query')['useQueryForComponent']>
627
+ readonly useQueryForDataSourceUrl: UnwrapRef<typeof import('./composables/query')['useQueryForDataSourceUrl']>
628
+ readonly useQueryForGroup: UnwrapRef<typeof import('./composables/query')['useQueryForGroup']>
629
+ readonly useQueryForProgressRatio: UnwrapRef<typeof import('./composables/query')['useQueryForProgressRatio']>
630
+ readonly useQueryForReplayStartTime: UnwrapRef<typeof import('./composables/query')['useQueryForReplayStartTime']>
631
+ readonly useQueryForSearch: UnwrapRef<typeof import('./composables/query')['useQueryForSearch']>
617
632
  readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
618
633
  readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
619
634
  readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
620
635
  readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
621
- readonly useRouteQueryForBattleOfGiants: UnwrapRef<typeof import('./composables/query')['useRouteQueryForBattleOfGiants']>
636
+ readonly useRouteQueryWithoutParam: UnwrapRef<typeof import('./composables/useRouteQueryWithoutParam')['useRouteQueryWithoutParam']>
622
637
  readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
623
638
  readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
624
639
  readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
@@ -1,7 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { getDataSourceUrl } from "@board/composables/query";
3
-
4
- const dataSourceUrl = getDataSourceUrl();
2
+ const dataSourceUrl = useQueryForDataSourceUrl();
5
3
  </script>
6
4
 
7
5
  <template>
@@ -1,7 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { getDataSourceUrl } from "@board/composables/query";
3
-
4
- const dataSourceUrl = getDataSourceUrl();
2
+ const dataSourceUrl = useQueryForDataSourceUrl();
5
3
  </script>
6
4
 
7
5
  <template>
@@ -1,7 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { getDataSourceUrl } from "@board/composables/query";
3
-
4
- const dataSourceUrl = getDataSourceUrl();
2
+ const dataSourceUrl = useQueryForDataSourceUrl();
5
3
  </script>
6
4
 
7
5
  <template>
@@ -1,7 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { getDataSourceUrl } from "@board/composables/query";
3
-
4
- const dataSourceUrl = getDataSourceUrl();
2
+ const dataSourceUrl = useQueryForDataSourceUrl();
5
3
  </script>
6
4
 
7
5
  <template>
@@ -1,9 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { getDataSourceUrl } from "@board/composables/query";
3
-
4
2
  const { t } = useI18n();
5
3
 
6
- const dataSourceUrl = getDataSourceUrl();
4
+ const dataSourceUrl = useQueryForDataSourceUrl();
7
5
  const dataSourceUrlText = "Data Source URL";
8
6
  const dataSourceUrlInput = ref(dataSourceUrl.value);
9
7
 
@@ -22,7 +22,7 @@ const widthClass = computed(() => {
22
22
 
23
23
  <template>
24
24
  <img
25
- v-if="image.base64"
25
+ v-if="image.base64 || image.url"
26
26
  :src="getImageSource(image)"
27
27
  alt="badge"
28
28
  :class="[widthClass]"
@@ -3,12 +3,6 @@ import type { Item } from "@board/components/board/SecondLevelMenu.vue";
3
3
  import type { Contest, Submissions, Teams } from "@xcpcio/core";
4
4
  import type { Contest as IContest, Submissions as ISubmissions, Teams as ITeams, Lang } from "@xcpcio/types";
5
5
 
6
- import FilterModal from "@board/components/board/FilterModal.vue";
7
-
8
- import { TITLE_SUFFIX } from "@board/composables/constant";
9
- import { onKeyStroke, useDocumentVisibility, useIntervalFn, useNow } from "@vueuse/core";
10
-
11
- import { useRouteQuery } from "@vueuse/router";
12
6
  import { createContest, createSubmissions, createTeams, getImageSource, getTimeDiff, Rank, RankOptions } from "@xcpcio/core";
13
7
  import { ContestState } from "@xcpcio/types";
14
8
 
@@ -34,10 +28,6 @@ const contestName = ref("");
34
28
 
35
29
  const enableAutoScroll = ref(false);
36
30
 
37
- function fixPath(path: string) {
38
- return path.replaceAll("%2F", "/");
39
- }
40
-
41
31
  (() => {
42
32
  const filterOrganizations = useLocalStorageForFilterOrganizations();
43
33
  const filterTeams = useLocalStorageForFilterTeams();
@@ -52,7 +42,7 @@ function fixPath(path: string) {
52
42
  })();
53
43
 
54
44
  (() => {
55
- const routeQueryForBattleOfGiants = useRouteQueryForBattleOfGiants();
45
+ const routeQueryForBattleOfGiants = useQueryForBattleOfGiants();
56
46
  if (
57
47
  routeQueryForBattleOfGiants.value !== null
58
48
  && routeQueryForBattleOfGiants.value !== undefined
@@ -70,17 +60,12 @@ function onChangeCurrentGroup(nextGroup: string) {
70
60
  rankOptions.value.setGroup(nextGroup);
71
61
  }
72
62
  (() => {
73
- const currentGroupFromRouteQuery = useRouteQuery(
74
- /* name */ "group",
75
- /* defaultValue */ "all",
76
- { transform: String },
77
- );
78
-
63
+ const currentGroupFromRouteQuery = useQueryForGroup();
79
64
  currentGroup.value = currentGroupFromRouteQuery.value;
80
65
  rankOptions.value.setGroup(currentGroupFromRouteQuery.value);
81
66
  })();
82
67
 
83
- const replayStartTime = useRouteQuery("replay-start-time", 0, { transform: Number });
68
+ const replayStartTime = useQueryForReplayStartTime();
84
69
 
85
70
  const isReBuildRank = ref(false);
86
71
  function reBuildRank(options = { force: false }) {
@@ -109,7 +94,7 @@ function updateContestName() {
109
94
  title.value = `${contestName.value} | ${TITLE_SUFFIX}`;
110
95
  }
111
96
 
112
- const { data, isError, error, refetch } = useQueryBoardData(props.dataSourceUrl ?? fixPath(route.path), now);
97
+ const { data, isError, error, refetch } = useQueryBoardData(props.dataSourceUrl ?? route.path, now);
113
98
  watch(data, async () => {
114
99
  if (data.value === null || data.value === undefined) {
115
100
  return;
@@ -420,7 +405,7 @@ const widthClass = "sm:w-[1260px] xl:w-screen";
420
405
  >
421
406
  <div class="max-w-[92%]">
422
407
  <img
423
- :src="getImageSource(rank.contest.banner, `${DATA_HOST}${fixPath(route.path).slice(1)}`)"
408
+ :src="getImageSource(rank.contest.banner, `${DATA_HOST}${route.path.slice(1)}`)"
424
409
  class="w-screen"
425
410
  alt="banner"
426
411
  >
@@ -66,7 +66,7 @@ const teamsOptions = computed(() => {
66
66
  return res;
67
67
  });
68
68
 
69
- const routeQueryForBattleOfGiants = useRouteQueryForBattleOfGiants();
69
+ const routeQueryForBattleOfGiants = useQueryForBattleOfGiants();
70
70
  function persistBattleOfGiants() {
71
71
  if (rankOptions.value.battleOfGiants.persist) {
72
72
  routeQueryForBattleOfGiants.value = rankOptions.value.battleOfGiants.ToBase64();
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import type { Rank, RankOptions } from "@xcpcio/core";
3
- import { useRouteQuery } from "@vueuse/router";
4
3
  import { createDayJS, getTimeDiff } from "@xcpcio/core";
5
4
  import { ContestState } from "@xcpcio/types";
6
5
 
@@ -44,7 +43,7 @@ const isDragging = ref(false);
44
43
  const dragWidth = ref(0);
45
44
  const barWidth = ref(props.width);
46
45
  const barWidthPX = ref(0);
47
- const progressRatio = useRouteQuery("progress-ratio", -1, { transform: Number });
46
+ const progressRatio = useQueryForProgressRatio();
48
47
 
49
48
  const scroll = ref<HTMLElement>(null as unknown as HTMLElement);
50
49
  const mask = ref<HTMLElement>(null as unknown as HTMLElement);
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { I18nText } from "@xcpcio/core";
3
3
  import type { Lang } from "@xcpcio/types";
4
- import { useRouteQuery } from "@vueuse/router";
5
4
 
6
5
  export interface Item {
7
6
  title: string | I18nText;
@@ -33,7 +32,7 @@ const defaultType = computed(() => {
33
32
  return props.items?.[0].keyword;
34
33
  });
35
34
 
36
- const currentItemFromRouteQuery = useRouteQuery(
35
+ const currentItemFromRouteQuery = useRouteQueryWithoutParam(
37
36
  props.queryParamName,
38
37
  defaultType.value,
39
38
  { transform: String },
@@ -167,7 +167,7 @@ const maxTeamLength = computed(() => {
167
167
  <th
168
168
  v-if="rank.contest.badge"
169
169
  class="title"
170
- style="width: 2rem;"
170
+ style="width: 2.5rem;"
171
171
  >
172
172
  {{ rank.contest.badge }}
173
173
  </th>
@@ -0,0 +1,116 @@
1
+ <script setup lang="ts">
2
+ import type { Rank, Team } from "@xcpcio/core";
3
+ import type { Lang } from "@xcpcio/types";
4
+
5
+ const props = defineProps<{
6
+ rank: Rank;
7
+ team: Team;
8
+ }>();
9
+
10
+ const { t, locale } = useI18n();
11
+ const lang = computed(() => locale.value as unknown as Lang);
12
+
13
+ const rank = computed(() => props.rank);
14
+ const team = computed(() => props.team);
15
+
16
+ const photoLoadError = ref(false);
17
+ function handlePhotoError() {
18
+ photoLoadError.value = true;
19
+ }
20
+
21
+ const hasPhoto = computed(() => {
22
+ if (team.value.missingPhoto || photoLoadError.value) {
23
+ return false;
24
+ }
25
+
26
+ return rank.value.contest.options.teamPhotoTemplate || team.value.photo;
27
+ });
28
+
29
+ const teamPhoto = computed(() => {
30
+ if (team.value.photo?.url) {
31
+ return {
32
+ url: team.value.photo.url,
33
+ width: team.value.photo.width,
34
+ height: team.value.photo.height,
35
+ };
36
+ }
37
+
38
+ const template = rank.value.contest.options.teamPhotoTemplate;
39
+ if (template?.url) {
40
+ return {
41
+ url: template.url.replace(/\$\{team_id\}/, team.value.id),
42
+ width: template.width,
43
+ height: template.height,
44
+ };
45
+ }
46
+
47
+ return { url: "", width: undefined, height: undefined };
48
+ });
49
+
50
+ const groupNames = computed(() => {
51
+ return team.value.group
52
+ .map((groupId) => {
53
+ const group = rank.value.contest.group.get(groupId);
54
+ return group ? group.name.getOrDefault(lang.value) : groupId;
55
+ })
56
+ .join(", ");
57
+ });
58
+
59
+ const tableRows = computed(() => {
60
+ const rows = [
61
+ { label: t("team_info.rank"), value: team.value.rank, show: true },
62
+ { label: t("team_info.team_name"), value: team.value.name.getOrDefault(lang.value), show: true },
63
+ { label: t("team_info.organization"), value: team.value.organization, show: !!team.value.organization },
64
+ { label: t("team_info.group"), value: groupNames.value, show: team.value.group.length > 0 },
65
+ { label: t("team_info.members"), value: team.value.membersToString(lang.value), show: team.value.members.length > 0 },
66
+ { label: t("team_info.coaches"), value: team.value.coachesToString(lang.value), show: team.value.coaches.length > 0 },
67
+ { label: t("team_info.location"), value: team.value.location, show: !!team.value.location },
68
+ ];
69
+ return rows.filter(row => row.show);
70
+ });
71
+ </script>
72
+
73
+ <template>
74
+ <div
75
+ flex w-full
76
+ :class="hasPhoto ? 'flex-row gap-8' : 'justify-center'"
77
+ >
78
+ <div
79
+ :class="hasPhoto ? 'flex-1' : 'w-1/2'"
80
+ flex items-center justify-center
81
+ >
82
+ <table w-full text-lg text-left text-gray-900 dark:text-gray-100>
83
+ <tbody>
84
+ <tr
85
+ v-for="(row, index) in tableRows"
86
+ :key="row.label"
87
+ :class="index % 2 === 0 ? 'bg-gray-100 dark:bg-gray-700' : 'bg-white dark:bg-gray-800'"
88
+ >
89
+ <td px-4 py-2>
90
+ {{ row.label }}
91
+ </td>
92
+ <td px-4 py-2>
93
+ {{ row.value }}
94
+ </td>
95
+ </tr>
96
+ </tbody>
97
+ </table>
98
+ </div>
99
+ <div v-if="hasPhoto" flex-1>
100
+ <div flex items-center justify-center>
101
+ <a
102
+ class="max-w-[98%]"
103
+ target="_blank" rel="nofollow" :href="teamPhoto.url"
104
+ >
105
+ <img
106
+ :src="teamPhoto.url"
107
+ :width="teamPhoto.width"
108
+ :height="teamPhoto.height"
109
+ alt="team photo"
110
+ @error="handlePhotoError"
111
+ >
112
+ </a>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </template>
@@ -25,7 +25,7 @@ const isHidden = computed({
25
25
  },
26
26
  });
27
27
 
28
- const currentType = ref("submissions");
28
+ const currentType = ref("info");
29
29
 
30
30
  const rank = computed(() => props.rank);
31
31
  const team = computed(() => props.team);
@@ -39,21 +39,14 @@ const headerTitle = computed(() => {
39
39
 
40
40
  res += `${team.value.name.getOrDefault(lang.value)}`;
41
41
 
42
- if (team.value.members && team.value.members.length > 0) {
43
- res += ` - ${team.value.membersToString(lang.value)}`;
44
- }
45
-
46
- if (team.value.coaches && team.value.coaches.length > 0) {
47
- res += ` - ${team.value.coachesToString(lang.value)}(coach)`;
48
- }
49
-
50
42
  return res;
51
43
  });
52
44
 
45
+ const TYPE_INFO = "info";
53
46
  const TYPE_SUBMISSIONS = "submissions";
54
47
  const TYPE_STATISTICS = "statistics";
55
48
  const TYPE_AWARDS = "awards";
56
- const types = [TYPE_SUBMISSIONS, TYPE_STATISTICS, TYPE_AWARDS];
49
+ const types = [TYPE_INFO, TYPE_SUBMISSIONS, TYPE_STATISTICS, TYPE_AWARDS];
57
50
  </script>
58
51
 
59
52
  <template>
@@ -75,13 +68,13 @@ const types = [TYPE_SUBMISSIONS, TYPE_STATISTICS, TYPE_AWARDS];
75
68
  space-y-3 md:space-y-0
76
69
  >
77
70
  <div
78
- flex flex-row
79
- space-x-3
71
+ flex flex-row items-center justify-center
72
+ space-x-4
80
73
  >
81
74
  <Badge
82
75
  v-if="team.badge"
83
76
  :image="team.badge"
84
- width-class="h-8 w-8"
77
+ width-class="h-16 w-16"
85
78
  />
86
79
 
87
80
  <Tooltip>
@@ -120,6 +113,16 @@ const types = [TYPE_SUBMISSIONS, TYPE_STATISTICS, TYPE_AWARDS];
120
113
  font-bold font-mono
121
114
  flex items-center justify-center
122
115
  >
116
+ <div
117
+ v-if="currentType === TYPE_INFO"
118
+ w-full
119
+ >
120
+ <TeamInfo
121
+ :rank="rank"
122
+ :team="team"
123
+ />
124
+ </div>
125
+
123
126
  <div
124
127
  v-if="currentType === TYPE_SUBMISSIONS"
125
128
  w-full
@@ -91,10 +91,11 @@ function isRenderByVisible() {
91
91
  <td
92
92
  v-if="rank.contest.badge && team.badge && isRenderByVisible()"
93
93
  class="empty flex items-center justify-center"
94
+ style="padding: 0px !important; margin: 0px !important;"
94
95
  >
95
96
  <Badge
96
97
  :image="team.badge"
97
- width-class="h-8 w-8"
98
+ width-class="w-full h-full"
98
99
  />
99
100
  </td>
100
101
  <td
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import type { Rank } from "@xcpcio/core";
3
- import { useRouteQuery } from "@vueuse/router";
4
3
 
5
4
  const props = defineProps<{
6
5
  rank: Rank;
@@ -10,7 +9,7 @@ const { t } = useI18n();
10
9
 
11
10
  const route = useRoute();
12
11
  const router = useRouter();
13
- const component = useRouteQuery("component", "board", { transform: String });
12
+ const component = useQueryForComponent();
14
13
 
15
14
  function goResolver() {
16
15
  if (window.DATA_SOURCE) {
@@ -60,6 +60,7 @@ declare module 'vue' {
60
60
  SubmitHeatMap: typeof import('./components/board/SubmitHeatMap.vue')['default']
61
61
  TablePagination: typeof import('./components/table/TablePagination.vue')['default']
62
62
  TeamAwards: typeof import('./components/board/TeamAwards.vue')['default']
63
+ TeamInfo: typeof import('./components/board/TeamInfo.vue')['default']
63
64
  TeamInfoModal: typeof import('./components/board/TeamInfoModal.vue')['default']
64
65
  TeamProblemBlock: typeof import('./components/board/TeamProblemBlock.vue')['default']
65
66
  TeamUI: typeof import('./components/board/TeamUI.vue')['default']
@@ -1,17 +1,57 @@
1
- import { useRouteQuery } from "@vueuse/router";
1
+ import { useRouteQueryWithoutParam } from "./useRouteQueryWithoutParam";
2
2
 
3
- export function getDataSourceUrl() {
4
- return useRouteQuery(
5
- "data-source",
3
+ export function useQueryForSearch() {
4
+ return useRouteQueryWithoutParam(
5
+ "s",
6
6
  "",
7
7
  { transform: String },
8
8
  );
9
9
  }
10
10
 
11
- export function useRouteQueryForBattleOfGiants() {
12
- return useRouteQuery(
11
+ export function useQueryForDataSourceUrl() {
12
+ return useRouteQueryWithoutParam(
13
+ "data-source-url",
14
+ "",
15
+ { transform: String },
16
+ );
17
+ }
18
+
19
+ export function useQueryForGroup() {
20
+ return useRouteQueryWithoutParam(
21
+ "group",
22
+ "all",
23
+ { transform: String },
24
+ );
25
+ }
26
+
27
+ export function useQueryForReplayStartTime() {
28
+ return useRouteQueryWithoutParam(
29
+ "replay-start-time",
30
+ "0",
31
+ { transform: Number },
32
+ );
33
+ }
34
+
35
+ export function useQueryForProgressRatio() {
36
+ return useRouteQueryWithoutParam(
37
+ "progress-ratio",
38
+ -1,
39
+ { transform: Number },
40
+ );
41
+ }
42
+
43
+ export function useQueryForBattleOfGiants() {
44
+ return useRouteQueryWithoutParam(
13
45
  "battle-of-giants",
14
46
  "",
15
47
  { transform: String },
16
48
  );
17
49
  }
50
+
51
+ export function useQueryForComponent() {
52
+ return useRouteQueryWithoutParam(
53
+ "component",
54
+ "board",
55
+ { transform: String },
56
+ );
57
+ }
@@ -1,4 +1,5 @@
1
1
  import type { RatingUser } from "@xcpcio/core";
2
+ import type { Lang } from "@xcpcio/types";
2
3
  import { RatingLevelToString, RatingUtility } from "@xcpcio/core";
3
4
 
4
5
  interface RatingGraphData {
@@ -27,6 +28,9 @@ export function getRatingGraphOptions(
27
28
  ) {
28
29
  const data: RatingGraphData[] = [];
29
30
 
31
+ const { locale } = useI18n();
32
+ const lang = locale.value as unknown as Lang;
33
+
30
34
  {
31
35
  let oldRating = 0;
32
36
  for (const h of ratingUser.ratingHistories) {
@@ -35,10 +39,10 @@ export function getRatingGraphOptions(
35
39
  d.y = h.rating;
36
40
  d.diffRating = getDiffRating(oldRating, h.rating);
37
41
  d.rank = h.rank;
38
- d.contestName = h.contestName;
42
+ d.contestName = h.contestName.getOrDefault(lang);
39
43
  d.link = `/board/${h.contestID}`;
40
44
  d.contestTime = h.contestTime.format("YYYY-MM-DD HH:mm:ss");
41
- d.teamName = h.teamName;
45
+ d.teamName = h.teamName.getOrDefault(lang);
42
46
  d.ratingTitle = RatingLevelToString[RatingUtility.getRatingLevel(h.rating)];
43
47
  data.push(d);
44
48