@xcpcio/board-app 0.46.3 → 0.47.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/404.html +1 -1
- package/dist/assets/Board-8cb099e8.js +1 -0
- package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-669bf7c0.js +1 -0
- package/dist/assets/{TheInput.vue_vue_type_script_setup_true_lang-51ad90e9.js → TheInput.vue_vue_type_script_setup_true_lang-ce96e373.js} +1 -1
- package/dist/assets/_...all_-729d96d7.js +6 -0
- package/dist/assets/_...all_-c60acc8c.css +1 -0
- package/dist/assets/_...all_-f3d30859.js +1 -0
- package/dist/assets/{_name_-c9a7b268.js → _name_-fa12f56d.js} +1 -1
- package/dist/assets/{about-6219edda.js → about-6e5a2f3e.js} +1 -1
- package/dist/assets/board-6c9ef349.js +1 -0
- package/dist/assets/{board-layout-b08cf01a.js → board-layout-1ad47386.js} +1 -1
- package/dist/assets/{headless-4d4f2337.js → headless-cbd40573.js} +1 -1
- package/dist/assets/{home-5f98843d.js → home-eb990606.js} +1 -1
- package/dist/assets/index-241beb5a.css +1 -0
- package/dist/assets/index-42e1a002.js +1 -0
- package/dist/assets/{index-ea1c658f.css → index-53dc9c92.css} +2 -2
- package/dist/assets/index-5e6a6648.js +1 -0
- package/dist/assets/{index-a771ea05.js → index-d70fb8f0.js} +78 -78
- package/dist/assets/index-fd9c106b.js +1 -0
- package/dist/assets/{index-layout-692a69df.js → index-layout-7a27b184.js} +1 -1
- package/dist/assets/pagination-fe331a94.js +3 -0
- package/dist/assets/{query-5fd73b94.js → query-ce1fd4de.js} +1 -1
- package/dist/assets/test-544e70d8.js +1 -0
- package/dist/assets/{user-62a1879b.js → user-449d45cf.js} +1 -1
- package/dist/assets/{virtual_pwa-register-10abc341.js → virtual_pwa-register-c3a257bf.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +3 -3
- package/src/auto-imports.d.ts +9 -0
- package/src/components/NavBar.vue +10 -1
- package/src/components/rating/Rating.vue +82 -0
- package/src/components/rating/RatingBadge.vue +32 -0
- package/src/components/rating/RatingIndexUI.vue +173 -0
- package/src/components/rating/RatingInfoModal.vue +112 -0
- package/src/components/rating/RatingTable.vue +270 -0
- package/src/components/rating/RatingUserUI.vue +74 -0
- package/src/components/rating/rating.less +68 -0
- package/src/components.d.ts +6 -0
- package/src/composables/constant.ts +6 -0
- package/src/composables/rating.ts +217 -0
- package/src/pages/rating/[...all].vue +13 -0
- package/src/pages/rating/index.vue +86 -0
- package/vite.config.ts +8 -5
- package/dist/assets/Board-6ef1c1b9.js +0 -3
- package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-3f783706.js +0 -1
- package/dist/assets/_...all_-5d9480a0.js +0 -1
- package/dist/assets/board-33bc86ab.js +0 -1
- package/dist/assets/index-168068d2.js +0 -1
- package/dist/assets/index-3c0f7a79.js +0 -1
- package/dist/assets/test-4379792f.js +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { MultiSelect } from "vue-search-select";
|
|
3
|
+
|
|
4
|
+
import type { Rating, SelectOptionItem } from "@xcpcio/core";
|
|
5
|
+
|
|
6
|
+
import { Pagination } from "~/composables/pagination";
|
|
7
|
+
|
|
8
|
+
interface FilterOptions {
|
|
9
|
+
organizations: string[];
|
|
10
|
+
members: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
rating: Rating,
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
removeBorder?: boolean;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
const rating = computed(() => props.rating);
|
|
20
|
+
|
|
21
|
+
const filterOptions = ref<FilterOptions>({
|
|
22
|
+
organizations: [],
|
|
23
|
+
members: [],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const orgOptions = computed(() => {
|
|
27
|
+
let res = rating.value.users.map((u) => {
|
|
28
|
+
return u.organization;
|
|
29
|
+
});
|
|
30
|
+
res = Array.from(new Set(res));
|
|
31
|
+
|
|
32
|
+
return res.map((o) => {
|
|
33
|
+
return {
|
|
34
|
+
value: o,
|
|
35
|
+
text: o,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const orgSelectedItems = ref<Array<SelectOptionItem>>([]);
|
|
41
|
+
const orgLastSelectItem = ref({});
|
|
42
|
+
function orgOnSelect(selectedItems: Array<SelectOptionItem>, lastSelectItem: SelectOptionItem) {
|
|
43
|
+
orgSelectedItems.value = selectedItems;
|
|
44
|
+
orgLastSelectItem.value = lastSelectItem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// const memberOptions = computed(() => {
|
|
48
|
+
// const se = new Set();
|
|
49
|
+
// rating.value.users.forEach((u) => {
|
|
50
|
+
// return u.members.forEach((m) => {
|
|
51
|
+
// se.add(m.name);
|
|
52
|
+
// });
|
|
53
|
+
// });
|
|
54
|
+
// const res = Array.from(se);
|
|
55
|
+
|
|
56
|
+
// return res.map((o) => {
|
|
57
|
+
// return {
|
|
58
|
+
// value: o,
|
|
59
|
+
// text: o,
|
|
60
|
+
// };
|
|
61
|
+
// });
|
|
62
|
+
// });
|
|
63
|
+
|
|
64
|
+
// const memberSelectedItems = ref<Array<SelectOptionItem>>([]);
|
|
65
|
+
// const memberLastSelectItem = ref({});
|
|
66
|
+
|
|
67
|
+
// function memberOnSelect(selectedItems: Array<SelectOptionItem>, lastSelectItem: SelectOptionItem) {
|
|
68
|
+
// memberSelectedItems.value = selectedItems;
|
|
69
|
+
// memberLastSelectItem.value = lastSelectItem;
|
|
70
|
+
// }
|
|
71
|
+
|
|
72
|
+
const users = computed(() => {
|
|
73
|
+
const us = rating.value.users;
|
|
74
|
+
return us.filter((u) => {
|
|
75
|
+
const o = filterOptions.value;
|
|
76
|
+
|
|
77
|
+
if (o.organizations.length === 0
|
|
78
|
+
&& o.members.length === 0) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const org of o.organizations) {
|
|
83
|
+
if (org === u.organization) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const member of o.members) {
|
|
89
|
+
for (const m of u.members) {
|
|
90
|
+
if (member === m.name) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
function onFilter() {
|
|
101
|
+
const newFilterOptions: FilterOptions = {
|
|
102
|
+
organizations: [],
|
|
103
|
+
members: [],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
newFilterOptions.organizations = orgSelectedItems.value.map(o => o.value);
|
|
107
|
+
// newFilterOptions.members = memberSelectedItems.value.map(o => o.value);
|
|
108
|
+
|
|
109
|
+
filterOptions.value = newFilterOptions;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const p = ref(new Pagination());
|
|
113
|
+
|
|
114
|
+
p.value.currentPage = 0;
|
|
115
|
+
p.value.pageSize = props.pageSize ?? 20;
|
|
116
|
+
p.value.totalSize = users.value.length;
|
|
117
|
+
|
|
118
|
+
watch(users, () => {
|
|
119
|
+
p.value.totalSize = users.value.length;
|
|
120
|
+
|
|
121
|
+
if (p.value.currentPage >= p.value.totalPage) {
|
|
122
|
+
p.value.currentPage = p.value.totalPage - 1;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const currentUsers = computed(() => {
|
|
127
|
+
return users.value.slice(p.value.currentLeft, p.value.currentRight);
|
|
128
|
+
});
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<template>
|
|
132
|
+
<section>
|
|
133
|
+
<div
|
|
134
|
+
mx-auto w-full
|
|
135
|
+
>
|
|
136
|
+
<div
|
|
137
|
+
relative overflow-hidden
|
|
138
|
+
bg-white dark:bg-gray-800
|
|
139
|
+
:class="{
|
|
140
|
+
'shadow-md': props.removeBorder !== true,
|
|
141
|
+
'sm:rounded-sm': props.removeBorder !== true,
|
|
142
|
+
}"
|
|
143
|
+
>
|
|
144
|
+
<div
|
|
145
|
+
space-y-3
|
|
146
|
+
flex flex-col
|
|
147
|
+
px-4 py-3
|
|
148
|
+
lg:flex-row lg:items-center lg:justify-between
|
|
149
|
+
lg:space-x-4 lg:space-y-0
|
|
150
|
+
>
|
|
151
|
+
<div
|
|
152
|
+
flex flex-shrink-0 flex-col
|
|
153
|
+
md:flex-row md:items-center
|
|
154
|
+
lg:justify-end space-y-3
|
|
155
|
+
md:space-x-3 md:space-y-0
|
|
156
|
+
>
|
|
157
|
+
<div
|
|
158
|
+
w-108
|
|
159
|
+
>
|
|
160
|
+
<MultiSelect
|
|
161
|
+
:options="orgOptions"
|
|
162
|
+
:selected-options="orgSelectedItems"
|
|
163
|
+
placeholder="Organization"
|
|
164
|
+
@select="orgOnSelect"
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- <div
|
|
169
|
+
w-68
|
|
170
|
+
>
|
|
171
|
+
<MultiSelect
|
|
172
|
+
:options="memberOptions"
|
|
173
|
+
:selected-options="memberSelectedItems"
|
|
174
|
+
placeholder="Member"
|
|
175
|
+
@select="memberOnSelect"
|
|
176
|
+
/>
|
|
177
|
+
</div> -->
|
|
178
|
+
|
|
179
|
+
<div>
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
class="flex flex-shrink-0 items-center justify-center border border-gray-200 rounded-lg bg-white px-3 py-2 text-sm font-medium text-gray-900 focus:z-10 dark:border-gray-600 dark:bg-gray-800 hover:bg-gray-100 dark:text-gray-400 hover:text-primary-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
|
|
183
|
+
@click="onFilter"
|
|
184
|
+
>
|
|
185
|
+
<div
|
|
186
|
+
i-material-symbols-search
|
|
187
|
+
mr-1 h-5 w-5
|
|
188
|
+
/>
|
|
189
|
+
Filter
|
|
190
|
+
</button>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div
|
|
196
|
+
class="overflow-x-auto"
|
|
197
|
+
>
|
|
198
|
+
<table
|
|
199
|
+
class="w-full text-left text-sm text-gray-500 dark:text-gray-400"
|
|
200
|
+
font-medium font-mono
|
|
201
|
+
>
|
|
202
|
+
<thead
|
|
203
|
+
text-xs
|
|
204
|
+
bg-gray-50 text-gray-700
|
|
205
|
+
dark:bg-gray-700 dark:text-gray-400
|
|
206
|
+
>
|
|
207
|
+
<tr>
|
|
208
|
+
<th
|
|
209
|
+
scope="col"
|
|
210
|
+
class="px-4 py-3"
|
|
211
|
+
w-68
|
|
212
|
+
>
|
|
213
|
+
Organization
|
|
214
|
+
</th>
|
|
215
|
+
<th
|
|
216
|
+
scope="col"
|
|
217
|
+
class="px-4 py-3"
|
|
218
|
+
w-128
|
|
219
|
+
>
|
|
220
|
+
TeamName
|
|
221
|
+
</th>
|
|
222
|
+
<th
|
|
223
|
+
scope="col"
|
|
224
|
+
class="px-4 py-3"
|
|
225
|
+
w-68
|
|
226
|
+
>
|
|
227
|
+
Members
|
|
228
|
+
</th>
|
|
229
|
+
<th
|
|
230
|
+
scope="col"
|
|
231
|
+
class="px-4 py-3"
|
|
232
|
+
>
|
|
233
|
+
Rating
|
|
234
|
+
</th>
|
|
235
|
+
<th
|
|
236
|
+
scope="col"
|
|
237
|
+
class="px-4 py-3"
|
|
238
|
+
>
|
|
239
|
+
MaxRating
|
|
240
|
+
</th>
|
|
241
|
+
<th
|
|
242
|
+
scope="col"
|
|
243
|
+
class="px-4 py-3"
|
|
244
|
+
>
|
|
245
|
+
MinRating
|
|
246
|
+
</th>
|
|
247
|
+
</tr>
|
|
248
|
+
</thead>
|
|
249
|
+
|
|
250
|
+
<tbody>
|
|
251
|
+
<template
|
|
252
|
+
v-for="(u, ix) in currentUsers"
|
|
253
|
+
:key="u.id"
|
|
254
|
+
>
|
|
255
|
+
<RatingUserUI
|
|
256
|
+
:ix="ix"
|
|
257
|
+
:rating-user="u"
|
|
258
|
+
/>
|
|
259
|
+
</template>
|
|
260
|
+
</tbody>
|
|
261
|
+
</table>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<TablePagination
|
|
265
|
+
v-model:pagination="p"
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</section>
|
|
270
|
+
</template>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RatingUser } from "@xcpcio/core";
|
|
3
|
+
import { RatingUtility } from "@xcpcio/core";
|
|
4
|
+
|
|
5
|
+
import "./rating.less";
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
ix: number,
|
|
9
|
+
ratingUser: RatingUser,
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const u = computed(() => props.ratingUser);
|
|
13
|
+
|
|
14
|
+
const hiddenRatingInfoModal = ref(true);
|
|
15
|
+
function onClickRatingInfoModal() {
|
|
16
|
+
hiddenRatingInfoModal.value = false;
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<tr
|
|
22
|
+
class="border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
23
|
+
>
|
|
24
|
+
<td
|
|
25
|
+
class="whitespace-nowrap px-4 py-2 text-gray-900 dark:text-white"
|
|
26
|
+
>
|
|
27
|
+
{{ u.organization }}
|
|
28
|
+
</td>
|
|
29
|
+
<td>
|
|
30
|
+
<div
|
|
31
|
+
class="whitespace-nowrap px-4 py-2 text-gray-900 dark:text-white"
|
|
32
|
+
cursor-pointer
|
|
33
|
+
:class="RatingUtility.getRatingLevelClass(u.rating)"
|
|
34
|
+
@click="onClickRatingInfoModal"
|
|
35
|
+
>
|
|
36
|
+
{{ u.name }}
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div>
|
|
40
|
+
<RatingInfoModal
|
|
41
|
+
v-if="!hiddenRatingInfoModal"
|
|
42
|
+
v-model:isHidden="hiddenRatingInfoModal"
|
|
43
|
+
:rating-user="u"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</td>
|
|
47
|
+
<td
|
|
48
|
+
class="whitespace-nowrap px-4 py-2 text-gray-900 dark:text-white"
|
|
49
|
+
>
|
|
50
|
+
{{ u.members.map(m => m.name.trim()).join(" ") }}
|
|
51
|
+
</td>
|
|
52
|
+
<td
|
|
53
|
+
class="whitespace-nowrap px-4 py-2 text-gray-900 dark:text-white"
|
|
54
|
+
>
|
|
55
|
+
<RatingBadge
|
|
56
|
+
:rating="u.rating"
|
|
57
|
+
/>
|
|
58
|
+
</td>
|
|
59
|
+
<td
|
|
60
|
+
class="whitespace-nowrap px-4 py-2 text-gray-900 dark:text-white"
|
|
61
|
+
>
|
|
62
|
+
<RatingBadge
|
|
63
|
+
:rating="u.maxRating"
|
|
64
|
+
/>
|
|
65
|
+
</td>
|
|
66
|
+
<td
|
|
67
|
+
class="whitespace-nowrap px-4 py-2 text-gray-900 dark:text-white"
|
|
68
|
+
>
|
|
69
|
+
<RatingBadge
|
|
70
|
+
:rating="u.minRating"
|
|
71
|
+
/>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
.recent-actions {
|
|
2
|
+
margin: 1em;
|
|
3
|
+
font-size: 0.9em;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.rated-user {
|
|
7
|
+
font-family: helvetica neue, Helvetica, Arial, sans-serif;
|
|
8
|
+
text-decoration: none;
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
display: inline-block;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.user-black {
|
|
14
|
+
color: #000;
|
|
15
|
+
font-weight: 400;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.user-legendary {
|
|
19
|
+
color: red;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.user-legendary::first-letter {
|
|
23
|
+
color: #000;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.legendary-user-first-letter {
|
|
27
|
+
color: #000;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.user-red {
|
|
31
|
+
color: red;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.user-fire {
|
|
35
|
+
color: red;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.user-yellow {
|
|
39
|
+
color: #bb0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.user-violet {
|
|
43
|
+
color: #a0a;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.user-orange {
|
|
47
|
+
color: #ff8c00;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.user-blue {
|
|
51
|
+
color: blue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.user-cyan {
|
|
55
|
+
color: #03a89e;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.user-green {
|
|
59
|
+
color: green;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.user-gray {
|
|
63
|
+
color: gray;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.user-admin {
|
|
67
|
+
color: #000;
|
|
68
|
+
}
|
package/src/components.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ declare module 'vue' {
|
|
|
35
35
|
ProblemBlock: typeof import('./components/board/ProblemBlock.vue')['default']
|
|
36
36
|
ProblemInfoModal: typeof import('./components/board/ProblemInfoModal.vue')['default']
|
|
37
37
|
Progress: typeof import('./components/board/Progress.vue')['default']
|
|
38
|
+
Rating: typeof import('./components/rating/Rating.vue')['default']
|
|
39
|
+
RatingBadge: typeof import('./components/rating/RatingBadge.vue')['default']
|
|
40
|
+
RatingIndexUI: typeof import('./components/rating/RatingIndexUI.vue')['default']
|
|
41
|
+
RatingInfoModal: typeof import('./components/rating/RatingInfoModal.vue')['default']
|
|
42
|
+
RatingTable: typeof import('./components/rating/RatingTable.vue')['default']
|
|
43
|
+
RatingUserUI: typeof import('./components/rating/RatingUserUI.vue')['default']
|
|
38
44
|
RightArrowIcon: typeof import('./components/icon/RightArrowIcon.vue')['default']
|
|
39
45
|
RouterLink: typeof import('vue-router')['RouterLink']
|
|
40
46
|
RouterView: typeof import('vue-router')['RouterView']
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const TITLE_SUFFIX = "Board - XCPCIO";
|
|
2
|
+
export const RATING_TITLE_SUFFIX = "Rating - XCPCIO";
|
|
2
3
|
export const BALLOON_TITLE_SUFFIX = "Balloon - XCPCIO";
|
|
3
4
|
export const RESOLVER_TITLE_SUFFIX = "Resolver - XCPCIO";
|
|
4
5
|
export const COUNTDOWN_TITLE_SUFFIX = "Countdown - XCPCIO";
|
|
@@ -19,6 +20,11 @@ export const DATA_HOST = computed(() => {
|
|
|
19
20
|
return window.DATA_HOST;
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
export const RATING_DATA_HOST = computed(() => {
|
|
24
|
+
const dataHost = DATA_HOST.value;
|
|
25
|
+
return dataHost.replace("/data/", "/rating-data/");
|
|
26
|
+
});
|
|
27
|
+
|
|
22
28
|
export const DATA_REGION = computed(() => {
|
|
23
29
|
if (!window) {
|
|
24
30
|
return "";
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { RatingLevelToString, RatingUtility } from "@xcpcio/core";
|
|
2
|
+
import type { RatingUser } from "@xcpcio/core";
|
|
3
|
+
|
|
4
|
+
interface RatingGraphData {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
rank: number;
|
|
8
|
+
diffRating: string;
|
|
9
|
+
ratingTitle: string;
|
|
10
|
+
contestName: string;
|
|
11
|
+
contestTime: string;
|
|
12
|
+
teamName: string;
|
|
13
|
+
link?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getDiffRating(oldRating: number, newRating: number) {
|
|
17
|
+
const diff = newRating - oldRating;
|
|
18
|
+
if (newRating > oldRating) {
|
|
19
|
+
return `+${diff.toString()}`;
|
|
20
|
+
} else {
|
|
21
|
+
return diff.toString();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getRatingGraphOptions(
|
|
26
|
+
ratingUser: RatingUser,
|
|
27
|
+
) {
|
|
28
|
+
const data: RatingGraphData[] = [];
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
let oldRating = 0;
|
|
32
|
+
for (const h of ratingUser.ratingHistories) {
|
|
33
|
+
const d: RatingGraphData = {} as RatingGraphData;
|
|
34
|
+
d.x = h.contestTime.unix() * 1000;
|
|
35
|
+
d.y = h.rating;
|
|
36
|
+
d.diffRating = getDiffRating(oldRating, h.rating);
|
|
37
|
+
d.rank = h.rank;
|
|
38
|
+
d.contestName = h.contestName;
|
|
39
|
+
d.link = `/board/${h.contestID}`;
|
|
40
|
+
d.contestTime = h.contestTime.format("YYYY-MM-DD HH:mm:ss");
|
|
41
|
+
d.teamName = h.teamName;
|
|
42
|
+
d.ratingTitle = RatingLevelToString[RatingUtility.getRatingLevel(h.rating)];
|
|
43
|
+
data.push(d);
|
|
44
|
+
|
|
45
|
+
oldRating = h.rating;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let tickPositions = [
|
|
50
|
+
1200,
|
|
51
|
+
1400,
|
|
52
|
+
1600,
|
|
53
|
+
1900,
|
|
54
|
+
2100,
|
|
55
|
+
2300,
|
|
56
|
+
2400,
|
|
57
|
+
2600,
|
|
58
|
+
3000,
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
const gap = 100;
|
|
63
|
+
let minRating = Math.max(0, ratingUser.minRating - gap);
|
|
64
|
+
let maxRating = ratingUser.maxRating + gap;
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < tickPositions.length; ++i) {
|
|
67
|
+
if (tickPositions[i] > maxRating) {
|
|
68
|
+
maxRating = tickPositions[i];
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (let i = tickPositions.length - 1; i >= 0; --i) {
|
|
73
|
+
if (tickPositions[i] < minRating) {
|
|
74
|
+
minRating = tickPositions[i];
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (minRating < 1200) {
|
|
79
|
+
tickPositions = [minRating, ...tickPositions];
|
|
80
|
+
}
|
|
81
|
+
if (maxRating > 3000) {
|
|
82
|
+
tickPositions.push(maxRating);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
tickPositions = tickPositions.filter(
|
|
86
|
+
x => x >= minRating && x <= maxRating,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const options = {
|
|
91
|
+
chart: {
|
|
92
|
+
type: "line",
|
|
93
|
+
height: "408px",
|
|
94
|
+
},
|
|
95
|
+
title: {
|
|
96
|
+
text: null,
|
|
97
|
+
},
|
|
98
|
+
xAxis: {
|
|
99
|
+
tickWidth: 0,
|
|
100
|
+
gridLineWidth: 0.4,
|
|
101
|
+
minRange: 30 * 24 * 60 * 60 * 1000,
|
|
102
|
+
type: "datetime",
|
|
103
|
+
showFirstLabel: true,
|
|
104
|
+
showLastLabel: true,
|
|
105
|
+
dateTimeLabelFormats: {
|
|
106
|
+
month: "%b %Y",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
yAxis: {
|
|
110
|
+
showEmpty: false,
|
|
111
|
+
showFirstLabel: false,
|
|
112
|
+
showLastLabel: false,
|
|
113
|
+
tickPositions,
|
|
114
|
+
tickWidth: 0,
|
|
115
|
+
gridLineWidth: 0.6,
|
|
116
|
+
labels: {
|
|
117
|
+
enabled: true,
|
|
118
|
+
format: "{value}",
|
|
119
|
+
},
|
|
120
|
+
title: {
|
|
121
|
+
text: null,
|
|
122
|
+
},
|
|
123
|
+
plotBands: [
|
|
124
|
+
{
|
|
125
|
+
from: 0,
|
|
126
|
+
to: 1199,
|
|
127
|
+
color: "#CCCCCC",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
from: 1200,
|
|
131
|
+
to: 1399,
|
|
132
|
+
color: "#98FA87",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
from: 1400,
|
|
136
|
+
to: 1599,
|
|
137
|
+
color: "#90DABD",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
from: 1600,
|
|
141
|
+
to: 1899,
|
|
142
|
+
color: "#A9ACF9",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
from: 1900,
|
|
146
|
+
to: 2099,
|
|
147
|
+
color: "#EF91F9",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
from: 2100,
|
|
151
|
+
to: 2299,
|
|
152
|
+
color: "#F7CD91",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
from: 2300,
|
|
156
|
+
to: 2399,
|
|
157
|
+
color: "#F5BD67",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
from: 2400,
|
|
161
|
+
to: 2599,
|
|
162
|
+
color: "#Ef7F7B",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
from: 2600,
|
|
166
|
+
to: 2999,
|
|
167
|
+
color: "#EB483F",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
from: 3000,
|
|
171
|
+
to: 0x3F3F3F3F,
|
|
172
|
+
color: "#9C1E14",
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
credits: {
|
|
177
|
+
enabled: false,
|
|
178
|
+
},
|
|
179
|
+
plotOptions: {
|
|
180
|
+
line: {
|
|
181
|
+
color: "#ffec3d",
|
|
182
|
+
dataLabels: {
|
|
183
|
+
enabled: false,
|
|
184
|
+
},
|
|
185
|
+
enableMouseTracking: true,
|
|
186
|
+
marker: {
|
|
187
|
+
enabled: true,
|
|
188
|
+
fillColor: "#fffb8f",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
tooltip: {
|
|
193
|
+
enabled: true,
|
|
194
|
+
headerFormat: "",
|
|
195
|
+
useHTML: true,
|
|
196
|
+
shared: true,
|
|
197
|
+
shadow: true,
|
|
198
|
+
followPointer: false,
|
|
199
|
+
followTouchMove: false,
|
|
200
|
+
pointFormat: `= {point.y} ({point.diffRating}), {point.ratingTitle}
|
|
201
|
+
<br/>Rank: {point.rank}
|
|
202
|
+
<br/>TeamName: {point.teamName}
|
|
203
|
+
<br/>ContestTime: {point.contestTime}
|
|
204
|
+
<br/><a href="{point.link}">{point.contestName}</a>
|
|
205
|
+
<br/>`,
|
|
206
|
+
},
|
|
207
|
+
series: [
|
|
208
|
+
{
|
|
209
|
+
showInLegend: false,
|
|
210
|
+
allowPointSelect: true,
|
|
211
|
+
// name: ratingUser.name,
|
|
212
|
+
data,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
return options;
|
|
217
|
+
}
|