First commit

This commit is contained in:
Daniel Ledda
2020-05-10 15:29:31 +02:00
commit 7cb8a03c24
48 changed files with 9990 additions and 0 deletions

3
.babelrc Executable file
View File

@@ -0,0 +1,3 @@
{
"presets": ["@babel/env", "@babel/preset-react"]
}

2
.gitignore vendored Executable file
View File

@@ -0,0 +1,2 @@
node_modules
dist

2
.idea/.gitignore generated vendored Executable file
View File

@@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

7
.idea/dictionaries/ledda.xml generated Executable file
View File

@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="ledda">
<words>
<w>kniffel</w>
</words>
</dictionary>
</component>

6
.idea/inspectionProfiles/Project_Default.xml generated Executable file
View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

85
.idea/jsLinters/jshint.xml generated Executable file
View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JSHintConfiguration" version="2.9.5" use-config-file="false">
<option asi="false" />
<option bitwise="true" />
<option boss="false" />
<option browser="true" />
<option browserify="false" />
<option camelcase="false" />
<option couch="false" />
<option curly="true" />
<option debug="false" />
<option devel="false" />
<option dojo="false" />
<option elision="false" />
<option enforceall="false" />
<option eqeqeq="true" />
<option eqnull="false" />
<option es3="false" />
<option es5="false" />
<option esnext="false" />
<option evil="false" />
<option expr="false" />
<option forin="true" />
<option freeze="false" />
<option funcscope="false" />
<option futurehostile="false" />
<option gcl="false" />
<option globalstrict="false" />
<option immed="false" />
<option iterator="false" />
<option jasmine="false" />
<option jquery="false" />
<option lastsemic="false" />
<option latedef="false" />
<option laxbreak="false" />
<option laxcomma="false" />
<option loopfunc="false" />
<option maxerr="50" />
<option mocha="false" />
<option module="true" />
<option mootools="false" />
<option moz="false" />
<option multistr="false" />
<option newcap="false" />
<option noarg="true" />
<option nocomma="false" />
<option node="false" />
<option noempty="true" />
<option nomen="false" />
<option nonbsp="false" />
<option nonew="true" />
<option nonstandard="false" />
<option notypeof="false" />
<option noyield="false" />
<option onevar="false" />
<option passfail="false" />
<option phantom="false" />
<option plusplus="false" />
<option proto="false" />
<option prototypejs="false" />
<option qunit="false" />
<option quotmark="false" />
<option rhino="false" />
<option scripturl="false" />
<option shadow="false" />
<option shelljs="false" />
<option singleGroups="false" />
<option smarttabs="false" />
<option strict="true" />
<option sub="false" />
<option supernew="false" />
<option trailing="false" />
<option typed="false" />
<option undef="true" />
<option unused="false" />
<option validthis="false" />
<option varstmt="false" />
<option white="false" />
<option withstmt="false" />
<option worker="false" />
<option wsh="false" />
<option yui="false" />
</component>
</project>

10
.idea/jsLinters/jslint.xml generated Executable file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JSLintConfiguration">
<option browser="true" />
<option es6="true" />
<option maxerr="50" />
<option maxlen="80" />
<option node="true" />
</component>
</project>

12
.idea/kadi_frontend.iml generated Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

10
.idea/kniffel.iml generated Executable file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="kadi_backend" />
<orderEntry type="module" module-name="kadi_frontend" />
</component>
</module>

12
.idea/kniffel_react_board.iml generated Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/misc.xml generated Executable file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="JSX" />
</component>
</project>

10
.idea/modules.xml generated Executable file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/../kadi_frontend/.idea/kadi_frontend.iml" filepath="$PROJECT_DIR$/../kadi_frontend/.idea/kadi_frontend.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/kniffel.iml" filepath="$PROJECT_DIR$/.idea/kniffel.iml" />
<module fileurl="file://$PROJECT_DIR$/../../expressjs/kniffel_backend/.idea/kniffel_backend.iml" filepath="$PROJECT_DIR$/../../expressjs/kniffel_backend/.idea/kniffel_backend.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Executable file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

8015
package-lock.json generated Executable file

File diff suppressed because it is too large Load Diff

46
package.json Executable file
View File

@@ -0,0 +1,46 @@
{
"name": "kadi-frontend",
"version": "1.0.0",
"homepage": "/kadi",
"proxy": "http://localhost:3000",
"license": "ISC",
"author": "Daniel Ledda",
"scripts": {
"build": "webpack --mode development",
"postbuild": "rsync -avu --delete dist/ ../kadi_backend/dist/frontend/static",
"start": "webpack-dev-server --mode development",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"axios": "^0.19.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.1.2",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^0.88.2"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"@types/node": "^13.11.1",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.7",
"@types/react-router-dom": "^5.1.5",
"ignore-loader": "^0.1.2",
"font-loader": "^0.1.2",
"babel-loader": "^8.1.0",
"css-loader": "^3.5.3",
"file-loader": "^6.0.0",
"react-hot-loader": "^4.12.21",
"style-loader": "^1.2.1",
"ts-loader": "^7.0.3",
"tslint": "^6.1.1",
"tslint-react": "^4.2.0",
"typescript": "^3.8.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
}
}

22
public/index.html Executable file
View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Score dice games with K A D I"
/>
<link rel="manifest" href="manifest.json" />
<title>K A D I - Digital Dice</title>
</head>
<body>
<div id="root"></div>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<script src="../dist/bundle.js"></script>
</body>
</html>

BIN
public/static/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

25
public/static/manifest.json Executable file
View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/static/robots.txt Executable file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

9
src/App.test.js Executable file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

116
src/App.tsx Executable file
View File

@@ -0,0 +1,116 @@
import React, {ReactNode} from "react";
import {BrowserRouter as Router, Link, Route} from "react-router-dom";
import {Redirect, Switch} from "react-router";
import {IntlStrings} from "./static/strings";
import {PageId, SupportedLang} from "./enums";
import {pageComponentFromId} from "./pageListings";
import KadiPage from "./Components/KadiPage";
import HomePage from "./Components/HomePage";
import {SERVER_BASE_NAME} from "./index";
import axios from "axios";
import UserContext, {IUserContext} from "./Contexts/UserContext";
import LocaleContext, {ILocaleContext} from "./Contexts/LocaleContext";
interface AppState {
userContext: IUserContext;
localeContext: ILocaleContext;
}
interface AppProps {}
class App extends React.Component<AppProps, AppState> {
private readonly updateUserContext: (username: string, loggedIn: boolean) => void;
private readonly changeLang: (lang: SupportedLang) => void;
constructor(props: AppProps) {
super(props);
this.updateUserContext = (username, loggedIn) => {
this.setState({userContext: {
username: username,
loggedIn: loggedIn,
updateUserContext: this.updateUserContext
}});
};
this.changeLang = (lang: SupportedLang) => {
this.setState({localeContext: {
strings: IntlStrings[lang],
currentLang: lang,
changeLang: this.changeLang
}});
};
this.state = {
userContext: {
username: "",
loggedIn: false,
updateUserContext: this.updateUserContext,
},
localeContext: {
currentLang: SupportedLang.gb,
strings: IntlStrings[SupportedLang.gb],
changeLang: this.changeLang,
}
};
axios.get("/api/user", {baseURL: SERVER_BASE_NAME})
.then((res) => {
const data = res.data as any;
if (data.loggedIn) {
this.updateUserContext(data.username, true);
}
else {
this.updateUserContext("", false);
}
})
.catch(err => console.log(err));
}
render(): ReactNode {
return (
<UserContext.Provider value={this.state.userContext}>
<LocaleContext.Provider value={this.state.localeContext}>
<Router basename={SERVER_BASE_NAME}>
<Route exact={true} path={"/"}>
<KadiPage activePage={PageId.home}>
<HomePage/>
</KadiPage>
</Route>
<KadiPageRoute pageId={PageId.history}/>
<KadiPageRoute pageId={PageId.friends}/>
<KadiPageRoute pageId={PageId.stats}/>
<KadiPageRoute pageId={PageId.profile}/>
<KadiPageRoute pageId={PageId.rulesets}/>
<Route path={"/"}>
<Redirect
to={{
pathname: "/",
}}
/>
</Route>
</Router>
</LocaleContext.Provider>
</UserContext.Provider>
);
}
}
interface KadiPageRouteProps {
pageId: PageId
}
const KadiPageRoute: React.FunctionComponent<KadiPageRouteProps> = (props: KadiPageRouteProps) => {
const {pageId} = props;
const PageComponent = pageComponentFromId[pageId];
return (
<Route path={"/" + pageId}>
<KadiPage activePage={pageId}>
<PageComponent/>
</KadiPage>
</Route>
);
};
export default App;

27
src/Components/FriendsPage.tsx Executable file
View File

@@ -0,0 +1,27 @@
import React, {ReactElement} from "react";
import KadiPage from "../Components/KadiPage";
import {Header} from "semantic-ui-react";
interface FriendsPageProps {}
interface FriendsPageState {
}
class FriendsPage extends React.Component<FriendsPageProps, FriendsPageState> {
constructor(props: FriendsPageProps) {
super(props);
this.state = {
};
}
render(): ReactElement {
return (
<Header>
My Friends
</Header>
);
}
}
export default FriendsPage;

53
src/Components/HistoryPage.tsx Executable file
View File

@@ -0,0 +1,53 @@
import React, {ReactElement} from "react";
import {Header, List, ListItem} from "semantic-ui-react";
import axios from "axios";
import {SERVER_BASE_NAME} from "../index";
interface HistoryPageProps {
}
interface HistoryPageState {
loadingGames: boolean;
gameListings: any[];
}
class HistoryPage extends React.Component<HistoryPageProps, HistoryPageState> {
constructor(props: HistoryPageProps) {
super(props);
this.state = {
loadingGames: true,
gameListings: [],
};
}
componentDidMount(): void {
axios.get(SERVER_BASE_NAME + "/api/games/")
.then(response => this.setState({gameListings: response.data.games}))
.catch(error => this.handleError(error))
.finally(() => this.setState({ loadingGames: false }));
}
handleError = (error: any) => void {
};
render(): ReactElement {
return (
<>
<Header size={"huge"}>
History
</Header>
<List bulleted={true}>
{
this.state.gameListings.map(listing => {
return <ListItem key={listing.createdAt}>Game played on: {listing.createdAt}</ListItem>;
})
}
</List>
</>
);
}
}
export default HistoryPage;

27
src/Components/HomePage.tsx Executable file
View File

@@ -0,0 +1,27 @@
import React, {ReactElement} from "react";
import KadiPage from "../Components/KadiPage";
import {Header} from "semantic-ui-react";
interface HomePageProps {}
interface HomePageState {
}
class HomePage extends React.Component<HomePageProps, HomePageState> {
constructor(props: HomePageProps) {
super(props);
this.state = {
};
}
render(): ReactElement {
return (
<Header>
Home
</Header>
);
}
}
export default HomePage;

36
src/Components/KadiPage.tsx Executable file
View File

@@ -0,0 +1,36 @@
import React, {ReactElement} from "react";
import {Segment} from "semantic-ui-react";
import "../static/css/site.css";
import KadiSidebarNav from "../Components/KadiSidebarNav";
import MainPageContent from "../Components/MainPageContent";
import {PageId} from "../enums";
interface KadiPageProps {
activePage: PageId;
}
interface KadiPageState {
}
class KadiPage extends React.Component<KadiPageProps, KadiPageState> {
constructor(props: KadiPageProps) {
super(props);
this.state = {
};
}
render(): ReactElement {
const {children, activePage} = this.props;
return (
<>
<KadiSidebarNav activeItem={activePage}/>
<MainPageContent>
{children}
</MainPageContent>
</>
);
}
}
export default KadiPage;

View File

@@ -0,0 +1,97 @@
import {Header, HeaderContent, Icon, Image, Menu, Segment, Sidebar, SidebarPusher} from "semantic-ui-react";
import logo from "../static/images/kadi.png";
import React from "react";
import LocaleContext from "../Contexts/LocaleContext";
import {Link} from "react-router-dom";
import {PageId} from "../enums";
import {SERVER_BASE_NAME} from "../index";
interface KadiSidebarNavProps {
activeItem: PageId;
}
const KadiSidebarNav: React.FunctionComponent<KadiSidebarNavProps> = (props) => {
const Locale = React.useContext(LocaleContext).strings;
const {activeItem} = props;
return (
<Menu
borderless={true}
vertical={true}
stackable={true}
fixed={"left"}
inverted={true}
className={"kadiSidebarNav"}
>
<Link to={"/"}>
<Menu.Item>
<Header inverted={true} size={"huge"}>
<Image src={logo} size={"tiny"} spaced={true} />
<HeaderContent>
<span className={"brandname"}>K&nbsp;&nbsp;A&nbsp;&nbsp;D&nbsp;&nbsp;I</span>
</HeaderContent>
</Header>
</Menu.Item>
</Link>
<Menu.Item
as={"a"}
icon={true}
href={SERVER_BASE_NAME + "/game"}
>
<Icon name={"game"} />
{Locale.menu.playTab}
</Menu.Item>
<Link to={"/" + PageId.profile}>
<Menu.Item
as={"a"}
icon={true}
active={activeItem === PageId.profile}
>
<Icon name={"user circle"} />
{Locale.menu.profileTab}
</Menu.Item>
</Link>
<Link to={"/" + PageId.stats}>
<Menu.Item
as={"a"}
icon={true}
active={activeItem === PageId.stats}
>
<Icon name={"chart pie"} />
{Locale.menu.statsTab}
</Menu.Item>
</Link>
<Link to={"/" + PageId.rulesets}>
<Menu.Item
as={"a"}
icon={true}
active={activeItem === PageId.rulesets}
>
<Icon name={"book"} />
{Locale.menu.rulesetsTab}
</Menu.Item>
</Link>
<Link to={"/" + PageId.friends}>
<Menu.Item
as={"a"}
icon={true}
active={activeItem === PageId.friends}
>
<Icon name={"group"} />
{Locale.menu.friendsTab}
</Menu.Item>
</Link>
<Link to={"/" + PageId.history}>
<Menu.Item
as={"a"}
icon={true}
active={activeItem === PageId.history}
>
<Icon name={"history"} />
{Locale.menu.historyTab}
</Menu.Item>
</Link>
</Menu>
);
};
export default KadiSidebarNav;

View File

@@ -0,0 +1,90 @@
import React, {ReactElement, SyntheticEvent} from "react";
import {Dropdown, DropdownItemProps, DropdownProps, Flag, Icon, Menu} from "semantic-ui-react";
import {SERVER_BASE_NAME} from "../index";
import LocaleContext from "../Contexts/LocaleContext";
import {LanguageNames} from "../static/strings";
import {IUserContext} from "../Contexts/UserContext";
import {SupportedLang} from "../enums";
interface KadiTopMenuBarProps {
user: IUserContext;
}
interface KadiTopMenuBarState {
currentLangSelection: SupportedLang;
}
class KadiTopMenuBar extends React.Component<KadiTopMenuBarProps, KadiTopMenuBarState> {
private readonly languageDropdowns: DropdownItemProps[];
private changeLanguageGlobally: (newLang: SupportedLang) => void;
constructor(props: KadiTopMenuBarProps) {
super(props);
this.state = {
currentLangSelection: SupportedLang.gb,
};
this.changeLanguageGlobally = () => {};
this.languageDropdowns = [];
for (const lang in LanguageNames) {
this.languageDropdowns.push(
{ key: lang, value: lang, flag: lang, text: LanguageNames[lang as SupportedLang] }
);
}
}
componentDidMount(): void {
this.changeLanguageGlobally = this.context.changeLang;
}
handleLanguageChange: (e: SyntheticEvent, data: DropdownProps) => void = (event, data) => {
const lang = data.value as SupportedLang;
this.setState({currentLangSelection: lang});
this.changeLanguageGlobally(lang);
};
render(): ReactElement {
const {loggedIn, username} = this.props.user;
const Locale = this.context.strings;
return (
<Menu
secondary={true}
pointing={true}
attached={"top"}
>
<Menu.Menu
position={"right"}
>
{ loggedIn && (
<Menu.Item>
{Locale.menu.userWelcome + username + "!"}
</Menu.Item>
)}
<Menu.Item
as={"a"}
href={SERVER_BASE_NAME + "/account/" + (loggedIn ? "logout" : "login")}
>
<Icon spaced={true} name={loggedIn ? "sign out" : "sign in"} />
{loggedIn ? Locale.menu.logoutButton : Locale.menu.loginButton}
</Menu.Item>
<Menu.Item>
<Dropdown
trigger={(
<span>
<Flag name={this.state.currentLangSelection} />
{LanguageNames[this.state.currentLangSelection]}
</span>
)}
options={this.languageDropdowns}
onChange={this.handleLanguageChange}
/>
</Menu.Item>
</Menu.Menu>
</Menu>
);
}
}
KadiTopMenuBar.contextType = LocaleContext;
export default KadiTopMenuBar;

View File

@@ -0,0 +1,29 @@
import {Container, Segment} from "semantic-ui-react";
import React from "react";
import KadiTopMenuBar from "./KadiTopMenuBar";
import UserContext from "../Contexts/UserContext";
interface KadiPageMainContentProps {
}
const KadiPageMainContent: React.FunctionComponent<KadiPageMainContentProps> = (props) => {
const {children} = props;
return (
<div className={"mainPageContent"}>
<UserContext.Consumer>
{ user => (
<KadiTopMenuBar user={user}/>
)}
</UserContext.Consumer>
<Container
className={"mainPageContentContainer"}
>
<Segment>
{children}
</Segment>
</Container>
</div>
);
};
export default KadiPageMainContent;

27
src/Components/ProfilePage.tsx Executable file
View File

@@ -0,0 +1,27 @@
import React, {ReactElement} from "react";
import KadiPage from "../Components/KadiPage";
import {Header} from "semantic-ui-react";
interface ProfilePageProps {}
interface ProfilePageState {
}
class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
constructor(props: ProfilePageProps) {
super(props);
this.state = {
};
}
render(): ReactElement {
return (
<Header>
My Profile
</Header>
);
}
}
export default ProfilePage;

27
src/Components/RulesetsPage.tsx Executable file
View File

@@ -0,0 +1,27 @@
import React, {ReactElement} from "react";
import KadiPage from "../Components/KadiPage";
import {Header} from "semantic-ui-react";
interface RulesetsPageProps {}
interface RulesetsPageState {
}
class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState> {
constructor(props: RulesetsPageProps) {
super(props);
this.state = {
};
}
render(): ReactElement {
return (
<Header>
My Rulesets
</Header>
);
}
}
export default RulesetsPage;

28
src/Components/StatsPage.tsx Executable file
View File

@@ -0,0 +1,28 @@
import React, {ReactNode} from "react";
import {BrowserRouter as Router, Link, Route} from "react-router-dom";
import {Header} from "semantic-ui-react";
import KadiPage from "../Components/KadiPage";
interface StatsPageProps {}
interface StatsPageState {
}
class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
constructor(props: StatsPageProps) {
super(props);
this.state = {
};
}
render(): ReactNode {
return (
<Header>
My Stats
</Header>
);
}
}
export default StatsPage;

19
src/Contexts/LocaleContext.ts Executable file
View File

@@ -0,0 +1,19 @@
import {SupportedLang} from "../enums";
import React from "react";
import {IntlStrings} from "../static/strings";
export interface ILocaleContext {
currentLang: SupportedLang;
strings: any;
changeLang: (lang: SupportedLang) => void;
}
export const localeDefaultVal: ILocaleContext = {
currentLang: SupportedLang.gb,
strings: IntlStrings[SupportedLang.gb as SupportedLang],
changeLang: () => {},
};
const LocaleContext = React.createContext(localeDefaultVal);
export default LocaleContext;

17
src/Contexts/UserContext.ts Executable file
View File

@@ -0,0 +1,17 @@
import React from "react";
export interface IUserContext {
username: string;
loggedIn: boolean;
updateUserContext: (username: string, loggedIn: boolean) => void;
}
const userDefaultVal = {
loggedIn: false,
username: "",
updateUserContext: () => {},
} as IUserContext;
const UserContext = React.createContext(userDefaultVal);
export default UserContext;

14
src/enums.ts Executable file
View File

@@ -0,0 +1,14 @@
export enum SupportedLang {
gb = "gb",
de = "de",
it = "it",
}
export enum PageId {
profile = "profile",
rulesets = "rulesets",
friends = "friends",
stats = "stats",
home = "home",
history = "history",
}

16
src/filetypes.d.ts vendored Executable file
View File

@@ -0,0 +1,16 @@
declare module '*.jpg' {
const value: any;
export = value;
}
declare module '*.gif' {
const value: any;
export = value;
}
declare module '*.png' {
const value: any;
export = value;
}
declare module '*.css' {
const value: any;
export = value;
}

20
src/index.tsx Executable file
View File

@@ -0,0 +1,20 @@
import React from "react";
import ReactDOM from "react-dom";
import "semantic-ui-css/semantic.min.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
export const SERVER_BASE_NAME = "/kadi";
ReactDOM.render((
<React.StrictMode>
<App />
</React.StrictMode>
),
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

21
src/pageListings.ts Executable file
View File

@@ -0,0 +1,21 @@
import {PageId} from "./enums";
import {Component as ReactComponent} from "react";
import ProfilePage from "./Components/ProfilePage";
import RulesetsPage from "./Components/RulesetsPage";
import FriendsPage from "./Components/FriendsPage";
import StatsPage from "./Components/StatsPage";
import HistoryPage from "./Components/HistoryPage";
import HomePage from "./Components/HomePage";
type PageComponentFromIdType = {
[key in PageId]: new (...args: any[]) => ReactComponent;
};
export const pageComponentFromId: PageComponentFromIdType = {
[PageId.profile]: ProfilePage,
[PageId.rulesets]: RulesetsPage,
[PageId.friends]: FriendsPage,
[PageId.stats]: StatsPage,
[PageId.home]: HomePage,
[PageId.history]: HistoryPage,
};

141
src/serviceWorker.js Executable file
View File

@@ -0,0 +1,141 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

5
src/setupTests.js Executable file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

23
src/static/css/site.css Executable file
View File

@@ -0,0 +1,23 @@
@font-face {
font-family: "Athiti";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("../fonts/Athiti-ExtraLight.ttf") format("truetype");
}
.brandname {
font-family: "Athiti", monospace;
}
.mainPageContent {
margin-left: 15rem;
}
.mainPageContentContainer {
margin-top: 1rem;
}
.kadiSidebarNav {
width: 15rem !important;
}

Binary file not shown.

BIN
src/static/fonts/Athiti-Light.ttf Executable file

Binary file not shown.

BIN
src/static/images/kadi.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

690
src/static/images/kadi.svg Executable file
View File

@@ -0,0 +1,690 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
inkscape:export-filename="/home/ledda/Desktop/kadi.png"
inkscape:version="1.0 (5b275a35d9, 2020-05-03)"
sodipodi:docname="kadi.svg"
width="1024px"
height="1024px"
viewBox="0 0 1024 1024"
version="1.1"
id="SVGRoot">
<defs
id="defs462">
<linearGradient
id="linearGradient5683"
inkscape:collect="always">
<stop
id="stop5679"
offset="0"
style="stop-color:#000000;stop-opacity:1" />
<stop
id="stop5681"
offset="1"
style="stop-color:#606060;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient3028-6"
inkscape:collect="always">
<stop
id="stop3024"
offset="0"
style="stop-color:#000000;stop-opacity:1" />
<stop
id="stop3026"
offset="1"
style="stop-color:#747474;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient2980"
inkscape:collect="always">
<stop
id="stop2976"
offset="0"
style="stop-color:#fdfdfd;stop-opacity:1" />
<stop
id="stop2978"
offset="1"
style="stop-color:#f1e6cc;stop-opacity:1" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient2927">
<stop
id="stop2925"
offset="0"
style="stop-color:#dbdbe3;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="246.16356"
x2="130.00171"
y1="429.99353"
x1="313.83167"
id="linearGradient2968-2-6"
xlink:href="#linearGradient2980"
inkscape:collect="always"
gradientTransform="matrix(1.8567885,0,0,1.8567885,-241.38568,-457.07369)" />
<linearGradient
gradientTransform="matrix(1.7800144,0,0,1.7800142,-537.16575,-357.11616)"
gradientUnits="userSpaceOnUse"
y2="375.14923"
x2="476.29993"
y1="217.86031"
x1="319.01102"
id="linearGradient2955-9-2"
xlink:href="#linearGradient2980"
inkscape:collect="always" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,-437.11911,-747.09584)" />
<radialGradient
gradientTransform="matrix(1.4862264,0,0,1.4862264,-311.26294,-478.20534)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-8"
xlink:href="#linearGradient3028-6"
inkscape:collect="always" />
<linearGradient
gradientTransform="matrix(1.8567885,0,0,1.8567885,-241.38568,-115.74038)"
inkscape:collect="always"
xlink:href="#linearGradient2980"
id="linearGradient2968-2-6-2"
x1="313.83167"
y1="429.99353"
x2="130.00171"
y2="246.16356"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2980"
id="linearGradient2955-9-2-6"
x1="319.01102"
y1="217.86031"
x2="476.29993"
y2="375.14923"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.7800144,0,0,1.7800142,-537.16577,-15.78284)" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,-355.60638,-321.15806)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-0-1-1"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,-518.81782,-491.17479)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-64-8-0"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<linearGradient
gradientTransform="matrix(1.8567885,0,0,1.8567885,441.281,-457.07369)"
inkscape:collect="always"
xlink:href="#linearGradient2980"
id="linearGradient2968-2-6-21"
x1="313.83167"
y1="429.99353"
x2="130.00171"
y2="246.16356"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2980"
id="linearGradient2955-9-2-9"
x1="319.01102"
y1="217.86031"
x2="476.29993"
y2="375.14923"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.7800144,0,0,1.7800142,145.50095,-357.11614)" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,245.54758,-747.09584)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-1"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,163.84889,-661.68358)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-64-9-8"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,327.06032,-662.49138)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-0-1-7"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,163.84889,-832.50814)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-64-8-4"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,327.06032,-833.31592)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-0-4-8"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<linearGradient
gradientTransform="matrix(1.8567885,0,0,1.8567885,99.947664,-115.74034)"
inkscape:collect="always"
xlink:href="#linearGradient2980"
id="linearGradient2968-2-6-29"
x1="313.83167"
y1="429.99353"
x2="130.00171"
y2="246.16356"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2980"
id="linearGradient2955-9-2-8"
x1="319.01102"
y1="217.86031"
x2="476.29993"
y2="375.14923"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.7800144,0,0,1.7800142,-195.8324,-15.782803)" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,-177.48445,-320.35023)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-64-9-2"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,-14.273021,-321.15803)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-0-1-5"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,-177.48445,-491.17477)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-64-8-49"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,-14.273021,-491.98258)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-0-4-9"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="246.16356"
x2="130.00171"
y1="429.99353"
x1="313.83167"
id="linearGradient2968-2-6-2-9"
xlink:href="#linearGradient2980"
inkscape:collect="always"
gradientTransform="matrix(1.8567885,0,0,1.8567885,-241.38568,225.59301)" />
<linearGradient
gradientTransform="matrix(1.7800144,0,0,1.7800142,-537.16577,325.55054)"
gradientUnits="userSpaceOnUse"
y2="375.14923"
x2="476.29993"
y1="217.86031"
x1="319.01102"
id="linearGradient2955-9-2-6-2"
xlink:href="#linearGradient2980"
inkscape:collect="always" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-2-8"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,-437.11913,-64.429122)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-0-1-1-1"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,-355.60638,20.175318)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-64-8-0-4"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,-518.81782,-149.84141)" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="246.16356"
x2="130.00171"
y1="429.99353"
x1="313.83167"
id="linearGradient2968-2-6-3-7"
xlink:href="#linearGradient2980"
inkscape:collect="always"
gradientTransform="matrix(1.8567885,0,0,1.8567885,441.28102,225.59303)" />
<linearGradient
gradientTransform="matrix(1.7800144,0,0,1.7800142,145.50095,325.55056)"
gradientUnits="userSpaceOnUse"
y2="375.14923"
x2="476.29993"
y1="217.86031"
x1="319.01102"
id="linearGradient2955-9-2-69-0"
xlink:href="#linearGradient2980"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,163.84889,-64.42914)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-64-6-1"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(2.7596081,0,0,2.7596081,327.06034,-65.236936)"
gradientUnits="userSpaceOnUse"
r="14.615433"
fy="327.62778"
fx="216.74664"
cy="327.62778"
cx="216.74664"
id="radialGradient3042-3-1-6-0-41-9"
xlink:href="#linearGradient5683"
inkscape:collect="always" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-64-9-88-7"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,163.84889,20.983132)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-0-1-9-8"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,327.06034,20.175336)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-64-8-88-5"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,163.84889,-149.84141)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5683"
id="radialGradient3042-3-1-6-0-4-86-3"
cx="216.74664"
cy="327.62778"
fx="216.74664"
fy="327.62778"
r="14.615433"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7596081,0,0,2.7596081,327.06034,-150.64921)" />
</defs>
<sodipodi:namedview
inkscape:object-paths="true"
inkscape:window-maximized="0"
inkscape:window-y="27"
inkscape:window-x="67"
inkscape:window-height="1023"
inkscape:window-width="1439"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="px"
inkscape:cy="653.82099"
inkscape:cx="654.51751"
inkscape:zoom="0.35355339"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base" />
<metadata
id="metadata465">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<rect
ry="61.433502"
y="0"
x="0"
height="341.33334"
width="341.33334"
id="rect58-6-9"
style="fill:url(#linearGradient2968-2-6);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5.94172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:url(#linearGradient2955-9-2);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.59501;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect58-5-2-5"
width="279.97653"
height="279.97647"
x="30.678432"
y="30.678488"
ry="50.39043" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="170.66669"
cx="170.66669" />
<rect
style="fill:url(#linearGradient2968-2-6-2);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5.94172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect58-6-9-4"
width="341.33334"
height="341.33334"
x="0"
y="341.33334"
ry="61.433502" />
<rect
ry="50.39043"
y="372.01187"
x="30.678432"
height="279.97647"
width="279.97653"
id="rect58-5-2-5-0"
style="fill:url(#linearGradient2955-9-2-6);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.59501;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
r="29.836327"
cx="252.17938"
cy="596.60443"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-1-1);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-54-0-3" />
<ellipse
r="29.836327"
cx="88.967949"
cy="426.58774"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-8-0);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-3-84-8" />
<rect
style="fill:url(#linearGradient2968-2-6-21);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5.94172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect58-6-9-0"
width="341.33334"
height="341.33334"
x="682.66669"
y="0"
ry="61.433502" />
<rect
ry="50.39043"
y="30.678488"
x="713.34515"
height="279.97647"
width="279.97653"
id="rect58-5-2-5-4"
style="fill:url(#linearGradient2955-9-2-9);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.59501;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
r="29.836327"
cx="853.33337"
cy="170.66669"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-1);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-29" />
<ellipse
r="29.836327"
cx="771.63464"
cy="256.07895"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-9-8);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-3-8-22" />
<ellipse
r="29.836327"
cx="934.84607"
cy="255.27113"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-1-7);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-54-0-2" />
<ellipse
r="29.836327"
cx="771.63464"
cy="85.254402"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-8-4);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-3-84-5" />
<ellipse
r="29.836327"
cx="934.84607"
cy="84.446587"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-4-8);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-54-7-5" />
<rect
style="fill:url(#linearGradient2968-2-6-29);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5.94172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect58-6-9-3"
width="341.33334"
height="341.33334"
x="341.33334"
y="341.33334"
ry="61.433502" />
<rect
ry="50.39043"
y="372.01187"
x="372.01175"
height="279.97647"
width="279.97653"
id="rect58-5-2-5-6"
style="fill:url(#linearGradient2955-9-2-8);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.59501;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
r="29.836327"
cx="430.30133"
cy="597.41235"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-9-2);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-3-8-4" />
<ellipse
r="29.836327"
cx="593.5127"
cy="596.60443"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-1-5);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-54-0-35" />
<ellipse
r="29.836327"
cx="430.30133"
cy="426.58774"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-8-49);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-3-84-7" />
<ellipse
r="29.836327"
cx="593.5127"
cy="425.77991"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-4-9);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-54-7-4" />
<rect
ry="61.433502"
y="682.66669"
x="0"
height="341.33334"
width="341.33334"
id="rect58-6-9-4-4"
style="fill:url(#linearGradient2968-2-6-2-9);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5.94172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:url(#linearGradient2955-9-2-6-2);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.59501;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect58-5-2-5-0-9"
width="279.97653"
height="279.97647"
x="30.678432"
y="713.34515"
ry="50.39043" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-39-0"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-2-8);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="853.33337"
cx="170.66666" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-54-0-3-6"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-1-1-1);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="937.93781"
cx="252.17938" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-3-84-8-1"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-8-0-4);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="767.92108"
cx="88.967972" />
<rect
ry="61.433502"
y="682.66669"
x="682.66669"
height="341.33334"
width="341.33334"
id="rect58-6-9-8-4"
style="fill:url(#linearGradient2968-2-6-3-7);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:5.94172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:url(#linearGradient2955-9-2-69-0);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.59501;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect58-5-2-5-3-0"
width="279.97653"
height="279.97647"
x="713.34515"
y="713.34515"
ry="50.39043" />
<ellipse
r="29.836327"
cx="771.63464"
cy="853.33337"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-6-1);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-3-3-4" />
<ellipse
r="29.836327"
cx="934.84607"
cy="852.52551"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-41-9);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
id="path2986-9-6-9-0-54-3-7" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-3-8-8-8"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-9-88-7);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="938.74567"
cx="771.63464" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-54-0-0-5"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-1-9-8);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="937.93781"
cx="934.84607" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-3-84-76-2"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-64-8-88-5);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="767.92108"
cx="771.63464" />
<ellipse
r="29.836327"
id="path2986-9-6-9-0-54-7-8-6"
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#radialGradient3042-3-1-6-0-4-86-3);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:4.448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
cy="767.11328"
cx="934.84607" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

69
src/static/strings.ts Executable file
View File

@@ -0,0 +1,69 @@
import React from "react";
import {SupportedLang} from "../enums";
// Formats strings
// formatUnicorn("Hello, {0}!", ["World"]) becomes "Hello, World!"
// {0} is the first entry in args, {1} the second, etc.
export function formatUnicorn(fmt: string, ...args: string[]): string {
if (!fmt.match(/^(?:(?:(?:[^{}]|(?:\{\{)|(?:\}\}))+)|(?:\{[0-9]+\}))+$/)) {
throw new Error('Invalid formatUnicorn input string.');
}
return fmt.replace(/((?:[^{}]|(?:\{\{)|(?:\}\}))+)|(?:\{([0-9]+)\})/g, (m: string, str: string, index: number) => {
if (str) {
return str.replace(/(?:{{)|(?:}})/g, m => m[0]);
} else {
if (index >= args.length) {
throw new Error('Argument index is out of range in formatUnicorn call.');
}
return args[index];
}
});
}
export const LanguageNames: Record<SupportedLang, string> = {
gb: "English",
de: "Deutsch",
it: "Italiano",
};
export const IntlStrings = {
gb: {
menu: {
profileTab: "Profile",
statsTab: "Stats",
playTab: "Play",
rulesetsTab: "Rulesets",
friendsTab: "Friends",
historyTab: "History",
loginButton: "Login",
logoutButton: "Logout",
userWelcome: "Hi, ",
},
},
de: {
menu: {
profileTab: "Profil",
statsTab: "Statistiken",
playTab: "Spielen",
rulesetsTab: "Regelwerke",
friendsTab: "Freunde",
historyTab: "Spielverlauf",
loginButton: "Anmelden",
logoutButton: "Abmelden",
userWelcome: "Hallo, ",
},
},
it: {
menu: {
profileTab: "Profilo",
statsTab: "Statistiche",
playTab: "Gioca",
rulesetsTab: "Regolamenti",
friendsTab: "Amici",
historyTab: "Storia",
loginButton: "Accedi",
logoutButton: "Esci",
userWelcome: "Ciao, ",
},
},
} as const;

23
tsconfig.json Executable file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "esnext",
"target": "es5",
"jsx": "react",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
},
"lib": [
"dom",
"dom.iterable",
"esnext"
]
}

33
tslint.json Executable file
View File

@@ -0,0 +1,33 @@
{
"defaultSeverity": "error",
"extends": [
"tslint-react"
],
"jsRules": {
},
"rules": {
"jsx-no-multiline-js": false,
"member-access": false,
"prefer-for-of": true,
"prefer-const": true,
"prefer-readonly": true,
"typedef": [
true,
"call-signature",
"property-declaration"
],
"ordered-imports": false,
"quotemark": false,
"no-console": false,
"jsx-no-lambda": false
},
"rulesDirectory": [
],
"linterOptions": {
"exclude": [
"build/**/*.js",
"config/**/*.js",
"node_modules/**/*.ts"
]
}
}

51
webpack.config.js Executable file
View File

@@ -0,0 +1,51 @@
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: "./src/index.tsx",
mode: "development",
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /(node_modules|bower_components|\.d\.ts$)/,
use: [
{
loader: "babel-loader",
options: { presets: ["@babel/env"] },
},
{ loader: "ts-loader" },
],
},
{
test: /\.d\.ts$/,
loader: 'ignore-loader'
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i,
use: [
{
loader: 'file-loader',
},
],
}
]
},
resolve: { extensions: [".tsx", ".ts", ".js", "*"] },
output: {
path: path.resolve(__dirname, "dist/"),
publicPath: "/kadi/static/",
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "public/"),
port: 3000,
publicPath: "http://localhost:3000/dist/",
hotOnly: true
},
plugins: [new webpack.HotModuleReplacementPlugin()]
};