@xcpcio/board-app 0.42.1 → 0.44.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 (44) hide show
  1. package/dist/404.html +1 -1
  2. package/dist/assets/Board-4e99f3b0.js +3 -0
  3. package/dist/assets/{Board-a97da662.css → Board-916a327e.css} +1 -1
  4. package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-6f721c51.js +1 -0
  5. package/dist/assets/TheInput.vue_vue_type_script_setup_true_lang-2baa4cb9.js +1 -0
  6. package/dist/assets/_...all_-43e0e546.js +1 -0
  7. package/dist/assets/_name_-0c7a5e9a.js +1 -0
  8. package/dist/assets/{about-d2a0bbf5.js → about-8e75730a.js} +1 -1
  9. package/dist/assets/board-1c696495.js +1 -0
  10. package/dist/assets/{board-layout-63b169c0.js → board-layout-a459854f.js} +1 -1
  11. package/dist/assets/{headless-62373911.js → headless-a892fa96.js} +1 -1
  12. package/dist/assets/{home-2a1feeb6.js → home-7372e007.js} +1 -1
  13. package/dist/assets/{index-6e725f83.css → index-06792736.css} +1 -1
  14. package/dist/assets/{index-899b891a.js → index-07dee158.js} +65 -65
  15. package/dist/assets/index-514de2ad.js +1 -0
  16. package/dist/assets/index-cdc3411c.js +1 -0
  17. package/dist/assets/{index-layout-c5de32dd.js → index-layout-d26ccb52.js} +1 -1
  18. package/dist/assets/query-059e99b1.js +1 -0
  19. package/dist/assets/{test-eafc8541.js → test-2e2b5680.js} +1 -1
  20. package/dist/assets/{user-1450ac23.js → user-6d6fe1a1.js} +1 -1
  21. package/dist/assets/{virtual_pwa-register-749ff62f.js → virtual_pwa-register-7a6bd0e7.js} +1 -1
  22. package/dist/index.html +1 -1
  23. package/dist/sw.js +1 -1
  24. package/package.json +3 -3
  25. package/src/auto-imports.d.ts +3 -0
  26. package/src/components/TheCheckbox.vue +21 -0
  27. package/src/components/TheInput.vue +8 -3
  28. package/src/components/battle-of-giants/GiantsOptions.vue +138 -0
  29. package/src/components/battle-of-giants/GiantsScoreBoard.vue +79 -0
  30. package/src/components/board/Board.vue +10 -0
  31. package/src/components/board/OptionsModal.vue +168 -70
  32. package/src/components/board/Standings.vue +115 -0
  33. package/src/components/board/TeamUI.vue +15 -2
  34. package/src/components.d.ts +3 -0
  35. package/src/composables/query.ts +8 -0
  36. package/dist/assets/Board-e8123300.js +0 -3
  37. package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-5fef6eb5.js +0 -1
  38. package/dist/assets/TheInput.vue_vue_type_script_setup_true_lang-16068874.js +0 -1
  39. package/dist/assets/_...all_-2c6aed90.js +0 -1
  40. package/dist/assets/_name_-61215807.js +0 -1
  41. package/dist/assets/board-4a7523cf.js +0 -1
  42. package/dist/assets/index-736d7bff.js +0 -1
  43. package/dist/assets/index-9e02c4dd.js +0 -1
  44. package/dist/assets/useQueryBoardData-32e95c39.js +0 -1
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import _ from "lodash";
2
3
  import { MultiSelect } from "vue-search-select";
3
4
  import type { Rank, RankOptions, SelectOptionItem } from "@xcpcio/core";
4
5
 
@@ -14,6 +15,8 @@ const emit = defineEmits([
14
15
  "update:rankOptions",
15
16
  ]);
16
17
 
18
+ const beforeRankOptions = _.cloneDeep(props.rankOptions);
19
+
17
20
  const { t } = useI18n();
18
21
 
19
22
  const isHidden = computed({
@@ -40,27 +43,6 @@ const title = computed(() => {
40
43
  return t("type_menu.options");
41
44
  });
42
45
 
43
- const enableAnimatedSubmissions = ref(rankOptions.value.enableAnimatedSubmissions);
44
-
45
- const orgOptions = computed(() => {
46
- const res = rank.value.organizations.map((o) => {
47
- return {
48
- value: o,
49
- text: o,
50
- };
51
- });
52
-
53
- return res;
54
- });
55
-
56
- const orgSelectedItems = ref<Array<SelectOptionItem>>(rankOptions.value.filterOrganizations);
57
- const orgLastSelectItem = ref({});
58
-
59
- function orgOnSelect(selectedItems: Array<SelectOptionItem>, lastSelectItem: SelectOptionItem) {
60
- orgSelectedItems.value = selectedItems;
61
- orgLastSelectItem.value = lastSelectItem;
62
- }
63
-
64
46
  const isComposing = ref(false);
65
47
 
66
48
  function onCompositionStart() {
@@ -77,6 +59,23 @@ function onDelete(event: Event) {
77
59
  }
78
60
  }
79
61
 
62
+ const orgOptions = computed(() => {
63
+ const res = rank.value.organizations.map((o) => {
64
+ return {
65
+ value: o,
66
+ text: o,
67
+ };
68
+ });
69
+
70
+ return res;
71
+ });
72
+
73
+ const orgSelectedItems = ref<Array<SelectOptionItem>>(rankOptions.value.filterOrganizations);
74
+ function orgOnSelect(selectedItems: Array<SelectOptionItem>, _lastSelectItem: SelectOptionItem) {
75
+ orgSelectedItems.value = selectedItems;
76
+ rankOptions.value.setFilterOrganizations(selectedItems);
77
+ }
78
+
80
79
  const teamsOptions = computed(() => {
81
80
  const res = rank.value.originTeams.map((t) => {
82
81
  return {
@@ -89,31 +88,32 @@ const teamsOptions = computed(() => {
89
88
  });
90
89
 
91
90
  const teamsSelectedItems = ref<Array<SelectOptionItem>>(rankOptions.value.filterTeams);
92
- const teamsLastSelectItem = ref({});
93
-
94
- function teamsOnSelect(selectedItems: Array<SelectOptionItem>, lastSelectItem: SelectOptionItem) {
91
+ function teamsOnSelect(selectedItems: Array<SelectOptionItem>, _lastSelectItem: SelectOptionItem) {
95
92
  teamsSelectedItems.value = selectedItems;
96
- teamsLastSelectItem.value = lastSelectItem;
93
+ rankOptions.value.setFilterTeams(selectedItems);
97
94
  }
98
95
 
99
96
  function onCancel() {
97
+ rankOptions.value.setSelf(beforeRankOptions);
100
98
  isHidden.value = true;
101
99
  }
102
100
 
103
101
  const localStorageKeyForFilterOrganizations = getLocalStorageKeyForFilterOrganizations();
104
102
  const localStorageKeyForFilterTeams = getLocalStorageKeyForFilterTeams();
103
+ const routeQueryForBattleOfGiants = useRouteQueryForBattleOfGiants();
105
104
 
106
105
  function onConfirm() {
107
- rankOptions.value.setFilterOrganizations(orgSelectedItems.value);
108
- rankOptions.value.setFilterTeams(teamsSelectedItems.value);
109
-
110
106
  // can't use useStorage, maybe it's a bug
111
107
  localStorage.setItem(localStorageKeyForFilterOrganizations, JSON.stringify(orgSelectedItems.value));
112
108
  localStorage.setItem(localStorageKeyForFilterTeams, JSON.stringify(teamsSelectedItems.value));
113
109
 
114
- rankOptions.value.enableAnimatedSubmissions = enableAnimatedSubmissions.value;
110
+ if (rankOptions.value.battleOfGiants.persist) {
111
+ routeQueryForBattleOfGiants.value = rankOptions.value.battleOfGiants.ToBase64();
112
+ } else {
113
+ routeQueryForBattleOfGiants.value = undefined as unknown as string;
114
+ }
115
115
 
116
- onCancel();
116
+ isHidden.value = true;
117
117
  }
118
118
  </script>
119
119
 
@@ -121,56 +121,149 @@ function onConfirm() {
121
121
  <Modal
122
122
  v-model:isHidden="isHidden"
123
123
  :title="title"
124
- width="w-180"
124
+ width="w-200"
125
125
  >
126
126
  <div
127
127
  w-full
128
- font-bold font-mono
128
+ font-bold font-mono text-base
129
129
  flex flex-col gap-4
130
130
  items-center justify-center
131
131
  >
132
132
  <div
133
- v-if="rank.contest.organization"
134
- flex flex-col
135
- w-full
133
+ flex flex-col w-full
136
134
  >
137
- <div>
138
- Filter {{ rank.contest.organization }}
135
+ <div
136
+ flex
137
+ >
138
+ Filter
139
139
  </div>
140
140
 
141
141
  <div
142
- w-full
143
- mt-2
142
+ ml-8 mt-2
143
+ grid grid-cols-6 gap-y-4
144
144
  >
145
- <MultiSelect
146
- :options="orgOptions"
147
- :selected-options="orgSelectedItems"
148
- @select="orgOnSelect"
149
- @compositionstart="onCompositionStart"
150
- @compositionend="onCompositionEnd"
151
- @keydown.delete.capture="onDelete"
152
- />
145
+ <div
146
+ v-if="rank.contest.organization"
147
+ flex items-center
148
+ text-sm
149
+ >
150
+ {{ rank.contest.organization }}:
151
+ </div>
152
+
153
+ <div
154
+ v-if="rank.contest.organization"
155
+ flex items-center
156
+ w-full
157
+ col-span-5
158
+ >
159
+ <MultiSelect
160
+ :options="orgOptions"
161
+ :selected-options="orgSelectedItems"
162
+ @select="orgOnSelect"
163
+ @compositionstart="onCompositionStart"
164
+ @compositionend="onCompositionEnd"
165
+ @keydown.delete.capture="onDelete"
166
+ />
167
+ </div>
168
+
169
+ <div
170
+ text-sm
171
+ flex items-center
172
+ >
173
+ Team:
174
+ </div>
175
+
176
+ <div
177
+ flex items-center
178
+ w-full
179
+ col-span-5
180
+ >
181
+ <MultiSelect
182
+ :options="teamsOptions"
183
+ :selected-options="teamsSelectedItems"
184
+ @select="teamsOnSelect"
185
+ />
186
+ </div>
153
187
  </div>
154
188
  </div>
155
189
 
156
190
  <div
157
- flex flex-col
158
- w-full
191
+ flex flex-col w-full
159
192
  >
160
- <div>
161
- Filter Team
193
+ <div
194
+ flex
195
+ >
196
+ Battle of Giants
162
197
  </div>
163
198
 
164
199
  <div
165
- w-full
166
- mt-2
200
+ ml-4 mt-2
167
201
  >
168
- <MultiSelect
169
- :options="teamsOptions"
170
- :selected-options="teamsSelectedItems"
171
- @select="teamsOnSelect"
172
- />
202
+ <div
203
+ grid grid-cols-8
204
+ items-center
205
+ >
206
+ <span
207
+ text-sm font-medium
208
+ text-gray-900 dark:text-gray-300
209
+ >
210
+ Enable
211
+ </span>
212
+
213
+ <TheCheckbox
214
+ v-model="rankOptions.battleOfGiants.enable"
215
+ />
216
+
217
+ <span
218
+ text-sm font-medium
219
+ text-gray-900 dark:text-gray-300
220
+ >
221
+ Equal Teams
222
+ </span>
223
+
224
+ <TheCheckbox
225
+ v-model="rankOptions.battleOfGiants.equalTeams"
226
+ />
227
+
228
+ <span
229
+ text-sm font-medium
230
+ text-gray-900 dark:text-gray-300
231
+ >
232
+ Persist
233
+ </span>
234
+
235
+ <TheCheckbox
236
+ v-model="rankOptions.battleOfGiants.persist"
237
+ />
238
+
239
+ <span
240
+ text-sm font-medium
241
+ text-gray-900 dark:text-gray-300
242
+ >
243
+ TopX
244
+ </span>
245
+
246
+ <TheInput
247
+ v-model="rankOptions.battleOfGiants.topX"
248
+ text-align="left"
249
+ text-type="number"
250
+ />
251
+ </div>
173
252
  </div>
253
+
254
+ <GiantsOptions
255
+ :rank="rank"
256
+ :org-options="orgOptions"
257
+ :teams-options="teamsOptions"
258
+ :giants="rankOptions.battleOfGiants.blueTeam"
259
+ />
260
+
261
+ <GiantsOptions
262
+ :rank="rank"
263
+ :org-options="orgOptions"
264
+ :teams-options="teamsOptions"
265
+ :giants="rankOptions.battleOfGiants.redTeam"
266
+ />
174
267
  </div>
175
268
 
176
269
  <div
@@ -179,30 +272,35 @@ function onConfirm() {
179
272
  >
180
273
  <div
181
274
  flex
182
- mb-2
183
275
  >
184
276
  Feature
185
277
  </div>
186
278
 
187
279
  <div
188
- flex flex-row
280
+ ml-4 mt-2
189
281
  >
190
- <label class="relative inline-flex items-center cursor-pointer">
191
- <input
192
- v-model="enableAnimatedSubmissions"
193
- type="checkbox"
194
- class="sr-only peer"
282
+ <div
283
+ flex flex-row
284
+ >
285
+ <TheCheckbox
286
+ v-model="rankOptions.enableAnimatedSubmissions"
195
287
  >
196
- <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" />
197
- <span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">Animated Submissions</span>
198
- </label>
288
+ <span
289
+ ml-3
290
+ text-sm font-medium
291
+ text-gray-900 dark:text-gray-300
292
+ >
293
+ Submission Queue
294
+ </span>
295
+ </TheCheckbox>
296
+ </div>
199
297
  </div>
200
298
  </div>
201
299
 
202
300
  <div
203
- mt-2
204
301
  w-full
205
- flex items-center space-x-4
302
+ flex flex-row-reverse items-center
303
+ gap-x-4
206
304
  >
207
305
  <button
208
306
  type="submit"
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { Rank } from "@xcpcio/core";
3
+ import { GiantsType, Team } from "@xcpcio/core";
3
4
 
4
5
  const props = defineProps<{
5
6
  rank: Rank,
@@ -9,6 +10,7 @@ const { t } = useI18n();
9
10
 
10
11
  const rank = computed(() => props.rank);
11
12
  const teams = computed(() => props.rank.teams);
13
+ const rankOptions = computed(() => props.rank.options);
12
14
 
13
15
  const filterTeams = computed(() => {
14
16
  const res = props.rank.teams.filter((t) => {
@@ -28,6 +30,93 @@ const filterTeams = computed(() => {
28
30
  return res;
29
31
  });
30
32
 
33
+ interface GiantTeam {
34
+ team: Team,
35
+ giantsType: GiantsType,
36
+ }
37
+
38
+ const giantTeams = computed(() => {
39
+ const battleOfGiants = rankOptions.value.battleOfGiants;
40
+ const blueTeam = battleOfGiants.blueTeam;
41
+ const redTeam = battleOfGiants.redTeam;
42
+ blueTeam.teams = [];
43
+ redTeam.teams = [];
44
+
45
+ let blueCnt = 0;
46
+ let redCnt = 0;
47
+
48
+ for (const t of props.rank.teams) {
49
+ const giantsType = (() => {
50
+ if (
51
+ blueTeam.filterOrganizationMap.has(t.organization)
52
+ || blueTeam.filterTeamMap.has(t.id)
53
+ ) {
54
+ if (blueCnt >= battleOfGiants.topX) {
55
+ return null;
56
+ }
57
+
58
+ blueCnt++;
59
+ return GiantsType.BLUE;
60
+ }
61
+
62
+ if (
63
+ redTeam.filterOrganizationMap.has(t.organization)
64
+ || redTeam.filterTeamMap.has(t.id)
65
+ ) {
66
+ if (redCnt >= battleOfGiants.topX) {
67
+ return null;
68
+ }
69
+
70
+ redCnt++;
71
+ return GiantsType.RED;
72
+ }
73
+
74
+ return null;
75
+ })();
76
+
77
+ if (giantsType === null) {
78
+ continue;
79
+ }
80
+
81
+ if (giantsType === GiantsType.BLUE) {
82
+ blueTeam.teams.push(t);
83
+ } else {
84
+ redTeam.teams.push(t);
85
+ }
86
+
87
+ if (blueCnt === battleOfGiants.topX && redCnt === battleOfGiants.topX) {
88
+ break;
89
+ }
90
+ }
91
+
92
+ if (battleOfGiants.equalTeams) {
93
+ while (blueTeam.teams.length < redTeam.teams.length) {
94
+ redTeam.teams.pop();
95
+ }
96
+ while (redTeam.teams.length < blueTeam.teams.length) {
97
+ blueTeam.teams.pop();
98
+ }
99
+ }
100
+
101
+ const res: GiantTeam[] = [...blueTeam.teams.map((t) => {
102
+ return {
103
+ team: t,
104
+ giantsType: GiantsType.BLUE,
105
+ };
106
+ }), ...redTeam.teams.map((t) => {
107
+ return {
108
+ team: t,
109
+ giantsType: GiantsType.RED,
110
+ };
111
+ })];
112
+
113
+ res.sort((lhs, rhs) => {
114
+ return Team.compare(lhs.team, rhs.team);
115
+ });
116
+
117
+ return res;
118
+ });
119
+
31
120
  const maxOrgLength = computed(() => {
32
121
  let res = 0;
33
122
  rank.value.teams.forEach((t) => {
@@ -49,6 +138,15 @@ const maxTeamLength = computed(() => {
49
138
 
50
139
  <template>
51
140
  <div>
141
+ <div
142
+ v-if="rankOptions.battleOfGiants.enable"
143
+ mb-4
144
+ >
145
+ <GiantsScoreBoard
146
+ :battle-of-giants="rankOptions.battleOfGiants"
147
+ />
148
+ </div>
149
+
52
150
  <div>
53
151
  <table
54
152
  class="standings"
@@ -148,6 +246,23 @@ const maxTeamLength = computed(() => {
148
246
  </tr>
149
247
  </thead>
150
248
  <tbody>
249
+ <template
250
+ v-if="rankOptions.battleOfGiants.enable"
251
+ >
252
+ <template
253
+ v-for="(giantTeam, ix) in giantTeams"
254
+
255
+ :key="`giant-team-${giantTeam.team.id}`"
256
+ >
257
+ <TeamUI
258
+ :ix="ix"
259
+ :rank="rank"
260
+ :team="giantTeam.team"
261
+ :giants-type="giantTeam.giantsType"
262
+ />
263
+ </template>
264
+ </template>
265
+
151
266
  <template
152
267
  v-for="(team, ix) in filterTeams"
153
268
  :key="`filter-${team.id}`"
@@ -1,12 +1,13 @@
1
1
  <script setup lang="ts">
2
- import { MedalType } from "@xcpcio/core";
3
2
  import type { Rank, Team } from "@xcpcio/core";
3
+ import { GiantsType, MedalType } from "@xcpcio/core";
4
4
 
5
5
  const props = defineProps<{
6
6
  ix: number,
7
7
  rank: Rank,
8
8
  team: Team,
9
9
  isFilter?: boolean;
10
+ giantsType?: GiantsType;
10
11
  }>();
11
12
 
12
13
  const el = ref(null);
@@ -39,6 +40,15 @@ function getStandClassName(t: Team, isRankField = false): string {
39
40
  }
40
41
  }
41
42
 
43
+ if (props.giantsType !== undefined) {
44
+ switch (props.giantsType) {
45
+ case GiantsType.BLUE:
46
+ return "bg-blue-400";
47
+ case GiantsType.RED:
48
+ return "bg-red-400";
49
+ }
50
+ }
51
+
42
52
  if (props.isFilter) {
43
53
  return "filter-team";
44
54
  }
@@ -61,7 +71,10 @@ function isRenderByVisible() {
61
71
  <tr
62
72
  ref="el"
63
73
  class="h-10"
64
- :class="[props.isFilter ? 'filter-team' : '']"
74
+ :class="[
75
+ props.isFilter ? 'filter-team' : '',
76
+ props.giantsType !== undefined ? getStandClassName(props.team) : '',
77
+ ]"
65
78
  >
66
79
  <td
67
80
  v-if="isRenderByVisible()"
@@ -24,6 +24,8 @@ declare module 'vue' {
24
24
  DataSourceInput: typeof import('./components/DataSourceInput.vue')['default']
25
25
  Export: typeof import('./components/board/Export.vue')['default']
26
26
  Footer: typeof import('./components/Footer.vue')['default']
27
+ GiantsOptions: typeof import('./components/battle-of-giants/GiantsOptions.vue')['default']
28
+ GiantsScoreBoard: typeof import('./components/battle-of-giants/GiantsScoreBoard.vue')['default']
27
29
  GirlIcon: typeof import('./components/icon/GirlIcon.vue')['default']
28
30
  GoBack: typeof import('./components/GoBack.vue')['default']
29
31
  Modal: typeof import('./components/board/Modal.vue')['default']
@@ -49,6 +51,7 @@ declare module 'vue' {
49
51
  TeamInfoModal: typeof import('./components/board/TeamInfoModal.vue')['default']
50
52
  TeamProblemBlock: typeof import('./components/board/TeamProblemBlock.vue')['default']
51
53
  TeamUI: typeof import('./components/board/TeamUI.vue')['default']
54
+ TheCheckbox: typeof import('./components/TheCheckbox.vue')['default']
52
55
  TheCounter: typeof import('./components/TheCounter.vue')['default']
53
56
  TheInput: typeof import('./components/TheInput.vue')['default']
54
57
  Tooltip: typeof import('./components/flowbite/Tooltip.vue')['default']
@@ -7,3 +7,11 @@ export function getDataSourceUrl() {
7
7
  { transform: String },
8
8
  );
9
9
  }
10
+
11
+ export function useRouteQueryForBattleOfGiants() {
12
+ return useRouteQuery(
13
+ "battle-of-giants",
14
+ "",
15
+ { transform: String },
16
+ );
17
+ }