Removed event bus and switched to vuex. Implemented vue dropzone.

This commit is contained in:
krzysiej
2022-06-15 15:19:46 +02:00
parent 5350c5c46b
commit 4536195a4c
16 changed files with 151 additions and 88 deletions

View File

@@ -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');
}
}

View File

@@ -8,8 +8,6 @@ export default {
}) })
}, },
bookUpdateProgress(bookId, progress) { bookUpdateProgress(bookId, progress) {
console.info(bookId);
console.info(progress);
return axios.post('/progress/update', { return axios.post('/progress/update', {
bookId: bookId, bookId: bookId,
progress: progress progress: progress

20
assets/js/api/file.js Normal file
View 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`;
}
}

View File

@@ -1,3 +0,0 @@
import Vue from 'vue';
window.EventBus = new Vue();
export const EventBus = window.EventBus;

View File

@@ -25,54 +25,63 @@
<td class="text-end">{{ file.file_size_human }}</td> <td class="text-end">{{ file.file_size_human }}</td>
<td>{{ file.extension }}</td> <td>{{ file.extension }}</td>
<td><a :href="'/file/'+ file.id" class="link-secondary">download</a></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> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </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> </div>
</template> </template>
<script> <script>
import axios from 'axios'; import vue2Dropzone from "vue2-dropzone";
import {EventBus} from "../event-bus"; import {mapActions, mapState} from "vuex";
export default { export default {
name: 'Files', name: 'Files',
components: { components: {
EventBus vueDropzone: vue2Dropzone
}, },
data() { data() {
return { return {
files: [] dropzoneOptions: {
capture: 'image/',
url: '/book/' + this.bookId,
thumbnailWidth: 150,
maxFilesize: 50.5,
acceptedFiles: '.pdf, .epub, .mobi'
}
} }
}, },
props: { props: {
bookId: {type: Number, default: null} bookId: {type: Number, default: null}
}, },
mounted() { mounted() {
this.getFiles(); this.getFiles(this.bookId);
}, },
created() { computed: {
window.EventBus.$on('fileUploaded', this.getFiles); ...mapState('filemodule', ['files'])
}, },
methods: { methods: {
deleteFile: function (fileId) { ...mapActions('filemodule', ['getFiles', 'deleteFile']),
axios.get(window.location.origin + '/file/delete/' + fileId).then(() => this.getFiles()) vdropzoneSuccess(file) {
}, this.$refs.myVueDropzone.removeFile(file);
getFiles: function () { this.getFiles(this.bookId);
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`;
} }
} }
} }

View File

@@ -11,15 +11,11 @@
</template> </template>
<script> <script>
import {EventBus} from "../event-bus";
import {mapActions, mapGetters} from "vuex"; import {mapActions, mapGetters} from "vuex";
import {TURN_ON_EDIT_MODE, TURN_OFF_EDIT_MODE} from '../store/mutation-types' import {TURN_ON_EDIT_MODE, TURN_OFF_EDIT_MODE} from '../store/mutation-types'
export default { export default {
name: 'Progresseditor', name: 'Progresseditor',
components: {
EventBus
},
props: { props: {
totalPages: Number, totalPages: Number,
readPages: Number, readPages: Number,

View File

@@ -2,13 +2,15 @@ import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import booksmodule from './modules/books'; import booksmodule from './modules/books';
import bookprogressmodule from './modules/bookprogress'; import bookprogressmodule from './modules/bookprogress';
import filemodule from './modules/filemodule';
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
modules:{ modules:{
booksmodule, booksmodule,
bookprogressmodule bookprogressmodule,
filemodule
} }
}) })

View 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);
}
},
}
};

View File

@@ -3,5 +3,10 @@ export const
UPDATING_PROGRESS_SUCCESS = "UPDATING_PROGRESS_SUCCESS", UPDATING_PROGRESS_SUCCESS = "UPDATING_PROGRESS_SUCCESS",
UPDATING_PROGRESS_ERROR = "UPDATING_PROGRESS_ERROR", UPDATING_PROGRESS_ERROR = "UPDATING_PROGRESS_ERROR",
TURN_ON_EDIT_MODE = "TURN_ON_EDIT_MODE", 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"
; ;

View File

@@ -1,4 +1,5 @@
@import "~bootstrap"; @import "~bootstrap";
@import "vue2-dropzone/dist/vue2Dropzone.min.css";
body { body {
//background-color: lightblue !important; //background-color: lightblue !important;

View File

@@ -28,6 +28,7 @@
"@popperjs/core": "^2.11.5", "@popperjs/core": "^2.11.5",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"popper": "^1.0.1", "popper": "^1.0.1",
"vue2-dropzone": "^3.6.0",
"vuex": "^3" "vuex": "^3"
} }
} }

View File

@@ -103,16 +103,14 @@ class BookController extends AbstractController
): Response { ): Response {
$fileForm = $this->createForm(FileType::class); $fileForm = $this->createForm(FileType::class);
$fileForm->handleRequest($request); $fileForm->handleRequest($request);
if ($fileForm->isSubmitted()) {
if ($fileForm->isSubmitted() && $fileForm->isValid()) { /** @var UploadedFile $ebook */
/** @var UploadedFile[] $ebooks */ $ebook = $request->files->get('file');
$ebook = $request->files->get('file')['file'];
$fileService->saveFile($ebook, $book); $fileService->saveFile($ebook, $book);
} }
return $this->renderForm('book/show.html.twig', [ return $this->render('book/show.html.twig', [
'book' => $book, 'book' => $book,
'file_form' => $fileForm
]); ]);
} }

View File

@@ -53,9 +53,8 @@ class BookType extends AbstractType
'required' => false, 'required' => false,
'attr' => ['accept' => ".pdf, .epub, .mobi"] '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_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/*"]])
; ;
} }

View File

@@ -22,7 +22,6 @@ class FileType extends AbstractType
'data_class' => null, 'data_class' => null,
'required' => true, 'required' => true,
'attr' => [ 'attr' => [
'data-controller' => 'filedropzone',
'accept' => ".pdf, .epub, .mobi", 'accept' => ".pdf, .epub, .mobi",
'placeholder' => 'Drag and drop or browse' 'placeholder' => 'Drag and drop or browse'
] ]

View File

@@ -70,9 +70,9 @@
<files :book-id="{{ book.id }}"></files> <files :book-id="{{ book.id }}"></files>
</div> </div>
{{ form_start(file_form) }} {# {{ form_start(file_form) }}#}
{{ form_widget(file_form) }} {# {{ form_widget(file_form) }}#}
{{ form_end(file_form) }} {# {{ form_end(file_form) }}#}
<a href="{{ path('app_book_index') }}">back to list</a> <a href="{{ path('app_book_index') }}">back to list</a>

View File

@@ -3188,6 +3188,11 @@ domutils@^2.5.2, domutils@^2.8.0:
domelementtype "^2.2.0" domelementtype "^2.2.0"
domhandler "^4.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: duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" 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" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== 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: vue@^2.5:
version "2.6.14" version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"