Editor: Use offcanvas for all breakpoints (#6441)

Avoids scrolling the editor out of the viewport. Closes #6436.

Done for the POS/Crowdfund editor, as well as the Forms one.
This commit is contained in:
d11n 2024-12-04 15:18:27 +01:00 committed by GitHub
parent 341bea48f8
commit a8d1f55544
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 94 additions and 97 deletions

View File

@ -1240,15 +1240,17 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Title")).SendKeys("Tea shop"); s.Driver.FindElement(By.Id("Title")).SendKeys("Tea shop");
s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click(); s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click();
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1)")).Click(); s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1)")).Click();
s.Driver.WaitUntilAvailable(By.Id("BuyButtonText"));
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money"); s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
s.Driver.FindElement(By.Id("EditorCategories-ts-control")).SendKeys("Drinks"); s.Driver.FindElement(By.Id("EditorCategories-ts-control")).SendKeys("Drinks");
s.Driver.FindElement(By.CssSelector(".offcanvas-header button")).Click();
s.Driver.WaitUntilAvailable(By.Id("CodeTabButton"));
s.Driver.ScrollTo(By.Id("CodeTabButton")); s.Driver.ScrollTo(By.Id("CodeTabButton"));
s.Driver.FindElement(By.Id("CodeTabButton")).Click(); s.Driver.FindElement(By.Id("CodeTabButton")).Click();
var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value"); var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");
Assert.Contains("\"buyButtonText\": \"Take my money\"", template); Assert.Contains("\"buyButtonText\": \"Take my money\"", template);
Assert.Matches("\"categories\": \\[\r?\n\\s*\"Drinks\"\\s*\\]", template); Assert.Matches("\"categories\": \\[\r?\n\\s*\"Drinks\"\\s*\\]", template);
s.ClickPagePrimary(); s.ClickPagePrimary();
Assert.Contains("App updated", s.FindAlertMessage().Text); Assert.Contains("App updated", s.FindAlertMessage().Text);
@ -1453,12 +1455,15 @@ namespace BTCPayServer.Tests
s.GoToUrl(editUrl); s.GoToUrl(editUrl);
s.Driver.ScrollTo(By.Id("btAddItem")); s.Driver.ScrollTo(By.Id("btAddItem"));
s.Driver.FindElement(By.Id("btAddItem")).Click(); s.Driver.FindElement(By.Id("btAddItem")).Click();
s.Driver.WaitUntilAvailable(By.Id("EditorTitle"));
s.Driver.FindElement(By.Id("EditorTitle")).SendKeys("Perk 1"); s.Driver.FindElement(By.Id("EditorTitle")).SendKeys("Perk 1");
s.Driver.FindElement(By.Id("EditorAmount")).SendKeys("20"); s.Driver.FindElement(By.Id("EditorAmount")).SendKeys("20");
// Test autogenerated ID // Test autogenerated ID
Assert.Equal("perk-1", s.Driver.FindElement(By.Id("EditorId")).GetAttribute("value")); Assert.Equal("perk-1", s.Driver.FindElement(By.Id("EditorId")).GetAttribute("value"));
s.Driver.FindElement(By.Id("EditorId")).Clear(); s.Driver.FindElement(By.Id("EditorId")).Clear();
s.Driver.FindElement(By.Id("EditorId")).SendKeys("Perk-1"); s.Driver.FindElement(By.Id("EditorId")).SendKeys("Perk-1");
s.Driver.FindElement(By.CssSelector(".offcanvas-header button")).Click();
s.Driver.WaitUntilAvailable(By.Id("CodeTabButton"));
s.Driver.ScrollTo(By.Id("CodeTabButton")); s.Driver.ScrollTo(By.Id("CodeTabButton"));
s.Driver.FindElement(By.Id("CodeTabButton")).Click(); s.Driver.FindElement(By.Id("CodeTabButton")).Click();
var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value"); var template = s.Driver.FindElement(By.Id("TemplateConfig")).GetAttribute("value");

View File

@ -54,70 +54,66 @@
{ {
<div asp-validation-summary="All" class="@(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")"></div> <div asp-validation-summary="All" class="@(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")"></div>
} }
<div class="row"> <div class="row" style="max-width:540px;">
<div class="col-sm-10 col-md-9 col-xl-7 col-xxl-6"> <div class="col-sm-6">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label asp-for="AppName" class="form-label" data-required></label>
<input asp-for="AppName" class="form-control" required />
<span asp-validation-for="AppName" class="text-danger"></span>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label asp-for="Title" class="form-label" data-required></label>
<input asp-for="Title" class="form-control" required />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label asp-for="Tagline" class="form-label"></label> <label asp-for="AppName" class="form-label" data-required></label>
<input asp-for="Tagline" class="form-control" /> <input asp-for="AppName" class="form-control" required />
<span asp-validation-for="Tagline" class="text-danger"></span> <span asp-validation-for="AppName" class="text-danger"></span>
</div> </div>
</div>
<div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<div class="d-flex align-items-center justify-content-between gap-2"> <label asp-for="Title" class="form-label" data-required></label>
<label asp-for="MainImageFile" class="form-label"></label> <input asp-for="Title" class="form-control" required />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Tagline" class="form-label"></label>
<input asp-for="Tagline" class="form-control" />
<span asp-validation-for="Tagline" class="text-danger"></span>
</div>
<div class="form-group">
<div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="MainImageFile" class="form-label"></label>
@if (!string.IsNullOrEmpty(Model.MainImageUrl))
{
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true">
<vc:icon symbol="cross" /> <span text-translate="true">Remove</span>
</button>
}
</div>
@if (canUpload)
{
<div class="d-flex align-items-center gap-3">
<input asp-for="MainImageFile" type="file" class="form-control flex-grow">
@if (!string.IsNullOrEmpty(Model.MainImageUrl)) @if (!string.IsNullOrEmpty(Model.MainImageUrl))
{ {
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true"> <img src="@Model.MainImageUrl" alt="Logo" style="height:2.1rem;max-width:10.5rem;" />
<vc:icon symbol="cross" /> <span text-translate="true">Remove</span>
</button>
} }
</div> </div>
@if (canUpload) <span asp-validation-for="MainImageFile" class="text-danger"></span>
{ }
<div class="d-flex align-items-center gap-3"> else
<input asp-for="MainImageFile" type="file" class="form-control flex-grow"> {
@if (!string.IsNullOrEmpty(Model.MainImageUrl)) <input asp-for="MainImageFile" type="file" class="form-control" disabled>
{ <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
<img src="@Model.MainImageUrl" alt="Logo" style="height:2.1rem;max-width:10.5rem;" /> }
} </div>
</div> <div class="form-group">
<span asp-validation-for="MainImageFile" class="text-danger"></span> <div class="d-flex align-items-center">
} <input asp-for="Enabled" type="checkbox" class="btcpay-toggle me-3"/>
else <div>
{ <label asp-for="Enabled" class="form-check-label"></label>
<input asp-for="MainImageFile" type="file" class="form-control" disabled> <span asp-validation-for="Enabled" class="text-danger"></span>
<div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div> <div class="text-muted" text-translate="true">The crowdfund will be visible to anyone.</div>
}
</div>
<div class="form-group">
<div class="d-flex align-items-center">
<input asp-for="Enabled" type="checkbox" class="btcpay-toggle me-3"/>
<div>
<label asp-for="Enabled" class="form-check-label"></label>
<span asp-validation-for="Enabled" class="text-danger"></span>
<div class="text-muted" text-translate="true">The crowdfund will be visible to anyone.</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-3"> <div class="row mt-3">
<div class="col-xl-10 col-xxl-constrain"> <div class="col-xxl-constrain">
<div class="form-group"> <div class="form-group">
<label asp-for="Description" class="form-label" data-required></label> <label asp-for="Description" class="form-label" data-required></label>
<textarea asp-for="Description" rows="20" cols="40" class="form-control richtext"></textarea> <textarea asp-for="Description" rows="20" cols="40" class="form-control richtext"></textarea>

View File

@ -58,47 +58,43 @@
{ {
<div asp-validation-summary="All" class="@(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")"></div> <div asp-validation-summary="All" class="@(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")"></div>
} }
<div class="row"> <div class="row" style="max-width:540px;">
<div class="col-sm-10 col-md-9 col-xl-7 col-xxl-6"> <div class="col-sm-6">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label asp-for="AppName" class="form-label" data-required></label>
<input asp-for="AppName" class="form-control" required />
<span asp-validation-for="AppName" class="text-danger"></span>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label asp-for="Title" class="form-label" data-required></label>
<input asp-for="Title" class="form-control" required />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label asp-for="DefaultView" class="form-label" data-required text-translate="true">Choose Point of Sale Style</label> <label asp-for="AppName" class="form-label" data-required></label>
<div class="btcpay-list-select"> <input asp-for="AppName" class="form-control" required />
@foreach (var type in Enum.GetValues<PosViewType>()) <span asp-validation-for="AppName" class="text-danger"></span>
{
<input type="radio" asp-for="DefaultView" value="@type" id="DefaultView_@type">
<label for="DefaultView_@type" class="btcpay-list-select-item">
<vc:icon symbol="pos-@type.ToString().ToLowerInvariant()" />
@typeof(PosViewType).DisplayName(type.ToString())
</label>
}
</div>
</div> </div>
<div class="form-group mb-0"> </div>
<label asp-for="Currency" class="form-label"></label> <div class="col-sm-6">
<input asp-for="Currency" class="form-control w-auto" currency-selection /> <div class="form-group">
<div class="form-text">@StringLocalizer["Uses the store's default currency ({0}) if empty.", Model.StoreDefaultCurrency]</div> <label asp-for="Title" class="form-label" data-required></label>
<span asp-validation-for="Currency" class="text-danger"></span> <input asp-for="Title" class="form-control" required />
<span asp-validation-for="Title" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="form-group">
<label asp-for="DefaultView" class="form-label" data-required text-translate="true">Choose Point of Sale Style</label>
<div class="btcpay-list-select">
@foreach (var type in Enum.GetValues<PosViewType>())
{
<input type="radio" asp-for="DefaultView" value="@type" id="DefaultView_@type">
<label for="DefaultView_@type" class="btcpay-list-select-item">
<vc:icon symbol="pos-@type.ToString().ToLowerInvariant()" />
@typeof(PosViewType).DisplayName(type.ToString())
</label>
}
</div>
</div>
<div class="form-group mb-0">
<label asp-for="Currency" class="form-label"></label>
<input asp-for="Currency" class="form-control w-auto" currency-selection />
<div class="form-text">@StringLocalizer["Uses the store's default currency ({0}) if empty.", Model.StoreDefaultCurrency]</div>
<span asp-validation-for="Currency" class="text-danger"></span>
</div>
</div> </div>
<div id="description" class="row mt-4"> <div id="description" class="row mt-4">
<div class="col-xl-10 col-xxl-constrain"> <div class="col-xxl-constrain">
<div class="form-group mb-0"> <div class="form-group mb-0">
<label asp-for="Description" class="form-label"></label> <label asp-for="Description" class="form-label"></label>
<textarea asp-for="Description" rows="10" cols="40" class="form-control richtext"></textarea> <textarea asp-for="Description" rows="10" cols="40" class="form-control richtext"></textarea>

View File

@ -139,7 +139,7 @@
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane fade show active" id="EditorTabPane" role="tabpanel" aria-labelledby="EditorTabButton" tabindex="0"> <div class="tab-pane fade show active" id="EditorTabPane" role="tabpanel" aria-labelledby="EditorTabButton" tabindex="0">
<div class="row align-items-start"> <div class="row align-items-start">
<div class="col-12 col-xl-7"> <div class="col-12">
<items-editor :items="items" <items-editor :items="items"
:selected-item="selectedItem" :selected-item="selectedItem"
v-on:add-item="addItem" v-on:add-item="addItem"
@ -149,13 +149,13 @@
:class="{ 'pt-2': (!items || items.length === 0) }" :class="{ 'pt-2': (!items || items.length === 0) }"
class="bg-tile pb-2 rounded" /> class="bg-tile pb-2 rounded" />
</div> </div>
<div class="col-xl-5 offcanvas-xl offcanvas-end" tabindex="-1" ref="editorOffcanvas"> <div class="offcanvas offcanvas-end" tabindex="-1" ref="editorOffcanvas">
<div class="offcanvas-header justify-content-between p-3"> <div class="offcanvas-header justify-content-between p-3">
<h5 class="offcanvas-title" text-translate="true">Edit Item</h5> <h5 class="offcanvas-title" text-translate="true">Edit Item</h5>
<button type="button" class="btn btn-sm rounded-pill" :class="{ 'btn-primary': itemChanged, 'btn-outline-secondary': !itemChanged }" v-on:click="hideOffcanvas" v-text="itemChanged ? 'Apply' : 'Close'"></button> <button type="button" class="btn btn-sm rounded-pill" :class="{ 'btn-primary': itemChanged, 'btn-outline-secondary': !itemChanged }" v-on:click="hideOffcanvas" v-text="itemChanged ? 'Apply' : 'Close'"></button>
</div> </div>
<div class="offcanvas-body p-3 p-xl-0"> <div class="offcanvas-body p-3">
<item-editor ref="itemEditor" :item="selectedItem" class="bg-tile w-100 p-xl-4 rounded" /> <item-editor ref="itemEditor" :item="selectedItem" class="bg-tile w-100 rounded" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -236,7 +236,7 @@
</div> </div>
</div> </div>
<div id="FormEditor" class="editor"> <div id="FormEditor" class="editor col-xxl-constrain">
<div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3"> <div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3">
<ul class="nav nav-pills gap-4" role="tablist"> <ul class="nav nav-pills gap-4" role="tablist">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
@ -255,7 +255,7 @@
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane fade show active" id="EditorTabPane" role="tabpanel" aria-labelledby="EditorTabButton" tabindex="0"> <div class="tab-pane fade show active" id="EditorTabPane" role="tabpanel" aria-labelledby="EditorTabButton" tabindex="0">
<div class="row align-items-start"> <div class="row align-items-start">
<div class="col-12 col-xl-7"> <div class="col-12">
<fields-editor :path="[]" <fields-editor :path="[]"
:fields="fields" :fields="fields"
:selected-field="selectedField" :selected-field="selectedField"
@ -266,15 +266,15 @@
:class="{ 'pt-2': (!fields || fields.length === 0) }" :class="{ 'pt-2': (!fields || fields.length === 0) }"
class="bg-tile pb-2 rounded" /> class="bg-tile pb-2 rounded" />
</div> </div>
<div class="col-xl-5 offcanvas-xl offcanvas-end" tabindex="-1" ref="editorOffcanvas"> <div class="offcanvas offcanvas-end" tabindex="-1" ref="editorOffcanvas">
<div class="offcanvas-header justify-content-between p-3"> <div class="offcanvas-header justify-content-between p-3">
<h5 class="offcanvas-title" text-translate="true">Edit Field</h5> <h5 class="offcanvas-title" text-translate="true">Edit Field</h5>
<button type="button" class="btn-close" aria-label="@StringLocalizer["Close"]" v-on:click="hideOffcanvas"> <button type="button" class="btn-close" aria-label="@StringLocalizer["Close"]" v-on:click="hideOffcanvas">
<vc:icon symbol="close" /> <vc:icon symbol="close" />
</button> </button>
</div> </div>
<div class="offcanvas-body p-3 p-xl-0"> <div class="offcanvas-body p-3">
<field-editor :field="selectedField" class="bg-tile w-100 p-xl-4 rounded" /> <field-editor :field="selectedField" class="bg-tile w-100 rounded" />
</div> </div>
</div> </div>
</div> </div>