feat: new frontend

This commit is contained in:
Rekryt
2025-07-02 20:40:13 +03:00
parent bda887db3c
commit 7d7c82514f
111 changed files with 5223 additions and 52 deletions

View File

@@ -0,0 +1,206 @@
<template>
<v-app-bar id="core-app-bar" absolute color="transparent" flat height="88">
<v-toolbar-title class="font-weight-light align-self-center text-no-wrap">
<v-btn v-show="!responsive" icon @click.stop="onClick">
<v-icon>mdi-view-list</v-icon>
</v-btn>
{{ title }}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="flex-fill">
<v-row align="center" justify="end" class="mx-0 px-4">
<v-col class="px-0 d-block d-md-none" cols="auto">
<github-button
class="d-block mt-1"
href="https://github.com/rekryt/iplist"
:data-color-scheme="theme.name.value"
data-icon="octicon-star"
data-size="small"
aria-label="Star rekryt/iplist on GitHub"
>
Star
</github-button>
</v-col>
<v-col class="d-none d-md-block" cols="auto">
<github-button
class="d-block mt-1"
href="https://github.com/rekryt/iplist"
:data-color-scheme="theme.name.value"
data-icon="octicon-star"
data-size="large"
data-show-count="true"
aria-label="Star rekryt/iplist on GitHub"
>
Star
</github-button>
</v-col>
<v-col>
<v-select
v-model="locale"
:items="localesList"
item-title="label"
item-value="code"
:label="t('language')"
variant="outlined"
density="compact"
class="w-32"
hide-details
@update:model-value="setLocale"
>
<template #selection="{ item }">
<div class="d-flex align-center">
<v-avatar size="20" class="mr-2">
<img :src="item.raw.flag" alt="flag" />
</v-avatar>
<span class="d-none d-md-block">{{ item.raw.label }}</span>
</div>
</template>
<template #item="{ item, props }">
<v-list-item v-bind="props">
<template #prepend>
<v-avatar size="20">
<img :src="item.raw.flag" alt="flag" />
</v-avatar>
</template>
</v-list-item>
</template>
</v-select>
</v-col>
<v-btn height="48" icon @click="toggleTheme">
<v-icon color="tertiary">mdi-theme-light-dark</v-icon>
</v-btn>
</v-row>
</v-toolbar-items>
</v-app-bar>
</template>
<i18n lang="json">
{
"en": {
"language": "Language",
"index___en": "Portals",
"about___en": "About",
"groups___en": "Groups"
},
"ru": {
"language": "Язык",
"index___ru": "Порталы",
"about___ru": "О проекте",
"groups___ru": "Группы"
},
"cn": {
"language": "语言",
"index___cn": "通过门户",
"about___cn": "关于项目",
"groups___cn": "分组"
}
}
</i18n>
<script>
// Utilities
import { mapActions } from 'pinia';
import { useAppStore } from '~/stores/app';
import { useDisplay, useTheme } from 'vuetify';
import GithubButton from 'vue-github-button';
export default {
components: {
GithubButton,
},
setup() {
const { t, locale, locales } = useI18n({
useScope: 'local',
});
const theme = useTheme();
const cookieTheme = useCookieTheme();
const localePath = useLocalePath();
const router = useRouter();
const localsData = [
{ code: 'en', language: 'English', flag: 'https://flagcdn.com/w40/us.png' },
{ code: 'ru', language: 'Русский', flag: 'https://flagcdn.com/w40/ru.png' },
{ code: 'cn', language: '简体中文', flag: 'https://flagcdn.com/w40/cn.png' },
];
const localesList = computed(() =>
locales.value.map((l) => ({
value: l,
code: l.code,
label: localsData.find((d) => d.code === l.code).language,
flag: localsData.find((d) => d.code === l.code).flag,
}))
);
const localeData = computed(() => localsData.find((l) => l.code === locale.value));
const setLocale = (value) => {
router.push(localePath(router.currentRoute.value.path, value));
};
const route = useRoute();
const title = computed(() => {
return t(route.name);
});
const toggleTheme = () => {
const themeValue = theme.global.current.value.dark ? 'light' : 'dark';
theme.global.name.value = themeValue;
cookieTheme.value = themeValue;
};
onMounted(async () => {
toggleTheme();
await nextTick();
toggleTheme();
});
return {
theme,
t,
locale,
localesList,
setLocale,
localeData,
title,
toggleTheme,
};
},
data: () => ({
notifications: [
'Mike, John responded to your email',
'You have 5 new tasks',
"You're now a friend with Andrew",
'Another Notification',
'Another One',
],
}),
computed: {
responsive() {
const display = useDisplay();
return display.lgAndUp.value;
},
},
created() {
this.setDrawer(this.responsive);
},
methods: {
...mapActions(useAppStore, ['setDrawer', 'toggleDrawer']),
onClick() {
this.setDrawer(!useAppStore().drawer);
},
},
};
</script>
<style>
/* Fix coming in v2.0.8 */
#core-app-bar {
width: auto;
}
#core-app-bar a {
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<v-navigation-drawer id="app-drawer" v-model="inputValue" width="260" elevation="5" floating rail>
<v-row justify="center" class="text-center">
<v-col class="pt-8">
<v-avatar color="white">
<v-img src="/icon.png" height="34" contain />
</v-avatar>
</v-col>
</v-row>
<v-divider class="mx-3 mb-3" />
<v-list density="compact" nav>
<v-list-item v-for="(link, i) in links" :key="i" :to="link.to" active-class="primary white--text">
<template #prepend>
<v-icon>{{ link.icon }}</v-icon>
</template>
<v-list-item-title>{{ link.text }}</v-list-item-title>
</v-list-item>
</v-list>
<template #append>
<v-list density="compact" nav>
<v-list-item tag="a" href="https://github.com/rekryt/iplist" target="_blank">
<template #prepend>
<v-icon>mdi-github</v-icon>
</template>
<v-list-item-title class="font-weight-light">GitHub</v-list-item-title>
</v-list-item>
</v-list>
</template>
</v-navigation-drawer>
</template>
<i18n lang="json">
{
"en": {
"main": "Portals",
"groups": "Groups",
"about": "About"
},
"ru": {
"main": "Порталы",
"groups": "Группы",
"about": "О проекте"
},
"cn": {
"main": "通过门户",
"groups": "分组",
"about": "关于项目"
}
}
</i18n>
<script>
import { mapActions, mapState } from 'pinia';
import { useAppStore } from '~/stores/app';
export default {
setup() {
const { t } = useI18n({
useScope: 'local',
});
const localePath = useLocalePath();
const links = computed(() => [
{
to: localePath('/'),
icon: 'mdi-web-sync',
text: t('main'),
},
{
to: localePath('/about'),
icon: 'mdi-information-outline',
text: t('about'),
},
]);
return { t, links };
},
computed: {
...mapState(useAppStore, ['color']),
inputValue: {
get() {
return useAppStore().drawer;
},
set(val) {
this.setDrawer(val);
},
},
},
methods: {
...mapActions(useAppStore, ['setDrawer', 'toggleDrawer']),
},
};
</script>

View File

@@ -0,0 +1,56 @@
<script lang="ts" setup>
const { t } = useI18n({
useScope: 'local',
});
const links = computed(() => [{ name: t('issue'), Link: 'https://github.com/rekryt/iplist/issues' }]);
</script>
<template>
<v-footer id="core-footer">
<div class="footer-items">
<a v-for="link in links" :key="link.name" :href="link.Link" class="footer-link">
{{ link.name }}
</a>
</div>
<v-spacer />
<span class="font-weight-light copyright">
&copy; {{ new Date().getFullYear() }}
<a href="https://vk.com/rekryt" target="_blank">Rekryt</a>
<v-icon style="margin-top: -3px" color="tertiary" size="17">mdi-star</v-icon>
for a better web
<br />
</span>
</v-footer>
</template>
<i18n lang="json">
{
"en": {
"issue": "Issue"
},
"ru": {
"issue": "Задать вопрос"
},
"cn": {
"issue": "提交问题"
}
}
</i18n>
<style lang="scss">
#core-footer {
flex: 0 0 auto;
z-index: 0;
margin-top: auto;
height: 100px;
}
.copyright {
font-size: 14px;
}
.footer-items {
display: flex;
flex-wrap: wrap;
font-size: 14px;
}
.footer-link {
margin: 5px 5px 5px 0;
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<v-main class="grey lighten-3">
<v-fade-transition mode="out-in">
<div id="core-view">
<slot></slot>
</div>
</v-fade-transition>
</v-main>
</template>
<script>
export default {
name: 'CoreView',
};
</script>
<style lang="scss">
body {
min-width: 320px;
}
#core-view {
display: flex;
flex-direction: column;
height: 100%;
}
</style>