Skip to content

Commit

Permalink
feat: import metadata from files
Browse files Browse the repository at this point in the history
Supported extensions are epub and opf, from computer or jelu server.
  • Loading branch information
bayang committed Nov 24, 2023
1 parent f69c85e commit ee36d57
Show file tree
Hide file tree
Showing 25 changed files with 1,323 additions and 261 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \
libegl1 \
libglx0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
python3 \
python3-xdg \
binutils \
Expand Down
4 changes: 2 additions & 2 deletions src/jelu-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/jelu-ui/src/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@

.edit-modal {
margin: 30px;
width: min(600px, 90vw);
}

.event-modal {
Expand Down
21 changes: 16 additions & 5 deletions src/jelu-ui/src/components/AddBook.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { key } from '../store';
import { ObjectUtils } from "../utils/ObjectUtils";
import { StringUtils } from "../utils/StringUtils";
import AutoImportFormModalVue from "./AutoImportFormModal.vue";
import AutoImportFileModalVue from "./AutoImportFileModal.vue";
import { SeriesOrder } from "../model/Series";
import SeriesInput from "./SeriesInput.vue";
Expand Down Expand Up @@ -354,11 +355,11 @@ function createTag(item: Tag | string) {
}
}
const toggleModal = () => {
const toggleModal = (file: boolean) => {
showModal.value = !showModal.value
oruga.modal.open({
parent: this,
component: AutoImportFormModalVue,
component: file ? AutoImportFileModalVue : AutoImportFormModalVue,
trapFocus: true,
active: true,
canCancel: ['x', 'button', 'outside'],
Expand Down Expand Up @@ -468,18 +469,28 @@ let displayDatepicker = computed(() => {
<h1 class="text-2xl typewriter capitalize">
{{ t('nav.add_book') }}
</h1>
<div class="flex">
<div class="flex gap-2">
<button
v-tooltip="t('labels.auto_fill_doc')"
class="btn btn-success button is-success is-light"
class="btn btn-success button"
:disabled="store != null && !store.getters.getMetadataFetchEnabled"
@click="toggleModal"
@click="toggleModal(false)"
>
<span class="icon">
<i class="mdi mdi-auto-fix mdi-18px" />
</span>
<span>{{ t('labels.auto_fill') }}</span>
</button>
<button
v-tooltip="t('labels.auto_fill_book')"
class="btn btn-primary button"
@click="toggleModal(true)"
>
<span class="icon">
<i class="mdi mdi-file-question mdi-18px" />
</span>
<span>{{ t('labels.auto_fill') }}</span>
</button>
<svg
v-if="store != null && !store.getters.getMetadataFetchEnabled"
v-tooltip="t('labels.auto_import_disabled')"
Expand Down
285 changes: 285 additions & 0 deletions src/jelu-ui/src/components/AutoImportFileModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
<script setup lang="ts">
import { Ref, ref } from "vue";
import { useI18n } from 'vue-i18n';
import { DirectoryListing, Path } from "../model/DirectoryListing";
import { Metadata } from "../model/Metadata";
import dataService from "../services/DataService";
import MetadataDetail from "./MetadataDetail.vue";
const { t } = useI18n({
inheritLocale: true,
useScope: 'global'
})
const file = ref(null);
const uploadPercentage = ref(0);
const handleFileUpload = (event: any) => {
file.value = event.target.files[0];
dataService.getMetadataFromUploadedFile(file.value,
(event: { loaded: number; total: number }) => {
let percent = Math.round((100 * event.loaded) / event.total);
uploadPercentage.value = percent;
}).then(res => {
metadata.value = res
displayMetadata.value = true
})
.catch(e => console.log(e))
};
const fromServer = ref(false);
const directoryListing:Ref<DirectoryListing|null> = ref(null)
const directories = (root: string|undefined) => {
if (root != null) {
dataService.getDirectoryListing(root)
.then(res => {
directoryListing.value = res
})
.catch(err => console.log(err))
}
}
const emit = defineEmits(['close', 'metadataReceived']);
const displayMetadata: Ref<boolean> = ref(false)
const metadata: Ref<Metadata|null> = ref(null)
const displayForm: Ref<boolean> = ref(true)
const discard = () => {
displayForm.value = true
metadata.value = null
displayMetadata.value = false
}
const importData = () => {
emit('metadataReceived', metadata.value)
emit('close')
}
const selectPath = (elem: Path) => {
if (elem.type == 'directory') {
directories(elem.path)
} else {
dataService.getMetadataFromFile(elem.path)
.then(res => {
metadata.value = res
displayMetadata.value = true
})
.catch(err => console.log(err))
}
}
directories('/')
</script>

<template>
<section class="edit-modal">
<div class="grid justify-center justify-items-center">
<div class="mb-2">
<h1 class="text-2xl typewriter capitalize">
{{ t('labels.import_book') }}
</h1>
</div>
<div
v-if="displayMetadata && metadata != null"
class="flex flex-col items-center"
>
<MetadataDetail :metadata="metadata" />
<div
class="col-span-5 space-x-5 mt-3"
>
<button
class="btn btn-primary"
@click="importData"
>
<span class="icon">
<i class="mdi mdi-check mdi-18px" />
</span><span>{{ t('labels.import') }}</span>
</button>
<button
class="btn btn-warning"
@click="discard"
>
<span class="icon">
<i class="mdi mdi-cancel mdi-18px" />
</span><span>{{ t('labels.discard') }}</span>
</button>
</div>
</div>
<div v-else>
<div class="field">
<input
v-model="fromServer"
type="checkbox"
class="toggle toggle-primary"
>
<span class="mx-2">{{ fromServer == true ? t('labels.upload_from_server') : t('labels.upload_from_computer') }}</span>
</div>
<div
v-if="fromServer"
class="mt-3"
>
<div
v-if="directoryListing?.parent != null"
class="flex items-center gap-2"
>
<button
class="btn btn-square bg-base-100"
@click="directories(directoryListing?.parent)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 19.5v-15m0 0l-6.75 6.75M12 4.5l6.75 6.75"
/>
</svg>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"
/>
</svg> {{ directoryListing.parent }}
</div>
<div
v-for="elem of directoryListing?.directories"
:key="elem.path"
class="card card-compact box mb-2 shadow-lg shadow-base-300 hover:shadow-2xl hover:border-2 hover:border-accent w-96"
>
<div class="card-body flex-row items-center">
<span>
<svg
v-if="elem.type == 'file'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
/>
</svg>
<svg
v-if="elem.type == 'directory'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"
/>
</svg>
</span>

<span class="grow">{{ elem.name }}</span>
<button
class="btn btn-square bg-base-100 justify-self-end"
@click="selectPath(elem)"
>
<svg
v-if="elem.type == 'directory'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"
/>
</svg>
<svg
v-if="elem.type == 'file'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 12.75l6 6 9-13.5"
/>
</svg>
</button>
</div>
</div>
<div
v-if="directoryListing?.directories &&
directoryListing?.directories.length < 1"
>
<div class="flex flex-row place-content-center mt-2">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5m6 4.125l2.25 2.25m0 0l2.25 2.25M12 13.875l2.25-2.25M12 13.875l-2.25 2.25M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
/>
</svg> <span>No supported file here</span>
</div>
</div>
</div>
<div v-else>
<input
type="file"
accept="image/*,.opf,.epub,.OPF,.EPUB"
class="block file-input w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold hover:file:bg-gray-300"
@change="handleFileUpload($event)"
>
<br>
<progress
max="100"
:value.prop="uploadPercentage"
class="progress progress-primary"
/>
<br>
</div>
</div>
</div>
</section>
</template>

<style lang="scss">
</style>
Loading

0 comments on commit ee36d57

Please sign in to comment.