refactor: pull channel list into its own class
This commit is contained in:
parent
43a2158c1c
commit
9aa1059e4d
|
@ -0,0 +1,405 @@
|
|||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Dalamud.Interface;
|
||||
using ExtraChat.Protocol;
|
||||
using ExtraChat.Protocol.Channels;
|
||||
using ExtraChat.Util;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace ExtraChat.Ui;
|
||||
|
||||
internal class ChannelList {
|
||||
private Plugin Plugin { get; }
|
||||
|
||||
private readonly List<(string, List<World>)> _worlds;
|
||||
|
||||
private string _createName = string.Empty;
|
||||
private Guid _selectedChannel = Guid.Empty;
|
||||
private string _inviteName = string.Empty;
|
||||
private ushort _inviteWorld;
|
||||
private string _rename = string.Empty;
|
||||
|
||||
internal ChannelList(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
|
||||
this._worlds = this.Plugin.DataManager.GetExcelSheet<World>()!
|
||||
.Where(row => row.IsPublic)
|
||||
.GroupBy(row => row.DataCenter.Value!)
|
||||
.Where(grouping => grouping.Key.Region != 0)
|
||||
.OrderBy(grouping => grouping.Key.Region)
|
||||
.ThenBy(grouping => grouping.Key.Name.RawString)
|
||||
.Select(grouping => (grouping.Key.Name.RawString, grouping.OrderBy(row => row.Name.RawString).ToList()))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal void Draw() {
|
||||
var anyChanged = false;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
|
||||
var syncButton = ImGui.CalcTextSize(FontAwesomeIcon.Sync.ToIconString()).X
|
||||
+ ImGui.GetStyle().FramePadding.X * 2;
|
||||
// PluginLog.Log($"syncButton: {syncButton}");
|
||||
var addButton = ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString()).X
|
||||
+ ImGui.GetStyle().FramePadding.X * 2;
|
||||
// PluginLog.Log($"addButton: {addButton}");
|
||||
var syncOffset = ImGui.GetContentRegionAvail().X - syncButton;
|
||||
var addOffset = ImGui.GetContentRegionAvail().X - syncButton - ImGui.GetStyle().ItemSpacing.X - addButton;
|
||||
ImGui.SameLine(syncOffset);
|
||||
|
||||
if (ImGui.Button(FontAwesomeIcon.Sync.ToIconString())) {
|
||||
Task.Run(async () => await this.Plugin.Client.ListAll());
|
||||
}
|
||||
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 1);
|
||||
|
||||
ImGui.SameLine(addOffset);
|
||||
|
||||
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString())) {
|
||||
ImGui.OpenPopup("create-channel-popup");
|
||||
}
|
||||
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 0);
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
if (ImGui.BeginPopup("create-channel-popup")) {
|
||||
ImGui.TextUnformatted("Create a new ExtraChat Linkshell");
|
||||
|
||||
ImGui.SetNextItemWidth(350 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint("##linkshell-name", "Linkshell name", ref this._createName, 64);
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this._createName) && ImGui.Button("Create") && !this.Plugin.PluginUi.Busy) {
|
||||
this.Plugin.PluginUi.Busy = true;
|
||||
var name = this._createName;
|
||||
Task.Run(async () => await this.Plugin.Client.Create(name))
|
||||
.ContinueWith(_ => this.Plugin.PluginUi.Busy = false);
|
||||
ImGui.CloseCurrentPopup();
|
||||
this._createName = string.Empty;
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (this.Plugin.Client.Channels.Count == 0 && this.Plugin.Client.InvitedChannels.Count == 0) {
|
||||
ImGui.TextUnformatted("You aren't in any linkshells yet. Try creating or joining one first.");
|
||||
goto AfterTable;
|
||||
}
|
||||
|
||||
this.DrawTable(ref anyChanged);
|
||||
|
||||
AfterTable:
|
||||
if (anyChanged) {
|
||||
this.Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTable(ref bool anyChanged) {
|
||||
if (!ImGui.BeginTable("ecls-list", 2, ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingFixedFit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.TableSetupColumn("##channels", ImGuiTableColumnFlags.WidthFixed, 125 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##members", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableNextRow();
|
||||
|
||||
var channelOrder = this.Plugin.ConfigInfo.ChannelOrder.ToDictionary(
|
||||
entry => entry.Value,
|
||||
entry => entry.Key
|
||||
);
|
||||
|
||||
var orderedChannels = this.Plugin.Client.Channels.Keys
|
||||
.OrderBy(id => channelOrder.ContainsKey(id) ? channelOrder[id] : int.MaxValue)
|
||||
.Concat(this.Plugin.Client.InvitedChannels.Keys);
|
||||
|
||||
var childSize = new Vector2(
|
||||
-1,
|
||||
ImGui.GetContentRegionAvail().Y
|
||||
- ImGui.GetStyle().WindowPadding.Y
|
||||
- ImGui.GetStyle().ItemSpacing.Y
|
||||
);
|
||||
|
||||
if (ImGui.TableSetColumnIndex(0)) {
|
||||
this.DrawChannelList(ref anyChanged, childSize, orderedChannels, channelOrder);
|
||||
}
|
||||
|
||||
if (ImGui.TableSetColumnIndex(1) && this._selectedChannel != Guid.Empty) {
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
Task.Run(async () => await this.Plugin.Client.ListMembers(this._selectedChannel));
|
||||
}
|
||||
|
||||
if (ImGui.BeginChild("channel-info", childSize)) {
|
||||
this.DrawInfo(ref anyChanged);
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
private void DrawChannelList(ref bool anyChanged, Vector2 childSize, IEnumerable<Guid> orderedChannels, Dictionary<Guid, int> channelOrder) {
|
||||
if (!ImGui.BeginChild("channel-list", childSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var id in orderedChannels) {
|
||||
this.DrawChannel(ref anyChanged, channelOrder, id, ref first);
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawChannel(ref bool anyChanged, IReadOnlyDictionary<Guid, int> channelOrder, Guid id, ref bool first) {
|
||||
this.Plugin.ConfigInfo.Channels.TryGetValue(id, out var info);
|
||||
var name = info?.Name ?? "???";
|
||||
|
||||
var order = "?";
|
||||
if (channelOrder.TryGetValue(id, out var o)) {
|
||||
order = (o + 1).ToString();
|
||||
}
|
||||
|
||||
if (!this.Plugin.Client.ChannelRanks.TryGetValue(id, out var rank)) {
|
||||
rank = Rank.Member;
|
||||
}
|
||||
|
||||
if (ImGui.Selectable($"{order}. {rank.Symbol()}{name}###{id}", this._selectedChannel == id)) {
|
||||
this._selectedChannel = id;
|
||||
|
||||
Task.Run(async () => await this.Plugin.Client.ListMembers(id));
|
||||
}
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 2);
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 3);
|
||||
}
|
||||
|
||||
if (!ImGui.BeginPopupContextItem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var invited = this.Plugin.Client.InvitedChannels.ContainsKey(id);
|
||||
if (invited) {
|
||||
if (ImGui.Selectable("Accept invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Join(id));
|
||||
}
|
||||
|
||||
if (ImGuiUtil.SelectableConfirm("Decline invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Leave(id));
|
||||
}
|
||||
} else {
|
||||
if (ImGuiUtil.SelectableConfirm("Leave")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Leave(id));
|
||||
}
|
||||
|
||||
if (rank == Rank.Admin) {
|
||||
if (ImGuiUtil.SelectableConfirm("Disband")) {
|
||||
Task.Run(async () => {
|
||||
if (await this.Plugin.Client.Disband(id) is { } error) {
|
||||
this.Plugin.ShowError($"Could not disband \"{name}\": {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (rank == Rank.Admin && info != null && ImGui.BeginMenu($"Rename##{id}-rename")) {
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._rename = string.Empty;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(350 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint($"##{id}-rename-input", "New name", ref this._rename, 64);
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
if (ImGui.Button($"Rename##{id}-rename-button") && !string.IsNullOrWhiteSpace(this._rename)) {
|
||||
var newName = SecretBox.Encrypt(info.SharedSecret, Encoding.UTF8.GetBytes(this._rename));
|
||||
Task.Run(async () => await this.Plugin.Client.UpdateToast(id, new UpdateKind.Name(newName)));
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu($"Invite##{id}-invite")) {
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._inviteName = string.Empty;
|
||||
this._inviteWorld = 0;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint("##invite-name", "Name", ref this._inviteName, 32);
|
||||
|
||||
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
||||
var preview = this._inviteWorld == 0 ? "World" : WorldUtil.WorldName(this._inviteWorld);
|
||||
if (ImGui.BeginCombo("##invite-world", preview)) {
|
||||
foreach (var (dc, worlds) in this._worlds) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
|
||||
ImGui.TextUnformatted(dc);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.Separator();
|
||||
|
||||
foreach (var world in worlds) {
|
||||
if (ImGui.Selectable(world.Name.RawString, this._inviteWorld == world.RowId)) {
|
||||
this._inviteWorld = (ushort) world.RowId;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
if (ImGui.Button($"Invite##{id}-invite-button") && !string.IsNullOrWhiteSpace(this._inviteName) && this._inviteWorld != 0) {
|
||||
var inviteName = this._inviteName;
|
||||
var inviteWorld = this._inviteWorld;
|
||||
|
||||
Task.Run(async () => await this.Plugin.Client.InviteToast(inviteName, inviteWorld, id));
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.BeginMenu("Change number")) {
|
||||
ImGui.SetNextItemWidth(150 * ImGuiHelpers.GlobalScale);
|
||||
channelOrder.TryGetValue(id, out var refOrder);
|
||||
var old = refOrder;
|
||||
refOrder += 1;
|
||||
if (ImGui.InputInt($"##{id}-order", ref refOrder)) {
|
||||
refOrder = Math.Max(1, refOrder) - 1;
|
||||
|
||||
if (this.Plugin.ConfigInfo.ChannelOrder.TryGetValue(refOrder, out var other) && other != id) {
|
||||
// another channel already has this number, so swap
|
||||
this.Plugin.ConfigInfo.ChannelOrder[old] = other;
|
||||
} else {
|
||||
this.Plugin.ConfigInfo.ChannelOrder.Remove(old);
|
||||
}
|
||||
|
||||
this.Plugin.ConfigInfo.ChannelOrder[refOrder] = id;
|
||||
this.Plugin.SaveConfig();
|
||||
this.Plugin.Commands.ReregisterAll();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
if (ImGui.Selectable("Request secrets")) {
|
||||
Task.Run(async () => await this.Plugin.Client.RequestSecrets(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
private void DrawInfo(ref bool anyChanged) {
|
||||
if (!this.Plugin.Client.TryGetChannel(this._selectedChannel, out var channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4 disabledColour;
|
||||
unsafe {
|
||||
disabledColour = *ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled);
|
||||
}
|
||||
|
||||
if (!this.Plugin.Client.ChannelRanks.TryGetValue(this._selectedChannel, out var rank)) {
|
||||
rank = Rank.Member;
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var member in channel.Members) {
|
||||
if (!member.Online) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, disabledColour);
|
||||
}
|
||||
|
||||
try {
|
||||
ImGui.TextUnformatted($"{member.Rank.Symbol()}{member.Name}{PluginUi.CrossWorld}{WorldUtil.WorldName(member.World)}");
|
||||
} finally {
|
||||
if (!member.Online) {
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
this.DrawMemberContextMenu(member, rank);
|
||||
|
||||
if (!first) {
|
||||
continue;
|
||||
}
|
||||
|
||||
first = false;
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 4);
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 5);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMemberContextMenu(Member member, Rank rank) {
|
||||
if (!ImGui.BeginPopupContextItem($"{this._selectedChannel}-{member.Name}@{member.World}-context")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cursor = ImGui.GetCursorPos();
|
||||
|
||||
if (rank == Rank.Admin) {
|
||||
if (member.Rank is not (Rank.Admin or Rank.Invited)) {
|
||||
if (ImGuiUtil.SelectableConfirm("Promote to admin", tooltip: "This will demote you to moderator.")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Promote(this._selectedChannel, member.Name, member.World, Rank.Admin));
|
||||
}
|
||||
}
|
||||
|
||||
if (member.Rank == Rank.Moderator && ImGuiUtil.SelectableConfirm("Demote")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Promote(this._selectedChannel, member.Name, member.World, Rank.Member));
|
||||
}
|
||||
|
||||
if (member.Rank == Rank.Member && ImGuiUtil.SelectableConfirm("Promote to moderator")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Promote(this._selectedChannel, member.Name, member.World, Rank.Moderator));
|
||||
}
|
||||
}
|
||||
|
||||
if (rank >= Rank.Moderator) {
|
||||
var canKick = member.Rank < rank && member.Rank != Rank.Invited;
|
||||
if (canKick && ImGuiUtil.SelectableConfirm("Kick")) {
|
||||
Task.Run(async () => {
|
||||
if (await this.Plugin.Client.Kick(this._selectedChannel, member.Name, member.World) is { } error) {
|
||||
this.Plugin.ShowError($"Could not kick {member.Name}: {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (member.Rank == Rank.Invited && ImGuiUtil.SelectableConfirm("Cancel invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Kick(this._selectedChannel, member.Name, member.World));
|
||||
}
|
||||
}
|
||||
|
||||
if (rank == Rank.Invited && member.Rank == Rank.Invited) {
|
||||
if (member.Name == this.Plugin.LocalPlayer?.Name.TextValue && member.World == this.Plugin.LocalPlayer?.HomeWorld.Id) {
|
||||
if (ImGui.Selectable("Accept invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Join(this._selectedChannel));
|
||||
}
|
||||
|
||||
if (ImGuiUtil.SelectableConfirm("Decline invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Leave(this._selectedChannel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor == ImGui.GetCursorPos()) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
|
||||
ImGui.TextUnformatted("No options available");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Channels;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using ExtraChat.Protocol;
|
||||
using ExtraChat.Protocol.Channels;
|
||||
using ExtraChat.Util;
|
||||
using ImGuiNET;
|
||||
|
@ -18,23 +16,15 @@ internal class PluginUi : IDisposable {
|
|||
internal const string CrossWorld = "\ue05d";
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
private ChannelList ChannelList { get; }
|
||||
|
||||
internal bool Visible;
|
||||
|
||||
private readonly List<(string, List<World>)> _worlds;
|
||||
private readonly List<(uint Id, Vector4 Abgr)> _uiColours;
|
||||
|
||||
internal PluginUi(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
|
||||
this._worlds = this.Plugin.DataManager.GetExcelSheet<World>()!
|
||||
.Where(row => row.IsPublic)
|
||||
.GroupBy(row => row.DataCenter.Value!)
|
||||
.Where(grouping => grouping.Key.Region != 0)
|
||||
.OrderBy(grouping => grouping.Key.Region)
|
||||
.ThenBy(grouping => grouping.Key.Name.RawString)
|
||||
.Select(grouping => (grouping.Key.Name.RawString, grouping.OrderBy(row => row.Name.RawString).ToList()))
|
||||
.ToList();
|
||||
this.ChannelList = new ChannelList(this.Plugin);
|
||||
|
||||
this._uiColours = this.Plugin.DataManager.GetExcelSheet<UIColor>()!
|
||||
.Where(row => row.UIForeground is not (0 or 0x000000FF))
|
||||
|
@ -66,9 +56,8 @@ internal class PluginUi : IDisposable {
|
|||
|
||||
internal (string, ushort)? InviteInfo;
|
||||
|
||||
private volatile bool _busy;
|
||||
internal volatile bool Busy;
|
||||
private string? _challenge;
|
||||
private string _createName = string.Empty;
|
||||
private Guid? _inviteId;
|
||||
private readonly Channel<string?> _challengeChannel = Channel.CreateUnbounded<string?>();
|
||||
|
||||
|
@ -104,20 +93,20 @@ internal class PluginUi : IDisposable {
|
|||
var status = this.Plugin.Client.Status;
|
||||
ImGui.TextUnformatted($"Status: {status}");
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.Wifi, tooltip: "Reconnect") && !this._busy) {
|
||||
this._busy = true;
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.Wifi, tooltip: "Reconnect") && !this.Busy) {
|
||||
this.Busy = true;
|
||||
|
||||
Task.Run(async () => {
|
||||
this.Plugin.Client.StopLoop();
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
this.Plugin.Client.StartLoop();
|
||||
this._busy = false;
|
||||
this.Busy = false;
|
||||
});
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case Client.State.Connected:
|
||||
this.DrawList();
|
||||
this.ChannelList.Draw();
|
||||
break;
|
||||
case Client.State.NotAuthenticated:
|
||||
case Client.State.RetrievingChallenge:
|
||||
|
@ -394,12 +383,12 @@ internal class PluginUi : IDisposable {
|
|||
if (this.Plugin.ConfigInfo.Key != null) {
|
||||
ImGui.TextUnformatted("Please wait...");
|
||||
} else {
|
||||
if (ImGui.Button($"Register {player.Name}") && !this._busy) {
|
||||
this._busy = true;
|
||||
if (ImGui.Button($"Register {player.Name}") && !this.Busy) {
|
||||
this.Busy = true;
|
||||
Task.Run(async () => {
|
||||
var challenge = await this.Plugin.Client.GetChallenge();
|
||||
await this._challengeChannel.Writer.WriteAsync(challenge);
|
||||
}).ContinueWith(_ => this._busy = false);
|
||||
}).ContinueWith(_ => this.Busy = false);
|
||||
}
|
||||
|
||||
ImGui.PushTextWrapPos();
|
||||
|
@ -451,367 +440,18 @@ internal class PluginUi : IDisposable {
|
|||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Verify") && !this._busy) {
|
||||
this._busy = true;
|
||||
if (ImGui.Button("Verify") && !this.Busy) {
|
||||
this.Busy = true;
|
||||
Task.Run(async () => {
|
||||
var key = await this.Plugin.Client.Register();
|
||||
this.Plugin.ConfigInfo.Key = key;
|
||||
this.Plugin.SaveConfig();
|
||||
await this.Plugin.Client.AuthenticateAndList();
|
||||
}).ContinueWith(_ => this._busy = false);
|
||||
}).ContinueWith(_ => this.Busy = false);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopTextWrapPos();
|
||||
}
|
||||
}
|
||||
|
||||
private Guid _selectedChannel = Guid.Empty;
|
||||
private string _inviteName = string.Empty;
|
||||
private ushort _inviteWorld;
|
||||
private string _rename = string.Empty;
|
||||
|
||||
private void DrawList() {
|
||||
var anyChanged = false;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
|
||||
var syncButton = ImGui.CalcTextSize(FontAwesomeIcon.Sync.ToIconString()).X
|
||||
+ ImGui.GetStyle().FramePadding.X * 2;
|
||||
// PluginLog.Log($"syncButton: {syncButton}");
|
||||
var addButton = ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString()).X
|
||||
+ ImGui.GetStyle().FramePadding.X * 2;
|
||||
// PluginLog.Log($"addButton: {addButton}");
|
||||
var syncOffset = ImGui.GetContentRegionAvail().X - syncButton;
|
||||
var addOffset = ImGui.GetContentRegionAvail().X - syncButton - ImGui.GetStyle().ItemSpacing.X - addButton;
|
||||
ImGui.SameLine(syncOffset);
|
||||
|
||||
if (ImGui.Button(FontAwesomeIcon.Sync.ToIconString())) {
|
||||
Task.Run(async () => await this.Plugin.Client.ListAll());
|
||||
}
|
||||
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 1);
|
||||
|
||||
ImGui.SameLine(addOffset);
|
||||
|
||||
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString())) {
|
||||
ImGui.OpenPopup("create-channel-popup");
|
||||
}
|
||||
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 0);
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
if (ImGui.BeginPopup("create-channel-popup")) {
|
||||
ImGui.TextUnformatted("Create a new ExtraChat Linkshell");
|
||||
|
||||
ImGui.SetNextItemWidth(350 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint("##linkshell-name", "Linkshell name", ref this._createName, 64);
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this._createName) && ImGui.Button("Create") && !this._busy) {
|
||||
this._busy = true;
|
||||
var name = this._createName;
|
||||
Task.Run(async () => await this.Plugin.Client.Create(name))
|
||||
.ContinueWith(_ => this._busy = false);
|
||||
ImGui.CloseCurrentPopup();
|
||||
this._createName = string.Empty;
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (this.Plugin.Client.Channels.Count == 0 && this.Plugin.Client.InvitedChannels.Count == 0) {
|
||||
ImGui.TextUnformatted("You aren't in any linkshells yet. Try creating or joining one first.");
|
||||
goto AfterTable;
|
||||
}
|
||||
|
||||
if (ImGui.BeginTable("ecls-list", 2, ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingFixedFit)) {
|
||||
ImGui.TableSetupColumn("##channels", ImGuiTableColumnFlags.WidthFixed, 125 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##members", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableNextRow();
|
||||
|
||||
var channelOrder = this.Plugin.ConfigInfo.ChannelOrder.ToDictionary(
|
||||
entry => entry.Value,
|
||||
entry => entry.Key
|
||||
);
|
||||
|
||||
var orderedChannels = this.Plugin.Client.Channels.Keys
|
||||
.OrderBy(id => channelOrder.ContainsKey(id) ? channelOrder[id] : int.MaxValue)
|
||||
.Concat(this.Plugin.Client.InvitedChannels.Keys);
|
||||
|
||||
var childSize = new Vector2(
|
||||
-1,
|
||||
ImGui.GetContentRegionAvail().Y
|
||||
- ImGui.GetStyle().WindowPadding.Y
|
||||
- ImGui.GetStyle().ItemSpacing.Y
|
||||
);
|
||||
|
||||
if (ImGui.TableSetColumnIndex(0)) {
|
||||
if (ImGui.BeginChild("channel-list", childSize)) {
|
||||
var first = true;
|
||||
foreach (var id in orderedChannels) {
|
||||
this.Plugin.ConfigInfo.Channels.TryGetValue(id, out var info);
|
||||
var name = info?.Name ?? "???";
|
||||
|
||||
var order = "?";
|
||||
if (channelOrder.TryGetValue(id, out var o)) {
|
||||
order = (o + 1).ToString();
|
||||
}
|
||||
|
||||
if (!this.Plugin.Client.ChannelRanks.TryGetValue(id, out var rank)) {
|
||||
rank = Rank.Member;
|
||||
}
|
||||
|
||||
if (ImGui.Selectable($"{order}. {rank.Symbol()}{name}###{id}", this._selectedChannel == id)) {
|
||||
this._selectedChannel = id;
|
||||
|
||||
Task.Run(async () => await this.Plugin.Client.ListMembers(id));
|
||||
}
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 2);
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 3);
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopupContextItem()) {
|
||||
var invited = this.Plugin.Client.InvitedChannels.ContainsKey(id);
|
||||
if (invited) {
|
||||
if (ImGui.Selectable("Accept invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Join(id));
|
||||
}
|
||||
|
||||
if (ImGuiUtil.SelectableConfirm("Decline invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Leave(id));
|
||||
}
|
||||
} else {
|
||||
if (ImGuiUtil.SelectableConfirm("Leave")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Leave(id));
|
||||
}
|
||||
|
||||
if (rank == Rank.Admin) {
|
||||
if (ImGuiUtil.SelectableConfirm("Disband")) {
|
||||
Task.Run(async () => {
|
||||
if (await this.Plugin.Client.Disband(id) is { } error) {
|
||||
this.Plugin.ShowError($"Could not disband \"{name}\": {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (rank == Rank.Admin && info != null && ImGui.BeginMenu($"Rename##{id}-rename")) {
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._rename = string.Empty;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(350 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint($"##{id}-rename-input", "New name", ref this._rename, 64);
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
ImGui.SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
if (ImGui.Button($"Rename##{id}-rename-button") && !string.IsNullOrWhiteSpace(this._rename)) {
|
||||
var newName = SecretBox.Encrypt(info.SharedSecret, Encoding.UTF8.GetBytes(this._rename));
|
||||
Task.Run(async () => await this.Plugin.Client.UpdateToast(id, new UpdateKind.Name(newName)));
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu($"Invite##{id}-invite")) {
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this._inviteName = string.Empty;
|
||||
this._inviteWorld = 0;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint("##invite-name", "Name", ref this._inviteName, 32);
|
||||
|
||||
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
||||
var preview = this._inviteWorld == 0 ? "World" : WorldUtil.WorldName(this._inviteWorld);
|
||||
if (ImGui.BeginCombo("##invite-world", preview)) {
|
||||
foreach (var (dc, worlds) in this._worlds) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
|
||||
ImGui.TextUnformatted(dc);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.Separator();
|
||||
|
||||
foreach (var world in worlds) {
|
||||
if (ImGui.Selectable(world.Name.RawString, this._inviteWorld == world.RowId)) {
|
||||
this._inviteWorld = (ushort) world.RowId;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
if (ImGui.Button($"Invite##{id}-invite-button") && !string.IsNullOrWhiteSpace(this._inviteName) && this._inviteWorld != 0) {
|
||||
var inviteName = this._inviteName;
|
||||
var inviteWorld = this._inviteWorld;
|
||||
|
||||
Task.Run(async () => await this.Plugin.Client.InviteToast(inviteName, inviteWorld, id));
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.BeginMenu("Change number")) {
|
||||
ImGui.SetNextItemWidth(150 * ImGuiHelpers.GlobalScale);
|
||||
channelOrder.TryGetValue(id, out var refOrder);
|
||||
var old = refOrder;
|
||||
refOrder += 1;
|
||||
if (ImGui.InputInt($"##{id}-order", ref refOrder)) {
|
||||
refOrder = Math.Max(1, refOrder) - 1;
|
||||
|
||||
if (this.Plugin.ConfigInfo.ChannelOrder.TryGetValue(refOrder, out var other) && other != id) {
|
||||
// another channel already has this number, so swap
|
||||
this.Plugin.ConfigInfo.ChannelOrder[old] = other;
|
||||
} else {
|
||||
this.Plugin.ConfigInfo.ChannelOrder.Remove(old);
|
||||
}
|
||||
|
||||
this.Plugin.ConfigInfo.ChannelOrder[refOrder] = id;
|
||||
this.Plugin.SaveConfig();
|
||||
this.Plugin.Commands.ReregisterAll();
|
||||
}
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
if (ImGui.Selectable("Request secrets")) {
|
||||
Task.Run(async () => await this.Plugin.Client.RequestSecrets(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.TableSetColumnIndex(1) && this._selectedChannel != Guid.Empty) {
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
Task.Run(async () => await this.Plugin.Client.ListMembers(this._selectedChannel));
|
||||
}
|
||||
|
||||
void DrawInfo() {
|
||||
if (!this.Plugin.Client.TryGetChannel(this._selectedChannel, out var channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector4 disabledColour;
|
||||
unsafe {
|
||||
disabledColour = *ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled);
|
||||
}
|
||||
|
||||
if (!this.Plugin.Client.ChannelRanks.TryGetValue(this._selectedChannel, out var rank)) {
|
||||
rank = Rank.Member;
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var member in channel.Members) {
|
||||
if (!member.Online) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, disabledColour);
|
||||
}
|
||||
|
||||
try {
|
||||
ImGui.TextUnformatted($"{member.Rank.Symbol()}{member.Name}{CrossWorld}{WorldUtil.WorldName(member.World)}");
|
||||
} finally {
|
||||
if (!member.Online) {
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 4);
|
||||
anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 5);
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopupContextItem($"{this._selectedChannel}-{member.Name}@{member.World}-context")) {
|
||||
var cursor = ImGui.GetCursorPos();
|
||||
|
||||
if (rank == Rank.Admin) {
|
||||
if (member.Rank is not (Rank.Admin or Rank.Invited)) {
|
||||
if (ImGuiUtil.SelectableConfirm("Promote to admin", tooltip: "This will demote you to moderator.")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Promote(this._selectedChannel, member.Name, member.World, Rank.Admin));
|
||||
}
|
||||
}
|
||||
|
||||
if (member.Rank == Rank.Moderator && ImGuiUtil.SelectableConfirm("Demote")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Promote(this._selectedChannel, member.Name, member.World, Rank.Member));
|
||||
}
|
||||
|
||||
if (member.Rank == Rank.Member && ImGuiUtil.SelectableConfirm("Promote to moderator")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Promote(this._selectedChannel, member.Name, member.World, Rank.Moderator));
|
||||
}
|
||||
}
|
||||
|
||||
if (rank >= Rank.Moderator) {
|
||||
var canKick = member.Rank < rank && member.Rank != Rank.Invited;
|
||||
if (canKick && ImGuiUtil.SelectableConfirm("Kick")) {
|
||||
Task.Run(async () => {
|
||||
if (await this.Plugin.Client.Kick(this._selectedChannel, member.Name, member.World) is { } error) {
|
||||
this.Plugin.ShowError($"Could not kick {member.Name}: {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (member.Rank == Rank.Invited && ImGuiUtil.SelectableConfirm("Cancel invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Kick(this._selectedChannel, member.Name, member.World));
|
||||
}
|
||||
}
|
||||
|
||||
if (rank == Rank.Invited && member.Rank == Rank.Invited) {
|
||||
if (member.Name == this.Plugin.LocalPlayer?.Name.TextValue && member.World == this.Plugin.LocalPlayer?.HomeWorld.Id) {
|
||||
if (ImGui.Selectable("Accept invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Join(this._selectedChannel));
|
||||
}
|
||||
|
||||
if (ImGuiUtil.SelectableConfirm("Decline invite")) {
|
||||
Task.Run(async () => await this.Plugin.Client.Leave(this._selectedChannel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor == ImGui.GetCursorPos()) {
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]);
|
||||
ImGui.TextUnformatted("No options available");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.BeginChild("channel-info", childSize)) {
|
||||
DrawInfo();
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
AfterTable:
|
||||
if (anyChanged) {
|
||||
this.Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue