update Module and Theme Install UI to match Marketplace - including logos and support for sorting

This commit is contained in:
sbwalker 2023-08-04 15:17:17 -04:00
parent 749e11762f
commit 92a4a1b210
6 changed files with 263 additions and 129 deletions

View File

@ -10,61 +10,106 @@
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3"> <div class="row justify-content-center mb-3">
<div class="col-sm-6"> <div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group"> <div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))"> <span class="input-group-text">Product</span>
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3">
@if (_packages != null) <div class="col">
{ @if (_initialized)
if (_packages.Count > 0) {
{ <br />
<Pager Items="@_packages"> <div class="row mb-3">
<Row> <div class="col-sm-4">
<td> <h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br /> </div>
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br /> <div class="col-sm-4">
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp; &nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp; </div>
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong> <div class="col-sm-4">
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : "")) <select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : "")) <option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
</td> <option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
<td style="width: 1px; vertical-align: middle;"> <option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl)) <option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
{ @if (_price == "paid")
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> {
} <option value="price">@SharedLocalizer["Search.Price"]</option>
</td> }
<td style="width: 1px; vertical-align: middle;"> </select>
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) </div>
{ </div>
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a> <Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
} <Row>
else <div class="m-2 p-2 d-flex justify-content-center">
{ <div class="container-fluid px-0">
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> <div class="row g-0">
} <div class="col-6">
</td> @if (context.LogoFileId != null)
</Row> {
</Pager> <img src="@GetLogo(context.LogoFileId.Value)" class="img-fluid" alt="@context.Name" />
} }
else else
{ {
<br /> <img src="/package.png" class="img-fluid" alt="@context.Name" />
<div class="mx-auto text-center"> }
@Localizer["Search.NoResults"] </div>
</div> <div class="col-6 text-end">
} <small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
} <br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<small>@SharedLocalizer["Search.Price"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br /> <br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" /> <ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel> </TabPanel>
@ -116,8 +161,10 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private bool _initialized = false;
private List<Package> _packages; private List<Package> _packages;
private string _price = "free"; private string _price = "free";
private string _sort = "popularity";
private string _search = ""; private string _search = "";
private string _productname = ""; private string _productname = "";
private string _packageid = ""; private string _packageid = "";
@ -131,6 +178,7 @@
try try
{ {
await LoadModuleDefinitions(); await LoadModuleDefinitions();
_initialized = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -141,8 +189,10 @@
private async Task LoadModuleDefinitions() private async Task LoadModuleDefinitions()
{ {
ShowProgressIndicator();
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, ""); _packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
if (_packages != null) if (_packages != null)
{ {
@ -154,21 +204,22 @@
} }
} }
} }
HideProgressIndicator();
} }
private async void PriceChanged(ChangeEventArgs e) private string GetLogo(int fileid)
{ {
try var url = ImageUrl(fileid, 100, 100, "", "", "ffffff", 0, false);
{ url = (!string.IsNullOrEmpty(PageState.Alias.Path)) ? url.Substring(PageState.Alias.Path.Length + 1) : url;
_price = (string)e.Value; return Constants.PackageRegistryUrl + url;
_search = ""; }
await LoadModuleDefinitions();
StateHasChanged(); private async void PriceChanged(string price)
} {
catch (Exception ex) _price = price;
{ await LoadModuleDefinitions();
await logger.LogError(ex, "Error On PriceChanged"); StateHasChanged();
}
} }
private async Task Search() private async Task Search()
@ -196,6 +247,12 @@
} }
} }
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadModuleDefinitions();
}
private void HideModal() private void HideModal()
{ {
_productname = ""; _productname = "";

View File

@ -10,61 +10,108 @@
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3"> <div class="row justify-content-center mb-3">
<div class="col-sm-6"> <div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group"> <div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))"> <span class="input-group-text">Product</span>
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0">
<div class="col-6">
@if (context.LogoFileId != null)
{
<img src="@GetLogo(context.LogoFileId.Value)" class="img-fluid" alt="@context.Name" />
}
else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</div>
<div class="col-6 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br />
@if (_packages != null) <small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
{ }
if (_packages.Count > 0) </div>
{ </div>
<Pager Items="@_packages"> <div class="row g-0">
<Row> <div class="col">
<td> <h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br /> <small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br /> @(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp; @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp; {
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong> <small>@SharedLocalizer["Search.Price"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : "")) @((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : "")) }
</td> <br />
<td style="width: 1px; vertical-align: middle;"> @if (!string.IsNullOrEmpty(context.PackageUrl))
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl)) {
{ <button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> }
} @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
</td> {
<td style="width: 1px; vertical-align: middle;"> <a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) }
{ <br />
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a> </div>
} </div>
else </div>
{ </div>
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> </Row>
} </Pager>
</td> }
</Row> </div>
</Pager> </div>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
<br /> <br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" /> <ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel> </TabPanel>
@ -116,8 +163,10 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private bool _initialized = false;
private List<Package> _packages; private List<Package> _packages;
private string _price = "free"; private string _price = "free";
private string _sort = "popularity";
private string _search = ""; private string _search = "";
private string _productname = ""; private string _productname = "";
private string _license = ""; private string _license = "";
@ -131,6 +180,7 @@
try try
{ {
await LoadThemes(); await LoadThemes();
_initialized = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -141,8 +191,10 @@
private async Task LoadThemes() private async Task LoadThemes()
{ {
ShowProgressIndicator();
var themes = await ThemeService.GetThemesAsync(); var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, ""); _packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
if (_packages != null) if (_packages != null)
{ {
@ -154,21 +206,22 @@
} }
} }
} }
HideProgressIndicator();
} }
private async void PriceChanged(ChangeEventArgs e) private string GetLogo(int fileid)
{ {
try var url = ImageUrl(fileid, 100, 100, "", "", "ffffff", 0, false);
{ url = (!string.IsNullOrEmpty(PageState.Alias.Path)) ? url.Substring(PageState.Alias.Path.Length + 1) : url;
_price = (string)e.Value; return Constants.PackageRegistryUrl + url;
_search = ""; }
await LoadThemes();
StateHasChanged(); private async void PriceChanged(string price)
} {
catch (Exception ex) _price = price;
{ await LoadThemes();
await logger.LogError(ex, "Error On PriceChanged"); StateHasChanged();
}
} }
private async Task Search() private async Task Search()
@ -196,6 +249,12 @@
} }
} }
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadThemes();
}
private void HideModal() private void HideModal()
{ {
_productname = ""; _productname = "";

View File

@ -136,7 +136,7 @@
<value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value> <value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value>
</data> </data>
<data name="Download.Heading" xml:space="preserve"> <data name="Download.Heading" xml:space="preserve">
<value>Download</value> <value>Marketplace</value>
</data> </data>
<data name="Upload.Heading" xml:space="preserve"> <data name="Upload.Heading" xml:space="preserve">
<value>Upload</value> <value>Upload</value>

View File

@ -135,4 +135,10 @@
<data name="Search.NoResults" xml:space="preserve"> <data name="Search.NoResults" xml:space="preserve">
<value>No Themes Match The Criteria Provided Or Package Service Is Disabled</value> <value>No Themes Match The Criteria Provided Or Package Service Is Disabled</value>
</data> </data>
<data name="Download.Heading" xml:space="preserve">
<value>Marketplace</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
</root> </root>

View File

@ -223,13 +223,13 @@
<value>by</value> <value>by</value>
</data> </data>
<data name="Search.Downloads" xml:space="preserve"> <data name="Search.Downloads" xml:space="preserve">
<value>downloads</value> <value>Downloads</value>
</data> </data>
<data name="Search.Released" xml:space="preserve"> <data name="Search.Released" xml:space="preserve">
<value>released</value> <value>Released</value>
</data> </data>
<data name="Search.Version" xml:space="preserve"> <data name="Search.Version" xml:space="preserve">
<value>version</value> <value>Version</value>
</data> </data>
<data name="Edit" xml:space="preserve"> <data name="Edit" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
@ -277,19 +277,19 @@
<value>Installed Version</value> <value>Installed Version</value>
</data> </data>
<data name="Search.Source" xml:space="preserve"> <data name="Search.Source" xml:space="preserve">
<value>source</value> <value>Source</value>
</data> </data>
<data name="Message.InfoRequired" xml:space="preserve"> <data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value> <value>Please Provide All Required Information</value>
</data> </data>
<data name="Free" xml:space="preserve"> <data name="Free" xml:space="preserve">
<value>Free</value> <value>Open Source</value>
</data> </data>
<data name="Paid" xml:space="preserve"> <data name="Paid" xml:space="preserve">
<value>Paid</value> <value>Commercial</value>
</data> </data>
<data name="Search.Price" xml:space="preserve"> <data name="Search.Price" xml:space="preserve">
<value>price</value> <value>Price</value>
</data> </data>
<data name="Accept" xml:space="preserve"> <data name="Accept" xml:space="preserve">
<value>Accept</value> <value>Accept</value>
@ -390,4 +390,16 @@
<data name="Support" xml:space="preserve"> <data name="Support" xml:space="preserve">
<value>Support</value> <value>Support</value>
</data> </data>
<data name="Search.Alphabetical" xml:space="preserve">
<value>Alphabetical</value>
</data>
<data name="Buy" xml:space="preserve">
<value>Buy Now</value>
</data>
<data name="Search.Popularity" xml:space="preserve">
<value>Popularity</value>
</data>
<data name="Search.Results" xml:space="preserve">
<value>Results</value>
</data>
</root> </root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB