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

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;