How to get the progress on file upload using filepond? - vue.js

I have an API endpoint which works on PUT Request to update User info such as user avatar. This API is built on Django. In my Frontend, I'm using NUXT to upload the file using FilePond, I did the following:
<template>
<section class="section">
<div class="container">
<file-pond
name="test"
ref="pond"
label-idle="Drop files here..."
v-bind:allow-multiple="true"
accepted-file-types="image/jpeg, image/png"
/>
<vs-button success #click='userinfo_put_avatar'>File upload data</vs-button>
</div>
</section>
</template>
<script>
import vueFilePond from 'vue-filepond';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
let FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview);
export default {
components: {
FilePond,
},
methods: {
async userinfo_put_avatar() {
let file = this.$refs.pond.getFiles(0)
let fileupload = new FormData();
fileupload.append('avatar', file)
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
let data = await this.$axios.put('user-info/', fileupload, config);
},
}
};
</script>
This works for me very well. But I want the feature of Filepond to show the upload status with the spinning svg, when in process of uploading and when completed show the green status.
I tried using the pond.processFile(0) but it doesnot upload the file.
I tried using the FilePond.setOptions() but it gives me an error setOptions is not a function. If this could work somehow.
I would be able to overwrite onaddfileprogress event using the following code from GITHUB ISSUE LINK
FilePond.setOptions({
instantUpload: true,
allowImagePreview: false,
server: {
url: '/images',
process: {
url: '/upload',
},
revert: '/revert',
restore: '/restore/',
load: '/load/',
},
onremovefile: function(error, file) {
if (file.serverId) {
let input = document.createElement('input');
input.type = 'hidden';
input.name = 'DeletedFilepondImages';
input.value = file.serverId;
uploadForm.appendChild(input);
}
},
onaddfilestart: function(file) {
console.log(`onaddfilestart`);
buttonForm.classList.add('filepondUpload');
buttonForm.setAttribute('disabled', 'true');
},
onaddfileprogress(file, progress) {
console.log(`onaddfileprogress`);
buttonForm.classList.remove('filepondUpload');
buttonForm.removeAttribute('disabled');
},
});
// get a reference to the input element
const filepondInput = document.querySelector(
'#filepondFileUploader input[type="file"]'
);
// create a FilePond instance at the input element location
const filepondObject = FilePond.create(filepondInput, {
maxFiles: 5,
acceptedFileTypes: ['image/*'],
labelIdle:
'<div class="image-upload__file-upload-content">Add images</div>',
files: filepondInitialFiles,
onprocessfiles() {
console.log('onprocessfiles');
buttonForm.classList.remove('filepondUpload');
buttonForm.removeAttribute('disabled');
},
});

You need to define the server prop on the component. You can use v-bind:server="myServer" and then add it to your component data.
<file-pond
name="test"
ref="pond"
v-bind:server="myServer"/>
export default {
name: 'app',
data: function() {
return {
myServer: {
// server config here
}
};
},
components: {
FilePond
}
};
If you need setOptions, you can import it from vueFilePond
import vueFilePond, { setOptions } from 'vueFilePond'

Related

How can I change the locale of a VueI18n instance in a Vue file, and have it apply to all other Vue files in the application?

I am currently trying to implement a feature where a user can select a language from a dropdown menu in a Settings page (SettingsDialog.vue), updating all of the text to match the new language. This application has multiple Vue files like a MenuBar.vue, HelpDialog.vue, each pulling from translation.ts for their English translations. However, I noticed that selecting a language from the dropdown menu only changed the elements inside my SettingsDialog.vue file, not all of the other Vue files I have.
I tried using the Vue-I18n documentation implementation of changing locale globally in the file. I was expecting for the locale of the entire application to change after selecting a language in SettingsDialog.vue, applying my English translations in translation.ts to the Menu Bar, Help Page, etc. What happened is that the translations from translation.ts only applied to the SettingsDialog.vue page, no where else.
I guess it would be helpful to add that this is an Electron application, and the Vue files in the project use Quasar. Each file does have the correct import statements.
main.ts:
// ...
window.datalayer = [];
const i18n = createI18n({
legacy: false,
locale: "",
messages,
});
createApp(App)
.use(store, storeKey)
.use(router)
.use(
createGtm({
id: process.env.VUE_APP_GTM_CONTAINER_ID ?? "GTM-DUMMY",
vueRouter: router,
enabled: false,
})
)
.use(Quasar, {
config: {
brand: {
primary: "#a5d4ad",
secondary: "#212121",
},
},
iconSet,
plugins: {
Dialog,
Loading,
},
})
.use(ipcMessageReceiver, { store })
.use(markdownItPlugin)
.use(i18n)
.mount("#app");
SettingsDialog.vue
// ...
<!-- Language Setting Card -->
<q-card flat class="setting-card">
<q-card-actions>
<div id="app" class="text-h5">{{ $t("言語") }}</div>
</q-card-actions>
<q-card-actions class="q-px-md q-py-sm bg-setting-item">
<div id="app">{{ $t("言語を選択する") }}</div>
<q-space />
<q-select
filled
v-model="locale"
dense
emit-value
map-options
options-dense
:options="[
{ value: 'ja', label: '日本語 (Japanese)' },
{ value: 'en', label: '英語 (English)' },
]"
label="Language"
>
<q-tooltip
:delay="500"
anchor="center left"
self="center right"
transition-show="jump-left"
transition-hide="jump-right"
>
Test
</q-tooltip>
</q-select>
</q-card-actions>
</q-card>
// ...
<script lang="ts">
import { useI18n } from "vue-i18n";
// ...
setup(props, { emit }) {
const { t, locale } = useI18n({ useScope: "global" });
// ...
return {
t,
locale,
// ...
};
MenuBar.vue
<template>
<q-bar class="bg-background q-pa-none relative-position">
<div
v-if="$q.platform.is.mac && !isFullscreen"
class="mac-traffic-light-space"
></div>
<img v-else src="icon.png" class="window-logo" alt="application logo" />
<menu-button
v-for="(root, index) of menudata"
:key="index"
:menudata="root"
v-model:selected="subMenuOpenFlags[index]"
:disable="menubarLocked"
#mouseover="reassignSubMenuOpen(index)"
#mouseleave="
root.type === 'button' ? (subMenuOpenFlags[index] = false) :
undefined
"
/>
// ...
<script lang="ts">
import { defineComponent, ref, computed, ComputedRef, watch } from "vue";
import { useStore } from "#/store";
import MenuButton from "#/components/MenuButton.vue";
import TitleBarButtons from "#/components/TitleBarButtons.vue";
import { useQuasar } from "quasar";
import { HotkeyAction, HotkeyReturnType } from "#/type/preload";
import { setHotkeyFunctions } from "#/store/setting";
import {
generateAndConnectAndSaveAudioWithDialog,
generateAndSaveAllAudioWithDialog,
generateAndSaveOneAudioWithDialog,
} from "#/components/Dialog";
import { useI18n } from "vue-i18n";
import messages from "../translation";
type MenuItemBase<T extends string> = {
type: T;
label?: string;
};
export type MenuItemSeparator = MenuItemBase<"separator">;
export type MenuItemRoot = MenuItemBase<"root"> & {
onClick: () => void;
subMenu: MenuItemData[];
};
export type MenuItemButton = MenuItemBase<"button"> & {
onClick: () => void;
};
export type MenuItemCheckbox = MenuItemBase<"checkbox"> & {
checked: ComputedRef<boolean>;
onClick: () => void;
};
export type MenuItemData =
| MenuItemSeparator
| MenuItemRoot
| MenuItemButton
| MenuItemCheckbox;
export type MenuItemType = MenuItemData["type"];
export default defineComponent({
name: "MenuBar",
components: {
MenuButton,
TitleBarButtons,
},
setup() {
const { t } = useI18n({
messages,
});
// ...
};
const menudata = ref<MenuItemData[]>([
{
type: "root",
label: t("ファイル"),
onClick: () => {
closeAllDialog();
},
// ...
]);
translation.ts
const messages = {
en: {
// MenuBar.vue
ファイル: "File",
エンジン: "Engine",
ヘルプ: "Help",
// SettingDialog.vue
言語: 'Language',
言語を選択する: 'Select Language',
オフ: 'OFF',
エンジンモード: 'Engine Mode',
// HelpDialog.vue
ソフトウェアの利用規約: 'test',
}
};
export default messages;
Maybe there are more problems but now I see two:
Your menudata should be computed instead of just ref. Right now you are creating a JS object and setting it label property to result of t() call. When global locale changes this object is NOT created again. It still holds same value the t() function returned the only time it was executed - when setup() was running
// correct
const menudata = computed<MenuItemData[]>(() => [
{
type: "root",
label: t("ファイル"),
onClick: () => {
closeAllDialog();
},
// ...
]);
This way whenever i18n.global.locale changes, your menudata is created again with new translation
As an alternative, set label to key and use t(label) inside the template. However computed is much more effective solution...
You don't need to pass messages to useI18n() in every component. Only to the global instance. By passing config object into a useI18n() in a component you are creating Local scope which makes no sense if you are storing all translations in a single global place anyway

Is vue2-dropzone compatible with vue3?

vue2-dropzone is working fine for vue2 but not working for vue3.
With the following code
import vue2Dropzone from 'vue2-dropzone'
import 'vue2-dropzone/dist/vue2Dropzone.min.css'
return {
dropzoneOptions: {
autoProcessQueue: false,
addRemoveLinks: true,
url: this.url,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
},
id: null,
myDropZone: null,
supervisorError: ''
}
}
I do have the following error
TypeError: Cannot read property '_c' of undefined vue3
vue3-dropzone
What you are after is vue3-dropzone.
It worked highly similar to the vue2-dropzone package that most of you may have been using with vue2. I myself am one of the contributors to the new vue3-dropzone package. I have just added the example code for those who want to Save Multiple Files at once, as shown below:
Example of Saving Multiple Files
<template>
<div>
<div v-bind="getRootProps()">
<input v-bind="getInputProps()" />
<p v-if="isDragActive">Drop the files here ...</p>
<p v-else>Drag 'n' drop some files here, or click to select files</p>
</div>
<button #click="open">open</button>
</div>
</template>
<script>
import { useDropzone } from "vue3-dropzone";
import axios from "axios";
export default {
name: "UseDropzoneDemo",
setup() {
const url = "{your_url}"; // Your url on the server side
const saveFiles = (files) => {
const formData = new FormData(); // pass data as a form
for (var x = 0; x < files.length; x++) {
// append files as array to the form, feel free to change the array name
formData.append("images[]", files[x]);
}
// post the formData to your backend where storage is processed. In the backend, you will need to loop through the array and save each file through the loop.
axios
.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.info(response.data);
})
.catch((err) => {
console.error(err);
});
};
function onDrop(acceptFiles, rejectReasons) {
saveFiles(acceptFiles); // saveFiles as callback
console.log(rejectReasons);
}
const { getRootProps, getInputProps, ...rest } = useDropzone({ onDrop });
return {
getRootProps,
getInputProps,
...rest,
};
},
};
</script>
As stated in this post: https://github.com/rowanwins/vue-dropzone/issues/578
It looks like vue-dropzone does not support Vue3 as of right now, I mean the mantainer was already struggling to manage the vue 2 and asked for help so it seems legit.
Maybe give a look to this vue3 one: https://github.com/Yaxian/vue3-dropzone
Here is a list of available alternatives: https://github.com/vuejs/awesome-vue#drag-and-drop
well, we're using this package for our production builds:
Vue3 Library Component for drag’n’drop file uploads with image previews.
https://github.com/darknessnerd/drop-zone

Howler.js: sound doesn't load

Following basic example in the docs, yet sound doesn't play. I see the mp3 file in Network tab but it's 90KB instead of 5MB so I suppose it doesn't load properly.
I tried different paths: src: ['#/assets/audios/test.mp3'], `src: ['../assets/audios/test.mp3']. Nothing works. No console error. Why is it not working?
<template>
<div class="container">
<button #click="play">
PLAY
</button>
</div>
</template>
<script>
import { Howl, Howler } from 'howler'
export default {
data () {
return {
sound: ''
}
},
mounted () {
this.sound = new Howl({
src: ['test.mp3']
})
},
methods: {
play () {
this.sound.play()
}
}
}
</script>
It sounds like you're trying to load an asset URL for src.
The asset URL needs to be required so that Webpack resolves the actual URL to the file.
Errors are silently ignored, but you can set onloaderror to handle them.
export default {
mounted () {
this.sound = new Howl({
// 1
src: [
require('#/assets/audios/test.mp3')
],
// 2
onloaderror(id, err) {
console.warn('failed to load sound file:', { id, err })
}
})
}
}

Get blank while using vue-pdf

I want to use vue-pdf to preview online pdf file. But I always got blank page and there is no error message on console. My code is
<template>
<div class="pdf">
<pdf
:src="src">
</pdf>
</div>
</template>
<script>
import pdf from 'vue-pdf'
export default {
name: 'Pdf',
components:{
pdf
},
data(){
return {
src:"http://file.dakawengu.com/file/2018-05-29/20180527-tianfeng.pdf",
}
},
mounted() {
this.src = pdf.createLoadingTask(this.src)
this.src.then(response => {
this.numPages = response.numPages
})
}
}
</script>
vue version: 2.9.6
vue-pdf version: 4.0.6
Do not re-assign variable src. Your onmounted function is re-assigning src to something else just change it to:-
.....
data(){
return {
src:"http://file.dakawengu.com/file/2018-05-29/20180527-tianfeng.pdf",
newsrc: null,
}
},
.................
........your code..............
........................
mounted() {
this.newsrc = pdf.createLoadingTask(this.src)
this.newsrc.then(response => {
this.numPages = response.numPages
})
}

How to retrieve and display a single record with Vue and axios

I have a basic CRUD rails 5.2 API with a Story model. I am building a Vuejs front end to consume it. Currently, The index view at /stories successfully pulls in data from the server. I can also add stories to the database via NewStory.vue at stories/new. I am trying now to show a single story on a page at stories/:id. The api server currently shows the single result I need at v1/stories/:id.
here is what I have at services/Api.js:
import axios from 'axios'
export default() => {
return axios.create({
baseURL: `http://localhost:3000/v1`
})
}
in StoriesService.js:
import Api from '#/services/Api'
export default {
fetchStories () {
return Api().get('stories')
},
addStory (params) {
return Api().post('stories', params)
},
getStory (params) {
return Api().get('/stories/1')
}
}
in ViewStory.vue:
<template>
<div class="stories">
<h1>Story</h1>
<div v-if="story" class="table-wrap">
<div>
<router-link v-bind:to="{ name: 'NewStory' }" class="">Add
Story</router-link>
</div>
<p>Title: {{story.attributes.title}}</p>
<p>Overview: {{story.attributes.description}}</p>
</div>
<div v-else>
The story with id:{{params}} does not exist <br><br>
<!-- <router-link v-bind:to="{ name: 'NewStory' }"
class="add_story_link">Add Story</router-link> -->
</div>
</div>
</template>
<script>
import StoriesService from '#/services/StoriesService'
export default {
name: 'story',
data () {
return {
title: '',
description: ''
}
},
mounted () {
this.getStory()
},
methods: {
async getStory (params) {
const response = await StoriesService.getStory(params)
this.story = response.data
console.log(this.story)
}
}
}
</script>
With the id of the record hard coded, in the Network tab, I see the request being made to the api and the correct record being retrieved.
However, if I change the getStory call to return Api().get('/stories/', params) I get a 304 response and can't retrieve data.
My question is how to get StoriesService.js to return localhost:3000/v1/stories/params.id, where params.id is the id of the story referenced in the url.
Currently you are not passing in any params to getStory, so you need to get them from the this.$route.params
async getStory () {
const response = await StoriesService.getStory(this.$route.params)
this.story = response.data
console.log(this.story)
}
Beside that axios only supports query string parameters so if your url looks like /stories/someId then you need to build it yourself in getStory:
getStory (params) {
return Api().get(`/stories/${params.id}`)
}
Additionally your data object is missing the story property:
data () {
return {
story: null,
title: '',
description: ''
}
},