work on page form; add image selector

This commit is contained in:
bkfox
2024-03-16 06:00:15 +01:00
parent c74ec6fb16
commit eb5bdcf167
29 changed files with 611 additions and 174 deletions

View File

@ -86,3 +86,8 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.container:empty {
display: none;
}
.header-cover {
display: flex;
flex-direction: column;
}

View File

@ -138,6 +138,11 @@
}
// ---- panels
.panels {
.panel:not(.active) { display: none; }
}
// ---- button
@mixin button {
.button, a.button, button.button {
@ -701,3 +706,40 @@
overflow: hidden;
}
}
/// ----------------
.a-select-file {
> *:not(:last-child) {
margin-bottom: v.$mp-3;
}
.upload-preview {
max-width: 100%;
}
.a-select-file-list {
max-height: 30rem;
overflow-y: auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: v.$mp-3;
}
.file-preview {
width: 100%;
overflow: hidden;
&:hover {
box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
}
&.active {
box-shadow: 0em 0em 1em rgba(0,0,0,0.4);
}
img {
width: 100%;
max-height: 10rem;
}
}
}

View File

@ -1,5 +1,7 @@
@use "./vars" as v;
.text-light { weight: 400; color: var(--text-color-light); }
// ---- layout
.align-left { text-align: left; justify-content: left; }
.align-right { text-align: right; justify-content: right; }
@ -7,6 +9,7 @@
.clear-left { clear: left !important }
.clear-right { clear: right !important }
.clear-both { clear: both !important }
.clear-unset { clear: unset !important }
.d-inline { display: inline !important; }
.d-block { display: block !important; }
@ -20,6 +23,7 @@
.ws-nowrap { white-space: nowrap; }
// ---- grid
@mixin grid {
display: grid;

View File

@ -5,6 +5,7 @@
// ---- main theme & layout
.page {
padding-bottom: 5rem;

View File

@ -0,0 +1,124 @@
<template>
<div class="a-select-file">
<div class="a-select-file-list" ref="list">
<div class="flex-column file-preview">
<div class="field flex-grow-1" v-if="!uploadFile">
<label class="label">{{ uploadLabel }}</label>
<input type="file" @change="previewFile"/>
</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>
</span>
</button>
<button class="button float-right" @click="doUpload">
<span class="icon">
<i class="fa fa-upload"></i>
</span>
Upload
</button>
</div>
</div>
<div v-if="prevUrl">
<a href="#" @click="load(prevUrl)">
{{ prevLabel }}
</a>
</div>
<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>
</div>
</template>
<div v-if="nextUrl">
<a href="#" @click="load(nextUrl)">
{{ nextLabel }}
</a>
</div>
</div>
<div class="a-select-footer">
<slot name="footer" :item="item" :items="items"></slot>
</div>
</div>
</template>
<script>
import {getCsrf} from "../model"
export default {
props: {
name: { type: String },
prevLabel: { type: String, default: "Prev" },
nextLabel: { type: String, default: "Next" },
listUrl: { type: String },
uploadLabel: { type: String, default: "Upload a file" },
},
data() {
return {
item: null,
items: [],
uploadFile: null,
uploadUrl: null,
uploadFieldName: null,
uploadCSRF: null,
nextUrl: "",
prevUrl: "",
}
},
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.listUrl, {
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.nextUrl = data.next
this.prevUrl = data.previous
this.items = data.results
this.$forceUpdate()
this.$refs.list.scroll(0, 0)
})
},
select(item) {
this.item = item;
},
},
mounted() {
this.load()
},
}
</script>

View File

@ -1,5 +1,6 @@
<template>
<button :title="ariaLabel"
type="button"
:aria-label="ariaLabel || label" :aria-description="ariaDescription"
@click="toggle" :class="buttonClass">
<slot name="default" :active="active">
@ -49,17 +50,21 @@ export default {
},
set(active) {
const el = document.querySelector(this.el)
if(active)
el.classList.add(this.activeClass)
else
el.classList.remove(this.activeClass)
if(this.el) {
const el = document.querySelector(this.el)
if(active)
el.classList.add(this.activeClass)
else
el.classList.remove(this.activeClass)
}
this.active = active
if(active)
this.resetGroup()
},
resetGroup() {
if(!this.groupClass)
return
const els = document.querySelectorAll("." + this.groupClass)
for(var el of els)
if(el != this.$el)

View File

@ -12,13 +12,15 @@ import ASoundItem from './ASoundItem.vue'
import ASwitch from './ASwitch.vue'
import AStatistics from './AStatistics.vue'
import AStreamer from './AStreamer.vue'
import ASelectFile from "./ASelectFile.vue"
/**
* Core components
*/
export const base = {
AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
AProgress, ASoundItem, ASwitch
AProgress, ASoundItem, ASwitch,
ASelectFile,
}
export default base

View File

@ -30,6 +30,7 @@ export default class Live {
response.ok ? response.json()
: Promise.reject(response)
).then(data => {
data = data.results
data.forEach(item => {
if(item.start) item.start = new Date(item.start)
if(item.end) item.end = new Date(item.end)