finish chores

This commit is contained in:
gidsi 2019-04-02 22:14:49 +02:00
commit 1cc4af9c1b
No known key found for this signature in database
GPG key ID: B47291090A6E5604
24 changed files with 542 additions and 224 deletions

View file

@ -7,7 +7,7 @@ RUN go install ./...
FROM alpine:latest FROM alpine:latest
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /go/bin/app . COPY --from=builder /go/bin/app .
COPY config.yaml config.yaml COPY config.yaml config.yaml

View file

@ -13,3 +13,5 @@ SpaceAPI extensions
=================== ===================
* Key `ext_ccc` describes Chaos Computer Club relation status (Example values: `"chaostreff"` or `"erfa"`) * Key `ext_ccc` describes Chaos Computer Club relation status (Example values: `"chaostreff"` or `"erfa"`)

View file

@ -15,7 +15,7 @@ func getCalendars() {
outputChan := parser.GetOutputChan() outputChan := parser.GetOutputChan()
calendar := Calendar{} calendar := Calendar{}
calendar.Space = spaceData.Space calendar.Space = spaceData.Space
events := []Event{} var events []Event
go func() { go func() {
for event := range outputChan { for event := range outputChan {
events = append(events, mapEventObject(event)) events = append(events, mapEventObject(event))

View file

@ -98,7 +98,7 @@ func readSpaceurl() []SpaceUrl {
return result return result
} }
func deleteSpaceurl(id string) { func deleteSpaceurl(url string) {
session, err := mgo.Dial(config.MongoDbServer) session, err := mgo.Dial(config.MongoDbServer)
if err != nil { if err != nil {
panic(err) panic(err)
@ -108,7 +108,7 @@ func deleteSpaceurl(id string) {
session.SetMode(mgo.Monotonic, true) session.SetMode(mgo.Monotonic, true)
c := session.DB(config.MongoDbDatabase).C("spaceurl") c := session.DB(config.MongoDbDatabase).C("spaceurl")
c.Remove(bson.M{"_id": id}) c.Remove(bson.M{"url": url})
} }
func readCalendar() []Calendar { func readCalendar() []Calendar {

View file

@ -15,7 +15,10 @@ var config = ConfigFile{}
func main() { func main() {
data, _ := ioutil.ReadFile("config.yaml") data, _ := ioutil.ReadFile("config.yaml")
yaml.Unmarshal(data, &config) err := yaml.Unmarshal(data, &config)
if err != nil {
panic("Can't load config")
}
config.SharedSecret = os.Getenv("SHARED_SECRET") config.SharedSecret = os.Getenv("SHARED_SECRET")
router := NewRouter() router := NewRouter()
@ -55,7 +58,7 @@ func SpaceUrlAdd(w http.ResponseWriter, r *http.Request) {
func SpaceUrlUpdate(w http.ResponseWriter, r *http.Request) { func SpaceUrlUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
SharedSecret := vars["SharedSecret"] SharedSecret := vars["SharedSecret"]
if(SharedSecret == config.SharedSecret) { if SharedSecret == config.SharedSecret {
spaceUrl := SpaceUrl{} spaceUrl := SpaceUrl{}
createEntry(&spaceUrl, w, r) createEntry(&spaceUrl, w, r)
updateSpaceurl(spaceUrl) updateSpaceurl(spaceUrl)
@ -65,9 +68,9 @@ func SpaceUrlUpdate(w http.ResponseWriter, r *http.Request) {
func SpaceUrlDelete(w http.ResponseWriter, r *http.Request) { func SpaceUrlDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
SharedSecret := vars["SharedSecret"] SharedSecret := vars["SharedSecret"]
Id := vars["id"] Url := vars["url"]
if SharedSecret == config.SharedSecret { if SharedSecret == config.SharedSecret {
deleteSpaceurl(Id) deleteSpaceurl(Url)
} }
} }
@ -77,11 +80,10 @@ func loadSpaceData() {
timestamp := time.Now().Unix() timestamp := time.Now().Unix()
for _, spaceUrl := range spaceUrls { for _, spaceUrl := range spaceUrls {
if(spaceUrl.Validated && int64(spaceUrl.LastUpdated + 60) < timestamp) { if spaceUrl.Validated && int64(spaceUrl.LastUpdated + 60) < timestamp {
spaceData := SpaceData{} spaceData := SpaceData{}
err := getJson(spaceUrl.Url, &spaceData) err := getJson(spaceUrl.Url, &spaceData)
if(err != nil) if err != nil {
{
log.Println(spaceUrl.Url) log.Println(spaceUrl.Url)
log.Println(err) log.Println(err)
} else { } else {

View file

@ -18,6 +18,5 @@ type Event struct {
Class string `json:"class"` Class string `json:"class"`
Url string `json:"url"` Url string `json:"url"`
Sequence int `json:"sequence"` Sequence int `json:"sequence"`
WholeDayEvent bool `json:"whileDayEvent"` WholeDayEvent bool `json:"wholeDayEvent"`
} }

View file

@ -52,7 +52,7 @@ var IndexRoutes = Routes{
Route{ Route{
"SpaceUrlDelete", "SpaceUrlDelete",
"DELETE", "DELETE",
"/urls/{id}/{SharedSecret}", "/urls/{url}/{SharedSecret}",
SpaceUrlDelete, SpaceUrlDelete,
}, },
Route{ Route{

View file

@ -1,12 +1,20 @@
version: "3" version: "3"
services: services:
frontend: frontend:
build: ./frontend
networks:
spaceapi-network:
ipv4_address: 172.16.238.10
image: gidsi/spaceapi-ccc-frontend:latest image: gidsi/spaceapi-ccc-frontend:latest
restart: always restart: always
expose:
- "80"
depends_on: depends_on:
- backend - backend
backend: backend:
build: ./backend build: ./backend
networks:
- spaceapi-network
image: gidsi/spaceapi-ccc-backend:latest image: gidsi/spaceapi-ccc-backend:latest
restart: always restart: always
environment: environment:
@ -15,6 +23,15 @@ services:
- database - database
database: database:
image: mongo:latest image: mongo:latest
networks:
- spaceapi-network
restart: always restart: always
volumes: volumes:
- /opt/eva:/data/db - /opt/eva:/data/db
networks:
spaceapi-network:
ipam:
driver: default
config:
- subnet: 172.16.238.0/24

View file

@ -8,4 +8,6 @@ npm install -g yarn
yarn install yarn install
yarn start yarn start
``` ```

View file

@ -9,7 +9,7 @@ http {
default_type application/octet-stream; default_type application/octet-stream;
sendfile on; sendfile on;
keepalive_timeout 65; keepalive_timeout 65;
gzip on; gzip on;
@ -44,16 +44,20 @@ http {
proxy_set_header Connection 'upgrade'; proxy_set_header Connection 'upgrade';
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade; proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
} }
location /map/tiles/ { location /map/tiles/ {
proxy_set_header Host b.basemaps.cartocdn.com; proxy_set_header Host b.basemaps.cartocdn.com;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection ""; proxy_set_header Connection "";
proxy_pass http://osm/dark_all/; proxy_pass http://osm/dark_all/;
proxy_cache osm; proxy_cache osm;
proxy_cache_valid 7d; proxy_cache_valid 7d;
expires 7d; expires 7d;
} }
} }
} }

View file

@ -3,28 +3,37 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"leaflet": "^1.0.3", "@material-ui/core": "^3.9.3",
"material-ui": "^0.17.4", "@material-ui/icons": "^3.0.2",
"moment": "^2.18.1", "leaflet": "^1.4.0",
"react": "^15.5.4", "moment": "^2.24.0",
"react-dom": "^15.5.4", "prop-types": "^15.7.2",
"react-leaflet": "^1.1.6", "react": "^16.8.6",
"react-redux": "^5.0.4", "react-dom": "^16.8.6",
"react-router-dom": "^4.1.1", "react-leaflet": "^2.2.1",
"react-tap-event-plugin": "^2.0.1", "react-redux": "^6.0.1",
"redux": "^3.6.0", "react-router-dom": "^5.0.0",
"redux-actions": "^2.0.2", "react-tap-event-plugin": "^3.0.3",
"redux-thunk": "^2.2.0", "react-virtualized": "^9.21.0",
"rrule": "^2.2.0", "redux": "^4.0.1",
"superagent": "^3.5.2" "redux-actions": "^2.6.5",
"redux-thunk": "^2.3.0",
"rrule": "^2.6.0",
"superagent": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
"react-scripts": "0.9.5" "react-scripts": "2.1.8"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --env=jsdom", "test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject" "eject": "react-scripts eject"
} },
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
} }

View file

@ -3,10 +3,8 @@ import {
BrowserRouter as Router, BrowserRouter as Router,
Route Route
} from 'react-router-dom'; } from 'react-router-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import { MuiThemeProvider } from '@material-ui/core/styles';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import theme from './style/theme'; import theme from './style/theme';
import store from './redux/store'; import store from './redux/store';
import IndexContainer from './views/Index'; import IndexContainer from './views/Index';
@ -14,10 +12,9 @@ import SpaceList from './views/SpaceList';
import UrlListView from './views/UrlListView'; import UrlListView from './views/UrlListView';
import layout from './layout'; import layout from './layout';
injectTapEventPlugin();
const App = () => ( const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme(theme)}> <MuiThemeProvider theme={theme}>
<Provider store={store}> <Provider store={store}>
<Router> <Router>
<div> <div>

View file

@ -1,26 +1,27 @@
import React from 'react'; /* import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Table, TableBody, TableRow, TableRowColumn } // import Table from '@material-ui/core/Table';
from 'material-ui/Table'; import TableBody from '@material-ui/core/TableBody';
import InfoIcon from 'material-ui/svg-icons/action/info-outline'; import TableRow from '@material-ui/core/TableRow';
import TableCell from '@material-ui/core/TableCell';
import PropTypes from 'prop-types';
import { AutoSizer, Column, SortDirection, Table } from 'react-virtualized';
import InfoIcon from '@material-ui/icons/InfoOutlined';
import { actions as calendarActions, eventStruct } from '../redux/modules/calendar'; import { actions as calendarActions, eventStruct } from '../redux/modules/calendar';
import { spacedataStruct } from '../redux/modules/spacedata'; import {actions as spaceDataActions, spacedataStruct} from '../redux/modules/spacedata';
const mapStateToProps = state => ({
events: state.calendars.items,
spacedata: state.spacedata,
});
class EventList extends React.Component { class EventList extends React.Component {
static propTypes = { static propTypes = {
events: React.PropTypes.arrayOf( events: PropTypes.arrayOf(
React.PropTypes.shape(eventStruct), PropTypes.shape(eventStruct),
), ),
fetchCalendars: React.PropTypes.func, fetchCalendars: PropTypes.func,
spacedata: spacedataStruct, spacedata: spacedataStruct,
}; };
defaultProps = { static defaultProps = {
events: [], events: [],
}; };
@ -33,15 +34,8 @@ class EventList extends React.Component {
render() { render() {
return ( return (
<Table <Table headerRenderer={()=>{}}>
selectable <TableBody>
multiSelectable
>
<TableBody
showRowHover
stripedRows
displayRowCheckbox={false}
>
{this.props.events {this.props.events
.filter(event => .filter(event =>
( (
@ -53,23 +47,23 @@ class EventList extends React.Component {
<TableRow <TableRow
key={event.importId + event.start.toLocaleString() + event.description} key={event.importId + event.start.toLocaleString() + event.description}
> >
<TableRowColumn style={{ width: '80px', padding: '5px' }}> <TableCell style={{ width: '80px', padding: '5px' }}>
{this.formatDate(event.start)} {this.formatDate(event.start)}
</TableRowColumn> </TableCell>
<TableRowColumn style={{ width: '55px', padding: '5px' }}> <TableCell style={{ width: '55px', padding: '5px' }}>
{event.wholeDayEvent ? null : this.formatTime(event.start)} {event.wholeDayEvent ? null : this.formatTime(event.start)}
</TableRowColumn> </TableCell>
<TableRowColumn> <TableCell>
{event.summary || event.description} {event.summary || event.description}
</TableRowColumn> </TableCell>
<TableRowColumn> <TableCell>
{event.space} {event.space}
</TableRowColumn> </TableCell>
<TableRowColumn style={{ textAlign: 'right' }}> <TableCell style={{ textAlign: 'right' }}>
{event.url && <a href={event.url}> {event.url && <a href={event.url}>
<InfoIcon style={{ cursor: 'pointer' }} /> <InfoIcon style={{ cursor: 'pointer' }} />
</a>} </a>}
</TableRowColumn> </TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
@ -78,6 +72,223 @@ class EventList extends React.Component {
} }
} }
export default connect(mapStateToProps, { export default connect(mapStateToProps, mapDispatchToProps)(EventList);
*/
import React from 'react';
import { connect } from 'react-redux';
import TableCell from '@material-ui/core/TableCell';
import PropTypes from 'prop-types';
import InfoIcon from '@material-ui/icons/InfoOutlined';
import { actions as calendarActions, eventStruct } from '../redux/modules/calendar';
import {actions as spaceDataActions, spacedataStruct} from '../redux/modules/spacedata';
import classNames from 'classnames';
import { withStyles } from '@material-ui/core/styles';
import { AutoSizer, Column, Table } from 'react-virtualized';
const styles = theme => ({
table: {
fontFamily: theme.typography.fontFamily,
},
flexContainer: {
display: 'flex',
alignItems: 'center',
boxSizing: 'border-box',
},
tableRow: {
border: 0,
},
tableRowHover: {
'&:hover': {
backgroundColor: theme.palette.grey[600],
},
},
tableCell: {
flex: 1,
color: '#fff',
},
noClick: {
cursor: 'initial',
},
});
class MuiVirtualizedTable extends React.PureComponent {
getRowClassName = ({ index }) => {
const { classes, rowClassName, onRowClick } = this.props;
return classNames(classes.tableRow, classes.flexContainer, rowClassName, {
[classes.tableRowHover]: index !== -1 && onRowClick != null,
});
};
cellRenderer = ({ cellData, columnIndex = null }) => {
const { columns, classes, rowHeight, onRowClick } = this.props;
return (
<TableCell
component="div"
className={classNames(classes.tableCell, classes.flexContainer, {
[classes.noClick]: onRowClick == null,
})}
variant="body"
style={{ height: rowHeight }}
align={(columnIndex != null && columns[columnIndex].numeric) || false ? 'right' : 'left'}
>
{cellData}
</TableCell>
);
};
render() {
const { classes, columns, ...tableProps } = this.props;
return (
<AutoSizer>
{({ height, width }) => (
<Table
className={classes.table}
height={height}
width={width}
{...tableProps}
rowClassName={this.getRowClassName}
>
{columns.map(({ cellContentRenderer = null, className, dataKey, ...other }, index) => {
let renderer;
if (cellContentRenderer != null) {
renderer = cellRendererProps =>
this.cellRenderer({
cellData: cellContentRenderer(cellRendererProps),
columnIndex: index,
});
} else {
renderer = this.cellRenderer;
}
return (
<Column
key={dataKey}
headerRenderer={() => {}}
className={classNames(classes.flexContainer, className)}
cellRenderer={renderer}
dataKey={dataKey}
{...other}
/>
);
})}
</Table>
)}
</AutoSizer>
);
}
}
MuiVirtualizedTable.propTypes = {
classes: PropTypes.object.isRequired,
columns: PropTypes.arrayOf(
PropTypes.shape({
cellContentRenderer: PropTypes.func,
dataKey: PropTypes.string.isRequired,
width: PropTypes.number.isRequired,
}),
).isRequired,
headerHeight: PropTypes.number,
onRowClick: PropTypes.func,
rowClassName: PropTypes.string,
rowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
sort: PropTypes.func,
};
MuiVirtualizedTable.defaultProps = {
rowHeight: 40,
headerHeight: 0,
};
const WrappedVirtualizedTable = withStyles(styles)(MuiVirtualizedTable);
const mapStateToProps = state => ({
events: state.calendars.items,
spacedata: state.spacedata,
});
const mapDispatchToProps = {
...calendarActions, ...calendarActions,
})(EventList); ...spaceDataActions,
};
class EventList extends React.Component {
static propTypes = {
events: PropTypes.arrayOf(
PropTypes.shape(eventStruct),
),
fetchCalendars: PropTypes.func,
spacedata: spacedataStruct,
};
static defaultProps = {
events: [],
};
componentWillMount() {
this.props.fetchCalendars();
}
formatDate = date => (date.format('DD.MM.YYYY'));
formatTime = date => (date.format('HH:mm'));
render() {
const rows = this.props.events.filter(event =>
(
this.props.spacedata.filter.indexOf(event.space) !== -1
|| this.props.spacedata.filter.length === 0
)
).map(event => {
return {
...event,
start_date: this.formatDate(event.start),
start_time: this.formatTime(event.start),
summary: event.summary || event.description,
link: event.url && <a href={event.url}>
<InfoIcon style={{ cursor: 'pointer', color: '#fff' }} />
</a>,
}
});
return (
<WrappedVirtualizedTable
rowCount={rows.length}
rowGetter={({ index }) => rows[index]}
columns={[
{
width: 120,
label: 'Date',
dataKey: 'start_date',
},
{
width: 120,
label: 'Time',
dataKey: 'start_time',
},
{
width: 120,
flexGrow: 1,
label: 'Summary',
dataKey: 'summary',
},
{
width: 120,
flexGrow: 1,
label: 'Carbs (g)',
dataKey: 'space',
},
{
width: 120,
label: 'Protein (g)',
dataKey: 'link',
},
]}
/>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EventList);

View file

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Map as LeafletMap, TileLayer } from 'react-leaflet'; import { Map as LeafletMap, TileLayer } from 'react-leaflet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Marker from './Marker'; import Marker from './Marker';
@ -15,8 +16,8 @@ const mapDispatchToProps = {
class Map extends React.Component { class Map extends React.Component {
static propTypes = { static propTypes = {
spacedata: spacedataStruct.isRequired, spacedata: spacedataStruct.isRequired,
fetchSpacedata: React.PropTypes.func.isRequired, fetchSpacedata: PropTypes.func.isRequired,
toggleFilterSpacedata: React.PropTypes.func.isRequired, toggleFilterSpacedata: PropTypes.func.isRequired,
}; };
componentWillMount() { componentWillMount() {
@ -29,7 +30,7 @@ class Map extends React.Component {
<LeafletMap <LeafletMap
center={centerGermany} center={centerGermany}
zoom={5} zoom={5}
style={{ width: '100vw', height: '50vh', margin: 0, padding: 0, maxWidth: '100%' }} style={{ width: '100vw', height: 'calc(50vh - 60px)', margin: 0, padding: 0, maxWidth: '100%' }}
> >
<TileLayer <TileLayer
url="https://spaceapi.ccc.de/map/tiles/{z}/{x}/{y}.png" url="https://spaceapi.ccc.de/map/tiles/{z}/{x}/{y}.png"
@ -40,8 +41,7 @@ class Map extends React.Component {
spacedata={spacedata} spacedata={spacedata}
key={spacedata.space} key={spacedata.space}
highlight={ highlight={
this.props.spacedata.filter.length === 0 this.props.spacedata.filter.indexOf(spacedata.space) !== -1}
|| this.props.spacedata.filter.indexOf(spacedata.space) !== -1}
toggleFilterSpacedata={this.props.toggleFilterSpacedata} toggleFilterSpacedata={this.props.toggleFilterSpacedata}
/> />
) )

View file

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { CircleMarker, Popup } from 'react-leaflet'; import { CircleMarker, Popup } from 'react-leaflet';
import theme from '../style/theme'; import theme from '../style/theme';
import { spacedataElementStruct } from '../redux/modules/spacedata'; import { spacedataElementStruct } from '../redux/modules/spacedata';
const Marker = (props) => { const Marker = (props) => {
const color = props.highlight ? theme.palette.accent2Color : theme.palette.primary1Color; const color = props.highlight ? theme.palette.secondary.light : theme.palette.primary.main;
const style = { const style = {
container: { container: {
@ -23,7 +24,10 @@ const Marker = (props) => {
radius={5} radius={5}
center={[props.spacedata.location.lat, props.spacedata.location.lon]} center={[props.spacedata.location.lat, props.spacedata.location.lon]}
> >
<Popup> <Popup
onOpen={() => props.toggleFilterSpacedata(props.spacedata.space)}
onClose={() => props.toggleFilterSpacedata(props.spacedata.space)}
>
<div style={style.container}> <div style={style.container}>
<div> <div>
{props.spacedata.space} {props.spacedata.space}
@ -40,7 +44,8 @@ const Marker = (props) => {
Marker.propTypes = { Marker.propTypes = {
spacedata: spacedataElementStruct.isRequired, spacedata: spacedataElementStruct.isRequired,
highlight: React.PropTypes.bool.isRequired, highlight: PropTypes.bool.isRequired,
toggleFilterSpacedata: PropTypes.func.isRequired,
}; };
export default Marker; export default Marker;

View file

@ -1,14 +1,15 @@
import React from 'react'; import React from 'react';
import request from 'superagent'; import request from 'superagent';
import TextField from 'material-ui/TextField'; import TextField from '@material-ui/core/TextField';
import FloatingActionButton from 'material-ui/FloatingActionButton'; import FloatingActionButton from '@material-ui/core/Fab';
import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentAdd from '@material-ui/icons/AddOutlined';
import Snackbar from 'material-ui/Snackbar'; import Snackbar from '@material-ui/core/Snackbar';
import PropTypes from 'prop-types';
import config from '../api/config'; import config from '../api/config';
class SpaceApiInput extends React.Component { class SpaceApiInput extends React.Component {
static propTypes = { static propTypes = {
style: React.PropTypes.shape({}), style: PropTypes.shape({}),
}; };
static defaultProps = { static defaultProps = {

View file

@ -1,15 +1,14 @@
import React from 'react'; import React from 'react';
import { import Table from '@material-ui/core/Table';
Table, import TableBody from '@material-ui/core/TableBody';
TableBody, import TableRow from '@material-ui/core/TableRow';
TableRow, import TableCell from '@material-ui/core/TableCell';
TableRowColumn, import PropTypes from 'prop-types';
} from 'material-ui/Table';
import { spacedataStruct } from '../redux/modules/spacedata'; import { spacedataStruct } from '../redux/modules/spacedata';
export class SpaceList extends React.Component { export class SpaceList extends React.Component {
static propTypes = { static propTypes = {
fetchSpacedata: React.PropTypes.func.isRequired, fetchSpacedata: PropTypes.func.isRequired,
spacedata: spacedataStruct, spacedata: spacedataStruct,
}; };
@ -28,34 +27,29 @@ export class SpaceList extends React.Component {
(a, b) => a.space.toUpperCase().localeCompare(b.space.toUpperCase()) (a, b) => a.space.toUpperCase().localeCompare(b.space.toUpperCase())
); );
return ( return (
<Table <div style={{ paddingTop:'60px' }}>
selectable <Table>
multiSelectable <TableBody>
> {items
<TableBody .map(space => (
showRowHover <TableRow key={space.space}>
stripedRows <TableCell style={{ color: '#fff' }}>
displayRowCheckbox={false} {space.space}
> </TableCell>
{items <TableCell>
.map(space => ( <a
<TableRow key={space.space}> href={space.url}
<TableRowColumn> style={{ textDecoration: 'none', color: 'white' }}
{space.space} >
</TableRowColumn> {space.url}
<TableRowColumn> </a>
<a </TableCell>
href={space.url} </TableRow>
style={{ textDecoration: 'none', color: 'white' }} )
> )}
{space.url} </TableBody>
</a> </Table>
</TableRowColumn> </div>
</TableRow>
)
)}
</TableBody>
</Table>
); );
} }
} }

View file

@ -1,43 +1,93 @@
import React from 'react'; import React from 'react';
import { import Toolbar from '@material-ui/core/Toolbar';
Toolbar as MuiToolbar, import AppBar from '@material-ui/core/AppBar';
ToolbarGroup, import Typography from '@material-ui/core/Typography';
ToolbarTitle, import IconButton from '@material-ui/core/IconButton';
} from 'material-ui/Toolbar'; import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import IconMenu from 'material-ui/IconMenu'; import Grow from '@material-ui/core/Grow';
import IconButton from 'material-ui/IconButton'; import Paper from '@material-ui/core/Paper';
import NavigationExpandMoreIcon from 'material-ui/svg-icons/navigation/expand-more'; import Popper from '@material-ui/core/Popper';
import MenuItem from 'material-ui/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import MenuIcon from '@material-ui/icons/Menu';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const Toolbar = () => (
<MuiToolbar>
<ToolbarTitle
text={'CCC Spaces'}
/>
<ToolbarGroup>
<IconMenu
iconButtonElement={
<IconButton touch>
<NavigationExpandMoreIcon />
</IconButton>
}
>
<MenuItem
primaryText={'Events'}
containerElement={<Link to="/" />}
/>
<MenuItem
primaryText={'Spaces'}
containerElement={<Link to="/list" />}
/>
<MenuItem
primaryText={'Impressum'}
href={'http://ccc.de/de/imprint'}
/>
</IconMenu>
</ToolbarGroup>
</MuiToolbar>
);
export default Toolbar; class MyToolbar extends React.Component {
state = {
open: false,
};
handleToggle = () => {
this.setState(state => ({ open: !state.open }));
};
handleClose = event => {
if (this.anchorEl.contains(event.target)) {
return;
}
this.setState({ open: false });
};
render() {
return (
<AppBar position="fixed">
<Toolbar>
<div style={{ width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Typography variant="h6" color="inherit">
CCC Spaces
</Typography>
<div>
<IconButton
buttonRef={node => {
this.anchorEl = node;
}}
aria-owns={this.state.open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={this.handleToggle}
>
<MenuIcon />
</IconButton>
<Popper open={this.state.open} anchorEl={this.anchorEl} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
id="menu-list-grow"
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={this.handleClose}>
<MenuList>
<MenuItem
onClick={() => window.location.href = '/'}
containerElement={<Link to="/" />}
>
Events
</MenuItem>
<MenuItem
onClick={() => window.location.href = '/list'}
containerElement={<Link to="/list" />}
>
Spaces
</MenuItem>
<MenuItem
onClick={() => window.location.href = 'http://ccc.de/de/imprint'}
>
Impressum
</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
</Toolbar>
</AppBar>
);
}
}
export default MyToolbar;

View file

@ -1,19 +1,19 @@
import React from 'react'; import React from 'react';
import { import Table from '@material-ui/core/Table';
Table, import TableBody from '@material-ui/core/TableBody';
TableBody, import TableRow from '@material-ui/core/TableRow';
TableRow, import TableCell from '@material-ui/core/TableCell';
TableRowColumn, import TextField from '@material-ui/core/TextField';
} from 'material-ui/Table'; import PropTypes from 'prop-types';
import TextField from 'material-ui/TextField'; import Button from '@material-ui/core/Button';
import FlatButton from 'material-ui/FlatButton';
import moment from 'moment'; import moment from 'moment';
import { spaceUrlStruct } from '../redux/modules/spaceurl'; import { spaceUrlStruct } from '../redux/modules/spaceurl';
export class UrlList extends React.Component { export class UrlList extends React.Component {
static propTypes = { static propTypes = {
fetchSpaceUrl: React.PropTypes.func.isRequired, fetchSpaceUrl: PropTypes.func.isRequired,
validateSpaceUrl: React.PropTypes.func.isRequired, validateSpaceUrl: PropTypes.func.isRequired,
deleteSpaceUrl: PropTypes.func.isRequired,
spaceurls: spaceUrlStruct, spaceurls: spaceUrlStruct,
}; };
@ -38,42 +38,46 @@ export class UrlList extends React.Component {
url: spaceUrl.url, url: spaceUrl.url,
validated: true, validated: true,
}; };
this.props.validateSpaceUrl(validatedSpaceUrl, this.secretInput.input.value); this.props.validateSpaceUrl(validatedSpaceUrl, this.state.secret);
};
deleteSpaceUrl = (spaceUrl) => {
this.props.deleteSpaceUrl(spaceUrl.url, this.state.secret);
}; };
render() { render() {
return ( return (
<div> <div>
<Table <Table>
selectable <TableBody>
multiSelectable
>
<TableBody
showRowHover
stripedRows
displayRowCheckbox={false}
>
{this.props.spaceurls.items {this.props.spaceurls.items
.map(spaceurl => ( .map(spaceurl => (
<TableRow key={spaceurl.url}> <TableRow key={spaceurl.url}>
<TableRowColumn> <TableCell>
<a <a
href={spaceurl.url} href={spaceurl.url}
style={{ color: 'white', textDecoration: 'none' }} style={{ color: 'white', textDecoration: 'none' }}
> >
{spaceurl.url} {spaceurl.url}
</a> </a>
</TableRowColumn> </TableCell>
<TableRowColumn> <TableCell>
{this.getFormatedDateTime(spaceurl.lastUpdated)} {this.getFormatedDateTime(spaceurl.lastUpdated)}
</TableRowColumn> </TableCell>
<TableRowColumn> <TableCell>
{!spaceurl.validated ? <FlatButton {!spaceurl.validated ? <Button
label={'validated'} onClick={() => this.validateSpaceUrl(spaceurl)}
onTouchTap={() => this.validateSpaceUrl(spaceurl)}
primary primary
/> : null} >validated</Button> : null}
</TableRowColumn> </TableCell>
<TableCell>
<Button
onClick={() => this.deleteSpaceUrl(spaceurl)}
primary
>
delete
</Button>
</TableCell>
</TableRow> </TableRow>
) )
)} )}
@ -81,6 +85,7 @@ export class UrlList extends React.Component {
</Table> </Table>
<TextField <TextField
name={'secret-input'} name={'secret-input'}
onChange={(event)=> this.setState({ secret: event.target.value })}
ref={ref => (this.secretInput = ref)} ref={ref => (this.secretInput = ref)}
/> />
</div> </div>

View file

@ -1,4 +1,4 @@
import { PropTypes } from 'react'; import PropTypes from 'prop-types';
import request from 'superagent'; import request from 'superagent';
import flatten from 'lodash/flatten'; import flatten from 'lodash/flatten';
import moment from 'moment'; import moment from 'moment';
@ -35,6 +35,9 @@ export const actions = {
fetchCalendars, fetchCalendars,
}; };
const formatDate = date => (date.format('DD.MM.YYYY'));
const formatTime = date => (date.format('HH:mm'));
export default handleActions({ export default handleActions({
[CALENDARS_FETCHED]: (state, { payload }) => { [CALENDARS_FETCHED]: (state, { payload }) => {
const items = flatten(flatten( const items = flatten(flatten(
@ -60,7 +63,7 @@ export default handleActions({
)); ));
} }
catch (ex) { catch (ex) {
console.log(ex); console.log(ex, event);
return []; return [];
} }
} }

View file

@ -1,4 +1,4 @@
import { PropTypes } from 'react'; import PropTypes from 'prop-types';
import request from 'superagent'; import request from 'superagent';
import { createAction, handleActions } from 'redux-actions'; import { createAction, handleActions } from 'redux-actions';
import config from '../../api/config'; import config from '../../api/config';

View file

@ -1,4 +1,4 @@
import { PropTypes } from 'react'; import PropTypes from 'prop-types';
import request from 'superagent'; import request from 'superagent';
import { createAction, handleActions } from 'redux-actions'; import { createAction, handleActions } from 'redux-actions';
import config from '../../api/config'; import config from '../../api/config';
@ -14,9 +14,11 @@ export const spaceUrlStruct = PropTypes.shape({
const SPACEURL_FETCHED = 'SPACEURL_FETCHED'; const SPACEURL_FETCHED = 'SPACEURL_FETCHED';
const SPACEURL_VALIDATE = 'SPACEURL_VALIDATE'; const SPACEURL_VALIDATE = 'SPACEURL_VALIDATE';
const SPACEURL_DELETE = 'SPACEURL_DELETE';
export const fetched = createAction(SPACEURL_FETCHED, result => result); export const fetched = createAction(SPACEURL_FETCHED, result => result);
export const validate = createAction(SPACEURL_VALIDATE, result => result); export const validate = createAction(SPACEURL_VALIDATE, result => result);
export const deleteSpace = createAction(SPACEURL_DELETE, result => result);
export const fetchSpaceUrl = () => (dispatch) => { export const fetchSpaceUrl = () => (dispatch) => {
request request
@ -45,9 +47,23 @@ export const validateSpaceUrl = (spaceUrl, secret) => (dispatch) => {
); );
}; };
export const deleteSpaceUrl = (spaceUrl, secret) => (dispatch) => {
request
.delete(`${config.api.url}/urls/spaceUrl/${secret}`)
.set('Content-Type', 'application/json')
.end(
(err) => {
if (!err) {
dispatch(deleteSpace(spaceUrl));
}
}
);
};
export const actions = { export const actions = {
fetchSpaceUrl, fetchSpaceUrl,
validateSpaceUrl, validateSpaceUrl,
deleteSpaceUrl,
}; };
export default handleActions({ export default handleActions({
@ -67,4 +83,5 @@ export default handleActions({
return newState; return newState;
}, },
[SPACEURL_DELETE]: (state, { payload }) => state.items.filter(ele => ele.url != payload.url),
}, { items: [] }); }, { items: [] });

View file

@ -1,23 +1,23 @@
import { fade } from 'material-ui/utils/colorManipulator'; import grey from '@material-ui/core/colors/grey';
import { spacing, colors } from 'material-ui/styles'; import green from '@material-ui/core/colors/green';
import red from '@material-ui/core/colors/red';
import { createMuiTheme } from '@material-ui/core/styles';
export default { export default createMuiTheme({
spacing, typography: {
fontFamily: 'Roboto, sans-serif', fontFamily: 'Roboto, sans-serif',
palette: { useNextVariants: true,
primary1Color: colors.grey900,
primary2Color: colors.grey900,
primary3Color: colors.grey600,
accent1Color: colors.grey500,
accent2Color: colors.grey500,
accent3Color: colors.grey100,
textColor: colors.fullWhite,
secondaryTextColor: fade(colors.fullWhite, 0.7),
alternateTextColor: '#303030',
canvasColor: '#303030',
borderColor: fade(colors.fullWhite, 0.3),
disabledColor: fade(colors.fullWhite, 0.3),
pickerHeaderColor: fade(colors.fullWhite, 0.12),
clockCircleColor: fade(colors.fullWhite, 0.12),
}, },
}; palette: {
primary: grey,
secondary: green,
error: red,
// Used by `getContrastText()` to maximize the contrast between the background and
// the text.
contrastThreshold: 3,
// Used to shift a color's luminance by approximately
// two indexes within its tonal palette.
// E.g., shift from Red 500 to Red 300 or Red 700.
tonalOffset: 0.2,
},
});

View file

@ -6,14 +6,14 @@ const style = {
container: { container: {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}, },
}; };
const IndexContainer = () => ( const IndexContainer = () => (
<div style={style.container}> <div style={style.container}>
<Map /> <div style={{ height: 'calc(50vh - 60px)', paddingTop: '60px'}}>
<Map />
</div>
<EventList /> <EventList />
</div> </div>
); );