]> git.localhorst.tv Git - alttp.git/commitdiff
client side aos base rom select
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 10 May 2022 10:51:51 +0000 (12:51 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 10 May 2022 10:51:51 +0000 (12:51 +0200)
13 files changed:
.env.example
config/aos.php [new file with mode: 0644]
package-lock.json
package.json
resources/js/app.js
resources/js/components/aos/App.js [new file with mode: 0644]
resources/js/components/aos/BaseRomButton.js [new file with mode: 0644]
resources/js/components/aos/Header.js [new file with mode: 0644]
resources/js/helpers/AosBaseRomContext.js [new file with mode: 0644]
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/views/aos.blade.php [new file with mode: 0644]
routes/web.php

index 071eccfe2ebc978959ddb6b9ba54816c21316463..3774044686de13597ff32fb080b4238292b5f498 100644 (file)
@@ -60,3 +60,5 @@ LARASCORD_SCOPE=identify
 DISCORD_BOT_TOKEN=
 DISCORD_BOT_CREATE_COMMANDS=
 DISCORD_BOT_ENABLE_COMMANDS=
+
+AOS_HOSTNAME=aos.localhorst.tv
diff --git a/config/aos.php b/config/aos.php
new file mode 100644 (file)
index 0000000..d093775
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+return [
+       'hostname' => env('AOS_HOSTNAME', 'aos.localhorst.tv'),
+];
index 181e184cef89646d262c387fd25b71eb8f7b324c..bd3412738b1df3f695d569c7da03ab9eb31c1aaa 100644 (file)
@@ -15,6 +15,7 @@
                 "i18next": "^21.6.13",
                 "i18next-browser-languagedetector": "^6.1.3",
                 "laravel-echo": "^1.11.3",
+                "localforage": "^1.10.0",
                 "moment": "^2.29.1",
                 "numeral": "^2.0.6",
                 "pusher-js": "^7.0.6",
                 "imagemin": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
             }
         },
+        "node_modules/immediate": {
+            "version": "3.0.6",
+            "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+            "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+        },
         "node_modules/immutable": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/lie": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+            "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+            "dependencies": {
+                "immediate": "~3.0.5"
+            }
+        },
         "node_modules/lilconfig": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
                 "json5": "lib/cli.js"
             }
         },
+        "node_modules/localforage": {
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+            "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+            "dependencies": {
+                "lie": "3.1.1"
+            }
+        },
         "node_modules/locate-path": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
                 "loader-utils": "^1.1.0"
             }
         },
+        "immediate": {
+            "version": "3.0.6",
+            "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+            "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+        },
         "immutable": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
                 "type-check": "~0.4.0"
             }
         },
+        "lie": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+            "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+            "requires": {
+                "immediate": "~3.0.5"
+            }
+        },
         "lilconfig": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
                 }
             }
         },
+        "localforage": {
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+            "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+            "requires": {
+                "lie": "3.1.1"
+            }
+        },
         "locate-path": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
index d494074a58886ffe2ea22519c0290b0cb3a53bd8..a71398d0b322802b156ddc232ec4b9a875040d6d 100644 (file)
@@ -12,7 +12,7 @@
     "eslintConfig": {
         "env": {
             "browser": true,
-                       "es6": true,
+            "es6": true,
             "node": true
         },
         "extends": [
@@ -81,6 +81,7 @@
         "i18next": "^21.6.13",
         "i18next-browser-languagedetector": "^6.1.3",
         "laravel-echo": "^1.11.3",
+        "localforage": "^1.10.0",
         "moment": "^2.29.1",
         "numeral": "^2.0.6",
         "pusher-js": "^7.0.6",
index 47b77032002b0fb66f064bb26f3502ae191c4bcf..16e3e65f89d0065c0a5b31a0cfdb89a6d17f777f 100644 (file)
@@ -18,6 +18,13 @@ toastr.options.positionClass = 'toast-bottom-right';
  * or customize the JavaScript scaffolding to fit your unique needs.
  */
 
+import AosApp from './components/aos/App';
 import App from './components/App';
 
-ReactDOM.render(<App />, document.getElementById('react-root'));
+if (document.getElementById('aos-root')) {
+       ReactDOM.render(<AosApp />, document.getElementById('aos-root'));
+}
+
+if (document.getElementById('react-root')) {
+       ReactDOM.render(<App />, document.getElementById('react-root'));
+}
diff --git a/resources/js/components/aos/App.js b/resources/js/components/aos/App.js
new file mode 100644 (file)
index 0000000..82f177c
--- /dev/null
@@ -0,0 +1,64 @@
+import axios from 'axios';
+import React, { useEffect, useState } from 'react';
+import { BrowserRouter, Route, Routes } from 'react-router-dom';
+
+import Header from './Header';
+import Front from '../pages/Front';
+import User from '../pages/User';
+import AosBaseRomProvider from '../../helpers/AosBaseRomContext';
+import UserContext from '../../helpers/UserContext';
+
+const App = () => {
+       const [user, setUser] = useState(null);
+
+       const checkAuth = async () => {
+               try {
+                       const response = await axios.get('/api/user');
+                       setUser(response.data);
+               } catch (e) {
+                       setUser(null);
+               }
+       };
+
+       const doLogout = async () => {
+               await axios.post('/logout');
+               await checkAuth();
+       };
+
+       useEffect(() => {
+               let timer = null;
+               axios
+                       .get('/sanctum/csrf-cookie')
+                       .then(() => {
+                               checkAuth();
+                               timer = setInterval(checkAuth, 15 * 60 * 1000);
+                       });
+               return () => {
+                       if (timer) clearInterval(timer);
+               };
+       }, []);
+
+       useEffect(() => {
+               window.Echo.channel('App.Control')
+                       .listen('PleaseRefresh', () => {
+                               location.reload();
+                       });
+               return () => {
+                       window.Echo.leave('App.Control');
+               };
+       }, []);
+
+       return <BrowserRouter>
+               <AosBaseRomProvider>
+                       <UserContext.Provider value={user}>
+                               <Header doLogout={doLogout} />
+                               <Routes>
+                                       <Route path="users/:id" element={<User />} />
+                                       <Route path="*" element={<Front />} />
+                               </Routes>
+                       </UserContext.Provider>
+               </AosBaseRomProvider>
+       </BrowserRouter>;
+};
+
+export default App;
diff --git a/resources/js/components/aos/BaseRomButton.js b/resources/js/components/aos/BaseRomButton.js
new file mode 100644 (file)
index 0000000..fe9d930
--- /dev/null
@@ -0,0 +1,39 @@
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import i18n from '../../i18n';
+
+import { useAosBaseRom } from '../../helpers/AosBaseRomContext';
+
+const BaseRomButton = () => {
+       const { rom, setRom } = useAosBaseRom();
+
+       const handleFile = React.useCallback(async e => {
+               if (e.target.files.length != 1) {
+                       setRom(null);
+               } else {
+                       const buf = await e.target.files[0].arrayBuffer();
+                       setRom(buf);
+               }
+       }, [setRom]);
+
+       if (rom) return null;
+
+       return <span>
+               <input
+                       accept=".gba"
+                       className="d-none"
+                       id="aos.baseRom"
+                       onChange={handleFile}
+                       type="file"
+               />
+               <label htmlFor="aos.baseRom">
+                       <Button as="span" variant="primary">
+                               {i18n.t('aos.setBaseRom')}
+                       </Button>
+               </label>
+       </span>;
+};
+
+export default withTranslation()(BaseRomButton);
diff --git a/resources/js/components/aos/Header.js b/resources/js/components/aos/Header.js
new file mode 100644 (file)
index 0000000..8d9415b
--- /dev/null
@@ -0,0 +1,60 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Container, Nav, Navbar } from 'react-bootstrap';
+import { LinkContainer } from 'react-router-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import BaseRomButton from './BaseRomButton';
+import Icon from '../common/Icon';
+import LanguageSwitcher from '../common/LanguageSwitcher';
+import { getAvatarUrl } from '../../helpers/User';
+import { withUser } from '../../helpers/UserContext';
+import i18n from '../../i18n';
+
+const Header = ({ doLogout, user }) =>
+       <Navbar id="header" bg="dark" variant="dark">
+               <Container fluid>
+                       <LinkContainer to="/">
+                               <Navbar.Brand>
+                                       AoS
+                               </Navbar.Brand>
+                       </LinkContainer>
+                       <Navbar.Text className="ms-auto me-2 button-bar">
+                               <BaseRomButton />
+                               <LanguageSwitcher />
+                       </Navbar.Text>
+                       <Nav>
+                               {user ?
+                                       <>
+                                               <LinkContainer to={`/users/${user.id}`}>
+                                                       <Nav.Link>
+                                                               <img alt="" src={getAvatarUrl(user)} />
+                                                               {user.username}
+                                                               <span className="text-muted">#{user.discriminator}</span>
+                                                       </Nav.Link>
+                                               </LinkContainer>
+                                               <Button
+                                                       onClick={doLogout}
+                                                       title={i18n.t('button.logout')}
+                                                       variant="outline-secondary"
+                                               >
+                                                       <Icon.LOGOUT title="" />
+                                               </Button>
+                                       </>
+                               : null}
+                       </Nav>
+               </Container>
+       </Navbar>
+;
+
+Header.propTypes = {
+       doLogout: PropTypes.func,
+       user: PropTypes.shape({
+               avatar: PropTypes.string,
+               discriminator: PropTypes.string,
+               id: PropTypes.string,
+               username: PropTypes.string,
+       }),
+};
+
+export default withTranslation()(withUser(Header));
diff --git a/resources/js/helpers/AosBaseRomContext.js b/resources/js/helpers/AosBaseRomContext.js
new file mode 100644 (file)
index 0000000..f187c73
--- /dev/null
@@ -0,0 +1,52 @@
+import CRC32 from 'crc-32';
+import localforage from 'localforage';
+import PropTypes from 'prop-types';
+import React from 'react';
+import toastr from 'toastr';
+
+import i18n from '../i18n';
+
+const AosBaseRomContext = React.createContext(null);
+
+const AosBaseRomProvider = ({ children }) => {
+       const [rom, setRom] = React.useState(null);
+
+       const setRomCallback = React.useCallback(buffer => {
+               if (buffer) {
+                       const crc = CRC32.buf(new Uint8Array(buffer));
+                       if (crc === 0x35536183) {
+                               setRom(buffer);
+                               localforage.setItem('aosBaseRom', buffer);
+                               toastr.success(i18n.t('aos.baseRomSet'));
+                       } else {
+                               toastr.error(i18n.t('aos.baseRomInvalid'));
+                       }
+               } else {
+                       setRom(null);
+                       localforage.removeItem('aosBaseRom');
+                       toastr.success(i18n.t('aos.baseRomRemoved'));
+               }
+       }, [setRom]);
+
+       React.useEffect(async () => {
+               const stored = await localforage.getItem('aosBaseRom');
+               if (stored) {
+                       const crc = CRC32.buf(new Uint8Array(stored));
+                       if (crc == 0x35536183) {
+                               setRom(stored);
+                       }
+               }
+       }, []);
+
+       return <AosBaseRomContext.Provider value={{ rom, setRom: setRomCallback }}>
+               {children}
+       </AosBaseRomContext.Provider>;
+};
+
+AosBaseRomProvider.propTypes = {
+       children: PropTypes.node,
+};
+
+export const useAosBaseRom = () => React.useContext(AosBaseRomContext);
+
+export default AosBaseRomProvider;
index 331346e69424bfe2ebb665a1d87099ce657180e4..dcc3d68d96a50bffdf10c5a1b69c227be31cdfb4 100644 (file)
@@ -1,6 +1,12 @@
 /* eslint-disable max-len */
 export default {
        translation: {
+               aos: {
+                       baseRomInvalid: 'CRC32 Check fehlgeschlagen (brauche 35:53:61:83). Falsche ROM Datei?',
+                       baseRomRemoved: 'Base ROM entfernt.',
+                       baseRomSet: 'Base ROM gespeichert.',
+                       setBaseRom: 'Base ROM auswählen',
+               },
                applications: {
                        accept: 'Annehmen',
                        acceptError: 'Fehler beim Annehmen',
index de9b30379c0732ef064e3e197cc89504134b5d82..5cc5ed1f0b88252256fc8b9f96d49ed88cc0707d 100644 (file)
@@ -1,6 +1,12 @@
 /* eslint-disable max-len */
 export default {
        translation: {
+               aos: {
+                       baseRomInvalid: 'CRC32 mismatch (need 35:53:61:83). Wrong ROM file?',
+                       baseRomRemoved: 'Base ROM removed.',
+                       baseRomSet: 'Base ROM set.',
+                       setBaseRom: 'Set base ROM',
+               },
                applications: {
                        accept: 'Accept',
                        acceptError: 'Error accepting',
diff --git a/resources/views/aos.blade.php b/resources/views/aos.blade.php
new file mode 100644 (file)
index 0000000..37bb874
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
+       <head>
+               <meta charset="utf-8">
+               <meta name="viewport" content="width=device-width, initial-scale=1">
+
+               <title>AoS</title>
+               <link rel="icon" href="{{ URL::asset('favicon.ico') }}" type="image/x-icon">
+
+               <script src="{{ mix('js/manifest.js') }}" defer></script>
+               <script src="{{ mix('js/vendor.js') }}" defer></script>
+               <script src="{{ mix('js/app.js') }}" defer></script>
+
+               <link href="{{ mix('css/app.css') }}" rel="stylesheet">
+       </head>
+       <body>
+               <div id="aos-root" class="title m-b-md">
+               </div>
+       </body>
+</html>
index 450d21f5ab5b2dcc50ca20cfa5c2dc58dc8ba74a..375962dd7a05b47ee449a3f3049fad36df2bafba 100644 (file)
@@ -17,6 +17,10 @@ use Illuminate\Support\Facades\Route;
 
 Route::get('/sitemap.xml', [SitemapXmlController::class, 'index']);
 
+Route::domain(config('aos.hostname'))->group(function() {
+       Route::view('/{path?}', 'aos')->where('path', '.*');
+});
+
 Route::view('/{path?}', 'app')->where('path', '.*');
 
 Route::group(['prefix' => config('larascord.prefix'), 'middleware' => ['web']], function() {