DISCORD_BOT_TOKEN=
DISCORD_BOT_CREATE_COMMANDS=
DISCORD_BOT_ENABLE_COMMANDS=
+
+AOS_HOSTNAME=aos.localhorst.tv
--- /dev/null
+<?php
+
+return [
+ 'hostname' => env('AOS_HOSTNAME', 'aos.localhorst.tv'),
+];
"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",
"eslintConfig": {
"env": {
"browser": true,
- "es6": true,
+ "es6": true,
"node": true
},
"extends": [
"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",
* 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'));
+}
--- /dev/null
+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;
--- /dev/null
+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);
--- /dev/null
+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));
--- /dev/null
+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;
/* 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',
/* 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',
--- /dev/null
+<!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>
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() {