Added Navbar
This commit is contained in:
		@@ -26,6 +26,7 @@
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Remove="ClientApp\src\common\components\BirdmapTitle.tsx" />
 | 
			
		||||
    <None Remove="ClientApp\src\common\ErrorDispatcher.ts" />
 | 
			
		||||
    <None Remove="ClientApp\src\common\ServiceBase.ts" />
 | 
			
		||||
    <None Remove="ClientApp\src\components\auth\Auth.tsx" />
 | 
			
		||||
@@ -34,6 +35,13 @@
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <TypeScriptCompile Include="ClientApp\src\components\auth\Auth.tsx" />
 | 
			
		||||
	<TypeScriptCompile Include="ClientApp\src\common\components\BirdmapTitle.tsx" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Folder Include="ClientApp\src\components\dashboard\" />
 | 
			
		||||
    <Folder Include="ClientApp\src\components\devices\" />
 | 
			
		||||
    <Folder Include="ClientApp\src\components\heatmap\" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								Birdmap/ClientApp/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								Birdmap/ClientApp/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1444,9 +1444,9 @@
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "csstype": {
 | 
			
		||||
          "version": "3.0.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz",
 | 
			
		||||
          "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag=="
 | 
			
		||||
          "version": "3.0.4",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz",
 | 
			
		||||
          "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA=="
 | 
			
		||||
        },
 | 
			
		||||
        "dom-helpers": {
 | 
			
		||||
          "version": "5.2.0",
 | 
			
		||||
@@ -7933,9 +7933,9 @@
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "csstype": {
 | 
			
		||||
          "version": "3.0.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz",
 | 
			
		||||
          "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag=="
 | 
			
		||||
          "version": "3.0.4",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz",
 | 
			
		||||
          "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA=="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +1,63 @@
 | 
			
		||||
import { Box, Container } from '@material-ui/core';
 | 
			
		||||
import AppBar from '@material-ui/core/AppBar';
 | 
			
		||||
import blue from '@material-ui/core/colors/blue';
 | 
			
		||||
import { createMuiTheme } from '@material-ui/core/styles';
 | 
			
		||||
import orange from '@material-ui/core/colors/orange';
 | 
			
		||||
import { createMuiTheme, createStyles, makeStyles, Theme } from '@material-ui/core/styles';
 | 
			
		||||
import Toolbar from '@material-ui/core/Toolbar';
 | 
			
		||||
import Typography from '@material-ui/core/Typography';
 | 
			
		||||
import { ThemeProvider } from '@material-ui/styles';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { BrowserRouter, NavLink, Redirect, Route, Switch } from 'react-router-dom';
 | 
			
		||||
import BirdmapTitle from './common/components/BirdmapTitle';
 | 
			
		||||
import Auth from './components/auth/Auth';
 | 
			
		||||
import AuthService from './components/auth/AuthService';
 | 
			
		||||
import Home from './components/Home';
 | 
			
		||||
import './custom.css';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const theme = createMuiTheme({
 | 
			
		||||
    palette: {
 | 
			
		||||
        primary: {
 | 
			
		||||
            main: blue[800]
 | 
			
		||||
            main: blue[900],
 | 
			
		||||
        },
 | 
			
		||||
        secondary: {
 | 
			
		||||
            main: orange[200],
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function App() {
 | 
			
		||||
 | 
			
		||||
    const [authenticated, setAuthenticated] = useState(AuthService.isAuthenticated());
 | 
			
		||||
 | 
			
		||||
    const onAuthenticated = () => {
 | 
			
		||||
        setAuthenticated(AuthService.isAuthenticated());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const LoginComponent = () => {
 | 
			
		||||
    const AuthComponent = () => {
 | 
			
		||||
        return (
 | 
			
		||||
            <Auth onAuthenticated={onAuthenticated} />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const DashboardComponent = () => {
 | 
			
		||||
        return <Typography>Dashboard</Typography>;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const DevicesComponent = () => {
 | 
			
		||||
        return <Typography>Devices</Typography>;
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
    const HeatmapComponent = () => {
 | 
			
		||||
        return <Typography>Heatmap</Typography>;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ThemeProvider theme={theme}>
 | 
			
		||||
                <BrowserRouter>
 | 
			
		||||
                    <Switch>
 | 
			
		||||
                        <PublicRoute path="/login" component={LoginComponent} />
 | 
			
		||||
                        <PrivateRoute path="/" exact authenticated={authenticated} component={Home} />
 | 
			
		||||
                        <PublicRoute path="/login" component={AuthComponent} />
 | 
			
		||||
                        <PrivateRoute path="/" exact authenticated={authenticated} component={DashboardComponent} />
 | 
			
		||||
                        <PrivateRoute path="/devices" exact authenticated={authenticated} component={DevicesComponent} />
 | 
			
		||||
                        <PrivateRoute path="/heatmap" exact authenticated={authenticated} component={HeatmapComponent} />
 | 
			
		||||
                    </Switch>
 | 
			
		||||
                </BrowserRouter>
 | 
			
		||||
        </ThemeProvider>
 | 
			
		||||
@@ -51,7 +69,7 @@ export default App;
 | 
			
		||||
const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Route {...rest} render={matchProps => (
 | 
			
		||||
            <NoLayout component={Component} {...matchProps} />
 | 
			
		||||
            <DefaultLayout component={Component} authenticated={false} {...matchProps} />
 | 
			
		||||
        )} />
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@@ -60,28 +78,32 @@ const PrivateRoute = ({ component: Component, authenticated: Authenticated, ...r
 | 
			
		||||
    return (
 | 
			
		||||
        <Route {...rest} render={matchProps => (
 | 
			
		||||
            Authenticated
 | 
			
		||||
                ? <DefaultLayout component={Component} {...matchProps} />
 | 
			
		||||
                ? <DefaultLayout component={Component} authenticated={Authenticated} {...matchProps} />
 | 
			
		||||
                : <Redirect to='/login' />
 | 
			
		||||
        )} />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const NoLayout = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Component {...rest} />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => {
 | 
			
		||||
    const classes = useDefaultLayoutStyles();
 | 
			
		||||
 | 
			
		||||
    const renderNavLinks = () => {
 | 
			
		||||
        return Authenticated
 | 
			
		||||
            ? <Container className={classes.nav_menu}>
 | 
			
		||||
                <NavLink exact to="/" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Dashboard</NavLink>
 | 
			
		||||
                <NavLink exact to="/devices" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Devices</NavLink>
 | 
			
		||||
                <NavLink exact to="/heatmap" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>Heatmap</NavLink>
 | 
			
		||||
            </Container>
 | 
			
		||||
            : null;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
const DefaultLayout = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <React.Fragment>
 | 
			
		||||
            <AppBar position="static">
 | 
			
		||||
                <Toolbar>
 | 
			
		||||
                    <Typography component={'span'}>
 | 
			
		||||
                        <Container className="nav-menu">
 | 
			
		||||
                            <NavLink exact to="/" className="nav-menu-item" activeStyle={{ color: 'white' }}>Dashboard</NavLink>
 | 
			
		||||
                            <NavLink exact to="/login" className="nav-menu-item" activeStyle={{ color: 'white' }}>Login</NavLink>
 | 
			
		||||
                        </Container>
 | 
			
		||||
                    <BirdmapTitle />
 | 
			
		||||
                    <Typography component={'span'} className={classes.typo}>
 | 
			
		||||
                        {renderNavLinks()}
 | 
			
		||||
                    </Typography>
 | 
			
		||||
                </Toolbar>
 | 
			
		||||
            </AppBar>
 | 
			
		||||
@@ -91,3 +113,34 @@ const DefaultLayout = ({ component: Component, ...rest }: { [x: string]: any, co
 | 
			
		||||
        </React.Fragment>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const useDefaultLayoutStyles = makeStyles((theme: Theme) =>
 | 
			
		||||
    createStyles({
 | 
			
		||||
        typo: {
 | 
			
		||||
            marginLeft: 'auto',
 | 
			
		||||
            color: 'white',
 | 
			
		||||
        },
 | 
			
		||||
        nav_menu: {
 | 
			
		||||
            display: 'flex',
 | 
			
		||||
            flexDirection: 'row',
 | 
			
		||||
        },
 | 
			
		||||
        nav_menu_item: {
 | 
			
		||||
            textDecoration: 'none',
 | 
			
		||||
            fontWeight: 'normal',
 | 
			
		||||
            color: 'inherit',
 | 
			
		||||
            marginLeft: '24px',
 | 
			
		||||
            '&:hover': {
 | 
			
		||||
                color: 'inherit',
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        nav_menu_item_active: {
 | 
			
		||||
            textDecoration: 'underline',
 | 
			
		||||
            fontWeight: 'bold',
 | 
			
		||||
            color: 'inherit',
 | 
			
		||||
            marginLeft: '24px',
 | 
			
		||||
            '&:hover': {
 | 
			
		||||
                color: 'inherit',
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    }),
 | 
			
		||||
);
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"version":3,"file":"ServiceBase.js","sourceRoot":"","sources":["ServiceBase.ts"],"names":[],"mappings":";;AAAA,qDAAgD;AAEhD,SAAS,GAAG,CAAC,GAAG;IACZ,IAAI,OAAO,GAAG;QACV,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;SAClD;KACJ,CAAC;IAEF,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,IAAI,CAAC,GAAG,EAAE,OAAO;IACtB,IAAI,OAAO,GAAG;QACV,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;SAClD;QACD,IAAI,EAAE,EAAE;KACX,CAAC;IAEF,IAAI,OAAO;QACP,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,WAAW,CAAC,GAAG,EAAE,OAAO;IAC7B,OAAO,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC;SACrB,IAAI,CAAC,qBAAqB,CAAC;SAC3B,KAAK,CAAC,YAAY,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAQ;IACnC,IAAI,CAAC,QAAQ,CAAC,EAAE;QACZ,OAAO,QAAQ,CAAC,IAAI,EAAE;aACjB,IAAI,CAAC,UAAA,IAAI,IAAI,OAAA,YAAY,CAAC,IAAI,CAAC,EAAlB,CAAkB,CAAC,CAAC;IAE1C,OAAO,QAAQ,CAAC,IAAI,EAAE;SACjB,IAAI,CAAC,UAAA,IAAI,IAAI,OAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAnC,CAAmC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,YAAY,CAAC,QAAQ;IAC1B,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEtB,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK;QAC1B,yBAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE/C,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,kBAAe;IACX,GAAG,KAAA;IACH,IAAI,MAAA;IACJ,WAAW,aAAA;CACd,CAAC"}
 | 
			
		||||
{"version":3,"file":"ServiceBase.js","sourceRoot":"","sources":["ServiceBase.ts"],"names":[],"mappings":";;AAAA,qDAAgD;AAEhD,SAAS,GAAG,CAAC,GAAW;IACpB,IAAI,OAAO,GAAG;QACV,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;SAClD;KACJ,CAAC;IAEF,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,IAAI,CAAC,GAAW,EAAE,OAAY;IACnC,IAAI,OAAO,GAAG;QACV,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACL,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;SAClD;QACD,IAAI,EAAE,EAAE;KACX,CAAC;IAEF,IAAI,OAAO;QACP,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,OAAY;IAC1C,OAAO,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC;SACrB,IAAI,CAAC,qBAAqB,CAAC;SAC3B,KAAK,CAAC,YAAY,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAa;IACxC,IAAI,CAAC,QAAQ,CAAC,EAAE;QACZ,OAAO,QAAQ,CAAC,IAAI,EAAE;aACjB,IAAI,CAAC,UAAC,IAAS,IAAK,OAAA,YAAY,CAAC,IAAI,CAAC,EAAlB,CAAkB,CAAC,CAAC;IAEjD,OAAO,QAAQ,CAAC,IAAI,EAAE;SACjB,IAAI,CAAC,UAAC,IAAS,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAnC,CAAmC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CAAC,QAAa;IAC/B,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEtB,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK;QAC1B,yBAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE/C,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,kBAAe;IACX,GAAG,KAAA;IACH,IAAI,MAAA;IACJ,WAAW,aAAA;CACd,CAAC"}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import ErrorDispatcher from './ErrorDispatcher';
 | 
			
		||||
 | 
			
		||||
function get(url) {
 | 
			
		||||
function get(url: string) {
 | 
			
		||||
    let options = {
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
        headers: {
 | 
			
		||||
@@ -12,7 +12,7 @@ function get(url) {
 | 
			
		||||
    return makeRequest(url, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function post(url, request) {
 | 
			
		||||
function post(url: string, request: any) {
 | 
			
		||||
    let options = {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
@@ -28,22 +28,22 @@ function post(url, request) {
 | 
			
		||||
    return makeRequest(url, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeRequest(url, options) {
 | 
			
		||||
function makeRequest(url: string, options: any) {
 | 
			
		||||
    return fetch(url, options)
 | 
			
		||||
        .then(ensureResponseSuccess)
 | 
			
		||||
        .catch(errorHandler);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ensureResponseSuccess(response) {
 | 
			
		||||
function ensureResponseSuccess(response: any) {
 | 
			
		||||
    if (!response.ok)
 | 
			
		||||
        return response.json()
 | 
			
		||||
            .then(data => errorHandler(data));
 | 
			
		||||
            .then((data: any) => errorHandler(data));
 | 
			
		||||
 | 
			
		||||
    return response.text()
 | 
			
		||||
        .then(text => text.length ? JSON.parse(text) : {});
 | 
			
		||||
        .then((text: any) => text.length ? JSON.parse(text) : {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function errorHandler(response) {
 | 
			
		||||
function errorHandler(response: any) {
 | 
			
		||||
    console.log(response);
 | 
			
		||||
 | 
			
		||||
    if (response && response.Error)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								Birdmap/ClientApp/src/common/components/BirdmapTitle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Birdmap/ClientApp/src/common/components/BirdmapTitle.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
import { Box, Typography } from '@material-ui/core';
 | 
			
		||||
import { BrowserRouter, NavLink, Redirect, Route, Switch } from 'react-router-dom';
 | 
			
		||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
export default function BirdmapTitle(props: any) {
 | 
			
		||||
    const classes = useStyles();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Box component="span" className={classes.root}>
 | 
			
		||||
            <Typography component="span" className={classes.bird}>
 | 
			
		||||
                <NavLink exact to="/" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>
 | 
			
		||||
                    Bird
 | 
			
		||||
                </NavLink>
 | 
			
		||||
            </Typography>
 | 
			
		||||
            <Typography component="span" className={classes.map}>
 | 
			
		||||
                <NavLink exact to="/heatmap" className={classes.nav_menu_item} activeClassName={classes.nav_menu_item_active}>
 | 
			
		||||
                    map
 | 
			
		||||
                </NavLink>
 | 
			
		||||
            </Typography>
 | 
			
		||||
        </Box>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const useStyles = makeStyles((theme: Theme) =>
 | 
			
		||||
    createStyles({
 | 
			
		||||
        root: {
 | 
			
		||||
            display: 'inline',
 | 
			
		||||
        },
 | 
			
		||||
        bird: {
 | 
			
		||||
            textAlign: "left",
 | 
			
		||||
            fontWeight: 1000,
 | 
			
		||||
            fontSize: 30,
 | 
			
		||||
            textShadow: '3px 3px 0px rgba(0,0,0,0.2)',
 | 
			
		||||
        },
 | 
			
		||||
        map: {
 | 
			
		||||
            textAlign: "left",
 | 
			
		||||
            fontWeight: 100,
 | 
			
		||||
            fontSize: 26,
 | 
			
		||||
            textShadow: '2px 2px 0px rgba(0,0,0,0.2)',
 | 
			
		||||
        },
 | 
			
		||||
        nav_menu_item: {
 | 
			
		||||
            textDecoration: 'none',
 | 
			
		||||
            color: 'white',
 | 
			
		||||
            '&:hover': {
 | 
			
		||||
                textDecoration: 'underline',
 | 
			
		||||
                color: 'white',
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        nav_menu_item_active: {
 | 
			
		||||
            textDecoration: 'none',
 | 
			
		||||
            color: 'white',
 | 
			
		||||
            '&:hover': {
 | 
			
		||||
                textDecoration: 'underline',
 | 
			
		||||
                color: 'white',
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    }),
 | 
			
		||||
);
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import { RouteComponentProps } from 'react-router';
 | 
			
		||||
import { ApplicationState } from '../store';
 | 
			
		||||
import * as CounterStore from '../store/Counter';
 | 
			
		||||
 | 
			
		||||
type CounterProps =
 | 
			
		||||
    CounterStore.CounterState &
 | 
			
		||||
    typeof CounterStore.actionCreators &
 | 
			
		||||
    RouteComponentProps<{}>;
 | 
			
		||||
 | 
			
		||||
class Counter extends React.PureComponent<CounterProps> {
 | 
			
		||||
    public render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <React.Fragment>
 | 
			
		||||
                <h1>Counter</h1>
 | 
			
		||||
 | 
			
		||||
                <p>This is a simple example of a React component.</p>
 | 
			
		||||
 | 
			
		||||
                <p aria-live="polite">Current count: <strong>{this.props.count}</strong></p>
 | 
			
		||||
 | 
			
		||||
                <button type="button"
 | 
			
		||||
                    className="btn btn-primary btn-lg"
 | 
			
		||||
                    onClick={() => { this.props.increment(); }}>
 | 
			
		||||
                    Increment
 | 
			
		||||
                </button>
 | 
			
		||||
            </React.Fragment>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default connect(
 | 
			
		||||
    (state: ApplicationState) => state.counter,
 | 
			
		||||
    CounterStore.actionCreators
 | 
			
		||||
)(Counter);
 | 
			
		||||
@@ -1,84 +0,0 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import { RouteComponentProps } from 'react-router';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { ApplicationState } from '../store';
 | 
			
		||||
import * as WeatherForecastsStore from '../store/WeatherForecasts';
 | 
			
		||||
 | 
			
		||||
// At runtime, Redux will merge together...
 | 
			
		||||
type WeatherForecastProps =
 | 
			
		||||
  WeatherForecastsStore.WeatherForecastsState // ... state we've requested from the Redux store
 | 
			
		||||
  & typeof WeatherForecastsStore.actionCreators // ... plus action creators we've requested
 | 
			
		||||
  & RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FetchData extends React.PureComponent<WeatherForecastProps> {
 | 
			
		||||
  // This method is called when the component is first added to the document
 | 
			
		||||
  public componentDidMount() {
 | 
			
		||||
    this.ensureDataFetched();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // This method is called when the route parameters change
 | 
			
		||||
  public componentDidUpdate() {
 | 
			
		||||
    this.ensureDataFetched();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <React.Fragment>
 | 
			
		||||
        <h1 id="tabelLabel">Weather forecast</h1>
 | 
			
		||||
        <p>This component demonstrates fetching data from the server and working with URL parameters.</p>
 | 
			
		||||
        {this.renderForecastsTable()}
 | 
			
		||||
        {this.renderPagination()}
 | 
			
		||||
      </React.Fragment>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private ensureDataFetched() {
 | 
			
		||||
    const startDateIndex = parseInt(this.props.match.params.startDateIndex, 10) || 0;
 | 
			
		||||
    this.props.requestWeatherForecasts(startDateIndex);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private renderForecastsTable() {
 | 
			
		||||
    return (
 | 
			
		||||
      <table className='table table-striped' aria-labelledby="tabelLabel">
 | 
			
		||||
        <thead>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <th>Date</th>
 | 
			
		||||
            <th>Temp. (C)</th>
 | 
			
		||||
            <th>Temp. (F)</th>
 | 
			
		||||
            <th>Summary</th>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
          {this.props.forecasts.map((forecast: WeatherForecastsStore.WeatherForecast) =>
 | 
			
		||||
            <tr key={forecast.date}>
 | 
			
		||||
              <td>{forecast.date}</td>
 | 
			
		||||
              <td>{forecast.temperatureC}</td>
 | 
			
		||||
              <td>{forecast.temperatureF}</td>
 | 
			
		||||
              <td>{forecast.summary}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
          )}
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private renderPagination() {
 | 
			
		||||
    const prevStartDateIndex = (this.props.startDateIndex || 0) - 5;
 | 
			
		||||
    const nextStartDateIndex = (this.props.startDateIndex || 0) + 5;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className="d-flex justify-content-between">
 | 
			
		||||
        <Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${prevStartDateIndex}`}>Previous</Link>
 | 
			
		||||
        {this.props.isLoading && <span>Loading...</span>}
 | 
			
		||||
        <Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${nextStartDateIndex}`}>Next</Link>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(
 | 
			
		||||
  (state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props
 | 
			
		||||
  WeatherForecastsStore.actionCreators // Selects which action creators are merged into the component's props
 | 
			
		||||
)(FetchData as any);
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
 | 
			
		||||
const Home = () => (
 | 
			
		||||
  <div>
 | 
			
		||||
    <h1>Hello, world!</h1>
 | 
			
		||||
    <p>Welcome to your new single-page application, built with:</p>
 | 
			
		||||
    <ul>
 | 
			
		||||
      <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
 | 
			
		||||
      <li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>
 | 
			
		||||
      <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <p>To help you get started, we've also set up:</p>
 | 
			
		||||
    <ul>
 | 
			
		||||
      <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
 | 
			
		||||
      <li><strong>Development server integration</strong>. In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>
 | 
			
		||||
      <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files.</li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <p>The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>npm</code> commands such as <code>npm test</code> or <code>npm install</code>.</p>
 | 
			
		||||
  </div>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export default connect()(Home);
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Container } from 'reactstrap';
 | 
			
		||||
import NavMenu from './NavMenu';
 | 
			
		||||
 | 
			
		||||
export default (props: { children?: React.ReactNode }) => (
 | 
			
		||||
    <React.Fragment>
 | 
			
		||||
        <NavMenu/>
 | 
			
		||||
        <Container>
 | 
			
		||||
            {props.children}
 | 
			
		||||
        </Container>
 | 
			
		||||
    </React.Fragment>
 | 
			
		||||
);
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
a.navbar-brand {
 | 
			
		||||
    white-space: normal;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    word-break: break-all;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html { font-size: 14px; }
 | 
			
		||||
 | 
			
		||||
@media (min-width: 768px) {
 | 
			
		||||
    html { font-size: 16px; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.box-shadow { box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); }
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import './NavMenu.css';
 | 
			
		||||
 | 
			
		||||
export default class NavMenu extends React.PureComponent<{}, { isOpen: boolean }> {
 | 
			
		||||
    public state = {
 | 
			
		||||
        isOpen: false
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <header>
 | 
			
		||||
                <Navbar className="navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3" light>
 | 
			
		||||
                    <Container>
 | 
			
		||||
                        <NavbarBrand tag={Link} to="/">Birdmap</NavbarBrand>
 | 
			
		||||
                        <NavbarToggler onClick={this.toggle} className="mr-2"/>
 | 
			
		||||
                        <Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={this.state.isOpen} navbar>
 | 
			
		||||
                            <ul className="navbar-nav flex-grow">
 | 
			
		||||
                                <NavItem>
 | 
			
		||||
                                    <NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
 | 
			
		||||
                                </NavItem>
 | 
			
		||||
                                <NavItem>
 | 
			
		||||
                                    <NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
 | 
			
		||||
                                </NavItem>
 | 
			
		||||
                                <NavItem>
 | 
			
		||||
                                    <NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
 | 
			
		||||
                                </NavItem>
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </Collapse>
 | 
			
		||||
                    </Container>
 | 
			
		||||
                </Navbar>
 | 
			
		||||
            </header>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private toggle = () => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            isOpen: !this.state.isOpen
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +1,19 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { useHistory } from 'react-router-dom';
 | 
			
		||||
import { Box, Grid, TextField, Button, Typography, Paper } from '@material-ui/core';
 | 
			
		||||
import { Box, Grid, TextField, Button, Typography, Paper, CircularProgress } from '@material-ui/core';
 | 
			
		||||
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
 | 
			
		||||
import AuthService from './AuthService';
 | 
			
		||||
 | 
			
		||||
export default function MenuItem(props: any) {
 | 
			
		||||
export default function Auth(props: any) {
 | 
			
		||||
    const history = useHistory();
 | 
			
		||||
    const classes = useStyles();
 | 
			
		||||
 | 
			
		||||
    const [username, setUsername] = useState(null);
 | 
			
		||||
    const [password, setPassword] = useState(null);
 | 
			
		||||
    const [username, setUsername] = useState<string>("");
 | 
			
		||||
    const [password, setPassword] = useState<string>("");
 | 
			
		||||
 | 
			
		||||
    const [showError, setShowError] = useState(false);
 | 
			
		||||
    const [errorMessage, setErrorMessage] = useState('');
 | 
			
		||||
    const [isLoggingIn, setIsLoggingIn] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const onUsernameChanged = (event: any) => {
 | 
			
		||||
        setUsername(event.target.value);
 | 
			
		||||
@@ -35,6 +36,8 @@ export default function MenuItem(props: any) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onLoginClicked = () => {
 | 
			
		||||
        setIsLoggingIn(true);
 | 
			
		||||
 | 
			
		||||
        if (!username) {
 | 
			
		||||
            setShowError(true);
 | 
			
		||||
            setErrorMessage('Username required');
 | 
			
		||||
@@ -49,13 +52,15 @@ export default function MenuItem(props: any) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AuthService.login(username, password).then(() => {
 | 
			
		||||
        AuthService.login(username, password)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
            props.onAuthenticated();
 | 
			
		||||
            history.push('/');
 | 
			
		||||
 | 
			
		||||
        }).catch(() => {
 | 
			
		||||
            setShowError(true);
 | 
			
		||||
            setErrorMessage('Invalid credentials');
 | 
			
		||||
        }).finally(() => {
 | 
			
		||||
            setIsLoggingIn(false);
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -65,9 +70,15 @@ export default function MenuItem(props: any) {
 | 
			
		||||
            : <React.Fragment />;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const renderLoginButton = () => {
 | 
			
		||||
        return isLoggingIn
 | 
			
		||||
            ? <CircularProgress className={classes.button} />
 | 
			
		||||
            : <Button className={classes.button} variant="contained" color="primary" onClick={onLoginClicked}>Sign in</Button>
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Box className={classes.root}>
 | 
			
		||||
            <Paper className={classes.paper}>
 | 
			
		||||
            <Paper className={classes.paper} elevation={8}>
 | 
			
		||||
                <Grid container className={classes.container} spacing={2}>
 | 
			
		||||
                    <Grid item>
 | 
			
		||||
                        <Typography component="h1" variant="h5">
 | 
			
		||||
@@ -84,7 +95,7 @@ export default function MenuItem(props: any) {
 | 
			
		||||
                        {renderErrorLabel()}
 | 
			
		||||
                    </Grid>
 | 
			
		||||
                    <Grid item xs={12} className={classes.button}>
 | 
			
		||||
                        <Button className={classes.button} variant="contained" color="primary" onClick={onLoginClicked}>Login</Button>
 | 
			
		||||
                        {renderLoginButton()}
 | 
			
		||||
                    </Grid>
 | 
			
		||||
                </Grid>
 | 
			
		||||
            </Paper>
 | 
			
		||||
@@ -108,9 +119,10 @@ const useStyles = makeStyles((theme: Theme) =>
 | 
			
		||||
            alignItems: "center",
 | 
			
		||||
        },
 | 
			
		||||
        paper: {
 | 
			
		||||
            borderRadius: 15,
 | 
			
		||||
        },
 | 
			
		||||
        button: {
 | 
			
		||||
            width: '100%',
 | 
			
		||||
            justifyContent: "center",
 | 
			
		||||
        },
 | 
			
		||||
        error: {
 | 
			
		||||
            color: "red",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
Object.defineProperty(exports, "__esModule", { value: true });
 | 
			
		||||
var ServiceBase_1 = require("../../common/ServiceBase");
 | 
			
		||||
var login_url = '/auth/authenticate';
 | 
			
		||||
var login_url = 'api/auth/authenticate';
 | 
			
		||||
exports.default = {
 | 
			
		||||
    isAuthenticated: function () {
 | 
			
		||||
        return sessionStorage.getItem('user') !== null;
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["AuthService.ts"],"names":[],"mappings":";;AAAA,wDAAmD;AAEnD,IAAM,SAAS,GAAG,oBAAoB,CAAC;AAEvC,kBAAe;IACX,eAAe;QACX,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,CAAC;IAED,KAAK,YAAC,QAAQ,EAAE,QAAQ;QACpB,IAAI,IAAI,GAAG;YACP,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;SACrB,CAAC;QACF,IAAI,OAAO,GAAG;YACV,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC7B,CAAC;QAEF,OAAO,qBAAW,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;aAC7C,IAAI,CAAC,UAAA,QAAQ;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,EAAK,QAAQ,CAAC,UAAU,SAAI,QAAQ,CAAC,YAAc,CAAC,CAAC;YAClF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACX,CAAC;CACJ,CAAA"}
 | 
			
		||||
{"version":3,"file":"AuthService.js","sourceRoot":"","sources":["AuthService.ts"],"names":[],"mappings":";;AAAA,wDAAmD;AAEnD,IAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,kBAAe;IACX,eAAe;QACX,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACnD,CAAC;IAED,KAAK,EAAL,UAAM,QAAgB,EAAE,QAAgB;QACpC,IAAI,IAAI,GAAG;YACP,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;SACrB,CAAC;QACF,IAAI,OAAO,GAAG;YACV,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC7B,CAAC;QAEF,OAAO,qBAAW,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;aAC7C,IAAI,CAAC,UAAA,QAAQ;YACV,cAAc,CAAC,OAAO,CAAC,MAAM,EAAK,QAAQ,CAAC,UAAU,SAAI,QAAQ,CAAC,YAAc,CAAC,CAAC;YAClF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;IACX,CAAC;CACJ,CAAA"}
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
import ServiceBase from '../../common/ServiceBase';
 | 
			
		||||
 | 
			
		||||
const login_url = '/auth/authenticate';
 | 
			
		||||
const login_url = 'api/auth/authenticate';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    isAuthenticated() {
 | 
			
		||||
        return sessionStorage.getItem('user') !== null;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    login(username, password) {
 | 
			
		||||
    login(username: string, password: string) {
 | 
			
		||||
        let body = {
 | 
			
		||||
            username: username,
 | 
			
		||||
            password: password
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
/* Provide sufficient contrast against white background */
 | 
			
		||||
a {
 | 
			
		||||
  color: #0366d6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
code {
 | 
			
		||||
  color: #E01A76;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-primary {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  background-color: #1b6ec2;
 | 
			
		||||
  border-color: #1861ac;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
import { Action, Reducer } from 'redux';
 | 
			
		||||
 | 
			
		||||
// -----------------
 | 
			
		||||
// STATE - This defines the type of data maintained in the Redux store.
 | 
			
		||||
 | 
			
		||||
export interface CounterState {
 | 
			
		||||
    count: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -----------------
 | 
			
		||||
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
 | 
			
		||||
// They do not themselves have any side-effects; they just describe something that is going to happen.
 | 
			
		||||
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
 | 
			
		||||
 | 
			
		||||
export interface IncrementCountAction { type: 'INCREMENT_COUNT' }
 | 
			
		||||
export interface DecrementCountAction { type: 'DECREMENT_COUNT' }
 | 
			
		||||
 | 
			
		||||
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
 | 
			
		||||
// declared type strings (and not any other arbitrary string).
 | 
			
		||||
export type KnownAction = IncrementCountAction | DecrementCountAction;
 | 
			
		||||
 | 
			
		||||
// ----------------
 | 
			
		||||
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
 | 
			
		||||
// They don't directly mutate state, but they can have external side-effects (such as loading data).
 | 
			
		||||
 | 
			
		||||
export const actionCreators = {
 | 
			
		||||
    increment: () => ({ type: 'INCREMENT_COUNT' } as IncrementCountAction),
 | 
			
		||||
    decrement: () => ({ type: 'DECREMENT_COUNT' } as DecrementCountAction)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ----------------
 | 
			
		||||
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
 | 
			
		||||
 | 
			
		||||
export const reducer: Reducer<CounterState> = (state: CounterState | undefined, incomingAction: Action): CounterState => {
 | 
			
		||||
    if (state === undefined) {
 | 
			
		||||
        return { count: 0 };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const action = incomingAction as KnownAction;
 | 
			
		||||
    switch (action.type) {
 | 
			
		||||
        case 'INCREMENT_COUNT':
 | 
			
		||||
            return { count: state.count + 1 };
 | 
			
		||||
        case 'DECREMENT_COUNT':
 | 
			
		||||
            return { count: state.count - 1 };
 | 
			
		||||
        default:
 | 
			
		||||
            return state;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@@ -1,91 +0,0 @@
 | 
			
		||||
import { Action, Reducer } from 'redux';
 | 
			
		||||
import { AppThunkAction } from './';
 | 
			
		||||
 | 
			
		||||
// -----------------
 | 
			
		||||
// STATE - This defines the type of data maintained in the Redux store.
 | 
			
		||||
 | 
			
		||||
export interface WeatherForecastsState {
 | 
			
		||||
    isLoading: boolean;
 | 
			
		||||
    startDateIndex?: number;
 | 
			
		||||
    forecasts: WeatherForecast[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface WeatherForecast {
 | 
			
		||||
    date: string;
 | 
			
		||||
    temperatureC: number;
 | 
			
		||||
    temperatureF: number;
 | 
			
		||||
    summary: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -----------------
 | 
			
		||||
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
 | 
			
		||||
// They do not themselves have any side-effects; they just describe something that is going to happen.
 | 
			
		||||
 | 
			
		||||
interface RequestWeatherForecastsAction {
 | 
			
		||||
    type: 'REQUEST_WEATHER_FORECASTS';
 | 
			
		||||
    startDateIndex: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReceiveWeatherForecastsAction {
 | 
			
		||||
    type: 'RECEIVE_WEATHER_FORECASTS';
 | 
			
		||||
    startDateIndex: number;
 | 
			
		||||
    forecasts: WeatherForecast[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
 | 
			
		||||
// declared type strings (and not any other arbitrary string).
 | 
			
		||||
type KnownAction = RequestWeatherForecastsAction | ReceiveWeatherForecastsAction;
 | 
			
		||||
 | 
			
		||||
// ----------------
 | 
			
		||||
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
 | 
			
		||||
// They don't directly mutate state, but they can have external side-effects (such as loading data).
 | 
			
		||||
 | 
			
		||||
export const actionCreators = {
 | 
			
		||||
    requestWeatherForecasts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
 | 
			
		||||
        // Only load data if it's something we don't already have (and are not already loading)
 | 
			
		||||
        const appState = getState();
 | 
			
		||||
        if (appState && appState.weatherForecasts && startDateIndex !== appState.weatherForecasts.startDateIndex) {
 | 
			
		||||
            fetch(`weatherforecast`)
 | 
			
		||||
                .then(response => response.json() as Promise<WeatherForecast[]>)
 | 
			
		||||
                .then(data => {
 | 
			
		||||
                    dispatch({ type: 'RECEIVE_WEATHER_FORECASTS', startDateIndex: startDateIndex, forecasts: data });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            dispatch({ type: 'REQUEST_WEATHER_FORECASTS', startDateIndex: startDateIndex });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ----------------
 | 
			
		||||
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
 | 
			
		||||
 | 
			
		||||
const unloadedState: WeatherForecastsState = { forecasts: [], isLoading: false };
 | 
			
		||||
 | 
			
		||||
export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState | undefined, incomingAction: Action): WeatherForecastsState => {
 | 
			
		||||
    if (state === undefined) {
 | 
			
		||||
        return unloadedState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const action = incomingAction as KnownAction;
 | 
			
		||||
    switch (action.type) {
 | 
			
		||||
        case 'REQUEST_WEATHER_FORECASTS':
 | 
			
		||||
            return {
 | 
			
		||||
                startDateIndex: action.startDateIndex,
 | 
			
		||||
                forecasts: state.forecasts,
 | 
			
		||||
                isLoading: true
 | 
			
		||||
            };
 | 
			
		||||
        case 'RECEIVE_WEATHER_FORECASTS':
 | 
			
		||||
            // Only accept the incoming data if it matches the most recent request. This ensures we correctly
 | 
			
		||||
            // handle out-of-order responses.
 | 
			
		||||
            if (action.startDateIndex === state.startDateIndex) {
 | 
			
		||||
                return {
 | 
			
		||||
                    startDateIndex: action.startDateIndex,
 | 
			
		||||
                    forecasts: action.forecasts,
 | 
			
		||||
                    isLoading: false
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return state;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										39
									
								
								Birdmap/ClientApp/src/store/configureStore.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Birdmap/ClientApp/src/store/configureStore.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
var __assign = (this && this.__assign) || function () {
 | 
			
		||||
    __assign = Object.assign || function(t) {
 | 
			
		||||
        for (var s, i = 1, n = arguments.length; i < n; i++) {
 | 
			
		||||
            s = arguments[i];
 | 
			
		||||
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
 | 
			
		||||
                t[p] = s[p];
 | 
			
		||||
        }
 | 
			
		||||
        return t;
 | 
			
		||||
    };
 | 
			
		||||
    return __assign.apply(this, arguments);
 | 
			
		||||
};
 | 
			
		||||
var __spreadArrays = (this && this.__spreadArrays) || function () {
 | 
			
		||||
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
 | 
			
		||||
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
 | 
			
		||||
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
 | 
			
		||||
            r[k] = a[j];
 | 
			
		||||
    return r;
 | 
			
		||||
};
 | 
			
		||||
Object.defineProperty(exports, "__esModule", { value: true });
 | 
			
		||||
var redux_1 = require("redux");
 | 
			
		||||
var redux_thunk_1 = require("redux-thunk");
 | 
			
		||||
var connected_react_router_1 = require("connected-react-router");
 | 
			
		||||
var _1 = require("./");
 | 
			
		||||
function configureStore(history, initialState) {
 | 
			
		||||
    var middleware = [
 | 
			
		||||
        redux_thunk_1.default,
 | 
			
		||||
        connected_react_router_1.routerMiddleware(history)
 | 
			
		||||
    ];
 | 
			
		||||
    var rootReducer = redux_1.combineReducers(__assign(__assign({}, _1.reducers), { router: connected_react_router_1.connectRouter(history) }));
 | 
			
		||||
    var enhancers = [];
 | 
			
		||||
    var windowIfDefined = typeof window === 'undefined' ? null : window;
 | 
			
		||||
    if (windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__) {
 | 
			
		||||
        enhancers.push(windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__());
 | 
			
		||||
    }
 | 
			
		||||
    return redux_1.createStore(rootReducer, initialState, redux_1.compose.apply(void 0, __spreadArrays([redux_1.applyMiddleware.apply(void 0, middleware)], enhancers)));
 | 
			
		||||
}
 | 
			
		||||
exports.default = configureStore;
 | 
			
		||||
//# sourceMappingURL=configureStore.js.map
 | 
			
		||||
							
								
								
									
										1
									
								
								Birdmap/ClientApp/src/store/configureStore.js.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Birdmap/ClientApp/src/store/configureStore.js.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"file":"configureStore.js","sourceRoot":"","sources":["configureStore.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,+BAA+E;AAC/E,2CAAgC;AAChC,iEAAyE;AAEzE,uBAAgD;AAEhD,SAAwB,cAAc,CAAC,OAAgB,EAAE,YAA+B;IACpF,IAAM,UAAU,GAAG;QACf,qBAAK;QACL,yCAAgB,CAAC,OAAO,CAAC;KAC5B,CAAC;IAEF,IAAM,WAAW,GAAG,uBAAe,uBAC5B,WAAQ,KACX,MAAM,EAAE,sCAAa,CAAC,OAAO,CAAC,IAChC,CAAC;IAEH,IAAM,SAAS,GAAG,EAAE,CAAC;IACrB,IAAM,eAAe,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAa,CAAC;IAC7E,IAAI,eAAe,IAAI,eAAe,CAAC,4BAA4B,EAAE;QACjE,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,4BAA4B,EAAE,CAAC,CAAC;KAClE;IAED,OAAO,mBAAW,CACd,WAAW,EACX,YAAY,EACZ,eAAO,+BAAC,uBAAe,eAAI,UAAU,IAAM,SAAS,GACvD,CAAC;AACN,CAAC;AAtBD,iCAsBC"}
 | 
			
		||||
							
								
								
									
										8
									
								
								Birdmap/ClientApp/src/store/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Birdmap/ClientApp/src/store/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
Object.defineProperty(exports, "__esModule", { value: true });
 | 
			
		||||
exports.reducers = void 0;
 | 
			
		||||
// Whenever an action is dispatched, Redux will update each top-level application state property using
 | 
			
		||||
// the reducer with the matching name. It's important that the names match exactly, and that the reducer
 | 
			
		||||
// acts on the corresponding ApplicationState property type.
 | 
			
		||||
exports.reducers = {};
 | 
			
		||||
//# sourceMappingURL=index.js.map
 | 
			
		||||
							
								
								
									
										1
									
								
								Birdmap/ClientApp/src/store/index.js.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Birdmap/ClientApp/src/store/index.js.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAMA,sGAAsG;AACtG,wGAAwG;AACxG,4DAA4D;AAC/C,QAAA,QAAQ,GAAG,EACvB,CAAC"}
 | 
			
		||||
@@ -1,18 +1,13 @@
 | 
			
		||||
import * as WeatherForecasts from './WeatherForecasts';
 | 
			
		||||
import * as Counter from './Counter';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// The top-level state object
 | 
			
		||||
export interface ApplicationState {
 | 
			
		||||
    counter: Counter.CounterState | undefined;
 | 
			
		||||
    weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Whenever an action is dispatched, Redux will update each top-level application state property using
 | 
			
		||||
// the reducer with the matching name. It's important that the names match exactly, and that the reducer
 | 
			
		||||
// acts on the corresponding ApplicationState property type.
 | 
			
		||||
export const reducers = {
 | 
			
		||||
    counter: Counter.reducer,
 | 
			
		||||
    weatherForecasts: WeatherForecasts.reducer
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user