├── .github └── workflows │ └── build.yml ├── .gitignore ├── .nvmrc ├── license.md ├── package-lock.json ├── package.json ├── public ├── electron.js ├── icon-white.png ├── icon.ico ├── icon.png ├── index.html ├── index.js ├── manifest.json ├── tray-icon-white.png ├── tray-icon-white@2x.png └── tray-icon.png ├── readme.md ├── renovate.json └── src ├── App.css ├── App.js ├── WebView.js ├── index.css └── index.js /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build/release 2 | 3 | on: push 4 | 5 | jobs: 6 | release: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [macos-latest, windows-latest] 12 | 13 | steps: 14 | - name: Check out Git repository 15 | uses: actions/checkout@master 16 | 17 | - name: Install Node.js, NPM and Yarn 18 | uses: actions/setup-node@v2-beta 19 | with: 20 | node-version: 12 21 | 22 | - name: Build/release Electron app 23 | uses: samuelmeuli/action-electron-builder@v1.6.0 24 | with: 25 | github_token: ${{ secrets.github_token }} 26 | 27 | # If the commit is tagged with a version (e.g. "v1.0.0"), 28 | # release the app after building 29 | release: ${{ startsWith(github.ref, 'refs/tags/v') }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zach Toben 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Zach Toben ", 3 | "description": "An app for viewing plex in a pop up window", 4 | "name": "plex-viewer", 5 | "license": "MIT", 6 | "version": "0.4.13", 7 | "dependencies": { 8 | "ajv": "8.2.0", 9 | "electron-about-window": "1.14.0", 10 | "electron-debug": "3.2.0", 11 | "electron-is-dev": "2.0.0", 12 | "electron-platform": "1.2.0", 13 | "electron-store": "8.0.0", 14 | "electron-window-state": "5.0.3", 15 | "react": "17.0.2", 16 | "react-dom": "17.0.2" 17 | }, 18 | "scripts": { 19 | "build": "react-scripts build", 20 | "dev": "concurrently \"npm run react-start\" \"wait-on http://localhost:3000/ && electron .\"", 21 | "dist": "npm run build && electron-builder -mw", 22 | "pack": "electron-builder --dir", 23 | "postinstall": "electron-builder install-app-deps", 24 | "publish": "build", 25 | "react-start": "cross-env BROWSER=none react-scripts start" 26 | }, 27 | "devDependencies": { 28 | "concurrently": "6.0.2", 29 | "cross-env": "7.0.3", 30 | "electron": "12.0.6", 31 | "electron-builder": "22.11.1", 32 | "react-scripts": "4.0.3", 33 | "typescript": "4.2.4", 34 | "wait-on": "5.3.0" 35 | }, 36 | "homepage": "./", 37 | "main": "public/electron.js", 38 | "build": { 39 | "win": { 40 | "target": "NSIS", 41 | "icon": "build/icon.ico", 42 | "publish": "github" 43 | }, 44 | "mac": { 45 | "icon": "build/icon.ico", 46 | "publish": "github" 47 | } 48 | }, 49 | "browserslist": [ 50 | ">0.2%", 51 | "not dead", 52 | "not ie <= 11", 53 | "not op_mini all" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /public/electron.js: -------------------------------------------------------------------------------- 1 | const {app, globalShortcut, BrowserWindow, Tray, Menu} = require('electron'); 2 | const isDev = require('electron-is-dev'); 3 | const windowStateKeeper = require('electron-window-state'); 4 | const openAboutWindow = require('electron-about-window').default; 5 | const Store = require('electron-store'); 6 | const platform = require('electron-platform'); 7 | const path = require('path'); 8 | 9 | require('electron-debug')(); 10 | 11 | const store = new Store(); 12 | 13 | let mainWindow; 14 | let tray = null; 15 | let mainWindowState = null; 16 | 17 | // Don't show the app in the dock on osx 18 | if (platform.isDarwin) { 19 | app.dock.hide(); 20 | } 21 | 22 | function initStore() { 23 | if (!store.has('plexWebLeftPanelHidden')) { 24 | store.set('plexWebLeftPanelHidden', true); 25 | } 26 | 27 | if (!store.has('simplePlayerMode')) { 28 | store.set('simplePlayerMode', false); 29 | } 30 | 31 | if (!store.has('windowChromeHidden')) { 32 | store.set('windowChromeHidden', false); 33 | } 34 | 35 | if (!store.has('showOnAllWorkspaces')) { 36 | store.set('showOnAllWorkspaces', false); 37 | } 38 | 39 | if (!store.has('autoPause')) { 40 | store.set('autoPause', true); 41 | } 42 | 43 | if (!store.has('positionLocked')) { 44 | store.set('positionLocked', true); 45 | } 46 | 47 | if (!store.has('windowOpacity')) { 48 | store.set('windowOpacity', 1.0); 49 | } 50 | } 51 | 52 | function createWindow() { 53 | if (!mainWindowState) { 54 | mainWindowState = windowStateKeeper({ 55 | defaultWidth: 600, 56 | defaultHeight: 500 57 | }); 58 | 59 | manageWindow(); 60 | } else { 61 | manageWindow(); 62 | } 63 | } 64 | 65 | function setWindowAspectRatio(ratio, direction) { 66 | const [currentWidth, currentHeight] = mainWindow.getSize(); 67 | 68 | if (ratio === '4:3') { 69 | if (direction === 'vertical') { 70 | mainWindow.setSize(currentWidth, Math.round(currentWidth * 3 / 4)); 71 | } 72 | 73 | if (direction === 'horizontal') { 74 | mainWindow.setSize(Math.round(currentHeight * 4 / 3), currentHeight); 75 | } 76 | } 77 | 78 | if (ratio === '16:9') { 79 | if (direction === 'vertical') { 80 | mainWindow.setSize(currentWidth, Math.round(currentWidth * 9 / 16)); 81 | } 82 | 83 | if (direction === 'horizontal') { 84 | mainWindow.setSize(Math.round(currentHeight * 16 / 9), currentHeight); 85 | } 86 | } 87 | } 88 | 89 | function manageWindow() { 90 | mainWindow = new BrowserWindow({ 91 | alwaysOnTop: true, 92 | 'x': mainWindowState.x, 93 | 'y': mainWindowState.y, 94 | 'width': mainWindowState.width, 95 | 'height': mainWindowState.height, 96 | frame: !store.get('windowChromeHidden'), 97 | skipTaskbar: true, // Don't show app in the taskbar on windows 98 | resizable: !store.get('positionLocked'), 99 | movable: !store.get('positionLocked'), 100 | fullscreenable: !store.get('showOnAllWorkspaces'), 101 | visibleOnAllWorkspaces: store.get('showOnAllWorkspaces'), 102 | webPreferences: { 103 | nodeIntegration: true, 104 | webviewTag: true, 105 | enableRemoteModule: true 106 | } 107 | }); 108 | mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`); 109 | mainWindow.setMenu(null); 110 | mainWindowState.manage(mainWindow); 111 | } 112 | 113 | function removeFrame() { 114 | store.set('windowChromeHidden', !store.get('windowChromeHidden')); 115 | 116 | let currentWindowId = mainWindow.id; 117 | 118 | createWindow(); 119 | 120 | BrowserWindow.fromId(currentWindowId).close(); 121 | } 122 | 123 | function toggleWindow() { 124 | if (mainWindow.isVisible()) { 125 | mainWindow.hide(); 126 | if (store.get('autoPause')) { 127 | mainWindow.webContents.send('pause'); 128 | } 129 | } else { 130 | mainWindow.show(); 131 | mainWindow.focus(); 132 | } 133 | } 134 | 135 | function setPositionLocked(isLocked) { 136 | mainWindow.setResizable(!isLocked); 137 | mainWindow.setMovable(!isLocked); 138 | store.set('positionLocked', isLocked); 139 | } 140 | 141 | function setShowOnAllWorkspaces(show) { 142 | mainWindow.setVisibleOnAllWorkspaces(show); 143 | mainWindow.setFullScreenable(!show); 144 | store.set('showOnAllWorkspaces', show); 145 | } 146 | 147 | function setWindowOpacity(opacity) { 148 | store.set('windowOpacity', opacity); 149 | mainWindow.setOpacity(opacity); 150 | } 151 | 152 | function registerShortcuts() { 153 | globalShortcut.register('Shift+Control+X', () => { 154 | toggleWindow(); 155 | }); 156 | 157 | globalShortcut.register('Shift+Control+Z', () => { 158 | removeFrame(); 159 | }); 160 | 161 | globalShortcut.register('medianexttrack', function () { 162 | mainWindow.webContents.send('next'); 163 | }); 164 | 165 | globalShortcut.register('mediaplaypause', function () { 166 | mainWindow.webContents.send('play-pause'); 167 | }); 168 | 169 | globalShortcut.register('mediaprevioustrack', function () { 170 | mainWindow.webContents.send('previous'); 171 | }); 172 | 173 | globalShortcut.register('Shift+Control+Left', function () { 174 | mainWindow.webContents.send('back'); 175 | }); 176 | 177 | globalShortcut.register('Shift+Control+Right', function () { 178 | mainWindow.webContents.send('forward'); 179 | }); 180 | 181 | globalShortcut.register('Shift+Control+>', function () { 182 | mainWindow.webContents.send('next'); 183 | }); 184 | 185 | globalShortcut.register('Shift+Control+<', function () { 186 | mainWindow.webContents.send('previous'); 187 | }); 188 | 189 | globalShortcut.register('Shift+Control+M', function () { 190 | mainWindow.webContents.send('simple-player-mode'); 191 | }); 192 | } 193 | 194 | function buildTray() { 195 | if (platform.isDarwin) { 196 | tray = new Tray(path.join(__dirname, 'tray-icon-white.png')); 197 | } else { 198 | tray = new Tray(path.join(__dirname, 'tray-icon.png')); 199 | } 200 | 201 | tray.setToolTip('Plex Viewer'); 202 | tray.on('double-click', toggleWindow); 203 | 204 | const contextMenu = Menu.buildFromTemplate([ 205 | { 206 | label: 'Aspect Ratio', 207 | submenu: [ 208 | { 209 | label: '4:3 (Horizontal)', 210 | click: () => setWindowAspectRatio('4:3', 'horizontal') 211 | }, 212 | { 213 | label: '4:3 (Vertical)', 214 | click: () => setWindowAspectRatio('4:3', 'vertical') 215 | }, 216 | { 217 | label: '16:9 (Horizontal)', 218 | click: () => setWindowAspectRatio('16:9', 'horizontal') 219 | }, 220 | { 221 | label: '16:9 (Vertical)', 222 | click: () => setWindowAspectRatio('16:9', 'vertical') 223 | } 224 | ] 225 | }, 226 | { 227 | label: 'Window Opacity', 228 | submenu: [.1, .2, .3, .4, .5, .6, .7, .8, .9, 1].map(val => { 229 | return { 230 | label: val.toFixed(1).toString(), 231 | type: 'radio', 232 | checked: store.get('windowOpacity') === val, 233 | click: () => setWindowOpacity(val) 234 | } 235 | }) 236 | }, 237 | { 238 | label: 'Pause on Minimize', 239 | submenu: [ 240 | { 241 | label: 'On', 242 | type: 'radio', 243 | checked: store.get('autoPause'), 244 | click: () => store.set('autoPause', true) 245 | }, 246 | { 247 | label: 'Off', 248 | type: 'radio', 249 | checked: !store.get('autoPause'), 250 | click: () => store.set('autoPause', false) 251 | } 252 | ] 253 | }, 254 | { 255 | label: 'Lock Window', 256 | submenu: [ 257 | { 258 | label: 'On', 259 | type: 'radio', 260 | checked: store.get('positionLocked'), 261 | click: () => setPositionLocked(true) 262 | }, 263 | { 264 | label: 'Off', 265 | type: 'radio', 266 | checked: !store.get('positionLocked'), 267 | click: () => setPositionLocked(false) 268 | } 269 | ] 270 | }, 271 | { 272 | ...platform.isNode && { 273 | label: 'Show on All Workspaces', 274 | submenu: [ 275 | { 276 | label: 'On', 277 | type: 'radio', 278 | checked: store.get('showOnAllWorkspaces'), 279 | click: () => setShowOnAllWorkspaces(true) 280 | }, 281 | { 282 | label: 'Off', 283 | type: 'radio', 284 | checked: !store.get('showOnAllWorkspaces'), 285 | click: () => setShowOnAllWorkspaces(false) 286 | } 287 | ] 288 | } 289 | }, 290 | { 291 | label: 'Toggle Window', 292 | click: toggleWindow 293 | }, 294 | { 295 | label: 'About', 296 | click: () => 297 | openAboutWindow({ 298 | icon_path: path.join(__dirname, 'icon.png'), 299 | product_name: 'Plex Viewer', 300 | bug_report_url: 'https://github.com/ztoben/plex-viewer/issues', 301 | description: 'An electron wrapper for viewing Plex', 302 | license: 'MIT' 303 | }) 304 | } 305 | ]); 306 | 307 | tray.setContextMenu(contextMenu); 308 | } 309 | 310 | app.on('ready', () => { 311 | initStore(); 312 | buildTray(); 313 | registerShortcuts(); 314 | createWindow(); 315 | }); 316 | 317 | app.on('window-all-closed', function () { 318 | if (process.platform !== 'darwin') { 319 | app.quit() 320 | } 321 | }); 322 | 323 | app.on('activate', function () { 324 | if (mainWindow === null) { 325 | createWindow() 326 | } 327 | }); 328 | -------------------------------------------------------------------------------- /public/icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztoben/plex-viewer-deprecated/241eec1fe24b6793d9071cffce653cfa07cd7989/public/icon-white.png -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztoben/plex-viewer-deprecated/241eec1fe24b6793d9071cffce653cfa07cd7989/public/icon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztoben/plex-viewer-deprecated/241eec1fe24b6793d9071cffce653cfa07cd7989/public/icon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Plex Viewer 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | const {ipcRenderer} = window.require('electron'); 2 | const Store = window.require('electron-store'); 3 | const webview = document.getElementById('web-view'); 4 | const store = new Store(); 5 | 6 | function toggleSimplePlayerModeOn() { 7 | // Controls Container 8 | webview.insertCSS('div[class^="PlayerControls-controls"]{ flex-direction: column; }'); 9 | webview.insertCSS('div[class^="PlayerControls-buttonGroup"]{ width: 100%; }'); 10 | webview.insertCSS('div[class^="ControlsContainer-controlsContainer"]{ min-width: 150px; }'); 11 | 12 | // Left Controls 13 | webview.insertCSS('div[class^="PlayerControls-buttonGroupLeft"]{ display: none !important; }'); 14 | webview.insertCSS('div[class^="PlayerControls-balanceLeft"]{ padding: none; }'); 15 | 16 | // Center Controls 17 | webview.insertCSS('div[class^="PlayerControls-buttonGroupCenter"]{ flex: 1; }'); 18 | webview.insertCSS('div[class^="PlayerControls-buttonGroupCenter"]{ margin: 0 !important; }'); 19 | 20 | // Right Controls 21 | webview.insertCSS('div[class^="PlayerControls-buttonGroupRight"]{ flex: 1; }'); 22 | webview.insertCSS('div[class^="PlayerControls-buttonGroupRight"]{ justify-content: center; }'); 23 | webview.insertCSS('div[class^="PlayerControls-buttonGroupRight"]{ margin: 0; }'); 24 | webview.insertCSS('div[class^="PlayerControls-buttonGroupRight"]{ padding: 0; }'); 25 | 26 | // Volume Control 27 | webview.insertCSS('div[class^="PlayerControls-volumeSlider"]{ display: none !important; }'); 28 | 29 | // Search 30 | webview.insertCSS('div[class^="QuickSearch-container"]{ width: calc(100% - 100px); }'); 31 | webview.insertCSS('div[class^="QuickSearch-container"]{ display: block; }'); 32 | 33 | // Navbar 34 | webview.insertCSS('div[class^="NavBar-left"]{ width: 100%; padding-right: 10px; }'); 35 | webview.insertCSS('div[class^="NavBar-right"]{ display: none !important; }'); 36 | } 37 | 38 | function toggleSimplePlayerModeOff() { 39 | // Controls Container 40 | webview.insertCSS('div[class^="PlayerControls-controls"]{ flex-direction: row }'); 41 | webview.insertCSS('div[class^="ControlsContainer-controlsContainer"]{ min-width: 640px; }'); 42 | 43 | // Left Controls 44 | webview.insertCSS('div[class^="PlayerControls-buttonGroupLeft"]{ display: flex !important; }'); 45 | webview.insertCSS('div[class^="PlayerControls-balanceLeft"]{ padding-left: 35px; }'); 46 | 47 | // Volume Control 48 | webview.insertCSS('div[class^="PlayerControls-volumeSlider"]{ display: flex !important; }'); 49 | 50 | // Navbar 51 | webview.insertCSS('div[class^="NavBar-right"]{ display: flex !important; }'); 52 | } 53 | 54 | webview.addEventListener('dom-ready', function () { 55 | if (store.get('simplePlayerMode')) { 56 | toggleSimplePlayerModeOn(); 57 | } 58 | }); 59 | 60 | ipcRenderer.on('simple-player-mode', () => { 61 | const simplePlayerModeEnabled = !store.get('simplePlayerMode'); 62 | store.set('simplePlayerMode', simplePlayerModeEnabled); 63 | 64 | simplePlayerModeEnabled ? toggleSimplePlayerModeOn() : toggleSimplePlayerModeOff(); 65 | }); 66 | 67 | ipcRenderer.on('back', () => { 68 | webview.goBack(); 69 | }); 70 | 71 | ipcRenderer.on('forward', () => { 72 | webview.goForward(); 73 | }); 74 | 75 | ipcRenderer.on('play-pause', () => { 76 | webview.sendInputEvent({type: 'keyDown', keyCode: 'Space'}); 77 | }); 78 | 79 | ipcRenderer.on('pause', () => { 80 | if (webview.isCurrentlyAudible()) { 81 | webview.sendInputEvent({type: 'keyDown', keyCode: 'Space'}); 82 | } 83 | }); 84 | 85 | ipcRenderer.on('next', () => { 86 | webview.sendInputEvent({type: 'keyDown', keyCode: 'Right'}); 87 | }); 88 | 89 | ipcRenderer.on('previous', () => { 90 | webview.sendInputEvent({type: 'keyDown', keyCode: 'Left'}); 91 | }); 92 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Plex Viewer", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "icon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/tray-icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztoben/plex-viewer-deprecated/241eec1fe24b6793d9071cffce653cfa07cd7989/public/tray-icon-white.png -------------------------------------------------------------------------------- /public/tray-icon-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztoben/plex-viewer-deprecated/241eec1fe24b6793d9071cffce653cfa07cd7989/public/tray-icon-white@2x.png -------------------------------------------------------------------------------- /public/tray-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztoben/plex-viewer-deprecated/241eec1fe24b6793d9071cffce653cfa07cd7989/public/tray-icon.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # plex-viewer 2 | 3 | A simple electron app for viewing plex in it's own window. 4 | 5 | screenshot 1 6 | 7 | ## Features 8 | * Keyboard shortcuts 9 | * Sticks on top and remembers window location 10 | * Toggle-able window chrome for a cleaner look 11 | * Media controls for Play/Pause/Skip/Back 12 | * Simple player mode for condensed playback windows 13 | * Quick aspect ratio switching from context menu (4:3 and 16:9) 14 | * Automatically pause content on minimize 15 | * Lock window position 16 | * Change window opacity 17 | * [OSX only] Show across all workspaces 18 | 19 | simple player mode 1 20 | 21 | simple player mode 2 22 | 23 | ## Shortcuts 24 | * Toggle minimize - `Shift + Ctrl + X` 25 | * Toggle window frame (chrome) - `Shift + Ctrl + Z` 26 | * Navigate back - `Shift + Ctrl + ←` 27 | * Navigate forward - `Shift + Ctrl + →` 28 | * Simple player mode - `Shift + Ctrl + M` 29 | 30 | ## Todo 31 | * ~~Persistent settings~~ 32 | * Keyboard shortcut editor/viewer 33 | * ~~Keyboard shortcut for pause/play/mute~~ 34 | * ~~Auto pause on minimize~~ 35 | * ~~Hide/show top navigation~~ 36 | * Custom background colors 37 | * Headphone controls 38 | 39 | 40 | ## Releases 41 | 1. Update the version in the project's package.json file (e.g. 1.2.3) 42 | 2. Commit that change (git commit -am v1.2.3) 43 | 3. Tag your commit (git tag v1.2.3). Make sure your tag name's format is v*.*.*. Your workflow will use this tag to detect when to create a release 44 | 4. Push your changes to GitHub (git push && git push --tags) 45 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | width: 100%; 3 | height: 100%; 4 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import WebView from './WebView'; 3 | import './App.css'; 4 | 5 | class App extends Component { 6 | render() { 7 | return ( 8 |
9 | 10 |
11 | ); 12 | } 13 | } 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /src/WebView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class WebView extends Component { 4 | render() { 5 | const {src} = this.props; 6 | 7 | return ( 8 | 14 | ); 15 | } 16 | } 17 | 18 | export default WebView; 19 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | font-family: sans-serif; 7 | } 8 | 9 | #root, webview, html { 10 | height: 100%; 11 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | --------------------------------------------------------------------------------