edit media + replace file in media entity
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -12,6 +12,12 @@
|
||||
},
|
||||
|
||||
fields: [
|
||||
{
|
||||
field: 'source',
|
||||
display: 'custom',
|
||||
required: true,
|
||||
path: '@zero/pages/media/upload.vue'
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
display: 'text',
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user