From db80c79f9b0ac761b0647f9523fd501a9774eb87 Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Sun, 13 Jan 2019 21:07:58 +0700 Subject: [PATCH 1/9] Add signalr --- .../AwesomeCMSCore/Hubs/CmsCoreHub.cs | 16 ++++++++++++++++ .../React/js/App/Modules/Admin/admin.js | 8 +++++++- src/AwesomeCMSCore/AwesomeCMSCore/Startup.cs | 10 ++++++++-- src/AwesomeCMSCore/AwesomeCMSCore/package.json | 1 + 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/AwesomeCMSCore/AwesomeCMSCore/Hubs/CmsCoreHub.cs diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/Hubs/CmsCoreHub.cs b/src/AwesomeCMSCore/AwesomeCMSCore/Hubs/CmsCoreHub.cs new file mode 100644 index 00000000..bea0b16e --- /dev/null +++ b/src/AwesomeCMSCore/AwesomeCMSCore/Hubs/CmsCoreHub.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.SignalR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AwesomeCMSCore.Hubs +{ + public class CmsCoreHub : Hub + { + public async Task NotifyImageProcessCompleted(string user, string message) + { + await Clients.All.SendAsync("NotifyImageProcessCompleted", user, message); + } + } +} diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/admin.js b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/admin.js index daee48b3..c840df73 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/admin.js +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/admin.js @@ -2,4 +2,10 @@ import "../../../../css/Admin/admin.scss"; import "jquery/dist/jquery.min.js"; import "bootstrap/dist/js/bootstrap.min.js"; -import "./script"; \ No newline at end of file +import "./script"; + +import * as signalR from '@aspnet/signalr'; + +let connection = new signalR.HubConnectionBuilder() + .withUrl("/cmscore") + .build(); \ No newline at end of file diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/Startup.cs b/src/AwesomeCMSCore/AwesomeCMSCore/Startup.cs index ce81d17e..3e4543a3 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/Startup.cs +++ b/src/AwesomeCMSCore/AwesomeCMSCore/Startup.cs @@ -1,5 +1,6 @@ using AutoMapper; using AwesomeCMSCore.Extension; +using AwesomeCMSCore.Hubs; using AwesomeCMSCore.Infrastructure.Config; using AwesomeCMSCore.Infrastructure.Module.Views; using Microsoft.AspNetCore.Builder; @@ -52,7 +53,8 @@ public void ConfigureServices(IServiceCollection services) services.IntegrateSwagger(); services.RegisterGzip(); services.IntegrateRedis(_configuration); - } + services.AddSignalR(); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) @@ -64,7 +66,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.ConfigSwagger(); app.ServeStaticModuleFile(GlobalConfiguration.Modules); app.UseAuthentication(); - app.UseCustomizeMvc(); + app.UseSignalR(routes => + { + routes.MapHub("/cmscore"); + }); + app.UseCustomizeMvc(); } } } diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/package.json b/src/AwesomeCMSCore/AwesomeCMSCore/package.json index d6f22e82..f6f14168 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/package.json +++ b/src/AwesomeCMSCore/AwesomeCMSCore/package.json @@ -54,6 +54,7 @@ "optimize-css-assets-webpack-plugin": "^5.0.1" }, "dependencies": { + "@aspnet/signalr": "^1.1.0", "@tinymce/tinymce-react": "^2.2.5", "axios": "^0.18.0", "bootstrap": "^4.1.1", From 0a84a406ef778b46d5f1864d27369224fc36fcc0 Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Sun, 13 Jan 2019 22:53:10 +0700 Subject: [PATCH 2/9] preview & clear image --- .../React/css/Admin/Post/Post.scss | 10 + .../js/App/Modules/Admin/Post/NewPost.jsx | 431 ++++++++++-------- 2 files changed, 258 insertions(+), 183 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/css/Admin/Post/Post.scss b/src/AwesomeCMSCore/AwesomeCMSCore/React/css/Admin/Post/Post.scss index de29fbde..688860c4 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/css/Admin/Post/Post.scss +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/css/Admin/Post/Post.scss @@ -3,5 +3,15 @@ #postContainer { margin: 20px 0 20px 0; + + #thumbnail-preview { + margin-top: 15px; + width: 295px; + height: 205px; + } + + #remove-icon { + position: absolute; + } } } \ No newline at end of file diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index fede64bc..d8f7ebc5 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -1,15 +1,22 @@ -import React, { Component } from "react"; -import { render } from "react-dom"; -import { Container, Row, Col, Button, Card, CardTitle } from "reactstrap"; +import React, {Component} from "react"; +import {render} from "react-dom"; +import { + Container, + Row, + Col, + Button, + Card, + CardTitle +} from "reactstrap"; import toastr from "toastr"; import PropTypes from "prop-types"; -import { Get, PostWithSpinner } from "Helper/Http"; -import { STATUS_CODE, POST_STATUS } from "Helper/AppEnum"; -import { SAVE_POST_API } from "Helper/API_Endpoint/PostEndpoint"; -import { isDomExist } from "Helper/Util"; -import { onChange, onBlur } from "Helper/StateHelper"; -import { POST_OPTIONS_API } from "Helper/API_Endpoint/PostOptionEndpoint"; +import {Get, PostWithSpinner} from "Helper/Http"; +import {STATUS_CODE, POST_STATUS} from "Helper/AppEnum"; +import {SAVE_POST_API} from "Helper/API_Endpoint/PostEndpoint"; +import {isDomExist} from "Helper/Util"; +import {onChange, onBlur} from "Helper/StateHelper"; +import {POST_OPTIONS_API} from "Helper/API_Endpoint/PostOptionEndpoint"; import ACCEditor from "Common/ACCInput/ACCEditor.jsx"; import ACCButton from "Common/ACCButton/ACCButton.jsx"; @@ -18,184 +25,242 @@ import ACCReactSelect from "Common/ACCSelect/ACCReactSelect.jsx"; import Spinner from "Common/ACCAnimation/Spinner.jsx"; class NewPost extends Component { - constructor(props) { - super(props); - this.state = { - postContent: "", - title: "", - shortDescription: "", - tagValue: [], - tagOptions: [], - categoriesOptions: [], - categoriesValue: [], - loading: false - }; - } - - componentDidMount() { - Get(`${POST_OPTIONS_API}/Options`).then(res => { - this.setState({ - tagOptions: res.data.tagViewModel.value - ? JSON.parse(res.data.tagViewModel.value) - : [], - categoriesOptions: res.data.categoriesViewModel.value - ? JSON.parse(res.data.categoriesViewModel.value) - : [] - }); - }); - } - - newPost = (e, postStatus) => { - e.preventDefault(); - - const postOptionsDefaultViewModel = { - tagViewModel: { - key: JSON.stringify(this.state.tagValue.map(x => x.value)), - value: JSON.stringify(this.state.tagValue) - }, - categoriesViewModel: { - key: JSON.stringify(this.state.categoriesValue.map(x => x.value)), - value: JSON.stringify(this.state.categoriesValue) - } - }; - - const viewModel = { - Title: this.state.title, - ShortDescription: this.state.shortDescription, - Content: this.state.postContent, - PostOptionsDefaultViewModel: postOptionsDefaultViewModel, - PostStatus: postStatus - }; - - PostWithSpinner.call(this, SAVE_POST_API, viewModel).then(res => { - if (res.status === STATUS_CODE.Success) - return toastr.success("Create new post success"); - }); - }; - - handleEditorChange = e => { - this.setState({ - postContent: e.target.getContent() - }); - }; - - handleOnTagChange = tagValue => { - this.setState({ tagValue }); - }; - - handleOnCatChange = categoriesValue => { - this.setState({ categoriesValue }); - }; - - render() { - const { - shortDescription, - title, - disabled, - loading, - tagValue, - tagOptions, - categoriesOptions, - categoriesValue - } = this.state; - - return ( - -
-
- - - - - onChange.call(this, title)} - onBlur={title => onBlur.call(this, title)} - /> - - - - - - onChange.call(this, shortDescription) - } - onBlur={shortDescription => - onBlur.call(this, shortDescription) - } - /> - - - - - - - - - - - - Post Options - this.handleOnTagChange(value)} - /> -
- this.handleOnCatChange(value)} - /> -
- -
{" "} - {!loading ? ( -
- this.newPost(e, POST_STATUS.Draft)} - /> -
- this.newPost(e, POST_STATUS.Published)} - /> -
- ) : ( - - )} -
- -
- -
-
- ); - } + constructor(props) { + super(props); + this.state = { + postContent: "", + title: "", + shortDescription: "", + tagValue: [], + tagOptions: [], + categoriesOptions: [], + categoriesValue: [], + loading: false, + thumbnail: null + }; + } + + componentDidMount() { + Get(`${POST_OPTIONS_API}/Options`).then(res => { + this.setState({ + tagOptions: res.data.tagViewModel.value + ? JSON.parse(res.data.tagViewModel.value) + : [], + categoriesOptions: res.data.categoriesViewModel.value + ? JSON.parse(res.data.categoriesViewModel.value) + : [] + }); + }); + } + + newPost = (e, postStatus) => { + e.preventDefault(); + + const postOptionsDefaultViewModel = { + tagViewModel: { + key: JSON.stringify(this.state.tagValue.map(x => x.value)), + value: JSON.stringify(this.state.tagValue) + }, + categoriesViewModel: { + key: JSON.stringify(this.state.categoriesValue.map(x => x.value)), + value: JSON.stringify(this.state.categoriesValue) + } + }; + + const viewModel = { + Title: this.state.title, + ShortDescription: this.state.shortDescription, + Content: this.state.postContent, + PostOptionsDefaultViewModel: postOptionsDefaultViewModel, + PostStatus: postStatus + }; + + PostWithSpinner + .call(this, SAVE_POST_API, viewModel) + .then(res => { + if (res.status === STATUS_CODE.Success) + return toastr.success("Create new post success"); + } + ); + }; + + handleEditorChange = e => { + this.setState({ + postContent: e + .target + .getContent() + }); + }; + + handleOnTagChange = tagValue => { + this.setState({tagValue}); + }; + + handleOnCatChange = categoriesValue => { + this.setState({categoriesValue}); + }; + + handleImagePreview = imageValue => { + this.clearImageState(); + // eslint-disable-next-line no-undef + let reader = new FileReader(); + + reader.onload = function (e) { + // eslint-disable-next-line no-undef + $('#thumbnail-preview').attr('src', e.target.result); + } + + this.setState({thumbnail: imageValue}); + reader.readAsDataURL(imageValue); + } + + removeImage = () => { + this.clearImageState(); + } + + clearImageState = () => { + this.setState({thumbnail: null}); + } + render() { + const { + shortDescription, + title, + disabled, + loading, + tagValue, + tagOptions, + categoriesOptions, + categoriesValue, + thumbnail + } = this.state; + + return ( + +
+
+ + + + + onChange.call(this, title)} + onBlur={title => onBlur.call(this, title)}/> + + + + + onChange.call(this, shortDescription)} + onBlur={shortDescription => onBlur.call(this, shortDescription)}/> + + + + +
+
+ Upload +
+
+ this.handleImagePreview(thumbnail.target.files[0])}/> + +
+
+ +
+ + +
+
+ Preview +
+
+ + +
+

Please note that image will be resize when upload

+
+
+
+ +
+ + + + + + + + + + Post Options + this.handleOnTagChange(value)}/> +
+ this.handleOnCatChange(value)}/> +
+ +
{" "} {!loading + ? ( +
+ this.newPost(e, POST_STATUS.Draft)}/> +
+ this.newPost(e, POST_STATUS.Published)}/> +
+ ) + : ()} +
+ +
+ +
+
+ ); + } } NewPost.propTypes = { - visible: PropTypes.bool + visible: PropTypes.bool }; if (isDomExist("newPostContent")) { - render(, document.getElementById("newPostContent")); + render( + , document.getElementById("newPostContent")); } From 34bd7047b093cd66ac42af2d7f8e495386e3c003 Mon Sep 17 00:00:00 2001 From: ngohungphuc Date: Mon, 14 Jan 2019 14:16:43 +0700 Subject: [PATCH 3/9] Modify Media entity --- .../js/App/Modules/Admin/Post/NewPost.jsx | 24 ++++++++++--------- .../ViewModels/PostViewModel.cs | 4 ++-- .../Data/ApplicationDbContext.cs | 7 +++++- .../Entities/Media.cs | 16 +++++++------ .../Entities/Post.cs | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index d8f7ebc5..a4c75a27 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -36,7 +36,7 @@ class NewPost extends Component { categoriesOptions: [], categoriesValue: [], loading: false, - thumbnail: null + media: null }; } @@ -72,9 +72,10 @@ class NewPost extends Component { ShortDescription: this.state.shortDescription, Content: this.state.postContent, PostOptionsDefaultViewModel: postOptionsDefaultViewModel, - PostStatus: postStatus + PostStatus: postStatus, + Media: this.state.media }; - + console.log(viewModel); PostWithSpinner .call(this, SAVE_POST_API, viewModel) .then(res => { @@ -100,7 +101,8 @@ class NewPost extends Component { this.setState({categoriesValue}); }; - handleImagePreview = imageValue => { + handleImagePreview = media => { + console.log(media); this.clearImageState(); // eslint-disable-next-line no-undef let reader = new FileReader(); @@ -110,8 +112,8 @@ class NewPost extends Component { $('#thumbnail-preview').attr('src', e.target.result); } - this.setState({thumbnail: imageValue}); - reader.readAsDataURL(imageValue); + this.setState({media}); + reader.readAsDataURL(media); } removeImage = () => { @@ -119,7 +121,7 @@ class NewPost extends Component { } clearImageState = () => { - this.setState({thumbnail: null}); + this.setState({media: null}); } render() { const { @@ -131,7 +133,7 @@ class NewPost extends Component { tagOptions, categoriesOptions, categoriesValue, - thumbnail + media } = this.state; return ( @@ -176,8 +178,8 @@ class NewPost extends Component { this.handleImagePreview(thumbnail.target.files[0])}/> + name="media" + onChange={media => this.handleImagePreview(media.target.files[0])}/> @@ -186,7 +188,7 @@ class NewPost extends Component {
diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs index e6acb25c..65e56cda 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using AwesomeCMSCore.Modules.Entities.Entities; using AwesomeCMSCore.Modules.Entities.Enums; @@ -13,7 +13,7 @@ public class PostViewModel public string Content { get; set; } public PostOptionsDefaultViewModel PostOptionsDefaultViewModel { get; set; } public DateTime DateCreated { get; set; } = DateTime.Now; - public ICollection Media { get; set; } + public Media Media { get; set; } public PostStatus PostStatus { get; set; } } } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Data/ApplicationDbContext.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Data/ApplicationDbContext.cs index 8cbe12b4..d979a03d 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Data/ApplicationDbContext.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Data/ApplicationDbContext.cs @@ -36,6 +36,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasMany(r => r.Users).WithOne().HasForeignKey(r => r.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade); modelBuilder.EnableAutoHistory(null); - } + + modelBuilder.Entity() + .HasOne(p => p.Medias) + .WithOne(m => m.Post) + .HasForeignKey(p => p.PostId); + } } } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs index 2ef13021..6ebd5b98 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs @@ -1,11 +1,13 @@ namespace AwesomeCMSCore.Modules.Entities.Entities { - public class Media:BaseEntity - { - public string Name { get; set; } - public string Path { get; set; } - public bool IsDeleted { get; set; } - public virtual Post Post { get; set; } + public class Media : BaseEntity + { + public string Name { get; set; } + public string Path { get; set; } + public string Type { get; set; } + public bool IsDeleted { get; set; } + public int PostId { get; set; } + public virtual Post Post { get; set; } public virtual User User { get; set; } - } + } } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Post.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Post.cs index 0b3f79d4..bd09a217 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Post.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Post.cs @@ -14,6 +14,6 @@ public class Post: BaseEntity public int Views { get; set; } public virtual User User { get; set; } public virtual ICollection Comments { get; set; } - public virtual ICollection Medias { get; set; } + public virtual Media Medias { get; set; } } } From abbc04fea5d21ceafe6e99fde1d9b8e6032a038c Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Tue, 15 Jan 2019 20:11:34 +0700 Subject: [PATCH 4/9] get upload info --- .../React/js/App/Modules/Admin/Post/NewPost.jsx | 11 ++++++++--- .../Controllers/API/V1/PostController.cs | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index a4c75a27..cc838e69 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -75,7 +75,7 @@ class NewPost extends Component { PostStatus: postStatus, Media: this.state.media }; - console.log(viewModel); + PostWithSpinner .call(this, SAVE_POST_API, viewModel) .then(res => { @@ -102,7 +102,6 @@ class NewPost extends Component { }; handleImagePreview = media => { - console.log(media); this.clearImageState(); // eslint-disable-next-line no-undef let reader = new FileReader(); @@ -112,7 +111,13 @@ class NewPost extends Component { $('#thumbnail-preview').attr('src', e.target.result); } - this.setState({media}); + const mediaData = { + name: media.name, + size: media.size, + type: media.type + }; + + this.setState({media: mediaData}); reader.readAsDataURL(media); } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs index 56c440e4..83f7f3e9 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs @@ -1,9 +1,11 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using AwesomeCMSCore.Modules.Admin.Repositories; using AwesomeCMSCore.Modules.Admin.ViewModels; +using AwesomeCMSCore.Modules.Entities.Entities; using AwesomeCMSCore.Modules.Helper.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace AwesomeCMSCore.Modules.Admin.Controllers.API.V1 From 581474d5e4a31f9798433814c3ea8f0ca4951692 Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Tue, 15 Jan 2019 20:24:40 +0700 Subject: [PATCH 5/9] update code --- .../js/App/Modules/Admin/Post/NewPost.jsx | 7 +- .../Controllers/API/V1/PostController.cs | 114 ++++++++++-------- .../Entities/Media.cs | 1 + 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index cc838e69..91467f8e 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -73,7 +73,7 @@ class NewPost extends Component { Content: this.state.postContent, PostOptionsDefaultViewModel: postOptionsDefaultViewModel, PostStatus: postStatus, - Media: this.state.media + Thumbnail: this.state.media }; PostWithSpinner @@ -113,8 +113,9 @@ class NewPost extends Component { const mediaData = { name: media.name, - size: media.size, - type: media.type + fileName: media.name, + length: media.size, + contentType: media.type }; this.setState({media: mediaData}); diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs index 83f7f3e9..7ed7b96a 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs @@ -1,3 +1,4 @@ +using System.IO; using System.Threading.Tasks; using AwesomeCMSCore.Modules.Admin.Repositories; using AwesomeCMSCore.Modules.Admin.ViewModels; @@ -10,63 +11,70 @@ namespace AwesomeCMSCore.Modules.Admin.Controllers.API.V1 { - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ApiVersion("1.0")] - [ApiExplorerSettings(GroupName = "v1")] - [Route("api/v{version:apiVersion}/Post/")] - public class PostController : Controller - { - private readonly IPostRepository _postRepository; + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ApiVersion("1.0")] + [ApiExplorerSettings(GroupName = "v1")] + [Route("api/v{version:apiVersion}/Post/")] + public class PostController : Controller + { + private readonly IPostRepository _postRepository; - public PostController( - IPostRepository postRepository, - IUserService userService) - { - _postRepository = postRepository; - } + public PostController( + IPostRepository postRepository, + IUserService userService) + { + _postRepository = postRepository; + } - [HttpGet("")] - public async Task GetPosts() - { - var postList = await _postRepository.GetPostsDefaultViewModel(); - return Ok(postList); - } - - [HttpGet("{postId}")] - public async Task GetPost(int postId) - { - var post = await _postRepository.GetPost(postId); - return Ok(post); - } + [HttpGet("")] + public async Task GetPosts() + { + var postList = await _postRepository.GetPostsDefaultViewModel(); + return Ok(postList); + } - [HttpPost("SavePost")] - [AllowAnonymous] - public async Task SavePost([FromBody] PostViewModel viewModel) - { - if (viewModel.Id.HasValue) - { - await _postRepository.EditPost(viewModel); - } - else - { - await _postRepository.SavePost(viewModel); - } + [HttpGet("{postId}")] + public async Task GetPost(int postId) + { + var post = await _postRepository.GetPost(postId); + return Ok(post); + } - return Ok(); - } + [HttpPost("SavePost")] + [AllowAnonymous] + public async Task SavePost([FromBody] PostViewModel viewModel, [FromBody] IFormFile Thumbnail) + { + if (viewModel.Id.HasValue) + { + await _postRepository.EditPost(viewModel); + } + else + { + var path = Path.Combine( + Directory.GetCurrentDirectory(), "wwwroot/assets/", + viewModel.Media.Name); + using (var stream = new FileStream(path, FileMode.Create)) + { + await Thumbnail.CopyToAsync(stream); + } + //await _postRepository.SavePost(viewModel); + } - [HttpPut("{postId}")] - public async Task RestorePost(int postId) - { - await _postRepository.RestorePost(postId); - return Ok(); - } + return Ok(); + } - [HttpDelete("{postId}")] - public async Task DeletePost(int postId) - { - await _postRepository.DeletePost(postId); - return Ok(); - } - } + [HttpPut("{postId}")] + public async Task RestorePost(int postId) + { + await _postRepository.RestorePost(postId); + return Ok(); + } + + [HttpDelete("{postId}")] + public async Task DeletePost(int postId) + { + await _postRepository.DeletePost(postId); + return Ok(); + } + } } \ No newline at end of file diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs index 6ebd5b98..6d8cb35d 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Entities/Entities/Media.cs @@ -4,6 +4,7 @@ public class Media : BaseEntity { public string Name { get; set; } public string Path { get; set; } + public byte Size { get; set; } public string Type { get; set; } public bool IsDeleted { get; set; } public int PostId { get; set; } From d8c6a79a01e61cbf5746355d4c966c380dc4f7d5 Mon Sep 17 00:00:00 2001 From: ngohungphuc Date: Mon, 21 Jan 2019 18:16:42 +0700 Subject: [PATCH 6/9] update code --- .../AwesomeCMSCore/AwesomeCMSCore.csproj | 1 + .../React/js/App/Helper/Http.js | 253 ++++----- .../js/App/Modules/Admin/Post/NewPost.jsx | 483 +++++++++--------- .../Controllers/API/V1/PostController.cs | 32 +- .../ViewModels/PostViewModel.cs | 24 +- 5 files changed, 401 insertions(+), 392 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/AwesomeCMSCore.csproj b/src/AwesomeCMSCore/AwesomeCMSCore/AwesomeCMSCore.csproj index 623ea63f..351c8e88 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/AwesomeCMSCore.csproj +++ b/src/AwesomeCMSCore/AwesomeCMSCore/AwesomeCMSCore.csproj @@ -3,6 +3,7 @@ netcoreapp2.2 69ce3cf7-0978-46d5-8702-8ccc4ce831d0 + 3.1 diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Helper/Http.js b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Helper/Http.js index 463fbb12..e5d2d933 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Helper/Http.js +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Helper/Http.js @@ -2,157 +2,160 @@ import axios from "axios"; import qs from 'qs'; import { - APP_ENUM, - STATUS_CODE + APP_ENUM, + STATUS_CODE } from "./AppEnum"; import { - getStorage, - setStorage + getStorage, + setStorage } from "./StorageHelper"; -import { TOKEN_ENDPOINT } from './API_Endpoint/AccountEndpoint'; +import { + TOKEN_ENDPOINT +} from './API_Endpoint/AccountEndpoint'; export function Get(url) { - const authHeader = initAuthHeaders(); - const config = { - headers: { - Authorization: "Bearer " + authHeader - } - }; - - return axios.get(url, config); + const authHeader = initAuthHeaders(); + const config = { + headers: { + Authorization: "Bearer " + authHeader + } + }; + + return axios.get(url, config); } export function Post(url, data) { - const authHeader = initAuthHeaders(); - const config = { - headers: { + const authHeader = initAuthHeaders(); + const config = { + headers: { Authorization: "Bearer " + authHeader - } - }; + } + }; - return axios.post(url, data, config); + return axios.post(url, data, config); } -export function PostWithSpinner(url, data) { - return new Promise((resolve, reject) => { - const authHeader = initAuthHeaders(); - const config = { - headers: { - Authorization: "Bearer " + authHeader - } - }; - - this.setState({ - loading: true - }); - - axios - .post(url, data, config) - .then(data => { - this.setState({ - loading: false - }); - resolve(data); - }) - .catch(error => { - this.setState({ - loading: false - }); - reject(error); - }); - }); +export function PostWithSpinner(url, data, contentType = "") { + return new Promise((resolve, reject) => { + const authHeader = initAuthHeaders(); + const config = { + headers: { + Authorization: "Bearer " + authHeader, + 'Content-Type': contentType == "" ? "application/json;charset=UTF-8" : contentType + } + }; + + this.setState({ + loading: true + }); + + axios + .post(url, data, config) + .then(data => { + this.setState({ + loading: false + }); + resolve(data); + }) + .catch(error => { + this.setState({ + loading: false + }); + reject(error); + }); + }); } export function PutWithSpinner(url, data) { - return new Promise((resolve, reject) => { - const authHeader = initAuthHeaders(); - const config = { - headers: { - Authorization: "Bearer " + authHeader - } - }; - - this.setState({ - loading: true - }); - - axios - .put(url, data, config) - .then(data => { - this.setState({ - loading: false - }); - resolve(data); - }) - .catch(error => { - this.setState({ - loading: false - }); - reject(error); - }); - }); + return new Promise((resolve, reject) => { + const authHeader = initAuthHeaders(); + const config = { + headers: { + Authorization: "Bearer " + authHeader + } + }; + + this.setState({ + loading: true + }); + + axios + .put(url, data, config) + .then(data => { + this.setState({ + loading: false + }); + resolve(data); + }) + .catch(error => { + this.setState({ + loading: false + }); + reject(error); + }); + }); } export function Put(url, data) { - const authHeader = initAuthHeaders(); - const config = { - headers: { - Authorization: "Bearer " + authHeader - } - }; - - return axios.put(url, data, config); + const authHeader = initAuthHeaders(); + const config = { + headers: { + Authorization: "Bearer " + authHeader + } + }; + + return axios.put(url, data, config); } export function Delete(url) { - const authHeader = initAuthHeaders(); - const config = { - headers: { - Authorization: "Bearer " + authHeader - } - }; - - return axios.delete(url, config); + const authHeader = initAuthHeaders(); + const config = { + headers: { + Authorization: "Bearer " + authHeader + } + }; + + return axios.delete(url, config); } function initAuthHeaders() { - const token = getStorage(APP_ENUM.AUTH_TOKEN); - if (token != null) { - return token.access_token; - } + const token = getStorage(APP_ENUM.AUTH_TOKEN); + if (token != null) { + return token.access_token; + } } axios.interceptors.response.use(function (response) { - return response; + return response; }, function (error) { - const originalRequest = error.config; - if (error.response.status === STATUS_CODE.NotAuthorize) { - const token = getStorage(APP_ENUM.AUTH_TOKEN); - const refreshToken = token.refresh_token; - - Post( - TOKEN_ENDPOINT, - qs.stringify({ - refresh_token: refreshToken, - grant_type: "refresh_token", - scope: "offline_access" - }) - ).then(function (res) { - let token = { - access_token: res.data.access_token, - refresh_token: refreshToken, - token_type: res.data.token_type, - expires_in: res.data.expires_in - }; - - setStorage(APP_ENUM.AUTH_TOKEN, token); - window.location.reload(); - return axios(originalRequest); - - }).catch(function (error) { - console.log(error); - }); - } - - return Promise.reject(error); + const originalRequest = error.config; + if (error.response.status === STATUS_CODE.NotAuthorize) { + const token = getStorage(APP_ENUM.AUTH_TOKEN); + const refreshToken = token.refresh_token; + + Post( + TOKEN_ENDPOINT, + qs.stringify({ + refresh_token: refreshToken, + grant_type: "refresh_token", + scope: "offline_access" + }) + ).then(function (res) { + let token = { + access_token: res.data.access_token, + refresh_token: refreshToken, + token_type: res.data.token_type, + expires_in: res.data.expires_in + }; + + setStorage(APP_ENUM.AUTH_TOKEN, token); + window.location.reload(); + return axios(originalRequest); + + }).catch(function (error) { + console.log(error); + }); + } + + return Promise.reject(error); }) \ No newline at end of file diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index 91467f8e..f9db6513 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -1,22 +1,15 @@ -import React, {Component} from "react"; -import {render} from "react-dom"; -import { - Container, - Row, - Col, - Button, - Card, - CardTitle -} from "reactstrap"; +import React, { Component } from "react"; +import { render } from "react-dom"; +import { Container, Row, Col, Button, Card, CardTitle } from "reactstrap"; import toastr from "toastr"; import PropTypes from "prop-types"; -import {Get, PostWithSpinner} from "Helper/Http"; -import {STATUS_CODE, POST_STATUS} from "Helper/AppEnum"; -import {SAVE_POST_API} from "Helper/API_Endpoint/PostEndpoint"; -import {isDomExist} from "Helper/Util"; -import {onChange, onBlur} from "Helper/StateHelper"; -import {POST_OPTIONS_API} from "Helper/API_Endpoint/PostOptionEndpoint"; +import { Get, Post } from "Helper/Http"; +import { STATUS_CODE, POST_STATUS } from "Helper/AppEnum"; +import { SAVE_POST_API } from "Helper/API_Endpoint/PostEndpoint"; +import { isDomExist } from "Helper/Util"; +import { onChange, onBlur } from "Helper/StateHelper"; +import { POST_OPTIONS_API } from "Helper/API_Endpoint/PostOptionEndpoint"; import ACCEditor from "Common/ACCInput/ACCEditor.jsx"; import ACCButton from "Common/ACCButton/ACCButton.jsx"; @@ -25,250 +18,260 @@ import ACCReactSelect from "Common/ACCSelect/ACCReactSelect.jsx"; import Spinner from "Common/ACCAnimation/Spinner.jsx"; class NewPost extends Component { - constructor(props) { - super(props); - this.state = { - postContent: "", - title: "", - shortDescription: "", - tagValue: [], - tagOptions: [], - categoriesOptions: [], - categoriesValue: [], - loading: false, - media: null - }; - } + constructor(props) { + super(props); + this.state = { + postContent: "", + title: "", + shortDescription: "", + tagValue: [], + tagOptions: [], + categoriesOptions: [], + categoriesValue: [], + loading: false, + thumbnail: null + }; + } - componentDidMount() { - Get(`${POST_OPTIONS_API}/Options`).then(res => { - this.setState({ - tagOptions: res.data.tagViewModel.value - ? JSON.parse(res.data.tagViewModel.value) - : [], - categoriesOptions: res.data.categoriesViewModel.value - ? JSON.parse(res.data.categoriesViewModel.value) - : [] - }); - }); - } + componentDidMount() { + Get(`${POST_OPTIONS_API}/Options`).then(res => { + this.setState({ + tagOptions: res.data.tagViewModel.value + ? JSON.parse(res.data.tagViewModel.value) + : [], + categoriesOptions: res.data.categoriesViewModel.value + ? JSON.parse(res.data.categoriesViewModel.value) + : [] + }); + }); + } - newPost = (e, postStatus) => { - e.preventDefault(); + newPost = (e, postStatus) => { + e.preventDefault(); - const postOptionsDefaultViewModel = { - tagViewModel: { - key: JSON.stringify(this.state.tagValue.map(x => x.value)), - value: JSON.stringify(this.state.tagValue) - }, - categoriesViewModel: { - key: JSON.stringify(this.state.categoriesValue.map(x => x.value)), - value: JSON.stringify(this.state.categoriesValue) - } - }; + const postOptionsDefaultViewModel = { + tagViewModel: { + key: JSON.stringify(this.state.tagValue.map(x => x.value)), + value: JSON.stringify(this.state.tagValue) + }, + categoriesViewModel: { + key: JSON.stringify(this.state.categoriesValue.map(x => x.value)), + value: JSON.stringify(this.state.categoriesValue) + } + }; - const viewModel = { - Title: this.state.title, - ShortDescription: this.state.shortDescription, - Content: this.state.postContent, - PostOptionsDefaultViewModel: postOptionsDefaultViewModel, - PostStatus: postStatus, - Thumbnail: this.state.media - }; + // eslint-disable-next-line no-undef + const formdata = new FormData(); + formdata.append("thumbnail", this.state.thumbnail); - PostWithSpinner - .call(this, SAVE_POST_API, viewModel) - .then(res => { - if (res.status === STATUS_CODE.Success) - return toastr.success("Create new post success"); - } - ); + const viewModel = { + Title: this.state.title, + ShortDescription: this.state.shortDescription, + Content: this.state.postContent, + PostOptionsDefaultViewModel: postOptionsDefaultViewModel, + PostStatus: postStatus, + Media: formdata.get("thumbnail") }; - handleEditorChange = e => { - this.setState({ - postContent: e - .target - .getContent() - }); - }; + console.log(viewModel); - handleOnTagChange = tagValue => { - this.setState({tagValue}); - }; + Post(SAVE_POST_API, viewModel).then(res => { + if (res.status === STATUS_CODE.Success) + return toastr.success("Create new post success"); + }); + }; - handleOnCatChange = categoriesValue => { - this.setState({categoriesValue}); - }; + handleEditorChange = e => { + this.setState({ + postContent: e.target.getContent() + }); + }; + + handleOnTagChange = tagValue => { + this.setState({ tagValue }); + }; - handleImagePreview = media => { - this.clearImageState(); - // eslint-disable-next-line no-undef - let reader = new FileReader(); + handleOnCatChange = categoriesValue => { + this.setState({ categoriesValue }); + }; - reader.onload = function (e) { - // eslint-disable-next-line no-undef - $('#thumbnail-preview').attr('src', e.target.result); - } + handleImagePreview = thumbnail => { + this.clearImageState(); + + // eslint-disable-next-line no-undef + let reader = new FileReader(); + reader.onload = function(e) { + // eslint-disable-next-line no-undef + $("#thumbnail-preview").attr("src", e.target.result); + }; - const mediaData = { - name: media.name, - fileName: media.name, - length: media.size, - contentType: media.type - }; + this.setState({ thumbnail }); + reader.readAsDataURL(thumbnail); + }; - this.setState({media: mediaData}); - reader.readAsDataURL(media); - } + removeImage = () => { + this.clearImageState(); + }; - removeImage = () => { - this.clearImageState(); - } + clearImageState = () => { + this.setState({ thumbnail: null }); + }; - clearImageState = () => { - this.setState({media: null}); - } - render() { - const { - shortDescription, - title, - disabled, - loading, - tagValue, - tagOptions, - categoriesOptions, - categoriesValue, - media - } = this.state; + render() { + const { + shortDescription, + title, + disabled, + loading, + tagValue, + tagOptions, + categoriesOptions, + categoriesValue, + thumbnail + } = this.state; - return ( - -
-
- - - - - onChange.call(this, title)} - onBlur={title => onBlur.call(this, title)}/> - - - - - onChange.call(this, shortDescription)} - onBlur={shortDescription => onBlur.call(this, shortDescription)}/> - - - - -
-
- Upload -
-
- this.handleImagePreview(media.target.files[0])}/> - -
-
- -
- - -
-
- Preview -
-
- - -
-

Please note that image will be resize when upload

-
-
-
- -
- - - - - - - - - - Post Options - this.handleOnTagChange(value)}/> -
- this.handleOnCatChange(value)}/> -
- -
{" "} {!loading - ? ( -
- this.newPost(e, POST_STATUS.Draft)}/> -
- this.newPost(e, POST_STATUS.Published)}/> -
- ) - : ()} -
- -
- -
-
- ); - } + return ( + +
+
+ + + + + onChange.call(this, title)} + onBlur={title => onBlur.call(this, title)} + /> + + + + + + onChange.call(this, shortDescription) + } + onBlur={shortDescription => + onBlur.call(this, shortDescription) + } + /> + + + + +
+
+ + Upload + +
+
+ + this.handleImagePreview(thumbnail.target.files[0]) + } + /> + +
+
+ +
+ + +
+
Preview
+
+ + +
+

+ Please note that image will be resize when upload +

+
+
+
+ +
+ + + + + + + + + + Post Options + this.handleOnTagChange(value)} + /> +
+ this.handleOnCatChange(value)} + /> +
+ +
{" "} + {!loading ? ( +
+ this.newPost(e, POST_STATUS.Draft)} + /> +
+ this.newPost(e, POST_STATUS.Published)} + /> +
+ ) : ( + + )} +
+ +
+ +
+
+ ); + } } NewPost.propTypes = { - visible: PropTypes.bool + visible: PropTypes.bool }; if (isDomExist("newPostContent")) { - render( - , document.getElementById("newPostContent")); + render(, document.getElementById("newPostContent")); } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs index 7ed7b96a..18c2442c 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Controllers/API/V1/PostController.cs @@ -42,23 +42,23 @@ public async Task GetPost(int postId) [HttpPost("SavePost")] [AllowAnonymous] - public async Task SavePost([FromBody] PostViewModel viewModel, [FromBody] IFormFile Thumbnail) + public async Task SavePost(PostViewModel viewModel) { - if (viewModel.Id.HasValue) - { - await _postRepository.EditPost(viewModel); - } - else - { - var path = Path.Combine( - Directory.GetCurrentDirectory(), "wwwroot/assets/", - viewModel.Media.Name); - using (var stream = new FileStream(path, FileMode.Create)) - { - await Thumbnail.CopyToAsync(stream); - } - //await _postRepository.SavePost(viewModel); - } + //if (viewModel.Id.HasValue) + //{ + // await _postRepository.EditPost(viewModel); + //} + //else + //{ + // var path = Path.Combine( + // Directory.GetCurrentDirectory(), "wwwroot/assets/", + // thumbnail.Name); + // using (var stream = new FileStream(path, FileMode.Create)) + // { + // await thumbnail.CopyToAsync(stream); + // } + // await _postRepository.SavePost(viewModel); + //} return Ok(); } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs index 65e56cda..d609cf3c 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs @@ -2,18 +2,20 @@ using System.Collections.Generic; using AwesomeCMSCore.Modules.Entities.Entities; using AwesomeCMSCore.Modules.Entities.Enums; +using Microsoft.AspNetCore.Http; namespace AwesomeCMSCore.Modules.Admin.ViewModels { - public class PostViewModel - { - public int? Id { get; set; } - public string Title { get; set; } - public string ShortDescription { get; set; } - public string Content { get; set; } - public PostOptionsDefaultViewModel PostOptionsDefaultViewModel { get; set; } - public DateTime DateCreated { get; set; } = DateTime.Now; - public Media Media { get; set; } - public PostStatus PostStatus { get; set; } - } + public class PostViewModel + { + public int? Id { get; set; } + public string Title { get; set; } + public string ShortDescription { get; set; } + public string Content { get; set; } + public PostOptionsDefaultViewModel PostOptionsDefaultViewModel { get; set; } + public DateTime DateCreated { get; set; } = DateTime.Now; + public IFormFile Thumbnail { get; set; } + public ICollection Media { get; set; } + public PostStatus PostStatus { get; set; } + } } From 194cf10544ea89c8b7f96ffce5636d517420b3d0 Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Mon, 21 Jan 2019 23:22:53 +0700 Subject: [PATCH 7/9] get name of file in preview --- .../js/App/Modules/Admin/Post/NewPost.jsx | 487 +++++++++--------- 1 file changed, 245 insertions(+), 242 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index f9db6513..e65cef75 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -1,15 +1,22 @@ -import React, { Component } from "react"; -import { render } from "react-dom"; -import { Container, Row, Col, Button, Card, CardTitle } from "reactstrap"; +import React, {Component} from "react"; +import {render} from "react-dom"; +import { + Container, + Row, + Col, + Button, + Card, + CardTitle +} from "reactstrap"; import toastr from "toastr"; import PropTypes from "prop-types"; -import { Get, Post } from "Helper/Http"; -import { STATUS_CODE, POST_STATUS } from "Helper/AppEnum"; -import { SAVE_POST_API } from "Helper/API_Endpoint/PostEndpoint"; -import { isDomExist } from "Helper/Util"; -import { onChange, onBlur } from "Helper/StateHelper"; -import { POST_OPTIONS_API } from "Helper/API_Endpoint/PostOptionEndpoint"; +import {Get, PostWithSpinner} from "Helper/Http"; +import {STATUS_CODE, POST_STATUS} from "Helper/AppEnum"; +import {SAVE_POST_API} from "Helper/API_Endpoint/PostEndpoint"; +import {isDomExist} from "Helper/Util"; +import {onChange, onBlur} from "Helper/StateHelper"; +import {POST_OPTIONS_API} from "Helper/API_Endpoint/PostOptionEndpoint"; import ACCEditor from "Common/ACCInput/ACCEditor.jsx"; import ACCButton from "Common/ACCButton/ACCButton.jsx"; @@ -18,260 +25,256 @@ import ACCReactSelect from "Common/ACCSelect/ACCReactSelect.jsx"; import Spinner from "Common/ACCAnimation/Spinner.jsx"; class NewPost extends Component { - constructor(props) { - super(props); - this.state = { - postContent: "", - title: "", - shortDescription: "", - tagValue: [], - tagOptions: [], - categoriesOptions: [], - categoriesValue: [], - loading: false, - thumbnail: null - }; - } + constructor(props) { + super(props); + this.state = { + postContent: "", + title: "", + shortDescription: "", + tagValue: [], + tagOptions: [], + categoriesOptions: [], + categoriesValue: [], + loading: false, + thumbnail: null + }; + } - componentDidMount() { - Get(`${POST_OPTIONS_API}/Options`).then(res => { - this.setState({ - tagOptions: res.data.tagViewModel.value - ? JSON.parse(res.data.tagViewModel.value) - : [], - categoriesOptions: res.data.categoriesViewModel.value - ? JSON.parse(res.data.categoriesViewModel.value) - : [] - }); - }); - } + componentDidMount() { + Get(`${POST_OPTIONS_API}/Options`).then(res => { + this.setState({ + tagOptions: res.data.tagViewModel.value + ? JSON.parse(res.data.tagViewModel.value) + : [], + categoriesOptions: res.data.categoriesViewModel.value + ? JSON.parse(res.data.categoriesViewModel.value) + : [] + }); + }); + } - newPost = (e, postStatus) => { - e.preventDefault(); + newPost = (e, postStatus) => { + e.preventDefault(); - const postOptionsDefaultViewModel = { - tagViewModel: { - key: JSON.stringify(this.state.tagValue.map(x => x.value)), - value: JSON.stringify(this.state.tagValue) - }, - categoriesViewModel: { - key: JSON.stringify(this.state.categoriesValue.map(x => x.value)), - value: JSON.stringify(this.state.categoriesValue) - } - }; + const postOptionsDefaultViewModel = { + tagViewModel: { + key: JSON.stringify(this.state.tagValue.map(x => x.value)), + value: JSON.stringify(this.state.tagValue) + }, + categoriesViewModel: { + key: JSON.stringify(this.state.categoriesValue.map(x => x.value)), + value: JSON.stringify(this.state.categoriesValue) + } + }; - // eslint-disable-next-line no-undef - const formdata = new FormData(); - formdata.append("thumbnail", this.state.thumbnail); + // eslint-disable-next-line no-undef + const formdata = new FormData(); + formdata.append("thumbnail", this.state.thumbnail); - const viewModel = { - Title: this.state.title, - ShortDescription: this.state.shortDescription, - Content: this.state.postContent, - PostOptionsDefaultViewModel: postOptionsDefaultViewModel, - PostStatus: postStatus, - Media: formdata.get("thumbnail") - }; + const viewModel = { + Title: this.state.title, + ShortDescription: this.state.shortDescription, + Content: this.state.postContent, + PostOptionsDefaultViewModel: postOptionsDefaultViewModel, + PostStatus: postStatus, + Media: formdata + }; - console.log(viewModel); + PostWithSpinner + .call(this, SAVE_POST_API, viewModel) + .then(res => { + if (res.status === STATUS_CODE.Success) + return toastr.success("Create new post success"); + } + ); + }; - Post(SAVE_POST_API, viewModel).then(res => { - if (res.status === STATUS_CODE.Success) - return toastr.success("Create new post success"); - }); - }; + handleEditorChange = e => { + this.setState({ + postContent: e + .target + .getContent() + }); + }; - handleEditorChange = e => { - this.setState({ - postContent: e.target.getContent() - }); - }; + handleOnTagChange = tagValue => { + this.setState({tagValue}); + }; - handleOnTagChange = tagValue => { - this.setState({ tagValue }); - }; + handleOnCatChange = categoriesValue => { + this.setState({categoriesValue}); + }; - handleOnCatChange = categoriesValue => { - this.setState({ categoriesValue }); - }; + handleImagePreview = thumbnail => { + this.clearImageState(); - handleImagePreview = thumbnail => { - this.clearImageState(); + // eslint-disable-next-line no-undef + let reader = new FileReader(); + reader.onload = function (e) { + // eslint-disable-next-line no-undef + $("#thumbnail-preview").attr("src", e.target.result); + }; - // eslint-disable-next-line no-undef - let reader = new FileReader(); - reader.onload = function(e) { - // eslint-disable-next-line no-undef - $("#thumbnail-preview").attr("src", e.target.result); + this.setState({thumbnail}); + reader.readAsDataURL(thumbnail); }; - this.setState({ thumbnail }); - reader.readAsDataURL(thumbnail); - }; - - removeImage = () => { - this.clearImageState(); - }; + removeImage = () => { + this.clearImageState(); + }; - clearImageState = () => { - this.setState({ thumbnail: null }); - }; + clearImageState = () => { + this.setState({thumbnail: null}); + }; - render() { - const { - shortDescription, - title, - disabled, - loading, - tagValue, - tagOptions, - categoriesOptions, - categoriesValue, - thumbnail - } = this.state; + render() { + const { + shortDescription, + title, + disabled, + loading, + tagValue, + tagOptions, + categoriesOptions, + categoriesValue, + thumbnail + } = this.state; - return ( - -
-
- - - - - onChange.call(this, title)} - onBlur={title => onBlur.call(this, title)} - /> - - - - - - onChange.call(this, shortDescription) - } - onBlur={shortDescription => - onBlur.call(this, shortDescription) - } - /> - - - - -
-
- - Upload - -
-
- - this.handleImagePreview(thumbnail.target.files[0]) - } - /> - -
-
- -
- - -
-
Preview
-
- - -
-

- Please note that image will be resize when upload -

-
-
-
- -
- - - - - - - - - - Post Options - this.handleOnTagChange(value)} - /> -
- this.handleOnCatChange(value)} - /> -
- -
{" "} - {!loading ? ( -
- this.newPost(e, POST_STATUS.Draft)} - /> -
- this.newPost(e, POST_STATUS.Published)} - /> -
- ) : ( - - )} -
- -
- -
-
- ); - } + return ( + +
+
+ + + + + onChange.call(this, title)} + onBlur={title => onBlur.call(this, title)}/> + + + + + onChange.call(this, shortDescription)} + onBlur={shortDescription => onBlur.call(this, shortDescription)}/> + + + + +
+
+ + Upload + +
+
+ this.handleImagePreview(thumbnail.target.files[0])}/> + +
+
+ +
+ + +
+
Preview
+
+ + +
+

+ Please note that image will be resize when upload +

+
+
+
+ +
+ + + + + + + + + + Post Options + this.handleOnTagChange(value)}/> +
+ this.handleOnCatChange(value)}/> +
+ +
{" "} {!loading + ? ( +
+ this.newPost(e, POST_STATUS.Draft)}/> +
+ this.newPost(e, POST_STATUS.Published)}/> +
+ ) + : ()} +
+ +
+ +
+
+ ); + } } NewPost.propTypes = { - visible: PropTypes.bool + visible: PropTypes.bool }; if (isDomExist("newPostContent")) { - render(, document.getElementById("newPostContent")); + render( + , document.getElementById("newPostContent")); } From 5a14e41d1d6f67e4860da916b70909106220b040 Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Mon, 21 Jan 2019 23:48:18 +0700 Subject: [PATCH 8/9] Work around when upload same image --- .../js/App/Modules/Admin/Post/NewPost.jsx | 114 ++++++++++-------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index e65cef75..b3b4e3ec 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -36,7 +36,8 @@ class NewPost extends Component { categoriesOptions: [], categoriesValue: [], loading: false, - thumbnail: null + thumbnail: null, + resetting: false }; } @@ -123,8 +124,18 @@ class NewPost extends Component { this.clearImageState(); }; + /** + * + * Work around when upload same image + * @memberof NewPost + */ clearImageState = () => { this.setState({thumbnail: null}); + this.setState({ + resetting: true + }, () => { + this.setState({resetting: false}); + }); }; render() { @@ -137,7 +148,8 @@ class NewPost extends Component { tagOptions, categoriesOptions, categoriesValue, - thumbnail + thumbnail, + resetting } = this.state; return ( @@ -172,54 +184,58 @@ class NewPost extends Component { onBlur={shortDescription => onBlur.call(this, shortDescription)}/> - - -
-
- - Upload - -
-
- this.handleImagePreview(thumbnail.target.files[0])}/> - -
-
- -
- - -
-
Preview
-
- - -
-

- Please note that image will be resize when upload -

+ {!resetting && ( +
+ + +
+
+ + Upload + +
+
+ this.handleImagePreview(thumbnail.target.files[0])}/> + +
-
-
- - + + + + +
+
Preview
+
+ + +
+

+ Please note that image will be resize when upload +

+
+
+
+ +
+
+ )} From acc5b5e26cff2757241122d023e9b12761673f18 Mon Sep 17 00:00:00 2001 From: Tony Hudson Date: Tue, 22 Jan 2019 00:44:03 +0700 Subject: [PATCH 9/9] Upload image --- .../Extension/ServiceCollectionExtensions.cs | 1 + .../js/App/Modules/Admin/Post/NewPost.jsx | 18 +++---- .../01b1b398ce5f7ac22f3dcb29f2bff2f5.jpg | Bin 0 -> 27821 bytes .../Controllers/API/V1/PostController.cs | 44 ++++++++++-------- .../Repositories/IPostRepository.cs | 2 +- .../ViewModels/PostViewModel.cs | 7 ++- .../Extensions/IFormFileExtensions.cs | 33 +++++++++++++ .../Services/IJsonParseService.cs | 11 +++++ .../Services/JsonParseService.cs | 16 +++++++ 9 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 src/AwesomeCMSCore/AwesomeCMSCore/wwwroot/assets/01b1b398ce5f7ac22f3dcb29f2bff2f5.jpg create mode 100644 src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Extensions/IFormFileExtensions.cs create mode 100644 src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/IJsonParseService.cs create mode 100644 src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/JsonParseService.cs diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/Extension/ServiceCollectionExtensions.cs b/src/AwesomeCMSCore/AwesomeCMSCore/Extension/ServiceCollectionExtensions.cs index 785af4b7..32a10c1e 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/Extension/ServiceCollectionExtensions.cs +++ b/src/AwesomeCMSCore/AwesomeCMSCore/Extension/ServiceCollectionExtensions.cs @@ -143,6 +143,7 @@ public static IServiceCollection InjectApplicationServices(this IServiceCollecti services.AddScoped(); services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>)); + services.AddScoped(typeof(IJsonParseService<>), typeof(JsonParseService<>)); services.AddScoped(); services.AddScoped(); diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx index b3b4e3ec..0f436675 100644 --- a/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx +++ b/src/AwesomeCMSCore/AwesomeCMSCore/React/js/App/Modules/Admin/Post/NewPost.jsx @@ -70,19 +70,15 @@ class NewPost extends Component { // eslint-disable-next-line no-undef const formdata = new FormData(); - formdata.append("thumbnail", this.state.thumbnail); - - const viewModel = { - Title: this.state.title, - ShortDescription: this.state.shortDescription, - Content: this.state.postContent, - PostOptionsDefaultViewModel: postOptionsDefaultViewModel, - PostStatus: postStatus, - Media: formdata - }; + formdata.append("Title", this.state.title); + formdata.append("ShortDescription", this.state.shortDescription); + formdata.append("Content", this.state.postContent); + formdata.append("PostOptionsViewModel", JSON.stringify(postOptionsDefaultViewModel)); + formdata.append("PostStatus", postStatus); + formdata.append("Thumbnail", this.state.thumbnail); PostWithSpinner - .call(this, SAVE_POST_API, viewModel) + .call(this, SAVE_POST_API, formdata, 'multipart/form-data') .then(res => { if (res.status === STATUS_CODE.Success) return toastr.success("Create new post success"); diff --git a/src/AwesomeCMSCore/AwesomeCMSCore/wwwroot/assets/01b1b398ce5f7ac22f3dcb29f2bff2f5.jpg b/src/AwesomeCMSCore/AwesomeCMSCore/wwwroot/assets/01b1b398ce5f7ac22f3dcb29f2bff2f5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a8ec3b8658d860cbcac2dae394f00b7a0422558 GIT binary patch literal 27821 zcmb5Vbx<5Zw?4eU;TJw4|<&(p7~ue$({f{eTj01gfSfO|UtuNweK01^-p2@!~dgouQU zjD&&)LPJAEMZ?F!#sCrF6B85R6B3eA&{2_+(U21oQnOIg&_fxS7)hvDIanDu=olCo z{__wxWMpJC6f`_EG&}}ULQ;nR-}X8Hz(Il2fagJgg8<-h;1F=&UWWi=002A!+<%7q ze+G{LfO{J+@|%<7&G8@mKiAt84gnsB___|jK!5|lgAhP(JvYg!B|kI3x*FM?P4Z~T z(i5G@dtF>pT8aFJtG2v}gd-&RkqS&qa`G%yEF@k!W_@&W9W|V4)(YS#FIay%{hcDt}$-?L^z^6T4(Y&Sl81ivC2~(OdFaQDGw@l@%Hylm{R(CyV8I1 zYv!kQ&try#2mvY^uAD+Uu(PHLVnoe!oS~x?9XuVupA(S$IGCXV=y8-su+^gr25cJH2mAcaR`AqUMr3VDv|5yN6z-UhBV#Kit_|Dh7%&G z>zqhBX+Nc_n>T927Et!^3$;h#-PqnAIaVNBr6cS&n!o2tU9}U8`Y`EYjL)5w$neqVjKXqonZ&^vFE$v z;S-;V*Z)CZu+Pj7`zX9Tf0FB985>bjrT(5;UXtL4KX%&9U#`t>A7H3AY`2WuC#p{S zJ$LAJ(_d$;=w@8-iF6oC79UmRk0+&A$h0mr3Mjpc)$)Z}Kq61RqA(GACYC z*p8k)Uf%N9YM&PvdlLIenC_gJ33SS)s@18@T)HQjp?_P3V<{?^V zVz*ev?Mv*fCa<6CL?3)<8l7}8Dz=53R3!mEf{Z^T#~;BpM))NjYToVStzx`}&HuGp zF5gxSn`X%Y3!8hzS04#(%%mdZl<&aXUAJP2LM34LF;7Jgo6_Iu$Yr32f38R8sl$Gv z$uwipq+;st_whMX3O=WkVdHc81I=^XOdrwStoM<{Jw9^Dhl?k^#=RzV733R&{GM0@ z_7cG4?U)WPcp~XW8B9%c_hYu-AeH4_-npN>danT1W8wU-OptQ#qYwmrZh;xwtUm$T z!WUhC@~nTn6QTY4?P;chs>nijbIe&ue`?gwew}w!()FjVOxe|DgZ1I3o}xe7$jd() z)W&KF+QQM{*kY$Xz04BsAOU*>^r!#iw|zPO`043i+XteejzfxX+u5O8v^VR`_Zc|_ z6~Q^iE>>oojh|=2ELN7H22XGDD~{W@#HgCSJN&ExNLC_4)vg=8+=V`RUzgnlB=C-_ z4~`!bSW?X%i<@mRqi<L4)k@yvMhc*J5-0s}dKFn&o6I~y5mtwcFjrOyHNLtl7v zUh(!|GgZpk7p)l~0z1?SnlE`NN?Zw5Rav}$d;GUd^%ZL9zQfr-jtBZXv~6gO^~AR* zEJv3`-?Pttg#B!w`<2GE_U$6L@pjLxH&4$>+Rx5U#RFj<{|Wk=LK_MiF=lP+W&O0j()|Q~ z<+<>5bH07JC|#MfLS*f4!e|PLkxtT8$Z(VNlBSJLV?rlviuJi@;SkQBpkGV8m88Uc zGUh(1$oYQfvp9X>k&$||^nSXCV5;jo3I752Cls*Xtqx_INwmA>_rt0?oGOy|%S_B5 zruF)wF1eb~b2?t`3JwXc#lZ%;!=}@*x|JN&l1U3qBP0cl5JRqm2I-r0$ITK#A6q><5z=h+A3>PZ6z9w~5W7j6I!knh@bq43ZlDawPXW zJx%LOw(nQ-V-N1*pXEhWQsBalP0Vh}_<%UBa;QvZSmK{F5CM!D#?hpO`aQK5T3dZ; zA`2s>FG1O~@uvv=fHNZ*G;_P`G+VXox0pZdeg|B!SNQeLLYZgJ#sAx*$EnF`IiE~? zxwA)Q+X^pFe@!E#9*2Lgor9>3xg=`rtZ~*x(c~Sjl~igFX3>(+_Z4U%S#PS$I9`q< z%NU=?HNUR9tw|sZl>ZKMf>V&okEaa*zO##%7EJk(yb}j&w%9YxZqO(`gTI-#v13`= zX%763JM+@({rCL?mC#>#KiKn6Pb2s_2TIXCamvd`#Ir0cX=%FK&pIawHEvKljznmo z6=B6^wYuo^_*{og8X2>;7;UX~rs?`$w;?|Is}0;N@9HNoUwfQhsO4SP?j{s=R<}{d z*Pf~S5sC=YFieGFgj@{TIsZ`BmGoMeKf7;vG2I_KHwlJWcpppx)*PI2um zS?ITK34^l@Rr5Fb!Ml@W)~wd{tr)U4?pkslsd;>n!n)XV$HtSRE3s(&HT;$cdv~f z_cV%W#9||ScuYu+kJ5jp>_@Aj!R1YM`#pEVuw#O}T%d_LU0d{U6}SDYox2dZi}!4` zsQs7NkJqupP4e2a+j*{ooik zyWEheUu}Q-cggh5h%LgmCz9{x#9Jg0rWB?~mGlhNVv zeTJ1$;q;nU@K>IxiJ^->AvfVvO+noD!8G^#{*}B&8wzttREg+n0p7pDS(2_N8KN#B zQ^x6O<@Jzs-aMyjKlu?EQjhuYwu3LMm90vI<#EaHk0!k$SSQJd-Z7eeP)yodna(QL zjZVg`Cz@W%Y3KT>CS5dYAsJw5bYwp*f^3=Zv}tV<5b5H2g4{rp%T_I!jKu0-BBo0@ zK97pI2O3#t7=upUJ;*5jN{YZxN)f$~V@y`l3atY6sIpfB)2$ibsgM0Qpx^e5SGuo) z3dw{01%+ni<(GBc32`N6lm&g{b+{Bdpa~mET&nu@@>@$$#*A!^f9w?Yp>b- zxj?iEBq$nekI=6_FQbVi-B5TcNYeNpnp-o`@jIftDABb{mO3=_T=EU19n$jrY6 z+n8}LF*BW7B+4G+5ONK^L>^JF_y;x6`XvrtyQQC<-v%l6o(fJOW4%${$i4RlO-)Tqk7@lIym)n@X z0$`a8Jv-_xTYZa}^w22dTn$)kk)oxl%e&@GV1LYc><8s9wK$Qu%i~M;@wRc=k-VaJ zq@O*ZuuY&yp^eY?T!vHDoy`!*a@#2FFl7}=ryO(Q-<1edcKM}HAsZc7`T>8-P1Qby zOy&@c=ekTcZ%Zwifsjv$8+5IbIp_NdNVK`&Zh(2qE;_~z*MMuR(-w$%rSJ?;+o-;O?;&iR z`r^zzgj_H}{|k(FjUQ3_@b9k>J57blu3W?xR-uh^lqz<=_5JFc2N0(nAc^r8LCET$ zmAFLRayX>Gy2dcf=a9n4?Q4Ze?K97fq`;`j)%j<0;-;G3M zf=3l%@<=cODaP(`T6%mJombmdC}!^6bdb{s{_u@0l$?Jo9%SWt@F>D!c=9h#AYFInrpjM^T`%c7RxB0#L+8_1) ziv#tyerxD zyK}C3{cv5k9dGn9eWuTtB#qozHvWnmk1y*e!VIITYJ13CRYsn-uK;bwv!~>LVF3*X zJ@mdgK6Ab$*|z|*So^!;SXtp?EJz2}tVo{RALDsmPv?G7Khd$ z&U)BA`hJv}b=idjm%2H>_w!*wDI6XRs`O|%WTAGsD!2UGupo1Qv`=$kfjS2hi3(nmt!Q2ZyuA= zNwr_4)hSb(7e0QYog=Y&t0@80^cVAO@UPnHF5pwzJMwFp)<6r_%3(|6q@P_#G|ma; z&Qe0m(;E}oxRx=DcYflZCBPJMR}`WP@0GVRE^%5;7FRlT&nW9`K#|GXegxMbaL|a(XVDtRIQTLS1T)|R?VSLiH+`nA=E*=%I5gKWzbp4igi1$ksb`{4H2)>yx;Osh3K=p^?D zZUP{E!thQZH~8BfYj6df{geWMSwxn#VDRUf<%(<7YL&lj{l%YJKvOCWe*;!e)Hau1 z0U)SQu+*Kwd#=tuNEJzzD0=zR>pI+IQ+S!^QajC8Tbes2=2_vKL$aDImtQ2s=ze!b zXKyf+Y8$H6n&isR@++*xb|A$&?wkChNggd#`RE~3&0iNeAv=1d<8sm>N{F*M5FmbD zqGUD4N7_AiyS*uGG%FQ3NUCWzaz&I`m3b1!*x=d9S)8lNOtlT)vvP7pY@zA0R-mCTH=rl=wr8vX=9JK1RE-e3kBVh|IzXw+>)&Vm4V5 zo=~a@-lws$YoG4%cw(Fq^|*Kih@ZYg3jsOJFJ)FLAz)$^xL_@D?Cb9iANk2JG8o?! zUIQIEq7K(a%cs$B%JeyX7^tPGb@nGocl%uyqt!Ag+K19#T$;p%t^PEmepnOErD>#hQ=w4V$qf+$g4eKCXZ-@ zABd{IzU?fEe3y{$GG%+0C(~=$8mWiE@u`s|N;u`(E??;C_sM48+$8hi6@f+7aLH$G zL?eMANp3{n*e+`^@cwW#a4k!(Z>n4*FRYD&QTIjZxe8^-aY(T< zANg@-c@K3tH@5>}5JJ$fAm*MR6DO;N0zKU;V7X&5ubK?+6elyh@fE5Zn0~)$QYlfQ1wY$$Qm{UJgU5*zYdV+Y$Qf!pAU|yr z-wep>3g^K29Pe(n*9fEp+IgyZVz|@nW66x<-ctz`h~!Beu3S>F*dZ0$;9)f3%tl@x z4cwMcWaXQyQ*aWJE0gK9;6fcIn7hI7 z9TsgnPd+kZ9rN|e!})2rHS(6Qef-qa$&Wp}%6E=++~(14*qqQMQB~F^uImu=>H!+1 zJ|3Z}0tTraW}&uV9?K|W>s>PI=n0^&I*66_& zv4cbUS)G>9s~;o&n1bZsls7lT7; zNTk4{^09FbOH-1&l2lkVw)2}up8MFa%ut~A1e+c z5Ft4PE{aYVwq-jhb8#xxcqPPrenF~#=vJX?^9vHoR$x$oVM(dwR{jnsJY_Z|DLh>` z-OfxjbxkBmJ_z!tEbwo0UFVPAF8ODovz>z}n+LZrE9NhqpBLzbeYo<=a05YbUKO~H zDL(IIg1W@RjJj)|NtR{5JKM?GW~+`B0b_ZYCL1-&_xpbbGb8M)XVs)0?uEamnj&fbkaAgM(Qk9ku|%mMi<(iT_&h4`c~@LefHc#(H+E3%wV;_DTX86f9P(9YEm2mI?%bT8_`NG@|u={=oT zx=}?QL<-{3M47S!;`QrI^N(=M(zgPzsKX5_y$2;SOmw|zRm=*vtv zQlG?>NkGZdaQ}jd?HtJJ1|*+u)RF+>j6$%H$Ee2hrABPVbf~}{3`#guF&JM42|R@H zAwwHz`wX*YbKV>)zb%JWH}NJ9|Uejmi+%ctkQ&zItqivj;sn9l-9O;*)Qh+)H)2)JqM+4K!p3CzZ&j&Z>E zXdKI@0S)7@Bs3S_7HV5bwYup}EHe08{gu>gy?rteuHcG&$EXA+o zB;`jlM>*Lp_8n!l-YZuTQ-Qx0ekX=Z_p(3Bs#Z37+U*dA2d=o@Y`hh1bU1I61Msa{ zdm}0ShlY6L9dHmp)Er<=TrL<8PaLBD-&*a>F9!G0fE)QjJ)rx`1liWUHhu}cqu@&B zs7(tlB#XI@TEX#?yN9i#Azv7=V2^=)o8a92^Xe7>^5!aS<-#7am96HAkfgS<=c7x6 zSDx@#w@T}r7}2Is*A~g5@J@A&{Zp2Km@+{f+mKcTXNjft=6)s4b&u>W6%PL9@8V$oFa{Fl>jWA-;3Xaz zTkz*pXTud&+|Mu|NP8+TFh*%M3zodEX~-6OKZ-gXDp5;)7N!F4fL;UCKfHTn10Z1x zJgX=_xosp0vkRaIhcM4(G2g>$ZZSli2L!94EVxR^a1_bF9nyEYl9Q;GJCJBI`dBPK z5{tuqPblOQ?gxPUJkC zRR6BSH+|`%v|b^e_l%rAf)_clEOq41wWpdCs?bKLFMMj@-aI{eFS+Fv;Zd9d?TTgM zlfkhYhzzPbNSuy6j4(DbM?DWIDpsIy`p}lfe9oD&He{(eHO!@{Kja*E?xU==HRiA% z$Dvsz8(GO`m(u+TXyXwU8b?_ABWY!>y0s8Bl92rh81so|&e(^CmZGIGCH=ChPIhv2 zY;RP@dn8AqVo<3KNK$9QXA_|h#b8hMZ%FD;$W??>c~6ddif7tKzjRoEObNzSDKWDg zS}(Z&$)T-eF#fH`Vf2@wt9?>+J*Ms2a9hnk2L`iQUg7b+{SEE=9~j+zs6zR_i()MP!2#*MSYZ3o9J~;3oYA`2zW%v{q)<9y-Dxcs{a_i0%&$}J6jr<=W_xk{>FRevz)X~ z>}0#w#Sx)B7^-h*Y^K~0={y|27fDO8H>s(iUs*Fu@1#!zbm*GZd0k-St7PYB$dov` z=0-P5UyY-btEv^rq}NTWLUVpa2K#X`JCX+bHo=jQtQjg%hFeMQJ9E-7P*v}WtP!h5 z_x+wAz?*OK1n#esWKqO!dRA4n#7c^QLYjDR{g6#?RJv8qO@dwljVSql)ci_t5Cin! zw2#TbE1d)%nF*-U@D*57?HE!u;&JQV)t`R<>-t<7f9xycM)@OE9l~#=+wk1m`9kC~ zE;$g=MPEWdMhn10pPI*xBeU1{qkSngMP`iJr&!E# z9XjxC@{yyl|Lexa{nx1h;g32+)h)J$#dD%=-YvCA##*6k#9k-dX358WwavR0mch=7)=5B$VJQndBmP!z;qF7HOw;$P{cIn!gT0XdsgLtlZ z#A5(Krk9dXng}EtwQFYx+E*zv5-*47eT?TNlZ>iMWRW>x@w}R*&X)KPFD3?6DSRd> zw><)&4xW#ezMO@~ayuIS*(h81p%&^h=w5^;6!|1UJ|H4{xO+ z*@1`wuiFG}>ntGNKm#@H>3I2Hex2e0@4~zvEyZ3K!Dwt2r;G3wra$1b-m^HYGA~*k zeWo7^0NpUOB}rxwBxia_HvK_ox3l(RSFUbLREA0VO3@2CTASE}OI3(z_->akkgDWI zWTs7E=gStiPQ4`B3+PTTO@`9E=v@1r7cX`57j)e)D>?*U)<@6*BnT~z!>`{fu+H(AgMo@Av*34C;>=ynz%zIx+bb_Q;iReQR6fu9UOuau z_M#zlktfP|QeT9HC-bh=aN;p*j>k|4jwBtkYRjbV@Yax*lpRcfy`T~hx^W#Jk@^cA z*Y&GJ66C2Lo!~P!gpq^tD}KjcylvTZe?0A$fVA{G;-0dj94l&@ql!Yc>tWzA1<~&k zhis~hbG=Zn0?>)}3seXh)-^L$b4-#4U>w(HJ`7|&)KM7M*Joy?LriW`@6hKOH-+@L z1?bMOPkfWTZqoUWX^9>@zV%95)utuCwMDRkrC=rsNh48*w{@5CKOpx1*#F5k;Bdg4 zFp#>rTToI_BXvK=-qrl2`2XS8V&E5MTAta5qKqjmO;J(o26iuIXvQ9`A>cGKP~02s*xYtnwI z$$0u1)-*>~M4$c7O7-&!x-e)KLch{jwtkW}RFE#iw(4ht&p7}4iHQY$ypwKCfatpN z{5^_iXe0FqAJp;_HiCabC_PUmDC?6hikt)*Avfv1Sgh=2G)+3;0tNEF2(9+M4-i+W z`1mkKj??m3;gCpYp0!=6&ZNbn3Hix}-N*F7{5S-sU76vKg|q8jI`T;_+qnnf%kXSz z1m&Vc@$~Q9<89P)Nr}*U1^gYH0nktc`LR_b-BMD#bD@V+#`%O&G_D_=j+B6v4SeTz z6f=~%F&Q-eH$OaPp6}QeaSDf+Pzl*qH6(jjDVtCz?kH*T&>LbzV}b_l!HzA|3#XQl z8A}t?>_1$D3YScnVrV$-A*EexXuI(tAq;CDWmzq`1s|cTVwy zLD5bcIqV-_bi6gA7)wybgunuQHfpOjpgFOTi=>UEQ%Fd8RJ)s+xkqB z^OJJyZ90WPkgcc^&xGy&^95ArsJ89+}MpqF|>aIdZQ`l^3@U_;^9Apn9O%6MOy4Gj9nA7V44G@V@WKF_ik`(V^lo!30PV$y|%rme9QC z=DT3iVs^?TO-GcKaC8Wvt)#{zqw2Wx;&T@6D`1~TTaS7Z#(#kt*F`!~=rWBK`a1?s znLbYxFN9_67bIO){brK34rr3q2)s-o!caIcQ=gqnR5v)bNzFa5AT_Nf!0#Sk+K%j| zdl1nl#5ANv?;#sa!W3n&bi)Z9QgzBBqeTt!*GnNHTCE!_ny`?@gejmd@kwJLCU!X~ zZ+NKrIv?68XQ?X92HOaUw8H0@Qi0fufa5!Z3XnjTURt;NqDei^(MND=hmQ>Z0QDum z_y~)t?SO=an&fWKZ&vGfZd|tTuJ1UPw_wLvC;>1`L8={`P)1q^{z?na?d|SKeFXI% z_P)Hu$=ljIR=wVcTmF5fr|^oV%|z6|5{rI}fu}e}OU;&X#xO1=lpCT^tniCc7m#n@ z`+qXp9f3_}m>jsET*9?I8{rsLUInq$w5Gsg$*06OwaZCqn#FIclhT}a{1ysK=kCF? z02%5*Q{g~!h_~Jo8BMwR6J9vo4Sn%QSj>?$kTf=`(rkpJR)nEZK^igq?-P0&(b4sb z6gHUWiuTaM&ONI7>XufM_MBd6i$jv;q5AI)jySGI8MRJ3V^Ix z)s98wDt!LRkY4NHggq{!C(Icm_ma3A$_}=+oV4TN;Y|Fb5ycNHk!lgt$4z?JypM&~_Br2uIVj#;I(_L98rsVlDPQJDh8@z(l5yi_hP^2{6s6wB252LG5BPW<)ezC z69Rct?O58J!rMPt)gUMwx9_?7N_?PYB7rljgx(@;hjJHTpTSTSGRW}QoK{G~Cru2b zSC!RuU*vet^Qx~uDN|w#(SKz#rVVq5wwJi%4f@#_QcrN8P=>4g$-JC8$tv1%uN*Vj zSd5H8x&>|R()=93-hukojA%Eak#jQ1z)I7x1iff?CLIWL9ffu923mln;8`_yWvDY6 zqgN;Jn*>;|PvnV%LJ1%8*dbLR{KH%MH1!m?!YrG5ONopF?l4BWt8sk!RV(>4qA%+@ zTW7HmX)z#MjD&GyOQjICda+wx4Y1+ZS_uR?V&rIJO~VM0Gv~;`latf0&KSG97RD*{ z6tmMxso|*eOC$u16qgpc=3o9!9-ClK49LYsg#0C$(tZ^C?8~CkJQ78PKtA>FhX7t# zQHT3AxFDV`AG4l}*mJ1o<{cPJ3_SBA!;~;E0A>8EBp2ZfEzT~(I~zhyy?IV_;@5cv z6x#!bXd{41EuPk4`sk6N|44|FrI?J^_*hZHt_ze>K4sgeq_V{CKM=|>Om&ixGZDd4 zevH~lIzod167y;PA#QhbAvvknX2IxD@QQKHt`N>yND9A`7H^nSP|_ya6h_(2eZIVR&eSp%j!L&56#PdTRsOmjm znG~81f$RLS%lKT~WodNz?#N&h>ioym(4q7oluw)`TRllGFN$aweZC%yRxqfBH}4U8 zF|IljL|Sznx4ex(VL5WD3@EQMOS9*t*QnkQgbv4P@I#GFJWUD`$*4!3s1g#4cpp2m zJ8q_Q`Ktst7(hZvs#Pe%msSxR1TjmZ2*OR4kYr9Gy_Q0!6T79@=aI0<{JuI|uscgv zH;>oC^p7q`-GY8{;t{XdKfFCj3JTFmgEwLVm(&DBeN+_-GOj*nNphgrLL+%bE0!Z( zsN_9U&`UeCqf|*%#l}2$Emdp;66sxuEFTr%cN_GCnxlrcFeW;-Qn zf6RGpsi@CM7`aMry6kkoNHz^MCQ`1k^h+5%B?k`(r4~pbsuymgi)1UlMFB8UW{uLt z%p8Wp;wf<tUR*SS))(%5N5ud3SSvp1+B2^l7x{>iY=TSkhw zZ;TzP9|ZoKL>P)`I0tP{=A3;e8cr%IZ6>0l={PmjMF5bJ>-Lozmq!OS(i^KBQuxD z&9TdbgkISMQbSH5WjEhvD+)*S@jQ!^0zR<-5_NU6H3m7w!=MJ)pZrJ09%z+Tk zC80Rcy;WRR8S~D1UwA4;?>XH%8IpvB2tLfEax#i_^!;vxX=JO#fNF5h9J0Tu+os_s z*sj8)31Si1C5nmo?sg4b*)Rm?q?w3=Qqv@)KSy731kKgq%J&B4OyG%1jY&F^7Brdw zF6lTt3G}PfEVwR(Fy^U5-iT6Ud3hKv`mB&GL zp0mN;Dx?^(xbazZq&q-}42{%W#-Vu$JGqrIE&XIMb$h=5gSYlmW@?30cWjYlq6RD)$ zvPhg#(Q&BwT(ircxn^h4HO(-;ARS`rL1p}`_c73VmH@orq9NCq9wJiEQ>^sFnrl)I z&#v1E+gdI!7M}gP!s-B2XqW`p>L*kPtf&{o=sJ>y8AA;Ub*3U3kirg*n>(Q|3{m?^ zW7eF?X=);!MLrMg*RjD|4UK}7E2xAXr|YHpo-v`=wP~5uIy*Dld9achJdg<37||fR z4ClXL$8OXs7@v3}T?Cvs=JaV3xPVQ1T9I0ML%6_Tm*`>(1sRUS1{@twME}<&{HIp{ zJYn&%!(q-FEs5-;7OKJZF&U9G{2fK5BuuEYasHr9c=3LmUupywGI-0E{Pj}XU4r@* z5RA~ONBw6gv3c;|y3~0MYcw{nCtU7BgD?8aRSsRt;t)iScbDbl%RVVMXQ`5RJFBQ5Ua|jc6F;qSja8Et z>DA6IkHQ%8vesZj{gA2|Bss{E^CcZ+d}cT+KKqZ6TD_q z%f0w)E*_>65Y+q2RM&MB5juO=fX|O6GV+&-L-`5nJR0U0j8DQG?XKg9WZ=Xd8@gg! zOJm}?uo6WiuqIf{IE!mcK_XMc0jI83>gU}+BqfP9HiHcz#IDoYHbjGw#6;k469wmKsa%CBhIQ$kY%_S=1r2}(u)ooa{w=nCC zaYEv11zJ?$emQx0jgkZ6t<2cgA$IiVpf){1f4m5*Dq}FY5gQV>TH2S;^FM2ZJM2+& zsRf;&2usD=$clNiZnXv)+9G;|bac17a z*2lfmLhjxQBRrOkj4UjMklr1N(c4-OkDCh;t6%klD>-AYmU@)Zyo(Fz~Q#oNd>{ml8Uhh~w6Z0}fgv%Hmr6j=f~ zoopp14N(kna4fUr zr9ZHZ^||B~phRVfZ@S4!n?X@<3+z3^+;{e+`i-1*?^|Gb;h$Hrde*o*7WP~=lRx`U z_POL`t~{{C+LqsvpZmuOs^eV3^7A!8LR+AD@Sf#JuD6vHw~Yi{*i1ya(-Sgw96ItF zKKkD+Y=Hl;iy$Fp281I(2u-|#6M*^zrP7SA3aiSfW(0Mw2Z9Q}8pj3k*>$iuW@f>|MT?+qR07tVT`xA9MGbuER>hee0G5%aYexI1g$@c( zQuV>oq#(?4J3aZuz(IS!8gf(Zmik zZ+DS!QfH`;4?OPvmz*$`i_~hskESM5R63gPFNKYd?)0X2+QRLvGO6yl#jD2jfa^-Q zrbUwtuUgl=WyLg*$kRa@2lKR=O33rKM-%!HtOKN~BWmLMcNJ%`qpEwe!Xt~O!I zp1=_L1O%H|Um*-8!u0U(4ZM6i@e<*Kv{SNc71qh}BX##o-QEZo^nHM~`ol$~cZ7j$ zgQ@>?qdH*coR=2su$Gsyl>rQv{tcdVX3DefT-V1NmQZ1rf!%2pWS zNHKA6>9!1WOD5cgTy96eeUi89nP)`2{7U}Lit#Iea?cRDd4V}1uuyWsn}FK~{v1Oq zCVV_3|Go)%%pacr2?NE{l?V<61a?~;0HDyIj~@6S_xulu{a*$f9uD}nUGl#MhXdlI z2E#bi&Bfgs``-}z`TrvI|4}c1o)z)BNZXWD=MYXB-~LK15+9w!N-&M1%m-r&nBgU8 zH6k^>H~ zB+DnX$*|g}<9*uVTr648fib~qdH3e9@v;fSjt!ZHM5xo+jV7DCQFTKJUc^N9vq zRCbf~{07%lrkZ^meA9|ih2&I8F+XXmuFk2T(w@*?Fa+|n?zcbNTIERAs1zhrn zXlR)rA*eVsx-MCb+qMI{a5tigM?x+U_- z)zo}vd&I!%TQ=@Wuv~sjGT%O4Vzd&`JgU*NRie@o1%Hu9Vp-7C1y`b_49l5}oL;C< z0m|tFfqzh0rvZcdx=cG4oKwH}rlkob3*FU1R8vFw<-^~-FU72}jv-q9mQ_DqqIyI@ zt;nJ6PD+L8xRJSkoE&VgXY^Pm=0-}wx+ZjZeVp}fttgur12BJF@+;1w9r1a+&9n0;fz{u`WAmKd5y!licm6+| z>`r#THZc>UA%yd7-Y74oPXj#WmQMB}yks>0NLlI*oWQ^SL+gWa-eea4C6?Zr|Cz7= z91xh38fMNRuI~0fB>sOC7oe9QjLi<84mZAfSE}R9--BnW&BwR!tJ!X1zD_t}*&c6t zmHXXD)ex|vCgKH3 zA+U*q8?p#+`-d+&G7C?ls2WM&KLkOQnbLrfr|7lEP^BO@-n;`QW5p|3w*S^mVlTt!b#b{T_ zRNiE$Ex!0fTKT}G7->9maNdY;4MIz!*IoE~!b;rk2}2U3n;hLlaBF!-EFt2d-{Iq- zCvM21{f{DP5K&BP!de0(cNZrn`3gvmQT&PyGbzjay@62`#OltqW*tJnNM83tyv@0i zm6)K-B;pa2CRnVu050clA`lPT#97;O2%syjCoG$eS&c40Nr(iHrI9hIAEBx_S+pIT zQTtF#=J1AwO-n}HAZh%>z#1IH`;c5&95@D{Dd!{czg5gJjLC~--f#A3-xbz}XPv$l z3U_~>vcrP%0+Yej;!)CZDce7N7XrHh6I`=C{6-jRKq8(gA|e0^S8P(wuSS`?>s$Tt zfxv7~Z0g;W#hzZfS?s>ICX>m=xHjfQ_JE3It=(JII)D>#M%D}8kQ3c!y{k-7VAlMe zzF0fW-k0J3f5o zUEntKGy}(t7K1dt^>;o)S%!Wa2h6D%Wy+hPsjO zh3ln0rd}`|2N$5^R0?HjT^B?-AR&Az``nzX>Iwm!;_F&{*<|{`Y$trj9TO8os(S&I z2+2%7f1bdQHn9-Tk)naM6V8~|Se4FjqV(frHIR=KysIz=Z{QjAZ$IZdnJ~ z-rYM@mv$jt0#xk_AJ;plk!dkYTlHXyn&;l&s^MPf#!4AW-LQKRftJ}RhRS0sZ9cpS zlvlMht;hs3i)K5#9e=S%Q64GqU;39xYnqsjAv^6}P8g?8G!7TE=tlm{8W+uSw-Ig+ zwDt|%ha2wM4eyBmk9|1RYLnG!Jud zDGkPhZIbbIn7O_@K~jp`KwY;QS8Y3q3x2^C)gA~qKS%>;?*W(>yCF?1n-(R9Gbky< z)5HG&)R)T4HsV`&myF!X*AXT6RWegKaioo0#Lp2d)q9oGamJv3^$DmIEQ_B~zG^ts zx{Vk&iwT#|YnE`^_m0On%^$g7+@a^U<_Ijy9w99db4n~T6gx91x;hyD0DQ$u4sgT# zWrB@^e+BY?yh|yE2R@g-5v3Mt1hiStguKUaxR)yuvkUr7n)JmmJKz+KDl5Yf-M*|KY1;gx?bB^AV}t-GJ= zDl`zZFer-T@6^B;V$X?qxvr?0F{r7Kh$;o{Hf3s3p^=CN-C^r9_3Psaby;^V<*EeIH~R%mvZO-04=!E z6xR%=sy?DPSa^yO!sg``wF@f><~poAL<$Gzb08P*7dZr1kB<>nt@&kjqYb_F1}Yq- zHyeR>%%E{s0>Wxm2w{9ow;Q?<$9=*emB2ghTbz+nQN2YNVd5aVaVVEt2&OV*qD0rFW-EeOCq_67BGBDp{;e)uvztu zy~bXlcFLneQ>Cb|s;H*kC9qs}LwDv>a=piM@eJj28|vX^7-9>1j&PTJ&jteAK*Zx0 zQ5Fs(OHFY!)GP_W=fqxg!hhJ7dw!)Xs2w6{;#(^o#-h_s$4}vzp47Du_?I{#cq#gB zI5TXYAIOePs`^ZsqSb~D0#WzCvqP7?Z-O+2yC=ID8eVYipIJokB&2KR5& zu$m9}OB?PSz6j;}maE(bzY>tG-NkOkG2w{jlOc}YWtR+cN~}YSj!5CK^3PAQA;2T2 zvI9wl4Y6_`9wUTMHE@zJmKknX&FJ`tbvxp4B^|Px!STPW%%+?pWJ|`KOD`-_jgYJ> z8>{%b+UmTC&KkM&gES+2g;U{Yi6GJbooJoZF4Y80G<$moum>uZ(_E=>q=% z@+RvfLxs&sP5eaCP&e+Y_YGy$IQlZFwyggEePC(+nBQQ1<)@+}=l=kw;!`t?JUo4W zGi7%ZH&bq1LB?m{E)Kk0xY#ZJCy`~XW+Vk|jiGN95yCA{)#_0obP_KIta|?dRCO;z zRaher7`*qv8sue_n33OorPde=V-5M=<_mryS4c1|7t^_Jr-%sA*XlKAnNCg+QYN5T zOl)%6v#;#GAC8V~i1l)D>28uKi*{y2}kA>%rsyP zV8dCe?g%Mo?hrArQLLciFl0LfV${$dZn#777h-Twd}GZ%-2SLPup z5grSn`Z4sDP7@at9(8>)3EtkIDt86eqL*b)ylpsw!EH{5{34GqI0Q1$UShUy;&~6e z;;PJX%DqP&kZjo%ucQ*lx`y&#iCL9_%m-U?sc||PSE3PpKn^U+0;u}pAOj?Ij6`rU z*$|?|wam&gnTKd6v&Y?^h(OST-=C~h%dB6hp)M$RU+^N^pkLC0Yl?RBDc!%>fah5H zGyRmRzK)rF7+Cj={^63hm$;yu2Rh6A`@?M#$}hyKL0kYZLITO-G%}Hz12U64C|lqm|H4{ zzuT4N-{x4SkMRJ&-s7>!nu)k@#>#C2;#zKJVHZ zY*{{I-GL9p(CogyKbQno*nS_>u!SJ~!QL?d-jshQ#IOY}>r(Ttg@D9J)tqoQ}rwM7;8NU59bjdhFE+? zIt(QRJp^Cm$`X`E?+p^jtl*7wj#J4U9g(d!h>Yg2qYEy}J{jbaFoWtZ|y4hUIlrM|64;T0d3{DfSgK8?z!D@gFEyw$jNe(?skFl>*T zk9^wm8EDbt4AgrzCh_)1Fv#oY{>%JP+cX#!Yz{rK7l#)e==@3mFz>|Yh;BQ$=(xTv zRP=b5TQ1n;?p1r3;hx}L8ztV{R5@Pu&0XN}1%BoOykqGQ3fn|jiW@uv{-L!I^!M>M zd6FIN5UvWN=lbG%xU{2E*ar}m}4y4xbkbJ(#fF2$m zWm4w(O2|;C4;S?4u#P_^F zevWPn4o`BzoRGMH96x!1hEaeMzGh=9e$u}b;otm}IjAF)30$E|8;HP}^twjzkwk5P2-WBVeEfxc>ld6am5xezWji^BN~A?hsaq z*mWuhrTcxFf$2;6XZl1aSJE1{90Uk3!lBDrn?m2%2>2A=@k!#|&T>acg+n z^nTE7Ljd3~ON1p9_W{jIQA}HN9U{nK>Fy$12wG(5-#J|UB>{X_K;TV>=iF@q+74AV za<9c5N1!%c_*3>v!ESCLR~l>2h|X$1E$wMpBGGuR)@1r2CY4bXFXm!f8i4hP5LH@ArR+!QZxhEaFGy!(LQ zTDiM)kB)YYO zdFm+Wf(BY(S=~95pdI5;k0x^#%CD>-ew3b#eD_3(+%(;dL+uR^}7RuF;bdr>Y4N29RmIDwWXyUZ|%%8T;bbr1f zP-)V!3181ZZCROUO=-Y?Q4Tnm8`f*MA+HP>`GroKAuaQFEdQmz{9p2%bb7^L%hFe(L*-OYb#ORn~EWA-`F@Bci zt`+WcY_eRA%sJ3}1^mprq%;@HhyVZtQC&PAeXx#oddJdGOoppB6a#wBE&{v0=cp}g z)l&mlw|qxMEjWg=4^Q$FO?NIJu8cE0>R5H|0%oxa+^h2`&jTyqs-?aLT#a2*HW$B4 zDyHym%}P6rCpO9Zl%V3^O;3zU=r>S7@hrNk=2K`(svzVGh&@l_Hi0R@i=>gDdmM>iD~DW!!B8sf7ZGp;4maau8hYg}qvM~QS6zG_;r ze4RvWn2Y}aMung#*~|v=!by7e?= z0RmsRil^36i>lql&dcI-p~jkQ`^+#dmDf`DiBqG8-x7jc3dSW$mYA1dG*kY#NC#jW zSenoiV5E#4u)uM8g;qib%`bz3WiJJ{(aZXki(>$9)Mi7W*!WmbLqMrw0=Xb+EA+fd zUXEQt-xtS^33M_EyHh#%OUwVT86wDnkbaIszA$3x>#Hb3)^6c>p zY6JrrTE+v-SN!mjMPI$SQ+cchTQ-L<9qHij#N%H;u;dvfa$bTg<_Xoy{=# zinEJyRB5xi#9X467ort|%W<3KXVCK&j1aWnxa8)zVy6CXwJ@}D^%_+c&P_#ZT5v@5 zOb$xIkVJ`!^-)Zh!^Iihqhe-yE{BE3>57r zgJw4>q@(fOLlfMZeE$G56{`v_3-N5B;BZ`6hdN~+BZdMiT|B_(D)QVy3VbdiWf&Og zT0FxVUw0f?yUylILmD*X)S(-?(-EmSI)(&NvmrBOu=tGGTN5pL;#8{69O;dfp>aWF zyNccek~p|SD+iEV_m1Jv&|tuSx`%S6GvkTFmj3{0EcA}@=2;p3<58V3=S*M2 zv>JvJZ!l~tRQ%Mav;gCnO3NU!+`gi7UQi7wXQ@Ca1~{v8;uvy1X6o9?X!BDeW*HPy!T`T^Q5w@%V!6?dv_RX5l5a z7q1>5O#tGf$=8{nVqlK}Ad6)Qo1QS>(&KHuF*ox9Fs^W{#Iy$@g$lR;AGQUE9ummm z%i^Q;s4X418;nrk3aWy>RFy;+DcxQOf$$>dX*#YA)5nd+6)#i12(VBK^8b=kV(St{oEjfjwMh}9N(D4RbI(O20BB< z39pFe=C#zmDpq&7Q$jVwFzD8JWs3%`hxs|N)Z75`H6pDmaLC4rN*mnc@~;V_u~zyZS()u_+Tz5j5bYW$sYN5CE%bi;9{D%mSGVb1O|O)~7MP zq2bL^W?JDIvN!TUQ>v2t@o(`qQMRyQ}-k=&DOJe3U zYP9Y#RRlKFNy=l)MFlPRjSMjAEp&{*#CM5aWq8Fi3B4-0i-aY)Xx!dEm}PKbErVFO zhS`PNQzAE#UlS}r>f4;sT-mfH{;{q;K3QG|#-hQXR#DuwNVK5RZ`2HrJ~)@fZHucp zJwO2zy4&k2c!JNwZgWU^D*pgd)20P?h8C?_A3`rl9whFL~}-eX>K08*>W2;obn4ipBZ(;&S<%eTc%gk5T==HT0C z9^k=6KN96y_XG;O_oy`5T5YVoR0+l*cJq7$uY%IB3*?A0;4-t6m2Z~f;E=Ud@hB@4 z9Sh>4h!e2YJ|cqYF7r&K;Mu=(8!4EAM(n{y64oFuC(;Yh6@V4@Qv;7?h>M36lCCI0 zbyhKL@fR2)^Hp(G;fj=3QnQ7D^@}nb+;bw*=`5{AvRQ?(f?8;mJ5%ZWARO1&a(m%+ch0Ej@7thG|Zi$NxC9qcN&)_0g>(j!Fopj01?Th^aXq{GZ`9D zlLy{k(3LMIK~A(&_?&(L_>DoJJ>`ESEMr!_q~HVQCAWy#VRZ$wE?V^q8@v-BM-JkG zHK{=t-FcQ0RfjCRUf(i{Z2U~alR9DEgNlI{xlLr;R;t6pF|v$q1rsrawVcCOK;uTG z?!`UCw5kxnTWA8}tN3C0h;}Rf&(t9XRH@(&{TuyFw7!^Gi!grw0Nx;ptMqncg8SFsD{5CS3Im`shZJbcSpI#bLjAx^Fb1MVs1d;vahc4|4A685rC*^s^X zmh3U9SPzPT7OUzNs-5u`O@}e^;QY(z2AZbh76Rn(OOV>~aW=hPQ!wy5w}_A8&REY3 zo1&%cR{3CLW5lol_7S0J_=e1t_O@efRs6+O0vV~A-I_ixQ4~$v)BgZMSWCJ-FCVys z(~@FjqI%i+lq%#0+EG`U{fWsx_LeK3Hyu-k8@E-(M`D)AN^w;MYDU1%5|?2bt5we8 z5oqQunCfCLJV2?FfcG5M+(zo)Y0qfGHho<|!<6sljVZ;-L_DWpXm^SGJBhj|B}>Lk(9OPeC{!)J>}w)Dc=B z;%VOz*^AzJi)ef9F=2YfCBn;2B`(&Mi^UR_zbO!Gr7Wz+bjobMGUT%pIYPhKm?i7$ zHF^%<78^wHg<*6n+@Z6Qny(NB#feX6h5-1P`!aswr071QC*ldJ$c*Uzn9w5zEdq8m z{EWoj>ToTt>o8C?oVlXEa6_2qKNiM}uO1@W4>@I8l|yNA#X7zisa5>96$NonqE|%M z&K{-VV3GJss+bq3##Y1);PEvZZZIV~mH=(SB6?H4lO&vHGN%)VUSATC6-94r{{WC0 zaIO3>X4-=WZU&qH_ZY(aLQg*hOJAZxy2RI>QF6- zyu^0XXAy@4a|lL4)F_tMnVTw@B`%;QFJphH?H2VhwPscdxM0W1yh{>>;B-uFhGNz6 zsfu8|O9c)wse+dVb%~jhxs>26%hB+44}X3Z3bNK6bx)#M7t66 z7os%SkGfG{tysb3m?B<-m)x>^%zlBgY^$6+4yx1 zy*4rNUaGLNnefZ>Ag~4XGwP!khts&jEsnm?Ue6cbP@}hZ$iRh8d@qNn;mGb}xGO<1 zf2cD{$#H8?G%@LvH(4dX7(x=L-Bf=_m?K{hzVqt;0OVDH7bT6l`$`tjWEb@*(Ga8K zEL`M^7De2r2^FhtN>f#ET04Bgnc@oHs${_CwG=b=jWC9@=YFCaIoxS!`~-`7XW4;({{sjm^y*yFcPQ= zZSx(8z0L(A_J)zI!u>F@C8KHExI9+~wh-(p6-v*tm}>yw4Ug^sQ}q&bl{c}uXfG0) z%N}JE(o`~t^1Dd)h#$3;piSPqxAZjtqwq33Lq+{?LgE)zQxI^6I3|V&?xEvwa$FpD z0`^O%5O1i`d6o_#Z}J%kBC%Bm8QM$`LogI9K63!&S}MIT1Lt2^{{SFmt@j!)k?JFq zBh)s=Bd4g)kb^SOsb%brBHETOEEK?XD@JeBZ`XsFhz*r_nl@rRBWn{C5;qLJ$`~oG znZ0@>4m=MH#NeX4f)z`m1#@+4=3uiPBKA|ynWr@TAf}n-5TH;`ps5s4T$&(vi zC3GVUqZ-IdYz)f~UopmvcE!XL!Qu$y(PL+@XHLy9>)C2 zjW%1)L}xoTN``%sm;8dv8RHWm36Sv}lK^!aZxXHU41mnsrNP8X;t_*A3_!<%X22zv zC3VcQXy2%?nbGOb=HqSM#iq9y6^6$l7S7W4*^Xsvy+gRbU*P@9L4#D-csrC<9?#bh z2|?kV&4DZ8TPa)|OVI`kIh!gzDLS|etD8she8z56{EkV4t63$v!vMu02AgqgrxNX* zN_oLGzGdHuB9~1OZM%4 zuuIpr@#B{u^90IsE_?Kda}aWU#qza+7peWQcQWsj;8*stnm1X)W@sdK>b z4hEHcpSen!z4cP}ZTxcuZ>D1oq62pE+_)aYr}ipr-DT){fXU7Aa8ly_V5nVVc!t+> z`ob0vyvGU~mX?EgPT&h?jB^BX4(WhZ*>}^sh(;KXnAM$lhuJP(?Gp@mVpdCV9A7#QjXy)brl z2@XzTj6^nOe8hsP;s$Uy@t6oUs{6UVC0x;bukuzDcATx<@dCD<4h%^0aW5JTVQ=d3r4+6BE`d5nr$_3&=v9#8?)$h^9DIzNBX87=oz)Wg}CLROlgZV7xW*8FdX=WxgM3}=aSd=V+vuYED^8;mtaSAOF} zZ;4^(M3RHD2KirzQD6(p!*Yf(eepSkg{cq~N-BtLQY!NH^&Vbl2W(4f0;gg<>T5ZJ z7BR$Ot=b%GaGNmMAsf1Rj|Da%M&;yci}6u_Eo!AI2nN)%^VT5ZLd1tPcQOLy@foea zhuSS^m-&qr;)!wmLXaJZSG-4V7d~Sb);NmWtVfuo*5vqz ztm;uzf?ytcmxlLijd}4ASXJYdjYZS&MX(w zP=#w3EL(Y-&ty&vwS7e>6my&qyU}#)=2F-=G0Y$W%#ZdG?>8fN;D6eL87ss`FfP7{ zb2vWX{{YyH@d$^OG)!gU8Gnh%`-~F}cq4SX)X320^BtS z>NlEZ@hmpfR1*EbvCc_t#4td4-xUj(gWw5avj&AVXEMeCbd3@TyWLEqHl_YUIWJ5A zwR2@-KiRLd*)p^J}(j@)hgA28$<9%TniT0#qRP>+Hola8YT zfXM#{+>!`siKwqfWYQfJKhFHNU3Rg_4yKSx*?XW_n+WN!_ z!w++2jLhqRw)l;SYm|WfJfC=rjvINFYa16>GXsPPlX1fra_eU*!^@uK_i+pnV8d%T z{aEiR>PI&o8(r}NZx6iPl>*ma>X_E10EI1i+;j=^6}U-;9O4!z)ZGkx9QTQ9lpiwN zWf5l&#{Ol=Rg?~Bii8aeUL&Cq2Le@ixH@E8W&^~iRNNd)0_%En1w}!G)8UWhsDHSY zqN<|BKeCYm@P`$|&s|fu?HSeX>%GP3hlRa9#4kmBt4RuMd_tK4q#KXbz@b2igTOsjA&T16NPxSwew_19ZmtaR_W+JQ-sPePfBQ zThKU(MA)<8E1fok`&`OFtvHvk(ASnPNJ7IJj2TTIc!heHtHDM zjtOXEI4G+jeIlaq#Bex>s|dI10CxZ+y||kr9$-~|A;4*XmNZlwmO{IiA#rR|l`sww z=C(x^1iQo*r4lNw+awf9J4+U-JYV}06x!W7di%g7fzjWeq{^8xiv8G@@)n+D9oebK z8;WSuv1X=z-0*q$n4B1Ly|aO(m*6c=t99aZE;jH8UpkSNtw&oYA&P!k8JMD?EL)y%nl<5me=Ttcc?wgH$Bw6}?-qFjq29TM{u zf%^eDgV}@?G3OC^EL_`(O{eP0oeFq!DHs`qrj9V_j?*sDZo;cHuj4Pkw)#3>tO2q1 z2wjFE-;1b^WJN86a3xH-A1{B&QWXMO!BWRcWAOoTB}VkT$7R^UY}6=$7#c`t#VXAk zCU;I=u4G=0XBv1q^7)t^S(>?+11BBCNekQhnFVOeeM}-zPQ9xWHr*v=$Mq3awOJrK z_P&inbU;k6(jhjA`h#%gLy@>N&Ga)3;JR~h-$i3k7ZaO;&CDp3gc1Fi7mqN~>oBTe z%H`Mae)6`~V*o9!OMr0Om}>O`^QdfSmo)*axsV9-YB5k)m()NIb1YEvn0>uV^HHv~ z(|eYnWhsLDj9TAwJYeEmWX?AZfsMlo8ocHfJxk0MvW$kH60 zt^7=pN!)0L-AwU5;Q^Y2eO{Al<7Kb5oOc%IpYP zrN`*_luTRZS11h}dyBz{jea0!iiK=*s)5asMxmp9xc$n22LTa9%QUVgt4pAn2lWs} z9Y?Q{He&j8hT+8-ZS@w0U+le?j^YUb1#G>+Cf&kmMj-Z_Qy^SS1)?1UK+6yv%+aEs zyuO_G2}ZJJHlnR{G1}YI$fjP9jY_c?>^yo z^P-z?pHl_q_LeJ58&d1}P`ToJM=wukzJYlW4B;tmcEMq*v>Ro&-X2Wt5U+P=AVO<* zJ;&Z<=nELY#RdNWT*P~S>f>O>L`M**`H`>pp-+djzx&e9?L`4orC}( _jsonParsePostOptionDefaultVm; public PostController( IPostRepository postRepository, - IUserService userService) + IUserService userService, + IJsonParseService jsonParsePostOptionDefaultVm) { _postRepository = postRepository; + _jsonParsePostOptionDefaultVm = jsonParsePostOptionDefaultVm; } [HttpGet("")] @@ -41,24 +45,26 @@ public async Task GetPost(int postId) } [HttpPost("SavePost")] - [AllowAnonymous] - public async Task SavePost(PostViewModel viewModel) + public async Task SavePost([FromForm]PostViewModel viewModel) { - //if (viewModel.Id.HasValue) - //{ - // await _postRepository.EditPost(viewModel); - //} - //else - //{ - // var path = Path.Combine( - // Directory.GetCurrentDirectory(), "wwwroot/assets/", - // thumbnail.Name); - // using (var stream = new FileStream(path, FileMode.Create)) - // { - // await thumbnail.CopyToAsync(stream); - // } - // await _postRepository.SavePost(viewModel); - //} + var postOptionsViewModel = _jsonParsePostOptionDefaultVm.ToObject(viewModel.PostOptionsViewModel); + viewModel.PostOptionsDefaultViewModel = postOptionsViewModel; + + if (viewModel.Id.HasValue) + { + await _postRepository.EditPost(viewModel); + } + else + { + var path = Path.Combine( + Directory.GetCurrentDirectory(), "wwwroot\\assets", + viewModel.Thumbnail.GetFilename()); + using (var stream = new FileStream(path, FileMode.Create)) + { + await viewModel.Thumbnail.CopyToAsync(stream); + } + await _postRepository.SavePost(viewModel); + } return Ok(); } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Repositories/IPostRepository.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Repositories/IPostRepository.cs index 6d6faac4..22f5b27c 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Repositories/IPostRepository.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/Repositories/IPostRepository.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using AwesomeCMSCore.Modules.Admin.ViewModels; diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs index d609cf3c..9a99b9cb 100644 --- a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Admin/ViewModels/PostViewModel.cs @@ -3,6 +3,7 @@ using AwesomeCMSCore.Modules.Entities.Entities; using AwesomeCMSCore.Modules.Entities.Enums; using Microsoft.AspNetCore.Http; +using Newtonsoft.Json.Linq; namespace AwesomeCMSCore.Modules.Admin.ViewModels { @@ -14,8 +15,12 @@ public class PostViewModel public string Content { get; set; } public PostOptionsDefaultViewModel PostOptionsDefaultViewModel { get; set; } public DateTime DateCreated { get; set; } = DateTime.Now; - public IFormFile Thumbnail { get; set; } public ICollection Media { get; set; } public PostStatus PostStatus { get; set; } + /// + /// These 2 only use to map with submit data using FormData since we submit JsonStringify + /// + public string PostOptionsViewModel { get; set; } + public IFormFile Thumbnail { get; set; } } } diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Extensions/IFormFileExtensions.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Extensions/IFormFileExtensions.cs new file mode 100644 index 00000000..be127a23 --- /dev/null +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Extensions/IFormFileExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace AwesomeCMSCore.Modules.Helper.Extensions +{ + public static class IFormFileExtensions + { + public static string GetFilename(this IFormFile file) + { + return ContentDispositionHeaderValue.Parse( + file.ContentDisposition).FileName.ToString().Trim('"'); + } + + public static async Task GetFileStream(this IFormFile file) + { + MemoryStream filestream = new MemoryStream(); + await file.CopyToAsync(filestream); + return filestream; + } + + public static async Task GetFileArray(this IFormFile file) + { + MemoryStream filestream = new MemoryStream(); + await file.CopyToAsync(filestream); + return filestream.ToArray(); + } + } +} diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/IJsonParseService.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/IJsonParseService.cs new file mode 100644 index 00000000..f7b0d71a --- /dev/null +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/IJsonParseService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace AwesomeCMSCore.Modules.Helper.Extensions +{ + public interface IJsonParseService where T : class + { + T ToObject(string json); + } +} diff --git a/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/JsonParseService.cs b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/JsonParseService.cs new file mode 100644 index 00000000..a84ccc4e --- /dev/null +++ b/src/AwesomeCMSCore/Modules/AwesomeCMSCore.Modules.Helper/Services/JsonParseService.cs @@ -0,0 +1,16 @@ +using AwesomeCMSCore.Modules.Helper.Extensions; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Text; + +namespace AwesomeCMSCore.Modules.Helper.Services +{ + public class JsonParseService : IJsonParseService where T : class + { + public T ToObject(string json) + { + return JObject.Parse(json).Root.ToObject(); + } + } +}