Perfect panel

This commit is contained in:
zyronon
2023-10-02 17:03:53 +08:00
parent 12da731c58
commit 0d33096a57
13 changed files with 348 additions and 379 deletions

21
Note.md
View File

@@ -16,4 +16,23 @@ wink-nlp
compromise
表现良好,另外自带分词功能
以上所有库都会将:'Do you always get up so late? It's one o'clock!'分成两句....
以上所有库都会将:'Do you always get up so late? It's one o'clock!'分成两句....
1 错题本,添加错误次数
bug
换段的时候没发音
打完一段的一最后一行的时候,没有自动换行,需要按下空格才能换段
打完了没检测到
所有的图标hover时有放大效果
各种声音可以单独调节音量大小
列表加搜索
BaseIcon 在选中模式下,应该显示白色
添加文章时正文输入123报错
没有内容时,要显示占位符

View File

@@ -1,15 +0,0 @@
1 错题本,添加错误次数
bug
换段的时候没发音
打完一段的一最后一行的时候,没有自动换行,需要按下空格才能换段
打完了没检测到
所有的图标hover时有放大效果
各种声音可以单独调节音量大小
列表加搜索
BaseIcon 在选中模式下,应该显示白色
添加文章时正文输入123报错

1
components.d.ts vendored
View File

@@ -39,6 +39,7 @@ declare module 'vue' {
List: typeof import('./src/components/List.vue')['default']
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
Panel: typeof import('./src/components/Practice/Panel.vue')['default']
PopConfirm: typeof import('./src/components/PopConfirm.vue')['default']
Practice: typeof import('./src/components/Practice/Practice.vue')['default']
RepeatSetting: typeof import('./src/components/Toolbar/RepeatSetting.vue')['default']

View File

@@ -28,7 +28,6 @@
"mitt": "^3.0.1",
"pinia": "^2.1.6",
"sentence-splitter": "^4.2.1",
"swiper": "^10.1.0",
"tesseract.js": "^4.1.1",
"uuid": "^9.0.1",
"vue": "^3.3.4",

View File

@@ -0,0 +1,286 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import WordList from "@/components/WordList.vue"
import {$ref} from "vue/macros"
import {computed, provide, watch} from "vue"
import 'swiper/css';
import {DictType, Word} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
import {useSettingStore} from "@/stores/setting.ts";
const props = defineProps<{
list?: Word[],
index: number
}>()
const store = useBaseStore()
const settingStore = useSettingStore()
let tabIndex = $ref(0)
provide('tabIndex', computed(() => tabIndex))
watch(() => settingStore.showPanel, n => {
if (n) {
switch (store.current.dictType) {
case DictType.newDict:
return tabIndex = 1;
case DictType.skipDict:
return tabIndex = 3;
case DictType.wrongDict:
return tabIndex = 2;
case DictType.publicDict:
case DictType.customDict:
return tabIndex = 0;
}
}
})
const newWordDictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.newDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
const wrongWordDictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.wrongDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
const skipWordDictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.skipDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
</script>
<template>
<Transition name="fade">
<div class="panel" v-if="settingStore.showPanel">
<header>
<div class="tabs">
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">{{ store.dictTitle }}</div>
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">{{ store.newWordDict.name }}</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{
store.wrongWordDict.name
}}
</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.skipWordDict.name }}</div>
</div>
</header>
<div class="slide">
<div class="slide-list" :class="`step${tabIndex}`">
<div class="slide-item">
<header>
<div class="dict-name">{{ store.currentDict.chapterIndex + 1 }}.</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.currentDict,store.currentDict.chapterIndex,e)"
:isActive="settingStore.showPanel && tabIndex === 0"
:list="props.list??[]"
:activeIndex="props.index"/>
</div>
<footer v-if="![DictType.customDict,DictType.publicDict].includes(store.current.dictType)">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.currentDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
<div class="slide-item">
<header>
<div class="dict-name">总词数{{ store.newWordDict.originWords.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.newWordDict,store.newWordDict.chapterIndex,e)"
:isActive="settingStore.showPanel && tabIndex === 1"
:list="store.newWordDict.words"
:activeIndex="newWordDictActiveIndex"/>
</div>
<footer v-if="store.current.dictType !== DictType.newDict && store.newWordDict.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.newWordDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
<div class="slide-item">
<header>
<a href="" target="_blank"></a>
<div class="dict-name">总词数{{ store.wrongWordDict.originWords.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.wrongWordDict,store.wrongWordDict.chapterIndex,e)"
:isActive="settingStore.showPanel && tabIndex === 2"
:list="store.wrongWordDict.words"
:activeIndex="wrongWordDictActiveIndex"/>
</div>
<footer
v-if="store.current.dictType !== DictType.wrongDict && store.wrongWordDict.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.wrongWordDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
<div class="slide-item">
<header>
<div class="dict-name">总词数{{ store.skipWordDict.originWords.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.skipWordDict,store.skipWordDict.chapterIndex,e)"
:isActive="settingStore.showPanel && tabIndex === 3"
:list="store.skipWordDict.words"
:activeIndex="skipWordDictActiveIndex"/>
</div>
<footer v-if="store.current.dictType !== DictType.skipDict && store.skipWordDict.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.skipWordDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
</div>
</div>
</div>
</Transition>
</template>
<style scoped lang="scss">
@import "@/assets/css/colors";
$width: 20vw;
$header-height: 50rem;
.slide {
width: 100%;
height: calc(100% - $header-height);
overflow: hidden;
.slide-list {
width: 400%;
height: 100%;
display: flex;
transition: all .5s;
.slide-item {
width: $width;
height: 100%;
display: flex;
flex-direction: column;
> header {
padding: 0 $space;
height: $header-height;
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 18rem;
color: black;
}
.content {
flex: 1;
overflow: auto;
padding-bottom: $space;
}
footer {
padding-right: $space;
height: 50rem;
align-items: center;
}
}
}
.step1 {
transform: translate3d(-25%, 0, 0);
}
.step2 {
transform: translate3d(-50%, 0, 0);
}
.step3 {
transform: translate3d(-75%, 0, 0);
}
}
.panel {
position: fixed;
left: 0;
top: 0;
margin-left: calc(50% + (var(--toolbar-width) / 2) + $space);
width: $width;
background: var(--color-second-bg);
height: 100%;
display: flex;
flex-direction: column;
transition: all .3s;
z-index: 1;
& > header {
height: $header-height;
position: relative;
display: flex;
align-items: center;
.tabs {
padding: 10rem 20rem;
justify-content: flex-end;
width: 100%;
display: flex;
align-items: flex-end;
border-bottom: 1px solid #e1e1e1;
gap: 15rem;
font-size: 14rem;
color: gray;
.tab {
cursor: pointer;
&.active {
font-size: 16rem;
color: rgb(36, 127, 255);
font-weight: bold;
}
}
}
.close {
cursor: pointer;
position: absolute;
right: 20rem;
}
}
}
</style>

View File

@@ -57,10 +57,15 @@ watch(() => store.load, n => {
}
})
watch([() => store.current.index, () => store.current.dictType], n => {
getCurrentPractice()
})
function getCurrentPractice() {
// console.log('store.currentDict',store.currentDict)
if (store.isArticle) {
return
// return
let currentArticle = store.currentDict.articles[store.currentDict.chapterIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
console.log('article', tempArticle)

View File

@@ -13,6 +13,7 @@ import {useOnKeyboardEventListener} from "@/hooks/event.ts";
import {Icon} from "@iconify/vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
import Tooltip from "@/components/Tooltip.vue";
import Panel from "@/components/Practice/Panel.vue";
interface IProps {
words: Word[],
@@ -142,6 +143,9 @@ function prev() {
function ignore() {
activeBtnIndex = 2
next(false)
setTimeout(() => {
activeBtnIndex = -1
}, 200)
}
function collect() {
@@ -151,9 +155,12 @@ function collect() {
store.newWordDict.chapterWords = [store.newWordDict.words]
}
activeBtnIndex = 1
setTimeout(() => {
activeBtnIndex = -1
}, 200)
}
function remove(){
function remove() {
if (!store.skipWordNames.includes(word.name.toLowerCase())) {
store.skipWordDict.originWords.push(word)
store.skipWordDict.words.push(word)
@@ -161,6 +168,9 @@ function remove(){
}
activeBtnIndex = 0
next(false)
setTimeout(() => {
activeBtnIndex = -1
}, 200)
}
function onKeyUp(e: KeyboardEvent) {
@@ -286,16 +296,23 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
</div>
<div class="phonetic">{{ word.usphone }}</div>
<div class="options">
<BaseButton keyboard="`" :active="activeBtnIndex === 0">
<BaseButton keyboard="`"
@click="remove"
:active="activeBtnIndex === 0">
忽略
</BaseButton>
<BaseButton keyboard="Enter" :active="activeBtnIndex === 1">
<BaseButton keyboard="Enter"
@click="collect"
:active="activeBtnIndex === 1">
收藏
</BaseButton>
<BaseButton keyboard="Tab" :active="activeBtnIndex === 2">
<BaseButton keyboard="Tab"
@click="ignore"
:active="activeBtnIndex === 2">
跳过
</BaseButton>
</div>
<Panel :list="data.words" :index="data.index"/>
</div>
</template>
@@ -313,11 +330,12 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
color: gray;
gap: 2rem;
position: relative;
width: var(--toolbar-width);
.near-word {
position: absolute;
top: 0;
width: var(--toolbar-width);
width: 100%;
.word {
div {

View File

@@ -1,307 +0,0 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import WordList from "@/components/WordList.vue"
import {$ref} from "vue/macros"
import {computed, onMounted, provide} from "vue"
import {Swiper, SwiperSlide} from 'swiper/vue';
import 'swiper/css';
import {Swiper as SwiperClass} from "swiper/types"
import {DictType} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts"
const store = useBaseStore()
const swiperIns0: SwiperClass = $ref(null as any)
let tabIndex = $ref(0)
provide('tabIndex', computed(() => tabIndex))
function slideTo(index: number) {
swiperIns0.slideTo(tabIndex = index)
}
onMounted(() => {
emitter.on(EventKey.openSide, () => {
store.sideIsOpen = !store.sideIsOpen
if (store.sideIsOpen) {
switch (store.current.dictType) {
case DictType.newDict:
return tabIndex = 1;
case DictType.skipDict:
return tabIndex = 3;
case DictType.wrongDict:
return tabIndex = 2;
case DictType.publicDict:
case DictType.customDict:
return tabIndex = 0;
}
}
})
})
const newWordDictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.newDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
const dictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.publicDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
const wrongWordDictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.wrongDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
const skipWordDictActiveIndex = computed(() => {
if (store.current.dictType !== DictType.skipDict) return -1
else {
if (store.current.repeatNumber) {
return store.chapter.findIndex(v => v.name === store.word.name)
}
return store.current.index
}
})
</script>
<template>
<Transition name="fade">
<div class="side" v-if="store.sideIsOpen">
<header>
<div class="tabs">
<div class="tab" :class="tabIndex===0&&'active'" @click="slideTo(0)">{{ store.currentDict.name }}</div>
<div class="tab" :class="tabIndex===1&&'active'" @click="slideTo(1)">{{ store.newWordDict.name }}</div>
<div class="tab" :class="tabIndex===2&&'active'" @click="slideTo(2)">{{ store.wrongWordDict.name }}</div>
<div class="tab" :class="tabIndex===3&&'active'" @click="slideTo(3)">{{ store.skipWordDict.name }}</div>
</div>
</header>
<div class="side-content">
<swiper :initial-slide="tabIndex" @swiper="e=>swiperIns0 = e" class="mySwiper" :allow-touch-move="false">
<swiper-slide>
<div class="page0">
<header>
<div class="dict-name">{{ store.currentDict.chapterIndex + 1 }}.</div>
</header>
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.currentDict,store.currentDict.chapterIndex,e)"
:isActive="store.sideIsOpen && tabIndex === 0"
:list="store.currentDict.chapterWords[store.currentDict.chapterIndex]??[]"
:activeIndex="dictActiveIndex"/>
<footer v-if="![DictType.customDict,DictType.publicDict].includes(store.current.dictType)">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.currentDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
</swiper-slide>
<swiper-slide>
<div class="page0">
<header>
<div class="dict-name">总词数{{ store.newWordDict.originWords.length }}</div>
</header>
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.newWordDict,store.newWordDict.chapterIndex,e)"
:isActive="store.sideIsOpen && tabIndex === 1"
:list="store.newWordDict.words"
:activeIndex="newWordDictActiveIndex"/>
<footer v-if="store.current.dictType !== DictType.newDict && store.newWordDict.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.newWordDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
</swiper-slide>
<swiper-slide>
<div class="page0">
<header>
<a href="" target="_blank"></a>
<div class="dict-name">总词数{{ store.wrongWordDict.originWords.length }}</div>
</header>
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.wrongWordDict,store.wrongWordDict.chapterIndex,e)"
:isActive="store.sideIsOpen && tabIndex === 2"
:list="store.wrongWordDict.words"
:activeIndex="wrongWordDictActiveIndex"/>
<footer
v-if="store.current.dictType !== DictType.wrongDict && store.wrongWordDict.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.wrongWordDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
</swiper-slide>
<swiper-slide>
<div class="page0">
<header>
<div class="dict-name">总词数{{ store.skipWordDict.originWords.length }}</div>
</header>
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.skipWordDict,store.skipWordDict.chapterIndex,e)"
:isActive="store.sideIsOpen && tabIndex === 3"
:list="store.skipWordDict.words"
:activeIndex="skipWordDictActiveIndex"/>
<footer v-if="store.current.dictType !== DictType.skipDict && store.skipWordDict.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.skipWordDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
</swiper-slide>
</swiper>
</div>
</div>
</Transition>
</template>
<style scoped lang="scss">
@import "@/assets/css/colors";
.menu {
position: fixed;
right: 20rem;
top: 20rem;
}
.side {
$width: 20vw;
position: absolute;
left: calc(100% + $space);
width: $width;
background: var(--color-second-bg);
//background: white;
height: 100%;
display: flex;
flex-direction: column;
transition: all .3s;
//transform: rotate(-90deg);
//transform-origin: 0 0;
z-index: 1;
$header-height: 40rem;
& > header {
height: $header-height;
position: relative;
display: flex;
align-items: center;
.tabs {
padding: 10rem 20rem;
justify-content: flex-end;
width: 100%;
display: flex;
align-items: flex-end;
border-bottom: 1px solid #e1e1e1;
gap: 15rem;
font-size: 14rem;
color: gray;
.tab {
cursor: pointer;
&.active {
font-size: 16rem;
color: rgb(36, 127, 255);
font-weight: bold;
}
}
}
.close {
cursor: pointer;
position: absolute;
right: 20rem;
}
}
.side-content {
height: calc(100% - $header-height);
.mySwiper {
height: 100%;
}
}
footer {
padding-right: $space;
height: 50rem;
align-items: center;
}
.page0 {
height: 100%;
display: flex;
flex-direction: column;
header {
padding: 0 $space;
height: $header-height;
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 18rem;
color: black;
}
.word-list {
flex: 1;
padding-bottom: $space;
}
}
.page1 {
height: 100%;
display: flex;
flex-direction: column;
header {
padding: 0 $space;
height: $header-height;
position: relative;
display: flex;
align-items: center;
gap: 10rem;
font-size: 18rem;
color: white;
}
}
}
</style>

View File

@@ -47,7 +47,6 @@ watch(() => settingStore.showToolbar, n => {
{{ store.dictTitle }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
</div>
<div class="options">
<Tooltip title="开关默写模式">
<IconWrapper>
<Icon icon="majesticons:eye-off-line"
@@ -89,7 +88,7 @@ watch(() => settingStore.showToolbar, n => {
<Tooltip title="单词本">
<IconWrapper>
<Icon icon="tdesign:menu-unfold" class="menu" @click="emitter.emit(EventKey.openSide)"/>
<Icon icon="tdesign:menu-unfold" class="menu" @click="settingStore.showPanel = !settingStore.showPanel"/>
</IconWrapper>
</Tooltip>
</div>
@@ -130,15 +129,24 @@ header {
.content {
min-height: 60rem;
display: flex;
align-items: center;
justify-content: space-between;
.info {
font-size: 16rem;
padding: 6rem 10rem;
border-radius: 6rem;
color: var(--color-font-1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all .3s;
&:hover {
background: var(--color-main-active);
color: white;
}
}
.options {

View File

@@ -17,7 +17,6 @@ export interface State {
repeatNumber: number,
},
simpleWords: string[],
sideIsOpen: boolean,
load: boolean
}
@@ -130,7 +129,6 @@ export const useBaseStore = defineStore('base', {
editIndex: 0,
repeatNumber: 0,
},
sideIsOpen: false,
simpleWords: [
'a', 'an', 'of', 'and',
'i', 'my', 'you', 'your',

View File

@@ -29,7 +29,7 @@ export interface SettingState {
wordForeignFontSize: number,
wordTranslateFontSize: number,
},
showPanel: boolean,
theme: string,
}
@@ -58,6 +58,7 @@ export const useSettingStore = defineStore('setting', {
showNearWord: true,
ignoreCase: true,
allowWordTip: true,
showPanel: true,
fontSize: {
articleForeignFontSize: 48,
articleTranslateFontSize: 20,

View File

@@ -1,43 +0,0 @@
// ==UserScript==
// @name Youtube新标签页打开
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Youtube新标签页打开.
// @author You
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @license MIT
// @grant none
// ==/UserScript==
(function () {
'use strict';
function load() {
console.log('load1')
setTimeout(() => {
console.log('load2')
window.addEventListener('click', function (e) {
let list = e.target.classList
let isStop = Array.from(list).some(v => v.includes('preview'))
if (isStop && location.pathname !== '/watch') {
event.stopPropagation()
}
}, true);
const ele = document.querySelector('#media-container-link')
ele.setAttribute("target", "_blank");
let imgList = $('img.yt-core-image')
imgList.each(function (v) {
let a = this.parentNode.parentNode
a.setAttribute("target", "_blank");
})
})
}
// window.addEventListener('load', load)
//火狐不触发load事件
setTimeout(load, 3000)
})();

View File

@@ -3,7 +3,6 @@ import mitt from 'mitt'
export const emitter = mitt()
export const EventKey = {
resetWord: 'resetWord',
openSide: 'openSide',
openStatModal: 'openStatModal',
closeOther: 'closeOther',
keydown: 'keydown',