Aggiunto pèrogetto CORE LAND x rifare LAND site

This commit is contained in:
Samuele Locatelli
2021-09-17 19:14:45 +02:00
parent 1c11e03d5f
commit f0419fdf72
279 changed files with 107384 additions and 1 deletions
+31
View File
@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31229.75
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.FileData", "MP.FileData\MP.FileData.csproj", "{48693321-1FA6-4DBB-A730-B8EF3E0B68D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.Land", "MP.Land\MP.Land.csproj", "{D949AB45-9B65-4594-A97E-182BC3831707}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{48693321-1FA6-4DBB-A730-B8EF3E0B68D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48693321-1FA6-4DBB-A730-B8EF3E0B68D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48693321-1FA6-4DBB-A730-B8EF3E0B68D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48693321-1FA6-4DBB-A730-B8EF3E0B68D2}.Release|Any CPU.Build.0 = Release|Any CPU
{D949AB45-9B65-4594-A97E-182BC3831707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D949AB45-9B65-4594-A97E-182BC3831707}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D949AB45-9B65-4594-A97E-182BC3831707}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D949AB45-9B65-4594-A97E-182BC3831707}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {632D11D1-088B-4795-97E5-048534002558}
EndGlobalSection
EndGlobal
+1 -1
View File
@@ -77,7 +77,7 @@ namespace MP.FileData
{
if (!optionsBuilder.IsConfigured)
{
string connString = _configuration.GetConnectionString("Mp.Prog");
string connString = _configuration.GetConnectionString("MP.Land");
if (!string.IsNullOrEmpty(connString))
{
optionsBuilder.UseSqlServer(connString);
+5
View File
@@ -0,0 +1,5 @@
{
"version": 1,
"isRoot": true,
"tools": {}
}
+10
View File
@@ -0,0 +1,10 @@
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
+99
View File
@@ -0,0 +1,99 @@
<div class="row">
<div class="col-4">
<button id="btnForceCheck" class="btn btn-primary btn-sm btn-block" @onclick="() => ForceCheck(0)" title="Forza verifica archivio (totale)">
<i class="fas fa-sync-alt"></i> Update Completo Archivio <i class="far fa-folder-open"></i>
</button>
</div>
<div class="col-8 py-2">
@if (showProgress)
{
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width:@percLoading%;"></div>
</div>
}
</div>
<div class="col-12 mt-2">
@if (ListRecords == null)
{
<div class="row">
<div class="col-4">
@if (showProgress)
{
<div class="col-12 mt-2">
<div class="alert alert-primary">
@if (setupMessages.Count > 0)
{
<ul>
@foreach (var item in setupMessages)
{
<li>@item</li>
}
</ul>
}
</div>
</div>
}
</div>
<div class="col-8">
<LoadingData></LoadingData>
</div>
</div>
}
else if (totalCount == 0)
{
<div class="alert alert-warning text-center display-4">Nessun record trovato</div>
}
else
{
<table class="table table-sm table-striped table-responsive-lg">
<thead>
<tr>
<th></th>
<th>Macchina</th>
<th>Path</th>
<th class="text-right">Tags</th>
<th class="text-right">Senza Tag</th>
<th class="text-right">Modificati</th>
<th class="text-right">Tot File</th>
</tr>
</thead>
<tbody>
@foreach (var record in ListRecords)
{
<tr>
<td>
@if (!string.IsNullOrEmpty(record.BasePath))
{
<button id="btnForceCheck" class="btn btn-warning btn-sm" @onclick="() => ForceCheckMacchina(record.IdxMacchina)" title="Forza verifica Archivio singola macchina">
<i class="fas fa-sync-alt"></i>
</button>
}
</td>
<td>
@record.Nome
</td>
<td>
<div class="small">@record.BasePath</div>
</td>
<td class="text-right">
@record.TotalTags
</td>
<td class="text-right">
@record.NoTags
</td>
<td class="text-right">
@record.NumChanged
</td>
<td class="text-right">
<b>
@record.TotFile
</b>
</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
+133
View File
@@ -0,0 +1,133 @@
using Microsoft.AspNetCore.Components;
using MP.FileData.DTO;
using MP.Land.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land.Components
{
public partial class ArchiveStatus
{
#region Private Fields
private List<ArchiveStatusDTO> ListRecords;
private int numChecks = 0;
private int totalCount = 0;
#endregion Private Fields
#region Private Properties
private List<string> setupMessages { get; set; } = new List<string>();
#endregion Private Properties
#region Protected Properties
[Inject]
protected FileArchDataService DataService { get; set; }
protected int percLoading { get; set; } = 0;
protected bool showProgress { get; set; } = false;
#endregion Protected Properties
#region Private Methods
private async Task verificaSingola(string idxMacchina, int numDays, int numMacchine)
{
// recupero elenco macchine
percLoading += 100 / numMacchine;
numChecks = await DataService.updateMachineArchive(idxMacchina, numDays, true, false);
await Task.Delay(1);
setupMessages.Add($"{idxMacchina}: {numChecks} files");
StateHasChanged();
await Task.Delay(1);
}
private async Task verificaTutte(int numDays)
{
// recupero elenco macchine
var listaMacchine = await DataService.MacchineGetAll();
int numMacchine = listaMacchine.Count();
foreach (var item in listaMacchine.Where(x => !string.IsNullOrEmpty(x.BasePath)).ToList())
{
await verificaSingola(item.IdxMacchina, numDays, numMacchine);
}
}
#endregion Private Methods
#region Protected Methods
protected async Task ArchiveCheck(int maxHour)
{
showProgress = true;
percLoading = 0;
await verificaTutte(maxHour);
setupMessages.Add($"Effettuata verifica e rilettura di {numChecks} files!");
await ReloadData();
}
protected async Task ArchiveSingleCheck(string idxMacchina, int maxHour)
{
showProgress = true;
percLoading = 0;
await verificaSingola(idxMacchina, maxHour, 2);
setupMessages.Add($"Effettuata verifica e rilettura di {numChecks} files!");
await ReloadData();
}
protected async Task ClearMessage()
{
await Task.Delay(10);
setupMessages = new List<string>();
}
protected async Task ForceCheck(int maxHour)
{
setupMessages.Add("Inizio Analisi Archivio...");
ListRecords = null;
await Task.Delay(1);
await ArchiveCheck(maxHour);
await ClearMessage();
await ReloadData();
}
protected async Task ForceCheckMacchina(string idxMacchina)
{
setupMessages.Add("Inizio Analisi Archivio...");
ListRecords = null;
await Task.Delay(1);
await ArchiveSingleCheck(idxMacchina, 0);
await ClearMessage();
await ReloadData();
}
protected override async Task OnInitializedAsync()
{
ListRecords = null;
await ReloadData();
}
protected async Task ReloadData()
{
await Task.Delay(1);
numChecks = 0;
ListRecords = await DataService.GetArchiveStatus();
totalCount = ListRecords.Count;
await Task.Delay(1);
setupMessages = new List<string>();
showProgress = false;
percLoading = 0;
}
#endregion Protected Methods
}
}
+16
View File
@@ -0,0 +1,16 @@
<div class="form-row text-light">
<div class="col-5 pr-0 text-left">
MP.Land <span class="small">v.@version</span>
</div>
<div class="col-7 pl-0 text-right">
<span class="small">@adesso</span>
<a class="text-light" href="https://www.egalware.com/" target="_blank">Egalware<img class="img-fluid" width="16" src="img/LogoBlu.svg" /></a>
</div>
</div>
@code {
protected DateTime adesso = DateTime.Now;
Version version = typeof(Program).Assembly.GetName().Version;
}
+84
View File
@@ -0,0 +1,84 @@
@using MP.Land.Components
@using System.Security.Claims
@*@using Microsoft.AspNetCore.Components.Authorization*@
@using MP.Land.Data
@inject MessageService AppMessages
@*@inject AuthenticationStateProvider AuthenticationStateProvider*@
<div class="form-row pt-3">
<div class="col-7 col-md-6 col-lg-4 col-xl-3">
@*<LoginDisplay></LoginDisplay>*@
@*<i class="fas fa-user-alt"></i> <b>@userName</b>*@
</div>
<div class="col-12 col-lg-4 col-xl-6 d-none d-lg-block text-center h4 text-truncate">
<span class="@PageIcon" aria-hidden="true"></span> @PageName
</div>
<div class="col-5 col-md-6 col-lg-4 col-xl-3 text-right">
@if (ShowSearch)
{
<SearchMod></SearchMod>
}
</div>
</div>
@code {
//[CascadingParameter]
//private Task<AuthenticationState> AuthenticationStateTask { get; set; }
[CascadingParameter(Name = "ShowSearch")]
private bool ShowSearch { get; set; }
private string userName = "";
private string PageName { get; set; }
private string PageIcon { get; set; }
protected override async Task OnInitializedAsync()
{
await forceReload();
}
protected override void OnInitialized()
{
AppMessages.EA_PageUpdated += OnPageUpdate;
}
public void OnPageUpdate()
{
PageName = AppMessages.PageName;
PageIcon = AppMessages.PageIcon;
InvokeAsync(() =>
{
StateHasChanged();
});
}
public void Dispose()
{
AppMessages.EA_PageUpdated -= OnPageUpdate;
}
private async Task forceReload()
{
userName = "N.A.";
await Task.Delay(1);
#if false
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
userName = $"{user.Identity.Name}";
}
else
{
userName = "N.A.";
}
#endif
}
}
@* // Vedere anche:
// https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0#:~:text=Blazor%20uses%20the%20existing%20ASP.NET%20Core%20authentication%20mechanisms,all%20client-side%20code%20can%20be%20modified%20by%20users
// https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0*@
+110
View File
@@ -0,0 +1,110 @@
@using MP.FileData.DatabaseModels
@using MP.Prog.Data
@inject FileArchDataService DataService
@inject MessageService AppMService
@if (ArtList == null)
{
<LoadingData></LoadingData>
}
else
{
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<span class="fas fa-search" aria-hidden="true"></span>
</span>
</div>
<input type="text" class="form-control form-control-sm" placeholder="Ricerca Articolo" @bind-value="@SearchArt" />
<select @bind="@SelCodArt" class="form-control form-control-sm">
@if (ArtList != null)
{
foreach (var item in ArtList)
{
<option value="@item.CodArticolo">@item.Disegno | @item.DescArticolo [@item.CodArticolo]</option>
}
}
</select>
<div class="input-group-append">
<button id="searchReset" class="btn btn-sm btn-secondary" @onclick="() => ResetSearchArt()" title="Reset ricerca articolo"><i class="fas fa-ban"></i></button>
</div>
</div>
}
@code {
[Parameter]
public EventCallback<string> searchUpdated { get; set; }
[Parameter]
public string SelCodArt
{
get
{
string answ = "";
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.CodArticolo;
}
return answ;
}
set
{
if (!AppMService.File_Filter.CodArticolo.Equals(value))
{
AppMService.File_Filter.CodArticolo = value;
}
reportChange();
}
}
private void reportChange()
{
searchUpdated.InvokeAsync(SelCodArt);
}
protected string _SearchArt;
protected string defCodArt = "";
protected List<ArticoloModel> ArtList;
protected string SearchArt
{
get
{
return _SearchArt;
}
set
{
_SearchArt = value;
// se son > 3 char... debounce...
if (string.IsNullOrEmpty(value))
{
_SearchArt = defCodArt;
}
if (value.Length >= defCodArt.Length)
{
var pUpd = Task.Run(async () =>
{
ArtList = await DataService.ArticoliGetFilt(SearchArt);
});
pUpd.Wait();
}
}
}
protected override async Task OnInitializedAsync()
{
await ReloadAllData();
_SearchArt = defCodArt;
}
protected async Task ReloadAllData()
{
SelCodArt = defCodArt;
ArtList = await DataService.ArticoliGetFilt(SearchArt);
}
protected void ResetSearchArt()
{
SearchArt = defCodArt;
}
}
+56
View File
@@ -0,0 +1,56 @@
<div class="row">
<div class="col-12 col-lg-9 text-left">
<div class="row">
<div class="col-12 small">
@if (totalCount > 0)
{
<ul class="pagination pagination-sm mb-1">
<li class="page-item"><button class="page-link" @onclick="() => PaginationItemClick(1)"><i class="fas fa-angle-double-left"></i></button></li>
<li class="page-item"><button class="page-link" @onclick="() => PaginationItemClick(prevBlock)"><i class="fas fa-angle-left"></i></button></li>
@for (int i = @startPage; i <= endPage; ++i)
{
var pageNum = i;
<li class="page-item @cssActive(pageNum)"><button class="page-link" @onclick="() => PaginationItemClick(pageNum)">@pageNum</button></li>
}
<li class="page-item"><button class="page-link" @onclick="() => PaginationItemClick(nextBlock)"><i class="fas fa-angle-right"></i></button></li>
<li class="page-item"><button class="page-link" @onclick="() => PaginationItemClick(LastPage)"><i class="fas fa-angle-double-right"></i></button></li>
</ul>
}
</div>
</div>
<div class="row">
<div class="col-12 small">
@if (showLoading)
{
<div class="progress" style="height: 10px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width:@percLoading%;"></div>
</div>
}
</div>
</div>
</div>
<div class="col-12 col-lg-3">
<div class="d-flex">
<div class="p-1 flex-fill text-right">
@if (!showLoading)
{
<span>@totalCount records</span>
}
</div>
<div class="p-1 flex-fill text-right small">
@if (totalCount > 0)
{
<div class="input-group input-group-sm">
<select @bind="@PageSize" class="form-control form-control-sm">
<option value="5">5</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
}
</div>
</div>
</div>
</div>
+189
View File
@@ -0,0 +1,189 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Threading.Tasks;
namespace MP.Land.Components
{
public partial class DataPager : ComponentBase
{
#region Protected Fields
protected bool _showLoading = false;
#endregion Protected Fields
#region Private Properties
private int endPage
{
get
{
int answ = (int)(currPage / numPages) * numPages + numPages;
answ = answ < LastPage ? answ : LastPage;
return answ;
}
}
private int LastPage
{
get
{
return Math.Max((int)Math.Ceiling(totalCount / (double)PageSize), 1);
}
}
private int nextBlock
{
get
{
int answ = currPage + numPages;
answ = answ < LastPage ? answ : LastPage;
return answ;
}
}
private int numPages { get; set; } = 10;
private int prevBlock
{
get
{
int answ = currPage - numPages;
answ = answ > 0 ? answ : 1;
return answ;
}
}
// calcola un set 1 .. numPages centrato sulla pagina corrente...
private int startPage
{
get
{
int answ = (int)(currPage / numPages) * numPages;
answ = answ > 0 ? answ : 1;
return answ;
}
}
#endregion Private Properties
#region Protected Properties
protected int _numPage { get; set; } = 1;
protected int _numRecord { get; set; } = 10;
protected int percLoading { get; set; } = 0;
#endregion Protected Properties
#region Public Properties
[Parameter]
public int currPage
{
get
{
return _numPage;
}
set
{
bool doReport = !_numPage.Equals(value);
if (doReport)
{
_numPage = value;
reportChangePage();
}
}
}
[Parameter]
public EventCallback<int> numPageChanged { get; set; }
[Parameter]
public EventCallback<int> numRecordChanged { get; set; }
[Parameter]
public int PageSize
{
get
{
return _numRecord;
}
set
{
bool doReport = !_numRecord.Equals(value);
if (doReport)
{
_numRecord = value;
reportChange();
}
}
}
[Parameter]
public bool showLoading
{
get
{
return _showLoading;
}
set
{
if (value)
{
Random random = new Random();
percLoading = random.Next(30, 90);
}
else
{
percLoading = 5;
}
_showLoading = value;
}
}
[Parameter]
public int totalCount { get; set; } = 0;
#endregion Public Properties
#region Private Methods
private void reportChange()
{
numRecordChanged.InvokeAsync(PageSize);
}
private void reportChangePage()
{
numPageChanged.InvokeAsync(currPage);
}
#endregion Private Methods
#region Protected Methods
protected string cssActive(int numPage)
{
string answ = "";
if (numPage == currPage)
{
answ = "active";
}
return answ;
}
protected override async Task OnInitializedAsync()
{
await Task.Run(() => showLoading = false);
}
protected void PaginationItemClick(int page)
{
currPage = page;
}
#endregion Protected Methods
}
}
+154
View File
@@ -0,0 +1,154 @@
@using MP.Land.Data
@using System.Text
@using DiffMatchPatch
<div class="row">
<div class="col-6 table-success">
<div class="row">
<div class="col-4 text-success">
<h4>Archivio</h4>
</div>
<div class="col-4">
</div>
<div class="col-4 text-right">
<input @bind="@pHeight" class="text-right" width="20" inputmode="numeric" /> <i class="fas fa-arrows-alt-v"></i>
</div>
</div>
</div>
@if (numChanges > 0)
{
<div class="col-6 table-danger">
<div class="row">
<div class="col-4 text-danger">
<h4>Attuale</h4>
</div>
<div class="col-4">
</div>
<div class="col-4 text-right p-2">
<span class="border border-danger table-danger py-1 px-2"><b>@numChanges</b> modifiche</span>
</div>
</div>
</div>
}
</div>
<div class="row" style="height: @(pHeight)em; overflow-y: scroll;">
<div class="col-6 table-success">
<div class="border border-success p-2 bg-light">
<p>@((MarkupString)oldResult)</p>
</div>
</div>
@if (numChanges > 0)
{
<div class="col-6 table-danger">
<div class="border border-danger p-2 bg-light">
<p>@((MarkupString)newResult)</p>
</div>
</div>
}
</div>
@code {
string sepDest = "<br />";
protected int pHeight = 25;
protected string oldResult = "";
protected string newResult = "";
protected string _oldText = "";
protected string _newText = "";
[Parameter]
public EventCallback<int> diffDone { get; set; }
protected int numChanges { get; set; } = 0;
[Parameter]
public string oldText
{
get
{
return _oldText;
}
set
{
_oldText = value;
}
}
protected string oldTextFix
{
get
{
return _oldText.Replace(Environment.NewLine, sepDest).Replace("\n", sepDest).Replace("\r", sepDest);
}
}
protected string newTextFix
{
get
{
return _newText.Replace(Environment.NewLine, sepDest).Replace("\n", sepDest).Replace("\r", sepDest);
}
}
[Parameter]
public string newText
{
get
{
return _newText;
}
set
{
_newText = value;
ReloadData();
}
}
protected void ReloadData()
{
numChanges = 0;
// calcolo diff
diff_match_patch dmp = new diff_match_patch();
List<Diff> diff = dmp.diff_main(oldTextFix, newTextFix);
dmp.diff_cleanupSemantic(diff);
// predispongo la stringa secondo l'elenco dei diff....
StringBuilder sbNew = new StringBuilder();
StringBuilder sbOld = new StringBuilder();
foreach (var item in diff)
{
switch (item.operation)
{
case Operation.DELETE:
sbOld.Append($"<span class=\"border border-success table-success\">{item.text}</span>");
break;
case Operation.INSERT:
sbNew.Append($"<span class=\"border border-danger table-danger\">{item.text}</span>");
numChanges++;
break;
case Operation.EQUAL:
sbNew.Append($"<span class=\"text-dark\">{item.text}</span>");
sbOld.Append($"<span class=\"text-dark\">{item.text}</span>");
break;
default:
break;
}
}
newResult = sbNew.ToString().Trim();
oldResult = sbOld.ToString().Trim();
var pUpd = Task.Run(async () =>
{
await diffDone.InvokeAsync(numChanges);
});
pUpd.Wait();
}
protected override Task OnInitializedAsync()
{
ReloadData();
return base.OnInitializedAsync();
}
}
+31
View File
@@ -0,0 +1,31 @@
<div class="card">
<div class="card-header bg-secondary text-light">
<div class="row">
<div class="col-4">
<h5>Dettaglio modifiche</h5>
</div>
<div class="col-4">
@if (_currItem.DiskStatus != FileData.FileState.Ok)
{
<div class="row">
<div class="col-6">
<button type="button" class="btn btn-success btn-block" value="Cancel" @onclick="RejectChange">Mantieni Archivio <i class="fas fa-file-download"></i></button>
</div>
<div class="col-6">
<button type="button" class="btn btn-danger btn-block" value="Cancel" @onclick="ApproveChange">Accetta Attuale <i class="fas fa-file-upload"></i></button>
</div>
</div>
}
</div>
<div class="col-2">
@numDiff
</div>
<div class="col-2 text-right">
<button type="button" class="btn btn-primary btn-block" value="Cancel" @onclick="cancelUpdate" title="Chiudi">Chiudi <i class="fas fa-window-close"></i></button>
</div>
</div>
</div>
<div class="card-body small py-1">
<MP.Land.Components.DiffView oldText="@_currItem.FileStringContent" newText="@(CurrFileContent(_currItem.Path))"></MP.Land.Components.DiffView>
</div>
</div>
+161
View File
@@ -0,0 +1,161 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MP.FileData.DatabaseModels;
using MP.Land.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land.Components
{
public partial class FileEditor : ComponentBase
{
#region Protected Fields
protected int numDiff = 0;
#endregion Protected Fields
#region Public Fields
public FileModel _currItem = new FileModel();
#endregion Public Fields
#region Protected Properties
[Inject]
protected FileArchDataService DataService { get; set; }
[Inject]
protected IJSRuntime JSRuntime { get; set; }
#endregion Protected Properties
#region Public Properties
[Parameter]
public FileModel currItem
{
get
{
return _currItem = null;
}
set
{
_currItem = value;
}
}
[Parameter]
public EventCallback<int> DataReset { get; set; }
[Parameter]
public EventCallback<int> DataUpdated { get; set; }
[Parameter]
public List<MacchinaModel> MacList { get; set; }
[Parameter]
public List<TagModel> TagList { get; set; }
#endregion Public Properties
#region Private Methods
private async Task ApproveChange()
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sicuro di voler asccettare la modifica del file selezionato generando una nuova revisione?"))
return;
if (_currItem != null)
{
await DataService.FileApprove(_currItem);
await DataUpdated.InvokeAsync(1);
}
else
{
Console.WriteLine("File null!");
}
}
private async Task cancelUpdate()
{
await DataReset.InvokeAsync(0);
}
private async Task deleteRecord()
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sicuro di voler eliminare il file selezionato??"))
return;
if (_currItem != null)
{
await DataService.FileDelete(_currItem);
await DataUpdated.InvokeAsync(1);
}
else
{
Console.WriteLine("File null!");
}
}
private async Task RejectChange()
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sicuro di voler eliminare la modifica del file selezionato e sovrascrivere la versione in rete?"))
return;
if (_currItem != null)
{
await DataService.FileReject(_currItem);
await DataUpdated.InvokeAsync(1);
}
else
{
Console.WriteLine("File null!");
}
}
private async Task saveUpdate()
{
if (_currItem != null)
{
await DataService.FileUpdate(_currItem);
await DataUpdated.InvokeAsync(1);
}
else
{
Console.WriteLine("File null!");
}
}
#endregion Private Methods
#region Protected Methods
protected void diffDoneHandler(int numChanges)
{
#if false
numDiff = numChanges;
#endif
}
#endregion Protected Methods
#region Public Methods
public string CurrFileContent(string fullPath)
{
string answ = "";
if (File.Exists(fullPath))
{
answ = File.ReadAllText(fullPath);
}
return answ;
}
#endregion Public Methods
}
}
+7
View File
@@ -0,0 +1,7 @@
@*<h1 class="alert alert-info">Working</h1>*@
<div class="row">
<div class="col-12 text-center mt-5 py-5 alert alert-primary">
<h3>loading data</h3>
<i class="fas fa-spinner fa-spin fa-5x"></i>
</div>
</div>
+61
View File
@@ -0,0 +1,61 @@
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
<AuthorizeView>
<Authorized>
<div class="input-group text-truncate">
<div class="input-group-prepend">
<a title="LogOut" href="Identity/Account/LogOut" class="btn btn-sm btn-danger"><i class="fas fa-sign-out-alt"></i></a>
</div>
<a title="Gestione account @userName" href="Identity/Account/Manage" class="btn btn-sm btn-outline-dark mx-0 px-1">
<div class="d-none d-sm-block">
<i class="fas fa-user-alt"></i> @StringLim(userName, 30)
</div>
<div class="d-block d-sm-none">
<i class="fas fa-user-alt"></i> @StringLim(userName, 15)
</div>
</a>
</div>
</Authorized>
<NotAuthorized>
<div class="input-group">
<div class="input-group-prepend">
<a title="LogIn" href="Identity/Account/LogIn" class="btn btn-sm btn-success"><i class="fas fa-sign-in-alt"></i></a>
</div>
<div class="form-control form-control-sm">
<i class="fas fa-user-alt"></i>&nbsp;@userName
</div>
</div>
</NotAuthorized>
</AuthorizeView>
@code{
private string userName = "";
protected override async Task OnInitializedAsync()
{
await forceReload();
}
private async Task forceReload()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
userName = $"{user.Identity.Name}";
}
else
{
userName = "Non Autenticato";
}
}
protected string StringLim(string original, int maxLen)
{
return original.Length <= maxLen ? original : $"{original.Substring(0, maxLen - 3)}...";
}
}
+43
View File
@@ -0,0 +1,43 @@
@using MP.Land.Components
@using MP.Land.Data
@using Majorsoft.Blazor.Components.Debounce
@inject MessageService MessageService
<div class="input-group input-group-sm">
<DebounceInput id="sVal" class="form-control" placeholder="@("Ricerca (min " + _minChar + " char)")" autocomplete="off" @ref="debInput" @bind-Value="@_searchVal" @bind-Value:event="OnInput" DebounceTime="@_debMsec" MinLength="@_minChar" OnValueChanged="e => { searchVal = e; }" ForceNotifyByEnter="true" ForceNotifyOnBlur="true" />
<div class="input-group-append">
<button @onclick="reset" class="btn btn-success input-group-text">reset</button>
</div>
</div>
@code {
private string _searchVal = "";
private int _debMsec = 200;
private int _minChar = 2;
private DebounceInput debInput;
[Parameter]
public string searchVal
{
get
{
return MessageService.SearchVal;
}
set
{
if (!MessageService.SearchVal.Equals(value))
{
MessageService.SearchVal = value;
}
}
}
private void reset()
{
_searchVal = "";
searchVal = "";
}
}
+36
View File
@@ -0,0 +1,36 @@
<div class="modal fade show" id="myModal" style="display:block; background-color: rgba(10,10,10,.8);"
aria-modal="true" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">@Title</h4>
<button type="button" class="close" @onclick="@ModalCancel">&times;</button>
</div>
<div class="modal-body">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<span class="fas fa-search" aria-hidden="true"></span>
</span>
</div>
<input type="text" class="form-control form-control-sm" placeholder="Ricerca Tag" @bind-value="@SearchTag" />
<select @bind="@SelTag" class="form-control form-control-sm">
@if (TagList != null)
{
foreach (var item in TagList)
{
<option value="@item.ValueField">@item.LabelField</option>
}
}
</select>
<div class="input-group-append">
<button id="searchReset" class="btn btn-sm btn-secondary" @onclick="() => ResetSearchTag()" title="Reset ricerca articolo"><i class="fas fa-ban"></i></button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick=@ModalOk>OK</button>
</div>
</div>
</div>
</div>
+142
View File
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using MP.FileData.DatabaseModels;
using MP.Land;
using MP.Land.Data;
namespace MP.Land.Components
{
public partial class TagSearch
{
#region Protected Fields
protected string _SearchTag;
protected string defTag = "###";
protected string LastSelTag = "";
#endregion Protected Fields
#region Protected Properties
[Inject]
protected MessageService AppMService { get; set; }
[Inject]
protected FileArchDataService DataService { get; set; }
protected string SearchTag
{
get
{
return _SearchTag;
}
set
{
_SearchTag = value;
// se son > 3 char... debounce...
if (string.IsNullOrEmpty(value))
{
_SearchTag = defTag;
}
if (value.Length >= defTag.Length)
{
var pUpd = Task.Run(async () =>
{
TagList = await DataService.TagGetSearch(_SearchTag, 50);
});
pUpd.Wait();
}
}
}
protected List<AutocompleteModel> TagList { get; set; } = new List<AutocompleteModel>();
#endregion Protected Properties
#region Public Properties
[Parameter]
public EventCallback<bool> OnClose { get; set; }
[Parameter]
public EventCallback<string> searchUpdated { get; set; }
[Parameter]
public string SelTag
{
get
{
string answ = "";
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.Tag;
}
return answ;
}
set
{
if (!AppMService.File_Filter.Tag.Equals(value))
{
AppMService.File_Filter.Tag = value;
}
reportChange();
}
}
[Parameter]
public string Text { get; set; }
[Parameter]
public string Title { get; set; }
#endregion Public Properties
#region Private Methods
private Task ModalCancel()
{
// resetto ricerca
SelTag = LastSelTag;
return OnClose.InvokeAsync(false);
}
private Task ModalOk()
{
return OnClose.InvokeAsync(true);
}
private void reportChange()
{
searchUpdated.InvokeAsync(SelTag);
}
#endregion Private Methods
#region Protected Methods
protected override async Task OnInitializedAsync()
{
LastSelTag = SelTag;
await ReloadAllData();
_SearchTag = defTag;
}
protected async Task ReloadAllData()
{
SelTag = defTag;
TagList = await DataService.TagGetSearch(SearchTag, 20);
}
protected async Task ResetSearchTag()
{
SearchTag = defTag;
await Task.Delay(1);
}
#endregion Protected Methods
}
}
+1
View File
@@ -0,0 +1 @@

+29
View File
@@ -0,0 +1,29 @@
{
"ExcludedTags": [
"M4",
"M5",
"M4+A",
"M4+B",
"M5+A",
"M5+B"
],
"ExcludedFileExt": [
".xls",
".xls#",
".xlsx"
],
"FileNameExtReplace": {
".P-2": ""
},
"MaxChar2Search": 100,
"Mode": 0,
"Name": "Tag da Commento Filename",
"OutExcludeFileName": true,
"OutReplace": {
"(": " ",
")": " "
},
"RegExPattern": "\\b{{fileName}}.{0,2}\\([\\w\\d\\s./\\-=]+\\)",
"RegExRepFileName": true,
"ReplaceCR": true
}
+23
View File
@@ -0,0 +1,23 @@
{
"ExcludedTags": [],
"ExcludedFileExt": [
".xls",
".xls#",
".xlsx"
],
"FileNameExtReplace": {
".P-2": ""
},
"MaxChar2Search": 1,
"Mode": 1,
"Name": "Tag da Directory + Filename",
"OutExcludeFileName": false,
"OutReplace": {
".WPD": "",
".MPF": "",
".SPF": ""
},
"RegExPattern": "",
"RegExRepFileName": true,
"ReplaceCR": false
}
+35
View File
@@ -0,0 +1,35 @@
{
"ExcludedTags": [
"M4",
"M5",
"M4+A",
"M4+B",
"M5+A",
"M5+B",
"M6+A",
"M6+B",
"+A"
],
"ExcludedFileExt": [
".xls",
".xls#",
".xlsx"
],
"FileNameExtReplace": {
".P-2": "",
".tim": ""
},
"MaxChar2Search": 100,
"Mode": 0,
"Name": "Tag da Commento Filename Tornoss TISIS",
"OutExcludeFileName": true,
"OutReplace": {
"(": " ",
")": " ",
"<": " ",
">": " "
},
"RegExPattern": "\\b{{fileName}}.{0,2}\\([\\<\\w\\d\\s./\\-=\\+\\>]+\\)",
"RegExRepFileName": true,
"ReplaceCR": true
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land.Data
{
public class AutocompleteModel
{
#region Public Properties
public string LabelField { get; set; }
public string ValueField { get; set; }
#endregion Public Properties
}
}
+341
View File
@@ -0,0 +1,341 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MP.FileData;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using MP.FileData.Controllers;
using MP.FileData.DTO;
namespace MP.Land.Data
{
public class FileArchDataService : IDisposable
{
#region Private Fields
private static IConfiguration _configuration;
private static ILogger<FileArchDataService> _logger;
private static List<FileData.DatabaseModels.MacchinaModel> ElencoMacchine = new List<FileData.DatabaseModels.MacchinaModel>();
private static NLog.Logger Log = LogManager.GetCurrentClassLogger();
private readonly IDistributedCache distributedCache;
private readonly IMemoryCache memoryCache;
/// <summary>
/// Durata assoluta massima della cache
/// </summary>
private int chAbsExp = 15;
/// <summary>
/// Durata della cache in modalità inattiva (non acceduta) prima di venire rimossa
/// NON estende oltre il tempo massimo di validità della cache (chAbsExp)
/// </summary>
private int chSliExp = 5;
#endregion Private Fields
#region Protected Fields
protected static string connStringBBM = "";
protected static string connStringFatt = "";
#endregion Protected Fields
#region Public Fields
public static FileData.Controllers.FileController dbController;
#endregion Public Fields
#region Public Constructors
public FileArchDataService(IConfiguration configuration, ILogger<FileArchDataService> logger, IMemoryCache memoryCache, IDistributedCache distributedCache)
{
_logger = logger;
_configuration = configuration;
// conf cache
this.memoryCache = memoryCache;
this.distributedCache = distributedCache;
// conf DB
string connStr = _configuration.GetConnectionString("MP.Land");
if (string.IsNullOrEmpty(connStr))
{
_logger.LogError("ConnString empty!");
}
else
{
dbController = new FileData.Controllers.FileController(configuration);
_logger.LogInformation("DbController OK");
}
}
#endregion Public Constructors
#region Private Properties
private DistributedCacheEntryOptions cacheOpt
{
get
{
return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(chAbsExp)).SetSlidingExpiration(TimeSpan.FromMinutes(chSliExp));
}
}
private DistributedCacheEntryOptions cacheOptLong
{
get
{
return new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(chAbsExp * 10)).SetSlidingExpiration(TimeSpan.FromMinutes(chSliExp));
}
}
#endregion Private Properties
#region Internal Methods
internal Task FileApprove(FileData.DatabaseModels.FileModel currItem)
{
return Task.FromResult(dbController.FileModApprove(currItem));
}
internal Task FileDelete(FileData.DatabaseModels.FileModel currItem)
{
return Task.FromResult(dbController.FileDelete(currItem));
}
internal Task FileReject(FileData.DatabaseModels.FileModel currItem)
{
return Task.FromResult(dbController.FileModReject(currItem));
}
internal Task FileUpdate(FileData.DatabaseModels.FileModel updItem)
{
return Task.FromResult(dbController.FileUpdate(updItem));
}
internal void ResetController()
{
dbController.ResetController();
}
#endregion Internal Methods
#region Public Methods
public void Dispose()
{
// Clear database controller
dbController.Dispose();
}
public async Task<int> FileCountFilt(SelectData CurrFilter)
{
int numCount = 0;
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
numCount = dbController.FileCountFilt(CurrFilter.IdxMacchina, CurrFilter.OnlyActive, CurrFilter.OnlyMod, CurrFilter.OnlyNoTag, CurrFilter.FileName, CurrFilter.Tag, CurrFilter.SearchVal);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per FileCountFilt: {ts.TotalMilliseconds} ms");
return await Task.FromResult(numCount);
}
public Task<FileData.DatabaseModels.FileModel> FileGetByKey(int FileId)
{
return Task.FromResult(dbController.FileGetByKey(FileId));
}
public async Task<List<FileData.DatabaseModels.FileModel>> FileGetFilt(SelectData CurrFilter)
{
List<FileData.DatabaseModels.FileModel> dbResult = new List<FileData.DatabaseModels.FileModel>();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
dbResult = dbController.FileGetFilt(CurrFilter.IdxMacchina, CurrFilter.OnlyActive, CurrFilter.OnlyMod, CurrFilter.OnlyNoTag, CurrFilter.FileName, CurrFilter.Tag, CurrFilter.SearchVal, CurrFilter.NumSkip, CurrFilter.PageSize).ToList();
//dbResult = dbController.FileGetFilt(CurrFilter.IdxMacchina, CurrFilter.OnlyActive, CurrFilter.OnlyMod, CurrFilter.FirstRecord, CurrFilter.PageSize * 10, CurrFilter.SearchVal).ToList();
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per FileGetFilt: {ts.TotalMilliseconds} ms");
return await Task.FromResult(dbResult);
}
public async Task<List<ArchiveStatusDTO>> GetArchiveStatus()
{
List<ArchiveStatusDTO> dbResult = new List<ArchiveStatusDTO>();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
dbResult = dbController.GetArchiveStatus();
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Trace($"Effettuata lettura da DB per GetArchiveStatus: {ts.TotalMilliseconds} ms");
return await Task.FromResult(dbResult);
}
public Task<FileData.DatabaseModels.MacchinaModel> MacchinaGetByKey(string idxMacchina)
{
return Task.FromResult(dbController.MacchinaGetByKey(idxMacchina));
}
public Task<List<FileData.DatabaseModels.MacchinaModel>> MacchineGetAll()
{
return Task.FromResult(dbController.MacchineGetAll().ToList());
}
public async Task<List<AutocompleteModel>> MachineList()
{
List<AutocompleteModel> answ = new List<AutocompleteModel>();
answ.Add(new AutocompleteModel { LabelField = "--- TUTTE ---", ValueField = "*" });
answ.AddRange(dbController.MacchineGetAll().Select(x => new AutocompleteModel { LabelField = $"{x.IdxMacchina} | {x.Nome} {x.Descrizione} ", ValueField = x.IdxMacchina }).ToList());
return await Task.FromResult(answ);
}
public void rollBackEdit(object item)
{
dbController.RollBackEntity(item);
}
public async Task<List<FileData.DatabaseModels.TagModel>> TagGetFilt(string SearchVal)
{
return await Task.FromResult(dbController.TagGetFilt(SearchVal, 200).ToList());
}
public Task<List<AutocompleteModel>> TagGetSearch(string searchVal, int numRecord)
{
List<AutocompleteModel> answ = new List<AutocompleteModel>();
answ.Add(new AutocompleteModel { LabelField = "--- TUTTE ---", ValueField = "*" });
if (numRecord > -1)
{
answ.AddRange(dbController.TagGetFilt(searchVal, numRecord).Select(x => new AutocompleteModel { LabelField = $"{x.TagId}", ValueField = x.TagId }).ToList());
}
return Task.FromResult(answ);
}
#if false
protected string getCacheKey(string TableName, SelectData CurrFilter)
{
string answ = $"{TableName}:M_{CurrFilter.IdxMacchina}:A_{CurrFilter.CodArticolo}:K_{CurrFilter.KeyRichiesta}:O_{CurrFilter.IdxOdl}:D_{CurrFilter.DateStart:yyyyMMddHHmm}_{CurrFilter.DateEnd:yyyyMMddHHmm}";
return answ;
}
protected string getCacheKeyPaged(string TableName, SelectData CurrFilter)
{
string answ = $"{TableName}:M_{CurrFilter.IdxMacchina}:A_{CurrFilter.CodArticolo}:K_{CurrFilter.KeyRichiesta}:O_{CurrFilter.IdxOdl}:D_{CurrFilter.DateStart:yyMMddHHmm}_{CurrFilter.DateEnd:yyMMddHHmm}:R_{CurrFilter.FirstRecord}_{CurrFilter.FirstRecord + CurrFilter.NumRecord}";
return answ;
}
#endif
/// <summary>
/// Aggiorna intero archivio scansionando dati x tutte le macchine che hanno un path valido
/// </summary>
/// <param name="numDayPre">Numero giorni x ricerca all'indietro da data corrente / 0 = nessun limite</param>
/// <returns></returns>
public async Task<int> updateAllArchive(int numDayPre, bool forceTag)
{
int checkDone = 0;
var listaMacchine = await MacchineGetAll();
foreach (var item in listaMacchine.Where(x => !string.IsNullOrEmpty(x.BasePath)).ToList())
{
checkDone += await updateMachineArchive(item.IdxMacchina, numDayPre, forceTag, false);
}
return await Task.FromResult(checkDone);
}
/// <summary>
/// Aggiorna archivio di una amcchina scansionando path relativo
/// </summary>
/// <param name="idxMacchina">Codice macchina</param>
/// <param name="numDayPre">Numero giorni x ricerca all'indietro da data corrente / 0 = nessun limite</param>
/// <param name="forceTag">Forza la riverifica dei tags (x update da setup)</param>
/// <param name="fullLog">Scrittura log verboso macchina</param>
/// <returns></returns>
public async Task<int> updateMachineArchive(string idxMacchina, int numDayPre, bool forceTag, bool fullLog)
{
int checkDone = 0;
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
string ruleName = "Rule00.json";
try
{
var macchina = MacchinaGetByKey(idxMacchina).Result;
if (macchina != null && !string.IsNullOrEmpty(macchina.BasePath))
{
if (!string.IsNullOrEmpty(macchina.RuleName))
{
ruleName = macchina.RuleName;
// gestione confRule...
SearchRules currRule = new SearchRules();
try
{
string rawData = File.ReadAllText(FileController.rulePath(ruleName));
currRule = JsonConvert.DeserializeObject<SearchRules>(rawData);
//Log.Info($"Conf rule acquisito da file {ruleName}:{Environment.NewLine}{rawData}");
}
catch (Exception exc)
{
Log.Error($"Eccezione in deserializzazione conf rule{Environment.NewLine}{exc}");
}
// se NON deserializzato inizializzo hard-coded
if (currRule.Name == "ND")
{
// fare: lettura conf rule x recupero tag x singola macchina
//$"\\b{fileName}" + @".{0,2}\([\w\d\s.]+\)";
Dictionary<string, string> confReplace = new Dictionary<string, string>();
confReplace.Add("(", " ");
confReplace.Add(")", " ");
Dictionary<string, string> fileExtReplace = new Dictionary<string, string>();
fileExtReplace.Add(".P-2", "");
// hard coded + salvataggio conf x creare json
currRule = new SearchRules()
{
Name = "Commento Filename",
Mode = SearchMode.StringOnFile,
MaxChar2Search = 100,
ReplaceCR = true,
RegExPattern = "\\b{{fileName}}" + @".{0,2}\([\w\d\s./]+\)",
RegExRepFileName = true,
FileNameExtReplace = fileExtReplace,
ExcludedTags = new List<string>() { "M4", "M5", "M4+A", "M4+B", "M5+A", "M5+B" },
OutReplace = confReplace,
OutExcludeFileName = true
};
if (fullLog)
{
// serializzo
string rawRule = JsonConvert.SerializeObject(currRule, Formatting.Indented);
Log.Trace($"Conf rule generato:{Environment.NewLine}{rawRule}");
}
}
checkDone = dbController.CheckFileArchived(macchina.IdxMacchina, macchina.BasePath, numDayPre, "*.*", forceTag, currRule);
}
}
}
catch (Exception exc)
{
Log.Error($"Eccezione in updateMachineArchive{Environment.NewLine}{exc}{Environment.NewLine}{exc.InnerException}");
}
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
Log.Info($"Effettuato update archivio file MACCHINA | last {numDayPre} days | {checkDone} checked | {ts.TotalMilliseconds} ms");
return await Task.FromResult(checkDone);
}
#endregion Public Methods
}
}
+155
View File
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
namespace MP.Land.Data
{
public class MessageService
{
#region Private Fields
private SelectData _fileFilter = SelectData.Init(5, 15);
private string _pageIcon;
private string _pageName;
private string _searchVal = "";
private bool showSearch;
#endregion Private Fields
#region Public Events
public event Action EA_FilterUpdated;
public event Action EA_HideSearch;
public event Action EA_PageUpdated;
public event Action EA_SearchUpdated;
public event Action EA_ShowSearch;
#endregion Public Events
#region Public Properties
public SelectData File_Filter
{
get => _fileFilter;
set
{
if (_fileFilter != value)
{
_fileFilter = value;
ReportFilter();
}
}
}
public string PageIcon
{
get => _pageIcon;
set
{
if (_pageIcon != value)
{
_pageIcon = value;
ReportPageUpd();
}
}
}
public string PageName
{
get => _pageName;
set
{
if (_pageName != value)
{
_pageName = value;
ReportPageUpd();
}
}
}
public string SearchVal
{
get => _searchVal;
set
{
if (_searchVal != value)
{
_searchVal = value;
ReportSearch();
}
}
}
public bool ShowSearch
{
get => showSearch;
set
{
if (showSearch != value)
{
showSearch = value;
if (showSearch)
{
ReportShowSearch();
}
else
{
ReportHideSearch();
}
}
}
}
#endregion Public Properties
#region Private Methods
private void ReportFilter()
{
if (EA_FilterUpdated != null)
{
EA_FilterUpdated?.Invoke();
}
}
private void ReportHideSearch()
{
if (EA_HideSearch != null)
{
EA_HideSearch?.Invoke();
}
}
private void ReportPageUpd()
{
if (EA_PageUpdated != null)
{
EA_PageUpdated?.Invoke();
}
}
private void ReportSearch()
{
if (EA_SearchUpdated != null)
{
EA_SearchUpdated?.Invoke();
}
}
private void ReportShowSearch()
{
if (EA_ShowSearch != null)
{
EA_ShowSearch?.Invoke();
}
}
#endregion Private Methods
}
}
+113
View File
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land.Data
{
public class SelectData
{
#region Public Properties
public DateTime DateEnd { get; set; } = DateTime.Now.AddMinutes(1);
public DateTime DateStart { get; set; } = DateTime.Now.AddDays(-7);
public string FileName { get; set; } = "";
/// <summary>
/// Primo record x selezione paginata, tipicamente primo della "decina" della pagina corrente
/// </summary>
public int FirstRecord
{
get
{
int primaPag = PageNum % 10;
int decina = PageNum - primaPag;
return PageSize * decina + 1;
}
}
public string IdxMacchina { get; set; } = "";
/// <summary>
/// Recorda da saltare x arrivare alla pagina corrente
/// </summary>
public int NumSkip
{
get
{
return PageSize * (PageNum - 1);
}
}
public bool OnlyActive { get; set; } = true;
public bool OnlyMod { get; set; } = false;
public bool OnlyNoTag { get; set; } = false;
public int PageNum { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string SearchVal { get; set; } = "";
public string Tag { get; set; } = "";
#endregion Public Properties
#region Public Methods
/// <summary>
/// Inizializzazione con periodo e arrotondamento
/// </summary>
/// <param name="minRound"></param>
/// <param name="numDayPrev"></param>
/// <returns></returns>
public static SelectData Init(int minRound, int numDayPrev)
{
TimeSpan DayElapsed = DateTime.Now.Subtract(DateTime.Today);
int minDay = (int)((DayElapsed.TotalMinutes / minRound) + 1) * minRound;
DateTime endRounded = DateTime.Today.AddMinutes(minDay);
SelectData answ = new SelectData()
{
DateEnd = endRounded,
DateStart = endRounded.AddDays(-numDayPrev),
SearchVal = ""
};
return answ;
}
public override bool Equals(object obj)
{
if (!(obj is SelectData item))
return false;
if (PageSize != item.PageSize)
return false;
if (PageNum != item.PageNum)
return false;
if (OnlyActive != item.OnlyActive)
return false;
if (OnlyMod != item.OnlyMod)
return false;
if (OnlyNoTag != item.OnlyNoTag)
return false;
if (SearchVal != item.SearchVal)
return false;
if (FileName != item.FileName)
return false;
if (IdxMacchina != item.IdxMacchina)
return false;
if (Tag != item.Tag)
return false;
if (DateEnd != item.DateEnd)
return false;
if (DateStart != item.DateStart)
return false;
return true;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
#endregion Public Methods
}
}
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
namespace MP.Land.Extensions
{
public static class NavigationManagerExtension
{
#region Public Methods
/// <summary>
/// Estensione metodo NavigationManager
///
/// https://code-maze.com/query-strings-blazor-webassembly/
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="navManager"></param>
/// <param name="key"></param>
/// <returns></returns>
public static T ExtractQueryStringByKey<T>(this NavigationManager navManager, string key)
{
var uri = navManager.ToAbsoluteUri(navManager.Uri);
QueryHelpers.ParseQuery(uri.Query)
.TryGetValue(key, out var queryValue);
if (typeof(T).Equals(typeof(int)))
{
int.TryParse(queryValue, out int result);
return (T)(object)result;
}
if (typeof(T).Equals(typeof(string)))
return (T)(object)queryValue.ToString();
return default;
}
#endregion Public Methods
}
}
+33
View File
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>MP.Land</RootNamespace>
<Version>1.1.2109.1719</Version>
</PropertyGroup>
<ItemGroup>
<Content Remove="Components\CodArtSelector.razor" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DiffMatchPatch" Version="1.0.3" />
<PackageReference Include="Majorsoft.Blazor.Components.Debounce" Version="1.5.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MP.FileData\MP.FileData.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="powershell.exe -ExecutionPolicy Unrestricted -NoProfile -NonInteractive -File $(ProjectDir)\post-build.ps1 -ProjectDir $(ProjectDir) -ProjectPath $(ProjectPath)" />
</Target>
</Project>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
<!-- optional, add some variables
https://github.com/nlog/NLog/wiki/Configuration-file#variables
-->
<variable name="myvar" value="myvalue" />
<!--
See https://github.com/nlog/nlog/wiki/Configuration-file
for information on customizing logging rules and outputs.
-->
<targets>
<!--
add your targets here
See https://github.com/nlog/NLog/wiki/Targets for possible targets.
See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers.
-->
<!--
Write events to a file with the date in the filename.
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
-->
<target xsi:type="File" name="fileTarget" fileName="${basedir}/logs/${shortdate}.log" layout="${longdate} | ${uppercase:${level}} | ${logger:shortName=false} | ${message}" />
<target xsi:type="ColoredConsole" name="consoleTarget" layout="${longdate} | ${uppercase:${level}} | ${logger:shortName=true}| ${message}" />
</targets>
<rules>
<!-- add your logging rules here -->
<!--
Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace) to "f"
<logger name="*" minlevel="Debug" writeTo="f" />
-->
<logger name="*" minlevel="Trace" writeTo="consoleTarget" />
<!--<logger name="Microsoft.*" maxlevel="Info" final="true" />-->
<logger name="*" minlevel="Info" writeTo="fileTarget" />
</rules>
</nlog>
+208
View File
@@ -0,0 +1,208 @@
@page "/Archive"
@using MP.Land.Components
<div class="card">
<div class="card-header table-primary">
<div class="row">
<div class="col-12 col-lg-4">
<div class="d-flex">
<div class="px-2">
<h3>Elenco Programmi</h3>
</div>
<div class="px-2">
<div class="form-group mb-0">
<button id="btnForceCheck" class="btn btn-warning btn-sm" @onclick="() => ForceCheck(30)" title="Analisi Modifiche ultimi 30gg">
<i class="fas fa-sync-alt"></i> Update (30gg) <i class="far fa-folder-open"></i>
</button>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-8 text-right">
<div class="d-flex flex-row-reverse">
<div class="px-2">
<div class="form-group mb-0">
<button id="btnReset" class="btn btn-info btn-sm" @onclick="() => ResetFilter()" title="Reset Filter"><span class="oi oi-loop-circular"></span></button>
</div>
</div>
<div class="px-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<span class="fas fa-industry" aria-hidden="true"></span>
</span>
</div>
<select @bind="@SelIdxMacc" class="form-control form-control-sm">
@if (MacList != null)
{
foreach (var item in MacList)
{
<option value="@item.IdxMacchina">@item.Descrizione (@item.RuleName)</option>
}
}
</select>
</div>
</div>
<div class="px-2">
<div class="input-group input-group-sm">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="togModificati" title="Solo Aperti / Mostra tutti" @bind-value="@OnlyMod" checked="@OnlyMod" />
<label class="custom-control-label" for="togModificati"><sub>Mod</sub></label>
</div>
</div>
</div>
<div class="px-2">
<div class="input-group input-group-sm">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="togAttivi" title="Solo Attivi / Mostra tutti" @bind-value="@OnlyActive" checked="@OnlyActive" />
<label class="custom-control-label" for="togAttivi"><sub>Attivi</sub></label>
</div>
</div>
</div>
<div class="px-2">
<div class="input-group input-group-sm">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="togNoTag" title="Solo senza Tag / Mostra tutti" @bind-value="@OnlyNoTag" checked="@OnlyNoTag" />
<label class="custom-control-label" for="togNoTag"><sub>No Tag</sub></label>
</div>
</div>
</div>
<div class="px-2">
@if (!string.IsNullOrEmpty(SelFileName))
{
<span class="badge badge-secondary">@SelFileName</span>
}
</div>
<div class="px-2">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<button type="button" class="btn btn-sm btn-primary" @onclick="() => OpenDialog()" title="Aggiunta Tag">
<i class="fas fa-search"></i>
</button>
<span class="input-group-text">Tags</span>
</div>
<div class="form-control form-control-sm">
@if (!string.IsNullOrEmpty(SelTag))
{
<span class="badge badge-info">@SelTag</span>
}
else
{
<span> &nbsp; ... &nbsp; </span>
}
</div>
<div class="input-group-append">
<button type="button" class="btn btn-sm btn-secondary" @onclick="() => ResetTag()" title="Reset Tag">
<i class="fas fa-sync"></i>
</button>
</div>
</div>
@if (DeleteDialogOpen)
{
<MP.Land.Components.TagSearch Title="Ricerca Tag" OnClose="@OnDialogClose"></MP.Land.Components.TagSearch>
}
</div>
</div>
</div>
</div>
</div>
<div class="card-body p-1">
@if (currRecord != null)
{
<FileEditor currItem="@currRecord" DataReset="ResetData" DataUpdated="UpdateData" TagList="@TagList" MacList="@MacList"></FileEditor>
}
@if (ListRecords == null)
{
<LoadingData></LoadingData>
}
else if (totalCount == 0)
{
<div class="alert alert-warning text-center display-4">Nessun record trovato</div>
}
else
{
<div class="row">
<div class="col-12">
<table class="table table-sm table-striped table-responsive-lg">
<thead>
<tr>
<th></th>
<th>File</th>
<th>Rev</th>
<th>Size</th>
<th class="text-center">State</th>
<th>Macchina</th>
<th>Tags</th>
<th class="text-right">Modificato</th>
@*<th>Controllo</th>*@
<th></th>
</tr>
</thead>
<tbody>
@foreach (var record in ListRecords)
{
<tr class="@checkSelect(record.FileId)">
<td class="text-nowrap">
@if (currRecord == null && record.Active)
{
<button class="btn btn-sm btn-info" @onclick="() => Edit(record)" title="Edit record #@record.FileId">
<i class="oi oi-pencil"></i>
</button>
}
else
{
<button class="btn btn-sm btn-secondary disabled">
<i class="oi oi-pencil"></i>
</button>
}
</td>
<td>
<div class="@(cssActive(record.Active))">
<div>@record.Name</div>
<button class="btn btn-sm btn-outline-secondary px-1 py-0" @onclick="() => FilterPath(record.Path)" title="Filtra per FilePath">
<div class="small">@record.Path</div>
</button>
</div>
</td>
<td>
<div>@record.Rev</div>
</td>
<td>
<div>@((((double)record.Size)/1024).ToString("N2")) k</div>
</td>
<td class="text-center">
<span title="@record.DiskStatus | Ultimo controllo: @record.LastCheck.ToString(" yyyy.MM.dd HH:mm:ss")">
<span class="@(cssStatusByCod(record.DiskStatus))">
@record.DiskStatus
</span>
</span>
</td>
<td>
<div>@record.Macchina.Nome</div>
<div class="small">@record.Macchina.Descrizione</div>
</td>
<td>
@foreach (var item in record.Tags)
{
<button class="btn btn-sm btn-outline-info px-1 py-0 mr-1" @onclick="() => FilterTag(item.TagId)" title="Filtra Tag">
<div class="small">@item.TagId</div>
</button>
}
</td>
<td class="text-right">
<div>@record.LastMod.ToString("yyyy.MM.dd")</div>
<div class="small">@record.LastMod.ToString("ddd HH:mm.ss")</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
</div>
<div class="card-footer p-1">
<DataPager PageSize="numRecord" currPage="currPage" numRecordChanged="PagerReloadNum" numPageChanged="PagerReloadPage" totalCount="totalCount" showLoading="isLoading" />
</div>
</div>
+523
View File
@@ -0,0 +1,523 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MP.FileData.DatabaseModels;
using MP.Land.Data;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MP.Land.Pages
{
public partial class Archive : ComponentBase, IDisposable
{
#region Private Fields
private static NLog.Logger Log = LogManager.GetCurrentClassLogger();
private FileModel currRecord = null;
private List<FileModel> ListRecords;
private List<MacchinaModel> MacList;
private List<TagModel> TagList;
#endregion Private Fields
#region Protected Fields
protected string _SearchTag;
protected string defTag = "";
protected int totalCount = 0;
#endregion Protected Fields
#region Private Properties
private int currPage
{
get
{
return AppMService.File_Filter.PageNum;
}
set
{
AppMService.File_Filter.PageNum = value;
}
}
private bool isLoading { get; set; } = false;
private int numRecord
{
get
{
return AppMService.File_Filter.PageSize;
}
set
{
AppMService.File_Filter.PageSize = value;
}
}
private bool OnlyActive
{
get
{
bool answ = false;
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.OnlyActive;
}
return answ;
}
set
{
if (!AppMService.File_Filter.OnlyActive.Equals(value))
{
AppMService.File_Filter.OnlyActive = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
private bool OnlyMod
{
get
{
bool answ = false;
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.OnlyMod;
}
return answ;
}
set
{
if (!AppMService.File_Filter.OnlyMod.Equals(value))
{
AppMService.File_Filter.OnlyMod = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
private bool OnlyNoTag
{
get
{
bool answ = false;
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.OnlyNoTag;
}
return answ;
}
set
{
if (!AppMService.File_Filter.OnlyNoTag.Equals(value))
{
AppMService.File_Filter.OnlyNoTag = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
private string SearchVal
{
get
{
string answ = "";
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.SearchVal;
}
return answ;
}
set
{
if (!AppMService.File_Filter.SearchVal.Equals(value))
{
AppMService.File_Filter.SearchVal = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
private string SelFileName
{
get
{
string answ = "";
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.FileName;
}
return answ;
}
set
{
if (!AppMService.File_Filter.FileName.Equals(value))
{
AppMService.File_Filter.FileName = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
private string SelIdxMacc
{
get
{
string answ = "";
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.IdxMacchina;
}
return answ;
}
set
{
if (!AppMService.File_Filter.IdxMacchina.Equals(value))
{
AppMService.File_Filter.IdxMacchina = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
private string SelTag
{
get
{
string answ = "";
if (AppMService.File_Filter != null)
{
answ = AppMService.File_Filter.Tag;
}
return answ;
}
set
{
if (!AppMService.File_Filter.Tag.Equals(value))
{
AppMService.File_Filter.Tag = value;
var pUpd = Task.Run(async () =>
{
await AsyncReload();
});
pUpd.Wait();
}
}
}
#endregion Private Properties
#region Protected Properties
[Inject]
protected MessageService AppMService { get; set; }
[Inject]
protected FileArchDataService DataService { get; set; }
[Inject]
protected IJSRuntime JSRuntime { get; set; }
[Inject]
protected NavigationManager NavManager { get; set; }
protected string SearchTag
{
get
{
return _SearchTag;
}
set
{
_SearchTag = value;
// se son > 3 char... debounce...
if (string.IsNullOrEmpty(value))
{
_SearchTag = defTag;
}
if (value.Length >= defTag.Length)
{
var pUpd = Task.Run(async () =>
{
TagList = await DataService.TagGetFilt(SearchTag);
});
pUpd.Wait();
}
isLoading = false;
}
}
#endregion Protected Properties
#region Public Properties
public bool DeleteDialogOpen { get; set; }
#endregion Public Properties
#region Private Methods
private string cssActive(bool active)
{
string answ = active ? "text-dark" : "text-secondary textStriked";
return answ;
}
private string cssStatusByCod(FileData.FileState currStatus)
{
string answ = "badge";
switch (currStatus)
{
case FileData.FileState.Changed:
answ += " badge-warning";
break;
case FileData.FileState.Deleted:
answ += " badge-danger";
break;
case FileData.FileState.Ok:
answ += " badge-success";
break;
case FileData.FileState.ND:
default:
answ += " badge-light";
break;
}
return answ;
}
private async Task OnDialogClose(bool accepted)
{
DeleteDialogOpen = false;
currPage = 1;
await AsyncReload();
//StateHasChanged();
}
private void OpenDialog()
{
DeleteDialogOpen = true;
StateHasChanged();
}
#endregion Private Methods
#region Protected Methods
protected async Task AsyncReload()
{
isLoading = true;
currRecord = null;
ListRecords = null;
await ReloadData();
isLoading = false;
}
protected void Edit(FileModel selRecord)
{
// rileggo dal DB il record corrente...
var pUpd = Task.Run(async () => currRecord = await DataService.FileGetByKey(selRecord.FileId));
pUpd.Wait();
}
protected async Task FilterPath(string searchVal)
{
SelFileName = searchVal;
OnlyActive = false;
await ReloadAllData();
isLoading = false;
}
protected async Task FilterTag(string searchVal)
{
SelTag = searchVal;
await ReloadAllData();
isLoading = false;
}
protected async Task ForceCheck(int numDays)
{
currRecord = null;
ListRecords = null;
// importante altrimenti NON mostra update UI
await Task.Delay(1);
//AppMService.File_Filter = SelectData.Init(5, 10);
var numCheck = await DataService.updateAllArchive(numDays, false);
await ReloadAllData();
await Task.Delay(1);
await RefreshDisplayLoading();
}
protected override async Task OnInitializedAsync()
{
SearchTag = defTag;
AppMService.ShowSearch = true;
AppMService.PageName = "Archivio File Programmi";
AppMService.PageIcon = "fas fa-folder pr-2";
AppMService.EA_SearchUpdated += OnSeachUpdated;
AppMService.EA_FilterUpdated += OnFilterUpdated;
await ReloadAllData();
isLoading = false;
}
protected async Task PagerReloadNum(int newNum)
{
numRecord = newNum;
await ReloadData();
isLoading = false;
}
protected async Task PagerReloadPage(int newNum)
{
currPage = newNum;
await ReloadData();
isLoading = false;
}
protected async Task RefreshDisplayLoading()
{
await Task.Delay(1);
isLoading = false;
}
protected async Task ReloadAllData()
{
isLoading = true;
MacList = await DataService.MacchineGetAll();
SelIdxMacc = "0";
SearchTag = defTag;
TagList = await DataService.TagGetFilt(SearchTag);
await ReloadData();
}
protected async Task ReloadData()
{
isLoading = true;
// importante altrimenti NON mostra update UI
await Task.Delay(1);
totalCount = await DataService.FileCountFilt(AppMService.File_Filter);
//SearchRecords = await DataService.FileGetFilt(AppMService.File_Filter);
//// faccio paginazione SOLO NELLA DECINA attuale... (quindi non tutte le pagine ma solo subset)
//ListRecords = SearchRecords.Skip(numRecord * (currPage % 10 - 1)).Take(numRecord).ToList();
ListRecords = await DataService.FileGetFilt(AppMService.File_Filter);
await Task.Delay(1);
}
protected void ResetData()
{
DataService.rollBackEdit(currRecord);
currRecord = null;
}
protected async Task ResetFilter()
{
currRecord = null;
ListRecords = null;
AppMService.File_Filter = SelectData.Init(5, 10);
AppMService.SearchVal = "";
SearchTag = defTag;
await ReloadAllData();
isLoading = false;
}
protected void ResetSearchTag()
{
SearchTag = defTag;
}
protected void Select(FileModel selRecord)
{
// applico filtro da selezione
currRecord = selRecord;
}
protected async Task UpdateData()
{
currRecord = null;
ListRecords = null;
DataService.ResetController();
await ReloadData();
await Task.Delay(1);
isLoading = false;
}
#endregion Protected Methods
#region Public Methods
public string checkSelect(int FileId)
{
string answ = "";
if (currRecord != null)
{
try
{
answ = (currRecord.FileId == FileId) ? "table-info" : "";
}
catch
{ }
}
return answ;
}
public void Dispose()
{
AppMService.EA_SearchUpdated -= OnSeachUpdated;
AppMService.EA_FilterUpdated -= OnFilterUpdated;
}
public async void OnFilterUpdated()
{
await ReloadData();
//StateHasChanged();
}
public async void OnSeachUpdated()
{
SearchVal = AppMService.SearchVal;
//await ReloadData();
StateHasChanged();
}
public void ResetTag()
{
SelTag = "";
}
#endregion Public Methods
}
}
+41
View File
@@ -0,0 +1,41 @@
@page
@model MP.Land.Pages.ErrorModel
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/app.css" rel="stylesheet" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
#region Private Fields
private readonly ILogger<ErrorModel> _logger;
#endregion Private Fields
#region Public Constructors
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
#endregion Public Properties
#region Public Methods
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
#endregion Public Methods
}
}
+57
View File
@@ -0,0 +1,57 @@
@page "/"
@using MP.Land.Data
@inject MessageService AppMService
<div class="jumbotron py-4">
<div class="row">
<div class="col-12 col-lg-4">
<h1>MAPO Prog</h1>
<div>
Gestione archivio programmi macchina per MES 4.0
</div>
</div>
<div class="col-12 col-lg-8 text-right">
<div class="text-light h1 d-none d-md-block">
<span class="fas fa-home" aria-hidden="true"></span> | <span class="fas fa-folder" aria-hidden="true"></span> | <span class="fas fa-wrench" aria-hidden="true"></span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 my-lg-5">
@*<SetupDiagnostics></SetupDiagnostics>*@
</div>
<div class="col-12 text-center">
<div class="row">
<div class="col-4"></div>
<div class="col-4">
<i class="fas fa-code-branch fa-5x"></i>
</div>
<div class="col-4"></div>
</div>
<h4>
Sistema di gestione ed archiviazione storica dei programmi macchina CNC.
</h4>
</div>
<div class="col-12 text-center mt-5">
<div class="col-4"></div>
<div class="col-4"></div>
<div class="col-4 badge badge-pill badge-dark">
<div class="px-1">
<a class="text-light" href="https://www.egalware.com/" target="_blank">powered by&nbsp;EgalWare <img width="24" class="img-fluid" src="img/LogoBlu.svg" /></a>
</div>
</div>
</div>
</div>
@code
{
protected override void OnInitialized()
{
AppMService.ShowSearch = false;
AppMService.PageName = "Home";
AppMService.PageIcon = "fas fa-home pr-2";
}
}
+17
View File
@@ -0,0 +1,17 @@
@page "/Setup"
@using MP.Land.Data
@using System.Text
@using DiffMatchPatch
@inject MessageService AppMService
<div class="card">
<div class="card-header">
<h4>Setup</h4>
</div>
<div class="card-body">
<MP.Land.Components.ArchiveStatus></MP.Land.Components.ArchiveStatus>
</div>
@*<div class="card-footer">Footer</div>*@
</div>
+21
View File
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Components;
using MP.Land.Data;
using System;
using System.Threading.Tasks;
namespace MP.Land.Pages
{
public partial class Setup : ComponentBase
{
#region Protected Methods
protected override void OnInitialized()
{
AppMService.ShowSearch = false;
AppMService.PageName = "Setup";
AppMService.PageIcon = "fas fa-wrench pr-2";
}
#endregion Protected Methods
}
}
+28
View File
@@ -0,0 +1,28 @@
@page "/Test"
@using Majorsoft.Blazor.Components.Debounce
<h3>Test</h3>
<DebounceInput id="in1" class="form-control w-100" placeholder="@("Digitare almeno " + _minCharsLength + " caratteri")" autocomplete="off"
@ref="input1"
@bind-Value="@_debounceInputValue" @bind-Value:event="OnInput"
DebounceTime="@_debounceMilisec"
MinLength="@_minCharsLength"
OnValueChanged="e => { _notifiedInputValue = e; }"
ForceNotifyByEnter="@_forceNotifyByEnter"
ForceNotifyOnBlur="@_forceNotifyOnBlur" />
<div>Notified value: @_notifiedInputValue</div>
<div>Actual value: @_debounceInputValue</div>
<input type="button" class="btn btn-primary" value="Read out actual value" @onclick="(_ => { _debounceInputValue = input1.Value; })" />
@code {
//DebounceInput
private string _debounceInputValue = "";
private string _notifiedInputValue = "";
private int _debounceMilisec = 200;
private int _minCharsLength = 2;
private bool _forceNotifyByEnter = true;
private bool _forceNotifyOnBlur = true;
private DebounceInput input1;
}
+47
View File
@@ -0,0 +1,47 @@
@page "/"
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@namespace MP.Land.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>MP.Land</title>
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" />
<link rel="stylesheet" href="font-awesome/css/fontawesome.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="MP.Land.styles.css" rel="stylesheet" />
</head>
<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<!-- inside of body section and after the div/app tag -->
<script src="jquery/jquery.min.js"></script>
<script src="popper.js/umd/popper.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
<script src="font-awesome/js/all.min.js"></script>
<script src="_framework/blazor.server.js"></script>
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land
{
public class Program
{
#region Public Methods
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog();
public static void Main(string[] args)
{
// inclusione NLog:
// https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-5
// https://codewithmukesh.com/blog/logging-with-nlog-in-aspnet-core/
var logger = NLog.Web.NLogBuilder.ConfigureNLog("NLog.config").GetCurrentClassLogger();
try
{
logger.Info("MP.Land Application Starting Up");
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
logger.Error(exception, "Stopped MP.Land program because of exception");
throw;
}
finally
{
NLog.LogManager.Shutdown();
}
}
#endregion Public Methods
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:7312",
"sslPort": 44309
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MP.Land": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+27
View File
@@ -0,0 +1,27 @@
<body>
<i>Modulo gestione Programmi MAPO</i>
<h4>Versione: {{CURRENT-REL}}</h4>
<br />
Note di rilascio:
<ul>
<li>
<b>Ultime modifiche:</b>
<ul>{{LAST-CHANGES}}</ul>
</li>
<li>
<b>v.1.* &rarr;</b>
<ul>
<li>Prima release dotnet5</li>
<li>Integrazione EFCore</li>
</ul>
</li>
</ul>
<div>
<div style="float: left;">
<img src="logoSteamware.png" />
</div>
<div style="float: right;">
<a href="https://www.steamware.net/IOT" target="_blank">&copy; Steamware 2006-2021</a>
</div>
</div>
</body>
+27
View File
@@ -0,0 +1,27 @@
<body>
<i>Modulo gestione Programmi MAPO</i>
<h4>Versione: 1.1.2109.1719</h4>
<br />
Note di rilascio:
<ul>
<li>
<b>Ultime modifiche:</b>
<ul>{{LAST-CHANGES}}</ul>
</li>
<li>
<b>v.1.* &rarr;</b>
<ul>
<li>Prima release dotnet5</li>
<li>Integrazione EFCore</li>
</ul>
</li>
</ul>
<div>
<div style="float: left;">
<img src="logoSteamware.png" />
</div>
<div style="float: right;">
<a href="https://www.steamware.net/IOT" target="_blank">&copy; Steamware 2006-2021</a>
</div>
</div>
</body>
+1
View File
@@ -0,0 +1 @@
1.1.2109.1719
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.0.0.0</version>
<url>https://nexus.steamware.net/repository/SWS/{{DIRNAME}}/{{BRANCHNAME}}/{{PACKNAME}}.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/{{DIRNAME}}/{{BRANCHNAME}}/ChangeLog.html</changelog>
<mandatory>false</mandatory>
</item>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.1.2109.1719</version>
<url>https://nexus.steamware.net/repository/SWS/MP-PROG/stable/LAST/MP.Land.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-PROG/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
</item>
+60
View File
@@ -0,0 +1,60 @@
@inherits LayoutComponentBase
@using MP.Land.Data
@using MP.Land.Components
@inject MessageService AppMService
@implements IDisposable
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<CascadingValue Name="ShowSearch" Value=@ShowSearch>
<div class="main mr-1">
<div class="top-row">
<CmpTop></CmpTop>
</div>
<div class="content pt-1 pt-lg-2 mb-5">
@Body
</div>
<div class="fixed-bottom bottom-row">
<CmpFooter></CmpFooter>
</div>
</div>
</CascadingValue>
</div>
@code {
bool ShowSearch { get; set; } = false;
protected override void OnInitialized()
{
AppMService.EA_ShowSearch += OnShowSearch;
AppMService.EA_HideSearch += OnHideSearch;
}
public void OnShowSearch()
{
ShowSearch = true;
InvokeAsync(() =>
{
StateHasChanged();
});
}
public void OnHideSearch()
{
ShowSearch = false;
InvokeAsync(() =>
{
StateHasChanged();
});
}
public void Dispose()
{
AppMService.EA_ShowSearch -= OnShowSearch;
AppMService.EA_ShowSearch -= OnHideSearch;
}
}
+78
View File
@@ -0,0 +1,78 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
.sidebar, .sidebarSmall {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 20%, #3aa6ff 90%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
height: 3.5rem;
align-items: center;
/*justify-content: space-evenly;
display: flex;*/
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.bottom-row {
color: #dedede;
background-color: #000000;
height: 2rem;
align-items: center;
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.sidebarSmall {
width: 80px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.bottom-row {
position: fixed;
bottom: 0;
z-index: 1;
}
.main > div {
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
/*padding-left: 2rem !important;
padding-right: 1.5rem !important;*/
}
}
+37
View File
@@ -0,0 +1,37 @@
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">MP.Land</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="fas fa-home fa-2x pr-2" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="Archive">
<span class="far fa-folder fa-2x pr-2" aria-hidden="true"></span> Archivio File
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="Setup">
<span class="fas fa-wrench fa-2x pr-2" aria-hidden="true"></span> Setup
</NavLink>
</li>
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
+62
View File
@@ -0,0 +1,62 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}
+233
View File
@@ -0,0 +1,233 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MP.Land.Data;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace MP.Land
{
public class Startup
{
#region Public Constructors
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
#endregion Public Constructors
#region Public Properties
public IConfiguration Configuration { get; }
#endregion Public Properties
#region Public Methods
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// cultura IT...
var supportedCultures = new[]{
new CultureInfo("it-IT")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("it-IT"),
SupportedCultures = supportedCultures,
FallBackToParentCultures = false
});
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture("it-IT");
//// Registrazione Elmah:
//// https://github.com/ElmahCore/ElmahCore
//app.UseElmah();
// fix forwarders
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
//app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
//app.UseAuthentication();
//app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
endpoints.MapBlazorHub();
//endpoints.MapHealthChecksUI();
//endpoints.MapHealthChecks("/health", new HealthCheckOptions
//{
// Predicate = _ => true,
// ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
//});
endpoints.MapFallbackToPage("/_Host");
});
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
#if false
// init info x DB
string dbServerAddr = Configuration["DbConfig:Server"];
string nKey = Configuration["DbConfig:nKey"];
string sKey = Configuration["DbConfig:sKey"];
DbConfig.InitDb(dbServerAddr, nKey, sKey);
// inizializzo il DB e creo (se necessario) l'utente
DbConfig.CheckUser(nKey, sKey);
// verifico se serve applicazione migrazioni
//DbConfig.ExecMigrationMain();
//DbConfig.ExecMigrationIdentity();
// altri parametri per check vari
string connStringDB = DbConfig.CONNECTION_STRING;
string connStringRedis = Configuration.GetConnectionString("Redis");
string redisSrvAddr = connStringRedis.Substring(0, connStringRedis.IndexOf(":"));
//var qrCodeUri = new Uri(Configuration["ZCodeUrl"]);
//string qrCodeAddr = qrCodeUri.Host;
#endif
#if false
// healthchecks
services.AddHealthChecks()
.AddMySql(connStringDB, "MySql instance")
.AddAsyncCheck($"DB PING ({dbServerAddr})", () => Health.Checks.PingCheck(dbServerAddr))
.AddAsyncCheck($"Redis PING ({redisSrvAddr})", () => Health.Checks.PingCheck(redisSrvAddr))
//.AddAsyncCheck($"QrCode PING ({qrCodeAddr})", () => Health.Checks.PingCheck(qrCodeAddr))
.AddProcessAllocatedMemoryHealthCheck(512, "Max Process memory (<512MB)", failureStatus: HealthStatus.Degraded) // 512 MB max allocated memory
.AddRedis(Configuration.GetConnectionString("Redis"), "Redis", failureStatus: HealthStatus.Degraded)
//.AddUrlGroup(new Uri(Configuration["ZCodeUrl"]), name: $"QrCode Gen ({Configuration["ZCodeUrl"]})", failureStatus: HealthStatus.Degraded)
.AddAsyncCheck($"MySql Root User", () => Health.Checks.DbUserRoot("MySql"))
.AddAsyncCheck($"MySql App Users", () => Health.Checks.DbUserApp(DbConfig.DATABASE_NAME))
.AddAsyncCheck($"MySql Identity", () => Health.Checks.DbIdentity(DbConfig.DATABASE_NAME))
.AddAsyncCheck($"MySql PlantLog", () => Health.Checks.DbPlantLogTable(DbConfig.DATABASE_NAME))
;
//.AddDiskStorageHealthCheck(s => s.AddDrive("C:\\", 1024)) // 1024 MB (1 GB) free minimum
//.AddProcessHealthCheck("ProcessName", p => p.Length > 0) // check if process is running
//.AddWindowsServiceHealthCheck("someservice", s => s.Status == ServiceControllerStatus.Running);
services
.AddHealthChecksUI(s =>
{
s.AddHealthCheckEndpoint("GWMS_Services", "health");
s.SetEvaluationTimeInSeconds(60);
s.SetMinimumSecondsBetweenFailureNotifications(120);
s.SetApiMaxActiveRequests(5);
s.SetHeaderText("GWMS Health Check Status");
})
.AddInMemoryStorage();
// abilitazione x email management con MailKit
services.AddTransient<IEmailSender, MailKitEmailSender>();
services.Configure<MailKitEmailSenderOptions>(options =>
{
options.Host_Address = Configuration["ExternalProviders:MailKit:SMTP:Address"];
options.Host_Port = Convert.ToInt32(Configuration["ExternalProviders:MailKit:SMTP:Port"]);
options.Host_Username = Configuration["ExternalProviders:MailKit:SMTP:Account"];
options.Host_Password = Configuration["ExternalProviders:MailKit:SMTP:Password"];
options.Sender_EMail = Configuration["ExternalProviders:MailKit:SMTP:SenderEmail"];
options.Sender_Name = Configuration["ExternalProviders:MailKit:SMTP:SenderName"];
});
#endif
// cookie applicazione da 14 gg (defaul) a 30
services.ConfigureApplicationCookie(o =>
{
o.ExpireTimeSpan = TimeSpan.FromDays(30);
o.SlidingExpiration = true;
});
#if false
// token di sicurezza dati a 3h
services.Configure<DataProtectionTokenProviderOptions>(o =>
o.TokenLifespan = TimeSpan.FromHours(3));
#endif
#if false
// setup MySql
//string connString = Configuration.GetConnectionString("AdminConnection");
var serverVersion = DbConfig.MysqlServerVersion(connStringDB);
services.AddDbContext<UserIdentityDbContext>(options =>
options.UseMySql(connStringDB, serverVersion));
// identity management
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<UserIdentityDbContext>();
#endif
#if false
services.AddBlazorise(options =>
{
options.ChangeTextOnKeyPress = true; // optional
})
.AddBootstrapProviders()
.AddFontAwesomeIcons();
#endif
//// Elmah
//services.AddElmah();
//string elmaConn = "Data Source=SQL2016DEV;Initial Catalog=Elmah;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=SHERPA.BBM;";
//services.AddElmah<SqlErrorLog>(options =>
//{
// options.ConnectionString = elmaConn;
//});
services.AddStackExchangeRedisCache(options =>
{
//options.Configuration = "localhost:6379";
options.ConfigurationOptions = new StackExchange.Redis.ConfigurationOptions() { KeepAlive = 180, DefaultDatabase = 5, EndPoints = { { "localhost", 6379 } } };
options.InstanceName = "MP:Prog";
});
services.AddLocalization();
services.AddRazorPages();
services.AddServerSideBlazor();
#if false
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
#endif
services.AddSingleton<IConfiguration>(Configuration);
//services.AddTransient<Services.BlazorTimer>();
//services.AddSingleton<FileArchDataService>();
services.AddScoped<FileArchDataService>();
services.AddScoped<MessageService>();
}
#endregion Public Methods
}
}
+10
View File
@@ -0,0 +1,10 @@
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using MP.Land
@using MP.Land.Shared
+10
View File
@@ -0,0 +1,10 @@
{
//"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=MoonPro_PROG;Trusted_Connection=True;MultipleActiveResultSets=true",
"MP.Land": "Server=localhost\\SQLEXPRESS;Database=MoonPro_PROG;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;"
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=SQL2016DEV;Database=MoonPro_PROG;Trusted_Connection=True;MultipleActiveResultSets=true",
"MP.Land": "Server=SQL2016DEV;Database=MoonPro_PROG;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;"
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
//"LogLevel": {
// "Default": "Debug",
// "Microsoft": "Information",
// "Microsoft.AspNetCore.SignalR": "Debug",
// "Microsoft.AspNetCore.Http.Connections": "Debug",
// "System": "Information"
//}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=SQL2016DEV;Database=MoonPro_PROG;Trusted_Connection=True;MultipleActiveResultSets=true",
"MP.Land": "Server=SQL2016DEV;Database=MoonPro_PROG;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;",
"Redis": "localhost:6379"
}
}
+8
View File
@@ -0,0 +1,8 @@
[
{
"outputFileName": "wwwroot/bootstrap/css/bootstrap.min.css",
"inputFiles": [
"wwwroot/bootstrap/css/bootstrap.css"
]
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"outputFile": "wwwroot/css/font.css",
"inputFile": "wwwroot/css/font.less"
},
{
"outputFile": "wwwroot/css/site.css",
"inputFile": "wwwroot/css/site.less"
}
]
+22
View File
@@ -0,0 +1,22 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "font-awesome@5.15.4",
"destination": "wwwroot/font-awesome/"
},
{
"library": "bootstrap@4.6.0",
"destination": "wwwroot/bootstrap/"
},
{
"library": "popper.js@1.16.1",
"destination": "wwwroot/popper.js/"
},
{
"library": "jquery@3.5.1",
"destination": "wwwroot/jquery/"
}
]
}
+1
View File
@@ -0,0 +1 @@

+31
View File
@@ -0,0 +1,31 @@
param([string]$ProjectDir, [string]$ProjectPath);
$FileVers="Resources\VersNum.txt"
$FileManIn="Resources\manifest-original.xml"
$FileManOut="Resources\manifest.xml"
$FileCLogIn="Resources\ChangeLog-original.html"
$FileCLogOut="Resources\ChangeLog.html"
$MajMin="1.1."
$currentDate = get-date -format yyMM;
$currentTime = get-date -format ddHH;
$find = "<Version>(.|\n)*?</Version>";
$currRelNum=$MajMin + $currentDate +"." + $currentTime
$replace = "<Version>" + $MajMin + $currentDate +"." + $currentTime + "</Version>";
$csproj = Get-Content $ProjectPath
$csprojUpdated = $csproj -replace $find, $replace
Set-Content -Path $ProjectPath -Value $csprojUpdated
Set-Content -Path $FileVers -Value $currRelNum
# replace x manifest
$manData = Get-Content $FileManIn
$manData = $manData -replace "1.0.0.0", $currRelNum
$manData = $manData -replace "{{DIRNAME}}", "MP-PROG"
$manData = $manData -replace "{{BRANCHNAME}}", "stable/LAST"
$manData = $manData -replace "{{PACKNAME}}", "MP.Land"
Set-Content -Path $FileManOut -Value $manData
# replace x ChangeLog
$clogData = Get-Content $FileCLogIn
$clogData = $clogData -replace "{{CURRENT-REL}}", $currRelNum
Set-Content -Path $FileCLogOut -Value $clogData
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+325
View File
@@ -0,0 +1,325 @@
/*!
* Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([class]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[role="button"] {
cursor: pointer;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
//
// Base styles
//
.alert {
position: relative;
padding: $alert-padding-y $alert-padding-x;
margin-bottom: $alert-margin-bottom;
border: $alert-border-width solid transparent;
@include border-radius($alert-border-radius);
}
// Headings for larger alerts
.alert-heading {
// Specified to prevent conflicts of changing $headings-color
color: inherit;
}
// Provide class for links that match alerts
.alert-link {
font-weight: $alert-link-font-weight;
}
// Dismissible alerts
//
// Expand the right padding and account for the close button's positioning.
.alert-dismissible {
padding-right: $close-font-size + $alert-padding-x * 2;
// Adjust close link position
.close {
position: absolute;
top: 0;
right: 0;
z-index: 2;
padding: $alert-padding-y $alert-padding-x;
color: inherit;
}
}
// Alternate styles
//
// Generate contextual modifier classes for colorizing the alert.
@each $color, $value in $theme-colors {
.alert-#{$color} {
@include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level));
}
}
@@ -0,0 +1,54 @@
// Base class
//
// Requires one of the contextual, color modifier classes for `color` and
// `background-color`.
.badge {
display: inline-block;
padding: $badge-padding-y $badge-padding-x;
@include font-size($badge-font-size);
font-weight: $badge-font-weight;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
@include border-radius($badge-border-radius);
@include transition($badge-transition);
@at-root a#{&} {
@include hover-focus() {
text-decoration: none;
}
}
// Empty badges collapse automatically
&:empty {
display: none;
}
}
// Quick fix for badges in buttons
.btn .badge {
position: relative;
top: -1px;
}
// Pill badges
//
// Make them extra rounded with a modifier to replace v3's badges.
.badge-pill {
padding-right: $badge-pill-padding-x;
padding-left: $badge-pill-padding-x;
@include border-radius($badge-pill-border-radius);
}
// Colors
//
// Contextual variations (linked badges get darker on :hover).
@each $color, $value in $theme-colors {
.badge-#{$color} {
@include badge-variant($value);
}
}
@@ -0,0 +1,42 @@
.breadcrumb {
display: flex;
flex-wrap: wrap;
padding: $breadcrumb-padding-y $breadcrumb-padding-x;
margin-bottom: $breadcrumb-margin-bottom;
@include font-size($breadcrumb-font-size);
list-style: none;
background-color: $breadcrumb-bg;
@include border-radius($breadcrumb-border-radius);
}
.breadcrumb-item {
// The separator between breadcrumbs (by default, a forward-slash: "/")
+ .breadcrumb-item {
padding-left: $breadcrumb-item-padding;
&::before {
float: left; // Suppress inline spacings and underlining of the separator
padding-right: $breadcrumb-item-padding;
color: $breadcrumb-divider-color;
content: escape-svg($breadcrumb-divider);
}
}
// IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built
// without `<ul>`s. The `::before` pseudo-element generates an element
// *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
//
// To trick IE into suppressing the underline, we give the pseudo-element an
// underline and then immediately remove it.
+ .breadcrumb-item:hover::before {
text-decoration: underline;
}
// stylelint-disable-next-line no-duplicate-selectors
+ .breadcrumb-item:hover::before {
text-decoration: none;
}
&.active {
color: $breadcrumb-active-color;
}
}
@@ -0,0 +1,163 @@
// stylelint-disable selector-no-qualifying-type
// Make the div behave like a button
.btn-group,
.btn-group-vertical {
position: relative;
display: inline-flex;
vertical-align: middle; // match .btn alignment given font-size hack above
> .btn {
position: relative;
flex: 1 1 auto;
// Bring the hover, focused, and "active" buttons to the front to overlay
// the borders properly
@include hover() {
z-index: 1;
}
&:focus,
&:active,
&.active {
z-index: 1;
}
}
}
// Optional: Group multiple button groups together for a toolbar
.btn-toolbar {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.input-group {
width: auto;
}
}
.btn-group {
// Prevent double borders when buttons are next to each other
> .btn:not(:first-child),
> .btn-group:not(:first-child) {
margin-left: -$btn-border-width;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-right-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-left-radius(0);
}
}
// Sizing
//
// Remix the default button sizing classes into new ones for easier manipulation.
.btn-group-sm > .btn { @extend .btn-sm; }
.btn-group-lg > .btn { @extend .btn-lg; }
//
// Split button dropdowns
//
.dropdown-toggle-split {
padding-right: $btn-padding-x * .75;
padding-left: $btn-padding-x * .75;
&::after,
.dropup &::after,
.dropright &::after {
margin-left: 0;
}
.dropleft &::before {
margin-right: 0;
}
}
.btn-sm + .dropdown-toggle-split {
padding-right: $btn-padding-x-sm * .75;
padding-left: $btn-padding-x-sm * .75;
}
.btn-lg + .dropdown-toggle-split {
padding-right: $btn-padding-x-lg * .75;
padding-left: $btn-padding-x-lg * .75;
}
// The clickable button for toggling the menu
// Set the same inset shadow as the :active state
.btn-group.show .dropdown-toggle {
@include box-shadow($btn-active-box-shadow);
// Show no shadow for `.btn-link` since it has no other button styles.
&.btn-link {
@include box-shadow(none);
}
}
//
// Vertical button groups
//
.btn-group-vertical {
flex-direction: column;
align-items: flex-start;
justify-content: center;
> .btn,
> .btn-group {
width: 100%;
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) {
margin-top: -$btn-border-width;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-bottom-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-top-radius(0);
}
}
// Checkbox and radio options
//
// In order to support the browser's form validation feedback, powered by the
// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
// `display: none;` or `visibility: hidden;` as that also hides the popover.
// Simply visually hiding the inputs via `opacity` would leave them clickable in
// certain cases which is prevented by using `clip` and `pointer-events`.
// This way, we ensure a DOM element is visible to position the popover from.
//
// See https://github.com/twbs/bootstrap/pull/12794 and
// https://github.com/twbs/bootstrap/pull/14559 for more information.
.btn-group-toggle {
> .btn,
> .btn-group > .btn {
margin-bottom: 0; // Override default `<label>` value
input[type="radio"],
input[type="checkbox"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
}
}
@@ -0,0 +1,142 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.btn {
display: inline-block;
font-family: $btn-font-family;
font-weight: $btn-font-weight;
color: $body-color;
text-align: center;
text-decoration: if($link-decoration == none, null, none);
white-space: $btn-white-space;
vertical-align: middle;
user-select: none;
background-color: transparent;
border: $btn-border-width solid transparent;
@include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-line-height, $btn-border-radius);
@include transition($btn-transition);
@include hover() {
color: $body-color;
text-decoration: none;
}
&:focus,
&.focus {
outline: 0;
box-shadow: $btn-focus-box-shadow;
}
// Disabled comes first so active can properly restyle
&.disabled,
&:disabled {
opacity: $btn-disabled-opacity;
@include box-shadow(none);
}
&:not(:disabled):not(.disabled) {
cursor: if($enable-pointer-cursor-for-buttons, pointer, null);
&:active,
&.active {
@include box-shadow($btn-active-box-shadow);
&:focus {
@include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
}
}
}
}
// Future-proof disabling of clicks on `<a>` elements
a.btn.disabled,
fieldset:disabled a.btn {
pointer-events: none;
}
//
// Alternate buttons
//
@each $color, $value in $theme-colors {
.btn-#{$color} {
@include button-variant($value, $value);
}
}
@each $color, $value in $theme-colors {
.btn-outline-#{$color} {
@include button-outline-variant($value);
}
}
//
// Link buttons
//
// Make a button look and behave like a link
.btn-link {
font-weight: $font-weight-normal;
color: $link-color;
text-decoration: $link-decoration;
@include hover() {
color: $link-hover-color;
text-decoration: $link-hover-decoration;
}
&:focus,
&.focus {
text-decoration: $link-hover-decoration;
}
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
pointer-events: none;
}
// No need for an active state here
}
//
// Button Sizes
//
.btn-lg {
@include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-line-height-lg, $btn-border-radius-lg);
}
.btn-sm {
@include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-line-height-sm, $btn-border-radius-sm);
}
//
// Block button
//
.btn-block {
display: block;
width: 100%;
// Vertically space out multiple block buttons
+ .btn-block {
margin-top: $btn-block-spacing-y;
}
}
// Specificity overrides
input[type="submit"],
input[type="reset"],
input[type="button"] {
&.btn-block {
width: 100%;
}
}
+286
View File
@@ -0,0 +1,286 @@
//
// Base styles
//
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106
height: $card-height;
word-wrap: break-word;
background-color: $card-bg;
background-clip: border-box;
border: $card-border-width solid $card-border-color;
@include border-radius($card-border-radius);
> hr {
margin-right: 0;
margin-left: 0;
}
> .list-group {
border-top: inherit;
border-bottom: inherit;
&:first-child {
border-top-width: 0;
@include border-top-radius($card-inner-border-radius);
}
&:last-child {
border-bottom-width: 0;
@include border-bottom-radius($card-inner-border-radius);
}
}
// Due to specificity of the above selector (`.card > .list-group`), we must
// use a child selector here to prevent double borders.
> .card-header + .list-group,
> .list-group + .card-footer {
border-top: 0;
}
}
.card-body {
// Enable `flex-grow: 1` for decks and groups so that card blocks take up
// as much space as possible, ensuring footers are aligned to the bottom.
flex: 1 1 auto;
// Workaround for the image size bug in IE
// See: https://github.com/twbs/bootstrap/pull/28855
min-height: 1px;
padding: $card-spacer-x;
color: $card-color;
}
.card-title {
margin-bottom: $card-spacer-y;
}
.card-subtitle {
margin-top: -$card-spacer-y / 2;
margin-bottom: 0;
}
.card-text:last-child {
margin-bottom: 0;
}
.card-link {
@include hover() {
text-decoration: none;
}
+ .card-link {
margin-left: $card-spacer-x;
}
}
//
// Optional textual caps
//
.card-header {
padding: $card-spacer-y $card-spacer-x;
margin-bottom: 0; // Removes the default margin-bottom of <hN>
color: $card-cap-color;
background-color: $card-cap-bg;
border-bottom: $card-border-width solid $card-border-color;
&:first-child {
@include border-radius($card-inner-border-radius $card-inner-border-radius 0 0);
}
}
.card-footer {
padding: $card-spacer-y $card-spacer-x;
color: $card-cap-color;
background-color: $card-cap-bg;
border-top: $card-border-width solid $card-border-color;
&:last-child {
@include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius);
}
}
//
// Header navs
//
.card-header-tabs {
margin-right: -$card-spacer-x / 2;
margin-bottom: -$card-spacer-y;
margin-left: -$card-spacer-x / 2;
border-bottom: 0;
}
.card-header-pills {
margin-right: -$card-spacer-x / 2;
margin-left: -$card-spacer-x / 2;
}
// Card image
.card-img-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: $card-img-overlay-padding;
@include border-radius($card-inner-border-radius);
}
.card-img,
.card-img-top,
.card-img-bottom {
flex-shrink: 0; // For IE: https://github.com/twbs/bootstrap/issues/29396
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
}
.card-img,
.card-img-top {
@include border-top-radius($card-inner-border-radius);
}
.card-img,
.card-img-bottom {
@include border-bottom-radius($card-inner-border-radius);
}
// Card deck
.card-deck {
.card {
margin-bottom: $card-deck-margin;
}
@include media-breakpoint-up(sm) {
display: flex;
flex-flow: row wrap;
margin-right: -$card-deck-margin;
margin-left: -$card-deck-margin;
.card {
// Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4
flex: 1 0 0%;
margin-right: $card-deck-margin;
margin-bottom: 0; // Override the default
margin-left: $card-deck-margin;
}
}
}
//
// Card groups
//
.card-group {
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
margin-bottom: $card-group-margin;
}
@include media-breakpoint-up(sm) {
display: flex;
flex-flow: row wrap;
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
// Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4
flex: 1 0 0%;
margin-bottom: 0;
+ .card {
margin-left: 0;
border-left: 0;
}
// Handle rounded corners
@if $enable-rounded {
&:not(:last-child) {
@include border-right-radius(0);
.card-img-top,
.card-header {
// stylelint-disable-next-line property-disallowed-list
border-top-right-radius: 0;
}
.card-img-bottom,
.card-footer {
// stylelint-disable-next-line property-disallowed-list
border-bottom-right-radius: 0;
}
}
&:not(:first-child) {
@include border-left-radius(0);
.card-img-top,
.card-header {
// stylelint-disable-next-line property-disallowed-list
border-top-left-radius: 0;
}
.card-img-bottom,
.card-footer {
// stylelint-disable-next-line property-disallowed-list
border-bottom-left-radius: 0;
}
}
}
}
}
}
//
// Columns
//
.card-columns {
.card {
margin-bottom: $card-columns-margin;
}
@include media-breakpoint-up(sm) {
column-count: $card-columns-count;
column-gap: $card-columns-gap;
orphans: 1;
widows: 1;
.card {
display: inline-block; // Don't let them vertically span multiple columns
width: 100%; // Don't let their width change
}
}
}
//
// Accordion
//
.accordion {
overflow-anchor: none;
> .card {
overflow: hidden;
&:not(:last-of-type) {
border-bottom: 0;
@include border-bottom-radius(0);
}
&:not(:first-of-type) {
@include border-top-radius(0);
}
> .card-header {
@include border-radius(0);
margin-bottom: -$card-border-width;
}
}
}
@@ -0,0 +1,197 @@
// Notes on the classes:
//
// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
// even when their scroll action started on a carousel, but for compatibility (with Firefox)
// we're preventing all actions instead
// 2. The .carousel-item-left and .carousel-item-right is used to indicate where
// the active slide is heading.
// 3. .active.carousel-item is the current slide.
// 4. .active.carousel-item-left and .active.carousel-item-right is the current
// slide in its in-transition state. Only one of these occurs at a time.
// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right
// is the upcoming slide in transition.
.carousel {
position: relative;
}
.carousel.pointer-event {
touch-action: pan-y;
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
@include clearfix();
}
.carousel-item {
position: relative;
display: none;
float: left;
width: 100%;
margin-right: -100%;
backface-visibility: hidden;
@include transition($carousel-transition);
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
.carousel-item-next:not(.carousel-item-left),
.active.carousel-item-right {
transform: translateX(100%);
}
.carousel-item-prev:not(.carousel-item-right),
.active.carousel-item-left {
transform: translateX(-100%);
}
//
// Alternate transitions
//
.carousel-fade {
.carousel-item {
opacity: 0;
transition-property: opacity;
transform: none;
}
.carousel-item.active,
.carousel-item-next.carousel-item-left,
.carousel-item-prev.carousel-item-right {
z-index: 1;
opacity: 1;
}
.active.carousel-item-left,
.active.carousel-item-right {
z-index: 0;
opacity: 0;
@include transition(opacity 0s $carousel-transition-duration);
}
}
//
// Left/right controls for nav
//
.carousel-control-prev,
.carousel-control-next {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
// Use flex for alignment (1-3)
display: flex; // 1. allow flex styles
align-items: center; // 2. vertically center contents
justify-content: center; // 3. horizontally center contents
width: $carousel-control-width;
color: $carousel-control-color;
text-align: center;
opacity: $carousel-control-opacity;
@include transition($carousel-control-transition);
// Hover/focus state
@include hover-focus() {
color: $carousel-control-color;
text-decoration: none;
outline: 0;
opacity: $carousel-control-hover-opacity;
}
}
.carousel-control-prev {
left: 0;
@if $enable-gradients {
background-image: linear-gradient(90deg, rgba($black, .25), rgba($black, .001));
}
}
.carousel-control-next {
right: 0;
@if $enable-gradients {
background-image: linear-gradient(270deg, rgba($black, .25), rgba($black, .001));
}
}
// Icons for within
.carousel-control-prev-icon,
.carousel-control-next-icon {
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background: 50% / 100% 100% no-repeat;
}
.carousel-control-prev-icon {
background-image: escape-svg($carousel-control-prev-icon-bg);
}
.carousel-control-next-icon {
background-image: escape-svg($carousel-control-next-icon-bg);
}
// Optional indicator pips
//
// Add an ordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
display: flex;
justify-content: center;
padding-left: 0; // override <ol> default
// Use the .carousel-control's width as margin so we don't overlay those
margin-right: $carousel-control-width;
margin-left: $carousel-control-width;
list-style: none;
li {
box-sizing: content-box;
flex: 0 1 auto;
width: $carousel-indicator-width;
height: $carousel-indicator-height;
margin-right: $carousel-indicator-spacer;
margin-left: $carousel-indicator-spacer;
text-indent: -999px;
cursor: pointer;
background-color: $carousel-indicator-active-bg;
background-clip: padding-box;
// Use transparent borders to increase the hit area by 10px on top and bottom.
border-top: $carousel-indicator-hit-area-height solid transparent;
border-bottom: $carousel-indicator-hit-area-height solid transparent;
opacity: .5;
@include transition($carousel-indicator-transition);
}
.active {
opacity: 1;
}
}
// Optional captions
//
//
.carousel-caption {
position: absolute;
right: (100% - $carousel-caption-width) / 2;
bottom: 20px;
left: (100% - $carousel-caption-width) / 2;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: $carousel-caption-color;
text-align: center;
}
@@ -0,0 +1,40 @@
.close {
float: right;
@include font-size($close-font-size);
font-weight: $close-font-weight;
line-height: 1;
color: $close-color;
text-shadow: $close-text-shadow;
opacity: .5;
// Override <a>'s hover style
@include hover() {
color: $close-color;
text-decoration: none;
}
&:not(:disabled):not(.disabled) {
@include hover-focus() {
opacity: .75;
}
}
}
// Additional properties for button version
// iOS requires the button element instead of an anchor tag.
// If you want the anchor version, it requires `href="#"`.
// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
// stylelint-disable-next-line selector-no-qualifying-type
button.close {
padding: 0;
background-color: transparent;
border: 0;
}
// Future-proof disabling of clicks on `<a>` elements
// stylelint-disable-next-line selector-no-qualifying-type
a.close.disabled {
pointer-events: none;
}
+48
View File
@@ -0,0 +1,48 @@
// Inline code
code {
@include font-size($code-font-size);
color: $code-color;
word-wrap: break-word;
// Streamline the style when inside anchors to avoid broken underline and more
a > & {
color: inherit;
}
}
// User input typically entered via keyboard
kbd {
padding: $kbd-padding-y $kbd-padding-x;
@include font-size($kbd-font-size);
color: $kbd-color;
background-color: $kbd-bg;
@include border-radius($border-radius-sm);
@include box-shadow($kbd-box-shadow);
kbd {
padding: 0;
@include font-size(100%);
font-weight: $nested-kbd-font-weight;
@include box-shadow(none);
}
}
// Blocks of code
pre {
display: block;
@include font-size($code-font-size);
color: $pre-color;
// Account for some code outputs that place code tags in pre tags
code {
@include font-size(inherit);
color: inherit;
word-break: normal;
}
}
// Enable scrollable blocks of code
.pre-scrollable {
max-height: $pre-scrollable-max-height;
overflow-y: scroll;
}
@@ -0,0 +1,526 @@
// Embedded icons from Open Iconic.
// Released under MIT and copyright 2014 Waybury.
// https://useiconic.com/open
// Checkboxes and radios
//
// Base class takes care of all the key behavioral aspects.
.custom-control {
position: relative;
z-index: 1;
display: block;
min-height: $font-size-base * $line-height-base;
padding-left: $custom-control-gutter + $custom-control-indicator-size;
color-adjust: exact; // Keep themed appearance for print
}
.custom-control-inline {
display: inline-flex;
margin-right: $custom-control-spacer-x;
}
.custom-control-input {
position: absolute;
left: 0;
z-index: -1; // Put the input behind the label so it doesn't overlay text
width: $custom-control-indicator-size;
height: ($font-size-base * $line-height-base + $custom-control-indicator-size) / 2;
opacity: 0;
&:checked ~ .custom-control-label::before {
color: $custom-control-indicator-checked-color;
border-color: $custom-control-indicator-checked-border-color;
@include gradient-bg($custom-control-indicator-checked-bg);
@include box-shadow($custom-control-indicator-checked-box-shadow);
}
&:focus ~ .custom-control-label::before {
// the mixin is not used here to make sure there is feedback
@if $enable-shadows {
box-shadow: $input-box-shadow, $custom-control-indicator-focus-box-shadow;
} @else {
box-shadow: $custom-control-indicator-focus-box-shadow;
}
}
&:focus:not(:checked) ~ .custom-control-label::before {
border-color: $custom-control-indicator-focus-border-color;
}
&:not(:disabled):active ~ .custom-control-label::before {
color: $custom-control-indicator-active-color;
background-color: $custom-control-indicator-active-bg;
border-color: $custom-control-indicator-active-border-color;
@include box-shadow($custom-control-indicator-active-box-shadow);
}
// Use [disabled] and :disabled to work around https://github.com/twbs/bootstrap/issues/28247
&[disabled],
&:disabled {
~ .custom-control-label {
color: $custom-control-label-disabled-color;
&::before {
background-color: $custom-control-indicator-disabled-bg;
}
}
}
}
// Custom control indicators
//
// Build the custom controls out of pseudo-elements.
.custom-control-label {
position: relative;
margin-bottom: 0;
color: $custom-control-label-color;
vertical-align: top;
cursor: $custom-control-cursor;
// Background-color and (when enabled) gradient
&::before {
position: absolute;
top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;
left: -($custom-control-gutter + $custom-control-indicator-size);
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
pointer-events: none;
content: "";
background-color: $custom-control-indicator-bg;
border: $custom-control-indicator-border-color solid $custom-control-indicator-border-width;
@include box-shadow($custom-control-indicator-box-shadow);
}
// Foreground (icon)
&::after {
position: absolute;
top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;
left: -($custom-control-gutter + $custom-control-indicator-size);
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
content: "";
background: 50% / #{$custom-control-indicator-bg-size} no-repeat;
}
}
// Checkboxes
//
// Tweak just a few things for checkboxes.
.custom-checkbox {
.custom-control-label::before {
@include border-radius($custom-checkbox-indicator-border-radius);
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-image: escape-svg($custom-checkbox-indicator-icon-checked);
}
}
.custom-control-input:indeterminate ~ .custom-control-label {
&::before {
border-color: $custom-checkbox-indicator-indeterminate-border-color;
@include gradient-bg($custom-checkbox-indicator-indeterminate-bg);
@include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow);
}
&::after {
background-image: escape-svg($custom-checkbox-indicator-icon-indeterminate);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
&:indeterminate ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
}
}
// Radios
//
// Tweak just a few things for radios.
.custom-radio {
.custom-control-label::before {
// stylelint-disable-next-line property-disallowed-list
border-radius: $custom-radio-indicator-border-radius;
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-image: escape-svg($custom-radio-indicator-icon-checked);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
}
}
// switches
//
// Tweak a few things for switches
.custom-switch {
padding-left: $custom-switch-width + $custom-control-gutter;
.custom-control-label {
&::before {
left: -($custom-switch-width + $custom-control-gutter);
width: $custom-switch-width;
pointer-events: all;
// stylelint-disable-next-line property-disallowed-list
border-radius: $custom-switch-indicator-border-radius;
}
&::after {
top: add(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2, $custom-control-indicator-border-width * 2);
left: add(-($custom-switch-width + $custom-control-gutter), $custom-control-indicator-border-width * 2);
width: $custom-switch-indicator-size;
height: $custom-switch-indicator-size;
background-color: $custom-control-indicator-border-color;
// stylelint-disable-next-line property-disallowed-list
border-radius: $custom-switch-indicator-border-radius;
@include transition(transform .15s ease-in-out, $custom-forms-transition);
}
}
.custom-control-input:checked ~ .custom-control-label {
&::after {
background-color: $custom-control-indicator-bg;
transform: translateX($custom-switch-width - $custom-control-indicator-size);
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
@include gradient-bg($custom-control-indicator-checked-disabled-bg);
}
}
}
// Select
//
// Replaces the browser default select with a custom one, mostly pulled from
// https://primer.github.io/.
//
.custom-select {
display: inline-block;
width: 100%;
height: $custom-select-height;
padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x;
font-family: $custom-select-font-family;
@include font-size($custom-select-font-size);
font-weight: $custom-select-font-weight;
line-height: $custom-select-line-height;
color: $custom-select-color;
vertical-align: middle;
background: $custom-select-bg $custom-select-background;
border: $custom-select-border-width solid $custom-select-border-color;
@include border-radius($custom-select-border-radius, 0);
@include box-shadow($custom-select-box-shadow);
appearance: none;
&:focus {
border-color: $custom-select-focus-border-color;
outline: 0;
@if $enable-shadows {
@include box-shadow($custom-select-box-shadow, $custom-select-focus-box-shadow);
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: $custom-select-focus-box-shadow;
}
&::-ms-value {
// For visual consistency with other platforms/browsers,
// suppress the default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
&[multiple],
&[size]:not([size="1"]) {
height: auto;
padding-right: $custom-select-padding-x;
background-image: none;
}
&:disabled {
color: $custom-select-disabled-color;
background-color: $custom-select-disabled-bg;
}
// Hides the default caret in IE11
&::-ms-expand {
display: none;
}
// Remove outline from select box in FF
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $custom-select-color;
}
}
.custom-select-sm {
height: $custom-select-height-sm;
padding-top: $custom-select-padding-y-sm;
padding-bottom: $custom-select-padding-y-sm;
padding-left: $custom-select-padding-x-sm;
@include font-size($custom-select-font-size-sm);
}
.custom-select-lg {
height: $custom-select-height-lg;
padding-top: $custom-select-padding-y-lg;
padding-bottom: $custom-select-padding-y-lg;
padding-left: $custom-select-padding-x-lg;
@include font-size($custom-select-font-size-lg);
}
// File
//
// Custom file input.
.custom-file {
position: relative;
display: inline-block;
width: 100%;
height: $custom-file-height;
margin-bottom: 0;
}
.custom-file-input {
position: relative;
z-index: 2;
width: 100%;
height: $custom-file-height;
margin: 0;
overflow: hidden;
opacity: 0;
&:focus ~ .custom-file-label {
border-color: $custom-file-focus-border-color;
box-shadow: $custom-file-focus-box-shadow;
}
// Use [disabled] and :disabled to work around https://github.com/twbs/bootstrap/issues/28247
&[disabled] ~ .custom-file-label,
&:disabled ~ .custom-file-label {
background-color: $custom-file-disabled-bg;
}
@each $lang, $value in $custom-file-text {
&:lang(#{$lang}) ~ .custom-file-label::after {
content: $value;
}
}
~ .custom-file-label[data-browse]::after {
content: attr(data-browse);
}
}
.custom-file-label {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
height: $custom-file-height;
padding: $custom-file-padding-y $custom-file-padding-x;
overflow: hidden;
font-family: $custom-file-font-family;
font-weight: $custom-file-font-weight;
line-height: $custom-file-line-height;
color: $custom-file-color;
background-color: $custom-file-bg;
border: $custom-file-border-width solid $custom-file-border-color;
@include border-radius($custom-file-border-radius);
@include box-shadow($custom-file-box-shadow);
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
display: block;
height: $custom-file-height-inner;
padding: $custom-file-padding-y $custom-file-padding-x;
line-height: $custom-file-line-height;
color: $custom-file-button-color;
content: "Browse";
@include gradient-bg($custom-file-button-bg);
border-left: inherit;
@include border-radius(0 $custom-file-border-radius $custom-file-border-radius 0);
}
}
// Range
//
// Style range inputs the same across browsers. Vendor-specific rules for pseudo
// elements cannot be mixed. As such, there are no shared styles for focus or
// active states on prefixed selectors.
.custom-range {
width: 100%;
height: add($custom-range-thumb-height, $custom-range-thumb-focus-box-shadow-width * 2);
padding: 0; // Need to reset padding
background-color: transparent;
appearance: none;
&:focus {
outline: 0;
// Pseudo-elements must be split across multiple rulesets to have an effect.
// No box-shadow() mixin for focus accessibility.
&::-webkit-slider-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
&::-moz-range-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
&::-ms-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }
}
&::-moz-focus-outer {
border: 0;
}
&::-webkit-slider-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
margin-top: ($custom-range-track-height - $custom-range-thumb-height) / 2; // Webkit specific
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-webkit-slider-runnable-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent; // Why?
cursor: $custom-range-track-cursor;
background-color: $custom-range-track-bg;
border-color: transparent;
@include border-radius($custom-range-track-border-radius);
@include box-shadow($custom-range-track-box-shadow);
}
&::-moz-range-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-moz-range-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent;
cursor: $custom-range-track-cursor;
background-color: $custom-range-track-bg;
border-color: transparent; // Firefox specific?
@include border-radius($custom-range-track-border-radius);
@include box-shadow($custom-range-track-box-shadow);
}
&::-ms-thumb {
width: $custom-range-thumb-width;
height: $custom-range-thumb-height;
margin-top: 0; // Edge specific
margin-right: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.
margin-left: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.
@include gradient-bg($custom-range-thumb-bg);
border: $custom-range-thumb-border;
@include border-radius($custom-range-thumb-border-radius);
@include box-shadow($custom-range-thumb-box-shadow);
@include transition($custom-forms-transition);
appearance: none;
&:active {
@include gradient-bg($custom-range-thumb-active-bg);
}
}
&::-ms-track {
width: $custom-range-track-width;
height: $custom-range-track-height;
color: transparent;
cursor: $custom-range-track-cursor;
background-color: transparent;
border-color: transparent;
border-width: $custom-range-thumb-height / 2;
@include box-shadow($custom-range-track-box-shadow);
}
&::-ms-fill-lower {
background-color: $custom-range-track-bg;
@include border-radius($custom-range-track-border-radius);
}
&::-ms-fill-upper {
margin-right: 15px; // arbitrary?
background-color: $custom-range-track-bg;
@include border-radius($custom-range-track-border-radius);
}
&:disabled {
&::-webkit-slider-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
&::-webkit-slider-runnable-track {
cursor: default;
}
&::-moz-range-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
&::-moz-range-track {
cursor: default;
}
&::-ms-thumb {
background-color: $custom-range-thumb-disabled-bg;
}
}
}
.custom-control-label::before,
.custom-file-label,
.custom-select {
@include transition($custom-forms-transition);
}
@@ -0,0 +1,192 @@
// The dropdown wrapper (`<div>`)
.dropup,
.dropright,
.dropdown,
.dropleft {
position: relative;
}
.dropdown-toggle {
white-space: nowrap;
// Generate the caret automatically
@include caret();
}
// The dropdown menu
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: $zindex-dropdown;
display: none; // none by default, but block on "open" of the menu
float: left;
min-width: $dropdown-min-width;
padding: $dropdown-padding-y $dropdown-padding-x;
margin: $dropdown-spacer 0 0; // override default ul
@include font-size($dropdown-font-size);
color: $dropdown-color;
text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)
list-style: none;
background-color: $dropdown-bg;
background-clip: padding-box;
border: $dropdown-border-width solid $dropdown-border-color;
@include border-radius($dropdown-border-radius);
@include box-shadow($dropdown-box-shadow);
}
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.dropdown-menu#{$infix}-left {
right: auto;
left: 0;
}
.dropdown-menu#{$infix}-right {
right: 0;
left: auto;
}
}
}
// Allow for dropdowns to go bottom up (aka, dropup-menu)
// Just add .dropup after the standard .dropdown class and you're set.
.dropup {
.dropdown-menu {
top: auto;
bottom: 100%;
margin-top: 0;
margin-bottom: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(up);
}
}
.dropright {
.dropdown-menu {
top: 0;
right: auto;
left: 100%;
margin-top: 0;
margin-left: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(right);
&::after {
vertical-align: 0;
}
}
}
.dropleft {
.dropdown-menu {
top: 0;
right: 100%;
left: auto;
margin-top: 0;
margin-right: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(left);
&::before {
vertical-align: 0;
}
}
}
// When Popper is enabled, reset the basic dropdown position
// stylelint-disable-next-line no-duplicate-selectors
.dropdown-menu {
&[x-placement^="top"],
&[x-placement^="right"],
&[x-placement^="bottom"],
&[x-placement^="left"] {
right: auto;
bottom: auto;
}
}
// Dividers (basically an `<hr>`) within the dropdown
.dropdown-divider {
@include nav-divider($dropdown-divider-bg, $dropdown-divider-margin-y, true);
}
// Links, buttons, and more within the dropdown menu
//
// `<button>`-specific styles are denoted with `// For <button>s`
.dropdown-item {
display: block;
width: 100%; // For `<button>`s
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
clear: both;
font-weight: $font-weight-normal;
color: $dropdown-link-color;
text-align: inherit; // For `<button>`s
text-decoration: if($link-decoration == none, null, none);
white-space: nowrap; // prevent links from randomly breaking onto new lines
background-color: transparent; // For `<button>`s
border: 0; // For `<button>`s
// Prevent dropdown overflow if there's no padding
// See https://github.com/twbs/bootstrap/pull/27703
@if $dropdown-padding-y == 0 {
&:first-child {
@include border-top-radius($dropdown-inner-border-radius);
}
&:last-child {
@include border-bottom-radius($dropdown-inner-border-radius);
}
}
@include hover-focus() {
color: $dropdown-link-hover-color;
text-decoration: none;
@include gradient-bg($dropdown-link-hover-bg);
}
&.active,
&:active {
color: $dropdown-link-active-color;
text-decoration: none;
@include gradient-bg($dropdown-link-active-bg);
}
&.disabled,
&:disabled {
color: $dropdown-link-disabled-color;
pointer-events: none;
background-color: transparent;
// Remove CSS gradients if they're enabled
@if $enable-gradients {
background-image: none;
}
}
}
.dropdown-menu.show {
display: block;
}
// Dropdown section headers
.dropdown-header {
display: block;
padding: $dropdown-header-padding;
margin-bottom: 0; // for use with heading elements
@include font-size($font-size-sm);
color: $dropdown-header-color;
white-space: nowrap; // as with > li > a
}
// Dropdown text
.dropdown-item-text {
display: block;
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
color: $dropdown-link-color;
}
+347
View File
@@ -0,0 +1,347 @@
// stylelint-disable selector-no-qualifying-type
//
// Textual form controls
//
.form-control {
display: block;
width: 100%;
height: $input-height;
padding: $input-padding-y $input-padding-x;
font-family: $input-font-family;
@include font-size($input-font-size);
font-weight: $input-font-weight;
line-height: $input-line-height;
color: $input-color;
background-color: $input-bg;
background-clip: padding-box;
border: $input-border-width solid $input-border-color;
// Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.
@include border-radius($input-border-radius, 0);
@include box-shadow($input-box-shadow);
@include transition($input-transition);
// Unstyle the caret on `<select>`s in IE10+.
&::-ms-expand {
background-color: transparent;
border: 0;
}
// Remove select outline from select box in FF
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $input-color;
}
// Customize the `:focus` state to imitate native WebKit styles.
@include form-control-focus($ignore-warning: true);
// Placeholder
&::placeholder {
color: $input-placeholder-color;
// Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.
opacity: 1;
}
// Disabled and read-only inputs
//
// HTML5 says that controls under a fieldset > legend:first-child won't be
// disabled if the fieldset is disabled. Due to implementation difficulty, we
// don't honor that edge case; we style them as disabled anyway.
&:disabled,
&[readonly] {
background-color: $input-disabled-bg;
// iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.
opacity: 1;
}
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
&.form-control {
appearance: none; // Fix appearance for date inputs in Safari
}
}
select.form-control {
&:focus::-ms-value {
// Suppress the nested default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge, as it looks bad and cannot be made to
// match the appearance of the native widget.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
// Make file inputs better match text inputs by forcing them to new lines.
.form-control-file,
.form-control-range {
display: block;
width: 100%;
}
//
// Labels
//
// For use with horizontal and inline forms, when you need the label (or legend)
// text to align with the form controls.
.col-form-label {
padding-top: add($input-padding-y, $input-border-width);
padding-bottom: add($input-padding-y, $input-border-width);
margin-bottom: 0; // Override the `<label>/<legend>` default
@include font-size(inherit); // Override the `<legend>` default
line-height: $input-line-height;
}
.col-form-label-lg {
padding-top: add($input-padding-y-lg, $input-border-width);
padding-bottom: add($input-padding-y-lg, $input-border-width);
@include font-size($input-font-size-lg);
line-height: $input-line-height-lg;
}
.col-form-label-sm {
padding-top: add($input-padding-y-sm, $input-border-width);
padding-bottom: add($input-padding-y-sm, $input-border-width);
@include font-size($input-font-size-sm);
line-height: $input-line-height-sm;
}
// Readonly controls as plain text
//
// Apply class to a readonly input to make it appear like regular plain
// text (without any border, background color, focus indicator)
.form-control-plaintext {
display: block;
width: 100%;
padding: $input-padding-y 0;
margin-bottom: 0; // match inputs if this class comes on inputs with default margins
@include font-size($input-font-size);
line-height: $input-line-height;
color: $input-plaintext-color;
background-color: transparent;
border: solid transparent;
border-width: $input-border-width 0;
&.form-control-sm,
&.form-control-lg {
padding-right: 0;
padding-left: 0;
}
}
// Form control sizing
//
// Build on `.form-control` with modifier classes to decrease or increase the
// height and font-size of form controls.
//
// Repeated in `_input_group.scss` to avoid Sass extend issues.
.form-control-sm {
height: $input-height-sm;
padding: $input-padding-y-sm $input-padding-x-sm;
@include font-size($input-font-size-sm);
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
.form-control-lg {
height: $input-height-lg;
padding: $input-padding-y-lg $input-padding-x-lg;
@include font-size($input-font-size-lg);
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
// stylelint-disable-next-line no-duplicate-selectors
select.form-control {
&[size],
&[multiple] {
height: auto;
}
}
textarea.form-control {
height: auto;
}
// Form groups
//
// Designed to help with the organization and spacing of vertical forms. For
// horizontal forms, use the predefined grid classes.
.form-group {
margin-bottom: $form-group-margin-bottom;
}
.form-text {
display: block;
margin-top: $form-text-margin-top;
}
// Form grid
//
// Special replacement for our grid system's `.row` for tighter form layouts.
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -$form-grid-gutter-width / 2;
margin-left: -$form-grid-gutter-width / 2;
> .col,
> [class*="col-"] {
padding-right: $form-grid-gutter-width / 2;
padding-left: $form-grid-gutter-width / 2;
}
}
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
.form-check {
position: relative;
display: block;
padding-left: $form-check-input-gutter;
}
.form-check-input {
position: absolute;
margin-top: $form-check-input-margin-y;
margin-left: -$form-check-input-gutter;
// Use [disabled] and :disabled for workaround https://github.com/twbs/bootstrap/issues/28247
&[disabled] ~ .form-check-label,
&:disabled ~ .form-check-label {
color: $text-muted;
}
}
.form-check-label {
margin-bottom: 0; // Override default `<label>` bottom margin
}
.form-check-inline {
display: inline-flex;
align-items: center;
padding-left: 0; // Override base .form-check
margin-right: $form-check-inline-margin-x;
// Undo .form-check-input defaults and add some `margin-right`.
.form-check-input {
position: static;
margin-top: 0;
margin-right: $form-check-inline-input-margin-x;
margin-left: 0;
}
}
// Form validation
//
// Provide feedback to users when form field values are valid or invalid. Works
// primarily for client-side validation via scoped `:invalid` and `:valid`
// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for
// server side validation.
@each $state, $data in $form-validation-states {
@include form-validation-state($state, map-get($data, color), map-get($data, icon));
}
// Inline forms
//
// Make forms appear inline(-block) by adding the `.form-inline` class. Inline
// forms begin stacked on extra small (mobile) devices and then go inline when
// viewports reach <768px.
//
// Requires wrapping inputs and labels with `.form-group` for proper display of
// default HTML form controls and our custom form controls (e.g., input groups).
.form-inline {
display: flex;
flex-flow: row wrap;
align-items: center; // Prevent shorter elements from growing to same height as others (e.g., small buttons growing to normal sized button height)
// Because we use flex, the initial sizing of checkboxes is collapsed and
// doesn't occupy the full-width (which is what we want for xs grid tier),
// so we force that here.
.form-check {
width: 100%;
}
// Kick in the inline
@include media-breakpoint-up(sm) {
label {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0;
}
// Inline-block all the things for "inline"
.form-group {
display: flex;
flex: 0 0 auto;
flex-flow: row wrap;
align-items: center;
margin-bottom: 0;
}
// Allow folks to *not* use `.form-group`
.form-control {
display: inline-block;
width: auto; // Prevent labels from stacking above inputs in `.form-group`
vertical-align: middle;
}
// Make static controls behave like regular ones
.form-control-plaintext {
display: inline-block;
}
.input-group,
.custom-select {
width: auto;
}
// Remove default margin on radios/checkboxes that were used for stacking, and
// then undo the floating of radios and checkboxes to match.
.form-check {
display: flex;
align-items: center;
justify-content: center;
width: auto;
padding-left: 0;
}
.form-check-input {
position: relative;
flex-shrink: 0;
margin-top: 0;
margin-right: $form-check-input-margin-x;
margin-left: 0;
}
.custom-control {
align-items: center;
justify-content: center;
}
.custom-control-label {
margin-bottom: 0;
}
}
}
@@ -0,0 +1,144 @@
// Bootstrap functions
//
// Utility mixins and functions for evaluating source code across our variables, maps, and mixins.
// Ascending
// Used to evaluate Sass maps like our grid breakpoints.
@mixin _assert-ascending($map, $map-name) {
$prev-key: null;
$prev-num: null;
@each $key, $num in $map {
@if $prev-num == null or unit($num) == "%" or unit($prev-num) == "%" {
// Do nothing
} @else if not comparable($prev-num, $num) {
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
} @else if $prev-num >= $num {
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
}
$prev-key: $key;
$prev-num: $num;
}
}
// Starts at zero
// Used to ensure the min-width of the lowest breakpoint starts at 0.
@mixin _assert-starts-at-zero($map, $map-name: "$grid-breakpoints") {
@if length($map) > 0 {
$values: map-values($map);
$first-value: nth($values, 1);
@if $first-value != 0 {
@warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}.";
}
}
}
// Replace `$search` with `$replace` in `$string`
// Used on our SVG icon backgrounds for custom forms.
//
// @author Hugo Giraudel
// @param {String} $string - Initial string
// @param {String} $search - Substring to replace
// @param {String} $replace ('') - New value
// @return {String} - Updated string
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
// See https://codepen.io/kevinweber/pen/dXWoRw
//
// Requires the use of quotes around data URIs.
@function escape-svg($string) {
@if str-index($string, "data:image/svg+xml") {
@each $char, $encoded in $escaped-characters {
// Do not escape the url brackets
@if str-index($string, "url(") == 1 {
$string: url("#{str-replace(str-slice($string, 6, -3), $char, $encoded)}");
} @else {
$string: str-replace($string, $char, $encoded);
}
}
}
@return $string;
}
// Color contrast
@function color-yiq($color, $dark: $yiq-text-dark, $light: $yiq-text-light) {
$r: red($color);
$g: green($color);
$b: blue($color);
$yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
@if ($yiq >= $yiq-contrasted-threshold) {
@return $dark;
} @else {
@return $light;
}
}
// Retrieve color Sass maps
@function color($key: "blue") {
@return map-get($colors, $key);
}
@function theme-color($key: "primary") {
@return map-get($theme-colors, $key);
}
@function gray($key: "100") {
@return map-get($grays, $key);
}
// Request a theme color level
@function theme-color-level($color-name: "primary", $level: 0) {
$color: theme-color($color-name);
$color-base: if($level > 0, $black, $white);
$level: abs($level);
@return mix($color-base, $color, $level * $theme-color-interval);
}
// Return valid calc
@function add($value1, $value2, $return-calc: true) {
@if $value1 == null {
@return $value2;
}
@if $value2 == null {
@return $value1;
}
@if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) {
@return $value1 + $value2;
}
@return if($return-calc == true, calc(#{$value1} + #{$value2}), $value1 + unquote(" + ") + $value2);
}
@function subtract($value1, $value2, $return-calc: true) {
@if $value1 == null and $value2 == null {
@return null;
}
@if $value1 == null {
@return -$value2;
}
@if $value2 == null {
@return $value1;
}
@if type-of($value1) == number and type-of($value2) == number and comparable($value1, $value2) {
@return $value1 - $value2;
}
@return if($return-calc == true, calc(#{$value1} - #{$value2}), $value1 + unquote(" - ") + $value2);
}
+73
View File
@@ -0,0 +1,73 @@
// Container widths
//
// Set the container width, and override it for fixed navbars in media queries.
@if $enable-grid-classes {
// Single container class with breakpoint max-widths
.container,
// 100% wide container at all breakpoints
.container-fluid {
@include make-container();
}
// Responsive containers that are 100% wide until a breakpoint
@each $breakpoint, $container-max-width in $container-max-widths {
.container-#{$breakpoint} {
@extend .container-fluid;
}
@include media-breakpoint-up($breakpoint, $grid-breakpoints) {
%responsive-container-#{$breakpoint} {
max-width: $container-max-width;
}
// Extend each breakpoint which is smaller or equal to the current breakpoint
$extend-breakpoint: true;
@each $name, $width in $grid-breakpoints {
@if ($extend-breakpoint) {
.container#{breakpoint-infix($name, $grid-breakpoints)} {
@extend %responsive-container-#{$breakpoint};
}
// Once the current breakpoint is reached, stop extending
@if ($breakpoint == $name) {
$extend-breakpoint: false;
}
}
}
}
}
}
// Row
//
// Rows contain your columns.
@if $enable-grid-classes {
.row {
@include make-row();
}
// Remove the negative margin from default .row, then the horizontal padding
// from all immediate children columns (to prevent runaway style inheritance).
.no-gutters {
margin-right: 0;
margin-left: 0;
> .col,
> [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
}
}
// Columns
//
// Common styles for small and large grid columns
@if $enable-grid-classes {
@include make-grid-columns();
}
@@ -0,0 +1,42 @@
// Responsive images (ensure images don't scale beyond their parents)
//
// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s.
// We previously tried the "images are responsive by default" approach in Bootstrap v2,
// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)
// which weren't expecting the images within themselves to be involuntarily resized.
// See also https://github.com/twbs/bootstrap/issues/18178
.img-fluid {
@include img-fluid();
}
// Image thumbnails
.img-thumbnail {
padding: $thumbnail-padding;
background-color: $thumbnail-bg;
border: $thumbnail-border-width solid $thumbnail-border-color;
@include border-radius($thumbnail-border-radius);
@include box-shadow($thumbnail-box-shadow);
// Keep them at most 100% wide
@include img-fluid();
}
//
// Figures
//
.figure {
// Ensures the caption's text aligns with the image.
display: inline-block;
}
.figure-img {
margin-bottom: $spacer / 2;
line-height: 1;
}
.figure-caption {
@include font-size($figure-caption-font-size);
color: $figure-caption-color;
}
@@ -0,0 +1,208 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.input-group {
position: relative;
display: flex;
flex-wrap: wrap; // For form validation feedback
align-items: stretch;
width: 100%;
> .form-control,
> .form-control-plaintext,
> .custom-select,
> .custom-file {
position: relative; // For focus state's z-index
flex: 1 1 auto;
width: 1%;
min-width: 0; // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
margin-bottom: 0;
+ .form-control,
+ .custom-select,
+ .custom-file {
margin-left: -$input-border-width;
}
}
// Bring the "active" form control to the top of surrounding elements
> .form-control:focus,
> .custom-select:focus,
> .custom-file .custom-file-input:focus ~ .custom-file-label {
z-index: 3;
}
// Bring the custom file input above the label
> .custom-file .custom-file-input:focus {
z-index: 4;
}
> .form-control,
> .custom-select {
&:not(:first-child) { @include border-left-radius(0); }
}
// Custom file inputs have more complex markup, thus requiring different
// border-radius overrides.
> .custom-file {
display: flex;
align-items: center;
&:not(:last-child) .custom-file-label,
&:not(:first-child) .custom-file-label { @include border-left-radius(0); }
}
&:not(.has-validation) {
> .form-control:not(:last-child),
> .custom-select:not(:last-child),
> .custom-file:not(:last-child) .custom-file-label::after {
@include border-right-radius(0);
}
}
&.has-validation {
> .form-control:nth-last-child(n + 3),
> .custom-select:nth-last-child(n + 3),
> .custom-file:nth-last-child(n + 3) .custom-file-label::after {
@include border-right-radius(0);
}
}
}
// Prepend and append
//
// While it requires one extra layer of HTML for each, dedicated prepend and
// append elements allow us to 1) be less clever, 2) simplify our selectors, and
// 3) support HTML5 form validation.
.input-group-prepend,
.input-group-append {
display: flex;
// Ensure buttons are always above inputs for more visually pleasing borders.
// This isn't needed for `.input-group-text` since it shares the same border-color
// as our inputs.
.btn {
position: relative;
z-index: 2;
&:focus {
z-index: 3;
}
}
.btn + .btn,
.btn + .input-group-text,
.input-group-text + .input-group-text,
.input-group-text + .btn {
margin-left: -$input-border-width;
}
}
.input-group-prepend { margin-right: -$input-border-width; }
.input-group-append { margin-left: -$input-border-width; }
// Textual addons
//
// Serves as a catch-all element for any text or radio/checkbox input you wish
// to prepend or append to an input.
.input-group-text {
display: flex;
align-items: center;
padding: $input-padding-y $input-padding-x;
margin-bottom: 0; // Allow use of <label> elements by overriding our default margin-bottom
@include font-size($input-font-size); // Match inputs
font-weight: $font-weight-normal;
line-height: $input-line-height;
color: $input-group-addon-color;
text-align: center;
white-space: nowrap;
background-color: $input-group-addon-bg;
border: $input-border-width solid $input-group-addon-border-color;
@include border-radius($input-border-radius);
// Nuke default margins from checkboxes and radios to vertically center within.
input[type="radio"],
input[type="checkbox"] {
margin-top: 0;
}
}
// Sizing
//
// Remix the default form control sizing classes into new ones for easier
// manipulation.
.input-group-lg > .form-control:not(textarea),
.input-group-lg > .custom-select {
height: $input-height-lg;
}
.input-group-lg > .form-control,
.input-group-lg > .custom-select,
.input-group-lg > .input-group-prepend > .input-group-text,
.input-group-lg > .input-group-append > .input-group-text,
.input-group-lg > .input-group-prepend > .btn,
.input-group-lg > .input-group-append > .btn {
padding: $input-padding-y-lg $input-padding-x-lg;
@include font-size($input-font-size-lg);
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
.input-group-sm > .form-control:not(textarea),
.input-group-sm > .custom-select {
height: $input-height-sm;
}
.input-group-sm > .form-control,
.input-group-sm > .custom-select,
.input-group-sm > .input-group-prepend > .input-group-text,
.input-group-sm > .input-group-append > .input-group-text,
.input-group-sm > .input-group-prepend > .btn,
.input-group-sm > .input-group-append > .btn {
padding: $input-padding-y-sm $input-padding-x-sm;
@include font-size($input-font-size-sm);
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
.input-group-lg > .custom-select,
.input-group-sm > .custom-select {
padding-right: $custom-select-padding-x + $custom-select-indicator-padding;
}
// Prepend and append rounded corners
//
// These rulesets must come after the sizing ones to properly override sm and lg
// border-radius values when extending. They're more specific than we'd like
// with the `.input-group >` part, but without it, we cannot override the sizing.
.input-group > .input-group-prepend > .btn,
.input-group > .input-group-prepend > .input-group-text,
.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .btn,
.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .input-group-text,
.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .btn,
.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .input-group-text,
.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
@include border-right-radius(0);
}
.input-group > .input-group-append > .btn,
.input-group > .input-group-append > .input-group-text,
.input-group > .input-group-prepend:not(:first-child) > .btn,
.input-group > .input-group-prepend:not(:first-child) > .input-group-text,
.input-group > .input-group-prepend:first-child > .btn:not(:first-child),
.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {
@include border-left-radius(0);
}
@@ -0,0 +1,17 @@
.jumbotron {
padding: $jumbotron-padding ($jumbotron-padding / 2);
margin-bottom: $jumbotron-padding;
color: $jumbotron-color;
background-color: $jumbotron-bg;
@include border-radius($border-radius-lg);
@include media-breakpoint-up(sm) {
padding: ($jumbotron-padding * 2) $jumbotron-padding;
}
}
.jumbotron-fluid {
padding-right: 0;
padding-left: 0;
@include border-radius(0);
}
@@ -0,0 +1,154 @@
// Base class
//
// Easily usable on <ul>, <ol>, or <div>.
.list-group {
display: flex;
flex-direction: column;
// No need to set list-style: none; since .list-group-item is block level
padding-left: 0; // reset padding because ul and ol
margin-bottom: 0;
@include border-radius($list-group-border-radius);
}
// Interactive list items
//
// Use anchor or button elements instead of `li`s or `div`s to create interactive
// list items. Includes an extra `.active` modifier class for selected items.
.list-group-item-action {
width: 100%; // For `<button>`s (anchors become 100% by default though)
color: $list-group-action-color;
text-align: inherit; // For `<button>`s (anchors inherit)
// Hover state
@include hover-focus() {
z-index: 1; // Place hover/focus items above their siblings for proper border styling
color: $list-group-action-hover-color;
text-decoration: none;
background-color: $list-group-hover-bg;
}
&:active {
color: $list-group-action-active-color;
background-color: $list-group-action-active-bg;
}
}
// Individual list items
//
// Use on `li`s or `div`s within the `.list-group` parent.
.list-group-item {
position: relative;
display: block;
padding: $list-group-item-padding-y $list-group-item-padding-x;
color: $list-group-color;
text-decoration: if($link-decoration == none, null, none);
background-color: $list-group-bg;
border: $list-group-border-width solid $list-group-border-color;
&:first-child {
@include border-top-radius(inherit);
}
&:last-child {
@include border-bottom-radius(inherit);
}
&.disabled,
&:disabled {
color: $list-group-disabled-color;
pointer-events: none;
background-color: $list-group-disabled-bg;
}
// Include both here for `<a>`s and `<button>`s
&.active {
z-index: 2; // Place active items above their siblings for proper border styling
color: $list-group-active-color;
background-color: $list-group-active-bg;
border-color: $list-group-active-border-color;
}
& + & {
border-top-width: 0;
&.active {
margin-top: -$list-group-border-width;
border-top-width: $list-group-border-width;
}
}
}
// Horizontal
//
// Change the layout of list group items from vertical (default) to horizontal.
@each $breakpoint in map-keys($grid-breakpoints) {
@include media-breakpoint-up($breakpoint) {
$infix: breakpoint-infix($breakpoint, $grid-breakpoints);
.list-group-horizontal#{$infix} {
flex-direction: row;
> .list-group-item {
&:first-child {
@include border-bottom-left-radius($list-group-border-radius);
@include border-top-right-radius(0);
}
&:last-child {
@include border-top-right-radius($list-group-border-radius);
@include border-bottom-left-radius(0);
}
&.active {
margin-top: 0;
}
+ .list-group-item {
border-top-width: $list-group-border-width;
border-left-width: 0;
&.active {
margin-left: -$list-group-border-width;
border-left-width: $list-group-border-width;
}
}
}
}
}
}
// Flush list items
//
// Remove borders and border-radius to keep list group items edge-to-edge. Most
// useful within other components (e.g., cards).
.list-group-flush {
@include border-radius(0);
> .list-group-item {
border-width: 0 0 $list-group-border-width;
&:last-child {
border-bottom-width: 0;
}
}
}
// Contextual variants
//
// Add modifier classes to change text and background color on individual items.
// Organizationally, this must come after the `:hover` states.
@each $color, $value in $theme-colors {
@include list-group-item-variant($color, theme-color-level($color, -9), theme-color-level($color, 6));
}

Some files were not shown because too many files have changed in this diff Show More