mirror of
https://github.com/claunia/marechai.git
synced 2026-07-08 17:57:08 +00:00
Add support for software compilation releases
- Introduced SoftwareVersionBySoftwareRelease model to manage relationships between software releases and their included versions. - Updated SoftwareRelease model to include a Title property and make SoftwareVersionId nullable for compilation releases. - Modified MarechaiContext to include DbSet for SoftwareVersionBySoftwareRelease and adjusted relationships. - Enhanced SoftwareReleasesController to handle adding and removing included versions for compilation releases. - Updated SoftwareReleaseDialog to allow setting titles and managing included software versions. - Added UI components to display and manage included software versions in compilation releases. - Implemented service methods for fetching and managing included versions and compilations. - Updated localization resources for new fields and UI elements related to compilations.
This commit is contained in:
@@ -68,6 +68,14 @@ namespace Marechai.ApiClient.Models
|
||||
#endif
|
||||
/// <summary>The subvariant_id property</summary>
|
||||
public int? SubvariantId { get; set; }
|
||||
/// <summary>The title property</summary>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public string? Title { get; set; }
|
||||
#nullable restore
|
||||
#else
|
||||
public string Title { get; set; }
|
||||
#endif
|
||||
/// <summary>The variant property</summary>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
@@ -115,6 +123,7 @@ namespace Marechai.ApiClient.Models
|
||||
{ "software_version_id", n => { SoftwareVersionId = n.GetIntValue(); } },
|
||||
{ "subvariant", n => { Subvariant = n.GetStringValue(); } },
|
||||
{ "subvariant_id", n => { SubvariantId = n.GetIntValue(); } },
|
||||
{ "title", n => { Title = n.GetStringValue(); } },
|
||||
{ "variant", n => { Variant = n.GetStringValue(); } },
|
||||
{ "variant_id", n => { VariantId = n.GetIntValue(); } },
|
||||
};
|
||||
@@ -138,6 +147,7 @@ namespace Marechai.ApiClient.Models
|
||||
writer.WriteIntValue("software_version_id", SoftwareVersionId);
|
||||
writer.WriteStringValue("subvariant", Subvariant);
|
||||
writer.WriteIntValue("subvariant_id", SubvariantId);
|
||||
writer.WriteStringValue("title", Title);
|
||||
writer.WriteStringValue("variant", Variant);
|
||||
writer.WriteIntValue("variant_id", VariantId);
|
||||
writer.WriteAdditionalData(AdditionalData);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System;
|
||||
namespace Marechai.ApiClient.Models
|
||||
{
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
#pragma warning disable CS1591
|
||||
public partial class SoftwareVersionBySoftwareReleaseDto : IAdditionalDataHolder, IParsable
|
||||
#pragma warning restore CS1591
|
||||
{
|
||||
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
|
||||
public IDictionary<string, object> AdditionalData { get; set; }
|
||||
/// <summary>The release_id property</summary>
|
||||
public int? ReleaseId { get; set; }
|
||||
/// <summary>The software_name property</summary>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public string? SoftwareName { get; set; }
|
||||
#nullable restore
|
||||
#else
|
||||
public string SoftwareName { get; set; }
|
||||
#endif
|
||||
/// <summary>The software_version property</summary>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public string? SoftwareVersion { get; set; }
|
||||
#nullable restore
|
||||
#else
|
||||
public string SoftwareVersion { get; set; }
|
||||
#endif
|
||||
/// <summary>The software_version_id property</summary>
|
||||
public int? SoftwareVersionId { get; set; }
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.ApiClient.Models.SoftwareVersionBySoftwareReleaseDto"/> and sets the default values.
|
||||
/// </summary>
|
||||
public SoftwareVersionBySoftwareReleaseDto()
|
||||
{
|
||||
AdditionalData = new Dictionary<string, object>();
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new instance of the appropriate class based on discriminator value
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.ApiClient.Models.SoftwareVersionBySoftwareReleaseDto"/></returns>
|
||||
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
|
||||
public static global::Marechai.ApiClient.Models.SoftwareVersionBySoftwareReleaseDto CreateFromDiscriminatorValue(IParseNode parseNode)
|
||||
{
|
||||
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
|
||||
return new global::Marechai.ApiClient.Models.SoftwareVersionBySoftwareReleaseDto();
|
||||
}
|
||||
/// <summary>
|
||||
/// The deserialization information for the current model
|
||||
/// </summary>
|
||||
/// <returns>A IDictionary<string, Action<IParseNode>></returns>
|
||||
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
|
||||
{
|
||||
return new Dictionary<string, Action<IParseNode>>
|
||||
{
|
||||
{ "release_id", n => { ReleaseId = n.GetIntValue(); } },
|
||||
{ "software_name", n => { SoftwareName = n.GetStringValue(); } },
|
||||
{ "software_version", n => { SoftwareVersion = n.GetStringValue(); } },
|
||||
{ "software_version_id", n => { SoftwareVersionId = n.GetIntValue(); } },
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// Serializes information the current object
|
||||
/// </summary>
|
||||
/// <param name="writer">Serialization writer to use to serialize this model</param>
|
||||
public virtual void Serialize(ISerializationWriter writer)
|
||||
{
|
||||
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
|
||||
writer.WriteIntValue("release_id", ReleaseId);
|
||||
writer.WriteStringValue("software_name", SoftwareName);
|
||||
writer.WriteStringValue("software_version", SoftwareVersion);
|
||||
writer.WriteIntValue("software_version_id", SoftwareVersionId);
|
||||
writer.WriteAdditionalData(AdditionalData);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -0,0 +1,87 @@
|
||||
// <auto-generated/>
|
||||
#pragma warning disable CS0618
|
||||
using Marechai.ApiClient.Models;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
using Microsoft.Kiota.Abstractions.Serialization;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
namespace Marechai.ApiClient.Software.Item.Compilations
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds and executes requests for operations under \software\{-id}\compilations
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompilationsRequestBuilder : BaseRequestBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="pathParameters">Path parameters for the request</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompilationsRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/software/{%2Did}/compilations", pathParameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder"/> and sets the default values.
|
||||
/// </summary>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
|
||||
public CompilationsRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/software/{%2Did}/compilations", rawUrl)
|
||||
{
|
||||
}
|
||||
/// <returns>A List<global::Marechai.ApiClient.Models.SoftwareReleaseDto></returns>
|
||||
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public async Task<List<global::Marechai.ApiClient.Models.SoftwareReleaseDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public async Task<List<global::Marechai.ApiClient.Models.SoftwareReleaseDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = ToGetRequestInformation(requestConfiguration);
|
||||
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.ApiClient.Models.SoftwareReleaseDto>(requestInfo, global::Marechai.ApiClient.Models.SoftwareReleaseDto.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false);
|
||||
return collectionResult?.AsList();
|
||||
}
|
||||
/// <returns>A <see cref="RequestInformation"/></returns>
|
||||
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
|
||||
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
|
||||
#nullable enable
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
|
||||
{
|
||||
#nullable restore
|
||||
#else
|
||||
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
|
||||
{
|
||||
#endif
|
||||
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
|
||||
requestInfo.Configure(requestConfiguration);
|
||||
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
|
||||
return requestInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder"/></returns>
|
||||
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
|
||||
public global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder WithUrl(string rawUrl)
|
||||
{
|
||||
return new global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder(rawUrl, RequestAdapter);
|
||||
}
|
||||
/// <summary>
|
||||
/// Configuration for the request such as headers, query parameters, and middleware options.
|
||||
/// </summary>
|
||||
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
|
||||
public partial class CompilationsRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
@@ -3,6 +3,7 @@
|
||||
using Marechai.ApiClient.Models;
|
||||
using Marechai.ApiClient.Software.Item.Companies;
|
||||
using Marechai.ApiClient.Software.Item.CompanyRoles;
|
||||
using Marechai.ApiClient.Software.Item.Compilations;
|
||||
using Marechai.ApiClient.Software.Item.Platforms;
|
||||
using Marechai.ApiClient.Software.Item.Screenshots;
|
||||
using Marechai.ApiClient.Software.Item.Variants;
|
||||
@@ -33,6 +34,11 @@ namespace Marechai.ApiClient.Software.Item
|
||||
{
|
||||
get => new global::Marechai.ApiClient.Software.Item.CompanyRoles.CompanyRolesRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The compilations property</summary>
|
||||
public global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder Compilations
|
||||
{
|
||||
get => new global::Marechai.ApiClient.Software.Item.Compilations.CompilationsRequestBuilder(PathParameters, RequestAdapter);
|
||||
}
|
||||
/// <summary>The platforms property</summary>
|
||||
public global::Marechai.ApiClient.Software.Item.Platforms.PlatformsRequestBuilder Platforms
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"descriptionHash": "180C13118CC0FB9D7D20641B70174A8A6E9087183E8E28E0B4E9F58D8D72F75BCD8A178553461F31625ED679C9345769F48799D007D30D599D917A54D9142E62",
|
||||
"descriptionHash": "6ECD8149DF04C05884080CBEDD44D59FF2F659A91828A3E9DA76134646F6EE25C60C63C0C8278EADD405EFA6B2E04F5B362DEFC22D341CF989DB25C982F54EE6",
|
||||
"descriptionLocation": "../../../../../tmp/openapi.json",
|
||||
"lockFileVersion": "1.0.0",
|
||||
"kiotaVersion": "1.31.1",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<local:InvertBoolConverter x:Key="InvertBoolConverter" />
|
||||
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<local:InvertBoolToVisibilityConverter x:Key="InvertBoolToVisibilityConverter" />
|
||||
<local:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<local:RolesListConverter x:Key="RolesListConverter" />
|
||||
<local:LocalizeConverter x:Key="Localize" />
|
||||
|
||||
@@ -80,6 +80,18 @@ public class InvertBoolToVisibilityConverter : IValueConverter
|
||||
value is Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates a boolean value (true → false, false → true)
|
||||
/// </summary>
|
||||
public class BoolNegationConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language) =>
|
||||
value is true ? false : true;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
value is true ? false : true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts null to Visibility (null = Collapsed, not null = Visible)
|
||||
/// </summary>
|
||||
|
||||
@@ -86,6 +86,15 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
[ObservableProperty] private string _soundSynthSearchText = string.Empty;
|
||||
[ObservableProperty] private ObservableCollection<SoundSynthDto> _soundSynthSuggestions = [];
|
||||
|
||||
// Compilation support
|
||||
[ObservableProperty] private string _title = string.Empty;
|
||||
[ObservableProperty] private bool _isCompilation;
|
||||
[ObservableProperty] private ObservableCollection<SoftwareVersionBySoftwareReleaseDto> _includedVersions = [];
|
||||
[ObservableProperty] private ObservableCollection<string> _includedVersionDisplays = [];
|
||||
[ObservableProperty] private SoftwareVersionDto? _selectedIncludedVersion;
|
||||
[ObservableProperty] private string _includedVersionSearchText = string.Empty;
|
||||
[ObservableProperty] private ObservableCollection<SoftwareVersionDto> _includedVersionSuggestions = [];
|
||||
|
||||
private int? _editingId;
|
||||
private List<SoftwareReleaseDto>? _allReleases;
|
||||
private List<SoftwareVersionDto>? _allVersions;
|
||||
@@ -130,6 +139,8 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
RemoveRecommendedGpuCommand = new AsyncRelayCommand<string>(RemoveRecommendedGpuByDisplayAsync);
|
||||
AddSoundSynthCommand = new AsyncRelayCommand(AddSoundSynthAsync);
|
||||
RemoveSoundSynthCommand = new AsyncRelayCommand<string>(RemoveSoundSynthByDisplayAsync);
|
||||
AddIncludedVersionCommand = new AsyncRelayCommand(AddIncludedVersionAsync);
|
||||
RemoveIncludedVersionCommand = new AsyncRelayCommand<string>(RemoveIncludedVersionByDisplayAsync);
|
||||
|
||||
CheckAdminRole();
|
||||
}
|
||||
@@ -151,6 +162,8 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
public IAsyncRelayCommand<string> RemoveRecommendedGpuCommand { get; }
|
||||
public IAsyncRelayCommand AddSoundSynthCommand { get; }
|
||||
public IAsyncRelayCommand<string> RemoveSoundSynthCommand { get; }
|
||||
public IAsyncRelayCommand AddIncludedVersionCommand { get; }
|
||||
public IAsyncRelayCommand<string> RemoveIncludedVersionCommand { get; }
|
||||
|
||||
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
|
||||
public void OnNavigatedFrom(NavigationContext navigationContext) { }
|
||||
@@ -259,6 +272,8 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
EditPanelTitle = _localizer["EditSoftwareReleaseDialog_Title"];
|
||||
IsEditingExisting = true;
|
||||
ReleaseDate = item.ReleaseDate;
|
||||
Title = item.Title ?? string.Empty;
|
||||
IsCompilation = item.SoftwareVersionId is null;
|
||||
|
||||
// Version picker
|
||||
if(item.SoftwareVersionId.HasValue && _allVersions != null)
|
||||
@@ -320,6 +335,9 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
await LoadMinimumGpusAsync(item.Id.Value);
|
||||
await LoadRecommendedGpusAsync(item.Id.Value);
|
||||
await LoadSoundSynthsAsync(item.Id.Value);
|
||||
|
||||
if(IsCompilation)
|
||||
await LoadIncludedVersionsAsync(item.Id.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,14 +357,15 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
{
|
||||
try
|
||||
{
|
||||
if(SelectedVersion == null)
|
||||
if(SelectedVersion == null && !IsCompilation)
|
||||
{
|
||||
ErrorMessage = _localizer["VersionIsRequired"]; HasError = true; return;
|
||||
}
|
||||
|
||||
var dto = new SoftwareReleaseDto
|
||||
{
|
||||
SoftwareVersionId = SelectedVersion.Id,
|
||||
Title = string.IsNullOrWhiteSpace(Title) ? null : Title,
|
||||
SoftwareVersionId = IsCompilation ? null : SelectedVersion?.Id,
|
||||
PlatformId = SelectedPlatform?.Id,
|
||||
RegionId = SelectedRegion?.Id,
|
||||
PublisherId = SelectedPublisher?.Id,
|
||||
@@ -388,6 +407,7 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
IEnumerable<SoftwareReleaseDto> source = (IEnumerable<SoftwareReleaseDto>?)_allReleases ?? Releases;
|
||||
if(!string.IsNullOrWhiteSpace(FilterText))
|
||||
source = source.Where(r =>
|
||||
(r.Title != null && r.Title.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(r.SoftwareVersion != null && r.SoftwareVersion.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(r.Platform != null && r.Platform.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(r.Region != null && r.Region.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) ||
|
||||
@@ -816,6 +836,93 @@ public partial class AdminSoftwareReleasesViewModel : ObservableObject, IRegionA
|
||||
SoundSynthDisplays.Clear();
|
||||
SelectedSoundSynth = null;
|
||||
SoundSynthSearchText = string.Empty;
|
||||
Title = string.Empty;
|
||||
IsCompilation = false;
|
||||
IncludedVersions.Clear();
|
||||
IncludedVersionDisplays.Clear();
|
||||
SelectedIncludedVersion = null;
|
||||
IncludedVersionSearchText = string.Empty;
|
||||
HasError = false; ErrorMessage = string.Empty;
|
||||
}
|
||||
|
||||
// ── Included Version management (compilations only) ──
|
||||
|
||||
public void UpdateIncludedVersionSuggestions(string query)
|
||||
{
|
||||
IncludedVersionSuggestions.Clear();
|
||||
if(_allVersions == null) return;
|
||||
IEnumerable<SoftwareVersionDto> source = _allVersions;
|
||||
if(!string.IsNullOrWhiteSpace(query))
|
||||
source = source.Where(v =>
|
||||
(v.VersionString != null && v.VersionString.Contains(query, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(v.Software != null && v.Software.Contains(query, StringComparison.OrdinalIgnoreCase)));
|
||||
foreach(SoftwareVersionDto match in source) IncludedVersionSuggestions.Add(match);
|
||||
}
|
||||
|
||||
private async Task LoadIncludedVersionsAsync(int releaseId)
|
||||
{
|
||||
IncludedVersions.Clear();
|
||||
IncludedVersionDisplays.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
List<SoftwareVersionBySoftwareReleaseDto> items = await _service.GetIncludedVersionsAsync(releaseId);
|
||||
|
||||
foreach(SoftwareVersionBySoftwareReleaseDto item in items)
|
||||
{
|
||||
IncludedVersions.Add(item);
|
||||
IncludedVersionDisplays.Add($"{item.SoftwareName} — {item.SoftwareVersion}");
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading included versions for release {ReleaseId}", releaseId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddIncludedVersionAsync()
|
||||
{
|
||||
if(SelectedIncludedVersion?.Id == null || _editingId == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var dto = new SoftwareVersionBySoftwareReleaseDto
|
||||
{
|
||||
ReleaseId = _editingId.Value,
|
||||
SoftwareVersionId = SelectedIncludedVersion.Id
|
||||
};
|
||||
|
||||
await _service.AddIncludedVersionAsync(dto);
|
||||
await LoadIncludedVersionsAsync(_editingId.Value);
|
||||
SelectedIncludedVersion = null;
|
||||
IncludedVersionSearchText = string.Empty;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding included version");
|
||||
ErrorMessage = _localizer["FailedToAddIncludedVersion"];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveIncludedVersionByDisplayAsync(string? display)
|
||||
{
|
||||
if(display == null || _editingId == null) return;
|
||||
int idx = IncludedVersionDisplays.IndexOf(display);
|
||||
if(idx < 0 || idx >= IncludedVersions.Count) return;
|
||||
|
||||
SoftwareVersionBySoftwareReleaseDto iv = IncludedVersions[idx];
|
||||
|
||||
try
|
||||
{
|
||||
await _service.RemoveIncludedVersionAsync(_editingId.Value, (int)(iv.SoftwareVersionId ?? 0));
|
||||
await LoadIncludedVersionsAsync(_editingId.Value);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error removing included version");
|
||||
ErrorMessage = _localizer["FailedToRemoveIncludedVersion"];
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,12 @@ public partial class SoftwareReleaseViewViewModel : ObservableObject, IRegionAwa
|
||||
[ObservableProperty]
|
||||
private Visibility _showCompanies = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _showIncludedVersions = Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isCompilation;
|
||||
|
||||
public SoftwareReleaseViewViewModel(ILogger<SoftwareReleaseViewViewModel> logger,
|
||||
IRegionManager regionManager,
|
||||
SoftwareBrowsingService browsingService,
|
||||
@@ -103,9 +109,10 @@ public partial class SoftwareReleaseViewViewModel : ObservableObject, IRegionAwa
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public ObservableCollection<string> Barcodes { get; } = [];
|
||||
public ObservableCollection<string> ProductCodes { get; } = [];
|
||||
public ObservableCollection<string> Companies { get; } = [];
|
||||
public ObservableCollection<string> Barcodes { get; } = [];
|
||||
public ObservableCollection<string> ProductCodes { get; } = [];
|
||||
public ObservableCollection<string> Companies { get; } = [];
|
||||
public ObservableCollection<string> IncludedVersions { get; } = [];
|
||||
|
||||
public bool IsNavigationTarget(NavigationContext navigationContext) => false;
|
||||
|
||||
@@ -154,6 +161,7 @@ public partial class SoftwareReleaseViewViewModel : ObservableObject, IRegionAwa
|
||||
Barcodes.Clear();
|
||||
ProductCodes.Clear();
|
||||
Companies.Clear();
|
||||
IncludedVersions.Clear();
|
||||
|
||||
SoftwareReleaseDto? release = await _browsingService.GetReleaseByIdAsync(releaseId);
|
||||
|
||||
@@ -176,14 +184,32 @@ public partial class SoftwareReleaseViewViewModel : ObservableObject, IRegionAwa
|
||||
if(release.ReleaseDate.HasValue)
|
||||
ReleaseDateDisplay = release.ReleaseDate.Value.DateTime.ToString("MMMM d, yyyy");
|
||||
|
||||
// Build title
|
||||
ReleaseTitle = VersionString ?? _localizer["Software Release"];
|
||||
// Determine if this is a compilation
|
||||
IsCompilation = release.SoftwareVersionId is null;
|
||||
|
||||
// Load software name for display
|
||||
if(_sourceSoftwareId > 0)
|
||||
if(IsCompilation)
|
||||
{
|
||||
SoftwareDto? software = await _browsingService.GetSoftwareByIdAsync(_sourceSoftwareId);
|
||||
SoftwareName = software?.Name;
|
||||
// Compilation: use Title or fallback
|
||||
ReleaseTitle = release.Title ?? _localizer["Compilation"];
|
||||
|
||||
// Load included versions
|
||||
List<SoftwareVersionBySoftwareReleaseDto> includedVersions =
|
||||
await _browsingService.GetIncludedVersionsAsync(releaseId);
|
||||
|
||||
foreach(SoftwareVersionBySoftwareReleaseDto iv in includedVersions)
|
||||
IncludedVersions.Add($"{iv.SoftwareName} — {iv.SoftwareVersion}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single-version: build title from version
|
||||
ReleaseTitle = VersionString ?? _localizer["Software Release"];
|
||||
|
||||
// Load software name for display
|
||||
if(_sourceSoftwareId > 0)
|
||||
{
|
||||
SoftwareDto? software = await _browsingService.GetSoftwareByIdAsync(_sourceSoftwareId);
|
||||
SoftwareName = software?.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Load barcodes
|
||||
@@ -225,6 +251,7 @@ public partial class SoftwareReleaseViewViewModel : ObservableObject, IRegionAwa
|
||||
ShowBarcodes = Barcodes.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowProductCodes = ProductCodes.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowCompanies = Companies.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowIncludedVersions = IncludedVersions.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async Task LoadAllCompaniesAsync(SoftwareReleaseDto release)
|
||||
|
||||
@@ -74,6 +74,12 @@
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1" Padding="16" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBox Header="Title"
|
||||
Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
PlaceholderText="Optional. Used for compilation releases." />
|
||||
<CheckBox Content="Is Compilation"
|
||||
IsChecked="{Binding IsCompilation, Mode=TwoWay}"
|
||||
IsEnabled="{Binding IsEditingExisting, Converter={StaticResource BoolNegationConverter}}" />
|
||||
<AutoSuggestBox Header="{Binding Source={StaticResource Strings}, Path=SoftwareVersionLabel}"
|
||||
PlaceholderText="{Binding Source={StaticResource Strings}, Path=SearchPlaceholder}"
|
||||
Text="{Binding VersionSearchText, Mode=TwoWay}" QueryIcon="Find"
|
||||
@@ -238,6 +244,42 @@
|
||||
DisplayMemberPath="Name" HorizontalAlignment="Stretch" PlaceholderText="{Binding Source={StaticResource Strings}, Path=SoundSynthLabel}" />
|
||||
<Button Content="+" Command="{Binding AddSoundSynthCommand}" Style="{ThemeResource AccentButtonStyle}" Padding="8,4" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Included Software Versions Section (compilation only, edit only) -->
|
||||
<StackPanel Visibility="{Binding IsCompilation, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Spacing="8" Margin="0,16,0,0">
|
||||
<TextBlock Text="Included Software Versions" FontSize="16" FontWeight="SemiBold" />
|
||||
<ItemsRepeater ItemsSource="{Binding IncludedVersionDisplays}">
|
||||
<ItemsRepeater.Layout><StackLayout Spacing="4" /></ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding}" VerticalAlignment="Center" TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1" Content="" FontFamily="Segoe MDL2 Assets"
|
||||
Command="{Binding DataContext.RemoveIncludedVersionCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}" Padding="4,2" Style="{ThemeResource TextBlockButtonStyle}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
<AutoSuggestBox Header="Search Software Version"
|
||||
PlaceholderText="{Binding Source={StaticResource Strings}, Path=SearchPlaceholder}"
|
||||
Text="{Binding IncludedVersionSearchText, Mode=TwoWay}" QueryIcon="Find"
|
||||
x:Name="IncludedVersionSearchBox" TextChanged="IncludedVersionSearchBox_TextChanged" />
|
||||
<ComboBox ItemsSource="{Binding IncludedVersionSuggestions}" SelectedItem="{Binding SelectedIncludedVersion, Mode=TwoWay}"
|
||||
HorizontalAlignment="Stretch" PlaceholderText="Select version">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding Software}" /><Run Text=" — " /><Run Text="{Binding VersionString}" />
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button Content="+" Command="{Binding AddIncludedVersionCommand}" Style="{ThemeResource AccentButtonStyle}" Padding="8,4" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<Grid Grid.Row="2" Padding="16,12" BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" BorderThickness="0,1,0,0" ColumnSpacing="8">
|
||||
|
||||
@@ -58,4 +58,10 @@ public sealed partial class AdminSoftwareReleasesPage : Page
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && DataContext is AdminSoftwareReleasesViewModel vm)
|
||||
vm.UpdateSoundSynthSuggestions(sender.Text);
|
||||
}
|
||||
|
||||
private void IncludedVersionSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && DataContext is AdminSoftwareReleasesViewModel vm)
|
||||
vm.UpdateIncludedVersionSuggestions(sender.Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +251,31 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Included Software (compilations only) -->
|
||||
<StackPanel Visibility="{Binding ShowIncludedVersions}"
|
||||
Spacing="12">
|
||||
<TextBlock
|
||||
Text="Included Software"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<ItemsControl ItemsSource="{Binding IncludedVersions}"
|
||||
Margin="0,0,0,0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="12,8"
|
||||
Margin="0,0,0,8">
|
||||
<TextBlock Text="{Binding}"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Barcodes -->
|
||||
<StackPanel Visibility="{Binding ShowBarcodes}"
|
||||
Spacing="12">
|
||||
|
||||
@@ -1971,4 +1971,8 @@
|
||||
<data name="FailedToUploadScreenshot" xml:space="preserve"><value>Failed to upload screenshot</value></data>
|
||||
<data name="FailedToDeleteScreenshot" xml:space="preserve"><value>Failed to delete screenshot</value></data>
|
||||
<data name="FailedToSaveScreenshot" xml:space="preserve"><value>Failed to save screenshot</value></data>
|
||||
<data name="Compilation" xml:space="preserve"><value>Zusammenstellung</value></data>
|
||||
<data name="IncludedSoftware" xml:space="preserve"><value>Enthaltene Software</value></data>
|
||||
<data name="FailedToAddIncludedVersion" xml:space="preserve"><value>Fehler beim Hinzufügen der enthaltenen Version</value></data>
|
||||
<data name="FailedToRemoveIncludedVersion" xml:space="preserve"><value>Fehler beim Entfernen der enthaltenen Version</value></data>
|
||||
</root>
|
||||
|
||||
@@ -2111,4 +2111,8 @@
|
||||
<data name="FailedToUploadScreenshot" xml:space="preserve"><value>Error al subir captura de pantalla</value></data>
|
||||
<data name="FailedToDeleteScreenshot" xml:space="preserve"><value>Error al eliminar captura de pantalla</value></data>
|
||||
<data name="FailedToSaveScreenshot" xml:space="preserve"><value>Error al guardar captura de pantalla</value></data>
|
||||
<data name="Compilation" xml:space="preserve"><value>Compilación</value></data>
|
||||
<data name="IncludedSoftware" xml:space="preserve"><value>Software incluido</value></data>
|
||||
<data name="FailedToAddIncludedVersion" xml:space="preserve"><value>Error al añadir versión incluida</value></data>
|
||||
<data name="FailedToRemoveIncludedVersion" xml:space="preserve"><value>Error al eliminar versión incluida</value></data>
|
||||
</root>
|
||||
@@ -2075,4 +2075,8 @@
|
||||
<data name="FailedToUploadScreenshot" xml:space="preserve"><value>Failed to upload screenshot</value></data>
|
||||
<data name="FailedToDeleteScreenshot" xml:space="preserve"><value>Failed to delete screenshot</value></data>
|
||||
<data name="FailedToSaveScreenshot" xml:space="preserve"><value>Failed to save screenshot</value></data>
|
||||
<data name="Compilation" xml:space="preserve"><value>Compilation</value></data>
|
||||
<data name="IncludedSoftware" xml:space="preserve"><value>Logiciels inclus</value></data>
|
||||
<data name="FailedToAddIncludedVersion" xml:space="preserve"><value>Échec de l'ajout de la version incluse</value></data>
|
||||
<data name="FailedToRemoveIncludedVersion" xml:space="preserve"><value>Échec de la suppression de la version incluse</value></data>
|
||||
</root>
|
||||
@@ -1971,4 +1971,8 @@
|
||||
<data name="FailedToUploadScreenshot" xml:space="preserve"><value>Failed to upload screenshot</value></data>
|
||||
<data name="FailedToDeleteScreenshot" xml:space="preserve"><value>Failed to delete screenshot</value></data>
|
||||
<data name="FailedToSaveScreenshot" xml:space="preserve"><value>Failed to save screenshot</value></data>
|
||||
<data name="Compilation" xml:space="preserve"><value>Compilatio</value></data>
|
||||
<data name="IncludedSoftware" xml:space="preserve"><value>Programmata inclusa</value></data>
|
||||
<data name="FailedToAddIncludedVersion" xml:space="preserve"><value>Error addendi versionem inclusam</value></data>
|
||||
<data name="FailedToRemoveIncludedVersion" xml:space="preserve"><value>Error removendi versionem inclusam</value></data>
|
||||
</root>
|
||||
|
||||
@@ -1971,4 +1971,8 @@
|
||||
<data name="FailedToUploadScreenshot" xml:space="preserve"><value>Failed to upload screenshot</value></data>
|
||||
<data name="FailedToDeleteScreenshot" xml:space="preserve"><value>Failed to delete screenshot</value></data>
|
||||
<data name="FailedToSaveScreenshot" xml:space="preserve"><value>Failed to save screenshot</value></data>
|
||||
<data name="Compilation" xml:space="preserve"><value>Compilação</value></data>
|
||||
<data name="IncludedSoftware" xml:space="preserve"><value>Software incluído</value></data>
|
||||
<data name="FailedToAddIncludedVersion" xml:space="preserve"><value>Falha ao adicionar versão incluída</value></data>
|
||||
<data name="FailedToRemoveIncludedVersion" xml:space="preserve"><value>Falha ao remover versão incluída</value></data>
|
||||
</root>
|
||||
@@ -2318,4 +2318,8 @@
|
||||
<data name="FailedToUploadScreenshot" xml:space="preserve"><value>Failed to upload screenshot</value></data>
|
||||
<data name="FailedToDeleteScreenshot" xml:space="preserve"><value>Failed to delete screenshot</value></data>
|
||||
<data name="FailedToSaveScreenshot" xml:space="preserve"><value>Failed to save screenshot</value></data>
|
||||
<data name="Compilation" xml:space="preserve"><value>Compilation</value></data>
|
||||
<data name="IncludedSoftware" xml:space="preserve"><value>Included Software</value></data>
|
||||
<data name="FailedToAddIncludedVersion" xml:space="preserve"><value>Failed to add included version</value></data>
|
||||
<data name="FailedToRemoveIncludedVersion" xml:space="preserve"><value>Failed to remove included version</value></data>
|
||||
</root>
|
||||
@@ -460,4 +460,38 @@ public class SoftwareBrowsingService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareVersionBySoftwareReleaseDto>> GetIncludedVersionsAsync(int releaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareVersionBySoftwareReleaseDto>? versions =
|
||||
await _apiClient.Software.Releases[releaseId].Versions.GetAsync();
|
||||
|
||||
return versions ?? [];
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching included versions for release {ReleaseId}", releaseId);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareReleaseDto>> GetCompilationsForSoftwareAsync(int softwareId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareReleaseDto>? compilations =
|
||||
await _apiClient.Software[softwareId].Compilations.GetAsync();
|
||||
|
||||
return compilations ?? [];
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching compilations for software {SoftwareId}", softwareId);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,4 +363,33 @@ public class SoftwareReleasesService
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Compilation junction methods ──
|
||||
|
||||
public async Task<List<SoftwareVersionBySoftwareReleaseDto>> GetIncludedVersionsAsync(int releaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareVersionBySoftwareReleaseDto>? versions =
|
||||
await _apiClient.Software.Releases[releaseId].Versions.GetAsync();
|
||||
|
||||
return versions ?? [];
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading included versions for release {ReleaseId}", releaseId);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddIncludedVersionAsync(SoftwareVersionBySoftwareReleaseDto dto)
|
||||
{
|
||||
await _apiClient.Software.Releases[(int)(dto.ReleaseId ?? 0)].Versions.PostAsync(dto);
|
||||
}
|
||||
|
||||
public async Task RemoveIncludedVersionAsync(int releaseId, int versionId)
|
||||
{
|
||||
await _apiClient.Software.Releases[releaseId].Versions[versionId].DeleteAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,10 @@ namespace Marechai.Data.Dtos;
|
||||
|
||||
public class SoftwareReleaseDto : BaseDto<ulong>
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public string? Title { get; set; }
|
||||
[JsonPropertyName("software_version_id")]
|
||||
[Required]
|
||||
public ulong SoftwareVersionId { get; set; }
|
||||
public ulong? SoftwareVersionId { get; set; }
|
||||
[JsonPropertyName("software_version")]
|
||||
public string? SoftwareVersion { get; set; }
|
||||
[JsonPropertyName("variant_id")]
|
||||
|
||||
15
Marechai.Data/Dtos/SoftwareVersionBySoftwareReleaseDto.cs
Normal file
15
Marechai.Data/Dtos/SoftwareVersionBySoftwareReleaseDto.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Marechai.Data.Dtos;
|
||||
|
||||
public class SoftwareVersionBySoftwareReleaseDto
|
||||
{
|
||||
[JsonPropertyName("release_id")]
|
||||
public ulong ReleaseId { get; set; }
|
||||
[JsonPropertyName("software_version_id")]
|
||||
public ulong SoftwareVersionId { get; set; }
|
||||
[JsonPropertyName("software_version")]
|
||||
public string? SoftwareVersion { get; set; }
|
||||
[JsonPropertyName("software_name")]
|
||||
public string? SoftwareName { get; set; }
|
||||
}
|
||||
8541
Marechai.Database/Migrations/20260425192908_AddSoftwareCompilationSupport.Designer.cs
generated
Normal file
8541
Marechai.Database/Migrations/20260425192908_AddSoftwareCompilationSupport.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marechai.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSoftwareCompilationSupport : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_SoftwareReleases_SoftwareVersions_SoftwareVersionId",
|
||||
table: "SoftwareReleases");
|
||||
|
||||
migrationBuilder.AlterColumn<ulong>(
|
||||
name: "SoftwareVersionId",
|
||||
table: "SoftwareReleases",
|
||||
type: "bigint unsigned",
|
||||
nullable: true,
|
||||
oldClrType: typeof(ulong),
|
||||
oldType: "bigint unsigned");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Title",
|
||||
table: "SoftwareReleases",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SoftwareVersionBySoftwareRelease",
|
||||
columns: table => new
|
||||
{
|
||||
ReleaseId = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
SoftwareVersionId = table.Column<ulong>(type: "bigint unsigned", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SoftwareVersionBySoftwareRelease", x => new { x.ReleaseId, x.SoftwareVersionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_SoftwareVersionBySoftwareRelease_SoftwareReleases_ReleaseId",
|
||||
column: x => x.ReleaseId,
|
||||
principalTable: "SoftwareReleases",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_SoftwareVersionBySoftwareRelease_SoftwareVersions_SoftwareVe~",
|
||||
column: x => x.SoftwareVersionId,
|
||||
principalTable: "SoftwareVersions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SoftwareVersionBySoftwareRelease_SoftwareVersionId",
|
||||
table: "SoftwareVersionBySoftwareRelease",
|
||||
column: "SoftwareVersionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_SoftwareReleases_SoftwareVersions_SoftwareVersionId",
|
||||
table: "SoftwareReleases",
|
||||
column: "SoftwareVersionId",
|
||||
principalTable: "SoftwareVersions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_SoftwareReleases_SoftwareVersions_SoftwareVersionId",
|
||||
table: "SoftwareReleases");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SoftwareVersionBySoftwareRelease");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Title",
|
||||
table: "SoftwareReleases");
|
||||
|
||||
migrationBuilder.AlterColumn<ulong>(
|
||||
name: "SoftwareVersionId",
|
||||
table: "SoftwareReleases",
|
||||
type: "bigint unsigned",
|
||||
nullable: false,
|
||||
defaultValue: 0ul,
|
||||
oldClrType: typeof(ulong),
|
||||
oldType: "bigint unsigned",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_SoftwareReleases_SoftwareVersions_SoftwareVersionId",
|
||||
table: "SoftwareReleases",
|
||||
column: "SoftwareVersionId",
|
||||
principalTable: "SoftwareVersions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5650,12 +5650,15 @@ namespace Marechai.Database.Migrations
|
||||
b.Property<DateTime?>("ReleaseDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<ulong>("SoftwareVersionId")
|
||||
b.Property<ulong?>("SoftwareVersionId")
|
||||
.HasColumnType("bigint unsigned");
|
||||
|
||||
b.Property<ulong?>("SubvariantId")
|
||||
.HasColumnType("bigint unsigned");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("UpdatedOn")
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("datetime(6)");
|
||||
@@ -5932,6 +5935,21 @@ namespace Marechai.Database.Migrations
|
||||
b.ToTable("SoftwareVersions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marechai.Database.Models.SoftwareVersionBySoftwareRelease", b =>
|
||||
{
|
||||
b.Property<ulong>("ReleaseId")
|
||||
.HasColumnType("bigint unsigned");
|
||||
|
||||
b.Property<ulong>("SoftwareVersionId")
|
||||
.HasColumnType("bigint unsigned");
|
||||
|
||||
b.HasKey("ReleaseId", "SoftwareVersionId");
|
||||
|
||||
b.HasIndex("SoftwareVersionId");
|
||||
|
||||
b.ToTable("SoftwareVersionBySoftwareRelease");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marechai.Database.Models.SoundByMachine", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@@ -7806,8 +7824,7 @@ namespace Marechai.Database.Migrations
|
||||
b.HasOne("Marechai.Database.Models.SoftwareVersion", "SoftwareVersion")
|
||||
.WithMany("Releases")
|
||||
.HasForeignKey("SoftwareVersionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("Marechai.Database.Models.SoftwareSubvariant", "Subvariant")
|
||||
.WithMany()
|
||||
@@ -7958,6 +7975,25 @@ namespace Marechai.Database.Migrations
|
||||
b.Navigation("Software");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marechai.Database.Models.SoftwareVersionBySoftwareRelease", b =>
|
||||
{
|
||||
b.HasOne("Marechai.Database.Models.SoftwareRelease", "Release")
|
||||
.WithMany("IncludedVersions")
|
||||
.HasForeignKey("ReleaseId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Marechai.Database.Models.SoftwareVersion", "SoftwareVersion")
|
||||
.WithMany("CompilationReleases")
|
||||
.HasForeignKey("SoftwareVersionId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Release");
|
||||
|
||||
b.Navigation("SoftwareVersion");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marechai.Database.Models.SoundByMachine", b =>
|
||||
{
|
||||
b.HasOne("Marechai.Database.Models.Machine", "Machine")
|
||||
@@ -8441,6 +8477,8 @@ namespace Marechai.Database.Migrations
|
||||
{
|
||||
b.Navigation("Barcodes");
|
||||
|
||||
b.Navigation("IncludedVersions");
|
||||
|
||||
b.Navigation("MinimumGpus");
|
||||
|
||||
b.Navigation("ProductCodes");
|
||||
@@ -8470,6 +8508,8 @@ namespace Marechai.Database.Migrations
|
||||
|
||||
b.Navigation("Companies");
|
||||
|
||||
b.Navigation("CompilationReleases");
|
||||
|
||||
b.Navigation("OSCompatibility");
|
||||
|
||||
b.Navigation("Releases");
|
||||
|
||||
@@ -144,6 +144,7 @@ public class MarechaiContext : IdentityDbContext<ApplicationUser, ApplicationRol
|
||||
public virtual DbSet<MinimumGpuBySoftwareRelease> MinimumGpuBySoftwareRelease { get; set; }
|
||||
public virtual DbSet<RecommendedGpuBySoftwareRelease> RecommendedGpuBySoftwareRelease { get; set; }
|
||||
public virtual DbSet<SoundSynthBySoftwareRelease> SoundSynthBySoftwareRelease { get; set; }
|
||||
public virtual DbSet<SoftwareVersionBySoftwareRelease> SoftwareVersionBySoftwareRelease { get; set; }
|
||||
public virtual DbSet<SoftwareScreenshot> SoftwareScreenshots { get; set; }
|
||||
public virtual DbSet<SoundByMachine> SoundByMachine { get; set; }
|
||||
public virtual DbSet<SoundByOwnedMachine> SoundByOwnedMachine { get; set; }
|
||||
@@ -2251,7 +2252,11 @@ public class MarechaiContext : IdentityDbContext<ApplicationUser, ApplicationRol
|
||||
x.PlatformId
|
||||
});
|
||||
|
||||
entity.HasOne(x => x.SoftwareVersion).WithMany(x => x.Releases).HasForeignKey(x => x.SoftwareVersionId);
|
||||
entity.HasOne(x => x.SoftwareVersion)
|
||||
.WithMany(x => x.Releases)
|
||||
.HasForeignKey(x => x.SoftwareVersionId)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
entity.HasOne(x => x.Variant).WithMany().HasForeignKey(x => x.VariantId).OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
@@ -2426,5 +2431,24 @@ public class MarechaiContext : IdentityDbContext<ApplicationUser, ApplicationRol
|
||||
.WithMany(x => x.SupportedBySoftwareReleases)
|
||||
.HasForeignKey(x => x.SoundSynthId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SoftwareVersionBySoftwareRelease>(entity =>
|
||||
{
|
||||
entity.HasKey(x => new
|
||||
{
|
||||
x.ReleaseId,
|
||||
x.SoftwareVersionId
|
||||
});
|
||||
|
||||
entity.HasOne(x => x.Release)
|
||||
.WithMany(x => x.IncludedVersions)
|
||||
.HasForeignKey(x => x.ReleaseId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(x => x.SoftwareVersion)
|
||||
.WithMany(x => x.CompilationReleases)
|
||||
.HasForeignKey(x => x.SoftwareVersionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ namespace Marechai.Database.Models;
|
||||
|
||||
public class SoftwareRelease : BaseModel<ulong>
|
||||
{
|
||||
public ulong SoftwareVersionId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
|
||||
public ulong? SoftwareVersionId { get; set; }
|
||||
public virtual SoftwareVersion SoftwareVersion { get; set; }
|
||||
|
||||
public ulong? VariantId { get; set; }
|
||||
@@ -28,9 +30,10 @@ public class SoftwareRelease : BaseModel<ulong>
|
||||
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
public virtual ICollection<SoftwareBarcode> Barcodes { get; set; }
|
||||
public virtual ICollection<SoftwareProductCode> ProductCodes { get; set; }
|
||||
public virtual ICollection<MinimumGpuBySoftwareRelease> MinimumGpus { get; set; }
|
||||
public virtual ICollection<RecommendedGpuBySoftwareRelease> RecommendedGpus { get; set; }
|
||||
public virtual ICollection<SoundSynthBySoftwareRelease> SupportedSoundSynths { get; set; }
|
||||
public virtual ICollection<SoftwareBarcode> Barcodes { get; set; }
|
||||
public virtual ICollection<SoftwareProductCode> ProductCodes { get; set; }
|
||||
public virtual ICollection<MinimumGpuBySoftwareRelease> MinimumGpus { get; set; }
|
||||
public virtual ICollection<RecommendedGpuBySoftwareRelease> RecommendedGpus { get; set; }
|
||||
public virtual ICollection<SoundSynthBySoftwareRelease> SupportedSoundSynths { get; set; }
|
||||
public virtual ICollection<SoftwareVersionBySoftwareRelease> IncludedVersions { get; set; }
|
||||
}
|
||||
@@ -45,7 +45,8 @@ public class SoftwareVersion : BaseModel<ulong>
|
||||
public virtual ICollection<SoftwareVariant> Variants { get; set; }
|
||||
public virtual ICollection<SoftwareRequirement> Requirements { get; set; }
|
||||
public virtual ICollection<SoftwareOSCompatibility> OSCompatibility { get; set; }
|
||||
public virtual ICollection<SoftwareRelease> Releases { get; set; }
|
||||
public virtual ICollection<CompanyBySoftwareVersion> Companies { get; set; }
|
||||
public virtual ICollection<SoftwareScreenshot> Screenshots { get; set; }
|
||||
public virtual ICollection<SoftwareRelease> Releases { get; set; }
|
||||
public virtual ICollection<CompanyBySoftwareVersion> Companies { get; set; }
|
||||
public virtual ICollection<SoftwareScreenshot> Screenshots { get; set; }
|
||||
public virtual ICollection<SoftwareVersionBySoftwareRelease> CompilationReleases { get; set; }
|
||||
}
|
||||
10
Marechai.Database/Models/SoftwareVersionBySoftwareRelease.cs
Normal file
10
Marechai.Database/Models/SoftwareVersionBySoftwareRelease.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Marechai.Database.Models;
|
||||
|
||||
public class SoftwareVersionBySoftwareRelease
|
||||
{
|
||||
public ulong ReleaseId { get; set; }
|
||||
public virtual SoftwareRelease Release { get; set; }
|
||||
|
||||
public ulong SoftwareVersionId { get; set; }
|
||||
public virtual SoftwareVersion SoftwareVersion { get; set; }
|
||||
}
|
||||
@@ -53,6 +53,7 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
.Select(r => new SoftwareReleaseDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Title = r.Title,
|
||||
SoftwareVersionId = r.SoftwareVersionId,
|
||||
SoftwareVersion = r.SoftwareVersion.VersionString,
|
||||
VariantId = r.VariantId,
|
||||
@@ -79,6 +80,7 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
.Select(r => new SoftwareReleaseDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Title = r.Title,
|
||||
SoftwareVersionId = r.SoftwareVersionId,
|
||||
SoftwareVersion = r.SoftwareVersion.VersionString,
|
||||
VariantId = r.VariantId,
|
||||
@@ -103,6 +105,7 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
.Select(r => new SoftwareReleaseDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Title = r.Title,
|
||||
SoftwareVersionId = r.SoftwareVersionId,
|
||||
SoftwareVersion = r.SoftwareVersion.VersionString,
|
||||
VariantId = r.VariantId,
|
||||
@@ -134,6 +137,14 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
|
||||
if(model is null) return NotFound();
|
||||
|
||||
// Enforce mutual exclusivity: cannot change between single-version and compilation modes
|
||||
bool isCurrentlyCompilation = model.SoftwareVersionId is null;
|
||||
bool isDtoCompilation = dto.SoftwareVersionId is null;
|
||||
|
||||
if(isCurrentlyCompilation != isDtoCompilation)
|
||||
return BadRequest("Cannot change a release between single-version and compilation modes.");
|
||||
|
||||
model.Title = dto.Title;
|
||||
model.SoftwareVersionId = dto.SoftwareVersionId;
|
||||
model.VariantId = dto.VariantId;
|
||||
model.SubvariantId = dto.SubvariantId;
|
||||
@@ -142,7 +153,7 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
model.PublisherId = dto.PublisherId;
|
||||
model.ReleaseDate = dto.ReleaseDate;
|
||||
|
||||
string newsName = await BuildSoftwareReleaseNewsNameAsync(model.SoftwareVersionId, model.PlatformId);
|
||||
string newsName = await BuildSoftwareReleaseNewsNameAsync(model);
|
||||
|
||||
await context.News.AddAsync(new News
|
||||
{
|
||||
@@ -170,6 +181,7 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
|
||||
var model = new SoftwareRelease
|
||||
{
|
||||
Title = dto.Title,
|
||||
SoftwareVersionId = dto.SoftwareVersionId,
|
||||
VariantId = dto.VariantId,
|
||||
SubvariantId = dto.SubvariantId,
|
||||
@@ -182,7 +194,7 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
await context.SoftwareReleases.AddAsync(model);
|
||||
await context.SaveChangesWithUserAsync(userId);
|
||||
|
||||
string newsName = await BuildSoftwareReleaseNewsNameAsync(dto.SoftwareVersionId, dto.PlatformId);
|
||||
string newsName = await BuildSoftwareReleaseNewsNameAsync(model);
|
||||
|
||||
await context.News.AddAsync(new News
|
||||
{
|
||||
@@ -219,25 +231,200 @@ public class SoftwareReleasesController(MarechaiContext context) : ControllerBas
|
||||
return Ok();
|
||||
}
|
||||
|
||||
async Task<string> BuildSoftwareReleaseNewsNameAsync(ulong versionId, ulong? platformId)
|
||||
// --- Compilation junction endpoints ---
|
||||
|
||||
[HttpGet("{releaseId:ulong}/versions")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<List<SoftwareVersionBySoftwareReleaseDto>> GetIncludedVersionsAsync(ulong releaseId) =>
|
||||
context.SoftwareVersionBySoftwareRelease
|
||||
.Where(x => x.ReleaseId == releaseId)
|
||||
.OrderBy(x => x.SoftwareVersion.Software.Name)
|
||||
.ThenBy(x => x.SoftwareVersion.VersionString)
|
||||
.Select(x => new SoftwareVersionBySoftwareReleaseDto
|
||||
{
|
||||
ReleaseId = x.ReleaseId,
|
||||
SoftwareVersionId = x.SoftwareVersionId,
|
||||
SoftwareVersion = x.SoftwareVersion.VersionString,
|
||||
SoftwareName = x.SoftwareVersion.Software.Name
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
[HttpPost("{releaseId:ulong}/versions")]
|
||||
[Authorize(Roles = "Admin,UberAdmin")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> AddIncludedVersionAsync(ulong releaseId,
|
||||
[FromBody] SoftwareVersionBySoftwareReleaseDto dto)
|
||||
{
|
||||
string name = "";
|
||||
string userId = User.FindFirstValue(ClaimTypes.Sid);
|
||||
|
||||
SoftwareVersion version = await context.SoftwareVersions.Include(v => v.Software)
|
||||
.FirstOrDefaultAsync(v => v.Id == versionId);
|
||||
if(userId is null) return Unauthorized();
|
||||
|
||||
if(version?.Software is not null)
|
||||
name = $"{version.Software.Name} {version.VersionString}";
|
||||
else if(version is not null)
|
||||
name = version.VersionString;
|
||||
SoftwareRelease release = await context.SoftwareReleases.FindAsync(releaseId);
|
||||
|
||||
if(platformId is not null)
|
||||
if(release is null) return NotFound();
|
||||
|
||||
if(release.SoftwareVersionId is not null)
|
||||
return BadRequest("Cannot add included versions to a single-version release.");
|
||||
|
||||
bool exists = await context.SoftwareVersionBySoftwareRelease
|
||||
.AnyAsync(x => x.ReleaseId == releaseId
|
||||
&& x.SoftwareVersionId == dto.SoftwareVersionId);
|
||||
|
||||
if(exists) return BadRequest("This version is already included in the release.");
|
||||
|
||||
bool versionExists = await context.SoftwareVersions.AnyAsync(v => v.Id == dto.SoftwareVersionId);
|
||||
|
||||
if(!versionExists) return BadRequest("The specified software version does not exist.");
|
||||
|
||||
await context.SoftwareVersionBySoftwareRelease.AddAsync(new SoftwareVersionBySoftwareRelease
|
||||
{
|
||||
SoftwarePlatform platform = await context.SoftwarePlatforms.FindAsync(platformId);
|
||||
ReleaseId = releaseId,
|
||||
SoftwareVersionId = dto.SoftwareVersionId
|
||||
});
|
||||
|
||||
if(platform is not null) name = string.IsNullOrEmpty(name) ? platform.Name : $"{name} ({platform.Name})";
|
||||
await context.SaveChangesWithUserAsync(userId);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpDelete("{releaseId:ulong}/versions/{versionId:ulong}")]
|
||||
[Authorize(Roles = "Admin,UberAdmin")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> RemoveIncludedVersionAsync(ulong releaseId, ulong versionId)
|
||||
{
|
||||
string userId = User.FindFirstValue(ClaimTypes.Sid);
|
||||
|
||||
if(userId is null) return Unauthorized();
|
||||
|
||||
SoftwareRelease release = await context.SoftwareReleases.FindAsync(releaseId);
|
||||
|
||||
if(release is null) return NotFound();
|
||||
|
||||
if(release.SoftwareVersionId is not null)
|
||||
return BadRequest("Cannot remove included versions from a single-version release.");
|
||||
|
||||
SoftwareVersionBySoftwareRelease entry =
|
||||
await context.SoftwareVersionBySoftwareRelease
|
||||
.FirstOrDefaultAsync(x => x.ReleaseId == releaseId
|
||||
&& x.SoftwareVersionId == versionId);
|
||||
|
||||
if(entry is null) return NotFound();
|
||||
|
||||
context.SoftwareVersionBySoftwareRelease.Remove(entry);
|
||||
await context.SaveChangesWithUserAsync(userId);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// --- Compilation listing endpoints ---
|
||||
|
||||
[HttpGet("compilations")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<List<SoftwareReleaseDto>> GetCompilationsAsync() => context.SoftwareReleases
|
||||
.Where(r => r.SoftwareVersionId == null)
|
||||
.OrderBy(r => r.Title)
|
||||
.Select(r => new SoftwareReleaseDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Title = r.Title,
|
||||
SoftwareVersionId = r.SoftwareVersionId,
|
||||
PlatformId = r.PlatformId,
|
||||
Platform = r.Platform.Name,
|
||||
RegionId = r.RegionId,
|
||||
Region = r.Region.Name,
|
||||
PublisherId = r.PublisherId,
|
||||
Publisher = r.Publisher.Name,
|
||||
ReleaseDate = r.ReleaseDate
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
[HttpGet("/software/{softwareId:ulong}/compilations")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<List<SoftwareReleaseDto>> GetCompilationsForSoftwareAsync(ulong softwareId) =>
|
||||
context.SoftwareVersionBySoftwareRelease
|
||||
.Where(x => x.SoftwareVersion.SoftwareId == softwareId)
|
||||
.Select(x => x.Release)
|
||||
.Distinct()
|
||||
.OrderBy(r => r.Title)
|
||||
.Select(r => new SoftwareReleaseDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Title = r.Title,
|
||||
SoftwareVersionId = r.SoftwareVersionId,
|
||||
PlatformId = r.PlatformId,
|
||||
Platform = r.Platform.Name,
|
||||
RegionId = r.RegionId,
|
||||
Region = r.Region.Name,
|
||||
PublisherId = r.PublisherId,
|
||||
Publisher = r.Publisher.Name,
|
||||
ReleaseDate = r.ReleaseDate
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
async Task<string> BuildSoftwareReleaseNewsNameAsync(SoftwareRelease model)
|
||||
{
|
||||
// Use Title if available
|
||||
if(!string.IsNullOrWhiteSpace(model.Title))
|
||||
{
|
||||
if(model.PlatformId is not null)
|
||||
{
|
||||
SoftwarePlatform platform = await context.SoftwarePlatforms.FindAsync(model.PlatformId);
|
||||
|
||||
if(platform is not null) return $"{model.Title} ({platform.Name})";
|
||||
}
|
||||
|
||||
return model.Title;
|
||||
}
|
||||
|
||||
return name;
|
||||
// Single-version release: build from version info
|
||||
if(model.SoftwareVersionId is not null)
|
||||
{
|
||||
string name = "";
|
||||
|
||||
SoftwareVersion version = await context.SoftwareVersions.Include(v => v.Software)
|
||||
.FirstOrDefaultAsync(v => v.Id == model.SoftwareVersionId);
|
||||
|
||||
if(version?.Software is not null)
|
||||
name = $"{version.Software.Name} {version.VersionString}";
|
||||
else if(version is not null)
|
||||
name = version.VersionString;
|
||||
|
||||
if(model.PlatformId is not null)
|
||||
{
|
||||
SoftwarePlatform platform = await context.SoftwarePlatforms.FindAsync(model.PlatformId);
|
||||
|
||||
if(platform is not null)
|
||||
name = string.IsNullOrEmpty(name) ? platform.Name : $"{name} ({platform.Name})";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
// Compilation without title: build from included versions
|
||||
List<string> versionNames = await context.SoftwareVersionBySoftwareRelease
|
||||
.Where(x => x.ReleaseId == model.Id)
|
||||
.OrderBy(x => x.SoftwareVersion.Software.Name)
|
||||
.Select(x => $"{x.SoftwareVersion.Software.Name} {x.SoftwareVersion.VersionString}")
|
||||
.ToListAsync();
|
||||
|
||||
string compilationName = versionNames.Count > 0 ? string.Join(" + ", versionNames) : "Compilation";
|
||||
|
||||
if(model.PlatformId is not null)
|
||||
{
|
||||
SoftwarePlatform plat = await context.SoftwarePlatforms.FindAsync(model.PlatformId);
|
||||
|
||||
if(plat is not null) compilationName = $"{compilationName} ({plat.Name})";
|
||||
}
|
||||
|
||||
return compilationName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,17 @@
|
||||
<MudTabs Elevation="0" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-4">
|
||||
@* Tab 1 - Basic Info *@
|
||||
<MudTabPanel Text="@L["Basic Info"]" Icon="@Icons.Material.Filled.Info">
|
||||
<MudTextField @bind-Value="Title"
|
||||
Label="@L["Title"]"
|
||||
Variant="Variant.Outlined"
|
||||
Class="mb-4"
|
||||
HelperText="@L["Optional. Used as the display name for compilation releases."]"/>
|
||||
|
||||
<MudCheckBox @bind-Value="_isCompilation"
|
||||
Label="@L["Is Compilation"]"
|
||||
Disabled="@(!IsNew)"
|
||||
Class="mb-4"/>
|
||||
|
||||
<MudAutocomplete T="SoftwarePlatformDto"
|
||||
Label="@L["Platform"]"
|
||||
@bind-Value="_selectedPlatform"
|
||||
@@ -279,6 +290,50 @@
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudTabPanel>
|
||||
|
||||
@* Tab 7 - Included Software (compilations only) *@
|
||||
@if(_isCompilation)
|
||||
{
|
||||
<MudTabPanel Text="@L["Included Software"]" Icon="@Icons.Material.Filled.LibraryBooks">
|
||||
@if(_includedVersions is { Count: > 0 })
|
||||
{
|
||||
<MudList T="SoftwareVersionBySoftwareReleaseDto" Dense="true">
|
||||
@foreach(SoftwareVersionBySoftwareReleaseDto iv in _includedVersions)
|
||||
{
|
||||
<MudListItem T="SoftwareVersionBySoftwareReleaseDto">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||
<MudText>@iv.SoftwareName — @iv.SoftwareVersion</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="() => RemoveIncludedVersion(iv)"/>
|
||||
</MudStack>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
|
||||
<MudPaper Class="pa-4 mt-2" Elevation="1">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudAutocomplete T="SoftwareVersionDto"
|
||||
Label="@L["Search Software Version"]"
|
||||
@bind-Value="_selectedIncludedVersion"
|
||||
SearchFunc="SearchAllVersions"
|
||||
ToStringFunc="@(v => v is null ? string.Empty : $"{v.Software} — {v.VersionString}")"
|
||||
Variant="Variant.Outlined"
|
||||
Clearable="true"
|
||||
Style="flex: 1;"/>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
OnClick="AddIncludedVersion"
|
||||
Disabled="@(_selectedIncludedVersion is null)">
|
||||
@L["Add"]
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudTabPanel>
|
||||
}
|
||||
}
|
||||
</MudTabs>
|
||||
</DialogContent>
|
||||
@@ -314,6 +369,11 @@
|
||||
SoundSynthDto? _selectedSoundSynth;
|
||||
List<SoundSynthDto>? _allSoundSynths;
|
||||
|
||||
bool _isCompilation;
|
||||
List<SoftwareVersionBySoftwareReleaseDto>? _includedVersions;
|
||||
SoftwareVersionDto? _selectedIncludedVersion;
|
||||
List<SoftwareVersionDto>? _allVersionsForPicker;
|
||||
|
||||
[CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter] public bool IsNew { get; set; }
|
||||
@@ -325,11 +385,15 @@
|
||||
[Parameter] public int? RegionId { get; set; }
|
||||
[Parameter] public int? PublisherId { get; set; }
|
||||
[Parameter] public DateTime? ReleaseDate { get; set; }
|
||||
[Parameter] public string? Title { get; set; }
|
||||
[Parameter] public bool IsCompilation { get; set; }
|
||||
|
||||
[Inject] SoftwareReleasesService SoftwareReleasesService { get; set; } = null!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_isCompilation = IsCompilation;
|
||||
|
||||
_allPlatforms = await SoftwareReleasesService.GetAllPlatformsAsync();
|
||||
_allRegions = await SoftwareReleasesService.GetCountriesAsync();
|
||||
_allCompanies = await SoftwareReleasesService.GetAllCompaniesAsync();
|
||||
@@ -356,7 +420,13 @@
|
||||
_minimumGpus = await SoftwareReleasesService.GetMinimumGpusAsync(ReleaseId);
|
||||
_recommendedGpus = await SoftwareReleasesService.GetRecommendedGpusAsync(ReleaseId);
|
||||
_soundSynths = await SoftwareReleasesService.GetSoundSynthsAsync(ReleaseId);
|
||||
|
||||
if(_isCompilation)
|
||||
_includedVersions = await SoftwareReleasesService.GetIncludedVersionsAsync(ReleaseId);
|
||||
}
|
||||
|
||||
if(_isCompilation)
|
||||
_allVersionsForPicker = await SoftwareReleasesService.GetAllSoftwareVersionsForPickerAsync();
|
||||
}
|
||||
|
||||
Task<IEnumerable<SoftwarePlatformDto>> SearchPlatforms(string? value, CancellationToken ct)
|
||||
@@ -508,12 +578,55 @@
|
||||
_soundSynths = await SoftwareReleasesService.GetSoundSynthsAsync(ReleaseId);
|
||||
}
|
||||
|
||||
// ── Included version management (compilations only) ──
|
||||
|
||||
Task<IEnumerable<SoftwareVersionDto>> SearchAllVersions(string? value, CancellationToken ct)
|
||||
{
|
||||
IEnumerable<SoftwareVersionDto> results = _allVersionsForPicker ?? [];
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
results = (_allVersionsForPicker ?? []).Where(v =>
|
||||
(v.Software?.Contains(value, StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||
(v.VersionString?.Contains(value, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||
}
|
||||
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
async Task AddIncludedVersion()
|
||||
{
|
||||
if(_selectedIncludedVersion is null) return;
|
||||
|
||||
var dto = new SoftwareVersionBySoftwareReleaseDto
|
||||
{
|
||||
ReleaseId = ReleaseId,
|
||||
SoftwareVersionId = _selectedIncludedVersion.Id
|
||||
};
|
||||
|
||||
(bool succeeded, string? _) = await SoftwareReleasesService.AddIncludedVersionAsync(dto);
|
||||
|
||||
if(succeeded)
|
||||
{
|
||||
_includedVersions = await SoftwareReleasesService.GetIncludedVersionsAsync(ReleaseId);
|
||||
_selectedIncludedVersion = null;
|
||||
}
|
||||
}
|
||||
|
||||
async Task RemoveIncludedVersion(SoftwareVersionBySoftwareReleaseDto iv)
|
||||
{
|
||||
await SoftwareReleasesService.RemoveIncludedVersionAsync(iv.ReleaseId ?? 0, iv.SoftwareVersionId ?? 0);
|
||||
_includedVersions = await SoftwareReleasesService.GetIncludedVersionsAsync(ReleaseId);
|
||||
}
|
||||
|
||||
void Cancel() => MudDialog.Cancel();
|
||||
|
||||
void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(new SoftwareReleaseDialogResult
|
||||
{
|
||||
Title = Title,
|
||||
IsCompilation = _isCompilation,
|
||||
VariantId = _selectedVariant?.Id,
|
||||
SubvariantId = SubvariantId,
|
||||
PlatformId = _selectedPlatform?.Id,
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Marechai.Pages.Admin;
|
||||
|
||||
public sealed class SoftwareReleaseDialogResult
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
public bool IsCompilation { get; set; }
|
||||
public int? VariantId { get; set; }
|
||||
public int? SubvariantId { get; set; }
|
||||
public int? PlatformId { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/admin/software/versions/{VersionId:int}/releases"
|
||||
@page "/admin/software/releases"
|
||||
@attribute [Authorize(Roles = "Admin, UberAdmin")]
|
||||
@using Marechai.ApiClient.Models
|
||||
@using Marechai.Services
|
||||
@@ -45,6 +46,7 @@
|
||||
|
||||
<MudDataGrid Items="_releases" Dense="true" Hover="true" Striped="true" Filterable="true">
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Title" Title="@L["Title"]"/>
|
||||
<PropertyColumn Property="x => x.Platform" Title="@L["Platform"]"/>
|
||||
<PropertyColumn Property="x => x.Region" Title="@L["Region"]"/>
|
||||
<PropertyColumn Property="x => x.Publisher" Title="@L["Publisher"]"/>
|
||||
|
||||
@@ -15,21 +15,32 @@ public partial class SoftwareReleases
|
||||
List<SoftwareReleaseDto>? _releases;
|
||||
string? _successMessage;
|
||||
string? _versionName;
|
||||
bool _isVersionContext;
|
||||
|
||||
[Parameter] public int VersionId { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
SoftwareVersionDto? version = await SoftwareVersionsService.GetByIdAsync(VersionId);
|
||||
_versionName = version is not null ? $"{version.Software} - {version.VersionString}" : null;
|
||||
_parentSoftwareId = version?.SoftwareId;
|
||||
_isVersionContext = VersionId > 0;
|
||||
|
||||
if(_isVersionContext)
|
||||
{
|
||||
SoftwareVersionDto? version = await SoftwareVersionsService.GetByIdAsync(VersionId);
|
||||
_versionName = version is not null ? $"{version.Software} - {version.VersionString}" : null;
|
||||
_parentSoftwareId = version?.SoftwareId;
|
||||
}
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
|
||||
async Task LoadDataAsync()
|
||||
{
|
||||
_isLoading = true;
|
||||
_releases = await SoftwareReleasesService.GetByVersionAsync(VersionId);
|
||||
|
||||
_releases = _isVersionContext
|
||||
? await SoftwareReleasesService.GetByVersionAsync(VersionId)
|
||||
: await SoftwareReleasesService.GetAllAsync();
|
||||
|
||||
_isLoading = false;
|
||||
}
|
||||
|
||||
@@ -37,6 +48,13 @@ public partial class SoftwareReleases
|
||||
|
||||
void GoBack()
|
||||
{
|
||||
if(!_isVersionContext)
|
||||
{
|
||||
NavigationManager.NavigateTo("/admin/software");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(_parentSoftwareId.HasValue)
|
||||
NavigationManager.NavigateTo($"/admin/software/{_parentSoftwareId.Value}/versions");
|
||||
else
|
||||
@@ -48,7 +66,8 @@ public partial class SoftwareReleases
|
||||
DialogParameters<SoftwareReleaseDialog> parameters = new()
|
||||
{
|
||||
{ x => x.IsNew, true },
|
||||
{ x => x.ParentVersionId, VersionId }
|
||||
{ x => x.ParentVersionId, VersionId },
|
||||
{ x => x.IsCompilation, !_isVersionContext }
|
||||
};
|
||||
|
||||
IDialogReference dialog =
|
||||
@@ -65,7 +84,8 @@ public partial class SoftwareReleases
|
||||
{
|
||||
var dto = new SoftwareReleaseDto
|
||||
{
|
||||
SoftwareVersionId = VersionId,
|
||||
Title = data.Title,
|
||||
SoftwareVersionId = data.IsCompilation ? null : VersionId,
|
||||
VariantId = data.VariantId,
|
||||
SubvariantId = data.SubvariantId,
|
||||
PlatformId = data.PlatformId,
|
||||
@@ -104,6 +124,8 @@ public partial class SoftwareReleases
|
||||
{ x => x.IsNew, false },
|
||||
{ x => x.ReleaseId, full.Id ?? 0 },
|
||||
{ x => x.ParentVersionId, VersionId },
|
||||
{ x => x.Title, full.Title },
|
||||
{ x => x.IsCompilation, full.SoftwareVersionId is null },
|
||||
{ x => x.VariantId, full.VariantId },
|
||||
{ x => x.SubvariantId, full.SubvariantId },
|
||||
{ x => x.PlatformId, full.PlatformId },
|
||||
@@ -127,7 +149,8 @@ public partial class SoftwareReleases
|
||||
var dto = new SoftwareReleaseDto
|
||||
{
|
||||
Id = full.Id,
|
||||
SoftwareVersionId = VersionId,
|
||||
Title = data.Title,
|
||||
SoftwareVersionId = data.IsCompilation ? null : (int?)VersionId,
|
||||
VariantId = data.VariantId,
|
||||
SubvariantId = data.SubvariantId,
|
||||
PlatformId = data.PlatformId,
|
||||
|
||||
@@ -50,10 +50,18 @@
|
||||
<MudPaper Class="pa-6 mb-6" Elevation="0" Style="background: linear-gradient(135deg, #7e57c2 0%, #42a5f5 100%); border-radius: 16px;">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudIcon Icon="@Icons.Material.Filled.NewReleases" Size="Size.Large" Style="color: white;"/>
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Style="color: white; font-weight: 700;">@(!string.IsNullOrEmpty(_softwareName) ? _softwareName : L["Software Release"])</MudText>
|
||||
@if(!string.IsNullOrEmpty(_release.SoftwareVersion))
|
||||
@if(_release.SoftwareVersionId is null && !string.IsNullOrEmpty(_release.Title))
|
||||
{
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Style="color: rgba(255,255,255,0.85);">@_release.SoftwareVersion</MudText>
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Style="color: white; font-weight: 700;">@_release.Title</MudText>
|
||||
<MudChip T="string" Color="Color.Warning" Variant="Variant.Filled" Size="Size.Small">@L["Compilation"]</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Style="color: white; font-weight: 700;">@(!string.IsNullOrEmpty(_softwareName) ? _softwareName : L["Software Release"])</MudText>
|
||||
@if(!string.IsNullOrEmpty(_release.SoftwareVersion))
|
||||
{
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Style="color: rgba(255,255,255,0.85);">@_release.SoftwareVersion</MudText>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
@@ -130,6 +138,32 @@
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
@* ── Included Software Card (compilations only) ── *@
|
||||
@if(_release.SoftwareVersionId is null && _includedVersions.Count > 0)
|
||||
{
|
||||
<MudExpansionPanels Elevation="2" Class="mb-4 rounded-lg">
|
||||
<MudExpansionPanel>
|
||||
<TitleContent>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LibraryBooks" Color="Color.Primary"/>
|
||||
<MudText Typo="Typo.h6">@L["Included Software"]</MudText>
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Success">@_includedVersions.Count</MudChip>
|
||||
</MudStack>
|
||||
</TitleContent>
|
||||
<ChildContent>
|
||||
<MudList T="SoftwareVersionBySoftwareReleaseDto" Dense="true">
|
||||
@foreach(SoftwareVersionBySoftwareReleaseDto iv in _includedVersions)
|
||||
{
|
||||
<MudListItem T="SoftwareVersionBySoftwareReleaseDto" Icon="@Icons.Material.Filled.Apps">
|
||||
<MudText>@iv.SoftwareName — @iv.SoftwareVersion</MudText>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</ChildContent>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
|
||||
@* ── Barcodes Card ── *@
|
||||
<MudExpansionPanels Elevation="2" Class="mb-4 rounded-lg">
|
||||
<MudExpansionPanel Disabled="@(_barcodes.Count == 0)">
|
||||
|
||||
@@ -38,6 +38,7 @@ public partial class ReleaseView
|
||||
List<(int CompanyId, string CompanyName, string Role)> _aggregatedCompanies = [];
|
||||
List<SoftwareBarcodeDto> _barcodes = [];
|
||||
int _id;
|
||||
List<SoftwareVersionBySoftwareReleaseDto> _includedVersions = [];
|
||||
bool _loaded;
|
||||
List<GpuBySoftwareReleaseDto> _minimumGpus = [];
|
||||
List<SoftwareProductCodeDto> _productCodes = [];
|
||||
@@ -80,12 +81,17 @@ public partial class ReleaseView
|
||||
return;
|
||||
}
|
||||
|
||||
// Get software name from version
|
||||
// Get software name from version (single-version releases)
|
||||
if(_release.SoftwareVersionId.HasValue)
|
||||
{
|
||||
SoftwareVersionDto? version = await Service.GetVersionByIdAsync(_release.SoftwareVersionId.Value);
|
||||
_softwareName = version?.Software;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compilation: load included versions
|
||||
_includedVersions = await Service.GetIncludedVersionsAsync(Id);
|
||||
}
|
||||
|
||||
_barcodes = await Service.GetBarcodesAsync(Id);
|
||||
_productCodes = await Service.GetProductCodesAsync(Id);
|
||||
|
||||
@@ -193,6 +193,46 @@
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
|
||||
@* ── Compilations Card ── *@
|
||||
@if(_compilations.Count > 0)
|
||||
{
|
||||
<MudExpansionPanels Elevation="2" Class="mb-4 rounded-lg">
|
||||
<MudExpansionPanel>
|
||||
<TitleContent>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.LibraryBooks" Color="Color.Primary"/>
|
||||
<MudText Typo="Typo.h6">@L["Compilations"]</MudText>
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Success">@_compilations.Count</MudChip>
|
||||
</MudStack>
|
||||
</TitleContent>
|
||||
<ChildContent>
|
||||
<MudList T="SoftwareReleaseDto" Dense="true">
|
||||
@foreach(SoftwareReleaseDto compilation in _compilations)
|
||||
{
|
||||
<MudListItem T="SoftwareReleaseDto" Href="@($"/software/release/{compilation.Id}")" Icon="@Icons.Material.Filled.Collections">
|
||||
<div class="d-flex flex-wrap align-center gap-2">
|
||||
<MudText Typo="Typo.body1"><strong>@(compilation.Title ?? L["Compilation"])</strong></MudText>
|
||||
@if(!string.IsNullOrEmpty(compilation.Platform))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Info">@compilation.Platform</MudChip>
|
||||
}
|
||||
@if(!string.IsNullOrEmpty(compilation.Region))
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary">@compilation.Region</MudChip>
|
||||
}
|
||||
@if(compilation.ReleaseDate.HasValue)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">@compilation.ReleaseDate.Value.DateTime.ToLongDateString()</MudText>
|
||||
}
|
||||
</div>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</ChildContent>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
|
||||
@* ── Screenshots Card ── *@
|
||||
@if(_screenshots.Count > 0)
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace Marechai.Pages.Software;
|
||||
public partial class View
|
||||
{
|
||||
List<SoftwareCompanyRoleDto> _companies = [];
|
||||
List<SoftwareReleaseDto> _compilations = [];
|
||||
int _id;
|
||||
bool _loaded;
|
||||
Dictionary<int, List<SoftwareReleaseDto>> _releasesByVersion = new();
|
||||
@@ -92,6 +93,9 @@ public partial class View
|
||||
}
|
||||
}
|
||||
|
||||
// Load compilations that include this software
|
||||
_compilations = await Service.GetCompilationsForSoftwareAsync(Id);
|
||||
|
||||
// Load screenshots
|
||||
List<Guid?> screenshotIds = await Service.GetScreenshotIdsAsync(Id);
|
||||
|
||||
|
||||
@@ -166,4 +166,19 @@
|
||||
<data name="Save" xml:space="preserve">
|
||||
<value>Guardar</value>
|
||||
</data>
|
||||
<data name="Title" xml:space="preserve">
|
||||
<value>Título</value>
|
||||
</data>
|
||||
<data name="Is Compilation" xml:space="preserve">
|
||||
<value>Es compilación</value>
|
||||
</data>
|
||||
<data name="Included Software" xml:space="preserve">
|
||||
<value>Software incluido</value>
|
||||
</data>
|
||||
<data name="Search Software Version" xml:space="preserve">
|
||||
<value>Buscar versión de software</value>
|
||||
</data>
|
||||
<data name="Optional. Used as the display name for compilation releases." xml:space="preserve">
|
||||
<value>Opcional. Se usa como nombre de visualización para lanzamientos de compilación.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -426,4 +426,13 @@
|
||||
<data name="No screenshots yet." xml:space="preserve">
|
||||
<value>Aún no hay capturas de pantalla.</value>
|
||||
</data>
|
||||
<data name="Compilations" xml:space="preserve">
|
||||
<value>Compilaciones</value>
|
||||
</data>
|
||||
<data name="Compilation" xml:space="preserve">
|
||||
<value>Compilación</value>
|
||||
</data>
|
||||
<data name="Included Software" xml:space="preserve">
|
||||
<value>Software incluido</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -275,4 +275,8 @@
|
||||
<value>Gestión de plataformas de software</value>
|
||||
<comment>Software platform management admin link</comment>
|
||||
</data>
|
||||
<data name="Software Release Management" xml:space="preserve">
|
||||
<value>Gestión de lanzamientos de software</value>
|
||||
<comment>Software release management admin link</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -492,4 +492,85 @@ public class SoftwareReleasesService(Marechai.ApiClient.Client client)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Compilation junction methods ──
|
||||
|
||||
public async Task<List<SoftwareVersionBySoftwareReleaseDto>> GetIncludedVersionsAsync(int releaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareVersionBySoftwareReleaseDto>? versions =
|
||||
await client.Software.Releases[releaseId].Versions.GetAsync();
|
||||
|
||||
return versions ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool succeeded, string? error)> AddIncludedVersionAsync(SoftwareVersionBySoftwareReleaseDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.Software.Releases[(int)(dto.ReleaseId ?? 0)].Versions.PostAsync(dto);
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
catch(ApiException ex)
|
||||
{
|
||||
return (false, ex.Message);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool succeeded, string? error)> RemoveIncludedVersionAsync(int releaseId, int versionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.Software.Releases[releaseId].Versions[versionId].DeleteAsync();
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
catch(ApiException ex)
|
||||
{
|
||||
return (false, ex.Message);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareVersionDto>> GetAllSoftwareVersionsForPickerAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareVersionDto>? versions = await client.Software.Versions.GetAsync();
|
||||
|
||||
return versions ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareReleaseDto>> GetCompilationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareReleaseDto>? compilations = await client.Software.Releases.Compilations.GetAsync();
|
||||
|
||||
return compilations ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,4 +577,33 @@ public class SoftwareService(Marechai.ApiClient.Client client)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareVersionBySoftwareReleaseDto>> GetIncludedVersionsAsync(int releaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareVersionBySoftwareReleaseDto>? versions =
|
||||
await client.Software.Releases[releaseId].Versions.GetAsync();
|
||||
|
||||
return versions ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareReleaseDto>> GetCompilationsForSoftwareAsync(int softwareId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareReleaseDto>? compilations = await client.Software[softwareId].Compilations.GetAsync();
|
||||
|
||||
return compilations ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<MudNavLink Href="/admin/software" Icon="@Icons.Material.Filled.Apps">@L["Software Management"]</MudNavLink>
|
||||
<MudNavLink Href="/admin/software-families" Icon="@Icons.Material.Filled.FolderSpecial">@L["Software Family Management"]</MudNavLink>
|
||||
<MudNavLink Href="/admin/software-platforms" Icon="@Icons.Material.Filled.Devices">@L["Software Platform Management"]</MudNavLink>
|
||||
<MudNavLink Href="/admin/software/releases" Icon="@Icons.Material.Filled.Album">@L["Software Release Management"]</MudNavLink>
|
||||
</MudNavMenu>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
Reference in New Issue
Block a user