Merge branch 'boilerplate'
This commit is contained in:
commit
da324392f5
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
.idea
|
||||
public
|
||||
dist
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"bundles": {
|
||||
"app": "js/app.js",
|
||||
"layout": "sass/layout.scss",
|
||||
"error": "sass/error.scss",
|
||||
"logo": "img/logo.svg",
|
||||
"logo_png": "img/logox128.png",
|
||||
"logo_png_xxl": "img/logox1024.png"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
inkscape:export-filename="/r/arisu/dev/streams/wms/assets/img/logox128.png"
|
||||
inkscape:export-xdpi="512"
|
||||
inkscape:export-ydpi="512">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1381"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="9.0192696"
|
||||
inkscape:cy="11.978294"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6">
|
||||
<inkscape:grid
|
||||
spacingx="0.5"
|
||||
spacingy="0.5"
|
||||
type="xygrid"
|
||||
id="grid4542" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#00766c;fill-opacity:1;fill-rule:nonzero;stroke:#00766c;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 4.6020263,4.6020261 c -4.08077722,4.0820101 -4.08077722,10.7139369 0,14.7959479 m 0,0 0.6656889,-0.665689 c -3.7210992,-3.722224 -3.7210992,-9.7423466 0,-13.4645699 L 4.6020263,4.6020261 m 14.7959477,0 -0.665689,0.665689 c 3.721099,3.7222233 3.721099,9.7423459 0,13.4645699 l 0.665689,0.665689 m 0,0 c 4.080777,-4.082011 4.080777,-10.7139378 0,-14.7959479 M 8.0959735,15.904026 c -2.1644289,-2.162022 -2.1644289,-5.655225 0,-7.8172472 m 0,0 L 7.4302847,7.419251 c -2.5246945,2.5218873 -2.5246945,6.628577 0,9.150465 l 0.6656888,-0.66569 M 15.904026,8.0959736 c 2.164429,2.1620214 2.164429,5.6552244 0,7.8172474 m 0,0 0.66569,0.667527 c 2.524693,-2.521887 2.524693,-6.6285761 0,-9.1504633 l -0.66569,0.6656889"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccccccccccccccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
|
@ -0,0 +1,6 @@
|
|||
import './external_links';
|
||||
import './message_icons';
|
||||
|
||||
import '../sass/app.scss';
|
||||
|
||||
console.log('Hello world!');
|
|
@ -0,0 +1,9 @@
|
|||
import feather from "feather-icons";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('a[target="_blank"]').forEach(el => {
|
||||
el.innerHTML += `<i data-feather="external-link"></i>`;
|
||||
});
|
||||
|
||||
feather.replace();
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
import feather from "feather-icons";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const messageTypeToIcon = {
|
||||
info: 'info',
|
||||
success: 'check',
|
||||
warning: 'alert-triangle',
|
||||
error: 'x-circle',
|
||||
question: 'help-circle',
|
||||
};
|
||||
document.querySelectorAll('.message').forEach(el => {
|
||||
const type = el.dataset['type'];
|
||||
const icon = el.querySelector('.icon');
|
||||
const svgContainer = document.createElement('div');
|
||||
svgContainer.innerHTML = feather.icons[messageTypeToIcon[type]].toSvg();
|
||||
el.insertBefore(svgContainer.firstChild, icon);
|
||||
icon.remove();
|
||||
});
|
||||
|
||||
feather.replace();
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5iU1EQVg.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5jU1EQVg.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Light'), local('NunitoSans-Light'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8WAc5tU1E.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8cceyI9tScg.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8ccezI9tScg.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Regular'), local('NunitoSans-Regular'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe0qMImSLYBIv1o4X1M8cce9I9s.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5iU1EQVg.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5jU1EQVg.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Nunito Sans Bold'), local('NunitoSans-Bold'), url(https://fonts.gstatic.com/s/nunitosans/v5/pe03MImSLYBIv1o4X1M8cc8GBs5tU1E.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
$primary: #1e2932;
|
||||
$primaryForeground: #f0f0f0;
|
||||
$secondary: #00766c;
|
||||
$secondaryForeground: $primaryForeground;
|
||||
|
||||
$backgroundColor: $primary;
|
||||
$defaultTextColor: $primaryForeground;
|
||||
|
||||
$info: #49fb;
|
||||
$infoText: darken($info, 42%);
|
||||
$infoColor: desaturate($infoText, 50%);
|
||||
|
||||
$success: #5f5b;
|
||||
$successText: darken($success, 45%);
|
||||
$successColor: desaturate($successText, 50%);
|
||||
|
||||
$warning: #fc0b;
|
||||
$warningText: darken($warning, 30%);
|
||||
$warningColor: desaturate($warningText, 50%);
|
||||
|
||||
$error: #f00b;
|
||||
$errorText: darken($error, 30%);
|
||||
$errorColor: desaturate($errorText, 50%);
|
|
@ -0,0 +1 @@
|
|||
@import "layout";
|
|
@ -0,0 +1,90 @@
|
|||
@import "layout";
|
||||
|
||||
header, footer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.messages {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.error-instructions {
|
||||
margin-top: 32px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "Oops";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
font-size: #{'min(50vh, 40vw)'};
|
||||
opacity: 0.025;
|
||||
}
|
||||
}
|
||||
|
||||
.contact {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
|
||||
color: $defaultTextColor;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
|
||||
&::before {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
background-image: url(../img/logo.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 64px;
|
||||
|
||||
opacity: 0.075;
|
||||
filter: contrast(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,512 @@
|
|||
@import "vars";
|
||||
@import 'fonts';
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: 0;
|
||||
font-family: "Nunito Sans", sans-serif;
|
||||
font-size: 16px;
|
||||
|
||||
color: $defaultTextColor;
|
||||
background-color: $backgroundColor;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
$headerHeight: 64px;
|
||||
height: $headerHeight;
|
||||
line-height: $headerHeight;
|
||||
|
||||
$headerBackground: darken($primary, 1.75%);
|
||||
background-color: $headerBackground;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
padding: 0 24px 0 16px;
|
||||
font-size: 32px;
|
||||
color: $defaultTextColor;
|
||||
|
||||
&:hover {
|
||||
color: lighten($defaultTextColor, 10%);
|
||||
}
|
||||
|
||||
img {
|
||||
width: $headerHeight;
|
||||
height: $headerHeight;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
nav ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-size: 20px;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
|
||||
a, span {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
padding: 0 24px;
|
||||
|
||||
.feather {
|
||||
--icon-size: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #fff1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
background-color: darken($primary, 3%);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
|
||||
& + p {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
section > h2, .panel > h2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
|
||||
&::before, &::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
margin: 0 32px;
|
||||
height: 0;
|
||||
border-bottom: 1px solid $defaultTextColor;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
section > hr, .panel > hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid $defaultTextColor;
|
||||
opacity: 0.2;
|
||||
|
||||
margin: 8px 32px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $secondary;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: lighten($secondary, 10%);
|
||||
}
|
||||
|
||||
.feather.feather-external-link {
|
||||
--icon-size: 16px;
|
||||
margin-left: 4px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 8px 16px;
|
||||
text-align: center;
|
||||
|
||||
.form-field {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 16px auto;
|
||||
|
||||
label {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 20px;
|
||||
user-select: none;
|
||||
font-size: 16px;
|
||||
opacity: 0.75;
|
||||
|
||||
transition-property: top, font-size;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease-out;
|
||||
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
input, select, .input-group {
|
||||
border: 0;
|
||||
border-bottom: 2px solid #0008;
|
||||
color: $defaultTextColor;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 3px 3px 0 0;
|
||||
font-size: 16px;
|
||||
|
||||
&:focus, &:not([value=""]) {
|
||||
~ label {
|
||||
top: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input, select, .form-display {
|
||||
display: block;
|
||||
padding: 32px 8px 8px 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
|
||||
&::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& + .feather {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
|
||||
transition: transform 150ms ease-out;
|
||||
}
|
||||
|
||||
// Temporary
|
||||
&:focus + .feather {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
input[type=color] {
|
||||
height: calc(32px + 8px + 32px);
|
||||
}
|
||||
|
||||
&.inline {
|
||||
flex-direction: row;
|
||||
|
||||
input[type=checkbox] {
|
||||
text-align: left;
|
||||
width: min-content;
|
||||
height: min-content;
|
||||
|
||||
& ~ label {
|
||||
position: static;
|
||||
display: inline;
|
||||
padding-left: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-shrink: 1;
|
||||
flex-direction: row;
|
||||
|
||||
div {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
padding-top: 8px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
> input + * {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 28px;
|
||||
user-select: none;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error, .hint {
|
||||
padding: 2px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
|
||||
.feather {
|
||||
--icon-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button, .button {
|
||||
display: inline-flex;
|
||||
margin: 8px;
|
||||
padding: 12px 16px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
text-transform: uppercase;
|
||||
font-size: 16px;
|
||||
font-weight: bolder;
|
||||
|
||||
line-height: 16px;
|
||||
|
||||
.feather {
|
||||
--icon-size: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&, &.primary {
|
||||
color: $primaryForeground;
|
||||
background-color: $secondary;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($secondary, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: $infoColor;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $successColor;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: $warningColor;
|
||||
}
|
||||
|
||||
&.error, &.danger {
|
||||
background-color: $errorColor;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($errorColor, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $primaryForeground;
|
||||
}
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-collapse: collapse;
|
||||
|
||||
th, td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom: 1px solid #ffffff17;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #ffffff08;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #ffffff18;
|
||||
}
|
||||
|
||||
thead tr:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// --- Layout helpers
|
||||
// ---
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
max-width: 632px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin: 16px 0;
|
||||
padding: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 5px;
|
||||
|
||||
p {
|
||||
margin: 16px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-panel {
|
||||
margin: 32px -18px;
|
||||
padding: 1px 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 5px;
|
||||
|
||||
input, select, .input-group {
|
||||
border-radius: 5px !important;
|
||||
border-width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---
|
||||
// --- Feather
|
||||
// ---
|
||||
.feather {
|
||||
flex-shrink: 0;
|
||||
--icon-size: 24px;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: square;
|
||||
stroke-linejoin: miter;
|
||||
fill: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// ---
|
||||
// --- Helper classes
|
||||
// ---
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
padding: 8px 16px;
|
||||
|
||||
border-radius: 5px;
|
||||
|
||||
.feather {
|
||||
--icon-size: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:not(&-discreet) {
|
||||
background-color: #fff5;
|
||||
|
||||
&[data-type=info], &[data-type=question] {
|
||||
background-color: $infoColor;
|
||||
}
|
||||
|
||||
&[data-type=success] {
|
||||
background-color: $successColor;
|
||||
}
|
||||
|
||||
&[data-type=warning] {
|
||||
background-color: $warningColor;
|
||||
}
|
||||
|
||||
&[data-type=error] {
|
||||
background-color: $errorColor;
|
||||
}
|
||||
}
|
||||
|
||||
&-discreet {
|
||||
opacity: 0.75;
|
||||
|
||||
.feather {
|
||||
--icon-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messages .message:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.container > .messages:first-child {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.copyable-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 8px;
|
||||
|
||||
background-color: darken($backgroundColor, 2%);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
|
||||
.feather {
|
||||
--icon-size: 20px;
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export default Object.assign(require("wms-core/config/default").default, {
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
export default Object.assign(require("wms-core/config/production").default, {
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
export default Object.assign(require("wms-core/config/test").default, {
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
transform: {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'ts',
|
||||
'd.ts'
|
||||
],
|
||||
testMatch: [
|
||||
'**/test/**/*.test.ts'
|
||||
],
|
||||
testEnvironment: 'node'
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "aldap",
|
||||
"version": "0.1.0",
|
||||
"description": "Authentication LDAP server",
|
||||
"repository": "git@gitlab.com:ArisuOngaku/aldap.git",
|
||||
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||
"private": true,
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
"test": "jest --verbose --runInBand",
|
||||
"dist-webpack": "webpack --mode production",
|
||||
"dist": "tsc && npm run dist-webpack",
|
||||
"dev": "concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon dist/main.js\" \"webpack --watch --mode development\" \"maildev\"",
|
||||
"start": "yarn dist && node dist/main.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@types/config": "^0.0.36",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.13.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"concurrently": "^5.1.0",
|
||||
"css-loader": "^3.5.2",
|
||||
"feather-icons": "^4.28.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"imagemin": "^7.0.1",
|
||||
"imagemin-gifsicle": "^7.0.0",
|
||||
"imagemin-mozjpeg": "^8.0.0",
|
||||
"imagemin-pngquant": "^8.0.0",
|
||||
"imagemin-svgo": "^7.1.0",
|
||||
"img-loader": "^3.0.1",
|
||||
"jest": "^25.4.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.14.0",
|
||||
"nodemon": "^2.0.3",
|
||||
"sass-loader": "^8.0.2",
|
||||
"ts-jest": "^25.4.0",
|
||||
"typescript": "^3.8.3",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"wms-core": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"config": "^3.3.1",
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import Application from "wms-core/Application";
|
||||
import {Type} from "wms-core/Utils";
|
||||
import Migration from "wms-core/db/Migration";
|
||||
import CreateMigrationsTable from "wms-core/migrations/CreateMigrationsTable";
|
||||
import CreateLogsTable from "wms-core/migrations/CreateLogsTable";
|
||||
import ExpressAppComponent from "wms-core/components/ExpressAppComponent";
|
||||
import NunjucksComponent from "wms-core/components/NunjucksComponent";
|
||||
import MysqlComponent from "wms-core/components/MysqlComponent";
|
||||
import LogRequestsComponent from "wms-core/components/LogRequestsComponent";
|
||||
import RedisComponent from "wms-core/components/RedisComponent";
|
||||
import ServeStaticDirectoryComponent from "wms-core/components/ServeStaticDirectoryComponent";
|
||||
import MaintenanceComponent from "wms-core/components/MaintenanceComponent";
|
||||
import MailComponent from "wms-core/components/MailComponent";
|
||||
import SessionComponent from "wms-core/components/SessionComponent";
|
||||
import RedirectBackComponent from "wms-core/components/RedirectBackComponent";
|
||||
import FormHelperComponent from "wms-core/components/FormHelperComponent";
|
||||
import CsrfProtectionComponent from "wms-core/components/CsrfProtectionComponent";
|
||||
import WebSocketServerComponent from "wms-core/components/WebSocketServerComponent";
|
||||
import HomeController from "./controllers/HomeController";
|
||||
|
||||
export default class ExampleApp extends Application {
|
||||
private readonly port: number;
|
||||
|
||||
constructor(port: number) {
|
||||
super(require('../package.json').version);
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
protected getMigrations(): Type<Migration>[] {
|
||||
return [
|
||||
CreateMigrationsTable,
|
||||
CreateLogsTable,
|
||||
];
|
||||
}
|
||||
|
||||
protected async init(): Promise<void> {
|
||||
this.registerComponents();
|
||||
this.registerWebSocketListeners();
|
||||
this.registerControllers();
|
||||
}
|
||||
|
||||
private registerComponents() {
|
||||
const redisComponent = new RedisComponent();
|
||||
const mysqlComponent = new MysqlComponent();
|
||||
|
||||
const expressAppComponent = new ExpressAppComponent(this.port);
|
||||
this.use(expressAppComponent);
|
||||
this.use(new NunjucksComponent());
|
||||
this.use(new LogRequestsComponent());
|
||||
|
||||
// Static files
|
||||
this.use(new ServeStaticDirectoryComponent('public'));
|
||||
this.use(new ServeStaticDirectoryComponent('node_modules/feather-icons/dist', '/icons'));
|
||||
|
||||
// Maintenance
|
||||
this.use(new MaintenanceComponent(this, () => {
|
||||
return redisComponent.canServe() && mysqlComponent.canServe();
|
||||
}));
|
||||
|
||||
// Services
|
||||
this.use(mysqlComponent);
|
||||
this.use(new MailComponent());
|
||||
|
||||
// Session
|
||||
this.use(redisComponent);
|
||||
this.use(new SessionComponent(redisComponent));
|
||||
|
||||
// Utils
|
||||
this.use(new RedirectBackComponent());
|
||||
this.use(new FormHelperComponent());
|
||||
|
||||
// Middlewares
|
||||
this.use(new CsrfProtectionComponent());
|
||||
|
||||
// WebSocket server
|
||||
this.use(new WebSocketServerComponent(this, expressAppComponent, redisComponent));
|
||||
}
|
||||
|
||||
private registerWebSocketListeners() {
|
||||
}
|
||||
|
||||
private registerControllers() {
|
||||
this.use(new HomeController());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import Controller from "wms-core/Controller";
|
||||
import {Request, Response} from "express";
|
||||
|
||||
export default class HomeController extends Controller {
|
||||
routes(): void {
|
||||
this.get('/', this.getHome, 'home');
|
||||
this.get('/about', this.getAbout, 'about');
|
||||
}
|
||||
|
||||
private async getHome(req: Request, res: Response) {
|
||||
res.render('home');
|
||||
}
|
||||
|
||||
private async getAbout(req: Request, res: Response) {
|
||||
res.render('about');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import Logger from "wms-core/Logger";
|
||||
import ExampleApp from "./ExampleApp";
|
||||
|
||||
(async () => {
|
||||
const app = new ExampleApp(4899);
|
||||
await app.start();
|
||||
})().catch(err => {
|
||||
Logger.error(err);
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
describe('Write your tests', () => {
|
||||
test('Remove this when you have some tests', () => {
|
||||
expect(false).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"target": "ES6",
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2020"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"node_modules/wms-core"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{% extends 'layouts/base.njk' %}
|
||||
|
||||
{% set title = 'Example App - About us' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Very interesting</h1>
|
||||
|
||||
<div class="container">
|
||||
<section class="panel">
|
||||
<h2>This is us</h2>
|
||||
<p class="center">And we like wms!</p>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,2 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -0,0 +1,2 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -0,0 +1,2 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -0,0 +1,2 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -0,0 +1,2 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -0,0 +1,2 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -0,0 +1,36 @@
|
|||
{% extends '../layouts/barebone.njk' %}
|
||||
|
||||
{% set title = error_code + ' - ' + error_message %}
|
||||
|
||||
{% block _stylesheets %}
|
||||
<link rel="stylesheet" href="/css/error.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block _body %}
|
||||
<div class="logo"><a href="/">Example app</a></div>
|
||||
|
||||
<main class="{% block class %}{% endblock %}">
|
||||
{% if flash %}
|
||||
{{ macros.messages(flash) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="error-code">{{ error_code }}</div>
|
||||
<div class="error-message">{{ error_message }}</div>
|
||||
<div class="error-instructions">{{ error_instructions|safe }}</div>
|
||||
|
||||
<nav>
|
||||
{% if session.previousUrl and session.previousUrl != '/' and session.previousUrl != url %}
|
||||
<a href="{{ session.previousUrl }}" class="button"><i data-feather="arrow-left"></i> Go back</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="/" class="button"><i data-feather="home"></i> Go to homepage</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<div class="contact">
|
||||
Error ID: {{ error_id }}
|
||||
<br>
|
||||
If you think this isn't right, please contact us with the above error ID at
|
||||
<a href="mailto:contact@example.net">contact@example.net</a>.
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'layouts/base.njk' %}
|
||||
|
||||
{% set title = 'Example App - Hello world!' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Hello world!</h1>
|
||||
{% endblock %}
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="/img/logox1024.png">
|
||||
<link rel="shortcut icon" type="image/png" href="/img/logox128.png">
|
||||
<link rel="shortcut icon" type="image/svg" href="/img/logo.svg">
|
||||
|
||||
{% if description %}
|
||||
<meta name="description" content="{{ description }}">
|
||||
{% endif %}
|
||||
|
||||
{% if refresh_after %}
|
||||
<meta http-equiv="refresh" content="{{ refresh_after }}">
|
||||
{% endif %}
|
||||
|
||||
{% block _stylesheets %}{% endblock %}
|
||||
{% block _scripts %}
|
||||
<script src="/js/app.js" defer></script>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
|
||||
{% block _body %}{% endblock %}
|
||||
|
||||
<footer>{% block footer %}{% endblock %}</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,40 @@
|
|||
{% extends './barebone.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
||||
|
||||
{% block _stylesheets %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
{% block stylesheets %}{% endblock %}
|
||||
{% endblock %}
|
||||
{% block _scripts %}
|
||||
{{ super() }}
|
||||
{% block scripts %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<a href="/" class="logo"><img src="/img/logo.svg" alt="Logo"> Example app</a>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="{{ route('about') }}"><i data-feather="info"></i> About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block _body %}
|
||||
<div class="container">
|
||||
{{ macros.messages(flash) }}
|
||||
</div>
|
||||
|
||||
<main>
|
||||
{% if h1 %}
|
||||
<h1>{{ h1 }}</h1>
|
||||
{% endif %}
|
||||
{% if subtitle %}
|
||||
<p>{{ subtitle }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}Example app v{{ app_version }} - all rights reserved.{% endblock %}
|
|
@ -0,0 +1,136 @@
|
|||
{% macro message(type, content, raw=false, discreet=false) %}
|
||||
<div class="message{{ ' message-discreet' if discreet }}" data-type="{{ type }}">
|
||||
<i class="icon"></i>
|
||||
<span class="content">
|
||||
{{ content|safe if raw else content }}
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro messages(flash) %}
|
||||
{% set flashed = flash() %}
|
||||
{% set display = 0 %}
|
||||
|
||||
{% for type, bag in flashed %}
|
||||
{% if bag|length %}
|
||||
{% set display = 1 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if display %}
|
||||
<div class="messages">
|
||||
{% for type, bag in flashed %}
|
||||
{% for content in bag %}
|
||||
{{ message(type, content) }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro csrf(getCSRFToken) %}
|
||||
<input type="hidden" name="csrf" value="{{ getCSRFToken() }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro field(_locals, type, name, value, placeholder, hint, validation_attributes='', extraData='') %}
|
||||
{% set validation = _locals.validation() %}
|
||||
{% set validation = validation[name] if validation[name] or null %}
|
||||
{% set previousFormData = _locals.previousFormData() %}
|
||||
{% set value = previousFormData[name] or value or validation.value or '' %}
|
||||
|
||||
{% if type == 'hidden' %}
|
||||
{% if validation %}
|
||||
{{ message('error', validation.message) }}
|
||||
{% endif %}
|
||||
<input type="hidden" name="{{ name }}" value="{{ value }}">
|
||||
{% else %}
|
||||
<div class="form-field{{ ' inline' if type == 'checkbox' }}">
|
||||
{% if type == 'duration' %}
|
||||
<div class="input-group">
|
||||
{% for f in extraData %}
|
||||
<div class="time-input">
|
||||
{% if previousFormData[name] %}
|
||||
{% set v = value[f] %}
|
||||
{% else %}
|
||||
{% set v = (value % 60) if f == 's' else (((value - value % 60) / 60 % 60) if f == 'm' else ((value - value % 3600) / 3600 if f == 'h')) %}
|
||||
{% endif %}
|
||||
<input type="number" name="{{ name }}[{{ f }}]" id="field-{{ name }}-{{ f }}"
|
||||
value="{{ v }}"
|
||||
min="0" {{ 'max=60' if (f == 's' or f == 'm') }}
|
||||
{{ validation_attributes }}>
|
||||
<label for="field-{{ name }}-{{ f }}">{{ f }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elseif type == 'select' %}
|
||||
<select name="{{ name }}" id="field-{{ name }}">
|
||||
{% for option in extraData %}
|
||||
<option value="{{ option }}" {{ 'selected' if value == option }}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<i data-feather="chevron-down"></i>
|
||||
{% else %}
|
||||
<input type="{{ type }}" name="{{ name }}" id="field-{{ name }}"
|
||||
{% if type != 'checkbox' %} value="{{ value }}" {% endif %}
|
||||
{{ 'checked' if (type == 'checkbox' and value == 'on') }}
|
||||
{{ validation_attributes|safe }}>
|
||||
{% endif %}
|
||||
|
||||
<label for="field-{{ name }}{{ '-' + extraData[0] if type == 'duration' }}">{{ placeholder }}</label>
|
||||
{% if validation %}
|
||||
<div class="error"><i data-feather="x-circle"></i> {{ validation.message }}</div>
|
||||
{% endif %}
|
||||
{% if hint %}
|
||||
<div class="hint"><i data-feather="info"></i> {{ hint }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro websocket(websocketUrl, listener, reconnectOnClose = 1, checkFunction = 0) %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
{% if checkFunction %}
|
||||
if (!{{ checkFunction }}()) return;
|
||||
{% endif %}
|
||||
|
||||
const run = () => {
|
||||
const websocket = new WebSocket('{{ websocketUrl }}');
|
||||
websocket.onopen = (e) => {
|
||||
console.debug('Websocket connected');
|
||||
};
|
||||
websocket.onmessage = (e) => {
|
||||
{{ listener }}(websocket, e);
|
||||
};
|
||||
websocket.onerror = (e) => {
|
||||
console.error('Websocket error', e);
|
||||
};
|
||||
websocket.onclose = (e) => {
|
||||
console.debug('Websocket closed', e.code, e.reason);
|
||||
|
||||
{% if reconnectOnClose %}
|
||||
setTimeout(run, 1000);
|
||||
{% endif %}
|
||||
};
|
||||
};
|
||||
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate(pagination, routeName) %}
|
||||
{% if pagination.hasPrevious() or pagination.hasNext() %}
|
||||
<div class="pagination">
|
||||
{% if pagination.hasPrevious() %}
|
||||
<a href="{{ route(routeName, {page: pagination.page - 1}) }}"><i data-feather="chevron-left"></i></a>
|
||||
{% endif %}
|
||||
|
||||
<span>{{ pagination.page }}</span>
|
||||
|
||||
{% if pagination.hasNext() %}
|
||||
<a href="{{ route(routeName, {page: pagination.page + 1}) }}"><i data-feather="chevron-right"></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
|
@ -0,0 +1,86 @@
|
|||
const path = require('path');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const dev = process.env.NODE_ENV === 'development';
|
||||
|
||||
const userConfig = require('./assets/config.json');
|
||||
for (const b in userConfig.bundles) {
|
||||
if (userConfig.bundles.hasOwnProperty(b)) {
|
||||
userConfig.bundles[b] = `./assets/${userConfig.bundles[b]}`;
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
entry: userConfig.bundles,
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'public/js'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
devtool: dev ? 'eval-source-map' : undefined,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
publicPath: '/',
|
||||
}
|
||||
},
|
||||
'css-loader',
|
||||
'sass-loader',
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)$/i,
|
||||
use: 'file-loader?name=../fonts/[name].[ext]',
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/i,
|
||||
use: [
|
||||
'file-loader?name=../img/[name].[ext]',
|
||||
{
|
||||
loader: 'img-loader',
|
||||
options: {
|
||||
enabled: !dev,
|
||||
plugins: [
|
||||
require('imagemin-gifsicle')({}),
|
||||
require('imagemin-mozjpeg')({}),
|
||||
require('imagemin-pngquant')({}),
|
||||
require('imagemin-svgo')({}),
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '../css/[name].css',
|
||||
}),
|
||||
]
|
||||
};
|
||||
|
||||
if (!dev) {
|
||||
config.optimization = {
|
||||
minimizer: [
|
||||
new UglifyJSPlugin(),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = config;
|
Loading…
Reference in New Issue