add library, tsconfig.base.json
This commit is contained in:
1 parent
0348f84255
commit
ffbe5fed18
8 files changed
+758
-25
No files matched your search
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<section>
|
||||
<div class="card">
|
||||
<header class="card-header" style="display: flex; flex-direction: row">
|
||||
<div class="card-header-title" style="flex-grow: 1">
|
||||
<p
|
||||
class="has-context"
|
||||
@click="
|
||||
(evt) => {
|
||||
selected = currentData
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
@contextmenu.prevent="
|
||||
(evt) => {
|
||||
selected = currentData
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ title }}
|
||||
</p>
|
||||
<div
|
||||
@click="isOpen = !isOpen"
|
||||
style="flex-grow: 1; height: 100%; cursor: pointer"
|
||||
></div>
|
||||
</div>
|
||||
<a
|
||||
v-if="isOpen"
|
||||
class="card-header-icon"
|
||||
role="button"
|
||||
@click="isList = !isList"
|
||||
>
|
||||
<b-icon :icon="isList ? 'th' : 'list-ul'"> </b-icon>
|
||||
</a>
|
||||
<a class="card-header-icon" role="button" @click="isOpen = !isOpen">
|
||||
<b-icon :icon="isOpen ? 'caret-down' : 'caret-up'"> </b-icon>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="card-content"
|
||||
:data-mode="isList ? 'list' : 'item'"
|
||||
>
|
||||
<div v-if="isList">
|
||||
<b-table
|
||||
:data="list"
|
||||
paginated
|
||||
:per-page="perPage"
|
||||
@page-change="(p) => makeList(p)"
|
||||
>
|
||||
<b-table-column field="entry" label="Entry" v-slot="props">
|
||||
<span
|
||||
:class="
|
||||
'tag clickable is-medium ' + getTagClass(props.row.entry)
|
||||
"
|
||||
@click.stop="
|
||||
(evt) => {
|
||||
selected = [props.row.entry]
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
@contextmenu.prevent="
|
||||
(evt) => {
|
||||
selected = [props.row.entry]
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ props.row.entry }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-for="t in props.row.alt || []"
|
||||
:key="t"
|
||||
:class="'tag clickable is-medium ' + getTagClass(t)"
|
||||
@click.stop="
|
||||
(evt) => {
|
||||
selected = [t]
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
@contextmenu.prevent="
|
||||
(evt) => {
|
||||
selected = [t]
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ t }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
<b-table-column field="reading" label="Pinyin" v-slot="props">
|
||||
{{ (props.row.reading || []).join(' / ') }}
|
||||
</b-table-column>
|
||||
<b-table-column field="english" label="English" v-slot="props">
|
||||
<div
|
||||
class="no-scrollbar"
|
||||
style="max-width: 40vw; max-height: 200px"
|
||||
>
|
||||
{{ (props.row.english || []).join(' / ') }}
|
||||
</div>
|
||||
</b-table-column>
|
||||
|
||||
<template slot="empty">
|
||||
<div style="position: relative; height: 120px">
|
||||
<b-loading active :is-full-page="false"></b-loading>
|
||||
</div>
|
||||
</template>
|
||||
</b-table>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<span
|
||||
v-for="t in currentData"
|
||||
:key="t"
|
||||
:class="'tag clickable is-medium ' + getTagClass(t)"
|
||||
@click.stop="
|
||||
(evt) => {
|
||||
selected = [t]
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
@contextmenu.prevent="
|
||||
(evt) => {
|
||||
selected = [t]
|
||||
$refs.context.open(evt)
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ t }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ContextMenu
|
||||
ref="context"
|
||||
:type="type"
|
||||
:entry="selected"
|
||||
:description="title + ' ' + description"
|
||||
:additional="additional"
|
||||
@quiz:added="(evt) => reload(evt.entries)"
|
||||
@quiz:removed="(evt) => reload(evt.entries)"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Ref, Prop } from 'nuxt-property-decorator'
|
||||
import { api, IEntry, IType } from '~/assets/api'
|
||||
import ContextMenu from '../ContextMenu.vue'
|
||||
|
||||
type ILibraryEntry = Pick<
|
||||
IEntry,
|
||||
'entry' | 'reading' | 'translation' | 'type'
|
||||
> & {
|
||||
originalEntry: string
|
||||
}
|
||||
|
||||
@Component<LibraryCard>({
|
||||
components: {
|
||||
ContextMenu,
|
||||
},
|
||||
watch: {
|
||||
open() {
|
||||
this.isOpen = this.open
|
||||
},
|
||||
isList() {
|
||||
this.makeList()
|
||||
},
|
||||
isOpen() {
|
||||
this.reload(this.entries)
|
||||
},
|
||||
entries() {
|
||||
this.isList = false
|
||||
this.list = []
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.reload(this.entries)
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.isOpen = this.open
|
||||
},
|
||||
})
|
||||
export default class LibraryCard extends Vue {
|
||||
@Prop() title!: string
|
||||
@Prop() entries!: string[]
|
||||
@Prop() type!: IType
|
||||
@Prop({ default: '' }) description!: string
|
||||
@Prop({ default: false }) open!: boolean
|
||||
|
||||
@Prop({ default: () => [] }) additional!: {
|
||||
name: string
|
||||
handler: () => void
|
||||
}[]
|
||||
|
||||
@Ref() context!: ContextMenu
|
||||
|
||||
isOpen = false
|
||||
isList = false
|
||||
|
||||
list: ILibraryEntry[] = this.entries.map((el) => ({
|
||||
originalEntry: el,
|
||||
entry: [el],
|
||||
reading: [],
|
||||
translation: [],
|
||||
type: this.type,
|
||||
}))
|
||||
|
||||
perPage = 5
|
||||
|
||||
selected: string[] = []
|
||||
srsLevel: {
|
||||
[entry: string]: number
|
||||
} = {}
|
||||
|
||||
readonly tagClassMap = [
|
||||
(lv: number) => (lv > 2 ? 'is-success' : ''),
|
||||
(lv: number) => (lv > 0 ? 'is-warning' : ''),
|
||||
(lv: number) => (lv === 0 ? 'is-danger' : ''),
|
||||
]
|
||||
|
||||
async reload(entries: string[]) {
|
||||
if (!this.isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
if (entries.length > 0) {
|
||||
entries = [...new Set(entries)]
|
||||
|
||||
const { result } = await api.quiz_getSrsLevel({
|
||||
entries,
|
||||
type: this.type,
|
||||
})
|
||||
|
||||
entries.map((entry) => {
|
||||
delete this.srsLevel[entry]
|
||||
})
|
||||
|
||||
result.map(({ entry, srsLevel }) => {
|
||||
this.srsLevel[entry] = srsLevel
|
||||
})
|
||||
|
||||
this.$set(this, 'srsLevel', this.srsLevel)
|
||||
this.$forceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
async makeList(p = 1) {
|
||||
const offset = (p - 1) * this.perPage
|
||||
|
||||
const listMap = new Map<number, ILibraryEntry>()
|
||||
Array(this.perPage)
|
||||
.fill(null)
|
||||
.map((_, i) => {
|
||||
const el = this.list[offset + i]
|
||||
if (el && !el.reading.length) {
|
||||
listMap.set(offset + i, el)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
[...listMap].map(([k, v]) =>
|
||||
api
|
||||
.all_getLookup({
|
||||
entry: v.originalEntry,
|
||||
type: this.type,
|
||||
forced: true,
|
||||
limit: 1,
|
||||
})
|
||||
.then((r) =>
|
||||
listMap.set(k, {
|
||||
...r.result[0]!,
|
||||
...v,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for (const [k, v] of listMap) {
|
||||
this.list = [...this.list.slice(0, k), v, ...this.list.slice(k + 1)]
|
||||
}
|
||||
|
||||
await this.reload([...listMap].flatMap(([, { entry }]) => entry))
|
||||
}
|
||||
|
||||
getTagClass(it: IEntry) {
|
||||
const srsLevel = Math.min(
|
||||
...it.entry.map((el) => this.srsLevel[el] || -Infinity)
|
||||
)
|
||||
|
||||
if (srsLevel === -Infinity) return 'is-light'
|
||||
|
||||
if (srsLevel === -1) {
|
||||
return 'is-info'
|
||||
}
|
||||
|
||||
for (const fn of this.tagClassMap) {
|
||||
const c = fn(srsLevel)
|
||||
if (c) {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
return 'is-light'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag {
|
||||
margin-right: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.card-content[data-mode='item'] {
|
||||
max-height: 400px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.has-context:hover {
|
||||
color: blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user