edit media + replace file in media entity

This commit is contained in:
2020-08-24 15:17:43 +02:00
parent ffe8624d36
commit 30199de4d8
9 changed files with 216 additions and 27 deletions
+30 -18
View File
@@ -29,7 +29,7 @@ namespace zero.Core.Api
private const string UPLOAD_PREFIX = "upload:";
private string[] ImageExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".webp", ".jfif" };
private string[] ImageExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".webp", ".jfif", ".gif" };
public MediaUploadApi(IPaths paths)
@@ -66,6 +66,10 @@ namespace zero.Core.Api
await file.CopyToAsync(stream, cancellationToken);
}
// set new properties
media.Source = Path.Combine(PATH_PREFIX, media.FileId, media.Name).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
media.Size = file.Length;
// write additional image data + thumbnail
if (media.Type == MediaType.Image)
{
@@ -73,36 +77,44 @@ namespace zero.Core.Api
{
media.ImageMeta = GetImageMeta(image);
image.Mutate(x => x.Resize(new ResizeOptions()
Image<Rgba32> imageFrame = media.ImageMeta.Frames > 1 ? image.Frames.CloneFrame(0) : image;
media.PreviewSource = SaveThumbnail(media, imageFrame, PREVIEW_EXTENSION, new ResizeOptions()
{
Size = new Size(210, 210),
Mode = ResizeMode.Min
}));
});
string thumbFileName = media.Name.TrimEnd(extension) + PREVIEW_EXTENSION + extension;
image.Save(Path.Combine(Paths.Media, media.FileId, thumbFileName));
media.PreviewSource = Path.Combine(PATH_PREFIX, media.FileId, thumbFileName).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
image.Mutate(x => x.Resize(new ResizeOptions()
media.ThumbnailSource = SaveThumbnail(media, imageFrame, THUMB_EXTENSION, new ResizeOptions()
{
Size = new Size(100, 100),
Mode = ResizeMode.Max
}));
thumbFileName = media.Name.TrimEnd(extension) + THUMB_EXTENSION + extension;
image.Save(Path.Combine(Paths.Media, media.FileId, thumbFileName));
media.ThumbnailSource = Path.Combine(PATH_PREFIX, media.FileId, thumbFileName).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
});
}
}
// set new properties
media.Source = Path.Combine(PATH_PREFIX, media.FileId, media.Name).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
media.Size = file.Length;
return media;
}
/// <summary>
/// Saves a thumbnail of an image
/// </summary>
string SaveThumbnail(IMedia media, Image<Rgba32> image, string extensionPrefix, ResizeOptions resizeOptions)
{
string extension = Path.GetExtension(media.Source);
image.Mutate(x => x.Resize(resizeOptions));
string thumbFileName = media.Name.TrimEnd(extension) + extensionPrefix + extension;
image.Save(Path.Combine(Paths.Media, media.FileId, thumbFileName));
return Path.Combine(PATH_PREFIX, media.FileId, thumbFileName).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
}
/// <summary>
/// Create image data if available
/// </summary>
MediaImageMeta GetImageMeta(Image<Rgba32> image)
{
var pngMetadata = image.Metadata.GetPngMetadata();
@@ -115,7 +127,7 @@ namespace zero.Core.Api
DPI = image.Metadata.HorizontalResolution,
ColorSpace = image.Metadata.IccProfile?.Header?.DataColorSpace.ToString(),
HasTransparency = pngMetadata?.HasTransparency ?? false,
IsAnimated = image.Frames.Count > 1
Frames = image.Frames.Count
};
}
}
+2 -2
View File
@@ -38,8 +38,8 @@ namespace zero.Core.Entities
public bool HasTransparency { get; set; }
/// <summary>
/// Whether this image contains multiple frames
/// How many frames contains this image (for animation)
/// </summary>
public bool IsAnimated { get; set; }
public int Frames { get; set; } = 1;
}
}
+8 -1
View File
@@ -208,7 +208,14 @@
onChange(value)
{
Objects.setValue(this.value, this.selector, value);
if (typeof value === 'function')
{
value(this.value);
}
else
{
Objects.setValue(this.value, this.selector, value);
}
this.$emit('input', this.value);
}
}
+3
View File
@@ -21,6 +21,9 @@
<ui-property v-if="model.imageMeta.colorSpace" label="@media.fields.colorSpace" :is-text="true">
{{model.imageMeta.colorSpace}}
</ui-property>
<ui-property v-if="model.imageMeta.frames > 1" label="@media.fields.frames" :is-text="true">
{{model.imageMeta.frames}}
</ui-property>
</div>
</template>
</ui-editor>
+151
View File
@@ -0,0 +1,151 @@
<template>
<div class="media-upload">
<div v-if="entity.source" class="media-upload-preview" :data-type="entity.type">
<img v-if="entity.type === 'image'" :src="entity.previewSource" class="media-upload-preview-image" />
<a href="#" v-if="entity.type === 'file'" class="media-upload-preview-file">
<i :class="icons[entity.type]" :data-extension="entity.source.split('.').pop()"></i>
<div>
<span>{{entity.source.split('/').pop()}}</span><br />
<span class="is-minor">{{entity.source.split('.').pop()}}</span>
</div>
</a>
</div>
<div v-if="entity.source">
<button type="button" class="ui-link media-upload-preview-remove" @click="removeFile">Remove file</button>
</div>
<div v-if="!entity.source" class="ui-select-button type-light">
<span class="ui-select-button-icon"><i class="fth-plus"></i></span>
<content class="ui-select-button-content">
<strong class="ui-select-button-label">Upload file</strong>
</content>
<input class="media-upload-input" type="file" @change="onUpload" />
</div>
</div>
</template>
<script>
import MediaApi from 'zero/resources/media.js';
export default {
props: {
config: Object,
entity: Object,
value: String,
disabled: Boolean
},
data: () => ({
icons: {
image: 'fth-image',
video: 'fth-video',
file: 'fth-file'
}
}),
methods: {
removeFile()
{
this.$emit('input', x => x.source = null);
},
onUpload(event)
{
let file = event.target.files[0];
if (!file)
{
return;
}
// TODO do not allow switch of file to a new type, e.g. "image" => "file"
MediaApi.upload(file, this.entity.folderId, null, true).then(res =>
{
this.$emit('input', x =>
{
x.source = res.source;
x.previewSource = res.previewSource;
x.thumbnailSource = res.thumbnailSource;
x.imageMeta = res.imageMeta;
x.type = res.type;
x.size = res.size;
x.focalPoint = res.focalPoint;
x.name = res.name;
});
});
},
}
}
</script>
<style lang="scss">
.media-upload-preview
{
display: block;
&[data-type="image"]
{
padding: var(--radius);
border-radius: var(--radius);
background: var(--color-bg-dim);
display: inline-block;
}
}
.media-upload-preview-image
{
display: block;
max-width: 100%;
max-height: 400px;
border-radius: var(--radius);
}
.media-upload-preview-file
{
display: flex;
background: var(--color-bg-bright-two);
border-radius: var(--radius);
align-items: center;
justify-content: flex-start;
color: var(--color-fg);
padding: 15px 30px 15px 15px;
i
{
font-size: 28px;
position: relative;
margin-right: 12px;
}
.is-minor
{
color: var(--color-fg-dim);
font-size: var(--font-size-xs);
text-transform: uppercase;
}
}
.media-upload-preview-remove
{
margin-right: 2px;
margin-top: 10px;
}
input[type="file"].media-upload-input
{
position: absolute;
height: 100%;
top: 0;
left: 0;
width: 100%;
z-index: 1;
bottom: 0;
opacity: 0.001;
cursor: pointer;
}
</style>
+6
View File
@@ -12,6 +12,12 @@
},
fields: [
{
field: 'source',
display: 'custom',
required: true,
path: '@zero/pages/media/upload.vue'
},
{
field: 'name',
display: 'text',
+2 -2
View File
@@ -45,13 +45,13 @@ export default {
},
// uploads a file
upload(file, folderId, onProgress)
upload(file, folderId, onProgress, isTemporary)
{
var data = new FormData();
data.append('file', file);
data.append('folderId', folderId);
return Axios.post(base + 'upload', data, {
return Axios.post(base + (isTemporary ? 'uploadTemporary' : 'upload'), data, {
onUploadProgress: (progressEvent) =>
{
if (typeof onProgress === 'function')
+12 -3
View File
@@ -78,10 +78,9 @@ namespace zero.Web.Controllers
/// <summary>
/// Save a media item
/// </summary>
public async Task<IActionResult> Save([FromBody] MediaEditModel model)
public async Task<IActionResult> Save([FromBody] Media model)
{
Media entity = await Mapper.Map(model, await Api.GetById(model.Id));
return await As<Media, MediaEditModel>(await Api.Save(entity));
return Json(await Api.Save(model));
}
@@ -95,6 +94,16 @@ namespace zero.Web.Controllers
}
/// <summary>
/// Upload a file
/// </summary>
public async Task<IActionResult> UploadTemporary(IFormFile file, string folderId)
{
Media media = await MediaUploadApi.Upload(file, folderId);
return Json(media);
}
/// <summary>
/// Deletes a media item
/// </summary>
@@ -448,12 +448,13 @@
"size": "Size",
"date": "Last modified at",
"filename": "Filename",
"source": "File",
"alternativeText": "Alternative text",
"alternativeText_text": "Text which is used when the image can't be loaded",
"dpi": "DPI",
"colorSpace": "Color space",
"hasTransparency": "Has transparency",
"isAnimated": "Is animated"
"frames": "Frames"
}
},