Created FileList with ability to set used files

This commit is contained in:
schnulller
2022-06-07 16:14:27 +02:00
parent 715cf167a0
commit d8155e26f6
44 changed files with 1468 additions and 119 deletions

View File

@@ -16,6 +16,7 @@ using HaXMLReader.Interfaces;
using Microsoft.FeatureManagement.Mvc;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Http.Features;
using System.Text;
// Controlling all the API-Endpoints
public class APIController : Controller {
@@ -78,6 +79,7 @@ public class APIController : Controller {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
return BadRequest(ModelState);
}
while (section != null) {
@@ -178,7 +180,82 @@ public class APIController : Controller {
[ValidateAntiForgeryToken]
[FeatureGate(Features.UploadService, Features.AdminService)]
public async Task<IActionResult> SetUsed(string id) {
return Ok();
var f = _xmlProvider.GetFiles(id);
if (f == null) {
ModelState.AddModelError("Error", "Wrong Endpoint");
return BadRequest(ModelState);
}
var files = f.GetFileList();
if (files == null) {
ModelState.AddModelError("Error", "Wrong Endpoint");
return BadRequest(ModelState);
}
List<XMLRootDocument>? newUsed = null;
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) {
ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request");
return BadRequest(ModelState);
}
// Same as above, check Upload()
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
MultipartSection? section = null;
try {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
return BadRequest(ModelState);
}
while (section != null) {
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
if (contentDisposition != null && contentDisposition.Name == "__RequestVerificationToken") {
try {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
}
continue;
}
var filename = "";
if (hasContentDispositionHeader && contentDisposition != null) {
if (!MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition)) {
ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document");
return BadRequest(ModelState);
}
filename = XMLFileHelpers.StreamToString(section.Body, ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState);
var isFile = files.Where(x => x.FileName == filename);
if (isFile == null || !isFile.Any()) {
ModelState.AddModelError("Error", "Tried to add a file that does not exist.");
return BadRequest(ModelState);
}
if (newUsed == null) newUsed = new List<XMLRootDocument>();
newUsed.Add(isFile.First());
}
try {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
return BadRequest(ModelState);
}
}
if (newUsed != null && newUsed.Any()) {
_xmlService.UnUse(id);
newUsed.ForEach(x => _xmlService.Use(x));
}
return Created("/", newUsed);
}
@@ -188,6 +265,84 @@ public class APIController : Controller {
[ValidateAntiForgeryToken]
[FeatureGate(Features.UploadService, Features.AdminService)]
public async Task<IActionResult> SetUsedHamann() {
return Ok();
var hF = _xmlProvider.GetHamannFiles();
if (hF == null) {
ModelState.AddModelError("Error", "There are no Hamman.xml files available.");
return BadRequest(ModelState);
}
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) {
ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request");
return BadRequest(ModelState);
}
// Same as above, check Upload()
string? filename = null;
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
MultipartSection? section = null;
try {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
return BadRequest(ModelState);
}
while (section != null) {
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
if (contentDisposition != null && contentDisposition.Name == "__RequestVerificationToken") {
try {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
}
continue;
}
filename = XMLFileHelpers.StreamToString(section.Body, ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState);
if (hasContentDispositionHeader && contentDisposition != null) {
if (!MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition)) {
ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document");
return BadRequest(ModelState);
}
filename = XMLFileHelpers.StreamToString(section.Body, ModelState);
}
try {
section = await reader.ReadNextSectionAsync();
} catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
return BadRequest(ModelState);
}
}
if (filename == null) {
ModelState.AddModelError("Error", "No filename given");
return BadRequest(ModelState);
}
var newFile = hF.Where(x => x.Name == filename);
if (newFile == null || !newFile.Any()) {
ModelState.AddModelError("Error", "Trying to set a unavailable file.");
return BadRequest(ModelState);
}
try {
_ = _lib.SetLibrary(newFile.First().PhysicalPath, ModelState);
}
catch (Exception ex) {
ModelState.AddModelError("Error", "Error parsing the file: " + ex.Message);
return BadRequest(ModelState);
}
_xmlProvider.SetInProduction(newFile.First());
_xmlService.UnUseProduction();
return Created("/", null);
}
}

View File

@@ -48,18 +48,18 @@ public class UploadController : Controller {
if (roots == null) return error404();
var hF = _xmlProvider.GetHamannFiles();
List<(string, DateTime)>? hamannFiles = null;
List<FileModel>? hamannFiles = null;
if (hF != null)
hamannFiles = hF
.OrderByDescending(x => x.LastModified)
.Select(x => (x.Name, x.LastModified.LocalDateTime))
.Select(x => new FileModel(x.Name, string.Empty, x.LastModified.LocalDateTime, false, x == _xmlProvider.GetInProduction()))
.ToList();
var uF = _xmlService.GetUsedDictionary();
var pF = _xmlService.GetInProduction();
Dictionary<string, List<FileModel>?>? usedFiles = null;
if (uF != null) {
if (uF != null) {
usedFiles = new Dictionary<string, List<FileModel>?>();
foreach (var kv in uF) {
if (kv.Value == null) continue;
@@ -68,7 +68,7 @@ public class UploadController : Controller {
}
Dictionary<string, List<FileModel>?>? productionFiles = null;
if (pF != null) {
if (pF != null) {
productionFiles = new Dictionary<string, List<FileModel>?>();
foreach (var kv in pF) {
if (kv.Value == null) continue;
@@ -81,16 +81,15 @@ public class UploadController : Controller {
var root = _xmlService.GetRoot(id);
if (root == null) return error404();
var model = new UploadViewModel(root.Type, id, roots, usedFiles);
model.ProductionFiles = productionFiles;
model.HamannFiles = hamannFiles;
model.AvailableFiles = XMLFileHelpers.ToFileModel(_xmlProvider.GetFiles(id), pF, uF);
return View("../Admin/Upload/Index", model);
}
else {
var model = new UploadViewModel("Upload", id, roots, usedFiles);
} else {
var model = new UploadViewModel("Upload & Veröffentlichen", id, roots, usedFiles);
model.ProductionFiles = productionFiles;
model.HamannFiles = hamannFiles;

View File

@@ -15,15 +15,13 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
_startYear = configuration.GetValue<int>("AvailableStartYear");
_endYear = configuration.GetValue<int>("AvailableEndYear");
var filelist = xmlProvider.GetHamannFiles();
if (filelist != null && filelist.Any())
if (filelist != null && filelist.Any()) {
_AutoLoad(filelist);
}
// Use Fallback library
if (Library == null)
if (Library == null)
Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions() { AvailableYearRange = (_startYear, _endYear) });
}
public ILibrary ResetLibrary() {
@@ -38,6 +36,7 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
}
catch (Exception ex) {
if (ModelState != null) ModelState.AddModelError("Error:", "Das Dokument konnte nicht geparst werden: " + ex.Message);
Console.WriteLine(ex.Message);
return null;
}
return Library;
@@ -50,7 +49,10 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
private void _AutoLoad(List<IFileInfo> files) {
var orderdlist = files.OrderByDescending(x => x.LastModified);
foreach(var item in orderdlist) {
if (SetLibrary(item.PhysicalPath) != null) return;
if (SetLibrary(item.PhysicalPath) != null) {
_xmlProvider.SetInProduction(item);
return;
}
}
}
}

View File

@@ -10,4 +10,6 @@ public interface IXMLProvider {
public Task Save(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState);
public Task<IFileInfo?> SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState);
public List<IFileInfo>? GetHamannFiles();
public IFileInfo? GetInProduction();
public void SetInProduction(IFileInfo info);
}

View File

@@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using HaWeb.Models;
using System.Text;
public static class XMLFileHelpers {
// File Signatures Database (https://www.filesignatures.net/)
@@ -199,6 +200,20 @@ public static class XMLFileHelpers {
return null;
}
public static string? StreamToString(System.IO.Stream stream, ModelStateDictionary modelState) {
string? ret = null;
try {
using (var rd = new StreamReader(stream, Encoding.UTF8)) {
ret = rd.ReadToEnd();
return ret;
}
}
catch (Exception ex) {
modelState.AddModelError("Error", "Reading of the message failed with " + ex.Message);
return null;
}
}
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) {
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
return false;

View File

@@ -17,7 +17,7 @@ public class XMLProvider : IXMLProvider {
_Roots = xmlservice.GetRootsDictionary();
_Files = _ScanFiles();
_HamannFiles = _ScanHamannFiles();
if (_Files != null)
foreach(var category in _Files)
if (category.Value != null)
@@ -26,6 +26,10 @@ public class XMLProvider : IXMLProvider {
public List<IFileInfo>? GetHamannFiles() => this._HamannFiles;
public IFileInfo? GetInProduction() => this._InProduction;
public void SetInProduction(IFileInfo info) => _InProduction = info;
public FileList? GetFiles(string prefix)
=> _Files != null && _Files.ContainsKey(prefix) ? _Files[prefix] : null;
@@ -83,6 +87,7 @@ public class XMLProvider : IXMLProvider {
}
if (_HamannFiles == null) _HamannFiles = new List<IFileInfo>();
_HamannFiles.RemoveAll(x => x.Name == info.Name);
_HamannFiles.Add(info);
_InProduction = info;
return info;

View File

@@ -8,10 +8,10 @@ public class FileModel {
public bool InProduction { get; private set; }
public List<(string, string?)>? Fields { get; set; }
public FileModel(string name, string prefix, DateTime lastModified, bool isUSed, bool inProduction) {
public FileModel(string name, string prefix, DateTime lastModified, bool isUsed, bool inProduction) {
FileName = name;
IsUsed = IsUsed;
IsUsed = isUsed;
LastModified = lastModified;
InProduction = InProduction;
InProduction = inProduction;
}
}

View File

@@ -11,7 +11,7 @@ public class UploadViewModel {
public Dictionary<string, List<FileModel>?>? UsedFiles { get; private set; }
public Dictionary<string, List<FileModel>?>? ProductionFiles { get; set; }
public List<(string, DateTime)>? HamannFiles { get; set; }
public List<FileModel>? HamannFiles { get; set; }
public UploadViewModel(string title, string? prefix, List<IXMLRoot>? roots, Dictionary<string, List<FileModel>?>? usedFiles) {
Prefix = prefix;

View File

@@ -30,10 +30,10 @@ if (filepath == null) {
var physicalProvider = new PhysicalFileProvider(filepath);
builder.Services.AddSingleton<IFileProvider>(physicalProvider);
builder.Services.AddSingleton<HaWeb.FileHelpers.IHaDocumentWrappper, HaWeb.FileHelpers.HaDocumentWrapper>();
builder.Services.AddTransient<IReaderService, ReaderService>();
builder.Services.AddSingleton<IXMLProvider, XMLProvider>();
builder.Services.AddSingleton<IXMLService, XMLService>();
builder.Services.AddSingleton<HaWeb.FileHelpers.IHaDocumentWrappper, HaWeb.FileHelpers.HaDocumentWrapper>();
builder.Services.AddFeatureManagement();
var app = builder.Build();

View File

@@ -44,7 +44,7 @@
</label>
<div class="ha-publishmessage" id="ha-publishmessage">
@* Fehler!<br/> *@
<output form="uploadForm" name="publish-result"></output>
<output form="uploadForm" name="publish-result" id="publish-result"></output>
</div>
</form>
</div>
@@ -52,31 +52,42 @@
<div class="ha-uploadheader">
<h1 class="ha-uploadtitle">@Model.ActiveTitle</h1>
@if (Model.Prefix != null) {
<div class="ha-usedfilesheader">
<div class="ha-usedfilesheaderlist">
@if(Model.UsedFiles != null && Model.UsedFiles.ContainsKey(Model.Prefix)) {
@foreach (var item in Model.UsedFiles[Model.Prefix]!)
{
<div class="ha-usedfilesheaderfile">@item.FileName</div>
}
}
else {
<div class="ha-usedfilesnone">Keine Datei geladen</div>
}
@if (Model.AvailableFiles != null)
{
<div class="ha-availablefilechooser"><div class="ha-plussign"></div><a class="ha-loadotherfilesbtn">Andere Dateien laden...</a></div>
}
</div>
</div>
}
</div>
<div class="ha-uploadcontainer">
@* File Category Page File List *@
@if (Model.AvailableFiles != null && Model.AvailableFiles.Any()) {
<div class="ha-filesheader">
<div class="ha-availablefiles" id="ha-availablefiles">
<div class="ha-availablefilestitle">Datei(en)</div>
@if(Model.UsedFiles != null && Model.UsedFiles.ContainsKey(Model.Prefix)) {
<div class="ha-usedfilelist">
@foreach (var item in Model.UsedFiles[Model.Prefix]!)
{
if(item == Model.UsedFiles[Model.Prefix]!.Last()) {
<span class="ha-usedfile">@item.FileName</span>
}
else {
<span class="ha-usedfile">@item.FileName,</span>
}
}
</div>
}
</div>
<div class="ha-availablefileslist hidden" id="ha-availablefileslist">
@await Html.PartialAsync("/Views/Shared/_FileList.cshtml", (Model.AvailableFiles, "Verfügbare Dateien:", "API", "SetUsed", Model.Prefix, "/Download/XML/" + Model.Prefix + "/", true))
</div>
</div>
}
@* Start Page File List *@
else {
<div class="ha-hamannfilechooser">
@await Html.PartialAsync("/Views/Shared/_FileList.cshtml", (Model.HamannFiles, "Verfügbare Hamann-Dateien", "API", "SetUsedHamann", string.Empty, "/Download/XML/", false))
</div>
}
@* File Category Page Syntax Check *@
@if (Model.UsedFiles != null && Model.Prefix != null && Model.UsedFiles.ContainsKey(Model.Prefix)) {
<div class="ha-errorswarnings">
<div class="ha-criticalerrors">
@@ -92,17 +103,25 @@
</div>
}
else {
<form class="ha-selecthamannfilesform" id="selecthamannfilesform" asp-controller="API" asp-action="SetUsedHamann" method="post" enctype="multipart/form-data">
</form>
}
</div>
@section Scripts {
<script>
"use strict";
const hideshowfiles = function() {
let elem = document.getElementById("ha-availablefileslist");
console.log("hello!");
if (elem.classList.contains('hidden')) {
elem.classList.remove('hidden');
elem.classList.add('block');
}
else {
elem.classList.add('hidden');
elem.classList.remove('block');
}
}
const dropHandler = function (formelement, ev, dropzone) {
ev.preventDefault();
if (ev.dataTransfer.items) {
@@ -145,18 +164,19 @@ else {
document.getElementById("ha-publishfilelabel").style.pointerEvents = "auto";
document.getElementById("ha-lds-ellipsis-publish").style.display = "none";
document.getElementById("ha-publishmessage").style.opacity = "1";
oFormElement.elements.namedItem("update-result").value = json.Error;
document.getElementById("publish-result").value = json.Error;
} else {
document.getElementById("ha-publishfilelabel").style.pointerEvents = "auto";
document.getElementById("ha-lds-ellipsis-publish").style.display = "none";
document.getElementById("ha-publishmessage").style.opacity = "1";
oFormElement.elements.namedItem("update-result").value = "Erfolg!";
document.getElementById("publish-result").value = "Erfolg!";
window.location.replace("/Admin/Upload/");
}
})
.catch ((e) => {
document.getElementById("ha-publishfilelabel").style.pointerEvents = "auto";
document.getElementById("ha-lds-ellipsis-publish").style.display = "none";
console.log('Error:', e);
document.getElementById("publish-result").value = "Keine Antwort. Bitte Seite neu laden!";
})
}
@@ -212,6 +232,9 @@ else {
var dropzone = document.getElementById("dropzone");
var publishelement = document.getElementById("ha-publishform");
var publishbutton = document.getElementById("ha-publishfilelabel");
var filesbutton = document.getElementById("ha-availablefiles");
if (filesbutton !== null)
filesbutton.addEventListener("click", () => hideshowfiles());
publishbutton.addEventListener("click", () => LOCALPUBLISHSubmit(publishelement));
submitelement.addEventListener("change", () => UPLOADSubmit(formelement));
dropzone.addEventListener("drop", (ev) => dropHandler(formelement, ev, dropzone));

View File

@@ -0,0 +1,71 @@
@model (List<FileModel>? files, string title, string aspcontrolller, string aspaction, string id, string downloadprefix, bool multipleallowed);
<fieldset class="ha-filelistfieldset">
<legend class="ha-filelistlegend">@Model.title</legend>
@if(Model.files != null && Model.files.Any()) {
<form class="ha-selectfilesform" id="selecthamannfilesform" asp-controller="@Model.aspcontrolller" asp-action="@Model.aspaction" asp-route-id="@Model.id" method="post" onsubmit="USESubmit(this);return false;" enctype="multipart/form-data">
<div class="ha-filelistlist">
@foreach (var file in Model.files.OrderByDescending(x => x.LastModified)) {
<div class="ha-filelistfile">
@if (Model.multipleallowed) {
<input type="checkbox" id="@file.FileName" name="file" value="@file.FileName" @(file.IsUsed ? "checked='checked'" : "")>
}
else {
<input type="radio" id="@file.FileName" name="file" value="@file.FileName" @(file.InProduction ? "checked='checked'" : "")>
}
<div class="ha-filelistname">@file.FileName</div>
@if (file.InProduction || file.IsUsed) {
<div class="ha-filelistusedproduction">
@if (file.InProduction) {
<div class="ha-filelistproduction">in Verwendung</div>
}
@if (file.IsUsed) {
<div class="ha-filelistused">geladen</div>
}
</div>
}
@if (file.Fields != null && file.Fields.Any()) {
<div class="ha-filelistfields">
@foreach (var field in file.Fields) {
@if (field.Item2 != null) {
<div class="ha-filelistfield">field.Item2</div>
}
}
</div>
}
<div class="ha-filelistmodified">@file.LastModified</div>
</div>
}
</div>
<input class="btn ha-filelistbutton" type="submit" value="Laden" />
</form>
}
else {
<div>Keine Hamann-Dateien gefunden! Es wird eine fallback-Datei verwendet!</div>
}
</fieldset>
<script>
const USESubmit = async function (oFormElement, file = null) {
let fd = new FormData(oFormElement);
await fetch(oFormElement.action, {
method: 'POST',
headers: {
'RequestVerificationToken': getCookie('RequestVerificationToken')
},
body: fd
})
.then(response => response.json())
.then(json => {
if ("Error" in json) {
}
else {
location.reload();
}
})
.catch ((e) => {
location.reload();
})
}
</script>

View File

@@ -14,4 +14,6 @@ public interface IXMLService {
public void AutoUse(string prefix);
public void AutoUse(FileList filelist);
public Dictionary<string, FileList?>? GetInProduction();
public void UnUse(string prefix);
public void UnUseProduction();
}

View File

@@ -34,6 +34,8 @@ public class XMLService : IXMLService {
public Dictionary<string, FileList?>? GetInProduction() => this._InProduction;
public void UnUseProduction() => this._InProduction = null;
public List<XMLRootDocument>? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState) {
if (document.Root!.Name != "opus") {
ModelState.AddModelError("Error", "A valid Hamann-Docuemnt must begin with <opus>");
@@ -66,6 +68,11 @@ public class XMLService : IXMLService {
_Used[doc.Prefix]!.Add(doc);
}
public void UnUse(string prefix) {
if (_Used != null && _Used.ContainsKey(prefix)) _Used.Remove(prefix);
return;
}
// Performs detection of using on the specified document type
public void AutoUse(string prefix) {
if (_Used == null || !_Used.ContainsKey(prefix)) return;

View File

@@ -19,6 +19,7 @@ module.exports = {
sans: ['Biolinum', 'sans-serif'],
serif: ['Libertine', 'serif'],
classy: ['Playfair', 'serif'],
mono: ['ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', 'mono']
},
screens: {
'sm': '700px',

View File

@@ -123,7 +123,7 @@ code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, mono;
/* 1 */
font-size: 1em;
/* 2 */
@@ -478,6 +478,8 @@ Ensure the default browser behavior of the `hidden` attribute.
/* TODO: check what can be inlined (eg. used once in the code, has no double paths etc...) */
/* TODO: Copy color classes for all thing upload to colors on top */
/* Everything related to theme color */
body {
@@ -1868,8 +1870,8 @@ body {
.ha-adminuploadfields .ha-uploadfield.active {
--tw-text-opacity: 1 !important;
color: rgb(0 0 0 / var(--tw-text-opacity)) !important;
--tw-shadow-color: #fee2e2;
--tw-shadow: var(--tw-shadow-colored);
--tw-brightness: brightness(1.1);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.ha-adminuploadfields .ha-uploadfield .ha-uploadfieldname {
@@ -2022,46 +2024,6 @@ body {
line-height: 1;
}
.ha-uploadheader .ha-usedfilesheader {
display: flex;
}
.ha-uploadheader .ha-usedfilesheaderlist {
margin-left: 1.5rem;
margin-bottom: 0.25rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-end;
-moz-column-gap: 0.5rem;
column-gap: 0.5rem;
row-gap: 0.25rem;
align-self: flex-end;
}
.ha-uploadheader .ha-usedfilesheaderlist .ha-usedfilesheaderfile {
border-radius: 0.25rem;
background-color: rgb(51 65 85 / var(--tw-bg-opacity));
--tw-bg-opacity: 0.3;
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser {
border-radius: 0.25rem;
background-color: rgb(51 65 85 / var(--tw-bg-opacity));
--tw-bg-opacity: 0.5;
padding-right: 0.25rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser .ha-loadotherfilesbtn {
display: inline-block;
}
.ha-uploadcontainer {
display: flex;
height: 100%;
@@ -2072,6 +2034,40 @@ body {
background-color: rgb(248 250 252 / var(--tw-bg-opacity));
}
.ha-uploadcontainer .ha-availablefiles {
cursor: pointer;
border-width: 1px;
--tw-border-opacity: 1;
border-color: rgb(226 232 240 / var(--tw-border-opacity));
padding-left: 4rem;
padding-right: 4rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.ha-uploadcontainer .ha-availablefiles:hover {
--tw-border-opacity: 1;
border-color: rgb(30 41 59 / var(--tw-border-opacity));
}
.ha-uploadcontainer .ha-availablefiles .ha-availablefilestitle {
font-size: 1.5rem;
line-height: 2rem;
}
.ha-uploadcontainer .ha-availablefiles .ha-usedfilelist {
}
.ha-filesheader {
margin-bottom: 2rem;
}
.ha-availablefileslist {
padding-left: 4rem;
padding-right: 4rem;
padding-top: 1rem;
}
.ha-uploadcontainer .ha-errorswarnings {
display: flex;
flex-direction: row;
@@ -2109,6 +2105,98 @@ body {
background-color: rgb(165 243 252 / var(--tw-bg-opacity));
}
.ha-uploadcontainer .ha-hamannfilechooser {
padding-left: 4rem;
padding-right: 4rem;
padding-bottom: 4rem;
}
/* Classes for FileList Component */
.ha-filelistfieldset {
}
.ha-filelistfieldset .ha-filelistlegend {
margin-bottom: 0.5rem;
font-size: 1.25rem;
line-height: 1.75rem;
}
.ha-selectfilesform {
}
.ha-selectfilesform .ha-filelistfile {
display: flex;
flex-direction: row;
-moz-column-gap: 1rem;
column-gap: 1rem;
}
.ha-selectfilesform .ha-filelistlist {
height: 24rem;
overflow-x: hidden;
overflow-y: scroll;
padding-right: 1rem;
}
.ha-selectfilesform .ha-filelistfile .ha-filelistname {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, mono;
}
.ha-selectfilesform .ha-filelistfile .ha-filelistusedproduction {
align-self: flex-start;
font-size: 0.875rem;
line-height: 1.25rem;
}
.ha-selectfilesform .ha-filelistfile .ha-filelistusedproduction .ha-filelistproduction {
margin-right: 0.5rem;
display: inline-block;
border-radius: 0.375rem;
border-width: 1px;
--tw-border-opacity: 1;
border-color: rgb(13 148 136 / var(--tw-border-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
--tw-text-opacity: 1;
color: rgb(13 148 136 / var(--tw-text-opacity));
}
.ha-selectfilesform .ha-filelistfile .ha-filelistusedproduction .ha-filelistused {
display: inline-block;
border-radius: 0.375rem;
border-width: 1px;
--tw-border-opacity: 1;
border-color: rgb(79 70 229 / var(--tw-border-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
}
.ha-selectfilesform .ha-filelistfile .ha-filelistmodified {
flex-grow: 1;
text-align: right;
}
.ha-selectfilesform .ha-filelistbutton {
float: right;
margin-top: 0.5rem;
margin-left: 1.5rem;
cursor: pointer;
border-radius: 0.375rem;
border-width: 2px;
--tw-border-opacity: 1;
border-color: rgb(29 78 216 / var(--tw-border-opacity));
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.ha-selectfilesform .ha-filelistbutton:hover {
--tw-bg-opacity: 1;
background-color: rgb(147 197 253 / var(--tw-bg-opacity));
}
/* Classes for Letter View */
.ha-letterheader {
@@ -3055,6 +3143,10 @@ body {
margin-right: 0px !important;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}

View File

@@ -84,6 +84,7 @@
@apply transition-colors duration-100
}
/* TODO: check what can be inlined (eg. used once in the code, has no double paths etc...) */
/* TODO: Copy color classes for all thing upload to colors on top */
/* Everything related to theme color */
body {
@@ -678,7 +679,7 @@
}
.ha-adminuploadfields .ha-uploadfield.active {
@apply !text-black shadow-red-100
@apply !text-black brightness-110
}
.ha-adminuploadfields .ha-uploadfield .ha-uploadfieldname {
@@ -745,30 +746,30 @@
@apply text-5xl
}
.ha-uploadheader .ha-usedfilesheader {
@apply flex
}
.ha-uploadheader .ha-usedfilesheaderlist {
@apply flex flex-row flex-wrap ml-6 gap-x-2 gap-y-1 self-end content-end mb-1
}
.ha-uploadheader .ha-usedfilesheaderlist .ha-usedfilesheaderfile {
@apply text-sm px-2 bg-slate-700 bg-opacity-30 rounded
}
.ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser {
@apply text-sm pr-1 bg-slate-700 bg-opacity-50 rounded
}
.ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser .ha-loadotherfilesbtn {
@apply inline-block
}
.ha-uploadcontainer {
@apply w-full bg-slate-50 flex flex-col gap-y-2 h-full
}
.ha-uploadcontainer .ha-availablefiles {
@apply px-16 border border-slate-200 hover:border-slate-800 py-2 cursor-pointer
}
.ha-uploadcontainer .ha-availablefiles .ha-availablefilestitle {
@apply text-2xl
}
.ha-uploadcontainer .ha-availablefiles .ha-usedfilelist {
}
.ha-filesheader {
@apply mb-8
}
.ha-availablefileslist {
@apply px-16 pt-4
}
.ha-uploadcontainer .ha-errorswarnings {
@apply flex flex-row gap-x-2
}
@@ -790,6 +791,55 @@
@apply w-full bg-cyan-200 grow shrink-0 h-full min-h-[400px]
}
.ha-uploadcontainer .ha-hamannfilechooser {
@apply px-16 pb-16
}
/* Classes for FileList Component */
.ha-filelistfieldset {
}
.ha-filelistfieldset .ha-filelistlegend {
@apply mb-2 text-xl
}
.ha-selectfilesform {
}
.ha-selectfilesform .ha-filelistfile {
@apply flex flex-row gap-x-4
}
.ha-selectfilesform .ha-filelistlist {
@apply h-96 overflow-x-hidden overflow-y-scroll pr-4
}
.ha-selectfilesform .ha-filelistfile .ha-filelistname {
@apply font-mono
}
.ha-selectfilesform .ha-filelistfile .ha-filelistusedproduction {
@apply text-sm self-start
}
.ha-selectfilesform .ha-filelistfile .ha-filelistusedproduction .ha-filelistproduction {
@apply inline-block border rounded-md text-teal-600 border-teal-600 px-2 mr-2
}
.ha-selectfilesform .ha-filelistfile .ha-filelistusedproduction .ha-filelistused {
@apply inline-block border rounded-md text-indigo-600 border-indigo-600 px-2
}
.ha-selectfilesform .ha-filelistfile .ha-filelistmodified {
@apply grow text-right
}
.ha-selectfilesform .ha-filelistbutton {
@apply mt-2 ml-6 rounded-md px-3 border-2 border-blue-700 hover:bg-blue-300 cursor-pointer float-right;
}
/* Classes for Letter View */
.ha-letterheader {