init
Este commit está contenido en:
commit
c1d231c201
Se han modificado 25 ficheros con 18708 adiciones y 0 borrados
3
.browserslistrc
Archivo normal
3
.browserslistrc
Archivo normal
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
21
.gitignore
vendido
Archivo normal
21
.gitignore
vendido
Archivo normal
|
@ -0,0 +1,21 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
29
README.md
Archivo normal
29
README.md
Archivo normal
|
@ -0,0 +1,29 @@
|
|||
# meikan
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
babel.config.js
Archivo normal
5
babel.config.js
Archivo normal
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
10590
package-lock.json
generado
Archivo normal
10590
package-lock.json
generado
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar diff
46
package.json
Archivo normal
46
package.json
Archivo normal
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "meikan",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.119",
|
||||
"lodash": "^4.17.11",
|
||||
"material-design-icons-iconfont": "^4.0.3",
|
||||
"vue": "^2.5.17",
|
||||
"vue-class-component": "^6.0.0",
|
||||
"vue-material": "^1.0.0-beta-10.2",
|
||||
"vue-property-decorator": "^7.0.0",
|
||||
"vue-router": "3.0.1",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-persist": "^2.0.0",
|
||||
"vuex-typex": "^3.0.1",
|
||||
"vuex-xhr-state": "^0.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vue-material": "git+https://github.com/calebsander/vue-material-types.git",
|
||||
"@vue/cli-plugin-babel": "^3.2.0",
|
||||
"@vue/cli-plugin-typescript": "^3.2.0",
|
||||
"@vue/cli-service": "^3.2.0",
|
||||
"lint-staged": "^7.2.2",
|
||||
"typescript": "^3.0.0",
|
||||
"vue-template-compiler": "^2.5.17"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
],
|
||||
"*.vue": [
|
||||
"vue-cli-service lint",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
5
postcss.config.js
Archivo normal
5
postcss.config.js
Archivo normal
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
BIN
public/favicon.ico
Archivo normal
BIN
public/favicon.ico
Archivo normal
Archivo binario no mostrado.
Después Anchura: | Altura: | Tamaño: 1,1 KiB |
20
public/index.html
Archivo normal
20
public/index.html
Archivo normal
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
|
||||
<title>meikan</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but meikan doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
5
src/App.vue
Archivo normal
5
src/App.vue
Archivo normal
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
BIN
src/assets/logo.png
Archivo normal
BIN
src/assets/logo.png
Archivo normal
Archivo binario no mostrado.
Después Anchura: | Altura: | Tamaño: 6,7 KiB |
20
src/domain/index.ts
Archivo normal
20
src/domain/index.ts
Archivo normal
|
@ -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
Archivo normal
29
src/layouts/Base.vue
Archivo normal
|
@ -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
Archivo normal
18
src/main.ts
Archivo normal
|
@ -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
Archivo normal
32
src/router.ts
Archivo normal
|
@ -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
Archivo normal
27
src/store/index.ts
Archivo normal
|
@ -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
Archivo normal
57
src/store/module/user.ts
Archivo normal
|
@ -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),
|
||||
}
|
43
src/store/module/userAnime.ts
Archivo normal
43
src/store/module/userAnime.ts
Archivo normal
|
@ -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
vendido
Archivo normal
14
src/types/shims-vue.d.ts
vendido
Archivo normal
|
@ -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
Archivo normal
54
src/views/UserAnime.vue
Archivo normal
|
@ -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
Archivo normal
63
src/views/UserSelect.vue
Archivo normal
|
@ -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>
|
40
tsconfig.json
Archivo normal
40
tsconfig.json
Archivo normal
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
20
tslint.json
Archivo normal
20
tslint.json
Archivo normal
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"defaultSeverity": "warning",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"node_modules/**"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"quotemark": [true, "single"],
|
||||
"indent": [true, "tabs", 4],
|
||||
"interface-name": false,
|
||||
"ordered-imports": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"no-consecutive-blank-lines": true,
|
||||
"semicolon": [true, "never"]
|
||||
}
|
||||
}
|
3
vue.config.js
Archivo normal
3
vue.config.js
Archivo normal
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
lintOnSave: false
|
||||
}
|
7564
yarn.lock
Archivo normal
7564
yarn.lock
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar diff
Cargando…
Añadir tabla
Añadir enlace
Referenciar en una nueva incidencia