Merge pull request #49 from turniere/ticket/TURNIERE-233
Ticket/turniere 233: Add page content loading indicators
This commit is contained in:
commit
475e00157c
|
|
@ -2,7 +2,9 @@ stages:
|
||||||
- build
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
kaniko:
|
kaniko:
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
stage: build
|
stage: build
|
||||||
image:
|
image:
|
||||||
name: gcr.io/kaniko-project/executor:debug
|
name: gcr.io/kaniko-project/executor:debug
|
||||||
|
|
@ -11,7 +13,9 @@ kaniko:
|
||||||
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
||||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:latest --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:latest --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||||
|
|
||||||
kubernetes:
|
kubernetes:
|
||||||
|
tags:
|
||||||
|
- turniere-deploy
|
||||||
stage: deploy
|
stage: deploy
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,7 @@ const reducerTournamentStatistics = (state = defaultStateTournamentStatistics, a
|
||||||
storeOptionalToken(error.response);
|
storeOptionalToken(error.response);
|
||||||
}
|
}
|
||||||
action.parameters.errorCallback();
|
action.parameters.errorCallback();
|
||||||
|
return Object.assign({}, state, {loaded: true});
|
||||||
});
|
});
|
||||||
return state;
|
return state;
|
||||||
case actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS:
|
case actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS:
|
||||||
|
|
@ -361,11 +362,12 @@ const reducerTournamentStatistics = (state = defaultStateTournamentStatistics, a
|
||||||
storeOptionalToken(error.response);
|
storeOptionalToken(error.response);
|
||||||
}
|
}
|
||||||
action.parameters.errorCallback();
|
action.parameters.errorCallback();
|
||||||
|
return Object.assign({}, state, {loaded: true});
|
||||||
});
|
});
|
||||||
return Object.assign({}, state, action.parameters.tournamentInfo);
|
return Object.assign({}, state, action.parameters.tournamentInfo, {loaded: true});
|
||||||
case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS:
|
case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS:
|
||||||
action.parameters.successCallback();
|
action.parameters.successCallback();
|
||||||
return Object.assign({}, state, action.parameters.tournamentStatistics);
|
return Object.assign({}, state, action.parameters.tournamentStatistics, {loaded: true});
|
||||||
default: return state;
|
default: return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Head from 'next/head';
|
||||||
|
import {TurniereNavigation} from './Navigation';
|
||||||
|
import {Container} from 'reactstrap';
|
||||||
|
import {Footer} from './Footer';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function LoadingPage(props) {
|
||||||
|
return (<div>
|
||||||
|
<Head>
|
||||||
|
<title>{props.title}</title>
|
||||||
|
</Head>
|
||||||
|
<TurniereNavigation/>
|
||||||
|
<Container>
|
||||||
|
<div className='text-center'>
|
||||||
|
<img src='/static/images/logo-questionmark.png' alt='' className='loading-logo'/>
|
||||||
|
</div>
|
||||||
|
<div className='py-5 text-center w-100 h1 custom-font'>
|
||||||
|
{props.text}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
<Footer/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
@ -1,35 +1,51 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {requestTournamentList} from '../api';
|
import {requestTournamentList} from '../api';
|
||||||
|
import {Spinner} from 'react-bootstrap';
|
||||||
|
|
||||||
export default class TournamentList extends React.Component {
|
export default class TournamentList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
tournaments: []
|
tournaments: [],
|
||||||
|
loaded: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
requestTournamentList(this.props.type, tournaments => {
|
requestTournamentList(this.props.type, tournaments => {
|
||||||
this.setState({
|
this.setState({
|
||||||
tournaments: tournaments
|
tournaments: tournaments,
|
||||||
|
loaded: true
|
||||||
});
|
});
|
||||||
}, () => {});
|
}, () => {
|
||||||
|
this.setState({loaded: true});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.tournaments.length === 0) {
|
if (!this.state.loaded) {
|
||||||
return <p className="text-center border-light font-italic text-secondary border-top border-bottom p-1">keine
|
return (<EmptyList>
|
||||||
Turniere vorhanden</p>;
|
<Spinner animation='border' role='status' size='sm'/>
|
||||||
} else {
|
<span className='ml-3'>lade Turnier-Liste</span>
|
||||||
return this.state.tournaments.map(item => (
|
</EmptyList>);
|
||||||
// The code should be item.code but the api just supports it this way by now
|
|
||||||
<TournamentListEntry name={item.name} code={item.id} key={item.id}/>
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.tournaments.length === 0) {
|
||||||
|
return <EmptyList>keine Turniere vorhanden</EmptyList>;
|
||||||
|
}
|
||||||
|
return this.state.tournaments.map(item => (
|
||||||
|
// The code should be item.code but the api just supports it this way by now
|
||||||
|
<TournamentListEntry name={item.name} code={item.id} key={item.id}/>
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EmptyList(props) {
|
||||||
|
return (<p className="text-center border-light font-italic text-secondary border-top border-bottom p-1">
|
||||||
|
{props.children}
|
||||||
|
</p>);
|
||||||
|
}
|
||||||
|
|
||||||
function TournamentListEntry(props) {
|
function TournamentListEntry(props) {
|
||||||
return (
|
return (
|
||||||
<a className="w-100 d-inline-block mt-2 text-left btn btn-outline-primary" href={'/t/' + props.code}>
|
<a className="w-100 d-inline-block mt-2 text-left btn btn-outline-primary" href={'/t/' + props.code}>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export const defaultStateTournamentStatistics = {
|
||||||
owner_username: '',
|
owner_username: '',
|
||||||
isPublic: '',
|
isPublic: '',
|
||||||
|
|
||||||
|
loaded: false,
|
||||||
statistics_available: false,
|
statistics_available: false,
|
||||||
|
|
||||||
most_dominant_team: {},
|
most_dominant_team: {},
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
import '../static/css/everypage.css';
|
import '../static/css/everypage.css';
|
||||||
import '../static/css/index.css';
|
import '../static/css/index.css';
|
||||||
|
import {LoadingPage} from '../js/components/LoadingPage';
|
||||||
|
|
||||||
class EditTournamentPage extends React.Component {
|
class EditTournamentPage extends React.Component {
|
||||||
static async getInitialProps({query}) {
|
static async getInitialProps({query}) {
|
||||||
|
|
@ -29,26 +30,31 @@ class EditTournamentPage extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
validCode: false
|
validCode: false,
|
||||||
|
loaded: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
requestTournament(this.props.query.code, () => {
|
requestTournament(this.props.query.code, () => {
|
||||||
this.setState({validCode: true});
|
this.setState({validCode: true, loaded: true});
|
||||||
|
|
||||||
if (this._edittournamentcontent != null) {
|
if (this._edittournamentcontent != null) {
|
||||||
this._edittournamentcontent.notifyOfContentUpdate();
|
this._edittournamentcontent.notifyOfContentUpdate();
|
||||||
}
|
}
|
||||||
}, () => {
|
}, () => {
|
||||||
this.setState({validCode: false});
|
this.setState({validCode: false, loaded: true});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {validCode} = this.state;
|
const {validCode, loaded} = this.state;
|
||||||
const {tournamentname, ownerUsername, isSignedIn, username} = this.props;
|
const {tournamentname, ownerUsername, isSignedIn, username} = this.props;
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return <LoadingPage title='turnie.re' text='Turnier-Einstellungen werden geladen...'/>;
|
||||||
|
}
|
||||||
|
|
||||||
return (<UserRestrictor>
|
return (<UserRestrictor>
|
||||||
<Option condition={validCode && isSignedIn && ownerUsername === username}>
|
<Option condition={validCode && isSignedIn && ownerUsername === username}>
|
||||||
<div className='pb-5'>
|
<div className='pb-5'>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {requestTournamentStatistics} from '../js/api';
|
||||||
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
|
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
|
||||||
import Navbar from 'react-bootstrap/Navbar';
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
import {TournamentBigImage} from '../js/components/TournamentBigImage';
|
import {TournamentBigImage} from '../js/components/TournamentBigImage';
|
||||||
|
import {LoadingPage} from '../js/components/LoadingPage';
|
||||||
|
|
||||||
class StatisticsTournamentPage extends React.Component {
|
class StatisticsTournamentPage extends React.Component {
|
||||||
static async getInitialProps({query}) {
|
static async getInitialProps({query}) {
|
||||||
|
|
@ -24,6 +25,10 @@ class StatisticsTournamentPage extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const {tournamentStatistics} = this.props;
|
const {tournamentStatistics} = this.props;
|
||||||
|
|
||||||
|
if (!tournamentStatistics.loaded) {
|
||||||
|
return <LoadingPage title='turnie.re' text='Statistiken zum Turnier werden geladen...'/>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@ import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
import '../static/css/everypage.css';
|
import '../static/css/everypage.css';
|
||||||
import '../static/css/tournament.css';
|
import '../static/css/tournament.css';
|
||||||
import {getTournament} from '../js/redux/tournamentApi';
|
|
||||||
import {PlayoffStages} from '../js/components/PlayoffStages';
|
import {PlayoffStages} from '../js/components/PlayoffStages';
|
||||||
import GroupStage from '../js/components/GroupStage';
|
import GroupStage from '../js/components/GroupStage';
|
||||||
import {TournamentBigImage} from '../js/components/TournamentBigImage';
|
import {TournamentBigImage} from '../js/components/TournamentBigImage';
|
||||||
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
|
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
|
||||||
|
import {LoadingPage} from '../js/components/LoadingPage';
|
||||||
|
import {getTournament} from '../js/redux/tournamentApi';
|
||||||
|
|
||||||
class PrivateTournamentPage extends React.Component {
|
class PrivateTournamentPage extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
|
|
@ -71,7 +72,8 @@ class Main extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
tournament: null
|
tournament: null,
|
||||||
|
loaded: false
|
||||||
};
|
};
|
||||||
this.onTournamentRequestSuccess = this.onTournamentRequestSuccess.bind(this);
|
this.onTournamentRequestSuccess = this.onTournamentRequestSuccess.bind(this);
|
||||||
this.onTournamentRequestError = this.onTournamentRequestError.bind(this);
|
this.onTournamentRequestError = this.onTournamentRequestError.bind(this);
|
||||||
|
|
@ -82,14 +84,14 @@ class Main extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onTournamentRequestSuccess(requestStatus, tournament) {
|
onTournamentRequestSuccess(requestStatus, tournament) {
|
||||||
this.setState({status: requestStatus, tournament: tournament});
|
this.setState({status: requestStatus, tournament: tournament, loaded: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
onTournamentRequestError(error) {
|
onTournamentRequestError(error) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
this.setState({status: error.response.status});
|
this.setState({status: error.response.status, loaded: true});
|
||||||
} else {
|
} else {
|
||||||
this.setState({status: -1});
|
this.setState({status: -1, loaded: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +111,9 @@ class Main extends React.Component {
|
||||||
<Footer/>
|
<Footer/>
|
||||||
</div>);
|
</div>);
|
||||||
} else {
|
} else {
|
||||||
|
if (!this.state.loaded) {
|
||||||
|
return <LoadingPage title='turnie.re' text='Turnier wird geladen...'/>;
|
||||||
|
}
|
||||||
return <ErrorPageComponent code={status}/>;
|
return <ErrorPageComponent code={status}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,3 +72,19 @@ footer {
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-logo {
|
||||||
|
animation: blink .8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0% {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
filter: grayscale(0%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue