Permalink
Browse files

Dnd images in composer and supported file type and size error handlin…

…g in gallery (#275)

* chore: Upgrade vuetify to v1.3.5 #264

* fix: Resize animated GIF results in large file size #264

I limit gif to only save original so there is no resizing, I also validate file's type and size on both client and server side.

* feat: Drag and drop images to upload in Composer #229

* fix:  "this" not defined on the instance but referenced during render error #229

I encountered this js error on media component, it happens only in development with un-minified vuejs and on Microsoft Edge.

* fix: Media gallery file upload redone for MS Edge #229

Edge does not work with file input onchange event, so I re-did it with addEventListener.  I also added file input tag in the html instead of dynamically creating it in js.
  • Loading branch information...
FanrayMedia committed Nov 3, 2018
1 parent 4336be6 commit b61ead8947f74117b8a6933a4eb84d105c3dc23f
@@ -1,9 +1,11 @@
using Fan.Blog.Enums;
using Fan.Blog.Services.Interfaces;
using Fan.Exceptions;
using Fan.Helpers;
using Fan.Medias;
using Fan.Settings;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -47,6 +49,26 @@ public class ImageService : IImageService
/// </remarks>
public static readonly string[] Accepted_Image_Types = { ".jpg", ".jpeg", ".gif", ".png" };
/// <summary>
/// validFileTypes: ['.jpg', '.jpeg', '.png', '.gif']
/// </summary>
public static string ValidFileTypesJson = JsonConvert.SerializeObject(Accepted_Image_Types);
/// <summary>
/// Max image file size is 5MB.
/// </summary>
public const long MAX_FILE_SIZE = 5 * ByteSize.BytesInMegaByte;
/// <summary>
/// Error message for valid file types.
/// </summary>
public const string ERR_MSG_FILETYPE = "Only .jpg, .jpeg, .png and .gif are supported.";
/// <summary>
/// Error message for valid file size.
/// </summary>
public const string ERR_MSG_FILESIZE = "File cannot be larger than 5MB.";
/// <summary>
/// The separator used in image paths is '/'.
/// </summary>
@@ -102,6 +124,21 @@ public static List<ImageResizeInfo> GetImageResizeList(DateTimeOffset uploadedOn
};
}
/// <summary>
/// For gif I only save original so there is no resizing.
/// </summary>
/// <param name="uploadedOn"></param>
public static List<ImageResizeInfo> GetImageResizeListForGif(DateTimeOffset uploadedOn)
{
return new List<ImageResizeInfo> {
new ImageResizeInfo {
TargetSize = int.MaxValue,
Path = GetImagePath(uploadedOn, EImageSize.Original),
PathSeparator = IMAGE_PATH_SEPARATOR,
},
};
}
/// <summary>
/// Returns the stored image path, "{app}/{year}/{month}" or "{app}/{year}/{month}/{sizePath}".
/// </summary>
@@ -213,7 +250,13 @@ public string GetAbsoluteUrl(Media media, EImageSize size)
var ctype = "." + contentType.Substring(contentType.LastIndexOf("/") + 1).ToLower();
if (ext.IsNullOrEmpty() || !Accepted_Image_Types.Contains(ext) || !Accepted_Image_Types.Contains(ctype))
{
throw new NotSupportedException("Upload file type is not supported.");
throw new NotSupportedException(ERR_MSG_FILETYPE);
}
// check file size
if (source.Length > MAX_FILE_SIZE)
{
throw new FanException(ERR_MSG_FILESIZE);
}
// uploadedOn
@@ -226,7 +269,8 @@ public string GetAbsoluteUrl(Media media, EImageSize size)
var uniqueFileName = await GetUniqueFileNameAsync(fileNameSlugged, uploadedOn);
// get image resizes
var resizes = GetImageResizeList(uploadedOn);
var resizes = contentType.Equals("image/gif") ?
GetImageResizeListForGif(uploadedOn) : GetImageResizeList(uploadedOn);
return await _mediaSvc.UploadImageAsync(source, resizes, uniqueFileName, contentType, title,
uploadedOn, EAppType.Blog, userId, uploadFrom);
@@ -12,14 +12,30 @@
<title>@ViewData["Title"] - Fanray</title>
<link rel="shortcut icon" href="/favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify@1.2.8/dist/vuetify.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify@1.3.5/dist/vuetify.min.css" rel="stylesheet">
<link rel="stylesheet" href="~/admin/css/compose.css" asp-append-version="true" />
<link rel="stylesheet" href="~/themes/@Model.Theme/css/content.css" asp-append-version="true" />
</head>
<body>
@Html.AntiForgeryToken()
<div id="app" v-cloak>
<div style="visibility:hidden; opacity:0" id="dropzone" v-once>
<span class="droptext">Drop files to upload</span>
</div>
<v-dialog v-model="composerUploadProgress"
persistent
width="300">
<v-card color="primary" dark>
<v-card-text>
Uploading ...
<v-progress-linear indeterminate
color="white"
class="mb-0"></v-progress-linear>
</v-card-text>
</v-card>
</v-dialog>
<v-app id="inspire" style="background-color:white">
@* Drawer *@
<v-navigation-drawer fixed
@@ -121,12 +137,12 @@
</v-expansion-panel-content>
</v-expansion-panel>
@*<v-footer class="pa-3" absolute style="height:auto">
<v-layout justify-center>
<a target="_blank" style="text-decoration:none" href="https://github.com/FanrayMedia/Fanray/wiki/Using-the-Composer" title="How to use the composer">
<v-icon large color="info">help_outline</v-icon>
</a>
</v-layout>
</v-footer>*@
<v-layout justify-center>
<a target="_blank" style="text-decoration:none" href="https://github.com/FanrayMedia/Fanray/wiki/Using-the-Composer" title="How to use the composer">
<v-icon large color="info">help_outline</v-icon>
</a>
</v-layout>
</v-footer>*@
</v-navigation-drawer>
@* Topbar *@
<v-toolbar color="blue darken-3"
@@ -203,11 +219,11 @@
</div>
<environment include="Development">
<script src="https://unpkg.com/vue@2.5.17/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify@1.2.8/dist/vuetify.js"></script>
<script src="https://unpkg.com/vuetify@1.3.5/dist/vuetify.js"></script>
</environment>
<environment exclude="Development">
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuetify@1.2.8/dist/vuetify.min.js"></script>
<script src="https://unpkg.com/vuetify@1.3.5/dist/vuetify.min.js"></script>
</environment>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.min.js"></script>
<script src="https://unpkg.com/axios@0.18.0/dist/axios.min.js"></script>
@@ -220,6 +236,10 @@
images: [],
count: 0,
pageSize: @MediaModel.PAGE_SIZE,
maxImageFileSize: @ImageService.MAX_FILE_SIZE,
validFileTypes: @Html.Raw(ImageService.ValidFileTypesJson),
errFileType: '@ImageService.ERR_MSG_FILETYPE',
errFileSize: '@ImageService.ERR_MSG_FILESIZE',
};
}
};
@@ -16,6 +16,10 @@
images: @Html.Raw(Model.Data.ImagesJson),
count: @Model.ImageCount,
pageSize: @MediaModel.PAGE_SIZE,
maxImageFileSize: @ImageService.MAX_FILE_SIZE,
validFileTypes: @Html.Raw(ImageService.ValidFileTypesJson),
errFileType: '@ImageService.ERR_MSG_FILETYPE',
errFileSize: '@ImageService.ERR_MSG_FILESIZE',
};
}
};
@@ -1,5 +1,6 @@
using Fan.Blog.Enums;
using Fan.Blog.Services.Interfaces;
using Fan.Exceptions;
using Fan.Medias;
using Fan.Membership;
using Microsoft.AspNetCore.Http;
@@ -64,11 +65,9 @@ public class ImageVM : Media
public class ImageData
{
public IEnumerable<ImageVM> Images { get; set; }
public string ErrorMessage { get; set; }
public string ImagesJson =>
(Images == null || Images.Count() <=0) ? "[]" :
public IEnumerable<string> ErrorMessages { get; set; }
public string ImagesJson =>
(Images == null || Images.Count() <= 0) ? "[]" :
JsonConvert.SerializeObject(Images);
}
@@ -133,9 +132,9 @@ public async Task<JsonResult> OnGetMoreAsync(int pageNumber = 1)
public async Task<JsonResult> OnPostImageAsync(IList<IFormFile> images)
{
var userId = Convert.ToInt32(_userManager.GetUserId(HttpContext.User));
List<ImageVM> imageVMs = new List<ImageVM>();
var imageVMs = new List<ImageVM>();
var errMsgs = new List<string>();
int failCount = 0;
foreach (var image in images)
{
try
@@ -148,15 +147,18 @@ public async Task<JsonResult> OnPostImageAsync(IList<IFormFile> images)
}
catch (NotSupportedException ex)
{
failCount++;
errMsgs.Add(ex.Message);
}
catch (FanException ex) // todo consider errcode
{
errMsgs.Add(ex.Message);
}
}
var imageData = new ImageData
{
Images = imageVMs,
ErrorMessage = failCount <= 0 ? null :
$"Only .jpg, .jpeg, .png and .gif are supported, {failCount} file(s) could not be uploaded.",
ErrorMessages = errMsgs.Distinct(),
};
return new JsonResult(imageData);
@@ -1,11 +1,11 @@
@*
@*
The media component template used by Admin Media and Compose pages.
See blog-media.js, Media.cshtml, Compose.cshtml, and blog-compose.js.
*@
<script type="text/x-template" id="blog-media-template">
<div>
<div style="visibility:hidden; opacity:0" id="dropzone" v-once>
<span id="droptext">Drop files to upload</span>
<span class="droptext">Drop files to upload</span>
</div>
<v-toolbar class="elevation-1 media-toolbar" v-bind:style="[isEditor ? {'top' : '0'} : {'top' : '48px'}]">
@@ -15,28 +15,28 @@
</v-btn>
<v-btn @@click="editImages"
color="info"
v-show="this.selectedImages.length > 0">
v-show="selectedImages.length > 0">
Edit
</v-btn>
<v-btn @@click="deleteImages"
color="error"
v-show="this.selectedImages.length > 0">
v-show="selectedImages.length > 0">
<v-icon>delete</v-icon>
</v-btn>
<v-btn @@click="insertImages"
color="info"
v-show="this.selectedImages.length > 0 && this.isEditor">
v-show="selectedImages.length > 0 && isEditor">
Insert
</v-btn>
<v-spacer></v-spacer>
<v-btn @@click.stop="closeMediaDialog"
color="info"
v-show="this.isEditor">
v-show="isEditor">
Close
</v-btn>
</v-toolbar>
<v-alert :value="errMsg.length" type="error" dismissible>{{ errMsg }}</v-alert>
<v-alert :value="errMsg.length" type="warning" dismissible>{{ errMsg }}</v-alert>
<v-container grid-list-md fluid>
<v-layout row wrap>
@@ -145,5 +145,7 @@
</v-card-text>
</v-card>
</v-dialog>
<input type="file" id="fileInput" multiple accept="image/*" style="visibility: hidden">
</div>
</script>
@@ -1,4 +1,5 @@
@{
@* Layout for Admin Panel *@
@{
var coreSettings = await settingService.GetSettingsAsync<CoreSettings>();
var currentUser = await userManager.GetUserAsync(Context.User);
}
@@ -10,7 +11,7 @@
<title>@ViewData["Title"] - Fanray Admin Console</title>
<link rel="shortcut icon" href="/favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify@1.2.8/dist/vuetify.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify@1.3.5/dist/vuetify.min.css" rel="stylesheet">
<link rel="stylesheet" href="~/admin/css/admin.css" asp-append-version="true" />
@RenderSection("Styles", required: false)
</head>
@@ -91,11 +92,11 @@
</div>
<environment include="Development">
<script src="https://unpkg.com/vue@2.5.17/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify@1.2.8/dist/vuetify.js"></script>
<script src="https://unpkg.com/vuetify@1.3.5/dist/vuetify.js"></script>
</environment>
<environment exclude="Development">
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuetify@1.2.8/dist/vuetify.min.js"></script>
<script src="https://unpkg.com/vuetify@1.3.5/dist/vuetify.min.js"></script>
</environment>
<script src="https://unpkg.com/axios@0.18.0/dist/axios.min.js"></script>
@RenderSection("ComponentScripts", required: false)
@@ -1,4 +1,5 @@
@using Fan.Membership
@using Fan.Blog.Services
@using Fan.Membership
@using Fan.Settings
@using Fan.Web.Pages.Admin
@using Microsoft.AspNetCore.Identity
Oops, something went wrong.

0 comments on commit b61ead8

Please sign in to comment.