diff --git a/.gitignore b/.gitignore index 34fe528..d8ca3b3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +static/index.js +static/index.js.LICENSE.txt # PyInstaller # Usually these files are written by a python script from a template @@ -131,3 +133,7 @@ dmypy.json # SSL certificate files *.pem *.key + + +# Node.js +node_modules/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1295b91..7f6e07b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,16 +23,14 @@ git checkout -b your_username/fix_name Make the changes, test them locally, and them commit. Make sure to comply with the linters. +## Setup the client-side build +``` +yarn install +yarn run build +``` + ## Starting development server ``` python main.py ``` - -## Converting SCSS to CSS - -1. Install [Live Sass Compiler](https://marketplace.visualstudio.com/items?itemName=ritwickdey.live-sass) (Or an equivalent Sass Pre-compiler) -2. Click on the `Watch Sass` button in the lower right of your code editor while in the project directory. -3. The `.scss` file will compile into `.css` while the compiler is watching for any file changes. - -**To learn more about Sass, [click here](https://sass-lang.com/)** diff --git a/package.json b/package.json new file mode 100644 index 0000000..91574b3 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "remote-play", + "devDependencies": { + "@types/hammerjs": "^2.0.41", + "@webpack-cli/generators": "^2.5.0", + "css-loader": "^6.7.1", + "prettier": "^2.7.1", + "sass": "^1.55.0", + "sass-loader": "^13.0.2", + "style-loader": "^3.3.1", + "ts-loader": "^9.4.1", + "tsc": "^2.0.4", + "typescript": "^4.8.4", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" + }, + "scripts": { + "build": "npm run build:prod", + "build:dev": "webpack --mode=development", + "build:prod": "webpack --mode=production --node-env=production", + "watch": "webpack --watch" + }, + "dependencies": { + "hammerjs": "^2.0.8" + }, + "version": "1.0.0", + "description": "Remote-Play uses pyautogui to press keyboard shortcuts to control your media. It uses fastapi to serve a minimal UI to your mobile device." +} diff --git a/static/index.js b/src/index.ts similarity index 54% rename from static/index.js rename to src/index.ts index 8d08f46..730da13 100644 --- a/static/index.js +++ b/src/index.ts @@ -1,12 +1,19 @@ -if (location.protocol === 'https:') { +import * as Hammer from "hammerjs"; + +import "./style.scss"; + +let ws: WebSocket; + +if (location.protocol === "https:") { ws = new WebSocket(`wss://${window.location.host}/ws`); } else { ws = new WebSocket(`ws://${window.location.host}/ws`); } + const start = { x: 0, y: 0 }; const appThemeLocalKey = "darkModeLocal"; -function callEndpoint(endpoint) { +function callEndpoint(endpoint: string) { const xhr = new XMLHttpRequest(); xhr.open("get", endpoint, true); xhr.setRequestHeader( @@ -16,13 +23,13 @@ function callEndpoint(endpoint) { xhr.send(); } -function touchStart(ev) { +function touchStart(ev: TouchEvent) { ev.preventDefault(); start.x = ev.changedTouches[0].clientX; start.y = ev.changedTouches[0].clientY; } -function touchMove(ev) { +function touchMove(ev: TouchEvent) { ev.preventDefault(); const offsetX = ev.changedTouches[0].clientX - start.x; const offsetY = ev.changedTouches[0].clientY - start.y; @@ -34,50 +41,62 @@ function touchMove(ev) { } function configureDisplayTheme() { - const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches; + const prefersDarkScheme = window.matchMedia( + "(prefers-color-scheme: dark)" + ).matches; // if first time, then set theme based on system preference if (localStorage.getItem(appThemeLocalKey) === null) { if (prefersDarkScheme) { localStorage.setItem(appThemeLocalKey, "dark"); - } - else { + } else { localStorage.setItem(appThemeLocalKey, "light"); } } // set theme based on local storage if (localStorage.getItem(appThemeLocalKey) == "dark") { document.documentElement.classList.toggle("dark", true); - document.getElementById("darkMode").checked = true; - } - else { + (document.getElementById("darkMode") as HTMLInputElement).checked = true; + } else { document.documentElement.classList.toggle("dark", false); - document.getElementById("darkMode").checked = false; + (document.getElementById("darkMode") as HTMLInputElement).checked = false; } } -document.addEventListener("DOMContentLoaded", event => { - document.getElementById("touchpad").addEventListener("touchstart", touchStart); - document.getElementById("touchpad").addEventListener("touchmove", touchMove); - const hammer = new Hammer.Manager(document.getElementById("touchpad")); - const vscrollHammer = new Hammer.Manager(document.getElementById("vscrollpad")); - const hscrollHammer = new Hammer.Manager(document.getElementById("hscrollpad")); +document.addEventListener("DOMContentLoaded", (event) => { + (document.getElementById("touchpad") as HTMLElement).addEventListener( + "touchstart", + touchStart + ); + (document.getElementById("touchpad") as HTMLElement).addEventListener( + "touchmove", + touchMove + ); + const hammer = new Hammer.Manager( + document.getElementById("touchpad") as HTMLElement + ); + const vscrollHammer = new Hammer.Manager( + document.getElementById("vscrollpad") as HTMLElement + ); + const hscrollHammer = new Hammer.Manager( + document.getElementById("hscrollpad") as HTMLElement + ); var singleTap = new Hammer.Tap({ event: "tap", - taps: 1 + taps: 1, }); var doubleTap = new Hammer.Tap({ event: "doubletap", - taps: 2 + taps: 2, }); hammer.add([doubleTap, singleTap]); - hammer.on("tap", ev => { + hammer.on("tap", (ev) => { // left mouse button click ws.send(JSON.stringify({ type: "tap" })); }); - hammer.on("doubletap", ev => { + hammer.on("doubletap", (ev) => { // right mouse button click ws.send(JSON.stringify({ type: "doubletap" })); }); @@ -87,52 +106,64 @@ document.addEventListener("DOMContentLoaded", event => { var scrolly = new Hammer.Pan({ event: "vscroll", - threshhold: 50, - direction: Hammer.DIRECTION_VERTICAL + threshold: 50, + direction: Hammer.DIRECTION_VERTICAL, }); var scrollx = new Hammer.Pan({ event: "hscroll", - threshhold: 50, - direction: Hammer.DIRECTION_HORIZONTAL + threshold: 50, + direction: Hammer.DIRECTION_HORIZONTAL, }); vscrollHammer.add(scrolly); hscrollHammer.add(scrollx); - vscrollHammer.on("vscroll", ev => { + vscrollHammer.on("vscroll", (ev) => { // scroll vertically - if (ev.direction == 8) { // direction = up + if (ev.direction == 8) { + // direction = up ws.send(JSON.stringify({ type: "scrolly", y: "up" })); } - if (ev.direction == 16) { // direction = down + if (ev.direction == 16) { + // direction = down ws.send(JSON.stringify({ type: "scrolly", y: "down" })); } }); - hscrollHammer.on("hscroll", ev => { + hscrollHammer.on("hscroll", (ev) => { // scroll horizontally - if (ev.direction == 2) { //direction = left + if (ev.direction == 2) { + //direction = left ws.send(JSON.stringify({ type: "scrollx", x: "left" })); } - if (ev.direction == 4) { // direction = right + if (ev.direction == 4) { + // direction = right ws.send(JSON.stringify({ type: "scrollx", x: "right" })); } }); - var darkSwitch = document.querySelector('input[name=darkMode]'); + const darkSwitch = document.querySelector("input[name=darkMode]"); - darkSwitch.addEventListener('change', function (_event) { + darkSwitch?.addEventListener("change", function (_event) { // toggles darkmode when user taps switch if (localStorage.getItem(appThemeLocalKey) == "dark") { document.documentElement.classList.toggle("dark", false); - document.getElementById("darkMode").checked = false; + (document.getElementById("darkMode") as HTMLInputElement).checked = false; localStorage.setItem(appThemeLocalKey, "light"); - } - else if (localStorage.getItem(appThemeLocalKey) == "light") { + } else if (localStorage.getItem(appThemeLocalKey) == "light") { document.documentElement.classList.toggle("dark", true); - document.getElementById("darkMode").checked = true; + (document.getElementById("darkMode") as HTMLInputElement).checked = true; localStorage.setItem(appThemeLocalKey, "dark"); } }); configureDisplayTheme(); }); + +// moved the callEndPoiny function that is being run from the index markup file +document + .querySelectorAll("main.container button") + .forEach((button: HTMLButtonElement) => { + button.addEventListener("click", (event) => { + callEndpoint(`/${(event.target as HTMLButtonElement).name}`); + }); + }); diff --git a/src/style.scss b/src/style.scss new file mode 100644 index 0000000..4b6c8da --- /dev/null +++ b/src/style.scss @@ -0,0 +1,410 @@ +/*# sourceMappingURL=style.css.map */ +body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + width: 100vw; + height: 100vh; + margin: 0; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-item-align: center; + align-self: center; + overflow-x: hidden; +} +header { + height: 10vh; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + margin: 0; + padding: 0 1rem; + background-color: #234d8d; + font-family: Arial, Helvetica, sans-serif; + color: white; + h1 { + width: 93vw; + letter-spacing: 1px; + display: inline-block; + } +} +main { + height: 80vh; +} +footer { + height: 10vh; + text-align: center; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + background-color: #fafafa; + p { + font-size: 0.9rem; + -ms-flex-item-align: center; + -ms-grid-row-align: center; + align-self: center; + margin: auto; + color: #515151; + font-family: Arial, Helvetica, sans-serif; + } + a { + color: #4877b8; + text-decoration: none; + &:hover { + color: rgba(72, 119, 184, 0.75); + } + } +} +.container { + width: 95vw; + margin: 0.25rem auto; + display: -ms-grid; + display: grid; + -ms-grid-columns: 1fr 1fr; + grid-template-columns: 1fr 1fr; + -ms-grid-rows: 1fr 1fr 1fr 1fr 4fr; + grid-template-rows: 1fr 1fr 1fr 1fr 4fr; +} +.full-width { + -ms-grid-column: 1; + -ms-grid-column-span: 2; + grid-column: 1 / span 2; +} +#wrapper { + -ms-grid-column: 1; + -ms-grid-column-span: 2; + grid-column: 1 / span 2; + color: #535050; + border-radius: 10px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +.touchpad { + background-color: silver; + border: 1px solid lightgray; + float: left; + width: 82%; + color: #535050; + border-top-left-radius: 10px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +.vscrollpad { + background-color: silver; + margin-right: auto; + border: 1px solid lightgray; + color: #535050; + width: 18%; + border-top-right-radius: 10px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +.hscrollpad { + background-color: silver; + border: 1px solid lightgray; + color: #535050; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + height: 50px; + -ms-grid-column: 1; + -ms-grid-column-span: 2; + grid-column: 1 / span 2; + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} +.button { + border-radius: 10px; + border: 2px solid lightgray; + font-size: large; + background-color: white; + margin: 1px; + -webkit-transition: 0.25s ease-in-out; + transition: 0.25s ease-in-out; + &:active { + color: #1d33b1; + background-color: #b2bee2; + } +} +.switch { + position: relative; + display: inline-block; + width: 75px; + height: 30px; + input { + opacity: 0; + width: 0; + height: 0; + } +} +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: 0.5s; + z-index: 1; + &:before { + position: absolute; + content: ""; + height: 22px; + width: 22px; + left: 4px; + bottom: 4px; + background-color: white; + transition: 0.5s; + } +} +input { + &:checked { + + { + .slider { + &:before { + transform: translateX(200%); + } + } + } + } +} +.slider.round { + border-radius: 30px; + &:before { + border-radius: 50%; + } +} +.iconSun { + visibility: visible; + position: absolute; + display: inline-block; + font-size: 18px; + color: orange; + z-index: 2; + left: 60%; + bottom: -2px; +} +.iconMoon { + visibility: hidden; +} +#switchwrap { + width: 75px; + height: 30px; +} +.dark { + body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + width: 100vw; + height: 100vh; + margin: 0; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-item-align: center; + align-self: center; + overflow-x: hidden; + background-color: #181818; + } + header { + height: 10vh; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + margin: 0; + padding: 0 1rem; + background-color: #121212; + font-family: Arial, Helvetica, sans-serif; + color: #e4e4e4; + h1 { + width: 93vw; + letter-spacing: 1px; + display: inline-block; + } + } + main { + height: 80vh; + } + footer { + height: 10vh; + text-align: center; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + background-color: #121212; + p { + font-size: 0.9rem; + -ms-flex-item-align: center; + -ms-grid-row-align: center; + align-self: center; + margin: auto; + color: #e4e4e4; + font-family: Arial, Helvetica, sans-serif; + } + a { + color: #4877b8; + text-decoration: none; + &:hover { + color: rgba(72, 119, 184, 0.75); + } + } + } + .container { + width: 95vw; + margin: 0.25rem auto; + display: -ms-grid; + display: grid; + -ms-grid-columns: 1fr 1fr; + grid-template-columns: 1fr 1fr; + -ms-grid-rows: 1fr 1fr 1fr 1fr 4fr; + grid-template-rows: 1fr 1fr 1fr 1fr 4fr; + } + .full-width { + -ms-grid-column: 1; + -ms-grid-column-span: 2; + grid-column: 1 / span 2; + } + #wrapper { + -ms-grid-column: 1; + -ms-grid-column-span: 2; + grid-column: 1 / span 2; + color: #535050; + border-radius: 10px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + } + .touchpad { + background-color: #363636; + border: 1px solid #5a5a5a; + float: left; + width: 82%; + color: #535050; + border-top-left-radius: 10px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + } + .vscrollpad { + background-color: #363636; + margin-right: auto; + border: 1px solid #5a5a5a; + color: #535050; + width: 18%; + border-top-right-radius: 10px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + } + .hscrollpad { + background-color: #363636; + border: 1px solid #5a5a5a; + color: #535050; + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + height: 50px; + -ms-grid-column: 1; + -ms-grid-column-span: 2; + grid-column: 1 / span 2; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + } + .button { + border-radius: 10px; + border: 2px solid #242424; + font-size: large; + color: #e4e4e4; + background-color: #363636; + margin: 1px; + -webkit-transition: 0.25s ease-in-out; + transition: 0.25s ease-in-out; + &:active { + color: #908888; + background-color: #5a5a5a; + } + } + .switch { + position: relative; + display: inline-block; + width: 75px; + height: 30px; + input { + opacity: 0; + width: 0; + height: 0; + } + } + .slider { + position: absolute; + cursor: pointer; + background-color: #ccc; + transition: 0.5s; + z-index: 1; + &:before { + position: absolute; + content: ""; + height: 22px; + width: 22px; + left: 4px; + bottom: 4px; + background-color: white; + transition: 0.5s; + } + } + input { + &:checked { + + { + .slider { + background-color: #363636; + &:before { + transform: translateX(200%); + } + } + } + } + } + .slider.round { + border-radius: 30px; + &:before { + border-radius: 50%; + } + } + .iconSun { + visibility: hidden; + } + .iconMoon { + visibility: visible; + position: absolute; + display: inline-block; + font-size: 22px; + color: grey; + z-index: 2; + bottom: 1px; + right: 65%; + } + #switchwrap { + width: 75px; + height: 30px; + } +} diff --git a/static/index.html b/static/index.html index d9352e1..99a8a7b 100755 --- a/static/index.html +++ b/static/index.html @@ -7,60 +7,50 @@ name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1" /> - - - - + + +