diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..59dcb89
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,13 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "targets": {
+ "node": "10"
+ }
+ }
+ ],
+ "@babel/preset-react"
+ ]
+}
diff --git a/README.md b/README.md
index 34d96f9..dd3ae2f 100644
--- a/README.md
+++ b/README.md
@@ -17,4 +17,6 @@ WatsonC Vidi extension
`test.py` - runs tests for the `intersectiontool.py`
-The `FOHM_layers_v2.npy` was not added to repo, please download it from https://s3-eu-west-1.amazonaws.com/mapcentia-tmp/ProfileTool.zip and put in `/extensions/watsonc/scripts` folder.
\ No newline at end of file
+The `FOHM_layers_v2.npy` was not added to repo, please download it from https://s3-eu-west-1.amazonaws.com/mapcentia-tmp/ProfileTool.zip and put in `/extensions/watsonc/scripts` folder.
+
+test
diff --git a/browser/PlotManager.js b/browser/PlotManager.js
index 56358a9..9950fd2 100644
--- a/browser/PlotManager.js
+++ b/browser/PlotManager.js
@@ -10,112 +10,6 @@ class PlotManager {
this.apiUrlLocal = `/api/key-value/` + window.vidiConfig.appDatabase;
}
- dehydratePlots(plots) {
- plots.map((plot, index) => {
- delete plots[index].measurements;
- delete plots[index].measurementsCachedData;
- });
-
- return plots;
- }
-
- hydratePlotsFromIds(plots) {
- if (typeof plots === "undefined") return;
- plots = plots.filter(e => !!e.id)
- return new Promise((methodResolve, methodReject) => {
- let hydrationPromises = [];
- plots.map((plot, index) => {
- let hydrateRequest = new Promise((resolve, reject) => {
- $.ajax({
- url: `${this.apiUrlLocal}/${plot.id}`,
- method: 'GET',
- dataType: 'json',
- contentType: 'application/json; charset=utf-8',
- success: (body) => {
- if (body.success) {
- resolve(body.data);
- } else {
- throw new Error(`Failed to perform operation`, body);
- }
- },
- error: error => {
- console.error(error);
- reject(`Failed to query keyvalue API`);
- }
- });
- });
-
- hydrationPromises.push(hydrateRequest);
- });
-
- Promise.all(hydrationPromises).then(results => {
- plots.map((item, index) => {
- results.map((dataItem) => {
- if (dataItem.key === item.id && typeof dataItem.value !== "undefined") {
- plots[index] = JSON.parse(dataItem.value);
- delete plots[index].measurementsCachedData;
- }
- });
- });
-
- plots.map((item, index) => {
- if (typeof item === "object" && (`measurements` in item === false || !item.measurements
- || `measurementsCachedData` in item === false || !item.measurementsCachedData)) {
- console.warn(`The ${item.id} plot was not properly populated`, item);
- }
- });
- // Filter non objects. A non object can occur when time a project time series is deleted from from key value store
- plots = plots.filter((item)=>{
- return (typeof item === 'object')
- });
- methodResolve(plots);
- }).catch(methodReject);
- });
- }
-
- hydratePlotsFromUser() {
- return new Promise((methodResolve, methodReject) => {
- let hydrationPromises = [];
- let userId = session.getUserName();
- $.ajax({
- url: `${this.apiUrlLocal}/?like=watsonc_plot_%&filter='{userId}'='${userId}'`,
- method: 'GET',
- dataType: 'json',
- contentType: 'application/json; charset=utf-8',
- success: (body) => {
- if (body.success) {
- let results = [];
- body.data.map(item => {
- results.push(item);
- });
- let plots = [];
- results.map((item, index) => {
- plots[index] = JSON.parse(item.value);
- delete plots[index].measurementsCachedData;
- });
-
- plots.map((item, index) => {
- if (`measurements` in item === false || !item.measurements
- || `measurementsCachedData` in item === false || !item.measurementsCachedData) {
- console.warn(`The ${item.id} plot was not properly populated`, item);
- }
- });
-
- methodResolve(plots);
-
-
- } else {
- throw new Error(`Failed to perform operation`, body);
- }
- },
- error: error => {
- console.error(error);
- reject(`Failed to query keyvalue API`);
- }
- });
-
- });
- }
create(title) {
return new Promise((resolve, reject) => {
@@ -126,7 +20,8 @@ class PlotManager {
title,
userId: session.getUserName(),
measurements: [],
- measurementsCachedData: {}
+ measurementsCachedData: {},
+ relations: {}
};
$.ajax({
diff --git a/browser/ProfileManager.js b/browser/ProfileManager.js
index 9c0bfe3..792c329 100644
--- a/browser/ProfileManager.js
+++ b/browser/ProfileManager.js
@@ -1,68 +1,87 @@
-/**
- * Abstraction class for storing profiles in the key-value storage
- */
-
-import axios from 'axios';
-
-class ProfileManager {
- constructor() {
- let hostname = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
- //this.apiUrl = hostname + `/api/key-value/` + window.vidiConfig.appDatabase;
- this.apiUrl = hostname + `/api/extension/watsonc/${window.vidiConfig.appDatabase}/profiles`;
- }
-
- getAll() {
- return new Promise((resolve, reject) => {
- $.ajax({
- url: this.apiUrl,
- method: 'GET',
- dataType: 'json'
- }).then(response => {
- let parsedData = [];
- response.map(item => {
- parsedData.push(JSON.parse(item.value));
- });
-
- resolve(parsedData);
- }, (jqXHR) => {
- console.error(`Error occured while refreshing profiles list`);
- reject(`Error occured while refreshing profiles list`);
- });
- });
- }
-
- create(savedProfile) {
- return new Promise((resolve, reject) => {
- axios.post(`/api/extension/watsonc/profile`, savedProfile).then(response => {
- if (response.data) {
- savedProfile.data = response.data;
- axios.post(`${this.apiUrl}`, savedProfile).then(response => {
- let data = JSON.parse(response.data.data.value);
- resolve(data);
- }).catch(error => {
- console.error(error);
- reject(error);
- });
- } else {
- console.error(`Unable to generate plot data`);
- reject(`Unable to generate plot data`);
- }
- }).catch(error => {
- console.error(`Error occured during plot generation`, error);
- reject(error);
- });
- });
- }
-
- delete(profileKey) {
- return new Promise((resolve, reject) => {
- if (profileKey) {
- axios.delete(`${this.apiUrl}/${profileKey}`).then(resolve).catch(reject);
- } else {
- reject(`Empty profile identifier was provided`);
- }
- });
- }
-}
-
-export default ProfileManager;
+/**
+ * Abstraction class for storing profiles in the key-value storage
+ */
+
+import axios from "axios";
+
+class ProfileManager {
+ constructor() {
+ let hostname =
+ location.protocol +
+ "//" +
+ location.hostname +
+ (location.port ? ":" + location.port : "");
+ //this.apiUrl = hostname + `/api/key-value/` + window.vidiConfig.appDatabase;
+ // let hostname = 'https://map.calypso.watsonc.dk'
+ this.apiUrl =
+ hostname +
+ `/api/extension/watsonc/${window.vidiConfig.appDatabase}/profiles`;
+ }
+
+ getAll() {
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ url: this.apiUrl,
+ method: "GET",
+ dataType: "json",
+ }).then(
+ (response) => {
+ let parsedData = [];
+ response.map((item) => {
+ parsedData.push(JSON.parse(item.value));
+ });
+
+ resolve(parsedData);
+ },
+ (jqXHR) => {
+ console.error(`Error occured while refreshing profiles list`);
+ reject(`Error occured while refreshing profiles list`);
+ }
+ );
+ });
+ }
+
+ create(savedProfile) {
+ return new Promise((resolve, reject) => {
+ axios
+ .post(`/api/extension/watsonc/profile`, savedProfile)
+ .then((response) => {
+ if (response.data) {
+ savedProfile.data = response.data;
+ axios
+ .post(`${this.apiUrl}`, savedProfile)
+ .then((response) => {
+ let data = JSON.parse(response.data.data.value);
+ resolve(data);
+ })
+ .catch((error) => {
+ console.error(error);
+ reject(error);
+ });
+ } else {
+ console.error(`Unable to generate plot data`);
+ reject(`Unable to generate plot data`);
+ }
+ })
+ .catch((error) => {
+ console.error(`Error occured during plot generation`, error);
+ reject(error);
+ });
+ });
+ }
+
+ delete(profileKey) {
+ return new Promise((resolve, reject) => {
+ if (profileKey) {
+ axios
+ .delete(`${this.apiUrl}/${profileKey}`)
+ .then(resolve)
+ .catch(reject);
+ } else {
+ reject(`Empty profile identifier was provided`);
+ }
+ });
+ }
+}
+
+export default ProfileManager;
diff --git a/browser/api/baseApi.js b/browser/api/baseApi.js
new file mode 100644
index 0000000..928f667
--- /dev/null
+++ b/browser/api/baseApi.js
@@ -0,0 +1,52 @@
+
+export default class BaseApi {
+
+ get(url = "") {
+ try {
+
+ // Default options are marked with *
+ return fetch(url, {
+ method: 'GET', // *GET, POST, PUT, DELETE, etc.
+ // mode: 'cors', // no-cors, *cors, same-origin
+ // cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
+ // credentials: 'same-origin', // include, *same-origin, omit
+ // headers: {
+ // 'Content-Type': 'application/json; charset=utf-8'
+ // },
+ // redirect: 'follow', // manual, *follow, error
+ // referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
+ // body: JSON.stringify(query) // body data type must match "Content-Type" header
+ });
+
+
+ } catch (error) {
+ // console.log("BaseApi error", error);
+ return "";
+ }
+ }
+
+ post(url = "", body = "") {
+
+ try {
+
+ // Default options are marked with *
+ return fetch(url, {
+ method: 'POST', // *GET, POST, PUT, DELETE, etc.
+ // mode: 'cors', // no-cors, *cors, same-origin
+ // cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
+ // credentials: 'same-origin', // include, *same-origin, omit
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8'
+ },
+ // redirect: 'follow', // manual, *follow, error
+ // referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
+ // body: JSON.stringify(query) // body data type must match "Content-Type" header
+ body: JSON.stringify(body)
+ });
+
+ } catch (error) {
+ // console.log("BaseApi error", error);
+ return "";
+ }
+ }
+}
diff --git a/browser/api/meta/MetaApi.js b/browser/api/meta/MetaApi.js
new file mode 100644
index 0000000..89b8494
--- /dev/null
+++ b/browser/api/meta/MetaApi.js
@@ -0,0 +1,31 @@
+import BaseApi from "../baseApi";
+
+const metaUrl = "/api/meta/jupiter/";
+
+export default class MetaApi {
+ getMetaData(parameter) {
+ const baseApi = new BaseApi();
+ const url = metaUrl + parameter;
+ return baseApi
+ .get(url)
+ .then((response) => {
+ return response.json();
+ })
+ .then((results) => {
+ return results.data
+ .filter((item) => item.f_table_schema == parameter)
+ .map((item) => {
+ let value = `${item.f_table_schema}.${item.f_table_name}`;
+ if (item.f_table_title == "Jupiter boringer") {
+ value = "v:system.all";
+ }
+ return {
+ label: item.f_table_title,
+ group: item.layergroup,
+ value: value,
+ privileges: JSON.parse(item.privileges),
+ };
+ });
+ });
+ }
+}
diff --git a/browser/api/plots/PlotApi.js b/browser/api/plots/PlotApi.js
new file mode 100644
index 0000000..40db26c
--- /dev/null
+++ b/browser/api/plots/PlotApi.js
@@ -0,0 +1,13 @@
+import BaseApi from '../baseApi';
+
+const downloadPlotUrl = '/api/extension/watsonc/download-plot';
+
+export default class PlotApi {
+ downloadPlot(payload) {
+ const baseApi = new BaseApi();
+ const response = baseApi.post(downloadPlotUrl, payload);
+ return response.then((response) => {
+ return response.blob();
+ });
+ }
+}
diff --git a/browser/api/projects/ProjectsApi.js b/browser/api/projects/ProjectsApi.js
new file mode 100644
index 0000000..dbeba76
--- /dev/null
+++ b/browser/api/projects/ProjectsApi.js
@@ -0,0 +1,11 @@
+import BaseApi from '../baseApi';
+
+const projectsUrl = '/api/state-snapshots/jupiter?ownerOnly=true'
+
+export default class ProjectsApi {
+ getProjects() {
+ const baseApi = new BaseApi();
+ const response = baseApi.get(projectsUrl);
+ return response;
+ }
+}
diff --git a/browser/components/AnalyticsComponent.js b/browser/components/AnalyticsComponent.js
index c8598aa..a84ce44 100644
--- a/browser/components/AnalyticsComponent.js
+++ b/browser/components/AnalyticsComponent.js
@@ -1,62 +1,84 @@
import React from "react";
import axios from "axios";
import fileSaver from "file-saver";
-import LoadingOverlay from './../../../../browser/modules/shared/LoadingOverlay';
-
+import LoadingOverlay from "./../../../../browser/modules/shared/LoadingOverlay";
/**
* Analytics Component
*/
+
+const session = require("./../../../session/browser/index");
+
class AnalyticsComponent extends React.Component {
- constructor(props) {
- super(props);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.kommune = React.createRef();
- this.type = React.createRef();
- this.state = {
- loading: false
- };
- }
+ constructor(props) {
+ super(props);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.kommune = React.createRef();
+ this.type = React.createRef();
+ this.state = {
+ loading: false,
+ };
+ }
- handleSubmit(event) {
- this.setState({loading: true});
- axios.get(`/api/extension/watsonc/report?komcode=${this.kommune.current.value}&userid=1234`).then(response => {
- fileSaver.saveAs(response.data.url, "rapport.xlsx");
- }).catch(error => {
- console.log(`Error occured`, error);
- }).finally(() => {
- this.setState({loading: false});
- }
- )
- event.preventDefault();
- }
+ handleSubmit(event) {
+ this.setState({ loading: true });
+ axios
+ .get(
+ `/api/extension/watsonc/report?komcode=${
+ this.kommune.current.value
+ }&userid=${session.getUserName()}`
+ )
+ .then((response) => {
+ fileSaver.saveAs(response.data.url, "rapport.xlsx");
+ })
+ .catch((error) => {
+ console.log(`Error occured`, error);
+ })
+ .finally(() => {
+ this.setState({ loading: false });
+ });
+ event.preventDefault();
+ }
- render() {
- let data = this.props.kommuner;
- let makeItem = function (i) {
- return ;
- };
+ render() {
+ let data = this.props.kommuner;
+ let makeItem = function (i) {
+ return (
+
+ );
+ };
- return (
-
- );
- }
+ return (
+
+ );
+ }
}
export default AnalyticsComponent;
diff --git a/browser/components/ChemicalSelector.js b/browser/components/ChemicalSelector.js
deleted file mode 100644
index 2de1814..0000000
--- a/browser/components/ChemicalSelector.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import React from 'react';
-import {connect} from 'react-redux';
-
-import SearchFieldComponent from './../../../../browser/modules/shared/SearchFieldComponent';
-import {selectChemical} from '../redux/actions';
-
-import {WATER_LEVEL_KEY} from './../constants';
-
-const uuidv4 = require('uuid/v4');
-
-/**
- * Chemical selector
- */
-class ChemicalSelector extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {searchTerm: ``};
- this.handleSearch = this.handleSearch.bind(this);
- }
-
- generateWaterGroup(runId) {
- let checked = false;
- if (this.props.useLocalSelectedChemical) {
- checked = this.props.localSelectedChemical === WATER_LEVEL_KEY;
- } else {
- checked = this.props.selectedChemical === WATER_LEVEL_KEY;
- }
-
- return (
-
-
{__(`Water level`)}
-
-
-
-
-
-
-
-
-
);
- }
-
- generateChemicalGroups(runId) {
- let chemicalGroupsForLayer = [];
- for (let layerName in this.props.categories) {
- if (layerName.indexOf(LAYER_NAMES[0]) > -1) {
- for (let key in this.props.categories[layerName]) {
- let chemicalsMarkup = [];
- for (let key2 in this.props.categories[layerName][key]) {
- if (this.state.searchTerm === `` || this.props.categories[layerName][key][key2].toLowerCase().indexOf(this.state.searchTerm.toLowerCase()) > -1) {
- let checked = false;
- if (this.props.useLocalSelectedChemical) {
- checked = this.props.localSelectedChemical === key2;
- } else {
- checked = this.props.selectedChemical === key2;
- }
-
- chemicalsMarkup.push(
-
-
-
-
);
- }
- }
-
- if (chemicalsMarkup.length > 0) {
- if (key !== `Vandstand`) {
- chemicalGroupsForLayer.push(
-
-
{key}
-
-
{chemicalsMarkup}
-
);
- }
- }
- }
- }
- }
- return chemicalGroupsForLayer;
- }
-
- handleSearch(searchTerm) {
- this.setState({searchTerm});
- }
-
- render() {
- let runId = uuidv4();
-
- let layerGroupsList = [];
-
- if (this.props.emptyOptionTitle) {
- layerGroupsList.push(
-
-
-
-
-
-
-
-
);
- }
-
- if (this.props.selectedLayers.indexOf(LAYER_NAMES[0]) > -1) {
- let waterGroup = this.generateWaterGroup(runId);
- layerGroupsList.push(waterGroup);
- let chemicalGroups = this.generateChemicalGroups(runId);
- layerGroupsList = layerGroupsList.concat(chemicalGroups);
- }
-
- return (
- {this.props.selectedLayers.length > 0 ? (
-
- {layerGroupsList.length > 0 ? (
- {layerGroupsList}
) : (
- {__(`Nothing found`)}
)}
- ) : false}
-
);
- }
-}
-
-ChemicalSelector.defaultProps = {
- useLocalSelectedChemical: false,
- localSelectedChemical: false
-};
-
-const mapStateToProps = state => ({
- categories: state.global.categories,
- selectedLayers: state.global.selectedLayers,
- selectedChemical: state.global.selectedChemical
-});
-
-const mapDispatchToProps = dispatch => ({
- selectChemical: (key) => dispatch(selectChemical(key)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChemicalSelector);
diff --git a/browser/components/ChemicalSelectorModal.js b/browser/components/ChemicalSelectorModal.js
deleted file mode 100644
index c098115..0000000
--- a/browser/components/ChemicalSelectorModal.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-
-import ChemicalSelector from './ChemicalSelector';
-import { selectChemical } from '../redux/actions';
-
-/**
- * Chemical selector
- */
-class ChemicalSelectorModal extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- return (
-
-
- {__(`Select datatype`)}
-
-
-
-
-
-
-
-
- {this.props.onCancelControl ? () : false}
-
-
-
);
- }
-}
-
-ChemicalSelectorModal.defaultProps = {
- useLocalSelectedChemical: false,
- localSelectedChemical: false
-};
-
-const mapStateToProps = state => ({
- selectedChemical: state.global.selectedChemical,
- selectedLayers: state.global.selectedLayers,
-});
-
-const mapDispatchToProps = dispatch => ({
- selectChemical: (key) => dispatch(selectChemical(key)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChemicalSelectorModal);
diff --git a/browser/components/DashboardComponent.js b/browser/components/DashboardComponent.js
index de14e2c..bd151b7 100644
--- a/browser/components/DashboardComponent.js
+++ b/browser/components/DashboardComponent.js
@@ -1,24 +1,24 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Provider} from 'react-redux';
-import reduxStore from './../redux/store';
-
-import ReactTooltip from 'react-tooltip';
-import {SELECT_CHEMICAL_DIALOG_PREFIX, TEXT_FIELD_DIALOG_PREFIX, VIEW_MATRIX, VIEW_ROW} from './../constants';
-import PlotManager from './../PlotManager';
-import ProfileManager from './../ProfileManager';
-import TextFieldModal from './TextFieldModal';
-import SortablePlotComponent from './SortablePlotComponent';
-import SortableProfileComponent from './SortableProfileComponent';
-import SortablePlotsGridComponent from './SortablePlotsGridComponent';
-import {isNumber} from 'util';
-import arrayMove from 'array-move';
-import trustedIpAddresses from '../trustedIpAddresses';
-import {getPlotData} from '../services/plot';
-
-let syncInProg = false;
-
-const uuidv1 = require('uuid/v1');
+import React from "react";
+import PropTypes from "prop-types";
+import { Provider } from "react-redux";
+import reduxStore from "./../redux/store";
+import { setDashboardMode } from "./../redux/actions";
+import {
+ SELECT_CHEMICAL_DIALOG_PREFIX,
+ TEXT_FIELD_DIALOG_PREFIX,
+ VIEW_MATRIX,
+ VIEW_ROW,
+} from "./../constants";
+import PlotManager from "./../PlotManager";
+import ProfileManager from "./../ProfileManager";
+import TextFieldModal from "./TextFieldModal";
+import arrayMove from "array-move";
+import trustedIpAddresses from "../trustedIpAddresses";
+import { getPlotData } from "../services/plot";
+import ProjectContext from "../contexts/project/ProjectContext";
+
+const uuidv1 = require("uuid/v1");
+const session = require("./../../../session/browser/index");
const DASHBOARD_ITEM_PLOT = 0;
const DASHBOARD_ITEM_PROJECT_PLOT = 3;
@@ -28,1180 +28,1046 @@ const DASHBOARD_ITEM_PROJECT_PROFILE = 2;
const DISPLAY_MIN = 0;
const DISPLAY_HALF = 1;
const DISPLAY_MAX = 2;
-let currentDisplay = DISPLAY_HALF, previousDisplay = DISPLAY_MAX;
+let currentDisplay = DISPLAY_HALF,
+ previousDisplay = DISPLAY_MAX;
let modalHeaderHeight = 70;
-let _self = false, resizeTimeout = false;
+let _self = false,
+ resizeTimeout = false;
/**
* Component creates plots management form and is the source of truth for plots overall
*/
class DashboardComponent extends React.Component {
- constructor(props) {
- super(props);
- let queryParams = new URLSearchParams(window.location.search);
- let licenseToken = queryParams.get('license');
- let license = null;
- if (licenseToken) {
- license = JSON.parse(base64.decode(licenseToken.split('.')[1]));
- if (typeof license === 'object') {
- license = license.license;
- }
- }
- if (trustedIpAddresses.includes(window._vidiIp)) {
- license = "premium";
- }
-
- let dashboardItems = [];
- if (this.props.initialPlots) {
- this.props.initialPlots.map(item => {
- dashboardItems.push({
- type: DASHBOARD_ITEM_PLOT,
- item
- });
- });
- }
-
- this.state = {
- view: VIEW_MATRIX,
- newPlotName: ``,
- dashboardItems,
- plots: this.props.initialPlots,
- projectPlots: [],
- profiles: [],
- projectProfiles: [],
- activePlots: [],
- activeProfiles: [],
- dataSource: [],
- highlightedPlot: false,
- createdProfileChemical: false,
- createdProfileName: false,
- lastUpdate: false,
- license: license,
- modalScroll: {}
- };
-
- this.plotManager = new PlotManager();
- this.profileManager = new ProfileManager();
-
- this.handleShowPlot = this.handleShowPlot.bind(this);
- this.handleHidePlot = this.handleHidePlot.bind(this);
- this.handleCreatePlot = this.handleCreatePlot.bind(this);
- this.handleRemovePlot = this.handleRemovePlot.bind(this);
- this.handleDeletePlot = this.handleDeletePlot.bind(this);
- this.handleHighlightPlot = this.handleHighlightPlot.bind(this);
- this.handleArchivePlot = this.handleArchivePlot.bind(this);
-
- this.handleShowProfile = this.handleShowProfile.bind(this);
- this.handleHideProfile = this.handleHideProfile.bind(this);
- this.handleCreateProfile = this.handleCreateProfile.bind(this);
- this.handleRemoveProfile = this.handleRemoveProfile.bind(this);
- this.handleDeleteProfile = this.handleDeleteProfile.bind(this);
- this.handleProfileClick = this.handleProfileClick.bind(this);
- this.handleChangeDatatypeProfile = this.handleChangeDatatypeProfile.bind(this);
- this.setProjectProfiles = this.setProjectProfiles.bind(this);
- this.getProfilesLength = this.getProfilesLength.bind(this);
- this.getPlotsLength = this.getPlotsLength.bind(this);
-
- this.getFeatureByGidFromDataSource = this.getFeatureByGidFromDataSource.bind(this);
- this.handleNewPlotNameChange = this.handleNewPlotNameChange.bind(this);
- this.handlePlotSort = this.handlePlotSort.bind(this);
- this.getLicense = this.getLicense.bind(this);
-
- this.setModalScroll = this.setModalScroll.bind(this);
- this.getModalScroll = this.getModalScroll.bind(this);
-
- _self = this;
- }
-
- UNSAFE_componentWillMount() {
- $(window).resize(function () {
- clearTimeout(resizeTimeout);
- resizeTimeout = setTimeout(() => {
- _self.setState({lastUpdate: new Date()});
- }, 500);
- });
-
- this.props.backboneEvents.get().on(`session:authChange`, (authenticated) => {
- if (authenticated) {
- _self.refreshProfilesList();
- _self.hydratePlotsFromIds();
- } else {
- let newDashboardItems = [];
- _self.state.dashboardItems.map(item => {
- if (item.type === DASHBOARD_ITEM_PLOT || item.type === DASHBOARD_ITEM_PROJECT_PLOT) {
- newDashboardItems.push(JSON.parse(JSON.stringify(item)));
- }
- });
-
- _self.setState({
- profiles: [],
- activeProfiles: [],
- dashboardItems: newDashboardItems
- });
- }
+ static contextType = ProjectContext;
+
+ constructor(props) {
+ super(props);
+ let license = session.getProperties()?.["license"];
+
+ let dashboardItems = [];
+ if (this.props.initialPlots) {
+ this.props.initialPlots.map((item) => {
+ dashboardItems.push({
+ type: DASHBOARD_ITEM_PLOT,
+ item,
});
-
- this.refreshProfilesList();
- }
-
- componentDidMount() {
- this.nextDisplayType();
- }
-
- getLicense() {
- return this.state.license;
- }
-
- getModalScroll() {
- return this.state.modalScroll;
+ });
}
- setModalScroll(modalScroll) {
- this.setState({modalScroll});
- }
-
- refreshProfilesList() {
- this.profileManager.getAll().then(profiles => {
- let newDashboardItems = [];
- this.state.dashboardItems.map(item => {
- if (item.type !== DASHBOARD_ITEM_PROFILE) {
- newDashboardItems.push(JSON.parse(JSON.stringify(item)));
- }
- });
-
- profiles.map(item => {
- newDashboardItems.push({
- type: DASHBOARD_ITEM_PROFILE,
- item
- });
- });
-
- this.setState({
- profiles,
- dashboardItems: newDashboardItems
- });
- this.props.onProfilesChange(this.getProfiles());
-
- });
- }
-
- dehydratePlots(plots) {
- return this.plotManager.dehydratePlots(plots);
- }
-
- hydratePlotsFromIds(plots) {
- return this.plotManager.hydratePlotsFromIds(plots);
- }
-
- getProfiles() {
- let allProfiles = [];
- this.state.projectProfiles.map(item => {
- item.fromProject = true;
- allProfiles.push(item);
- });
- this.state.profiles.map(item => {
- allProfiles.push(item);
- })
- allProfiles = allProfiles.sort((a, b) => b['created_at'] - a['created_at']);
- return allProfiles;
- }
-
- getActiveProfiles() {
- return JSON.parse(JSON.stringify(this.state.activeProfiles));
- }
-
- getActiveProfileObjects() {
- let activeProfiles = this.getProfiles().filter((item) => {
- if (this.state.activeProfiles.indexOf(item.key) !== -1) {
- return item;
- }
- });
- return JSON.parse(JSON.stringify(activeProfiles));
- }
-
- handleCreateProfile(data, activateOnCreate = true, callback = false) {
- this.profileManager.create(data).then(newProfile => {
- let profilesCopy = JSON.parse(JSON.stringify(this.state.profiles));
- profilesCopy.unshift(newProfile);
-
- if (activateOnCreate) {
- let activeProfilesCopy = JSON.parse(JSON.stringify(this.state.activeProfiles));
- if (activeProfilesCopy.indexOf(newProfile.key) === -1) activeProfilesCopy.push(newProfile.key);
-
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.push({
- type: DASHBOARD_ITEM_PROFILE,
- item: newProfile
- });
-
- this.setState({
- profiles: profilesCopy,
- dashboardItems: dashboardItemsCopy,
- activeProfiles: activeProfilesCopy
- });
-
- this.props.onActiveProfilesChange(activeProfilesCopy);
- } else {
- this.setState({profiles: profilesCopy});
- }
-
- if (callback) callback();
-
- this.props.onProfilesChange(this.getProfiles());
- }).catch(error => {
- console.error(`Error occured while creating profile (${error})`);
- alert(`Error occured while creating profile (${error})`);
- if (callback) callback();
- });
- }
-
- handleChangeDatatypeProfile(profileKey) {
- let selectedProfile = false;
- this.getProfiles().map(item => {
- if (item.key === profileKey) {
- selectedProfile = item;
- }
- });
-
- if (selectedProfile === false) throw new Error(`Unable to find the profile with key ${profileKey}`);
-
- this.setState({createdProfileChemical: false}, () => {
- const abortDataTypeChange = () => {
- this.setState({createdProfileChemical: false});
- $('#' + SELECT_CHEMICAL_DIALOG_PREFIX).modal('hide');
- };
-
- const uniqueKey = uuidv1();
-
- try {
- ReactDOM.render(
-
- {
- this.setState({createdProfileChemical: selectorValue}, () => {
- try {
- ReactDOM.render(
- {
- $.snackbar({
- id: "snackbar-watsonc",
- content: "" + __("The profile with the new datatype is being created") + "",
- htmlAllowed: true,
- timeout: 1000000
- });
-
- this.handleCreateProfile({
- title,
- profile: selectedProfile.value.profile,
- buffer: selectedProfile.value.buffer,
- depth: selectedProfile.value.depth,
- compound: this.state.createdProfileChemical,
- boreholeNames: selectedProfile.value.boreholeNames,
- layers: selectedProfile.value.layers,
- }, true, () => {
- this.setState({createdProfileChemical: false}, () => {
- jquery("#snackbar-watsonc").snackbar("hide");
- });
- });
- }}
- onCancelControl={abortDataTypeChange}/>
-
, document.getElementById(`${TEXT_FIELD_DIALOG_PREFIX}-placeholder`));
- } catch (e) {
- console.error(e);
- }
-
- $('#' + TEXT_FIELD_DIALOG_PREFIX).modal({backdrop: `static`});
- });
+ this.state = {
+ view: VIEW_MATRIX,
+ newPlotName: ``,
+ dashboardItems,
+ plots: this.props.initialPlots,
+ projectPlots: [],
+ profiles: [],
+ projectProfiles: [],
+ activePlots: [],
+ activeProfiles: [],
+ dataSource: [],
+ highlightedPlot: false,
+ createdProfileChemical: false,
+ createdProfileName: false,
+ lastUpdate: false,
+ license: license,
+ modalScroll: {},
+ };
- $('#' + SELECT_CHEMICAL_DIALOG_PREFIX).modal('hide');
- }}
- onCancelControl={abortDataTypeChange}/>
-
-
, document.getElementById(`${SELECT_CHEMICAL_DIALOG_PREFIX}-placeholder`));
- } catch (e) {
- console.error(e);
+ this.plotManager = new PlotManager();
+ this.profileManager = new ProfileManager();
+
+ // this.handleHighlightPlot = this.handleHighlightPlot.bind(this);
+
+ this.handleShowProfile = this.handleShowProfile.bind(this);
+ this.handleHideProfile = this.handleHideProfile.bind(this);
+ this.handleCreateProfile = this.handleCreateProfile.bind(this);
+ this.handleAddProfile = this.handleAddProfile.bind(this);
+ this.handleDeleteProfile = this.handleDeleteProfile.bind(this);
+ this.handleProfileClick = this.handleProfileClick.bind(this);
+ this.handleChangeDatatypeProfile =
+ this.handleChangeDatatypeProfile.bind(this);
+ this.setProjectProfiles = this.setProjectProfiles.bind(this);
+ this.getProfilesLength = this.getProfilesLength.bind(this);
+ this.getPlotsLength = this.getPlotsLength.bind(this);
+ this.getDashboardItems = this.getDashboardItems.bind(this);
+
+ // this.getFeatureByGidFromDataSource = this.getFeatureByGidFromDataSource.bind(this);
+ // this.handleNewPlotNameChange = this.handleNewPlotNameChange.bind(this);
+ this.handlePlotSort = this.handlePlotSort.bind(this);
+ this.getLicense = this.getLicense.bind(this);
+
+ this.setModalScroll = this.setModalScroll.bind(this);
+ this.getModalScroll = this.getModalScroll.bind(this);
+
+ _self = this;
+ }
+
+ UNSAFE_componentWillMount() {
+ $(window).resize(function () {
+ clearTimeout(resizeTimeout);
+ resizeTimeout = setTimeout(() => {
+ _self.setState({ lastUpdate: new Date() });
+ }, 500);
+ });
+
+ this.props.backboneEvents
+ .get()
+ .on(`session:authChange`, (authenticated) => {
+ if (authenticated) {
+ } else {
+ let newDashboardItems = [];
+ _self.state.dashboardItems.map((item) => {
+ if (
+ item.type === DASHBOARD_ITEM_PLOT ||
+ item.type === DASHBOARD_ITEM_PROJECT_PLOT
+ ) {
+ newDashboardItems.push(JSON.parse(JSON.stringify(item)));
}
+ });
- $('#' + SELECT_CHEMICAL_DIALOG_PREFIX).modal({backdrop: `static`});
- });
- }
-
- handleProfileClick(e) {
- if (e && e.points && e.points.length === 1 && e.points[0].data && e.points[0].data.text) {
- if (e.points[0].data.text.indexOf(`DGU`) > -1) {
- let boreholeNumber = false;
- let lines = e.points[0].data.text.split(`
`);
- lines.map(item => {
- if (item.indexOf(`DGU`) > -1) {
- boreholeNumber = item.replace(`DGU`, ``)
- .replace(/>/g, ``)
- .replace(/ {
+ this.setState({ profiles: response });
+ this.props.setProfiles({ profiles: response });
+ });
+ this.props.backboneEvents.get().on("refresh:meta", () => {
+ this.profileManager.getAll().then((response) => {
+ this.setState({ profiles: response });
+ this.props.setProfiles({ profiles: response });
+ });
+ });
+ }
+
+ getLicense() {
+ return session.getProperties()?.["license"];
+ }
+
+ getModalScroll() {
+ return this.state.modalScroll;
+ }
+
+ setModalScroll(modalScroll) {
+ this.setState({ modalScroll });
+ }
+
+ getProfiles() {
+ let allProfiles = [];
+ this.state.projectProfiles.map((item) => {
+ item.fromProject = true;
+ allProfiles.push(item);
+ });
+ this.state.profiles.map((item) => {
+ allProfiles.push(item);
+ });
+ allProfiles = allProfiles.sort((a, b) => b["created_at"] - a["created_at"]);
+ return allProfiles;
+ }
+
+ getActiveProfiles() {
+ return JSON.parse(JSON.stringify(this.state.activeProfiles));
+ }
+
+ getActiveProfileObjects() {
+ let activeProfiles = this.getProfiles().filter((item) => {
+ if (this.state.activeProfiles.indexOf(item.key) !== -1) {
+ return item;
+ }
+ });
+ return JSON.parse(JSON.stringify(activeProfiles));
+ }
+
+ handleCreateProfile(data, activateOnCreate = true, callback = false) {
+ _self.profileManager
+ .create(data)
+ .then((newProfile) => {
+ let profilesCopy = JSON.parse(JSON.stringify(_self.state.profiles));
+ profilesCopy.unshift(newProfile);
+
+ if (activateOnCreate) {
+ let activeProfilesCopy = JSON.parse(
+ JSON.stringify(_self.state.activeProfiles)
+ );
+ if (activeProfilesCopy.indexOf(newProfile.key) === -1)
+ activeProfilesCopy.push(newProfile.key);
+
+ let dashboardItemsCopy = JSON.parse(
+ JSON.stringify(_self.state.dashboardItems)
+ );
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PROFILE,
+ item: newProfile,
+ });
+
+ _self.setState({
+ profiles: profilesCopy,
+ dashboardItems: dashboardItemsCopy,
+ activeProfiles: activeProfilesCopy,
+ });
+
+ _self.props.onActiveProfilesChange(
+ activeProfilesCopy,
+ profilesCopy,
+ _self.context
+ );
+ } else {
+ _self.setState({ profiles: profilesCopy });
}
- }
-
- handleDeleteProfile(profileKey, callback = false) {
- this.profileManager.delete(profileKey).then(() => {
- let profilesCopy = JSON.parse(JSON.stringify(this.state.profiles));
-
- let profileWasDeleted = false;
- profilesCopy.map((profile, index) => {
- if (profile.key === profileKey) {
- profilesCopy.splice(index, 1);
- profileWasDeleted = true;
- return false;
- }
- });
- if (profileWasDeleted === false) {
- console.warn(`Profile ${profileKey} was deleted only from backend storage`);
- }
+ if (callback) callback();
+
+ _self.props.onProfilesChange(_self.getProfiles());
+ })
+ .catch((error) => {
+ console.error(`Error occured while creating profile (${error})`);
+ alert(`Error occured while creating profile (${error})`);
+ if (callback) callback();
+ });
+ }
+
+ handleChangeDatatypeProfile(profileKey) {
+ let selectedProfile = false;
+ this.getProfiles().map((item) => {
+ if (item.key === profileKey) {
+ selectedProfile = item;
+ }
+ });
+
+ if (selectedProfile === false)
+ throw new Error(`Unable to find the profile with key ${profileKey}`);
+
+ this.setState({ createdProfileChemical: false }, () => {
+ const abortDataTypeChange = () => {
+ this.setState({ createdProfileChemical: false });
+ $("#" + SELECT_CHEMICAL_DIALOG_PREFIX).modal("hide");
+ };
+
+ const uniqueKey = uuidv1();
+
+ try {
+ ReactDOM.render(
+
+
+ {
+ this.setState(
+ { createdProfileChemical: selectorValue },
+ () => {
+ try {
+ ReactDOM.render(
+
+ {
+ $.snackbar({
+ id: "snackbar-watsonc",
+ content:
+ "" +
+ __(
+ "The profile with the new datatype is being created"
+ ) +
+ "",
+ htmlAllowed: true,
+ timeout: 1000000,
+ });
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.map((item, index) => {
- if (item.type === DASHBOARD_ITEM_PROFILE) {
- if (item.key === profileKey) {
- dashboardItemsCopy.splice(index, 1);
- return false;
+ this.handleCreateProfile(
+ {
+ title,
+ profile: selectedProfile.value.profile,
+ buffer: selectedProfile.value.buffer,
+ depth: selectedProfile.value.depth,
+ compound: this.state.createdProfileChemical,
+ boreholeNames:
+ selectedProfile.value.boreholeNames,
+ layers: selectedProfile.value.layers,
+ },
+ true,
+ () => {
+ this.setState(
+ { createdProfileChemical: false },
+ () => {
+ jquery("#snackbar-watsonc").snackbar(
+ "hide"
+ );
+ }
+ );
+ }
+ );
+ }}
+ onCancelControl={abortDataTypeChange}
+ />
+
,
+ document.getElementById(
+ `${TEXT_FIELD_DIALOG_PREFIX}-placeholder`
+ )
+ );
+ } catch (e) {
+ console.error(e);
+ }
+
+ $("#" + TEXT_FIELD_DIALOG_PREFIX).modal({
+ backdrop: `static`,
+ });
}
- }
- });
-
- let activeProfilesCopy = JSON.parse(JSON.stringify(this.state.activeProfiles));
- activeProfilesCopy.map((profile, index) => {
- if (profile === profileKey) {
- activeProfilesCopy.splice(index, 1);
- return false;
- }
- });
-
- if (callback) callback();
-
- this.setState({
- profiles: profilesCopy,
- activeProfiles: activeProfilesCopy,
- dashboardItems: dashboardItemsCopy
- });
- this.props.onProfilesChange(this.getProfiles());
-
- }).catch(error => {
- console.error(`Error occured while deleting profile (${error})`)
- });
- }
-
- handleShowProfile(profileId) {
- if (!profileId) throw new Error(`Empty profile identifier`);
-
- let activeProfiles = JSON.parse(JSON.stringify(this.state.activeProfiles));
- if (activeProfiles.indexOf(profileId) === -1) activeProfiles.push(profileId);
- this.setState({activeProfiles}, () => {
- this.props.onActiveProfilesChange(this.state.activeProfiles);
- });
- }
-
- handleHideProfile(profileId) {
- if (!profileId) throw new Error(`Empty profile identifier`);
-
- let activeProfiles = JSON.parse(JSON.stringify(this.state.activeProfiles));
- if (activeProfiles.indexOf(profileId) > -1) activeProfiles.splice(activeProfiles.indexOf(profileId), 1);
- this.setState({activeProfiles}, () => {
- this.props.onActiveProfilesChange(this.state.activeProfiles);
- });
- }
-
- getPlots(getArchived = true) {
- let allPlots = [];
- this.state.projectPlots.map((item) => {
- item.fromProject = true;
- allPlots.push(item);
+ );
+
+ $("#" + SELECT_CHEMICAL_DIALOG_PREFIX).modal("hide");
+ }}
+ onCancelControl={abortDataTypeChange}
+ />
+
+
,
+ document.getElementById(
+ `${SELECT_CHEMICAL_DIALOG_PREFIX}-placeholder`
+ )
+ );
+ } catch (e) {
+ console.error(e);
+ }
+
+ $("#" + SELECT_CHEMICAL_DIALOG_PREFIX).modal({ backdrop: `static` });
+ });
+ }
+
+ handleProfileClick(e) {
+ if (
+ e &&
+ e.points &&
+ e.points.length === 1 &&
+ e.points[0].data &&
+ e.points[0].data.text
+ ) {
+ if (e.points[0].data.text.indexOf(`DGU`) > -1) {
+ let boreholeNumber = false;
+ let lines = e.points[0].data.text.split(`
`);
+ lines.map((item) => {
+ if (item.indexOf(`DGU`) > -1) {
+ boreholeNumber = item
+ .replace(`DGU`, ``)
+ .replace(/>/g, ``)
+ .replace(/ {
- item.fromProject = false;
- allPlots.push(item);
- })
- allPlots = allPlots.filter((item) => {
- if (getArchived) {
- return item;
- } else if (!item.isArchived) {
- return item;
- }
- })
- allPlots = allPlots.sort((a, b) => b['created_at'] - a['created_at']);
- const unique = (myArr) => {
- return myArr.filter((obj, pos, arr) => {
- return arr.map(mapObj => mapObj["id"]).indexOf(obj["id"]) === pos;
- });
+ if (boreholeNumber !== false) {
+ this.props.onOpenBorehole(boreholeNumber);
}
- allPlots = unique(allPlots);
- return allPlots;
- }
-
- getActivePlots() {
- let activePlots = this.state.plots.filter((item) => {
- if (this.state.activePlots.indexOf(item.id) !== -1) {
- return item.id;
- }
- });
- this.state.projectPlots.map((item) => {
- activePlots.push(item.id);
- });
- return JSON.parse(JSON.stringify(activePlots));
- }
-
-
- addPlot(newPlotName, activateOnCreate = false) {
- this.handleCreatePlot(newPlotName, activateOnCreate);
- }
-
- setProjectPlots(projectPlots) {
- let dashboardItemsCopy = [];
- let plotsNotOnDashboard = [];
- let profilesNotOnDashboard = [];
- const unique = (data, key) => {
- return [...new Map(data.map(item => [key(item), item])).values()]
- };
-
- this.state.dashboardItems.map(item => {
- if (item.type !== DASHBOARD_ITEM_PROJECT_PLOT) {
- dashboardItemsCopy.push(item);
- }
- });
-
- projectPlots.map(item => {
- dashboardItemsCopy.push({
- type: DASHBOARD_ITEM_PROJECT_PLOT,
- item
- });
- });
-
- dashboardItemsCopy.map(item => {
- if (typeof item.type !== "undefined" && item.type === 0) {
- plotsNotOnDashboard.push(item.item.id);
- } else if (typeof item.type !== "undefined" && item.type === 3) {
- // Remove an id again if it appears more than once in this.state.dashboardItems
- plotsNotOnDashboard = plotsNotOnDashboard.filter(e => e !== item.item.id);
- }
- if (typeof item.type !== "undefined" && item.type === 1) {
- profilesNotOnDashboard.push(item.item.key);
- } else if (typeof item.type !== "undefined" && item.type === 2) {
- // Remove a key again if it appears more than once in this.state.dashboardItems
- profilesNotOnDashboard = profilesNotOnDashboard.filter(e => e !== item.item.key);
- }
- });
-
- // Remove duplets
- dashboardItemsCopy = unique(dashboardItemsCopy, item => item.item.id);
- this.setState({projectPlots, dashboardItems: dashboardItemsCopy}, () => {
- setTimeout(() => {
- plotsNotOnDashboard.forEach(id => this.handleHidePlot(id));
- profilesNotOnDashboard.forEach(id => this.handleHideProfile(id));
- }, 1000)
- });
+ }
}
-
- setPlots(plots) {
- let dashboardItemsCopy = [];
- this.state.dashboardItems.map(item => {
- if (item.type !== DASHBOARD_ITEM_PLOT /* type 0 */ && item.type !== DASHBOARD_ITEM_PROJECT_PLOT /* type 3*/) {
- dashboardItemsCopy.push(item);
- }
+ }
+
+ handleDeleteProfile(profileKey, callback = false) {
+ this.profileManager
+ .delete(profileKey)
+ .then(() => {
+ var profilesCopy = JSON.parse(JSON.stringify(this.state.profiles));
+ let profileWasDeleted = false;
+ profilesCopy = profilesCopy.filter((profile) => {
+ if (profile.key === profileKey) {
+ return false;
+ }
+ return true;
});
- plots.map(item => {
- dashboardItemsCopy.push({
- type: DASHBOARD_ITEM_PLOT,
- item
- });
- });
-
- this.setState({plots, dashboardItems: dashboardItemsCopy});
- }
-
- setProjectProfiles(projectProfiles) {
- const unique = (myArr) => {
- return myArr.filter((obj, pos, arr) => {
- return arr.map(mapObj => mapObj["key"]).indexOf(obj["key"]) === pos;
- });
+ if (profileWasDeleted === false) {
+ console.warn(
+ `Profile ${profileKey} was deleted only from backend storage`
+ );
}
- // Remove duplets
- projectProfiles = unique(projectProfiles);
- let dashboardItemsCopy = [];
- this.state.dashboardItems.map(item => {
- if (item.type !== DASHBOARD_ITEM_PROJECT_PROFILE) {
- dashboardItemsCopy.push(item);
- }
- });
- projectProfiles.map(item => {
- dashboardItemsCopy.push({
- type: DASHBOARD_ITEM_PROJECT_PROFILE,
- item
- });
- this.handleShowProfile(item.key);
- })
- this.setState({projectProfiles, dashboardItems: dashboardItemsCopy});
- }
- getProfilesLength() {
- let activeProfiles = [];
- this.state.profiles.map((item) => {
- if (activeProfiles.indexOf(item.key) === -1) {
- activeProfiles.push(item.key);
- }
- });
- this.state.activeProfiles.map((item) => {
- if (activeProfiles.indexOf(item.key) === -1) {
- activeProfiles.push(item.key);
+ var dashboardItemsCopy = JSON.parse(
+ JSON.stringify(this.state.dashboardItems)
+ );
+ dashboardItemsCopy = dashboardItemsCopy.filter((item) => {
+ if (item.type === DASHBOARD_ITEM_PROFILE) {
+ if (item.key === profileKey) {
+ return false;
}
+ }
+ return true;
});
- return activeProfiles.length;
- }
-
- getPlotsLength() {
- return this.state.plots.length + this.state.projectPlots.length;
- }
- syncPlotData() {
- let activePlots = this.state.activePlots;
- let plots = this.state.dashboardItems.map(e => e.item);
- let newPlots = plots;
- let preCount = 0;
- let count = 0;
- plots.forEach((e, i) => {
- if ('id' in e) {
- let obj = e.measurements;
- if (obj && Object.keys(obj).length === 0 && obj.constructor === Object) {
- preCount++;
- } else if (!obj) {
- preCount++;
- } else {
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- preCount++;
- }
- }
- }
- }
- });
- plots.forEach((e, i) => {
- if ('id' in e) {
- let obj = e.measurementsCachedData;
- let shadowI = i;
- newPlots[shadowI] = e;
- if (!e.measurementsCachedData) {
- e.measurementsCachedData = {};
- }
- for (let index in e.measurements) {
- let key = e.measurements[index];
- // Lazy load data and sync
- // Only load active plots
- if (activePlots.includes(e.id)) {
- getPlotData(key).then((response) => {
- if (!newPlots[shadowI].measurementsCachedData[key]) {
- newPlots[shadowI].measurementsCachedData[key] = {};
- }
- newPlots[shadowI].measurementsCachedData[key].data = response.data.features[0];
- count++;
- if (count === preCount) {
- console.log("All plots synced");
- _self.setPlots(newPlots);
- }
- }).catch((error) => {
- console.log(error);
- })
- } else {
- count++;
- }
- }
- }
+ var activeProfilesCopy = JSON.parse(
+ JSON.stringify(this.state.activeProfiles)
+ );
+ activeProfilesCopy = activeProfilesCopy.filter((profile) => {
+ if (profile === profileKey) {
+ return false;
+ }
+ return true;
});
- }
-
- handleCreatePlot(title, activateOnCreate = false) {
- this.plotManager.create(title).then(newPlot => {
- let plotsCopy = JSON.parse(JSON.stringify(this.state.plots));
- plotsCopy.unshift(newPlot);
-
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.push({
- type: DASHBOARD_ITEM_PLOT,
- item: newPlot
- });
- if (activateOnCreate) {
- let activePlotsCopy = JSON.parse(JSON.stringify(this.state.activePlots));
- if (activePlotsCopy.indexOf(newPlot.id) === -1) activePlotsCopy.push(newPlot.id);
-
- this.setState({
- plots: plotsCopy,
- dashboardItems: dashboardItemsCopy,
- activePlots: activePlotsCopy
- });
-
- this.props.onActivePlotsChange(activePlotsCopy, this.getPlots());
- } else {
- this.setState({
- plots: plotsCopy,
- dashboardItems: dashboardItemsCopy
- });
- }
+ if (callback) callback();
- this.props.onPlotsChange(this.getPlots());
- }).catch(error => {
- console.error(`Error occured while creating plot (${error})`)
+ this.setState({
+ profiles: profilesCopy,
+ activeProfiles: activeProfilesCopy,
+ dashboardItems: dashboardItemsCopy,
});
+ this.props.onProfilesChange(this.getProfiles());
+ this.props.onActiveProfilesChange(
+ activeProfilesCopy,
+ profilesCopy,
+ this.context
+ );
+ })
+ .catch((error) => {
+ console.error(`Error occured while deleting profile (${error})`);
+ });
+ }
+
+ handleShowProfile(profileId) {
+ if (!profileId) throw new Error(`Empty profile identifier`);
+
+ let activeProfiles = JSON.parse(JSON.stringify(this.state.activeProfiles));
+ if (activeProfiles.indexOf(profileId) === -1)
+ activeProfiles.push(profileId);
+ this.setState({ activeProfiles }, () => {
+ this.props.onActiveProfilesChange(
+ this.state.activeProfiles,
+ this.state.profiles,
+ this.context
+ );
+ });
+ }
+
+ handleAddProfile(profile) {
+ let dashboardItemsCopy = [];
+ let activeProfiles = [];
+
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PROFILE,
+ item: profile,
+ });
+
+ this.state.dashboardItems.forEach((item) => {
+ dashboardItemsCopy.push(item);
+ if (item.type === DASHBOARD_ITEM_PROFILE) {
+ activeProfiles.push(item.item.key);
+ }
+ });
+
+ activeProfiles.push(profile.key);
+
+ if (reduxStore.getState().global.dashboardMode === "minimized") {
+ reduxStore.dispatch(setDashboardMode("half"));
}
- handleRemoveProfile(profileKey) {
- if (!profileKey) throw new Error(`Empty profile key`);
-
- let activeProfilesCopy = JSON.parse(JSON.stringify(this.state.activeProfiles));
- if (activeProfilesCopy.indexOf(profileKey) > -1) activeProfilesCopy.splice(activeProfilesCopy.indexOf(profileKey), 1);
-
- this.setState({activeProfiles: activeProfilesCopy});
- this.props.onActiveProfilesChange(activeProfilesCopy);
- }
-
- handleRemovePlot(id) {
- if (!id) throw new Error(`Empty plot identifier`);
-
- let activePlotsCopy = JSON.parse(JSON.stringify(this.state.activePlots));
- if (activePlotsCopy.indexOf(id) > -1) activePlotsCopy.splice(activePlotsCopy.indexOf(id), 1);
-
- this.setState({activePlots: activePlotsCopy});
- this.props.onActivePlotsChange(activePlotsCopy, this.getPlots());
- }
-
- handleDeletePlot(id, name) {
- if (!id) throw new Error(`Empty plot identifier`);
-
- if (confirm(__(`Delete plot`) + ` ${name ? name : id}?`)) {
- this.plotManager.delete(id).then(() => {
- let plotsCopy = JSON.parse(JSON.stringify(this.state.plots));
- let plotWasDeleted = false;
- plotsCopy.map((plot, index) => {
- if (plot.id === id) {
- plotsCopy.splice(index, 1);
- plotWasDeleted = true;
- return false;
- }
- });
-
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.map((item, index) => {
- if (item.type === DASHBOARD_ITEM_PLOT || item.type === DASHBOARD_ITEM_PROJECT_PLOT) {
- if (item.item.id === id) {
- dashboardItemsCopy.splice(index, 1);
- return false;
- }
- }
- });
-
- if (plotWasDeleted === false) {
- console.warn(`Plot ${id} was deleted only from backend storage`);
- }
-
- this.setState({
- plots: plotsCopy,
- dashboardItems: dashboardItemsCopy
- });
+ document.getElementById("chartsContainer").scrollTop = 0;
+
+ this.setState(
+ {
+ dashboardItems: dashboardItemsCopy,
+ activeProfiles: activeProfiles,
+ },
+ () => {
+ this.props.onActiveProfilesChange(
+ this.state.activeProfiles,
+ this.state.profiles,
+ this.context
+ );
+ }
+ );
+ }
+
+ handleHideProfile(profileId) {
+ if (!profileId) throw new Error(`Empty profile identifier`);
+
+ let activeProfiles = JSON.parse(JSON.stringify(this.state.activeProfiles));
+ if (activeProfiles.indexOf(profileId) > -1)
+ activeProfiles.splice(activeProfiles.indexOf(profileId), 1);
+ this.setState({ activeProfiles }, () => {
+ this.props.onActiveProfilesChange(
+ this.state.activeProfiles,
+ this.state.profiles,
+ this.context
+ );
+ });
+ }
+
+ getDashboardItems() {
+ return this.state.dashboardItems;
+ }
+
+ getPlots() {
+ return this.state.plots;
+ }
+
+ getActivePlots() {
+ let addedPlots = [];
+ let activePlots = this.state.plots.filter((item) => {
+ if (
+ this.state?.activePlots &&
+ addedPlots &&
+ this.state.activePlots.indexOf(item.id) !== -1 &&
+ addedPlots.indexOf(item.id) === -1
+ ) {
+ addedPlots.push(item.id);
+ return item.id;
+ }
+ });
+ this.state.projectPlots.map((item) => {
+ if (
+ this.state?.activePlots &&
+ addedPlots &&
+ this.state.activePlots.indexOf(item.id) !== -1 &&
+ addedPlots.indexOf(item.id) === -1
+ ) {
+ addedPlots.push(item.id);
+ activePlots.push(item);
+ }
+ });
+ return JSON.parse(JSON.stringify(activePlots));
+ }
+
+ setProjectPlots(projectPlots) {
+ let dashboardItemsCopy = [];
+ let plotsNotOnDashboard = [];
+ let profilesNotOnDashboard = [];
+ const unique = (data, key) => {
+ return [...new Map(data.map((item) => [key(item), item])).values()];
+ };
- this.props.onPlotsChange(this.getPlots());
- }).catch(error => {
- console.error(`Error occured while creating plot (${error})`)
- });
+ this.state.dashboardItems.map((item) => {
+ if (item.type !== DASHBOARD_ITEM_PROJECT_PLOT) {
+ dashboardItemsCopy.push(item);
+ }
+ });
+
+ projectPlots.map((item) => {
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PROJECT_PLOT,
+ item,
+ });
+ });
+
+ dashboardItemsCopy.map((item) => {
+ if (typeof item.type !== "undefined" && item.type === 0) {
+ plotsNotOnDashboard.push(item.item.id);
+ } else if (typeof item.type !== "undefined" && item.type === 3) {
+ // Remove an id again if it appears more than once in this.state.dashboardItems
+ plotsNotOnDashboard = plotsNotOnDashboard.filter(
+ (e) => e !== item.item.id
+ );
+ }
+ if (typeof item.type !== "undefined" && item.type === 1) {
+ profilesNotOnDashboard.push(item.item.key);
+ } else if (typeof item.type !== "undefined" && item.type === 2) {
+ // Remove a key again if it appears more than once in this.state.dashboardItems
+ profilesNotOnDashboard = profilesNotOnDashboard.filter(
+ (e) => e !== item.item.key
+ );
+ }
+ });
+
+ // Remove duplets
+ dashboardItemsCopy = unique(dashboardItemsCopy, (item) => item.item.id);
+ this.setState({ projectPlots, dashboardItems: dashboardItemsCopy }, () => {
+ setTimeout(() => {
+ plotsNotOnDashboard.forEach((id) => this.handleHidePlot(id));
+ profilesNotOnDashboard.forEach((id) => this.handleHideProfile(id));
+ }, 1000);
+ });
+ }
+
+ setActiveProfiles(activeProfiles) {
+ if (typeof activeProfiles !== "undefined") {
+ let dashboardItemsCopy = [];
+ this.state.dashboardItems.map((item) => {
+ if (
+ item.type !== DASHBOARD_ITEM_PROFILE /* type 0 */ &&
+ item.type !== DASHBOARD_ITEM_PROJECT_PLOT /* type 3*/
+ ) {
+ dashboardItemsCopy.push(item);
}
- }
-
- handleHighlightPlot(plotId) {
- if (!plotId) throw new Error(`Empty plot identifier`);
-
- this.setState({highlightedPlot: (plotId === this.state.highlightedPlot ? false : plotId)}, () => {
- this.props.onHighlightedPlotChange(this.state.highlightedPlot, this.state.plots);
- });
- }
-
- handleShowPlot(plotId) {
- if (!plotId) throw new Error(`Empty plot identifier`);
-
- let activePlots = JSON.parse(JSON.stringify(this.state.activePlots));
-
- if (activePlots.indexOf(plotId) === -1) activePlots.push(plotId);
- let plots = this.getPlots()
- this.setState({activePlots}, () => {
- this.props.onActivePlotsChange(this.state.activePlots, plots);
- setTimeout(() => {
- // document.getElementById("syncWithDatabaseBtn").click();
- this.syncPlotData();
- }, 200);
- });
- }
-
- handleHidePlot(plotId) {
- if (!plotId) throw new Error(`Empty plot identifier`);
+ });
- let activePlots = JSON.parse(JSON.stringify(this.state.activePlots));
- if (activePlots.indexOf(plotId) > -1) activePlots.splice(activePlots.indexOf(plotId), 1);
- this.setState({activePlots}, () => {
- this.props.onActivePlotsChange(this.state.activePlots, this.getPlots());
- });
- }
-
- handleNewPlotNameChange(event) {
- this.setState({newPlotName: event.target.value});
- }
-
- handleArchivePlot(plotId, isArchived) {
- let plots = JSON.parse(JSON.stringify(this.state.plots));
- let correspondingPlot = false;
- let correspondingPlotIndex = false;
- plots.map((plot, index) => {
- if (plot.id == plotId) {
- correspondingPlot = plot;
- correspondingPlotIndex = index;
- }
+ let profiles = this.getProfiles().filter((elem) =>
+ activeProfiles.includes(elem.key)
+ );
+ profiles.map((item) => {
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PROFILE,
+ item,
});
- if (correspondingPlot === false) throw new Error(`Plot with id ${plotId} does not exist`);
- correspondingPlot.isArchived = isArchived;
- plots[correspondingPlotIndex] = correspondingPlot;
-
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.map((item, index) => {
- if (item.type === DASHBOARD_ITEM_PLOT || item.type === DASHBOARD_ITEM_PROJECT_PLOT) {
- if (item.item.id === correspondingPlot.id) {
- dashboardItemsCopy[index].item = correspondingPlot;
- return false;
- }
- }
- });
- this.plotManager.update(correspondingPlot).then(() => {
- this.setState({
- plots,
- dashboardItems: dashboardItemsCopy
- });
-
- this.props.onPlotsChange(this.getPlots());
- }).catch(error => {
- console.error(`Error occured while updating plot (${error})`)
- });
+ });
+
+ this.setState(
+ { activeProfiles, dashboardItems: dashboardItemsCopy },
+ () => {
+ this.props.onActiveProfilesChange(
+ this.state.activeProfiles,
+ this.state.profiles,
+ this.context
+ );
+ }
+ );
}
-
- _modifyAxes(plotId, gid, measurementKey, measurementIntakeIndex, action) {
- if (!plotId) throw new Error(`Invalid plot identifier`);
- if ((!gid && gid !== 0) || !measurementKey || (!measurementIntakeIndex && measurementIntakeIndex !== 0)) throw new Error(`Invalid measurement location parameters`);
-
- let plots = JSON.parse(JSON.stringify(this.state.plots));
- let correspondingPlot = false;
- let correspondingPlotIndex = false;
- plots.map((plot, index) => {
- if (plot.id === plotId) {
- correspondingPlot = plot;
- correspondingPlotIndex = index;
- }
- });
-
- if (correspondingPlot === false) throw new Error(`Plot with id ${plotId} does not exist`);
- let measurementIndex = gid + ':' + measurementKey + ':' + measurementIntakeIndex;
- if (action === `add`) {
- if (correspondingPlot.measurements.indexOf(measurementIndex) === -1) {
- let measurementData = this.getFeatureByGidFromDataSource(gid);
- if (measurementData) {
- var currentTime = new Date();
- correspondingPlot.measurements.push(measurementIndex);
- correspondingPlot.measurementsCachedData[measurementIndex] = {
- data: measurementData,
- created_at: currentTime.toISOString()
- }
- } else {
- throw new Error(`Unable to find data for measurement index ${measurementIndex}`);
- }
- }
- } else if (action === `delete`) {
- if (correspondingPlot.measurements.indexOf(measurementIndex) === -1) {
- throw new Error(`Unable to find measurement ${measurementIndex} for ${plotId} plot`);
- } else {
- if (measurementIndex in correspondingPlot.measurementsCachedData) {
- correspondingPlot.measurements.splice(correspondingPlot.measurements.indexOf(measurementIndex), 1);
- delete correspondingPlot.measurementsCachedData[measurementIndex];
- } else {
- throw new Error(`Data integrity violation: plot ${plotId} does not contain cached data for measurement ${measurementIndex}`);
- }
- }
+ }
+
+ setProfiles(profiles) {
+ this.setState(profiles, () => {
+ this.props.onProfilesChange(this.getProfiles());
+ });
+ }
+
+ setPlots(plots) {
+ let dashboardItemsCopy = [];
+ this.state.dashboardItems.map((item) => {
+ if (
+ item.type !== DASHBOARD_ITEM_PLOT /* type 0 */ &&
+ item.type !== DASHBOARD_ITEM_PROJECT_PLOT /* type 3*/
+ ) {
+ dashboardItemsCopy.push(item);
+ }
+ });
+
+ plots.map((item) => {
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PLOT,
+ item,
+ });
+ });
+
+ this.setState({ plots, dashboardItems: dashboardItemsCopy }, () => {
+ this.props.onPlotsChange(this.getPlots(), this.context);
+ });
+ }
+
+ setItems(items) {
+ let dashboardItemsCopy = [];
+
+ items.map((item) => {
+ dashboardItemsCopy.push({
+ type: item?.key ? DASHBOARD_ITEM_PROFILE : DASHBOARD_ITEM_PLOT,
+ item,
+ });
+ });
+
+ this.setState(
+ {
+ // profiles: items.filter((e) => !!e?.key),
+ activeProfiles: items.filter((e) => !!e?.key).map((e) => e.key),
+ plots: items.filter((e) => !!e?.id),
+ dashboardItems: dashboardItemsCopy,
+ },
+ () => {
+ // this.props.onProfilesChange(this.getProfiles());
+ this.props.onPlotsChange(this.getPlots(), this.context);
+ this.props.onActiveProfilesChange(
+ this.state.activeProfiles,
+ this.state.profiles,
+ this.context
+ );
+ }
+ );
+ }
+
+ setActivePlots(activePlots) {
+ this.setState({ activePlots });
+ }
+
+ setProjectProfiles(projectProfiles) {
+ const unique = (myArr) => {
+ return myArr.filter((obj, pos, arr) => {
+ return arr.map((mapObj) => mapObj["key"]).indexOf(obj["key"]) === pos;
+ });
+ };
+ // Remove duplets
+ projectProfiles = unique(projectProfiles);
+ let dashboardItemsCopy = [];
+ this.state.dashboardItems.map((item) => {
+ if (item.type !== DASHBOARD_ITEM_PROJECT_PROFILE) {
+ dashboardItemsCopy.push(item);
+ }
+ });
+ projectProfiles.map((item) => {
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PROJECT_PROFILE,
+ item,
+ });
+ this.handleShowProfile(item.key);
+ });
+ this.setState({ projectProfiles, dashboardItems: dashboardItemsCopy });
+ }
+
+ getProfilesLength() {
+ let activeProfiles = [];
+ this.state.profiles.map((item) => {
+ if (activeProfiles.indexOf(item.key) === -1) {
+ activeProfiles.push(item.key);
+ }
+ });
+ this.state.activeProfiles.map((item) => {
+ if (activeProfiles.indexOf(item.key) === -1) {
+ activeProfiles.push(item.key);
+ }
+ });
+ return activeProfiles.length;
+ }
+
+ getPlotsLength() {
+ return this.state.plots.length + this.state.projectPlots.length;
+ }
+
+ handleHighlightPlot(plotId) {
+ if (!plotId) throw new Error(`Empty plot identifier`);
+
+ this.setState(
+ {
+ highlightedPlot: plotId === this.state.highlightedPlot ? false : plotId,
+ },
+ () => {
+ this.props.onHighlightedPlotChange(
+ this.state.highlightedPlot,
+ this.state.plots
+ );
+ }
+ );
+ }
+
+ handleNewPlotNameChange(event) {
+ this.setState({ newPlotName: event.target.value });
+ }
+
+ _modifyAxes(
+ plotId,
+ gid,
+ measurementKey,
+ measurementIntakeIndex,
+ action,
+ measurementsData,
+ relation
+ ) {
+ if (!plotId) throw new Error(`Invalid plot identifier`);
+ if (
+ (!gid && gid !== 0) ||
+ !measurementKey ||
+ (!measurementIntakeIndex && measurementIntakeIndex !== 0)
+ )
+ throw new Error(`Invalid measurement location parameters`);
+
+ let plots = JSON.parse(JSON.stringify(this.state.plots));
+ let correspondingPlot = false;
+ let correspondingPlotIndex = false;
+ plots.map((plot, index) => {
+ if (plot.id === plotId) {
+ correspondingPlot = plot;
+ correspondingPlotIndex = index;
+ }
+ });
+
+ if (correspondingPlot === false)
+ throw new Error(`Plot with id ${plotId} does not exist`);
+ let measurementIndex = gid + ":_0:" + measurementIntakeIndex;
+ if (action === `add`) {
+ if (correspondingPlot.measurements.indexOf(measurementIndex) === -1) {
+ let measurementData = this.getFeatureByGidFromDataSource(gid);
+ if (measurementsData) {
+ measurementData = measurementsData;
+ }
+ if (measurementData) {
+ correspondingPlot.measurements.push(measurementIndex);
+ correspondingPlot.measurementsCachedData[measurementIndex] =
+ measurementData;
+ correspondingPlot.relations[measurementIndex] = relation;
} else {
- throw new Error(`Unrecognized action ${action}`);
+ throw new Error(
+ `Unable to find data for measurement index ${measurementIndex}`
+ );
}
-
- plots[correspondingPlotIndex] = correspondingPlot;
-
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.map((item, index) => {
- if (item.type === DASHBOARD_ITEM_PLOT || item.type === DASHBOARD_ITEM_PROJECT_PLOT) {
- if (item.item.id === correspondingPlot.id) {
- dashboardItemsCopy[index].item = correspondingPlot;
- return false;
- }
- }
- });
- //console.log("dashboardItemsCopy", dashboardItemsCopy)
- //console.log("plots", plots)
- this.plotManager.update(correspondingPlot).then(() => {
- this.setState({
- plots,
- projectPlots: plots,
- dashboardItems: dashboardItemsCopy
- });
- this.props.onPlotsChange(this.getPlots());
- }).catch(error => {
- console.error(`Error occured while updating plot (${error})`)
- });
- }
-
- setDataSource(dataSource) {
- let plots = JSON.parse(JSON.stringify(this.state.plots));
- let updatePlotsPromises = [];
- for (let i = 0; i < plots.length; i++) {
- let plot = plots[i];
- let plotWasUpdatedAtLeastOnce = false;
- if (typeof plot.measurements === "undefined") {
- break;
- }
- plot.measurements.map(measurementIndex => {
- let splitMeasurementIndex = measurementIndex.split(`:`);
- if (splitMeasurementIndex.length !== 3 && splitMeasurementIndex.length !== 4) throw new Error(`Invalid measurement index`);
- let measurementData = false;
- dataSource.map(item => {
- if (item.properties.boreholeno === parseInt(splitMeasurementIndex[0])) {
- measurementData = item;
- return false;
- }
- });
-
- if (measurementData) {
- let currentTime = new Date();
- plot.measurementsCachedData[measurementIndex] = {
- data: measurementData,
- created_at: currentTime.toISOString()
- };
-
- plotWasUpdatedAtLeastOnce = true;
- }
- });
-
- if (plotWasUpdatedAtLeastOnce) {
- updatePlotsPromises.push(this.plotManager.update(plot));
- }
+ }
+ } else if (action === `delete`) {
+ if (correspondingPlot.measurements.indexOf(measurementIndex) === -1) {
+ throw new Error(
+ `Unable to find measurement ${measurementIndex} for ${plotId} plot`
+ );
+ } else {
+ if (measurementIndex in correspondingPlot.measurementsCachedData) {
+ correspondingPlot.measurements.splice(
+ correspondingPlot.measurements.indexOf(measurementIndex),
+ 1
+ );
+ delete correspondingPlot.measurementsCachedData[measurementIndex];
+ } else {
+ throw new Error(
+ `Data integrity violation: plot ${plotId} does not contain cached data for measurement ${measurementIndex}`
+ );
}
-
- Promise.all(updatePlotsPromises).then(() => {
- let dashboardItemsCopy = JSON.parse(JSON.stringify(this.state.dashboardItems));
- dashboardItemsCopy.map((item, index) => {
- if (item.type === DASHBOARD_ITEM_PLOT || item.type === DASHBOARD_ITEM_PROJECT_PLOT) {
- plots.map(updatedPlot => {
- if (item.item.id === updatedPlot.id) {
- dashboardItemsCopy[index].item = updatedPlot;
- return false;
- }
- });
- }
- });
-
- this.setState({dataSource, plots, dashboardItems: dashboardItemsCopy});
- }).catch(errors => {
- console.error(`Unable to update measurement data upon updating the data source`, errors);
- });
+ }
+ } else {
+ throw new Error(`Unrecognized action ${action}`);
}
-
- getFeatureByGidFromDataSource(boreholeno, check = true) {
- if (check === false) {
- throw new Error(`Invalid boreholeno ${boreholeno} was provided`);
+ plots[correspondingPlotIndex] = correspondingPlot;
+
+ let dashboardItemsCopy = JSON.parse(
+ JSON.stringify(this.state.dashboardItems)
+ );
+ dashboardItemsCopy.map((item, index) => {
+ if (
+ item.type === DASHBOARD_ITEM_PLOT ||
+ item.type === DASHBOARD_ITEM_PROJECT_PLOT
+ ) {
+ if (item.item.id === correspondingPlot.id) {
+ dashboardItemsCopy[index].item = correspondingPlot;
+ return false;
}
-
- let featureWasFound = false;
- this.state.dataSource.map(item => {
- if (item.properties.boreholeno === boreholeno) {
- featureWasFound = item;
- return false;
- }
+ }
+ });
+ this.setState({
+ plots,
+ projectPlots: plots,
+ dashboardItems: dashboardItemsCopy,
+ });
+ this.plotManager
+ .update(correspondingPlot)
+ .then(() => {
+ this.props.onPlotsChange(this.getPlots(), this.context);
+ })
+ .catch((error) => {
+ console.error(`Error occured while updating plot (${error})`);
+ });
+ }
+
+ setDataSource(dataSource) {
+ let plots = JSON.parse(JSON.stringify(this.state.plots));
+ let updatePlotsPromises = [];
+ for (let i = 0; i < plots.length; i++) {
+ let plot = plots[i];
+ let plotWasUpdatedAtLeastOnce = false;
+ if (typeof plot.measurements === "undefined") {
+ break;
+ }
+ plot.measurements.map((measurementIndex) => {
+ let splitMeasurementIndex = measurementIndex.split(`:`);
+ if (
+ splitMeasurementIndex.length !== 3 &&
+ splitMeasurementIndex.length !== 4
+ )
+ throw new Error(`Invalid measurement index`);
+ let measurementData = false;
+ dataSource.map((item) => {
+ if (
+ item.properties.boreholeno === parseInt(splitMeasurementIndex[0])
+ ) {
+ measurementData = item;
+ return false;
+ }
});
- return featureWasFound;
- }
-
- addMeasurement(plotId, gid, measurementKey, measurementIntakeIndex) {
- this._modifyAxes(plotId, gid, measurementKey, measurementIntakeIndex, `add`);
- }
-
- deleteMeasurement(plotId, gid, measurementKey, measurementIntakeIndex) {
- this._modifyAxes(plotId, gid, measurementKey, measurementIntakeIndex, `delete`);
- }
+ if (measurementData) {
+ let currentTime = new Date();
+ plot.measurementsCachedData[measurementIndex] = {
+ data: measurementData,
+ created_at: currentTime.toISOString(),
+ };
- handlePlotSort({oldIndex, newIndex}) {
- this.setState(({dashboardItems}) => ({
- dashboardItems: arrayMove(dashboardItems, oldIndex, newIndex)
- }));
- };
-
- onSetMin() {
- $(PLOTS_ID).animate({
- top: ($(document).height() - modalHeaderHeight) + 'px'
- }, 500, function () {
- $(PLOTS_ID).find('.modal-body').css(`max-height`, modalHeaderHeight + 'px');
- });
+ plotWasUpdatedAtLeastOnce = true;
+ }
+ });
- $('.js-expand-less').hide();
- $('.js-expand-half').show();
- $('.js-expand-more').show();
- $(PLOTS_ID + ' .modal-body').css(`visibility`, `hidden`);
+ if (plotWasUpdatedAtLeastOnce) {
+ updatePlotsPromises.push(this.plotManager.update(plot));
+ }
}
- onSetHalf() {
- $(PLOTS_ID).animate({
- top: "50%"
- }, 500, function () {
- $(PLOTS_ID).find('.modal-body').css(`max-height`, ($(document).height() * 0.5 - modalHeaderHeight - 20) + 'px');
+ Promise.all(updatePlotsPromises)
+ .then(() => {
+ let dashboardItemsCopy = JSON.parse(
+ JSON.stringify(this.state.dashboardItems)
+ );
+ dashboardItemsCopy.map((item, index) => {
+ if (
+ item.type === DASHBOARD_ITEM_PLOT ||
+ item.type === DASHBOARD_ITEM_PROJECT_PLOT
+ ) {
+ plots.map((updatedPlot) => {
+ if (item.item.id === updatedPlot.id) {
+ dashboardItemsCopy[index].item = updatedPlot;
+ return false;
+ }
+ });
+ }
});
- $('.js-expand-less').show();
- $('.js-expand-half').hide();
- $('.js-expand-more').show();
- $(PLOTS_ID + ' .modal-body').css(`visibility`, `visible`);
- }
-
- onSetMax() {
- $(PLOTS_ID).animate({
- top: "10%"
- }, 500, function () {
- $(PLOTS_ID).find('.modal-body').css(`max-height`, ($(document).height() * 0.9 - modalHeaderHeight - 10) + 'px');
+ this.setState({
+ dataSource,
+ plots,
+ dashboardItems: dashboardItemsCopy,
});
-
- $('.js-expand-less').show();
- $('.js-expand-half').show();
- $('.js-expand-more').hide();
- $(PLOTS_ID + ' .modal-body').css(`visibility`, `visible`);
+ })
+ .catch((errors) => {
+ console.error(
+ `Unable to update measurement data upon updating the data source`,
+ errors
+ );
+ });
+ }
+
+ getFeatureByGidFromDataSource(boreholeno, check = true) {
+ if (check === false) {
+ throw new Error(`Invalid boreholeno ${boreholeno} was provided`);
}
- nextDisplayType() {
- if (currentDisplay === DISPLAY_MIN) {
- this.onSetHalf();
- currentDisplay = DISPLAY_HALF;
- previousDisplay = DISPLAY_MIN;
- } else if (currentDisplay === DISPLAY_HALF) {
- if (previousDisplay === DISPLAY_MIN) {
- this.onSetMax();
- currentDisplay = DISPLAY_MAX;
- } else {
- this.onSetMin();
- currentDisplay = DISPLAY_MIN;
- }
-
- previousDisplay = DISPLAY_HALF;
- } else if (currentDisplay === DISPLAY_MAX) {
- this.onSetHalf();
- currentDisplay = DISPLAY_HALF;
- previousDisplay = DISPLAY_MAX;
- }
+ let featureWasFound = false;
+ this.state.dataSource.map((item) => {
+ if (item.properties.boreholeno === boreholeno) {
+ featureWasFound = item;
+ return false;
+ }
+ });
+
+ return featureWasFound;
+ }
+
+ addMeasurement(
+ plotId,
+ gid,
+ measurementKey,
+ measurementIntakeIndex,
+ measurementsData,
+ relation
+ ) {
+ this._modifyAxes(
+ plotId,
+ gid,
+ measurementKey,
+ measurementIntakeIndex,
+ `add`,
+ measurementsData,
+ relation
+ );
+ }
+
+ deleteMeasurement(plotId, gid, measurementKey, measurementIntakeIndex) {
+ this._modifyAxes(
+ plotId,
+ gid,
+ measurementKey,
+ measurementIntakeIndex,
+ `delete`
+ );
+ }
+
+ handlePlotSort({ oldIndex, newIndex }) {
+ this.setState(({ dashboardItems }) => ({
+ dashboardItems: arrayMove(dashboardItems, oldIndex, newIndex),
+ }));
+ }
+
+ onSetMin() {
+ $(PLOTS_ID).animate(
+ {
+ top: $(document).height() - modalHeaderHeight + "px",
+ },
+ 500,
+ function () {
+ $(PLOTS_ID)
+ .find(".modal-body")
+ .css(`max-height`, modalHeaderHeight + "px");
+ }
+ );
+
+ $(".js-expand-less").hide();
+ $(".js-expand-half").show();
+ $(".js-expand-more").show();
+ $(PLOTS_ID + " .modal-body").css(`visibility`, `hidden`);
+ }
+
+ onSetHalf() {
+ $(PLOTS_ID).animate(
+ {
+ top: "50%",
+ },
+ 500,
+ function () {
+ $(PLOTS_ID)
+ .find(".modal-body")
+ .css(
+ `max-height`,
+ $(document).height() * 0.5 - modalHeaderHeight - 20 + "px"
+ );
+ }
+ );
+
+ $(".js-expand-less").show();
+ $(".js-expand-half").hide();
+ $(".js-expand-more").show();
+ $(PLOTS_ID + " .modal-body").css(`visibility`, `visible`);
+ }
+
+ onSetMax() {
+ $(PLOTS_ID).animate(
+ {
+ top: "10%",
+ },
+ 500,
+ function () {
+ $(PLOTS_ID)
+ .find(".modal-body")
+ .css(
+ `max-height`,
+ $(document).height() * 0.9 - modalHeaderHeight - 10 + "px"
+ );
+ }
+ );
+
+ $(".js-expand-less").show();
+ $(".js-expand-half").show();
+ $(".js-expand-more").hide();
+ $(PLOTS_ID + " .modal-body").css(`visibility`, `visible`);
+ }
+
+ nextDisplayType() {
+ if (currentDisplay === DISPLAY_MIN) {
+ this.onSetHalf();
+ currentDisplay = DISPLAY_HALF;
+ previousDisplay = DISPLAY_MIN;
+ } else if (currentDisplay === DISPLAY_HALF) {
+ if (previousDisplay === DISPLAY_MIN) {
+ this.onSetMax();
+ currentDisplay = DISPLAY_MAX;
+ } else {
+ this.onSetMin();
+ currentDisplay = DISPLAY_MIN;
+ }
+
+ previousDisplay = DISPLAY_HALF;
+ } else if (currentDisplay === DISPLAY_MAX) {
+ this.onSetHalf();
+ currentDisplay = DISPLAY_HALF;
+ previousDisplay = DISPLAY_MAX;
}
+ }
- render() {
- setTimeout(() => {
- if (!syncInProg) {
- //console.log("Syncing plots")
- //this.syncPlotData();
- }
- // Debounce sync
- syncInProg = true;
- setTimeout(() => syncInProg = false, 2000);
- }, 500);
-
- let plotsControls = ({__(`No timeseries were created or set as active yet`)}
);
-
- // Actualize elements location
- if (currentDisplay === DISPLAY_MIN) {
- this.onSetMin();
- } else if (currentDisplay === DISPLAY_HALF) {
- this.onSetHalf();
- } else if (currentDisplay === DISPLAY_MAX) {
- this.onSetMax();
- }
-
- let listItemHeightPx = Math.round(($(document).height() * 0.9 - modalHeaderHeight - 10) / 2);
-
- let localPlotsControls = [];
- let plottedProfiles = [];
- this.state.dashboardItems.map((item, index) => {
- if (item.type === DASHBOARD_ITEM_PLOT || item.type === DASHBOARD_ITEM_PROJECT_PLOT) {
- let plot = item.item;
- let plotId = plot.id || plot.key;
- if (this.state.activePlots.indexOf(plotId) > -1) {
- localPlotsControls.push();
- }
- } else if (item.type === DASHBOARD_ITEM_PROFILE || item.type === DASHBOARD_ITEM_PROJECT_PROFILE) {
- if (plottedProfiles.indexOf(item.item.key) > -1) {
- return;
- }
- let profile = item.item;
- if (this.state.activeProfiles.indexOf(profile.key) > -1) {
- localPlotsControls.push();
- plottedProfiles.push(profile.key);
- }
- } else {
- throw new Error(`Unrecognized dashboard item type ${item.type}`);
- }
- });
-
- if (localPlotsControls.length > 0) {
- plotsControls = (
- {localPlotsControls});
- }
-
- const setNoExpanded = () => {
- currentDisplay = DISPLAY_HALF;
- previousDisplay = DISPLAY_MAX;
- this.nextDisplayType();
- };
-
- const setHalfExpanded = () => {
- currentDisplay = DISPLAY_MIN;
- previousDisplay = DISPLAY_HALF;
- this.nextDisplayType();
- };
-
- const setFullExpanded = () => {
- currentDisplay = DISPLAY_HALF;
- previousDisplay = DISPLAY_MIN;
- this.nextDisplayType();
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {__(`Calypso dashboard`)}
-
-
-
- ({__(`Timeseries total`).toLowerCase()}: {this.getPlotsLength()}, {__(`timeseries active`)}: {this.state.activePlots.length}; {__(`Profiles total`).toLowerCase()}: {this.getProfilesLength()}, {__(`profiles active`)}: {this.state.activeProfiles.length})
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
);
- }
+ render() {
+ return ;
+ }
}
DashboardComponent.propTypes = {
- initialPlots: PropTypes.array.isRequired,
- onOpenBorehole: PropTypes.func.isRequired,
- onPlotsChange: PropTypes.func.isRequired,
- onActivePlotsChange: PropTypes.func.isRequired,
- onHighlightedPlotChange: PropTypes.func.isRequired,
+ initialPlots: PropTypes.array.isRequired,
+ //onOpenBorehole: PropTypes.func.isRequired,
+ onPlotsChange: PropTypes.func.isRequired,
+ onActivePlotsChange: PropTypes.func.isRequired,
+ onHighlightedPlotChange: PropTypes.func.isRequired,
};
export default DashboardComponent;
diff --git a/browser/components/DashboardWrapper.js b/browser/components/DashboardWrapper.js
new file mode 100644
index 0000000..849f019
--- /dev/null
+++ b/browser/components/DashboardWrapper.js
@@ -0,0 +1,41 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import reduxStore from "../redux/store";
+import { Provider } from "react-redux";
+import { ToastContainer } from "react-toastify";
+import ThemeProvider from "../themes/ThemeProvider";
+import ProjectProvider from "../contexts/project/ProjectProvider";
+import DashboardShell from "./dashboardshell/DashboardShell";
+import DashboardComponent from "./DashboardComponent";
+
+const DASHBOARD_CONTAINER_ID = "watsonc-plots-dialog-form";
+const HIDDEN_DIV = "watsonc-plots-dialog-form-hidden";
+
+const DashboardWrapper = React.forwardRef((props, ref) => {
+ const DashboardShellWrapper = (props) =>
+ ReactDOM.createPortal(
+
+
+
+
+
+ ,
+ document.getElementById(DASHBOARD_CONTAINER_ID)
+ );
+
+ const DashboardComponentWrapper = React.forwardRef((props, ref) =>
+ ReactDOM.createPortal(
+ ,
+ document.getElementById(HIDDEN_DIV)
+ )
+ );
+
+ return (
+
+
+
+
+ );
+});
+
+export default DashboardWrapper;
diff --git a/browser/components/DataSelectorFilterComponent.js b/browser/components/DataSelectorFilterComponent.js
deleted file mode 100644
index 8e1a679..0000000
--- a/browser/components/DataSelectorFilterComponent.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react';
-import {connect} from 'react-redux';
-
-const DEFAULT_FILTER_COUNT = 3;
-
-class DataSelectorFilterComponent extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- filters: {
- filter1: {
- label: 'Filter 1',
- operators: ['<', '>', '<='],
- type: 'number'
- },
- filter2: {
- label: 'Filter 2',
- operators: ['===', '!='],
- type: 'text'
- }
- },
- selectedFilters: [{filter: null, selectedOperator: null, type: 'text', value: null, operators: []}, {filter: null, selectedOperator: null, type: 'text', value: null, operators: []}, {filter: null, selectedOperator: null, type: 'text', value: null, operators: []}],
-
- };
- }
-
- componentDidMount() {
- }
-
- handleFilterChange(event, index) {
- var selectedFilters = this.state.selectedFilters;
- var filterInfo = this.state.filters[event.target.value];
- var operators = [];
- var type = 'text'
- if (filterInfo) {
- operators = filterInfo.operators;
- type = filterInfo.type;
- }
- selectedFilters[index].operators = operators;
- selectedFilters[index].type = type;
- this.setState(selectedFilters);
- }
-
- getFilters(filterCount) {
- return this.state.selectedFilters.map((filter, index) => {
- return
-
-
- |
-
-
- |
-
-
- |
-
- });
- }
-
- render() {
- return (
-
- {__('Match')}
-
- {__(' the conditions')}
-
-
-
- {this.getFilters(DEFAULT_FILTER_COUNT)}
-
-
-
-
)
- }
-}
-
-DataSelectorFilterComponent.propTypes = {
-};
-
-
-const mapStateToProps = state => ({
-});
-
-const mapDispatchToProps = dispatch => ({
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(DataSelectorFilterComponent);
diff --git a/browser/components/DataSelectorGlobalFilterComponent.js b/browser/components/DataSelectorGlobalFilterComponent.js
deleted file mode 100644
index 036ab2b..0000000
--- a/browser/components/DataSelectorGlobalFilterComponent.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import {connect} from 'react-redux';
-import {selectStartDate, selectEndDate, selectMeasurementCount} from "../redux/actions";
-
-class DataSelectorGlobalFilterComponent extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- startDate: null,
- endDate: null,
- count: null
- };
- }
-
- componentDidMount() {
- }
-
- render() {
- return (
-
-
-
Periode
-
-
-
-
Antal målinger
- {
- e.preventDefault();
- this.props.selectMeasurementCount(e.target.value)
- }} className='form-control'
- value={this.props.selectedMeasurementCount}/>
-
-
-
-
)
- }
-}
-
-DataSelectorGlobalFilterComponent.propTypes = {};
-
-const mapStateToProps = state => ({
- selectedStartDate: state.global.selectedStartDate,
- selectedEndDate: state.global.selectedEndDate,
- selectedMeasurementCount: state.global.selectedMeasurementCount
-});
-
-const mapDispatchToProps = dispatch => ({
- selectStartDate: (date) => dispatch(selectStartDate(date)),
- selectEndDate: (date) => dispatch(selectEndDate(date)),
- selectMeasurementCount: (count) => dispatch(selectMeasurementCount(count))
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(DataSelectorGlobalFilterComponent);
diff --git a/browser/components/DataSourceSelector.js b/browser/components/DataSourceSelector.js
deleted file mode 100644
index b33e357..0000000
--- a/browser/components/DataSourceSelector.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {connect} from 'react-redux';
-import DataSelectorFilterComponent from './DataSelectorFilterComponent';
-
-import {selectLayer, unselectLayer} from '../redux/actions'
-
-/**
- * Data source selector
- */
-class DataSourceSelector extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- openFilter: null
- }
- }
-
- componentDidMount() {
- // If coming from state link, then check off boxes
- if (typeof this.props.enabledLoctypeIds !== "undefined" && typeof this.props.urlparser !== "undefined" && this.props.urlparser.urlVars && this.props.urlparser.urlVars.state) {
- if (this.props.boreholes) {
- $(`#key0`).trigger("click");
- }
- this.props.enabledLoctypeIds.forEach((v) => {
- let key;
- switch (v) {
- case "1":
- key = `key1`;
- break;
- case "3":
- key = `key2`;
- break;
- case "4":
- key = `key3`;
- break;
- case "5":
- key = `key4`;
- break;
- }
- $(`#${key}`).trigger("click");
- });
- }
- }
-
- render() {
- const generateLayerRecord = (key, item) => {
- let selected = (this.props.selectedLayers.indexOf(item.originalLayerKey + (item.additionalKey ? `#${item.additionalKey}` : ``)) !== -1);
- let titles = []
- let itemTitle = item.title;
- if (itemTitle) {
- titles.push(itemTitle);
- }
- let translatedTitle = __(item.title);
- if (translatedTitle && titles.indexOf(translatedTitle) === -1) {
- titles.push(translatedTitle)
- }
-
- return (
-
-
- {/**/}
- {/*{this.state.openFilter === key ? : null}*/}
-
-
);
- };
-
- return (
-
{__(`Please select at least one layer`)}
-
-
Grundvand
- {generateLayerRecord(`key0`, this.props.layers[0])}
- {generateLayerRecord(`key4`, this.props.layers[4])}
- {generateLayerRecord(`key1`, this.props.layers[1])}
- Vandløb, kyst, bassiner
- {generateLayerRecord(`key2`, this.props.layers[2])}
- Nedbør
- {generateLayerRecord(`key3`, this.props.layers[3])}
-
-
);
- }
-}
-
-DataSourceSelector.propTypes = {
- layers: PropTypes.array.isRequired,
-};
-
-const mapStateToProps = state => ({
- selectedLayers: state.global.selectedLayers
-});
-
-const mapDispatchToProps = dispatch => ({
- selectLayer: (originalLayerKey, additionalKey) => dispatch(selectLayer(originalLayerKey, additionalKey)),
- unselectLayer: (originalLayerKey, additionalKey) => dispatch(unselectLayer(originalLayerKey, additionalKey)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(DataSourceSelector);
diff --git a/browser/components/IntroModal.js b/browser/components/IntroModal.js
deleted file mode 100644
index c16ad8b..0000000
--- a/browser/components/IntroModal.js
+++ /dev/null
@@ -1,217 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {connect} from 'react-redux'
-
-import DataSourceSelector from './DataSourceSelector';
-import ChemicalSelector from './ChemicalSelector';
-import GlobalFilter from './DataSelectorGlobalFilterComponent';
-import {selectStartDate, setCategories} from '../redux/actions';
-
-import StateSnapshotsDashboard from './../../../../browser/modules/stateSnapshots/components/StateSnapshotsDashboard';
-
-const MODE_INDEX = 0;
-const MODE_NEW = 1;
-const MODE_SELECT = 2;
-
-/**
- * Intro modal window content
- */
-class IntroModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- mode: MODE_INDEX,
- layers: this.props.layers,
- initialCategories: props.categories,
- };
- }
-
- componentDidMount() {
- if (this.state.initialCategories) {
- this.props.setCategories(this.state.initialCategories);
- }
- }
-
- setCategories(categories) {
- this.props.setCategories(categories);
- }
-
- applyParameters() {
- this.props.onApply({
- layers: this.props.selectedLayers,
- chemical: (this.props.selectedChemical ? this.props.selectedChemical : false),
- selectedStartDate: this.props.selectedStartDate,
- selectedEndDate: this.props.selectedEndDate,
- selectedMeasurementCount: this.props.selectedMeasurementCount,
- });
- }
-
- render() {
- let modalBodyStyle = {
- paddingLeft: `0px`,
- paddingRight: `0px`,
- paddingTop: `35px`,
- paddingBottom: `0px`
- };
-
- let buttonColumnStyle = {
- paddingTop: `30px`,
- backgroundColor: `rgb(0, 150, 136)`,
- height: `90px`,
- };
-
- let buttonStyle = {
- color: `white`,
- fontSize: `20px`,
- fontWeight: `600`,
- cursor: `pointer`
- };
-
- let leftColumnBorder = {borderRadius: `0px 0px 0px 40px`};
- let rightColumnBorder = {borderRadius: `0px 0px 40px 0px`};
- if (this.state.mode !== MODE_INDEX) {
- modalBodyStyle.minHeight = `550px`;
- leftColumnBorder = {};
- rightColumnBorder = {};
- }
-
- let shadowStyle = {
- boxShadow: `0 6px 8px 0 rgba(0, 0, 0, 0.5), 0 6px 20px 0 rgba(0, 0, 0, 0.19)`,
- zIndex: 1000
- };
-
- return (
-
-
- {__(`Welcome to Calypso`)}
-
-
-
-
-
-
{
- this.setState({mode: MODE_NEW})
- }}>
-
- {__(`New project`)} {this.state.mode === MODE_NEW ? (
- ) : ()}
-
-
-
{
- this.setState({mode: MODE_SELECT})
- }}>
-
- {__(`Open existing project`)} {this.state.mode === MODE_SELECT ? (
- ) : ()}
-
-
-
-
-
- {this.state.mode === MODE_NEW ? (
) : false}
-
- {this.state.mode === MODE_SELECT ? (
)
-
-
- /* (
-
-
- Her bliver det muligt at åbne et gemt projekt. Et projekt kan indeholde tidsserie-grafer, profiler mv.
-
-
-
- )*/ : false}
-
-
-
- {this.state.mode === MODE_NEW ? (
) : false}
-
-
- {this.state.mode === MODE_NEW ? (
) : false}
-
-
);
- }
-}
-
-IntroModal.propTypes = {
- layers: PropTypes.array.isRequired,
- categories: PropTypes.object.isRequired,
- onApply: PropTypes.func.isRequired,
- onClose: PropTypes.func.isRequired,
-};
-
-const mapStateToProps = state => ({
- authenticated: state.global.authenticated,
- selectedLayers: state.global.selectedLayers,
- selectedChemical: state.global.selectedChemical,
- selectedMeasurementCount: state.global.selectedMeasurementCount,
- selectedStartDate: state.global.selectedStartDate,
- selectedEndDate: state.global.selectedEndDate,
-});
-
-const mapDispatchToProps = dispatch => ({
- setCategories: (categories) => dispatch(setCategories(categories)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(IntroModal);
diff --git a/browser/components/LoginModal.js b/browser/components/LoginModal.js
new file mode 100644
index 0000000..59f73ed
--- /dev/null
+++ b/browser/components/LoginModal.js
@@ -0,0 +1,259 @@
+import React from "react";
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import CloseButton from "./shared/button/CloseButton";
+import ButtonGroup from "./shared/button/ButtonGroup";
+import Button from "./shared/button/Button";
+import { Variants } from "./shared/constants/variants";
+import Card from "./shared/card/Card";
+import Title from "./shared/title/Title";
+import TextInput from "./shared/inputs/TextInput";
+import { LOGIN_MODAL_DIALOG_PREFIX } from "./../constants";
+import { useState, useEffect, useRef } from "react";
+import useInterval from "./shared/hooks/useInterval";
+
+const LoginModal = (props) => {
+ const [userName, setUserName] = useState("");
+ const [password, setPassword] = useState("");
+ const [statusText, setStatusText] = useState("");
+ const [loggedIn, setLoggedIn] = useState(props.session.isAuthenticated());
+ const [stopPoll, setStopPoll] = useState(false);
+ const timer = useRef(null);
+
+ useInterval(
+ () => {
+ if (props.session.isStatusChecked()) {
+ setStopPoll(true);
+ setLoggedIn(props.session.isAuthenticated());
+ if (!props.session.isAuthenticated()) {
+ console.log(props.urlparser);
+ if (!props.urlparser.urlVars || !props.urlparser.urlVars.state) {
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("show");
+ $("#watsonc-menu-dialog").modal("hide");
+ }
+ }
+ }
+ },
+ stopPoll ? null : 1000
+ );
+
+ useEffect(() => {
+ props.backboneEvents.get().on(`session:authChange`, (authenticated) => {
+ clearTimeout(timer.current);
+ if (authenticated) {
+ setLoggedIn(true);
+ setUserName("");
+ setPassword("");
+ setTimeout(() => {
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("hide");
+ $("#watsonc-menu-dialog").modal("show");
+ }, 1500);
+ } else {
+ setUserName("");
+ setPassword("");
+ setLoggedIn(false);
+ setStatusText("Du er nu logget ud");
+ setTimeout(() => {
+ setStatusText("");
+ }, 2000);
+ }
+ });
+ }, []);
+
+ const log_in = () => {
+ var input = document.getElementById("sessionScreenName");
+ let lastValue = input.value;
+ input.value = userName;
+ let event = new Event("change", { bubbles: true });
+ let tracker = input._valueTracker;
+ if (tracker) {
+ tracker.setValue(lastValue);
+ }
+ input.dispatchEvent(event);
+
+ var input = document.getElementById("sessionPassword");
+ lastValue = input.value;
+ input.value = password;
+ event = new Event("change", { bubbles: true });
+ tracker = input._valueTracker;
+ if (tracker) {
+ tracker.setValue(lastValue);
+ }
+ input.dispatchEvent(event);
+
+ $("#login-modal .btn-raised").removeAttr("disabled");
+ setTimeout(() => {
+ $("#login-modal .btn-raised").click();
+ }, 100);
+
+ timer.current = setTimeout(() => {
+ if ($("#login-modal .alert-dismissible").text().includes("Wrong")) {
+ setStatusText("Forkert brugernavn og eller password");
+ } else {
+ setStatusText("Noget gik galt. Prøv igen senere.");
+ }
+ }, 1000);
+ };
+
+ const log_out = () => {
+ $("#login-modal .btn-raised").click();
+ };
+
+ const close_window = () => {
+ $(`#${LOGIN_MODAL_DIALOG_PREFIX}`).modal("hide");
+ if (!loggedIn) {
+ $("#watsonc-menu-dialog").modal("show");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {(statusText !== "" || loggedIn) && (
+
+
+
+ )}
+ {!loggedIn && (
+ <>
+
+
+
+ opret bruger
+
+ }
+ color={DarkTheme.colors.interaction[4]}
+ level={4}
+ onClick={() => console.log("hej")}
+ />
+
+
+
+
+
+
+
+ {
+ if (e.key === "Enter") {
+ log_in();
+ }
+ }}
+ />
+
+
+ >
+ )}
+
+ {!loggedIn && (
+
+
+
+ );
+};
+
+const Root = styled.div`
+ background: ${({ theme }) => hexToRgbA(theme.colors.primary[1], 0.96)};
+ border-radius: ${({ theme }) => `${theme.layout.borderRadius.large}px`};
+ color: ${({ theme }) => `${theme.colors.headings}`};
+`;
+
+const ModalHeader = styled.div`
+ padding: ${({ theme }) =>
+ `${theme.layout.gutter}px ${theme.layout.gutter}px 0 ${theme.layout.gutter}px`};
+`;
+
+const ModalBody = styled.div`
+ padding: ${({ theme }) => `${theme.layout.gutter}px`};
+`;
+export default LoginModal;
diff --git a/browser/components/MenuComponent.js b/browser/components/MenuComponent.js
deleted file mode 100644
index 5c1f391..0000000
--- a/browser/components/MenuComponent.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import TitleFieldComponent from './../../../../browser/modules/shared/TitleFieldComponent';
-import MenuPlotComponent from './MenuPlotComponent';
-
-const uuidv4 = require('uuid/v4');
-
-/**
- * Component creates plots management form and is the source of truth for plots overall
- */
-class MenuPanelComponent extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- newPlotName: ``,
- plots: this.props.initialPlots,
- dataSource: []
- };
-
- this.handleCreatePlot = this.handleCreatePlot.bind(this);
- this.handleDeletePlot = this.handleDeletePlot.bind(this);
- this.getFeatureByGidFromDataSource = this.getFeatureByGidFromDataSource.bind(this);
- this.handleNewPlotNameChange = this.handleNewPlotNameChange.bind(this);
- }
-
- componentDidMount() {}
-
- getPlots() {
- return JSON.parse(JSON.stringify(this.state.plots));
- }
-
- addPlot(newPlotName) {
- this.handleCreatePlot(newPlotName);
- }
-
- setPlots(plots) {
- this.setState({ plots });
- }
-
- handleCreatePlot(title) {
- let plotsCopy = JSON.parse(JSON.stringify(this.state.plots));
- plotsCopy.push({
- id: uuidv4(),
- title,
- measurements: [],
- });
-
- this.setState({ plots: plotsCopy });
- this.props.onPlotsChange(plotsCopy);
- }
-
- handleDeletePlot(id) {
- let plotsCopy = JSON.parse(JSON.stringify(this.state.plots));
- let plotWasDeleted = false;
- plotsCopy.map((plot, index) => {
- if (plot.id === id) {
- plotsCopy.splice(index, 1);
- plotWasDeleted = true;
- return false;
- }
- });
-
- if (plotWasDeleted === false) {
- throw new Error(`Unable to delete plot with id ${id}`);
- }
-
- this.setState({ plots: plotsCopy });
- this.props.onPlotsChange(plotsCopy);
- }
-
- handleNewPlotNameChange(event) {
- this.setState({ newPlotName: event.target.value});
- }
-
- _modifyAxes(plotId, gid, measurementKey, measurementIntakeIndex, action) {
- if (!plotId) throw new Error(`Invalid plot identifier`);
- if ((!gid && gid !== 0) || !measurementKey || (!measurementIntakeIndex && measurementIntakeIndex !== 0)) throw new Error(`Invalid measurement location parameters`);
-
- let plots = JSON.parse(JSON.stringify(this.state.plots));
- let correspondingPlot = false;
- let correspondingPlotIndex = false;
- plots.map((plot, index) => {
- if (plot.id === plotId) {
- correspondingPlot = plot;
- correspondingPlotIndex = index;
- }
- });
-
- if (correspondingPlot === false) throw new Error(`Plot with id ${plotId} does not exist`);
- let measurementIndex = gid + ':' + measurementKey + ':' + measurementIntakeIndex;
- if (action === `add`) {
- if (correspondingPlot.measurements.indexOf(measurementIndex) === -1) {
- correspondingPlot.measurements.push(measurementIndex);
- }
- } else if (action === `delete`) {
- if (correspondingPlot.measurements.indexOf(measurementIndex) === -1) {
- throw new Error(`Unable to find measurement ${measurementIndex} for ${plotId} plot`);
- } else {
- correspondingPlot.measurements.splice(correspondingPlot.measurements.indexOf(measurementIndex), 1);
- }
- } else {
- throw new Error(`Unrecognized action ${action}`);
- }
-
- plots[correspondingPlotIndex] = correspondingPlot;
- this.setState({ plots });
- this.props.onPlotsChange(plots);
- }
-
- setDataSource(dataSource) {
- this.setState({ dataSource });
- }
-
- getFeatureByGidFromDataSource(boreholeno) {
- let featureWasFound = false;
- this.state.dataSource.map(item => {
- if (item.properties.boreholeno === boreholeno) {
- featureWasFound = item;
- return false;
- }
- });
-
- return featureWasFound;
- }
-
- addMeasurement(plotId, gid, measurementKey, measurementIntakeIndex) {
- this._modifyAxes(plotId, gid, measurementKey, measurementIntakeIndex, `add`);
- }
-
- deleteMeasurement(plotId, gid, measurementKey, measurementIntakeIndex) {
- this._modifyAxes(plotId, gid, measurementKey, measurementIntakeIndex, `delete`);
- }
-
- render() {
- let plotsControls = ({__(`No plots were created yet`)}
);
-
- if (this.state.dataSource.length > 0) {
- let localPlotsControls = [];
- this.state.plots.map((plot, index) => {
- localPlotsControls.push(
-
- { return this.getFeatureByGidFromDataSource(boreholeno)}}
- onDelete={(id) => { this.handleDeletePlot(id)}}
- plotMeta={plot}/>
-
- );
- });
-
- if (localPlotsControls.length > 0) {
- plotsControls = ();
- }
- }
-
- return (
-
-
- {__(`Plots`)}
- { this.handleCreatePlot(title) }} type="userOwned"/>
-
-
-
{plotsControls}
-
);
- }
-}
-
-MenuPanelComponent.propTypes = {
- initialPlots: PropTypes.array.isRequired,
- onPlotsChange: PropTypes.func.isRequired,
-};
-
-export default MenuPanelComponent;
diff --git a/browser/components/MenuDataSourceAndTypeSelectorComponent.js b/browser/components/MenuDataSourceAndTypeSelectorComponent.js
deleted file mode 100644
index 6abe02c..0000000
--- a/browser/components/MenuDataSourceAndTypeSelectorComponent.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Provider} from 'react-redux';
-import {connect} from 'react-redux';
-import reduxStore from '../redux/store';
-
-import DataSourceSelector from './DataSourceSelector';
-import ChemicalSelectorModal from './ChemicalSelectorModal';
-import GlobalFilter from './DataSelectorGlobalFilterComponent';
-
-const utils = require('./../utils');
-
-/**
- * Creates data source and type selector
- */
-class MenuDataSourceAndTypeSelectorComponent extends React.Component {
- constructor(props) {
- super(props);
- }
-
- applyParameters() {
- this.props.onApply({
- layers: this.props.selectedLayers,
- chemical: (this.props.selectedChemical ? this.props.selectedChemical : false),
- selectedStartDate: this.props.selectedStartDate,
- selectedEndDate: this.props.selectedEndDate,
- selectedMeasurementCount: this.props.selectedMeasurementCount,
- });
- }
-
- render() {
- let chemicalName = __(`Not selected`);
- if (this.props.selectedChemical) {
- chemicalName = utils.getChemicalName(this.props.selectedChemical, this.props.categories);
- }
-
- return (
-
-
-
-
-
-
{__(`Select datatype`)}
-
{chemicalName}
- {
- const dialogPrefix = `watsonc-select-chemical-dialog`;
- const selectChemicalModalPlaceholderId = `${dialogPrefix}-placeholder`;
-
- if ($(`#${selectChemicalModalPlaceholderId}`).children().length > 0) {
- ReactDOM.unmountComponentAtNode(document.getElementById(selectChemicalModalPlaceholderId))
- }
-
- try {
- ReactDOM.render(
-
- {
- $('#' + dialogPrefix).modal('hide');
- }}/>
-
-
, document.getElementById(selectChemicalModalPlaceholderId));
- } catch (e) {
- console.error(e);
- }
-
- $('#' + dialogPrefix).modal({backdrop: `static`});
- }}>
-
-
-
-
-
-
-
- {__(`Apply`)}
-
-
);
- }
-}
-
-MenuDataSourceAndTypeSelectorComponent.propTypes = {};
-
-const mapStateToProps = state => ({
- selectedLayers: state.global.selectedLayers,
- selectedChemical: state.global.selectedChemical,
- categories: state.global.categories,
- selectedMeasurementCount: state.global.selectedMeasurementCount,
- selectedStartDate: state.global.selectedStartDate,
- selectedEndDate: state.global.selectedEndDate,
-});
-
-export default connect(mapStateToProps)(MenuDataSourceAndTypeSelectorComponent);
diff --git a/browser/components/MenuPlotComponent.js b/browser/components/MenuPlotComponent.js
deleted file mode 100644
index 21aa6ad..0000000
--- a/browser/components/MenuPlotComponent.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { DropTarget } from 'react-dnd';
-
-const utils = require('./../utils');
-
-/**
- * Plot component
- */
-class ModalPlotComponent extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- let removeButtons = [];
- this.props.plot.measurements.map((measurement, index) => {
- let measurementDisplayTitle = measurement;
- let splitMeasurementId = measurement.split(':');
-
- let customGraph = -1, key, intakeIndex;
- if (splitMeasurementId.length === 3) {
- customGraph = false;
- key = splitMeasurementId[1];
- intakeIndex = splitMeasurementId[2];
- } else if (splitMeasurementId.length === 4) {
- customGraph = true;
- key = splitMeasurementId[1] + ':' + splitMeasurementId[2];
- intakeIndex = splitMeasurementId[3];
- } else {
- throw new Error(`Invalid measurement key (${measurement})`);
- }
-
- let boreholeno = parseInt(splitMeasurementId[0]);
- if (this.props.dataSource && this.props.dataSource.length > 0) {
- this.props.dataSource.map(item => {
- if (item.properties.boreholeno === boreholeno) {
- if (customGraph) {
- let json = JSON.parse(item.properties[splitMeasurementId[1]]).data[splitMeasurementId[2]];
- let intakeName = `#` + (parseInt(splitMeasurementId[3]) + 1);
- if (`intakes` in json && Array.isArray(json.intakes) && json.intakes[parseInt(splitMeasurementId[3])] !== null) {
- intakeName = json.intakes[parseInt(splitMeasurementId[3])];
- }
-
- measurementDisplayTitle = (`${item.properties.boreholeno}, ${json.data[0].name} (${intakeName})`);
- return false;
- } else {
- let json = JSON.parse(item.properties[splitMeasurementId[1]]);
- let intakeName = `#` + (parseInt(splitMeasurementId[2]) + 1);
- if (`intakes` in json && Array.isArray(json.intakes) && json.intakes[parseInt(splitMeasurementId[2])] !== null) {
- intakeName = json.intakes[parseInt(splitMeasurementId[2])];
- }
-
- let title = utils.getMeasurementTitle(item);
- measurementDisplayTitle = (`${title}, ${json.title} (${intakeName})`);
- return false;
- }
- }
- });
- }
-
- const onDelete = () => { this.props.onDeleteMeasurement(this.props.plot.id, boreholeno, key, intakeIndex); };
-
- removeButtons.push(
-
- {measurementDisplayTitle}
-
-
);
- });
-
- const isOver = this.props.isOver;
- return this.props.connectDropTarget(
-
{this.props.plot.title}
-
{removeButtons}
-
);
- }
-}
-
-const plotTarget = {
- drop(props, monitor) {
- let item = monitor.getItem();
- item.onAddMeasurement(props.plot.id, item.boreholeno, item.itemKey, item.intakeIndex);
- }
-};
-
-function collect(connect, monitor) {
- return {
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver()
- };
-}
-
-ModalPlotComponent.propTypes = {
- onDeleteMeasurement: PropTypes.func.isRequired
-};
-
-export default DropTarget(`MEASUREMENT`, plotTarget, collect)(ModalPlotComponent);
diff --git a/browser/components/MenuProfilesComponent.js b/browser/components/MenuProfilesComponent.js
index 9afbef1..9cc6874 100644
--- a/browser/components/MenuProfilesComponent.js
+++ b/browser/components/MenuProfilesComponent.js
@@ -1,738 +1,983 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import axios from 'axios';
-import Slider from 'rc-slider';
-import {Provider} from 'react-redux';
-import {connect} from 'react-redux';
-
-import {SELECT_CHEMICAL_DIALOG_PREFIX, FREE_PLAN_MAX_PROFILES_COUNT} from './../constants';
-import TitleFieldComponent from './../../../../browser/modules/shared/TitleFieldComponent';
-import LoadingOverlay from './../../../../browser/modules/shared/LoadingOverlay';
-import SearchFieldComponent from './../../../../browser/modules/shared/SearchFieldComponent';
-
-import reduxStore from './../redux/store';
-
-import {selectChemical} from './../redux/actions';
-
-const utils = require('./../utils');
-
-const wkt = require('terraformer-wkt-parser');
-const utmZone = require('./../../../../browser/modules/utmZone');
-
-
-const STEP_ENTER_NAME = -1;
-const STEP_NOT_READY = 0;
-const STEP_BEING_DRAWN = 1;
-const STEP_READY_TO_LOAD = 2;
-const STEP_CHOOSE_LAYERS = 3;
-
-const DEFAULT_API_URL = `/api/key-value`;
-
-let drawnItems = new L.FeatureGroup(), displayedItems = new L.FeatureGroup(), embedDrawControl = false;
-
-/**
- * Component for creating profiles
- */
-class MenuProfilesComponent extends React.Component {
- constructor(props) {
- super(props);
-
-
- this.state = {
- apiUrl: (props.apiUrl ? props.apiUrl : DEFAULT_API_URL),
- loading: false,
- localSelectedChemical: false,
- showDrawingForm: true,
- showExistingProfiles: true,
- showProjectProfiles: true,
- boreholeNames: [],
- layers: [],
- selectedLayers: [],
- authenticated: props.authenticated ? props.authenticated : false,
- profiles: (props.initialProfiles ? props.initialProfiles : []),
- activeProfiles: (props.initialActiveProfiles ? props.initialActiveProfiles : []),
- profile: false,
- step: STEP_ENTER_NAME,
- bufferedProfile: false,
- profileBottom: -100,
- buffer: 100,
- newTitle: '',
- profilesSearchTerm: '',
- };
-
- this.search = this.search.bind(this);
- this.startDrawing = this.startDrawing.bind(this);
- this.stopDrawing = this.stopDrawing.bind(this);
- this.saveProfile = this.saveProfile.bind(this);
- this.handleLayerSelect = this.handleLayerSelect.bind(this);
-
- props.cloud.get().map.addLayer(drawnItems);
- props.cloud.get().map.addLayer(displayedItems);
-
- this.bufferSliderRef = React.createRef();
- this.bufferValueRef = React.createRef();
- this.onNewProfileAdd = this.onNewProfileAdd.bind(this);
- this.canCreateProfile = this.canCreateProfile.bind(this);
-
- window.menuProfilesComponentInstance = this;
- }
-
- componentDidMount() {
- let _self = this;
- this.props.backboneEvents.get().on(`session:authChange`, (authenticated) => {
- if (_self.state.authenticated !== authenticated) {
- _self.setState({authenticated});
- }
- });
-
- this.displayActiveProfiles();
- }
-
- canCreateProfile() {
- if (this.props.license === 'premium') {
- return true;
- } else {
- return this.getProfilesLength() < FREE_PLAN_MAX_PROFILES_COUNT;
-
- }
- }
-
- displayActiveProfiles() {
- displayedItems.eachLayer(layer => {
- displayedItems.removeLayer(layer);
- });
-
- if (this.state.activeProfiles) {
- this.state.activeProfiles.map(activeProfileKey => {
- this.state.profiles.map(profile => {
- if (profile.key === activeProfileKey) {
- this.displayProfile(profile);
- }
- });
- });
- }
- }
-
- setProfiles(profiles) {
- this.setState({profiles});
- }
-
- setActiveProfiles(activeProfiles) {
- this.setState({activeProfiles}, () => {
- this.displayActiveProfiles();
- });
- }
-
- onNewProfileAdd(newTitle) {
- if (!this.canCreateProfile()) {
- $('#watsonc-limits-reached-text').show();
- $('#upgrade-modal').modal('show');
- return;
- }
- this.setState({newTitle, step: STEP_NOT_READY});
- }
-
- getProjectProfilesLength() {
- let count = 0;
- this.state.profiles.map(item => {
- if (item.fromProject) {
- count += 1;
- }
- });
- return count;
- }
-
- getProfilesLength() {
- let count = 0;
- this.state.profiles.map(item => {
- if (!item.fromProject) {
- count += 1;
- }
- });
- return count;
- }
-
- saveProfile() {
- let layers = [];
- this.state.layers.map(item => {
- if (this.state.selectedLayers.indexOf(item.id) > -1) {
- layers.push(item);
- }
- });
-
- this.setState({loading: true});
- this.props.onProfileCreate({
- title: this.state.newTitle,
- profile: this.state.profile,
- buffer: this.state.buffer,
- depth: this.state.profileBottom,
- compound: this.state.localSelectedChemical,
- boreholeNames: this.state.boreholeNames,
- layers
- }, true, () => {
- this.setState({
- step: STEP_ENTER_NAME,
- bufferedProfile: false,
- profileBottom: -100,
- buffer: 100,
- newTitle: '',
- loading: false
- });
- });
- }
-
- handleProfileDelete(item) {
- if (confirm(__(`Delete`) + ' ' + item.profile.title + '?')) {
- this.setState({loading: true});
- this.props.onProfileDelete(item.key, () => {
- this.setState({loading: false});
- });
- }
- }
-
- handleLayerSelect(checked, layer) {
- let layesrCopy = JSON.parse(JSON.stringify(this.state.selectedLayers));
- if (checked) {
- if (layesrCopy.indexOf(layer.id) === -1) {
- layesrCopy.push(layer.id);
- }
- } else {
- if (layesrCopy.indexOf(layer.id) > -1) {
- layesrCopy.splice(layesrCopy.indexOf(layer.id), 1);
- }
- }
-
- this.setState({selectedLayers: layesrCopy})
- }
-
- search() {
- this.setState({
- step: STEP_NOT_READY,
- layers: [],
- selectedLayers: []
- }, () => {
- this.stopDrawing();
- this.setState({loading: true});
- axios.post(`/api/extension/watsonc/intersection`, {
- data: wkt.convert(this.state.bufferedProfile),
- bufferRadius: this.state.buffer,
- profileDepth: this.state.profileBottom,
- profile: this.state.profile
- }).then(response => {
- let responseCopy = JSON.parse(JSON.stringify(response.data.result));
- response.data.result.map((item, index) => {
- responseCopy[index].id = btoa(item.title);
- });
-
- this.setState({
- step: STEP_CHOOSE_LAYERS,
- loading: false,
- layers: responseCopy,
- boreholeNames: response.data.boreholeNames
- });
- }).catch(error => {
- this.setState({loading: false});
- console.log(`Error occured`, error);
- });
- });
- }
-
- clearDrawnLayers() {
- drawnItems.eachLayer(layer => {
- drawnItems.removeLayer(layer);
- });
- }
-
- startDrawing() {
- this.clearDrawnLayers();
-
- if (embedDrawControl) embedDrawControl.disable();
- embedDrawControl = new L.Draw.Polyline(this.props.cloud.get().map);
- embedDrawControl.enable();
-
- embedDrawControl._map.off('draw:created');
- embedDrawControl._map.on('draw:created', e => {
- if (embedDrawControl) embedDrawControl.disable();
-
- let coord, layer = e.layer;
-
- let primitive = layer.toGeoJSON();
- if (primitive) {
- if (typeof layer.getBounds !== "undefined") {
- coord = layer.getBounds().getSouthWest();
- } else {
- coord = layer.getLatLng();
- }
-
- // Get utm zone
- var zone = utmZone.getZone(coord.lat, coord.lng);
- var crss = {
- "proj": "+proj=utm +zone=" + zone + " +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
- "unproj": "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
- };
-
- var reader = new jsts.io.GeoJSONReader();
- var writer = new jsts.io.GeoJSONWriter();
- var geom = reader.read(reproject.reproject(primitive, "unproj", "proj", crss));
- var buffer4326 = reproject.reproject(writer.write(geom.geometry.buffer(this.state.buffer)), "proj", "unproj", crss);
-
- L.geoJson(buffer4326, {
- "color": "#ff7800",
- "weight": 1,
- "opacity": 1,
- "fillOpacity": 0.1,
- "dashArray": '5,3'
- }).addTo(drawnItems);
-
- this.setState({
- step: STEP_READY_TO_LOAD,
- bufferedProfile: buffer4326,
- profile: primitive
- });
- }
- });
- }
-
- stopDrawing() {
- if (drawnItems) drawnItems.clearLayers();
- if (embedDrawControl) embedDrawControl.disable();
- }
-
- displayProfile(data) {
- this.clearDrawnLayers();
- let profile = data.profile.profile;
-
- // Get utm zone
- var zone = utmZone.getZone(profile.geometry.coordinates[0][1], profile.geometry.coordinates[0][0]);
- var crss = {
- "proj": "+proj=utm +zone=" + zone + " +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
- "unproj": "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
- };
-
- let reader = new jsts.io.GeoJSONReader();
- let writer = new jsts.io.GeoJSONWriter();
- let geom = reader.read(reproject.reproject(profile, "unproj", "proj", crss));
- let buffer4326 = reproject.reproject(writer.write(geom.geometry.buffer(data.profile.buffer)), "proj", "unproj", crss);
-
- L.geoJson(buffer4326, {
- "color": "#ff7800",
- "weight": 1,
- "opacity": 1,
- "fillOpacity": 0.1,
- "dashArray": '5,3'
- }).addTo(displayedItems);
-
- var profileLayer = new L.geoJSON(profile);
-
- profileLayer.bindTooltip(data.profile.title, {
- className: 'watsonc-profile-tooltip',
- permanent: true,
- offset: [0, 0]
- });
-
- profileLayer.addTo(displayedItems);
- }
-
- handleProfileToggle(checked, profileKey) {
- if (checked) {
- this.props.onProfileShow(profileKey);
- } else {
- this.props.onProfileHide(profileKey);
- }
- }
-
- render() {
- let overlay = false;
- if (this.state.loading) {
- overlay = ();
- }
-
- let availableLayers = (
-
{__(`No layers found`)}
-
);
-
- if (this.state.layers && this.state.layers.length > 0) {
- availableLayers = [];
-
- const generateLayerRecord = (item, index, prefix) => {
- let points = [];
- item.intersectionSegments.map(item => {
- points.push(`${Math.round(item[0] / 1000)}km - ${Math.round(item[1] / 1000)}km`);
- });
-
- return (
-
-
-
-
- {item.subtitle}
-
- {__(`Stationing points`) + ': ' + points.join(', ')}
-
-
);
- };
-
- this.state.layers.filter(item => item.type !== `geology`).map((item, index) => {
- availableLayers.push(generateLayerRecord(item, index, `non_geology_layer_`));
- });
- if (availableLayers.length > 0) availableLayers.push(
);
- this.state.layers.filter(item => item.type === `geology`).map((item, index) => {
- availableLayers.push(generateLayerRecord(item, index, `geology_layer_`));
- });
- }
-
- let existingProfilesControls = (
-
{__(`No profiles found`)}
-
);
- let projectProfilesControls = (
-
{__(`No profiles found`)}
-
);
-
- let plotRows = [];
- let projectProfileRows = [];
- this.state.profiles.map((item, index) => {
- if (this.state.profilesSearchTerm.length > 0) {
- if (item.profile.title.toLowerCase().indexOf(this.state.profilesSearchTerm.toLowerCase()) === -1) {
- return;
- }
- }
- var deleteButton = item.fromProject ? null :
- {
- this.handleProfileDelete(item);
- }}
- style={{padding: `4px`, margin: `0px`}}>
- delete
-
- |
- var itemHtml =
-
-
-
-
-
-
-
-
- {item.profile.title}
-
-
- |
-
- {item.profile.compound ? utils.getChemicalName(item.profile.compound, this.props.categories) : __(`Not selected`)}
- |
- {deleteButton}
-
;
- if (item.fromProject === true) {
- projectProfileRows.push(itemHtml);
- } else {
- plotRows.push(itemHtml);
- }
- });
-
- if (plotRows.length > 0) {
- existingProfilesControls = (
-
-
-
- grid_on
- {__(`Title`)}
- |
- {__(`Datatype`)} |
-
- delete
- |
-
-
-
- {plotRows}
-
-
);
- }
-
- if (projectProfileRows.length > 0) {
- projectProfilesControls = (
-
-
-
- grid_on
- {__(`Title`)}
- |
- {__(`Datatype`)} |
-
-
-
- {projectProfileRows}
-
-
)
- }
-
- let chemicalName = __(`Not selected`);
- if (this.state.localSelectedChemical) {
- chemicalName = utils.getChemicalName(this.state.localSelectedChemical, this.props.categories);
- }
-
- let renderText = '';
- if (this.state.authenticated) {
- renderText = (
- {overlay}
-
-
- {this.state.showDrawingForm ? (
-
-
- {this.state.step !== STEP_ENTER_NAME ? (
-
-
-
{__(`Select datatype`)}: {chemicalName}
- {
- const selectChemicalModalPlaceholderId = `${SELECT_CHEMICAL_DIALOG_PREFIX}-placeholder`;
-
- if ($(`#${selectChemicalModalPlaceholderId}`).children().length > 0) {
- ReactDOM.unmountComponentAtNode(document.getElementById(selectChemicalModalPlaceholderId));
- }
-
- try {
- ReactDOM.render(
-
- {
- this.setState({localSelectedChemical: selectorValue})
- $('#' + SELECT_CHEMICAL_DIALOG_PREFIX).modal('hide');
- }}/>
-
-
, document.getElementById(selectChemicalModalPlaceholderId));
- } catch (e) {
- console.error(e);
- }
-
- $('#' + SELECT_CHEMICAL_DIALOG_PREFIX).modal({backdrop: `static`});
- }}>
- {
- this.setState({localSelectedChemical: false});
- }}>
-
-
-
-
-
-
-
-
-
{__(`Adjust buffer`)}
-
-
- {
- this.setState({buffer: value});
- }}/>
-
-
- {
- this.setState({buffer: event.target.value});
- }}
- value={this.state.buffer}/>
-
-
-
-
-
-
{__(`Adjust profile bottom`)}
-
-
- {
- this.setState({profileBottom: event.target.value});
- }}
- value={this.state.profileBottom}/>
-
-
-
-
-
- {this.state.step === STEP_CHOOSE_LAYERS ? (
-
-
- {availableLayers}
-
-
-
-
- {
- this.saveProfile();
- }}>{__(`Save and exit`)}
-
-
-
) : false}
-
) : false}
-
) : false}
-
-
-
-
- {
- this.setState({ profilesSearchTerm });
- }}/>
-
-
-
-
-
-
-
- {this.state.showExistingProfiles ? (
-
-
- {existingProfilesControls}
-
-
-
) : false}
-
-
);
- } else {
- renderText = (
-
-
{__(`Please sign in to create / edit Profiles`)}
-
-
);
- }
-
- if (projectProfileRows.length > 0) {
- renderText =
- {renderText}
-
-
- {this.state.showProjectProfiles ? (
-
-
- {projectProfilesControls}
-
-
-
) : false}
-
-
-
- }
- return renderText;
- }
-}
-
-MenuProfilesComponent.propTypes = {
- cloud: PropTypes.any.isRequired,
- backboneEvents: PropTypes.any.isRequired,
-};
-
-
-const mapStateToProps = state => ({
- selectedChemical: state.global.selectedChemical,
- authenticated: state.global.authenticated,
-});
-
-const mapDispatchToProps = dispatch => ({
- selectChemical: (key) => dispatch(selectChemical(key)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(MenuProfilesComponent);
+import React from "react";
+import PropTypes from "prop-types";
+import axios from "axios";
+import Slider from "rc-slider";
+import { Provider } from "react-redux";
+import { connect } from "react-redux";
+
+import {
+ SELECT_CHEMICAL_DIALOG_PREFIX,
+ FREE_PLAN_MAX_PROFILES_COUNT,
+} from "./../constants";
+import TitleFieldComponent from "./../../../../browser/modules/shared/TitleFieldComponent";
+import LoadingOverlay from "./../../../../browser/modules/shared/LoadingOverlay";
+import SearchFieldComponent from "./../../../../browser/modules/shared/SearchFieldComponent";
+
+import reduxStore from "./../redux/store";
+
+import { selectChemical } from "./../redux/actions";
+import ChemicalSelectorModal from "./dataselector/ChemicalSelectorModal";
+import ThemeProvider from "../themes/ThemeProvider";
+import { showSubscriptionIfFree } from "../helpers/show_subscriptionDialogue";
+
+const utils = require("./../utils");
+
+const wkt = require("terraformer-wkt-parser");
+const utmZone = require("./../../../../browser/modules/utmZone");
+const session = require("./../../../session/browser/index");
+
+const STEP_ENTER_NAME = -1;
+const STEP_NOT_READY = 0;
+const STEP_BEING_DRAWN = 1;
+const STEP_READY_TO_LOAD = 2;
+const STEP_CHOOSE_LAYERS = 3;
+
+const DEFAULT_API_URL = `/api/key-value`;
+
+let drawnItems = new L.FeatureGroup(),
+ displayedItems = new L.FeatureGroup(),
+ embedDrawControl = false;
+
+/**
+ * Component for creating profiles
+ */
+class MenuProfilesComponent extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ apiUrl: props.apiUrl ? props.apiUrl : DEFAULT_API_URL,
+ loading: false,
+ localSelectedChemical: false,
+ showDrawingForm: true,
+ showExistingProfiles: true,
+ showProjectProfiles: true,
+ boreholeNames: [],
+ layers: [],
+ selectedLayers: [],
+ authenticated: props.authenticated ? props.authenticated : false,
+ profiles: props.initialProfiles ? props.initialProfiles : [],
+ activeProfiles: props.initialActiveProfiles
+ ? props.initialActiveProfiles
+ : [],
+ profile: false,
+ step: STEP_ENTER_NAME,
+ bufferedProfile: false,
+ profileBottom: -100,
+ buffer: 100,
+ newTitle: "",
+ profilesSearchTerm: "",
+ };
+
+ this.search = this.search.bind(this);
+ this.startDrawing = this.startDrawing.bind(this);
+ this.stopDrawing = this.stopDrawing.bind(this);
+ this.saveProfile = this.saveProfile.bind(this);
+ this.handleLayerSelect = this.handleLayerSelect.bind(this);
+
+ props.cloud.get().map.addLayer(drawnItems);
+ props.cloud.get().map.addLayer(displayedItems);
+
+ this.bufferSliderRef = React.createRef();
+ this.bufferValueRef = React.createRef();
+ this.onNewProfileAdd = this.onNewProfileAdd.bind(this);
+ this.canCreateProfile = this.canCreateProfile.bind(this);
+
+ window.menuProfilesComponentInstance = this;
+ }
+
+ componentDidMount() {
+ let _self = this;
+ this.props.backboneEvents
+ .get()
+ .on(`session:authChange`, (authenticated) => {
+ if (_self.state.authenticated !== authenticated) {
+ _self.setState({ authenticated });
+ }
+ });
+
+ this.displayActiveProfiles();
+ }
+
+ canCreateProfile() {
+ if (session.getProperties()?.["license"] === "premium") {
+ return true;
+ } else {
+ return this.getProfilesLength() < FREE_PLAN_MAX_PROFILES_COUNT;
+ }
+ }
+
+ displayActiveProfiles() {
+ displayedItems.eachLayer((layer) => {
+ displayedItems.removeLayer(layer);
+ });
+
+ if (this.state.activeProfiles) {
+ this.state.activeProfiles.map((activeProfileKey) => {
+ this.state.profiles.map((profile) => {
+ if (profile.key === activeProfileKey) {
+ this.displayProfile(profile);
+ }
+ });
+ });
+ }
+ }
+
+ setProfiles(profiles) {
+ this.setState({ profiles });
+ }
+
+ setActiveProfiles(activeProfiles) {
+ this.setState({ activeProfiles });
+ }
+
+ onNewProfileAdd(newTitle) {
+ if (!this.canCreateProfile()) {
+ showSubscription();
+ return;
+ }
+ this.setState({ newTitle, step: STEP_NOT_READY });
+ }
+
+ getProjectProfilesLength() {
+ let count = 0;
+ this.state.profiles.map((item) => {
+ if (item.fromProject) {
+ count += 1;
+ }
+ });
+ return count;
+ }
+
+ getProfilesLength() {
+ let count = 0;
+ this.state.profiles.map((item) => {
+ if (!item.fromProject) {
+ count += 1;
+ }
+ });
+ return count;
+ }
+
+ saveProfile() {
+ let layers = [];
+ this.state.layers.map((item) => {
+ if (this.state.selectedLayers.indexOf(item.id) > -1) {
+ layers.push(item);
+ }
+ });
+
+ this.setState({ loading: true });
+ this.props.onProfileCreate(
+ {
+ title: this.state.newTitle,
+ profile: this.state.profile,
+ buffer: this.state.buffer,
+ depth: this.state.profileBottom,
+ compound: this.state.localSelectedChemical,
+ boreholeNames: this.state.boreholeNames,
+ layers,
+ },
+ true,
+ () => {
+ this.setState({
+ step: STEP_ENTER_NAME,
+ bufferedProfile: false,
+ profileBottom: -100,
+ buffer: 100,
+ newTitle: "",
+ loading: false,
+ });
+ }
+ );
+ }
+
+ handleProfileDelete(item) {
+ if (confirm(__(`Delete`) + " " + item.profile.title + "?")) {
+ this.setState({ loading: true });
+ this.props.onProfileDelete(item.key, () => {
+ this.setState({ loading: false });
+ });
+ }
+ }
+
+ handleLayerSelect(checked, layer) {
+ let layesrCopy = JSON.parse(JSON.stringify(this.state.selectedLayers));
+ if (checked) {
+ if (layesrCopy.indexOf(layer.id) === -1) {
+ layesrCopy.push(layer.id);
+ }
+ } else {
+ if (layesrCopy.indexOf(layer.id) > -1) {
+ layesrCopy.splice(layesrCopy.indexOf(layer.id), 1);
+ }
+ }
+
+ this.setState({ selectedLayers: layesrCopy });
+ }
+
+ search() {
+ this.setState(
+ {
+ step: STEP_NOT_READY,
+ layers: [],
+ selectedLayers: [],
+ },
+ () => {
+ this.stopDrawing();
+ this.setState({ loading: true });
+ axios
+ .post(`/api/extension/watsonc/intersection`, {
+ data: wkt.convert(this.state.bufferedProfile),
+ bufferRadius: this.state.buffer,
+ profileDepth: this.state.profileBottom,
+ profile: this.state.profile,
+ })
+ .then((response) => {
+ let responseCopy = JSON.parse(JSON.stringify(response.data.result));
+ response.data.result.map((item, index) => {
+ responseCopy[index].id = btoa(item.title);
+ });
+
+ this.setState({
+ step: STEP_CHOOSE_LAYERS,
+ loading: false,
+ layers: responseCopy,
+ boreholeNames: response.data.boreholeNames,
+ });
+ })
+ .catch((error) => {
+ this.setState({ loading: false });
+ console.log(`Error occured`, error);
+ });
+ }
+ );
+ }
+
+ clearDrawnLayers() {
+ drawnItems.eachLayer((layer) => {
+ drawnItems.removeLayer(layer);
+ });
+ }
+
+ startDrawing() {
+ this.clearDrawnLayers();
+
+ if (embedDrawControl) embedDrawControl.disable();
+ embedDrawControl = new L.Draw.Polyline(this.props.cloud.get().map);
+ embedDrawControl.enable();
+
+ embedDrawControl._map.off("draw:created");
+ embedDrawControl._map.on("draw:created", (e) => {
+ if (embedDrawControl) embedDrawControl.disable();
+
+ let coord,
+ layer = e.layer;
+
+ let primitive = layer.toGeoJSON();
+ if (primitive) {
+ if (typeof layer.getBounds !== "undefined") {
+ coord = layer.getBounds().getSouthWest();
+ } else {
+ coord = layer.getLatLng();
+ }
+
+ // Get utm zone
+ var zone = utmZone.getZone(coord.lat, coord.lng);
+ var crss = {
+ proj:
+ "+proj=utm +zone=" +
+ zone +
+ " +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
+ unproj: "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
+ };
+
+ var reader = new jsts.io.GeoJSONReader();
+ var writer = new jsts.io.GeoJSONWriter();
+ var geom = reader.read(
+ reproject.reproject(primitive, "unproj", "proj", crss)
+ );
+ var buffer4326 = reproject.reproject(
+ writer.write(geom.geometry.buffer(this.state.buffer)),
+ "proj",
+ "unproj",
+ crss
+ );
+
+ L.geoJson(buffer4326, {
+ color: "#ff7800",
+ weight: 1,
+ opacity: 1,
+ fillOpacity: 0.1,
+ dashArray: "5,3",
+ }).addTo(drawnItems);
+
+ this.setState({
+ step: STEP_READY_TO_LOAD,
+ bufferedProfile: buffer4326,
+ profile: primitive,
+ });
+ }
+ });
+ }
+
+ stopDrawing() {
+ if (drawnItems) drawnItems.clearLayers();
+ if (embedDrawControl) embedDrawControl.disable();
+ }
+
+ displayProfile(data) {
+ this.clearDrawnLayers();
+ let profile = data.profile.profile;
+
+ // Get utm zone
+ var zone = utmZone.getZone(
+ profile.geometry.coordinates[0][1],
+ profile.geometry.coordinates[0][0]
+ );
+ var crss = {
+ proj:
+ "+proj=utm +zone=" +
+ zone +
+ " +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
+ unproj: "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
+ };
+
+ let reader = new jsts.io.GeoJSONReader();
+ let writer = new jsts.io.GeoJSONWriter();
+ let geom = reader.read(
+ reproject.reproject(profile, "unproj", "proj", crss)
+ );
+ let buffer4326 = reproject.reproject(
+ writer.write(geom.geometry.buffer(data.profile.buffer)),
+ "proj",
+ "unproj",
+ crss
+ );
+
+ L.geoJson(buffer4326, {
+ color: "#ff7800",
+ weight: 1,
+ opacity: 1,
+ fillOpacity: 0.1,
+ dashArray: "5,3",
+ }).addTo(displayedItems);
+
+ var profileLayer = new L.geoJSON(profile);
+
+ profileLayer.bindTooltip(data.profile.title, {
+ className: "watsonc-profile-tooltip",
+ permanent: true,
+ offset: [0, 0],
+ });
+
+ profileLayer.addTo(displayedItems);
+ }
+
+ handleProfileToggle(checked, profileKey) {
+ if (checked) {
+ this.props.onProfileShow(profileKey);
+ } else {
+ this.props.onProfileHide(profileKey);
+ }
+ }
+
+ addToDashboard(item) {
+ this.props.onProfileAdd(item);
+ }
+
+ render() {
+ let overlay = false;
+ if (this.state.loading) {
+ overlay = ;
+ }
+
+ let availableLayers = (
+
+
{__(`No layers found`)}
+
+ );
+
+ if (this.state.layers && this.state.layers.length > 0) {
+ availableLayers = [];
+
+ const generateLayerRecord = (item, index, prefix) => {
+ let points = [];
+ item.intersectionSegments.map((item) => {
+ points.push(
+ `${Math.round(item[0] / 1000)}km - ${Math.round(item[1] / 1000)}km`
+ );
+ });
+
+ return (
+
+
+
+
+
+ {item.subtitle}
+
+ {__(`Stationing points`) + ": " + points.join(", ")}
+
+
+ );
+ };
+
+ this.state.layers
+ .filter((item) => item.type !== `geology`)
+ .map((item, index) => {
+ availableLayers.push(
+ generateLayerRecord(item, index, `non_geology_layer_`)
+ );
+ });
+ if (availableLayers.length > 0)
+ availableLayers.push(
+
+ );
+ this.state.layers
+ .filter((item) => item.type === `geology`)
+ .map((item, index) => {
+ availableLayers.push(
+ generateLayerRecord(item, index, `geology_layer_`)
+ );
+ });
+ }
+
+ let existingProfilesControls = (
+
+
{__(`Ingen profiler fundet`)}
+
+ );
+ let projectProfilesControls = (
+
+
{__(`Ingen profiler fundet`)}
+
+ );
+
+ let plotRows = [];
+ let projectProfileRows = [];
+ this.state.profiles.map((item, index) => {
+ if (this.state.profilesSearchTerm.length > 0) {
+ if (
+ item.profile.title
+ .toLowerCase()
+ .indexOf(this.state.profilesSearchTerm.toLowerCase()) === -1
+ ) {
+ return;
+ }
+ }
+ var deleteButton = item.fromProject ? null : (
+
+ {
+ this.handleProfileDelete(item);
+ }}
+ style={{ padding: `4px`, margin: `0px` }}
+ >
+ delete
+
+ |
+ );
+ var itemHtml = (
+
+
+
+
+ -1}
+ onClick={() => this.addToDashboard(item)}
+ // onChange={(event) => {
+ // this.handleProfileToggle(event.target.checked, item.key);
+ // }}
+ style={{ padding: `0px`, margin: `0px` }}
+ >
+ add
+
+
+
+ {item.profile.title}
+
+
+ |
+
+ {item.profile.compound
+ ? utils.getChemicalName(
+ item.profile.compound,
+ this.props.categories
+ )
+ : "Ikke valgt"}
+ |
+ {deleteButton}
+
+ );
+ if (item.fromProject === true) {
+ projectProfileRows.push(itemHtml);
+ } else {
+ plotRows.push(itemHtml);
+ }
+ });
+
+ if (plotRows.length > 0) {
+ existingProfilesControls = (
+
+
+
+
+
+
+ grid_on
+
+
+
+ {__(`Title`)}
+
+ |
+ {__(`Datatype`)} |
+
+
+ delete
+
+ |
+
+
+ {plotRows}
+
+ );
+ }
+
+ if (projectProfileRows.length > 0) {
+ projectProfilesControls = (
+
+
+
+
+
+
+ grid_on
+
+
+
+ {__(`Title`)}
+
+ |
+ {__(`Datatype`)} |
+
+
+ {projectProfileRows}
+
+ );
+ }
+
+ let chemicalName = "Ikke valgt";
+ if (this.state.localSelectedChemical) {
+ chemicalName = utils.getChemicalName(
+ this.state.localSelectedChemical,
+ this.props.categories
+ );
+ }
+
+ let renderText = "";
+ if (this.state.authenticated) {
+ renderText = (
+
+ {overlay}
+
+
+ {this.state.showDrawingForm ? (
+
+
+
+ {this.state.step !== STEP_ENTER_NAME ? (
+
+
+
+
+ {__(`Select datatype`)}:{" "}
+ {chemicalName}
+ {
+ const selectChemicalModalPlaceholderId = `${SELECT_CHEMICAL_DIALOG_PREFIX}-placeholder`;
+
+ if (
+ $(
+ `#${selectChemicalModalPlaceholderId}`
+ ).children().length > 0
+ ) {
+ ReactDOM.unmountComponentAtNode(
+ document.getElementById(
+ selectChemicalModalPlaceholderId
+ )
+ );
+ }
+
+ try {
+ ReactDOM.render(
+
+
+
+ {
+ this.setState({
+ localSelectedChemical:
+ selectorValue,
+ });
+ $(
+ "#" +
+ SELECT_CHEMICAL_DIALOG_PREFIX
+ ).modal("hide");
+ }}
+ />
+
+
+
,
+ document.getElementById(
+ selectChemicalModalPlaceholderId
+ )
+ );
+ } catch (e) {
+ console.error(e);
+ }
+
+ $("#" + SELECT_CHEMICAL_DIALOG_PREFIX).modal({
+ backdrop: `static`,
+ });
+ }}
+ >
+
+
+ {
+ this.setState({ localSelectedChemical: false });
+ }}
+ >
+
+
+
+
+
+
+
+
+
+ {__(`Adjust buffer`)}
+
+
+
+ {
+ this.setState({ buffer: value });
+ }}
+ />
+
+
+ {
+ this.setState({ buffer: event.target.value });
+ }}
+ value={this.state.buffer}
+ />
+
+
+
+
+
+
+ {__(`Adjust profile bottom`)}
+
+
+
+ {
+ this.setState({
+ profileBottom: event.target.value,
+ });
+ }}
+ value={this.state.profileBottom}
+ />
+
+
+
+
+
+ {this.state.step === STEP_CHOOSE_LAYERS ? (
+
+
+
+
+ {
+ this.saveProfile();
+ }}
+ >
+ {__(`Save and exit`)}
+
+
+
+
+ ) : (
+ false
+ )}
+
+ ) : (
+ false
+ )}
+
+ ) : (
+ false
+ )}
+
+
+
+
+ {
+ this.setState({ profilesSearchTerm });
+ }}
+ />
+
+
+
+
+
+
+ {this.state.showExistingProfiles ? (
+
+
+
{existingProfilesControls}
+
+
+ ) : (
+ false
+ )}
+
+
+ );
+ } else {
+ renderText = (
+
+
+
{__(`Log ind for at se og rette i profiler`)}
+
+
+ );
+ }
+
+ if (projectProfileRows.length > 0) {
+ renderText = (
+
+ {renderText}
+
+
+ {this.state.showProjectProfiles ? (
+
+
+
{projectProfilesControls}
+
+
+ ) : (
+ false
+ )}
+
+
+ );
+ }
+ return renderText;
+ }
+}
+
+MenuProfilesComponent.propTypes = {
+ cloud: PropTypes.any.isRequired,
+ backboneEvents: PropTypes.any.isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ selectedChemical: state.global.selectedChemical,
+ authenticated: state.global.authenticated,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ selectChemical: (key) => dispatch(selectChemical(key)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(MenuProfilesComponent);
diff --git a/browser/components/MenuTimeSeriesComponent.js b/browser/components/MenuTimeSeriesComponent.js
deleted file mode 100644
index 7935f7e..0000000
--- a/browser/components/MenuTimeSeriesComponent.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Switch from '@material-ui/core/Switch';
-import {Provider, connect} from 'react-redux';
-
-import PlotComponent from './PlotComponent';
-import {isNumber} from 'util';
-import {FREE_PLAN_MAX_PROFILES_COUNT} from './../constants';
-import TitleFieldComponent from './../../../../browser/modules/shared/TitleFieldComponent';
-import SearchFieldComponent from './../../../../browser/modules/shared/SearchFieldComponent';
-
-/**
- * Component for managing time series
- */
-class MenuTimeSeriesComponent extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- plots: this.props.initialPlots,
- activePlots: this.props.initialActivePlots,
- highlightedPlot: false,
- showArchivedPlots: false,
- authenticated: props.authenticated ? props.authenticated : false,
- plotsSearchTerm: '',
- };
- this.getPlots = this.getPlots.bind(this);
- this.setShowArchivedPlots = this.setShowArchivedPlots.bind(this);
- this.onPlotAdd = this.onPlotAdd.bind(this);
- window.menuTimeSeriesComponentInstance = this;
- }
-
- componentDidMount() {
- let _self = this;
- this.props.backboneEvents.get().on(`session:authChange`, (authenticated) => {
- if (_self.state.authenticated !== authenticated) {
- _self.setState({authenticated});
- }
- });
- }
-
- setPlots(plots) {
- this.setState({plots});
- }
-
- setActivePlots(activePlots) {
- this.setState({activePlots});
- }
-
- setHighlightedPlot(highlightedPlot) {
- this.setState({highlightedPlot})
- }
-
- setShowArchivedPlots(showArchivedPlots) {
- this.setState({showArchivedPlots});
- }
-
- getPlots() {
- if (this.state.showArchivedPlots) {
- return this.state.plots;
- } else {
- return this.state.plots.filter((plot) => plot.isArchived != true);
- }
- }
-
- canCreatePlot() {
- if (this.props.license === 'premium') {
- return true;
- } else {
- let plots = this.state.plots.filter((plot) => plot.fromProject != true);
- return plots.length < FREE_PLAN_MAX_TIME_SERIES_COUNT;
- }
- }
-
- onPlotAdd(title) {
- if (!this.canCreatePlot()) {
- $('#watsonc-limits-reached-text').show();
- $('#upgrade-modal').modal('show');
- return;
- }
-
- this.props.onPlotCreate(title);
- }
-
- render() {
- let plotsTable = [];
- let projectPlotsTable = [];
- this.getPlots().map((plot, index) => {
- if (this.state.plotsSearchTerm.length > 0) {
- if (plot.title.toLowerCase().indexOf(this.state.plotsSearchTerm.toLowerCase()) === -1) {
- return;
- }
- }
- let isChecked = (this.state.activePlots.indexOf(plot.id) > -1);
- let isHighlighted = (this.state.highlightedPlot === plot.id);
- let highlightingIsDisabled = (isChecked ? false : true);
- let archiveButton = plot.isArchived ?
- {
- this.props.onPlotArchive(plot.id, false)
- }}>
- unarchive
- :
- {
- this.props.onPlotArchive(plot.id, true)
- }}>
- archive
- ;
- if (plot.fromProject) {
- archiveButton = null;
- }
-
- let deleteButton = plot.fromProject ? null :
- {
- this.props.onPlotDelete(plot.id, plot.title);
- }} style={{padding: `4px`, margin: `0px`}}>
- delete
- ;
- let itemHtml =
-
-
-
-
-
-
- |
- {plot.title} |
-
-
- {archiveButton}
-
- |
-
- {deleteButton}
- |
-
;
-
- if (plot.fromProject === true) {
- projectPlotsTable.push(itemHtml)
- }
- plotsTable.push(itemHtml);
-
- });
-
- var showArchivedPlotsButton =
- Show Archived
- {
- this.setShowArchivedPlots(!this.state.showArchivedPlots)
- }}/>
-
;
- if (Array.isArray(plotsTable) && plotsTable.length > 0) {
- plotsTable = (
-
-
- border_all |
- {__(`Title`)} |
- |
- delete |
-
-
- {plotsTable}
-
);
- } else if (projectPlotsTable.length === 0) {
- plotsTable = ({__(`No time series were created yet`)}
);
- } else {
- plotsTable = null;
- }
- if (Array.isArray(projectPlotsTable) && projectPlotsTable.length > 0) {
- projectPlotsTable = (
-
-
- {__('Select Time Series from Project')}
-
-
-
-
- border_all |
- {__(`Title`)} |
-
-
- {projectPlotsTable}
-
-
);
- } else {
- projectPlotsTable = null;
- }
-
- var addTimeSeriesComponent = this.state.authenticated ?
-
{__(`Timeseries`)}
-
-
- :
-
-
{__(`Please sign in to create / edit Time series`)}
-
-
;
-
- return (
-
- {addTimeSeriesComponent}
-
-
{
- this.setState({plotsSearchTerm});
- }}/>
-
- {showArchivedPlotsButton}
-
-
{plotsTable}
-
- {projectPlotsTable}
-
-
- );
- }
-}
-
-const mapStateToProps = state => ({
- authenticated: state.global.authenticated
-})
-
-const mapDispatchToProps = dispatch => ({})
-
-MenuTimeSeriesComponent.propTypes = {
- initialPlots: PropTypes.array.isRequired,
- initialActivePlots: PropTypes.array.isRequired,
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(MenuTimeSeriesComponent);
diff --git a/browser/components/ModalComponent.js b/browser/components/ModalComponent.js
deleted file mode 100644
index f7c1864..0000000
--- a/browser/components/ModalComponent.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Provider} from 'react-redux';
-import reduxStore from '../redux/store';
-
-import withDragDropContext from './withDragDropContext';
-import ModalFeatureComponent from './ModalFeatureComponent';
-
-/**
- * Creates borehole parameters display and visualization panel
- */
-class ModalComponent extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- activeTabIndex: 0
- }
- }
-
- setPlots(plots) {
- this.setState({ plots });
- }
-
- render() {
- let tabs = false;
- if (this.props.features.length > 0) {
- let tabControls = [];
- this.props.features.map((item, index) => {
- let name;
- if (typeof item.properties.alias !== "undefined") {
- try {
- name = item.properties.alias;
- } catch (e) {
- name = item.properties.boreholeno;
- }
- } else {
- name = item.properties.boreholeno;
- }
- tabControls.push(
- { this.setState({activeTabIndex: index})}}>{name}
- );
- });
-
- tabs = ()
- }
-
- return ();
- }
-}
-
-ModalComponent.propTypes = {
- categories: PropTypes.object.isRequired,
- features: PropTypes.array.isRequired,
- names: PropTypes.object.isRequired,
- limits: PropTypes.object.isRequired,
- initialPlots: PropTypes.array.isRequired,
- initialActivePlots: PropTypes.array.isRequired,
- onPlotShow: PropTypes.func.isRequired,
- onPlotHide: PropTypes.func.isRequired,
- onPlotAdd: PropTypes.func.isRequired,
- onAddMeasurement: PropTypes.func.isRequired,
- onDeleteMeasurement: PropTypes.func.isRequired
-};
-
-export default withDragDropContext(ModalComponent);
diff --git a/browser/components/ModalFeatureComponent.js b/browser/components/ModalFeatureComponent.js
deleted file mode 100644
index 081e816..0000000
--- a/browser/components/ModalFeatureComponent.js
+++ /dev/null
@@ -1,458 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {connect} from 'react-redux';
-import Switch from '@material-ui/core/Switch';
-
-import withDragDropContext from './withDragDropContext';
-import ModalMeasurementComponent from './ModalMeasurementComponent';
-import ModalPlotComponent from './ModalPlotComponent';
-import TitleFieldComponent from './../../../../browser/modules/shared/TitleFieldComponent';
-import SearchFieldComponent from './../../../../browser/modules/shared/SearchFieldComponent';
-
-const evaluateMeasurement = require('./../evaluateMeasurement');
-const measurementIcon = require('./../measurementIcon');
-
-/**
- * Creates borehole parameters display and visualization panel
- */
-class ModalFeatureComponent extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- plots: this.props.initialPlots,
- measurementsSearchTerm: ``,
- plotsSearchTerm: ``,
- activePlots: this.props.initialActivePlots,
- }
- this.listRef = React.createRef();
- this.onPlotAdd = this.onPlotAdd.bind(this);
- this.handleHidePlot = this.handleHidePlot.bind(this);
- this.handleShowPlot = this.handleShowPlot.bind(this);
- this.hasSelectAll = this.hasSelectAll.bind(this);
- this.setSelectAll = this.setSelectAll.bind(this);
- }
-
- getSnapshotBeforeUpdate(prevProps, prevState) {
- const list = this.listRef.current;
- const scroll = list.scrollHeight - list.scrollTop;
- this.props.setModalScroll(scroll);
- }
-
- componentDidMount() {
- let {selectedChemical} = this.props;
- // Simulating the separate group for water level
- let categories = JSON.parse(JSON.stringify(this.props.categories));
- let selectedCategory = null;
- for (let category in categories) {
- for (let itemId in categories[category]) {
- if ((itemId + '') === (selectedChemical + '')) {
- selectedCategory = category;
- }
- }
- }
- try {
- let selectedCategoryKey = 'show' + selectedCategory.trim() + 'Measurements';
- this.setState({[selectedCategoryKey]: true});
- } catch (e) {
- console.info(e.message);
- // Hack to open group when Pesticid Overblik is chosen
- this.setState({"showPesticider og nedbrydningsprodMeasurements": true});
- }
- }
-
- componentDidUpdate(prevProps, prevState, snapshot) {
- if (this.props.modalScroll) {
- const list = this.listRef.current;
- list.scrollTop = list.scrollHeight - this.props.modalScroll;
- }
- }
-
- hasSelectAll() {
- let categories = JSON.parse(JSON.stringify(this.props.categories));
- let hasSelectAll = true;
- for (let category in categories) {
- if (!hasSelectAll) {
- break;
- }
- let selectedCategoryKey = 'show' + category.trim() + 'Measurements'
- let isCategorySelected = this.state[selectedCategoryKey];
- hasSelectAll = hasSelectAll && isCategorySelected;
- }
- return hasSelectAll;
- }
-
- handleHidePlot(plot) {
- let activePlots = this.state.activePlots.filter((activePlot) => {
- return activePlot.id != plot.id;
- })
- this.setState({activePlots});
- this.props.onPlotHide(plot.id);
- }
-
- handleShowPlot(plot) {
- let activePlots = this.state.activePlots;
- activePlots.push(plot);
- this.setState({activePlots});
- this.props.onPlotShow(plot.id);
- }
-
- setSelectAll(selectAll) {
- let categories = JSON.parse(JSON.stringify(this.props.categories));
- let stateUpdate = {}
- for (let category in categories) {
- let selectedCategoryKey = 'show' + category.trim() + 'Measurements';
- stateUpdate[selectedCategoryKey] = selectAll;
- }
- this.setState(stateUpdate);
- }
-
- setPlots(plots) {
- this.setState({plots});
- }
-
- canCreatePlot() {
- if (this.props.license === 'premium') {
- return true;
- } else {
- let plots = this.state.plots.filter((plot) => plot.fromProject !== true);
- return plots.length < FREE_PLAN_MAX_TIME_SERIES_COUNT;
- }
- }
-
- onPlotAdd(title) {
- if (!this.canCreatePlot()) {
- $('#watsonc-limits-reached-text').show();
- $('#upgrade-modal').modal('show');
- return;
- }
- this.props.onPlotAdd(title);
-
- }
-
- render() {
-
- // Simulating the separate group for water level
- let categories = JSON.parse(JSON.stringify(this.props.categories));
- categories[`Vandstand`] = {};
- categories[`Vandstand`][`watlevmsl`] = `Water level`;
- // Detect measurements from feature properties
- let plottedProperties = [];
- for (let key in this.props.feature.properties) {
- try {
- let data = JSON.parse(this.props.feature.properties[key]);
- if (typeof data === `object` && data !== null && `boreholeno` in data && `unit` in data && `title` in data
- && `measurements` in data && `timeOfMeasurement` in data) {
- // Regular properties ("measurements" and "timeOfMeasurement" exist)
- let isPlottableProperty = true;
- if (Array.isArray(data.measurements) === false) {
- data.measurements = JSON.parse(data.measurements);
- }
-
- // Checking if number of measurements corresponds to the number of time measurements for each intake
- data.measurements.map((measurements, intakeIndex) => {
- if (data.measurements[intakeIndex].length !== data.timeOfMeasurement[intakeIndex].length) {
- console.warn(`${data.title} property has not corresponding number of measurements and time measurements for intake ${intakeIndex + 1}`);
- isPlottableProperty = false;
- }
- });
-
- if (isPlottableProperty) {
- for (let i = 0; i < data.measurements.length; i++) {
- plottedProperties.push({
- key,
- intakeIndex: i,
- boreholeno: data.boreholeno,
- title: data.title,
- unit: data.unit
- });
- }
- }
- } else if (typeof data === `object` && data !== null && `title` in data && `data` in data) {
- for (let key in data.data) {
- for (let i = 0; i < data.data[key].data.length; i++) {
- plottedProperties.push({
- custom: true,
- key: data.key + ':' + key,
- intakeIndex: i,
- boreholeno: this.props.feature.properties.boreholeno ? this.props.feature.properties.boreholeno : ``,
- title: data.data[key].data[i].name,
- data: data.data[key]
- });
- }
- }
- }
- } catch (e) {
- }
- }
-
- // Preparing measurements
- let measurementsText = __(`Data series`);
- if (this.state.measurementsSearchTerm.length > 0) {
- measurementsText = __(`Found data series`);
- }
-
- /**
- * Creates measurement control
- *
- * @returns {Boolean|Object}
- */
- const createMeasurementControl = (item, key) => {
- let display = true;
- if (this.state.measurementsSearchTerm.length > 0) {
- if (item.title.toLowerCase().indexOf(this.state.measurementsSearchTerm.toLowerCase()) === -1) {
- display = false;
- }
- }
-
- let control = false;
- if (display) {
- let json;
- // Checking if the item is the custom one
- if (item.key.indexOf(':') > -1) {
- json = item;
- } else {
- try {
- json = JSON.parse(this.props.feature.properties[item.key]);
- } catch (e) {
- console.error(item);
- throw new Error(`Unable to parse measurements data`);
- }
- }
-
- let intakeName = `#` + (parseInt(item.intakeIndex) + 1);
- if (`intakes` in json && Array.isArray(json.intakes) && json.intakes[item.intakeIndex] !== null) {
- intakeName = json.intakes[item.intakeIndex] + '';
- }
-
- let icon = false;
- let measurementData = null;
- if (!item.custom) {
- measurementData = evaluateMeasurement(json, this.props.limits, item.key, item.intakeIndex);
- icon = measurementIcon.generate(measurementData.maxColor, measurementData.latestColor);
- }
-
- control = ();
- }
-
- return control;
- };
-
-
- let propertiesControls = [];
- if (Object.keys(categories).length > 0) {
- let numberOfDisplayedCategories = 0;
- for (let categoryName in categories) {
- let measurementsThatBelongToCategory = Object.keys(categories[categoryName]).map(e => categories[categoryName][e]);
- let measurementControls = [];
- plottedProperties = plottedProperties.filter((item, index) => {
- if (measurementsThatBelongToCategory.indexOf(item.title) !== -1) {
- // Measurement is in current category
- let control = createMeasurementControl(item, ('measurement_' + index));
- if (control) {
- measurementControls.push(control);
- }
-
- return false;
- } else {
- return true;
- }
- });
- if (measurementControls.length > 0) {
- measurementControls.sort(function (a, b) {
- return (b.props.detectionLimitReachedForLatest ? 0 : b.props.latestMeasurementRelative) - (a.props.detectionLimitReachedForLatest ? 0 : a.props.latestMeasurementRelative)
- })
- let key = 'show' + categoryName.trim() + 'Measurements'
- // Category has at least one displayed measurement
- numberOfDisplayedCategories++;
- propertiesControls.push(
-
- {this.state[key] ? (
{measurementControls}
) : false}
-
);
- }
- }
-
- // Placing uncategorized measurements in separate category
- let uncategorizedMeasurementControls = [];
- plottedProperties.slice().map((item, index) => {
- let control = createMeasurementControl(item, ('measurement_' + index));
- plottedProperties.splice(index, 1);
- if (control) {
- uncategorizedMeasurementControls.push(control);
- }
- });
-
- if (uncategorizedMeasurementControls.length > 0) {
- uncategorizedMeasurementControls.sort(function (a, b) {
- return (b.props.detectionLimitReachedForLatest ? 0 : b.props.latestMeasurementRelative) - (a.props.detectionLimitReachedForLatest ? 0 : a.props.latestMeasurementRelative)
- })
- // Category has at least one displayed measurement
- numberOfDisplayedCategories++;
- propertiesControls.push(
-
-
{__(`Uncategorized`)}
-
-
{uncategorizedMeasurementControls}
-
);
- }
- } else {
- plottedProperties.map((item, index) => {
- let control = createMeasurementControl(item, (`measurement_` + index));
- if (control) {
- propertiesControls.push(control);
- }
- });
- }
-
- // Preparing plots
- let plotsText = __(`Time series`);
- if (this.state.plotsSearchTerm.length > 0) {
- plotsText = __(`Found time series`);
- }
-
- let plotsControls = (
-
{__(`No timeseries were created yet`)}
-
-
{__(`Create a new table and then drag your desired data series into the box - and you're off`)}
-
-
);
-
- if (this.state.plots && this.state.plots.length > 0) {
- plotsControls = [];
- let activePlotIds = this.state.activePlots.map((plot) => {
- return plot?.id;
- });
- activePlotIds = activePlotIds.filter((id) => {
- return !!id;
- })
- this.state.plots.map((plot) => {
- let display = true;
- if (this.state.plotsSearchTerm.length > 0) {
- if (plot.title.toLowerCase().indexOf(this.state.plotsSearchTerm.toLowerCase()) === -1) {
- display = false;
- }
- }
-
- if (display) {
- plotsControls.push( -1}
- onPlotShow={this.handleShowPlot}
- onPlotHide={this.handleHidePlot}
- onDeleteMeasurement={this.props.onDeleteMeasurement}
- dataSource={this.props.dataSource}/>);
- }
- });
- }
-
- let borproUrl;
- try {
- borproUrl = this.props.feature.properties.boreholeno.replace(/\s/g, '');
- } catch (e) {
- borproUrl = "";
- }
-
- return (
-
-
-
-
-
-
- {
- this.setSelectAll(isChecked);
- }}/>
- Fold ind/ud
-
-
-
{measurementsText}
-
- {
- this.setState({measurementsSearchTerm});
- }}/>
-
-
-
-
{plotsText}
-
-
- {
- this.setState({plotsSearchTerm});
- }}/>
-
-
- add)}
- inputPlaceholder={__(`Create new`)}
- onAdd={(title) => {
- this.onPlotAdd(title)
- }}
- type="userOwned"
- customStyle={{width: `100%`}}/>
-
-
-
-
-
-
{propertiesControls}
-
{plotsControls}
-
-
);
- }
-}
-
-ModalFeatureComponent.propTypes = {
- categories: PropTypes.object.isRequired,
- feature: PropTypes.object.isRequired,
- names: PropTypes.object.isRequired,
- limits: PropTypes.object.isRequired,
- initialPlots: PropTypes.array.isRequired,
- onPlotAdd: PropTypes.func.isRequired,
- onAddMeasurement: PropTypes.func.isRequired,
- onDeleteMeasurement: PropTypes.func.isRequired
-};
-
-const mapStateToProps = state => ({
- selectedChemical: state.global.selectedChemical
-})
-
-export default connect(mapStateToProps)(withDragDropContext(ModalFeatureComponent));
diff --git a/browser/components/ModalMeasurementComponent.js b/browser/components/ModalMeasurementComponent.js
deleted file mode 100644
index 73b393d..0000000
--- a/browser/components/ModalMeasurementComponent.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {DragSource} from 'react-dnd';
-
-
-/**
- * Measurement component
- */
-class ModalMeasurementComponent extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- const isDragging = this.props.isDragging;
-
- let circleIcon = false;
- if (this.props.icon) {
- circleIcon = ();
- }
-
- if (this.props.chemicalLimits === null) {
- return this.props.connectDragSource(
- {circleIcon} {this.props.title} ({this.props.intakeName})
-
);
- } else {
- return this.props.connectDragSource(
-
-
- {circleIcon} {this.props.title} ({this.props.intakeName})
-
-
- Historisk: {this.props.detectionLimitReachedForMax ? "< " : ""}{this.props.maxMeasurement === 0 ? "-" : this.props.maxMeasurement} {this.props.maxMeasurement === 0 ? "" : {this.props.unit}} |
- Seneste: {this.props.detectionLimitReachedForLatest ? "< " : ""}{this.props.latestMeasurement} {this.props.unit}
-
-
-
);
- }
- }
-}
-
-const measurementSource = {
- beginDrag(props) {
- return {
- gid: props.gid,
- itemKey: props.itemKey,
- intakeIndex: props.intakeIndex,
- onAddMeasurement: props.onAddMeasurement
- };
- }
-};
-
-const collect = (connect, monitor) => {
- return {
- connectDragSource: connect.dragSource(),
- isDragging: monitor.isDragging()
- }
-};
-
-ModalMeasurementComponent.propTypes = {
- itemKey: PropTypes.string.isRequired,
- intakeIndex: PropTypes.number.isRequired,
- intakeName: PropTypes.string.isRequired,
- onAddMeasurement: PropTypes.func.isRequired,
-};
-
-export default DragSource(`MEASUREMENT`, measurementSource, collect)(ModalMeasurementComponent);
diff --git a/browser/components/ModalPlotComponent.js b/browser/components/ModalPlotComponent.js
deleted file mode 100644
index 99941ab..0000000
--- a/browser/components/ModalPlotComponent.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { DropTarget } from 'react-dnd';
-
-const utils = require('./../utils');
-
-/**
- * Plot component
- */
-class ModalPlotComponent extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- let removeButtons = [];
- if (this.props.plot?.measurements?.length) {
- for (let i = 0; i < this.props.plot.measurements.length; i++) {
- let measurement = this.props.plot.measurements[i];
- let measurementDisplayTitle = measurement;
- let splitMeasurementId = measurement.split(':');
-
- let customGraph = -1, key, intakeIndex;
- if (splitMeasurementId.length === 3) {
- customGraph = false;
- key = splitMeasurementId[1];
- intakeIndex = splitMeasurementId[2];
- } else if (splitMeasurementId.length === 4) {
- customGraph = true;
- key = splitMeasurementId[1] + ':' + splitMeasurementId[2];
- intakeIndex = splitMeasurementId[3];
- } else {
- throw new Error(`Invalid measurement key (${measurement})`);
- }
-
- let boreholeno = splitMeasurementId[0];
- if (this.props.dataSource && this.props.dataSource.length > 0) {
- this.props.dataSource.map(item => {
- if (item.properties.boreholeno === boreholeno) {
- if (customGraph) {
- let json = JSON.parse(item.properties[splitMeasurementId[1]]).data[splitMeasurementId[2]];
- let intakeName = `#` + (parseInt(splitMeasurementId[3]) + 1);
- if (`intakes` in json && Array.isArray(json.intakes) && json.intakes[parseInt(splitMeasurementId[3])] !== null) {
- intakeName = json.intakes[parseInt(splitMeasurementId[3])];
- }
-
- measurementDisplayTitle = (`${item.properties.boreholeno}, ${json.data[0].name} (${intakeName})`);
- return false;
- } else {
- let json = JSON.parse(item.properties[splitMeasurementId[1]]);
- let intakeName = `#` + (parseInt(splitMeasurementId[2]) + 1);
- if (`intakes` in json && Array.isArray(json.intakes) && json.intakes[parseInt(splitMeasurementId[2])] !== null) {
- intakeName = json.intakes[parseInt(splitMeasurementId[2])];
- }
-
- let title = utils.getMeasurementTitle(item);
- measurementDisplayTitle = (`${title}, ${json.title} (${intakeName})`);
- return false;
- }
- }
- });
- }
-
- const onDelete = () => {
- this.props.onDeleteMeasurement(this.props.plot.id, boreholeno, key, intakeIndex);
- };
-
- removeButtons.push(
-
- {measurementDisplayTitle}
-
-
);
- }
- }
-
- const isOver = this.props.isOver;
- return this.props.connectDropTarget(
-
-
{this.props.plot.title}
-
- {__(`Dashboard`)} {
- event.target.checked ? this.props.onPlotShow(this.props.plot) : this.props.onPlotHide(this.props.plot)}}/>
-
-
-
{removeButtons}
-
);
- }
-}
-
-const plotTarget = {
- drop(props, monitor) {
- let item = monitor.getItem();
- item.onAddMeasurement(props.plot.id, item.gid, item.itemKey, item.intakeIndex);
- }
-};
-
-function collect(connect, monitor) {
- return {
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver()
- };
-}
-
-ModalPlotComponent.propTypes = {
- onDeleteMeasurement: PropTypes.func.isRequired
-};
-
-export default DropTarget(`MEASUREMENT`, plotTarget, collect)(ModalPlotComponent);
diff --git a/browser/components/PlotComponent.js b/browser/components/PlotComponent.js
deleted file mode 100644
index 5af0e4c..0000000
--- a/browser/components/PlotComponent.js
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * @author Martin Høgh
- * @copyright 2013-2018 MapCentia ApS
- * @license http://www.gnu.org/licenses/#AGPL GNU AFFERO GENERAL PUBLIC LICENSE 3
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import axios from 'axios';
-import dayjs from 'dayjs';
-//import createPlotlyComponent from 'react-plotly.js/factory';
-//import Plotly from 'plotly.js-basic-dist';
-import Plot from 'react-plotly.js';
-
-//const Plot = createPlotlyComponent(Plotly);
-
-import {LIMIT_CHAR} from '../constants';
-import LoadingOverlay from './../../../../browser/modules/shared/LoadingOverlay';
-import SortableHandleComponent from './SortableHandleComponent';
-
-const utils = require('./../utils');
-
-/**
- * Creates single plot with multiple measurements displayed on it
- */
-class MenuPanelPlotComponent extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- loading: false
- };
- }
-
- download() {
- let data = [];
- this.props.plotMeta.measurements.map((measurementLocationRaw, index) => {
- if (measurementLocationRaw in this.props.plotMeta.measurementsCachedData &&
- this.props.plotMeta.measurementsCachedData[measurementLocationRaw]) {
- let measurementLocation = measurementLocationRaw.split(':');
- if (measurementLocation.length === 3) {
- let key = measurementLocation[1];
- let intakeIndex = parseInt(measurementLocation[2]);
-
- let feature = this.props.plotMeta.measurementsCachedData[measurementLocationRaw].data;
- let measurementData = JSON.parse(feature.properties[key]);
- if (Array.isArray(measurementData.measurements) === false) {
- measurementData.measurements = JSON.parse(measurementData.measurements);
- }
- let formatedDates = measurementData.timeOfMeasurement[intakeIndex].map(x => x.replace("T", " "));
- data.push({
- name: (`${feature.properties.boreholeno} - ${measurementData.title} (${measurementData.unit})`),
- x: formatedDates,
- y: measurementData.measurements[intakeIndex],
- });
- } else if (measurementLocation.length === 4) {
- let key = measurementLocation[1];
- let customSpecificator = measurementLocation[2];
-
- if ([`daily`, `weekly`, `monthly`].indexOf(customSpecificator) === -1) {
- throw new Error(`The custom specificator (${customSpecificator}) is invalid`);
- }
-
- let feature = this.props.plotMeta.measurementsCachedData[measurementLocationRaw].data;
- let measurementData = JSON.parse(feature.properties[key]);
- let measurementDataCopy = JSON.parse(JSON.stringify(measurementData.data));
- data.push(measurementDataCopy[customSpecificator].data[0]);
- } else {
- throw new Error(`Invalid key and intake notation: ${measurementLocationRaw}`);
- }
-
- } else {
- console.error(`Plot does not contain measurement ${measurementLocationRaw}`);
- }
- });
-
- this.setState({loading: true});
- axios.post('/api/extension/watsonc/download-plot', {
- title: this.props.plotMeta.title,
- data
- }, {responseType: 'arraybuffer'}).then(response => {
- const filename = this.props.plotMeta.title.replace(/\s+/g, '_').toLowerCase() + '.xlsx';
- const url = window.URL.createObjectURL(new Blob([response.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}));
-
- const link = document.createElement('a');
- link.href = url;
- link.setAttribute('download', filename);
- document.body.appendChild(link);
- link.click();
- window.URL.revokeObjectURL(url);
-
- this.setState({loading: false});
- }).catch(error => {
- console.error(error);
- alert(`Error occured while generating plot XSLS file`);
- this.setState({loading: false});
- })
-
- }
-
- render() {
- let plot = ({__(`At least one y axis has to be provided`)}
);
- let data = [];
- if (this.props.plotMeta.measurements && this.props.plotMeta.measurements.length > 0) {
- let colors = ['rgb(19,128,196)', 'rgb(16,174,140)', 'rgb(235,96,29)', 'rgb(247,168,77)', 'rgb(119,203,231)', `black`]
-
- let minTime = false;
- let maxTime = false;
-
- let yAxis2LayoutSettings = false;
- this.props.plotMeta.measurements.map((measurementLocationRaw, index) => {
- if (this.props.plotMeta.measurementsCachedData && measurementLocationRaw in this.props.plotMeta.measurementsCachedData &&
- this.props.plotMeta.measurementsCachedData[measurementLocationRaw] &&
- this.props.plotMeta.measurementsCachedData[measurementLocationRaw].data
- ) {
- let measurementLocation = measurementLocationRaw.split(':');
-
- let feature = this.props.plotMeta.measurementsCachedData[measurementLocationRaw].data;
- if (measurementLocation.length === 3) {
- let key = measurementLocation[1];
- let intakeIndex = parseInt(measurementLocation[2]);
- let createdAt = this.props.plotMeta.measurementsCachedData[measurementLocationRaw].created_at;
- let measurementData = JSON.parse(feature.properties[key]);
- /*
- let localMinTime = measurementData.timeOfMeasurement[intakeIndex][0];
- if (minTime === false) {
- minTime = localMinTime;
- } else {
- if (dayjs(localMinTime).isBefore(minTime)) {
- minTime = localMinTime;
- }
- }
-
- let localMaxTime = measurementData.timeOfMeasurement[intakeIndex][measurementData.timeOfMeasurement[intakeIndex].length - 1];
- if (maxTime === false) {
- maxTime = localMaxTime;
- } else {
- if (dayjs(localMaxTime).isAfter(maxTime)) {
- maxTime = localMaxTime;
- }
- }
- */
-
- let textValues = [];
- if (measurementData.attributes && Array.isArray(measurementData.attributes[intakeIndex]) && measurementData.attributes[intakeIndex].length > 0) {
- let xValues = [], yValues = [];
-
- measurementData.attributes[intakeIndex].map((item, index) => {
- if (item === LIMIT_CHAR) {
- xValues.push(measurementData.timeOfMeasurement[intakeIndex][index]);
- yValues.push(measurementData.measurements[intakeIndex][index]);
- textValues.push(measurementData.measurements[intakeIndex][index] + ' ' + LIMIT_CHAR);
- } else {
- textValues.push(measurementData.measurements[intakeIndex][index]);
- }
- });
-
- if (xValues.length > 0) {
- data.push({
- x: xValues,
- y: yValues,
- type: 'scattergl',
- mode: 'markers',
- hoverinfo: 'none',
- showlegend: false,
- marker: {
- color: 'rgba(17, 157, 255, 0)',
- size: 20,
- line: {
- color: 'rgb(231, 0, 0)',
- width: 3
- }
- },
- });
- }
- } else { // Calypso stations
- measurementData.measurements[intakeIndex].map((item, index) => {
- textValues.push(Math.round(measurementData.measurements[intakeIndex][index] * 100) / 100);
- });
- }
-
- let title = utils.getMeasurementTitle(feature);
- let plotData = {
- name: (`${title} (${measurementData.intakes ? measurementData.intakes[intakeIndex] : (intakeIndex + 1)}) - ${measurementData.title} (${measurementData.unit})`),
- x: measurementData.timeOfMeasurement[intakeIndex],
- y: measurementData.measurements[intakeIndex],
- type: 'scattergl',
- mode: 'lines+markers',
- hoverinfo: 'text',
- marker: {
- color: colors[index]
- }
- };
-
- if (textValues.length > 0) plotData.hovertext = textValues;
- data.push(plotData);
- } else if (measurementLocation.length === 4) {
- let key = measurementLocation[1];
- let customSpecificator = measurementLocation[2];
-
- if ([`daily`, `weekly`, `monthly`].indexOf(customSpecificator) === -1) {
- throw new Error(`The custom specificator (${customSpecificator}) is invalid`);
- }
-
- let measurementData = JSON.parse(feature.properties[key]);
- let measurementDataCopy = JSON.parse(JSON.stringify(measurementData.data));
- data.push(measurementDataCopy[customSpecificator].data[0]);
-
- let range = [0, 0];
- for (let key in measurementDataCopy) {
- if (measurementDataCopy[key].layout.yaxis2.range) {
- if (measurementDataCopy[key].layout.yaxis2.range[0] < range[0]) range[0] = measurementDataCopy[key].layout.yaxis2.range[0];
- if (measurementDataCopy[key].layout.yaxis2.range[1] > range[1]) range[1] = measurementDataCopy[key].layout.yaxis2.range[1];
- }
-
- yAxis2LayoutSettings = measurementDataCopy[key].layout.yaxis2;
- }
-
- yAxis2LayoutSettings.range = range;
- yAxis2LayoutSettings.showgrid = false;
- } else {
- throw new Error(`Invalid key and intake notation: ${measurementLocationRaw}`);
- }
- } else {
- console.error(`Plot does not contain measurement ${measurementLocationRaw}`);
- }
- });
-
- let layout = {
- displayModeBar: false,
- margin: {
- l: 30,
- r: (yAxis2LayoutSettings ? 30 : 5),
- b: 30,
- t: 5,
- pad: 4
- },
- xaxis: {
- autorange: true,
- margin: 0,
- type: 'date'
- },
- yaxis: {
- autorange: true,
- },
- showlegend: true,
- legend: {
- orientation: "h",
- y: -0.2
- },
- autosize: true
- };
-
- if (yAxis2LayoutSettings) {
- layout.yaxis2 = yAxis2LayoutSettings;
- }
-
- plot = ( false}
- style={{width: "100%", height: `${this.props.height - 60}px`}}/>);
- }
-
- return (
- {this.state.loading ?
: false}
-
-
-
{this.props.plotMeta.title}
-
-
-
-
{plot}
-
);
- }
-}
-
-MenuPanelPlotComponent.propTypes = {
- onDelete: PropTypes.func.isRequired,
- plotMeta: PropTypes.object.isRequired
-};
-
-export default MenuPanelPlotComponent;
diff --git a/browser/components/ProfileComponent.js b/browser/components/ProfileComponent.js
deleted file mode 100644
index 0aa13f1..0000000
--- a/browser/components/ProfileComponent.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-//import createPlotlyComponent from 'react-plotly.js/factory';
-//import Plotly from 'plotly.js-basic-dist';
-//const Plot = createPlotlyComponent(Plotly);
-import Plot from 'react-plotly.js';
-
-import SortableHandleComponent from './SortableHandleComponent';
-
-/**
- * Creates single profile with multiple measurements displayed on it
- */
-class ProfileComponent extends React.Component {
- constructor(props) {
- super(props);
- }
-
- render() {
- let dataCopy = JSON.parse(JSON.stringify(this.props.plotMeta.profile.data.data).replace(/%28/g, '(').replace(/%29/g, ')'));
- dataCopy.map((item, index) => {
- if (!dataCopy[index].mode) dataCopy[index].mode = 'lines';
- });
-
- let plot = ({__(`At least one y axis has to be provided`)}
);
- if (this.props.plotMeta) {
- let layoutCopy = JSON.parse(JSON.stringify(this.props.plotMeta.profile.data.layout));
- layoutCopy.margin = {
- l: 50,
- r: 5,
- b: 45,
- t: 5,
- pad: 1
- };
- layoutCopy.autosize = true;
-
- plot = ();
- }
-
- return (
-
-
-
{this.props.plotMeta.profile.title}
-
-
-
-
{plot}
-
);
- }
-}
-
-ProfileComponent.propTypes = {
- onDelete: PropTypes.func.isRequired,
- onChangeDatatype: PropTypes.func.isRequired,
- plotMeta: PropTypes.object.isRequired
-};
-
-export default ProfileComponent;
diff --git a/browser/components/SortableHandleComponent.js b/browser/components/SortableHandleComponent.js
deleted file mode 100644
index 4db77b8..0000000
--- a/browser/components/SortableHandleComponent.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import {sortableHandle} from 'react-sortable-hoc';
-
-const SortableHandleComponent = (props) => {
- return (
- {__(`Move`)}
- );
-}
-
-export default sortableHandle(SortableHandleComponent);
\ No newline at end of file
diff --git a/browser/components/SortablePlotComponent.js b/browser/components/SortablePlotComponent.js
deleted file mode 100644
index 5110a1b..0000000
--- a/browser/components/SortablePlotComponent.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {sortableElement} from 'react-sortable-hoc';
-import {VIEW_ROW} from './../constants';
-
-import PlotComponent from './PlotComponent';
-
-/**
- * Wrapper for making a Plot component sortable inside of Plots grid
- */
-const SortablePlotComponent = (props) => {
- return (
-
-
{ props.handleDelete(id)}}
- height={props.height}
- viewMode={props.viewMode}
- plotMeta={props.meta}/>
-
- );
-}
-
-SortablePlotComponent.propTypes = {
- handleDelete: PropTypes.func.isRequired,
- meta: PropTypes.object.isRequired,
-};
-
-export default sortableElement(SortablePlotComponent);
\ No newline at end of file
diff --git a/browser/components/SortablePlotsGridComponent.js b/browser/components/SortablePlotsGridComponent.js
deleted file mode 100644
index 0a3668f..0000000
--- a/browser/components/SortablePlotsGridComponent.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react';
-import {sortableContainer} from 'react-sortable-hoc';
-
-const SortablePlotsGridComponent = sortableContainer(({children}) => {
- return ();
-});
-
-export default SortablePlotsGridComponent;
\ No newline at end of file
diff --git a/browser/components/SortableProfileComponent.js b/browser/components/SortableProfileComponent.js
deleted file mode 100644
index 7904c6b..0000000
--- a/browser/components/SortableProfileComponent.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {sortableElement} from 'react-sortable-hoc';
-import {VIEW_ROW} from './../constants';
-
-import ProfileComponent from './ProfileComponent';
-
-/**
- * Wrapper for making a Profile component sortable inside of Plots&Profiles grid
- */
-const SortableProfileComponent = (props) => {
- return (
-
-
{ props.handleDelete(id)}}
- onClick={props.handleClick}
- height={props.height}
- onChangeDatatype={(id) => { props.handleChangeDatatype(id)}}
- plotMeta={props.meta}/>
-
- );
-}
-
-SortableProfileComponent.propTypes = {
- handleDelete: PropTypes.func.isRequired,
- handleClick: PropTypes.func.isRequired,
- handleChangeDatatype: PropTypes.func.isRequired,
- meta: PropTypes.object.isRequired,
-};
-
-export default sortableElement(SortableProfileComponent);
\ No newline at end of file
diff --git a/browser/components/SubscriptionDialogue.js b/browser/components/SubscriptionDialogue.js
new file mode 100644
index 0000000..4d021ab
--- /dev/null
+++ b/browser/components/SubscriptionDialogue.js
@@ -0,0 +1,437 @@
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import Title from "./shared/title/Title";
+import Icon from "./shared/icons/Icon";
+import CloseButton from "./shared/button/CloseButton";
+import { hexToRgbA } from "../helpers/colors";
+import { DarkTheme } from "../themes/DarkTheme";
+import { Align } from "./shared/constants/align";
+import { Variants } from "./shared/constants/variants";
+import { Size } from "./shared/constants/size";
+import ThemeProvider from "../themes/ThemeProvider";
+import reduxStore from "../redux/store";
+import { Provider } from "react-redux";
+import { setDashboardMode } from "../redux/actions";
+import { LOGIN_MODAL_DIALOG_PREFIX } from "../constants";
+const session = require("./../../../session/browser/index");
+
+function SubscriptionDialogue(props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+
+
+
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+
+
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+
+
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+
+
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!props.session.isAuthenticated() && (
+ {
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("show");
+ $("#upgrade-modal").modal("hide");
+ }}
+ >
+ {__("Log ind")}{" "}
+
+ )}
+
+
+ {props.session?.getProperties()?.license !== "premium" && (
+
+ window.open(
+ "https://admin.calypso.watsonc.dk/login?new=1",
+ "_blank"
+ )
+ }
+ >
+ {__("Vælg premium")}{" "}
+
+ )}
+
+
+
+ window.open("https://calypso.watsonc.dk/kontakt/", "_blank")
+ }
+ >
+ {__("Kontakt")}{" "}
+
+
+
+
+
+
+
+ );
+}
+
+const Root = styled.div`
+ background: ${({ theme }) => hexToRgbA(theme.colors.primary[1], 0.96)};
+ border-radius: ${({ theme }) => `${theme.layout.borderRadius.large}px`};
+ color: ${({ theme }) => `${theme.colors.headings}`};
+`;
+
+const ModalHeader = styled.div`
+ padding: ${({ theme }) =>
+ `${theme.layout.gutter}px ${theme.layout.gutter}px 0 ${theme.layout.gutter}px`};
+`;
+
+const ModalBody = styled.div`
+ padding: ${({ theme }) => `${theme.layout.gutter}px`};
+`;
+
+const LabelContainer = styled.div`
+ display: block;
+`;
+
+const Plan = styled.div`
+ width: 100%;
+ border: 3px solid ${({ theme }) => `${theme.colors.primary[3]}`};
+ border-radius: ${({ theme }) => `12px 12px 0px 0px`};
+ &.short-title {
+ margin-top: 16px;
+ }
+ &:hover {
+ border: 3px solid ${({ theme }) => `${theme.colors.interaction[4]}`};
+ }
+`;
+
+const PlanPlaceholder = styled.div`
+ height: 80px;
+ width: 100%;
+`;
+
+const PlanItemTitle = styled.div`
+ width: 100%;
+ height: 80px;
+ text-align: center;
+ background: ${({ theme }) => `${theme.colors.primary[3]}`};
+ padding: ${({ theme }) => `${theme.layout.gutter / 4}px`};
+ border-radius: ${({ theme }) =>
+ `${theme.layout.gutter / 4}px ${theme.layout.gutter / 4}px 0px 0px`};
+ &.short-title {
+ height: 64px;
+ }
+`;
+
+const PlanFeature = styled.div``;
+
+const PlanItem = styled.div`
+ height: 80px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ border-bottom: 1px solid ${({ theme }) => `${theme.colors.gray[2]}`};
+ padding-bottom: ${({ theme }) => `${theme.layout.gutter / 4}px`};
+ ${PlanFeature} {
+ margin: auto;
+ }
+ &:last-child {
+ border-bottom: 0;
+ }
+`;
+
+const IconContainer = styled.div`
+ height: 16px;
+ width: 16px;
+ border-radius: 50%;
+ background: ${({ theme }) => `${theme.colors.interaction[4]}`};
+ color: #000;
+ margin: auto;
+ text-align: center;
+`;
+
+const SelectPlanButton = styled.button`
+ background: ${({ theme }) => `${theme.colors.interaction[4]}`};
+ color: #000;
+ border: none;
+ border-radius: ${({ theme }) => `${theme.layout.borderRadius.small}px`};
+ text-align: center;
+ width: 100%;
+ height: 50px;
+ margin: auto;
+ margin-top: ${({ theme }) => `${theme.layout.gutter / 2}px`};
+ margin-left: 6px;
+`;
+
+export default SubscriptionDialogue;
diff --git a/browser/components/TopBar.js b/browser/components/TopBar.js
new file mode 100644
index 0000000..5ad672d
--- /dev/null
+++ b/browser/components/TopBar.js
@@ -0,0 +1,119 @@
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import { getLogo } from "../utils";
+import Button from "./shared/button/Button";
+import { Variants } from "./shared/constants/variants";
+import SearchBox from "./shared/inputs/Searchbox";
+import UserProfileButton from "./shared/userProfileButton/UserProfileButton";
+import { showSubscription } from "../helpers/show_subscriptionDialogue";
+import { useState, useEffect } from "react";
+import useInterval from "./shared/hooks/useInterval";
+import { LOGIN_MODAL_DIALOG_PREFIX } from "../constants";
+
+const FREE = "FREE";
+const NOTLOGGEDIN = "NOTLOGGEDIN";
+const PREMIUM = "PREMIUM";
+
+function TopBar(props) {
+ const [status, setStatus] = useState(NOTLOGGEDIN);
+ const [stopPoll, setStopPoll] = useState(false);
+
+ useInterval(
+ () => {
+ if (props.session.isStatusChecked()) {
+ setStopPoll(true);
+ if (props.session.getProperties() !== null) {
+ setStatus(
+ props.session.getProperties()["license"] === "premium"
+ ? PREMIUM
+ : FREE
+ );
+ } else {
+ setStatus(NOTLOGGEDIN);
+ }
+ }
+ },
+ stopPoll ? null : 1000
+ );
+
+ useEffect(() => {
+ props.backboneEvents.get().on("refresh:meta", () => {
+ console.log("whaat");
+ console.log(props.session.getProperties());
+ if (props.session.getProperties() !== null) {
+ setStatus(
+ props.session.getProperties()["license"] === "premium"
+ ? PREMIUM
+ : FREE
+ );
+ } else {
+ setStatus(NOTLOGGEDIN);
+ }
+ });
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+ Vælg data
+
+
+
+
+
+
+
+
+ (document.getElementById("custom-search").value = "")
+ }
+ >
+
+
+
+
+ {status === FREE && (
+
+ )}
+ {status === NOTLOGGEDIN && (
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("show")}
+ />
+ )}
+
+
+
+
+
+ );
+}
+
+const Row = styled(Grid)`
+ background-color: ${({ theme }) => theme.colors.primary[4]};
+ height: ${({ theme }) => theme.layout.gutter * 2}px;
+`;
+
+const Logo = styled.img`
+ height: ${({ theme }) => theme.layout.gutter}px;
+ margin-left: ${({ theme }) => theme.layout.gutter / 2}px;
+`;
+
+export default TopBar;
diff --git a/browser/components/dashboardshell/CardListItem.js b/browser/components/dashboardshell/CardListItem.js
new file mode 100644
index 0000000..b3d5788
--- /dev/null
+++ b/browser/components/dashboardshell/CardListItem.js
@@ -0,0 +1,209 @@
+import { useState, useEffect } from "react";
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import Icon from "../shared/icons/Icon";
+import Title from "../shared/title/Title";
+
+const utils = require("../../utils");
+
+const options = [
+ { index: 0, text: "Døgnmiddel", window: "day", func: "mean" },
+ { index: 1, text: "Ugemiddel", window: "week", func: "mean" },
+ { index: 2, text: "Månedmiddel", window: "month", func: "mean" },
+
+ { index: 3, text: "Døgnsum", window: "day", func: "sum" },
+ { index: 4, text: "Ugesum", window: "week", func: "sum" },
+ { index: 5, text: "Månedsum", window: "month", func: "sum" },
+];
+
+function CardListItem(props) {
+ const [name, setName] = useState("");
+ const [infoForDeletion, setInfoForDeletion] = useState({});
+ const [useSumInsteadOfMean, setUseSumInsteadOfMean] = useState(false);
+
+ const aggregateValue = options.find((elem) => {
+ return (
+ elem.window === props.aggregate?.window &&
+ elem.func === props.aggregate?.func
+ );
+ });
+
+ const selectValue = aggregateValue ? aggregateValue.index : "";
+
+ useEffect(() => {
+ if (props.measurement) {
+ let splitMeasurement = props.measurement.split(":");
+ let measurementLength = splitMeasurement.length;
+ let key = null;
+ let feature = null;
+ let intakeIndex = null;
+ let boreholeno = splitMeasurement[0];
+ let idx = null;
+ if (measurementLength === 3) {
+ key = splitMeasurement[1];
+ intakeIndex = splitMeasurement[2];
+ } else if (measurementLength === 4) {
+ key = splitMeasurement[1] + ":" + splitMeasurement[2];
+ intakeIndex = splitMeasurement[3];
+ }
+ if (
+ props.plot.measurementsCachedData &&
+ props.plot.measurementsCachedData[props.measurement]
+ ) {
+ feature = props.plot.measurementsCachedData[props.measurement].data;
+ let measurementData = JSON.parse(feature.properties[key]);
+
+ idx = measurementData?.ts_id.findIndex(
+ (elem) => elem.toString() == intakeIndex
+ );
+ // const NAME = measurementData.title
+ // ? `${measurementData.locname} ${measurementData.title}, ${measurementData.parameter}`
+ // : `${measurementData.locname}, ${measurementData.parameter}`;
+ setName(measurementData?.data[idx]?.name);
+
+ if (measurementData?.data[idx]?.tstype_id === 4) {
+ setUseSumInsteadOfMean(true);
+ }
+ // setName(`${measurementData.title} (${measurementData.unit})`);
+ }
+ setInfoForDeletion({
+ plotId: props.plot.id,
+ boreholeno,
+ key,
+ intakeIndex,
+ });
+ }
+ }, [props.measurement, props.plot]);
+
+ return (
+
+
+ {/*
+
+
+
+ */}
+
+
+ {/* {
+ console.log(props);
+ props.setAggregate(props.measurement, "day", "mean");
+ }}
+ >
+ Tryk
+ */}
+
+
+ {
+ props.deleteAggregate(props.measurement);
+ props.onDeleteMeasurement(
+ infoForDeletion.plotId,
+ infoForDeletion.boreholeno,
+ infoForDeletion.key,
+ infoForDeletion.intakeIndex
+ );
+ }}
+ >
+
+
+
+
+
+ );
+}
+
+const RemoveIconContainer = styled.div`
+ width: 18px;
+ height: 18px;
+ margin-top: 3px;
+ border: 1px solid ${(props) => props.theme.colors.gray[2]};
+ border-radius: 50%;
+ display: none;
+ cursor: pointer;
+`;
+
+const Root = styled.div`
+ width: 100%;
+ padding: ${(props) => props.theme.layout.gutter / 8}px;
+ vertical-align: middle;
+ height: ${(props) => props.theme.layout.gutter}px;
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ &:hover {
+ background: ${(props) => props.theme.colors.gray[4]};
+ ${RemoveIconContainer} {
+ display: block;
+ }
+ }
+`;
+
+const CardListLabel = styled.div`
+ vertical-align: middle;
+ display: inline-block;
+ // margin-left: ${(props) => props.theme.layout.gutter / 4}px;
+`;
+
+const Select = styled.select`
+ width: 100%;
+ height: ${(props) =>
+ props.theme.layout.gutter - props.theme.layout.gutter / 8 + 2}px;
+ background: transparent;
+ padding-left: 5px;
+ font: ${(props) => props.theme.fonts.label};
+ border: none;
+ margin-left: 10px;
+ word-wrap: break-word;
+ text-overflow: inherit;
+ white-space: normal;
+ color: black;
+
+ option {
+ color: black;
+ background: transparent;
+ display: flex;
+ white-space: pre;
+ min-height: 20px;
+ padding: 0px 2px 1px;
+ }
+
+ s
+`;
+
+export default CardListItem;
diff --git a/browser/components/dashboardshell/ChemicalSelector.js b/browser/components/dashboardshell/ChemicalSelector.js
new file mode 100644
index 0000000..f47a62b
--- /dev/null
+++ b/browser/components/dashboardshell/ChemicalSelector.js
@@ -0,0 +1,224 @@
+import { useState, useEffect } from "react";
+import { connect } from "react-redux";
+import Collapse from "@material-ui/core/Collapse";
+import styled from "styled-components";
+import Searchbox from "../shared/inputs/Searchbox";
+import ChemicalsListItem from "./ChemicalsListItem";
+import CircularProgress from "@material-ui/core/CircularProgress";
+import Grid from "@material-ui/core/Grid";
+import { DarkTheme } from "../../themes/DarkTheme";
+
+function ChemicalSelector(props) {
+ const [chemicalsList, setChemicalsList] = useState([]);
+ const [searchTerm, setSearchTerm] = useState("");
+ const [loadingData, setLoadingData] = useState(false);
+
+ props.backboneEvents.get().on("watsonc:clearChemicalList", () => {
+ setChemicalsList([]);
+ });
+
+ useEffect(() => {
+ // setLoadingData(true);
+ if (!props.feature || !props.feature.properties) {
+ setChemicalsList([]);
+ return;
+ }
+ let currentgroup = null;
+
+ let param2group = {};
+ let tmp = Object.entries(props.categories).map((elem) => {
+ return { parameter: Object.values(elem[1]), group: elem[0] };
+ });
+ tmp.forEach((prop) => {
+ prop.parameter.forEach((param) => {
+ param2group[param] = prop.group;
+ });
+ });
+
+ const createMeasurementControl = (item, key) => {
+ return new Promise(function (resolve, reject) {
+ const relation = item.feature.properties.relation;
+ const loc_id = item.feature.properties.loc_id;
+ const isJupiter = relation.includes("._");
+ fetch(
+ `/api/sql/jupiter?q=SELECT gid,trace,ts_id,locname,loc_id,ts_name,parameter,unit FROM ${relation} WHERE loc_id='${loc_id}'&base64=false&lifetime=60&srs=4326`
+ ).then(
+ (res) => {
+ res.json().then((json) => {
+ if (!res.ok) {
+ reject(json);
+ }
+ let properties = json.features[0].properties;
+ let controls = [];
+ if (properties.ts_name.length < 10) {
+ controls.push(
+ properties.locname + "_" + elem
+ )}
+ intakeIndex={properties.ts_id.map((elem) => elem)}
+ feature={properties}
+ />
+ );
+ }
+ // console.log(properties);
+
+ properties.ts_name.forEach((prop, index) => {
+ properties.relation = relation;
+ let intakeName = `#` + properties.ts_id[index];
+ let icon = false;
+ if (isJupiter) {
+ let group = param2group[properties.parameter[index]];
+ if (group != currentgroup) {
+ controls.push(
+
+
+
+
+
+ );
+ currentgroup = group;
+ }
+ }
+
+ controls.push(
+
+ );
+ });
+ resolve(controls);
+ });
+ },
+ (err) => {
+ alert(err.message);
+ }
+ );
+ });
+ };
+
+ createMeasurementControl(props)
+ .then((controls) => {
+ setLoadingData(false);
+ // controls.unshift(allChems)
+ setChemicalsList(controls);
+ })
+ .catch((json) => {
+ alert(json.message);
+ setLoadingData(false);
+ });
+ }, [props.categories, props.feature]);
+
+ return (
+
+
+ {loadingData && (
+
+
+
+ )}
+ setSearchTerm(value)}
+ />
+
+
+ {chemicalsList.filter((item, index) => {
+ let searchTermLower = searchTerm.toLowerCase();
+ if (
+ searchTerm.length &&
+ item.props?.label &&
+ item.props.label.toLowerCase().indexOf(searchTermLower) === -1
+ ) {
+ return false;
+ } else {
+ return true;
+ }
+ })}
+
+
+ );
+}
+
+const Root = styled.div`
+ background: ${(props) => props.theme.colors.primary[2]};
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ height: 100%;
+ width: 100%;
+ padding: ${(props) => props.theme.layout.gutter / 2}px;
+`;
+
+const SearchboxContainer = styled.div`
+ position: relative;
+ width: 100%;
+ // padding: ${(props) => props.theme.layout.gutter / 2}px 0px;
+`;
+
+const ChemicalsList = styled.div`
+ width: 100%;
+ padding: ${(props) => props.theme.layout.gutter / 4}px;
+`;
+
+const ChemicalsListTitle = styled.div`
+ color: ${(props) => props.theme.colors.headings};
+ margin: 10px 0;
+ cursor: pointer;
+`;
+
+const mapStateToProps = (state) => ({
+ categories: state.global.categories,
+ limits: state.global.limits,
+});
+
+const mapDispatchToProps = (dispatch) => ({});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ChemicalSelector);
diff --git a/browser/components/dashboardshell/ChemicalsListItem.js b/browser/components/dashboardshell/ChemicalsListItem.js
new file mode 100644
index 0000000..95fa322
--- /dev/null
+++ b/browser/components/dashboardshell/ChemicalsListItem.js
@@ -0,0 +1,118 @@
+import { useState, useEffect } from "react";
+import { useDrag } from "react-dnd";
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import { DarkTheme } from "../../themes/DarkTheme";
+
+function ChemicalsListItem(props) {
+ const [{ isDragging }, drag] = useDrag(() => ({
+ type: "MEASUREMENT",
+ collect: (monitor) => ({
+ isDragging: !!monitor.isDragging(),
+ }),
+ item: {
+ gid: props.gid,
+ itemKey: props.itemKey,
+ intakeIndex: props.intakeIndex,
+ onAddMeasurement: props.onAddMeasurement,
+ feature: props.feature,
+ },
+ }));
+
+ const [descriptionText, setDescriptionText] = useState("");
+
+ useEffect(() => {
+ // TODO brug nyt felt
+ let description = "";
+ setDescriptionText("ohyeah");
+ }, [
+ props.detectionLimitReachedForMax,
+ props.maxMeasurement,
+ props.detectionLimitReachedForLatest,
+ props.latestMeasurement,
+ props.unit,
+ ]);
+ return (
+
+
+
+
+
+
+
+ {/**/}
+ {/* */}
+ {/**/}
+
+
+
+
+ {/*
+
+ */}
+
+
+
+ );
+}
+
+const Root = styled.div`
+ color: ${(props) => props.theme.colors.headings};
+ margin-top: ${(props) => props.theme.layout.gutter / 8}px;
+ padding: ${(props) => props.theme.layout.gutter / 8}px 0;
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ cursor: move;
+ &:hover {
+ background: ${(props) => props.theme.colors.primary[5]};
+ & svg {
+ color: ${(props) => props.theme.colors.primary[2]};
+ }
+ }
+`;
+
+const IconContainer = styled.div`
+ color: ${(props) => props.theme.colors.gray[4]};
+`;
+
+const CircleImage = styled.img`
+ height: 11px;
+ width: 11px;
+ border-radius: 50%;
+ display: inline-block;
+ margin-top: ${(props) => props.theme.layout.gutter / 4}px;
+`;
+
+const LabelRow = styled.div`
+ width: 100%;
+ display: block;
+`;
+
+const measurementSource = {
+ beginDrag(props) {
+ return {
+ gid: props.gid,
+ itemKey: props.itemKey,
+ intakeIndex: props.intakeIndex,
+ onAddMeasurement: props.onAddMeasurement,
+ };
+ },
+};
+
+const collect = (connect, monitor) => {
+ return {
+ connectDragSource: connect.dragSource(),
+ isDragging: monitor.isDragging(),
+ };
+};
+
+export default ChemicalsListItem;
diff --git a/browser/components/dashboardshell/DashboardContent.js b/browser/components/dashboardshell/DashboardContent.js
new file mode 100644
index 0000000..281bbcc
--- /dev/null
+++ b/browser/components/dashboardshell/DashboardContent.js
@@ -0,0 +1,743 @@
+import { useState, useEffect, useContext, useCallback } from "react";
+import { connect } from "react-redux";
+import styled from "styled-components";
+import { hexToRgbA } from "../../helpers/colors";
+import Grid from "@material-ui/core/Grid";
+import { DarkTheme } from "../../themes/DarkTheme";
+import ButtonGroup from "../shared/button/ButtonGroup";
+import Button from "../shared/button/Button";
+import { Variants } from "../shared/constants/variants";
+import { Align } from "../shared/constants/align";
+import arrayMove from "array-move";
+import SortableList from "../shared/list/SortableList";
+import Icon from "../shared/icons/Icon";
+import ChemicalsListItem from "./ChemicalsListItem";
+import GraphCard from "./GraphCard";
+import ChemicalSelector from "./ChemicalSelector";
+import ProjectContext from "../../contexts/project/ProjectContext";
+import ProjectList from "../dataselector/ProjectList";
+import MapDecorator from "../decorators/MapDecorator";
+import { getNewPlotId } from "../../helpers/common";
+import Collapse from "@material-ui/core/Collapse";
+import ExpandLess from "@material-ui/icons/ExpandLess";
+import ExpandMore from "@material-ui/icons/ExpandMore";
+import Title from "../shared/title/Title";
+import Searchbox from "../shared/inputs/Searchbox";
+import reduxStore from "../../redux/store";
+import { addBoreholeFeature } from "../../redux/actions";
+import Backdrop from "@material-ui/core/Backdrop";
+import CircularProgress from "@material-ui/core/CircularProgress";
+import _ from "lodash";
+import usePrevious from "../shared/hooks/usePrevious";
+
+const DASHBOARD_ITEM_PLOT = 0;
+const DASHBOARD_ITEM_PROFILE = 1;
+const session = require("./../../../../session/browser/index");
+
+function DashboardContent(props) {
+ const [selectedBoreholeIndex, setSelectedBoreholeIndex] = useState(0);
+ const [selectedBorehole, setSelectedBorehole] = useState(null);
+ const [dashboardItems, setDashboardItems] = useState([]);
+ const [groups, setGroups] = useState([]);
+ const [myStations, setMyStations] = useState([]);
+ const projectContext = useContext(ProjectContext);
+ const [searchTerm, setSearchTerm] = useState("");
+ const [loadingData, setLoadingData] = useState(false);
+ const [filteredMystations, setFilteredMystations] = useState([]);
+ const [filteredBorehole, setFilteredBorehole] = useState(
+ props.boreholeFeatures.map((item, index) => {
+ return { ...item, index: index };
+ })
+ );
+
+ const prevCount = usePrevious(props.boreholeFeatures.length);
+
+ const deleteFromDashboard = (index) => {
+ const newBoreholes = props.boreholeFeatures;
+ newBoreholes.splice(index, 1);
+ reduxStore.dispatch(clearBoreholeFeatures());
+ newBoreholes.forEach((feature) => {
+ reduxStore.dispatch(addBoreholeFeature(feature));
+ });
+ };
+
+ const [open, setOpen] = useState({});
+ const handleClick = (group) => {
+ return () =>
+ setOpen((prev_state) => {
+ return { ...prev_state, [group]: !prev_state[group] };
+ });
+ };
+
+ const handlePlotSort = ({ oldIndex, newIndex }) => {
+ let allPlots = arrayMove(props.getAllPlots(), oldIndex, newIndex);
+ let activePlots = projectContext.activePlots;
+ activePlots = arrayMove(
+ activePlots.map((plot) => plot.id),
+ oldIndex,
+ newIndex
+ );
+ props.setPlots(allPlots, activePlots);
+ // props.onActivePlotsChange(activePlots, allPlots, projectContext);
+ setDashboardItems(arrayMove(dashboardItems, oldIndex, newIndex));
+ };
+
+ const handleRemoveProfile = (key) => {
+ let activeProfiles = projectContext.activeProfiles;
+ activeProfiles = activeProfiles.filter((profile) => profile.key !== key);
+ activeProfiles = activeProfiles.map((profile) => profile.key);
+
+ props.setProfiles(props.getAllProfiles(), activeProfiles);
+ };
+
+ const handleRemovePlot = (id) => {
+ let activePlots = props.activePlots;
+ let allPlots = props.getAllPlots();
+ activePlots = activePlots.filter((plot) => plot.id !== id);
+ allPlots = allPlots.filter((plot) => plot.id !== id);
+ activePlots = activePlots.map((plot) => plot.id);
+ // props.onActivePlotsChange(activePlots, allPlots, projectContext);
+ props.setPlots(allPlots, activePlots);
+ };
+
+ const handleDrop = (id, item) => {
+ setLoadingData(true);
+ let plot = props.getAllPlots().filter((p) => {
+ if (p.id === id) return true;
+ })[0];
+ $.ajax({
+ url: `/api/sql/jupiter?q=SELECT * FROM ${item.feature.relation} WHERE loc_id='${item.feature.loc_id}'&base64=false&lifetime=60&srs=4326`,
+ method: "GET",
+ dataType: "json",
+ }).then(
+ (response) => {
+ let ts_ids = [];
+ let item_keys = [];
+ if (!Array.isArray(item.intakeIndex)) {
+ ts_ids.push(item.intakeIndex);
+ item_keys.push(item.itemKey);
+ } else {
+ ts_ids = item.intakeIndex;
+ item_keys = item.itemKey;
+ }
+ ts_ids.forEach((ts_id, index) => {
+ const itidx = item.feature.ts_id.indexOf(ts_id);
+ let measurementsData = {
+ data: {
+ properties: {
+ _0: JSON.stringify({
+ unit: item.feature.unit[itidx],
+ title: item.feature.ts_name[itidx],
+ locname: item.feature.locname,
+ intakes: [1],
+ boreholeno: item.feature.loc_id,
+ data: response.features[0].properties.data,
+ trace: item.feature.trace,
+ relation: item.feature.relation,
+ parameter: item.feature.parameter[itidx],
+ ts_id: item.feature.ts_id,
+ ts_name: item.feature.ts_name,
+ }),
+ boreholeno: item.feature.loc_id,
+ numofintakes: 1,
+ },
+ },
+ };
+ item.onAddMeasurement(
+ plot.id,
+ item.gid,
+ item_keys[index],
+ ts_id,
+ measurementsData,
+ item.feature.relation
+ );
+ });
+
+ setLoadingData(false);
+ },
+ (jqXHR) => {
+ console.error(`Error occured while getting data`);
+ }
+ );
+ };
+
+ useEffect(() => {
+ window.Calypso = {
+ render: (id, popupType, trace, data) => {
+ data.properties.relation = popupType; // add relation name to feature properties
+ ReactDOM.render(
+
+
+ ,
+ document.getElementById(`pop_up_${id}`)
+ );
+ },
+ };
+ });
+
+ useEffect(() => {
+ const dashboardItemsCopy = [];
+
+ props.getDashboardItems().map((item, index) => {
+ if (item.type === DASHBOARD_ITEM_PROFILE) {
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PROFILE,
+ item: { ...item.item, title: item.item.profile.title },
+ plotsIndex: index,
+ });
+ } else {
+ dashboardItemsCopy.push({
+ type: DASHBOARD_ITEM_PLOT,
+ item: item.item,
+ plotsIndex: index,
+ });
+ }
+ });
+ setDashboardItems(dashboardItemsCopy);
+ }, [props.activePlots, props.activeProfiles]);
+
+ const get_my_stations = () => {
+ if (session.getProperties()?.organisation.id && session.getUserName()) {
+ $.ajax({
+ url: `/api/sql/watsonc?q=SELECT loc_id, locname, groupname, relation FROM calypso_stationer.calypso_my_stations_v2 WHERE user_id in (${
+ session.getProperties()?.organisation.id
+ }, ${session.getUserName()}) &base64=false&lifetime=60&srs=4326`,
+ method: "GET",
+ dataType: "json",
+ }).then((response) => {
+ function getArray(object) {
+ return Object.keys(object).reduce(function (r, k) {
+ object[k].forEach(function (a, i) {
+ r[i] = r[i] || {};
+ r[i][k] = a;
+ });
+ return r;
+ }, []);
+ }
+ var features = response.features;
+ var myStations = [];
+
+ features.forEach((element) => {
+ myStations = myStations.concat(getArray(element.properties));
+ });
+
+ myStations = _.uniqWith(myStations, _.isEqual);
+
+ myStations = myStations.sort((a, b) =>
+ a.locname.localeCompare(b.locname)
+ );
+
+ setMyStations(
+ myStations.map((elem) => {
+ return { properties: elem };
+ })
+ );
+ });
+ } else {
+ setMyStations([]);
+ }
+ };
+
+ useEffect(() => {
+ get_my_stations();
+ props.backboneEvents.get().on("refresh:meta", () => get_my_stations());
+ }, []);
+
+ useEffect(() => {
+ setOpen(
+ groups.map((elem) => {
+ return { [elem]: false };
+ })
+ );
+ }, [groups]);
+
+ useEffect(() => {
+ const filt = myStations
+ .map((item, index) => {
+ return { ...item, index: index };
+ })
+ .filter((elem) =>
+ elem.properties.locname.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ setFilteredMystations(filt);
+ const grp = filt
+ .map((item) => item.properties.groupname)
+ .sort(function (a, b) {
+ // equal items sort equally
+ if (a === b) {
+ return 0;
+ }
+ // nulls sort after anything else
+ else if (a === null) {
+ return 1;
+ } else if (b === null) {
+ return -1;
+ }
+ // otherwise, if we're ascending, lowest sorts first
+ else {
+ return a < b ? -1 : 1;
+ }
+ });
+
+ setGroups([...new Set(grp)]);
+ setFilteredBorehole(
+ props.boreholeFeatures
+ .map((item, index) => {
+ return { ...item, index: index };
+ })
+ .filter((elem) =>
+ elem.properties.locname
+ .toLowerCase()
+ .includes(searchTerm.toLowerCase())
+ )
+ );
+ }, [searchTerm, props.boreholeFeatures, myStations]);
+
+ useEffect(() => {
+ if (selectedBoreholeIndex === null) {
+ setSelectedBorehole(null);
+ }
+ if (selectedBoreholeIndex >= props.boreholeFeatures.length) {
+ setSelectedBorehole(
+ myStations[selectedBoreholeIndex - props.boreholeFeatures.length]
+ );
+ } else {
+ setSelectedBorehole(props.boreholeFeatures[selectedBoreholeIndex]);
+ }
+ props.onPlotsChange();
+ }, [selectedBoreholeIndex]);
+
+ useEffect(() => {
+ if (props.boreholeFeatures.length > prevCount) {
+ setSelectedBoreholeIndex(props.boreholeFeatures.length - 1);
+ }
+ if (props.boreholeFeatures.length === 1) {
+ setSelectedBoreholeIndex(0);
+ }
+ if (props.boreholeFeatures.length === 0) {
+ setSelectedBoreholeIndex(null);
+ }
+ props.onPlotsChange();
+ }, [props.boreholeFeatures]);
+
+ const handleTitleChange = (id) => {
+ return (title) => {
+ var allPlots = props.getAllPlots();
+ const index = allPlots.findIndex((plot) => plot.id === id);
+
+ if (index < 0) {
+ return;
+ }
+
+ allPlots[index] = {
+ ...allPlots[index],
+ title: title,
+ };
+ let activePlots = allPlots.map((plot) => plot.id);
+ props.setPlots(allPlots, activePlots);
+ };
+ };
+
+ return (
+
+ {props.dashboardContent === "charts" ? (
+
+
+
+
+
+
+
+ setSearchTerm(value)}
+ />
+
+
+
+
+
+ {filteredBorehole
+ ? filteredBorehole.map((item, index) => {
+ let name = item.properties.locname;
+ index = item.index;
+ let id = item.properties.loc_id + "_" + index;
+ const isJupiter =
+ item.properties.relation.includes("._");
+ return (
+ setSelectedBoreholeIndex(index)}
+ active={selectedBoreholeIndex === index}
+ key={id}
+ >
+
+ isJupiter
+ ? window.open(
+ `https://data.geus.dk/JupiterWWW/borerapport.jsp?dgunr=${name.replace(
+ /\s+/g,
+ ""
+ )}`,
+ "_blank",
+ "noopener,noreferrer"
+ )
+ : null
+ }
+ />
+
+ deleteFromDashboard(index)}
+ >
+
+
+
+ );
+ })
+ : null}
+
+
+
+
+
+ {groups.map((group) => {
+ return (
+
+ {group !== null ? (
+
+
+ {open[group] ? : }
+
+
+
+ {filteredMystations.map((item, index) => {
+ let name = item.properties.locname;
+ index = item.index;
+ return item.properties.groupname === group ? (
+
+ setSelectedBoreholeIndex(
+ index + props.boreholeFeatures.length
+ )
+ }
+ active={
+ selectedBoreholeIndex ===
+ index + props.boreholeFeatures.length
+ }
+ key={item.properties.loc_id}
+ >
+
+
+
+ ) : null;
+ })}
+
+
+ ) : (
+
+ {filteredMystations.map((item, index) => {
+ let name = item.properties.locname;
+ index = item.index;
+ return item.properties.groupname === group ? (
+
+ setSelectedBoreholeIndex(
+ index + props.boreholeFeatures.length
+ )
+ }
+ active={
+ selectedBoreholeIndex ===
+ index + props.boreholeFeatures.length
+ }
+ key={item.properties.loc_id}
+ >
+
+
+
+ ) : null;
+ })}
+
+ )}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+ {loadingData && (
+
+
+
+ )}
+
+ {dashboardItems.map((dashboardItem, index) => {
+ let id = dashboardItem.item.id;
+ if (dashboardItem.type === DASHBOARD_ITEM_PLOT) {
+ return (
+ handleRemovePlot(id)}
+ onDrop={(item) => handleDrop(id, item)}
+ onChange={handleTitleChange(dashboardItem.item.id)}
+ />
+ );
+ } else if (dashboardItem.type === DASHBOARD_ITEM_PROFILE) {
+ return (
+
+ handleRemoveProfile(dashboardItem.item.key)
+ }
+ onChange={handleTitleChange(index)}
+ />
+ );
+ }
+ })}
+
+
+
+
+ ) : props.dashboardContent === "projects" ? (
+
+
+
+
+
+
+
+ ) : null}
+
+ );
+}
+
+const Root = styled.div`
+ height: calc(100% - ${(props) => props.theme.layout.gutter * 2}px);
+ width: 100%;
+ background-color: ${(props) =>
+ hexToRgbA(props.theme.colors.primary[1], 0.92)};
+ // overflow-y: auto;
+`;
+
+const DashboardList = styled.div`
+ background-color: ${(props) => props.theme.colors.primary[1]};
+ padding: ${(props) => props.theme.layout.gutter / 2}px
+ ${(props) => props.theme.layout.gutter}px;
+ width: 100%;
+ height: 100%;
+ // overflow-y: auto;
+`;
+
+const SearchboxContainer = styled.div`
+ width: 90%;
+ padding: ${(props) => props.theme.layout.gutter / 2}px 0px;
+`;
+
+const BoreholesList = styled.div`
+ width: 100%;
+ height: 100%;
+ // overflow-y: auto;
+`;
+
+const DashboardListTitle = styled.div`
+ margin-top: ${(props) => props.theme.layout.gutter / 4}px;
+ width: 100%;
+ color: ${(props) => props.theme.colors.headings};
+`;
+
+const SelectedList = styled.div`
+ display: flex;
+ flex-direction: row;
+`;
+
+const RemoveIconContainer = styled.div`
+ width: 18px;
+ height: 18px;
+ margin-top: 3px;
+ margin-right: 5px;
+ border: 1px solid ${(props) => props.theme.colors.gray[2]};
+ border-radius: 50%;
+ display: none;
+ cursor: pointer;
+ float: right;
+`;
+
+const DashboardListItem = styled.div`
+ margin-top: ${(props) => props.theme.layout.gutter / 8}px;
+ margin-bottom: ${(props) => props.theme.layout.gutter / 8}px;
+ height: ${(props) => props.theme.layout.gutter}px;
+ padding: ${(props) => props.theme.layout.gutter / 8}px 0px;
+ width: 100%;
+ color: ${(props) =>
+ props.active ? props.theme.colors.headings : props.theme.colors.gray[4]};
+ background-color: ${(props) =>
+ props.active ? props.theme.colors.primary[2] : "transparent"};
+ cursor: pointer;
+ display: inline-block;
+
+ &:hover {
+ background-color: ${(props) => props.theme.colors.primary[2]};
+ color: ${(props) => props.theme.colors.headings};
+
+ ${RemoveIconContainer} {
+ display: block;
+ }
+ }
+`;
+
+const FavoritterList = styled.div`
+ margin-top: ${(props) => props.theme.layout.gutter}px;
+ height: auto;
+`;
+
+const FavoritterListTitle = styled.div`
+ color: ${(props) => props.theme.colors.primary[5]};
+ margin-top: ${(props) => props.theme.layout.gutter / 2}px;
+`;
+
+const ChartsContainer = styled.ul`
+ padding-top: 0px;
+ padding-bottom: 0px;
+ width: 100%;
+ padding-left: ${(props) => props.theme.layout.gutter / 4}px;
+ padding-right: ${(props) => props.theme.layout.gutter / 4}px;
+ height: 100%;
+ position: relative;
+`;
+
+const ProjectsContainer = styled.div`
+ padding: ${(props) => props.theme.layout.gutter / 2}px;
+ width: 80%;
+`;
+
+const mapStateToProps = (state) => ({
+ boreholeFeatures: state.global.boreholeFeatures,
+ dashboardContent: state.global.dashboardContent,
+});
+
+const mapDispatchToProps = (dispatch) => ({});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DashboardContent);
diff --git a/browser/components/dashboardshell/DashboardHeader.js b/browser/components/dashboardshell/DashboardHeader.js
new file mode 100644
index 0000000..16553b5
--- /dev/null
+++ b/browser/components/dashboardshell/DashboardHeader.js
@@ -0,0 +1,332 @@
+import { useContext, useState, useEffect } from "react";
+import { connect } from "react-redux";
+import styled, { css } from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import Icon from "../shared/icons/Icon";
+import Title from "../shared/title/Title";
+import { Align } from "../shared/constants/align";
+import { Variants } from "../shared/constants/variants";
+import { Size } from "../shared/constants/size";
+import {
+ showSubscription,
+ showSubscriptionIfFree,
+} from "./../../helpers/show_subscriptionDialogue";
+import Button from "../shared/button/Button";
+import ButtonGroup from "../shared/button/ButtonGroup";
+import { DarkTheme } from "../../themes/DarkTheme";
+import ProjectContext from "../../contexts/project/ProjectContext";
+import { getNewPlotId } from "../../helpers/common";
+import base64url from "base64url";
+import reduxStore from "../../redux/store";
+import { clearBoreholeFeatures } from "../../redux/actions";
+
+const session = require("./../../../../session/browser/index");
+
+function DashboardHeader(props) {
+ const [showSaveButtons, setShowSaveButtons] = useState(true);
+ const [dashboardTitle, setDashboardTitle] = useState(null);
+ const [dashboardId, setDashboardId] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const projectContext = useContext(ProjectContext);
+
+ useEffect(() => {
+ let canShowSaveButtons = true;
+ if (
+ props.dashboardMode === "minimized" ||
+ props.dashboardContent === "projects"
+ ) {
+ canShowSaveButtons = false;
+ }
+ setShowSaveButtons(canShowSaveButtons);
+ }, [props.dashboardMode, props.dashboardContent]);
+
+ useEffect(() => {
+ props.backboneEvents.get().on("statesnapshot:apply", (snapshot) => {
+ setDashboardTitle(snapshot.title);
+ setDashboardId(snapshot.id);
+ });
+ }, []);
+
+ const addNewPlot = () => {
+ let allPlots = props.getAllPlots();
+
+ if (showSubscriptionIfFree(allPlots.length > 0)) return;
+
+ if (props.dashboardMode === "minimized") {
+ props.setDashboardMode("half");
+ }
+
+ document.getElementById("chartsContainer").scrollTop = 0;
+
+ let activePlots = projectContext.activePlots;
+ let newPlotId = getNewPlotId(allPlots);
+ let plotData = {
+ id: `plot_${newPlotId}`,
+ title: `Graf ${newPlotId}`,
+ measurements: [],
+ measurementsCachedData: {},
+ relations: {},
+ };
+ activePlots.unshift(plotData);
+ allPlots.unshift(plotData);
+ activePlots = activePlots.map((plot) => plot.id);
+ props.setPlots(allPlots, activePlots);
+ // props.onActivePlotsChange(activePlots, allPlots, projectContext);
+ };
+
+ const save = () => {
+ if (showSubscriptionIfFree()) return;
+
+ let title;
+ if (!dashboardTitle) {
+ title = prompt("Navn på dashboard");
+ if (title) {
+ createSnapshot(title);
+ }
+ } else {
+ updateSnapShot();
+ }
+ };
+
+ const saveAs = () => {
+ if (showSubscriptionIfFree()) return;
+ let title = prompt("Navn på dashboard");
+ if (title) {
+ createSnapshot(title);
+ }
+ };
+
+ const clearDashboard = () => {
+ if (confirm("Er du sikker på, at du vil fjerne alt fra Dashboard?")) {
+ reduxStore.dispatch(clearBoreholeFeatures());
+ props.setItems([]);
+ props.backboneEvents.get().trigger("watsonc:clearChemicalList");
+ }
+ };
+
+ const createSnapshot = (title) => {
+ setSaving(true);
+ props.state.getState().then((state) => {
+ state.map = props.anchor.getCurrentMapParameters();
+ state.meta = getSnapshotMeta();
+ let data = {
+ title: title,
+ anonymous: false,
+ snapshot: state,
+ database: vidiConfig.appDatabase,
+ schema: vidiConfig.appSchema,
+ host: props.urlparser.hostname,
+ };
+ $.ajax({
+ url: `/api/state-snapshots` + "/" + vidiConfig.appDatabase,
+ method: "POST",
+ contentType: "text/plain; charset=utf-8",
+ dataType: "text",
+ data: base64url(JSON.stringify(data)),
+ })
+ .then((response) => {
+ props.backboneEvents.get().trigger("statesnapshot:refresh");
+ try {
+ const id = JSON.parse(response).id;
+ setDashboardId(id);
+ setDashboardTitle(title);
+ } catch (e) {
+ console.error(e.message);
+ }
+ setSaving(false);
+ })
+ .catch((error) => {
+ console.error(error);
+ setSaving(false);
+ });
+ });
+ };
+
+ const updateSnapShot = () => {
+ setSaving(true);
+ props.state.getState().then((state) => {
+ state.map = props.anchor.getCurrentMapParameters();
+ state.meta = getSnapshotMeta();
+ let data = {
+ title: dashboardTitle,
+ snapshot: state,
+ };
+ $.ajax({
+ url:
+ `/api/state-snapshots` +
+ "/" +
+ vidiConfig.appDatabase +
+ "/" +
+ dashboardId,
+ method: "PUT",
+ contentType: "text/plain; charset=utf-8",
+ dataType: "text",
+ data: base64url(JSON.stringify(data)),
+ })
+ .then((response) => {
+ props.backboneEvents.get().trigger("statesnapshot:refresh");
+ setSaving(false);
+ })
+ .catch((error) => {
+ console.error(error);
+ setSaving(false);
+ });
+ });
+ };
+
+ const getSnapshotMeta = () => {
+ let result = {};
+ let queryParameters = props.urlparser.urlVars;
+ if (`config` in queryParameters && queryParameters.config) {
+ result.config = queryParameters.config;
+ }
+
+ if (`tmpl` in queryParameters && queryParameters.tmpl) {
+ result.tmpl = queryParameters.tmpl;
+ }
+ return result;
+ };
+
+ return (
+
+
+
+
+
+
+
+ save()}
+ variant={
+ saving || !dashboardId
+ ? Variants.PrimaryDisabled
+ : Variants.Primary
+ }
+ disabled={saving || !dashboardId}
+ />
+ saveAs()}
+ variant={saving ? Variants.PrimaryDisabled : Variants.Primary}
+ disabled={saving}
+ />
+ clearDashboard()}
+ variant={saving ? Variants.PrimaryDisabled : Variants.Primary}
+ />
+
+
+ {/* */}
+
+
+ addNewPlot()}
+ variant={saving ? Variants.PrimaryDisabled : Variants.Primary}
+ />
+
+
+ props.setDashboardMode("minimized")}
+ active={props.dashboardMode === "minimized"}
+ >
+
+
+ props.setDashboardMode("half")}
+ active={props.dashboardMode === "half"}
+ >
+
+
+ props.setDashboardMode("full")}
+ active={props.dashboardMode === "full"}
+ >
+
+
+
+
+
+
+ );
+}
+
+const Root = styled.div`
+ height: ${(props) => props.theme.layout.gutter * 2}px;
+ background: ${(props) => props.theme.colors.primary[2]};
+ padding: ${(props) => props.theme.layout.gutter / 2}px;
+ border-radius: ${(props) => props.theme.layout.gutter / 2}px
+ ${(props) => props.theme.layout.gutter / 2}px 0 0;
+`;
+
+const IconsLayout = styled.div`
+ height: 32px;
+ border: 1px solid ${(props) => props.theme.colors.primary[3]};
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ padding: 2px;
+`;
+
+const IconContainer = styled.div`
+ display: inline-block;
+ height: 100%;
+ width: ${(props) => props.theme.layout.gutter}px;
+ padding-left: ${(props) => props.theme.layout.gutter / 4}px;
+ padding-top: ${(props) => props.theme.layout.gutter / 8}px;
+ cursor: pointer;
+ color: ${(props) => props.theme.colors.gray[3]};
+
+ &:hover {
+ background-color: ${(props) => props.theme.colors.primary[3]};
+ color: ${(props) => props.theme.colors.headings};
+ }
+
+ ${({ active, theme }) => {
+ const styles = {
+ true: css`
+ color: ${theme.colors.interaction[4]};
+ `,
+ };
+ return styles[active];
+ }}
+`;
+
+const mapStateToProps = (state) => ({
+ dashboardMode: state.global.dashboardMode,
+ dashboardContent: state.global.dashboardContent,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ setDashboardMode: (key) => dispatch(setDashboardMode(key)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DashboardHeader);
diff --git a/browser/components/dashboardshell/DashboardPlotCard.js b/browser/components/dashboardshell/DashboardPlotCard.js
new file mode 100644
index 0000000..d92d3d5
--- /dev/null
+++ b/browser/components/dashboardshell/DashboardPlotCard.js
@@ -0,0 +1,164 @@
+import { useEffect, useState } from "react";
+import styled from "styled-components";
+import { useDrop } from "react-dnd";
+import Grid from "@material-ui/core/Grid";
+import Icon from "../shared/icons/Icon";
+import Title from "../shared/title/Title";
+import Button from "../shared/button/Button";
+import { Variants } from "../shared/constants/variants";
+import { LIMIT_CHAR } from "../../constants";
+import { Size } from "../shared/constants/size";
+import CheckBox from "../shared/inputs/CheckBox";
+import PlotComponent from "./PlotComponent";
+import CardListItem from "./CardListItem";
+
+const utils = require("../../utils");
+
+function DashboardPlotCard(props) {
+ const [plotData, setPlotData] = useState([]);
+ const [yAxis2LayoutSettings, setYAxis2LayoutSettings] = useState(null);
+ const [aggregate, setAggregate] = useState(props.plot.aggregate || []);
+ const [collectedProps, drop] = useDrop(() => ({
+ accept: "MEASUREMENT",
+ drop: props.onDrop,
+ collect: (monitor) => ({
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ }),
+ }));
+ const handleSetAggregate = (idx, window, func) => {
+ let ind = aggregate.findIndex((elem) => elem.idx === idx);
+ if (ind === -1) {
+ setAggregate((prev) => {
+ prev.push({ idx: idx, window: window, func: func });
+ return [...prev];
+ });
+ } else {
+ setAggregate((prev) => {
+ prev[ind] = { idx: idx, window: window, func: func };
+ return [...prev];
+ });
+ }
+
+ const newDashboardItems = props.getDashboardItems().map((elem) => {
+ if (elem.item.id === props.plot.id) {
+ elem.item.aggregate = aggregate;
+ }
+ return elem;
+ });
+
+ props.setItems(newDashboardItems.map((elem) => elem.item));
+ };
+
+ const handleDeleteAggregate = (idx) => {
+ let ind = aggregate.findIndex((elem) => elem.idx === idx);
+ if (ind === -1) {
+ return;
+ } else {
+ setAggregate((prev) => {
+ prev.splice(ind, 1);
+ return [...prev];
+ });
+ }
+ };
+
+ useEffect(() => {
+ let data = [];
+ if (
+ props.plot &&
+ props.plot.measurements &&
+ props.plot.measurements.length > 0
+ ) {
+ props.plot.measurements.map((measurementLocationRaw, index) => {
+ if (
+ props.plot.measurementsCachedData &&
+ measurementLocationRaw in props.plot.measurementsCachedData &&
+ props.plot.measurementsCachedData[measurementLocationRaw] &&
+ props.plot.measurementsCachedData[measurementLocationRaw].data
+ ) {
+ let measurementLocation = measurementLocationRaw.split(":");
+ let feature =
+ props.plot.measurementsCachedData[measurementLocationRaw].data;
+ let key = measurementLocation[1];
+ let measurementData = JSON.parse(feature.properties[key]);
+ let intakeIndex = measurementData.ts_id
+ .map((elem) => elem.toString())
+ .indexOf(measurementLocation[2]);
+ // Merge trace and data
+ const plotInfoMergedWithTrace = {
+ ...measurementData.data[intakeIndex],
+ ...measurementData.trace[intakeIndex],
+ };
+ data.push(plotInfoMergedWithTrace);
+ } else {
+ console.info(
+ `Plot does not contain measurement ${measurementLocationRaw}`
+ );
+ }
+ });
+ }
+ setPlotData(data);
+ setYAxis2LayoutSettings(yAxis2LayoutSettings);
+ }, [props.plot]);
+
+ return (
+
+
+
+
+ {props.plot?.measurements?.map((measurement, index) => {
+ return (
+ elem.idx === measurement)}
+ setAggregate={handleSetAggregate}
+ deleteAggregate={handleDeleteAggregate}
+ />
+ );
+ })}
+
+
+
+
+ console.log("Testing")}
+ plotMeta={props.plot}
+ plotData={plotData}
+ aggregate={aggregate}
+ yAxis2LayoutSettings={yAxis2LayoutSettings}
+ />
+
+
+
+
+ );
+}
+
+const DashboardPlotContent = styled.div`
+ padding: ${(props) => props.theme.layout.gutter / 2}px;
+ height: ${(props) => props.height};
+`;
+
+const CardList = styled.div`
+ height: 100%;
+ width: 95%;
+ vertical-align: middle;
+`;
+
+const PlotContainer = styled.div`
+ width: 100%;
+ margin-left: 10px;
+`;
+
+export default DashboardPlotCard;
diff --git a/browser/components/dashboardshell/DashboardProfileCard.js b/browser/components/dashboardshell/DashboardProfileCard.js
new file mode 100644
index 0000000..a3b928f
--- /dev/null
+++ b/browser/components/dashboardshell/DashboardProfileCard.js
@@ -0,0 +1,43 @@
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import ProfileComponent from "./ProfileComponent";
+
+function DashboardProfileCard(props) {
+ return (
+
+
+
+
+ console.log("Testing")}
+ plotMeta={props.plot}
+ onChangeDatatype={(id) => {
+ console.log("Test");
+ }}
+ />
+
+
+
+
+ );
+}
+
+const DashboardPlotContent = styled.div`
+ padding: ${(props) => props.theme.layout.gutter / 2}px;
+ height: ${(props) => props.height};
+`;
+
+const CardList = styled.div`
+ height: 100%;
+ width: 95%;
+ vertical-align: middle;
+`;
+
+const PlotContainer = styled.div`
+ width: 100%;
+ margin-left: 10px;
+`;
+
+export default DashboardProfileCard;
diff --git a/browser/components/dashboardshell/DashboardShell.js b/browser/components/dashboardshell/DashboardShell.js
new file mode 100644
index 0000000..1e36c98
--- /dev/null
+++ b/browser/components/dashboardshell/DashboardShell.js
@@ -0,0 +1,51 @@
+import { useState, useContext } from 'react';
+import {connect} from 'react-redux';
+import styled, { css } from 'styled-components';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import DashboardHeader from './DashboardHeader';
+import DashboardContent from './DashboardContent';
+import ProjectContext from '../../contexts/project/ProjectContext';
+
+function DashboardShell(props) {
+ return (
+
+ {context => {
+ return
+
+
+
+ }}
+
+ )
+}
+
+const Root = styled.div`
+ width: 100%;
+ height: 75vh;
+ transition: height 0.5s linear;
+ ${({ mode, theme }) => {
+ const styles = {
+ full: css `
+ height: 75vh;
+ `,
+ half: css `
+ height: 50vh;
+ `,
+ minimized: css `
+ height: ${theme.layout.gutter*2}px;
+ `
+ }
+ return styles[mode];
+ }}
+`
+
+const mapStateToProps = state => ({
+ dashboardMode: state.global.dashboardMode
+})
+
+const mapDispatchToProps = dispatch => ({
+ setDashboardMode: (key) => dispatch(setDashboardMode(key)),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(DashboardShell);
diff --git a/browser/components/dashboardshell/GraphCard.js b/browser/components/dashboardshell/GraphCard.js
new file mode 100644
index 0000000..e4c282a
--- /dev/null
+++ b/browser/components/dashboardshell/GraphCard.js
@@ -0,0 +1,335 @@
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import Icon from "../shared/icons/Icon";
+import Title from "../shared/title/Title";
+import DashboardPlotCard from "./DashboardPlotCard";
+import DashboardProfileCard from "./DashboardProfileCard";
+import { sortableElement } from "react-sortable-hoc";
+import SortHandleComponent from "./SortHandleComponent";
+import PlotApi from "../../api/plots/PlotApi";
+import { useState, useEffect } from "react";
+import Dialog from "@material-ui/core/Dialog";
+import DialogActions from "@material-ui/core/DialogActions";
+import Button from "@material-ui/core/Button";
+import moment from "moment";
+
+const utmZone = require("./../../../../../browser/modules/utmZone");
+let displayedItems = new L.FeatureGroup();
+
+function GraphCard(props) {
+ const [fullscreen, setFullscreen] = useState(false);
+ const [graphName, setGraphName] = useState(
+ props.cardType === "profile" ? props.plot.profile.title : props.plot.title
+ );
+ const [profileShown, setProfileShown] = useState(false);
+
+ useEffect(() => {
+ if (props.cardType === "profile") {
+ props.cloud.get().map.addLayer(displayedItems);
+ }
+
+ return () => {
+ displayedItems.eachLayer((layer) => {
+ displayedItems.removeLayer(layer);
+ });
+ };
+ }, []);
+
+ const download = () => {
+ const regexExp =
+ /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i;
+ if (!props.plot) {
+ return;
+ }
+ let data = [];
+ props.plot.measurements.map((measurementLocationRaw, index) => {
+ if (
+ measurementLocationRaw in props.plot.measurementsCachedData &&
+ props.plot.measurementsCachedData[measurementLocationRaw]
+ ) {
+ let measurementLocation = measurementLocationRaw.split(":");
+ if (measurementLocation.length === 3) {
+ let key = measurementLocation[1];
+ let ts_id = regexExp.test(measurementLocation[2])
+ ? measurementLocation[2]
+ : parseInt(measurementLocation[2]);
+ let feature =
+ props.plot.measurementsCachedData[measurementLocationRaw].data;
+ let measurementData = JSON.parse(feature.properties[key]);
+ let index = measurementData.ts_id.indexOf(ts_id);
+
+ var x = [];
+ var y = [];
+ measurementData.data[index].y.map((elem, idx) => {
+ if (elem != null) {
+ x.push(
+ moment(measurementData.data[index].x[idx]).format(
+ "YYYY-MM-DD HH:mm:ss"
+ )
+ );
+ y.push(elem.toString().replace(".", ","));
+ }
+ });
+
+ // let formatedDates = measurementData.data[index].x.map((elem) =>
+ // moment(elem).format("YYYY-MM-DD HH:mm:ss")
+ // );
+ data.push({
+ title: measurementData.title,
+ unit: measurementData.unit,
+ name: measurementData.data[index].name,
+ x: x,
+ y: y,
+ });
+ }
+ } else {
+ console.error(
+ `Plot does not contain measurement ${measurementLocationRaw}`
+ );
+ }
+ });
+ const plotApi = new PlotApi();
+
+ plotApi
+ .downloadPlot({
+ title: props.plot.title,
+ data,
+ })
+ .then((response) => {
+ const filename =
+ props.plot.title.replace(/\s+/g, "_").toLowerCase() + ".xlsx";
+ const url = window.URL.createObjectURL(
+ new Blob([response], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ })
+ );
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", filename);
+ document.body.appendChild(link);
+ link.click();
+ window.URL.revokeObjectURL(url);
+ })
+ .catch((error) => {
+ console.error(error);
+ alert(`Error occured while generating plot XSLS file`);
+ });
+ };
+
+ const toggle_profile = () => {
+ if (!profileShown) {
+ let data = props.plot;
+ let profile = data.profile.profile;
+
+ // Get utm zone
+ var zone = utmZone.getZone(
+ profile.geometry.coordinates[0][1],
+ profile.geometry.coordinates[0][0]
+ );
+ var crss = {
+ proj:
+ "+proj=utm +zone=" +
+ zone +
+ " +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
+ unproj: "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
+ };
+
+ let reader = new jsts.io.GeoJSONReader();
+ let writer = new jsts.io.GeoJSONWriter();
+ let geom = reader.read(
+ reproject.reproject(profile, "unproj", "proj", crss)
+ );
+ let buffer4326 = reproject.reproject(
+ writer.write(geom.geometry.buffer(data.profile.buffer)),
+ "proj",
+ "unproj",
+ crss
+ );
+
+ L.geoJson(buffer4326, {
+ color: "#ff7800",
+ weight: 1,
+ opacity: 1,
+ fillOpacity: 0.1,
+ dashArray: "5,3",
+ }).addTo(displayedItems);
+
+ var profileLayer = new L.geoJSON(profile);
+
+ profileLayer.bindTooltip(data.profile.title, {
+ className: "watsonc-profile-tooltip",
+ permanent: true,
+ offset: [0, 0],
+ });
+
+ profileLayer.addTo(displayedItems);
+ setProfileShown(true);
+ } else {
+ displayedItems.eachLayer((layer) => {
+ displayedItems.removeLayer(layer);
+ });
+ setProfileShown(false);
+ }
+ };
+
+ const handleFullScreen = () => {
+ setFullscreen(true);
+ };
+
+ const handleClose = () => {
+ setFullscreen(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {/* */}
+ setGraphName(e.target.value)}
+ onBlur={(e) => props.onChange(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.target.blur();
+ }
+ }}
+ disabled={props.cardType === "plot" ? false : true}
+ />
+
+
+
+ {/* {}}*/}
+ {/* text={__("Gem")}*/}
+ {/*/>*/}
+
+
+ {props.cardType === "plot" ? (
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {props.cardType === "plot" ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+const Root = styled.div`
+ background: ${(props) => props.theme.colors.gray[5]};
+ margin-top: ${(props) => props.theme.layout.gutter / 4}px;
+ border-radius: ${(props) => props.theme.layout.borderRadius.medium}px;
+ height: ${(props) => props.theme.layout.gutter * 12.5}px;
+ width: 100%;
+`;
+
+const DashboardPlotHeader = styled.div`
+ background: ${(props) => props.theme.colors.headings};
+ height: ${(props) => props.theme.layout.gutter * 1.5}px;
+ width: 100%;
+ color: ${(props) => props.theme.colors.primary[2]};
+ padding: ${(props) => (props.theme.layout.gutter * 3) / 8}px
+ ${(props) => props.theme.layout.gutter / 2}px;
+ border-radius: ${(props) => props.theme.layout.borderRadius.medium}px;
+`;
+
+const HeaderSvg = styled.div`
+ display: inline-block;
+ padding: ${(props) => props.theme.layout.gutter / 8}px;
+ vertical-align: middle;
+`;
+
+const HeaderActionItem = styled.div`
+ margin-right: ${(props) => props.theme.layout.gutter / 2}px;
+ vertical-align: middle;
+ cursor: pointer;
+`;
+
+const IconContainer = styled.div`
+ height: ${(props) => (props.theme.layout.gutter * 3) / 4}px;
+ width: ${(props) => (props.theme.layout.gutter * 3) / 4}px;
+ background: ${(props) => props.theme.colors.gray[4]};
+ display: inline-block;
+ border-radius: 50%;
+ padding: ${(props) => props.theme.layout.gutter / 8}px;
+ vertical-align: middle;
+`;
+
+const CloseButton = styled.div`
+ display: inline-block;
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ border: 1px solid ${(props) => props.theme.colors.gray[4]};
+ height: ${(props) => (props.theme.layout.gutter * 3) / 4}px;
+ cursor: pointer;
+`;
+
+const Input = styled.input`
+ display: inline-block;
+ font-weight: normal;
+ margin: 0;
+ line-height: 1.3;
+ box-shadow: none;
+ border: 0;
+ text-align: ${(props) => props.align};
+ margin-left: ${(props) => props.marginLeft || 0}px;
+ margin-top: ${(props) => props.theme.layout.gutter / 2}px;
+ font: ${(props) => props.theme.fonts.subbody};
+ &:focus {
+ border: 1px solid;
+ }
+`;
+
+export default sortableElement(GraphCard);
diff --git a/browser/components/dashboardshell/PlotComponent.js b/browser/components/dashboardshell/PlotComponent.js
new file mode 100644
index 0000000..35991b8
--- /dev/null
+++ b/browser/components/dashboardshell/PlotComponent.js
@@ -0,0 +1,317 @@
+import Plot from "react-plotly.js";
+// import Plotly from "plotly.js";
+import { useState, useEffect } from "react";
+import { aggregate } from "../../helpers/aggregate.js";
+import moment from "moment";
+import _ from "lodash";
+// import { Colorscales } from "../shared/constants/colorscales";
+const config = require("../../../../../config/config.js");
+
+function next_color() {
+ let colorscale =
+ JSON.parse(JSON.stringify(config?.extensionConfig?.watsonc?.colorScales)) ||
+ {};
+ return (custom_colorscale) => {
+ return colorscale[custom_colorscale].shift();
+ };
+}
+
+var autoscale = {
+ width: 500,
+ // height: "1em",
+ // viewBox: "0 0 1000 1000",
+ path: "M447.1 319.1v135.1c0 13.26-10.75 23.1-23.1 23.1h-135.1c-12.94 0-24.61-7.781-29.56-19.75c-4.906-11.1-2.203-25.72 6.937-34.87l30.06-30.06L224 323.9l-71.43 71.44l30.06 30.06c9.156 9.156 11.91 22.91 6.937 34.87C184.6 472.2 172.9 479.1 160 479.1H24c-13.25 0-23.1-10.74-23.1-23.1v-135.1c0-12.94 7.781-24.61 19.75-29.56C23.72 288.8 27.88 288 32 288c8.312 0 16.5 3.242 22.63 9.367l30.06 30.06l71.44-71.44L84.69 184.6L54.63 214.6c-9.156 9.156-22.91 11.91-34.87 6.937C7.798 216.6 .0013 204.9 .0013 191.1v-135.1c0-13.26 10.75-23.1 23.1-23.1h135.1c12.94 0 24.61 7.781 29.56 19.75C191.2 55.72 191.1 59.87 191.1 63.1c0 8.312-3.237 16.5-9.362 22.63L152.6 116.7l71.44 71.44l71.43-71.44l-30.06-30.06c-9.156-9.156-11.91-22.91-6.937-34.87c4.937-11.95 16.62-19.75 29.56-19.75h135.1c13.26 0 23.1 10.75 23.1 23.1v135.1c0 12.94-7.781 24.61-19.75 29.56c-11.1 4.906-25.72 2.203-34.87-6.937l-30.06-30.06l-71.43 71.43l71.44 71.44l30.06-30.06c9.156-9.156 22.91-11.91 34.87-6.937C440.2 295.4 447.1 307.1 447.1 319.1z",
+ // path: "M224 376V512H24C10.7 512 0 501.3 0 488v-464c0-13.3 10.7-24 24-24h336c13.3 0 24 10.7 24 24V352H248c-13.2 0-24 10.8-24 24zm76.45-211.36-96.42-95.7c-6.65-6.61-17.39-6.61-24.04 0l-96.42 95.7C73.42 174.71 80.54 192 94.82 192H160v80c0 8.84 7.16 16 16 16h32c8.84 0 16-7.16 16-16v-80h65.18c14.28 0 21.4-17.29 11.27-27.36zM377 407 279.1 505c-4.5 4.5-10.6 7-17 7H256v-128h128v6.1c0 6.3-2.5 12.4-7 16.9z",
+ ascent: 500,
+ descent: -50,
+ // transform: "matrix(1 0 0 -1 0 850)",
+};
+
+function PlotComponent(props) {
+ const [layoutState, setLayoutState] = useState(
+ JSON.parse(JSON.stringify(config?.extensionConfig?.watsonc?.plotLayout)) ||
+ {}
+ );
+ const [plotDataState, setPlotDataState] = useState(props.plotData);
+ const [configState, setConfigState] = useState({});
+ const [minmaxRange, setminmaxRange] = useState([0, 1]);
+ // let layout =
+
+ const changeLayout = (minmax) => {
+ // console.log(gd.layout);
+
+ return () =>
+ setLayoutState((prev) => {
+ prev.xaxis = {
+ ...prev.xaxis,
+ autorange: false,
+ range: [minmax[0], minmax[1]],
+ };
+ prev.yaxis = {
+ ...prev.yaxis,
+ autorange: true,
+ };
+ prev.yaxis2 = {
+ ...prev.yaxis2,
+ autorange: true,
+ };
+ prev.yaxis3 = {
+ ...prev.yaxis3,
+ autorange: true,
+ };
+ return { ...prev };
+ });
+ };
+
+ let yaxisTitles =
+ JSON.parse(JSON.stringify(config?.extensionConfig?.watsonc?.yaxisTitles)) ||
+ {};
+
+ useEffect(() => {
+ let layout = JSON.parse(
+ JSON.stringify(config?.extensionConfig?.watsonc?.plotLayout)
+ );
+
+ let num_types = [
+ ...new Set(
+ props.plotData
+ .sort((a, b) => {
+ if (Object.hasOwn(a, "yaxis") && Object.hasOwn(b, "yaxis")) {
+ return 0;
+ }
+ if (Object.hasOwn(a, "yaxis")) {
+ return 1;
+ }
+ if (Object.hasOwn(b, "yaxis")) {
+ return -1;
+ }
+ // a must be equal to b
+ return 0;
+ })
+ .map((elem) => elem.tstype_id)
+ ),
+ ];
+
+ const give_color = next_color();
+ let plotData = props.plotData.map((elem) => {
+ if (typeof elem.custom_colorscale != "undefined") {
+ var color = give_color(elem.custom_colorscale);
+ return {
+ ...elem,
+ line: {
+ ...elem.line,
+ color: typeof color == "undefined" ? null : color,
+ },
+ };
+ } else {
+ return elem;
+ }
+ });
+
+ if (!num_types.includes(undefined)) {
+ if (num_types.length == 1) {
+ layout.yaxis = {
+ ...layout.yaxis,
+ title: {
+ ...layout.yaxis.title,
+ text: yaxisTitles[num_types[0]],
+ },
+ };
+
+ plotData = plotData.map((elem) => {
+ return {
+ yaxis: "y1",
+ ...elem,
+ };
+ });
+ } else if (num_types.length == 2) {
+ plotData = plotData.map((elem) => {
+ return {
+ yaxis: elem.tstype_id === num_types[0] ? "y1" : "y3",
+ ...elem,
+ };
+ });
+
+ layout.yaxis = {
+ ...layout.yaxis,
+ title: {
+ ...layout.yaxis.title,
+ text: yaxisTitles[num_types[0]],
+ },
+ };
+ layout.yaxis3 = {
+ ...layout.yaxis3,
+ title: {
+ ...layout.yaxis3.title,
+ text: yaxisTitles[num_types[1]],
+ },
+ };
+ } else if (num_types.length > 2) {
+ plotData = plotData.map((elem) => {
+ return {
+ yaxis:
+ elem.tstype_id === num_types[0]
+ ? "y1"
+ : elem.tstype_id === num_types[1]
+ ? "y2"
+ : "y3",
+ ...elem,
+ };
+ });
+ layout.xaxis = {
+ ...layout.xaxis,
+ domain: [0, 0.9],
+ };
+ layout.yaxis = {
+ ...layout.yaxis,
+ title: {
+ ...layout.yaxis.title,
+ text: yaxisTitles[num_types[0]],
+ },
+ };
+ layout.yaxis2 = {
+ ...layout.yaxis2,
+ title: {
+ ...layout.yaxis2.title,
+ text: yaxisTitles[num_types[1]],
+ },
+ position: 0.9,
+ anchor: "free",
+ };
+ layout.yaxis3 = {
+ ...layout.yaxis3,
+ title: {
+ ...layout.yaxis3.title,
+ text: num_types.length == 3 ? yaxisTitles[num_types[2]] : "Øvrige",
+ },
+ position: 0.97,
+ anchor: "free",
+ };
+ }
+ }
+
+ if (plotData.length > 0) {
+ let xmin = moment.min(
+ plotData
+ .filter(
+ (elem) =>
+ elem.xaxis != "x2" &&
+ typeof elem.x != "undefined" &&
+ (elem.tstype_id != 4 || plotData.length == 1)
+ )
+ .map((elem) => moment(elem.x[0]))
+ );
+
+ let xmax = moment.max(
+ plotData
+ .filter(
+ (elem) =>
+ elem.xaxis != "x2" &&
+ typeof elem.x != "undefined" &&
+ (elem.tstype_id != 4 || plotData.length == 1)
+ )
+ .map((elem) => moment(elem.x.slice(-1)[0]))
+ );
+ var diff = xmax.diff(xmin);
+ xmin = xmin.subtract(diff * 0.02);
+
+ xmin = xmin.format("YYYY-MM-DD HH:mm:ss.SSS");
+ xmax = xmax.format("YYYY-MM-DD HH:mm:ss.SSS");
+ setminmaxRange([xmin, xmax]);
+ layout.xaxis = {
+ ...layout.xaxis,
+ range: [xmin, xmax],
+ };
+
+ var resetAxes = {
+ name: "myAutoscale",
+ title: "Autoscale",
+ icon: autoscale,
+ click: changeLayout([xmin, xmax]),
+ };
+
+ var conf = {
+ responsive: true,
+ modeBarButtons: [
+ ["toImage", "zoom2d", "pan2d", "zoomIn2d", "zoomOut2d"],
+ [resetAxes],
+ ["hoverClosestCartesian", "hoverCompareCartesian"],
+ ],
+ // modeBarButtonsToAdd: [resetAxes],
+ // modeBarButtonsToRemove: ["resetScale2d", "autoScale2d", "toggleSpikelines"],
+ doubleClick: false,
+ };
+ }
+ for (const agg of props.aggregate) {
+ let ind = props.plotMeta.measurements.findIndex(
+ (elem) => elem === agg.idx
+ );
+ if (ind != -1 && plotData[ind]) {
+ var grouped = aggregate(
+ plotData[ind].x,
+ plotData[ind].y,
+ agg.window,
+ agg.func
+ );
+ plotData[ind] = {
+ ...plotData[ind],
+ ...grouped,
+ };
+ // plotData[ind].x = grouped.x;
+ // plotData[ind].y = grouped.y;
+ // if (agg.func === 'sum') {
+ // plotData[ind].width = grouped.width;
+ // }
+ }
+ }
+ setConfigState(conf);
+ setLayoutState(layout);
+ setPlotDataState(plotData);
+ // setTriggerAggregate((prev) => !prev);
+ }, [props.plotData, props.yAxis2LayoutSettings, props.aggregate]);
+
+ // useEffect(() => {
+ // let plotData = plotDataState;
+ // for (const [key, value] of Object.entries(props.aggregate)) {
+ // var grouped = aggregate(
+ // props.plotData[key].x,
+ // props.plotData[key].y,
+ // value,
+ // _.meanBy
+ // );
+ // plotData[key].x = grouped.x;
+ // plotData[key].y = grouped.y;
+ // console.log(plotData);
+ // }
+ // setPlotDataState([...plotData]);
+ // }, [props.aggregate, triggerAggregate]);
+
+ return (
+ //
+
+
+ console.log("Legend double clicked", param)
+ }
+ useResizeHandler={true}
+ onDoubleClick={changeLayout(minmaxRange)}
+ onLegendClick={(param) => console.log("Legend clicked", param)}
+ style={{ width: "100%", height: `100%` }}
+ />
+
+ //
+ );
+}
+
+export default PlotComponent;
diff --git a/browser/components/dashboardshell/ProfileComponent.js b/browser/components/dashboardshell/ProfileComponent.js
new file mode 100644
index 0000000..f35d993
--- /dev/null
+++ b/browser/components/dashboardshell/ProfileComponent.js
@@ -0,0 +1,64 @@
+import Plot from "react-plotly.js";
+import { useState, useEffect } from "react";
+
+function ProfileComponent(props) {
+ const [data, setData] = useState(props.plotMeta.profile.data.data);
+ const [layout, setLayout] = useState(props.plotMeta.profile.data.layout);
+
+ const [plot, setPlot] = useState(
+ {__(`At least one y axis has to be provided`)}
+ );
+
+ useEffect(() => {
+ if (props.plotMeta) {
+ let dataCopy = JSON.parse(
+ JSON.stringify(props.plotMeta.profile.data.data)
+ .replace(/%28/g, "(")
+ .replace(/%29/g, ")")
+ );
+ dataCopy.map((item, index) => {
+ if (!dataCopy[index].mode) dataCopy[index].mode = "lines";
+ });
+ let layoutCopy = JSON.parse(
+ JSON.stringify(props.plotMeta.profile.data.layout)
+ );
+ layoutCopy.margin = {
+ l: 50,
+ r: 5,
+ b: 45,
+ t: 5,
+ pad: 1,
+ };
+ layoutCopy.autosize = true;
+ setData(dataCopy);
+ setLayout(layoutCopy);
+ }
+ }, [props.plotMeta]);
+
+ return (
+
+ );
+}
+
+export default ProfileComponent;
diff --git a/browser/components/dashboardshell/SortHandleComponent.js b/browser/components/dashboardshell/SortHandleComponent.js
new file mode 100644
index 0000000..cd0b11f
--- /dev/null
+++ b/browser/components/dashboardshell/SortHandleComponent.js
@@ -0,0 +1,31 @@
+import styled from 'styled-components';
+import Title from '../shared/title/Title';
+import {sortableHandle} from 'react-sortable-hoc';
+
+const SortHandleComponent = (props) => {
+ return (
+
+
+
+
+ )
+};
+
+const HeaderActionItem = styled.div`
+ margin-right: ${props => props.theme.layout.gutter/2}px;
+ vertical-align: middle;
+ cursor: pointer;
+`;
+
+
+const IconContainer = styled.div`
+ height: ${props => props.theme.layout.gutter*3/4}px;
+ width: ${props => props.theme.layout.gutter*3/4}px;
+ background: ${props => props.theme.colors.gray[4]};
+ display: inline-block;
+ border-radius: 50%;
+ padding: ${props => props.theme.layout.gutter/8}px;
+ vertical-align: middle;
+`;
+
+export default sortableHandle(SortHandleComponent);
diff --git a/browser/components/dataselector/ChemicalSelectorModal.js b/browser/components/dataselector/ChemicalSelectorModal.js
new file mode 100644
index 0000000..ec44d48
--- /dev/null
+++ b/browser/components/dataselector/ChemicalSelectorModal.js
@@ -0,0 +1,168 @@
+import { useState, useEffect } from "react";
+import { connect } from "react-redux";
+import styled from "styled-components";
+import { selectChemical } from "../../redux/actions";
+import Title from "../shared/title/Title";
+import CloseButton from "../shared/button/CloseButton";
+import PropTypes from "prop-types";
+import Grid from "@material-ui/core/Grid";
+import Card from "../shared/card/Card";
+import CheckBoxList from "../shared/list/CheckBoxList";
+import RadioButtonList from "../shared/list/RadioButtonList";
+import Button from "../shared/button/Button";
+import ButtonGroup from "../shared/button/ButtonGroup";
+import ProjectList from "./ProjectList";
+import PredefinedDatasourceViews from "./PredefinedDatasourceViews";
+import { Variants } from "../shared/constants/variants";
+import { Size } from "../shared/constants/size";
+import { Align } from "../shared/constants/align";
+import { hexToRgbA } from "../../helpers/colors";
+import { WATER_LEVEL_KEY } from "../../constants";
+import { DarkTheme } from "../../themes/DarkTheme";
+import MetaApi from "../../api/meta/MetaApi";
+import Searchbox from "../shared/inputs/Searchbox";
+
+ChemicalSelectorModal.propTypes = {
+ text: PropTypes.string,
+ state: PropTypes.object,
+ categories: PropTypes.object,
+ onApply: PropTypes.func,
+};
+
+function ChemicalSelectorModal(props) {
+ const [allParameters, setAllParameters] = useState([
+ // {
+ // label: __("Vandstand"),
+ // value: WATER_LEVEL_KEY,
+ // group: __("Vandstand"),
+ // },
+ ]);
+ const [parameters, setParameters] = useState([
+ // {
+ // label: __("Vandstand"),
+ // value: WATER_LEVEL_KEY,
+ // group: __("Vandstand"),
+ // },
+ ]);
+ const [parameterSearchTerm, setParameterSearchTerm] = useState("");
+ const [selectedParameter, setSelectedParameter] = useState({
+ label: __("Vandstand"),
+ value: WATER_LEVEL_KEY,
+ group: __("Vandstand"),
+ });
+
+ useEffect(() => {
+ const notSelected = {
+ label: __("Ikke valgt"),
+ value: false,
+ group: __("Ikke valgt"),
+ };
+ let chemicals = [notSelected];
+ for (let key in props.categories[LAYER_NAMES[0]]) {
+ if (key == "Vandstand") {
+ continue;
+ }
+ for (let key2 in props.categories[LAYER_NAMES[0]][key]) {
+ var label = props.categories[LAYER_NAMES[0]][key][key2];
+ chemicals.push({ label: label, value: key2, group: key });
+ }
+ }
+ setAllParameters([...chemicals]);
+ setSelectedParameter(notSelected);
+ }, [props.categories]);
+
+ const onChemicalSelect = (param) => {
+ setSelectedParameter(param);
+ props.selectChemical(param.value);
+ };
+
+ useEffect(() => {
+ const params = allParameters.filter((parameter) => {
+ return (
+ parameterSearchTerm.length == 0 ||
+ parameter.label
+ .toLowerCase()
+ .indexOf(parameterSearchTerm.toLowerCase()) > -1
+ );
+ });
+ setParameters(params);
+ }, [parameterSearchTerm, allParameters]);
+
+ const applyParameter = () => {
+ props.onCloseButtonClick ? props.onCloseButtonClick() : null;
+ };
+
+ return (
+
+
+
+
+
+
+
+ $("#watsonc-select-chemical-dialog").modal("hide")}
+ />
+
+
+
+
+
+ setParameterSearchTerm(value)}
+ />
+
+
+
+ {
+ props.onClickControl(selectedParameter.value);
+ }}
+ size={Size.Large}
+ />
+
+
+
+ );
+}
+
+const Root = styled.div`
+ background: ${({ theme }) => hexToRgbA(theme.colors.primary[1], 1)};
+ border-radius: ${({ theme }) => `${theme.layout.borderRadius.large}px`};
+ color: ${({ theme }) => `${theme.colors.headings}`};
+`;
+
+const ModalHeader = styled.div`
+ padding: ${({ theme }) =>
+ `${theme.layout.gutter}px ${theme.layout.gutter}px 0 ${theme.layout.gutter}px`};
+`;
+
+const ModalBody = styled.div`
+ padding: ${({ theme }) => `${theme.layout.gutter}px`};
+`;
+
+const GridContainer = styled.div`
+ padding-top: ${(props) => props.theme.layout.gutter / 2}px;
+`;
+
+const mapStateToProps = (state) => ({});
+
+const mapDispatchToProps = (dispatch) => ({
+ selectChemical: (key) => dispatch(selectChemical(key)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ChemicalSelectorModal);
diff --git a/browser/components/dataselector/DataSelectorDialogue.js b/browser/components/dataselector/DataSelectorDialogue.js
new file mode 100644
index 0000000..ae71c78
--- /dev/null
+++ b/browser/components/dataselector/DataSelectorDialogue.js
@@ -0,0 +1,294 @@
+import { useState, useEffect } from "react";
+import { connect } from "react-redux";
+import styled from "styled-components";
+import { selectChemical } from "../../redux/actions";
+import Title from "../shared/title/Title";
+import CloseButton from "../shared/button/CloseButton";
+import PropTypes from "prop-types";
+import Grid from "@material-ui/core/Grid";
+import Card from "../shared/card/Card";
+import CheckBoxList from "../shared/list/CheckBoxList";
+import RadioButtonList from "../shared/list/RadioButtonList";
+import Button from "../shared/button/Button";
+import ButtonGroup from "../shared/button/ButtonGroup";
+import ProjectList from "./ProjectList";
+import PredefinedDatasourceViews from "./PredefinedDatasourceViews";
+import { Variants } from "../shared/constants/variants";
+import { Size } from "../shared/constants/size";
+import { Align } from "../shared/constants/align";
+import { hexToRgbA } from "../../helpers/colors";
+import {
+ CUSTOM_LAYER_NAME,
+ USER_LAYER_NAME,
+ WATER_LEVEL_KEY,
+} from "../../constants";
+import { DarkTheme } from "../../themes/DarkTheme";
+import MetaApi from "../../api/meta/MetaApi";
+import Searchbox from "../shared/inputs/Searchbox";
+import { showSubscriptionIfFree } from "../../helpers/show_subscriptionDialogue";
+
+DataSelectorDialogue.propTypes = {
+ text: PropTypes.string,
+ state: PropTypes.object,
+ categories: PropTypes.object,
+ onApply: PropTypes.func,
+};
+
+const session = require("./../../../../session/browser/index");
+
+function DataSelectorDialogue(props) {
+ const [allParameters, setAllParameters] = useState([]);
+ const [showProjectsList, setShowProjectsList] = useState(false);
+ const [parameters, setParameters] = useState([]);
+ const [selectedDataSources, setSelectedDataSources] = useState([]);
+ const [selectedParameter, setSelectedParameter] = useState();
+ const [dataSources, setDataSources] = useState([]);
+ const [parameterSearchTerm, setParameterSearchTerm] = useState("");
+ const [filter, setFilter] = useState({});
+
+ useEffect(() => {
+ const waterLevelParameter = {
+ label: __("Vandstand"),
+ value: WATER_LEVEL_KEY,
+ group: __("Vandstand"),
+ };
+ let chemicals = [waterLevelParameter];
+ for (let key in props.categories[LAYER_NAMES[0]]) {
+ if (key == "Vandstand") {
+ continue;
+ }
+ for (let key2 in props.categories[LAYER_NAMES[0]][key]) {
+ var label = props.categories[LAYER_NAMES[0]][key][key2];
+ chemicals.push({ label: label, value: key2, group: key });
+ }
+ }
+ setAllParameters([...chemicals]);
+ setSelectedParameter(waterLevelParameter);
+ }, [props.categories]);
+
+ const onChemicalSelect = (param) => {
+ setSelectedParameter(param);
+ props.selectChemical(param.value);
+ };
+
+ const loadDataSources = () => {
+ const api = new MetaApi();
+ api.getMetaData("calypso_stationer").then((response) => {
+ let datasources = [];
+ response.map((elem) => {
+ if (elem.group == CUSTOM_LAYER_NAME) {
+ if (
+ elem.privileges.hasOwnProperty(
+ session.getProperties()?.organisation.id
+ )
+ ) {
+ datasources.push({ ...elem, group: USER_LAYER_NAME });
+ }
+ } else {
+ datasources.push(elem);
+ }
+ });
+ console.log(datasources);
+ setDataSources(datasources);
+ });
+ };
+ const applyLayer = (layer, chem = false) => {
+ props.onApply({
+ layers: [layer],
+ chemical: chem,
+ filters: filter,
+ });
+ props.onCloseButtonClick();
+ };
+
+ useEffect(() => {
+ loadDataSources();
+ props.backboneEvents.get().on("refresh:meta", () => loadDataSources());
+ }, []);
+
+ useEffect(() => {
+ $.ajax({
+ url: `/api/sql/watsonc?q=SELECT loc_id, locname, groupname, relation FROM calypso_stationer.calypso_my_stations_v2 WHERE user_id in (${
+ session.getProperties()?.organisation.id
+ }, ${session.getUserName()}) &base64=false&lifetime=60&srs=4326`,
+ method: "GET",
+ dataType: "json",
+ }).then((response) => {
+ var features = response.features;
+ var myStations = [];
+
+ features.forEach((element) => {
+ myStations = myStations.concat(element.properties.loc_id);
+ });
+
+ let filter = {
+ match: "any",
+ columns: [
+ {
+ fieldname: "loc_id",
+ expression: "=",
+ value: "ANY( ARRAY[" + myStations.join(", ") + "])",
+ restriction: false,
+ },
+ ],
+ };
+
+ let filters = { "calypso_stationer.all_stations": filter };
+ setFilter(filters);
+ });
+ }, []);
+
+ useEffect(() => {
+ const params = allParameters.filter((parameter) => {
+ return (
+ parameterSearchTerm.length == 0 ||
+ parameter.label
+ .toLowerCase()
+ .indexOf(parameterSearchTerm.toLowerCase()) > -1
+ );
+ });
+ setParameters(params);
+ }, [parameterSearchTerm, allParameters]);
+
+ const applyParameter = () => {
+ const layers = selectedDataSources.map((source) => {
+ return source.value;
+ });
+
+ props.onApply({
+ layers: layers,
+ chemical: selectedParameter ? selectedParameter.value : false,
+ filters: filter,
+ });
+ props.onCloseButtonClick();
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {showProjectsList ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+ {selectedDataSources.findIndex(
+ (item) => item.value == "v:system.all"
+ ) > -1 ? (
+
+ setParameterSearchTerm(value)}
+ />
+
+
+ ) : null}
+
+
+
+
+ )}
+ {showProjectsList ? (
+
+ setShowProjectsList(false)}
+ size={Size.Large}
+ />
+
+ ) : (
+
+ {
+ if (showSubscriptionIfFree()) return;
+ setShowProjectsList(!showProjectsList);
+ }}
+ size={Size.Large}
+ />
+ applyParameter()}
+ size={Size.Large}
+ disabled={selectedDataSources.length === 0}
+ />
+
+ )}
+
+
+ );
+}
+
+const Root = styled.div`
+ background: ${({ theme }) => hexToRgbA(theme.colors.primary[1], 1)};
+ border-radius: ${({ theme }) => `${theme.layout.borderRadius.large}px`};
+ color: ${({ theme }) => `${theme.colors.headings}`};
+`;
+
+const ModalHeader = styled.div`
+ padding: ${({ theme }) =>
+ `${theme.layout.gutter}px ${theme.layout.gutter}px 0 ${theme.layout.gutter}px`};
+`;
+
+const ModalBody = styled.div`
+ padding: ${({ theme }) => `${theme.layout.gutter}px`};
+`;
+
+const GridContainer = styled.div`
+ padding-top: ${(props) => props.theme.layout.gutter / 4}px;
+`;
+
+const mapStateToProps = (state) => ({});
+
+const mapDispatchToProps = (dispatch) => ({
+ selectChemical: (key) => dispatch(selectChemical(key)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(DataSelectorDialogue);
diff --git a/browser/components/dataselector/PredefinedDatasourceViews.js b/browser/components/dataselector/PredefinedDatasourceViews.js
new file mode 100644
index 0000000..cad573a
--- /dev/null
+++ b/browser/components/dataselector/PredefinedDatasourceViews.js
@@ -0,0 +1,59 @@
+import IconButton from "../shared/button/IconButton";
+import { LAYER_NAMES } from "../../constants";
+
+function PredefinedDatasourceViews(props) {
+ return (
+
+ {
+ props.setSelectedDataSources([
+ {
+ label: "Pesticidoverblik",
+ group: "Grundvand, analyser",
+ value: "calypso_stationer.pesticidoverblik",
+ },
+ ]);
+ props.applyLayer("calypso_stationer.pesticidoverblik");
+ }}
+ />
+ {
+ props.applyLayer(LAYER_NAMES[0], "246");
+ props.setSelectedDataSources([
+ {
+ label: "Jupiter boringer",
+ group: "Grundvand",
+ value: "v:system.all",
+ },
+ ]);
+ props.setSelectedParameter({
+ label: "Nitrat",
+ group: "Kemiske hovedbestanddele",
+ value: "246",
+ });
+ }}
+ />
+ {
+ props.applyLayer("calypso_stationer.all_stations");
+ props.setSelectedDataSources([
+ {
+ label: "Mine stationer",
+ group: "Brugerspecifikke lag",
+ value: "calypso_stationer.all_stations",
+ },
+ ]);
+ }}
+ />
+ {/**/}
+
+ );
+}
+
+export default PredefinedDatasourceViews;
diff --git a/browser/components/dataselector/ProjectList.js b/browser/components/dataselector/ProjectList.js
new file mode 100644
index 0000000..4042b79
--- /dev/null
+++ b/browser/components/dataselector/ProjectList.js
@@ -0,0 +1,169 @@
+import { useState, useEffect } from "react";
+import { connect } from "react-redux";
+import ProjectsApi from "../../api/projects/ProjectsApi";
+import Title from "../shared/title/Title";
+import styled from "styled-components";
+import Grid from "@material-ui/core/Grid";
+import { DarkTheme } from "../../themes/DarkTheme";
+import { Align } from "../shared/constants/align";
+import { Variants } from "../shared/constants/variants";
+import { Size } from "../shared/constants/size";
+import CircularProgress from "@material-ui/core/CircularProgress";
+import Button from "../shared/button/Button";
+import ProjectListItem from "./ProjectListItem";
+import Searchbox from "../shared/inputs/Searchbox";
+import base64url from "base64url";
+
+function ProjectList(props) {
+ const [projects, setProjects] = useState([]);
+ const [allProjects, setAllProjects] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [hoveredItem, setHoveredItem] = useState();
+ const [searchTerm, setSearchTerm] = useState("");
+
+ const getProjectParameters = (project) => {
+ let parameters = [];
+ let queryParameters = props.urlparser.urlVars;
+ parameters.push(`state=${project.id}`);
+ let config = null;
+
+ if (project.snapshot && project.snapshot.meta) {
+ if (project.snapshot.meta.config) {
+ config = project.snapshot.meta.config;
+ }
+
+ if (project.snapshot.meta.tmpl) {
+ parameters.push(`tmpl=${project.snapshot.meta.tmpl}`);
+ }
+ }
+
+ if (!config && "config" in queryParameters && queryParameters.config) {
+ // If config is present in project meta, use that.
+ // Else take it from queryparams.
+ config = queryParameters.config;
+ }
+
+ if (config) {
+ parameters.push(`config=${config}`);
+ }
+ return parameters;
+ };
+
+ const getPermalinkForProject = (project) => {
+ let parameters = getProjectParameters(project);
+ let permalink = `${
+ window.location.origin
+ }${props.anchor.getUri()}?${parameters.join("&")}`;
+ return permalink;
+ };
+
+ const loadProjects = () => {
+ setIsLoading(true);
+ const projectsApi = new ProjectsApi();
+ projectsApi.getProjects().then((response) => {
+ response.text().then((text) => {
+ const json = JSON.parse(base64url.decode(text));
+ json.map((project) => {
+ project.permalink = getPermalinkForProject(project);
+ });
+ setAllProjects(json);
+ setIsLoading(false);
+ });
+ });
+ };
+
+ useEffect(() => {
+ loadProjects();
+ }, []);
+
+ useEffect(() => {
+ let term = searchTerm.toLowerCase();
+ let projectsToShow = allProjects.filter((project) => {
+ if (searchTerm.length === 0) {
+ return true;
+ }
+ return project.title.toLowerCase().indexOf(searchTerm) > -1;
+ });
+ setProjects(projectsToShow);
+ }, [allProjects, searchTerm]);
+
+ return (
+
+ {props.authenticated ? null : (
+
+ )}
+ {isLoading ? (
+
+ ) : (
+
+
+ setSearchTerm(value)}
+ />
+
+ {projects.length > 0 ? (
+
+
+
+
+
+
+
+
+ {projects.map((project, index) => {
+ return (
+
+ );
+ })}
+
+
+
+ ) : (
+
+ )}
+
+ )}
+
+ );
+}
+
+const Root = styled.div`
+ margin-top: ${(props) => props.theme.layout.gutter / 2}px;
+ margin-bottom: ${(props) => props.theme.layout.gutter * 2}px;
+ width: 100%;
+`;
+
+const SearchContainer = styled.div`
+ width: 100%;
+ margin: 10px 0px;
+`;
+
+const mapStateToProps = (state) => ({
+ authenticated: state.global.authenticated,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ setDashboardContent: (value) => dispatch(setDashboardContent(value)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ProjectList);
diff --git a/browser/components/dataselector/ProjectListItem.js b/browser/components/dataselector/ProjectListItem.js
new file mode 100644
index 0000000..8c16373
--- /dev/null
+++ b/browser/components/dataselector/ProjectListItem.js
@@ -0,0 +1,106 @@
+import { useState } from "react";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+import { toast } from "react-toastify";
+import styled from "styled-components";
+import Icon from "../shared/icons/Icon";
+import { DarkTheme } from "../../themes/DarkTheme";
+import { Variants } from "../shared/constants/variants";
+import { Spacing } from "../shared/constants/spacing";
+import Grid from "@material-ui/core/Grid";
+import Title from "../shared/title/Title";
+
+function ProjectListItem(props) {
+ const [isHovered, setHover] = useState(false);
+ const [disableStateApply, setDisableStateApply] = useState(false);
+ const applySnapshot = () => {
+ if (disableStateApply) {
+ return;
+ }
+ if (props.onStateSnapshotApply) props.onStateSnapshotApply();
+ setDisableStateApply(true);
+ props.state.applyState(props.project.snapshot).then(() => {
+ setDisableStateApply(false);
+ });
+ };
+
+ return (
+ applySnapshot()}
+ onMouseEnter={() => setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ >
+
+
+
+
+
+
+ e.stopPropagation()}>
+
+ toast.success("Kopieret", {
+ autoClose: 1500,
+ position: toast.POSITION.BOTTOM_RIGHT,
+ toastId: "copytoast",
+ })
+ }
+ >
+
+
+
+
+
+
+ );
+}
+
+const Root = styled.div`
+ background: ${(props) => props.theme.colors.primary[2]};
+ border-radius: ${(props) => props.theme.layout.borderRadius.medium}px;
+ width: 100%;
+ border: 0;
+ box-shadow: none;
+ cursor: pointer;
+ margin-top: ${(props) => props.theme.layout.gutter / 2}px;
+ padding: ${(props) => props.theme.layout.gutter / 4}px;
+ &:hover {
+ background: ${(props) => props.theme.colors.primary[5]};
+ }
+`;
+
+const IconContainer = styled.div`
+ display: inline-block;
+ width: ${(props) => (props.theme.layout.gutter * 3) / 4}px;
+ height: ${(props) => (props.theme.layout.gutter * 3) / 4}px;
+ cursor: pointer;
+ padding: 3px;
+ padding-left: 5px;
+ &:hover {
+ border: 1px solid ${(props) => props.theme.colors.primary[2]};
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ }
+`;
+
+export default ProjectListItem;
diff --git a/browser/components/decorators/InfoComponent.js b/browser/components/decorators/InfoComponent.js
new file mode 100644
index 0000000..9dc829f
--- /dev/null
+++ b/browser/components/decorators/InfoComponent.js
@@ -0,0 +1,58 @@
+import React from "react";
+import Title from "../shared/title/Title";
+import { DarkTheme } from "../../themes/DarkTheme";
+import { Grid } from "@material-ui/core";
+
+const InfoComponent = (props) => {
+ return (
+
+ {props.info.map((elem, index) => {
+ if (elem.type == "header") {
+ return (
+
+
+
+ );
+ }
+
+ if (elem.type == "label") {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+ if (elem.type == "text") {
+ return (
+
+
+
+ );
+ }
+ })}
+
+ );
+};
+
+export default InfoComponent;
diff --git a/browser/components/decorators/MapDecorator.js b/browser/components/decorators/MapDecorator.js
new file mode 100644
index 0000000..aff8fc7
--- /dev/null
+++ b/browser/components/decorators/MapDecorator.js
@@ -0,0 +1,329 @@
+import styled from "styled-components";
+import { DarkTheme } from "../../themes/DarkTheme";
+import { useContext, useState, useEffect } from "react";
+import Title from "../shared/title/Title";
+import Button from "../shared/button/Button";
+import ButtonGroup from "../shared/button/ButtonGroup";
+import Grid from "@material-ui/core/Grid";
+import { Variants } from "../shared/constants/variants";
+import { Size } from "../shared/constants/size";
+import { Align } from "../shared/constants/align";
+import Icon from "../shared/icons/Icon";
+import ProjectContext from "../../contexts/project/ProjectContext";
+import reduxStore from "../../redux/store";
+import { addBoreholeFeature } from "../../redux/actions";
+import { getNewPlotId } from "../../helpers/common";
+import ImageCarousel from "../shared/images/ImageCarousel";
+import { showSubscriptionIfFree } from "../../helpers/show_subscriptionDialogue";
+import InfoComponent from "./InfoComponent";
+
+const session = require("./../../../../session/browser/index");
+
+function MapDecorator(props) {
+ const [showMoreInfo, setShowMoreInfo] = useState(false);
+ const [moreInfo, setMoreInfo] = useState(
+ props.data.properties.location_info
+ ? props.data.properties.location_info
+ : []
+ );
+
+ useEffect(() => {
+ $.ajax({
+ url: `/api/sql/watsonc?q=SELECT ts_info FROM calypso_stationer.location_info_timeseries WHERE loc_id='${props.data.properties.loc_id}'&base64=false&lifetime=60&srs=4326`,
+ method: "GET",
+ dataType: "json",
+ }).then(
+ (response) => {
+ let data = response.features[0].properties;
+ console.log(JSON.parse(data.ts_info));
+
+ setMoreInfo((prev) => [
+ ...prev,
+ { type: "header", value: "Tidsserie info" },
+ ...JSON.parse(data.ts_info),
+ ]);
+ },
+ (jqXHR) => {
+ console.error(`Error occured while getting data`);
+ }
+ );
+ }, []);
+
+ const plot = () => {
+ props.setLoadingData(true);
+ let plot = props.data.properties;
+ let allPlots = props.getAllPlots();
+ let plotData = {
+ id: `plot_${getNewPlotId(allPlots)}`,
+ title: props.data.properties.locname,
+ measurements: [],
+ relations: {},
+ measurementsCachedData: {},
+ };
+ allPlots.unshift(plotData);
+ $.ajax({
+ url: `/api/sql/jupiter?q=SELECT * FROM ${props.relation} WHERE loc_id='${props.data.properties.loc_id}'&base64=false&lifetime=60&srs=4326`,
+ method: "GET",
+ dataType: "json",
+ }).then(
+ (response) => {
+ let data = response.features[0].properties;
+ var indices = [];
+ if (typeof plot.compound_list != "undefined") {
+ let idx = plot.compound_list.indexOf(plot.compound);
+ while (idx != -1) {
+ indices.push(idx);
+ idx = plot.compound_list.indexOf(plot.compound, idx + 1);
+ }
+ } else {
+ indices = [...Array(data.data.length).keys()];
+ }
+
+ for (const u of indices) {
+ const measurement = plot.loc_id + ":_0:" + plot.ts_id[u];
+ plotData.relations[measurement] = props.relation;
+ plotData.measurements.push(measurement);
+ plotData.measurementsCachedData[measurement] = {
+ data: {
+ properties: {
+ _0: JSON.stringify({
+ unit: data.unit[u],
+ title: data.ts_name[u],
+ locname: data.locname,
+ intakes: [1],
+ boreholeno: data.loc_id,
+ data: data.data,
+ trace: data.trace,
+ relation: props.relation,
+ parameter: data.parameter[u],
+ ts_id: data.ts_id,
+ ts_name: data.ts_name,
+ }),
+ boreholeno: data.loc_id,
+ numofintakes: 1,
+ },
+ },
+ };
+ }
+ let activePlots = allPlots.map((plot) => plot.id);
+ props.setPlots(allPlots, activePlots);
+ props.setLoadingData(false);
+ //props.onActivePlotsChange(activePlots, allPlots, projectContext);
+ },
+ (jqXHR) => {
+ console.error(`Error occured while getting data`);
+ }
+ );
+ };
+ const addToDashboard = () => {
+ reduxStore.dispatch(addBoreholeFeature(props.data));
+ };
+ let links = [];
+
+ if (typeof props.data.properties.compound_list != "undefined") {
+ const indices = [];
+ let idx = props.data.properties.compound_list.indexOf(
+ props.data.properties.compound
+ );
+ while (idx != -1) {
+ indices.push(idx);
+ idx = props.data.properties.compound_list.indexOf(
+ props.data.properties.compound,
+ idx + 1
+ );
+ }
+ links.push(
+ indices.map((elem, index) => {
+ return (
+
+
+
+
+ );
+ })
+ );
+ } else {
+ links.push(
+ props.data.properties.ts_name.map((v, index) => {
+ return (
+
+
+
+
+ );
+ })
+ );
+ }
+ return (
+
+ {showMoreInfo ? (
+ <>
+ {/*
+
+ */}
+
+
+
+
+ setShowMoreInfo(false)}
+ disabled={false}
+ />
+
+ >
+ ) : (
+ <>
+ {/*
+
+ */}
+
+
+ {/* */}
+
+
+
+
+
+
+ {links}
+
+ {
+ if (showSubscriptionIfFree(props.getAllPlots().length > 0))
+ return;
+ addToDashboard();
+ plot();
+ props.setDashboardMode("half");
+ }}
+ disabled={false}
+ />
+ {
+ addToDashboard();
+ props.setDashboardMode("half");
+ }}
+ disabled={false}
+ />
+ {moreInfo.length > 0 && (
+ setShowMoreInfo(true)}
+ disabled={false}
+ />
+ )}
+
+ >
+ )}
+
+ );
+}
+
+const Root = styled.div`
+ width: 100%;
+ // height: 600px;
+ background-color: ${(props) => props.theme.colors.primary[2]};
+ border-radius: ${(props) => props.theme.layout.borderRadius.medium}px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+
+ img {
+ border-radius: ${(props) => props.theme.layout.borderRadius.medium}px;
+ }
+`;
+
+const Img = styled.img`
+ width: 100%;
+ height: 125px;
+`;
+
+const LabelsContainer = styled.div`
+ margin-top: ${(props) => props.theme.layout.gutter / 4}px;
+ padding-left: ${(props) => props.theme.layout.gutter / 2}px;
+`;
+
+const LinksContainer = styled.div`
+ margin-top: 8px;
+ padding-left: 16px;
+
+ > div {
+ margin-top: 8px;
+ }
+`;
+
+const RatingStarContainer = styled.div`
+ position: absolute;
+ top: ${(props) => props.theme.layout.gutter / 4}px;
+ right: ${(props) => props.theme.layout.gutter / 4}px;
+`;
+
+export default MapDecorator;
diff --git a/browser/components/modal/Modal.js b/browser/components/modal/Modal.js
deleted file mode 100644
index 8135331..0000000
--- a/browser/components/modal/Modal.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { Variants } from '../../constants';
-import Button from '../shared/button/Button';
-
-
-class Modal extends React.Component {
- render() {
- console.log(this.props);
- return (
-
-
Testing Modal
-
-
-
- window.alert("Primary Button")}>
-
- window.alert("Secondary Button")}>
-
-
-
- );
- }
-}
-
-export default Modal;
-
-const Title = styled.div`
- color: ${({ theme }) => { return theme.colors.headings}};
- padding: ${({ theme }) => theme.padding.titlePadding};
- text-align: center;
- font-size: 34px;
- text-transform: uppercase;
- font-weight: 700;
-`;
-
-const ModalContent = styled.div`
- background: ${({ theme }) => theme.colors.background};
-`;
-
-const Right = styled.div`
- text-align: right;
- align-items: right;
- width: 100%;
-`;
diff --git a/browser/components/shared/button/Button.js b/browser/components/shared/button/Button.js
index 72b59c9..dc342ca 100644
--- a/browser/components/shared/button/Button.js
+++ b/browser/components/shared/button/Button.js
@@ -1,47 +1,88 @@
import styled, { css } from "styled-components";
-import { Variants } from "../../../constants";
-import PropTypes from 'prop-types';
+import { Variants } from "../constants/variants";
+import { Size } from "../constants/size";
+import PropTypes from "prop-types";
-function Button(props) {
- return (
-
- {props.text}
-
- );
-}
Button.propTypes = {
- text: PropTypes.string,
- variant: PropTypes.oneOf(Object.keys(Variants)),
- onClick: PropTypes.func.isRequired
+ text: PropTypes.string,
+ variant: PropTypes.oneOf(Object.keys(Variants)),
+ size: PropTypes.oneOf(Object.keys(Size)),
+ onClick: PropTypes.func.isRequired,
+};
+
+function Button(props) {
+ const onClick = (e) => {
+ if (props.onClick) {
+ props.onClick(e);
+ }
+ };
+ return (
+
+ {props.text}
+
+ );
}
const Root = styled.button`
- height: 40px;
- padding: 0 24px 0 24px;
- font-size: 16px;
- cursor: pointer;
- border: 0;
- border-radius: ${props => props.theme.layout.borderRadius.small}px;
- color: black;
- margin: 0 10px 0 10px;
- ${({ variant, theme }) => {
- const styles = {
- [Variants.Primary]: css `
- background-color: ${theme.colors.interaction[4]};
- &:hover {
- background-color: ${props => props.theme.colors.interaction[5]};
- }
- `,
- [Variants.Secondary]: css `
- background-color: ${theme.colors.primary[4]};
- `,
- [Variants.None]: css `
- background-color: ${theme.colors.gray[3]};
- `
- };
- return styles[variant];
+ padding: 0 24px 0 24px;
+ font-size: 16px;
+ cursor: pointer;
+ border: 0;
+ font: ${(props) => props.theme.fonts.body};
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ color: black;
+ ${({ variant, theme }) => {
+ const styles = {
+ [Variants.Primary]: css`
+ background-color: ${theme.colors.interaction[4]};
+ &:hover {
+ background-color: ${(props) => props.theme.colors.interaction[5]};
+ }
+ `,
+ [Variants.PrimaryDisabled]: css`
+ background-color: ${theme.colors.interaction[4]};
+ opacity: 0.5;
+ &:hover {
+ background-color: ${(props) => props.theme.colors.interaction[5]};
+ }
+ `,
+ [Variants.Secondary]: css`
+ background-color: ${theme.colors.primary[3]};
+ color: ${theme.colors.headings};
+ `,
+ [Variants.None]: css`
+ background-color: ${theme.colors.gray[4]};
+ `,
+ [Variants.Transparent]: css`
+ background-color: transparent;
+ border: 1px solid ${(props) => props.theme.colors.headings};
+ color: ${(props) => props.theme.colors.headings};
+ `,
+ };
+ return styles[variant];
+ }}
+ ${({ size, theme }) => {
+ const styles = {
+ [Size.Small]: css`
+ width: ${theme.layout.gutter * 3}px;
+ font: ${theme.fonts.label};
+ min-height: 24px;
+ `,
+ [Size.Medium]: css`
+ width: ${theme.layout.gutter * 5}px;
+ min-height: 24px;
+ font: ${theme.fonts.label};
+ `,
+ [Size.Large]: css`
+ width: ${theme.layout.gutter * 10}px;
+ `,
+ };
+ return styles[size];
}}
-`
+`;
export default Button;
diff --git a/browser/components/shared/button/ButtonGroup.js b/browser/components/shared/button/ButtonGroup.js
new file mode 100644
index 0000000..29741bc
--- /dev/null
+++ b/browser/components/shared/button/ButtonGroup.js
@@ -0,0 +1,55 @@
+import styled, { css } from "styled-components";
+import { Align } from '../constants/align';
+import PropTypes from 'prop-types';
+
+ButtonGroup.propTypes = {
+ text: PropTypes.string,
+ align: PropTypes.string,
+ marginTop: PropTypes.number,
+ marginLeft: PropTypes.number,
+ marginRight: PropTypes.number,
+ spacing: PropTypes.number,
+}
+
+function ButtonGroup(props) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+const Root = styled.div`
+ display: flex;
+ margin-top: ${props => props.marginTop || props.theme.layout.gutter}px;
+ margin-right: ${props => props.marginRight || 0}px;
+ margin-left: ${props => props.marginLeft || 0}px;
+ ${({ align, theme }) => {
+ const styles = {
+ [Align.Left]: css `
+ justify-content: start;
+ align-items: flex-start;
+ button {
+ margin-right: ${theme.layout.gutter / 2}px;
+ }
+ `,
+ [Align.Center]: css`
+ justify-content: center;
+ align-items: center;
+ `,
+ [Align.Right]: css`
+ justify-content: flex-end;
+ align-items: flex-end;
+ button {
+ margin-left: ${theme.layout.gutter / 2}px;
+ }
+ `
+ }
+ return styles[align];
+ }}
+
+ button + button {
+ margin-left: ${props => props.spacing*8}px;
+ }
+`
+export default ButtonGroup;
diff --git a/browser/components/shared/button/CloseButton.js b/browser/components/shared/button/CloseButton.js
new file mode 100644
index 0000000..8520976
--- /dev/null
+++ b/browser/components/shared/button/CloseButton.js
@@ -0,0 +1,37 @@
+import styled, { css } from "styled-components";
+import { Variants } from "../../../constants";
+import PropTypes from 'prop-types';
+import Icon from "@material-ui/core/Icon";
+
+CloseButton.propTypes = {
+ text: PropTypes.string,
+ onClick: PropTypes.func.isRequired
+}
+
+function CloseButton(props) {
+ return (
+
+
+
+ );
+}
+
+const Root = styled.button`
+ height: 20px;
+ width: 20px;
+ margin-top: 16px;
+ cursor: pointer;
+ border: 1px solid ${({ theme }) => theme.colors.headings};
+ border-radius: ${({ theme }) => theme.layout.borderRadius.small}px;
+ color: ${({ theme }) => theme.colors.headings};
+ background: transparent;
+ padding: 0;
+`
+export default CloseButton;
diff --git a/browser/components/shared/button/IconButton.js b/browser/components/shared/button/IconButton.js
new file mode 100644
index 0000000..4ee5119
--- /dev/null
+++ b/browser/components/shared/button/IconButton.js
@@ -0,0 +1,39 @@
+import styled, { css } from "styled-components";
+import PropTypes from 'prop-types';
+import Icon from '../icons/Icon';
+
+IconButton.propTypes = {
+ onClick: PropTypes.func,
+ icon: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+}
+
+function IconButton(props) {
+ return (
+
+
+ {props.label}
+
+ )
+}
+
+const Root = styled.button`
+ height: 80px;
+ width: 80px;
+ background: ${props => props.theme.colors.primary[1]};
+ border 2px solid ${props => props.theme.colors.primary[3]};
+ color: ${props => props.theme.colors.gray[4]};
+ border-radius: ${props => props.theme.layout.borderRadius.medium}px;
+ margin-right: ${props => props.theme.layout.gutter/2}px;
+ &:hover {
+ border: 2px solid ${props => props.theme.colors.interaction[4]};
+ }
+`;
+
+const IconLabel = styled.div`
+ font: ${props => props.theme.fonts.footnote};
+ color: ${props => props.theme.colors.gray[4]};
+`;
+
+export default IconButton;
diff --git a/browser/components/shared/card/Card.js b/browser/components/shared/card/Card.js
new file mode 100644
index 0000000..02c5ebf
--- /dev/null
+++ b/browser/components/shared/card/Card.js
@@ -0,0 +1,24 @@
+import styled, { css } from "styled-components";
+import PropTypes from 'prop-types';
+import { Spacing } from '../constants/spacing';
+
+function Card(props) {
+
+ return (
+
+ {props.children}
+
+ );
+}
+
+const Root = styled.div`
+ background: ${props => props.theme.colors.primary[2]};
+ border-radius: ${props => props.theme.layout.borderRadius.medium}px;
+ width: 100%;
+ border: 0;
+ box-shadow: none;
+ margin-top: ${props => props.theme.layout.gutter}px;
+ padding: ${props => props.theme.layout.gutter/2}px;
+`;
+
+export default Card;
diff --git a/browser/components/shared/constants/align.js b/browser/components/shared/constants/align.js
new file mode 100644
index 0000000..58e62de
--- /dev/null
+++ b/browser/components/shared/constants/align.js
@@ -0,0 +1,7 @@
+const Align = {
+ Left: 'left',
+ Center: 'center',
+ Right: 'right'
+}
+
+export {Align};
diff --git a/browser/components/shared/constants/size.js b/browser/components/shared/constants/size.js
new file mode 100644
index 0000000..551e0d1
--- /dev/null
+++ b/browser/components/shared/constants/size.js
@@ -0,0 +1,9 @@
+const Size = {
+ Small: 'Small',
+ Medium: 'Medium',
+ Large: 'Large',
+};
+
+export {
+ Size,
+}
diff --git a/browser/components/shared/constants/spacing.js b/browser/components/shared/constants/spacing.js
new file mode 100644
index 0000000..ad4d863
--- /dev/null
+++ b/browser/components/shared/constants/spacing.js
@@ -0,0 +1,8 @@
+const Spacing = {
+ Standard: 'Standard',
+ Lite: 'Lite',
+};
+
+export {
+ Spacing,
+}
diff --git a/browser/components/shared/constants/variants.js b/browser/components/shared/constants/variants.js
new file mode 100644
index 0000000..1a887f2
--- /dev/null
+++ b/browser/components/shared/constants/variants.js
@@ -0,0 +1,12 @@
+const Variants = {
+ None: 'None',
+ Primary: 'Primary',
+ PrimaryDisabled: 'PrimaryDisabled',
+ Secondary: 'Secondary',
+ Tertiary: 'Tertiary',
+ Transparent: 'Transparent',
+};
+
+export {
+ Variants,
+}
diff --git a/browser/components/shared/hooks/useInterval.js b/browser/components/shared/hooks/useInterval.js
new file mode 100644
index 0000000..927da7e
--- /dev/null
+++ b/browser/components/shared/hooks/useInterval.js
@@ -0,0 +1,23 @@
+import React from "react";
+
+const useInterval = (callback, delay) => {
+ const savedCallback = React.useRef();
+
+ React.useEffect(() => {
+ savedCallback.current = callback;
+ }, [callback]);
+
+ React.useEffect(() => {
+ // Don't schedule if no delay is specified.
+ // Note: 0 is a valid value for delay.
+ if (!delay && delay !== 0) {
+ return;
+ }
+
+ const id = setInterval(() => savedCallback.current(), delay);
+
+ return () => clearInterval(id);
+ }, [delay]);
+};
+
+export default useInterval;
diff --git a/browser/components/shared/hooks/usePrevious.js b/browser/components/shared/hooks/usePrevious.js
new file mode 100644
index 0000000..70dfec4
--- /dev/null
+++ b/browser/components/shared/hooks/usePrevious.js
@@ -0,0 +1,10 @@
+import { useRef, useEffect } from "react";
+
+function usePrevious(value) {
+ const ref = useRef();
+ useEffect(() => {
+ ref.current = value; //assign the value of ref to the argument
+ }, [value]); //this code will run when the value of 'value' changes
+ return ref.current; //in the end, return the current ref value.
+}
+export default usePrevious;
diff --git a/browser/components/shared/icons/Icon.js b/browser/components/shared/icons/Icon.js
new file mode 100644
index 0000000..5dde852
--- /dev/null
+++ b/browser/components/shared/icons/Icon.js
@@ -0,0 +1,99 @@
+import * as React from "react";
+import styled, { css } from "styled-components";
+import { Variants } from "../constants/variants";
+import icons from "../../../../shared/icons/icons.json";
+import { IconName } from "../../../../shared/icons/icons";
+import PropTypes from "prop-types";
+
+Icon.propTypes = {
+ name: PropTypes.string,
+ variant: PropTypes.oneOf(Object.keys(Variants)),
+ onClick: PropTypes.func,
+ strokeColor: PropTypes.string,
+ fillColor: PropTypes.string,
+ align: PropTypes.string,
+ marginRight: PropTypes.number,
+ size: PropTypes.number,
+};
+
+Icon.defaultProps = {
+ strokeColor: "currentColor",
+ fillColor: "currentColor",
+ size: 24,
+ variant: "Primary",
+ marginRight: 0,
+ align: "left",
+};
+
+function Icon(props) {
+ const icon = icons.find((i) => i.name === props.name);
+ if (!icon) {
+ // tslint:disable-next-line:no-console
+ console.warn(`Unable to find icon with name "${props.name}"`);
+ return null;
+ }
+ var size = props.size;
+ var viewBox = icon.viewbox || "0 0 24 24";
+ return (
+
+
+
+ );
+}
+
+const Root = styled.div`
+ display: inline-block;
+ position: relative;
+ margin-right: ${(props) => props.marginRight}px;
+ padding-left: ${(props) => props.paddingLeft}px;
+ align: ${(props) => props.align};
+ cursor: ${(props) => (props.onClick ? "pointer" : "inherit")};
+ ${({ variant, theme }) => {
+ const styles = {
+ [Variants.Primary]: css`
+ background-color: ${theme.colors.gray[4]};
+ &:hover {
+ background-color: ${(props) => props.theme.colors.gray[5]};
+ }
+ `,
+ [Variants.Secondary]: css`
+ background-color: ${theme.colors.primary[4]};
+ `,
+ [Variants.None]: css`
+ background-color: ${theme.colors.gray[3]};
+ `,
+ };
+ return styles[variant];
+ }}
+`;
+
+export default Icon;
diff --git a/browser/components/shared/images/ImageCarousel.js b/browser/components/shared/images/ImageCarousel.js
new file mode 100644
index 0000000..5bd0cfe
--- /dev/null
+++ b/browser/components/shared/images/ImageCarousel.js
@@ -0,0 +1,50 @@
+import styled, { css } from "styled-components";
+import PropTypes from "prop-types";
+import { useState } from "react";
+import { Spacing } from "../constants/spacing";
+import useInterval from "../../shared/hooks/useInterval";
+
+function ImageCarousel(props) {
+ var length;
+ if (typeof props.images === "undefined") {
+ length = 0;
+ } else {
+ length = !props.images?.[0] ? 0 : props.images.length;
+ }
+
+ const [index, setIndex] = useState(0);
+ // const [lastindex, setlastindex] = useState(0);
+
+ useInterval(() => {
+ if (index === length - 1) {
+ setIndex(0);
+ // setlastindex(length);
+ } else {
+ // setlastindex(index);
+ setIndex((prev) => prev + 1);
+ }
+ }, 4000);
+ return {length > 0 ?
: null}
;
+}
+
+// const Root = styled.div`
+// background: ${(props) => props.theme.colors.primary[2]};
+// border-radius: ${(props) => props.theme.layout.borderRadius.medium}px;
+// width: 100%;
+// border: 0;
+// box-shadow: none;
+// margin-top: ${(props) => props.theme.layout.gutter}px;
+// padding: ${(props) => props.theme.layout.gutter / 2}px;
+// `;
+
+const Img = styled.img`
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ // width: 100%;
+ height: 125px;
+ transition: opacity 1s;
+ // opacity: ${(props) => (props.animate ? "1" : "0")};
+`;
+
+export default ImageCarousel;
diff --git a/browser/components/shared/inputs/CheckBox.js b/browser/components/shared/inputs/CheckBox.js
new file mode 100644
index 0000000..927d18b
--- /dev/null
+++ b/browser/components/shared/inputs/CheckBox.js
@@ -0,0 +1,53 @@
+import styled from "styled-components";
+import Icon from '../icons/Icon';
+
+function CheckBox(props) {
+ return (
+
+ props.onChange(props.value)}>
+
+
+ {props.label}
+
+ );
+}
+
+const HiddenCheckBox = styled.input.attrs({ type: 'checkbox' })`
+ border: 0;
+ clip: rect(0 0 0 0);
+ clippath: inset(50%);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+`
+const StyledCheckBox = styled.div`
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ font-size: 12px;
+ background: ${props => props.checked ? props.theme.colors.interaction[4] : '#fff'};
+ border-radius: 3px;
+ transition: all 150ms;
+ padding-left: ${props => props.theme.layout.gutter / 8}px;
+ margin-right: ${props => props.theme.layout.gutter / 4}px;
+ div {
+ visibility: ${props => props.checked ? 'visible' : 'hidden'}
+ }
+`
+
+const Root = styled.div`
+ display: inline-block;
+ vertical-align: middle;
+`;
+
+const CheckBoxLabel = styled.label`
+ margin-top: ${props => props.theme.layout.gutter/4}px;
+ color: ${props => props.theme.colors.gray[5]};
+ font: ${props => props.theme.fonts.label};
+`;
+
+export default CheckBox;
diff --git a/browser/components/shared/inputs/RadioButton.js b/browser/components/shared/inputs/RadioButton.js
new file mode 100644
index 0000000..d79d194
--- /dev/null
+++ b/browser/components/shared/inputs/RadioButton.js
@@ -0,0 +1,61 @@
+import styled from "styled-components";
+
+function RadioButton(props) {
+ return (
+
+ props.onChange(props.value)}>
+
+
+ {props.label}
+
+ );
+}
+
+const HiddenRadioButton = styled.input.attrs({ type: 'radio' })`
+ border: 0;
+ clip: rect(0 0 0 0);
+ clippath: inset(50%);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+`
+
+const RadioButtonSelect = styled.div`
+ display: block;
+`;
+
+const StyledRadioButton = styled.div`
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: ${props => props.checked ? props.theme.colors.interaction[4] : '#fff'};
+ transition: all 150ms;
+ padding: ${props => props.theme.layout.gutter / 8}px;
+ margin-right: ${props => props.theme.layout.gutter / 4}px;
+ div {
+ visibility: ${props => props.checked ? 'visible' : 'hidden'};
+ height: 8px;
+ width: 8px;
+ border-radius: 50%;
+ background: #000;
+ }
+`
+
+const Root = styled.div`
+ display: inline-block;
+ vertical-align: middle;
+`;
+
+const RadioButtonLabel = styled.label`
+ margin-top: ${props => props.theme.layout.gutter/4}px;
+ color: ${props => props.theme.colors.gray[5]};
+ font: ${props => props.theme.fonts.label};
+ vertical-align: text-bottom;
+`;
+
+export default RadioButton;
diff --git a/browser/components/shared/inputs/Searchbox.js b/browser/components/shared/inputs/Searchbox.js
new file mode 100644
index 0000000..bd5043c
--- /dev/null
+++ b/browser/components/shared/inputs/Searchbox.js
@@ -0,0 +1,72 @@
+import styled, { css } from "styled-components";
+import Icon from "../icons/Icon";
+import PropTypes from "prop-types";
+
+Searchbox.propTypes = {
+ placeholder: PropTypes.string,
+ variant: PropTypes.string,
+ onChange: PropTypes.func,
+};
+
+Searchbox.defaultProps = {
+ variant: Variants.Transparent,
+};
+
+function Searchbox(props) {
+ const onChange = (event) => {
+ if (props.onChange) {
+ return props.onChange(event.target.value);
+ }
+ };
+ return (
+
+
+
+
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ height: 40px;
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ border: 1px solid ${(props) => props.theme.colors.gray[2]};
+ width: 100%;
+ color: ${(props) => props.theme.colors.gray[4]};
+ > div {
+ margin: 10px;
+ }
+ ${({ variant, theme }) => {
+ const styles = {
+ [Variants.Primary]: css`
+ background-color: ${theme.colors.headings};
+ `,
+ [Variants.Transparent]: css`
+ background-color: transparent;
+ `,
+ };
+ return styles[variant];
+ }}
+`;
+
+const Input = styled.input`
+ background: transparent;
+ height: 40px;
+ box-shadow: none;
+ border: 0;
+ color: ${(props) => props.theme.colors.gray[5]};
+ &:focus {
+ outline: none;
+ }
+ ::placeholder {
+ color: ${(props) => props.theme.colors.gray[4]};
+ }
+ flex: 1;
+`;
+
+export default Searchbox;
diff --git a/browser/components/shared/inputs/Select.js b/browser/components/shared/inputs/Select.js
new file mode 100644
index 0000000..e69de29
diff --git a/browser/components/shared/inputs/TextInput.js b/browser/components/shared/inputs/TextInput.js
new file mode 100644
index 0000000..1067035
--- /dev/null
+++ b/browser/components/shared/inputs/TextInput.js
@@ -0,0 +1,73 @@
+import styled, { css } from "styled-components";
+import Icon from "../icons/Icon";
+import PropTypes from "prop-types";
+
+TextInput.propTypes = {
+ placeholder: PropTypes.string,
+ variant: PropTypes.string,
+ onChange: PropTypes.func,
+};
+
+TextInput.defaultProps = {
+ variant: Variants.Transparent,
+};
+
+function TextInput(props) {
+ const onChange = (event) => {
+ if (props.onChange) {
+ return props.onChange(event.target.value);
+ }
+ };
+ return (
+
+
+
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ height: 40px;
+ border-radius: ${(props) => props.theme.layout.borderRadius.small}px;
+ border: 1px solid ${(props) => props.theme.colors.gray[2]};
+ width: 100%;
+ color: ${(props) => props.theme.colors.gray[4]};
+ > div {
+ margin: 10px;
+ }
+ ${({ variant, theme }) => {
+ const styles = {
+ [Variants.Primary]: css`
+ background-color: ${theme.colors.headings};
+ `,
+ [Variants.Transparent]: css`
+ background-color: transparent;
+ `,
+ };
+ return styles[variant];
+ }}
+`;
+
+const Input = styled.input`
+ background: transparent;
+ height: 40px;
+ box-shadow: none;
+ border: 0;
+ color: ${(props) => props.theme.colors.gray[5]};
+ &:focus {
+ outline: none;
+ }
+ ::placeholder {
+ color: ${(props) => props.theme.colors.gray[2]};
+ }
+ flex: 1;
+`;
+
+export default TextInput;
diff --git a/browser/components/shared/list/CheckBoxList.js b/browser/components/shared/list/CheckBoxList.js
new file mode 100644
index 0000000..244f853
--- /dev/null
+++ b/browser/components/shared/list/CheckBoxList.js
@@ -0,0 +1,104 @@
+import { useState, useEffect } from "react";
+import styled from "styled-components";
+import PropTypes from "prop-types";
+import Checkbox from "../inputs/CheckBox";
+import { selectEndDate } from "../../../redux/actions";
+
+CheckBoxList.propTypes = {
+ listItems: PropTypes.array.isRequired,
+ onChange: PropTypes.func,
+ selectedItems: PropTypes.array,
+};
+
+CheckBoxList.defaultProps = {
+ selectedItems: [],
+};
+
+function CheckBoxList(props) {
+ const [selectedItems, setSelectedItems] = useState(props.selectedItems);
+ const [listItems, setListItems] = useState([]);
+ var currentGroup = null;
+
+ function onChangeCheckbox(value) {
+ let array = props.selectedItems.map((elem) => elem.value);
+ let index = array.indexOf(value);
+ // let array = [...props.selectedItems];
+ if (index > -1) {
+ array.splice(index, 1);
+ } else {
+ array.push(value);
+ }
+ setSelectedItems(array);
+ }
+
+ const getSelectedItems = () => {
+ return listItems.filter((item) => {
+ if (selectedItems.indexOf(item.value) > -1) {
+ return item;
+ }
+ return null;
+ });
+ };
+
+ const renderItem = (item, index) => {
+ let returnData = [];
+ if (item.group != currentGroup) {
+ returnData.push(
+
+ );
+ currentGroup = item.group;
+ }
+ returnData.push(
+
+ elem.value).indexOf(item.value) >
+ -1
+ }
+ onChange={onChangeCheckbox}
+ label={item.label}
+ />
+
+ );
+ return returnData;
+ };
+
+ useEffect(() => {
+ var _listItems = [...props.listItems];
+ _listItems.sort((a, b) => (a.group > b.group ? 1 : -1));
+ setListItems(_listItems);
+ }, [props.listItems]);
+
+ useEffect(() => {
+ if (props.onChange) {
+ props.onChange(getSelectedItems());
+ }
+ }, [selectedItems]);
+
+ return (
+
+ {listItems.map((item, index) => {
+ return renderItem(item, index);
+ })}
+
+ );
+}
+
+const Root = styled.div`
+ height: ${(props) => props.theme.layout.gutter * 10}px;
+ overflow-y: scroll;
+ color: ${(props) => props.theme.colors.gray[4]};
+`;
+
+const ListItem = styled.div`
+ margin-left: ${(props) => props.theme.layout.gutter / 4}px;
+ color: ${(props) => props.theme.colors.gray[5]};
+`;
+
+export default CheckBoxList;
diff --git a/browser/components/shared/list/RadioButtonList.js b/browser/components/shared/list/RadioButtonList.js
new file mode 100644
index 0000000..a2ff0f0
--- /dev/null
+++ b/browser/components/shared/list/RadioButtonList.js
@@ -0,0 +1,71 @@
+import { useState, useEffect } from "react";
+import styled from "styled-components";
+import PropTypes from "prop-types";
+import RadioButton from "../inputs/RadioButton";
+
+RadioButtonList.propTypes = {
+ listItems: PropTypes.array.isRequired,
+ onChange: PropTypes.func,
+};
+
+function RadioButtonList(props) {
+ const [selectedItem, setSelectedItem] = useState(props.selectedParameter);
+ var currentGroup = null;
+ function onChangeRadioButton(value) {
+ let selectedObject = props.listItems.find((item) => item.value == value);
+ setSelectedItem(selectedObject);
+ }
+
+ const renderItem = (item, index) => {
+ let returnData = [];
+ if (item.group != currentGroup) {
+ returnData.push(
+
+ );
+ currentGroup = item.group;
+ }
+ returnData.push(
+
+
+
+ );
+ return returnData;
+ };
+
+ useEffect(() => {
+ if (props.onChange) {
+ props.onChange(selectedItem);
+ }
+ }, [selectedItem]);
+
+ return (
+
+ {props.listItems.map((item, index) => {
+ return renderItem(item, index);
+ })}
+
+ );
+}
+
+const Root = styled.div`
+ height: ${(props) => props.theme.layout.gutter * 10}px;
+ overflow-y: scroll;
+ color: ${(props) => props.theme.colors.gray[4]};
+`;
+
+const ListItem = styled.div`
+ margin-left: ${(props) => props.theme.layout.gutter / 4}px;
+ color: ${(props) => props.theme.colors.gray[5]};
+`;
+
+export default RadioButtonList;
diff --git a/browser/components/shared/list/SortableList.js b/browser/components/shared/list/SortableList.js
new file mode 100644
index 0000000..579ed36
--- /dev/null
+++ b/browser/components/shared/list/SortableList.js
@@ -0,0 +1,7 @@
+import { sortableContainer } from "react-sortable-hoc";
+
+const SortableList = sortableContainer((props) => {
+ return ;
+});
+
+export default SortableList;
diff --git a/browser/components/shared/title/Title.js b/browser/components/shared/title/Title.js
new file mode 100644
index 0000000..233ce16
--- /dev/null
+++ b/browser/components/shared/title/Title.js
@@ -0,0 +1,88 @@
+import styled, { css } from "styled-components";
+import PropTypes from "prop-types";
+
+function Title(props) {
+ return (
+
+ {props.text}
+
+ );
+}
+Title.propTypes = {
+ //text: PropTypes.string.isRequired,
+ level: PropTypes.number,
+ className: PropTypes.string,
+ align: PropTypes.string,
+ color: PropTypes.string,
+ marginTop: PropTypes.number,
+ marginLeft: PropTypes.number,
+ width: PropTypes.string,
+};
+
+Title.defaultProps = {
+ level: 1,
+ align: "left",
+ marginLeft: 0,
+ marginTop: 0,
+ color: "currentColor",
+ width: null,
+};
+
+const Root = styled.div`
+ display: inline-block;
+ font-weight: normal;
+ margin: 0;
+ line-height: 1.3;
+ width: ${(props) => props.width};
+ text-align: ${(props) => props.align};
+ margin-left: ${(props) => props.marginLeft || 0}px;
+ ${({ level, theme }) => {
+ const styles = {
+ 1: css`
+ font: ${(props) => props.theme.fonts.title};
+ color: ${(props) => props.color || props.theme.colors.headings};
+ `,
+ 2: css`
+ font: ${(props) => props.theme.fonts.subtitle};
+ color: ${(props) => props.color || props.theme.colors.headings};
+ `,
+ 3: css`
+ font: ${(props) => props.theme.fonts.heading};
+ color: ${(props) => props.color || props.theme.colors.gray[3]};
+ `,
+ 4: css`
+ font: ${(props) => props.theme.fonts.body};
+ color: ${(props) => props.color || props.theme.colors.primary[5]};
+ `,
+ 5: css`
+ margin-top: ${(props) =>
+ props.marginTop != null
+ ? props.marginTop
+ : props.theme.layout.gutter / 2}px;
+ font: ${(props) => props.theme.fonts.subbody};
+ color: ${(props) => props.color || props.theme.colors.gray[4]};
+ `,
+ 6: css`
+ margin-top: ${(props) =>
+ props.marginTop != null
+ ? props.marginTop
+ : props.theme.layout.gutter / 4}px;
+ font: ${(props) => props.theme.fonts.label};
+ color: ${(props) => props.color || props.theme.colors.gray[5]};
+ `,
+ 7: css`
+ font: ${(props) => props.theme.fonts.footnote};
+ color: ${(props) => props.color || props.theme.colors.gray[5]};
+ `,
+ };
+ return styles[level];
+ }}
+`;
+export default Title;
diff --git a/browser/components/shared/userProfileButton/UserAvatar.js b/browser/components/shared/userProfileButton/UserAvatar.js
new file mode 100644
index 0000000..8b1b81b
--- /dev/null
+++ b/browser/components/shared/userProfileButton/UserAvatar.js
@@ -0,0 +1,34 @@
+import styled from "styled-components";
+import PropTypes from 'prop-types';
+import Icon from '../icons/Icon';
+
+function UserAvatar(props) {
+
+ return(
+
+
+
+ );
+}
+
+const Avatar = styled.div`
+ background: ${({ theme }) => theme.colors.primary[5]};
+ border-radius: 50%;
+ padding-left: 10px;
+ height: 44px;
+ width: 44px;
+ color: white;
+ font: ${({ theme }) => theme.fonts.heading};
+ display: flex;
+ text-align: center;
+ justify-content: center;
+ flex-direction: column;
+ cursor: pointer;
+ padding-left: 10px;
+
+ :hover {
+ background: ${({ theme }) => theme.colors.primary[3]};
+ }
+`;
+
+export default UserAvatar;
diff --git a/browser/components/shared/userProfileButton/UserProfileButton.js b/browser/components/shared/userProfileButton/UserProfileButton.js
new file mode 100644
index 0000000..d5f48c5
--- /dev/null
+++ b/browser/components/shared/userProfileButton/UserProfileButton.js
@@ -0,0 +1,111 @@
+import styled from "styled-components";
+import { useState, useEffect, useRef } from 'react';
+import { usePopper } from 'react-popper';
+import {connect} from 'react-redux'
+import PropTypes from 'prop-types';
+import UserAvatar from "./UserAvatar";
+import UserProfileOptionsList from "./UserProfileOptionsList";
+
+
+function UserProfileButton(props) {
+
+ const [showPopper, setShowPopper] = useState(false);
+ const buttonRef = useRef(null);
+ const popperRef = useRef(null);
+ const [arrowRef, setArrowRef] = useState(null);
+ const { styles, attributes } = usePopper(
+ buttonRef.current,
+ popperRef.current,
+ {
+ modifiers: [
+ {
+ name: "arrow",
+ options: {
+ element: arrowRef
+ }
+ },
+ {
+ name: "offset",
+ options: {
+ offset: [0, 3]
+ }
+ },
+ {
+ name: "preventOverflow",
+ options: {
+ altAxis: true,
+ padding: 10
+ }
+ }
+ ]
+ }
+ );
+
+ useEffect(() => {
+ document.addEventListener("mousedown", handleDocumentClick);
+ return () => {
+ document.removeEventListener("mousedown", handleDocumentClick);
+ };
+ }, []);
+
+ return (
+ <>
+ setShowPopper(!showPopper)}>
+
+
+
+ { showPopper ? (
+
+
+
+
+ ) : null }
+
+ >
+ );
+
+ function handleDocumentClick(event) {
+ if (buttonRef.current.contains(event.target) ||
+ (popperRef.current && popperRef.current.contains(event.target))) {
+ return;
+ }
+ setShowPopper(false);
+ }
+}
+
+const PopperContainer = styled.div`
+ box-shadow: ${({ theme }) => theme.layout.boxShadow};
+ border-radius: ${({ theme }) => theme.layout.borderRadius.small}px;
+ background: ${({ theme }) => theme.colors.gray[5]};
+ padding: ${({ theme }) => theme.layout.gutter / 2}px;
+ text-align: left;
+
+ #arrow {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ &:after {
+ content: " ";
+ background-color: ${({ theme }) => theme.colors.gray[5]};
+ position: absolute;
+ top: -20px;
+ left: 0;
+ transform: rotate(45deg);
+ width: 10px;
+ height: 10px;
+ }
+ }
+
+ &[data-popper-placement^='top'] > #arrow {
+ bottom: -30px;
+ :after {
+ box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
+ }
+ }
+`;
+
+export default UserProfileButton;
diff --git a/browser/components/shared/userProfileButton/UserProfileOptionsList.js b/browser/components/shared/userProfileButton/UserProfileOptionsList.js
new file mode 100644
index 0000000..0cc3e0d
--- /dev/null
+++ b/browser/components/shared/userProfileButton/UserProfileOptionsList.js
@@ -0,0 +1,64 @@
+import styled from "styled-components";
+import { connect } from "react-redux";
+import { showSubscription } from "../../../helpers/show_subscriptionDialogue";
+import { showLoginModal } from "../../../helpers/show_loginmodal";
+import { LOGIN_MODAL_DIALOG_PREFIX } from "../../../constants";
+
+function UserProfileOptionsList(props) {
+ return (
+
+ {
+ window.open("https://admin.calypso.watsonc.dk", "_blank");
+ }}
+ >
+ Min profil
+
+ Abonnement
+ {/* Nulstil */}
+ {
+ window.open("http://watsonc.dk", "_blank");
+ }}
+ >
+ Watsonc.dk
+
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("show")}>
+ Log ud
+ {/* {}}>
+ Log ud
+ */}
+
+
+ );
+}
+
+const Root = styled.ul`
+ background: ${({ theme }) => theme.colors.gray[5]};
+ font: ${({ theme }) => theme.fonts.body};
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ padding: ${({ theme }) => theme.layout.gutter / 4}px
+ ${({ theme }) => theme.layout.gutter / 2}px;
+ border-radius: ${({ theme }) => theme.layout.borderRadius.small}px;
+ cursor: pointer;
+ color: ${({ theme }) => theme.colors.gray[1]};
+ &:hover {
+ background: ${({ theme }) => theme.colors.gray[4]};
+ }
+ }
+`;
+
+const mapStateToProps = (state) => ({});
+
+const mapDispatchToProps = (dispatch) => ({
+ setDashboardMode: (key) => dispatch(setDashboardMode(key)),
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(UserProfileOptionsList);
diff --git a/browser/components/withDragDropContext.js b/browser/components/withDragDropContext.js
deleted file mode 100644
index 9461f90..0000000
--- a/browser/components/withDragDropContext.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import {DragDropContext} from 'react-dnd';
-var MultiBackend = require('react-dnd-multi-backend').default;
-var HTML5toTouch = require('react-dnd-multi-backend/lib/HTML5toTouch').default; // or any other pipeline
-
-export default DragDropContext(MultiBackend(HTML5toTouch));
\ No newline at end of file
diff --git a/browser/constants.js b/browser/constants.js
index a2f291f..d92f894 100644
--- a/browser/constants.js
+++ b/browser/constants.js
@@ -1,15 +1,20 @@
const LAYER_NAMES = [
- `v:system.all`, // 0
- `v:sensor.sensordata_without_correction`, // 1 Calypso stations
- `chemicals.boreholes_time_series_without_chemicals`, // 2 Raster layer with all boreholes
- `v:chemicals.pesticidoverblik`, // 3
- `chemicals.pesticidoverblik_raster` // 4
+ `v:system.all`, // 0
+ `v:sensor.sensordata_without_correction`, // 1 Calypso stations
+ `chemicals.boreholes_time_series_without_chemicals`, // 2 Raster layer with all boreholes
+ `v:chemicals.pesticidoverblik`, // 3
+ `chemicals.pesticidoverblik_raster`, // 4
];
+const CUSTOM_LAYER_NAME = "Særskilte";
+const USER_LAYER_NAME = "Brugerspecifikke lag";
+
const WATER_LEVEL_KEY = `99999`;
const SELECT_CHEMICAL_DIALOG_PREFIX = `watsonc-select-chemical-dialog`;
+const LOGIN_MODAL_DIALOG_PREFIX = `watsonc-login-modal`;
+
const TEXT_FIELD_DIALOG_PREFIX = `watsonc-text-field-dialog`;
const LIMIT_CHAR = `<`;
@@ -20,424 +25,417 @@ const VIEW_ROW = 1;
const FREE_PLAN_MAX_TIME_SERIES_COUNT = 4;
const FREE_PLAN_MAX_PROFILES_COUNT = 1;
-const KOMMUNER =[
- {
- "komkode": "580",
- "komnavn": "Aabenraa"
- },
- {
- "komkode": "851",
- "komnavn": "Aalborg"
- },
- {
- "komkode": "751",
- "komnavn": "Aarhus"
- },
- {
- "komkode": "492",
- "komnavn": "Ærø"
- },
- {
- "komkode": "165",
- "komnavn": "Albertslund"
- },
- {
- "komkode": "201",
- "komnavn": "Allerød"
- },
- {
- "komkode": "420",
- "komnavn": "Assens"
- },
- {
- "komkode": "151",
- "komnavn": "Ballerup"
- },
- {
- "komkode": "530",
- "komnavn": "Billund"
- },
- {
- "komkode": "400",
- "komnavn": "Bornholm"
- },
- {
- "komkode": "153",
- "komnavn": "Brøndby"
- },
- {
- "komkode": "810",
- "komnavn": "Brønderslev"
- },
- {
- "komkode": "411",
- "komnavn": "Christiansø"
- },
- {
- "komkode": "155",
- "komnavn": "Dragør"
- },
- {
- "komkode": "240",
- "komnavn": "Egedal"
- },
- {
- "komkode": "561",
- "komnavn": "Esbjerg"
- },
- {
- "komkode": "430",
- "komnavn": "Faaborg-Midtfyn"
- },
- {
- "komkode": "563",
- "komnavn": "Fanø"
- },
- {
- "komkode": "710",
- "komnavn": "Favrskov"
- },
- {
- "komkode": "320",
- "komnavn": "Faxe"
- },
- {
- "komkode": "210",
- "komnavn": "Fredensborg"
- },
- {
- "komkode": "607",
- "komnavn": "Fredericia"
- },
- {
- "komkode": "147",
- "komnavn": "Frederiksberg"
- },
- {
- "komkode": "813",
- "komnavn": "Frederikshavn"
- },
- {
- "komkode": "250",
- "komnavn": "Frederikssund"
- },
- {
- "komkode": "190",
- "komnavn": "Furesø"
- },
- {
- "komkode": "157",
- "komnavn": "Gentofte"
- },
- {
- "komkode": "159",
- "komnavn": "Gladsaxe"
- },
- {
- "komkode": "161",
- "komnavn": "Glostrup"
- },
- {
- "komkode": "253",
- "komnavn": "Greve"
- },
- {
- "komkode": "270",
- "komnavn": "Gribskov"
- },
- {
- "komkode": "376",
- "komnavn": "Guldborgsund"
- },
- {
- "komkode": "510",
- "komnavn": "Haderslev"
- },
- {
- "komkode": "260",
- "komnavn": "Halsnæs"
- },
- {
- "komkode": "766",
- "komnavn": "Hedensted"
- },
- {
- "komkode": "217",
- "komnavn": "Helsingør"
- },
- {
- "komkode": "163",
- "komnavn": "Herlev"
- },
- {
- "komkode": "657",
- "komnavn": "Herning"
- },
- {
- "komkode": "219",
- "komnavn": "Hillerød"
- },
- {
- "komkode": "860",
- "komnavn": "Hjørring"
- },
- {
- "komkode": "169",
- "komnavn": "Høje-Taastrup"
- },
- {
- "komkode": "316",
- "komnavn": "Holbæk"
- },
- {
- "komkode": "661",
- "komnavn": "Holstebro"
- },
- {
- "komkode": "615",
- "komnavn": "Horsens"
- },
- {
- "komkode": "223",
- "komnavn": "Hørsholm"
- },
- {
- "komkode": "167",
- "komnavn": "Hvidovre"
- },
- {
- "komkode": "756",
- "komnavn": "Ikast-Brande"
- },
- {
- "komkode": "183",
- "komnavn": "Ishøj"
- },
- {
- "komkode": "849",
- "komnavn": "Jammerbugt"
- },
- {
- "komkode": "326",
- "komnavn": "Kalundborg"
- },
- {
- "komkode": "440",
- "komnavn": "Kerteminde"
- },
- {
- "komkode": "101",
- "komnavn": "København"
- },
- {
- "komkode": "259",
- "komnavn": "Køge"
- },
- {
- "komkode": "621",
- "komnavn": "Kolding"
- },
- {
- "komkode": "825",
- "komnavn": "Læsø"
- },
- {
- "komkode": "482",
- "komnavn": "Langeland"
- },
- {
- "komkode": "350",
- "komnavn": "Lejre"
- },
- {
- "komkode": "665",
- "komnavn": "Lemvig"
- },
- {
- "komkode": "360",
- "komnavn": "Lolland"
- },
- {
- "komkode": "173",
- "komnavn": "Lyngby-Taarbæk"
- },
- {
- "komkode": "846",
- "komnavn": "Mariagerfjord"
- },
- {
- "komkode": "410",
- "komnavn": "Middelfart"
- },
- {
- "komkode": "773",
- "komnavn": "Morsø"
- },
- {
- "komkode": "370",
- "komnavn": "Næstved"
- },
- {
- "komkode": "707",
- "komnavn": "Norddjurs"
- },
- {
- "komkode": "480",
- "komnavn": "Nordfyns"
- },
- {
- "komkode": "450",
- "komnavn": "Nyborg"
- },
- {
- "komkode": "727",
- "komnavn": "Odder"
- },
- {
- "komkode": "461",
- "komnavn": "Odense"
- },
- {
- "komkode": "306",
- "komnavn": "Odsherred"
- },
- {
- "komkode": "730",
- "komnavn": "Randers"
- },
- {
- "komkode": "840",
- "komnavn": "Rebild"
- },
- {
- "komkode": "760",
- "komnavn": "Ringkøbing-Skjern"
- },
- {
- "komkode": "329",
- "komnavn": "Ringsted"
- },
- {
- "komkode": "175",
- "komnavn": "Rødovre"
- },
- {
- "komkode": "265",
- "komnavn": "Roskilde"
- },
- {
- "komkode": "230",
- "komnavn": "Rudersdal"
- },
- {
- "komkode": "741",
- "komnavn": "Samsø"
- },
- {
- "komkode": "740",
- "komnavn": "Silkeborg"
- },
- {
- "komkode": "746",
- "komnavn": "Skanderborg"
- },
- {
- "komkode": "779",
- "komnavn": "Skive"
- },
- {
- "komkode": "330",
- "komnavn": "Slagelse"
- },
- {
- "komkode": "269",
- "komnavn": "Solrød"
- },
- {
- "komkode": "540",
- "komnavn": "Sønderborg"
- },
- {
- "komkode": "340",
- "komnavn": "Sorø"
- },
- {
- "komkode": "336",
- "komnavn": "Stevns"
- },
- {
- "komkode": "671",
- "komnavn": "Struer"
- },
- {
- "komkode": "479",
- "komnavn": "Svendborg"
- },
- {
- "komkode": "706",
- "komnavn": "Syddjurs"
- },
- {
- "komkode": "185",
- "komnavn": "Tårnby"
- },
- {
- "komkode": "787",
- "komnavn": "Thisted"
- },
- {
- "komkode": "550",
- "komnavn": "Tønder"
- },
- {
- "komkode": "187",
- "komnavn": "Vallensbæk"
- },
- {
- "komkode": "573",
- "komnavn": "Varde"
- },
- {
- "komkode": "575",
- "komnavn": "Vejen"
- },
- {
- "komkode": "630",
- "komnavn": "Vejle"
- },
- {
- "komkode": "820",
- "komnavn": "Vesthimmerlands"
- },
- {
- "komkode": "791",
- "komnavn": "Viborg"
- },
- {
- "komkode": "390",
- "komnavn": "Vordingborg"
- }
-]
-
-
-const Variants = {
- None: 'None',
- Primary: 'Primary',
- Secondary: 'Secondary',
- Tertiary: 'Tertiary',
-};
-
+const KOMMUNER = [
+ {
+ komkode: "580",
+ komnavn: "Aabenraa",
+ },
+ {
+ komkode: "851",
+ komnavn: "Aalborg",
+ },
+ {
+ komkode: "751",
+ komnavn: "Aarhus",
+ },
+ {
+ komkode: "492",
+ komnavn: "Ærø",
+ },
+ {
+ komkode: "165",
+ komnavn: "Albertslund",
+ },
+ {
+ komkode: "201",
+ komnavn: "Allerød",
+ },
+ {
+ komkode: "420",
+ komnavn: "Assens",
+ },
+ {
+ komkode: "151",
+ komnavn: "Ballerup",
+ },
+ {
+ komkode: "530",
+ komnavn: "Billund",
+ },
+ {
+ komkode: "400",
+ komnavn: "Bornholm",
+ },
+ {
+ komkode: "153",
+ komnavn: "Brøndby",
+ },
+ {
+ komkode: "810",
+ komnavn: "Brønderslev",
+ },
+ {
+ komkode: "411",
+ komnavn: "Christiansø",
+ },
+ {
+ komkode: "155",
+ komnavn: "Dragør",
+ },
+ {
+ komkode: "240",
+ komnavn: "Egedal",
+ },
+ {
+ komkode: "561",
+ komnavn: "Esbjerg",
+ },
+ {
+ komkode: "430",
+ komnavn: "Faaborg-Midtfyn",
+ },
+ {
+ komkode: "563",
+ komnavn: "Fanø",
+ },
+ {
+ komkode: "710",
+ komnavn: "Favrskov",
+ },
+ {
+ komkode: "320",
+ komnavn: "Faxe",
+ },
+ {
+ komkode: "210",
+ komnavn: "Fredensborg",
+ },
+ {
+ komkode: "607",
+ komnavn: "Fredericia",
+ },
+ {
+ komkode: "147",
+ komnavn: "Frederiksberg",
+ },
+ {
+ komkode: "813",
+ komnavn: "Frederikshavn",
+ },
+ {
+ komkode: "250",
+ komnavn: "Frederikssund",
+ },
+ {
+ komkode: "190",
+ komnavn: "Furesø",
+ },
+ {
+ komkode: "157",
+ komnavn: "Gentofte",
+ },
+ {
+ komkode: "159",
+ komnavn: "Gladsaxe",
+ },
+ {
+ komkode: "161",
+ komnavn: "Glostrup",
+ },
+ {
+ komkode: "253",
+ komnavn: "Greve",
+ },
+ {
+ komkode: "270",
+ komnavn: "Gribskov",
+ },
+ {
+ komkode: "376",
+ komnavn: "Guldborgsund",
+ },
+ {
+ komkode: "510",
+ komnavn: "Haderslev",
+ },
+ {
+ komkode: "260",
+ komnavn: "Halsnæs",
+ },
+ {
+ komkode: "766",
+ komnavn: "Hedensted",
+ },
+ {
+ komkode: "217",
+ komnavn: "Helsingør",
+ },
+ {
+ komkode: "163",
+ komnavn: "Herlev",
+ },
+ {
+ komkode: "657",
+ komnavn: "Herning",
+ },
+ {
+ komkode: "219",
+ komnavn: "Hillerød",
+ },
+ {
+ komkode: "860",
+ komnavn: "Hjørring",
+ },
+ {
+ komkode: "169",
+ komnavn: "Høje-Taastrup",
+ },
+ {
+ komkode: "316",
+ komnavn: "Holbæk",
+ },
+ {
+ komkode: "661",
+ komnavn: "Holstebro",
+ },
+ {
+ komkode: "615",
+ komnavn: "Horsens",
+ },
+ {
+ komkode: "223",
+ komnavn: "Hørsholm",
+ },
+ {
+ komkode: "167",
+ komnavn: "Hvidovre",
+ },
+ {
+ komkode: "756",
+ komnavn: "Ikast-Brande",
+ },
+ {
+ komkode: "183",
+ komnavn: "Ishøj",
+ },
+ {
+ komkode: "849",
+ komnavn: "Jammerbugt",
+ },
+ {
+ komkode: "326",
+ komnavn: "Kalundborg",
+ },
+ {
+ komkode: "440",
+ komnavn: "Kerteminde",
+ },
+ {
+ komkode: "101",
+ komnavn: "København",
+ },
+ {
+ komkode: "259",
+ komnavn: "Køge",
+ },
+ {
+ komkode: "621",
+ komnavn: "Kolding",
+ },
+ {
+ komkode: "825",
+ komnavn: "Læsø",
+ },
+ {
+ komkode: "482",
+ komnavn: "Langeland",
+ },
+ {
+ komkode: "350",
+ komnavn: "Lejre",
+ },
+ {
+ komkode: "665",
+ komnavn: "Lemvig",
+ },
+ {
+ komkode: "360",
+ komnavn: "Lolland",
+ },
+ {
+ komkode: "173",
+ komnavn: "Lyngby-Taarbæk",
+ },
+ {
+ komkode: "846",
+ komnavn: "Mariagerfjord",
+ },
+ {
+ komkode: "410",
+ komnavn: "Middelfart",
+ },
+ {
+ komkode: "773",
+ komnavn: "Morsø",
+ },
+ {
+ komkode: "370",
+ komnavn: "Næstved",
+ },
+ {
+ komkode: "707",
+ komnavn: "Norddjurs",
+ },
+ {
+ komkode: "480",
+ komnavn: "Nordfyns",
+ },
+ {
+ komkode: "450",
+ komnavn: "Nyborg",
+ },
+ {
+ komkode: "727",
+ komnavn: "Odder",
+ },
+ {
+ komkode: "461",
+ komnavn: "Odense",
+ },
+ {
+ komkode: "306",
+ komnavn: "Odsherred",
+ },
+ {
+ komkode: "730",
+ komnavn: "Randers",
+ },
+ {
+ komkode: "840",
+ komnavn: "Rebild",
+ },
+ {
+ komkode: "760",
+ komnavn: "Ringkøbing-Skjern",
+ },
+ {
+ komkode: "329",
+ komnavn: "Ringsted",
+ },
+ {
+ komkode: "175",
+ komnavn: "Rødovre",
+ },
+ {
+ komkode: "265",
+ komnavn: "Roskilde",
+ },
+ {
+ komkode: "230",
+ komnavn: "Rudersdal",
+ },
+ {
+ komkode: "741",
+ komnavn: "Samsø",
+ },
+ {
+ komkode: "740",
+ komnavn: "Silkeborg",
+ },
+ {
+ komkode: "746",
+ komnavn: "Skanderborg",
+ },
+ {
+ komkode: "779",
+ komnavn: "Skive",
+ },
+ {
+ komkode: "330",
+ komnavn: "Slagelse",
+ },
+ {
+ komkode: "269",
+ komnavn: "Solrød",
+ },
+ {
+ komkode: "540",
+ komnavn: "Sønderborg",
+ },
+ {
+ komkode: "340",
+ komnavn: "Sorø",
+ },
+ {
+ komkode: "336",
+ komnavn: "Stevns",
+ },
+ {
+ komkode: "671",
+ komnavn: "Struer",
+ },
+ {
+ komkode: "479",
+ komnavn: "Svendborg",
+ },
+ {
+ komkode: "706",
+ komnavn: "Syddjurs",
+ },
+ {
+ komkode: "185",
+ komnavn: "Tårnby",
+ },
+ {
+ komkode: "787",
+ komnavn: "Thisted",
+ },
+ {
+ komkode: "550",
+ komnavn: "Tønder",
+ },
+ {
+ komkode: "187",
+ komnavn: "Vallensbæk",
+ },
+ {
+ komkode: "573",
+ komnavn: "Varde",
+ },
+ {
+ komkode: "575",
+ komnavn: "Vejen",
+ },
+ {
+ komkode: "630",
+ komnavn: "Vejle",
+ },
+ {
+ komkode: "820",
+ komnavn: "Vesthimmerlands",
+ },
+ {
+ komkode: "791",
+ komnavn: "Viborg",
+ },
+ {
+ komkode: "390",
+ komnavn: "Vordingborg",
+ },
+];
export {
- LAYER_NAMES,
- WATER_LEVEL_KEY,
- SELECT_CHEMICAL_DIALOG_PREFIX,
- TEXT_FIELD_DIALOG_PREFIX,
- LIMIT_CHAR,
- VIEW_MATRIX,
- VIEW_ROW,
- FREE_PLAN_MAX_TIME_SERIES_COUNT,
- FREE_PLAN_MAX_PROFILES_COUNT,
- KOMMUNER,
- Variants
+ LAYER_NAMES,
+ WATER_LEVEL_KEY,
+ SELECT_CHEMICAL_DIALOG_PREFIX,
+ CUSTOM_LAYER_NAME,
+ USER_LAYER_NAME,
+ LOGIN_MODAL_DIALOG_PREFIX,
+ TEXT_FIELD_DIALOG_PREFIX,
+ LIMIT_CHAR,
+ VIEW_MATRIX,
+ VIEW_ROW,
+ FREE_PLAN_MAX_TIME_SERIES_COUNT,
+ FREE_PLAN_MAX_PROFILES_COUNT,
+ KOMMUNER,
};
diff --git a/browser/contexts/map/MapContext.js b/browser/contexts/map/MapContext.js
new file mode 100644
index 0000000..aa58e27
--- /dev/null
+++ b/browser/contexts/map/MapContext.js
@@ -0,0 +1,27 @@
+import React, { useState } from 'react';
+
+const MapContext = React.createContext();
+
+const MapProvider = props => {
+ const [selectedBoreholes, setSelectedBoreholes] = useState([]);
+ window.mapDataContext = {
+ selectedBoreholes, setSelectedBoreholes
+ }
+
+ return (
+
+ {props.children}
+
+ )
+}
+
+function useMapContext() {
+ const context = React.useContext(MapContext);
+ if(context === undefined){
+ throw new Error('useAuth must be used within a AuthProvider')
+ }
+ return context;
+
+}
+
+export { MapProvider, useMapContext }
diff --git a/browser/contexts/project/ProjectContext.js b/browser/contexts/project/ProjectContext.js
new file mode 100644
index 0000000..037d996
--- /dev/null
+++ b/browser/contexts/project/ProjectContext.js
@@ -0,0 +1,10 @@
+import React from 'react';
+
+const ProjectContext = React.createContext({
+ activePlots: [],
+ setActivePlots: (plots) => {console.log("Setting active plots")},
+ activeProfiles: [],
+ setActiveProfiles: (profiles) => {}
+});
+
+export default ProjectContext;
diff --git a/browser/contexts/project/ProjectProvider.js b/browser/contexts/project/ProjectProvider.js
new file mode 100644
index 0000000..893efbe
--- /dev/null
+++ b/browser/contexts/project/ProjectProvider.js
@@ -0,0 +1,15 @@
+import { useState, useEffect } from 'react';
+import ProjectContext from './ProjectContext';
+
+function ProjectProvider(props) {
+ const [activePlots, setActivePlots] = useState([]);
+ const [activeProfiles, setActiveProfiles] = useState([]);
+
+ return (
+
+ {props.children}
+
+ )
+}
+
+export default ProjectProvider;
diff --git a/browser/helpers/aggregate.js b/browser/helpers/aggregate.js
new file mode 100644
index 0000000..11f6e0b
--- /dev/null
+++ b/browser/helpers/aggregate.js
@@ -0,0 +1,46 @@
+import _ from "lodash";
+import moment from "moment";
+
+const func_map = {
+ mean: _.meanBy,
+ sum: _.sumBy,
+};
+
+function aggregate(x, y, window, func) {
+ const grouped = _(
+ x.map((elem, index) => {
+ return {
+ x: moment(elem).startOf(window).format("YYYY-MM-DD HH:mm:ss.SSS"),
+ y: y[index],
+ };
+ })
+ )
+ .groupBy("x")
+ .mapValues((item) => {
+ return func_map[func](item, "y");
+ })
+ .value();
+
+ var start = moment([2020, 1, 1]);
+ var end = moment([2020, 1, 1]);
+ var width = start.diff(end.add(1, window));
+ const entries = Object.entries(grouped);
+ if (func === "mean") {
+ return {
+ x: entries.map((elem) => elem[0]),
+ y: entries.map((elem) => elem[1]),
+ };
+ } else {
+ return {
+ x: entries.map((elem) =>
+ moment(elem[0])
+ .add(width / 2, "ms")
+ .format("YYYY-MM-DD HH:mm:ss.SSS")
+ ),
+ y: entries.map((elem) => elem[1]),
+ width: new Array(entries.length).fill(width),
+ };
+ }
+}
+
+export { aggregate };
diff --git a/browser/helpers/colors.js b/browser/helpers/colors.js
new file mode 100644
index 0000000..9167ae0
--- /dev/null
+++ b/browser/helpers/colors.js
@@ -0,0 +1,16 @@
+function hexToRgbA(hex, opacity){
+ var c;
+ if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){
+ c= hex.substring(1).split('');
+ if(c.length== 3){
+ c= [c[0], c[0], c[1], c[1], c[2], c[2]];
+ }
+ c= '0x'+c.join('');
+ return 'rgba(' + [(c>>16)&255, (c>>8)&255, c&255, (opacity || 1)].join(',') + ')';
+ }
+ return hex;
+}
+
+export {
+ hexToRgbA,
+}
diff --git a/browser/helpers/common.js b/browser/helpers/common.js
new file mode 100644
index 0000000..c5663f2
--- /dev/null
+++ b/browser/helpers/common.js
@@ -0,0 +1,15 @@
+function getNewPlotId(plots) {
+ let newId = plots.length + 1;
+ if (newId > 1) {
+ let existingPlot = plots.find((plot) => plot.id == `plot_${newId}`);
+ while (existingPlot != null) {
+ newId += 1;
+ existingPlot = plots.find((plot) => plot.id == `plot_${newId}`);
+ }
+ }
+ return newId;
+}
+
+export {
+ getNewPlotId
+}
diff --git a/browser/helpers/show_loginmodal.js b/browser/helpers/show_loginmodal.js
new file mode 100644
index 0000000..7a48e78
--- /dev/null
+++ b/browser/helpers/show_loginmodal.js
@@ -0,0 +1,25 @@
+import React from "react";
+
+import { LOGIN_MODAL_DIALOG_PREFIX } from "./../constants";
+import { Provider } from "react-redux";
+import reduxStore from "./../redux/store";
+import ThemeProvider from "../themes/ThemeProvider";
+import LoginModal from "../components/LoginModal";
+
+function showLoginModal() {
+ // const placeholderId = `${LOGIN_MODAL_DIALOG_PREFIX}-placeholder`;
+
+ ReactDOM.render(
+ ,
+ document.getElementById(LOGIN_MODAL_DIALOG_PREFIX)
+ );
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("show");
+}
+
+export { showLoginModal };
diff --git a/browser/helpers/show_subscriptionDialogue.js b/browser/helpers/show_subscriptionDialogue.js
new file mode 100644
index 0000000..c5a8e24
--- /dev/null
+++ b/browser/helpers/show_subscriptionDialogue.js
@@ -0,0 +1,56 @@
+import SubscriptionDialogue from "../components/SubscriptionDialogue";
+import { setDashboardMode } from "../redux/actions";
+var ReactDOM = require("react-dom");
+const config = require("../../../../config/config.js");
+
+const session = require("./../../../session/browser/index");
+
+function showSubscription() {
+ var subscriptionDialoguePlaceholderId = "upgrade-modal";
+ const onCloseButtonClick = () => {
+ $("#" + subscriptionDialoguePlaceholderId).modal("hide");
+ };
+
+ const openAbonnement = () => {
+ setDashboardMode("minimized");
+ $("#watsonc-limits-reached-text").hide();
+ ReactDOM.render(
+ ,
+ document.getElementById(subscriptionDialoguePlaceholderId)
+ );
+ $("#" + subscriptionDialoguePlaceholderId).modal("show");
+ };
+
+ openAbonnement();
+}
+
+function showSubscriptionIfFree(other_logic = true) {
+ const isFree = session.getProperties()?.["license"] !== "premium";
+ const email = session?.getEmail();
+
+ const premiumEmailExtensions =
+ JSON.parse(
+ JSON.stringify(config?.extensionConfig?.watsonc?.premiumEmailExtensions)
+ ) || [];
+
+ if (premiumEmailExtensions.length > 0 && email) {
+ const emailDomain = email.split("@")[1];
+ premiumEmailExtensions.forEach((domain) => {
+ const regex = new RegExp(domain.replace(/\./g, "\\."), "i");
+ if (regex.test(emailDomain)) {
+ return false;
+ }
+ });
+ }
+
+ if (isFree && other_logic) {
+ showSubscription();
+ return true;
+ }
+ return false;
+}
+
+export { showSubscription, showSubscriptionIfFree };
diff --git a/browser/index.js b/browser/index.js
index a665850..28ccdae 100644
--- a/browser/index.js
+++ b/browser/index.js
@@ -1,62 +1,77 @@
-'use strict';
-
-import {Provider} from 'react-redux';
-
-import PlotManager from './PlotManager';
-import ModalComponent from './components/ModalComponent';
-import DashboardComponent from './components/DashboardComponent';
-import MenuTimeSeriesComponent from './components/MenuTimeSeriesComponent';
-import MenuDataSourceAndTypeSelectorComponent from './components/MenuDataSourceAndTypeSelectorComponent';
-import MenuProfilesComponent from './components/MenuProfilesComponent';
-import IntroModal from './components/IntroModal';
-import Modal from './components/modal/Modal';
-import AnalyticsComponent from './components/AnalyticsComponent';
-import {LAYER_NAMES, WATER_LEVEL_KEY, KOMMUNER} from './constants';
-import trustedIpAddresses from './trustedIpAddresses';
-
-
-import reduxStore from './redux/store';
-import {setAuthenticated} from './redux/actions';
-
-const symbolizer = require('./symbolizer');
-
-const utils = require('./utils');
-
-const evaluateMeasurement = require('./evaluateMeasurement');
+"use strict";
+
+import { Provider } from "react-redux";
+import "regenerator-runtime/runtime";
+
+import AnalyticsComponent from "./components/AnalyticsComponent";
+import MenuProfilesComponent from "./components/MenuProfilesComponent";
+import {
+ KOMMUNER,
+ LAYER_NAMES,
+ WATER_LEVEL_KEY,
+ LOGIN_MODAL_DIALOG_PREFIX,
+} from "./constants";
+import trustedIpAddresses from "./trustedIpAddresses";
+import ThemeProvider from "./themes/ThemeProvider";
+import DataSelectorDialogue from "./components/dataselector/DataSelectorDialogue";
+import DashboardWrapper from "./components/DashboardWrapper";
+import TopBar from "./components/TopBar";
+import LoginModal from "./components/LoginModal";
+import { showSubscriptionIfFree } from "./helpers/show_subscriptionDialogue";
+
+import reduxStore from "./redux/store";
+import {
+ addBoreholeFeature,
+ clearBoreholeFeatures,
+ setAuthenticated,
+ setBoreholeFeatures,
+ setCategories,
+} from "./redux/actions";
+
+const symbolizer = require("./symbolizer");
+
+const utils = require("./utils");
const MODULE_NAME = `watsonc`;
/**
* The feature dialog constants
*/
-const FEATURE_CONTAINER_ID = 'watsonc-features-dialog';
-const FORM_FEATURE_CONTAINER_ID = 'watsonc-features-dialog-form';
+const FEATURE_CONTAINER_ID = "watsonc-features-dialog";
/**
* The plots dialog constants
*/
-const DASHBOARD_CONTAINER_ID = 'watsonc-plots-dialog-form';
+const DASHBOARD_CONTAINER_ID = "watsonc-plots-dialog-form";
let PLOTS_ID = `#` + DASHBOARD_CONTAINER_ID;
/**
*
* @type {*|exports|module.exports}
*/
-var cloud, switchLayer, backboneEvents, session = false;
-
+var cloud,
+ switchLayer,
+ backboneEvents,
+ session = false;
/**
*
* @type {*|exports|module.exports}
*/
var layerTree, layers, anchor, state, urlparser;
-var React = require('react');
+var React = require("react");
-var ReactDOM = require('react-dom');
+var ReactDOM = require("react-dom");
+var ReactDOMServer = require("react-dom/server");
-let dashboardComponentInstance = false, modalComponentInstance = false, infoModalInstance = false;
+let dashboardComponentInstance = false,
+ modalComponentInstance = false,
+ infoModalInstance = false;
+let dashboardShellInstance = false;
-let lastSelectedChemical = false, categoriesOverall = false, enabledLoctypeIds = [];
+let lastSelectedChemical = false,
+ categoriesOverall = false,
+ enabledLoctypeIds = [];
let _self = false;
@@ -67,9 +82,6 @@ let lastTitleAsLink = null;
let dataSource = [];
let boreholesDataSource = [];
-let waterLevelDataSource = [];
-
-let previousZoom = -1;
let store;
@@ -79,614 +91,821 @@ let names = {};
let currentRasterLayer = null;
-const DATA_SOURCES = [{
- originalLayerKey: LAYER_NAMES[0],
- additionalKey: ``,
- title: __(`Jupiter drilling`)
-}, {
- originalLayerKey: LAYER_NAMES[1],
- additionalKey: `1`,
- title: "Online stationer"
-}, {
- originalLayerKey: LAYER_NAMES[1],
- additionalKey: `3`,
- title: "Online stationer"
-}, {
- originalLayerKey: LAYER_NAMES[1],
- additionalKey: `4`,
- title: "Online stationer"
-}, {
- originalLayerKey: LAYER_NAMES[3],
- additionalKey: ``,
- title: "Pesticidoverblik"
-}];
-
/**
*
* @type {{set: module.exports.set, init: module.exports.init}}
*/
module.exports = module.exports = {
- set: function (o) {
- cloud = o.cloud;
- switchLayer = o.switchLayer;
- backboneEvents = o.backboneEvents;
- layers = o.layers;
- layerTree = o.layerTree;
- anchor = o.anchor;
- state = o.state;
- urlparser = o.urlparser;
- if (o.extensions && o.extensions.session) {
- session = o.extensions.session.index;
- }
-
- _self = this;
- return this;
- },
-
- init: function () {
- state.listenTo(MODULE_NAME, _self);
- state.listen(MODULE_NAME, `plotsUpdate`);
- state.listen(MODULE_NAME, `chemicalChange`);
- state.listen(MODULE_NAME, `enabledLoctypeIdsChange`);
-
- this.initializeSearchBar();
-
- let queryParams = new URLSearchParams(window.location.search);
- let licenseToken = queryParams.get('license');
- let license = null;
-
- if (licenseToken) {
- license = JSON.parse(base64.decode(licenseToken.split('.')[1]));
- if (typeof license === 'object') {
- license = license.license;
-
- }
- }
- if (trustedIpAddresses.includes(window._vidiIp)) {
- license = "premium";
- }
-
- if (license === "premium") {
- $("#watsonc-licens-btn1").html("");
- $("#watsonc-licens-btn2").html("Valgt");
- $("#watsonc-licens-btn2").attr("disabled", true);
- $("#watsonc-licens-btn2").css("pointer-events", "none");
-
- } else {
- $("#watsonc-licens-btn1").html("Valgt");
- $("#watsonc-licens-btn2").html("Vælg");
- }
-
- $("#btn-plan").on("click", () => {
- $('#watsonc-limits-reached-text').hide();
- $('#upgrade-modal').modal('show');
- })
-
- backboneEvents.get().on(`session:authChange`, authenticated => {
- reduxStore.dispatch(setAuthenticated(authenticated));
- });
+ set: function (o) {
+ cloud = o.cloud;
+ switchLayer = o.switchLayer;
+ backboneEvents = o.backboneEvents;
+ layers = o.layers;
+ layerTree = o.layerTree;
+ anchor = o.anchor;
+ state = o.state;
+ urlparser = o.urlparser;
+ if (o.extensions && o.extensions.session) {
+ session = o.extensions.session.index;
+ }
+ _self = this;
+ return this;
+ },
- backboneEvents.get().on("ready:meta", function () {
- setTimeout(() => {
- $(".panel-title a").trigger("click");
- }, 1000);
+ init: function () {
+ state.listenTo(MODULE_NAME, _self);
+ state.listen(MODULE_NAME, `plotsUpdate`);
+ state.listen(MODULE_NAME, `chemicalChange`);
+ state.listen(MODULE_NAME, `enabledLoctypeIdsChange`);
- });
+ state.getModuleState(MODULE_NAME).then((initialState) => {
+ _self.applyState(initialState);
+ });
- $('#watsonc-plots-dialog-form').click(function () {
- $('#watsonc-plots-dialog-form').css('z-index', '1000');
+ this.initializeSearchBar();
- if ($('#watsonc-features-dialog').css('z-index') === '1000') {
- $('#watsonc-features-dialog').css('z-index', '100');
- } else {
- $('#watsonc-features-dialog').css('z-index', '10');
- }
+ let queryParams = new URLSearchParams(window.location.search);
+ let licenseToken = queryParams.get("license");
+ let license = null;
- if ($('#search-ribbon').css('z-index') === '1000') {
- $('#search-ribbon').css('z-index', '100');
- } else {
- $('#search-ribbon').css('z-index', '10');
- }
- });
-
- $('#watsonc-features-dialog').click(function () {
- _self.bringFeaturesDialogToFront();
- });
-
- $('#search-ribbon').click(function () {
- if ($('#watsonc-plots-dialog-form').css('z-index') === '1000') {
- $('#watsonc-plots-dialog-form').css('z-index', '100');
- } else {
- $('#watsonc-plots-dialog-form').css('z-index', '10');
- }
-
- if ($('#watsonc-features-dialog').css('z-index') === '1000') {
- $('#watsonc-features-dialog').css('z-index', '100');
- } else {
- $('#watsonc-features-dialog').css('z-index', '10');
- }
-
- $('#search-ribbon').css('z-index', '1000');
- });
-
- var lc = L.control.locate({
- drawCircle: false
- }).addTo(cloud.get().map);
+ if (licenseToken) {
+ license = JSON.parse(base64.decode(licenseToken.split(".")[1]));
+ if (typeof license === "object") {
+ license = license.license;
+ }
+ }
+ if (trustedIpAddresses.includes(window._vidiIp)) {
+ license = "premium";
+ }
- $(`#search-border`).trigger(`click`);
+ if (license === "premium") {
+ $("#watsonc-licens-btn1").html("");
+ $("#watsonc-licens-btn2").html("Valgt");
+ $("#watsonc-licens-btn2").attr("disabled", true);
+ $("#watsonc-licens-btn2").css("pointer-events", "none");
+ } else {
+ $("#watsonc-licens-btn1").html("Valgt");
+ $("#watsonc-licens-btn2").html("Vælg");
+ }
- $(`#js-open-state-snapshots-panel`).click(() => {
- $(`[href="#state-snapshots-content"]`).trigger(`click`);
- });
+ $("#btn-plan").on("click", () => {
+ $("#watsonc-limits-reached-text").hide();
+ $("#upgrade-modal").modal("show");
+ });
- $(`#js-open-watsonc-panel`).click(() => {
- $(`[href="#watsonc-content"]`).trigger(`click`);
- });
+ backboneEvents.get().on(`session:authChange`, (authenticated) => {
+ reduxStore.dispatch(setAuthenticated(authenticated));
+ });
+ backboneEvents.get().on("ready:meta", function () {
+ setTimeout(() => {
$(".panel-title a").trigger("click");
-
- // Turn on raster layer with all boreholes.
- switchLayer.init(LAYER_NAMES[2], true, true, false);
-
- $.ajax({
- url: '/api/sql/jupiter?q=SELECT * FROM codes.compunds_view&base64=false&lifetime=10800',
- scriptCharset: "utf-8",
- success: function (response) {
- if (`features` in response) {
- categories = {};
- limits = {};
-
- response.features.map(function (v) {
- categories[v.properties.kategori.trim()] = {};
- names[v.properties.compundno] = v.properties.navn;
- });
-
- names[WATER_LEVEL_KEY] = "Vandstand";
-
- for (var key in categories) {
- response.features.map(function (v) {
- if (key === v.properties.kategori) {
- categories[key][v.properties.compundno] = v.properties.navn;
- limits["_" + v.properties.compundno] = [v.properties.attention || 0, v.properties.limit || 0];
- }
- });
- }
-
- _self.buildBreadcrumbs();
-
- categoriesOverall = {};
- categoriesOverall[LAYER_NAMES[0]] = categories;
- categoriesOverall[LAYER_NAMES[0]]["Vandstand"] = {"0": WATER_LEVEL_KEY};
- categoriesOverall[LAYER_NAMES[1]] = {"Vandstand": {"0": WATER_LEVEL_KEY}};
-
- if (infoModalInstance) infoModalInstance.setCategories(categoriesOverall);
-
- // Setup menu
- let dd = $('li .dropdown-toggle');
- dd.on('click', function (event) {
- $(".dropdown-top").not($(this).parent()).removeClass('open');
- $('.dropdown-submenu').removeClass('open');
- $(this).parent().toggleClass('open');
- });
-
- // Open intro modal only if there is no predefined state
- if (!urlparser.urlVars || !urlparser.urlVars.state) {
- _self.openMenuModal(true);
- } else {
- _self.openMenuModal(false);
- }
-
- backboneEvents.get().trigger(`${MODULE_NAME}:initialized`);
- } else {
- console.error(`Unable to request codes.compunds`);
- }
- },
- error: function () {
- }
- });
-
- state.getState().then(applicationState => {
- $(PLOTS_ID).attr(`style`, `
+ }, 1000);
+ });
+
+ $("#watsonc-plots-dialog-form").click(function () {
+ $("#watsonc-plots-dialog-form").css("z-index", "1000");
+
+ if ($("#watsonc-features-dialog").css("z-index") === "1000") {
+ $("#watsonc-features-dialog").css("z-index", "100");
+ } else {
+ $("#watsonc-features-dialog").css("z-index", "10");
+ }
+
+ if ($("#search-ribbon").css("z-index") === "1000") {
+ $("#search-ribbon").css("z-index", "100");
+ } else {
+ $("#search-ribbon").css("z-index", "10");
+ }
+ });
+
+ $("#watsonc-features-dialog").click(function () {
+ _self.bringFeaturesDialogToFront();
+ });
+
+ $("#watsonc-data-sources").on("click", () => {
+ $("#watsonc-menu-dialog").modal("show");
+ });
+
+ $("#search-ribbon").click(function () {
+ if ($("#watsonc-plots-dialog-form").css("z-index") === "1000") {
+ $("#watsonc-plots-dialog-form").css("z-index", "100");
+ } else {
+ $("#watsonc-plots-dialog-form").css("z-index", "10");
+ }
+
+ if ($("#watsonc-features-dialog").css("z-index") === "1000") {
+ $("#watsonc-features-dialog").css("z-index", "100");
+ } else {
+ $("#watsonc-features-dialog").css("z-index", "10");
+ }
+
+ $("#search-ribbon").css("z-index", "1000");
+ });
+
+ var lc = L.control
+ .locate({
+ drawCircle: false,
+ })
+ .addTo(cloud.get().map);
+
+ $(`#search-border`).trigger(`click`);
+
+ $(`#js-open-state-snapshots-panel`).click(() => {
+ //$(`[href="#state-snapshots-content"]`).trigger(`click`);
+ });
+
+ $(`#state-snapshots-content`).click((e) => {
+ if (showSubscriptionIfFree()) {
+ var elem = document.getElementById("state-snapshots");
+ elem.style.pointerEvents = "none";
+ }
+
+ //$(`[href="#state-snapshots-content"]`).trigger(`click`);
+ });
+
+ $("#projects-trigger").click((e) => {
+ e.preventDefault();
+ //reduxStore.dispatch(setDashboardContent('projects'));
+ });
+
+ $("#main-tabs a").on("click", function (e) {
+ $("#module-container.slide-right").css("right", "0");
+ });
+
+ $(document).on(
+ "click",
+ "#module-container .modal-header button",
+ function (e) {
+ e.preventDefault();
+ $("#module-container.slide-right").css("right", "-" + 466 + "px");
+ $("#side-panel ul li").removeClass("active");
+ $("#search-ribbon").css("right", "-550px");
+ $("#pane").css("right", "0");
+ $("#map").css("width", "100%");
+ }
+ );
+
+ $(`#js-open-watsonc-panel`).click(() => {
+ $(`[href="#watsonc-content"]`).trigger(`click`);
+ });
+
+ $(".panel-title a").trigger("click");
+
+ // Turn on raster layer with all boreholes.
+ // switchLayer.init(LAYER_NAMES[2], true, true, false);
+ ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById("top-bar")
+ );
+
+ ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById(LOGIN_MODAL_DIALOG_PREFIX)
+ );
+ $("#" + LOGIN_MODAL_DIALOG_PREFIX).modal("hide");
+
+ $.ajax({
+ url: "/api/sql/jupiter?q=SELECT * FROM codes.compunds_view&base64=false&lifetime=10800",
+ scriptCharset: "utf-8",
+ success: function (response) {
+ if (`features` in response) {
+ categories = {};
+ limits = {};
+
+ response.features.map(function (v) {
+ categories[v.properties.kategori.trim()] = {};
+ names[v.properties.compundno] = v.properties.our_name;
+ });
+
+ names[WATER_LEVEL_KEY] = "Vandstand";
+
+ for (var key in categories) {
+ response.features.map(function (v) {
+ if (key === v.properties.kategori) {
+ categories[key][v.properties.compundno] = v.properties.our_name;
+ limits["_" + v.properties.compundno] = [
+ v.properties.attention || 0,
+ v.properties.limit || 0,
+ ];
+ }
+ });
+ }
+ reduxStore.dispatch(setLimits(limits));
+
+ _self.buildBreadcrumbs();
+
+ categoriesOverall = {};
+ categoriesOverall[LAYER_NAMES[0]] = categories;
+ categoriesOverall[LAYER_NAMES[0]]["Vandstand"] = {
+ 0: WATER_LEVEL_KEY,
+ };
+ categoriesOverall[LAYER_NAMES[1]] = {
+ Vandstand: { 0: WATER_LEVEL_KEY },
+ };
+
+ if (infoModalInstance)
+ infoModalInstance.setCategories(categoriesOverall);
+ reduxStore.dispatch(setCategories(categories));
+
+ // Setup menu
+ let dd = $("li .dropdown-toggle");
+ dd.on("click", function (event) {
+ $(".dropdown-top").not($(this).parent()).removeClass("open");
+ $(".dropdown-submenu").removeClass("open");
+ $(this).parent().toggleClass("open");
+ });
+
+ // Open intro modal only if there is no predefined state
+ if (!urlparser.urlVars || !urlparser.urlVars.state) {
+ _self.openMenuModal(true);
+ } else {
+ _self.openMenuModal(false);
+ }
+
+ backboneEvents.get().trigger(`${MODULE_NAME}:initialized`);
+ } else {
+ console.error(`Unable to request codes.compunds`);
+ }
+ },
+ error: function () {},
+ });
+
+ state.getState().then((applicationState) => {
+ $(PLOTS_ID).attr(
+ `style`,
+ `
margin-bottom: 0px;
- width: 80%;
- max-width: 80%;
- right: 10%;
- left: 10%;
- bottom: 0px;`);
-
- LAYER_NAMES.map(layerName => {
- layerTree.setOnEachFeature(layerName, (clickedFeature, layer) => {
- layer.on("click", function (e) {
- $("#" + FEATURE_CONTAINER_ID).animate({
- bottom: "0"
- }, 500, function () {
- $("#" + FEATURE_CONTAINER_ID).find(".expand-less").show();
- $("#" + FEATURE_CONTAINER_ID).find(".expand-more").hide();
- });
-
- let intersectingFeatures = [];
- if (e.latlng) {
- var clickBounds = L.latLngBounds(e.latlng, e.latlng);
- let res = [156543.033928, 78271.516964, 39135.758482, 19567.879241, 9783.9396205,
- 4891.96981025, 2445.98490513, 1222.99245256, 611.496226281, 305.748113141, 152.87405657,
- 76.4370282852, 38.2185141426, 19.1092570713, 9.55462853565, 4.77731426782, 2.38865713391,
- 1.19432856696, 0.597164283478, 0.298582141739, 0.149291, 0.074645535];
-
- let distance = 10 * res[cloud.get().getZoom()];
-
- let mapObj = cloud.get().map;
- for (var l in mapObj._layers) {
- var overlay = mapObj._layers[l];
- if (overlay._layers) {
- for (var f in overlay._layers) {
- var feature = overlay._layers[f];
- var bounds;
- if (feature.getBounds) {
- bounds = feature.getBounds();
- } else if (feature._latlng) {
- let circle = new L.circle(feature._latlng, {radius: distance});
- // DIRTY HACK
- circle.addTo(mapObj);
- bounds = circle.getBounds();
- circle.removeFrom(mapObj);
- }
-
- try {
- if (bounds && clickBounds.intersects(bounds) && overlay.id) {
- intersectingFeatures.push(feature.feature);
- }
- } catch (e) {
- console.log(e);
- }
- }
- }
- }
- } else {
- // In case marker "click" event was triggered from the code
- intersectingFeatures.push(e.target.feature);
- }
-
- let titleAsLink = false;
-
- if (layerName.indexOf(LAYER_NAMES[0]) > -1) {
- titleAsLink = true;
- }
-
- let clickedFeatureAlreadyDetected = false;
- intersectingFeatures.map(feature => {
- if (feature.properties.boreholeno === clickedFeature.properties.boreholeno) {
- clickedFeatureAlreadyDetected = true;
- }
- });
-
- if (clickedFeatureAlreadyDetected === false) intersectingFeatures.unshift(clickedFeature);
-
- let boreholes = [];
-
- intersectingFeatures.map((feature) => {
- boreholes.push(feature.properties.boreholeno)
- });
-
- let qLayer;
- if (layerName.indexOf(LAYER_NAMES[0]) > -1 || layerName.indexOf(LAYER_NAMES[3]) > -1) {
- qLayer = "chemicals.boreholes_time_series_with_chemicals";
- } else {
- qLayer = "sensor.sensordata_with_correction";
- // Filter NaN values, so SQL doesn't return type error
- boreholes = boreholes.filter((v) => {
- if (!isNaN(v)) {
- return v;
- }
- });
- }
-
- // Lazy load features
- $.ajax({
- url: "/api/sql/jupiter?srs=25832&q=SELECT * FROM " + qLayer + " WHERE boreholeno in('" + boreholes.join("','") + "')",
- scriptCharset: "utf-8",
- success: function (response) {
-
- dataSource = [];
- boreholesDataSource = response.features;
- dataSource = dataSource.concat(boreholesDataSource);
- if (dashboardComponentInstance) {
- dashboardComponentInstance.setDataSource(dataSource);
- }
-
-
- _self.createModal(response.features, false, titleAsLink, false);
- if (!dashboardComponentInstance) {
- throw new Error(`Unable to find the component instance`);
- }
- },
- error: function () {
- }
- });
- });
- }, "watsonc");
-
- let svgCirclePart = symbolizer.getSymbol(layerName);
- if (svgCirclePart) {
- layerTree.setPointToLayer(layerName, (feature, latlng) => {
- let renderIcon = true;
- if (layerName === LAYER_NAMES[1]) {
- if (feature.properties.loctypeid &&
- (enabledLoctypeIds.indexOf(parseInt(feature.properties.loctypeid) + '') === -1 && enabledLoctypeIds.indexOf(parseInt(feature.properties.loctypeid)) === -1)) {
- renderIcon = false;
- }
- } else {
- return L.circleMarker(latlng);
- }
-
- if (renderIcon) {
- let participatingIds = [];
- if (dashboardComponentInstance) {
- let plots = dashboardComponentInstance.getPlots();
- plots.map(plot => {
- participatingIds = participatingIds.concat(_self.participatingIds(plot));
- });
- }
-
- let highlighted = (participatingIds.indexOf(feature.properties.boreholeno) > -1);
- let localSvgCirclePart = symbolizer.getSymbol(layerName, {
- online: feature.properties.status,
- shape: feature.properties.loctypeid,
- highlighted
- });
-
- let icon = L.icon({
- iconUrl: 'data:image/svg+xml;base64,' + btoa(localSvgCirclePart),
- iconAnchor: [8, 33],
- iconSize: [30, 30],
- watsoncStatus: `default`
- });
-
- return L.marker(latlng, {icon});
- } else {
- return null;
- }
- });
+ width: 96%;
+ max-width: 96%;
+ right: 2%;
+ left: 2%;
+ bottom: 0px;`
+ );
+
+ LAYER_NAMES.map((layerName) => {
+ layerTree.setOnEachFeature(
+ layerName,
+ (clickedFeature, layer) => {
+ layer.on("click", (e) => {
+ $("#" + FEATURE_CONTAINER_ID).animate(
+ {
+ bottom: "0",
+ },
+ 500,
+ function () {
+ $("#" + FEATURE_CONTAINER_ID)
+ .find(".expand-less")
+ .show();
+ $("#" + FEATURE_CONTAINER_ID)
+ .find(".expand-more")
+ .hide();
}
- });
-
- // Renewing the already created store by rebuilding the layer tree
- setTimeout(() => {
-
- setTimeout(() => {
- layerTree.create(false, [], true).then(() => {
- //layerTree.reloadLayer(LAYER_NAMES[0]);
- if (layerTree.getActiveLayers().indexOf(LAYER_NAMES[1]) > -1) {
- layerTree.reloadLayer(LAYER_NAMES[1]);
- }
- if (layerTree.getActiveLayers().indexOf(LAYER_NAMES[0]) > -1) {
- layerTree.reloadLayer(LAYER_NAMES[0]);
- }
- if (layerTree.getActiveLayers().indexOf(LAYER_NAMES[3]) > -1) {
- layerTree.reloadLayer(LAYER_NAMES[3]);
- }
- });
- }, 500);
- }, 100);
-
- const proceedWithInitialization = () => {
- // Setting up feature dialog
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-less").on("click", function () {
- $("#" + FEATURE_CONTAINER_ID).animate({
- bottom: (($("#" + FEATURE_CONTAINER_ID).height() * -1) + 30) + "px"
- }, 500, function () {
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-less").hide();
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-more").show();
- });
- });
-
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-more").on("click", function () {
- $("#" + FEATURE_CONTAINER_ID).animate({
- bottom: "0"
- }, 500, function () {
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-less").show();
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-more").hide();
- });
- });
-
- $(`#` + FEATURE_CONTAINER_ID).find(".close-hide").on("click", function () {
- $("#" + FEATURE_CONTAINER_ID).animate({
- bottom: "-100%"
- }, 500, function () {
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-less").show();
- $(`#` + FEATURE_CONTAINER_ID).find(".expand-more").hide();
- });
- });
-
- // Initializing data source and types selector
- $(`[data-module-id="data-source-and-types-selector"]`).click(() => {
- if ($(`#data-source-and-types-selector-content`).children().length === 0) {
- try {
- ReactDOM.render(
- -1} // DIRTY HACK All raster layers has _ in name
- layers={DATA_SOURCES}/>
- , document.getElementById(`data-source-and-types-selector-content`));
- } catch (e) {
- console.log(e);
- }
- }
- });
-
- // Initializing TimeSeries management component
- $(`[data-module-id="timeseries"]`).click(() => {
- if ($(`#watsonc-timeseries`).children().length === 0) {
- try {
- ReactDOM.render(
- , document.getElementById(`watsonc-timeseries`));
- } catch (e) {
- console.error(e);
- }
- }
- });
-
- // Initializing profiles tab
- if ($(`#profile-drawing-content`).length === 0) throw new Error(`Unable to get the profile drawing tab`);
-
- // Initializing TimeSeries management component
- $(`[data-module-id="profile-drawing"]`).click(() => {
- try {
- ReactDOM.render(
-
- , document.getElementById(`profile-drawing-content`));
-
- backboneEvents.get().on(`reset:all reset:profile-drawing off:all`, () => {
- window.menuProfilesComponentInstance.stopDrawing();
+ );
+
+ let intersectingFeatures = [];
+ if (e.latlng) {
+ var clickBounds = L.latLngBounds(e.latlng, e.latlng);
+ let res = [
+ 156543.033928, 78271.516964, 39135.758482, 19567.879241,
+ 9783.9396205, 4891.96981025, 2445.98490513, 1222.99245256,
+ 611.496226281, 305.748113141, 152.87405657, 76.4370282852,
+ 38.2185141426, 19.1092570713, 9.55462853565, 4.77731426782,
+ 2.38865713391, 1.19432856696, 0.597164283478, 0.298582141739,
+ 0.149291, 0.074645535,
+ ];
+
+ let distance = 10 * res[cloud.get().getZoom()];
+
+ let mapObj = cloud.get().map;
+ for (var l in mapObj._layers) {
+ var overlay = mapObj._layers[l];
+ if (overlay._layers) {
+ for (var f in overlay._layers) {
+ var feature = overlay._layers[f];
+ var bounds;
+ if (feature.getBounds) {
+ bounds = feature.getBounds();
+ } else if (feature._latlng) {
+ let circle = new L.circle(feature._latlng, {
+ radius: distance,
});
- } catch (e) {
- console.error(e);
+ // DIRTY HACK
+ circle.addTo(mapObj);
+ bounds = circle.getBounds();
+ circle.removeFrom(mapObj);
+ }
+
+ try {
+ if (
+ bounds &&
+ clickBounds.intersects(bounds) &&
+ overlay.id
+ ) {
+ intersectingFeatures.push(feature.feature);
+ }
+ } catch (e) {
+ console.log(e);
+ }
}
- });
-
- if (dashboardComponentInstance) dashboardComponentInstance.onSetMin();
- };
-
- if (document.getElementById(DASHBOARD_CONTAINER_ID)) {
- let initialPlots = [];
- if (applicationState && `modules` in applicationState && MODULE_NAME in applicationState.modules && `plots` in applicationState.modules[MODULE_NAME]) {
- initialPlots = applicationState.modules[MODULE_NAME].plots;
+ }
}
-
- let initialProfiles = [];
- if (applicationState && `modules` in applicationState && MODULE_NAME in applicationState.modules && `profiles` in applicationState.modules[MODULE_NAME]) {
- initialProfiles = applicationState.modules[MODULE_NAME].profiles;
+ } else {
+ // In case marker "click" event was triggered from the code
+ intersectingFeatures.push(e.target.feature);
+ }
+
+ let titleAsLink = false;
+
+ if (layerName.indexOf(LAYER_NAMES[0]) > -1) {
+ titleAsLink = true;
+ }
+
+ let clickedFeatureAlreadyDetected = false;
+ intersectingFeatures.map((feature) => {
+ if (
+ feature.properties.boreholeno ===
+ clickedFeature.properties.boreholeno
+ ) {
+ clickedFeatureAlreadyDetected = true;
}
+ });
+
+ if (clickedFeatureAlreadyDetected === false)
+ intersectingFeatures.unshift(clickedFeature);
+
+ let boreholes = [];
+
+ intersectingFeatures.map((feature) => {
+ boreholes.push(feature.properties.boreholeno);
+ });
+
+ let qLayer;
+ if (
+ layerName.indexOf(LAYER_NAMES[0]) > -1 ||
+ layerName.indexOf(LAYER_NAMES[3]) > -1
+ ) {
+ qLayer = "chemicals.boreholes_time_series_with_chemicals";
+ } else {
+ qLayer = "sensor.sensordata_with_correction";
+ // Filter NaN values, so SQL doesn't return type error
+ boreholes = boreholes.filter((v) => {
+ if (!isNaN(v)) {
+ return v;
+ }
+ });
+ }
+
+ // Lazy load features
+ $.ajax({
+ url:
+ "/api/sql/jupiter?srs=25832&q=SELECT * FROM " +
+ qLayer +
+ " WHERE boreholeno in('" +
+ boreholes.join("','") +
+ "')",
+ scriptCharset: "utf-8",
+ success: function (response) {
+ dataSource = [];
+ boreholesDataSource = response.features;
+ dataSource = dataSource.concat(boreholesDataSource);
+ if (dashboardComponentInstance) {
+ dashboardComponentInstance.setDataSource(dataSource);
+ }
+
+ /* layer.bindPopup(ReactDOMServer.renderToString(),
+ { maxWidth: 500, className: 'map-decorator-popup' }); */
+ reduxStore.dispatch(setBoreholeFeatures(response.features));
+ _self.createModal(
+ response.features,
+ false,
+ titleAsLink,
+ false
+ );
+ if (!dashboardComponentInstance) {
+ throw new Error(`Unable to find the component instance`);
+ }
+ },
+ error: function () {},
+ });
+ });
+ },
+ "watsonc"
+ );
+
+ let svgCirclePart = symbolizer.getSymbol(layerName);
+ if (svgCirclePart) {
+ layerTree.setPointToLayer(layerName, (feature, latlng) => {
+ let renderIcon = true;
+ if (layerName === LAYER_NAMES[1]) {
+ if (
+ feature.properties.loctypeid &&
+ enabledLoctypeIds.indexOf(
+ parseInt(feature.properties.loctypeid) + ""
+ ) === -1 &&
+ enabledLoctypeIds.indexOf(
+ parseInt(feature.properties.loctypeid)
+ ) === -1
+ ) {
+ renderIcon = false;
+ }
+ } else {
+ return L.circleMarker(latlng);
+ }
- let plotManager = new PlotManager();
- plotManager.hydratePlotsFromUser(initialPlots).then(hydratedInitialPlots => { // User plots
- try {
- dashboardComponentInstance = ReactDOM.render( {
- backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
- if (plots) {
- _self.setStyleForPlots(plots);
-
- if (window.menuTimeSeriesComponentInstance) window.menuTimeSeriesComponentInstance.setPlots(plots);
- // Plots were updated from the DashboardComponent component
- if (modalComponentInstance) _self.createModal(false, plots);
- }
- }}
- onProfilesChange={(profiles = false) => {
- backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
- if (profiles && window.menuProfilesComponentInstance) window.menuProfilesComponentInstance.setProfiles(profiles);
- }}
- onActivePlotsChange={(activePlots, plots) => {
- backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
- if (window.menuTimeSeriesComponentInstance) window.menuTimeSeriesComponentInstance.setActivePlots(activePlots);
- if (modalComponentInstance) _self.createModal(false, plots);
- }}
- onActiveProfilesChange={(activeProfiles) => {
- backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
- if (window.menuProfilesComponentInstance) window.menuProfilesComponentInstance.setActiveProfiles(activeProfiles);
- }}
- onHighlightedPlotChange={(plotId, plots) => {
- _self.setStyleForHighlightedPlot(plotId, plots);
- if (window.menuTimeSeriesComponentInstance) window.menuTimeSeriesComponentInstance.setHighlightedPlot(plotId);
- }}/>, document.getElementById(DASHBOARD_CONTAINER_ID));
- } catch (e) {
- console.error(e);
- }
- proceedWithInitialization();
- }).catch(() => {
- console.error(`Unable to hydrate initial plots`, initialPlots);
+ if (renderIcon) {
+ let participatingIds = [];
+ if (dashboardComponentInstance) {
+ let plots = dashboardComponentInstance.getPlots();
+ plots.map((plot) => {
+ participatingIds = participatingIds.concat(
+ _self.participatingIds(plot)
+ );
});
+ }
+
+ let highlighted =
+ participatingIds.indexOf(feature.properties.boreholeno) > -1;
+ let localSvgCirclePart = symbolizer.getSymbol(layerName, {
+ online: feature.properties.status,
+ shape: feature.properties.loctypeid,
+ highlighted,
+ });
+
+ let icon = L.icon({
+ iconUrl:
+ "data:image/svg+xml;base64," + btoa(localSvgCirclePart),
+ iconAnchor: [8, 33],
+ iconSize: [30, 30],
+ watsoncStatus: `default`,
+ });
+
+ return L.marker(latlng, { icon });
} else {
- console.warn(`Unable to find the container for watsonc extension (element id: ${DASHBOARD_CONTAINER_ID})`);
+ return null;
}
- });
- $(`#search-border`).trigger(`click`);
+ });
+ }
+ });
- try {
+ // Renewing the already created store by rebuilding the layer tree
+ setTimeout(() => {
+ setTimeout(() => {
+ layerTree.create(false, [], true).then(() => {
+ //layerTree.reloadLayer(LAYER_NAMES[0]);
+ if (layerTree.getActiveLayers().indexOf(LAYER_NAMES[1]) > -1) {
+ layerTree.reloadLayer(LAYER_NAMES[1]);
+ }
+ if (layerTree.getActiveLayers().indexOf(LAYER_NAMES[0]) > -1) {
+ layerTree.reloadLayer(LAYER_NAMES[0]);
+ }
+ if (layerTree.getActiveLayers().indexOf(LAYER_NAMES[3]) > -1) {
+ layerTree.reloadLayer(LAYER_NAMES[3]);
+ }
+ });
+ }, 500);
+ }, 100);
+
+ const proceedWithInitialization = () => {
+ // Setting up feature dialog
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-less")
+ .on("click", function () {
+ $("#" + FEATURE_CONTAINER_ID).animate(
+ {
+ bottom: $("#" + FEATURE_CONTAINER_ID).height() * -1 + 30 + "px",
+ },
+ 500,
+ function () {
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-less")
+ .hide();
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-more")
+ .show();
+ }
+ );
+ });
+
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-more")
+ .on("click", function () {
+ $("#" + FEATURE_CONTAINER_ID).animate(
+ {
+ bottom: "0",
+ },
+ 500,
+ function () {
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-less")
+ .show();
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-more")
+ .hide();
+ }
+ );
+ });
+
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".close-hide")
+ .on("click", function () {
+ $("#" + FEATURE_CONTAINER_ID).animate(
+ {
+ bottom: "-100%",
+ },
+ 500,
+ function () {
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-less")
+ .show();
+ $(`#` + FEATURE_CONTAINER_ID)
+ .find(".expand-more")
+ .hide();
+ }
+ );
+ });
+
+ // Initializing TimeSeries management component
+
+ // Initializing profiles tab
+ if ($(`#profile-drawing-content`).length === 0)
+ throw new Error(`Unable to get the profile drawing tab`);
+
+ // Initializing TimeSeries management component
+ $(`[data-module-id="profile-drawing"]`).click(() => {
+ try {
ReactDOM.render(
- , document.getElementById("watsonc-analytics-content"));
- } catch (e) {
+
+
+ ,
+ document.getElementById(`profile-drawing-content`)
+ );
+
+ backboneEvents
+ .get()
+ .on(`reset:all reset:profile-drawing off:all`, () => {
+ window.menuProfilesComponentInstance.stopDrawing();
+ });
+ } catch (e) {
console.error(e);
+ }
+ });
+
+ // if (dashboardComponentInstance) dashboardComponentInstance.onSetMin();
+ };
+
+ if (document.getElementById(DASHBOARD_CONTAINER_ID)) {
+ let initialPlots = [];
+ if (
+ applicationState &&
+ `modules` in applicationState &&
+ MODULE_NAME in applicationState.modules &&
+ `plots` in applicationState.modules[MODULE_NAME]
+ ) {
+ initialPlots = applicationState.modules[MODULE_NAME].plots;
+ }
+ let initialProfiles = [];
+ if (
+ applicationState &&
+ `modules` in applicationState &&
+ MODULE_NAME in applicationState.modules &&
+ `profiles` in applicationState.modules[MODULE_NAME]
+ ) {
+ initialProfiles = applicationState.modules[MODULE_NAME].profiles;
}
- },
-
-
- openBorehole(boreholeIdentifier) {
- let mapLayers = layers.getMapLayers();
- let boreholeIsInViewport = false;
- mapLayers.map(layer => {
- if ([LAYER_NAMES[0], LAYER_NAMES[1]].indexOf(layer.id) > -1 && layer._layers) {
- for (let key in layer._layers) {
- if (layer._layers[key].feature && layer._layers[key].feature.properties && layer._layers[key].feature.properties.boreholeno) {
- if (layer._layers[key].feature.properties.boreholeno.trim() === boreholeIdentifier.trim()) {
- layer._layers[key].fire(`click`);
- boreholeIsInViewport = true;
- setTimeout(() => {
- _self.bringFeaturesDialogToFront();
- }, 500);
- }
- }
+ let reactRef = React.createRef();
+ try {
+ ReactDOM.render(
+ {
+ dashboardComponentInstance.deleteMeasurement(
+ plotId,
+ featureGid,
+ featureKey,
+ featureIntakeIndex
+ );
+ }}
+ onAddMeasurement={(
+ plotId,
+ featureGid,
+ featureKey,
+ featureIntakeIndex,
+ measurementsData,
+ relation
+ ) => {
+ dashboardComponentInstance.addMeasurement(
+ plotId,
+ featureGid,
+ featureKey,
+ featureIntakeIndex,
+ measurementsData,
+ relation
+ );
+ }}
+ onPlotsChange={(plots = false, context) => {
+ backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
+ if (plots) {
+ _self.setStyleForPlots(plots);
+
+ if (window.menuTimeSeriesComponentInstance)
+ window.menuTimeSeriesComponentInstance.setPlots(plots);
+ // Plots were updated from the DashboardComponent component
+ if (modalComponentInstance) _self.createModal(false, plots);
+ context.setActivePlots(_self.getExistingActivePlots());
}
+ }}
+ onProfilesChange={(profiles = false) => {
+ backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
+ if (profiles && window.menuProfilesComponentInstance)
+ window.menuProfilesComponentInstance.setProfiles(profiles);
+ }}
+ onActivePlotsChange={(activePlots, plots, context) => {
+ backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
+ if (window.menuTimeSeriesComponentInstance)
+ window.menuTimeSeriesComponentInstance.setActivePlots(
+ activePlots
+ );
+ if (modalComponentInstance) _self.createModal(false, plots);
+
+ context.setActivePlots(
+ plots.filter((plot) => activePlots.indexOf(plot.id) > -1)
+ );
+ }}
+ getAllPlots={() => {
+ return dashboardComponentInstance.getPlots();
+ }}
+ getAllProfiles={() => {
+ return dashboardComponentInstance.getProfiles();
+ }}
+ getDashboardItems={() => {
+ return dashboardComponentInstance.getDashboardItems();
+ }}
+ getActiveProfiles={() => {
+ return dashboardComponentInstance.getActiveProfileObjects();
+ }}
+ setPlots={(plots, activePlots) => {
+ dashboardComponentInstance.setPlots(plots);
+ dashboardComponentInstance.setActivePlots(activePlots);
+ }}
+ setItems={(plots) => {
+ dashboardComponentInstance.setItems(plots);
+ }}
+ setProfiles={(profiles, activeProfiles) => {
+ dashboardComponentInstance.setProfiles(profiles);
+ dashboardComponentInstance.setActiveProfiles(activeProfiles);
+ }}
+ onActiveProfilesChange={(activeProfiles, profiles, context) => {
+ backboneEvents.get().trigger(`${MODULE_NAME}:plotsUpdate`);
+ if (window.menuProfilesComponentInstance)
+ window.menuProfilesComponentInstance.setActiveProfiles(
+ activeProfiles
+ );
+ context.setActiveProfiles(
+ profiles.filter(
+ (profile) => activeProfiles.indexOf(profile.key) > -1
+ )
+ );
+ }}
+ onHighlightedPlotChange={(plotId, plots) => {
+ _self.setStyleForHighlightedPlot(plotId, plots);
+ if (window.menuTimeSeriesComponentInstance)
+ window.menuTimeSeriesComponentInstance.setHighlightedPlot(
+ plotId
+ );
+ }}
+ />,
+ document.getElementById("watsonc-plots-dialog-form-hidden")
+ );
+ dashboardComponentInstance = reactRef.current;
+ } catch (e) {
+ console.error(e);
+ }
+ proceedWithInitialization();
+ } else {
+ console.warn(
+ `Unable to find the container for watsonc extension (element id: ${DASHBOARD_CONTAINER_ID})`
+ );
+ }
+ });
+ $(`#search-border`).trigger(`click`);
+
+ try {
+ ReactDOM.render(
+ ,
+ document.getElementById("watsonc-analytics-content")
+ );
+ } catch (e) {
+ console.error(e);
+ }
+ },
+
+ let(boreholeIdentifier) {
+ let mapLayers = layers.getMapLayers();
+ let boreholeIsInViewport = false;
+ mapLayers.map((layer) => {
+ if (
+ [LAYER_NAMES[0], LAYER_NAMES[1]].indexOf(layer.id) > -1 &&
+ layer._layers
+ ) {
+ for (let key in layer._layers) {
+ if (
+ layer._layers[key].feature &&
+ layer._layers[key].feature.properties &&
+ layer._layers[key].feature.properties.boreholeno
+ ) {
+ if (
+ layer._layers[key].feature.properties.boreholeno.trim() ===
+ boreholeIdentifier.trim()
+ ) {
+ layer._layers[key].fire(`click`);
+ boreholeIsInViewport = true;
+ setTimeout(() => {
+ _self.bringFeaturesDialogToFront();
+ }, 500);
}
- });
-
- if (boreholeIsInViewport === false) {
- alert(__(`Requested borehole is not in a viewport (data is not loaded)`));
+ }
}
- },
+ }
+ });
- bringFeaturesDialogToFront() {
- if ($('#watsonc-plots-dialog-form').css('z-index') === '1000') {
- $('#watsonc-plots-dialog-form').css('z-index', '100');
- } else {
- $('#watsonc-plots-dialog-form').css('z-index', '10');
- }
+ if (boreholeIsInViewport === false) {
+ alert(__(`Requested borehole is not in a viewport (data is not loaded)`));
+ }
+ },
- $('#watsonc-features-dialog').css('z-index', '1000');
+ bringFeaturesDialogToFront() {
+ if ($("#watsonc-plots-dialog-form").css("z-index") === "1000") {
+ $("#watsonc-plots-dialog-form").css("z-index", "100");
+ } else {
+ $("#watsonc-plots-dialog-form").css("z-index", "10");
+ }
- if ($('#search-ribbon').css('z-index') === '1000') {
- $('#search-ribbon').css('z-index', '100');
- } else {
- $('#search-ribbon').css('z-index', '10');
- }
- },
-
- initializeSearchBar() {
- let searchBar = $(`#js-watsonc-search-field`);
- $(searchBar).parent().attr(`style`, `padding-top: 8px;`);
- $(searchBar).attr(`style`, `max-width: 200px; float: right;`);
- $(searchBar).append(`