Initial commit
This commit is contained in:
commit
b581e9fbcf
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/.idea
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
/dist
|
||||
/node_modules
|
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "livedj-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./dist/main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/pug": "^2.0.6",
|
||||
"@types/mongoose": "^5.11.97",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/node": "^18.11.9",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "2.0.0-beta11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-loader": "^9.4.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
}
|
||||
}
|
BIN
public/images/not-playing.png
Executable file
BIN
public/images/not-playing.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 855 KiB |
90
public/stylesheets/fs.css
Executable file
90
public/stylesheets/fs.css
Executable file
@ -0,0 +1,90 @@
|
||||
root, *, body {
|
||||
margin: 0;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
font-size: 3.5em;
|
||||
font-weight: bold;
|
||||
font-family: "Roboto Light", Ubuntu, sans-serif;
|
||||
margin-top: 2%;
|
||||
margin-left: 1%;
|
||||
margin-right: 1%;
|
||||
background-color: #0E2D2D;
|
||||
color: white;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
|
||||
.live-dj {
|
||||
color: #3DE072;
|
||||
margin-bottom: 4%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.title {
|
||||
margin-bottom: 4%;
|
||||
color: #7E3E41;
|
||||
}
|
||||
|
||||
|
||||
.cover {
|
||||
border-radius: 10px;
|
||||
display: block;
|
||||
width: 50%;
|
||||
margin: 1% auto;
|
||||
}
|
||||
|
||||
.current-playing-name {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.current-playing-artist {
|
||||
font-size: 0.4em;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 30% 25% 45%;
|
||||
grid-template-rows: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.queue {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
.now {
|
||||
grid-row: span 1;
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
table {
|
||||
font-family: arial, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 90%;
|
||||
margin-left: 5%;
|
||||
background-color: #1D3636;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #131A2A;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #1D3636;
|
||||
|
||||
}
|
104
public/stylesheets/style.css
Executable file
104
public/stylesheets/style.css
Executable file
@ -0,0 +1,104 @@
|
||||
body {
|
||||
background-color: #0E2D2D;
|
||||
color: white;
|
||||
font-family: "Roboto Light", Ubuntu, sans-serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1.2em;
|
||||
border-radius: 10px;
|
||||
background-color: #42A4A4;
|
||||
border: 1px solid #423434;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1.2em;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
font-family: arial, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 99.99%;
|
||||
background-color: #1D3636;
|
||||
margin-bottom: 8%;
|
||||
overflow-x: hidden;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none; /* Standard syntax */
|
||||
|
||||
}
|
||||
|
||||
|
||||
td, th {
|
||||
border: 1px solid #131A2A;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:active {
|
||||
background-color: #66A1A1 !important;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #1F4444;
|
||||
}
|
||||
|
||||
|
||||
/* The Modal (background) */
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
padding-top: 100px; /* Location of the box */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
/* Modal Content */
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* The Close Button */
|
||||
.close {
|
||||
color: #aaaaaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: #0E2D2D;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
9
routes/index.js
Normal file
9
routes/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
9
routes/users.js
Normal file
9
routes/users.js
Normal file
@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
91
src/App.ts
Normal file
91
src/App.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import express from 'express';
|
||||
import path from "path";
|
||||
import morgan from "morgan";
|
||||
import cookieParser from "cookie-parser";
|
||||
import debug from "debug";
|
||||
import * as http from "http";
|
||||
import {AddressInfo} from "net";
|
||||
|
||||
export class App {
|
||||
get app(): express.Application {
|
||||
return this._app;
|
||||
}
|
||||
|
||||
private readonly _app: express.Application;
|
||||
private readonly _server;
|
||||
|
||||
static port = 3000;
|
||||
|
||||
private log = debug("livedj:app");
|
||||
|
||||
constructor() {
|
||||
this._app = express();
|
||||
this._server = http.createServer(this._app);
|
||||
|
||||
this._app.set('views', path.join(__dirname, '../views'));
|
||||
this._app.set('view engine', 'pug');
|
||||
|
||||
this._app.use(morgan('dev'));
|
||||
this._app.use(express.json());
|
||||
this._app.use(express.urlencoded({extended: false}));
|
||||
this._app.use(cookieParser());
|
||||
this._app.use(express.static(path.join(__dirname, "../public")));
|
||||
this._app.use('/web.js', express.static(path.join(__dirname, "../dist/web.bundle.js")));
|
||||
|
||||
|
||||
|
||||
this._app.use( (req, res, next) => {
|
||||
next();
|
||||
} )
|
||||
this._app.use((err, req, res, next) => {
|
||||
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = err;
|
||||
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
this.log("Error " + err);
|
||||
});
|
||||
this.loadRoutes();
|
||||
}
|
||||
|
||||
public loadRoutes( ) : void
|
||||
{
|
||||
this._app.use( "/", require("./routes/index") );
|
||||
this._app.use( "/api/", require("./routes/api") );
|
||||
|
||||
}
|
||||
|
||||
public listen(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._server.on('error', (error) => {
|
||||
if (error.message != 'listen') {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
let bind = 'Port ' + App.port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.name) {
|
||||
case 'EACCES':
|
||||
reject(bind + ' requires elevated privileges');
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
reject(bind + ' is already in use');
|
||||
break;
|
||||
default:
|
||||
reject();
|
||||
}
|
||||
|
||||
});
|
||||
this._server.on('listening', () => {
|
||||
let addr = this._server.address() as AddressInfo;
|
||||
this.log("Listening on " + addr.port);
|
||||
resolve();
|
||||
})
|
||||
this._server.listen(App.port);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
46
src/main.ts
Normal file
46
src/main.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {App} from "./App";
|
||||
import debug from "debug";
|
||||
import path from "path";
|
||||
|
||||
const log = debug("itender:server");
|
||||
|
||||
const app = new App();
|
||||
|
||||
global.appRoot = path.resolve(__dirname);
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
|
||||
});
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
log("Starting...");
|
||||
|
||||
//await Database.connect();
|
||||
|
||||
await app.listen();
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.error("---- ERROR ----");
|
||||
console.error(e);
|
||||
process.exit(-1);
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* tst
|
||||
*/
|
||||
function init(): Promise<void> {
|
||||
|
||||
return new Promise(async resolve => {
|
||||
log("Initializing...");
|
||||
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
14
src/routes/api.ts
Normal file
14
src/routes/api.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import express from "express";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function (req, res, next) {
|
||||
res.render('index');
|
||||
});
|
||||
|
||||
router.get('/status', (req, res) => {
|
||||
res.status(200).json({status: "ok", code: 200});
|
||||
})
|
||||
|
||||
module.exports = router;
|
14
src/routes/index.ts
Normal file
14
src/routes/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import express from "express";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function (req, res, next) {
|
||||
res.render('index');
|
||||
});
|
||||
|
||||
router.get('/status', (req, res) => {
|
||||
res.status(200).json({status: "ok", code: 200});
|
||||
})
|
||||
|
||||
module.exports = router;
|
51
src/web/fs.js
Executable file
51
src/web/fs.js
Executable file
@ -0,0 +1,51 @@
|
||||
|
||||
let queue = document.getElementById("queue-items");
|
||||
let current_name = document.getElementById("current-name");
|
||||
let current_artist = document.getElementById("current-artist");
|
||||
let current_cover = document.getElementById("current-cover");
|
||||
|
||||
function refresh() {
|
||||
httpGetAsync("/api/current-queue", (response, status) => {
|
||||
if(status !== 200)
|
||||
return alert("Anfrage fehlgeschlagen!");
|
||||
queue.innerHTML = "";
|
||||
|
||||
current_name.innerText = String(response.current.name);
|
||||
current_artist.innerText = String(response.current.artist);
|
||||
current_cover.src = response.current.cover;
|
||||
|
||||
let i = 1;
|
||||
for( let e of response.next )
|
||||
{
|
||||
let tr = document.createElement("tr");
|
||||
let nr = document.createElement("td");
|
||||
nr.innerText = String(i);
|
||||
|
||||
let cover = document.createElement("td");
|
||||
let cover_img = document.createElement("img");
|
||||
cover_img.src = e.cover;
|
||||
cover_img.classList.add("cover");
|
||||
cover_img.style.width = "50px";
|
||||
cover_img.style.height = "50px";
|
||||
cover.append(cover_img);
|
||||
|
||||
let artist = document.createElement("td");
|
||||
artist.innerText = e.artist;
|
||||
|
||||
let name = document.createElement("td");
|
||||
name.innerText = e.name;
|
||||
|
||||
tr.append(nr,cover,artist, name);
|
||||
|
||||
queue.append(tr);
|
||||
if( i === 9 )
|
||||
return;
|
||||
i++;
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
refresh();
|
||||
setInterval(refresh, 1000*10);
|
0
src/web/main.ts
Normal file
0
src/web/main.ts
Normal file
87
src/web/push.js
Executable file
87
src/web/push.js
Executable file
@ -0,0 +1,87 @@
|
||||
const search_box = document.getElementById("search")
|
||||
const results = document.getElementById("results")
|
||||
const result_count = document.getElementById("result_count")
|
||||
const search_btn = document.getElementById("search_btn");
|
||||
|
||||
function search()
|
||||
{
|
||||
if( search_box.value.trim().length === 0)
|
||||
{
|
||||
return openModal("Huh?", "Gebe bitte einen Künstler, Titel oder ein Album ein");
|
||||
}
|
||||
search_btn.disabled = true;
|
||||
httpGetAsync("/api/search-song/" + search_box.value, (response, status) => {
|
||||
search_btn.disabled = false;
|
||||
|
||||
|
||||
if ( status === 429 )
|
||||
return openModal("Nicht so schnell!", "Lass Spotify etwas Zeit zwischen deinen Suchanfragen.");
|
||||
else if(status !== 200)
|
||||
return openModal("Oops!", "Etwas ist schiefgelaufen.<br>Versuche es später erneut oder kontaktiere die Verwaltung.");
|
||||
results.innerHTML = "";
|
||||
|
||||
|
||||
let i = 1;
|
||||
for( let e of response )
|
||||
{
|
||||
let tr = document.createElement("tr");
|
||||
tr.onclick = () => add(e.uri);
|
||||
|
||||
let cover = document.createElement("td");
|
||||
let cover_img = document.createElement("img");
|
||||
cover_img.src = e.cover;
|
||||
cover_img.classList.add("cover");
|
||||
cover_img.style.width = "30px";
|
||||
cover_img.style.height = "30px";
|
||||
cover.append(cover_img);
|
||||
|
||||
let artist = document.createElement("td");
|
||||
artist.innerText = e.artist;
|
||||
|
||||
let name = document.createElement("td");
|
||||
name.innerText = e.name;
|
||||
|
||||
tr.append(cover,artist, name);
|
||||
|
||||
results.append(tr);
|
||||
i++;
|
||||
}
|
||||
|
||||
result_count.innerText = i + " Ergebnisse";
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
search_box.addEventListener("keyup", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
function add(uri)
|
||||
{
|
||||
if( Date.now() < last_add+1000) // Anti double click
|
||||
return;
|
||||
|
||||
|
||||
httpPostAsync("/api/push-queue", {uri: uri}, (r, status) => {
|
||||
if( status === 429 )
|
||||
return openModal("Nicht so schnell 👋🛑", "Lass den anderen auch etwas Zeit ⏲️<br>Du darfst erneut in " + Math.round(r.retry_in) + "s");
|
||||
else if ( status === 404 )
|
||||
return openModal("The Party is over!", "Gerade wird keine Musik gespielt, weswegen auch keine Warteschlange existiert :(");
|
||||
else if(status === 200)
|
||||
{
|
||||
results.innerText = "";
|
||||
result_count.innerText = "0 Ergebnisse";
|
||||
search_box.value = "";
|
||||
last_add = Date.now();
|
||||
return openModal("Hinzugefügt! ➕", "Dein Titel wurde der Warteschlange hinzugefügt");
|
||||
}
|
||||
else
|
||||
{
|
||||
return openModal("Oops!", "Etwas ist schiefgelaufen.<br>Versuche es später erneut oder kontaktiere die Verwaltung.");
|
||||
}
|
||||
})
|
||||
|
||||
}
|
64
src/web/request.js
Executable file
64
src/web/request.js
Executable file
@ -0,0 +1,64 @@
|
||||
function httpGetAsync(url, callback) {
|
||||
const xmlHttp = new XMLHttpRequest();
|
||||
|
||||
xmlHttp.onreadystatechange = function () {
|
||||
if (xmlHttp.readyState === 4)
|
||||
{
|
||||
try {
|
||||
callback(JSON.parse(xmlHttp.responseText), xmlHttp.status);
|
||||
} catch( e )
|
||||
{
|
||||
callback({}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlHttp.open("GET", url, true); // true for asynchronous
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
function httpPostAsync(url, data, callback) {
|
||||
const xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = function () {
|
||||
if (xmlHttp.readyState === 4)
|
||||
try {
|
||||
callback(JSON.parse(xmlHttp.responseText), xmlHttp.status);
|
||||
} catch( e )
|
||||
{
|
||||
callback({}, 500);
|
||||
}
|
||||
|
||||
}
|
||||
xmlHttp.open("POST", url, true); // true for asynchronous
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
xmlHttp.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
|
||||
const modal = document.getElementById("myModal");
|
||||
const modal_title = document.getElementById("modal-title");
|
||||
const modal_text = document.getElementById("modal-text");
|
||||
let last_add = 0;
|
||||
|
||||
|
||||
// Get the <span> element that closes the modal
|
||||
const modal_span = document.getElementsByClassName("close")[0];
|
||||
|
||||
|
||||
// When the user clicks on <span> (x), close the modal
|
||||
modal_span.onclick = function() {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.onclick = function(event) {
|
||||
if (event.target === modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(title,text)
|
||||
{
|
||||
modal_title.innerText = title;
|
||||
modal_text.innerHTML = text;
|
||||
modal.style.display = "block"
|
||||
}
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"pretty": true,
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"target": "ES6",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"removeComments": true,
|
||||
"suppressImplicitAnyIndexErrors": false,
|
||||
"resolveJsonModule": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": false,
|
||||
"esModuleInterop": true,
|
||||
"strict": false,
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
},
|
||||
|
||||
"include": [
|
||||
"./src/"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
7
views/auth-post.pug
Normal file
7
views/auth-post.pug
Normal file
@ -0,0 +1,7 @@
|
||||
html
|
||||
head
|
||||
title Auth Pusher
|
||||
body
|
||||
h1 Sending...
|
||||
script(src="/javascripts/request.js")
|
||||
script(src="/javascripts/push.js")
|
6
views/error.pug
Normal file
6
views/error.pug
Normal file
@ -0,0 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
29
views/fullscreen.pug
Executable file
29
views/fullscreen.pug
Executable file
@ -0,0 +1,29 @@
|
||||
html
|
||||
head
|
||||
title Currently Playing and Queue!
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||
link(rel="stylesheet" href="/stylesheets/fs.css")
|
||||
body
|
||||
h1.live-dj #Live DJ
|
||||
div.container
|
||||
div.now
|
||||
h4.title Spielt aktuell
|
||||
img.cover#current-cover(style="width:350px; height:350px" src="https://i.scdn.co/image/ab67616d00001e020500a6a79bd0fd15046ae0c1")
|
||||
p.current-playing-name#current-name Happy Birthday
|
||||
p.current-playing-artist#current-artist Nika
|
||||
div.now
|
||||
h4.title Titel hinzufügen?
|
||||
img(src="https://api.qrserver.com/v1/create-qr-code/?size=350x350&ecc=M&color=90-195-195&bgcolor=14-45-45&data=https://iif.li/party/" style="width: 350px; height: 350px; border: 0; border-radius: 3px;")
|
||||
div.now
|
||||
h4.title Warteschlange
|
||||
table.queue
|
||||
thead
|
||||
tr
|
||||
th Nr.
|
||||
th Cover
|
||||
th Künstler
|
||||
th Song
|
||||
tbody#queue-items
|
||||
script(src="/javascripts/request.js")
|
||||
script(src="/javascripts/fs.js")
|
30
views/index.pug
Normal file
30
views/index.pug
Normal file
@ -0,0 +1,30 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
div#myModal.modal
|
||||
div.modal-content
|
||||
span.close ×
|
||||
h1#modal-title Title
|
||||
p#modal-text Text
|
||||
|
||||
h1 #Live DJ — Queue
|
||||
span(style="color: #54949D;") Suche zuerst nach einem Song, Künstler oder Album und klicke danach einfach auf ein Ergebnis
|
||||
hr
|
||||
div(style="width:100%")
|
||||
input#search(type="text" placeholder="In Spotify suchen" style="width: 65%; margin-right: 5%")
|
||||
button#search_btn(style="width: 25%" onclick="search()") Suchen
|
||||
br
|
||||
br
|
||||
span(style="color: grey")#result_count 0 Ergebnisse
|
||||
table.search
|
||||
thead
|
||||
tr
|
||||
th Cover
|
||||
th Künstler
|
||||
th Song
|
||||
tbody#results
|
||||
br
|
||||
footer.footer
|
||||
p Programmed with ❤️ by git/Tobstr02
|
||||
|
||||
|
11
views/layout.pug
Executable file
11
views/layout.pug
Executable file
@ -0,0 +1,11 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
meta(charset="UTF-8")
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1")
|
||||
body
|
||||
block content
|
||||
|
||||
script(src="/web.js")
|
27
webpack.config.js
Normal file
27
webpack.config.js
Normal file
@ -0,0 +1,27 @@
|
||||
const path = require( "path" );
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
entry: {
|
||||
web: "./src/web/main.ts",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
//exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js"],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].bundle.js",
|
||||
path: path.resolve( __dirname, "dist" ),
|
||||
},
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user