upload selector improvements
This commit is contained in:
parent
de858f45e8
commit
024db5f307
|
@ -2777,7 +2777,7 @@ a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-i
|
|||
#player .button, #player a.button, #player button.button, .ax .button, .ax a.button, .ax button.button {
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
padding: 0.4rem;
|
||||
padding: 0.4em;
|
||||
border: none;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
@ -2786,8 +2786,14 @@ a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-i
|
|||
color: var(--button-fg);
|
||||
background-color: var(--button-bg);
|
||||
}
|
||||
#player .button.smaller, #player a.button.smaller, #player button.button.smaller, .ax .button.smaller, .ax a.button.smaller, .ax button.button.smaller {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
#player .button.small, #player a.button.small, #player button.button.small, .ax .button.small, .ax a.button.small, .ax button.button.small {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
#player .button.square, #player a.button.square, #player button.button.square, .ax .button.square, .ax a.button.square, .ax button.button.square {
|
||||
min-width: 2.5rem;
|
||||
min-width: 2.5em;
|
||||
}
|
||||
#player .button.secondary, #player a.button.secondary, #player button.button.secondary, .ax .button.secondary, .ax a.button.secondary, .ax button.button.secondary {
|
||||
background-color: var(--button-sec-bg);
|
||||
|
@ -2799,10 +2805,10 @@ a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-i
|
|||
vertical-align: middle;
|
||||
}
|
||||
#player .button .icon:not(:only-child):first-child, #player a.button .icon:not(:only-child):first-child, #player button.button .icon:not(:only-child):first-child, .ax .button .icon:not(:only-child):first-child, .ax a.button .icon:not(:only-child):first-child, .ax button.button .icon:not(:only-child):first-child {
|
||||
margin: 0 0.6rem 0 0.2rem;
|
||||
margin: 0 0.6em 0 0.2em;
|
||||
}
|
||||
#player .button .icon:not(:only-child):last-child, #player a.button .icon:not(:only-child):last-child, #player button.button .icon:not(:only-child):last-child, .ax .button .icon:not(:only-child):last-child, .ax a.button .icon:not(:only-child):last-child, .ax button.button .icon:not(:only-child):last-child {
|
||||
margin: 0 0.6rem 0 0.2rem;
|
||||
margin: 0 0.6em 0 0.2em;
|
||||
}
|
||||
#player .button:hover, #player a.button:hover, #player button.button:hover, .ax .button:hover, .ax a.button:hover, .ax button.button:hover {
|
||||
color: var(--button-hv-fg);
|
||||
|
@ -9553,6 +9559,22 @@ a.tag:hover {
|
|||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
.bigger {
|
||||
font-size: 1.6rem !important;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.6rem !important;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
justify-content: left;
|
||||
|
@ -9621,6 +9643,58 @@ a.tag:hover {
|
|||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
|
|
@ -621,7 +621,7 @@
|
|||
.button, a.button, button.button {
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
padding: 0.4rem;
|
||||
padding: 0.4em;
|
||||
border: none;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
@ -630,8 +630,14 @@
|
|||
color: var(--button-fg);
|
||||
background-color: var(--button-bg);
|
||||
}
|
||||
.button.smaller, a.button.smaller, button.button.smaller {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.button.small, a.button.small, button.button.small {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
.button.square, a.button.square, button.button.square {
|
||||
min-width: 2.5rem;
|
||||
min-width: 2.5em;
|
||||
}
|
||||
.button.secondary, a.button.secondary, button.button.secondary {
|
||||
background-color: var(--button-sec-bg);
|
||||
|
@ -643,10 +649,10 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
.button .icon:not(:only-child):first-child, a.button .icon:not(:only-child):first-child, button.button .icon:not(:only-child):first-child {
|
||||
margin: 0 0.6rem 0 0.2rem;
|
||||
margin: 0 0.6em 0 0.2em;
|
||||
}
|
||||
.button .icon:not(:only-child):last-child, a.button .icon:not(:only-child):last-child, button.button .icon:not(:only-child):last-child {
|
||||
margin: 0 0.6rem 0 0.2rem;
|
||||
margin: 0 0.6em 0 0.2em;
|
||||
}
|
||||
.button:hover, a.button:hover, button.button:hover {
|
||||
color: var(--button-hv-fg);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -24,17 +24,25 @@
|
|||
next-label="{% translate "Show next" %}"
|
||||
ref="cover-select"
|
||||
>
|
||||
<template #upload-preview="{item}">
|
||||
<template v-if="item">
|
||||
<img :src="item.src" class="upload-preview"/>
|
||||
</template>
|
||||
<template #upload-preview="{upload}">
|
||||
<img :src="upload.fileURL" class="upload-preview blink"/>
|
||||
</template>
|
||||
<template #default="{item}">
|
||||
<div class="flex-column">
|
||||
<div class="flex-grow-1">
|
||||
<template #default="{item, load, lastUrl}">
|
||||
<div class="flex-column is-fullheight" v-if="item">
|
||||
<figure class="flex-grow-1">
|
||||
<img :src="item.file"/>
|
||||
</figure>
|
||||
<div>
|
||||
<label class="label small">[[ item.name || item.original_filename ]]</label>
|
||||
<a-action-button
|
||||
class="has-text-danger small float-right"
|
||||
icon="fa fa-trash"
|
||||
confirm="{% translate "Are you sure you want to remove this item from server?" %}"
|
||||
method="DELETE"
|
||||
:url="'{% url "api:image-detail" pk="123" %}'.replace('123', item.id)"q
|
||||
@done="load(lastUrl)">
|
||||
</a-action-button>
|
||||
</div>
|
||||
<label class="label">[[ item.name || item.original_filename ]]</label>
|
||||
</div>
|
||||
</template>
|
||||
</a-select-file>
|
||||
|
@ -62,7 +70,7 @@
|
|||
<input type="datetime-local" name="{{ field.name }}"
|
||||
value="{{ field.value|date:"Y-m-d" }}T{{ field.value|date:"H:i" }}"/>
|
||||
{% elif field.name == "content" %}
|
||||
<textarea name="{{ field.name }}" class="is-fullwidth">{{ field.value|striptags|safe }}</textarea>
|
||||
<textarea name="{{ field.name }}" class="is-fullwidth height-25">{{ field.value|striptags|safe }}</textarea>
|
||||
{% else %}
|
||||
{{ field }}
|
||||
{% endif %}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user