start media upload

This commit is contained in:
2021-12-18 01:51:38 +01:00
parent a89a74ec99
commit 07a0436ec8
16 changed files with 352 additions and 102 deletions
@@ -198,19 +198,21 @@ public abstract class ZeroApiEntityStoreController<TModel, TStore> : ZeroApiCont
return BadRequest(Result.Fail(nameof(id), "@errors.onupdate.noidmatch"));
}
TModel model = await Store.Load(id);
// TODO throws error on save: Attempted to associate a different object with id ...
// we need to map props to new object
//TModel model = await Store.Load(id);
if (model == null)
{
return BadRequest(Result.Fail(nameof(id), "@errors.idnotfound"));
}
//if (model == null)
//{
// return BadRequest(Result.Fail(nameof(id), "@errors.idnotfound"));
//}
string storedChangeToken = Store.GetChangeToken(model);
//string storedChangeToken = Store.GetChangeToken(model);
if (!changeToken.IsNullOrEmpty() && storedChangeToken != changeToken)
{
return BadRequest(Result.Fail("@errors.onupdate.changetokenmismatch"));
}
//if (!changeToken.IsNullOrEmpty() && storedChangeToken != changeToken)
//{
// return BadRequest(Result.Fail("@errors.onupdate.changetokenmismatch"));
//}
Result<TModel> result = await Store.Update(updateModel);
@@ -30,9 +30,9 @@ public class MediaMapperProfile : ZeroMapperProfile
target.Children = 0;
target.Size = source.Size;
if (source.Thumbnails.Any())
if (source.ImageMeta != null && source.ImageMeta.Thumbnails.Any())
{
string path = source.Thumbnails.GetValueOrDefault("preview");
string path = source.ImageMeta.Thumbnails.GetValueOrDefault("preview");
target.Image = path.IsNullOrEmpty() ? null : FileSystem.MapToPublicPath(path);
}
}
@@ -54,13 +54,13 @@ public class MediaMapperProfile : ZeroMapperProfile
protected virtual void Map(zero.Media.Media source, MediaFileEdit target, IZeroMapperContext ctx)
{
Map(source, (MediaEdit)target, ctx);
target.AlternativeText = source.AlternativeText;
target.AlternativeText = source.ImageMeta?.AlternativeText;
target.Caption = source.Caption;
target.Path = source.Path;
target.Thumbnails = source.Thumbnails;
target.Thumbnails = source.ImageMeta?.Thumbnails;
target.Size = source.Size;
target.ImageMeta = source.ImageMeta;
target.FocalPoint = source.FocalPoint;
target.FocalPoint = source.ImageMeta?.FocalPoint;
}
+9 -1
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Net.Http.Headers;
using Raven.Client.Documents;
@@ -40,6 +41,13 @@ public class MediaController : ZeroApiTreeEntityStoreController<zero.Media.Media
Media = media;
}
[HttpPost("uploadtest")]
public async Task<ActionResult> Upload(IFormFile file)
{
await Task.Delay(2000);
return Ok();
}
#region bulk operations
[HttpPut("bulk/move")]
+21 -1
View File
@@ -17,7 +17,27 @@ const files = {
move: (id: string, parentId: string, config?: ApiRequestConfig) => put(`media/${id}/move/${parentId}`, {}, { ...config }),
delete: (id: string, config?: ApiRequestConfig) => del('media/' + id, config)
delete: (id: string, config?: ApiRequestConfig) => del('media/' + id, config),
//uploadtest: (model: any, config?: ApiRequestConfig) => post('media/uploadtest', model, config),
uploadtest: async (file, onProgress) =>
{
var data = new FormData();
data.append('file', file);
//data.append('folderId', folderId);
return await post('media/uploadtest', data, {
onUploadProgress: (progressEvent) =>
{
if (typeof onProgress === 'function')
{
var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(percentCompleted);
}
}
});
}
};
@@ -8,11 +8,9 @@
<ui-error :catch-all="true" />
</ui-property>
</div>
<br /><br />
<div class="app-confirm-buttons">
<ui-button type="accent" v-if="!disabled" :submit="true" :state="form.state" :label="model.id ? '@ui.save' : '@ui.create'"></ui-button>
<ui-button type="accent" v-if="!disabled" :submit="true" :state="form.state" :label="model.id ? '@ui.update' : '@ui.create'"></ui-button>
<ui-button type="light" label="@ui.close" :disabled="loading" @click="config.close"></ui-button>
<ui-button v-if="!disabled && model.id" type="light" label="@ui.delete" @click="onDelete" style="float:right;"></ui-button>
</div>
</div>
</ui-form>
@@ -58,37 +56,6 @@
{
this.config.confirm(response.data);
}
},
//onDelete(item, opts)
//{
// opts.hide();
// thi
//onLoad(form)
//{
// form.load(!this.model.id ? MediaFolderApi.getEmpty() : MediaFolderApi.getById(this.model.id)).then(response =>
// {
// this.disabled = false; //!response.canEdit;
// this.item = response.entity;
// this.item.parentId = this.model.parentId;
// this.loading = false;
// });
//},
//onSubmit(form)
//{
// form.handle(MediaFolderApi.save(this.item)).then(response =>
// {
// this.config.confirm(response, this.config);
// });
//},
onDelete()
{
//this.config.confirm({ deleted: true });
}
}
}
@@ -0,0 +1,209 @@
<template>
<div class="ui-media-upload">
<h2 class="ui-headline" v-localize="'Upload status'"></h2>
<div class="ui-media-upload-items">
<button type="button" v-for="item in items" class="ui-media-upload-item">
<img class="-preview" v-if="item.isImage" :src="item.source" />
<span class="-preview" v-if="!item.isImage"><ui-icon symbol="fth-file" :size="20" /></span>
<p class="ui-media-upload-item-text">
{{item.name}}
<span class="-minor">
<br>
<span v-if="item.progress < 100 && !item.error && !item.success" class="ui-media-upload-item-progress">
<span class="-inner" :style="{ width: item.progress + '%' }"></span>
</span>
<span v-if="item.success">Completed</span>
</span>
</p>
</button>
</div>
<div class="app-confirm-buttons">
<ui-button v-if="!disabled" type="action" label="Upload" @click="upload"></ui-button>
<ui-button type="light" label="@ui.close" :disabled="loading" @click="config.close"></ui-button>
</div>
</div>
</template>
<script lang="ts">
import api from '../api';
import { generateId } from '../../../utils/numbers';
export default {
props: {
model: FileList,
config: Object
},
data: () => ({
loading: false,
items: []
}),
mounted()
{
const files = this.model;
console.info(files);
let items = [];
if (!files || files.length < 1)
{
return;
}
for (var i = 0; i < files.length; i++)
{
let file = files[i];
this.items.push({
id: 'upload:' + generateId(),
name: file.name,
size: file.size,
mimeType: file.type,
source: null,
progress: 0,
file: file,
isImage: false,
success: false,
error: null
});
}
},
methods: {
upload()
{
for (var i = 0; i < this.items.length; i++)
{
let item = this.items[i];
api.uploadtest(item.file, progress =>
{
console.info({ name: item.file.name, progress });
item.progress = progress;
}).then(res =>
{
console.info('success');
//if (res.success)
//{
// item.source = res.model.thumbnailSource || res.model.source;
// item.isImage = res.model.type === 'image';
// item.success = true;
//}
//else
//{
// item.success = false;
// // TODO output error
//}
});
}
},
onSubmit()
{
}
}
}
</script>
<style lang="scss">
.ui-media-upload
{
text-align: left;
}
.ui-media-upload
{
/*height: 200px;
background: var(--color-bg-mid);
border-radius: var(--radius) var(--radius) 0 0;
margin-top: var(--padding);
padding: 10px;
overflow: hidden;*/
}
.ui-media-upload-items
{
margin-top: 25px;
max-height: 495px;
overflow-y: auto;
}
.ui-media-upload-item
{
width: 100%;
height: 70px;
display: grid;
grid-template-columns: 70px 1fr;
gap: 15px;
align-items: center;
line-height: 1.4;
& + .ui-media-upload-item
{
margin-top: 15px;
}
.-preview
{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 70px;
width: 70px;
object-fit: cover;
background: var(--color-bg-mid);
border-radius: var(--radius);
position: relative;
text-align: center;
font-size: 22px;
}
/*&.is-upload .-preview
{
background: var(--color-primary);
color: var(--color-primary-text);
}*/
.-extension
{
font-size: 12px;
font-style: normal;
margin-top: 8px;
}
.-minor
{
color: var(--color-text-light);
font-size: var(--font-size-s);
}
}
.ui-media-upload-item-progress
{
display: block;
width: 50%;
height: 8px;
margin-top: 6px;
border-radius: 2px;
background: var(--color-bg-mid);
position: relative;
.-inner
{
position: absolute;
left: 0;
top: 0;
height: 100%;
border-radius: 2px;
background: var(--color-primary);
}
}
</style>
@@ -1,15 +1,48 @@
<template>
<div class="media-overview-drop">
<form enctype="multipart/form-data" class="media-overview-drop" :class="{ 'is-dragging': dragging }"
@dragenter.prevent="onDragEnter" @dragleave.prevent="onDragLeave" @dragover.prevent="" @drop.prevent.stop="onDrop">
<ui-icon symbol="fth-upload" :size="26" :stroke-width="2" />
</div>
</form>
</template>
<script>
<script lang="ts">
import * as overlays from '../../../../services/overlay';
export default {
name: 'mediaOverviewDrop',
props: {
},
data: () => ({
dragging: false
}),
methods: {
async onDrop(ev)
{
console.info(ev.dataTransfer);
this.dragging = false;
const result = await overlays.open({
component: () => import('../../overlays/upload-status.vue'),
model: ev.dataTransfer.files
});
},
onDragEnter(ev)
{
this.dragging = true;
},
onDrageLeave(ev)
{
this.dragging = false;
}
}
};
</script>
@@ -27,6 +60,11 @@
box-shadow: var(--shadow-short);
border: 1px dashed var(--color-line-dashed-onbg);
color: var(--color-text);
&.is-dragging
{
border-color: var(--color-accent);
}
}
.ui-datagrid-outer.is-selecting .media-overview-drop
@@ -16,7 +16,7 @@
<template v-if="selected.length < 1">
<ui-search v-model="gridConfig.search" class="onbg" />
<ui-button type="accent" label="@media.addfolder" @click="addFolder" />
<ui-button type="accent" label="@media.addfolder" @click="editFolder()" />
</template>
<media-selection v-else :selected="selected" @clear="clearSelection" @move="move" @remove="remove" />
</ui-header-bar>
@@ -30,12 +30,20 @@
</template>
<template v-if="selected.length < 1" v-slot:actions="props">
<ui-dropdown-button v-if="props.item && props.item.isFolder" label="@ui.open.title" icon="fth-arrow-right" @click="goToFolder(props.item.id)" />
<ui-dropdown-button label="@ui.edit.title" icon="fth-edit-2" @click="edit(props.item, props.item.isFolder)" />
<ui-dropdown-button label="@ui.move.title" icon="fth-corner-down-right" @click="move([props.item])" />
<ui-dropdown-button label="@ui.selection.select" icon="fth-check-circle-2" @click="$refs.grid.select(props.item)" />
<ui-dropdown-separator />
<ui-dropdown-button label="@ui.delete" icon="fth-trash" @click="remove(props.item, props.item.isFolder)" />
<template v-if="props.item && props.item.isFolder">
<ui-dropdown-button label="@ui.edit.title" icon="fth-edit-2" @click="editFolder(props.item)" />
<ui-dropdown-button label="@ui.move.title" icon="fth-corner-down-right" @click="move([props.item])" />
<ui-dropdown-button label="@ui.selection.select" icon="fth-check-circle-2" @click="$refs.grid.select(props.item)" />
<ui-dropdown-separator />
<ui-dropdown-button label="@ui.delete" icon="fth-trash" @click="remove([props.item])" />
</template>
<template v-if="props.item && !props.item.isFolder">
<ui-dropdown-button label="@ui.open.title" icon="fth-arrow-right" @click="goToFolder(props.item.id)" />
<ui-dropdown-button label="@ui.move.title" icon="fth-corner-down-right" @click="move([props.item])" />
<ui-dropdown-button label="@ui.selection.select" icon="fth-check-circle-2" @click="$refs.grid.select(props.item)" />
<ui-dropdown-separator />
<ui-dropdown-button label="@ui.delete" icon="fth-trash" @click="remove([props.item])" />
</template>
</template>
<template v-slot:default="props">
@@ -149,11 +157,11 @@
},
async addFolder()
async editFolder(item)
{
const result = await overlays.open({
component: () => import('../../overlays/createfolder.vue'),
model: { parentId: this.parentId },
component: () => import('../../overlays/editfolder.vue'),
model: item || { parentId: this.parentId },
display: 'dialog'
});
@@ -204,4 +204,14 @@
transform: scale(1) translateX(100%);
}
}
.app-confirm-buttons
{
margin-top: 40px;
}
.app-confirm p
{
line-height: 1.4;
}
</style>
+1 -13
View File
@@ -64,16 +64,4 @@
}
}
}
</script>
<style lang="scss">
.app-confirm-buttons
{
margin-top: 40px;
}
.app-confirm p
{
line-height: 1.4;
}
</style>
</script>
@@ -27,9 +27,9 @@
</template>
<script>
import MediaApi from 'zero/api/media.js'
import Strings from 'zero/helpers/strings.js';
<script lang="ts">
import api from '../api';
import { generateId } from '../../../utils/numbers';
export default {
@@ -59,7 +59,7 @@
let file = files[i];
this.items.push({
id: 'upload:' + Strings.guid(),
id: 'upload:' + generateId(),
name: file.name,
size: file.size,
mimeType: file.type,
@@ -77,7 +77,7 @@
{
let item = this.items[i];
MediaApi.upload(item.file, this.config.folderId, progress =>
api.uploadtest(item.file, this.config.folderId, progress =>
{
item.progress = progress;
}).then(res =>
+1 -1
View File
@@ -154,7 +154,7 @@ public class ZeroVue : IZeroVue
{
Id = app.Id,
Name = app.Name,
Image = app.IconId.IsNullOrEmpty() ? null : media.GetValueOrDefault(app.IconId)?.Thumbnails.GetValueOrDefault("thumb")
Image = app.IconId.IsNullOrEmpty() ? null : media.GetValueOrDefault(app.IconId)?.ImageMeta?.Thumbnails.GetValueOrDefault("thumb")
}).ToList();
}
+1 -1
View File
@@ -13,7 +13,7 @@ public class Media_ByParent : ZeroMultiMapIndex<MediaListItem>
CreatedDate = item.CreatedDate,
IsFolder = false,
Name = item.Name,
Image = item.Thumbnails["thumb"],
Image = item.ImageMeta != null ? item.ImageMeta.Thumbnails["thumb"] : null,
Children = 0,
Size = item.Size,
IsShared = item.Blueprint != null,
+2 -2
View File
@@ -27,7 +27,7 @@ public class MediaManagement : IMediaManagement
if (!thumbnailKey.IsNullOrWhiteSpace())
{
path = file?.Thumbnails?.GetValueOrDefault(thumbnailKey);
path = file?.ImageMeta?.Thumbnails?.GetValueOrDefault(thumbnailKey);
}
if (path.IsNullOrEmpty())
@@ -46,7 +46,7 @@ public class MediaManagement : IMediaManagement
if (!thumbnailKey.IsNullOrWhiteSpace())
{
path = file?.Thumbnails?.GetValueOrDefault(thumbnailKey);
path = file?.ImageMeta?.Thumbnails?.GetValueOrDefault(thumbnailKey);
}
if (path.IsNullOrEmpty())
-16
View File
@@ -26,11 +26,6 @@ public class Media : ZeroEntity, ISupportsTrees
/// </summary>
public string ParentId { get; set; }
/// <summary>
/// Alternative text which is used when the image can't be loaded
/// </summary>
public string AlternativeText { get; set; }
/// <summary>
/// Additional caption text
/// </summary>
@@ -41,12 +36,6 @@ public class Media : ZeroEntity, ISupportsTrees
/// </summary>
public string Path { get; set; }
/// <summary>
/// Define custom thumbnails which are generated on upload
/// (see IZeroOptions.For<MediaOptions>().Thumbnails)
/// </summary>
public Dictionary<string, string> Thumbnails { get; set; } = new();
/// <summary>
/// Filesize in bytes
/// </summary>
@@ -56,9 +45,4 @@ public class Media : ZeroEntity, ISupportsTrees
/// Meta data for images
/// </summary>
public MediaImageMetadata ImageMeta { get; set; }
/// <summary>
/// Optional focal point for an image
/// </summary>
public MediaFocalPoint FocalPoint { get; set; }
}
@@ -5,6 +5,22 @@
/// </summary>
public class MediaImageMetadata
{
/// <summary>
/// Alternative text which is used when the image can't be loaded
/// </summary>
public string AlternativeText { get; set; }
/// <summary>
/// Define custom thumbnails which are generated on upload
/// (see IZeroOptions.For<MediaOptions>().Thumbnails)
/// </summary>
public Dictionary<string, string> Thumbnails { get; set; } = new();
/// <summary>
/// Optional focal point for an image
/// </summary>
public MediaFocalPoint FocalPoint { get; set; }
/// <summary>
/// Width in pixels
/// </summary>