upload selector improvements
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<component :is="tag" @click="call" :class="buttonClass">
|
||||
<component :is="tag" @click.capture.stop="call" type="button" :class="buttonClass">
|
||||
<span v-if="promise && runIcon">
|
||||
<i :class="runIcon"></i>
|
||||
</span>
|
||||
@ -27,6 +27,8 @@ export default {
|
||||
data: Object,
|
||||
//! Action method, by default, `POST`
|
||||
method: { type: String, default: 'POST'},
|
||||
//! If provided open confirmation box before proceeding
|
||||
confirm: { type: String, default: ''},
|
||||
//! Action url
|
||||
url: String,
|
||||
//! Extra request options
|
||||
@ -60,6 +62,9 @@ export default {
|
||||
call() {
|
||||
if(this.promise || !this.url)
|
||||
return
|
||||
if(this.confirm && !confirm(this.confirm))
|
||||
return
|
||||
|
||||
const options = Model.getOptions({
|
||||
...this.fetchOptions,
|
||||
method: this.method,
|
||||
|
@ -1,27 +1,29 @@
|
||||
<template>
|
||||
<div class="a-select-file">
|
||||
<div :class="['a-select-file-list', listClass]" ref="list">
|
||||
<div class="flex-column">
|
||||
<div ref="list" :class="['a-select-file-list', listClass]">
|
||||
<!-- upload -->
|
||||
<form ref="uploadForm" class="flex-column" v-if="state == STATE.DEFAULT">
|
||||
<div class="field flex-grow-1" v-if="!uploadFile">
|
||||
<label class="label">{{ uploadLabel }}</label>
|
||||
<input type="file" @change="previewFile"/>
|
||||
<input type="file" ref="uploadFile" :name="uploadFieldName" @change="onSubmit"/>
|
||||
</div>
|
||||
<slot name="upload-preview" :item="uploadFile"></slot>
|
||||
<div v-if="uploadFile">
|
||||
<button class="button secondary" @click="removeUpload">
|
||||
<span class="icon">
|
||||
<i class="fa fa-trash"></i>
|
||||
<div class="flex-grow-1">
|
||||
<slot name="upload-form"></slot>
|
||||
</div>
|
||||
</form>
|
||||
<div class="flex-column" v-else>
|
||||
<slot name="upload-preview" :upload="upload"></slot>
|
||||
<div class="flex-row">
|
||||
<progress :max="upload.total" :value="upload.loaded"/>
|
||||
<button type="button" class="button small square ml-2" @click="uploadAbort">
|
||||
<span class="icon small">
|
||||
<i class="fa fa-close"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button class="button float-right" @click="doUpload">
|
||||
<span class="icon">
|
||||
<i class="fa fa-upload"></i>
|
||||
</span>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tiles -->
|
||||
<div v-if="prevUrl">
|
||||
<a href="#" @click="load(prevUrl)">
|
||||
{{ prevLabel }}
|
||||
@ -30,7 +32,7 @@
|
||||
|
||||
<template v-for="item in items" v-bind:key="item.id">
|
||||
<div :class="['file-preview', this.item && item.id == this.item.id && 'active']" @click="select(item)">
|
||||
<slot :item="item"></slot>
|
||||
<slot :item="item" :load="load" :lastUrl="lastUrl"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -55,55 +57,37 @@ export default {
|
||||
prevLabel: { type: String, default: "Prev" },
|
||||
nextLabel: { type: String, default: "Next" },
|
||||
listUrl: { type: String },
|
||||
|
||||
uploadUrl: { type: String },
|
||||
uploadFieldName: { type: String, default: "file" },
|
||||
uploadLabel: { type: String, default: "Upload a file" },
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
STATE: {
|
||||
DEFAULT: 0,
|
||||
UPLOADING: 1,
|
||||
},
|
||||
|
||||
state: 0,
|
||||
|
||||
item: null,
|
||||
items: [],
|
||||
uploadFile: null,
|
||||
uploadUrl: null,
|
||||
uploadFieldName: null,
|
||||
uploadCSRF: null,
|
||||
nextUrl: "",
|
||||
prevUrl: "",
|
||||
lastUrl: "",
|
||||
|
||||
upload: {},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
previewFile(event) {
|
||||
const [file] = event.target.files
|
||||
this.uploadFile = file && {
|
||||
file: file,
|
||||
src: URL.createObjectURL(file)
|
||||
}
|
||||
},
|
||||
|
||||
removeUpload() {
|
||||
this.uploadFile = null;
|
||||
},
|
||||
|
||||
doUpload() {
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.uploadFile.file)
|
||||
formData.append('original_filename', this.uploadFile.file.name)
|
||||
formData.append('csrfmiddlewaretoken', getCsrf())
|
||||
fetch(this.uploadUrl, {
|
||||
method: "POST",
|
||||
body: formData
|
||||
}).then(
|
||||
() => {
|
||||
this.uploadFile = null;
|
||||
this.load()
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
load(url) {
|
||||
fetch(url || this.listUrl).then(
|
||||
response => response.ok ? response.json() : Promise.reject(response)
|
||||
).then(data => {
|
||||
this.lastUrl = url
|
||||
this.nextUrl = data.next
|
||||
this.prevUrl = data.previous
|
||||
this.items = data.results
|
||||
@ -116,6 +100,56 @@ export default {
|
||||
select(item) {
|
||||
this.item = item;
|
||||
},
|
||||
|
||||
// ---- upload
|
||||
uploadAbort() {
|
||||
this.upload.request && this.upload.request.abort()
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
const [file] = this.$refs.uploadFile.files
|
||||
if(!file)
|
||||
return
|
||||
this._setUploadFile(file)
|
||||
|
||||
const req = new XMLHttpRequest()
|
||||
req.open("POST", this.uploadUrl || this.listUrl)
|
||||
req.upload.addEventListener("progress", (e) => this.onUploadProgress(e))
|
||||
req.addEventListener("load", (e) => this.onUploadDone(e, true))
|
||||
req.addEventListener("abort", (e) => this.onUploadDone(e))
|
||||
req.addEventListener("error", (e) => this.onUploadDone(e))
|
||||
|
||||
const formData = new FormData(this.$refs.uploadForm);
|
||||
formData.append('csrfmiddlewaretoken', getCsrf())
|
||||
req.send(formData)
|
||||
|
||||
this._resetUpload(this.STATE.UPLOADING, false, req)
|
||||
},
|
||||
|
||||
onUploadProgress(event) {
|
||||
this.upload.loaded = event.loaded
|
||||
this.upload.total = event.total
|
||||
},
|
||||
|
||||
onUploadDone(reload=false) {
|
||||
this._resetUpload(this.STATE.DEFAULT, true)
|
||||
reload && this.load()
|
||||
},
|
||||
|
||||
_setUploadFile(file) {
|
||||
this.upload.file = file
|
||||
this.upload.fileURL = file && URL.createObjectURL(file)
|
||||
},
|
||||
|
||||
_resetUpload(state, resetFile=false, request=null) {
|
||||
this.state = state
|
||||
this.upload.loaded = 0
|
||||
this.upload.total = 0
|
||||
this.upload.request = request
|
||||
if(resetFile)
|
||||
this.upload.file = null
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import AActionButton from './AActionButton'
|
||||
import AAutocomplete from './AAutocomplete'
|
||||
import ACarousel from './ACarousel'
|
||||
import ADropdown from "./ADropdown"
|
||||
@ -34,5 +35,5 @@ export const admin = {
|
||||
|
||||
export const dashboard = {
|
||||
...base,
|
||||
ASelectFile, AModal, APlaylistEditor,
|
||||
AActionButton, ASelectFile, AModal, APlaylistEditor,
|
||||
}
|
||||
|
@ -151,18 +151,19 @@
|
||||
.button, a.button, button.button {
|
||||
font-size: v.$text-size;
|
||||
display: inline-block;
|
||||
padding: v.$mp-2;
|
||||
border: none; //1px var(--button-fg) solid;
|
||||
padding: v.$mp-2e;
|
||||
border: none;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
// font-size: v.$text-size-medium;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
color: var(--button-fg);
|
||||
background-color: var(--button-bg);
|
||||
|
||||
&.square { min-width: 2.5rem; }
|
||||
&.smaller { font-size: v.$text-size-smaller; }
|
||||
&.small { font-size: v.$text-size-small; }
|
||||
&.square { min-width: 2.5em; }
|
||||
&.secondary { background-color: var(--button-sec-bg); }
|
||||
|
||||
.label, label {
|
||||
@ -172,8 +173,8 @@
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
&:not(:only-child) {
|
||||
&:first-child { margin: 0 v.$mp-3 0 v.$mp-1; }
|
||||
&:last-child { margin: 0 v.$mp-3 0 v.$mp-1; }
|
||||
&:first-child { margin: 0 v.$mp-3e 0 v.$mp-1e; }
|
||||
&:last-child { margin: 0 v.$mp-3e 0 v.$mp-1e; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,13 @@
|
||||
@use "./vars" as v;
|
||||
|
||||
// ---- text
|
||||
.text-light { weight: 400; color: var(--text-color-light); }
|
||||
|
||||
.bigger { font-size: v.$text-size-bigger !important; }
|
||||
.big { font-size: v.$text-size-big !important; }
|
||||
.smaller { font-size: v.$text-size-smaller !important; }
|
||||
.small { font-size: v.$text-size-small !important; }
|
||||
|
||||
// ---- layout
|
||||
.align-left {
|
||||
text-align: left;
|
||||
@ -34,6 +40,20 @@
|
||||
.ws-nowrap { white-space: nowrap; }
|
||||
|
||||
|
||||
.height-1 { height: 1em; }
|
||||
.height-2 { height: 2em; }
|
||||
.height-3 { height: 3em; }
|
||||
.height-4 { height: 4em; }
|
||||
.height-5 { height: 5em; }
|
||||
.height-6 { height: 6em; }
|
||||
.height-7 { height: 7em; }
|
||||
.height-8 { height: 8em; }
|
||||
.height-9 { height: 9em; }
|
||||
.height-10 { height: 10em; }
|
||||
.height-15 { height: 15em; }
|
||||
.height-20 { height: 20em; }
|
||||
.height-25 { height: 25em; }
|
||||
|
||||
// ---- grid
|
||||
@mixin grid {
|
||||
display: grid;
|
||||
|
Reference in New Issue
Block a user