Compare commits
146 Commits
Author | SHA1 | Date | |
---|---|---|---|
c8f56e1659 | |||
56631a3e6b | |||
84ee9de18c | |||
0aeb4e9173 | |||
bb65e5c373 | |||
45e2027c56 | |||
6dc5ef44b7 | |||
e88d3cca07 | |||
a4b7381141 | |||
2ea054dc72 | |||
13ec726ab2 | |||
2e32b65421 | |||
f48ca53cdb | |||
c5b632cb24 | |||
422e9ae99e | |||
68ada8fbe4 | |||
e40bf08691 | |||
a04c7222b2 | |||
172faec5a0 | |||
e01c3e7e4a | |||
7a3d5d0429 | |||
ddf1caaaaa | |||
021293cbb0 | |||
1438e61f1b | |||
182f4dbae7 | |||
26ec3fc7cf | |||
225c758795 | |||
5e653250f3 | |||
b7a3713946 | |||
44242fdb4a | |||
45515b2c06 | |||
2d95fe294c | |||
72cc44641b | |||
9ac3fa5269 | |||
d1ea141165 | |||
dca21fbb8c | |||
06812d5df8 | |||
564410d3cd | |||
4b1cec979a | |||
a5f1bc3895 | |||
b097d031b5 | |||
45df729711 | |||
802ee8a1ff | |||
e312970212 | |||
c0f4069a9b | |||
654352827e | |||
7dd210976d | |||
5302be8bc1 | |||
9ed7181e28 | |||
23ae4b01cb | |||
59764d3378 | |||
b8e2c729c1 | |||
530d80a011 | |||
2d306e8fda | |||
b3d9a70fd1 | |||
b880207f61 | |||
ee76d02999 | |||
804c33a375 | |||
de784714d9 | |||
2404e26b61 | |||
b191fdda2c | |||
e8adfd45d2 | |||
7158595801 | |||
ba97f63338 | |||
62eca2aedc | |||
b15f6b1fa7 | |||
5b22de589c | |||
d1f50f12af | |||
d40c1d9b31 | |||
b69041d4af | |||
64c5d9a09f | |||
dd170bb41a | |||
1e6e4033f8 | |||
01fabc8d9e | |||
2ca2539b53 | |||
51e2e2966f | |||
55d02d2db5 | |||
282a0b0c44 | |||
8432779b23 | |||
13b9982461 | |||
d76b8cebdc | |||
80315ae6d4 | |||
95e7344286 | |||
28f73727b5 | |||
68f5bf5759 | |||
075748d697 | |||
e8d86f94f2 | |||
d6bb802892 | |||
52680e9002 | |||
d6385d82ae | |||
d058de067c | |||
32d6d143dd | |||
b49432802b | |||
99d4d75d8e | |||
1f584d57ac | |||
2c1543aa82 | |||
4390cbbfae | |||
bbf9e5717e | |||
c7edc28bd9 | |||
6e0de6f7bf | |||
3659422165 | |||
1af30da44e | |||
56c082cb26 | |||
e8eca582de | |||
4084b352de | |||
633e4acf0e | |||
468df15d80 | |||
f4537b4fcb | |||
8bca345b45 | |||
ee80712c77 | |||
3cf7153f44 | |||
8e2fc75e48 | |||
4aa51c8583 | |||
3c6ebd7742 | |||
b85539dc17 | |||
4cae3f02ed | |||
469b436f10 | |||
66e3e6729b | |||
fc6a794714 | |||
d75ed3d5ac | |||
bd0a218214 | |||
f96129fa37 | |||
920418618a | |||
29247481a6 | |||
773710aeef | |||
d0c8ee57e6 | |||
cf2adc7f6a | |||
b621f24540 | |||
99be638525 | |||
d35c204e07 | |||
d8b4267668 | |||
3c2f3be451 | |||
e846cf8672 | |||
8804bce6c0 | |||
83acda6d05 | |||
063719532f | |||
7b1b061355 | |||
ed4540887e | |||
6968476ed0 | |||
5d2c7c3058 | |||
e6cb90e545 | |||
ec73f4dbea | |||
4f41a52ee7 | |||
c097956fcb | |||
8cbc17ed98 | |||
50d89d0f13 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,6 +10,8 @@ msbuild.binlog
|
||||
*.zip
|
||||
|
||||
*.idea
|
||||
_ReSharper.Caches
|
||||
.DS_Store
|
||||
|
||||
Oqtane.Server/appsettings.json
|
||||
Oqtane.Server/Data
|
||||
|
@ -16,7 +16,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
return services;
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
|
||||
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<SiteState>();
|
||||
services.AddScoped<IInstallationService, InstallationService>();
|
||||
|
@ -186,8 +186,8 @@
|
||||
if (firstRender)
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
|
||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head");
|
||||
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", "text/css", "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", "anonymous", "");
|
||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", "anonymous", "", "head");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
||||
{
|
||||
string url = NavigateUrl(p.Path);
|
||||
<div class="col-md-2 mx-auto text-center">
|
||||
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
|
||||
<div class="col-md-2 mx-auto text-center mb-3">
|
||||
<NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All">
|
||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
|
||||
</NavLink>
|
||||
</div>
|
||||
|
@ -132,22 +132,10 @@
|
||||
_isEnabled = job.IsEnabled.ToString();
|
||||
_interval = job.Interval.ToString();
|
||||
_frequency = job.Frequency;
|
||||
_startDate = job.StartDate;
|
||||
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
|
||||
{
|
||||
_startTime = job.StartDate.Value.ToString("HH:mm");
|
||||
}
|
||||
_endDate = job.EndDate;
|
||||
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
|
||||
{
|
||||
_endTime = job.EndDate.Value.ToString("HH:mm");
|
||||
}
|
||||
(_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate);
|
||||
(_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate);
|
||||
_retentionHistory = job.RetentionHistory.ToString();
|
||||
_nextDate = job.NextExecution;
|
||||
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
|
||||
{
|
||||
_nextTime = job.NextExecution.Value.ToString("HH:mm");
|
||||
}
|
||||
(_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution);
|
||||
createdby = job.CreatedBy;
|
||||
createdon = job.CreatedOn;
|
||||
modifiedby = job.ModifiedBy;
|
||||
@ -180,34 +168,10 @@
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
job.StartDate = _startDate;
|
||||
if (job.StartDate != null)
|
||||
{
|
||||
job.StartDate = job.StartDate.Value.Date;
|
||||
if (!string.IsNullOrEmpty(_startTime))
|
||||
{
|
||||
job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
|
||||
}
|
||||
}
|
||||
job.EndDate = _endDate;
|
||||
if (job.EndDate != null)
|
||||
{
|
||||
job.EndDate = job.EndDate.Value.Date;
|
||||
if (!string.IsNullOrEmpty(_endTime))
|
||||
{
|
||||
job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
|
||||
}
|
||||
}
|
||||
job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime);
|
||||
job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime);
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
job.NextExecution = _nextDate;
|
||||
if (job.NextExecution != null)
|
||||
{
|
||||
job.NextExecution = job.NextExecution.Value.Date;
|
||||
if (!string.IsNullOrEmpty(_nextTime))
|
||||
{
|
||||
job.NextExecution = DateTime.Parse(job.NextExecution.Value.ToShortDateString() + " " + _nextTime);
|
||||
}
|
||||
}
|
||||
job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime);
|
||||
|
||||
try
|
||||
{
|
||||
@ -226,4 +190,5 @@
|
||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ else
|
||||
<td>@context.Name</td>
|
||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||
<td>@context.NextExecution</td>
|
||||
<td>@context.NextExecution?.ToLocalTime()</td>
|
||||
<td>
|
||||
@if (context.IsStarted)
|
||||
{
|
||||
|
@ -110,6 +110,10 @@ else
|
||||
}
|
||||
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
|
||||
|
@ -30,7 +30,7 @@ else
|
||||
<td>@context.Version</td>
|
||||
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
|
||||
<td>
|
||||
@if (UpgradeAvailable(context.Code))
|
||||
@if (UpgradeAvailable(context.Code, context.Version))
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
@ -47,7 +47,7 @@ else
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
|
||||
|
||||
var cultures = await LocalizationService.GetCulturesAsync();
|
||||
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
|
||||
@ -75,15 +75,16 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private bool UpgradeAvailable(string code)
|
||||
private bool UpgradeAvailable(string code, string version)
|
||||
{
|
||||
var upgradeavailable = false;
|
||||
if (_packages != null)
|
||||
if (_packages != null && version != null)
|
||||
{
|
||||
var package = _packages.Where(item => item.PackageId == ("Oqtane.Client." + code)).FirstOrDefault();
|
||||
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
|
||||
if (package != null)
|
||||
{
|
||||
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0);
|
||||
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0) &&
|
||||
(Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -96,7 +97,7 @@ else
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, Constants.PackagesFolder);
|
||||
await PackageService.DownloadPackageAsync(Constants.PackageId + "." + code, Constants.Version, Constants.PackagesFolder);
|
||||
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
|
||||
await PackageService.InstallPackagesAsync();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
|
@ -3,7 +3,6 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject SiteState SiteState
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -184,11 +183,12 @@
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(login))
|
||||
{
|
||||
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||
|
||||
if (!twofactor)
|
||||
{
|
||||
user = await UserService.LoginUserAsync(user);
|
||||
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -199,11 +199,22 @@
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
||||
|
||||
if (hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
|
||||
.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
// post back to the Login page so that the cookies are set correctly
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
|
||||
|
@ -130,13 +130,14 @@
|
||||
private string _properties = string.Empty;
|
||||
private string _server = string.Empty;
|
||||
|
||||
public override string UrlParametersTemplate => "/{id}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logId = Int32.Parse(PageState.QueryString["id"]);
|
||||
_logId = Int32.Parse(UrlParameters["id"]);
|
||||
var log = await LogService.GetLogAsync(_logId);
|
||||
if (log != null)
|
||||
{
|
||||
@ -191,13 +192,6 @@
|
||||
|
||||
private string CloseUrl()
|
||||
{
|
||||
if (!PageState.QueryString.ContainsKey("level"))
|
||||
{
|
||||
return NavigateUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
return NavigateUrl(PageState.Page.Path, "level=" + PageState.QueryString["level"] + "&function=" + PageState.QueryString["function"] + "&rows=" + PageState.QueryString["rows"] + "&page=" + PageState.QueryString["page"]);
|
||||
}
|
||||
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ else
|
||||
<th>@Localizer["Function"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString() + "&level=" + _level + "&function=" + _function + "&rows=" + _rows + "&page=" + _page.ToString())" ResourceKey="LogDetails" /></td>
|
||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
||||
<td class="@GetClass(context.Function)">@context.LogDate</td>
|
||||
<td class="@GetClass(context.Function)">@context.Level</td>
|
||||
<td class="@GetClass(context.Function)">@context.Feature</td>
|
||||
@ -99,29 +99,32 @@ else
|
||||
private List<Log> _logs;
|
||||
private string _retention = "";
|
||||
|
||||
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// external link to log item will display Details component
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
|
||||
{
|
||||
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"id={id}"));
|
||||
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}"));
|
||||
}
|
||||
if (PageState.QueryString.ContainsKey("level"))
|
||||
|
||||
if (UrlParameters.ContainsKey("level"))
|
||||
{
|
||||
_level = PageState.QueryString["level"];
|
||||
_level = UrlParameters["level"];
|
||||
}
|
||||
if (PageState.QueryString.ContainsKey("function"))
|
||||
if (UrlParameters.ContainsKey("function"))
|
||||
{
|
||||
_function = PageState.QueryString["function"];
|
||||
_function = UrlParameters["function"];
|
||||
}
|
||||
if (PageState.QueryString.ContainsKey("rows"))
|
||||
if (UrlParameters.ContainsKey("rows"))
|
||||
{
|
||||
_rows = PageState.QueryString["rows"];
|
||||
_rows = UrlParameters["rows"];
|
||||
}
|
||||
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
||||
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
@ -114,6 +114,10 @@
|
||||
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
|
||||
|
||||
@code {
|
||||
private List<Package> _packages;
|
||||
private string _price = "free";
|
||||
|
@ -1,7 +1,10 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IPackageService PackageService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@ -99,35 +102,30 @@
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Translations" ResourceKey="Translations">
|
||||
@if (_packages != null)
|
||||
@if (_languages != null && _languages.Count > 0)
|
||||
{
|
||||
if (_packages.Count > 0)
|
||||
{
|
||||
<Pager Items="@_packages">
|
||||
<Pager Items="@_languages">
|
||||
<Header>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Code"]</th>
|
||||
<th>@Localizer["Version"]</th>
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Code</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3> by: <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"] |
|
||||
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong> |
|
||||
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
|
||||
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? " | " + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
|
||||
@((MarkupString)(context.TrialPeriod > 0 ? " | <strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
|
||||
</td>
|
||||
<td style="width: 1px; vertical-align: middle;">
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
|
||||
@if (context.IsDefault)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
</td>
|
||||
<td style="width: 1px; vertical-align: middle;">
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
{
|
||||
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</Row>
|
||||
@ -140,7 +138,7 @@
|
||||
<div class="mx-auto text-center">
|
||||
@Localizer["Search.NoResults"]
|
||||
</div>
|
||||
}
|
||||
<br />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
@ -169,7 +167,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success" @onclick="DownloadTranslation">@SharedLocalizer["Accept"]</button>
|
||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadPackage(_packageid))>@SharedLocalizer["Accept"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -204,10 +202,10 @@
|
||||
#pragma warning restore 649
|
||||
|
||||
private List<Package> _packages;
|
||||
private List<Language> _languages;
|
||||
private string _productname = "";
|
||||
private string _packageid = "";
|
||||
private string _packagelicense = "";
|
||||
private string _packageversion = "";
|
||||
private string _packageid = "";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
@ -236,7 +234,20 @@
|
||||
_modifiedby = moduleDefinition.ModifiedBy;
|
||||
_modifiedon = moduleDefinition.ModifiedOn;
|
||||
|
||||
_packages = await PackageService.GetPackagesAsync("translation", "", "", moduleDefinition.PackageName);
|
||||
if (!string.IsNullOrEmpty(_packagename))
|
||||
{
|
||||
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
|
||||
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
|
||||
foreach (var package in _packages)
|
||||
{
|
||||
var code = package.PackageId.Split('.').Last();
|
||||
if (!_languages.Any(item => item.Code == code))
|
||||
{
|
||||
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
|
||||
}
|
||||
}
|
||||
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -291,36 +302,52 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task GetPackage(string packageid, string version)
|
||||
private bool UpgradeAvailable(string packagename, string version)
|
||||
{
|
||||
var upgradeavailable = false;
|
||||
if (_packages != null)
|
||||
{
|
||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||
if (package != null)
|
||||
{
|
||||
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
|
||||
}
|
||||
|
||||
}
|
||||
return upgradeavailable;
|
||||
}
|
||||
|
||||
private async Task GetPackage(string packagename)
|
||||
{
|
||||
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
|
||||
try
|
||||
{
|
||||
var package = await PackageService.GetPackageAsync(packageid, version);
|
||||
var package = await PackageService.GetPackageAsync(packagename, version);
|
||||
if (package != null)
|
||||
{
|
||||
_productname = package.Name;
|
||||
_packageid = package.PackageId;
|
||||
if (!string.IsNullOrEmpty(package.License))
|
||||
{
|
||||
_packagelicense = package.License.Replace("\n", "<br />");
|
||||
}
|
||||
_packageversion = package.Version;
|
||||
_packageid = package.PackageId;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
|
||||
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packagename, version);
|
||||
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadTranslation()
|
||||
private async Task DownloadPackage(string packagename)
|
||||
{
|
||||
try
|
||||
{
|
||||
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
|
||||
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
|
||||
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
|
||||
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
|
||||
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", packagename, version);
|
||||
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
|
||||
_productname = "";
|
||||
_packagelicense = "";
|
||||
@ -328,7 +355,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
|
||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
||||
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ else
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != "Oqtane.Client")
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
|
||||
}
|
||||
@ -58,7 +58,7 @@ else
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
{
|
||||
<span>@SharedLocalizer["Yes"]</span>
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
@code {
|
||||
private string _content = string.Empty;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override string Title => "Export Content";
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
|
||||
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId);
|
||||
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -25,7 +25,7 @@
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override string Title => "Import Content";
|
||||
|
||||
private async Task ImportModule()
|
||||
@ -38,7 +38,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
|
||||
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, _content);
|
||||
if (success)
|
||||
{
|
||||
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);
|
||||
|
@ -24,7 +24,7 @@
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
|
||||
<option value="-1"><@Localizer["SiteRoot"]></option>
|
||||
@foreach (Page page in _pageList)
|
||||
@foreach (Page page in PageState.Pages)
|
||||
{
|
||||
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
||||
}
|
||||
@ -167,7 +167,6 @@
|
||||
private List<Theme> _themeList;
|
||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pageList;
|
||||
private string _name;
|
||||
private string _title;
|
||||
private string _meta;
|
||||
@ -201,7 +200,6 @@
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||
_containertype = PageState.Site.DefaultContainerType;
|
||||
_pageList = PageState.Pages;
|
||||
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
|
||||
_permissions = string.Empty;
|
||||
ThemeSettings();
|
||||
@ -307,6 +305,10 @@
|
||||
}
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
@ -329,15 +331,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
|
||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
if (_pages.Any(item => item.Path == page.Path))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -421,14 +424,4 @@
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PagePathIsUnique(string pagePath, int siteId, List<Page> existingPages)
|
||||
{
|
||||
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath);
|
||||
}
|
||||
|
||||
private static bool PagePathIsDeleted(string pagePath, int siteId, List<Page> existingPages)
|
||||
{
|
||||
return existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||
<option value="-1"><@Localizer["SiteRoot"]></option>
|
||||
@foreach (Page page in _pageList)
|
||||
@foreach (Page page in PageState.Pages)
|
||||
{
|
||||
if (page.PageId != _pageId)
|
||||
{
|
||||
@ -201,7 +201,6 @@
|
||||
private List<Theme> _themeList;
|
||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pageList;
|
||||
private List<Module> _pageModules;
|
||||
private int _pageId;
|
||||
private string _name;
|
||||
@ -238,7 +237,6 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_pageList = PageState.Pages;
|
||||
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
|
||||
_themeList = await ThemeService.GetThemesAsync();
|
||||
_themes = ThemeService.GetThemeControls(_themeList);
|
||||
@ -435,6 +433,10 @@
|
||||
}
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
@ -457,12 +459,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList))
|
||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
if (_pages.Any(item => item.Path == page.Path && item.PageId != page.PageId))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_insert != "=")
|
||||
{
|
||||
Page child;
|
||||
@ -567,9 +576,4 @@
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PagePathIsUnique(string pagePath, int siteId, int pageId, List<Page> existingPages)
|
||||
{
|
||||
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.PageId != pageId);
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,22 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_pages == null || _modules == null)
|
||||
{
|
||||
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages">
|
||||
@if (_pages == null)
|
||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages">
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted)">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@ -32,21 +38,19 @@
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@if (_pages.Any())
|
||||
{
|
||||
<br /><ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules">
|
||||
@if (_modules == null)
|
||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules">
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted)">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@ -58,20 +62,18 @@
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@if (_modules.Any())
|
||||
{
|
||||
<br /><ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Page> _pages;
|
||||
@ -95,10 +97,7 @@
|
||||
private async Task Load()
|
||||
{
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
_pages = _pages.Where(item => item.IsDeleted).ToList();
|
||||
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
_modules = _modules.Where(item => item.IsDeleted).ToList();
|
||||
}
|
||||
|
||||
private async Task RestorePage(Page page)
|
||||
@ -140,7 +139,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (Page page in _pages)
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
foreach (Page page in _pages.Where(item => item.IsDeleted))
|
||||
{
|
||||
await PageService.DeletePageAsync(page.PageId);
|
||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||
@ -148,6 +148,7 @@
|
||||
|
||||
await logger.LogInformation("Pages Permanently Deleted");
|
||||
await Load();
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@ -155,6 +156,7 @@
|
||||
{
|
||||
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,9 +183,8 @@
|
||||
try
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
// check if there are any remaining module instances in the site
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
|
||||
// check if there are any remaining module instances in the site
|
||||
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
|
||||
{
|
||||
await ModuleService.DeleteModuleAsync(module.ModuleId);
|
||||
@ -204,12 +205,12 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (Module module in _modules)
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
foreach (Module module in _modules.Where(item => item.IsDeleted))
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
// check if there are any remaining module instances in the site
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
|
||||
// check if there are any remaining module instances in the site
|
||||
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
|
||||
{
|
||||
await ModuleService.DeleteModuleAsync(module.ModuleId);
|
||||
@ -218,12 +219,14 @@
|
||||
|
||||
await logger.LogInformation("Modules Permanently Deleted");
|
||||
await Load();
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,21 @@
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="homepage" class="form-select" @bind="@_homepageid" required>
|
||||
<option value="-"><@Localizer["Not Specified"]></option>
|
||||
@foreach (Page page in PageState.Pages)
|
||||
{
|
||||
if (UserSecurity.ContainsRole(page.Permissions, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
||||
@ -228,6 +243,7 @@
|
||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
|
||||
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
|
||||
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -295,6 +311,7 @@
|
||||
private string _themetype = "-";
|
||||
private string _containertype = "-";
|
||||
private string _admincontainertype = "-";
|
||||
private string _homepageid = "-";
|
||||
private string _smtphost = string.Empty;
|
||||
private string _smtpport = string.Empty;
|
||||
private string _smtpssl = "False";
|
||||
@ -353,6 +370,11 @@
|
||||
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
||||
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||
|
||||
if (site.HomePageId != null)
|
||||
{
|
||||
_homepageid = site.HomePageId.Value.ToString();
|
||||
}
|
||||
|
||||
_pwaisenabled = site.PwaIsEnabled.ToString();
|
||||
if (site.PwaAppIconFileId != null)
|
||||
{
|
||||
@ -479,6 +501,7 @@
|
||||
refresh = true; // needs to be refreshed on client
|
||||
}
|
||||
site.AdminContainerType = _admincontainertype;
|
||||
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
||||
|
||||
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
|
||||
{
|
||||
|
@ -89,6 +89,7 @@ else
|
||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
|
||||
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
|
||||
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -336,7 +337,7 @@ else
|
||||
user.Username = _hostusername;
|
||||
user.Password = _hostpassword;
|
||||
user.LastIPAddress = PageState.RemoteIPAddress;
|
||||
user = await UserService.LoginUserAsync(user);
|
||||
user = await UserService.LoginUserAsync(user, false, false);
|
||||
if (user.IsAuthenticated)
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
|
@ -114,6 +114,10 @@
|
||||
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
|
||||
|
||||
@code {
|
||||
private List<Package> _packages;
|
||||
private string _price = "free";
|
||||
|
@ -29,7 +29,7 @@ else
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != "Oqtane.Client")
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||
}
|
||||
|
@ -429,6 +429,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
foreach(var Notification in notifications)
|
||||
{
|
||||
if (!Notification.IsDeleted)
|
||||
@ -444,12 +445,15 @@ else
|
||||
}
|
||||
await logger.LogInformation("Notifications Permanently Deleted");
|
||||
await LoadNotificationsAsync();
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
@inject ISiteService SiteService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject SiteState SiteState
|
||||
|
||||
@if (users == null)
|
||||
{
|
||||
@ -287,6 +286,15 @@ else
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -386,6 +394,7 @@ else
|
||||
private string _redirecturl;
|
||||
private string _identifierclaimtype;
|
||||
private string _emailclaimtype;
|
||||
private string _roleclaimtype;
|
||||
private string _domainfilter;
|
||||
private string _createusers;
|
||||
|
||||
@ -437,8 +446,9 @@ else
|
||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
|
||||
@ -556,6 +566,7 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
|
||||
@ -591,14 +602,10 @@ else
|
||||
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
_scopes = "openid,profile,email";
|
||||
_identifierclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
|
||||
_emailclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
|
||||
}
|
||||
else
|
||||
{
|
||||
_scopes = "";
|
||||
_identifierclaimtype = "sub";
|
||||
_emailclaimtype = "email";
|
||||
}
|
||||
}
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
|
@ -128,13 +128,6 @@
|
||||
|
||||
private string CloseUrl()
|
||||
{
|
||||
if (!PageState.QueryString.ContainsKey("type"))
|
||||
{
|
||||
return NavigateUrl();
|
||||
}
|
||||
else
|
||||
{
|
||||
return NavigateUrl(PageState.Page.Path, "type=" + PageState.QueryString["type"] + "&days=" + PageState.QueryString["days"] + "&page=" + PageState.QueryString["page"]);
|
||||
}
|
||||
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ else
|
||||
<th>@Localizer["Created"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString() + "&type=" + _type.ToString() + "&days=" + _days.ToString() + "&page=" + _page.ToString())" ResourceKey="Details" /></td>
|
||||
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
||||
<td>@context.IPAddress</td>
|
||||
<td>
|
||||
@if (context.UserId != null)
|
||||
|
@ -1,4 +1,5 @@
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@using System.Net
|
||||
@inherits LocalizableComponent
|
||||
@inject IUserService UserService
|
||||
|
||||
@ -71,6 +72,9 @@
|
||||
[Parameter]
|
||||
public bool IconOnly { get; set; } // optional - specifies only icon in link
|
||||
|
||||
[Parameter]
|
||||
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
@ -118,6 +122,10 @@
|
||||
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
|
||||
_text = Localize(nameof(Text), _text);
|
||||
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
|
||||
if (!string.IsNullOrEmpty(ReturnUrl))
|
||||
{
|
||||
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";
|
||||
}
|
||||
_authorized = IsAuthorized();
|
||||
}
|
||||
|
||||
|
@ -414,4 +414,24 @@
|
||||
public int GetFolderId() => FolderId;
|
||||
|
||||
public File GetFile() => _file;
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
await Refresh(-1);
|
||||
}
|
||||
|
||||
public async Task Refresh(int fileId)
|
||||
{
|
||||
await GetFiles();
|
||||
if (fileId != -1)
|
||||
{
|
||||
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
||||
if (file != null)
|
||||
{
|
||||
FileId = file.FileId;
|
||||
await SetImage();
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,9 @@
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="@RowClass">@Footer</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@ -185,6 +188,9 @@
|
||||
[Parameter]
|
||||
public RenderFragment<TableItem> Row { get; set; } = null; // required
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
|
||||
|
||||
@ -293,6 +299,7 @@
|
||||
{
|
||||
_page = 1;
|
||||
}
|
||||
if (_page < 1) _page = 1;
|
||||
|
||||
_startPage = 0;
|
||||
_endPage = 0;
|
||||
|
@ -6,24 +6,27 @@
|
||||
<div class="col">
|
||||
<TabStrip>
|
||||
<TabPanel Name="Rich" Heading="Rich Text Editor">
|
||||
@if (AllowFileManagement)
|
||||
{
|
||||
@if (_filemanagervisible)
|
||||
@if (_richfilemanager)
|
||||
{
|
||||
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
|
||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||
<br />
|
||||
}
|
||||
<div class="d-flex justify-content-center mb-2">
|
||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>
|
||||
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button>
|
||||
@if (_filemanagervisible)
|
||||
@if (AllowRawHtml)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>@((MarkupString)" ")
|
||||
}
|
||||
@if (AllowFileManagement)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
||||
}
|
||||
@if (_richfilemanager)
|
||||
{
|
||||
@((MarkupString)" ")
|
||||
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@Localizer["Close"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div @ref="@_toolBar">
|
||||
@ -65,19 +68,37 @@
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
@if (AllowRawHtml)
|
||||
{
|
||||
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
||||
@if (_rawfilemanager)
|
||||
{
|
||||
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
|
||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||
<br />
|
||||
}
|
||||
<div class="d-flex justify-content-center mb-2">
|
||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
|
||||
@if (AllowFileManagement)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
|
||||
}
|
||||
@if (_rawfilemanager)
|
||||
{
|
||||
@((MarkupString)" ")
|
||||
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
|
||||
}
|
||||
</div>
|
||||
@if (ReadOnly)
|
||||
{
|
||||
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
||||
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
||||
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
||||
}
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
</div>
|
||||
</div>
|
||||
@ -85,10 +106,11 @@
|
||||
@code {
|
||||
private ElementReference _editorElement;
|
||||
private ElementReference _toolBar;
|
||||
private bool _filemanagervisible = false;
|
||||
private bool _richfilemanager = false;
|
||||
private FileManager _fileManager;
|
||||
private string _richhtml = string.Empty;
|
||||
private string _originalrichhtml = string.Empty;
|
||||
private bool _rawfilemanager = false;
|
||||
private string _rawhtml = string.Empty;
|
||||
private string _originalrawhtml = string.Empty;
|
||||
private string _message = string.Empty;
|
||||
@ -102,6 +124,12 @@
|
||||
[Parameter]
|
||||
public string Placeholder { get; set; } = "Enter Your Content...";
|
||||
|
||||
[Parameter]
|
||||
public bool AllowFileManagement { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool AllowRawHtml { get; set; } = true;
|
||||
|
||||
// parameters only applicable to rich text editor
|
||||
[Parameter]
|
||||
public RenderFragment ToolbarContent { get; set; }
|
||||
@ -112,9 +140,6 @@
|
||||
[Parameter]
|
||||
public string DebugLevel { get; set; } = "info";
|
||||
|
||||
[Parameter]
|
||||
public bool AllowFileManagement { get; set; } = true;
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
|
||||
@ -152,9 +177,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseFileManager()
|
||||
public void CloseRichFileManager()
|
||||
{
|
||||
_filemanagervisible = false;
|
||||
_richfilemanager = false;
|
||||
_message = string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void CloseRawFileManager()
|
||||
{
|
||||
_rawfilemanager = false;
|
||||
_message = string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -196,17 +228,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InsertImage()
|
||||
public async Task InsertRichImage()
|
||||
{
|
||||
_message = string.Empty;
|
||||
if (_filemanagervisible)
|
||||
if (_richfilemanager)
|
||||
{
|
||||
var file = _fileManager.GetFile();
|
||||
if (file != null)
|
||||
{
|
||||
var interop = new RichTextEditorInterop(JSRuntime);
|
||||
await interop.InsertImage(_editorElement, file.Url, file.Name);
|
||||
_filemanagervisible = false;
|
||||
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
|
||||
_richfilemanager = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -215,7 +247,33 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
_filemanagervisible = true;
|
||||
_richfilemanager = true;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public async Task InsertRawImage()
|
||||
{
|
||||
_message = string.Empty;
|
||||
if (_rawfilemanager)
|
||||
{
|
||||
var file = _fileManager.GetFile();
|
||||
if (file != null)
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
int pos = await interop.GetCaretPosition("rawhtmleditor");
|
||||
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
||||
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
||||
_rawfilemanager = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = Localizer["Message.Require.Image"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_rawfilemanager = true;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
|
||||
@if (_content != null)
|
||||
{
|
||||
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor>
|
||||
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
@ -60,6 +60,7 @@
|
||||
|
||||
private RichTextEditor RichTextEditorHtml;
|
||||
private bool _allowfilemanagement;
|
||||
private bool _allowrawhtml;
|
||||
private string _content = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
@ -73,6 +74,7 @@
|
||||
try
|
||||
{
|
||||
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
|
||||
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
|
||||
await LoadContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -15,17 +15,28 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="files" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="files" class="form-select" @bind="@_allowrawhtml">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
|
||||
private string _allowfilemanagement;
|
||||
private string _allowrawhtml;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
try
|
||||
{
|
||||
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
|
||||
_allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -39,6 +50,7 @@
|
||||
{
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
|
||||
settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -15,6 +15,8 @@ namespace Oqtane.Modules
|
||||
public abstract class ModuleBase : ComponentBase, IModuleControl
|
||||
{
|
||||
private Logger _logger;
|
||||
private string _urlparametersstate;
|
||||
private Dictionary<string, string> _urlparameters;
|
||||
|
||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||
|
||||
@ -24,6 +26,9 @@ namespace Oqtane.Modules
|
||||
[Inject]
|
||||
protected IJSRuntime JSRuntime { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected SiteState SiteState { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
protected PageState PageState { get; set; }
|
||||
|
||||
@ -44,6 +49,21 @@ namespace Oqtane.Modules
|
||||
|
||||
public virtual List<Resource> Resources { get; set; }
|
||||
|
||||
// url parameters
|
||||
public virtual string UrlParametersTemplate { get; set; }
|
||||
|
||||
public Dictionary<string, string> UrlParameters {
|
||||
get
|
||||
{
|
||||
if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters)
|
||||
{
|
||||
_urlparametersstate = PageState.UrlParameters;
|
||||
_urlparameters = GetUrlParameters(UrlParametersTemplate);
|
||||
}
|
||||
return _urlparameters;
|
||||
}
|
||||
}
|
||||
|
||||
// base lifecycle method for handling JSInterop script registration
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
@ -55,7 +75,8 @@ namespace Oqtane.Modules
|
||||
var scripts = new List<object>();
|
||||
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
|
||||
{
|
||||
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
}
|
||||
if (scripts.Any())
|
||||
{
|
||||
@ -70,7 +91,7 @@ namespace Oqtane.Modules
|
||||
|
||||
public string ModulePath()
|
||||
{
|
||||
return "Modules/" + GetType().Namespace + "/";
|
||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||
}
|
||||
|
||||
// url methods
|
||||
@ -124,14 +145,23 @@ namespace Oqtane.Modules
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid)
|
||||
public string FileUrl(string folderpath, string filename)
|
||||
{
|
||||
return ContentUrl(fileid, false);
|
||||
return FileUrl(folderpath, filename, false);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
public string FileUrl(string folderpath, string filename, bool download)
|
||||
{
|
||||
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
|
||||
return Utilities.FileUrl(PageState.Alias, folderpath, filename, download);
|
||||
}
|
||||
public string FileUrl(int fileid)
|
||||
{
|
||||
return FileUrl(fileid, false);
|
||||
}
|
||||
|
||||
public string FileUrl(int fileid, bool download)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
@ -149,15 +179,26 @@ namespace Oqtane.Modules
|
||||
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")
|
||||
public string AddUrlParameters(params object[] parameters)
|
||||
{
|
||||
var url = "";
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
url += "/" + parameters[i].ToString();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// template is in the form of a standard route template ie. "/{id}/{name}" and produces dictionary of key/value pairs
|
||||
// if url parameters belong to a specific module you should embed a unique key into the route (ie. /!/blog/1) and validate the url parameter key in the module
|
||||
public virtual Dictionary<string, string> GetUrlParameters(string template = "")
|
||||
{
|
||||
var urlParameters = new Dictionary<string, string>();
|
||||
string[] templateSegments;
|
||||
var parameters = PageState.UrlParameters.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
var parameterId = 0;
|
||||
var parameters = _urlparametersstate.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (string.IsNullOrEmpty(parametersTemplate))
|
||||
if (string.IsNullOrEmpty(template))
|
||||
{
|
||||
// no template will populate dictionary with generic "parameter#" keys
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
urlParameters.TryAdd("parameter" + i, parameters[i]);
|
||||
@ -165,39 +206,37 @@ namespace Oqtane.Modules
|
||||
}
|
||||
else
|
||||
{
|
||||
templateSegments = parametersTemplate.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
var segments = template.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
string key;
|
||||
|
||||
if (parameters.Length == templateSegments.Length)
|
||||
{
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (parameters.Length > i)
|
||||
if (i < segments.Length)
|
||||
{
|
||||
if (templateSegments[i] == parameters[i])
|
||||
key = segments[i];
|
||||
if (key.StartsWith("{") && key.EndsWith("}"))
|
||||
{
|
||||
urlParameters.TryAdd("parameter" + parameterId, parameters[i]);
|
||||
parameterId++;
|
||||
}
|
||||
else if (templateSegments[i].StartsWith("{") && templateSegments[i].EndsWith("}"))
|
||||
{
|
||||
var key = templateSegments[i].Replace("{", "");
|
||||
key = key.Replace("}", "");
|
||||
urlParameters.TryAdd(key, parameters[i]);
|
||||
// dynamic segment
|
||||
key = key.Substring(1, key.Length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
i = parameters.Length;
|
||||
urlParameters.Clear();
|
||||
// static segments use generic "parameter#" keys
|
||||
key = "parameter" + i.ToString();
|
||||
}
|
||||
}
|
||||
else // unspecified segments use generic "parameter#" keys
|
||||
{
|
||||
key = "parameter" + i.ToString();
|
||||
}
|
||||
urlParameters.TryAdd(key, parameters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return urlParameters;
|
||||
}
|
||||
|
||||
// user feedback methods
|
||||
// UI methods
|
||||
public void AddModuleMessage(string message, MessageType type)
|
||||
{
|
||||
ModuleInstance.AddModuleMessage(message, type);
|
||||
@ -218,6 +257,18 @@ namespace Oqtane.Modules
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
}
|
||||
|
||||
public void SetModuleTitle(string title)
|
||||
{
|
||||
var obj = new { PageModuleId = ModuleState.PageModuleId, Title = title };
|
||||
SiteState.Properties.ModuleTitle = obj;
|
||||
}
|
||||
|
||||
public void SetModuleVisibility(bool visible)
|
||||
{
|
||||
var obj = new { PageModuleId = ModuleState.PageModuleId, Visible = visible };
|
||||
SiteState.Properties.ModuleVisibility = obj;
|
||||
}
|
||||
|
||||
// logging methods
|
||||
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
|
||||
{
|
||||
@ -365,5 +416,17 @@ namespace Oqtane.Modules
|
||||
await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
|
||||
public string ContentUrl(int fileid)
|
||||
{
|
||||
return ContentUrl(fileid, false);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)]
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,15 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>3.1.4</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
<Description>Modular Application Framework for Blazor</Description>
|
||||
<Description>Modular Application Framework for Blazor and MAUI</Description>
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -35,13 +35,8 @@
|
||||
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="System.Runtime" />
|
||||
<TrimmerRootAssembly Include="System.Linq.Parallel" />
|
||||
<TrimmerRootAssembly Include="System.Runtime.CompilerServices.VisualC" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
@ -15,7 +16,6 @@ using Microsoft.JSInterop;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
|
||||
namespace Oqtane.Client
|
||||
@ -33,7 +33,7 @@ namespace Oqtane.Client
|
||||
|
||||
builder.Services.AddOptions();
|
||||
|
||||
// Register localization services
|
||||
// register localization services
|
||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||
|
||||
// register auth services
|
||||
@ -42,7 +42,9 @@ namespace Oqtane.Client
|
||||
// register scoped core services
|
||||
builder.Services.AddOqtaneScopedServices();
|
||||
|
||||
await LoadClientAssemblies(httpClient);
|
||||
var serviceProvider = builder.Services.BuildServiceProvider();
|
||||
|
||||
await LoadClientAssemblies(httpClient, serviceProvider);
|
||||
|
||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
||||
foreach (var assembly in assemblies)
|
||||
@ -54,37 +56,106 @@ namespace Oqtane.Client
|
||||
RegisterClientStartups(assembly, builder.Services);
|
||||
}
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
await SetCultureFromLocalizationCookie(host.Services);
|
||||
|
||||
ServiceActivator.Configure(host.Services);
|
||||
|
||||
await host.RunAsync();
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
|
||||
private static async Task LoadClientAssemblies(HttpClient http)
|
||||
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
|
||||
{
|
||||
// get list of loaded assemblies on the client
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
|
||||
var dlls = new Dictionary<string, byte[]>();
|
||||
var pdbs = new Dictionary<string, byte[]>();
|
||||
var list = new List<string>();
|
||||
|
||||
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
|
||||
var interop = new Interop(jsRuntime);
|
||||
var files = await interop.GetIndexedDBKeys(".dll");
|
||||
|
||||
if (files.Count() != 0)
|
||||
{
|
||||
// get list of assemblies from server
|
||||
var json = await http.GetStringAsync("/api/Installation/list");
|
||||
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
|
||||
|
||||
// determine which assemblies need to be downloaded
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var file = files.FirstOrDefault(item => item.Contains(assembly));
|
||||
if (file == null)
|
||||
{
|
||||
list.Add(assembly);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if newer version available
|
||||
if (GetFileDate(assembly) > GetFileDate(file))
|
||||
{
|
||||
list.Add(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get assemblies already downloaded
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (assemblies.Contains(file) && !list.Contains(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
dlls.Add(file, await interop.GetIndexedDBItem<byte[]>(file));
|
||||
var pdb = file.Replace(".dll", ".pdb");
|
||||
if (files.Contains(pdb))
|
||||
{
|
||||
pdbs.Add(pdb, await interop.GetIndexedDBItem<byte[]>(pdb));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
else // file is deprecated
|
||||
{
|
||||
try
|
||||
{
|
||||
await interop.RemoveIndexedDBItem(file);
|
||||
await interop.RemoveIndexedDBItem(file.Replace(".dll", ".pdb"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add("*");
|
||||
}
|
||||
|
||||
if (list.Count != 0)
|
||||
{
|
||||
// get assemblies from server and load into client app domain
|
||||
var zip = await http.GetByteArrayAsync($"/api/Installation/load");
|
||||
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", list));
|
||||
|
||||
// asemblies and debug symbols are packaged in a zip file
|
||||
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
|
||||
{
|
||||
var dlls = new Dictionary<string, byte[]>();
|
||||
var pdbs = new Dictionary<string, byte[]>();
|
||||
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.FullName)))
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
entry.Open().CopyTo(memoryStream);
|
||||
byte[] file = memoryStream.ToArray();
|
||||
|
||||
// save assembly to indexeddb
|
||||
try
|
||||
{
|
||||
await interop.SetIndexedDBItem(entry.FullName, file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
switch (Path.GetExtension(entry.FullName))
|
||||
{
|
||||
case ".dll":
|
||||
@ -97,12 +168,14 @@ namespace Oqtane.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load assemblies into app domain
|
||||
foreach (var item in dlls)
|
||||
{
|
||||
if (pdbs.ContainsKey(item.Key))
|
||||
if (pdbs.ContainsKey(item.Key.Replace(".dll", ".pdb")))
|
||||
{
|
||||
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key]));
|
||||
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key.Replace(".dll", ".pdb")]));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -110,6 +183,11 @@ namespace Oqtane.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime GetFileDate(string filepath)
|
||||
{
|
||||
var segments = filepath.Split('.');
|
||||
return DateTime.ParseExact(segments[segments.Length - 2], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -160,7 +160,7 @@
|
||||
<value>Error Loading Pane Layouts For Theme</value>
|
||||
</data>
|
||||
<data name="Message.Page.Exists" xml:space="preserve">
|
||||
<value>A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.</value>
|
||||
<value>A page with path '{0}' already exists for this site. Page paths must be unique. You may need to check if a page with this path exists in the Recycle Bin.</value>
|
||||
</data>
|
||||
<data name="Message.Required.PageInfo" xml:space="preserve">
|
||||
<value>You Must Provide Page Name, Theme, and Container</value>
|
||||
@ -228,13 +228,13 @@
|
||||
<data name="Appearance.Name" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Message.Page.Deleted" xml:space="preserve">
|
||||
<value>A page with path {0} already exists for the selected parent page in the Recycle Bin. Either recover the page or remove from the Recycle Bin and create it again.</value>
|
||||
</data>
|
||||
<data name="Meta.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
|
||||
</data>
|
||||
<data name="Meta.Text" xml:space="preserve">
|
||||
<value>Meta:</value>
|
||||
</data>
|
||||
<data name="Message.Page.Reserved" xml:space="preserve">
|
||||
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
|
||||
</data>
|
||||
</root>
|
@ -151,7 +151,7 @@
|
||||
<value>Error Loading Pane Layouts For Theme</value>
|
||||
</data>
|
||||
<data name="Mesage.Page.PathExists" xml:space="preserve">
|
||||
<value>A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.</value>
|
||||
<value>A page with path '{0}' already exists for this site. Page paths must be unique. You may need to check if a page with this path exists in the Recycle Bin.</value>
|
||||
</data>
|
||||
<data name="Message.Required.PageInfo" xml:space="preserve">
|
||||
<value>You Must Provide Page Name, Theme, and Container</value>
|
||||
@ -270,4 +270,7 @@
|
||||
<data name="Meta.Text" xml:space="preserve">
|
||||
<value>Meta:</value>
|
||||
</data>
|
||||
<data name="Message.Page.Reserved" xml:space="preserve">
|
||||
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
|
||||
</data>
|
||||
</root>
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -166,16 +166,22 @@
|
||||
<value>Error Permanently Deleting Modules</value>
|
||||
</data>
|
||||
<data name="DeleteAllPages.Header" xml:space="preserve">
|
||||
<value>Delete All Pages</value>
|
||||
<value>Remove All Deleted Pages</value>
|
||||
</data>
|
||||
<data name="DeleteAllPages.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Permanently Delete All Pages?</value>
|
||||
<value>Are You Sure You Wish To Permanently Remove All Deleted Pages?</value>
|
||||
</data>
|
||||
<data name="DeleteAllPages.Text" xml:space="preserve">
|
||||
<value>Remove All Deleted Pages</value>
|
||||
</data>
|
||||
<data name="DeleteAllModules.Header" xml:space="preserve">
|
||||
<value>Delete All Modules</value>
|
||||
<value>Remove All Deleted Modules</value>
|
||||
</data>
|
||||
<data name="DeleteAllModules.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Permanently Delete All Modules?</value>
|
||||
<value>Are You Sure You Wish To Permanently Remove All Deleted Modules?</value>
|
||||
</data>
|
||||
<data name="DeleteAllModules.Text" xml:space="preserve">
|
||||
<value>Remove All Deleted Modules</value>
|
||||
</data>
|
||||
<data name="Pages.Heading" xml:space="preserve">
|
||||
<value>Pages</value>
|
||||
|
@ -333,4 +333,10 @@
|
||||
<data name="Confirm.Alias.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="HomePage.HelpText" xml:space="preserve">
|
||||
<value>Select the home page for the site (to be used if there is no page with a path of '/')</value>
|
||||
</data>
|
||||
<data name="HomePage.Text" xml:space="preserve">
|
||||
<value>Home Page:</value>
|
||||
</data>
|
||||
</root>
|
@ -211,25 +211,25 @@
|
||||
<value>Allow Login?</value>
|
||||
</data>
|
||||
<data name="Authority.HelpText" xml:space="preserve">
|
||||
<value>The Authority Url or Issuer Url associated with the OpenID Connect provider</value>
|
||||
<value>The authority url or issuer url associated with the identity provider</value>
|
||||
</data>
|
||||
<data name="Authority.Text" xml:space="preserve">
|
||||
<value>Authority:</value>
|
||||
</data>
|
||||
<data name="AuthorizationUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining an Authorization Code</value>
|
||||
<value>The endpoint for obtaining an authorization code</value>
|
||||
</data>
|
||||
<data name="AuthorizationUrl.Text" xml:space="preserve">
|
||||
<value>Authorization Url:</value>
|
||||
</data>
|
||||
<data name="ClientID.HelpText" xml:space="preserve">
|
||||
<value>The Client ID from the provider</value>
|
||||
<value>The client id for the identity provider</value>
|
||||
</data>
|
||||
<data name="ClientID.Text" xml:space="preserve">
|
||||
<value>Client ID:</value>
|
||||
</data>
|
||||
<data name="ClientSecret.HelpText" xml:space="preserve">
|
||||
<value>The Client Secret from the provider</value>
|
||||
<value>The client secret for the identity provider</value>
|
||||
</data>
|
||||
<data name="ClientSecret.Text" xml:space="preserve">
|
||||
<value>Client Secret:</value>
|
||||
@ -247,7 +247,7 @@
|
||||
<value>Domain Filter:</value>
|
||||
</data>
|
||||
<data name="EmailClaimType.HelpText" xml:space="preserve">
|
||||
<value>The name of the email address claim provided by the provider</value>
|
||||
<value>The name of the email address claim provided by the identity provider</value>
|
||||
</data>
|
||||
<data name="EmailClaimType.Text" xml:space="preserve">
|
||||
<value>Email Claim:</value>
|
||||
@ -259,7 +259,7 @@
|
||||
<value>Lockout Settings</value>
|
||||
</data>
|
||||
<data name="MetadataUrl.HelpText" xml:space="preserve">
|
||||
<value>The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)</value>
|
||||
<value>The discovery endpoint for obtaining metadata for this identity provider. Only specify if the identity provider does not use the standard approach (ie. /.well-known/openid-configuration)</value>
|
||||
</data>
|
||||
<data name="MetadataUrl.Text" xml:space="preserve">
|
||||
<value>Metadata Url:</value>
|
||||
@ -268,7 +268,7 @@
|
||||
<value>Password Settings</value>
|
||||
</data>
|
||||
<data name="PKCE.HelpText" xml:space="preserve">
|
||||
<value>Indicate if the provider supports Proof Key for Code Exchange (PKCE)</value>
|
||||
<value>Indicate if the identity provider supports proof key for code exchange (PKCE)</value>
|
||||
</data>
|
||||
<data name="PKCE.Text" xml:space="preserve">
|
||||
<value>Use PKCE?</value>
|
||||
@ -286,25 +286,25 @@
|
||||
<value>Provider Type:</value>
|
||||
</data>
|
||||
<data name="RedirectUrl.HelpText" xml:space="preserve">
|
||||
<value>The Redirect Url (or Callback Url) which usually needs to be registered with the provider</value>
|
||||
<value>The redirect url (or callback url) which usually needs to be registered with the identity provider</value>
|
||||
</data>
|
||||
<data name="RedirectUrl.Text" xml:space="preserve">
|
||||
<value>Redirect Url:</value>
|
||||
</data>
|
||||
<data name="Scopes.HelpText" xml:space="preserve">
|
||||
<value>A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default.</value>
|
||||
<value>A list of scopes to request from the identity provider (separated by commas). If none are specified, standard Scopes will be used by default.</value>
|
||||
</data>
|
||||
<data name="Scopes.Text" xml:space="preserve">
|
||||
<value>Scopes:</value>
|
||||
</data>
|
||||
<data name="TokenUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining an Auth Token</value>
|
||||
<value>The endpoint for obtaining an auth token</value>
|
||||
</data>
|
||||
<data name="TokenUrl.Text" xml:space="preserve">
|
||||
<value>Token Url:</value>
|
||||
</data>
|
||||
<data name="UserInfoUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address.</value>
|
||||
<value>The endpoint for obtaining user information. This should be an API endpoint or page url which contains the users email address.</value>
|
||||
</data>
|
||||
<data name="UserInfoUrl.Text" xml:space="preserve">
|
||||
<value>User Info Url:</value>
|
||||
@ -373,15 +373,21 @@
|
||||
<value>Last Login</value>
|
||||
</data>
|
||||
<data name="IdentifierClaimType.HelpText" xml:space="preserve">
|
||||
<value>The name of the unique user identifier claim provided by the provider</value>
|
||||
<value>The name of the unique user identifier claim provided by the identity provider</value>
|
||||
</data>
|
||||
<data name="IdentifierClaimType.Text" xml:space="preserve">
|
||||
<value>Identifier Claim:</value>
|
||||
</data>
|
||||
<data name="Parameters.HelpText" xml:space="preserve">
|
||||
<value>Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple).</value>
|
||||
<value>Optionally specify any additional parameters as name/value pairs to send to the identity provider (separated by commas if there are multiple).</value>
|
||||
</data>
|
||||
<data name="Parameters.Text" xml:space="preserve">
|
||||
<value>Parameters:</value>
|
||||
</data>
|
||||
<data name="RoleClaimType.HelpText" xml:space="preserve">
|
||||
<value>Optionally provide the name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site.</value>
|
||||
</data>
|
||||
<data name="RoleClaimType.Text" xml:space="preserve">
|
||||
<value>Role Claim Type:</value>
|
||||
</data>
|
||||
</root>
|
@ -123,4 +123,10 @@
|
||||
<data name="AllowFileManagement.Text" xml:space="preserve">
|
||||
<value>Allow File Management: </value>
|
||||
</data>
|
||||
<data name="AllowRawHtml.HelpText" xml:space="preserve">
|
||||
<value>Specify If Editors Can Enter Raw HTML</value>
|
||||
</data>
|
||||
<data name="AllowRawHtml.Text" xml:space="preserve">
|
||||
<value>Allow Raw HTML:</value>
|
||||
</data>
|
||||
</root>
|
@ -318,6 +318,9 @@
|
||||
<data name="BlazorWebAssembly" xml:space="preserve">
|
||||
<value>Blazor WebAssembly</value>
|
||||
</data>
|
||||
<data name="BlazorHybrid" xml:space="preserve">
|
||||
<value>Blazor Hybrid</value>
|
||||
</data>
|
||||
<data name="Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
@ -336,4 +339,7 @@
|
||||
<data name="Visitor Management" xml:space="preserve">
|
||||
<value>Visitor Management</value>
|
||||
</data>
|
||||
<data name="Oqtane.Marketplace" xml:space="preserve">
|
||||
<value>Please note that the third party extensions displayed above have been registered in the <a href="https://www.oqtane.net" target="_new">Oqtane Marketplace</a> which enables them to be seamlessly downloaded and installed into the framework.</value>
|
||||
</data>
|
||||
</root>
|
@ -1,10 +1,8 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Shared;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using Oqtane.Documentation;
|
||||
@ -20,9 +18,7 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task<List<Folder>> GetFoldersAsync(int siteId)
|
||||
{
|
||||
List<Folder> folders = await GetJsonAsync<List<Folder>>($"{ApiUrl}?siteid={siteId}");
|
||||
folders = GetFoldersHierarchy(folders);
|
||||
return folders;
|
||||
return await GetJsonAsync<List<Folder>>($"{ApiUrl}?siteid={siteId}");
|
||||
}
|
||||
|
||||
public async Task<Folder> GetFolderAsync(int folderId)
|
||||
@ -58,48 +54,5 @@ namespace Oqtane.Services
|
||||
{
|
||||
await DeleteAsync($"{ApiUrl}/{folderId}");
|
||||
}
|
||||
|
||||
private static List<Folder> GetFoldersHierarchy(List<Folder> folders)
|
||||
{
|
||||
List<Folder> hierarchy = new List<Folder>();
|
||||
Action<List<Folder>, Folder> getPath = null;
|
||||
var folders1 = folders;
|
||||
getPath = (folderList, folder) =>
|
||||
{
|
||||
IEnumerable<Folder> children;
|
||||
int level;
|
||||
if (folder == null)
|
||||
{
|
||||
level = -1;
|
||||
children = folders1.Where(item => item.ParentId == null);
|
||||
}
|
||||
else
|
||||
{
|
||||
level = folder.Level;
|
||||
children = folders1.Where(item => item.ParentId == folder.FolderId);
|
||||
}
|
||||
|
||||
foreach (Folder child in children)
|
||||
{
|
||||
child.Level = level + 1;
|
||||
child.HasChildren = folders1.Any(item => item.ParentId == child.FolderId);
|
||||
hierarchy.Add(child);
|
||||
if (getPath != null) getPath(folderList, child);
|
||||
}
|
||||
};
|
||||
folders = folders.OrderBy(item => item.Order).ToList();
|
||||
getPath(folders, null);
|
||||
|
||||
// add any non-hierarchical items to the end of the list
|
||||
foreach (Folder folder in folders)
|
||||
{
|
||||
if (hierarchy.Find(item => item.FolderId == folder.FolderId) == null)
|
||||
{
|
||||
hierarchy.Add(folder);
|
||||
}
|
||||
}
|
||||
|
||||
return hierarchy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,13 +50,13 @@ namespace Oqtane.Services
|
||||
/// <param name="moduleId"></param>
|
||||
/// <param name="content">module in JSON format</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> ImportModuleAsync(int moduleId, string content);
|
||||
Task<bool> ImportModuleAsync(int moduleId, int pageId, string content);
|
||||
|
||||
/// <summary>
|
||||
/// Exports a given module
|
||||
/// </summary>
|
||||
/// <param name="moduleId"></param>
|
||||
/// <returns>module in JSON</returns>
|
||||
Task<string> ExportModuleAsync(int moduleId);
|
||||
Task<string> ExportModuleAsync(int moduleId, int pageId);
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +54,10 @@ namespace Oqtane.Services
|
||||
/// Note that this will probably not be a real User, but a user object where the `Username` and `Password` have been filled.
|
||||
/// </summary>
|
||||
/// <param name="user">A <see cref="User"/> object which should have at least the <see cref="User.Username"/> and <see cref="User.Password"/> set.</param>
|
||||
/// <param name="setCookie">Determines if the login cookie should be set (only relevant for Hybrid scenarios)</param>
|
||||
/// <param name="isPersistent">Determines if the login cookie should be persisted for a long time.</param>
|
||||
/// <returns></returns>
|
||||
Task<User> LoginUserAsync(User user);
|
||||
Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent);
|
||||
|
||||
/// <summary>
|
||||
/// Logout a <see cref="User"/>
|
||||
|
@ -5,6 +5,7 @@ using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Modules.Controls;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@ -44,14 +45,14 @@ namespace Oqtane.Services
|
||||
await DeleteAsync($"{Apiurl}/{moduleId.ToString()}");
|
||||
}
|
||||
|
||||
public async Task<bool> ImportModuleAsync(int moduleId, string content)
|
||||
public async Task<bool> ImportModuleAsync(int moduleId, int pageId, string content)
|
||||
{
|
||||
return await PostJsonAsync<string,bool>($"{Apiurl}/import?moduleid={moduleId}", content);
|
||||
return await PostJsonAsync<string,bool>($"{Apiurl}/import?moduleid={moduleId}&pageid={pageId}", content);
|
||||
}
|
||||
|
||||
public async Task<string> ExportModuleAsync(int moduleId)
|
||||
public async Task<string> ExportModuleAsync(int moduleId, int pageId)
|
||||
{
|
||||
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}");
|
||||
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Shared;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Oqtane.Documentation;
|
||||
|
||||
@ -19,9 +17,7 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task<List<Page>> GetPagesAsync(int siteId)
|
||||
{
|
||||
List<Page> pages = await GetJsonAsync<List<Page>>($"{Apiurl}?siteid={siteId}");
|
||||
pages = GetPagesHierarchy(pages);
|
||||
return pages;
|
||||
return await GetJsonAsync<List<Page>>($"{Apiurl}?siteid={siteId}");
|
||||
}
|
||||
|
||||
public async Task<Page> GetPageAsync(int pageId)
|
||||
@ -73,45 +69,5 @@ namespace Oqtane.Services
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{pageId}");
|
||||
}
|
||||
|
||||
private static List<Page> GetPagesHierarchy(List<Page> pages)
|
||||
{
|
||||
List<Page> hierarchy = new List<Page>();
|
||||
Action<List<Page>, Page> getPath = null;
|
||||
getPath = (pageList, page) =>
|
||||
{
|
||||
IEnumerable<Page> children;
|
||||
int level;
|
||||
if (page == null)
|
||||
{
|
||||
level = -1;
|
||||
children = pages.Where(item => item.ParentId == null);
|
||||
}
|
||||
else
|
||||
{
|
||||
level = page.Level;
|
||||
children = pages.Where(item => item.ParentId == page.PageId);
|
||||
}
|
||||
foreach (Page child in children)
|
||||
{
|
||||
child.Level = level + 1;
|
||||
child.HasChildren = pages.Any(item => item.ParentId == child.PageId);
|
||||
hierarchy.Add(child);
|
||||
getPath(pageList, child);
|
||||
}
|
||||
};
|
||||
pages = pages.OrderBy(item => item.Order).ToList();
|
||||
getPath(pages, null);
|
||||
|
||||
// add any non-hierarchical items to the end of the list
|
||||
foreach (Page page in pages)
|
||||
{
|
||||
if (hierarchy.Find(item => item.PageId == page.PageId) == null)
|
||||
{
|
||||
hierarchy.Add(page);
|
||||
}
|
||||
}
|
||||
return hierarchy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ namespace Oqtane.Services
|
||||
await DeleteAsync($"{Apiurl}/{userId}?siteid={siteId}");
|
||||
}
|
||||
|
||||
public async Task<User> LoginUserAsync(User user)
|
||||
public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent)
|
||||
{
|
||||
return await PostJsonAsync<User>($"{Apiurl}/login", user);
|
||||
return await PostJsonAsync<User>($"{Apiurl}/login?setcookie={setCookie}&persistent={isPersistent}", user);
|
||||
}
|
||||
|
||||
public async Task LogoutUserAsync(User user)
|
||||
|
@ -21,7 +21,7 @@
|
||||
@code {
|
||||
private void CloseModal()
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
NavigationManager.NavigateTo((!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,9 @@
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", Integrity = "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", Integrity = "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", Integrity = "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", CrossOrigin = "anonymous" }
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" }
|
||||
};
|
||||
|
||||
}
|
@ -2,9 +2,9 @@
|
||||
@inherits ModuleActionsBase
|
||||
@attribute [OqtaneIgnore]
|
||||
|
||||
@if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions) && PageState.Action == Constants.DefaultAction)
|
||||
@if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) && PageState.Action == Constants.DefaultAction)
|
||||
{
|
||||
<div class="app-moduleactions">
|
||||
<div class="app-moduleactions py-2 px-3">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
|
||||
<ul class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);">
|
||||
@foreach (var action in Actions.Where(item => !item.Name.Contains("Pane")))
|
||||
|
@ -29,11 +29,12 @@ namespace Oqtane.Themes.Controls
|
||||
protected virtual List<ActionViewModel> GetActions()
|
||||
{
|
||||
var actionList = new List<ActionViewModel>();
|
||||
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.Permissions))
|
||||
|
||||
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = "Manage Settings", Action = async (u, m) => await Settings(u, m) });
|
||||
|
||||
if (UserSecurity.GetPermissionStrings(ModuleState.Permissions).FirstOrDefault(item => item.PermissionName == PermissionNames.View).Permissions.Split(';').Contains(RoleNames.Everyone))
|
||||
if (UserSecurity.ContainsRole(ModuleState.Permissions, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = "Unpublish Module", Action = async (s, m) => await Unpublish(s, m) });
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
@using System.ComponentModel
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@inherits ContainerBase
|
||||
@attribute [OqtaneIgnore]
|
||||
@inject SiteState SiteState
|
||||
|
||||
<span class="app-moduletitle">
|
||||
<a id="@ModuleState.PageModuleId.ToString()">
|
||||
@ -11,6 +13,11 @@
|
||||
@code {
|
||||
private string title = "";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ModuleState.ControlTitle))
|
||||
@ -22,4 +29,21 @@
|
||||
title = ModuleState.Title;
|
||||
}
|
||||
}
|
||||
|
||||
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "ModuleTitle")
|
||||
{
|
||||
if (SiteState.Properties.ModuleTitle.PageModuleId == ModuleState.PageModuleId)
|
||||
{
|
||||
title = SiteState.Properties.ModuleTitle.Title;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged -= PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@
|
||||
</div>
|
||||
<div class="row d-flex">
|
||||
<div class="col">
|
||||
@if (UserSecurity.GetPermissionStrings(PageState.Page.Permissions).FirstOrDefault(item => item.PermissionName == PermissionNames.View).Permissions.Split(';').Contains(RoleNames.Everyone))
|
||||
@if (UserSecurity.ContainsRole(PageState.Page.Permissions, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
|
||||
}
|
||||
@ -576,7 +576,21 @@
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
_category = SettingService.GetSetting(settings, settingCategory, "Common");
|
||||
var pane = SettingService.GetSetting(settings, settingPane, "");
|
||||
_pane = PageState.Page.Panes.Contains(pane) ? pane : PaneNames.Admin;
|
||||
if (PageState.Page.Panes.Contains(pane))
|
||||
{
|
||||
_pane = pane;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
|
||||
{
|
||||
_pane = PaneNames.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
_pane = PaneNames.Admin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateSettingsAsync()
|
||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Providers;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
@ -40,6 +41,16 @@ namespace Oqtane.Themes.Controls
|
||||
url = PageState.Alias.Path;
|
||||
}
|
||||
|
||||
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive logout
|
||||
await UserService.LogoutUserAsync(PageState.User);
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// post to the Logout page to complete the logout process
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url };
|
||||
var interop = new Interop(jsRuntime);
|
||||
@ -47,3 +58,4 @@ namespace Oqtane.Themes.Controls
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<Pane Name="@PaneNames.Admin" />
|
||||
<Pane Name="@PaneNames.Default" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -108,14 +108,14 @@
|
||||
@code {
|
||||
public override string Name => "Default Theme";
|
||||
|
||||
public override string Panes => PaneNames.Admin + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer";
|
||||
public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer";
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.1.3/cyborg/bootstrap.min.css", Integrity = "sha512-/in5IWTUhb7wOUd6iHotlyrLrZ7+2utJJR8ySzSxeeOMJ9fanjCr4fmyWzDW/ziw56shUNTVClBMWZaA677VhA==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.0/cyborg/bootstrap.min.css", Integrity = "sha512-d6pZJl/sNcj0GFkp4kTjXtPE14deuUsOqFQtxkj0KyBJQl+4e0qsEyuIDcNqrYuGoauAW3sWyDCQp49mhF4Syw==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", Integrity = "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", CrossOrigin = "anonymous" }
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" }
|
||||
};
|
||||
|
||||
private bool _login = true;
|
||||
|
@ -3,6 +3,7 @@ using Microsoft.JSInterop;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -34,7 +35,8 @@ namespace Oqtane.Themes
|
||||
var scripts = new List<object>();
|
||||
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
|
||||
{
|
||||
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
}
|
||||
if (scripts.Any())
|
||||
{
|
||||
@ -49,7 +51,7 @@ namespace Oqtane.Themes
|
||||
|
||||
public string ThemePath()
|
||||
{
|
||||
return "Themes/" + GetType().Namespace + "/";
|
||||
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
|
||||
}
|
||||
|
||||
// url methods
|
||||
@ -94,14 +96,23 @@ namespace Oqtane.Themes
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid)
|
||||
public string FileUrl(string folderpath, string filename)
|
||||
{
|
||||
return Utilities.ContentUrl(PageState.Alias, fileid);
|
||||
return FileUrl(folderpath, filename, false);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
public string FileUrl(string folderpath, string filename, bool download)
|
||||
{
|
||||
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
|
||||
return Utilities.FileUrl(PageState.Alias, folderpath, filename, download);
|
||||
}
|
||||
public string FileUrl(int fileid)
|
||||
{
|
||||
return FileUrl(fileid, false);
|
||||
}
|
||||
|
||||
public string FileUrl(int fileid, bool download)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
@ -118,5 +129,17 @@ namespace Oqtane.Themes
|
||||
{
|
||||
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
|
||||
public string ContentUrl(int fileid)
|
||||
{
|
||||
return ContentUrl(fileid, false);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)]
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
@using System.ComponentModel
|
||||
@namespace Oqtane.UI
|
||||
@inject SiteState SiteState
|
||||
|
||||
@if (_visible)
|
||||
{
|
||||
<CascadingValue Value="@ModuleState">
|
||||
@if (_useadminborder)
|
||||
{
|
||||
@ -12,8 +16,10 @@
|
||||
@DynamicComponent
|
||||
}
|
||||
</CascadingValue>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _visible = true;
|
||||
private bool _useadminborder = false;
|
||||
|
||||
[CascadingParameter]
|
||||
@ -24,6 +30,11 @@
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
string container = ModuleState.ContainerType;
|
||||
@ -53,4 +64,21 @@
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
|
||||
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "ModuleVisibility")
|
||||
{
|
||||
if (SiteState.Properties.ModuleVisibility.PageModuleId == ModuleState.PageModuleId)
|
||||
{
|
||||
_visible = SiteState.Properties.ModuleVisibility.Visible;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged -= PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
@ -293,5 +296,91 @@ namespace Oqtane.UI
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<int> GetCaretPosition(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<int>(
|
||||
"Oqtane.Interop.getCaretPosition",
|
||||
id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<int>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SetIndexedDBItem(string key, object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeVoidAsync(
|
||||
"Oqtane.Interop.manageIndexedDBItems",
|
||||
"put", key, value);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> GetIndexedDBItem<T>(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _jsRuntime.InvokeAsync<T>(
|
||||
"Oqtane.Interop.manageIndexedDBItems",
|
||||
"get", key, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetIndexedDBKeys()
|
||||
{
|
||||
return await GetIndexedDBKeys("");
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetIndexedDBKeys(string contains)
|
||||
{
|
||||
try
|
||||
{
|
||||
var items = await _jsRuntime.InvokeAsync<JsonDocument>(
|
||||
"Oqtane.Interop.manageIndexedDBItems",
|
||||
"getallkeys", null, null);
|
||||
if (!string.IsNullOrEmpty(contains))
|
||||
{
|
||||
return items.Deserialize<List<string>>()
|
||||
.Where(item => item.Contains(contains)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return items.Deserialize<List<string>>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public Task RemoveIndexedDBItem(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeVoidAsync(
|
||||
"Oqtane.Interop.manageIndexedDBItems",
|
||||
"delete", key, null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.UI
|
||||
@ -8,11 +10,8 @@ namespace Oqtane.UI
|
||||
{
|
||||
public Alias Alias { get; set; }
|
||||
public Site Site { get; set; }
|
||||
public List<Language> Languages { get; set; }
|
||||
public List<Page> Pages { get; set; }
|
||||
public Page Page { get; set; }
|
||||
public User User { get; set; }
|
||||
public List<Module> Modules { get; set; }
|
||||
public Uri Uri { get; set; }
|
||||
public Dictionary<string, string> QueryString { get; set; }
|
||||
public string UrlParameters { get; set; }
|
||||
@ -20,8 +19,22 @@ namespace Oqtane.UI
|
||||
public string Action { get; set; }
|
||||
public bool EditMode { get; set; }
|
||||
public DateTime LastSyncDate { get; set; }
|
||||
public Oqtane.Shared.Runtime Runtime { get; set; }
|
||||
public Shared.Runtime Runtime { get; set; }
|
||||
public int VisitorId { get; set; }
|
||||
public string RemoteIPAddress { get; set; }
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
public List<Page> Pages
|
||||
{
|
||||
get { return Site.Pages.Where(item => !item.IsDeleted).ToList(); }
|
||||
}
|
||||
public List<Module> Modules
|
||||
{
|
||||
get { return Site.Modules.Where(item => !item.IsDeleted).ToList(); }
|
||||
}
|
||||
public List<Language> Languages
|
||||
{
|
||||
get { return Site.Languages; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,18 @@ else
|
||||
{
|
||||
if (PageState.ModuleId != -1 && PageState.Action != Constants.DefaultAction)
|
||||
{
|
||||
if (Name.ToLower() == PaneNames.Admin.ToLower())
|
||||
// action route needs to inject module control into specific pane
|
||||
string pane = "";
|
||||
if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
|
||||
{
|
||||
pane = PaneNames.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
pane = PaneNames.Admin;
|
||||
|
||||
}
|
||||
if (Name.ToLower() == pane.ToLower())
|
||||
{
|
||||
Module module = PageState.Modules.FirstOrDefault(item => item.ModuleId == PageState.ModuleId);
|
||||
if (module != null)
|
||||
|
@ -1,10 +0,0 @@
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
public enum Refresh
|
||||
{
|
||||
None,
|
||||
Page,
|
||||
Site,
|
||||
Application
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
@using System.Diagnostics.CodeAnalysis
|
||||
@using System.Net
|
||||
@namespace Oqtane.UI
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject SiteState SiteState
|
||||
@ -6,21 +7,25 @@
|
||||
@inject INavigationInterception NavigationInterception
|
||||
@inject ISyncService SyncService
|
||||
@inject ISiteService SiteService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IPageService PageService
|
||||
@inject IUserService UserService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IUrlMappingService UrlMappingService
|
||||
@inject ILogService LogService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@implements IHandleAfterRender
|
||||
|
||||
@if (!string.IsNullOrEmpty(_error))
|
||||
{
|
||||
<ModuleMessage Message="@_error" Type="@MessageType.Warning" />
|
||||
}
|
||||
|
||||
@DynamicComponent
|
||||
|
||||
@code {
|
||||
private string _absoluteUri;
|
||||
private bool _navigationInterceptionEnabled;
|
||||
private PageState _pagestate;
|
||||
private string _error = "";
|
||||
|
||||
[Parameter]
|
||||
public string Runtime { get; set; }
|
||||
@ -71,20 +76,23 @@
|
||||
private async Task Refresh()
|
||||
{
|
||||
Site site;
|
||||
List<Language> languages;
|
||||
List<Page> pages;
|
||||
Page page;
|
||||
User user = null;
|
||||
List<Module> modules;
|
||||
var editmode = false;
|
||||
var refresh = UI.Refresh.None;
|
||||
var refresh = false;
|
||||
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
|
||||
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
|
||||
_error = "";
|
||||
|
||||
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
|
||||
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1;
|
||||
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
|
||||
var querystring = ParseQueryString(route.Query);
|
||||
var returnurl = "";
|
||||
if (querystring.ContainsKey("returnurl"))
|
||||
{
|
||||
returnurl = WebUtility.UrlDecode(querystring["returnurl"]);
|
||||
}
|
||||
|
||||
// reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias
|
||||
if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
|
||||
@ -108,7 +116,7 @@
|
||||
// the refresh parameter is used to refresh the client-side PageState
|
||||
if (querystring.ContainsKey("refresh"))
|
||||
{
|
||||
refresh = UI.Refresh.Site;
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
if (PageState != null)
|
||||
@ -117,42 +125,13 @@
|
||||
lastsyncdate = PageState.LastSyncDate;
|
||||
}
|
||||
|
||||
// process any sync events
|
||||
var sync = await SyncService.GetSyncAsync(lastsyncdate);
|
||||
lastsyncdate = sync.SyncDate;
|
||||
if (sync.SyncEvents.Any())
|
||||
{
|
||||
// reload client application if server was restarted or site runtime/rendermode was modified
|
||||
if (PageState != null && sync.SyncEvents.Exists(item => (item.TenantId == -1 || item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId) && item.Reload))
|
||||
{
|
||||
NavigationManager.NavigateTo(_absoluteUri, true);
|
||||
return;
|
||||
}
|
||||
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
|
||||
{
|
||||
refresh = UI.Refresh.Site;
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh == UI.Refresh.Site || PageState == null || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
{
|
||||
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
|
||||
refresh = UI.Refresh.Site;
|
||||
}
|
||||
else
|
||||
{
|
||||
site = PageState.Site;
|
||||
}
|
||||
|
||||
if (site != null)
|
||||
{
|
||||
if (PageState == null || refresh == UI.Refresh.Site)
|
||||
{
|
||||
// get user
|
||||
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity.IsAuthenticated)
|
||||
{
|
||||
user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId);
|
||||
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
|
||||
@ -164,81 +143,82 @@
|
||||
user = PageState.User;
|
||||
}
|
||||
|
||||
// process any sync events for user
|
||||
if (refresh != UI.Refresh.Site && user != null && sync.SyncEvents.Any())
|
||||
// process any sync events
|
||||
var sync = await SyncService.GetSyncAsync(lastsyncdate);
|
||||
lastsyncdate = sync.SyncDate;
|
||||
if (sync.SyncEvents.Any())
|
||||
{
|
||||
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
|
||||
// reload client application if server was restarted or site runtime/rendermode was modified
|
||||
if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload)))
|
||||
{
|
||||
refresh = UI.Refresh.Site;
|
||||
NavigationManager.NavigateTo(_absoluteUri, true);
|
||||
return;
|
||||
}
|
||||
// when site information has changed the PageState needs to be refreshed
|
||||
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
|
||||
{
|
||||
refresh = true;
|
||||
}
|
||||
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
|
||||
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
|
||||
{
|
||||
refresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (PageState == null || refresh == UI.Refresh.Site)
|
||||
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
{
|
||||
languages = await LanguageService.GetLanguagesAsync(site.SiteId);
|
||||
pages = await PageService.GetPagesAsync(site.SiteId);
|
||||
pages = pages.Where(item => !item.IsDeleted).ToList();
|
||||
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
|
||||
refresh = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
languages = PageState.Languages;
|
||||
pages = PageState.Pages;
|
||||
site = PageState.Site;
|
||||
}
|
||||
|
||||
if (PageState == null || refresh == UI.Refresh.Site)
|
||||
if (site != null)
|
||||
{
|
||||
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
|
||||
{
|
||||
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
editmode = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
page = PageState.Page;
|
||||
}
|
||||
|
||||
// get the page if the path has changed
|
||||
if (page == null || page.Path != route.PagePath)
|
||||
if (page == null && route.PagePath == "") // naked path refers to site home page
|
||||
{
|
||||
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
// if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify the home page)
|
||||
if (page == null && route.PagePath == "")
|
||||
if (site.HomePageId != null)
|
||||
{
|
||||
page = pages.FirstOrDefault();
|
||||
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
|
||||
}
|
||||
if (page == null)
|
||||
{
|
||||
// fallback to use the first page in the collection
|
||||
page = site.Pages.FirstOrDefault();
|
||||
}
|
||||
editmode = false;
|
||||
}
|
||||
|
||||
if (page != null)
|
||||
{
|
||||
if (PageState == null)
|
||||
{
|
||||
editmode = false;
|
||||
}
|
||||
|
||||
// check if user is authorized to view page
|
||||
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions))
|
||||
{
|
||||
// load additional metadata for current page
|
||||
page = await ProcessPage(page, site, user);
|
||||
|
||||
if (PageState == null || refresh == UI.Refresh.Site)
|
||||
{
|
||||
modules = await ModuleService.GetModulesAsync(site.SiteId);
|
||||
modules = modules.Where(item => !item.IsDeleted).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
modules = PageState.Modules;
|
||||
}
|
||||
|
||||
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
|
||||
// load additional metadata for modules
|
||||
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
|
||||
|
||||
// populate page state (which acts as a client-side cache for subsequent requests)
|
||||
_pagestate = new PageState
|
||||
{
|
||||
Alias = SiteState.Alias,
|
||||
Site = site,
|
||||
Languages = languages,
|
||||
Pages = pages,
|
||||
Page = page,
|
||||
User = user,
|
||||
Modules = modules,
|
||||
Uri = new Uri(_absoluteUri, UriKind.Absolute),
|
||||
QueryString = querystring,
|
||||
UrlParameters = route.UrlParameters,
|
||||
@ -248,7 +228,8 @@
|
||||
LastSyncDate = lastsyncdate,
|
||||
Runtime = runtime,
|
||||
VisitorId = VisitorId,
|
||||
RemoteIPAddress = SiteState.RemoteIPAddress
|
||||
RemoteIPAddress = SiteState.RemoteIPAddress,
|
||||
ReturnUrl = returnurl
|
||||
};
|
||||
|
||||
OnStateChange?.Invoke(_pagestate);
|
||||
@ -355,7 +336,7 @@
|
||||
page.Panes = new List<string>();
|
||||
page.Resources = new List<Resource>();
|
||||
|
||||
string panes = PaneNames.Admin;
|
||||
string panes = "";
|
||||
Type themetype = Type.GetType(page.ThemeType);
|
||||
if (themetype == null)
|
||||
{
|
||||
@ -375,7 +356,19 @@
|
||||
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(panes))
|
||||
{
|
||||
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin))
|
||||
{
|
||||
_error = "The Current Theme Does Not Contain A Default Or Admin Pane";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
page.Panes.Add(PaneNames.Admin);
|
||||
_error = "The Current Theme Does Not Contain Any Panes";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -467,11 +460,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ensure module's pane exists in current page and if not, assign it to the Admin pane
|
||||
if (page.Panes == null || page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
|
||||
// validate that module's pane exists in current page
|
||||
if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
|
||||
{
|
||||
// fallback to default pane if it exists
|
||||
if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
|
||||
{
|
||||
module.Pane = PaneNames.Default;
|
||||
}
|
||||
else // otherwise admin pane (legacy)
|
||||
{
|
||||
module.Pane = PaneNames.Admin;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate module position within pane
|
||||
if (paneindex.ContainsKey(module.Pane.ToLower()))
|
||||
|
@ -36,7 +36,8 @@
|
||||
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
|
||||
{
|
||||
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower();
|
||||
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
|
||||
}
|
||||
if (links.Any())
|
||||
{
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.1.4</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
<Description>Modular Application Framework for Blazor</Description>
|
||||
<Description>Modular Application Framework for Blazor and MAUI</Description>
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.MySQL</id>
|
||||
<version>3.1.4</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane MySQL Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.1.4</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
<Description>Modular Application Framework for Blazor</Description>
|
||||
<Description>Modular Application Framework for Blazor and MAUI</Description>
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.PostgreSQL</id>
|
||||
<version>3.1.4</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane PostgreSQL Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.1.4</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
<Description>Modular Application Framework for Blazor</Description>
|
||||
<Description>Modular Application Framework for Blazor and MAUI</Description>
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.SqlServer</id>
|
||||
<version>3.1.4</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane SQL Server Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.1.4</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
<Description>Modular Application Framework for Blazor</Description>
|
||||
<Description>Modular Application Framework for Blazor and MAUI</Description>
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.Sqlite</id>
|
||||
<version>3.1.4</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane SQLite Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
26
Oqtane.Maui.sln
Normal file
26
Oqtane.Maui.sln
Normal file
@ -0,0 +1,26 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31611.283
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Maui", "Oqtane.Maui\Oqtane.Maui.csproj", "{5EE64148-2152-4908-A3E7-658EB1D87754}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5EE64148-2152-4908-A3E7-658EB1D87754}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
26
Oqtane.Maui/App.xaml
Normal file
26
Oqtane.Maui/App.xaml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Oqtane.Maui"
|
||||
x:Class="Oqtane.Maui.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<Color x:Key="PageBackgroundColor">#512bdf</Color>
|
||||
<Color x:Key="PrimaryTextColor">White</Color>
|
||||
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="TextColor" Value="{DynamicResource PrimaryTextColor}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="TextColor" Value="{DynamicResource PrimaryTextColor}" />
|
||||
<Setter Property="FontFamily" Value="OpenSansRegular" />
|
||||
<Setter Property="BackgroundColor" Value="#2b0b98" />
|
||||
<Setter Property="Padding" Value="14,10" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
11
Oqtane.Maui/App.xaml.cs
Normal file
11
Oqtane.Maui/App.xaml.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new MainPage();
|
||||
}
|
||||
}
|
18
Oqtane.Maui/Main.razor
Normal file
18
Oqtane.Maui/Main.razor
Normal file
@ -0,0 +1,18 @@
|
||||
<DynamicComponent Type="@ComponentType" Parameters="@Parameters"></DynamicComponent>
|
||||
|
||||
@code {
|
||||
Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client");
|
||||
private IDictionary<string, object> Parameters { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Parameters = new Dictionary<string, object>();
|
||||
Parameters.Add(new KeyValuePair<string, object>("AntiForgeryToken", ""));
|
||||
Parameters.Add(new KeyValuePair<string, object>("Runtime", "Hybrid"));
|
||||
Parameters.Add(new KeyValuePair<string, object>("RenderMode", "Hybrid"));
|
||||
Parameters.Add(new KeyValuePair<string, object>("VisitorId", -1));
|
||||
Parameters.Add(new KeyValuePair<string, object>("RemoteIPAddress", ""));
|
||||
Parameters.Add(new KeyValuePair<string, object>("AuthorizationToken", ""));
|
||||
}
|
||||
}
|
||||
|
14
Oqtane.Maui/MainPage.xaml
Normal file
14
Oqtane.Maui/MainPage.xaml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Oqtane.Maui"
|
||||
x:Class="Oqtane.Maui.MainPage"
|
||||
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||
|
||||
<BlazorWebView HostPage="wwwroot/index.html">
|
||||
<BlazorWebView.RootComponents>
|
||||
<RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
|
||||
</BlazorWebView.RootComponents>
|
||||
</BlazorWebView>
|
||||
|
||||
</ContentPage>
|
9
Oqtane.Maui/MainPage.xaml.cs
Normal file
9
Oqtane.Maui/MainPage.xaml.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
234
Oqtane.Maui/MauiProgram.cs
Normal file
234
Oqtane.Maui/MauiProgram.cs
Normal file
@ -0,0 +1,234 @@
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Diagnostics;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Services;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
public static class MauiProgram
|
||||
{
|
||||
// the API service url
|
||||
static string apiurl = "https://www.oqtane.org"; // for testing
|
||||
//static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
|
||||
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
});
|
||||
|
||||
builder.Services.AddMauiBlazorWebView();
|
||||
#if DEBUG
|
||||
builder.Services.AddBlazorWebViewDeveloperTools();
|
||||
#endif
|
||||
|
||||
var httpClient = new HttpClient { BaseAddress = new Uri(apiurl) };
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Shared.Constants.MauiUserAgent);
|
||||
builder.Services.AddSingleton(httpClient);
|
||||
builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
|
||||
|
||||
// dynamically load client assemblies
|
||||
LoadClientAssemblies(httpClient);
|
||||
|
||||
// register localization services
|
||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||
|
||||
// register auth services
|
||||
builder.Services.AddOqtaneAuthorization();
|
||||
|
||||
// register scoped core services
|
||||
builder.Services.AddOqtaneScopedServices();
|
||||
|
||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
// dynamically register module services
|
||||
RegisterModuleServices(assembly, builder.Services);
|
||||
|
||||
// register client startup services
|
||||
RegisterClientStartups(assembly, builder.Services);
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private static void LoadClientAssemblies(HttpClient http)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ensure local assembly folder exists
|
||||
string folder = Path.Combine(FileSystem.Current.AppDataDirectory, "oqtane");
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
|
||||
var dlls = new Dictionary<string, byte[]>();
|
||||
var pdbs = new Dictionary<string, byte[]>();
|
||||
var list = new List<string>();
|
||||
|
||||
var files = new List<string>();
|
||||
foreach (var file in Directory.EnumerateFiles(folder, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
files.Add(file.Substring(folder.Length + 1).Replace("\\", "/"));
|
||||
}
|
||||
|
||||
if (files.Count() != 0)
|
||||
{
|
||||
// get list of assemblies from server
|
||||
var json = Task.Run(() => http.GetStringAsync("/api/Installation/list")).GetAwaiter().GetResult();
|
||||
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
|
||||
|
||||
// determine which assemblies need to be downloaded
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var file = files.FirstOrDefault(item => item.Contains(assembly));
|
||||
if (file == null)
|
||||
{
|
||||
list.Add(assembly);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if newer version available
|
||||
if (GetFileDate(assembly) > GetFileDate(file))
|
||||
{
|
||||
list.Add(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get assemblies already downloaded
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (assemblies.Contains(file) && !list.Contains(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
dlls.Add(file, File.ReadAllBytes(Path.Combine(folder, file)));
|
||||
var pdb = file.Replace(".dll", ".pdb");
|
||||
if (File.Exists(Path.Combine(folder, pdb)))
|
||||
{
|
||||
pdbs.Add(pdb, File.ReadAllBytes(Path.Combine(folder, pdb)));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
else // file is deprecated
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(folder, Path.GetFileNameWithoutExtension(file) + ".*"))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add("*");
|
||||
}
|
||||
|
||||
if (list.Count != 0)
|
||||
{
|
||||
// get assemblies from server
|
||||
var zip = Task.Run(() => http.GetByteArrayAsync("/api/Installation/load?list=" + string.Join(",", list))).GetAwaiter().GetResult();
|
||||
|
||||
// asemblies and debug symbols are packaged in a zip file
|
||||
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
entry.Open().CopyTo(memoryStream);
|
||||
byte[] file = memoryStream.ToArray();
|
||||
|
||||
// save assembly to local folder
|
||||
try
|
||||
{
|
||||
using var stream = File.Create(Path.Combine(folder, entry.FullName));
|
||||
stream.Write(file, 0, file.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (Path.GetExtension(entry.FullName) == ".dll")
|
||||
{
|
||||
dlls.Add(entry.FullName, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
pdbs.Add(entry.FullName, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load assemblies into app domain
|
||||
foreach (var item in dlls)
|
||||
{
|
||||
if (pdbs.ContainsKey(item.Key.Replace(".dll", ".pdb")))
|
||||
{
|
||||
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key.Replace(".dll", ".pdb")]));
|
||||
}
|
||||
else
|
||||
{
|
||||
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Oqtane Error: Loading Client Assemblies {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime GetFileDate(string filepath)
|
||||
{
|
||||
var segments = filepath.Split('.');
|
||||
return DateTime.ParseExact(segments[segments.Length - 2], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
|
||||
{
|
||||
// dynamically register module scoped services
|
||||
var implementationTypes = assembly.GetInterfaces<IService>();
|
||||
foreach (var implementationType in implementationTypes)
|
||||
{
|
||||
if (implementationType.AssemblyQualifiedName != null)
|
||||
{
|
||||
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
|
||||
services.AddScoped(serviceType ?? implementationType, implementationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterClientStartups(Assembly assembly, IServiceCollection services)
|
||||
{
|
||||
var startUps = assembly.GetInstances<IClientStartup>();
|
||||
foreach (var startup in startUps)
|
||||
{
|
||||
startup.ConfigureServices(services);
|
||||
}
|
||||
}
|
||||
}
|
86
Oqtane.Maui/Oqtane.Maui.csproj
Normal file
86
Oqtane.Maui/Oqtane.Maui.csproj
Normal file
@ -0,0 +1,86 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
<Description>Modular Application Framework for Blazor and MAUI</Description>
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Oqtane.Maui</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>3.2.1</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
<!-- Images -->
|
||||
<MauiImage Include="Resources\Images\*" />
|
||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
|
||||
|
||||
<!-- Custom Fonts -->
|
||||
<MauiFont Include="Resources\Fonts\*" />
|
||||
|
||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Platforms\Android\Resources\xml\network_security_config.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Oqtane.Client">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net6.0\Oqtane.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Oqtane.Shared">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net6.0\Oqtane.Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
6
Oqtane.Maui/Platforms/Android/AndroidManifest.xml
Normal file
6
Oqtane.Maui/Platforms/Android/AndroidManifest.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true" android:networkSecurityConfig="@xml/network_security_config"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
10
Oqtane.Maui/Platforms/Android/MainActivity.cs
Normal file
10
Oqtane.Maui/Platforms/Android/MainActivity.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
}
|
15
Oqtane.Maui/Platforms/Android/MainApplication.cs
Normal file
15
Oqtane.Maui/Platforms/Android/MainApplication.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
[Application]
|
||||
public class MainApplication : MauiApplication
|
||||
{
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
|
||||
: base(handle, ownership)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#512BD4</color>
|
||||
<color name="colorPrimaryDark">#2B0B98</color>
|
||||
<color name="colorAccent">#2B0B98</color>
|
||||
</resources>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
9
Oqtane.Maui/Platforms/MacCatalyst/AppDelegate.cs
Normal file
9
Oqtane.Maui/Platforms/MacCatalyst/AppDelegate.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
30
Oqtane.Maui/Platforms/MacCatalyst/Info.plist
Normal file
30
Oqtane.Maui/Platforms/MacCatalyst/Info.plist
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
15
Oqtane.Maui/Platforms/MacCatalyst/Program.cs
Normal file
15
Oqtane.Maui/Platforms/MacCatalyst/Program.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
16
Oqtane.Maui/Platforms/Tizen/Main.cs
Normal file
16
Oqtane.Maui/Platforms/Tizen/Main.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Hosting;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
class Program : MauiApplication
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var app = new Program();
|
||||
app.Run(args);
|
||||
}
|
||||
}
|
15
Oqtane.Maui/Platforms/Tizen/tizen-manifest.xml
Normal file
15
Oqtane.Maui/Platforms/Tizen/tizen-manifest.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.companyname.oqtane.maui" version="1.0.0" api-version="7" xmlns="http://tizen.org/ns/packages">
|
||||
<profile name="common" />
|
||||
<ui-application appid="com.companyname.oqtane.maui" exec="Oqtane.Maui.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
|
||||
<label>MauiApp1</label>
|
||||
<icon>appicon.xhigh.png</icon>
|
||||
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
|
||||
</ui-application>
|
||||
<shortcut-list />
|
||||
<privileges>
|
||||
<privilege>http://tizen.org/privilege/internet</privilege>
|
||||
</privileges>
|
||||
<dependencies />
|
||||
<provides-appdefined-privileges />
|
||||
</manifest>
|
8
Oqtane.Maui/Platforms/Windows/App.xaml
Normal file
8
Oqtane.Maui/Platforms/Windows/App.xaml
Normal file
@ -0,0 +1,8 @@
|
||||
<maui:MauiWinUIApplication
|
||||
x:Class="Oqtane.Maui.WinUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:maui="using:Microsoft.Maui"
|
||||
xmlns:local="using:Oqtane.Maui.WinUI">
|
||||
|
||||
</maui:MauiWinUIApplication>
|
24
Oqtane.Maui/Platforms/Windows/App.xaml.cs
Normal file
24
Oqtane.Maui/Platforms/Windows/App.xaml.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace Oqtane.Maui.WinUI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : MauiWinUIApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
||||
|
43
Oqtane.Maui/Platforms/Windows/Package.appxmanifest
Normal file
43
Oqtane.Maui/Platforms/Windows/Package.appxmanifest
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>$placeholder$</DisplayName>
|
||||
<PublisherDisplayName>User Name</PublisherDisplayName>
|
||||
<Logo>$placeholder$.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate" />
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="$placeholder$"
|
||||
Description="$placeholder$"
|
||||
Square150x150Logo="$placeholder$.png"
|
||||
Square44x44Logo="$placeholder$.png"
|
||||
BackgroundColor="transparent">
|
||||
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
|
||||
<uap:SplashScreen Image="$placeholder$.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
|
||||
</Package>
|
15
Oqtane.Maui/Platforms/Windows/app.manifest
Normal file
15
Oqtane.Maui/Platforms/Windows/app.manifest
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Oqtane.Maui.WinUI.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
9
Oqtane.Maui/Platforms/iOS/AppDelegate.cs
Normal file
9
Oqtane.Maui/Platforms/iOS/AppDelegate.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Foundation;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MauiUIApplicationDelegate
|
||||
{
|
||||
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
|
||||
}
|
32
Oqtane.Maui/Platforms/iOS/Info.plist
Normal file
32
Oqtane.Maui/Platforms/iOS/Info.plist
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/appicon.appiconset</string>
|
||||
</dict>
|
||||
</plist>
|
15
Oqtane.Maui/Platforms/iOS/Program.cs
Normal file
15
Oqtane.Maui/Platforms/iOS/Program.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Oqtane.Maui;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
8
Oqtane.Maui/Properties/launchSettings.json
Normal file
8
Oqtane.Maui/Properties/launchSettings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
4
Oqtane.Maui/Resources/AppIcon/appicon.svg
Normal file
4
Oqtane.Maui/Resources/AppIcon/appicon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||
</svg>
|
After Width: | Height: | Size: 228 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user