work on page form; add image selector
This commit is contained in:
		@ -86,3 +86,8 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
 | 
			
		||||
.container:empty {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header-cover {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ---- main theme & layout
 | 
			
		||||
 | 
			
		||||
.page {
 | 
			
		||||
    padding-bottom: 5rem;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										124
									
								
								assets/src/components/ASelectFile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								assets/src/components/ASelectFile.vue
									
									
									
									
									
										Normal 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>
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user