@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.
- package/.eslintrc.json +6 -0
- package/LICENSE +1 -1
- package/README.md +13 -30
- package/cypress.config.ts +14 -0
- package/dist/_headers +3 -0
- package/dist/about.html +11 -0
- package/dist/assets/_...all_-27c7ae93.css +1 -0
- package/dist/assets/_...all_-d56798b5.js +3 -0
- package/dist/assets/_name_-8eab6137.js +1 -0
- package/dist/assets/about-a8cb8700.js +11 -0
- package/dist/assets/app-37f77a84.js +65 -0
- package/dist/assets/board-layout-deaedfc1.js +1 -0
- package/dist/assets/en-caedd340.js +1 -0
- package/dist/assets/home-49c336e5.js +1 -0
- package/dist/assets/index-a270cacd.css +5 -0
- package/dist/assets/index-layout-d65c80ea.js +1 -0
- package/dist/assets/test-0a3d6f7a.js +1 -0
- package/dist/assets/user-108782a1.js +1 -0
- package/dist/assets/virtual_pwa-register-1c1b9161.js +1 -0
- package/dist/assets/workbox-window.prod.es5-a7b12eab.js +2 -0
- package/dist/assets/zh-CN-86269804.js +1 -0
- package/dist/favicon-dark.svg +1 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +1 -171
- package/dist/manifest.webmanifest +1 -0
- package/dist/pwa-192x192.png +0 -0
- package/dist/pwa-512x512.png +0 -0
- package/dist/robots.txt +4 -0
- package/dist/safari-pinned-tab.svg +41 -0
- package/dist/sitemap.xml +1 -0
- package/dist/ssr-manifest.json +486 -0
- package/dist/sw.js +1 -0
- package/dist/test.html +1 -0
- package/dist/workbox-b8d87ee1.js +1 -0
- package/package.json +94 -50
- package/public/_headers +3 -0
- package/public/favicon-dark.svg +1 -0
- package/public/favicon.svg +1 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/safari-pinned-tab.svg +41 -0
- package/src/App.vue +33 -0
- package/src/auto-imports.d.ts +909 -0
- package/src/components/ContestIndex.vue +227 -0
- package/src/components/Footer.vue +94 -0
- package/src/components/GoBack.vue +22 -0
- package/src/components/NavBar.vue +152 -0
- package/src/components/SearchInput.vue +50 -0
- package/src/components/TheCounter.vue +19 -0
- package/src/components/TheInput.vue +20 -0
- package/src/components/board/Balloon.vue +5 -0
- package/src/components/board/Board.vue +396 -0
- package/src/components/board/BottomStatistics.vue +159 -0
- package/src/components/board/ContestStateBadge.vue +41 -0
- package/src/components/board/Export.vue +75 -0
- package/src/components/board/Modal.vue +107 -0
- package/src/components/board/ModalMenu.vue +64 -0
- package/src/components/board/OptionsModal.vue +179 -0
- package/src/components/board/Progress.less +442 -0
- package/src/components/board/Progress.vue +229 -0
- package/src/components/board/SecondLevelMenu.vue +190 -0
- package/src/components/board/Standings.less +1162 -0
- package/src/components/board/Standings.vue +154 -0
- package/src/components/board/StandingsAnnotate.vue +38 -0
- package/src/components/board/Statistics.vue +77 -0
- package/src/components/board/SubmissionsTable.vue +312 -0
- package/src/components/board/SubmissionsTableModal.vue +52 -0
- package/src/components/board/TeamAwards.vue +93 -0
- package/src/components/board/TeamInfoModal.vue +128 -0
- package/src/components/board/TeamProblemBlock.vue +100 -0
- package/src/components/board/TeamUI.vue +161 -0
- package/src/components/board/Utility.vue +28 -0
- package/src/components/icon/GirlIcon.vue +80 -0
- package/src/components/icon/RightArrowIcon.vue +26 -0
- package/src/components/icon/StarIcon.vue +19 -0
- package/src/components/table/TablePagination.vue +108 -0
- package/src/components.d.ts +44 -0
- package/src/composables/dark.ts +4 -0
- package/src/composables/pagination.ts +81 -0
- package/src/composables/statistics.ts +280 -0
- package/src/composables/useLocalStorage.ts +29 -0
- package/src/composables/useQueryBoardData.ts +43 -0
- package/src/composables/utils.ts +11 -0
- package/src/layouts/board-layout.vue +14 -0
- package/src/layouts/default.vue +10 -0
- package/src/layouts/home.vue +12 -0
- package/src/layouts/index-layout.vue +15 -0
- package/src/main.ts +36 -0
- package/src/modules/README.md +11 -0
- package/src/modules/i18n.ts +52 -0
- package/src/modules/nprogress.ts +15 -0
- package/src/modules/pinia.ts +18 -0
- package/src/modules/pwa.ts +15 -0
- package/src/modules/toast.ts +10 -0
- package/src/pages/[...all].vue +34 -0
- package/src/pages/about.md +21 -0
- package/src/pages/hi/[name].vue +50 -0
- package/src/pages/index.vue +129 -0
- package/src/pages/test.vue +57 -0
- package/src/shims.d.ts +16 -0
- package/src/stores/user.ts +36 -0
- package/src/styles/color.css +51 -0
- package/src/styles/main.css +30 -0
- package/src/styles/markdown.css +28 -0
- package/src/styles/submission-status.css +123 -0
- package/src/types.ts +3 -0
- package/tsconfig.json +39 -0
- package/uno.config.ts +65 -0
- package/vite.config.ts +176 -0
- package/dist/favicon.ico +0 -0
- package/dist/umi.00ae29f6.js +0 -1
- package/dist/umi.bd64c248.css +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Modules
|
|
2
|
+
|
|
3
|
+
A custom user module system. Place a `.ts` file with the following template, it will be installed automatically.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { type UserModule } from "~/types";
|
|
7
|
+
|
|
8
|
+
export const install: UserModule = ({ app, router, isClient }) => {
|
|
9
|
+
// do something
|
|
10
|
+
};
|
|
11
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Locale } from "vue-i18n";
|
|
2
|
+
import { createI18n } from "vue-i18n";
|
|
3
|
+
import { type UserModule } from "~/types";
|
|
4
|
+
|
|
5
|
+
// Import i18n resources
|
|
6
|
+
// https://vitejs.dev/guide/features.html#glob-import
|
|
7
|
+
const i18n = createI18n({
|
|
8
|
+
legacy: false,
|
|
9
|
+
locale: "",
|
|
10
|
+
messages: {},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const localesMap = Object.fromEntries(
|
|
14
|
+
Object.entries(import.meta.glob("../../locales/*.yml"))
|
|
15
|
+
.map(([path, loadLocale]) => [path.match(/([\w-]*)\.yml$/)?.[1], loadLocale]),
|
|
16
|
+
) as Record<Locale, () => Promise<{ default: Record<string, string> }>>;
|
|
17
|
+
|
|
18
|
+
export const availableLocales = Object.keys(localesMap);
|
|
19
|
+
|
|
20
|
+
const loadedLanguages: string[] = [];
|
|
21
|
+
|
|
22
|
+
function setI18nLanguage(lang: Locale) {
|
|
23
|
+
i18n.global.locale.value = lang as any;
|
|
24
|
+
if (typeof document !== "undefined") {
|
|
25
|
+
document.querySelector("html")?.setAttribute("lang", lang);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return lang;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function loadLanguageAsync(lang: string): Promise<Locale> {
|
|
32
|
+
// If the same language
|
|
33
|
+
if (i18n.global.locale.value === lang) {
|
|
34
|
+
return setI18nLanguage(lang);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If the language was already loaded
|
|
38
|
+
if (loadedLanguages.includes(lang)) {
|
|
39
|
+
return setI18nLanguage(lang);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If the language hasn't been loaded yet
|
|
43
|
+
const messages = await localesMap[lang]();
|
|
44
|
+
i18n.global.setLocaleMessage(lang, messages.default);
|
|
45
|
+
loadedLanguages.push(lang);
|
|
46
|
+
return setI18nLanguage(lang);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const install: UserModule = async ({ app }) => {
|
|
50
|
+
app.use(i18n);
|
|
51
|
+
await loadLanguageAsync("en");
|
|
52
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import NProgress from "nprogress";
|
|
2
|
+
import { type UserModule } from "~/types";
|
|
3
|
+
|
|
4
|
+
export const install: UserModule = ({ isClient, router }) => {
|
|
5
|
+
if (isClient) {
|
|
6
|
+
router.beforeEach((to, from) => {
|
|
7
|
+
if (to.path !== from.path) {
|
|
8
|
+
NProgress.start();
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
router.afterEach(() => {
|
|
12
|
+
NProgress.done();
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createPinia } from "pinia";
|
|
2
|
+
import { type UserModule } from "~/types";
|
|
3
|
+
|
|
4
|
+
// Setup Pinia
|
|
5
|
+
// https://pinia.vuejs.org/
|
|
6
|
+
export const install: UserModule = ({ isClient, initialState, app }) => {
|
|
7
|
+
const pinia = createPinia();
|
|
8
|
+
app.use(pinia);
|
|
9
|
+
|
|
10
|
+
// Refer to
|
|
11
|
+
// https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization
|
|
12
|
+
// for other serialization strategies.
|
|
13
|
+
if (isClient) {
|
|
14
|
+
pinia.state.value = (initialState.pinia) || {};
|
|
15
|
+
} else {
|
|
16
|
+
initialState.pinia = pinia.state.value;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type UserModule } from "~/types";
|
|
2
|
+
|
|
3
|
+
// https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
|
|
4
|
+
export const install: UserModule = ({ isClient, router }) => {
|
|
5
|
+
if (!isClient) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
router.isReady()
|
|
10
|
+
.then(async () => {
|
|
11
|
+
const { registerSW } = await import("virtual:pwa-register");
|
|
12
|
+
registerSW({ immediate: true });
|
|
13
|
+
})
|
|
14
|
+
.catch(() => {});
|
|
15
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import ToastPlugin from "vue-toast-notification";
|
|
2
|
+
import "vue-toast-notification/dist/theme-sugar.css";
|
|
3
|
+
|
|
4
|
+
import { type UserModule } from "~/types";
|
|
5
|
+
|
|
6
|
+
export const install: UserModule = ({ app }) => {
|
|
7
|
+
app.use(ToastPlugin, {
|
|
8
|
+
position: "bottom-right",
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { t } = useI18n();
|
|
3
|
+
const route = useRoute();
|
|
4
|
+
|
|
5
|
+
const contestTypes = [
|
|
6
|
+
"camp",
|
|
7
|
+
"icpc",
|
|
8
|
+
"ccpc",
|
|
9
|
+
"provincial-contest",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const isNotFound = !contestTypes.some(c => route.fullPath.startsWith(`/${c}`));
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div
|
|
17
|
+
v-if="isNotFound"
|
|
18
|
+
class="flex flex-col items-center"
|
|
19
|
+
>
|
|
20
|
+
<div text-4xl>
|
|
21
|
+
<div i-carbon-warning />
|
|
22
|
+
</div>
|
|
23
|
+
{{ t('not-found') }}
|
|
24
|
+
<GoBack />
|
|
25
|
+
</div>
|
|
26
|
+
<div v-else>
|
|
27
|
+
<Board />
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<route lang="yaml">
|
|
32
|
+
meta:
|
|
33
|
+
layout: board-layout
|
|
34
|
+
</route>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: About
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div class="text-center">
|
|
6
|
+
<!-- You can use Vue components inside markdown -->
|
|
7
|
+
<div i-carbon-dicom-overlay class="text-4xl -mb-6 m-auto" />
|
|
8
|
+
<h3>About</h3>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
[Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **UnoCSS** for styling and icons.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// syntax highlighting example
|
|
15
|
+
function vitesse() {
|
|
16
|
+
const foo = "bar";
|
|
17
|
+
console.log(foo);
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Check out the [GitHub repo](https://github.com/antfu/vitesse) for more details.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{ name: string }>();
|
|
3
|
+
const router = useRouter();
|
|
4
|
+
const user = useUserStore();
|
|
5
|
+
const { t } = useI18n();
|
|
6
|
+
|
|
7
|
+
watchEffect(() => {
|
|
8
|
+
user.setNewName(props.name);
|
|
9
|
+
});
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div
|
|
14
|
+
w-full
|
|
15
|
+
flex flex-col justify-center items-center
|
|
16
|
+
>
|
|
17
|
+
<div text-4xl>
|
|
18
|
+
<div i-carbon-pedestrian inline-block />
|
|
19
|
+
</div>
|
|
20
|
+
<p>
|
|
21
|
+
{{ t('intro.hi', { name: props.name }) }}
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p text-sm opacity-75>
|
|
25
|
+
<em>{{ t('intro.dynamic-route') }}</em>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<template v-if="user.otherNames.length">
|
|
29
|
+
<p mt-4 text-sm>
|
|
30
|
+
<span opacity-75>{{ t('intro.aka') }}:</span>
|
|
31
|
+
<ul>
|
|
32
|
+
<li v-for="otherName in user.otherNames" :key="otherName">
|
|
33
|
+
<RouterLink :to="`/hi/${otherName}`" replace>
|
|
34
|
+
{{ otherName }}
|
|
35
|
+
</RouterLink>
|
|
36
|
+
</li>
|
|
37
|
+
</ul>
|
|
38
|
+
</p>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<div>
|
|
42
|
+
<button
|
|
43
|
+
m="3 t6" text-sm btn
|
|
44
|
+
@click="router.back()"
|
|
45
|
+
>
|
|
46
|
+
{{ t('button.back') }}
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useFetch } from "@vueuse/core";
|
|
3
|
+
import { useRouteQuery } from "@vueuse/router";
|
|
4
|
+
import { createContestIndexList } from "@xcpcio/core";
|
|
5
|
+
import type { ContestIndexList } from "@xcpcio/core";
|
|
6
|
+
import SearchInput from "~/components/SearchInput.vue";
|
|
7
|
+
|
|
8
|
+
const { t } = useI18n();
|
|
9
|
+
|
|
10
|
+
const now = ref(new Date());
|
|
11
|
+
const url = ref(`${window.DATA_HOST}index/contest_list.json?t=${now.value.getTime()}`);
|
|
12
|
+
const refetch = ref(false);
|
|
13
|
+
|
|
14
|
+
const s = useRouteQuery<string | null>("s", "", { transform: String });
|
|
15
|
+
const searchText = ref<string | null>(s.value);
|
|
16
|
+
const searchInputRef = ref<InstanceType<typeof SearchInput> | null>(null);
|
|
17
|
+
|
|
18
|
+
const contestIndexAllList = ref([] as ContestIndexList);
|
|
19
|
+
const contestIndexList = ref([] as ContestIndexList);
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
error,
|
|
23
|
+
isFetching,
|
|
24
|
+
isFinished,
|
|
25
|
+
} = useFetch(url, {
|
|
26
|
+
refetch,
|
|
27
|
+
afterFetch: (ctx) => {
|
|
28
|
+
contestIndexAllList.value = createContestIndexList(JSON.parse(ctx.data));
|
|
29
|
+
contestIndexList.value = contestIndexAllList.value.map(c => c);
|
|
30
|
+
|
|
31
|
+
return ctx;
|
|
32
|
+
},
|
|
33
|
+
}).get();
|
|
34
|
+
|
|
35
|
+
watch(searchText, () => {
|
|
36
|
+
contestIndexList.value = contestIndexAllList.value.filter((c) => {
|
|
37
|
+
if (searchText.value?.length === 0) {
|
|
38
|
+
searchText.value = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (searchText.value === null) {
|
|
42
|
+
s.value = undefined as unknown as string;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
s.value = searchText.value;
|
|
47
|
+
|
|
48
|
+
if (c.contest.name.includes(searchText.value)
|
|
49
|
+
|| c.contest.name.toLowerCase().includes(searchText.value.toLowerCase())) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function clearSearch() {
|
|
58
|
+
searchText.value = null;
|
|
59
|
+
searchInputRef.value?.focus();
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<div
|
|
65
|
+
class="sm:w-[1024px] lg:w-screen"
|
|
66
|
+
lg:of-x-hidden
|
|
67
|
+
flex flex-col justify-center items-center
|
|
68
|
+
>
|
|
69
|
+
<div>
|
|
70
|
+
<div
|
|
71
|
+
v-if="isFetching"
|
|
72
|
+
class="sm:w-[1000px] lg:w-screen"
|
|
73
|
+
flex justify-center
|
|
74
|
+
>
|
|
75
|
+
{{ t("common.loading") }}...
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div v-if="error">
|
|
79
|
+
{{ error }}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div
|
|
83
|
+
v-if="isFinished"
|
|
84
|
+
class="sm:w-[1000px] lg:w-screen min-h-120"
|
|
85
|
+
flex flex-col items-center
|
|
86
|
+
>
|
|
87
|
+
<div w-240>
|
|
88
|
+
<SearchInput
|
|
89
|
+
ref="searchInputRef"
|
|
90
|
+
v-model="searchText"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div
|
|
95
|
+
v-if="contestIndexList.length"
|
|
96
|
+
mt-4
|
|
97
|
+
>
|
|
98
|
+
<template
|
|
99
|
+
v-for="item in contestIndexList"
|
|
100
|
+
:key="item.boardLink"
|
|
101
|
+
>
|
|
102
|
+
<ContestIndex
|
|
103
|
+
:data="item"
|
|
104
|
+
/>
|
|
105
|
+
</template>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div v-else p10>
|
|
109
|
+
<div op40 italic mb5>
|
|
110
|
+
No result found
|
|
111
|
+
</div>
|
|
112
|
+
<div row justify-center>
|
|
113
|
+
<button
|
|
114
|
+
btn
|
|
115
|
+
@click="clearSearch()"
|
|
116
|
+
>
|
|
117
|
+
Clear search
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<route lang="yaml">
|
|
127
|
+
meta:
|
|
128
|
+
layout: index-layout
|
|
129
|
+
</route>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({
|
|
3
|
+
name: "IndexPage",
|
|
4
|
+
});
|
|
5
|
+
const user = useUserStore();
|
|
6
|
+
const name = ref(user.savedName);
|
|
7
|
+
|
|
8
|
+
const router = useRouter();
|
|
9
|
+
function go() {
|
|
10
|
+
if (name.value) {
|
|
11
|
+
router.push(`/hi/${encodeURIComponent(name.value)}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { t } = useI18n();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<div>
|
|
20
|
+
<div text-4xl>
|
|
21
|
+
<div i-carbon-campsite />
|
|
22
|
+
</div>
|
|
23
|
+
<p>
|
|
24
|
+
<a rel="noreferrer" href="https://github.com/antfu/vitesse" target="_blank">
|
|
25
|
+
Vitesse
|
|
26
|
+
</a>
|
|
27
|
+
</p>
|
|
28
|
+
<p>
|
|
29
|
+
<em text-sm opacity-75>{{ t('intro.desc') }}</em>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<div py-4 />
|
|
33
|
+
|
|
34
|
+
<TheInput
|
|
35
|
+
v-model="name"
|
|
36
|
+
:placeholder="t('intro.whats-your-name')"
|
|
37
|
+
autocomplete="false"
|
|
38
|
+
@keydown.enter="go"
|
|
39
|
+
/>
|
|
40
|
+
<label class="hidden" for="input">{{ t('intro.whats-your-name') }}</label>
|
|
41
|
+
|
|
42
|
+
<div>
|
|
43
|
+
<button
|
|
44
|
+
m-3 text-sm btn
|
|
45
|
+
:disabled="!name"
|
|
46
|
+
@click="go"
|
|
47
|
+
>
|
|
48
|
+
{{ t('button.go') }}
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<route lang="yaml">
|
|
55
|
+
meta:
|
|
56
|
+
layout: home
|
|
57
|
+
</route>
|
package/src/shims.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare interface Window {
|
|
2
|
+
DATA_HOST: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// with vite-plugin-vue-markdown, markdown files can be treated as Vue components
|
|
6
|
+
declare module '*.md' {
|
|
7
|
+
import { type DefineComponent } from 'vue'
|
|
8
|
+
const component: DefineComponent<{}, {}, any>
|
|
9
|
+
export default component
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module '*.vue' {
|
|
13
|
+
import { type DefineComponent } from 'vue'
|
|
14
|
+
const component: DefineComponent<{}, {}, any>
|
|
15
|
+
export default component
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { acceptHMRUpdate, defineStore } from "pinia";
|
|
2
|
+
|
|
3
|
+
export const useUserStore = defineStore("user", () => {
|
|
4
|
+
/**
|
|
5
|
+
* Current name of the user.
|
|
6
|
+
*/
|
|
7
|
+
const savedName = ref("");
|
|
8
|
+
const previousNames = ref(new Set<string>());
|
|
9
|
+
|
|
10
|
+
const usedNames = computed(() => Array.from(previousNames.value));
|
|
11
|
+
const otherNames = computed(() => usedNames.value.filter(name => name !== savedName.value));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Changes the current name of the user and saves the one that was used
|
|
15
|
+
* before.
|
|
16
|
+
*
|
|
17
|
+
* @param name - new name to set
|
|
18
|
+
*/
|
|
19
|
+
function setNewName(name: string) {
|
|
20
|
+
if (savedName.value) {
|
|
21
|
+
previousNames.value.add(savedName.value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
savedName.value = name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
setNewName,
|
|
29
|
+
otherNames,
|
|
30
|
+
savedName,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (import.meta.hot) {
|
|
35
|
+
import.meta.hot.accept(acceptHMRUpdate(useUserStore as any, import.meta.hot));
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
html {
|
|
2
|
+
--global-background: #fff;
|
|
3
|
+
|
|
4
|
+
--theme-status-pending: #6cf;
|
|
5
|
+
--theme-status-configuration-error: #e28989;
|
|
6
|
+
--theme-status-system-error: grey;
|
|
7
|
+
--theme-status-compilation-error: #1679dc;
|
|
8
|
+
--theme-status-canceled: #676fc1;
|
|
9
|
+
--theme-status-file-error: darkorchid;
|
|
10
|
+
--theme-status-runtime-error: darkorchid;
|
|
11
|
+
--theme-status-time-limit-exceeded: sandybrown;
|
|
12
|
+
--theme-status-memory-limit-exceeded: sandybrown;
|
|
13
|
+
--theme-status-output-limit-exceeded: sandybrown;
|
|
14
|
+
--theme-status-partially-correct: #01bab2;
|
|
15
|
+
--theme-status-wrong-answer: #ff4545;
|
|
16
|
+
--theme-status-accepted: #37da58;
|
|
17
|
+
--theme-status-judgement-failed: #FF5722;
|
|
18
|
+
--theme-status-waiting: grey;
|
|
19
|
+
--theme-status-preparing: #de4d9e;
|
|
20
|
+
--theme-status-compiling: #00b5ad;
|
|
21
|
+
--theme-status-running: #6cf;
|
|
22
|
+
--theme-status-skipped: #78909C;
|
|
23
|
+
--theme-status-unknown: #000;
|
|
24
|
+
--theme-status-undefined: var(--theme-status-unknown);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
html.dark {
|
|
28
|
+
--global-background: #323443;
|
|
29
|
+
|
|
30
|
+
--theme-status-pending: #6cf;
|
|
31
|
+
--theme-status-configuration-error: #e28989;
|
|
32
|
+
--theme-status-system-error: #a5a5a5;
|
|
33
|
+
--theme-status-compilation-error: #3387da;
|
|
34
|
+
--theme-status-canceled: #6770d0;
|
|
35
|
+
--theme-status-file-error: #bc35ff;
|
|
36
|
+
--theme-status-runtime-error: #bc35ff;
|
|
37
|
+
--theme-status-time-limit-exceeded: #ff9840;
|
|
38
|
+
--theme-status-memory-limit-exceeded: #ff9840;
|
|
39
|
+
--theme-status-output-limit-exceeded: #ff9840;
|
|
40
|
+
--theme-status-partially-correct: #01bab2;
|
|
41
|
+
--theme-status-wrong-answer: #ff4545;
|
|
42
|
+
--theme-status-accepted: #37da58;
|
|
43
|
+
--theme-status-judgement-failed: #FF5722;
|
|
44
|
+
--theme-status-waiting: #a5a5a5;
|
|
45
|
+
--theme-status-preparing: #de4d9e;
|
|
46
|
+
--theme-status-compiling: #00b5ad;
|
|
47
|
+
--theme-status-running: #6cf;
|
|
48
|
+
--theme-status-skipped: #78909C;
|
|
49
|
+
--theme-status-unknown: #fff;
|
|
50
|
+
--theme-status-undefined: var(--theme-status-unknown);
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@import './markdown.css';
|
|
2
|
+
@import './color.css';
|
|
3
|
+
|
|
4
|
+
html,
|
|
5
|
+
body,
|
|
6
|
+
#app {
|
|
7
|
+
height: 100%;
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
html.dark {
|
|
13
|
+
background: var(--global-background);
|
|
14
|
+
color-scheme: dark;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#nprogress {
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#nprogress .bar {
|
|
22
|
+
background: rgb(13,148,136);
|
|
23
|
+
opacity: 0.75;
|
|
24
|
+
position: fixed;
|
|
25
|
+
z-index: 1031;
|
|
26
|
+
top: 0;
|
|
27
|
+
left: 0;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 2px;
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.prose pre:not(.shiki) {
|
|
2
|
+
padding: 0;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.prose .shiki {
|
|
6
|
+
font-family: 'DM Mono', monospace;
|
|
7
|
+
font-size: 1.2em;
|
|
8
|
+
line-height: 1.4;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.prose img {
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.shiki-light {
|
|
16
|
+
background: #f8f8f8 !important;
|
|
17
|
+
}
|
|
18
|
+
.shiki-dark {
|
|
19
|
+
background: #0e0e0e !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
html.dark .shiki-light {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
html:not(.dark) .shiki-dark {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|