mirror of
https://github.com/osukey/osukey.git
synced 2025-08-06 16:13:57 +09:00
Merge pull request #22193 from Feodor0090/comment-editor-3
Integrate comment editor into comments view
This commit is contained in:
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -11,7 +9,10 @@ using osu.Game.Overlays;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
private CommentsContainer commentsContainer;
|
private CommentsContainer commentsContainer = null!;
|
||||||
|
|
||||||
|
private TextBox editorTextBox = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
Child = new BasicScrollContainer
|
Child = new BasicScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = commentsContainer = new CommentsContainer()
|
Child = commentsContainer = new CommentsContainer()
|
||||||
});
|
};
|
||||||
|
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
|
||||||
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestIdleState()
|
public void TestIdleState()
|
||||||
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
|
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)
|
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||||
=> AddStep("set up response", () =>
|
=> 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
|
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
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -17,16 +18,22 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
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
|
namespace osu.Game.Overlays.Comments
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public partial class CommentsContainer : CompositeDrawable
|
public partial class CommentsContainer : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
|
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
|
||||||
private readonly BindableLong id = new BindableLong();
|
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 Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
|
||||||
public readonly BindableBool ShowDeleted = new BindableBool();
|
public readonly BindableBool ShowDeleted = new BindableBool();
|
||||||
@ -46,12 +53,14 @@ namespace osu.Game.Overlays.Comments
|
|||||||
private DeletedCommentsCounter deletedCommentsCounter;
|
private DeletedCommentsCounter deletedCommentsCounter;
|
||||||
private CommentsShowMoreButton moreButton;
|
private CommentsShowMoreButton moreButton;
|
||||||
private TotalCommentsCounter commentCounter;
|
private TotalCommentsCounter commentCounter;
|
||||||
|
private UpdateableAvatar avatar;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
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
|
new CommentsHeader
|
||||||
{
|
{
|
||||||
Sort = { BindTarget = Sort },
|
Sort = { BindTarget = Sort },
|
||||||
@ -151,6 +186,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
User.BindValueChanged(_ => refetchComments());
|
User.BindValueChanged(_ => refetchComments());
|
||||||
|
User.BindValueChanged(e => avatar.User = e.NewValue);
|
||||||
Sort.BindValueChanged(_ => refetchComments(), true);
|
Sort.BindValueChanged(_ => refetchComments(), true);
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
}
|
}
|
||||||
@ -245,7 +281,6 @@ namespace osu.Game.Overlays.Comments
|
|||||||
{
|
{
|
||||||
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
|
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
|
||||||
content.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);
|
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
|
||||||
|
|
||||||
if (bundle.HasMore)
|
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)
|
private DrawableComment getDrawableComment(Comment comment)
|
||||||
{
|
{
|
||||||
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
|
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
|
||||||
@ -317,7 +380,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class NoCommentsPlaceholder : CompositeDrawable
|
internal partial class NoCommentsPlaceholder : CompositeDrawable
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user