App: Light PoS on top of the existing PoS (#1909)

* Initial commit

* review fixes

* Fix padding of logo, readjust display font size when clearing numbers

Co-authored-by: Kukks <evilkukka@gmail.com>
This commit is contained in:
Mario Dian
2020-09-21 14:06:31 +08:00
committed by GitHub
parent 8891111b2c
commit c640289f4f
10 changed files with 262 additions and 1 deletions

View File

@@ -65,6 +65,8 @@ namespace BTCPayServer.Controllers
var numberFormatInfo = _AppService.Currencies.GetNumberFormatInfo(settings.Currency) ?? _AppService.Currencies.GetNumberFormatInfo("USD"); var numberFormatInfo = _AppService.Currencies.GetNumberFormatInfo(settings.Currency) ?? _AppService.Currencies.GetNumberFormatInfo("USD");
double step = Math.Pow(10, -(numberFormatInfo.CurrencyDecimalDigits)); double step = Math.Pow(10, -(numberFormatInfo.CurrencyDecimalDigits));
viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
var store = await _AppService.GetStore(app);
var storeBlob = store.GetStoreBlob();
return View("PointOfSale/" + viewType, new ViewPointOfSaleViewModel() return View("PointOfSale/" + viewType, new ViewPointOfSaleViewModel()
{ {
@@ -91,6 +93,7 @@ namespace BTCPayServer.Controllers
CustomTipText = settings.CustomTipText, CustomTipText = settings.CustomTipText,
CustomTipPercentages = settings.CustomTipPercentages, CustomTipPercentages = settings.CustomTipPercentages,
CustomCSSLink = settings.CustomCSSLink, CustomCSSLink = settings.CustomCSSLink,
CustomLogoLink = storeBlob.CustomLogo,
AppId = appId, AppId = appId,
Description = settings.Description, Description = settings.Description,
EmbeddedCSS = settings.EmbeddedCSS EmbeddedCSS = settings.EmbeddedCSS

View File

@@ -49,6 +49,7 @@ namespace BTCPayServer.Models.AppViewModels
public int[] CustomTipPercentages { get; set; } public int[] CustomTipPercentages { get; set; }
public string CustomCSSLink { get; set; } public string CustomCSSLink { get; set; }
public string CustomLogoLink { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string EmbeddedCSS { get; set; } public string EmbeddedCSS { get; set; }
} }

View File

@@ -9,6 +9,7 @@ namespace BTCPayServer.Services.Apps
public enum PosViewType public enum PosViewType
{ {
Static, Static,
Cart Cart,
Light
} }
} }

View File

@@ -0,0 +1,16 @@
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@{
Layout = "_LayoutPos";
}
@if (Context.Request.Query.ContainsKey("simple"))
{
@await Html.PartialAsync("/Views/AppsPublic/PointOfSale/MinimalLight.cshtml", Model)
}
else
{
<noscript>
@await Html.PartialAsync("/Views/AppsPublic/PointOfSale/MinimalLight.cshtml", Model)
</noscript>
@await Html.PartialAsync("/Views/AppsPublic/PointOfSale/VueLight.cshtml", Model)
}

View File

@@ -0,0 +1,26 @@
<div class="container p-0 l-pos-wrapper">
<div class="l-pos-header bg-primary py-3 px-3">
@if (!string.IsNullOrEmpty(Model.CustomLogoLink)) {
<img src="@Model.CustomLogoLink" height="40">
} else {
<h1 class="mb-0">@Model.Title</h1>
}
</div>
<div class="py-5 px-3">
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">@Model.CurrencySymbol</span>
</div>
<input class="form-control" type="number" step="@Model.Step" name="amount" placeholder="Amount">
<div class="input-group-append">
<button class="btn btn-primary" type="submit">Pay</button>
</div>
</div>
</form>
<div class="text-center mt-5 mb-3 py-2 bg-dark">
<svg class="logo" viewBox="0 0 192 84" xmlns="http://www.w3.org/2000/svg"><g><path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="#CEDC21" class="logo-brand-light"/><path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="#51B13E" class="logo-brand-medium"/><path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="#CEDC21" class="logo-brand-light"/><path d="M10.066 31.725v20.553L24.01 42.006z" fill="#1E7A44" class="logo-brand-dark"/><path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="#CEDC21" class="logo-brand-light"/><path d="M74.355 41.412c3.114.884 4.84 3.704 4.84 7.238 0 5.513-3.368 8.082-7.955 8.082H60.761V27.271h9.259c4.504 0 7.997 2.146 7.997 7.743 0 2.821-1.179 5.43-3.662 6.398m-4.293-.716c3.324 0 6.018-1.179 6.018-5.724 0-4.586-2.776-5.808-6.145-5.808h-7.197v11.531h7.324v.001zm1.052 14.099c3.366 0 6.06-1.768 6.06-6.145 0-4.713-3.072-6.144-6.901-6.144h-7.534v12.288h8.375v.001zM98.893 27.271v1.81h-8.122v27.651h-1.979V29.081h-8.123v-1.81zM112.738 26.85c5.01 0 9.554 2.524 10.987 8.543h-1.895c-1.348-4.923-5.303-6.732-9.134-6.732-6.944 0-10.605 5.681-10.605 13.341 0 8.08 3.661 13.256 10.646 13.256 4.125 0 7.828-1.85 9.26-7.279h1.895c-1.264 6.271-6.229 9.174-11.154 9.174-7.87 0-12.583-5.808-12.583-15.15 0-8.966 4.969-15.153 12.583-15.153M138.709 27.271c5.091 0 8.795 3.326 8.795 9.764 0 6.06-3.704 9.722-8.795 9.722h-7.746v9.976h-1.935V27.271h9.681zm0 17.549c3.745 0 6.816-2.397 6.816-7.827 0-5.429-2.947-7.869-6.816-7.869h-7.746V44.82h7.746zM147.841 56.732v-.255l11.741-29.29h.885l11.615 29.29v.255h-2.062l-3.322-8.501H153.27l-3.324 8.501h-2.105zm12.164-26.052l-6.059 15.697h12.078l-6.019-15.697zM189.551 27.271h2.104v.293l-9.176 16.92v12.248h-2.02V44.484l-9.216-16.961v-.252h2.147l3.997 7.492 4.043 7.786h.04l4.081-7.786z" class="logo-brand-text"/></g></svg>
</div>
</div>
</div>

View File

@@ -0,0 +1,40 @@
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
<div id="app" class="l-pos-wrapper" v-cloak>
<form method="post" asp-controller="AppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy>
<div class="l-pos-header bg-primary py-3 px-3">
@if (!string.IsNullOrEmpty(Model.CustomLogoLink))
{
<img src="@Model.CustomLogoLink" height="40"/>
}
else
{
<h1 class="mb-0">@Model.Title</h1>
}
</div>
<div ref="display" class="l-pos-display pt-5 pb-3 px-3"><div class="text-muted">{{srvModel.currencyCode}}</div><span ref="amount" v-bind:style="{fontSize: fontSize + 'px'}">{{ payTotal }}</span></div>
<div class="l-pos-keypad">
<template
v-for="(key, index) in keys"
:key="index">
<div v-if="key !== ''" class="btn"
v-bind:class="{ 'btn-primary' : (isNaN(key) === false) || key === '.', 'btn-dark' : isNaN(key) && key !== '.' }"
v-on:click="buttonClicked(key)">{{ key }}</div>
<div v-else class="btn btn-empty"></div>
</template>
</div>
<div class="l-pos-controls mt-2">
<div class="btn btn-outline-secondary btn-lg mb-0" v-on:click="clearTotal">Clear</div><button class="btn btn-primary btn-lg mb-0" type="submit"><b>Pay</b></button>
</div>
<input class="form-control" type="hidden" name="amount" v-model="payTotalNumeric">
</form>
<div class="text-center mt-4 mb-3 py-2 bg-dark">
<svg class="logo" viewBox="0 0 192 84" xmlns="http://www.w3.org/2000/svg"><g><path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="#CEDC21" class="logo-brand-light"/><path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="#51B13E" class="logo-brand-medium"/><path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="#CEDC21" class="logo-brand-light"/><path d="M10.066 31.725v20.553L24.01 42.006z" fill="#1E7A44" class="logo-brand-dark"/><path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="#CEDC21" class="logo-brand-light"/><path d="M74.355 41.412c3.114.884 4.84 3.704 4.84 7.238 0 5.513-3.368 8.082-7.955 8.082H60.761V27.271h9.259c4.504 0 7.997 2.146 7.997 7.743 0 2.821-1.179 5.43-3.662 6.398m-4.293-.716c3.324 0 6.018-1.179 6.018-5.724 0-4.586-2.776-5.808-6.145-5.808h-7.197v11.531h7.324v.001zm1.052 14.099c3.366 0 6.06-1.768 6.06-6.145 0-4.713-3.072-6.144-6.901-6.144h-7.534v12.288h8.375v.001zM98.893 27.271v1.81h-8.122v27.651h-1.979V29.081h-8.123v-1.81zM112.738 26.85c5.01 0 9.554 2.524 10.987 8.543h-1.895c-1.348-4.923-5.303-6.732-9.134-6.732-6.944 0-10.605 5.681-10.605 13.341 0 8.08 3.661 13.256 10.646 13.256 4.125 0 7.828-1.85 9.26-7.279h1.895c-1.264 6.271-6.229 9.174-11.154 9.174-7.87 0-12.583-5.808-12.583-15.15 0-8.966 4.969-15.153 12.583-15.153M138.709 27.271c5.091 0 8.795 3.326 8.795 9.764 0 6.06-3.704 9.722-8.795 9.722h-7.746v9.976h-1.935V27.271h9.681zm0 17.549c3.745 0 6.816-2.397 6.816-7.827 0-5.429-2.947-7.869-6.816-7.869h-7.746V44.82h7.746zM147.841 56.732v-.255l11.741-29.29h.885l11.615 29.29v.255h-2.062l-3.322-8.501H153.27l-3.324 8.501h-2.105zm12.164-26.052l-6.059 15.697h12.078l-6.019-15.697zM189.551 27.271h2.104v.293l-9.176 16.92v12.248h-2.02V44.484l-9.216-16.961v-.252h2.147l3.997 7.492 4.043 7.786h.04l4.081-7.786z" class="logo-brand-text"/></g></svg>
</div>
</div>

View File

@@ -36,6 +36,14 @@
</script> </script>
<bundle name="wwwroot/bundles/cart-bundle.min.js" asp-append-version="true" /> <bundle name="wwwroot/bundles/cart-bundle.min.js" asp-append-version="true" />
} }
@if (Model.ViewType == PosViewType.Light)
{
<bundle name="wwwroot/bundles/light-pos-bundle.min.css" asp-append-version="true" />
<script type="text/javascript">
var srvModel = @Safe.Json(Model);
</script>
<bundle name="wwwroot/bundles/light-pos-bundle.min.js" asp-append-version="true" />
}
<style> <style>
.card-deck { .card-deck {
display: grid; display: grid;

View File

@@ -140,6 +140,19 @@
"wwwroot/crowdfund/**/*.css" "wwwroot/crowdfund/**/*.css"
] ]
}, },
{
"outputFileName": "wwwroot/bundles/light-pos-bundle.min.js",
"inputFiles": [
"wwwroot/vendor/vuejs/vue.min.js",
"wwwroot/light-pos/**/*.js"
]
},
{
"outputFileName": "wwwroot/bundles/light-pos-bundle.min.css",
"inputFiles": [
"wwwroot/light-pos/**/*.css"
]
},
{ {
"outputFileName": "wwwroot/bundles/payment-request-admin-bundle.min.js", "outputFileName": "wwwroot/bundles/payment-request-admin-bundle.min.js",
"inputFiles": [ "inputFiles": [

View File

@@ -0,0 +1,99 @@
var app = null;
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(function (ev) {
app = new Vue({
el: '#app',
data: function(){
var displayFontSize = 80;
return {
srvModel: window.srvModel,
payTotal: '0',
payTotalNumeric: 0,
fontSize: displayFontSize,
defaultFontSize: displayFontSize,
keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', 'C']
}
},
computed: {
Currency: function(){
return this.srvModel.Currency.toUpperCase();
},
},
watch: {
payTotal: function() {
// This must be timeouted because the updated width is not available yet
this.$nextTick(function(){
var displayWidth = this.getWidth(this.$refs.display),
amountWidth = this.getWidth(this.$refs.amount),
gamma = displayWidth / amountWidth || 0,
isAmountWider = displayWidth < amountWidth;
if (isAmountWider) {
// Font size will get smaller
this.fontSize = Math.floor(this.fontSize * gamma);
} else if (!isAmountWider && this.fontSize < this.defaultFontSize) {
// Font size will get larger up to the max size
this.fontSize = Math.min(this.fontSize * gamma, this.defaultFontSize);
}
});
}
},
methods: {
getWidth: function(el) {
var styles = window.getComputedStyle(el),
width = parseFloat(el.clientWidth),
padL = parseFloat(styles.paddingLeft),
padR = parseFloat(styles.paddingRight);
return width - padL - padR;
},
clearTotal: function() {
this.payTotal = '0';
this.payTotalNumeric = 0;
},
buttonClicked: function(key) {
var payTotal = this.payTotal;
if (key === 'C') {
payTotal = payTotal.substring(0, payTotal.length - 1);
payTotal = payTotal === '' ? '0' : payTotal;
} else if (key === '.') {
// Only add decimal point if it doesn't exist yet
if (payTotal.indexOf('.') === -1) {
payTotal += key;
}
} else { // Is a digit
if (payTotal === '0') {
payTotal = '';
}
payTotal += key;
var divsibility = this.srvModel.currencyInfo.divisibility;
var decimalIndex = payTotal.indexOf('.')
if (decimalIndex !== -1 && (payTotal.length - decimalIndex-1 > divsibility)) {
payTotal = payTotal.replace(".", "");
payTotal = payTotal.substr(0, payTotal.length - divsibility) + "." + payTotal.substr(payTotal.length - divsibility);
}
}
this.payTotal = payTotal;
this.payTotalNumeric = parseFloat(payTotal);
}
}
});
});

View File

@@ -0,0 +1,54 @@
[v-cloak] > * {
display: none
}
[v-cloak]::before {
content: "loading…"
}
.l-pos-wrapper {
max-width: 450px;
margin: auto;
}
.l-pos-header {
color: #fff;
}
.l-pos-display {
font-size: 1.4rem;
overflow: hidden;
}
.l-pos-display span {
display: inline-block;
height: 80px;
line-height: 80px;
}
.l-pos-keypad {
margin-left: 2%;
}
.l-pos-keypad .btn {
width: 32%;
margin-right: 1%;
margin-bottom: 1%;
border-radius: 0;
padding-top: 4%;
padding-bottom: 4%;
font-weight: bold;
font-size: 1.3rem;
}
.l-pos-controls .btn {
width: 47%;
border-radius: 0;
margin-right: 0%;
margin-left: 2%;
margin-bottom: 1%;
}
.logo {
height: 40px;
filter: contrast(0) brightness(200%);
}