mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-19 14:55:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86e3579383 | ||
|
|
ea39346678 | ||
|
|
e4cdc4f939 | ||
|
|
5acac1ae7a | ||
|
|
8c0e734c71 | ||
|
|
a56eb20aa0 | ||
|
|
482854801c | ||
|
|
1a37b8b08f | ||
|
|
40d8280ba6 | ||
|
|
9533ef0607 | ||
|
|
ac963f60b5 | ||
|
|
5b6ea99f79 |
@@ -12,6 +12,10 @@ on:
|
||||
paths:
|
||||
- "Daybreak/**"
|
||||
- "Daybreak.Installer/**"
|
||||
- "Daybreak.API/**"
|
||||
- "Daybreak.7ZipExtractor/**"
|
||||
- "Daybreak.Installer/**"
|
||||
- "Daybreak.Shared/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -233,7 +233,7 @@ public sealed class PartyService(
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogDebug("Spawning {heroCount} heroes for party loadout", partyLoadout.Entries.AsValueEnumerable().Count(c => c.HeroId != 0));
|
||||
foreach (var entry in partyLoadout.Entries)
|
||||
foreach (var entry in partyLoadout.Entries.AsValueEnumerable().OrderBy(e => e.Build.Primary))
|
||||
{
|
||||
if (entry.HeroId != 0 &&
|
||||
Hero.TryParse(entry.HeroId, out var hero))
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
|
||||
using System.Extensions.Core;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Daybreak.Shared.Services.Api;
|
||||
public sealed class ScopedApiContext(
|
||||
@@ -74,7 +75,8 @@ public sealed class ScopedApiContext(
|
||||
|
||||
public async Task<bool> PostMainPlayerBuild(string code, CancellationToken cancellationToken)
|
||||
{
|
||||
var path = PostMainPlayerBuildPath.Replace(CodePlaceholder, code);
|
||||
var encodedBuildCode = UrlEncoder.Default.Encode(code);
|
||||
var path = PostMainPlayerBuildPath.Replace(CodePlaceholder, encodedBuildCode);
|
||||
using var emptyContent = new StringContent(string.Empty);
|
||||
return await this.Post(path, request => emptyContent, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -204,15 +204,6 @@ public sealed class BuildTemplateManager(
|
||||
return (build, compositionEntry, index);
|
||||
});
|
||||
|
||||
var lockedSkill = loadoutEntryComposition
|
||||
.Select(entry => entry.build.Skills.FirstOrDefault(s => s != Skill.NoSkill && !IsSkillUnlocked(s.Id, mainPlayerBuildContext.UnlockedAccountSkills)))
|
||||
.OfType<Skill>()
|
||||
.FirstOrDefault();
|
||||
if (lockedSkill is not null)
|
||||
{
|
||||
scopedLogger.LogDebug("Invalid team build entry {buildName}. Skill {skillName} is not unlocked by the current account", teamBuildEntry.Name ?? string.Empty, lockedSkill.Name);
|
||||
}
|
||||
|
||||
return loadoutEntryComposition.Any(entry => entry.compositionEntry.Type is PartyCompositionMemberType.MainPlayer && this.CanApply(mainPlayerBuildContext, entry.build));
|
||||
}
|
||||
|
||||
@@ -232,12 +223,6 @@ public sealed class BuildTemplateManager(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (singleBuildEntry.Skills.FirstOrDefault(s => s != Skill.NoSkill && !IsSkillUnlocked(s.Id, mainPlayerBuildContext.UnlockedCharacterSkills)) is Skill lockedSkill)
|
||||
{
|
||||
scopedLogger.LogDebug("Invalid build entry {buildName}. Skill {skillName} is not unlocked by the current character", singleBuildEntry.Name ?? string.Empty, lockedSkill.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -257,16 +242,6 @@ public sealed class BuildTemplateManager(
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach(var skill in request.BuildSkills)
|
||||
{
|
||||
if (skill is not 0 &&
|
||||
!IsSkillUnlocked((int)skill, request.UnlockedSkills))
|
||||
{
|
||||
scopedLogger.LogError("Skill {skillId} is not unlocked", skill);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -583,10 +558,17 @@ public sealed class BuildTemplateManager(
|
||||
}
|
||||
else
|
||||
{
|
||||
build.Metadata =
|
||||
JsonConvert.DeserializeObject<Dictionary<string, string>>(
|
||||
Encoding.UTF8.GetString(
|
||||
Convert.FromBase64String(content[1])));
|
||||
try
|
||||
{
|
||||
build.Metadata =
|
||||
JsonConvert.DeserializeObject<Dictionary<string, string>>(
|
||||
Encoding.UTF8.GetString(
|
||||
Convert.FromBase64String(content[1])));
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return new InvalidOperationException("Failed to parse build metadata", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,16 +854,17 @@ public sealed class BuildTemplateManager(
|
||||
|
||||
private static bool IsProfessionUnlocked(int professionId, uint unlockedProfessions) => (unlockedProfessions & (1 << professionId)) != 0;
|
||||
|
||||
private static bool IsSkillUnlocked(int skillId, uint[] unlockedSkills)
|
||||
{
|
||||
var realIndex = skillId / 32;
|
||||
if (realIndex >= unlockedSkills.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Not using anymore. Skills are marked as locked if they are not part of the primary/secondary of the current character. Cannot rely on this for build viability checks.
|
||||
//private static bool IsSkillUnlocked(int skillId, uint[] unlockedSkills)
|
||||
//{
|
||||
// var realIndex = skillId / 32;
|
||||
// if (realIndex >= unlockedSkills.Length)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
var shift = skillId % 32;
|
||||
var flag = 1U << shift;
|
||||
return (unlockedSkills[realIndex] & flag) != 0;
|
||||
}
|
||||
// var shift = skillId % 32;
|
||||
// var flag = 1U << shift;
|
||||
// return (unlockedSkills[realIndex] & flag) != 0;
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Daybreak.Shared.Models.Metrics;
|
||||
using Daybreak.Shared.Models.Trade;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Kernel;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using System.Windows.Extensions.Services;
|
||||
|
||||
@@ -19,14 +16,6 @@ internal sealed class LiveChartInitializer : ILiveChartInitializer, IApplication
|
||||
config.AddSkiaSharp()
|
||||
.AddDefaultMappers()
|
||||
.AddDarkTheme()
|
||||
.AddLightTheme()
|
||||
.HasMap<Metric>((metric, index) =>
|
||||
{
|
||||
return new Coordinate(metric.Timestamp.Ticks, Convert.ToDouble(metric.Measurement));
|
||||
})
|
||||
.HasMap<TraderQuote>((quote, point) =>
|
||||
{
|
||||
return new Coordinate(quote.Timestamp?.Ticks ?? 0, ((double)quote.Price) / 20d);
|
||||
}));
|
||||
.AddLightTheme());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Daybreak.Services.Guildwars.Models;
|
||||
using Daybreak.Services.Guildwars.Utils;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Core.Extensions;
|
||||
using System.IO;
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ using Daybreak.Shared.Models.Notifications;
|
||||
using Daybreak.Shared.Models.Notifications.Handling;
|
||||
using Daybreak.Shared.Services.Notifications;
|
||||
using Daybreak.Shared.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Slim;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using System.Extensions.Core;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Daybreak.Services.Notifications;
|
||||
@@ -72,18 +74,26 @@ internal sealed class NotificationService(
|
||||
{
|
||||
if (storeNotification)
|
||||
{
|
||||
await this.storage.OpenNotification(new NotificationDTO
|
||||
try
|
||||
{
|
||||
Title = notification.Title,
|
||||
Description = notification.Description,
|
||||
Id = notification.Id,
|
||||
Level = (int)notification.Level,
|
||||
MetaData = notification.Metadata,
|
||||
HandlerType = notification.HandlingType?.AssemblyQualifiedName,
|
||||
ExpirationTime = notification.ExpirationTime.ToSafeDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
CreationTime = notification.CreationTime.ToSafeDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
Closed = true
|
||||
}, cancellationToken);
|
||||
await this.storage.OpenNotification(new NotificationDTO
|
||||
{
|
||||
Title = notification.Title,
|
||||
Description = notification.Description,
|
||||
Id = notification.Id,
|
||||
Level = (int)notification.Level,
|
||||
MetaData = notification.Metadata,
|
||||
HandlerType = notification.HandlingType?.AssemblyQualifiedName,
|
||||
ExpirationTime = notification.ExpirationTime.ToSafeDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
CreationTime = notification.CreationTime.ToSafeDateTimeOffset().ToUnixTimeMilliseconds(),
|
||||
Closed = true
|
||||
}, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to open notification");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.HandlingType is null)
|
||||
@@ -91,8 +101,8 @@ internal sealed class NotificationService(
|
||||
return;
|
||||
}
|
||||
|
||||
var handler = this.serviceManager.GetService(notification.HandlingType) as INotificationHandler;
|
||||
handler?.OpenNotification(notification);
|
||||
var handler = (INotificationHandler)this.serviceManager.GetRequiredService(notification.HandlingType);
|
||||
handler.OpenNotification(notification);
|
||||
}
|
||||
|
||||
async Task INotificationProducer.RemoveNotification(Notification notification, CancellationToken cancellationToken)
|
||||
|
||||
@@ -1,53 +1,105 @@
|
||||
using Daybreak.Services.Notifications.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions.Core;
|
||||
|
||||
namespace Daybreak.Services.Notifications;
|
||||
|
||||
internal sealed class NotificationStorage(
|
||||
NotificationsDbContext liteCollection) : INotificationStorage
|
||||
NotificationsDbContext liteCollection,
|
||||
ILogger<NotificationStorage> logger) : INotificationStorage
|
||||
{
|
||||
private List<NotificationDTO>? notificationsCache;
|
||||
private readonly NotificationsDbContext liteCollection = liteCollection.ThrowIfNull();
|
||||
private readonly ILogger<NotificationStorage> logger = logger.ThrowIfNull();
|
||||
|
||||
public async ValueTask<IEnumerable<NotificationDTO>> GetPendingNotifications(CancellationToken cancellationToken)
|
||||
{
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
return this.notificationsCache
|
||||
.Where(dto => dto.Closed == false && dto.ExpirationTime < DateTimeOffset.Now.ToUnixTimeMilliseconds());
|
||||
try
|
||||
{
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
return this.notificationsCache
|
||||
.Where(dto => dto.Closed == false && dto.ExpirationTime < DateTimeOffset.Now.ToUnixTimeMilliseconds());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to get pending notifications");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<IEnumerable<NotificationDTO>> GetNotifications(CancellationToken cancellationToken)
|
||||
{
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
return this.notificationsCache;
|
||||
try
|
||||
{
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
return this.notificationsCache;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to get notifications");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask StoreNotification(NotificationDTO notification, CancellationToken cancellationToken)
|
||||
{
|
||||
notification.ThrowIfNull();
|
||||
await this.liteCollection.Insert(notification, cancellationToken);
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
this.notificationsCache.Add(notification);
|
||||
try
|
||||
{
|
||||
await this.liteCollection.Insert(notification, cancellationToken);
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
this.notificationsCache.Add(notification);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to store notification");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask OpenNotification(NotificationDTO notificationDTO, CancellationToken cancellationToken)
|
||||
{
|
||||
notificationDTO.ThrowIfNull();
|
||||
notificationDTO.Closed = true;
|
||||
await this.liteCollection.Update(notificationDTO, cancellationToken);
|
||||
this.notificationsCache = await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
notificationDTO.Closed = true;
|
||||
await this.liteCollection.Update(notificationDTO, cancellationToken);
|
||||
this.notificationsCache = await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to open notification");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RemoveNotification(NotificationDTO notificationDTO, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.liteCollection.Delete(notificationDTO.Id, cancellationToken);
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
this.notificationsCache.Remove(notificationDTO);
|
||||
try
|
||||
{
|
||||
await this.liteCollection.Delete(notificationDTO.Id, cancellationToken);
|
||||
this.notificationsCache ??= await this.liteCollection.FindAll(cancellationToken).ToListAsync(cancellationToken);
|
||||
this.notificationsCache.Remove(notificationDTO);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to remove notification");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RemoveAllNotifications(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.liteCollection.DeleteAll(cancellationToken);
|
||||
this.notificationsCache = default;
|
||||
try
|
||||
{
|
||||
await this.liteCollection.DeleteAll(cancellationToken);
|
||||
this.notificationsCache = default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.CreateScopedLogger().LogError(ex, "Failed to remove all notifications");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,15 +114,6 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 10
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.GreenHillsCounty,
|
||||
Url = "https://media.discordapp.net/attachments/279231165045407744/1053453780580044940/image.png?ex=656a6c49&is=6557f749&hm=beab3433db2cac2d519fc8158978a949e06a57df50b587f41afe3718dc4dad20&=&format=webp&width=954&height=521",
|
||||
Credit = "",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
}
|
||||
]);
|
||||
public static readonly Location Ascalon = new(
|
||||
@@ -262,15 +253,6 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.TheGreatNorthernWall,
|
||||
Url = "https://media.discordapp.net/attachments/279231165045407744/853784390357876746/gw005.jpg?ex=656d152d&is=655aa02d&hm=03e8464e9fc60f8c832eebc246a4fff422ebb865d27dc189b727f22aacab6fb2&=&format=webp&width=954&height=380",
|
||||
Credit = "https://discordapp.com/users/Chrono#6655",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
}
|
||||
]);
|
||||
public static readonly Location NorthernShiverpeaks = new(
|
||||
@@ -365,15 +347,6 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 7
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.IronHorseMine,
|
||||
Url = "https://media.discordapp.net/attachments/279231165045407744/927936695230414888/gw064.jpg?ex=656f3864&is=655cc364&hm=75456e75a3a77e0dcbc8b0f1d3dfa292e477b535e90c7a8f40566e0ffa52cb64&=&format=webp&width=903&height=564",
|
||||
Credit = "https://discordapp.com/users/Pekka#4619",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
}
|
||||
]);
|
||||
public static readonly Location Kryta = new(
|
||||
@@ -1025,15 +998,6 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 60
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.TombOfThePrimevalKings,
|
||||
Url = "https://cdn.discordapp.com/attachments/279231165045407744/1137832214151843930/gw079.jpg?ex=656cd953&is=655a6453&hm=af57440037882d84e959b950945b738e611d976a2f0beb24d9b1a0fcd626086d&",
|
||||
Credit = "https://discordapp.com/users/Soldrand#2252",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
}
|
||||
]);
|
||||
public static readonly Location SouthernShiverpeaks = new(
|
||||
@@ -1264,25 +1228,7 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.TalusChute,
|
||||
Url = "https://cdn.discordapp.com/attachments/279231165045407744/1079051027753480232/gw500.jpg?ex=656b4294&is=6558cd94&hm=95afe18ecddc81d5cedc9865e5db76986dc4ee2fe34f4832ffcc35854298f371&",
|
||||
Credit = "https://discordapp.com/users/miragee#4827",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.TalusChute,
|
||||
Url = "https://cdn.discordapp.com/attachments/279231165045407744/1079051028000948345/gw507.jpg?ex=656b4294&is=6558cd94&hm=9e85f4560d8184fbb3a575515a72bdfb1d5812a64b57e486acb9f2a1ce658b4a&",
|
||||
Credit = "https://discordapp.com/users/miragee#4827",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
},
|
||||
}
|
||||
]);
|
||||
public static readonly Location RingOfFireIslandChain = new(
|
||||
Region.RingOfFireIslands,
|
||||
@@ -2484,15 +2430,6 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 2
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.SunquaVale,
|
||||
Url = "https://media.discordapp.net/attachments/279231165045407744/859817527739547678/gw010.jpg?ex=657092f9&is=655e1df9&hm=b77dd201214212cfc6dd68db2218f550136878f6a2dca37830cc0dcad4201363&=&format=webp&width=954&height=479",
|
||||
Credit = "https://discordapp.com/users/Sara#2170",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 1
|
||||
}
|
||||
]);
|
||||
public static readonly Location KainengCity = new(
|
||||
@@ -4204,15 +4141,6 @@ internal sealed class Location
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 5
|
||||
},
|
||||
new Entry
|
||||
{
|
||||
Map = Map.TheSulfurousWastes,
|
||||
Url = "https://media.discordapp.net/attachments/279231165045407744/1037396920001372240/unknown.png?ex=65709bab&is=655e26ab&hm=8329f7f07e635c9493c12f53054d090908b8643d0932313d49f2b2ff79601a36&=&format=webp",
|
||||
Credit = "https://discordapp.com/users/Planewalker#5903",
|
||||
IdFormat = "D2",
|
||||
StartIndex = 1,
|
||||
Count = 5
|
||||
}
|
||||
]);
|
||||
public static readonly Location GateOfTorment = new(
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Core.Extensions;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.Extensions.Core;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
@@ -68,6 +69,7 @@ internal sealed class ApplicationUpdater(
|
||||
|
||||
public async Task<bool> DownloadUpdate(Version version, UpdateStatus updateStatus)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
|
||||
if (version.HasPrefix is false)
|
||||
{
|
||||
version.HasPrefix = true;
|
||||
@@ -78,21 +80,29 @@ internal sealed class ApplicationUpdater(
|
||||
return await this.DownloadUpdateInternalLegacy(version, updateStatus);
|
||||
}
|
||||
|
||||
var maybeMetadataResponse = await this.httpClient.GetAsync(
|
||||
try
|
||||
{
|
||||
var maybeMetadataResponse = await this.httpClient.GetAsync(
|
||||
BlobStorageUrl
|
||||
.Replace(VersionTag, version.ToString().Replace(".", "-"))
|
||||
.Replace(FileTag, "Metadata.json"));
|
||||
if (maybeMetadataResponse.IsSuccessStatusCode)
|
||||
{
|
||||
var metaData = await maybeMetadataResponse.Content.ReadFromJsonAsync<List<Metadata>>();
|
||||
if (metaData is not null)
|
||||
if (maybeMetadataResponse.IsSuccessStatusCode)
|
||||
{
|
||||
return await
|
||||
await new TaskFactory().StartNew(() => this.DownloadUpdateInternalBlob(metaData, version, updateStatus), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
var metaData = await maybeMetadataResponse.Content.ReadFromJsonAsync<List<Metadata>>();
|
||||
if (metaData is not null)
|
||||
{
|
||||
return await
|
||||
await new TaskFactory().StartNew(() => this.DownloadUpdateInternalBlob(metaData, version, updateStatus), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await this.DownloadUpdateInternalLegacy(version, updateStatus);
|
||||
return await this.DownloadUpdateInternalLegacy(version, updateStatus);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
scopedLogger.LogError(e, "Failed to download update for version {version}", version);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadLatestUpdate(UpdateStatus updateStatus)
|
||||
@@ -122,31 +132,52 @@ internal sealed class ApplicationUpdater(
|
||||
|
||||
public async Task<IEnumerable<Version>> GetVersions()
|
||||
{
|
||||
this.logger.LogDebug($"Retrieving version list from {VersionListUrl}");
|
||||
var response = await this.httpClient.GetAsync(VersionListUrl);
|
||||
if (response.IsSuccessStatusCode)
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogDebug($"Retrieving version list from {VersionListUrl}");
|
||||
try
|
||||
{
|
||||
var serializedList = await response.Content.ReadAsStringAsync();
|
||||
var versionList = serializedList.Deserialize<GithubRefTag[]>();
|
||||
return versionList!.Select(v => v.Ref![RefTagPrefix.Length..]).Select(v => new Version(v));
|
||||
}
|
||||
var response = await this.httpClient.GetAsync(VersionListUrl);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var serializedList = await response.Content.ReadAsStringAsync();
|
||||
var versionList = serializedList.Deserialize<GithubRefTag[]>();
|
||||
return versionList!.Select(v => v.Ref![RefTagPrefix.Length..]).Select(v => new Version(v));
|
||||
}
|
||||
|
||||
return [];
|
||||
scopedLogger.LogError("Failed to retrieve version list. Status code: {statusCode}", response.StatusCode);
|
||||
return [];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
scopedLogger.LogError(e, "Failed to retrieve version list from {url}", VersionListUrl);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> GetChangelog(Version version)
|
||||
{
|
||||
var changeLogResponse = await this.httpClient.GetAsync(
|
||||
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
|
||||
try
|
||||
{
|
||||
var changeLogResponse = await this.httpClient.GetAsync(
|
||||
BlobStorageUrl
|
||||
.Replace(VersionTag, version.ToString().Replace(".", "-"))
|
||||
.Replace(FileTag, "changelog.txt"));
|
||||
|
||||
if (!changeLogResponse.IsSuccessStatusCode)
|
||||
if (!changeLogResponse.IsSuccessStatusCode)
|
||||
{
|
||||
scopedLogger.LogError("Failed to retrieve changelog for version {version}. Status code: {statusCode}", version, changeLogResponse.StatusCode);
|
||||
return default;
|
||||
}
|
||||
|
||||
scopedLogger.LogDebug("Retrieved changelog for version {version}", version);
|
||||
return await changeLogResponse.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
scopedLogger.LogError(e, "Failed to retrieve changelog for version {version}", version);
|
||||
return default;
|
||||
}
|
||||
|
||||
return await changeLogResponse.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
public void PeriodicallyCheckForUpdates()
|
||||
@@ -198,7 +229,7 @@ internal sealed class ApplicationUpdater(
|
||||
|
||||
private async Task<bool> DownloadUpdateInternalBlob(List<Metadata> metadata, Version version, UpdateStatus updateStatus)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.DownloadUpdateInternalBlob), version.ToString());
|
||||
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.ToString());
|
||||
updateStatus.CurrentStep = DownloadStatus.InitializingDownload;
|
||||
|
||||
// Exclude daybreak packed files
|
||||
@@ -341,34 +372,46 @@ internal sealed class ApplicationUpdater(
|
||||
|
||||
private async Task<bool> DownloadUpdateInternalLegacy(Version version, UpdateStatus updateStatus)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(flowIdentifier: version.VersionString);
|
||||
updateStatus.CurrentStep = DownloadStatus.InitializingDownload;
|
||||
var uri = DownloadUrl.Replace(VersionTag, version.ToString());
|
||||
if (await this.downloadService.DownloadFile(uri, TempFile, updateStatus) is false)
|
||||
{
|
||||
this.logger.LogError("Failed to download update file");
|
||||
scopedLogger.LogError("Failed to download update file");
|
||||
return false;
|
||||
}
|
||||
|
||||
updateStatus.CurrentStep = UpdateStatus.PendingRestart;
|
||||
this.logger.LogDebug("Downloaded update file");
|
||||
scopedLogger.LogDebug("Downloaded update file");
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<Version?> GetLatestVersion()
|
||||
{
|
||||
using var response = await this.httpClient.GetAsync(Url);
|
||||
if (response.IsSuccessStatusCode)
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
try
|
||||
{
|
||||
var versionTag = response.RequestMessage!.RequestUri!.ToString().Split('/').Last().TrimStart('v');
|
||||
if (Version.TryParse(versionTag, out var parsedVersion))
|
||||
using var response = await this.httpClient.GetAsync(Url);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return parsedVersion;
|
||||
var versionTag = response.RequestMessage!.RequestUri!.ToString().Split('/').Last().TrimStart('v');
|
||||
if (Version.TryParse(versionTag, out var parsedVersion))
|
||||
{
|
||||
return parsedVersion;
|
||||
}
|
||||
|
||||
scopedLogger.LogError("Failed to parse version from {versionTag}", versionTag);
|
||||
return default;
|
||||
}
|
||||
|
||||
scopedLogger.LogError("Failed to retrieve latest version. Status code: {statusCode}", response.StatusCode);
|
||||
return default;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
scopedLogger.LogError(e, "Failed to retrieve latest version from {url}", Url);
|
||||
return default;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private void LaunchExtractor()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Daybreak.Shared.Models.Metrics;
|
||||
using Daybreak.Shared.Services.Metrics;
|
||||
using LiveChartsCore.Defaults;
|
||||
using LiveChartsCore.Kernel;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using LiveChartsCore.SkiaSharpView.VisualElements;
|
||||
@@ -91,6 +93,7 @@ public partial class MetricsView : UserControl
|
||||
private void CartesianChart_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var cartesianChart = sender.Cast<CartesianChart>();
|
||||
cartesianChart.CoreChart.Update(new ChartUpdateParams { IsAutomaticUpdate = false, Throttling = false });
|
||||
if (cartesianChart.DataContext is not MetricSetViewModel metricSet)
|
||||
{
|
||||
return;
|
||||
@@ -112,22 +115,11 @@ public partial class MetricsView : UserControl
|
||||
cartesianChart.DrawMargin = new LiveChartsCore.Measure.Margin(30);
|
||||
cartesianChart.XAxes =
|
||||
[
|
||||
new Axis
|
||||
new DateTimeAxis(TimeSpan.FromSeconds(1), dateTime => dateTime.ToString("d"))
|
||||
{
|
||||
Name = "Time",
|
||||
Labeler = (ticks) =>
|
||||
{
|
||||
return new DateTime((long)ticks).ToString("HH:mm:ss");
|
||||
},
|
||||
LabelsPaint = this.foregroundPaint,
|
||||
SeparatorsPaint = this.transparentPaint,
|
||||
CrosshairLabelsPaint = this.transparentPaint,
|
||||
CrosshairPaint = this.transparentPaint,
|
||||
NamePaint = this.foregroundPaint,
|
||||
SubseparatorsPaint = this.transparentPaint,
|
||||
SubticksPaint = this.transparentPaint,
|
||||
TicksPaint = this.transparentPaint,
|
||||
ZeroPaint = this.transparentPaint,
|
||||
}
|
||||
];
|
||||
|
||||
@@ -137,27 +129,20 @@ public partial class MetricsView : UserControl
|
||||
{
|
||||
Name = metricSet.Instrument.Unit,
|
||||
LabelsPaint = this.foregroundPaint,
|
||||
SeparatorsPaint = this.transparentPaint,
|
||||
CrosshairLabelsPaint = this.transparentPaint,
|
||||
CrosshairPaint = this.transparentPaint,
|
||||
NamePaint = this.foregroundPaint,
|
||||
SubseparatorsPaint = this.transparentPaint,
|
||||
SubticksPaint = this.transparentPaint,
|
||||
TicksPaint = this.transparentPaint,
|
||||
ZeroPaint = this.transparentPaint,
|
||||
}
|
||||
];
|
||||
|
||||
cartesianChart.Series =
|
||||
[
|
||||
new LineSeries<Metric>
|
||||
new LineSeries<DateTimePoint>
|
||||
{
|
||||
Values = metricSet.AggregationType switch
|
||||
{
|
||||
AggregationTypes.NoAggregate => PlotNoAggregation(metricSet.Metrics),
|
||||
AggregationTypes.P95 => PlotPercentageAggregation(metricSet.Metrics, 0.95),
|
||||
AggregationTypes.P98 => PlotPercentageAggregation(metricSet.Metrics, 0.98),
|
||||
AggregationTypes.P99 => PlotPercentageAggregation(metricSet.Metrics, 0.99),
|
||||
AggregationTypes.NoAggregate => ToDateTimePoint(PlotNoAggregation(metricSet.Metrics)),
|
||||
AggregationTypes.P95 => ToDateTimePoint(PlotPercentageAggregation(metricSet.Metrics, 0.95)),
|
||||
AggregationTypes.P98 => ToDateTimePoint(PlotPercentageAggregation(metricSet.Metrics, 0.98)),
|
||||
AggregationTypes.P99 => ToDateTimePoint(PlotPercentageAggregation(metricSet.Metrics, 0.99)),
|
||||
_ => throw new InvalidOperationException("Unable to plot metrics. Unknown aggregation")
|
||||
},
|
||||
Fill = default,
|
||||
@@ -198,4 +183,9 @@ public partial class MetricsView : UserControl
|
||||
|
||||
return [.. finalDataSet];
|
||||
}
|
||||
|
||||
private static List<DateTimePoint> ToDateTimePoint(IEnumerable<Metric> dataSet)
|
||||
{
|
||||
return [.. dataSet.Select(s => new DateTimePoint(s.Timestamp, System.Convert.ToDouble(s.Measurement)))];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,42 @@
|
||||
<UserControl x:Class="Daybreak.Views.Trade.PriceHistoryView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Views.Trade"
|
||||
xmlns:chart="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
|
||||
xmlns:lvCore="clr-namespace:LiveChartsCore;assembly=LiveChartsCore"
|
||||
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
|
||||
xmlns:converters="clr-namespace:Daybreak.Shared.Converters;assembly=Daybreak.Shared"
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
DataContextChanged="UserControl_DataContextChanged"
|
||||
x:Name="_this"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid
|
||||
Background="{StaticResource Daybreak.Brushes.Background}">
|
||||
<chart:CartesianChart Series="{Binding ElementName=_this, Path=Series, Mode=OneWay}"
|
||||
Title="{Binding ElementName=_this, Path=Title, Mode=OneWay}"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
ZoomMode="X"
|
||||
EasingFunction="{x:Static lvCore:EasingFunctions.BounceOut}"
|
||||
AnimationsSpeed="0:0:0.1"
|
||||
Background="{StaticResource MahApps.Brushes.ThemeBackground}"
|
||||
XAxes="{Binding ElementName=_this, Path=XAxes, Mode=OneWay}"
|
||||
YAxes="{Binding ElementName=_this, Path=YAxes, Mode=OneWay}">
|
||||
</chart:CartesianChart>
|
||||
<buttons:BackButton
|
||||
Height="30"
|
||||
<UserControl
|
||||
x:Class="Daybreak.Views.Trade.PriceHistoryView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
|
||||
xmlns:chart="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
xmlns:converters="clr-namespace:Daybreak.Shared.Converters;assembly=Daybreak.Shared"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Views.Trade"
|
||||
xmlns:lvCore="clr-namespace:LiveChartsCore;assembly=LiveChartsCore"
|
||||
xmlns:lvCoreMeasure="clr-namespace:LiveChartsCore.Measure;assembly=LiveChartsCore"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
DataContextChanged="UserControl_DataContextChanged"
|
||||
mc:Ignorable="d">
|
||||
<Grid Background="{StaticResource Daybreak.Brushes.Background}">
|
||||
<chart:CartesianChart
|
||||
Title="{Binding ElementName=_this, Path=Title, Mode=OneWay}"
|
||||
AnimationsSpeed="0:0:0.1"
|
||||
Background="{StaticResource MahApps.Brushes.ThemeBackground}"
|
||||
EasingFunction="{x:Static lvCore:EasingFunctions.BounceOut}"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
Series="{Binding ElementName=_this, Path=Series, Mode=OneWay}"
|
||||
XAxes="{Binding ElementName=_this, Path=XAxes, Mode=OneWay}"
|
||||
YAxes="{Binding ElementName=_this, Path=YAxes, Mode=OneWay}"
|
||||
ZoomMode="X" />
|
||||
<buttons:BackButton
|
||||
Width="30"
|
||||
Height="30"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Margin="5"
|
||||
Clicked="BackButton_Clicked"
|
||||
ToolTip="Go back to price quotes"/>
|
||||
<buttons:HighlightButton
|
||||
Height="30"
|
||||
Width="100"
|
||||
Title="Reset Zoom"
|
||||
Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
Background="{StaticResource Daybreak.Brushes.Background}"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="5"
|
||||
FontSize="16"
|
||||
HighlightBrush="{StaticResource MahApps.Brushes.Accent}"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
BorderBrush="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
BorderThickness="1"
|
||||
Clicked="HighlightButton_Clicked"
|
||||
ToolTip="Reset zoom"/>
|
||||
<Grid
|
||||
Visibility="{Binding ElementName=_this, Path=Loading, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Background="{StaticResource Daybreak.Brushes.Background}">
|
||||
<controls:CircularLoadingWidget
|
||||
MaxWidth="200"
|
||||
MaxHeight="200"/>
|
||||
ToolTip="Go back to price quotes" />
|
||||
<Grid Background="{StaticResource Daybreak.Brushes.Background}" Visibility="{Binding ElementName=_this, Path=Loading, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<controls:CircularLoadingWidget MaxWidth="200" MaxHeight="200" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -3,6 +3,9 @@ using Daybreak.Shared.Models.Trade;
|
||||
using Daybreak.Shared.Services.Navigation;
|
||||
using Daybreak.Shared.Services.TradeChat;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Defaults;
|
||||
using LiveChartsCore.Drawing;
|
||||
using LiveChartsCore.Measure;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using LiveChartsCore.SkiaSharpView.VisualElements;
|
||||
@@ -45,6 +48,9 @@ public partial class PriceHistoryView : UserControl
|
||||
[GenerateDependencyProperty]
|
||||
private DateTime endDateTime = DateTime.MaxValue;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private Margin drawMargin = new(float.NaN, float.NaN, float.NaN, 300);
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool loading = false;
|
||||
|
||||
@@ -62,6 +68,7 @@ public partial class PriceHistoryView : UserControl
|
||||
this.foregroundPaint = new SolidColorPaint(new SKColor(skForegroundBrush.Color.R, skForegroundBrush.Color.G, skForegroundBrush.Color.B, skForegroundBrush.Color.A));
|
||||
this.accentPaint = new SolidColorPaint(new SKColor(skAccentBrush.Color.R, skAccentBrush.Color.G, skAccentBrush.Color.B, skAccentBrush.Color.A), 2);
|
||||
this.backgroundPaint = new SolidColorPaint(new SKColor(skBackgroundBrush.Color.R, skBackgroundBrush.Color.G, skBackgroundBrush.Color.B, skBackgroundBrush.Color.A));
|
||||
this.PopulateChart();
|
||||
}
|
||||
|
||||
private void UserControl_DataContextChanged(object _, DependencyPropertyChangedEventArgs __)
|
||||
@@ -74,18 +81,6 @@ public partial class PriceHistoryView : UserControl
|
||||
this.viewManager.ShowView<PriceQuotesView>();
|
||||
}
|
||||
|
||||
private void HighlightButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var xAxis = this.XAxes.FirstOrDefault();
|
||||
if (xAxis is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
xAxis.MinLimit = this.StartDateTime.Ticks;
|
||||
xAxis.MaxLimit = this.EndDateTime.Ticks;
|
||||
}
|
||||
|
||||
private async void FetchPriceHistory()
|
||||
{
|
||||
if (this.DataContext is not ItemBase itemBase)
|
||||
@@ -115,33 +110,15 @@ public partial class PriceHistoryView : UserControl
|
||||
|
||||
private void PopulateChart()
|
||||
{
|
||||
this.EndDateTime = this.traderQuotes.OrderByDescending(t => t.Timestamp).FirstOrDefault()?.Timestamp ?? DateTime.Now;
|
||||
this.StartDateTime = this.EndDateTime - TimeSpan.FromDays(1);
|
||||
|
||||
this.XAxes =
|
||||
[
|
||||
new Axis
|
||||
new DateTimeAxis(TimeSpan.FromHours(1), dateTime => dateTime.ToString("d"))
|
||||
{
|
||||
Name = "Date",
|
||||
LabelsPaint = this.foregroundPaint,
|
||||
Labeler = ticks =>
|
||||
{
|
||||
if (double.IsNaN(ticks))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var t = (long)Math.Round(ticks);
|
||||
if (t < DateTime.MinValue.Ticks || t > DateTime.MaxValue.Ticks)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return new DateTime(t, DateTimeKind.Utc).ToString("d");
|
||||
},
|
||||
MinLimit = this.StartDateTime.Ticks,
|
||||
MaxLimit = this.EndDateTime.Ticks,
|
||||
MinStep = TimeSpan.FromHours(1).Ticks,
|
||||
NameTextSize = 16,
|
||||
NamePaint = this.foregroundPaint,
|
||||
InLineNamePlacement = true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -150,24 +127,25 @@ public partial class PriceHistoryView : UserControl
|
||||
new Axis
|
||||
{
|
||||
Name = "Price",
|
||||
NameTextSize = 16,
|
||||
NamePadding = new Padding(0, 15),
|
||||
NamePaint = this.foregroundPaint,
|
||||
LabelsPaint = this.foregroundPaint,
|
||||
Labeler = (price) => $"{price*20d:0}g",
|
||||
Labeler = Labelers.Currency,
|
||||
InLineNamePlacement = true
|
||||
}
|
||||
];
|
||||
|
||||
this.Series =
|
||||
[
|
||||
new LineSeries<TraderQuote>
|
||||
new StepLineSeries<DateTimePoint>
|
||||
{
|
||||
Values = this.traderQuotes,
|
||||
Fill = default,
|
||||
IsHoverable = false,
|
||||
Values = [.. ProcessQuotes(this.traderQuotes).Select(t => new DateTimePoint(t.Timestamp ?? DateTime.MinValue, t.Price))],
|
||||
Stroke = this.accentPaint,
|
||||
LineSmoothness = 0,
|
||||
Name = "Historical Price",
|
||||
GeometryStroke = default,
|
||||
GeometryFill = default,
|
||||
GeometrySize = default,
|
||||
Name = string.Empty,
|
||||
}
|
||||
];
|
||||
|
||||
@@ -175,7 +153,20 @@ public partial class PriceHistoryView : UserControl
|
||||
{
|
||||
Text = $"{this.traderQuotes.FirstOrDefault()?.Item?.Name} Price Chart",
|
||||
TextSize = 22,
|
||||
Paint = this.foregroundPaint,
|
||||
Paint = this.foregroundPaint
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<TraderQuote> ProcessQuotes(IEnumerable<TraderQuote> quotes)
|
||||
{
|
||||
var lastPricePoint = double.MinValue;
|
||||
foreach(var quote in quotes.OrderBy(p => p.Timestamp))
|
||||
{
|
||||
if (quote.Price != lastPricePoint)
|
||||
{
|
||||
lastPricePoint = quote.Price;
|
||||
yield return quote;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);IL2104;IL3053;IL3000;IL3002;NU1701;CS0108</NoWarn>
|
||||
|
||||
<Version>0.9.9.76</Version>
|
||||
<Version>0.9.9.78</Version>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
+53
-57
@@ -1,78 +1,74 @@
|
||||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="SharpCompress" Version="0.40.0" />
|
||||
|
||||
<PackageVersion Include="Elastic.OpenTelemetry" Version="1.0.2" />
|
||||
<PackageVersion Include="AvalonEdit" Version="6.3.1.120" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="Daybreak.7ZipExtractor" Version="1.0.0" />
|
||||
<PackageVersion Include="DiffPlex" Version="1.8.0" />
|
||||
<PackageVersion Include="Elastic.OpenTelemetry" Version="1.1.0" />
|
||||
<PackageVersion Include="FluentAssertions" Version="8.5.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.2" />
|
||||
<PackageVersion Include="ini-parser-netstandard" Version="2.5.3" />
|
||||
<PackageVersion Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc4.5" />
|
||||
<PackageVersion Include="MahApps.Metro" Version="2.4.10" />
|
||||
<PackageVersion Include="MeaMod.DNS" Version="1.0.71" />
|
||||
<PackageVersion Include="MemoryPack" Version="1.21.4" />
|
||||
<PackageVersion Include="MemoryPack.Generator" Version="1.21.4" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3351.48" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageVersion Include="MinHook.NET" Version="1.1.1" />
|
||||
<PackageVersion Include="MSTest.TestAdapter" Version="3.9.3" />
|
||||
<PackageVersion Include="MSTest.TestFramework" Version="3.9.3" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
|
||||
<PackageVersion Include="NAudio" Version="2.2.1" />
|
||||
<PackageVersion Include="Net.Sdk.Web.Extensions" Version="0.8.10" />
|
||||
<PackageVersion Include="Net.Sdk.Web.Extensions.SourceGenerators" Version="0.9.5" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
|
||||
<PackageVersion Include="Reloaded.Assembler" Version="1.0.16" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="WpfExtended" Version="0.7.9" />
|
||||
<PackageVersion Include="WpfExtended.SourceGeneration" Version="0.3.0" />
|
||||
<PackageVersion Include="WpfScreenHelper" Version="2.1.1" />
|
||||
<PackageVersion Include="WriteableBitmapEx" Version="1.6.8" />
|
||||
<PackageVersion Include="Squealify" Version="0.8.2.2" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="9.0.6" />
|
||||
<PackageVersion Include="System.IO.Compression" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||
<PackageVersion Include="PeNet" Version="5.1.0" />
|
||||
<PackageVersion Include="Plumsy" Version="1.1.0" />
|
||||
<PackageVersion Include="Reloaded.Assembler" Version="1.0.16" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="AvalonEdit" Version="6.3.1.120" />
|
||||
<PackageVersion Include="DiffPlex" Version="1.8.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.1" />
|
||||
<PackageVersion Include="ini-parser-netstandard" Version="2.5.3" />
|
||||
<PackageVersion Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc2" />
|
||||
<PackageVersion Include="MahApps.Metro" Version="2.4.10" />
|
||||
<PackageVersion Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.6" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageVersion Include="NAudio" Version="2.2.1" />
|
||||
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||
<PackageVersion Include="MinHook.NET" Version="1.1.1" />
|
||||
<PackageVersion Include="Net.Sdk.Web.Extensions" Version="0.8.10" />
|
||||
<PackageVersion Include="Net.Sdk.Web.Extensions.SourceGenerators" Version="0.9.5" />
|
||||
<PackageVersion Include="Serilog" Version="4.3.0" />
|
||||
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.Swagger" Version="9.0.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
|
||||
<PackageVersion Include="SystemExtensions.NetStandard.Generators" Version="0.1.6" />
|
||||
<PackageVersion Include="ZLinq" Version="1.4.12" />
|
||||
|
||||
<PackageVersion Include="MeaMod.DNS" Version="1.0.71" />
|
||||
<PackageVersion Include="MemoryPack" Version="1.21.4" />
|
||||
<PackageVersion Include="MemoryPack.Generator" Version="1.21.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3296.44" />
|
||||
<PackageVersion Include="SharpCompress" Version="0.40.0" />
|
||||
<PackageVersion Include="Slim" Version="1.9.2" />
|
||||
<PackageVersion Include="Squealify" Version="0.8.2.2" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.Swagger" Version="9.0.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3" />
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="9.0.7" />
|
||||
<PackageVersion Include="System.IO.Compression" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="9.0.6" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.6" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="SystemExtensions.NetCore" Version="1.6.12" />
|
||||
<PackageVersion Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.6.9" />
|
||||
|
||||
<PackageVersion Include="FluentAssertions" Version="8.3.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="MSTest.TestAdapter" Version="3.9.3" />
|
||||
<PackageVersion Include="MSTest.TestFramework" Version="3.9.3" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
|
||||
<PackageVersion Include="SystemExtensions.NetStandard.Generators" Version="0.1.6" />
|
||||
<PackageVersion Include="WpfExtended" Version="0.7.9" />
|
||||
<PackageVersion Include="WpfExtended.SourceGeneration" Version="0.3.0" />
|
||||
<PackageVersion Include="WpfScreenHelper" Version="2.1.1" />
|
||||
<PackageVersion Include="WriteableBitmapEx" Version="1.6.8" />
|
||||
<PackageVersion Include="ZLinq" Version="1.5.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user