Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions fluXis/Graphics/UserInterface/Panel/Presets/FormPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ private IEnumerable<Drawable> createInputs(T data)

if (group == groupId && !string.IsNullOrEmpty(groupId))
{
groupItems.Add(prop, new GroupItem(createDrawable(prop, data), groupAttr));
groupItems.Add(prop, new GroupItem(createDrawable(prop, data, OnDataUpdate), groupAttr));
continue;
}

yield return createDrawable(prop, data);
yield return createDrawable(prop, data, OnDataUpdate);
}

if (!string.IsNullOrEmpty(groupId) && groupItems.Count > 0)
Expand Down Expand Up @@ -194,8 +194,10 @@ private static Drawable createGroup(Dictionary<PropertyInfo, GroupItem> dict)
};
}

private static Drawable createDrawable(PropertyInfo prop, T data)
private static Drawable createDrawable(PropertyInfo prop, T data, Action<T> onDataUpdate = null)
{
onDataUpdate ??= d => { };
Comment thread
orwenn22 marked this conversation as resolved.
Outdated

var type = prop.PropertyType;

if (type.IsNullable())
Expand Down Expand Up @@ -223,6 +225,7 @@ private static Drawable createDrawable(PropertyInfo prop, T data)
var bytes = File.ReadAllBytes(file.FullName);
var b64 = Convert.ToBase64String(bytes);
prop.SetValue(data, b64);
onDataUpdate(data);
Comment thread
orwenn22 marked this conversation as resolved.
Outdated
}
};

Expand All @@ -240,14 +243,19 @@ private static Drawable createDrawable(PropertyInfo prop, T data)
return new SetupColor(name)
{
Color = Colour4.TryParseHex(val as string ?? "#ffffff", out var c) ? c : Colour4.White,
OnColorChanged = v => prop.SetValue(data, v.ToHex())
OnColorChanged = v =>
{
prop.SetValue(data, v.ToHex());
onDataUpdate(data);
}
};
}
}

return new FluXisSpriteText { Text = $"could not create input for type {owr} ({name})" };
}

// TODO: call onDataUpdate here?
if (type.IsEnum)
{
var getValues = typeof(Enum)
Expand All @@ -273,10 +281,37 @@ private static Drawable createDrawable(PropertyInfo prop, T data)
{
Default = val as string,
Placeholder = prop.GetCustomAttribute<PlaceholderAttribute>()?.Placeholder ?? string.Empty,
TooltipText = prop.GetCustomAttribute<TooltipAttribute>()?.TooltipText ?? string.Empty,
MaxLength = prop.GetCustomAttribute<MaxLengthAttribute>()?.Length ?? 256,
ReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false,
Password = prop.GetCustomAttribute<PasswordPropertyTextAttribute>()?.Password ?? false,
OnChange = v => prop.SetValue(data, v)
OnChange = v =>
{
prop.SetValue(data, v);
onDataUpdate(data);
}
};
}

if (type == typeof(float))
{
var range = prop.GetCustomAttribute<RangeAttribute>();
var min = range != null ? Convert.ToSingle(range.Minimum) : float.MinValue;
var max = range != null ? Convert.ToSingle(range.Maximum) : float.MaxValue;

return new SetupNumberBox(name)
{
Default = val as string,
Placeholder = prop.GetCustomAttribute<PlaceholderAttribute>()?.Placeholder ?? string.Empty,
TooltipText = prop.GetCustomAttribute<TooltipAttribute>()?.TooltipText ?? string.Empty,
ReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false,
Min = min,
Max = max,
OnChange = v =>
{
prop.SetValue(data, v);
onDataUpdate(data);
}
};
}

Expand All @@ -286,6 +321,10 @@ void throwInvalidCombo(TypeOverrideAttribute.Type attr)
=> throw new InvalidOperationException($"Custom type '{attr}' can not be represented with '{prop.PropertyType}'.");
}

protected virtual void OnDataUpdate(T data)
{
}

public void Close()
{
if (Loading)
Expand Down
38 changes: 38 additions & 0 deletions fluXis/Online/API/Payloads/Maps/MapRateVotePayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using fluXis.Utils.Attributes;
using Newtonsoft.Json;

namespace fluXis.Online.API.Payloads.Maps;

// TODO: make fluxel use this instead of MapRateVoteRoute's internal 'Payload' class
public class MapRateVotePayload
{
[Description("Chart Difficulty (0-20)")]
[Tooltip("The base chart difficulty.")]
[Placeholder("0")]
[Range(0.0, 20.0)]
[JsonProperty("base")]
public float Base { get; set; }

[Description("Read Difficulty (0-5)")]
[Tooltip("SV stuff.")]
[Placeholder("0")]
[Range(0.0, 5.0)]
[JsonProperty("read")]
public float Reading { get; set; }

[Description("Track Difficulty (0-5)")]
[Tooltip("uuuh.")]
[Placeholder("0")]
[Range(0.0, 5.0)]
[JsonProperty("track")]
public float Tracking { get; set; }

[Description("Perception Difficulty (0-5)")]
[Tooltip("Blocking elements such as shaders and storyboard.")]
[Placeholder("0")]
[Range(0.0, 5.0)]
[JsonProperty("percept")]
public float Perception { get; set; }
}
28 changes: 28 additions & 0 deletions fluXis/Online/API/Requests/Maps/MapRateVoteRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Net.Http;
using fluXis.Online.API.Payloads.Maps;
using fluXis.Utils;
using WebRequest = osu.Framework.IO.Network.WebRequest;

namespace fluXis.Online.API.Requests.Maps;

public class MapRateVoteRequest : APIRequest<string>
{
protected override string Path => $"/map/{id}/rate";
protected override HttpMethod Method => HttpMethod.Post;

private long id { get; }
private MapRateVotePayload payload { get; }

public MapRateVoteRequest(long id, MapRateVotePayload payload)
{
this.id = id;
this.payload = payload;
}

protected override WebRequest CreateWebRequest(string url)
{
var req = base.CreateWebRequest(url);
req.AddRaw(payload.Serialize());
return req;
}
}
15 changes: 15 additions & 0 deletions fluXis/Online/API/Requests/Maps/MapRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using fluXis.Online.API.Models.Maps;

namespace fluXis.Online.API.Requests.Maps;

public class MapRequest : APIRequest<APIMap>
{
protected override string Path => $"/map/{id}";

private long id { get; }

public MapRequest(long id)
{
this.id = id;
}
}
48 changes: 48 additions & 0 deletions fluXis/Overlay/MapSet/MapSetOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
using fluXis.Graphics;
using fluXis.Graphics.Containers;
using fluXis.Graphics.Sprites.Icons;
using fluXis.Graphics.Sprites.Text;
using fluXis.Graphics.UserInterface.Buttons;
using fluXis.Graphics.UserInterface.Color;
using fluXis.Graphics.UserInterface.Panel;
using fluXis.Graphics.UserInterface.Tabs;
using fluXis.Input;
using fluXis.Online.API.Models.Maps;
Expand All @@ -12,6 +15,7 @@
using fluXis.Overlay.MapSet.Buttons;
using fluXis.Overlay.MapSet.Sidebar;
using fluXis.Overlay.MapSet.Tabs;
using fluXis.Overlay.MapSet.UI;
using fluXis.Overlay.MapSet.UI.Difficulties;
using JetBrains.Annotations;
using osu.Framework.Allocation;
Expand All @@ -30,6 +34,9 @@ public partial class MapSetOverlay : OverlayContainer, IKeyBindingHandler<FluXis
[Resolved]
private IAPIClient api { get; set; }

[Resolved]
private PanelContainer panels { get; set; }

[CanBeNull]
[Resolved(CanBeNull = true)]
private FluXisGame game { get; set; }
Expand All @@ -39,6 +46,10 @@ public partial class MapSetOverlay : OverlayContainer, IKeyBindingHandler<FluXis
private FluXisScrollContainer scroll;
private FillFlowContainer flow;

private FluXisButton rateVoteButton;
private Container alreadyVotedContainer;
private FluXisSpriteText alreadyVotedText;

[BackgroundDependencyLoader]
private void load()
{
Expand Down Expand Up @@ -211,6 +222,27 @@ private void displayData(APIMapSet set)
Spacing = new Vector2(20),
Children = new Drawable[]
{
rateVoteButton = new FluXisButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
Text = "Rate Vote",
Action = () => panels.Content = new RateVoteFormPanel(bindableMap.Value.ID),
Alpha = canVote(bindableMap.Value) ? 1f : 0f
},
alreadyVotedContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = 40,
Alpha = canVote(bindableMap.Value) ? 0f : 1f,
Child = alreadyVotedText = new FluXisSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
WebFontSize = 16,
Text = "Already voted", //TODO: display the user's rating
}
},
new MapSetSidebarMapper(bindableMap),
new MapSetSidebarVoting(set),
new MapSetSidebarStats(bindableMap)
Expand All @@ -222,6 +254,22 @@ private void displayData(APIMapSet set)
}
}
};

bindableMap.BindValueChanged(e => updateRateVoteAlpha(e.NewValue));
}

private void updateRateVoteAlpha(APIMap map)
{
bool votable = canVote(map);
rateVoteButton.Alpha = votable ? 1.0f : 0.0f;
alreadyVotedContainer.Alpha = votable ? 0.0f : 1.0f;
}

private bool canVote(APIMap map)
{
// if (!api.User.Value.IsPurifier()) return false; // <- uncomment this if we want to display the button to purifiers only

return !map.HasVotedRate && api.User.Value.ID != map.Mapper.ID;
}

private IEnumerable<TabContainer> createTabs(APIMapSet set, Bindable<APIMap> bindableMap)
Expand Down
105 changes: 105 additions & 0 deletions fluXis/Overlay/MapSet/UI/RateVoteFormPanel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using fluXis.Graphics.Sprites.Icons;
using fluXis.Graphics.UserInterface.Color;
using fluXis.Graphics.UserInterface.Panel.Presets;
using fluXis.Graphics.UserInterface.Text;
using fluXis.Online.API.Payloads.Maps;
using fluXis.Online.API.Requests.Maps;
using fluXis.Online.Fluxel;
using fluXis.Overlay.Notifications;
using fluXis.Utils;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;

namespace fluXis.Overlay.MapSet.UI;

public partial class RateVoteFormPanel : FormPanel<MapRateVotePayload>
{
[Resolved]
private IAPIClient api { get; set; }

[Resolved]
private NotificationManager notifications { get; set; }

private readonly FluXisTextFlow ratingText;
private readonly long mapId;
private readonly Action<float> onSuccess;

public RateVoteFormPanel(long mapId, Action<float> onSuccess = null)
: base(FontAwesome6.Solid.Check, new LocalisableString($"Rate Vote (id {mapId})"), new MapRateVotePayload(), (form, data) => ((RateVoteFormPanel)form).onVote(form, data), new LocalisableString("Vote"))
{
this.mapId = mapId;
this.onSuccess = onSuccess;

BottomContainer.Add(new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
new FluXisTextFlow
{
AutoSizeAxes = Axes.Both,
Text = "Your Rating: ",
WebFontSize = 16,
},
ratingText = new FluXisTextFlow
{
AutoSizeAxes = Axes.Both,
Text = "0",
Colour = Theme.GetDifficultyColor(0f),
WebFontSize = 16,
}
}
});
}

[BackgroundDependencyLoader]
private void load()
{
}
Comment thread
orwenn22 marked this conversation as resolved.
Outdated

protected override void OnDataUpdate(MapRateVotePayload data)
{
float rating = computeRating(data);
ratingText.Text = rating.ToStringInvariant();
ratingText.Colour = Theme.GetDifficultyColor(rating);
}

private static float computeRating(MapRateVotePayload data)
{
return (float)Math.Round(data.Base + ((data.Reading + data.Tracking + data.Perception) / 3) * 2, 2);
}

private bool onVote(FormPanel<MapRateVotePayload> form, MapRateVotePayload data)
{
// just in case
if (data.Base < 0 || data.Base > 20 ||
data.Reading < 0 || data.Reading > 5 ||
data.Tracking < 0 || data.Tracking > 5 ||
data.Perception < 0 || data.Perception > 5)
return false;
Comment thread
orwenn22 marked this conversation as resolved.

form.StartLoading();

var req = new MapRateVoteRequest(mapId, data);
req.Success += _ => Schedule(() =>
{
form.StopLoading();
form.Close();
onSuccess?.Invoke(computeRating(data));
});
req.Failure += ex => Schedule(() =>
{
form.StopLoading();

notifications.SendError("Failed to vote!", ex.Message);
});
api.PerformRequestAsync(req);

return false;
}
}
Loading
Loading