Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f703cc5727 | ||
|
|
2129cee5c4 | ||
|
|
3da110d6cc |
@@ -73,6 +73,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
npm run build:android
|
npm run build:android
|
||||||
|
zip -r dist.zip dist
|
||||||
|
|
||||||
- name: Sync Capacitor to Android
|
- name: Sync Capacitor to Android
|
||||||
working-directory: client
|
working-directory: client
|
||||||
@@ -105,4 +106,5 @@ jobs:
|
|||||||
- Client: `${{ vars.REGISTRY }}/${{ vars.CLIENT_IMAGE }}:latest`
|
- Client: `${{ vars.REGISTRY }}/${{ vars.CLIENT_IMAGE }}:latest`
|
||||||
files: |
|
files: |
|
||||||
client/android/app/build/outputs/apk/release/*.apk
|
client/android/app/build/outputs/apk/release/*.apk
|
||||||
|
client/dist.zip
|
||||||
api_key: ${{ secrets.GITHUB_TOKEN }}
|
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ android {
|
|||||||
|
|
||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':capacitor-app')
|
||||||
|
implementation project(':capgo-capacitor-updater')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
include ':capacitor-android'
|
include ':capacitor-android'
|
||||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
|
include ':capacitor-app'
|
||||||
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
include ':capgo-capacitor-updater'
|
||||||
|
project(':capgo-capacitor-updater').projectDir = new File('../node_modules/@capgo/capacitor-updater/android')
|
||||||
|
|||||||
@@ -5,5 +5,10 @@
|
|||||||
"server": {
|
"server": {
|
||||||
"androidScheme": "https",
|
"androidScheme": "https",
|
||||||
"cleartext": false
|
"cleartext": false
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"CapacitorUpdater": {
|
||||||
|
"autoUpdate": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
client/package-lock.json
generated
20
client/package-lock.json
generated
@@ -9,7 +9,9 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^8.0.0",
|
"@capacitor/android": "^8.0.0",
|
||||||
|
"@capacitor/app": "^8.0.0",
|
||||||
"@capacitor/core": "^8.0.0",
|
"@capacitor/core": "^8.0.0",
|
||||||
|
"@capgo/capacitor-updater": "^8.40.6",
|
||||||
"@eslint/eslintrc": "^3.3.3",
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@mdi/font": "^7.3.67",
|
"@mdi/font": "^7.3.67",
|
||||||
@@ -639,6 +641,15 @@
|
|||||||
"@capacitor/core": "^8.0.0"
|
"@capacitor/core": "^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor/app": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/app/-/app-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-OwzIkUs4w433Bu9WWAEbEYngXEfJXZ9Wmdb8eoaqzYBgB0W9/3Ed/mh6sAYPNBAZlpyarmewgP7Nb+d3Vrh+xA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor/cli": {
|
"node_modules/@capacitor/cli": {
|
||||||
"version": "7.4.4",
|
"version": "7.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.4.tgz",
|
||||||
@@ -681,6 +692,15 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capgo/capacitor-updater": {
|
||||||
|
"version": "8.40.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capgo/capacitor-updater/-/capacitor-updater-8.40.6.tgz",
|
||||||
|
"integrity": "sha512-Z2uG8E9VKClYeZRhvkN8p/w3tXqxj5DB61M3uKwuHyHgPWEOoBv+drlRtnwDL8+XhSljNLG7yGPWUg8WAi8cBQ==",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@csstools/css-parser-algorithms": {
|
"node_modules/@csstools/css-parser-algorithms": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^8.0.0",
|
"@capacitor/android": "^8.0.0",
|
||||||
|
"@capacitor/app": "^8.0.0",
|
||||||
"@capacitor/core": "^8.0.0",
|
"@capacitor/core": "^8.0.0",
|
||||||
|
"@capgo/capacitor-updater": "^8.40.6",
|
||||||
"@eslint/eslintrc": "^3.3.3",
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@mdi/font": "^7.3.67",
|
"@mdi/font": "^7.3.67",
|
||||||
|
|||||||
@@ -1,28 +1,7 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-app.zen-app.overflow-hidden
|
v-app.zen-app.overflow-hidden
|
||||||
.parallax-bg(
|
|
||||||
:style="parallaxStyle"
|
|
||||||
:class="{ 'zooming': viewState === 'zooming' }"
|
|
||||||
)
|
|
||||||
.bg-gradient
|
|
||||||
.bg-grid
|
|
||||||
|
|
||||||
.kanji-layer
|
|
||||||
span.bg-kanji(
|
|
||||||
v-for="(k, i) in backgroundKanjis"
|
|
||||||
:key="i"
|
|
||||||
:style="k.style"
|
|
||||||
) {{ k.char }}
|
|
||||||
|
|
||||||
.orb.orb-1(:class="{ 'zooming': viewState === 'zooming' }")
|
|
||||||
.orb.orb-2(:class="{ 'zooming': viewState === 'zooming' }")
|
|
||||||
.orb.orb-3(:class="{ 'zooming': viewState === 'zooming' }")
|
|
||||||
|
|
||||||
.app-content-wrapper(
|
|
||||||
v-if="store.token || viewState === 'zooming'"
|
|
||||||
:class="{ 'app-entering': viewState === 'zooming', 'app-visible': viewState === 'app' }"
|
|
||||||
)
|
|
||||||
v-navigation-drawer(
|
v-navigation-drawer(
|
||||||
|
v-if="store.token || viewState === 'zooming'"
|
||||||
v-model="drawer"
|
v-model="drawer"
|
||||||
temporary
|
temporary
|
||||||
location="right"
|
location="right"
|
||||||
@@ -83,6 +62,28 @@
|
|||||||
)
|
)
|
||||||
v-icon(start icon="mdi-logout")
|
v-icon(start icon="mdi-logout")
|
||||||
| {{ $t('nav.logout') }}
|
| {{ $t('nav.logout') }}
|
||||||
|
.parallax-bg(
|
||||||
|
:style="parallaxStyle"
|
||||||
|
:class="{ 'zooming': viewState === 'zooming' }"
|
||||||
|
)
|
||||||
|
.bg-gradient
|
||||||
|
.bg-grid
|
||||||
|
|
||||||
|
.kanji-layer
|
||||||
|
span.bg-kanji(
|
||||||
|
v-for="(k, i) in backgroundKanjis"
|
||||||
|
:key="i"
|
||||||
|
:style="k.style"
|
||||||
|
) {{ k.char }}
|
||||||
|
|
||||||
|
.orb.orb-1(:class="{ 'zooming': viewState === 'zooming' }")
|
||||||
|
.orb.orb-2(:class="{ 'zooming': viewState === 'zooming' }")
|
||||||
|
.orb.orb-3(:class="{ 'zooming': viewState === 'zooming' }")
|
||||||
|
|
||||||
|
.app-content-wrapper(
|
||||||
|
v-if="store.token || viewState === 'zooming'"
|
||||||
|
:class="{ 'app-entering': viewState === 'zooming', 'app-visible': viewState === 'app' }"
|
||||||
|
)
|
||||||
|
|
||||||
v-app-bar.px-2.app-bar-blur.safe-area-header(
|
v-app-bar.px-2.app-bar-blur.safe-area-header(
|
||||||
flat
|
flat
|
||||||
@@ -287,7 +288,9 @@ import {
|
|||||||
ref, watch, onMounted, computed, onUnmounted,
|
ref, watch, onMounted, computed, onUnmounted,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { App as CapacitorApp } from '@capacitor/app';
|
||||||
import { useAppStore } from '@/stores/appStore';
|
import { useAppStore } from '@/stores/appStore';
|
||||||
|
import { checkForUpdates } from '@/utils/autoUpdate';
|
||||||
import logo from '@/assets/icon.svg';
|
import logo from '@/assets/icon.svg';
|
||||||
|
|
||||||
const drawer = ref(false);
|
const drawer = ref(false);
|
||||||
@@ -388,6 +391,9 @@ const handleMouseMove = (e) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
CapacitorApp.addListener('appStateChange', ({ isActive }) => {
|
||||||
|
if (isActive) checkForUpdates();
|
||||||
|
});
|
||||||
if (store.token) {
|
if (store.token) {
|
||||||
viewState.value = 'app';
|
viewState.value = 'app';
|
||||||
store.fetchStats();
|
store.fetchStats();
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ const router = createRouter({
|
|||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/', component: Dashboard },
|
{ path: '/', component: Dashboard },
|
||||||
{ path: '/dashboard', component: Dashboard },
|
|
||||||
{ path: '/collection', component: Collection },
|
{ path: '/collection', component: Collection },
|
||||||
{ path: '/review', component: Review },
|
{ path: '/review', component: Review },
|
||||||
{ path: '/lesson', component: Lesson },
|
{ path: '/lesson', component: Lesson },
|
||||||
|
{ path: '/:pathMatch(.*)*', redirect: '/' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@use 'readings';
|
||||||
|
@use 'radicals';
|
||||||
@use 'buttons';
|
@use 'buttons';
|
||||||
@use 'cards';
|
@use 'cards';
|
||||||
@use 'kanji/index' as kanji;
|
@use 'kanji/index' as kanji;
|
||||||
|
|||||||
20
client/src/styles/components/_radicals.scss
Normal file
20
client/src/styles/components/_radicals.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@use '../abstracts' as *;
|
||||||
|
|
||||||
|
.radical-section {
|
||||||
|
width: 100%;
|
||||||
|
background: $color-zinc-900;
|
||||||
|
border-radius: $radius-md;
|
||||||
|
padding: $spacing-md;
|
||||||
|
margin-bottom: $spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radical-chip {
|
||||||
|
border-radius: $radius-md;
|
||||||
|
border: $border-width-sm solid $color-border;
|
||||||
|
background-color: $color-surface-lighter;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: invert(1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
client/src/styles/components/_readings.scss
Normal file
28
client/src/styles/components/_readings.scss
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
@use '../abstracts' as *;
|
||||||
|
|
||||||
|
.readings-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: $spacing-lg;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reading-box {
|
||||||
|
background: $color-reading-box;
|
||||||
|
padding: $spacing-lg;
|
||||||
|
border-radius: $radius-lg;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: $font-xs;
|
||||||
|
color: #666;
|
||||||
|
letter-spacing: 0.0625rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: $spacing-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.val {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: $weight-bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.v-navigation-drawer {
|
.v-navigation-drawer {
|
||||||
background-color: $color-surface !important;
|
background-color: $color-surface !important;
|
||||||
border-right: $border-width-sm solid $color-border;
|
padding-top: env(safe-area-inset-top) !important;
|
||||||
|
|
||||||
.v-list-item--active {
|
.v-list-item--active {
|
||||||
background-color: $bg-glass-strong;
|
background-color: $bg-glass-strong;
|
||||||
|
|||||||
@@ -16,52 +16,6 @@
|
|||||||
height: $size-hero-wrapper;
|
height: $size-hero-wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radical-section {
|
|
||||||
width: 100%;
|
|
||||||
background: $color-zinc-900;
|
|
||||||
border-radius: $radius-md;
|
|
||||||
padding: $spacing-md;
|
|
||||||
margin-bottom: $spacing-lg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radical-chip {
|
|
||||||
border-radius: $radius-md;
|
|
||||||
border: $border-width-sm solid $color-border;
|
|
||||||
background-color: $color-surface-lighter;
|
|
||||||
|
|
||||||
img {
|
|
||||||
filter: invert(1);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.readings-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: $spacing-lg;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reading-box {
|
|
||||||
background: $color-reading-box;
|
|
||||||
padding: $spacing-lg;
|
|
||||||
border-radius: $radius-lg;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: $font-xs;
|
|
||||||
color: #666;
|
|
||||||
letter-spacing: 0.0625rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: $spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.val {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: $weight-bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lesson-canvas-wrapper {
|
.lesson-canvas-wrapper {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,3 +45,10 @@
|
|||||||
.gap-2 {
|
.gap-2 {
|
||||||
gap: $spacing-sm;
|
gap: $spacing-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reading-box--small {
|
||||||
|
padding: $spacing-sm;
|
||||||
|
.val {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
55
client/src/utils/autoUpdate.js
Normal file
55
client/src/utils/autoUpdate.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Capacitor } from '@capacitor/core';
|
||||||
|
import { App } from '@capacitor/app';
|
||||||
|
|
||||||
|
export async function checkForUpdates() {
|
||||||
|
if (!Capacitor.isNativePlatform()) {
|
||||||
|
console.log('Auto-update skipped (running on web)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNewer(current, target) {
|
||||||
|
const c = current.replace(/^v/, '').split('.').map(Number);
|
||||||
|
const t = target.replace(/^v/, '').split('.').map(Number);
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if (t[i] > c[i]) return true;
|
||||||
|
if (t[i] < c[i]) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { CapacitorUpdater } = await import('@capgo/capacitor-updater');
|
||||||
|
|
||||||
|
await CapacitorUpdater.notifyAppReady();
|
||||||
|
|
||||||
|
const GITEA_API_URL = 'https://git.crylia.de/Crylia/zen-kanji/releases/latest';
|
||||||
|
|
||||||
|
const appInfo = await App.getInfo();
|
||||||
|
const currentVersion = appInfo.version;
|
||||||
|
|
||||||
|
const response = await fetch(GITEA_API_URL);
|
||||||
|
if (!response.ok) return;
|
||||||
|
|
||||||
|
const release = await response.json();
|
||||||
|
const latestTag = release.tag_name;
|
||||||
|
|
||||||
|
if (!isNewer(currentVersion, latestTag)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = release.assets.find((a) => a.name === 'dist.zip');
|
||||||
|
if (!asset) return;
|
||||||
|
|
||||||
|
console.log(`Downloading update: ${latestTag}`);
|
||||||
|
|
||||||
|
const version = await CapacitorUpdater.download({
|
||||||
|
url: asset.browser_download_url,
|
||||||
|
version: latestTag,
|
||||||
|
});
|
||||||
|
|
||||||
|
await CapacitorUpdater.set(version);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Auto-update failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,6 +92,18 @@
|
|||||||
:svg-content="preloadedSvg"
|
:svg-content="preloadedSvg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.radical-section.mb-4(v-if="selectedItem?.radicals?.length")
|
||||||
|
.text-caption.text-grey-darken-1.text-uppercase.mb-3.font-weight-bold
|
||||||
|
|{{ $t('lesson.components') }}
|
||||||
|
.d-flex.flex-wrap.gap-3.justify-center
|
||||||
|
v-sheet.radical-chip(
|
||||||
|
v-for="rad in selectedItem.radicals" :key="rad.meaning")
|
||||||
|
.d-flex.align-center.gap-2.px-3.py-1
|
||||||
|
v-avatar(size="24" rounded="0")
|
||||||
|
img(v-if="rad.image" :src="rad.image")
|
||||||
|
span.text-h6.font-weight-bold.text-white(v-else) {{ rad.char }}
|
||||||
|
.text-caption.text-grey-lighten-1 {{ rad.meaning }}
|
||||||
|
|
||||||
.readings-container.mb-4
|
.readings-container.mb-4
|
||||||
.reading-group(v-if="hasReading(selectedItem?.onyomi)")
|
.reading-group(v-if="hasReading(selectedItem?.onyomi)")
|
||||||
.reading-label {{ $t('collection.onyomi') }}
|
.reading-label {{ $t('collection.onyomi') }}
|
||||||
@@ -105,6 +117,17 @@
|
|||||||
.reading-label {{ $t('collection.nanori') }}
|
.reading-label {{ $t('collection.nanori') }}
|
||||||
.reading-value {{ selectedItem?.nanori.join(', ') }}
|
.reading-value {{ selectedItem?.nanori.join(', ') }}
|
||||||
|
|
||||||
|
.px-2.mb-4(v-if="selectedItem?.stats?.total > 0")
|
||||||
|
.d-flex.justify-space-between.align-center.mb-1
|
||||||
|
.text-caption.text-grey {{ $t('stats.accuracy') }}
|
||||||
|
.text-caption.font-weight-bold {{ accuracyStats.text }}
|
||||||
|
v-progress-linear(
|
||||||
|
:model-value="accuracyStats.percent"
|
||||||
|
color="light-blue"
|
||||||
|
height="6"
|
||||||
|
rounded
|
||||||
|
)
|
||||||
|
|
||||||
v-row.px-2.pb-2
|
v-row.px-2.pb-2
|
||||||
v-col.pr-2(cols="6")
|
v-col.pr-2(cols="6")
|
||||||
v-btn.text-white(
|
v-btn.text-white(
|
||||||
@@ -142,6 +165,16 @@ const searchQuery = ref('');
|
|||||||
const preparingId = ref(null);
|
const preparingId = ref(null);
|
||||||
const preloadedSvg = ref('');
|
const preloadedSvg = ref('');
|
||||||
|
|
||||||
|
const accuracyStats = computed(() => {
|
||||||
|
if (!selectedItem.value?.stats || selectedItem.value.stats.total === 0) {
|
||||||
|
return { percent: 100, text: 'No history' };
|
||||||
|
}
|
||||||
|
const { correct, total } = selectedItem.value.stats;
|
||||||
|
const percent = Math.round((correct / total) * 100);
|
||||||
|
const text = `${correct} / ${total}`;
|
||||||
|
return { percent, text };
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await store.fetchCollection();
|
await store.fetchCollection();
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
v-else-if="currentItem"
|
v-else-if="currentItem"
|
||||||
)
|
)
|
||||||
.d-flex.align-center.pa-4.border-b-subtle
|
.d-flex.align-center.pa-4.border-b-subtle
|
||||||
v-btn(icon="mdi-close" variant="text" to="/dashboard" color="grey")
|
v-btn(icon="mdi-close" variant="text" to="/" color="grey")
|
||||||
v-spacer
|
v-spacer
|
||||||
.text-caption.text-grey-darken-1.font-weight-bold
|
.text-caption.text-grey-darken-1.font-weight-bold
|
||||||
| {{ sessionDone + 1 }} / {{ sessionTotal }}
|
| {{ sessionDone + 1 }} / {{ sessionTotal }}
|
||||||
@@ -38,13 +38,13 @@
|
|||||||
img(v-if="rad.image" :src="rad.image")
|
img(v-if="rad.image" :src="rad.image")
|
||||||
span.text-h6.font-weight-bold.text-white(v-else) {{ rad.char }}
|
span.text-h6.font-weight-bold.text-white(v-else) {{ rad.char }}
|
||||||
.text-caption.text-grey-lighten-1 {{ rad.meaning }}
|
.text-caption.text-grey-lighten-1 {{ rad.meaning }}
|
||||||
.readings-grid
|
.readings-grid(v-if="(currentItem.onyomi && currentItem.onyomi.length > 0) || (currentItem.kunyomi && currentItem.kunyomi.length > 0)")
|
||||||
.reading-box
|
.reading-box(v-if="currentItem.onyomi && currentItem.onyomi.length > 0")
|
||||||
.label {{ $t('collection.onyomi') }}
|
.label {{ $t('collection.onyomi') }}
|
||||||
.val.text-cyan-lighten-3 {{ currentItem.onyomi[0] || '-' }}
|
.val.text-cyan-lighten-3 {{ currentItem.onyomi.join(', ') }}
|
||||||
.reading-box
|
.reading-box(v-if="currentItem.kunyomi && currentItem.kunyomi.length > 0")
|
||||||
.label {{ $t('collection.kunyomi') }}
|
.label {{ $t('collection.kunyomi') }}
|
||||||
.val.text-pink-lighten-3 {{ currentItem.kunyomi[0] || '-' }}
|
.val.text-pink-lighten-3 {{ currentItem.kunyomi.join(', ') }}
|
||||||
|
|
||||||
v-window-item(value="demo")
|
v-window-item(value="demo")
|
||||||
.d-flex.flex-column.align-center.justify-center.h-100.pa-6
|
.d-flex.flex-column.align-center.justify-center.h-100.pa-6
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
.text-h4.font-weight-bold.text-white.mb-2 {{ $t('lesson.completeTitle') }}
|
.text-h4.font-weight-bold.text-white.mb-2 {{ $t('lesson.completeTitle') }}
|
||||||
.text-body-1.text-grey.mb-8 {{ $t('lesson.learned', { n: sessionDone }) }}
|
.text-body-1.text-grey.mb-8 {{ $t('lesson.learned', { n: sessionDone }) }}
|
||||||
v-btn.glow-btn.text-black.font-weight-bold(
|
v-btn.glow-btn.text-black.font-weight-bold(
|
||||||
to="/dashboard"
|
to="/"
|
||||||
block
|
block
|
||||||
color="cyan"
|
color="cyan"
|
||||||
height="50"
|
height="50"
|
||||||
@@ -111,12 +111,14 @@ import {
|
|||||||
ref, computed, onMounted, nextTick, watch,
|
ref, computed, onMounted, nextTick, watch,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { useAppStore } from '@/stores/appStore';
|
import { useAppStore } from '@/stores/appStore';
|
||||||
import KanjiCanvas from '@/components/kanji/KanjiCanvas.vue';
|
import KanjiCanvas from '@/components/kanji/KanjiCanvas.vue';
|
||||||
import KanjiSvgViewer from '@/components/kanji/KanjiSvgViewer.vue';
|
import KanjiSvgViewer from '@/components/kanji/KanjiSvgViewer.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const store = useAppStore();
|
const store = useAppStore();
|
||||||
|
const route = useRoute();
|
||||||
const currentItem = ref(null);
|
const currentItem = ref(null);
|
||||||
const phase = ref('primer');
|
const phase = ref('primer');
|
||||||
const subPhase = ref('guided');
|
const subPhase = ref('guided');
|
||||||
@@ -179,7 +181,12 @@ async function handleDrawComplete() {
|
|||||||
canvas.value?.reset();
|
canvas.value?.reset();
|
||||||
canvasOpacity.value = 1;
|
canvasOpacity.value = 1;
|
||||||
} else {
|
} else {
|
||||||
|
const isRedo = !!route.query.subjectId;
|
||||||
|
|
||||||
|
if (!isRedo) {
|
||||||
await store.submitLesson(currentItem.value.wkSubjectId);
|
await store.submitLesson(currentItem.value.wkSubjectId);
|
||||||
|
}
|
||||||
|
|
||||||
sessionDone.value += 1;
|
sessionDone.value += 1;
|
||||||
store.lessonQueue.shift();
|
store.lessonQueue.shift();
|
||||||
loadNext();
|
loadNext();
|
||||||
@@ -200,7 +207,15 @@ function useHint() {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
const { subjectId } = route.query;
|
||||||
|
const historyState = window.history.state;
|
||||||
|
|
||||||
|
if (subjectId && historyState && historyState.item) {
|
||||||
|
store.lessonQueue = [historyState.item];
|
||||||
|
} else {
|
||||||
await store.fetchLessonQueue();
|
await store.fetchLessonQueue();
|
||||||
|
}
|
||||||
|
|
||||||
sessionTotal.value = store.lessonQueue.length;
|
sessionTotal.value = store.lessonQueue.length;
|
||||||
loadNext();
|
loadNext();
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -28,8 +28,15 @@
|
|||||||
.text-center.mb-6
|
.text-center.mb-6
|
||||||
.text-caption.text-grey.text-uppercase.tracking-wide.mb-1
|
.text-caption.text-grey.text-uppercase.tracking-wide.mb-1
|
||||||
| {{ $t('review.meaning') }}
|
| {{ $t('review.meaning') }}
|
||||||
.text-h3.font-weight-bold.text-white.text-shadow
|
.text-h3.font-weight-bold.text-white.text-shadow.mb-4
|
||||||
| {{ currentItem.meaning }}
|
| {{ currentItem.meaning }}
|
||||||
|
.readings-grid(v-if="(currentItem.onyomi && currentItem.onyomi.length > 0) || (currentItem.kunyomi && currentItem.kunyomi.length > 0)")
|
||||||
|
.reading-box.reading-box--small(v-if="currentItem.onyomi && currentItem.onyomi.length > 0")
|
||||||
|
.label {{ $t('collection.onyomi') }}
|
||||||
|
.val.text-cyan-lighten-3 {{ currentItem.onyomi.join(', ') }}
|
||||||
|
.reading-box.reading-box--small(v-if="currentItem.kunyomi && currentItem.kunyomi.length > 0")
|
||||||
|
.label {{ $t('collection.kunyomi') }}
|
||||||
|
.val.text-pink-lighten-3 {{ currentItem.kunyomi.join(', ') }}
|
||||||
|
|
||||||
.review-canvas-area
|
.review-canvas-area
|
||||||
KanjiCanvas(
|
KanjiCanvas(
|
||||||
@@ -96,7 +103,7 @@
|
|||||||
.text-caption.text-grey.text-uppercase.mt-1 {{ $t('stats.correct') }}
|
.text-caption.text-grey.text-uppercase.mt-1 {{ $t('stats.correct') }}
|
||||||
|
|
||||||
v-btn.text-black.font-weight-bold.glow-btn(
|
v-btn.text-black.font-weight-bold.glow-btn(
|
||||||
to="/dashboard"
|
to="/"
|
||||||
block
|
block
|
||||||
color="primary"
|
color="primary"
|
||||||
height="50"
|
height="50"
|
||||||
@@ -108,7 +115,7 @@
|
|||||||
.text-h4.mb-2.text-white {{ $t('review.caughtUp') }}
|
.text-h4.mb-2.text-white {{ $t('review.caughtUp') }}
|
||||||
.text-grey.mb-6 {{ $t('review.noReviews') }}
|
.text-grey.mb-6 {{ $t('review.noReviews') }}
|
||||||
v-btn.font-weight-bold(
|
v-btn.font-weight-bold(
|
||||||
to="/dashboard"
|
to="/"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
) {{ $t('review.viewCollection') }}
|
) {{ $t('review.viewCollection') }}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const getUserStats = async (user) => {
|
|||||||
accuracy: Math.round((item.stats.correct / item.stats.total) * 100)
|
accuracy: Math.round((item.stats.correct / item.stats.total) * 100)
|
||||||
})).filter(i => i.accuracy < 85)
|
})).filter(i => i.accuracy < 85)
|
||||||
.sort((a, b) => a.accuracy - b.accuracy)
|
.sort((a, b) => a.accuracy - b.accuracy)
|
||||||
.slice(0, 4);
|
.slice(0, 10);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
distribution: dist,
|
distribution: dist,
|
||||||
|
|||||||
Reference in New Issue
Block a user