feat: better handle new packs
This commit is contained in:
parent
c933a550ca
commit
ac19ac6792
@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using OrangeGuidanceTomestone.Helpers;
|
using OrangeGuidanceTomestone.Helpers;
|
||||||
|
|
||||||
namespace OrangeGuidanceTomestone;
|
namespace OrangeGuidanceTomestone;
|
||||||
@ -7,12 +8,17 @@ namespace OrangeGuidanceTomestone;
|
|||||||
public class Pack {
|
public class Pack {
|
||||||
internal static SemaphoreSlim AllMutex { get; } = new(1, 1);
|
internal static SemaphoreSlim AllMutex { get; } = new(1, 1);
|
||||||
internal static Pack[] All { get; set; } = [];
|
internal static Pack[] All { get; set; } = [];
|
||||||
|
private static readonly JsonSerializerOptions Options = new() {
|
||||||
|
Converters = {
|
||||||
|
new TemplateConverter(),
|
||||||
|
},
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
|
};
|
||||||
|
|
||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
public Guid Id { get; init; }
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(TemplateConverter))]
|
public Template[] Templates { get; init; }
|
||||||
public ITemplate[] Templates { get; init; }
|
|
||||||
|
|
||||||
public string[]? Conjunctions { get; init; }
|
public string[]? Conjunctions { get; init; }
|
||||||
public List<WordList>? Words { get; init; }
|
public List<WordList>? Words { get; init; }
|
||||||
@ -21,7 +27,7 @@ public class Pack {
|
|||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
var resp = await ServerHelper.SendRequest(null, HttpMethod.Get, "/packs");
|
var resp = await ServerHelper.SendRequest(null, HttpMethod.Get, "/packs");
|
||||||
var json = await resp.Content.ReadAsStringAsync();
|
var json = await resp.Content.ReadAsStringAsync();
|
||||||
var packs = JsonConvert.DeserializeObject<Pack[]>(json)!;
|
var packs = JsonSerializer.Deserialize<Pack[]>(json, Pack.Options)!;
|
||||||
await AllMutex.WaitAsync();
|
await AllMutex.WaitAsync();
|
||||||
try {
|
try {
|
||||||
All = packs;
|
All = packs;
|
||||||
@ -32,48 +38,50 @@ public class Pack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ITemplate {
|
public class Template {
|
||||||
public string Template { get; }
|
[JsonPropertyName("template")]
|
||||||
public string[]? Words { get; }
|
public string Text { get; init; }
|
||||||
|
public string[]? Words { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BasicTemplate : ITemplate {
|
public class TemplateConverter : JsonConverter<Template> {
|
||||||
public string Template { get; init; }
|
private static JsonSerializerOptions RemoveSelf(JsonSerializerOptions old) {
|
||||||
public string[]? Words => null;
|
var newOptions = new JsonSerializerOptions(old);
|
||||||
}
|
for (var i = 0; i < old.Converters.Count; i++) {
|
||||||
|
if (old.Converters[i] is TemplateConverter) {
|
||||||
[Serializable]
|
newOptions.Converters.RemoveAt(i);
|
||||||
public class WordListTemplate : ITemplate {
|
break;
|
||||||
public string Template { get; init; }
|
}
|
||||||
public string[] Words { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TemplateConverter : JsonConverter<ITemplate>
|
|
||||||
{
|
|
||||||
public override ITemplate? ReadJson(JsonReader reader, Type objectType, ITemplate? existingValue, bool hasExistingValue, JsonSerializer serializer) {
|
|
||||||
if (reader.TokenType == JsonToken.String) {
|
|
||||||
var template = reader.ReadAsString();
|
|
||||||
if (template == null) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BasicTemplate {
|
return newOptions;
|
||||||
Template = template,
|
}
|
||||||
|
|
||||||
|
public override Template? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
switch (reader.TokenType) {
|
||||||
|
case JsonTokenType.String: {
|
||||||
|
var template = reader.GetString() ?? throw new JsonException("template cannot be null");
|
||||||
|
return new Template {
|
||||||
|
Text = template,
|
||||||
|
Words = null,
|
||||||
};
|
};
|
||||||
} else if (reader.TokenType == JsonToken.StartObject) {
|
}
|
||||||
return serializer.Deserialize<WordListTemplate>(reader);
|
case JsonTokenType.StartObject: {
|
||||||
} else {
|
var newOptions = TemplateConverter.RemoveSelf(options);
|
||||||
throw new JsonReaderException("unexpected template kind");
|
return JsonSerializer.Deserialize<Template>(ref reader, newOptions);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new JsonException("unexpected template type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, ITemplate? value, JsonSerializer serializer) {
|
public override void Write(Utf8JsonWriter writer, Template value, JsonSerializerOptions options) {
|
||||||
if (value is BasicTemplate basic) {
|
if (value.Words == null) {
|
||||||
serializer.Serialize(writer, basic.Template);
|
JsonSerializer.Serialize(writer, value.Text, options);
|
||||||
} else if (value is WordListTemplate wordList) {
|
|
||||||
serializer.Serialize(writer, wordList);
|
|
||||||
} else {
|
} else {
|
||||||
throw new JsonWriterException("unexpected template kind");
|
var newOptions = TemplateConverter.RemoveSelf(options);
|
||||||
|
JsonSerializer.Serialize(writer, value, newOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using Dalamud.Interface.Internal;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OrangeGuidanceTomestone.Helpers;
|
using OrangeGuidanceTomestone.Helpers;
|
||||||
|
using OrangeGuidanceTomestone.Util;
|
||||||
|
|
||||||
namespace OrangeGuidanceTomestone.Ui.MainWindowTabs;
|
namespace OrangeGuidanceTomestone.Ui.MainWindowTabs;
|
||||||
|
|
||||||
@ -21,6 +22,39 @@ internal class Write : ITab {
|
|||||||
private (int, int) _word2 = (-1, -1);
|
private (int, int) _word2 = (-1, -1);
|
||||||
private int _glyph;
|
private int _glyph;
|
||||||
|
|
||||||
|
private const string Placeholder = "****";
|
||||||
|
private Pack? Pack => Pack.All.Get(this._pack);
|
||||||
|
private Template? Template1 => this.Pack?.Templates.Get(this._part1);
|
||||||
|
private Template? Template2 => this.Pack?.Templates.Get(this._part2);
|
||||||
|
private string? Word1 => this.GetWord(this._word1, this.Template1);
|
||||||
|
private string? Word2 => this.GetWord(this._word2, this.Template2);
|
||||||
|
private string? Conjunction => this.Pack?.Conjunctions?.Get(this._conj);
|
||||||
|
|
||||||
|
private string? GetWord((int, int) word, Template? template) {
|
||||||
|
if (word.Item2 == -1) {
|
||||||
|
return Placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.Words == null) {
|
||||||
|
if (word.Item1 == -1) {
|
||||||
|
return Placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pack = this.Pack;
|
||||||
|
if (pack?.Words == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pack.Words.Get(word.Item1)?.Words.Get(word.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.Words.Get(word.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
private List<IDalamudTextureWrap> GlyphImages { get; } = [];
|
private List<IDalamudTextureWrap> GlyphImages { get; } = [];
|
||||||
|
|
||||||
private void LoadSignImages() {
|
private void LoadSignImages() {
|
||||||
@ -69,6 +103,8 @@ internal class Write : ITab {
|
|||||||
|
|
||||||
var packPrev = Pack.All[this._pack].Name;
|
var packPrev = Pack.All[this._pack].Name;
|
||||||
if (ImGui.BeginCombo("Pack", packPrev)) {
|
if (ImGui.BeginCombo("Pack", packPrev)) {
|
||||||
|
using var endCombo = new OnDispose(ImGui.EndCombo);
|
||||||
|
|
||||||
for (var i = 0; i < Pack.All.Length; i++) {
|
for (var i = 0; i < Pack.All.Length; i++) {
|
||||||
var selPack = Pack.All[i];
|
var selPack = Pack.All[i];
|
||||||
if (!ImGui.Selectable(selPack.Name)) {
|
if (!ImGui.Selectable(selPack.Name)) {
|
||||||
@ -78,8 +114,6 @@ internal class Write : ITab {
|
|||||||
this._pack = i;
|
this._pack = i;
|
||||||
this.ResetWriter();
|
this.ResetWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const string placeholder = "****";
|
const string placeholder = "****";
|
||||||
@ -90,6 +124,8 @@ internal class Write : ITab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var endCombo = new OnDispose(ImGui.EndCombo);
|
||||||
|
|
||||||
if (ImGui.Selectable("<none>")) {
|
if (ImGui.Selectable("<none>")) {
|
||||||
x = -1;
|
x = -1;
|
||||||
}
|
}
|
||||||
@ -100,11 +136,22 @@ internal class Write : ITab {
|
|||||||
x = i;
|
x = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawSpecificWordPicker(string id, WordListTemplate template, ref (int, int) x) {
|
void DrawTemplatePicker(string id, IReadOnlyList<string> items, ref int x, ref (int, int) word) {
|
||||||
|
var wasAdvanced = this.Pack?.Templates[x].Words != null;
|
||||||
|
if (wasAdvanced) {
|
||||||
|
word = (-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawPicker(id, items, ref x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawSpecificWordPicker(string id, Template template, ref (int, int) x) {
|
||||||
|
if (template.Words == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var preview = x == (-1, -1) ? "" : template.Words[x.Item2];
|
var preview = x == (-1, -1) ? "" : template.Words[x.Item2];
|
||||||
if (!ImGui.BeginCombo(id, preview)) {
|
if (!ImGui.BeginCombo(id, preview)) {
|
||||||
return;
|
return;
|
||||||
@ -125,22 +172,22 @@ internal class Write : ITab {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var endCombo = new OnDispose(ImGui.EndCombo);
|
||||||
|
|
||||||
for (var listIdx = 0; listIdx < words.Count; listIdx++) {
|
for (var listIdx = 0; listIdx < words.Count; listIdx++) {
|
||||||
var list = words[listIdx];
|
var list = words[listIdx];
|
||||||
if (!ImGui.BeginMenu(list.Name)) {
|
if (!ImGui.BeginMenu(list.Name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var endMenu = new OnDispose(ImGui.EndMenu);
|
||||||
|
|
||||||
for (var wordIdx = 0; wordIdx < list.Words.Length; wordIdx++) {
|
for (var wordIdx = 0; wordIdx < list.Words.Length; wordIdx++) {
|
||||||
if (ImGui.MenuItem(list.Words[wordIdx])) {
|
if (ImGui.MenuItem(list.Words[wordIdx])) {
|
||||||
x = (listIdx, wordIdx);
|
x = (listIdx, wordIdx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndMenu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var pack = Pack.All[this._pack];
|
var pack = Pack.All[this._pack];
|
||||||
@ -151,6 +198,8 @@ internal class Write : ITab {
|
|||||||
var actualText = string.Empty;
|
var actualText = string.Empty;
|
||||||
|
|
||||||
if (ImGui.BeginTable("##message-preview", 2)) {
|
if (ImGui.BeginTable("##message-preview", 2)) {
|
||||||
|
using var endTable = new OnDispose(ImGui.EndTable);
|
||||||
|
|
||||||
ImGui.TableSetupColumn("##image", ImGuiTableColumnFlags.WidthFixed);
|
ImGui.TableSetupColumn("##image", ImGuiTableColumnFlags.WidthFixed);
|
||||||
ImGui.TableSetupColumn("##message", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("##message", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
@ -163,14 +212,13 @@ internal class Write : ITab {
|
|||||||
if (ImGui.TableSetColumnIndex(1) && this._part1 != -1) {
|
if (ImGui.TableSetColumnIndex(1) && this._part1 != -1) {
|
||||||
var preview = new StringBuilder();
|
var preview = new StringBuilder();
|
||||||
|
|
||||||
var template1 = pack.Templates[this._part1];
|
var word1 = this.Word1;
|
||||||
var wordList1 = template1.Words ?? pack.Words?[this._word1.Item1].Words;
|
if (this.Template1 is { } template1Preview && word1 != null) {
|
||||||
var word1 = this._word1 == (-1, -1) ? placeholder : wordList1?[this._word1.Item2];
|
preview.Append(string.Format(template1Preview.Text, word1));
|
||||||
preview.Append(string.Format(template1.Template, word1));
|
}
|
||||||
|
|
||||||
if (this._conj != -1) {
|
if (this.Conjunction is { } conj) {
|
||||||
var conj = pack.Conjunctions?[this._conj];
|
var isPunc = conj.Length == 1 && char.IsPunctuation(conj[0]);
|
||||||
var isPunc = conj?.Length == 1 && char.IsPunctuation(conj[0]);
|
|
||||||
if (isPunc) {
|
if (isPunc) {
|
||||||
preview.Append(conj);
|
preview.Append(conj);
|
||||||
preview.Append('\n');
|
preview.Append('\n');
|
||||||
@ -180,11 +228,9 @@ internal class Write : ITab {
|
|||||||
preview.Append(' ');
|
preview.Append(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._part2 != -1) {
|
var word2 = this.Word2;
|
||||||
var template2 = pack.Templates[this._part2];
|
if (this.Template2 is { } template2Preview && word2 != null) {
|
||||||
var wordList2 = template2.Words ?? pack.Words?[this._word2.Item1].Words;
|
preview.Append(string.Format(template2Preview.Text, word2));
|
||||||
var word2 = this._word2 == (-1, -1) ? placeholder : wordList2?[this._word2.Item2];
|
|
||||||
preview.Append(string.Format(template2.Template, word2));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,30 +239,20 @@ internal class Write : ITab {
|
|||||||
ImGui.Dummy(new Vector2(1, imageHeight / 2 - actualSize.Y / 2 - ImGui.GetStyle().ItemSpacing.Y));
|
ImGui.Dummy(new Vector2(1, imageHeight / 2 - actualSize.Y / 2 - ImGui.GetStyle().ItemSpacing.Y));
|
||||||
ImGui.TextUnformatted(actualText);
|
ImGui.TextUnformatted(actualText);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
var templateStrings = pack.Templates
|
var templateStrings = pack.Templates
|
||||||
.Select(template => template.Template)
|
.Select(template => template.Text)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
DrawPicker("Template##part-1", templateStrings, ref this._part1);
|
DrawTemplatePicker("Template##part-1", templateStrings, ref this._part1, ref this._word1);
|
||||||
if (this._part1 > -1 && pack.Templates[this._part1].Template.Contains("{0}")) {
|
if (this.Template1 is { } template1 && template1.Text.Contains("{0}")) {
|
||||||
switch (pack.Templates[this._part1]) {
|
if (template1.Words == null && pack.Words != null) {
|
||||||
case BasicTemplate basic: {
|
|
||||||
if (pack.Words != null) {
|
|
||||||
DrawWordPicker("Word##word-1", pack.Words, ref this._word1);
|
DrawWordPicker("Word##word-1", pack.Words, ref this._word1);
|
||||||
}
|
} else if (template1.Words != null) {
|
||||||
|
DrawSpecificWordPicker("Word##word-1", template1, ref this._word1);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case WordListTemplate wordListTemplate: {
|
|
||||||
DrawSpecificWordPicker("Word##word-1", wordListTemplate, ref this._word1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,25 +261,18 @@ internal class Write : ITab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._conj != -1) {
|
if (this._conj != -1) {
|
||||||
DrawPicker("Template##part-2", templateStrings, ref this._part2);
|
DrawTemplatePicker("Template##part-2", templateStrings, ref this._part2, ref this._word2);
|
||||||
if (this._part2 > -1 && pack.Templates[this._part2].Template.Contains("{0}")) {
|
if (this.Template1 is { } template2 && template2.Text.Contains("{0}")) {
|
||||||
switch (pack.Templates[this._part2]) {
|
if (template2.Words == null && pack.Words != null) {
|
||||||
case BasicTemplate basic: {
|
|
||||||
if (pack.Words != null) {
|
|
||||||
DrawWordPicker("Word##word-2", pack.Words, ref this._word2);
|
DrawWordPicker("Word##word-2", pack.Words, ref this._word2);
|
||||||
}
|
} else if (template2.Words != null) {
|
||||||
|
DrawSpecificWordPicker("Word##word-2", template2, ref this._word2);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case WordListTemplate wordListTemplate: {
|
|
||||||
DrawSpecificWordPicker("Word##word-2", wordListTemplate, ref this._word2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginCombo("Glyph", $"{this._glyph + 1}")) {
|
if (ImGui.BeginCombo("Glyph", $"{this._glyph + 1}")) {
|
||||||
|
using var endCombo = new OnDispose(ImGui.EndCombo);
|
||||||
var tooltipShown = false;
|
var tooltipShown = false;
|
||||||
|
|
||||||
for (var i = 0; i < Messages.VfxPaths.Length; i++) {
|
for (var i = 0; i < Messages.VfxPaths.Length; i++) {
|
||||||
@ -256,13 +285,11 @@ internal class Write : ITab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
|
using var endTooltip = new OnDispose(ImGui.EndTooltip);
|
||||||
var image = this.GlyphImages[i];
|
var image = this.GlyphImages[i];
|
||||||
ImGui.Image(image.ImGuiHandle, new Vector2(imageHeight));
|
ImGui.Image(image.ImGuiHandle, new Vector2(imageHeight));
|
||||||
ImGui.EndTooltip();
|
|
||||||
tooltipShown = true;
|
tooltipShown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ClearIfNecessary();
|
this.ClearIfNecessary();
|
||||||
@ -348,7 +375,7 @@ internal class Write : ITab {
|
|||||||
|
|
||||||
var pack = Pack.All[this._pack];
|
var pack = Pack.All[this._pack];
|
||||||
|
|
||||||
if (this._part1 == -1 || !pack.Templates[this._part1].Template.Contains("{0}")) {
|
if (this._part1 == -1 || !pack.Templates[this._part1].Text.Contains("{0}")) {
|
||||||
this._word1 = (-1, -1);
|
this._word1 = (-1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +383,7 @@ internal class Write : ITab {
|
|||||||
this._part2 = -1;
|
this._part2 = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._part2 == -1 || !pack.Templates[this._part2].Template.Contains("{0}")) {
|
if (this._part2 == -1 || !pack.Templates[this._part2].Text.Contains("{0}")) {
|
||||||
this._word2 = (-1, -1);
|
this._word2 = (-1, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,7 +395,7 @@ internal class Write : ITab {
|
|||||||
|
|
||||||
var pack = Pack.All[this._pack];
|
var pack = Pack.All[this._pack];
|
||||||
var template1 = pack.Templates[this._part1];
|
var template1 = pack.Templates[this._part1];
|
||||||
var temp1Variable = template1.Template.Contains("{0}");
|
var temp1Variable = template1.Text.Contains("{0}");
|
||||||
|
|
||||||
switch (temp1Variable) {
|
switch (temp1Variable) {
|
||||||
case true when this._word1 == (-1, -1):
|
case true when this._word1 == (-1, -1):
|
||||||
@ -386,7 +413,7 @@ internal class Write : ITab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var template2 = pack.Templates[this._part2];
|
var template2 = pack.Templates[this._part2];
|
||||||
var temp2Variable = template2.Template.Contains("{0}");
|
var temp2Variable = template2.Text.Contains("{0}");
|
||||||
|
|
||||||
switch (temp2Variable) {
|
switch (temp2Variable) {
|
||||||
case true when this._word2 == (-1, -1):
|
case true when this._word2 == (-1, -1):
|
||||||
|
11
client/Util/IReadOnlyListExt.cs
Normal file
11
client/Util/IReadOnlyListExt.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace OrangeGuidanceTomestone.Util;
|
||||||
|
|
||||||
|
internal static class IReadOnlyListExt {
|
||||||
|
internal static T? Get<T>(this IReadOnlyList<T> list, int idx) {
|
||||||
|
if (idx < 0 || idx >= list.Count) {
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list[idx];
|
||||||
|
}
|
||||||
|
}
|
19
client/Util/OnDispose.cs
Normal file
19
client/Util/OnDispose.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace OrangeGuidanceTomestone.Util;
|
||||||
|
|
||||||
|
internal class OnDispose : IDisposable {
|
||||||
|
private bool _disposed;
|
||||||
|
private readonly Action _action;
|
||||||
|
|
||||||
|
internal OnDispose(Action action) {
|
||||||
|
this._action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
if (this._disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposed = true;
|
||||||
|
this._action();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user