mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 04:42:58 +08:00
Merge pull request #22193 from Feodor0090/comment-editor-3
Integrate comment editor into comments view
This commit is contained in:
commit
c38f3b461c
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -11,7 +9,10 @@ using osu.Game.Overlays;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private CommentsContainer commentsContainer;
|
||||
private CommentsContainer commentsContainer = null!;
|
||||
|
||||
private TextBox editorTextBox = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = commentsContainer = new CommentsContainer()
|
||||
});
|
||||
};
|
||||
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestIdleState()
|
||||
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
|
||||
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPost()
|
||||
{
|
||||
setUpCommentsResponse(new CommentBundle { Comments = new List<Comment>() });
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
AddAssert("no comments placeholder shown", () => commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
|
||||
|
||||
setUpPostResponse();
|
||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||
AddStep("submit", () => commentsContainer.ChildrenOfType<RoundedButton>().First().TriggerClick());
|
||||
|
||||
AddUntilStep("comment sent", () =>
|
||||
{
|
||||
string writtenText = editorTextBox.Current.Value;
|
||||
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
|
||||
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
|
||||
});
|
||||
AddAssert("no comments placeholder removed", () => !commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPostWithExistingComments()
|
||||
{
|
||||
setUpCommentsResponse(getExampleComments());
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
|
||||
setUpPostResponse();
|
||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||
AddStep("submit", () => commentsContainer.ChildrenOfType<CommentEditor>().Single().ChildrenOfType<RoundedButton>().First().TriggerClick());
|
||||
|
||||
AddUntilStep("comment sent", () =>
|
||||
{
|
||||
string writtenText = editorTextBox.Current.Value;
|
||||
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
|
||||
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
@ -139,7 +183,33 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
});
|
||||
|
||||
private CommentBundle getExampleComments(bool withPinned = false)
|
||||
private void setUpPostResponse()
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
if (!(request is CommentPostRequest req))
|
||||
return false;
|
||||
|
||||
req.TriggerSuccess(new CommentBundle
|
||||
{
|
||||
Comments = new List<Comment>
|
||||
{
|
||||
new Comment
|
||||
{
|
||||
Id = 98,
|
||||
Message = req.Message,
|
||||
LegacyName = "FirstUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 98,
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
private static CommentBundle getExampleComments(bool withPinned = false)
|
||||
{
|
||||
var bundle = new CommentBundle
|
||||
{
|
||||
|
41
osu.Game/Online/API/Requests/CommentPostRequest.cs
Normal file
41
osu.Game/Online/API/Requests/CommentPostRequest.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Net.Http;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class CommentPostRequest : APIRequest<CommentBundle>
|
||||
{
|
||||
public readonly CommentableType Commentable;
|
||||
public readonly long CommentableId;
|
||||
public readonly string Message;
|
||||
public readonly long? ParentCommentId;
|
||||
|
||||
public CommentPostRequest(CommentableType commentable, long commentableId, string message, long? parentCommentId = null)
|
||||
{
|
||||
Commentable = commentable;
|
||||
CommentableId = commentableId;
|
||||
Message = message;
|
||||
ParentCommentId = parentCommentId;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
req.Method = HttpMethod.Post;
|
||||
|
||||
req.AddParameter(@"comment[commentable_type]", Commentable.ToString().ToLowerInvariant());
|
||||
req.AddParameter(@"comment[commentable_id]", $"{CommentableId}");
|
||||
req.AddParameter(@"comment[message]", Message);
|
||||
if (ParentCommentId.HasValue)
|
||||
req.AddParameter(@"comment[parent_id]", $"{ParentCommentId}");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => "comments";
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
@ -17,16 +18,22 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Threading;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
[Cached]
|
||||
public partial class CommentsContainer : CompositeDrawable
|
||||
{
|
||||
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
|
||||
private readonly BindableLong id = new BindableLong();
|
||||
public IBindable<CommentableType> Type => type;
|
||||
public IBindable<long> Id => id;
|
||||
|
||||
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
|
||||
public readonly BindableBool ShowDeleted = new BindableBool();
|
||||
@ -46,12 +53,14 @@ namespace osu.Game.Overlays.Comments
|
||||
private DeletedCommentsCounter deletedCommentsCounter;
|
||||
private CommentsShowMoreButton moreButton;
|
||||
private TotalCommentsCounter commentCounter;
|
||||
private UpdateableAvatar avatar;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -86,6 +95,32 @@ namespace osu.Game.Overlays.Comments
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 50, Vertical = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
avatar = new UpdateableAvatar(api.LocalUser.Value)
|
||||
{
|
||||
Size = new Vector2(50),
|
||||
CornerExponent = 2,
|
||||
CornerRadius = 25,
|
||||
Masking = true,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Padding = new MarginPadding { Left = 60 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = new NewCommentEditor
|
||||
{
|
||||
OnPost = prependPostedComments
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new CommentsHeader
|
||||
{
|
||||
Sort = { BindTarget = Sort },
|
||||
@ -151,6 +186,7 @@ namespace osu.Game.Overlays.Comments
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
User.BindValueChanged(_ => refetchComments());
|
||||
User.BindValueChanged(e => avatar.User = e.NewValue);
|
||||
Sort.BindValueChanged(_ => refetchComments(), true);
|
||||
base.LoadComplete();
|
||||
}
|
||||
@ -245,7 +281,6 @@ namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
|
||||
content.AddRange(loaded.Where(d => !d.Comment.Pinned));
|
||||
|
||||
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
|
||||
|
||||
if (bundle.HasMore)
|
||||
@ -288,6 +323,34 @@ namespace osu.Game.Overlays.Comments
|
||||
}
|
||||
}
|
||||
|
||||
private void prependPostedComments(CommentBundle bundle)
|
||||
{
|
||||
var topLevelComments = new List<DrawableComment>();
|
||||
|
||||
foreach (var comment in bundle.Comments)
|
||||
{
|
||||
// Exclude possible duplicated comments.
|
||||
if (CommentDictionary.ContainsKey(comment.Id))
|
||||
continue;
|
||||
|
||||
topLevelComments.Add(getDrawableComment(comment));
|
||||
}
|
||||
|
||||
if (topLevelComments.Any())
|
||||
{
|
||||
LoadComponentsAsync(topLevelComments, loaded =>
|
||||
{
|
||||
if (content.Count > 0 && content[0] is NoCommentsPlaceholder placeholder)
|
||||
content.Remove(placeholder, true);
|
||||
|
||||
foreach (var comment in loaded)
|
||||
{
|
||||
content.Insert((int)-Clock.CurrentTime, comment);
|
||||
}
|
||||
}, (loadCancellation = new CancellationTokenSource()).Token);
|
||||
}
|
||||
}
|
||||
|
||||
private DrawableComment getDrawableComment(Comment comment)
|
||||
{
|
||||
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
|
||||
@ -317,7 +380,7 @@ namespace osu.Game.Overlays.Comments
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
private partial class NoCommentsPlaceholder : CompositeDrawable
|
||||
internal partial class NoCommentsPlaceholder : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -336,5 +399,41 @@ namespace osu.Game.Overlays.Comments
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private partial class NewCommentEditor : CommentEditor
|
||||
{
|
||||
[Resolved]
|
||||
private CommentsContainer commentsContainer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
public Action<CommentBundle> OnPost;
|
||||
|
||||
//TODO should match web, left empty due to no multiline support
|
||||
protected override LocalisableString FooterText => default;
|
||||
|
||||
protected override LocalisableString CommitButtonText => CommonStrings.ButtonsPost;
|
||||
|
||||
protected override LocalisableString TextBoxPlaceholder => CommentsStrings.PlaceholderNew;
|
||||
|
||||
protected override void OnCommit(string text)
|
||||
{
|
||||
ShowLoadingSpinner = true;
|
||||
CommentPostRequest req = new CommentPostRequest(commentsContainer.Type.Value, commentsContainer.Id.Value, text);
|
||||
req.Failure += e => Schedule(() =>
|
||||
{
|
||||
ShowLoadingSpinner = false;
|
||||
Logger.Error(e, "Posting comment failed.");
|
||||
});
|
||||
req.Success += cb => Schedule(() =>
|
||||
{
|
||||
ShowLoadingSpinner = false;
|
||||
Current.Value = string.Empty;
|
||||
OnPost?.Invoke(cb);
|
||||
});
|
||||
api.Queue(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user