mirror of
https://github.com/IrosTheBeggar/mStream.git
synced 2025-10-27 07:31:02 +00:00
403 lines
12 KiB
HTML
403 lines
12 KiB
HTML
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
<meta content="yes" name="mobile-web-app-capable">
|
|
<meta content="yes" name="apple-mobile-web-app-capable">
|
|
<meta content="black" name="apple-mobile-web-app-status-bar-style">
|
|
|
|
<link rel="apple-touch-icon" sizes="57x57" href="/public/favicon/apple-icon-57x57.png">
|
|
<link rel="apple-touch-icon" sizes="60x60" href="/public/favicon/apple-icon-60x60.png">
|
|
<link rel="apple-touch-icon" sizes="72x72" href="/public/favicon/apple-icon-72x72.png">
|
|
<link rel="apple-touch-icon" sizes="76x76" href="/public/favicon/apple-icon-76x76.png">
|
|
<link rel="apple-touch-icon" sizes="114x114" href="/public/favicon/apple-icon-114x114.png">
|
|
<link rel="apple-touch-icon" sizes="120x120" href="/public/favicon/apple-icon-120x120.png">
|
|
<link rel="apple-touch-icon" sizes="144x144" href="/public/favicon/apple-icon-144x144.png">
|
|
<link rel="apple-touch-icon" sizes="152x152" href="/public/favicon/apple-icon-152x152.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/public/favicon/apple-icon-180x180.png">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="/public/favicon/android-icon-192x192.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/public/favicon/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="96x96" href="/public/favicon/favicon-96x96.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/public/favicon/favicon-16x16.png">
|
|
<link rel="manifest" href="/public/favicon/manifest.json">
|
|
<meta name="msapplication-TileColor" content="#ffffff">
|
|
<meta name="msapplication-TileImage" content="/public/favicon/ms-icon-144x144.png">
|
|
<meta name="theme-color" content="#ffffff">
|
|
|
|
<title>mStream Remote</title>
|
|
<!-- mStream CSS -->
|
|
<link rel="stylesheet" href="/public/css/remote.css">
|
|
<!-- Pure CSS -->
|
|
<link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" integrity="sha384-CCTZv2q9I9m3UOxRLaJneXrrqKwUNOzZ6NGEUMwHtShDJ+nCoiXJCAgi05KfkLGY"
|
|
crossorigin="anonymous">
|
|
|
|
<!--
|
|
This is the mStream Player stack
|
|
DO NOT Change to order these are loaded in
|
|
-->
|
|
<script src="/public/js/lib/aurora.js"></script>
|
|
<script src="/public/js/lib/flac.js"></script>
|
|
<script src="/public/js/lib/howler.core.js"></script>
|
|
<script src="/public/js/mstream.player.js"></script>
|
|
<!-- Vue JS -->
|
|
<script src="https://unpkg.com/vue/dist/vue.js"></script>
|
|
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
|
|
crossorigin="anonymous"></script>
|
|
|
|
<script src="/public/js/remote.js"></script>
|
|
|
|
<style>
|
|
.remote-controls {
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 100%;
|
|
height: 50px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.previous-button {
|
|
position: relative;
|
|
height: 100%;
|
|
width: 34%;
|
|
background-color: #333333;
|
|
float: left;
|
|
overflow: hidden;
|
|
box-shadow: 5px 0 8px -2px rgba(31, 73, 125, 0.8), -5px 0 8px -2px rgba(31, 73, 125, 0.8);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.play-pause-button {
|
|
height: 100%;
|
|
width: 33%;
|
|
background-color: rgb(102, 132, 178);
|
|
float: left;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.next-border {
|
|
height: 100%;
|
|
padding: 8px;
|
|
}
|
|
|
|
.previous-border {
|
|
height: 100%;
|
|
padding: 8px;
|
|
}
|
|
|
|
.play-pause-border {
|
|
border-left: 5px solid rgb(102, 132, 178);
|
|
border-top: 5px solid rgb(102, 132, 178);
|
|
height: 100%;
|
|
padding: 3px;
|
|
}
|
|
|
|
.next-button {
|
|
height: 100%;
|
|
width: 33%;
|
|
background-color: #333333;
|
|
float: left;
|
|
position: relative;
|
|
overflow: hidden;
|
|
box-shadow: 5px 0 8px -2px rgba(31, 73, 125, 0.8), -5px 0 8px -2px rgba(31, 73, 125, 0.8);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.center {
|
|
top: 50%;
|
|
left: 50%;
|
|
position: absolute;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.login-overlay {
|
|
position: fixed;
|
|
padding: 0;
|
|
margin: 0;
|
|
|
|
top: 0;
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
z-index: 9;
|
|
}
|
|
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity .5s
|
|
}
|
|
|
|
.fade-enter,
|
|
.fade-leave-to {
|
|
opacity: 0
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
var remoteProperties = {
|
|
code: 0,
|
|
guest: false,
|
|
error: false,
|
|
token: false
|
|
};
|
|
|
|
// Auto Focus
|
|
Vue.directive('focus', {
|
|
// When the bound element is inserted into the DOM...
|
|
inserted: function (el) {
|
|
// Focus the element
|
|
el.focus()
|
|
}
|
|
})
|
|
|
|
window.onload = function () {
|
|
$.ajaxPrefilter(function (options) {
|
|
options.beforeSend = function (xhr) {
|
|
xhr.setRequestHeader('x-access-token', remoteProperties.token);
|
|
}
|
|
});
|
|
|
|
// TODO: Handle guest controls
|
|
|
|
|
|
|
|
// Prompt user for code
|
|
// Check code against server
|
|
// Store code
|
|
|
|
// Function to send commands to server
|
|
function sendCommandToServer(command, file = false) {
|
|
var sendData = JSON.stringify({
|
|
code: remoteProperties.code,
|
|
command: command,
|
|
file: file,
|
|
});
|
|
|
|
var request = $.ajax({
|
|
url: "jukebox/push-to-client",
|
|
type: "POST",
|
|
contentType: "application/json",
|
|
dataType: "json",
|
|
data: sendData
|
|
});
|
|
|
|
request.done(function (msg) {
|
|
// var parsedMessage = $.parseJSON(msg);
|
|
// TODO:
|
|
});
|
|
|
|
request.fail(function (jqXHR, textStatus) {
|
|
// TODO: IF you get an access denied error, try re-validating the code. It could be the token expired
|
|
|
|
// TODO: clear remoteProperties if token doesn't exist anymore
|
|
});
|
|
|
|
}
|
|
|
|
$('#next-button').on('click', function () {
|
|
sendCommandToServer('next');
|
|
});
|
|
$('#play-pause-button').on('click', function () {
|
|
sendCommandToServer('playPause');
|
|
});
|
|
$('#previous-button').on('click', function () {
|
|
sendCommandToServer('previous');
|
|
});
|
|
|
|
|
|
var loginPanel = new Vue({
|
|
el: '#login-overlay',
|
|
data: {
|
|
remote: remoteProperties,
|
|
},
|
|
methods: {
|
|
submitCode: function (e) {
|
|
|
|
// Get Code
|
|
var loginCode = $('#login-code').val();
|
|
|
|
// Check Code Against Server
|
|
var request = $.ajax({
|
|
url: "jukebox/does-code-exist",
|
|
type: "POST",
|
|
contentType: "application/json",
|
|
dataType: "json",
|
|
data: JSON.stringify({ code: loginCode })
|
|
});
|
|
|
|
request.done(function (msg) {
|
|
// var parsedMessage = $.parseJSON(msg);
|
|
if (msg.status === true) {
|
|
remoteProperties.error = false;
|
|
remoteProperties.code = loginCode;
|
|
remoteProperties.guest = msg.guestStatus;
|
|
remoteProperties.token = msg.token;
|
|
|
|
// Load up the panel
|
|
MSTREAMAPI.getCurrentDirectoryContents();
|
|
|
|
} else {
|
|
remoteProperties.error = 'Code Not Found';
|
|
|
|
}
|
|
});
|
|
|
|
request.fail(function (jqXHR, textStatus) {
|
|
remoteProperties.error = 'Failed To Connect To Server';
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// Template for data items
|
|
Vue.component('browser-item', {
|
|
template: '<div v-on:click="handleClick($event)" class="browser-item">{{displayText}}</div>',
|
|
props: ['type', 'item'],
|
|
// We need the positionCache to track the currently playing song
|
|
data: function () {
|
|
return {
|
|
// positionCache: MSTREAMPLAYER.positionCache,
|
|
}
|
|
},
|
|
// Methods used by playlist item events
|
|
methods: {
|
|
// On Click
|
|
handleClick: function (event) {
|
|
// get data type
|
|
var thisType = this.type;
|
|
|
|
// do something based on data type
|
|
if (this.type === 'file') {
|
|
// TODO: Send to remote
|
|
sendCommandToServer('addSong', this.item.path);
|
|
|
|
} else if (this.type === 'directory') {
|
|
MSTREAMAPI.goToNextDirectory(this.item.name);
|
|
}
|
|
},
|
|
},
|
|
computed: {
|
|
displayText: function () {
|
|
// Get Type
|
|
// render based on type
|
|
// FIXME: Temp workaround
|
|
return this.item.name;
|
|
}
|
|
}
|
|
});
|
|
|
|
var browserPanel = new Vue({
|
|
el: '#browser-container',
|
|
data: {
|
|
dataList: MSTREAMAPI.dataList,
|
|
currentProperties: MSTREAMAPI.currentProperties,
|
|
fileExplorer: MSTREAMAPI.fileExplorerArray
|
|
},
|
|
computed: {
|
|
titleText: function () {
|
|
// FIXME: Temp workaround
|
|
return 'File Explorer';
|
|
},
|
|
|
|
filepath: function () {
|
|
if (this.fileExplorer.length === 1) {
|
|
return '/';
|
|
}
|
|
|
|
var directoryString = '';
|
|
for (var i = 0; i < this.fileExplorer.length; i++) {
|
|
// Ignore root directory
|
|
if (this.fileExplorer[i].name !== '/') {
|
|
directoryString += "/" + this.fileExplorer[i].name;
|
|
}
|
|
}
|
|
|
|
return directoryString;
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
$('#backbutton').on('click', function () {
|
|
MSTREAMAPI.goBackDirectory();
|
|
});
|
|
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<div v-if="remote.code === 0" id="login-overlay" class="login-overlay">
|
|
<div class="pure-g">
|
|
<div class="pure-u-1-3">
|
|
<p></p>
|
|
</div>
|
|
<div class="pure-u-1-3">
|
|
<img class="login-icon" src="/public/img/mstream-icon.svg">
|
|
<form id="login-form" class="login-form" v-on:submit.prevent="submitCode($event)">
|
|
<div>
|
|
<label>Code</label>
|
|
<input v-focus required type="text" class="form-control" id="login-code">
|
|
</div>
|
|
<button id="login-submit" type="submit" class="btn">Login</button </form>
|
|
<div v-show="remote.error !== false" id="login-alert" class="">{{remote.error}}</div>
|
|
</div>
|
|
<div class="pure-u-1-3">
|
|
<p></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<div class="logo-box">
|
|
<img class="mstream-image" src="/public/img/mstream-logo.svg">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Browser -->
|
|
<div id="browser-container" class="browser-container">
|
|
<!-- Browser Header -->
|
|
<div class="browser-header">
|
|
<div class="browser-header-text">{{titleText}}</div>
|
|
<div class="button-group">
|
|
<div class="repeat"></div>
|
|
<div></div>
|
|
<div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ToolBar -->
|
|
<div class="browser-tools">
|
|
<div id="backbutton" class="back-button">
|
|
<img src="/public/img/back-arrow.svg">
|
|
</div>
|
|
<div class="filepath">{{filepath}}</div>
|
|
</div>
|
|
|
|
<!-- List Area -->
|
|
<div id="browser" class="browser">
|
|
<div v-for="(item, index) in dataList" :item="item" :type="item.type" is="browser-item" :key="index">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="remote-controls">
|
|
<div id="previous-button" class="previous-button">
|
|
<img class="previous-image center" src="/public/img/previous-white.svg">
|
|
</div>
|
|
|
|
<div id="play-pause-button" class="play-pause-button">
|
|
<img id="play-pause-image" class="play-pause-image center" src="/public/img/play-white.svg">
|
|
</div>
|
|
|
|
<div id="next-button" class="next-button">
|
|
<img class="mext-image center" src="/public/img/next-white.svg">
|
|
</div>
|
|
</div>
|
|
|
|
</body> |