@xcpcio/board-app 0.42.1 → 0.43.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 (36) hide show
  1. package/dist/404.html +1 -1
  2. package/dist/assets/Board-5ab7c967.js +3 -0
  3. package/dist/assets/{Board-a97da662.css → Board-78c68c35.css} +1 -1
  4. package/dist/assets/{DataSourceInput.vue_vue_type_script_setup_true_lang-5fef6eb5.js → DataSourceInput.vue_vue_type_script_setup_true_lang-ae8a0f7c.js} +1 -1
  5. package/dist/assets/TheInput.vue_vue_type_script_setup_true_lang-9cd306c7.js +1 -0
  6. package/dist/assets/_...all_-b536c4f8.js +1 -0
  7. package/dist/assets/_name_-73ea91e2.js +1 -0
  8. package/dist/assets/{about-d2a0bbf5.js → about-8408eddf.js} +1 -1
  9. package/dist/assets/{board-4a7523cf.js → board-d5243ade.js} +1 -1
  10. package/dist/assets/{board-layout-63b169c0.js → board-layout-db1006f7.js} +1 -1
  11. package/dist/assets/{headless-62373911.js → headless-4685ba2c.js} +1 -1
  12. package/dist/assets/{home-2a1feeb6.js → home-649aa1f3.js} +1 -1
  13. package/dist/assets/{index-6e725f83.css → index-06792736.css} +1 -1
  14. package/dist/assets/{index-9e02c4dd.js → index-41b501c0.js} +1 -1
  15. package/dist/assets/{index-736d7bff.js → index-4339f7e3.js} +1 -1
  16. package/dist/assets/{index-899b891a.js → index-a4eda403.js} +43 -43
  17. package/dist/assets/{index-layout-c5de32dd.js → index-layout-f67288b7.js} +1 -1
  18. package/dist/assets/{test-eafc8541.js → test-838ba8f2.js} +1 -1
  19. package/dist/assets/{useQueryBoardData-32e95c39.js → useQueryBoardData-93b68e58.js} +1 -1
  20. package/dist/assets/{user-1450ac23.js → user-f204821f.js} +1 -1
  21. package/dist/assets/{virtual_pwa-register-749ff62f.js → virtual_pwa-register-53ed75bd.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/components/TheCheckbox.vue +21 -0
  26. package/src/components/TheInput.vue +8 -3
  27. package/src/components/battle-of-giants/GiantsOptions.vue +138 -0
  28. package/src/components/battle-of-giants/GiantsScoreBoard.vue +79 -0
  29. package/src/components/board/OptionsModal.vue +162 -71
  30. package/src/components/board/Standings.vue +115 -0
  31. package/src/components/board/TeamUI.vue +15 -2
  32. package/src/components.d.ts +3 -0
  33. package/dist/assets/Board-e8123300.js +0 -3
  34. package/dist/assets/TheInput.vue_vue_type_script_setup_true_lang-16068874.js +0 -1
  35. package/dist/assets/_...all_-2c6aed90.js +0 -1
  36. package/dist/assets/_name_-61215807.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,14 +88,13 @@ 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
 
@@ -104,16 +102,11 @@ const localStorageKeyForFilterOrganizations = getLocalStorageKeyForFilterOrganiz
104
102
  const localStorageKeyForFilterTeams = getLocalStorageKeyForFilterTeams();
105
103
 
106
104
  function onConfirm() {
107
- rankOptions.value.setFilterOrganizations(orgSelectedItems.value);
108
- rankOptions.value.setFilterTeams(teamsSelectedItems.value);
109
-
110
105
  // can't use useStorage, maybe it's a bug
111
106
  localStorage.setItem(localStorageKeyForFilterOrganizations, JSON.stringify(orgSelectedItems.value));
112
107
  localStorage.setItem(localStorageKeyForFilterTeams, JSON.stringify(teamsSelectedItems.value));
113
108
 
114
- rankOptions.value.enableAnimatedSubmissions = enableAnimatedSubmissions.value;
115
-
116
- onCancel();
109
+ isHidden.value = true;
117
110
  }
118
111
  </script>
119
112
 
@@ -121,56 +114,149 @@ function onConfirm() {
121
114
  <Modal
122
115
  v-model:isHidden="isHidden"
123
116
  :title="title"
124
- width="w-180"
117
+ width="w-200"
125
118
  >
126
119
  <div
127
120
  w-full
128
- font-bold font-mono
121
+ font-bold font-mono text-base
129
122
  flex flex-col gap-4
130
123
  items-center justify-center
131
124
  >
132
125
  <div
133
- v-if="rank.contest.organization"
134
- flex flex-col
135
- w-full
126
+ flex flex-col w-full
136
127
  >
137
- <div>
138
- Filter {{ rank.contest.organization }}
128
+ <div
129
+ flex
130
+ >
131
+ Filter
139
132
  </div>
140
133
 
141
134
  <div
142
- w-full
143
- mt-2
135
+ ml-8 mt-2
136
+ grid grid-cols-6 gap-y-4
144
137
  >
145
- <MultiSelect
146
- :options="orgOptions"
147
- :selected-options="orgSelectedItems"
148
- @select="orgOnSelect"
149
- @compositionstart="onCompositionStart"
150
- @compositionend="onCompositionEnd"
151
- @keydown.delete.capture="onDelete"
152
- />
138
+ <div
139
+ v-if="rank.contest.organization"
140
+ flex items-center
141
+ text-sm
142
+ >
143
+ {{ rank.contest.organization }}:
144
+ </div>
145
+
146
+ <div
147
+ v-if="rank.contest.organization"
148
+ flex items-center
149
+ w-full
150
+ col-span-5
151
+ >
152
+ <MultiSelect
153
+ :options="orgOptions"
154
+ :selected-options="orgSelectedItems"
155
+ @select="orgOnSelect"
156
+ @compositionstart="onCompositionStart"
157
+ @compositionend="onCompositionEnd"
158
+ @keydown.delete.capture="onDelete"
159
+ />
160
+ </div>
161
+
162
+ <div
163
+ text-sm
164
+ flex items-center
165
+ >
166
+ Team:
167
+ </div>
168
+
169
+ <div
170
+ flex items-center
171
+ w-full
172
+ col-span-5
173
+ >
174
+ <MultiSelect
175
+ :options="teamsOptions"
176
+ :selected-options="teamsSelectedItems"
177
+ @select="teamsOnSelect"
178
+ />
179
+ </div>
153
180
  </div>
154
181
  </div>
155
182
 
156
183
  <div
157
- flex flex-col
158
- w-full
184
+ flex flex-col w-full
159
185
  >
160
- <div>
161
- Filter Team
186
+ <div
187
+ flex
188
+ >
189
+ Battle of Giants
162
190
  </div>
163
191
 
164
192
  <div
165
- w-full
166
- mt-2
193
+ ml-4 mt-2
167
194
  >
168
- <MultiSelect
169
- :options="teamsOptions"
170
- :selected-options="teamsSelectedItems"
171
- @select="teamsOnSelect"
172
- />
195
+ <div
196
+ grid grid-cols-8
197
+ items-center
198
+ >
199
+ <span
200
+ text-sm font-medium
201
+ text-gray-900 dark:text-gray-300
202
+ >
203
+ Enable
204
+ </span>
205
+
206
+ <TheCheckbox
207
+ v-model="rankOptions.battleOfGiants.enable"
208
+ />
209
+
210
+ <span
211
+ text-sm font-medium
212
+ text-gray-900 dark:text-gray-300
213
+ >
214
+ Equal Teams
215
+ </span>
216
+
217
+ <TheCheckbox
218
+ v-model="rankOptions.battleOfGiants.equalTeams"
219
+ />
220
+
221
+ <!-- <span
222
+ text-sm font-medium
223
+ text-gray-900 dark:text-gray-300
224
+ >
225
+ Persist
226
+ </span>
227
+
228
+ <TheCheckbox
229
+ v-model="rankOptions.battleOfGiants.persist"
230
+ /> -->
231
+
232
+ <span
233
+ text-sm font-medium
234
+ text-gray-900 dark:text-gray-300
235
+ >
236
+ TopX
237
+ </span>
238
+
239
+ <TheInput
240
+ v-model="rankOptions.battleOfGiants.topX"
241
+ text-align="left"
242
+ text-type="number"
243
+ />
244
+ </div>
173
245
  </div>
246
+
247
+ <GiantsOptions
248
+ :rank="rank"
249
+ :org-options="orgOptions"
250
+ :teams-options="teamsOptions"
251
+ :giants="rankOptions.battleOfGiants.blueTeam"
252
+ />
253
+
254
+ <GiantsOptions
255
+ :rank="rank"
256
+ :org-options="orgOptions"
257
+ :teams-options="teamsOptions"
258
+ :giants="rankOptions.battleOfGiants.redTeam"
259
+ />
174
260
  </div>
175
261
 
176
262
  <div
@@ -179,30 +265,35 @@ function onConfirm() {
179
265
  >
180
266
  <div
181
267
  flex
182
- mb-2
183
268
  >
184
269
  Feature
185
270
  </div>
186
271
 
187
272
  <div
188
- flex flex-row
273
+ ml-4 mt-2
189
274
  >
190
- <label class="relative inline-flex items-center cursor-pointer">
191
- <input
192
- v-model="enableAnimatedSubmissions"
193
- type="checkbox"
194
- class="sr-only peer"
275
+ <div
276
+ flex flex-row
277
+ >
278
+ <TheCheckbox
279
+ v-model="rankOptions.enableAnimatedSubmissions"
195
280
  >
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>
281
+ <span
282
+ ml-3
283
+ text-sm font-medium
284
+ text-gray-900 dark:text-gray-300
285
+ >
286
+ Submission Queue
287
+ </span>
288
+ </TheCheckbox>
289
+ </div>
199
290
  </div>
200
291
  </div>
201
292
 
202
293
  <div
203
- mt-2
204
294
  w-full
205
- flex items-center space-x-4
295
+ flex flex-row-reverse items-center
296
+ gap-x-4
206
297
  >
207
298
  <button
208
299
  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']