Browse Source

* Closes #58: Relayout Profile Page

* Closes #55: Upload Avatar Picture
pull/65/head
Sebastian Stehle 9 years ago
parent
commit
ed5da5bfef
  1. 8
      src/Squidex.Infrastructure.GoogleCloud/GoogleCloudAssetStore.cs
  2. 8
      src/Squidex.Infrastructure/Assets/FolderAssetStore.cs
  3. 5
      src/Squidex.Infrastructure/Assets/IAssetStore.cs
  4. 43
      src/Squidex.Read/Users/AssetUserPictureStore.cs
  5. 20
      src/Squidex.Read/Users/IUserPictureStore.cs
  6. 10
      src/Squidex.Read/Users/UserExtensions.cs
  7. 4
      src/Squidex.Write/Assets/AssetCommandHandler.cs
  8. 5
      src/Squidex/Config/Domain/ReadModule.cs
  9. 12
      src/Squidex/Controllers/Api/Assets/AssetContentController.cs
  10. 17
      src/Squidex/Controllers/Api/Users/UsersController.cs
  11. 50
      src/Squidex/Controllers/UI/Profile/ProfileController.cs
  12. 4
      src/Squidex/Controllers/UI/Profile/ProfileVM.cs
  13. 253
      src/Squidex/Views/Profile/Profile.cshtml
  14. 29
      src/Squidex/app/theme/_static.scss
  15. 4
      tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs
  16. 2
      tests/Squidex.Write.Tests/Assets/AssetCommandHandlerTests.cs

8
src/Squidex.Infrastructure.GoogleCloud/GoogleCloudAssetStore.cs

@ -42,7 +42,7 @@ namespace Squidex.Infrastructure.GoogleCloud
} }
} }
public async Task DownloadAsync(Guid id, long version, string suffix, Stream stream) public async Task DownloadAsync(string id, long version, string suffix, Stream stream)
{ {
var objectName = GetObjectName(id, version, suffix); var objectName = GetObjectName(id, version, suffix);
@ -60,15 +60,17 @@ namespace Squidex.Infrastructure.GoogleCloud
} }
} }
public async Task UploadAsync(Guid id, long version, string suffix, Stream stream) public async Task UploadAsync(string id, long version, string suffix, Stream stream)
{ {
var objectName = GetObjectName(id, version, suffix); var objectName = GetObjectName(id, version, suffix);
await storageClient.UploadObjectAsync(bucketName, objectName, "application/octet-stream", stream); await storageClient.UploadObjectAsync(bucketName, objectName, "application/octet-stream", stream);
} }
private string GetObjectName(Guid id, long version, string suffix) private string GetObjectName(string id, long version, string suffix)
{ {
Guard.NotNullOrEmpty(id, nameof(id));
if (storageClient == null) if (storageClient == null)
{ {
throw new InvalidOperationException("No connection established yet."); throw new InvalidOperationException("No connection established yet.");

8
src/Squidex.Infrastructure/Assets/FolderAssetStore.cs

@ -50,7 +50,7 @@ namespace Squidex.Infrastructure.Assets
} }
} }
public async Task DownloadAsync(Guid id, long version, string suffix, Stream stream) public async Task DownloadAsync(string id, long version, string suffix, Stream stream)
{ {
var file = GetFile(id, version, suffix); var file = GetFile(id, version, suffix);
@ -67,7 +67,7 @@ namespace Squidex.Infrastructure.Assets
} }
} }
public async Task UploadAsync(Guid id, long version, string suffix, Stream stream) public async Task UploadAsync(string id, long version, string suffix, Stream stream)
{ {
var file = GetFile(id, version, suffix); var file = GetFile(id, version, suffix);
@ -77,8 +77,10 @@ namespace Squidex.Infrastructure.Assets
} }
} }
private FileInfo GetFile(Guid id, long version, string suffix) private FileInfo GetFile(string id, long version, string suffix)
{ {
Guard.NotNullOrEmpty(id, nameof(id));
var path = Path.Combine(directory.FullName, $"{id}_{version}"); var path = Path.Combine(directory.FullName, $"{id}_{version}");
if (!string.IsNullOrWhiteSpace(suffix)) if (!string.IsNullOrWhiteSpace(suffix))

5
src/Squidex.Infrastructure/Assets/IAssetStore.cs

@ -6,7 +6,6 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,8 +13,8 @@ namespace Squidex.Infrastructure.Assets
{ {
public interface IAssetStore public interface IAssetStore
{ {
Task DownloadAsync(Guid id, long version, string suffix, Stream stream); Task DownloadAsync(string id, long version, string suffix, Stream stream);
Task UploadAsync(Guid id, long version, string suffix, Stream stream); Task UploadAsync(string id, long version, string suffix, Stream stream);
} }
} }

43
src/Squidex.Read/Users/AssetUserPictureStore.cs

@ -0,0 +1,43 @@
// ==========================================================================
// AssetUserPictureStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.IO;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
namespace Squidex.Read.Users
{
public sealed class AssetUserPictureStore : IUserPictureStore
{
private readonly IAssetStore assetStore;
public AssetUserPictureStore(IAssetStore assetStore)
{
Guard.NotNull(assetStore, nameof(assetStore));
this.assetStore = assetStore;
}
public Task UploadAsync(string userId, Stream stream)
{
return assetStore.UploadAsync(userId, 0, "picture", stream);
}
public async Task<Stream> DownloadAsync(string userId)
{
var memoryStream = new MemoryStream();
await assetStore.DownloadAsync(userId, 0, "picture", memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
}
}

20
src/Squidex.Read/Users/IUserPictureStore.cs

@ -0,0 +1,20 @@
// ==========================================================================
// IUserPictureStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.IO;
using System.Threading.Tasks;
namespace Squidex.Read.Users
{
public interface IUserPictureStore
{
Task UploadAsync(string userId, Stream stream);
Task<Stream> DownloadAsync(string userId);
}
}

10
src/Squidex.Read/Users/UserExtensions.cs

@ -27,11 +27,21 @@ namespace Squidex.Read.Users
user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl); user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl);
} }
public static void SetPictureUrlToStore(this IUser user)
{
user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, "store");
}
public static void SetPictureUrlFromGravatar(this IUser user, string email) public static void SetPictureUrlFromGravatar(this IUser user, string email)
{ {
user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, GravatarHelper.CreatePictureUrl(email)); user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, GravatarHelper.CreatePictureUrl(email));
} }
public static bool IsPictureUrlStored(this IUser user)
{
return string.Equals(user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value, "store", StringComparison.OrdinalIgnoreCase);
}
public static string PictureUrl(this IUser user) public static string PictureUrl(this IUser user)
{ {
return user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value; return user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value;

4
src/Squidex.Write/Assets/AssetCommandHandler.cs

@ -44,7 +44,7 @@ namespace Squidex.Write.Assets
c.Create(command); c.Create(command);
await assetStore.UploadAsync(c.Id, c.FileVersion, null, command.File.OpenRead()); await assetStore.UploadAsync(c.Id.ToString(), c.FileVersion, null, command.File.OpenRead());
context.Succeed(EntityCreatedResult.Create(c.Id, c.Version)); context.Succeed(EntityCreatedResult.Create(c.Id, c.Version));
}); });
@ -58,7 +58,7 @@ namespace Squidex.Write.Assets
c.Update(command); c.Update(command);
await assetStore.UploadAsync(c.Id, c.FileVersion, null, command.File.OpenRead()); await assetStore.UploadAsync(c.Id.ToString(), c.FileVersion, null, command.File.OpenRead());
}); });
} }

5
src/Squidex/Config/Domain/ReadModule.cs

@ -21,6 +21,7 @@ using Squidex.Read.History;
using Squidex.Read.Schemas; using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services; using Squidex.Read.Schemas.Services;
using Squidex.Read.Schemas.Services.Implementations; using Squidex.Read.Schemas.Services.Implementations;
using Squidex.Read.Users;
// ReSharper disable UnusedAutoPropertyAccessor.Local // ReSharper disable UnusedAutoPropertyAccessor.Local
@ -62,6 +63,10 @@ namespace Squidex.Config.Domain
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();
builder.RegisterType<AssetUserPictureStore>()
.As<IUserPictureStore>()
.SingleInstance();
builder.RegisterType<AppHistoryEventsCreator>() builder.RegisterType<AppHistoryEventsCreator>()
.As<IHistoryEventsCreator>() .As<IHistoryEventsCreator>()
.SingleInstance(); .SingleInstance();

12
src/Squidex/Controllers/Api/Assets/AssetContentController.cs

@ -52,15 +52,17 @@ namespace Squidex.Controllers.Api.Assets
return NotFound(); return NotFound();
} }
var assetId = asset.Id.ToString();
return new FileCallbackResult(asset.MimeType, asset.FileName, async bodyStream => return new FileCallbackResult(asset.MimeType, asset.FileName, async bodyStream =>
{ {
if (asset.IsImage && (width.HasValue || height.HasValue)) if (asset.IsImage && (width.HasValue || height.HasValue))
{ {
var suffix = $"{width}_{height}_{mode}"; var assetSuffix = $"{width}_{height}_{mode}";
try try
{ {
await assetStorage.DownloadAsync(asset.Id, asset.FileVersion, suffix, bodyStream); await assetStorage.DownloadAsync(assetId, asset.FileVersion, assetSuffix, bodyStream);
} }
catch (AssetNotFoundException) catch (AssetNotFoundException)
{ {
@ -68,13 +70,13 @@ namespace Squidex.Controllers.Api.Assets
{ {
using (var destinationStream = GetTempStream()) using (var destinationStream = GetTempStream())
{ {
await assetStorage.DownloadAsync(asset.Id, asset.FileVersion, null, sourceStream); await assetStorage.DownloadAsync(assetId, asset.FileVersion, null, sourceStream);
sourceStream.Position = 0; sourceStream.Position = 0;
await assetThumbnailGenerator.CreateThumbnailAsync(sourceStream, destinationStream, width, height, mode); await assetThumbnailGenerator.CreateThumbnailAsync(sourceStream, destinationStream, width, height, mode);
destinationStream.Position = 0; destinationStream.Position = 0;
await assetStorage.UploadAsync(asset.Id, asset.FileVersion, suffix, destinationStream); await assetStorage.UploadAsync(assetId, asset.FileVersion, assetSuffix, destinationStream);
destinationStream.Position = 0; destinationStream.Position = 0;
await destinationStream.CopyToAsync(bodyStream); await destinationStream.CopyToAsync(bodyStream);
@ -84,7 +86,7 @@ namespace Squidex.Controllers.Api.Assets
} }
} }
await assetStorage.DownloadAsync(asset.Id, asset.FileVersion, null, bodyStream); await assetStorage.DownloadAsync(assetId, asset.FileVersion, null, bodyStream);
}); });
} }

17
src/Squidex/Controllers/Api/Users/UsersController.cs

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Controllers.Api.Users.Models; using Squidex.Controllers.Api.Users.Models;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Read.Users; using Squidex.Read.Users;
@ -33,6 +34,7 @@ namespace Squidex.Controllers.Api.Users
{ {
private static readonly byte[] AvatarBytes; private static readonly byte[] AvatarBytes;
private readonly UserManager<IUser> userManager; private readonly UserManager<IUser> userManager;
private readonly IUserPictureStore userPictureStore;
static UsersController() static UsersController()
{ {
@ -46,9 +48,10 @@ namespace Squidex.Controllers.Api.Users
} }
} }
public UsersController(UserManager<IUser> userManager) public UsersController(UserManager<IUser> userManager, IUserPictureStore userPictureStore)
{ {
this.userManager = userManager; this.userManager = userManager;
this.userPictureStore = userPictureStore;
} }
/// <summary> /// <summary>
@ -120,6 +123,18 @@ namespace Squidex.Controllers.Api.Users
return NotFound(); return NotFound();
} }
try
{
if (entity.IsPictureUrlStored())
{
return new FileStreamResult(await userPictureStore.DownloadAsync(entity.Id), "image/png");
}
}
catch
{
return new FileStreamResult(new MemoryStream(AvatarBytes), "image/png");
}
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
var url = entity.PictureNormalizedUrl(); var url = entity.PictureNormalizedUrl();

50
src/Squidex/Controllers/UI/Profile/ProfileController.cs

@ -7,14 +7,19 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Config;
using Squidex.Config.Identity; using Squidex.Config.Identity;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Read.Users; using Squidex.Read.Users;
@ -25,12 +30,20 @@ namespace Squidex.Controllers.UI.Profile
public class ProfileController : Controller public class ProfileController : Controller
{ {
private readonly UserManager<IUser> userManager; private readonly UserManager<IUser> userManager;
private readonly IUserPictureStore userPictureStore;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
private readonly IOptions<MyIdentityOptions> identityOptions; private readonly IOptions<MyIdentityOptions> identityOptions;
public ProfileController(UserManager<IUser> userManager, IOptions<MyIdentityOptions> identityOptions) public ProfileController(
UserManager<IUser> userManager,
IUserPictureStore userPictureStore,
IAssetThumbnailGenerator assetThumbnailGenerator,
IOptions<MyIdentityOptions> identityOptions)
{ {
this.userManager = userManager;
this.identityOptions = identityOptions; this.identityOptions = identityOptions;
this.userManager = userManager;
this.userPictureStore = userPictureStore;
this.assetThumbnailGenerator = assetThumbnailGenerator;
} }
[HttpGet] [HttpGet]
@ -73,6 +86,37 @@ namespace Squidex.Controllers.UI.Profile
"Password changed successfully."); "Password changed successfully.");
} }
[HttpPost]
[Route("/account/picture")]
public Task<IActionResult> UploadPicture(List<IFormFile> file)
{
return MakeChangeAsync(async user =>
{
if (file.Count != 1)
{
return IdentityResult.Failed(new IdentityError { Description = "Please upload a single file." });
}
var thumbnailStream = new MemoryStream();
try
{
await assetThumbnailGenerator.CreateThumbnailAsync(file[0].OpenReadStream(), thumbnailStream, 128, 128, "Crop");
thumbnailStream.Position = 0;
}
catch
{
return IdentityResult.Failed(new IdentityError { Description = "Picture is not a valid image." });
}
await userPictureStore.UploadAsync(user.Id, thumbnailStream);
user.SetPictureUrlToStore();
return await userManager.UpdateAsync(user);
}, "Password set successfully.");
}
private async Task<IActionResult> MakeChangeAsync(Func<IUser, Task<IdentityResult>> action, string successMessage, ChangeProfileModel model = null) private async Task<IActionResult> MakeChangeAsync(Func<IUser, Task<IdentityResult>> action, string successMessage, ChangeProfileModel model = null)
{ {
var user = await userManager.GetUserAsync(User); var user = await userManager.GetUserAsync(User);
@ -105,9 +149,9 @@ namespace Squidex.Controllers.UI.Profile
{ {
var result = new ProfileVM var result = new ProfileVM
{ {
Id = user.Id,
Email = user.Email, Email = user.Email,
DisplayName = user.DisplayName(), DisplayName = user.DisplayName(),
PictureUrl = user.PictureUrl(),
HasPassword = await userManager.HasPasswordAsync(user), HasPassword = await userManager.HasPasswordAsync(user),
HasPasswordAuth = identityOptions.Value.AllowPasswordAuth HasPasswordAuth = identityOptions.Value.AllowPasswordAuth
}; };

4
src/Squidex/Controllers/UI/Profile/ProfileVM.cs

@ -10,12 +10,12 @@ namespace Squidex.Controllers.UI.Profile
{ {
public sealed class ProfileVM public sealed class ProfileVM
{ {
public string Id { get; set; }
public string Email { get; set; } public string Email { get; set; }
public string DisplayName { get; set; } public string DisplayName { get; set; }
public string PictureUrl { get; set; }
public bool HasPassword { get; set; } public bool HasPassword { get; set; }
public bool HasPasswordAuth { get; set; } public bool HasPasswordAuth { get; set; }

253
src/Squidex/Views/Profile/Profile.cshtml

@ -28,129 +28,154 @@
@ViewBag.ErrorMessage @ViewBag.ErrorMessage
</div> </div>
} }
<div class="row"> <div class="row profile-section">
<div class="col profile-picture-col"> <div class="col profile-picture-col">
<img class="profile-picture" src="@Model.PictureUrl" /> <img class="profile-picture" src="@Url.Content($"~/../api/users/{Model.Id}/picture/")" />
</div> </div>
<div class="col"> <div class="col">
<form class="profile-form" asp-controller="Profile" asp-action="Profile" method="post"> <form id="pictureForm" class="profile-picture-form" asp-controller="Profile" asp-action="UploadPicture" method="post" enctype="multipart/form-data">
<div class="form-group"> <span class="btn btn-secondary" id="pictureButton">
<label for="email">Email</label> <span>Upload Picture</span>
<input class="profile-picture-input" name="file" type="file" id="pictureInput" />
</span>
</form>
</div>
</div>
<form class="profile-form profile-section" asp-controller="Profile" asp-action="Profile" method="post">
<div class="form-group">
<label for="email">Email</label>
@if (ViewContext.ViewData.ModelState["Email"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) @if (ViewContext.ViewData.ModelState["Email"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{ {
<div class="errors-container"> <div class="errors-container">
<span asp-validation-for="Email" class="errors"></span> <span asp-validation-for="Email" class="errors"></span>
</div>
}
<input type="email" ap class="form-control" asp-for="Email" name="email" id="email" />
</div> </div>
}
<div class="form-group"> <input type="email" ap class="form-control" asp-for="Email" name="email" id="email" />
<label for="displayName">Display Name</label> </div>
@if (ViewContext.ViewData.ModelState["DisplayName"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid) <div class="form-group">
{ <label for="displayName">Display Name</label>
<div class="errors-container">
<span asp-validation-for="DisplayName" class="errors"></span>
</div>
}
<input type="text" class="form-control" asp-for="DisplayName" name="displayName" id="displayName"/> @if (ViewContext.ViewData.ModelState["DisplayName"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-container">
<span asp-validation-for="DisplayName" class="errors"></span>
</div> </div>
}
<button type="submit" class="btn btn-primary">Save</button> <input type="text" class="form-control" asp-for="DisplayName" name="displayName" id="displayName"/>
</form>
</div> </div>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
@if (Model.HasPasswordAuth) <div class="profile-section">
{ @if (Model.HasPasswordAuth)
<h2>Password</h2> {
<h2>Password</h2>
@if (Model.HasPassword)
{ @if (Model.HasPassword)
<form class="profile-form" asp-controller="Profile" asp-action="ChangePassword" method="post"> {
<div class="form-group"> <form class="profile-form" asp-controller="Profile" asp-action="ChangePassword" method="post">
<label for="oldPassword">Old Password</label> <div class="form-group">
<label for="oldPassword">Old Password</label>
@if (ViewContext.ViewData.ModelState["OldPassword"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{ @if (ViewContext.ViewData.ModelState["OldPassword"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
<div class="errors-container"> {
<span class="errors">@Html.ValidationMessage("OldPassword")</span> <div class="errors-container">
</div> <span class="errors">@Html.ValidationMessage("OldPassword")</span>
} </div>
}
<input type="password" class="form-control" name="oldPassword" id="oldPassword"/>
</div> <input type="password" class="form-control" name="oldPassword" id="oldPassword" />
</div>
<div class="form-group">
<label for="password">Password</label> <div class="form-group">
<label for="password">Password</label>
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{ @if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
<div class="errors-container"> {
<span class="errors">@Html.ValidationMessage("Password")</span> <div class="errors-container">
</div> <span class="errors">@Html.ValidationMessage("Password")</span>
} </div>
}
<input type="password" class="form-control" name="password" id="password"/>
</div> <input type="password" class="form-control" name="password" id="password" />
</div>
<div class="form-group">
<label for="passwordConfirm">Confirm</label> <div class="form-group">
<label for="passwordConfirm">Confirm</label>
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{ @if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
<div class="errors-container"> {
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span> <div class="errors-container">
</div> <span class="errors">@Html.ValidationMessage("PasswordConfirm")</span>
} </div>
}
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/>
</div> <input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Change Password</button> <div class="form-group">
</div> <button type="submit" class="btn btn-primary">Change Password</button>
</form> </div>
} </form>
else }
{ else
<form class="profile-form" asp-controller="Profile" asp-action="SetPassword" method="post"> {
<div class="form-group"> <form class="profile-form" asp-controller="Profile" asp-action="SetPassword" method="post">
<label for="password">Password</label> <div class="form-group">
<label for="password">Password</label>
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{ @if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
<div class="errors-container"> {
<span class="errors">@Html.ValidationMessage("Password")</span> <div class="errors-container">
</div> <span class="errors">@Html.ValidationMessage("Password")</span>
} </div>
}
<input type="password" class="form-control" name="password" id="password"/>
</div> <input type="password" class="form-control" name="password" id="password" />
</div>
<div class="form-group">
<label for="passwordConfirm">Confirm</label> <div class="form-group">
<label for="passwordConfirm">Confirm</label>
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{ @if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
<div class="errors-container"> {
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span> <div class="errors-container">
</div> <span class="errors">@Html.ValidationMessage("PasswordConfirm")</span>
} </div>
}
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/>
</div> <input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Set Password</button> <div class="form-group">
</div> <button type="submit" class="btn btn-primary">Set Password</button>
</form> </div>
} </form>
} }
</div> }
</div>
</div>
<script>
var pictureButton = document.getElementById('pictureButton');
var pictureInput = document.getElementById('pictureInput');
var pictureForm = document.getElementById('pictureForm');
pictureButton.addEventListener('click',
function() {
pictureInput.click();
});
pictureInput.addEventListener('change',
function () {
pictureForm.submit();
});
</script>

29
src/Squidex/app/theme/_static.scss

@ -41,9 +41,9 @@ noscript {
} }
} }
// Fixed logo on the top left corner of the screen. // Fix logo on the top right corner of the screen.
&-logo { &-logo {
@include absolute(1rem, auto, auto, 1rem); @include absolute(1rem, 1rem, auto, auto);
} }
&-lg { &-lg {
@ -54,12 +54,24 @@ noscript {
color: $color-empty; color: $color-empty;
} }
&-section {
margin-top: 2rem;
}
&-picture-col { &-picture-col {
max-width: 7rem; max-width: 7rem;
} }
&-picture { &-picture {
max-width: 100%; @include circle(6rem);
}
&-picture-input {
@include hidden;
}
&-picture-form {
margin-top: 1.6rem;
} }
&-footer { &-footer {
@ -132,15 +144,4 @@ noscript {
font-size: 30px; font-size: 30px;
font-weight: lighter; font-weight: lighter;
} }
}
@media (max-width: 400px) {
.profile-picture-col {
min-width: 100%;
margin-bottom: 1rem;
}
.profile-picture {
height: 5rem;
}
} }

4
tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs

@ -46,7 +46,7 @@ namespace Squidex.Infrastructure.Assets
{ {
sut.Connect(); sut.Connect();
return Assert.ThrowsAsync<AssetNotFoundException>(() => sut.DownloadAsync(Guid.NewGuid(), 1, "suffix", new MemoryStream())); return Assert.ThrowsAsync<AssetNotFoundException>(() => sut.DownloadAsync(Guid.NewGuid().ToString(), 1, "suffix", new MemoryStream()));
} }
[Fact] [Fact]
@ -54,7 +54,7 @@ namespace Squidex.Infrastructure.Assets
{ {
sut.Connect(); sut.Connect();
var assetId = Guid.NewGuid(); var assetId = Guid.NewGuid().ToString();
var assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 }); var assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 });
await sut.UploadAsync(assetId, 1, "suffix", assetData); await sut.UploadAsync(assetId, 1, "suffix", assetData);

2
tests/Squidex.Write.Tests/Assets/AssetCommandHandlerTests.cs

@ -120,7 +120,7 @@ namespace Squidex.Write.Assets
private void SetupStore(long version) private void SetupStore(long version)
{ {
assetStore assetStore
.Setup(x => x.UploadAsync(assetId, version, null, stream)).Returns(TaskHelper.Done) .Setup(x => x.UploadAsync(assetId.ToString(), version, null, stream)).Returns(TaskHelper.Done)
.Verifiable(); .Verifiable();
} }
} }

Loading…
Cancel
Save