mirror of
https://github.com/rekryt/iplist.git
synced 2025-10-12 16:39:35 +03:00
feat: new frontend
This commit is contained in:
206
frontend/components/core/AppBar.vue
Normal file
206
frontend/components/core/AppBar.vue
Normal 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>
|
94
frontend/components/core/Drawer.vue
Normal file
94
frontend/components/core/Drawer.vue
Normal 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>
|
56
frontend/components/core/Footer.vue
Normal file
56
frontend/components/core/Footer.vue
Normal 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">
|
||||
© {{ 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>
|
25
frontend/components/core/View.vue
Normal file
25
frontend/components/core/View.vue
Normal 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>
|
Reference in New Issue
Block a user