This commit is contained in:
Crow Crowcrow 2018-12-03 20:27:07 +01:00
commit c1d231c201
Signed by: Crow
GPG key ID: 45A8E203AF859FD8
25 changed files with 18708 additions and 0 deletions

5
src/App.vue Normal file
View file

@ -0,0 +1,5 @@
<template>
<div id="app">
<router-view/>
</div>
</template>

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

20
src/domain/index.ts Normal file
View file

@ -0,0 +1,20 @@
export const GetAnimeList = async (userID: number): Promise<UserAnime[]> => {
const res = await fetch(`https://api.meikan.moe/v1/users/${userID}/anime`)
const anime: UserAnime[] = await res.json()
return anime
}
export const FindUsers = async (name: string): Promise<User[]> => {
const res = await fetch('https://api.meikan.moe/v1/users', {
method: 'POST',
mode: 'cors',
body: JSON.stringify({
name,
}),
})
const users: User[] = await res.json()
return users
}

29
src/layouts/Base.vue Normal file
View file

@ -0,0 +1,29 @@
<template>
<div>
<md-toolbar class="md-primary" md-elevation="0">
<h3 class="md-title">Meikan</h3>
<div class="md-toolbar-section-end">
<h3 v-if="currentUser" class="md-subheading">
{{ currentUser.name }}
</h3>
</div>
</md-toolbar>
<md-tabs class="md-primary" md-sync-route>
<md-tab id="tab-user" md-label="Users" to="/"></md-tab>
<md-tab v-if="currentUser" id="tab-anime" md-label="Anime" to="/anime"></md-tab>
</md-tabs>
<router-view/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { namespace } from 'vuex-class'
const user = namespace('user')
@Component({})
export default class Base extends Vue {
@user.Getter('currentUser') private currentUser!: User | null
}
</script>

18
src/main.ts Normal file
View file

@ -0,0 +1,18 @@
import Vue from 'vue'
import VueMaterial from 'vue-material'
import 'vue-material/dist/theme/default-dark.css'
import 'vue-material/dist/vue-material.min.css'
Vue.use(VueMaterial)
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app')

32
src/router.ts Normal file
View file

@ -0,0 +1,32 @@
import Store from '@/store'
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Base from './layouts/Base.vue'
import UserAnime from './views/UserAnime.vue'
import UserSelect from './views/UserSelect.vue'
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
component: Base,
children: [
{
path: '',
name: 'userSelect',
component: UserSelect,
},
{
path: 'anime',
name: 'userAnime',
component: UserAnime,
},
],
},
],
})

27
src/store/index.ts Normal file
View file

@ -0,0 +1,27 @@
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import { getStoreBuilder } from 'vuex-typex'
import './module/user'
import { UserState } from './module/user'
import './module/userAnime'
import { UserAnimeState } from './module/userAnime'
Vue.use(Vuex)
export interface RootState {
user: UserState
userAnime: UserAnimeState
}
const vuexLocal = new VuexPersistence({
storage: window.localStorage,
})
const store = getStoreBuilder<RootState>().vuexStore({
plugins: [vuexLocal.plugin],
})
export default store

57
src/store/module/user.ts Normal file
View file

@ -0,0 +1,57 @@
import { FindUsers } from '@/domain'
import { RootState } from '@/store'
import { BareActionContext, getStoreBuilder } from 'vuex-typex'
export interface UserState {
currentUser: User | null
foundUsers: User[]
}
const initialState: UserState = {
currentUser: null,
foundUsers: [],
}
const builder = getStoreBuilder<RootState>().module('user', initialState)
// Getters
const getterUserList = builder.read(function foundUsers(state: UserState) {
return state.foundUsers
})
const getterCurrentUser = builder.read(function currentUser(state: UserState) {
return state.currentUser
})
// Mutations
function setUserList(state: UserState, foundUsers: User[]) {
state.foundUsers = foundUsers
}
function setCurrentUser(state: UserState, currentUser: User) {
state.currentUser = currentUser
}
// Action
async function findUsers(context: BareActionContext<UserState, RootState>, name: string) {
const list = await FindUsers(name)
user.setUserList(list || [])
}
function resetFoundUsers(context: BareActionContext<UserState, RootState>) {
user.setUserList([])
}
export const user = {
get foundUsers(): User[] {
return getterUserList()
},
getCurrentUser(): User | null {
return getterCurrentUser()
},
setCurrentUser: builder.commit(setCurrentUser),
setUserList: builder.commit(setUserList),
resetFoundUsers: builder.dispatch(resetFoundUsers),
findUsers: builder.dispatch(findUsers),
}

View file

@ -0,0 +1,43 @@
import { GetAnimeList } from '@/domain'
import { RootState } from '@/store'
import { BareActionContext, getStoreBuilder } from 'vuex-typex'
export interface UserAnimeState {
anime: UserAnime[]
}
const initialState: UserAnimeState = {
anime: [],
}
const builder = getStoreBuilder<RootState>().module('userAnime', initialState)
// Getters
const getterUserAnime = builder.read(function anime(state: UserAnimeState) {
return state.anime
})
// Mutations
function setAnime(state: UserAnimeState, animeList: UserAnime[]) {
state.anime = animeList
}
// Action
async function fetchUserAnime(context: BareActionContext<UserAnimeState, RootState>, user: User) {
const list = await GetAnimeList(user.id)
userAnime.setAnime(list)
}
function resetUserAnime(context: BareActionContext<UserAnimeState, RootState>) {
userAnime.setAnime([])
}
export const userAnime = {
get anime(): UserAnime[] {
return getterUserAnime()
},
setAnime: builder.commit(setAnime),
fetchUserAnime: builder.dispatch(fetchUserAnime),
resetUserAnime: builder.dispatch(resetUserAnime),
}

14
src/types/shims-vue.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
interface User {
id: number
name: string
}
interface UserAnime {
id: number
title: string
}

54
src/views/UserAnime.vue Normal file
View file

@ -0,0 +1,54 @@
<template>
<md-table v-model="anime">
<md-table-toolbar>
<h1 class="md-title md-toolbar-section-start">{{ currentUser.name }}'s anime</h1>
<md-button
class="md-icon-button md-primary"
@click="refresh"
>
<md-icon>refresh</md-icon>
</md-button>
</md-table-toolbar>
<md-table-empty-state>
<md-progress-spinner class="md-accent" md-mode="indeterminate"></md-progress-spinner>
</md-table-empty-state>
<md-table-row slot="md-table-row" slot-scope="{ item }">
<md-table-cell md-label="#" md-numeric width="100">{{ item.anime.id }}</md-table-cell>
<md-table-cell md-label="Title" md-sort-by="title">{{ item.anime.title }}</md-table-cell>
</md-table-row>
</md-table>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { namespace } from 'vuex-class'
const user = namespace('user')
const userAnime = namespace('userAnime')
@Component({})
export default class Home extends Vue {
@user.Getter
private currentUser!: User
@userAnime.Action
private resetUserAnime!: () => void
@userAnime.Action
private fetchUserAnime!: (user: User) => Promise<UserAnime[]>
@userAnime.Getter
private anime!: UserAnime[]
private created() {
if (this.anime.length === 0) {
this.fetchUserAnime(this.currentUser)
}
}
private refresh() {
this.resetUserAnime()
this.fetchUserAnime(this.currentUser)
}
}
</script>

63
src/views/UserSelect.vue Normal file
View file

@ -0,0 +1,63 @@
<template>
<div>
<md-table v-model="foundUsers" @md-selected="onSelect">
<md-table-toolbar>
<div class="md-toolbar-section-start">
<h1 class="md-title">Users</h1>
</div>
<md-field md-clearable class="md-toolbar-section-end">
<md-input placeholder="Search users" @input="onInput" />
</md-field>
</md-table-toolbar>
<md-table-row slot="md-table-row" slot-scope="{ item }" md-selectable="single">
<md-table-cell md-label="#" md-numeric width="100">{{ item.id }}</md-table-cell>
<md-table-cell md-label="Title" md-sort-by="title">{{ item.name }}</md-table-cell>
</md-table-row>
</md-table>
</div>
</template>
<script lang="ts">
import _ from 'lodash'
import { Component, Vue } from 'vue-property-decorator'
import { namespace } from 'vuex-class'
const userStore = namespace('user')
const userAnimeStore = namespace('userAnime')
@Component({})
export default class Home extends Vue {
@userStore.Mutation('setCurrentUser')
private setCurrentUser!: (user: User) => void
@userStore.Action('findUsers')
private findUsers!: (name: string) => Promise<User[]>
@userStore.Action('resetFoundUsers')
private resetFoundUsers!: () => void
@userStore.Getter('foundUsers')
private foundUsers!: User[]
@userAnimeStore.Action
private resetUserAnime!: () => void
private onInput = _.debounce((name: string) => {
if (name === '') {
this.resetFoundUsers()
return
}
this.findUsers(name)
}, 200)
private onSelect(user: User) {
this.setCurrentUser(user)
this.resetUserAnime()
this.$router.push({ name: 'userAnime' })
}
}
</script>