first draft of media picker
This commit is contained in:
@@ -6,6 +6,18 @@ namespace zero.Core.Renderer
|
||||
{
|
||||
public class MediaOptions : AbstractFieldOptions
|
||||
{
|
||||
public uint Limit { get; set; } = 1;
|
||||
|
||||
public decimal MaxFileSize { get; set; } = 10;
|
||||
|
||||
public MediaOptionsType Type { get; set; }
|
||||
|
||||
public MediaOptionsDisplay Display { get; set; }
|
||||
|
||||
public List<string> FileExtensions { get; set; } = new List<string>();
|
||||
|
||||
public bool DisallowSelect { get; set; }
|
||||
|
||||
public bool DisallowUpload { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace zero.Core.Renderer
|
||||
{
|
||||
public enum MediaOptionsDisplay
|
||||
{
|
||||
Default = 0,
|
||||
Big = 1,
|
||||
Grid = 2
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@ namespace zero.Core.Renderer
|
||||
{
|
||||
public enum MediaOptionsType
|
||||
{
|
||||
All,
|
||||
Image,
|
||||
Video
|
||||
All = 0,
|
||||
Image = 1,
|
||||
Video = 2,
|
||||
Document = 3,
|
||||
Other = 99
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div class="ui-media">
|
||||
<div class="ui-media-previews">
|
||||
<div v-for="item in items" class="ui-media-preview">
|
||||
<div class="ui-media-preview-image">
|
||||
<img v-if="item.source" :src="getPreview(item)" :alt="item.name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-media-add" v-if="canAdd">
|
||||
<ui-select-button v-if="!configuration.disallowSelect" icon="fth-folder" label="@mediapicker.select_text" description="@mediapicker.select_description" :disabled="disabled" />
|
||||
<div v-if="!configuration.disallowUpload" class="ui-media-add-upload">
|
||||
<input type="file" :accept="acceptedFileExtensions" :multiple="multiple" @change="onUpload" />
|
||||
<ui-select-button icon="fth-upload" label="@mediapicker.upload_text" :description="uploadDescription" :disabled="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { each as _each, extend as _extend, debounce as _debounce, isArray as _isArray } from 'underscore';
|
||||
import Strings from 'zero/services/strings.js';
|
||||
|
||||
const TYPES = {
|
||||
ALL: 'all',
|
||||
IMAGE: 'image',
|
||||
VIDEO: 'video',
|
||||
DOCUMENT: 'document',
|
||||
OTHER: 'other'
|
||||
};
|
||||
|
||||
const DISPLAY = {
|
||||
DEFAULT: 'default',
|
||||
BIG: 'big',
|
||||
GRID: 'grid'
|
||||
};
|
||||
|
||||
const defaultConfig = {
|
||||
// maximum media items
|
||||
limit: 1,
|
||||
// media type
|
||||
type: TYPES.ALL,
|
||||
// how the previews are displayed
|
||||
display: DISPLAY.DEFAULT,
|
||||
// whether the select button is disallowed
|
||||
disallowSelect: false,
|
||||
// whether file upload is disallowed
|
||||
disallowUpload: false,
|
||||
// allowed file extensions
|
||||
fileExtensions: [],
|
||||
// maximum file size in MiB
|
||||
maxFileSize: 10
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'uiMedia',
|
||||
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () =>
|
||||
{
|
||||
return defaultConfig;
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: [Array, Object]
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
configuration: {},
|
||||
items: []
|
||||
}),
|
||||
|
||||
watch: {
|
||||
config: {
|
||||
deep: true,
|
||||
handler: function ()
|
||||
{
|
||||
this.initialize();
|
||||
}
|
||||
},
|
||||
value: {
|
||||
deep: true,
|
||||
handler: function ()
|
||||
{
|
||||
this.initialize();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
multiple()
|
||||
{
|
||||
return this.configuration.limit > 1;
|
||||
},
|
||||
canAdd()
|
||||
{
|
||||
return this.configuration.limit - this.items.length > 0;
|
||||
},
|
||||
uploadDescription()
|
||||
{
|
||||
return this.configuration.fileExtensions.length > 0 ? this.displayedFileExtensions : '@mediapicker.upload_description';
|
||||
},
|
||||
acceptedFileExtensions()
|
||||
{
|
||||
return this.configuration.fileExtensions.join(',');
|
||||
},
|
||||
displayedFileExtensions()
|
||||
{
|
||||
return this.configuration.fileExtensions.join(', ').replace(/\./g, '');
|
||||
}
|
||||
},
|
||||
|
||||
created()
|
||||
{
|
||||
this.initialize();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
initialize()
|
||||
{
|
||||
this.configuration = _extend(defaultConfig, this.config);
|
||||
|
||||
if (_isArray(this.value))
|
||||
{
|
||||
this.items = this.value;
|
||||
}
|
||||
else if (!!this.value && typeof this.value === 'object' && this.value.source)
|
||||
{
|
||||
this.items = [this.value];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onUpload(event)
|
||||
{
|
||||
let remaining = this.configuration.limit - this.items.length;
|
||||
const files = event.target.files;
|
||||
|
||||
if (files && files.length > 0)
|
||||
{
|
||||
for (var i = 0; i < files.length; i++)
|
||||
{
|
||||
if (i >= remaining)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
this.addFile(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
|
||||
addFromLibrary(item)
|
||||
{
|
||||
this.items.push(item);
|
||||
this.update();
|
||||
},
|
||||
|
||||
|
||||
addFile(file)
|
||||
{
|
||||
var source = URL.createObjectURL(file);
|
||||
var media = {
|
||||
id: 'upload:' + Strings.guid(),
|
||||
name: file.name,
|
||||
source: source,
|
||||
size: file.size,
|
||||
mimeType: file.type
|
||||
};
|
||||
|
||||
this.items.push(media);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e)
|
||||
{
|
||||
media.source = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
|
||||
|
||||
update()
|
||||
{
|
||||
this.$emit('input', this.multiple ? this.items : this.items[0]);
|
||||
},
|
||||
|
||||
|
||||
getPreview(item)
|
||||
{
|
||||
if (!item.id || item.id.indexOf('upload:') > -1 || item.source.indexOf("http") === 0)
|
||||
{
|
||||
return item.source;
|
||||
}
|
||||
|
||||
if (item.id.indexOf("native:") > -1)
|
||||
{
|
||||
return item.source + "?width=100&height=100";
|
||||
}
|
||||
|
||||
if (!item.hasThumb)
|
||||
{
|
||||
return item.source;
|
||||
}
|
||||
|
||||
var extension = '.' + item.source.split('.').pop();
|
||||
return item.source.replace(extension, ".thumb" + extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-media
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.ui-media-add
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.ui-media-add-upload
|
||||
{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.ui-select-button + &
|
||||
{
|
||||
margin-left: var(--padding);
|
||||
}
|
||||
|
||||
input[type="file"]
|
||||
{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-media-preview
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
.ui-media-preview-image
|
||||
{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100px;
|
||||
height: 80px;
|
||||
/*background: var(--color-bg);*/
|
||||
border: 1px solid var(--color-line-light);
|
||||
padding: 5px;
|
||||
border-radius: var(--radius);
|
||||
color: var(--color-fg);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
img
|
||||
{
|
||||
border-radius: 3px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
color: transprent;
|
||||
overflow: hidden;
|
||||
font-size: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -114,7 +114,12 @@
|
||||
position: relative;
|
||||
transition: color 0.2s ease;
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
background: rgba(white, 0.5); // TODO
|
||||
background: var(--color-bg);
|
||||
|
||||
.theme-dark &
|
||||
{
|
||||
background: rgba(black, 0.5);
|
||||
}
|
||||
|
||||
& + .ui-tabs-list-item
|
||||
{
|
||||
|
||||
@@ -26,14 +26,10 @@
|
||||
<input v-model="model.email" type="email" class="ui-input" maxlength="200" :readonly="disabled" />
|
||||
</ui-property>
|
||||
<ui-property label="@application.fields.image" description="@application.fields.image_text" :required="true">
|
||||
upload
|
||||
<ui-media :config="mediaConfig" v-model="model.image" />
|
||||
</ui-property>
|
||||
<ui-property label="@application.fields.icon" description="@application.fields.icon_text" :required="true">
|
||||
upload
|
||||
</ui-property>
|
||||
<ui-property label="@application.fields.domains" description="@application.fields.domains_text" :required="true">
|
||||
<ui-input-list v-model="model.domains" :disabled="disabled" add-label="@application.fields.domains_add" />
|
||||
<p class="ui-property-help" v-localize="'@application.fields.domains_help'"></p>
|
||||
<ui-media :config="mediaConfig" v-model="model.icon" />
|
||||
</ui-property>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,6 +47,13 @@
|
||||
</aside>
|
||||
</ui-tab>
|
||||
|
||||
<ui-tab label="@application.tab_domains" class="ui-box">
|
||||
<ui-property label="@application.fields.domains" description="@application.fields.domains_text" :required="true">
|
||||
<ui-input-list v-model="model.domains" :disabled="disabled" add-label="@application.fields.domains_add" />
|
||||
<p class="ui-property-help" v-localize="'@application.fields.domains_help'"></p>
|
||||
</ui-property>
|
||||
</ui-tab>
|
||||
|
||||
<ui-tab v-if="features.length > 0" label="@application.tab_features" class="ui-box" :count="model.features.length">
|
||||
<ui-property v-for="feature in features" :label="feature.name" :description="feature.description">
|
||||
<ui-toggle :value="model.features.indexOf(feature.alias) > -1" @input="onFeatureToggle($event, feature)" />
|
||||
@@ -73,7 +76,10 @@
|
||||
actions: [],
|
||||
model: { name: null, features: [] },
|
||||
disabled: false,
|
||||
features: []
|
||||
features: [],
|
||||
mediaConfig: {
|
||||
fileExtensions: zero.config.media.defaults.images_artificial
|
||||
}
|
||||
}),
|
||||
|
||||
created()
|
||||
@@ -111,6 +117,9 @@
|
||||
|
||||
onSubmit(form)
|
||||
{
|
||||
console.info(JSON.parse(JSON.stringify(this.model)));
|
||||
return;
|
||||
|
||||
form.handle(ApplicationsApi.save(this.model)).then(response =>
|
||||
{
|
||||
console.info(response);
|
||||
@@ -160,16 +169,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/*.role-permission-toggle + .role-permission-toggle
|
||||
{
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.role .ui-box + .ui-permissions
|
||||
{
|
||||
margin-top: var(--padding);
|
||||
}*/
|
||||
</style>
|
||||
</script>
|
||||
@@ -21,6 +21,7 @@
|
||||
import AuthApi from 'zero/services/auth.js'
|
||||
import 'zero/vue.config.js'
|
||||
import 'zero/axios.config.js'
|
||||
import 'zero/zero.config.js'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
window.zero = window.zero || {};
|
||||
window.zero.config = window.zero.config || {};
|
||||
|
||||
window.zero.config.media = {
|
||||
defaults: {
|
||||
images: ['.jpg', '.jpeg', '.png', '.webp', '.svg'],
|
||||
images_natural: ['.jpg', '.jpeg', '.webp'],
|
||||
images_artificial: ['.png', '.webp', '.svg']
|
||||
}
|
||||
};
|
||||
@@ -261,6 +261,13 @@
|
||||
"title": "Pick an icon"
|
||||
},
|
||||
|
||||
"mediapicker": {
|
||||
"select_text": "Select",
|
||||
"select_description": "Choose from library",
|
||||
"upload_text": "Upload",
|
||||
"upload_description": "Upload a new file"
|
||||
},
|
||||
|
||||
"application": {
|
||||
"list": "Applications",
|
||||
"name": "Application",
|
||||
|
||||
@@ -292,7 +292,7 @@ button::-moz-focus-inner
|
||||
background: var(--color-bg-mid);
|
||||
color: var(--color-fg);
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
@@ -310,4 +310,5 @@ button::-moz-focus-inner
|
||||
{
|
||||
color: var(--color-fg-mid);
|
||||
margin-top: 3px;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
Reference in New Issue
Block a user