@xcpcio/board-app 0.6.4 → 0.13.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 (112) hide show
  1. package/.eslintrc.json +6 -0
  2. package/LICENSE +1 -1
  3. package/README.md +13 -30
  4. package/cypress.config.ts +14 -0
  5. package/dist/_headers +3 -0
  6. package/dist/about.html +11 -0
  7. package/dist/assets/_...all_-27c7ae93.css +1 -0
  8. package/dist/assets/_...all_-d56798b5.js +3 -0
  9. package/dist/assets/_name_-8eab6137.js +1 -0
  10. package/dist/assets/about-a8cb8700.js +11 -0
  11. package/dist/assets/app-37f77a84.js +65 -0
  12. package/dist/assets/board-layout-deaedfc1.js +1 -0
  13. package/dist/assets/en-caedd340.js +1 -0
  14. package/dist/assets/home-49c336e5.js +1 -0
  15. package/dist/assets/index-a270cacd.css +5 -0
  16. package/dist/assets/index-layout-d65c80ea.js +1 -0
  17. package/dist/assets/test-0a3d6f7a.js +1 -0
  18. package/dist/assets/user-108782a1.js +1 -0
  19. package/dist/assets/virtual_pwa-register-1c1b9161.js +1 -0
  20. package/dist/assets/workbox-window.prod.es5-a7b12eab.js +2 -0
  21. package/dist/assets/zh-CN-86269804.js +1 -0
  22. package/dist/favicon-dark.svg +1 -0
  23. package/dist/favicon.svg +1 -0
  24. package/dist/index.html +1 -171
  25. package/dist/manifest.webmanifest +1 -0
  26. package/dist/pwa-192x192.png +0 -0
  27. package/dist/pwa-512x512.png +0 -0
  28. package/dist/robots.txt +4 -0
  29. package/dist/safari-pinned-tab.svg +41 -0
  30. package/dist/sitemap.xml +1 -0
  31. package/dist/ssr-manifest.json +486 -0
  32. package/dist/sw.js +1 -0
  33. package/dist/test.html +1 -0
  34. package/dist/workbox-b8d87ee1.js +1 -0
  35. package/package.json +94 -50
  36. package/public/_headers +3 -0
  37. package/public/favicon-dark.svg +1 -0
  38. package/public/favicon.svg +1 -0
  39. package/public/pwa-192x192.png +0 -0
  40. package/public/pwa-512x512.png +0 -0
  41. package/public/safari-pinned-tab.svg +41 -0
  42. package/src/App.vue +33 -0
  43. package/src/auto-imports.d.ts +909 -0
  44. package/src/components/ContestIndex.vue +227 -0
  45. package/src/components/Footer.vue +94 -0
  46. package/src/components/GoBack.vue +22 -0
  47. package/src/components/NavBar.vue +152 -0
  48. package/src/components/SearchInput.vue +50 -0
  49. package/src/components/TheCounter.vue +19 -0
  50. package/src/components/TheInput.vue +20 -0
  51. package/src/components/board/Balloon.vue +5 -0
  52. package/src/components/board/Board.vue +396 -0
  53. package/src/components/board/BottomStatistics.vue +159 -0
  54. package/src/components/board/ContestStateBadge.vue +41 -0
  55. package/src/components/board/Export.vue +75 -0
  56. package/src/components/board/Modal.vue +107 -0
  57. package/src/components/board/ModalMenu.vue +64 -0
  58. package/src/components/board/OptionsModal.vue +179 -0
  59. package/src/components/board/Progress.less +442 -0
  60. package/src/components/board/Progress.vue +229 -0
  61. package/src/components/board/SecondLevelMenu.vue +190 -0
  62. package/src/components/board/Standings.less +1162 -0
  63. package/src/components/board/Standings.vue +154 -0
  64. package/src/components/board/StandingsAnnotate.vue +38 -0
  65. package/src/components/board/Statistics.vue +77 -0
  66. package/src/components/board/SubmissionsTable.vue +312 -0
  67. package/src/components/board/SubmissionsTableModal.vue +52 -0
  68. package/src/components/board/TeamAwards.vue +93 -0
  69. package/src/components/board/TeamInfoModal.vue +128 -0
  70. package/src/components/board/TeamProblemBlock.vue +100 -0
  71. package/src/components/board/TeamUI.vue +161 -0
  72. package/src/components/board/Utility.vue +28 -0
  73. package/src/components/icon/GirlIcon.vue +80 -0
  74. package/src/components/icon/RightArrowIcon.vue +26 -0
  75. package/src/components/icon/StarIcon.vue +19 -0
  76. package/src/components/table/TablePagination.vue +108 -0
  77. package/src/components.d.ts +44 -0
  78. package/src/composables/dark.ts +4 -0
  79. package/src/composables/pagination.ts +81 -0
  80. package/src/composables/statistics.ts +280 -0
  81. package/src/composables/useLocalStorage.ts +29 -0
  82. package/src/composables/useQueryBoardData.ts +43 -0
  83. package/src/composables/utils.ts +11 -0
  84. package/src/layouts/board-layout.vue +14 -0
  85. package/src/layouts/default.vue +10 -0
  86. package/src/layouts/home.vue +12 -0
  87. package/src/layouts/index-layout.vue +15 -0
  88. package/src/main.ts +36 -0
  89. package/src/modules/README.md +11 -0
  90. package/src/modules/i18n.ts +52 -0
  91. package/src/modules/nprogress.ts +15 -0
  92. package/src/modules/pinia.ts +18 -0
  93. package/src/modules/pwa.ts +15 -0
  94. package/src/modules/toast.ts +10 -0
  95. package/src/pages/[...all].vue +34 -0
  96. package/src/pages/about.md +21 -0
  97. package/src/pages/hi/[name].vue +50 -0
  98. package/src/pages/index.vue +129 -0
  99. package/src/pages/test.vue +57 -0
  100. package/src/shims.d.ts +16 -0
  101. package/src/stores/user.ts +36 -0
  102. package/src/styles/color.css +51 -0
  103. package/src/styles/main.css +30 -0
  104. package/src/styles/markdown.css +28 -0
  105. package/src/styles/submission-status.css +123 -0
  106. package/src/types.ts +3 -0
  107. package/tsconfig.json +39 -0
  108. package/uno.config.ts +65 -0
  109. package/vite.config.ts +176 -0
  110. package/dist/favicon.ico +0 -0
  111. package/dist/umi.00ae29f6.js +0 -1
  112. package/dist/umi.bd64c248.css +0 -1
@@ -0,0 +1,227 @@
1
+ <script setup lang="ts">
2
+ import type { ContestIndex } from "@xcpcio/core";
3
+ import type { Image } from "@xcpcio/types";
4
+
5
+ import { useElementVisibility } from "@vueuse/core";
6
+
7
+ const props = defineProps<{
8
+ data: ContestIndex
9
+ }>();
10
+
11
+ const { t } = useI18n();
12
+
13
+ const contest = computed(() => props.data.contest);
14
+ const now = ref(new Date());
15
+
16
+ const el = ref(null);
17
+ const isVisible = useElementVisibility(el);
18
+
19
+ function getImageSource(image: Image): string {
20
+ if (image?.url) {
21
+ return image.url;
22
+ }
23
+
24
+ if (image?.base64) {
25
+ return `data:image/${image.type};base64,${image.base64}`;
26
+ }
27
+
28
+ return "";
29
+ }
30
+
31
+ const setNowIntervalId = setInterval(() => {
32
+ now.value = new Date();
33
+ }, 1000);
34
+
35
+ onUnmounted(() => {
36
+ clearInterval(setNowIntervalId);
37
+ });
38
+ </script>
39
+
40
+ <template>
41
+ <div ref="el">
42
+ <div h-32>
43
+ <div
44
+ v-if="isVisible"
45
+ >
46
+ <div
47
+ w-240
48
+ flex flex-col justify-center
49
+ font-serif pb-2
50
+ border="b-2 gray-200 dark:gray-700"
51
+ >
52
+ <div
53
+ w-full flex
54
+ >
55
+ <div
56
+ v-if="contest.logo !== undefined"
57
+ class="logo"
58
+ >
59
+ <img
60
+ class="h-10 w-10"
61
+ :src="getImageSource(contest.logo)"
62
+ alt="logo"
63
+ >
64
+ </div>
65
+
66
+ <VTooltip
67
+ w-inherit
68
+ >
69
+ <div
70
+ class="title"
71
+ overflow-hidden
72
+ text-2xl truncate
73
+ >
74
+ {{ contest.name }}
75
+ </div>
76
+
77
+ <template #popper>
78
+ {{ contest.name }}
79
+ </template>
80
+ </VTooltip>
81
+ </div>
82
+
83
+ <div flex items-end>
84
+ <div float-left font-mono text-base>
85
+ {{ t("index.start") }}:
86
+ {{ contest.startTime.format("YYYY-MM-DD HH:mm:ss") }}<sup class="pl-0.5">{{ contest.startTime.format("z") }}</sup>
87
+ <br>
88
+ {{ t("index.duration") }}:
89
+ {{ contest.getContestDuration() }}
90
+ </div>
91
+
92
+ <div flex-1>
93
+ <div flex items-center justify-center>
94
+ <div
95
+ font-bold font-mono
96
+ class="w-[68%]"
97
+ >
98
+ <div>
99
+ <ContestStateBadge
100
+ :state="contest.getContestState(now)"
101
+ :pending-time="contest.getContestPendingTime(now)"
102
+ />
103
+ </div>
104
+ <Progress
105
+ :width="contest.getContestProgressRatio(now)"
106
+ :state="contest.getContestState(now)"
107
+ />
108
+ </div>
109
+ </div>
110
+ </div>
111
+ <div float-right>
112
+ <RouterLink
113
+ class="go MuiIconButton-root"
114
+ :to="props.data.boardLink"
115
+ >
116
+ <span class="MuiIconButton-label">
117
+ <RightArrowIcon />
118
+ </span>
119
+ </RouterLink>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </template>
127
+
128
+ <style scoped>
129
+ .logo {
130
+ float: left;
131
+ text-align: left;
132
+ font-size: 16px;
133
+ padding-top: 12px;
134
+ padding-right: 8px;
135
+ }
136
+
137
+ .title {
138
+ --scroll-bar: 0;
139
+ font-variant: tabular-nums;
140
+ line-height: 1.5715;
141
+ box-sizing: border-box;
142
+ position: relative;
143
+ text-align: left;
144
+ font-weight: 400;
145
+ padding-left: 0px;
146
+ padding-right: 16px;
147
+ padding-top: 12px;
148
+ padding-bottom: 12px;
149
+ }
150
+
151
+ .MuiIconButton-root {
152
+ -webkit-font-smoothing: antialiased;
153
+ font-weight: 400;
154
+ line-height: 1.43;
155
+ letter-spacing: 0.01071em;
156
+ box-sizing: inherit;
157
+ border: 0;
158
+ cursor: pointer;
159
+ margin: 0;
160
+ display: inline-flex;
161
+ outline: 0;
162
+ position: relative;
163
+ align-items: center;
164
+ user-select: none;
165
+ vertical-align: middle;
166
+ justify-content: center;
167
+ text-decoration: none;
168
+ background-color: transparent;
169
+ -webkit-tap-highlight-color: transparent;
170
+ flex: 0 0 auto;
171
+ color: rgba(0, 0, 0, 0.54);
172
+ padding: 12px;
173
+ overflow: visible;
174
+ font-size: 1.5rem;
175
+ text-align: center;
176
+ transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
177
+ border-radius: 50%;
178
+ }
179
+
180
+ .MuiIconButton-label {
181
+ -webkit-font-smoothing: antialiased;
182
+ font-family: "Roboto", "Helvetica", "Arial", sans-serif;
183
+ font-weight: 400;
184
+ line-height: 1.43;
185
+ letter-spacing: 0.01071em;
186
+ cursor: pointer;
187
+ user-select: none;
188
+ -webkit-tap-highlight-color: transparent;
189
+ color: rgba(0, 0, 0, 0.54);
190
+ font-size: 1.5rem;
191
+ text-align: center;
192
+ box-sizing: inherit;
193
+ width: 100%;
194
+ display: flex;
195
+ align-items: inherit;
196
+ justify-content: inherit;
197
+ }
198
+
199
+ .go {
200
+ text-decoration: none;
201
+ color: #121314;
202
+ position: relative;
203
+ }
204
+
205
+ .go:after {
206
+ content: "";
207
+ position: absolute;
208
+ z-index: -1;
209
+ top: 60%;
210
+ left: -0.1em;
211
+ right: -0.1em;
212
+ bottom: 0;
213
+ transition: top 200ms cubic-bezier(0, 0.8, 0.13, 1);
214
+ background-color: #91d5ff;
215
+ }
216
+
217
+ .go:hover:after {
218
+ top: 0%;
219
+ }
220
+
221
+ .loading {
222
+ height: calc(100vh);
223
+ display: flex;
224
+ justify-content: center;
225
+ align-items: center;
226
+ }
227
+ </style>
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import { GITHUB_SHA, GITHUB_URL, VERSION } from "@xcpcio/types";
3
+ </script>
4
+
5
+ <template>
6
+ <footer
7
+ mt-8
8
+ py-4 md:py-8 lg:py-10
9
+ w-full
10
+ flex justify-center items-center
11
+ >
12
+ <div
13
+ text-center
14
+ w-full
15
+ >
16
+ <RouterLink
17
+ flex items-center justify-center
18
+ text-2xl font-semibold text-gray-900 dark:text-white
19
+ to="/"
20
+ >
21
+ <div
22
+ i-ion-balloon-sharp
23
+ mr-4 h-8
24
+ />
25
+ Get more balloons
26
+ </RouterLink>
27
+
28
+ <p
29
+ my-6 text-gray-500 dark:text-gray-400
30
+ w-full
31
+ flex justify-center
32
+ >
33
+ Open-source project of ICPC/CCPC Standings.
34
+ </p>
35
+
36
+ <ul
37
+ mb-6
38
+ w-full
39
+ flex flex-wrap items-center justify-center
40
+ text-gray-900 dark:text-white
41
+ >
42
+ <li>
43
+ <RouterLink
44
+ to="/"
45
+ mr-4 md:mr-6 hover:underline
46
+ >
47
+ Home
48
+ </RouterLink>
49
+ </li>
50
+
51
+ <li>
52
+ <VTooltip
53
+ w-inherit
54
+ >
55
+ <a
56
+ :href="GITHUB_URL"
57
+ class="mr-4 md:mr-6 hover:underline"
58
+ rel="noreferrer"
59
+ target="_blank"
60
+ title="GitHub"
61
+ >
62
+ GitHub
63
+ </a>
64
+
65
+ <template #popper>
66
+ <div
67
+ flex justify-center
68
+ flex-col
69
+ >
70
+ <div>Tag: {{ VERSION }}</div>
71
+ <div>Sha: {{ GITHUB_SHA }}</div>
72
+ </div>
73
+ </template>
74
+ </VTooltip>
75
+ </li>
76
+
77
+ <li>
78
+ <a
79
+ href="mailto:hi@dup4.com"
80
+ mr-4 md:mr-6 hover:underline
81
+ >Contact</a>
82
+ </li>
83
+ </ul>
84
+
85
+ <span
86
+ text-sm text-gray-500 sm:text-center dark:text-gray-400
87
+ w-full
88
+ >© 2020-PRESENT <RouterLink
89
+ to="/"
90
+ hover:underline
91
+ >XCPCIO™</RouterLink>. All Rights Reserved.</span>
92
+ </div>
93
+ </footer>
94
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ const router = useRouter();
3
+ const { t } = useI18n();
4
+ </script>
5
+
6
+ <template>
7
+ <div>
8
+ <div
9
+ class="m-3 text-sm btn"
10
+ @click="router.back()"
11
+ >
12
+ {{ t("button.back") }}
13
+ </div>
14
+
15
+ <RouterLink
16
+ class="m-3 text-sm btn"
17
+ to="/"
18
+ >
19
+ {{ t("button.home") }}
20
+ </RouterLink>
21
+ </div>
22
+ </template>
@@ -0,0 +1,152 @@
1
+ <script setup lang="ts">
2
+ import { GITHUB_URL } from "@xcpcio/types";
3
+ import { availableLocales, loadLanguageAsync } from "~/modules/i18n";
4
+
5
+ const props = defineProps<{
6
+ width?: string,
7
+ }>();
8
+
9
+ const { t, locale } = useI18n();
10
+ const { y: scroll } = useWindowScroll();
11
+
12
+ function toTop() {
13
+ window.scrollTo({
14
+ top: 0,
15
+ behavior: "smooth",
16
+ });
17
+ }
18
+
19
+ async function toggleLocales() {
20
+ const locales = availableLocales;
21
+ const newLocale = locales[(locales.indexOf(locale.value) + 1) % locales.length];
22
+ await loadLanguageAsync(newLocale);
23
+ locale.value = newLocale;
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <header
29
+ class="header z-40"
30
+ :class="[props?.width ?? '']"
31
+ >
32
+ <!-- <RouterLink
33
+ class="absolute h-12 w-12 select-none outline-none xl:fixed m-6"
34
+ text-xl
35
+ to="/"
36
+ focusable="false"
37
+ >
38
+ <div i-ion-balloon-sharp />
39
+ </RouterLink> -->
40
+
41
+ <button
42
+ title="Scroll to top"
43
+ fixed bottom-6 right-6 z-100 h-10 w-10
44
+ rounded-full transition duration-300
45
+ print:hidden
46
+ hover-bg-hex-8883 hover:op100
47
+ :class="scroll > 300 ? 'op30' : 'op0! pointer-events-none'"
48
+ @click="toTop()"
49
+ >
50
+ <div i-ri-arrow-up-line />
51
+ </button>
52
+
53
+ <nav
54
+ class="nav"
55
+ :class="[props?.width ? '' : 'fixed']"
56
+ text-xl
57
+ >
58
+ <div class="spacer" />
59
+ <div
60
+ class="right"
61
+ print:op0
62
+ >
63
+ <RouterLink
64
+ icon-btn
65
+ :title="t('button.home')"
66
+ to="/"
67
+ focusable="false"
68
+ >
69
+ <div i-ion-balloon-sharp />
70
+ </RouterLink>
71
+
72
+ <a
73
+ icon-btn
74
+ :title="t('button.toggle_langs')"
75
+ @click="toggleLocales()"
76
+ >
77
+ <div i-carbon-language />
78
+ </a>
79
+
80
+ <a
81
+ icon-btn
82
+ :title="t('button.toggle_dark')"
83
+ @click="toggleDark()"
84
+ >
85
+ <div i="carbon-sun dark:carbon-moon" />
86
+ </a>
87
+
88
+ <a
89
+ icon-btn
90
+ rel="noreferrer"
91
+ :href="GITHUB_URL"
92
+ target="_blank"
93
+ title="GitHub"
94
+ >
95
+ <div i-carbon-logo-github />
96
+ </a>
97
+ </div>
98
+ </nav>
99
+ </header>
100
+ </template>
101
+
102
+ <style scoped>
103
+ .header h1 {
104
+ margin-bottom: 0;
105
+ }
106
+
107
+ .logo {
108
+ position: absolute;
109
+ top: 1.5rem;
110
+ left: 1.5rem;
111
+ }
112
+
113
+ .nav {
114
+ padding: 1rem;
115
+ width: 100%;
116
+ display: grid;
117
+ grid-template-columns: auto max-content;
118
+ box-sizing: border-box;
119
+ }
120
+
121
+ .nav > * {
122
+ margin: auto;
123
+ }
124
+
125
+ .nav img {
126
+ margin-bottom: 0;
127
+ }
128
+
129
+ .nav a {
130
+ cursor: pointer;
131
+ text-decoration: none;
132
+ color: inherit;
133
+ transition: opacity 0.2s ease;
134
+ opacity: 0.6;
135
+ outline: none;
136
+ }
137
+
138
+ .nav a:hover {
139
+ opacity: 1;
140
+ text-decoration-color: inherit;
141
+ }
142
+
143
+ .nav .right {
144
+ display: grid;
145
+ grid-gap: 1.2rem;
146
+ grid-auto-flow: column;
147
+ }
148
+
149
+ .nav .right > * {
150
+ margin: auto;
151
+ }
152
+ </style>
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ const { modelValue } = defineModels<{
3
+ modelValue: string | null
4
+ }>();
5
+
6
+ const inputEl = $ref<HTMLInputElement>();
7
+ const vFocus = {
8
+ mounted: (el: HTMLElement) => el.focus(),
9
+ };
10
+
11
+ function clear() {
12
+ modelValue.value = null;
13
+ inputEl?.focus();
14
+ }
15
+
16
+ function focus() {
17
+ inputEl?.focus();
18
+ }
19
+
20
+ defineExpose({ focus });
21
+ </script>
22
+
23
+ <template>
24
+ <div relative>
25
+ <input
26
+ ref="inputEl"
27
+ v-model="modelValue"
28
+ v-focus
29
+ type="text"
30
+ v-bind="$attrs"
31
+ p="x-4 y-2"
32
+ text="left"
33
+ bg="transparent"
34
+ autocomplete="off"
35
+ w="full"
36
+ border="~ rounded gray-300 dark:gray-500"
37
+ outline="none active:none"
38
+ aria-label="Search"
39
+ placeholder="Search..."
40
+ >
41
+ <button
42
+ v-if="modelValue?.length"
43
+ absolute flex right-2 w-10 top-2 bottom-2 text-xl op30 hover:op90
44
+ aria-label="Clear search"
45
+ @click="clear()"
46
+ >
47
+ <span i-carbon-close ma block aria-hidden="true" />
48
+ </button>
49
+ </div>
50
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ initial: number
4
+ }>();
5
+
6
+ const { count, inc, dec } = useCounter(props.initial);
7
+ </script>
8
+
9
+ <template>
10
+ <div>
11
+ {{ count }}
12
+ <button class="inc" @click="inc()">
13
+ +
14
+ </button>
15
+ <button class="dec" @click="dec()">
16
+ -
17
+ </button>
18
+ </div>
19
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ const { modelValue } = defineModels<{
3
+ modelValue: string
4
+ }>();
5
+ </script>
6
+
7
+ <template>
8
+ <input
9
+ id="input"
10
+ v-model="modelValue"
11
+ type="text"
12
+ v-bind="$attrs"
13
+ p="x-4 y-2"
14
+ w="250px"
15
+ text="center"
16
+ bg="transparent"
17
+ border="~ rounded gray-200 dark:gray-700"
18
+ outline="none active:none"
19
+ >
20
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div
3
+ flex
4
+ />
5
+ </template>