Removed event bus and switched to vuex. Implemented vue dropzone.
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
// filedropzone_controller.js
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import axios from 'axios';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.addEventListener('dropzone:connect', this._onConnect);
|
||||
this.element.addEventListener('dropzone:change', this._onChange);
|
||||
this.element.addEventListener('dropzone:clear', this._onClear);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
// You should always remove listeners when the controller is disconnected to avoid side-effects
|
||||
this.element.removeEventListener('dropzone:connect', this._onConnect);
|
||||
this.element.removeEventListener('dropzone:change', this._onChange);
|
||||
this.element.removeEventListener('dropzone:clear', this._onClear);
|
||||
}
|
||||
|
||||
_onConnect(event) {
|
||||
// The dropzone was just created
|
||||
console.info('onconnect');
|
||||
}
|
||||
|
||||
_onChange(event) {
|
||||
// The dropzone just changed
|
||||
console.info('onchange');
|
||||
axios.post(document.URL, new FormData(event.target.closest('form')), {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}).then(() => {
|
||||
event.target.querySelector('.dropzone-preview-button').click();
|
||||
window.EventBus.$emit('fileUploaded');
|
||||
})
|
||||
}
|
||||
|
||||
_onClear(event) {
|
||||
// The dropzone has just been cleared
|
||||
console.info('onclear');
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@ export default {
|
||||
})
|
||||
},
|
||||
bookUpdateProgress(bookId, progress) {
|
||||
console.info(bookId);
|
||||
console.info(progress);
|
||||
return axios.post('/progress/update', {
|
||||
bookId: bookId,
|
||||
progress: progress
|
||||
|
||||
20
assets/js/api/file.js
Normal file
20
assets/js/api/file.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
deleteFile: function (fileId) {
|
||||
return axios.get(window.location.origin + '/file/delete/' + fileId);
|
||||
},
|
||||
getFiles: function (bookId) {
|
||||
return axios.get(this.getFilesEndpoint(bookId), {
|
||||
headers: {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
});
|
||||
},
|
||||
getFilesEndpoint: function (bookId) {
|
||||
if (bookId) {
|
||||
return `/api/books/${bookId}/files`;
|
||||
}
|
||||
return `/api/files`;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
window.EventBus = new Vue();
|
||||
export const EventBus = window.EventBus;
|
||||
@@ -25,54 +25,63 @@
|
||||
<td class="text-end">{{ file.file_size_human }}</td>
|
||||
<td>{{ file.extension }}</td>
|
||||
<td><a :href="'/file/'+ file.id" class="link-secondary">download</a></td>
|
||||
<td><a :href="'/file/delete/'+file.id" @click.prevent="deleteFile(file.id)" class="link-danger">remove</a>
|
||||
<td><a @click.prevent="deleteFile(file.id)" class="link-danger cursor-pointer">remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<vue-dropzone v-if="bookId"
|
||||
ref="myVueDropzone"
|
||||
id="dropzone"
|
||||
:options="dropzoneOptions"
|
||||
:useCustomSlot="true"
|
||||
@vdropzone-success="vdropzoneSuccess"
|
||||
>
|
||||
<div class="dropzone-custom-content">
|
||||
<h3 class="dropzone-custom-title">Drag and drop to upload content!</h3>
|
||||
<div class="subtitle">
|
||||
...or click to select a file from your computer
|
||||
</div>
|
||||
</div>
|
||||
</vue-dropzone>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import {EventBus} from "../event-bus";
|
||||
import vue2Dropzone from "vue2-dropzone";
|
||||
import {mapActions, mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'Files',
|
||||
components: {
|
||||
EventBus
|
||||
vueDropzone: vue2Dropzone
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
files: []
|
||||
dropzoneOptions: {
|
||||
capture: 'image/',
|
||||
url: '/book/' + this.bookId,
|
||||
thumbnailWidth: 150,
|
||||
maxFilesize: 50.5,
|
||||
acceptedFiles: '.pdf, .epub, .mobi'
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
bookId: {type: Number, default: null}
|
||||
},
|
||||
mounted() {
|
||||
this.getFiles();
|
||||
this.getFiles(this.bookId);
|
||||
},
|
||||
created() {
|
||||
window.EventBus.$on('fileUploaded', this.getFiles);
|
||||
computed: {
|
||||
...mapState('filemodule', ['files'])
|
||||
},
|
||||
methods: {
|
||||
deleteFile: function (fileId) {
|
||||
axios.get(window.location.origin + '/file/delete/' + fileId).then(() => this.getFiles())
|
||||
},
|
||||
getFiles: function () {
|
||||
axios.get(this.getFilesEndpoint(), {
|
||||
headers: {
|
||||
'accept': 'application/json'
|
||||
}
|
||||
}).then(response => this.files = response.data)
|
||||
},
|
||||
getFilesEndpoint: function () {
|
||||
if (this.bookId) {
|
||||
return `/api/books/${this.bookId}/files`;
|
||||
}
|
||||
return `/api/files`;
|
||||
...mapActions('filemodule', ['getFiles', 'deleteFile']),
|
||||
vdropzoneSuccess(file) {
|
||||
this.$refs.myVueDropzone.removeFile(file);
|
||||
this.getFiles(this.bookId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {EventBus} from "../event-bus";
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
import {TURN_ON_EDIT_MODE, TURN_OFF_EDIT_MODE} from '../store/mutation-types'
|
||||
|
||||
export default {
|
||||
name: 'Progresseditor',
|
||||
components: {
|
||||
EventBus
|
||||
},
|
||||
props: {
|
||||
totalPages: Number,
|
||||
readPages: Number,
|
||||
|
||||
@@ -2,13 +2,15 @@ import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import booksmodule from './modules/books';
|
||||
import bookprogressmodule from './modules/bookprogress';
|
||||
import filemodule from './modules/filemodule';
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules:{
|
||||
booksmodule,
|
||||
bookprogressmodule
|
||||
bookprogressmodule,
|
||||
filemodule
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
68
assets/js/store/modules/filemodule.js
Normal file
68
assets/js/store/modules/filemodule.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import FileApi from "../../api/file";
|
||||
import {
|
||||
REMOVE_FILE_FROM_LIST,
|
||||
FETCHING_FILES,
|
||||
FETCHING_FILES_SUCCESS,
|
||||
FETCHING_FILES_ERROR
|
||||
} from '../mutation-types.js'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
isLoading: false,
|
||||
error: null,
|
||||
files: []
|
||||
},
|
||||
getters: {
|
||||
isLoading(state) {
|
||||
return state.isLoading;
|
||||
},
|
||||
hasError(state) {
|
||||
return state.error !== null;
|
||||
},
|
||||
error(state) {
|
||||
return state.error;
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
[FETCHING_FILES](state) {
|
||||
state.isLoading = true;
|
||||
state.error = null;
|
||||
},
|
||||
[FETCHING_FILES_SUCCESS](state, files) {
|
||||
state.isLoading = false;
|
||||
state.error = null;
|
||||
state.files = files;
|
||||
},
|
||||
[FETCHING_FILES_ERROR](state, error) {
|
||||
state.isLoading = false;
|
||||
state.error = error;
|
||||
state.files = [];
|
||||
},
|
||||
[REMOVE_FILE_FROM_LIST](state, fileId) {
|
||||
|
||||
const index = state.files.findIndex(file => file.id === fileId);
|
||||
state.files.splice(index, 1);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async deleteFile({commit}, fileId) {
|
||||
try {
|
||||
await FileApi.deleteFile(fileId);
|
||||
commit(REMOVE_FILE_FROM_LIST, fileId)
|
||||
} catch (error) {
|
||||
commit(FETCHING_FILES_ERROR, error);
|
||||
}
|
||||
},
|
||||
async getFiles({commit}, bookId) {
|
||||
commit(FETCHING_FILES);
|
||||
try {
|
||||
let response = await FileApi.getFiles(bookId);
|
||||
commit(FETCHING_FILES_SUCCESS, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
commit(FETCHING_FILES_ERROR, error);
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -3,5 +3,10 @@ export const
|
||||
UPDATING_PROGRESS_SUCCESS = "UPDATING_PROGRESS_SUCCESS",
|
||||
UPDATING_PROGRESS_ERROR = "UPDATING_PROGRESS_ERROR",
|
||||
TURN_ON_EDIT_MODE = "TURN_ON_EDIT_MODE",
|
||||
TURN_OFF_EDIT_MODE = "TURN_OFF_EDIT_MODE"
|
||||
TURN_OFF_EDIT_MODE = "TURN_OFF_EDIT_MODE",
|
||||
|
||||
FETCHING_FILES = "FETCHING_FILES",
|
||||
FETCHING_FILES_SUCCESS = "FETCHING_FILES_SUCCESS",
|
||||
FETCHING_FILES_ERROR = "FETCHING_FILES_ERROR",
|
||||
REMOVE_FILE_FROM_LIST = "REMOVE_FILE_FROM_LIST"
|
||||
;
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "~bootstrap";
|
||||
@import "vue2-dropzone/dist/vue2Dropzone.min.css";
|
||||
|
||||
body {
|
||||
//background-color: lightblue !important;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"bootstrap": "^5.1.3",
|
||||
"popper": "^1.0.1",
|
||||
"vue2-dropzone": "^3.6.0",
|
||||
"vuex": "^3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,16 +103,14 @@ class BookController extends AbstractController
|
||||
): Response {
|
||||
$fileForm = $this->createForm(FileType::class);
|
||||
$fileForm->handleRequest($request);
|
||||
|
||||
if ($fileForm->isSubmitted() && $fileForm->isValid()) {
|
||||
/** @var UploadedFile[] $ebooks */
|
||||
$ebook = $request->files->get('file')['file'];
|
||||
if ($fileForm->isSubmitted()) {
|
||||
/** @var UploadedFile $ebook */
|
||||
$ebook = $request->files->get('file');
|
||||
$fileService->saveFile($ebook, $book);
|
||||
}
|
||||
|
||||
return $this->renderForm('book/show.html.twig', [
|
||||
return $this->render('book/show.html.twig', [
|
||||
'book' => $book,
|
||||
'file_form' => $fileForm
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,8 @@ class BookType extends AbstractType
|
||||
'required' => false,
|
||||
'attr' => ['accept' => ".pdf, .epub, .mobi"]
|
||||
])
|
||||
// ->add('cover', FileType::class, ['mapped' => false, 'data_class' => null, 'required' => false])
|
||||
->add('cover_url', TextType::class, ['mapped' => false, 'help' => 'Fill in the field with a link to a cover image to use it as a cover for the book.', 'label'=> 'Cover url', 'required' => false])
|
||||
->add('cover', DropzoneType::class, ['mapped' => false, 'data_class' => null, 'required' => false, 'attr' => ['accept' => "image/*", 'placeholder' => 'Drag and drop or browse']])
|
||||
->add('cover', FileType::class, ['mapped' => false, 'data_class' => null, 'required' => false, 'attr' => ['accept' => "image/*"]])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ class FileType extends AbstractType
|
||||
'data_class' => null,
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
'data-controller' => 'filedropzone',
|
||||
'accept' => ".pdf, .epub, .mobi",
|
||||
'placeholder' => 'Drag and drop or browse'
|
||||
]
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
<files :book-id="{{ book.id }}"></files>
|
||||
</div>
|
||||
|
||||
{{ form_start(file_form) }}
|
||||
{{ form_widget(file_form) }}
|
||||
{{ form_end(file_form) }}
|
||||
{# {{ form_start(file_form) }}#}
|
||||
{# {{ form_widget(file_form) }}#}
|
||||
{# {{ form_end(file_form) }}#}
|
||||
|
||||
<a href="{{ path('app_book_index') }}">back to list</a>
|
||||
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -3188,6 +3188,11 @@ domutils@^2.5.2, domutils@^2.8.0:
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
dropzone@^5.5.1:
|
||||
version "5.9.3"
|
||||
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.9.3.tgz#b3070ae090fa48cbc04c17535635537ca72d70d6"
|
||||
integrity sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA==
|
||||
|
||||
duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
||||
@@ -7426,6 +7431,13 @@ vue-template-es2015-compiler@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue2-dropzone@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vue2-dropzone/-/vue2-dropzone-3.6.0.tgz#b4bb4b64de1cbbb3b88f04b24878e06780a51546"
|
||||
integrity sha512-YXC1nCWIZvfa98e/i6h+EshZCkFSxFEh0Sxr9ODfThAPPDVhAzLLlz/4XIx0NGO1QeSy6htwSstte47R7vVhLQ==
|
||||
dependencies:
|
||||
dropzone "^5.5.1"
|
||||
|
||||
vue@^2.5:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
|
||||
|
||||
Reference in New Issue
Block a user