diff --git a/src/app/assets_webpack/assistant/javascripts/components/FillInScreen.js b/src/app/assets_webpack/assistant/javascripts/components/FillInScreen.js index 6fafc8ac..7ca7dc85 100644 --- a/src/app/assets_webpack/assistant/javascripts/components/FillInScreen.js +++ b/src/app/assets_webpack/assistant/javascripts/components/FillInScreen.js @@ -5,6 +5,9 @@ export function getAvailableVerticalSpace(element) { const offsetTop = element.getBoundingClientRect().top + window.pageYOffset - document.documentElement.clientTop; return screenHeight - offsetTop; } +export function getAvailableHorizontalSpace(element){ + return element.offsetWidth - window.pageXOffset; +} /** * Creates scrollable container that vertically fills in the remaining screen space diff --git a/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-coordinates.js b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-coordinates.js new file mode 100644 index 00000000..27bb0167 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-coordinates.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import googleMapsCoordinatesRoutes from '../modules/visualizers/googleMaps/bundles/coordinates/applicationRoutes' + +initEntry(googleMapsCoordinatesRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-places.js b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-places.js new file mode 100644 index 00000000..2cbd7a14 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-places.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import googleMapsPlacesRoutes from '../modules/visualizers/googleMaps/bundles/places/applicationRoutes' + +initEntry(googleMapsPlacesRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-quantified-places.js b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-quantified-places.js new file mode 100644 index 00000000..b6e7bb12 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-quantified-places.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import googleMapsPlacesValuesRoutes from '../modules/visualizers/googleMaps/bundles/quantifiedPlaces/applicationRoutes' + +initEntry(googleMapsPlacesValuesRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-quantified-things.js b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-quantified-things.js new file mode 100644 index 00000000..22134af6 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps-quantified-things.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import googleMapsThingsPlacesValuesRoutes from '../modules/visualizers/googleMaps/bundles/quantifiedThings/applicationRoutes' + +initEntry(googleMapsThingsPlacesValuesRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/googleMaps.js b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps.js index be91cd25..bd1a5fbc 100644 --- a/src/app/assets_webpack/assistant/javascripts/entries/googleMaps.js +++ b/src/app/assets_webpack/assistant/javascripts/entries/googleMaps.js @@ -1,4 +1,4 @@ -import createRoutes from '../modules/visualizers/googleMaps/applicationRoutes' +import createRoutes from '../modules/visualizers/googleMaps/bundles/v1/applicationRoutes' import initEntry from '../misc/initEntry' initEntry(createRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/timeline-instants.js b/src/app/assets_webpack/assistant/javascripts/entries/timeline-instants.js new file mode 100644 index 00000000..2fb611ce --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/timeline-instants.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import timelineInstantsRoutes from '../modules/visualizers/timeline/bundles/instants/applicationRoutes' + +initEntry(timelineInstantsRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/timeline-intervals.js b/src/app/assets_webpack/assistant/javascripts/entries/timeline-intervals.js new file mode 100644 index 00000000..effc8ac7 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/timeline-intervals.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import timelineIntervalsRoutes from '../modules/visualizers/timeline/bundles/intervals/applicationRoutes' + +initEntry(timelineIntervalsRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-instants.js b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-instants.js new file mode 100644 index 00000000..a340952d --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-instants.js @@ -0,0 +1,5 @@ +import initEntry from '../misc/initEntry' +import timelineThingsInstantsRoutes from '../modules/visualizers/timeline/bundles/thingsInstants/applicationRoutes' + +initEntry(timelineThingsInstantsRoutes); + diff --git a/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-intervals.js b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-intervals.js new file mode 100644 index 00000000..40dad004 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-intervals.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import timelineThingsIntervalsRoutes from '../modules/visualizers/timeline/bundles/thingsIntervals/applicationRoutes' + +initEntry(timelineThingsIntervalsRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-things-instants.js b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-things-instants.js new file mode 100644 index 00000000..9391b995 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-things-instants.js @@ -0,0 +1,5 @@ +import initEntry from '../misc/initEntry' +import timelineThingsThingsInstantsRoutes from '../modules/visualizers/timeline/bundles/thingsThingsInstants/applicationRoutes' + +initEntry(timelineThingsThingsInstantsRoutes); + diff --git a/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-things-intervals.js b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-things-intervals.js new file mode 100644 index 00000000..1b881579 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/entries/timeline-things-things-intervals.js @@ -0,0 +1,4 @@ +import initEntry from '../misc/initEntry' +import timelineThingsThingsIntervalsRoutes from '../modules/visualizers/timeline/bundles/thingsThingsIntervals/applicationRoutes' + +initEntry(timelineThingsThingsIntervalsRoutes); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/api.js b/src/app/assets_webpack/assistant/javascripts/modules/app/api.js index cd276367..c108f945 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/app/api.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/api.js @@ -32,3 +32,8 @@ export async function getLabels(appId, resourceUris) { const result = await rest('commonVisualizer/getLabels/' + appId, { resourceUris }); return result.data.labels; } + +export async function getComments(appId, resourceUris) { + const result = await rest('commonVisualizer/getComments/' + appId, { resourceUris }); + return result.data.comments; +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Comment.js b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Comment.js new file mode 100644 index 00000000..d39fe498 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Comment.js @@ -0,0 +1,46 @@ +import React, { Component, PropTypes } from 'react' +import { createStructuredSelector } from 'reselect' +import { connect } from 'react-redux' +import { Map } from 'immutable' +import makePureRender from '../../../misc/makePureRender' +import LocalizedValue from './LocalizedValue' +import { isLocalizedValueEmpty } from '../misc/languageUtils' +import { commentsSelector, getComments } from '../ducks/comments' + +class Comment extends Component { + static propTypes = { + dispatch: PropTypes.func.isRequired, + uri: PropTypes.string.isRequired, + comment: PropTypes.any, + availableComments: PropTypes.instanceOf(Map).isRequired + }; + + load() { + // Prop 'comment' might be a "custom comment" or passed down from wherever, we don't care, + // if it's missing, we will try to load a new one from the server. + const { dispatch, uri, comment, availableComments } = this.props; + if (isLocalizedValueEmpty(comment) && !availableComments.has(uri)) { + dispatch(getComments([uri])); + } + } + + componentWillMount() { + this.load(); + } + + componentDidUpdate() { + this.load(); + } + + render() { + const { uri, comment, availableComments } = this.props; + const finalComment = isLocalizedValueEmpty(comment) ? availableComments.get(uri) : comment; + return + } +} + +const selector = createStructuredSelector({ + availableComments: commentsSelector +}); + +export default connect(selector)(makePureRender(Comment)); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Label.js b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Label.js index 4d12236f..cfd96a53 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Label.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/Label.js @@ -15,7 +15,7 @@ class Label extends Component { availableLabels: PropTypes.instanceOf(Map).isRequired }; - componentDidMount() { + load() { // Prop 'label' might be a "custom label" or passed down from wherever, we don't care, // if it's missing, we will try to load a new one from the server. const { dispatch, uri, label, availableLabels } = this.props; @@ -24,10 +24,18 @@ class Label extends Component { } } + componentWillMount() { + this.load(); + } + + componentDidUpdate() { + this.load(); + } + render() { const { uri, label, availableLabels } = this.props; const finalLabel = isLocalizedValueEmpty(label) ? availableLabels.get(uri) : label; - return + return } } diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/containers/LimiterContainer.js b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/LimiterContainer.js new file mode 100644 index 00000000..d26cd131 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/LimiterContainer.js @@ -0,0 +1,74 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' +import { limit_default, limitSelector, setLimit, setLimitReset } from '../ducks/limit' +import Button from '../../../components/Button' + +class LimiterContainer extends Component { + static propTypes = { + dispatch: PropTypes.func.isRequired, + limit: PropTypes.number.isRequired + }; + + componentWillUnmount() { + const {dispatch} = this.props; + + dispatch(setLimitReset()); + } + + componentDidUpdate() { + const {limit} = this.props; + var elements = document.getElementsByName('limit'); + if (elements.length > 0) { + elements[0].value = limit; + } + } + + setLimit() { + const {dispatch} = this.props; + + var elements = document.getElementsByName('limit'); + if (elements.length > 0) { + const value = parseInt(elements[0].value); + dispatch(setLimit(value)); + } + } + + resetLimit() { + const {dispatch, limit} = this.props; + dispatch(setLimitReset()); + + var elements = document.getElementsByName('limit'); + if (elements.length > 0) { + elements[0].value = limit_default; + } + }; + + render() { + const {limit} = this.props; + + var resetEnabled = limit != limit_default; + return
+ + + + + + + + +
LIMIT this.setLimit()}/>
+
+ + } +} + +const selector = createStructuredSelector({ + limit: limitSelector +}); + +export default connect(selector)(LimiterContainer); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/containers/ObjectInfo.js b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/ObjectInfo.js new file mode 100644 index 00000000..3d493a3a --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/containers/ObjectInfo.js @@ -0,0 +1,70 @@ +import React, { Component, PropTypes } from 'react' +import { createStructuredSelector } from 'reselect' +import { connect } from 'react-redux' +import { getLabels, labelsSelector } from '../ducks/labels' +import { commentsSelector, getComments } from '../ducks/comments' +import { Map as ImmutableMap } from 'immutable' +import LocalizedValue from './LocalizedValue' +import makePureRender from '../../../misc/makePureRender' + +// Shows all available info about an object. +class ObjectInfo extends Component { + static propTypes = { + dispatch: PropTypes.func.isRequired, + + header: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + + availableLabels: PropTypes.instanceOf(ImmutableMap).isRequired, + availableComments: PropTypes.instanceOf(ImmutableMap).isRequired + }; + + load() { + const { dispatch, url, availableLabels, availableComments } = this.props; + if (!availableLabels.has(url)) { + dispatch(getLabels([url])); + } + if (!availableComments.has(url)) { + dispatch(getComments([url])); + } + } + + componentWillMount() { + this.load(); + } + + componentDidUpdate() { + this.load(); + } + + render() { + const { header, url, availableComments, availableLabels } = this.props; + + return + + + + + + + + + + + + + + +
{header}: {url}
Label: + +
Comment: + +
+ } +} +const selector = createStructuredSelector({ + availableLabels: labelsSelector, + availableComments: commentsSelector +}); + +export default connect(selector)(makePureRender(ObjectInfo)); \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/comments.js b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/comments.js new file mode 100644 index 00000000..697fabac --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/comments.js @@ -0,0 +1,53 @@ +import { fromJS, Map as ImmutableMap } from 'immutable' +import { createSelector } from 'reselect' +import prefix from '../../core/prefix' +import createAction from '../../../misc/createAction' +import * as api from '../api' +import withApplicationId from '../misc/withApplicationId' +import { GET_APPLICATION_START } from './application' +import moduleSelector from '../selector' + +// Actions + +export const GET_COMMENTS = prefix('GET_COMMENTS'); +export const GET_COMMENTS_START = prefix('GET_COMMENTS_START'); +export const GET_COMMENTS_ERROR = prefix('GET_COMMENTS_ERROR'); +export const GET_COMMENTS_SUCCESS = prefix('GET_COMMENTS_SUCCESS'); + +let resourceUrisBuffer = []; + +export function getComments(resourceUris) { + return withApplicationId(id => dispatch => { + // Just like labels, use a buffer to cache uris and then make one big request. + resourceUrisBuffer = resourceUrisBuffer.concat(resourceUris); + setTimeout(() => { + if (resourceUrisBuffer.length > 0) { + const promise = api.getComments(id, resourceUrisBuffer); + dispatch(createAction(GET_COMMENTS, { promise })); + resourceUrisBuffer = []; + } + }, 200); + }) +} + +// Reducer + +const initialState = new ImmutableMap(); +export default function commentsReducer(state = initialState, action) { + switch (action.type) { + case GET_APPLICATION_START: + return initialState; + + case GET_COMMENTS_SUCCESS: + return state.mergeDeep(fromJS(action.payload)); + } + return state; +} + + +// Selectors + +export const commentsSelector = createSelector( + [moduleSelector], + parentState => parentState.comments +); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/configuration.js b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/configuration.js index 8e251b58..4816b206 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/configuration.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/configuration.js @@ -37,7 +37,8 @@ export const GET_CONFIGURATION_RESET = prefix('GET_CONFIGURATION_RESET'); const commonConfigurationSelector = createSelector( [moduleSelector], state => ({ - customLabels: state.customLabels.toJS() + customLabels: state.customLabels.toJS(), + limit: state.limit }) ); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/dirty.js b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/dirty.js index 180fd893..55aee002 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/dirty.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/dirty.js @@ -1,17 +1,20 @@ import { GET_APPLICATION_START } from './application' -import { SAVE_CONFIGURATION_START, SAVE_CONFIGURATION_ERROR, GET_CONFIGURATION_SUCCESS } from './configuration' +import { GET_CONFIGURATION_SUCCESS, SAVE_CONFIGURATION_ERROR, SAVE_CONFIGURATION_START } from './configuration' import { UPDATE_CUSTOM_LABEL } from './customLabels' +import { SET_LIMIT, SET_LIMIT_RESET } from './limit' export function createDirtyReducer(actions) { return function dirtyReducer(state = false, action) { // Custom actions provided by visualizer - if (actions.indexOf(action.type) !== -1) { + if (actions.indexOf(action.type) !== -1) { return true; } - + switch (action.type) { case UPDATE_CUSTOM_LABEL: case SAVE_CONFIGURATION_ERROR: + case SET_LIMIT: + case SET_LIMIT_RESET: return true; // We need to use the START event, not the SUCCESS, as the START event marks the snapshot diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/limit.js b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/limit.js new file mode 100644 index 00000000..78c12373 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/ducks/limit.js @@ -0,0 +1,41 @@ +import createAction from '../../../misc/createAction' +import prefix from '../prefix' +import moduleSelector from '../selector' +import { GET_APPLICATION_START } from './application' +import { createSelector } from 'reselect' +import { GET_CONFIGURATION_SUCCESS } from './configuration' + +export const limit_default = 50; + +// Actions +export const SET_LIMIT = prefix('SET_LIMIT'); +export const SET_LIMIT_RESET = SET_LIMIT + '_RESET'; + +export function setLimit(limit) { + return createAction(SET_LIMIT, { limit }); +} + +export function setLimitReset() { + return createAction(SET_LIMIT_RESET); +} + +// Reducer +const initialState = limit_default; +export default function limitReducer(state = initialState, action) { + switch (action.type) { + case GET_APPLICATION_START: + return initialState; + case SET_LIMIT_RESET: + return initialState; + case SET_LIMIT: + return action.payload.limit; + case GET_CONFIGURATION_SUCCESS: + if ('limit' in action.payload) { + return action.payload.limit; + } + } + return state; +}; + +// Selectors +export const limitSelector = createSelector([moduleSelector], state => state.limit); \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/app/reducer.js b/src/app/assets_webpack/assistant/javascripts/modules/app/reducer.js index ac9cdd82..6dd589d1 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/app/reducer.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/app/reducer.js @@ -1,15 +1,19 @@ -import { combineReducers } from 'redux'; +import { combineReducers } from 'redux' import application from './ducks/application' import lang from './ducks/lang' import labels from './ducks/labels' +import comments from './ducks/comments' import labelEditor from './ducks/labelEditor' import customLabels from './ducks/customLabels' +import limit from './ducks/limit' const rootReducer = combineReducers({ application, lang, labels, + comments, labelEditor, - customLabels + customLabels, + limit }); export default rootReducer; \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/common/components/ConfigurationToolbar.js b/src/app/assets_webpack/assistant/javascripts/modules/common/components/ConfigurationToolbar.js new file mode 100644 index 00000000..4d3d5dbf --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/common/components/ConfigurationToolbar.js @@ -0,0 +1,70 @@ +import React, { Component, PropTypes } from 'react' +import Drawer from 'material-ui/Drawer' +import { AppBar, IconButton, RaisedButton, Tab, Tabs } from 'material-ui' +import { NavigationClose } from 'material-ui/svg-icons/index' + +export default class ConfigurationToolbar extends Component { + static propTypes = { + hidden: PropTypes.bool.isRequired, + label: PropTypes.string.isRequired, + children: PropTypes.instanceOf(Map).isRequired + }; + + changeOpen() { + this.open = !this.open; + this.forceUpdate(); + } + + componentWillMount() { + this.open = false; + } + + render() { + const {children, label, hidden} = this.props; + + // Tabs + let tabs = []; + if (children.size > 0) { + for (const [label, value] of children) { + tabs.push({value}) + } + } + + // Close icon + let appBarIcon = this.changeOpen()}> + + + + // Drawer bug - if width is set, component ignores open property + let width = '0%'; + if (this.open) width = '30%'; + + let configButton = this.changeOpen()} + />; + + if (hidden) { + width = '0%'; + configButton =
+ } + + // Rendering + return ( +
+ {configButton} + + this.changeOpen()} + /> + + {tabs} + + +
+ ); + } +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/common/components/RecordSelector.js b/src/app/assets_webpack/assistant/javascripts/modules/common/components/RecordSelector.js new file mode 100644 index 00000000..642cf4b8 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/common/components/RecordSelector.js @@ -0,0 +1,218 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' +import { extractFromLocalizedValue } from '../../app/misc/languageUtils' +import { Map as immutableMap, Set as ImmutableSet } from 'immutable' +import { getLabels, labelsSelector } from '../../app/ducks/labels' +import { langSelector } from '../../app/ducks/lang' +import Button from '../../../components/Button' +import CenteredMessage from '../../../components/CenteredMessage' +import { CardHeader, Checkbox, List, ListItem, TextField } from 'material-ui' + +class RecordSelector extends Component { + static propTypes = { + dispatch: PropTypes.func.isRequired, + header: PropTypes.string.isRequired, + + // Labels + labels: PropTypes.instanceOf(immutableMap).isRequired, + language: PropTypes.string.isRequired, + + // Keys + records: PropTypes.array.isRequired, + getKey: PropTypes.func.isRequired, + selectedKeys: PropTypes.instanceOf(ImmutableSet).isRequired, + + // Selectors + onKeySelect: PropTypes.func.isRequired + }; + + + componentWillMount() { + this.needle = ''; + this.page = 0; + } + + // SEARCH + setNeedle() { + var needleWasEmpty = this.needle == ''; + var elements = document.getElementsByName('search'); + + if (elements.length > 0) + this.needle = elements[0].value.toLowerCase(); + + if (!needleWasEmpty || this.needle != '') { + this.forceUpdate(); + } + }; + + resetNeedle() { + this.needle = ''; + + var elements = document.getElementsByName('search'); + if (elements.length > 0) { + elements[0].value = ''; + } + this.forceUpdate(); + } + + getSearchComponent() { + var resetEnabled = (this.needle && this.needle != ''); + return
+ this.setNeedle()} hintText={'\tSearch ...'}/> +
+ } + + // === CHECKBOX LIST === + isChecked(key) { + const {selectedKeys} = this.props; + if (selectedKeys.contains(key)) { + return true; + } + return false; + } + + getLabel(key) { + const {dispatch, labels} = this.props; + if (!labels.has(key)) { + dispatch(getLabels([key])); + return key; + } + else { + return extractFromLocalizedValue(this.props.language, labels.get(key), key); + } + } + + getMatchingRecords(map) { + var matchingValues = new Map(); + + for (const [key, value] of map) { + if (value.toLowerCase().includes(this.needle)) { + matchingValues.set(key, value); + } + } + + return matchingValues; + } + + getValuesForVisualization() { + const {records, getKey} = this.props; + + // Deduplication + var map = new Map(records.map(t => { + var key = getKey(t); + return [key, this.getLabel(key)] + })); + + // Needle Search + if (this.needle && this.needle != '') { + return this.getMatchingRecords(map); + } + else return map; + } + + getCheckboxRows(valuesMap) { + const {onKeySelect} = this.props; + + var valuesMap = this.getValuesForVisualization(); + + var counter = 0; + var rows = []; + + if (valuesMap.size > 0) { + for (const [key, value] of valuesMap) { + + // Checkbox props + const checked = this.isChecked(key); + + // Row render - only for current page + var currentPage = parseInt(counter / 10); + if (currentPage == this.page) { + rows.push( + + onKeySelect(key)} + defaultChecked={checked} + label={value}/> + + ); + } + ++counter; + } + } + else rows = + No values found. + ; + + return rows; + } + + // Bottom navigation + getNavigationToolbar(totalPages) { + const nextEnabled = this.page < totalPages; + const nextFunc = () => { + ++this.page; + this.forceUpdate(); + }; + + const prevEnabled = this.page > 0; + const prevFunc = () => { + --this.page; + this.forceUpdate(); + }; + + return ( +
+ Page {this.page + 1} of {totalPages + 1} + + + + + + + +
+ +
+
+ ); + } + +// === RENDERING === + render() { + var valuesMap = this.getValuesForVisualization(); + var pages = parseInt(valuesMap.size / 10); + if (valuesMap.size > 0 && valuesMap.size % 10 == 0) --pages; + + return
+ {this.props.header} + {this.getSearchComponent()} + + {this.getCheckboxRows(valuesMap)} + + {this.getNavigationToolbar(pages)} +
+
+ } +} +const selector = createStructuredSelector({ + labels: labelsSelector, + language: langSelector +}); + +export default connect(selector)(RecordSelector); \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/common/utils/apiUtils.js b/src/app/assets_webpack/assistant/javascripts/modules/common/utils/apiUtils.js new file mode 100644 index 00000000..b6102e1a --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/common/utils/apiUtils.js @@ -0,0 +1,67 @@ +// Functions for parsing large API calls into more batch-sized calls. + +// Apply callbacks until limit reaches out. +export async function applyByBatchesWithLimit(arr, batchSize, limit, callback) { + var retArr = []; + var remaining = limit; + + // For calls with empty array + if (arr.length == 0) { + return await callback(arr, limit); + } + + for (var i = 0; i < arr.length / batchSize && remaining > 0; ++i) { + + // Boundaries + const maybeEnd = (i + 1) * batchSize; + const end = maybeEnd < arr.length ? maybeEnd : arr.length; + const begin = i * batchSize; + + // Call function with array slice + var slice = arr.slice(begin, end); + var result = await callback(slice, remaining); + + // Incorrect result => return only last result ( error state ) + if (!Array.isArray(result)) { + return result; + } + + // Correct result => append to retArr + retArr = retArr.concat(result); + + // Reevaluate limit + remaining -= result.length; + } + return retArr; +} + +// Apply callbacks & count total of values returned +export async function applyByBatchesCount(arr, batchSize, callback) { + var retCount = {value: 0}; + + // For calls with empty array + if (arr.length == 0) { + return await callback(arr); + } + + for (var i = 0; i < arr.length / batchSize; ++i) { + + // Boundaries + const maybeEnd = (i + 1) * batchSize; + const end = maybeEnd < arr.length ? maybeEnd : arr.length; + const begin = i * batchSize; + + // Call function with array slice + var slice = arr.slice(begin, end); + var result = await callback(slice); + + // Incorrect result => return only last result ( error state ) + if (!Number.isInteger(result.value)) { + return result; + } + + // Correct result => append to retArr + retCount.value += result.value; + } + return retCount; +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/common/utils/arrayUtils.js b/src/app/assets_webpack/assistant/javascripts/modules/common/utils/arrayUtils.js new file mode 100644 index 00000000..2bf9cd6e --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/common/utils/arrayUtils.js @@ -0,0 +1,8 @@ +export function getDistinctCount(getKey, array) { + var set = new Set(); + for (var element of array) { + var key = getKey(element); + if (!set.has(key)) set.add(key); + } + return set.size; +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/actions.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/actions.js index 6248a422..40a7481b 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/actions.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/actions.js @@ -26,9 +26,9 @@ export function queryDataset() { const conceptUris = skosConcepts[property.schemeUri].map(concept => concept.uri); const getSkosConceptsCountsPromise = api.getSkosConceptsCounts(appId, property.uri, conceptUris) - .then(counts => ({ - [property.uri]: counts - })); + .then(counts => ({ + [property.uri]: counts + })); dispatch(createAction(GET_SKOS_CONCEPTS_COUNTS, { promise: getSkosConceptsCountsPromise }, diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/api.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/api.js index 828fab86..2458508a 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/api.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/api.js @@ -1,4 +1,7 @@ import rest from '../../../misc/rest' +import { applyByBatchesCount, applyByBatchesWithLimit } from '../../common/utils/apiUtils' + +const BATCH_SIZE = 100; export async function getProperties(id) { const result = await rest('mapsVisualizer/getProperties/' + id, {}); @@ -16,6 +19,113 @@ export async function getSkosConceptsCounts(id, propertyUri, conceptUris) { } export async function getMarkers(id, mapQueryData) { - const result = await rest('mapsVisualizer/getMarkers/' + id, mapQueryData); + const result = await rest('mapsVisualizer/getMarkers/' + id, mapQueryData); return result.data.markers; } + +export async function getCoordinates(id, urls, limit) { + const batchFunc = async function (batchUrls, batchLimit) { + let payload = { + 'urls': batchUrls, + 'limit': batchLimit + }; + const result = await rest('mapsVisualizer/getCoordinates/' + id, payload); + return result.data.coordinates; + }; + return applyByBatchesWithLimit(urls, BATCH_SIZE, limit, batchFunc); +} + +export async function getPlaces(id, urls, placesTypes, limit) { + const batchFunc = async function (batchUrls, batchLimit) { + let payload = { + 'urls': batchUrls, + 'placeTypes': placesTypes, + 'limit': batchLimit + }; + const result = await rest('mapsVisualizer/getPlaces/' + id, payload); + return result.data.places; + }; + return applyByBatchesWithLimit(urls, BATCH_SIZE, limit, batchFunc); +} + +export async function getQuantifiedThings(id, urls, valuePredicates, placePredicates, limit) { + const batchFunc = async function (batchUrls, batchLimit) { + let payload = { + 'urls': batchUrls, + 'valuePredicates': valuePredicates, + 'placePredicates': placePredicates, + 'limit': batchLimit + }; + const result = await rest('mapsVisualizer/getQuantifiedThings/' + id, payload); + return result.data.quantifiedThings; + }; + return applyByBatchesWithLimit(urls, BATCH_SIZE, limit, batchFunc); +} + +export async function getQuantifiedPlaces(id, urls, placeTypes, valuePredicates, limit) { + const batchFunc = async function (batchUrls, batchLimit) { + let payload = { + 'urls': batchUrls, + 'placeTypes': placeTypes, + 'valuePredicates': valuePredicates, + 'limit': batchLimit + }; + const result = await rest('mapsVisualizer/getQuantifiedPlaces/' + id, payload); + return result.data.quantifiedPlaces; + }; + return applyByBatchesWithLimit(urls, BATCH_SIZE, limit, batchFunc); +} + +export async function getCoordinatesCount(id, urls) { + const batchFunc = async function (batchUrls) { + + let payload = { + 'urls': batchUrls, + 'limit': -1 + }; + const result = await rest('mapsVisualizer/getCoordinates/count/' + id, payload); + return result.data.count; + }; + return applyByBatchesCount(urls, BATCH_SIZE, batchFunc); +} + +export async function getPlacesCount(id, urls, placesTypes) { + const batchFunc = async function (batchUrls) { + let payload = { + 'urls': batchUrls, + 'placeTypes': placesTypes, + 'limit': -1 + }; + const result = await rest('mapsVisualizer/getPlaces/count/' + id, payload); + return result.data.count; + }; + return applyByBatchesCount(urls, BATCH_SIZE, batchFunc); +} + +export async function getQuantifiedThingsCount(id, urls, valuePredicates, placePredicates) { + const batchFunc = async function (batchUrls) { + let payload = { + 'urls': batchUrls, + 'valuePredicates': valuePredicates, + 'placePredicates': placePredicates, + 'limit': -1 + }; + const result = await rest('mapsVisualizer/getQuantifiedThings/count/' + id, payload); + return result.data.count; + }; + return applyByBatchesCount(urls, BATCH_SIZE, batchFunc); +} + +export async function getQuantifiedPlacesCount(id, urls, placeTypes, valuePredicates) { + const batchFunc = async function (batchUrls) { + let payload = { + 'urls': batchUrls, + 'placeTypes': placeTypes, + 'valuePredicates': valuePredicates, + 'limit': -1 + }; + const result = await rest('mapsVisualizer/getQuantifiedPlaces/count/' + id, payload); + return result.data.count; + }; + return applyByBatchesCount(urls, BATCH_SIZE, batchFunc); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/applicationRoutes.js deleted file mode 100644 index 508988ea..00000000 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/applicationRoutes.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { Route, IndexRoute } from 'react-router' -import ApplicationLoader from '../../app/pages/ApplicationLoader' -import NotFound from '../../platform/pages/NotFound' -import Standalone from './pages/Standalone' -import Embed from './pages/Embed' - -export default function createRoutes(dispatch) { - return ( - - - - - - ); -} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/applicationRoutes.js new file mode 100644 index 00000000..11883f3d --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import Coordinates from '../../pages/Coordinates' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/configuratorRoutes.js new file mode 100644 index 00000000..47fbddc4 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import Coordinates from '../../pages/Coordinates' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/prefix.js new file mode 100644 index 00000000..5ceaafa8 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/coordinates/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-coordinates'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/applicationRoutes.js new file mode 100644 index 00000000..8c4cef9d --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import Places from '../../pages/Places' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/configuratorRoutes.js new file mode 100644 index 00000000..830121d8 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import Places from '../../pages/Places' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/prefix.js new file mode 100644 index 00000000..4be71b8c --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/places/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-places'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/applicationRoutes.js new file mode 100644 index 00000000..24cd1485 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import QuantifiedPlaces from '../../pages/QuantifiedPlaces' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/configuratorRoutes.js new file mode 100644 index 00000000..b1c01191 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import QuantifiedPlaces from '../../pages/QuantifiedPlaces' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/prefix.js new file mode 100644 index 00000000..8ad6e6ac --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedPlaces/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-quantified-places'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/applicationRoutes.js new file mode 100644 index 00000000..c0e4739c --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import QuantifiedThings from '../../pages/QuantifiedThings' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/configuratorRoutes.js new file mode 100644 index 00000000..81362911 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import QuantifiedThings from '../../pages/QuantifiedThings' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/prefix.js new file mode 100644 index 00000000..0eddcda9 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/quantifiedThings/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-quantified-things'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/applicationRoutes.js new file mode 100644 index 00000000..6a6b4bef --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/applicationRoutes.js @@ -0,0 +1,16 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import Standalone from '../../pages/Standalone' +import Embed from '../../pages/Embed' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/configuratorRoutes.js similarity index 55% rename from src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/configuratorRoutes.js rename to src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/configuratorRoutes.js index 3afc92fa..d080ba3d 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/configuratorRoutes.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/configuratorRoutes.js @@ -1,10 +1,10 @@ import React from 'react' import { Route } from 'react-router' -import Configurator from './pages/Configurator' +import Configurator from '../../pages/Configurator' import { MODULE_PREFIX } from './prefix' export default function createRoutes(dispatch) { - return ( - - ); + return ( + + ); } diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/prefix.js new file mode 100644 index 00000000..471b17ef --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/bundles/v1/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/GoogleMapsCircles.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/GoogleMapsCircles.js new file mode 100644 index 00000000..70670fb9 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/GoogleMapsCircles.js @@ -0,0 +1,226 @@ +import React, { Component, PropTypes } from 'react' +import { connect, Provider } from 'react-redux' +import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable' +import { Circle, InfoWindow } from 'react-google-maps' +import GoogleMap from '../../../../components/GoogleMap' +import { mapStateSelector, updateMapState } from '../ducks/mapState' +import { toggledMarkersSelector, toggleMarker } from '../ducks/toggledMarkers' +import { coordinatesSelector } from '../ducks/coordinates' +import MapMarker from './MapMarkerInfoWindow' +import { MapState } from '../models' +import { createStructuredSelector } from 'reselect' +import { placesSelector } from '../ducks/places' +import { quantifiedThingsSelector } from '../ducks/quantifiedThings' +import { quantifiedPlacesSelector } from '../ducks/quantifiedPlaces' +import { colorsSelector, setColors, setColorsReset } from '../ducks/colors' + +class GoogleMapsMarkers extends Component { + static propTypes = { + dispatch: PropTypes.func.isRequired, + + coordinates: PropTypes.instanceOf(Array).isRequired, + places: PropTypes.array.isRequired, + quantifiedThings: PropTypes.array.isRequired, + quantifiedPlaces: PropTypes.array.isRequired, + + mapState: PropTypes.instanceOf(MapState).isRequired, + toggledMarkers: PropTypes.instanceOf(ImmutableSet).isRequired, + + colors: PropTypes.instanceOf(ImmutableMap).isRequired + }; + + static contextTypes = { + store: PropTypes.object.isRequired + }; + + getColor(url) { + if (this.colors.has(url)) { + return this.colors.get(url); + } + else { + var color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6); + this.colors = this.colors.set(url, color); + return color; + } + } + + // === MARKERS === + infoWindow(coors) { + const { toggledMarkers, dispatch } = this.props; + + const position = { + lat: parseFloat(coors.latitude.toFixed(5)), + lng: parseFloat(coors.longitude.toFixed(5)) + }; + + if (toggledMarkers.contains(coors.url)) { + return ( + dispatch(toggleMarker(coors.url))} + position={position} + > + + + + + ); + } + + return ''; + } + + getQuantifiedThingConnection(url) { + const { places, quantifiedThings } = this.props; + + for (const place of places) { + if (place.coordinates == url) { + for (const thing of quantifiedThings) { + if (thing.place == place.url) { + return { thing: thing, place: place }; + } + } + } + } + return null; + } + + getQuantifiedPlaceConnection(url) { + const { quantifiedPlaces } = this.props; + + for (const place of quantifiedPlaces) { + if (place.coordinates == url) { + return { quantifiedPlace: place }; + } + } + return null; + } + + // === CIRCLES === + getMaxOfAllValues() { + const { quantifiedPlaces, quantifiedThings } = this.props; + + var qpvMax = Math.max.apply(null, quantifiedPlaces.map(p => p.value)); + var qtvMax = Math.max.apply(null, quantifiedThings.map(t => t.value)); + + return (qtvMax > qpvMax) ? qtvMax : qpvMax; + } + + circle(coordinates, maxValue) { + const { dispatch, mapState } = this.props; + + // Adjusting radius & stroke to zoom levels + const nonZeroZoom = 1 + mapState.zoomLevel; + + const MAX_RADIUS = (1000 / (nonZeroZoom * nonZeroZoom)) * 1000; // Radius is in meters... + const MAX_STROKE_W = nonZeroZoom; + + var value = 0; + var placeType = 'default_place_type'; + var valuePredicate = 'default_value_predicate'; + + // Values for radius, Place type & Value predicate for colors + + // From quantified thing + var qt = this.getQuantifiedThingConnection(coordinates.url); + + if (qt != null) { + value = qt.thing.value; + placeType = qt.place.placeType; + valuePredicate = qt.thing.valuePredicate; + } + + // From quantifiedPlace + var qp = this.getQuantifiedPlaceConnection(coordinates.url); + if (qp != null) { + value = qp.quantifiedPlace.value; + placeType = qp.quantifiedPlace.placeType; + valuePredicate = qp.quantifiedPlace.valuePredicate; + } + + // Map radius to (0,maxRadius) + const radius = (value / maxValue) * MAX_RADIUS; + + // Stroke + const strokeWidth = 1 + parseInt((value / maxValue) * MAX_STROKE_W); + + // Colors + const strokeColor = this.getColor(valuePredicate); + const fillColor = this.getColor(placeType); + + // Render the circle. + const position = { + lat: parseFloat(coordinates.latitude.toFixed(5)), + lng: parseFloat(coordinates.longitude.toFixed(5)) + }; + return ( + dispatch(toggleMarker(coordinates.url))} + options={{ + strokeColor: strokeColor, + strokeOpacity: 1, + strokeWeight: strokeWidth, + fillColor: fillColor, + fillOpacity: 0.5 + }} + /> + ); + } + + // === RENDER === + componentDidMount() { + this.colors = this.props.colors; + } + + componentWillReceiveProps(nextProps) { + if (nextProps.colors != this.props.colors) { + this.colors = nextProps.colors; + } + } + + componentDidUpdate() { + const { dispatch } = this.props; + if (this.props.colors != this.colors) { + dispatch(setColors(this.colors)); + } + } + + componentWillUnmount() { + const { dispatch } = this.props; + dispatch(setColorsReset()); + } + + render() { + const { dispatch, coordinates, mapState } = this.props; + + var maxValue = this.getMaxOfAllValues(); + return ( + dispatch(updateMapState({ zoomLevel }))} + onCenterChanged={center => dispatch(updateMapState({ center }))} + defaultZoom={mapState.zoomLevel} + defaultCenter={mapState.center.toJS()} + > + {coordinates.map(c => this.infoWindow(c))} + {coordinates.map(c => this.circle(c, maxValue))} + + ); + }; +} + +const selector = createStructuredSelector({ + coordinates: coordinatesSelector, + + places: placesSelector, + quantifiedThings: quantifiedThingsSelector, + quantifiedPlaces: quantifiedPlacesSelector, + + toggledMarkers: toggledMarkersSelector, + mapState: mapStateSelector, + + colors: colorsSelector +}); +export default connect(selector)(GoogleMapsMarkers); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/GoogleMapsMarkers.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/GoogleMapsMarkers.js new file mode 100644 index 00000000..8e705785 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/GoogleMapsMarkers.js @@ -0,0 +1,88 @@ +import React, { Component, PropTypes } from 'react' +import { connect, Provider } from 'react-redux' +import { Set as ImmutableSet } from 'immutable' +import { InfoWindow, Marker } from 'react-google-maps' +import MarkerClusterer from 'react-google-maps/lib/addons/MarkerClusterer' +import GoogleMap from '../../../../components/GoogleMap' +import { mapStateSelector, updateMapState } from '../ducks/mapState' +import { toggledMarkersSelector, toggleMarker } from '../ducks/toggledMarkers' +import { coordinatesSelector } from '../ducks/coordinates' +import MapMarker from './MapMarkerInfoWindow' +import { MapState } from '../models' +import { createStructuredSelector } from 'reselect' + +class GoogleMapsMarkers extends Component { + static propTypes = { + dispatch: PropTypes.func.isRequired, + + coordinates: PropTypes.instanceOf(Array).isRequired, + + mapState: PropTypes.instanceOf(MapState).isRequired, + toggledMarkers: PropTypes.instanceOf(ImmutableSet).isRequired + }; + + static contextTypes = { + store: PropTypes.object.isRequired + }; + + infoWindow(url) { + const { toggledMarkers, dispatch } = this.props; + if (toggledMarkers.contains(url)) { + return ( + dispatch(toggleMarker(url))}> + + + + + + ); + } + return ''; + } + + marker(coors) { + const { dispatch } = this.props; + + const position = { + lat: parseFloat(coors.latitude.toFixed(5)), + lng: parseFloat(coors.longitude.toFixed(5)) + }; + + return ( + dispatch(toggleMarker(coors.url))} + defaultAnimation={null}> + {this.infoWindow(coors.url)} + + ) + } + + render() { + const { dispatch, coordinates, mapState } = this.props; + return ( + dispatch(updateMapState({ zoomLevel }))} + onCenterChanged={center => dispatch(updateMapState({ center }))} + defaultZoom={mapState.zoomLevel} + defaultCenter={mapState.center.toJS()}> + + + {coordinates.map(c => this.marker(c))} + + + ); + }; +} + +const selector = createStructuredSelector({ + coordinates: coordinatesSelector, + toggledMarkers: toggledMarkersSelector, + mapState: mapStateSelector +}); +export default connect(selector)(GoogleMapsMarkers); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/MapMarkerInfoWindow.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/MapMarkerInfoWindow.js new file mode 100644 index 00000000..2cad96eb --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/MapMarkerInfoWindow.js @@ -0,0 +1,116 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' +import { coordinatesSelector } from '../ducks/coordinates' +import { placesSelector } from '../ducks/places' +import { quantifiedThingsSelector } from '../ducks/quantifiedThings' +import makePureRender from '../../../../misc/makePureRender' +import { quantifiedPlacesSelector } from '../ducks/quantifiedPlaces' +import ObjectInfo from '../../../app/containers/ObjectInfo' + +class MapMarkerInfoWindow extends Component { + static propTypes = { + url: PropTypes.string.isRequired, + coordinates: PropTypes.array.isRequired, + places: PropTypes.array.isRequired, + quantifiedThings: PropTypes.array.isRequired, + quantifiedPlaces: PropTypes.array.isRequired, + }; + + coordinate() { + const { url, coordinates } = this.props; + for (const c of coordinates) { + if (c.url == url) { + return
+ +
+ Latitude: + {c.latitude}
+ Longitude: + {c.longitude}
+
+
+ } + } + return
+ } + + // Places connected to the coordinates + connectPlaces() { + const { places, quantifiedPlaces, url } = this.props; + + // Places + Quantified Things connected to them + var connectedPlaces = []; + for (const p of places) { + if (p.coordinates == url) { + connectedPlaces.push( +
+ +
+ {this.connectQuantifiedThings(p.url)} +
+ ); + } + } + + // Quantified places + for (const p of quantifiedPlaces) { + if (p.coordinates == url) { + connectedPlaces.push( +
+ +
+ Value: + {p.value}
+
+ +
+
+ ); + } + } + return connectedPlaces; + } + + // Quantified things connected to the place + connectQuantifiedThings(placeUrl) { + const { quantifiedThings } = this.props; + + var connectedQuantifiedThings = []; + for (const t of quantifiedThings) { + if (t.place == placeUrl) { + connectedQuantifiedThings.push( +
+ +
+ +
+ Value: + {t.value}
+
+ +
+
+ ); + } + } + return connectedQuantifiedThings; + } + + render() { + return
+
+ {this.coordinate()} + {this.connectPlaces()} +
+ } +} + +const selector = createStructuredSelector({ + coordinates: coordinatesSelector, + places: placesSelector, + quantifiedThings: quantifiedThingsSelector, + quantifiedPlaces: quantifiedPlacesSelector +}); + +export default connect(selector)(makePureRender(MapMarkerInfoWindow)); \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/ToolbarV2.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/ToolbarV2.js new file mode 100644 index 00000000..97308735 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/components/ToolbarV2.js @@ -0,0 +1,36 @@ +import React, { Component, PropTypes } from 'react' +import ConfigurationToolbar from '../../../common/components/ConfigurationToolbar' +import OpenEmbedAppDialogButton from './OpenEmbedAppDialogButton' +import EmbedAppDialog from '../containers/EmbedAppDialog' +import SaveButton from '../containers/SaveButton' +import BodyPadding from '../../../../components/BodyPadding' + +class ToolbarV2 extends Component { + static propTypes = { + hidden: PropTypes.bool.isRequired, + configurations: PropTypes.instanceOf(Map).isRequired + }; + + render() { + const { configurations, hidden } = this.props; + + if (hidden) { + return
; Standalone.propTypes = { // As the component is at first initialized by the router, these props might not be available diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/reducer.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/reducer.js index 6aa39b66..96c81cf0 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/reducer.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/googleMaps/reducer.js @@ -1,9 +1,19 @@ -import { combineReducers } from 'redux'; +import { combineReducers } from 'redux' import markers from './ducks/markers' import filters from './ducks/filters' import mapState from './ducks/mapState' import toggledMarkers from './ducks/toggledMarkers' import publishSettings from './ducks/publishSettings' +import coordinates from './ducks/coordinates' +import places from './ducks/places' +import quantifiedThings from './ducks/quantifiedThings' +import quantifiedPlaces from './ducks/quantifiedPlaces' +import selectedPlacePredicates from './ducks/selectedPlacePredicates' +import selectedValuePredicates from './ducks/selectedValuePredicates' +import selectedPlaceTypes from './ducks/selectedPlaceTypes' +import selectedQuantifiedThings from './ducks/selectedQuantifiedThings' +import colors from './ducks/colors' +import count from './ducks/counts' import dirty from './ducks/dirty' const rootReducer = combineReducers({ @@ -12,6 +22,16 @@ const rootReducer = combineReducers({ mapState, toggledMarkers, publishSettings, + coordinates, + places, + quantifiedThings, + quantifiedPlaces, + selectedQuantifiedThings, + selectedPlacePredicates, + selectedValuePredicates, + selectedPlaceTypes, + colors, + count, dirty }); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/reducer.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/reducer.js index 38aa3cc2..6e524cc6 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/reducer.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/reducer.js @@ -1,9 +1,11 @@ -import { combineReducers } from 'redux'; +import { combineReducers } from 'redux' import googleMaps from './googleMaps/reducer' import chord from './chord/reducer' +import timeline from './timeline/reducer' const rootReducer = combineReducers({ googleMaps, - chord + chord, + timeline }); export default rootReducer; \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/routes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/routes.js index 201c205a..70a5960b 100644 --- a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/routes.js +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/routes.js @@ -2,8 +2,21 @@ import React from 'react' import { routeActions } from 'redux-simple-router' import ConfiguratorsRouteFactory from './utils/ConfiguratorsRouteFactory' import dataCubeRoutes from './datacube/configuratorRoutes' -import googleMapsRoutes from './googleMaps/configuratorRoutes' import chordRoutes from './chord/configuratorRoutes' + +import googleMapsV1Routes from './googleMaps/bundles/v1/configuratorRoutes' +import googleMapsCoordinatesRoutes from './googleMaps/bundles/coordinates/configuratorRoutes' +import googleMapsPlacesRoutes from './googleMaps/bundles/places/configuratorRoutes' +import googleMapsPlacesValuesRoutes from './googleMaps/bundles/quantifiedPlaces/configuratorRoutes' +import googleMapsThingsPlacesValuesRoutes from './googleMaps/bundles/quantifiedThings/configuratorRoutes' + +import timelineInstantsRoutes from './timeline/bundles/instants/configuratorRoutes' +import timelineIntervalsRoutes from './timeline/bundles/intervals/configuratorRoutes' +import timelineThingsInstantsRoutes from './timeline/bundles/thingsInstants/configuratorRoutes' +import timelineThingsIntervalsRoutes from './timeline/bundles/thingsIntervals/configuratorRoutes' +import timelineThingsThingsInstantsRoutes from './timeline/bundles/thingsThingsInstants/configuratorRoutes' +import timelineThingsThingsIntervalsRoutes from './timeline/bundles/thingsThingsIntervals/configuratorRoutes' + import { Visualizer, VisualizerWithPipelines } from '../core/models' import { applicationUrl } from '../app/configuratorRoutes' @@ -12,9 +25,23 @@ const routeFactory = new ConfiguratorsRouteFactory(); // ***Here*** you register all visualizer configurator routes routeFactory.register(dataCubeRoutes); -routeFactory.register(googleMapsRoutes); routeFactory.register(chordRoutes); +// Google Maps +routeFactory.register(googleMapsV1Routes); +routeFactory.register(googleMapsCoordinatesRoutes); +routeFactory.register(googleMapsPlacesRoutes); +routeFactory.register(googleMapsPlacesValuesRoutes); +routeFactory.register(googleMapsThingsPlacesValuesRoutes); + +// Timeline +routeFactory.register(timelineInstantsRoutes); +routeFactory.register(timelineIntervalsRoutes); +routeFactory.register(timelineThingsInstantsRoutes); +routeFactory.register(timelineThingsIntervalsRoutes); +routeFactory.register(timelineThingsThingsInstantsRoutes); +routeFactory.register(timelineThingsThingsIntervalsRoutes); + export default dispatch => routeFactory.createRoutes(dispatch); // "Named" routes @@ -33,7 +60,7 @@ export const getConfiguratorPath = visualizer => { }; export const visualizerConfiguratorUrl = (appId, visualizer) => - applicationUrl(appId) + '/' + getConfiguratorPath(visualizer); +applicationUrl(appId) + '/' + getConfiguratorPath(visualizer); export const visualizerConfigurator = (appId, visualizer) => routeActions.push(visualizerConfiguratorUrl(appId, visualizer)); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/api.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/api.js new file mode 100644 index 00000000..1b51b952 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/api.js @@ -0,0 +1,176 @@ +import rest from '../../../misc/rest' +import { applyByBatchesCount, applyByBatchesWithLimit } from '../../common/utils/apiUtils' + +const BATCH_SIZE = 100; + +export async function getIntervals(applicationId, urls, timeRange, limit) { + const batchFunc = async function (batchUrls, batchLimit) { + let payload = { + 'urls': batchUrls, + 'start': timeRange.begin.getTime(), + 'end': timeRange.end.getTime(), + 'limit': batchLimit + }; + + const result = await rest('timeLineVisualizer/getIntervals/' + applicationId, payload); + return result.data.intervals; + }; + return applyByBatchesWithLimit(urls, BATCH_SIZE, limit, batchFunc); +} + +export async function getInstants(applicationId, urls, timeRange, limit) { + const batchFunc = async function (batchUrls, batchLimit) { + let payload = { + 'urls': batchUrls, + 'start': timeRange.begin.getTime(), + 'end': timeRange.end.getTime(), + 'limit': batchLimit + }; + + const result = await rest('timeLineVisualizer/getInstants/' + applicationId, payload); + return result.data.instants; + }; + return applyByBatchesWithLimit(urls, BATCH_SIZE, limit, batchFunc); +} + +export async function getThingsWIntervals(applicationId, things, thingTypes, predicates, limit) { + const batchFunc = async function (batchThings, batchLimit) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': batchLimit + }; + const result = await rest('timeLineVisualizer/getThingsWIntervals/' + applicationId, payload); + return result.data.thingsWithIntervals; + }; + return applyByBatchesWithLimit(things, BATCH_SIZE, limit, batchFunc); +} + +export async function getThingsWInstants(applicationId, things, thingTypes, predicates, limit) { + const batchFunc = async function (batchThings, batchLimit) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': batchLimit + }; + const result = await rest('timeLineVisualizer/getThingsWInstants/' + applicationId, payload); + return result.data.thingsWithInstants; + }; + return applyByBatchesWithLimit(things, BATCH_SIZE, limit, batchFunc); +} + +export async function getThingsWThingsWIntervals(applicationId, things, thingTypes, predicates, limit) { + const batchFunc = async function (batchThings, batchLimit) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': batchLimit + }; + const result = await rest('timeLineVisualizer/getThingsWThingsWIntervals/' + applicationId, payload); + return result.data.thingsWithThingsWithIntervals; + }; + return applyByBatchesWithLimit(things, BATCH_SIZE, limit, batchFunc); +} + +export async function getThingsWThingsWInstants(applicationId, things, thingTypes, predicates, limit) { + const batchFunc = async function (batchThings, batchLimit) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': batchLimit + }; + const result = await rest('timeLineVisualizer/getThingsWThingsWInstants/' + applicationId, payload); + return result.data.thingsWithThingsWithInstants; + }; + return applyByBatchesWithLimit(things, BATCH_SIZE, limit, batchFunc); +} + +export async function getIntervalsCount(applicationId, urls, timeRange) { + const batchFunc = async function (batchUrls) { + let payload = { + 'urls': batchUrls, + 'start': timeRange.begin.getTime(), + 'end': timeRange.end.getTime(), + 'limit': -1 + }; + const result = await rest('timeLineVisualizer/getIntervals/count/' + applicationId, payload); + return result.data.count; + }; + return applyByBatchesCount(urls, BATCH_SIZE, batchFunc); +} + +export async function getInstantsCount(applicationId, urls, timeRange) { + const batchFunc = async function (batchUrls) { + let payload = { + 'urls': batchUrls, + 'start': timeRange.begin.getTime(), + 'end': timeRange.end.getTime(), + 'limit': -1 + }; + const result = await rest('timeLineVisualizer/getInstants/count/' + applicationId, payload); + return result.data.count; + }; + return applyByBatchesCount(urls, BATCH_SIZE, batchFunc); +} + +export async function getThingsWIntervalsCount(applicationId, things, thingTypes, predicates) { + const batchFunc = async function (batchThings) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': -1 + }; + const result = await rest('timeLineVisualizer/getThingsWIntervals/count/' + applicationId, payload); + return result.data.count; + }; + return applyByBatchesCount(things, BATCH_SIZE, batchFunc); +} + +export async function getThingsWInstantsCount(applicationId, things, thingTypes, predicates) { + const batchFunc = async function (batchThings) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': -1 + }; + + const result = await rest('timeLineVisualizer/getThingsWInstants/count/' + applicationId, payload); + return result.data.count; + + }; + return applyByBatchesCount(things, BATCH_SIZE, batchFunc); +} + +export async function getThingsWThingsWIntervalsCount(applicationId, things, thingTypes, predicates) { + const batchFunc = async function (batchThings) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': -1 + }; + const result = await rest('timeLineVisualizer/getThingsWThingsWIntervals/count/' + applicationId, payload); + return result.data.count; + }; + return applyByBatchesCount(things, BATCH_SIZE, batchFunc); +} + +export async function getThingsWThingsWInstantsCount(applicationId, things, thingTypes, predicates) { + const batchFunc = async function (batchThings) { + let payload = { + 'things': batchThings, + 'thingTypes': thingTypes, + 'predicates': predicates, + 'limit': -1 + }; + const result = await rest('timeLineVisualizer/getThingsWThingsWInstants/count/' + applicationId, payload); + return result.data.count; + }; + return applyByBatchesCount(things, BATCH_SIZE, batchFunc); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/applicationRoutes.js new file mode 100644 index 00000000..ada5c74c --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import Instants from '../../pages/Instants' +import NotFound from '../../../../platform/pages/NotFound' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/configuratorRoutes.js new file mode 100644 index 00000000..ca5c6633 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import Instants from '../../pages/Instants' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/prefix.js new file mode 100644 index 00000000..c6f90701 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/instants/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-' + 'instants'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/applicationRoutes.js new file mode 100644 index 00000000..4f3e162f --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import Intervals from '../../pages/Intervals' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/configuratorRoutes.js new file mode 100644 index 00000000..c75903e7 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import Intervals from '../../pages/Intervals' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/prefix.js new file mode 100644 index 00000000..bea4b8e3 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/intervals/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-' + 'intervals'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/applicationRoutes.js new file mode 100644 index 00000000..cd97c4de --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import InstantsToFirstLevel from '../../pages/InstantsToFirstLevel' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/configuratorRoutes.js new file mode 100644 index 00000000..922284bf --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import InstantsToFirstLevel from '../../pages/InstantsToFirstLevel' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/prefix.js new file mode 100644 index 00000000..98b22883 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsInstants/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-' + 'things-instants'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/applicationRoutes.js new file mode 100644 index 00000000..e3e4d204 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import IntervalsToFirstLevel from '../../pages/IntervalsToFirstLevel' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/configuratorRoutes.js new file mode 100644 index 00000000..7057ab27 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import IntervalsToFirstLevel from '../../pages/IntervalsToFirstLevel' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/prefix.js new file mode 100644 index 00000000..60ddaef9 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsIntervals/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-' + 'things-intervals'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/applicationRoutes.js new file mode 100644 index 00000000..7a72f36f --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import InstantsToSecondLevel from '../../pages/InstantsToSecondLevel' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/configuratorRoutes.js new file mode 100644 index 00000000..84d1b4db --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import InstantsToSecondLevel from '../../pages/InstantsToSecondLevel' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/prefix.js new file mode 100644 index 00000000..739396c0 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsInstants/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-' + 'things-things-instants'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/applicationRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/applicationRoutes.js new file mode 100644 index 00000000..59846d82 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/applicationRoutes.js @@ -0,0 +1,15 @@ +import React from 'react' +import { IndexRoute, Route } from 'react-router' +import ApplicationLoader from '../../../../app/pages/ApplicationLoader' +import NotFound from '../../../../platform/pages/NotFound' +import IntervalsToSecondLevel from '../../pages/IntervalsToSecondLevel' + +export default function createRoutes(dispatch) { + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/configuratorRoutes.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/configuratorRoutes.js new file mode 100644 index 00000000..84300b63 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/configuratorRoutes.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Route } from 'react-router' +import { MODULE_PREFIX } from './prefix' +import IntervalsToSecondLevel from '../../pages/IntervalsToSecondLevel' + +export default function createRoutes(dispatch) { + return ( + + ); +} diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/prefix.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/prefix.js new file mode 100644 index 00000000..1c321c92 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/bundles/thingsThingsIntervals/prefix.js @@ -0,0 +1,5 @@ +import createPrefixer from '../../../../../misc/createPrefixer' +import { MODULE_PREFIX as PARENT_MODULE_PREFIX } from '../../prefix' + +export const MODULE_PREFIX = PARENT_MODULE_PREFIX + '-' + 'things-things-intervals'; +export default createPrefixer(MODULE_PREFIX); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/InstantInfoWindow.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/InstantInfoWindow.js new file mode 100644 index 00000000..a1fb72b0 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/InstantInfoWindow.js @@ -0,0 +1,41 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' +import CenteredMessage from '../../../../components/CenteredMessage' +import { selectedTimeRecordSelector } from '../ducks/selectedTimeRecord' +import LevelsInfoWindow from './LevelsInfoWindow' +import makePureRender from '../../../../misc/makePureRender' +import ObjectInfo from '../../../app/containers/ObjectInfo' +import SubHeadLine from '../../../../components/Subheadline' + +class InstantInfoWindow extends Component { + static propTypes = { + selectedTimeRecord: PropTypes.instanceOf(Array).isRequired + }; + + render() { + const { selectedTimeRecord } = this.props; + + if (selectedTimeRecord.length == 0) { + return Select events on the Time Line to view them. + } + + var instant = selectedTimeRecord[0]; + + return
+ + +
+ Date-Time: + {new Date(instant.date).toUTCString()} +
+ +
+ } +} + +const selector = createStructuredSelector({ + selectedTimeRecord: selectedTimeRecordSelector, +}); + +export default connect(selector)(makePureRender(InstantInfoWindow)); \ No newline at end of file diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/IntervalInfoWindow.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/IntervalInfoWindow.js new file mode 100644 index 00000000..23a2bc1e --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/IntervalInfoWindow.js @@ -0,0 +1,43 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' +import CenteredMessage from '../../../../components/CenteredMessage' +import SubHeadLine from '../../../../components/Subheadline' +import { selectedTimeRecordSelector } from '../ducks/selectedTimeRecord' +import LevelsInfoWindow from './LevelsInfoWindow' +import ObjectInfo from '../../../app/containers/ObjectInfo' + +class IntervalInfoWindow extends Component { + static propTypes = { + selectedTimeRecord: PropTypes.instanceOf(Array).isRequired + }; + + render() { + const { selectedTimeRecord } = this.props; + + if (selectedTimeRecord.length == 0) { + return Select events on the Time Line to view them. + } + + var interval = selectedTimeRecord[0]; + return
+ + +
+ Begin: + {new Date(interval.begin).toUTCString()} +
+ End: + {new Date(interval.end).toUTCString()} +
+ +
+ + } +} + +const selector = createStructuredSelector({ + selectedTimeRecord: selectedTimeRecordSelector, +}); + +export default connect(selector)(IntervalInfoWindow); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/LevelsInfoWindow.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/LevelsInfoWindow.js new file mode 100644 index 00000000..a17e3988 --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/LevelsInfoWindow.js @@ -0,0 +1,73 @@ +import React, { Component, PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' + +import { selectedTimeRecordSelector } from '../ducks/selectedTimeRecord' +import { firstLevelSelector } from '../ducks/firstLevel' +import { secondLevelSelector } from '../ducks/secondLevel' +import SubHeadLine from '../../../../components/Subheadline' +import ObjectInfo from '../../../app/containers/ObjectInfo' + +class LevelsInfoWindow extends Component { + static propTypes = { + timeRecordUrl: PropTypes.string.isRequired, + firstLevel: PropTypes.instanceOf(Array).isRequired, + secondLevel: PropTypes.instanceOf(Array).isRequired + }; + + renderConnection(conn) { + return
+ +
+ +
+ +
+
+ } + + render() { + const { timeRecordUrl, firstLevel, secondLevel } = this.props; + + var matchingFirstLevel = []; + for (let conn of firstLevel) { + if (conn.inner == timeRecordUrl) matchingFirstLevel.push(conn) + } + + var firstVis; + if (matchingFirstLevel.length > 0) { + firstVis =
+ + {matchingFirstLevel.map(m => this.renderConnection(m))} +
+ } + + var matchingSecondLevel = []; + for (let i of matchingFirstLevel) { + for (let j of secondLevel) { + if (i.outer == j.inner) matchingSecondLevel.push(j) + } + } + var secondVis; + if (matchingSecondLevel.length > 0) { + secondVis =
+ + {matchingSecondLevel.map(m => this.renderConnection(m))} +
+ } + + return
+ {firstVis} + {secondVis} +
+ + } +} + +const selector = createStructuredSelector({ + selectedTimeRecord: selectedTimeRecordSelector, + firstLevel: firstLevelSelector, + secondLevel: secondLevelSelector +}); + +export default connect(selector)(LevelsInfoWindow); diff --git a/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/OpenEmbedAppDialogButton.js b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/OpenEmbedAppDialogButton.js new file mode 100644 index 00000000..71d3effd --- /dev/null +++ b/src/app/assets_webpack/assistant/javascripts/modules/visualizers/timeline/components/OpenEmbedAppDialogButton.js @@ -0,0 +1,19 @@ +import React, { PropTypes } from 'react' +import Button from '../../../../components/Button' +import withDialogControls from '../../../core/containers/withDialogControls' +import { dialogName } from './../containers/EmbedAppDialog' + +const OpenEmbedAppDialogButton = props => ( +
+ } + + var buttonsEnabled = selectedFirstLevelTypes.size > 0 || selectedFirstLevelPredicates.size > 0; + + return + t.outerType} + selectedKeys={selectedFirstLevelTypes} + onKeySelect={k => dispatch(setSelectFirstLevelType(k))} + /> + t.predicate} + selectedKeys={selectedFirstLevelPredicates} + onKeySelect={k => dispatch(setSelectFirstLevelPredicate(k))} + /> +