@ -1,12 +0,0 @@ | |||
[[source]] | |||
url = "https://pypi.org/simple" | |||
verify_ssl = true | |||
name = "pypi" | |||
[packages] | |||
gtts = ">=2.2.3" | |||
[dev-packages] | |||
[requires] | |||
python_version = "3.9" |
@ -1,84 +0,0 @@ | |||
{ | |||
"_meta": { | |||
"hash": { | |||
"sha256": "ff889e495c55c0c92b329fe9caf8fad69d314e15db1c62b97c7abbc709a211ab" | |||
}, | |||
"pipfile-spec": 6, | |||
"requires": { | |||
"python_version": "3.9" | |||
}, | |||
"sources": [ | |||
{ | |||
"name": "pypi", | |||
"url": "https://pypi.org/simple", | |||
"verify_ssl": true | |||
} | |||
] | |||
}, | |||
"default": { | |||
"certifi": { | |||
"hashes": [ | |||
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", | |||
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" | |||
], | |||
"version": "==2021.10.8" | |||
}, | |||
"charset-normalizer": { | |||
"hashes": [ | |||
"sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", | |||
"sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" | |||
], | |||
"markers": "python_version >= '3'", | |||
"version": "==2.0.7" | |||
}, | |||
"click": { | |||
"hashes": [ | |||
"sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", | |||
"sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" | |||
], | |||
"markers": "python_version >= '3.6'", | |||
"version": "==8.0.3" | |||
}, | |||
"gtts": { | |||
"hashes": [ | |||
"sha256:8376d9bde85ac8cda95ea92dcd12bf31a3efe9e810edcb04437a4151cbec30d6", | |||
"sha256:88cfaa112471867009a0450a696e5bc69cf1671fa0fa683fe17d0650023c863c" | |||
], | |||
"index": "pypi", | |||
"version": "==2.2.3" | |||
}, | |||
"idna": { | |||
"hashes": [ | |||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", | |||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" | |||
], | |||
"markers": "python_version >= '3'", | |||
"version": "==3.3" | |||
}, | |||
"requests": { | |||
"hashes": [ | |||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", | |||
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" | |||
], | |||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", | |||
"version": "==2.26.0" | |||
}, | |||
"six": { | |||
"hashes": [ | |||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", | |||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" | |||
], | |||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | |||
"version": "==1.16.0" | |||
}, | |||
"urllib3": { | |||
"hashes": [ | |||
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", | |||
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" | |||
], | |||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", | |||
"version": "==1.26.7" | |||
} | |||
}, | |||
"develop": {} | |||
} |
@ -1,57 +0,0 @@ | |||
export default { | |||
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode | |||
ssr: false, | |||
// Target: https://go.nuxtjs.dev/config-target | |||
target: 'static', | |||
// Global page headers: https://go.nuxtjs.dev/config-head | |||
head: { | |||
title: 'jaquiz', | |||
meta: [ | |||
{ charset: 'utf-8' }, | |||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, | |||
{ hid: 'description', name: 'description', content: '' }, | |||
{ name: 'format-detection', content: 'telephone=no' } | |||
], | |||
link: [ | |||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } | |||
] | |||
}, | |||
// Global CSS: https://go.nuxtjs.dev/config-css | |||
css: [ | |||
], | |||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins | |||
plugins: [ | |||
], | |||
// Auto import components: https://go.nuxtjs.dev/config-components | |||
components: true, | |||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules | |||
buildModules: [ | |||
// https://go.nuxtjs.dev/typescript | |||
'@nuxt/typescript-build', | |||
], | |||
// Modules: https://go.nuxtjs.dev/config-modules | |||
modules: [ | |||
// https://go.nuxtjs.dev/buefy | |||
'nuxt-buefy', | |||
// https://go.nuxtjs.dev/pwa | |||
'@nuxtjs/pwa', | |||
], | |||
// PWA module configuration: https://go.nuxtjs.dev/pwa | |||
pwa: { | |||
manifest: { | |||
lang: 'en' | |||
} | |||
}, | |||
// Build Configuration: https://go.nuxtjs.dev/config-build | |||
build: { | |||
} | |||
} |
@ -0,0 +1,56 @@ | |||
import { NuxtConfig } from '@nuxt/types' | |||
export default async (): Promise<NuxtConfig> => { | |||
return { | |||
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode | |||
ssr: false, | |||
// Target: https://go.nuxtjs.dev/config-target | |||
target: 'static', | |||
// Global page headers: https://go.nuxtjs.dev/config-head | |||
head: { | |||
title: 'jaquiz', | |||
meta: [ | |||
{ charset: 'utf-8' }, | |||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, | |||
{ hid: 'description', name: 'description', content: '' }, | |||
{ name: 'format-detection', content: 'telephone=no' }, | |||
], | |||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], | |||
}, | |||
// Global CSS: https://go.nuxtjs.dev/config-css | |||
css: [], | |||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins | |||
plugins: [], | |||
// Auto import components: https://go.nuxtjs.dev/config-components | |||
components: true, | |||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules | |||
buildModules: [ | |||
// https://go.nuxtjs.dev/typescript | |||
'@nuxt/typescript-build', | |||
], | |||
// Modules: https://go.nuxtjs.dev/config-modules | |||
modules: [ | |||
// https://go.nuxtjs.dev/buefy | |||
'nuxt-buefy', | |||
// https://go.nuxtjs.dev/pwa | |||
'@nuxtjs/pwa', | |||
], | |||
// PWA module configuration: https://go.nuxtjs.dev/pwa | |||
pwa: { | |||
manifest: { | |||
lang: 'en', | |||
}, | |||
}, | |||
// Build Configuration: https://go.nuxtjs.dev/config-build | |||
build: {}, | |||
} | |||
} |
@ -0,0 +1,342 @@ | |||
<template> | |||
<section class="AppPage"> | |||
<b-loading v-if="!isReady" active is-full-page></b-loading> | |||
<b-sidebar v-if="isReady" v-model="isDrawer" type="is-light" fullheight> | |||
<div class="p-2"> | |||
<aside class="menu"> | |||
<p class="menu-label">Menu</p> | |||
<ul class="menu-list"> | |||
<li v-for="(n, j) in navItems" :key="j"> | |||
<a | |||
role="button" | |||
:class="{ | |||
'is-active': tabs[activeTab].component === n.component, | |||
}" | |||
@click="setTab(activeTab, n.component)" | |||
> | |||
<b-icon v-if="n.icon" :icon="n.icon"></b-icon> | |||
<span v-if="n.text" :class="'icon ' + n.className"> | |||
{{ n.text }} | |||
</span> | |||
{{ n.title || n.component }} | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
href="https://github.com/zhquiz/zhquiz/discussions" | |||
target="_blank" | |||
rel="noopener noreferrer" | |||
> | |||
<b-icon pack="fab" icon="github"></b-icon> | |||
{{ 'Support & Contribute' }} | |||
</a> | |||
</li> | |||
</ul> | |||
</aside> | |||
</div> | |||
<div style="flex-grow: 1"></div> | |||
<div class="p-4"> | |||
<center> | |||
<a role="button" title="Click to logout" @click="doLogout"> | |||
Logged in as {{ $store.state.identifier }} | |||
</a> | |||
</center> | |||
</div> | |||
</b-sidebar> | |||
<nav v-if="isReady" class="tabs is-toggle"> | |||
<ul> | |||
<li @click="isDrawer = true"> | |||
<b-icon class="clickable" icon="bars" role="button"></b-icon> | |||
</li> | |||
<li | |||
v-for="(t, i) in tabs" | |||
:key="i" | |||
:class="{ 'is-active': activeTab === i }" | |||
> | |||
<a role="button"> | |||
<b-dropdown aria-role="menu" append-to-body :value="t.component"> | |||
<template #trigger> | |||
<a class="no-margin" role="button"> | |||
<span class="text" @click.stop="setCurrentTab(i)"> | |||
{{ t.title }} | |||
</span> | |||
<b-icon icon="caret-down"></b-icon> | |||
</a> | |||
</template> | |||
<b-dropdown-item | |||
v-for="(n, j) in navItems" | |||
:key="j" | |||
:value="n.component" | |||
aria-role="menuitem" | |||
@click="setTab(i, n.component)" | |||
> | |||
<b-icon v-if="n.icon" :icon="n.icon"></b-icon> | |||
<span v-if="n.text" :class="'icon ' + n.className"> | |||
{{ n.text }} | |||
</span> | |||
{{ n.title || n.component }} | |||
</b-dropdown-item> | |||
</b-dropdown> | |||
<button | |||
v-if="!t.permanent" | |||
class="delete is-small" | |||
@click="deleteTab(i)" | |||
></button> | |||
</a> | |||
</li> | |||
<li> | |||
<a role="button" @click="addTab('Random')">+</a> | |||
</li> | |||
</ul> | |||
</nav> | |||
<main v-if="isReady"> | |||
<component | |||
class="container" | |||
:is="t.component + 'Tab'" | |||
v-for="(t, i) in tabs" | |||
v-show="activeTab === i" | |||
:key="i" | |||
:query="t.query" | |||
@title="(t) => setTitle(i, t)" | |||
/> | |||
</main> | |||
</section> | |||
</template> | |||
<script lang="ts"> | |||
import BrowseTab from '@/components/tabs/BrowseTab.vue' | |||
import LookupTab from '@/components/tabs/LookupTab.vue' | |||
import LevelTab from '@/components/tabs/LevelTab.vue' | |||
import LibraryTab from '@/components/tabs/LibraryTab.vue' | |||
import { Component, Vue } from 'nuxt-property-decorator' | |||
import { api } from '~/assets/api' | |||
@Component<AppPage>({ | |||
components: { | |||
BrowseTab, | |||
LookupTab, | |||
LevelTab, | |||
LibraryTab, | |||
}, | |||
async mounted() { | |||
await this.$accessor.updateSettings() | |||
if (!this.$accessor.isApp) { | |||
this.$router.replace('/') | |||
} else { | |||
this.$accessor.ADD_TAB({ | |||
component: 'Lookup', | |||
first: true, | |||
}) | |||
this.isReady = true | |||
} | |||
}, | |||
}) | |||
export default class AppPage extends Vue { | |||
isReady = false | |||
isDrawer = false | |||
get navItems(): { | |||
title?: string | |||
component: string | |||
icon?: string | string[] | |||
text?: string | |||
className?: string | |||
}[] { | |||
return [ | |||
{ | |||
title: 'Lookup', | |||
component: 'Character', | |||
text: '字', | |||
className: 'font-han', | |||
}, | |||
{ | |||
component: 'Level', | |||
text: this.level, | |||
}, | |||
{ | |||
title: 'User content', | |||
component: 'Browse', | |||
icon: 'list-ol', | |||
}, | |||
{ | |||
component: 'Library', | |||
icon: 'book-reader', | |||
}, | |||
] | |||
} | |||
get level() { | |||
const { level } = this.$store.state.settings | |||
return level ? level.toString() : ' ' | |||
} | |||
get tabs() { | |||
return this.$store.state.tabs | |||
} | |||
get activeTab() { | |||
return this.$store.state.activeTab | |||
} | |||
setTab(i: number, component: string) { | |||
this.$accessor.SET_TAB_COMPONENT({ i, component }) | |||
} | |||
setCurrentTab(i: number) { | |||
this.$accessor.SET_TAB_CURRENT({ i }) | |||
} | |||
addTab(component: string) { | |||
this.$accessor.ADD_TAB({ component }) | |||
} | |||
deleteTab(i: number) { | |||
this.$accessor.DELETE_TAB({ i }) | |||
} | |||
setTitle(i: number, title: string) { | |||
this.$accessor.SET_TAB_TITLE({ i, title }) | |||
} | |||
doLogout() { | |||
this.$buefy.dialog.confirm({ | |||
message: 'Are you sure you want to logout?', | |||
onConfirm: async () => { | |||
await api.setWanikaniApiKey() | |||
this.$router.push('/') | |||
}, | |||
}) | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
::v-deep .sidebar-content { | |||
z-index: 60; | |||
} | |||
.AppPage { | |||
height: 100%; | |||
display: grid; | |||
grid-template-rows: 1fr auto; | |||
} | |||
$nav-height: 45px; | |||
nav.tabs { | |||
z-index: 50; | |||
height: $nav-height; | |||
padding-top: 0.5em; | |||
padding-left: 0.5em; | |||
margin-bottom: 0 !important; | |||
background-color: rgba(230, 230, 230, 0.1); | |||
li { | |||
opacity: 0.7; | |||
} | |||
li:not(.is-active) a { | |||
background-color: rgba(211, 211, 211, 0.5); | |||
border: none; | |||
} | |||
li + li { | |||
margin-left: 0.5em !important; | |||
} | |||
li a { | |||
border-bottom-left-radius: 0 !important; | |||
border-bottom-right-radius: 0 !important; | |||
span.text { | |||
max-width: 10em; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
padding: 0; | |||
} | |||
.no-margin { | |||
padding: 0; | |||
} | |||
} | |||
& :hover { | |||
border: none; | |||
} | |||
} | |||
main { | |||
border: 0; | |||
width: 100%; | |||
max-width: 100vw; | |||
overflow-y: scroll; | |||
position: absolute; | |||
margin-top: $nav-height; | |||
padding-top: 20px; | |||
padding-bottom: 100px; | |||
height: 100%; | |||
background-color: rgba(211, 211, 211, 0.2); | |||
box-sizing: border-box; | |||
} | |||
.delete { | |||
border: none !important; | |||
transform: translateX(50%); | |||
background-color: rgba(143, 183, 221); | |||
&:hover { | |||
background-color: lightgray; | |||
} | |||
} | |||
.clickable { | |||
cursor: pointer; | |||
&:hover { | |||
color: blue; | |||
} | |||
} | |||
.loading { | |||
color: lightgray; | |||
> span { | |||
$n: 3; | |||
$speed: 0.1s; | |||
position: relative; | |||
animation: loading (($n + 1) * $speed) infinite; | |||
@for $i from 1 through $n { | |||
&:nth-child(#{$i}) { | |||
animation-delay: $i * $speed; | |||
} | |||
} | |||
} | |||
@keyframes loading { | |||
0% { | |||
color: inherit; | |||
} | |||
40% { | |||
color: gray; | |||
} | |||
100% { | |||
color: inherit; | |||
} | |||
} | |||
} | |||
</style> |
@ -1,53 +1,62 @@ | |||
<template> | |||
<section class="section"> | |||
<div class="columns is-mobile"> | |||
<card | |||
title="Free" | |||
icon="github" | |||
> | |||
Open source on <a href="https://github.com/buefy/buefy"> | |||
GitHub | |||
</a> | |||
</card> | |||
<card | |||
title="Responsive" | |||
icon="cellphone-link" | |||
> | |||
<b class="has-text-grey"> | |||
Every | |||
</b> component is responsive | |||
</card> | |||
<card | |||
title="Modern" | |||
icon="alert-decagram" | |||
> | |||
Built with <a href="https://vuejs.org/"> | |||
Vue.js | |||
</a> and <a href="http://bulma.io/"> | |||
Bulma | |||
</a> | |||
</card> | |||
<card | |||
title="Lightweight" | |||
icon="arrange-bring-to-front" | |||
> | |||
No other internal dependency | |||
</card> | |||
</div> | |||
</section> | |||
<main class="HomePage"> | |||
<b-loading v-if="!isReady" active is-full-page></b-loading> | |||
<form v-else class="card login-card" @submit.prevent="login"> | |||
<div class="card-content"> | |||
<h2 class="title is-4">Login with WaniKani API key</h2> | |||
<b-field> | |||
<b-input v-model="apiKey" placeholder="WaniKani API key"></b-input> | |||
</b-field> | |||
<b-button class="is-primary is-fullwidth" @click="login"> | |||
Login | |||
</b-button> | |||
</div> | |||
</form> | |||
</main> | |||
</template> | |||
<script> | |||
import Card from '~/components/Card' | |||
<script lang="ts"> | |||
import { Vue, Component } from 'nuxt-property-decorator' | |||
import { api } from '~/assets/api' | |||
export default { | |||
name: 'HomePage', | |||
@Component<HomePage>({ | |||
async mounted() { | |||
if (await api.isAuthorized()) { | |||
this.$router.replace('/app') | |||
} else { | |||
this.isReady = true | |||
} | |||
}, | |||
}) | |||
export default class HomePage extends Vue { | |||
apiKey = '' | |||
components: { | |||
Card | |||
isReady = false | |||
async login() { | |||
if (await api.setWanikaniApiKey(this.apiKey)) { | |||
this.$router.push('/app') | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.HomePage { | |||
background-color: rgba(211, 211, 211, 0.3); | |||
width: 100%; | |||
height: 100%; | |||
overflow: scroll; | |||
} | |||
.login-card { | |||
position: absolute; | |||
max-width: 500px; | |||
left: 50%; | |||
top: 50%; | |||
transform: translate(-50%, -50%); | |||
overflow: visible; | |||
} | |||
</style> |
@ -1,15 +0,0 @@ | |||
<template> | |||
<section class="section"> | |||
<h2 class="title is-3 has-text-grey"> | |||
"Just start <b-icon | |||
icon="rocket" | |||
size="is-large" | |||
/>" | |||
</h2> | |||
<h3 class="subtitle is-6 has-text-grey"> | |||
Author: <a href="https://github.com/anteriovieira"> | |||
Antério Vieira | |||
</a> | |||
</h3> | |||
</section> | |||
</template> |