774 changes: 774 additions & 0 deletions mythtv/html/frontend/package-lock.json

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions mythtv/html/frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "mythtv-frontend",
"version": "1.0.0",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^14.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"rollup": "^2.3.4",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^6.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^1.0.0",
"svelte-routing": "^1.4.2"
}
}
7 changes: 7 additions & 0 deletions mythtv/html/frontend/public/frontend.css
20 changes: 20 additions & 0 deletions mythtv/html/frontend/public/frontend.css.map
2 changes: 2 additions & 0 deletions mythtv/html/frontend/public/frontend.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mythtv/html/frontend/public/frontend.js.map

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions mythtv/html/frontend/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />

<title>MythTV Frontend Development Env</title>

<link rel="stylesheet" href="/build/frontend.css" />

<script defer src="/build/frontend.js"></script>
</head>

<body>
<!-- This file is here to allow development using npm start -->
</body>
</html>
75 changes: 75 additions & 0 deletions mythtv/html/frontend/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';

const production = !process.env.ROLLUP_WATCH;

function serve() {
let server;

function toExit() {
if (server) server.kill(0);
}

return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});

process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}

export default {
input: 'src/frontend.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: '../apps/frontend.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file - better for performance
css: css => {
css.write('frontend.css');
}
}),

// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),

// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),

// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),

// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
27 changes: 27 additions & 0 deletions mythtv/html/frontend/src/Frontend.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script>
import { Router, Route } from "svelte-routing";
import Home from "./routes/Home.svelte";
import RemoteControl from "./remote/RemoteControl.svelte";
export let url= "";
</script>

<style>
:global(body) {
font: normal 13px Verdana, Geneva, sans-serif;
background-color: #657b83;
border: 0px;
margin: 0px;
padding: 0px;
max-width: 100%;
overflow-x: hidden;
text-overflow: ellipsis;
}
</style>

<Router url="{url}">
<div>
<Route path="/MythFE/GetRemote" component="{RemoteControl}" />
<Route path="/" component="{Home}" />
</div>
</Router>
7 changes: 7 additions & 0 deletions mythtv/html/frontend/src/frontend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Frontend from './Frontend.svelte';

var app = new Frontend({
target: document.getElementById("app"),
});

export default app;
6 changes: 6 additions & 0 deletions mythtv/html/frontend/src/navbar/Logo.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<style>
logo { color: #26c; }
</style>
<logo>
<img src="/images/mythtv.png" alt="MythTV logo">
</logo>
21 changes: 21 additions & 0 deletions mythtv/html/frontend/src/navbar/Navbar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script>
import Logo from "./Logo.svelte";
</script>

<style>
topbar {
background-color: #000000;
background-image: linear-gradient(to bottom, #222222, #000000 60%);
position: fixed;
top: 0px;
right: 0px;
left: 0px;
height: 34px;
border-bottom: 4px solid #004570;
z-index: 90;
}
</style>

<topbar>
<Logo/>
</topbar>
26 changes: 26 additions & 0 deletions mythtv/html/frontend/src/remote/RemoteButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script>
export let title, action;
export let icon = "";
async function handleClick() {
const query = new URLSearchParams();
query.append("Action", action);
const params = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
body: query
};
const res = fetch('/Frontend/SendAction', params);
}
</script>

<div class="item left waves-effect waves-light btn-large" on:click={handleClick}>
{#if icon === ""}
{title}
{:else}
<i class={icon}></i>
{/if}
</div>
66 changes: 66 additions & 0 deletions mythtv/html/frontend/src/remote/RemoteControl.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script>
import Link from "svelte-routing/src/Link.svelte";
import RemoteButton from "./RemoteButton.svelte";
function buttonNumber(num) {
// Passed 0-8, buttons listed as 1-9
return (num + 1).toString();
}
</script>

<style>
remote {
background-color: #073642;
}
remotelayout {
grid-template-columns: repeat(3, 80px);
grid-template-rows: repeat(4, 58px) 10px 58px 10px repeat(3, 58px) 10px repeat(2, 58px);
width: auto;
display: grid;
}
linktext {
color: #fdf6e3;
}
</style>

<remote>
<!-- Buttons: 0-9, menu, info, back, mute, up, down, left, right, enter, ffwd, rewind-->
<remotelayout>
{#each {length: 9} as _, num}
<RemoteButton title={buttonNumber(num)} action={buttonNumber(num)}/>
{/each}
<div class="item left"></div>
<RemoteButton title="0" action="0"/>
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<RemoteButton title="Menu" action="MENU" icon="fas fa-bars"/>
<RemoteButton title="Mute" action="MUTE" icon="fas fa-volume-mute"/>
<RemoteButton title="Info" action="INFO" icon="fas fa-info"/>
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<RemoteButton title="^" action="UP" icon="fas fa-angle-up"/>
<div class="item left"></div>
<RemoteButton title="<" action="LEFT" icon="fas fa-angle-left"/>
<RemoteButton title="ENTER" action="SELECT" icon="fas fa-check"/>
<RemoteButton title=">" action="RIGHT" icon="fas fa-angle-right"/>
<RemoteButton title="BACK" action="ESCAPE" icon="fas fa-backspace"/>
<RemoteButton title="v" action="DOWN" icon="fas fa-angle-down"/>
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<div class="item left"></div>
<RemoteButton title="<<" action="JUMPRWND" icon="fas fa-angle-double-left"/>
<RemoteButton title="II" action="PAUSE" icon="fas fa-pause"/>
<RemoteButton title=">>" action="JUMPFFWD" icon="fas fa-angle-double-right"/>
<RemoteButton title="SEEKRWND" action="SEEKRWND" icon="fas fa-step-backward"/>
<RemoteButton title="PLAY" action="PLAY" icon="fas fa-play"/>
<RemoteButton title="SEEKFFWD" action="SEEKFFWD" icon="fas fa-step-forward"/>
</remotelayout>
</remote>
<br>
<div class="item left waves-effect waves-light btn-large">
<Link to="/"><linktext>Back to Status</linktext></Link>
</div>
7 changes: 7 additions & 0 deletions mythtv/html/frontend/src/routes/Home.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import Navbar from "../navbar/Navbar.svelte";
import Status from "../status/Status.svelte";
</script>

<Navbar/>
<Status/>
84 changes: 84 additions & 0 deletions mythtv/html/frontend/src/status/Status.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script>
import { onMount } from "svelte";
import { Link } from "svelte-routing";
let fe_status = getStatus();
async function getStatus() {
const params = {
method: 'GET',
headers: {
'Accept': 'application/json'
},
};
const res = await fetch('/Frontend/GetStatus', params);
const data = await res.json();
if (res.ok) {
return data.FrontendStatus;
} else {
console.log ("failed to load status");
}
}
onMount(getStatus);
</script>

<style>
status {
position: absolute;
top: 64px;
right: 0px;
left: 27px;
width: calc(100% - 27px);
height: calc(100% - 64px);
padding: 0px;
box-sizing: border-box;
}
detailbox {
width: 1000px;
border-top: 1px solid #002b36;
border-right: 1px solid #002b36;
border-bottom: 1px solid #002b36;
border-left: 10px solid #002b36;
padding: 10px;
border-radius: 8px 0px 0px 8px;
margin: auto;
margin-bottom: 20px;
color: #002b36;
background-color: #93a1a1;
display: flow-root;
}
linktext {
color: #002b36;
}
</style>

{#await fe_status}
<status>
<p>loading...</p>
</status>
{:then Status}
<status>
<h2 class="center">MythFrontend Status</h2>
<detailbox>
<h4>This frontend</h4>
Name: {Status.Name}
<br>
Version: {Status.Version}
</detailbox>
<detailbox>
<h4>Services</h4>
<div class="item left waves-effect waves-light btn-small">
<Link to="/MythFE/GetRemote"><linktext>Remote Control</linktext></Link>
</div>
</detailbox>
<detailbox>
<h4>Machine Information</h4>
<span>
The current frontend status is: {Status.State.state}
{#if Status.State.state == "WatchingPreRecorded"}
, Title: {Status.State.title}, Subtitle: {Status.State.subtitle}
{/if}
</span>
</detailbox>
</status>
{/await}

123 changes: 14 additions & 109 deletions mythtv/html/frontend_index.qsp
Original file line number Diff line number Diff line change
@@ -1,115 +1,20 @@
<%
"use strict";

import "/js/utility.js"

var frontend = new Frontend();
var status = frontend.GetStatus();
%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>MythFrontend Status</title>
<link rel="stylesheet" href="/css/site.css" type="text/css">
<link rel="stylesheet" href="/css/Status.css" type="text/css">
<script type="text/javascript" src="/3rdParty/jquery/jquery-2.1.4.min.js"></script>
<!-- <script type="text/javascript" src="/js/util.qjs"></script>-->
</head>
<body>

<div id="header">
<div id="header_logo">
<a href="/"><img src="/images/mythtv.png" class="png" width="180" height="64" border="0" alt="MythTV"></a>
</div>
</div>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />

<div id="content">
<title>MythTV Frontend</title>

<h1 class="status">MythFrontend Status</h1>
<div class="content">
<h2 class="status">This Frontend</h2>
Name : <%=status.Name%><br />
Version : <%=status.Version%>
</div>
<!-- <div class="content">
<h2 class="status">MythTV Backends</h2>
Master: scafell&nbsp(<a href="http://192.168.159.2:6544">Status page</a>)
</div>-->
<div class="content">
<h2 class="status">Services</h2>
<a href="MythFE/GetRemote">Remote Control</a>
</div>
<div class="content">
<h2 class="status">Machine Information</h2>
<link rel="stylesheet" href="/3rdParty/materialize/css/materialize.css">
<link rel="stylesheet" href="/3rdParty/fontawesome-free-5.15.1-web/css/fontawesome.css">
<link rel="stylesheet" href="/3rdParty/fontawesome-free-5.15.1-web/css/solid.css">
<link rel="stylesheet" href="/apps/frontend.css">

<%
// This is temporary.
// It will be replaced in the services API with a state enum supplied with
// the translated string
var statusString = qsTr("Unknown");
if (status.State['state'] == "WatchingPreRecorded")
{
statusString = qsTr("Watching Recording");
}
else if (status.State['state'] == "WatchingLiveTV")
{
statusString = qsTr("Watching Live TV");
}
else if (status.State['state'] == "WatchingVideo")
{
statusString = qsTr("Watching Video");
}
else if (status.State['state'] == "WatchingBD")
{
statusString = qsTr("Watching Blu-ray Disc");
}
else if (status.State['state'] == "WatchingDVD")
{
statusString = qsTr("Watching DVD");
}
else if (status.State['state'] == "idle")
{
statusString = qsTr("Idle");
}
%>
<script defer src="/apps/frontend.js"></script>
</head>

<div class="statusInformation">
The current frontend status is: <%=statusString%>
</div>

<!-- <a onClick="toggleShowDiv('statusDetail')">[+] More</a>-->
<div class="extraDetail" id="statusDetail">
<div class="statesTable">

<%
for(var state in status.State)
{
if (!status.State[state].length) // Skip empty fields
continue;
%>
<div class="stateRow">
<div class="stateName"><%=state%></div>
<div class="stateValue"><%=status.State[state]%></div>
</div>
<%
}
%>
</div>
</div>
<!-- <div class="loadstatus">
This machine's load average:
<ul>
<li>1 Minute: 3.64</li>
<li>5 Minutes: 3.47</li>
<li>15 Minutes: 3.06</li>
</ul>
</div>-->
</div>

</div>
</body>
<body>
<div id="app"/>
</body>
</html>

2 changes: 1 addition & 1 deletion mythtv/html/html.pro
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ TEMPLATE = aux
html.path = $${PREFIX}/share/mythtv/html/
html.files = frontend_index.qsp backend_index.qsp overview.html menu.qsp robots.txt favicon.ico
html.files += css images js misc setup samples tv
html.files += 3rdParty xslt video debug
html.files += 3rdParty xslt video debug apps

INSTALLS += html
2 changes: 1 addition & 1 deletion mythtv/programs/mythfrontend/mythfexml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ bool MythFEXML::ProcessRequest( HTTPRequest *pRequest )
GetActionListTest(pRequest);
break;
case MFEXML_GetRemote:
GetRemote(pRequest);
pRequest->FormatFileResponse(m_sSharePath + "html/frontend_index.qsp");
break;
default:
UPnp::FormatErrorResponse(pRequest, UPnPResult_InvalidAction);
Expand Down