+
diff --git a/src/client/app/components/conversion/ConversionViewComponent.tsx b/src/client/app/components/conversion/ConversionViewComponent.tsx
index 418931908..a762ac50a 100644
--- a/src/client/app/components/conversion/ConversionViewComponent.tsx
+++ b/src/client/app/components/conversion/ConversionViewComponent.tsx
@@ -37,7 +37,6 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
const handleClose = () => {
setShowEditModal(false);
}
- React.useEffect(() => undefined, [props.conversion])
// Create header from sourceId, destinationId identifiers
// Arrow is bidirectional if conversion is bidirectional and one way if not.
let arrowShown: string;
@@ -46,7 +45,7 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
} else {
arrowShown = ' → ';
}
- const header = String(unitDataById[props.conversion.sourceId].identifier + arrowShown + unitDataById[props.conversion.destinationId].identifier);
+ const header = String(unitDataById[props.conversion.sourceId]?.identifier + arrowShown + unitDataById[props.conversion.destinationId]?.identifier);
// Unlike the details component, we don't check if units are loaded since must come through that page.
@@ -56,10 +55,10 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
{header}
- {unitDataById[props.conversion.sourceId].identifier}
+ {unitDataById[props.conversion.sourceId]?.identifier}
- {unitDataById[props.conversion.destinationId].identifier}
+ {unitDataById[props.conversion.destinationId]?.identifier}
{translate(`TrueFalseType.${props.conversion.bidirectional.toString()}`)}
diff --git a/src/client/app/components/conversion/ConversionsDetailComponent.tsx b/src/client/app/components/conversion/ConversionsDetailComponent.tsx
index 8fb6a4274..d96d5ee51 100644
--- a/src/client/app/components/conversion/ConversionsDetailComponent.tsx
+++ b/src/client/app/components/conversion/ConversionsDetailComponent.tsx
@@ -12,7 +12,10 @@ import { ConversionData } from '../../types/redux/conversions';
import TooltipMarkerComponent from '../TooltipMarkerComponent';
import ConversionViewComponent from './ConversionViewComponent';
import CreateConversionModalComponent from './CreateConversionModalComponent';
+import { UnitDataById } from 'types/redux/units';
+const stableEmptyConversions: ConversionData[] = []
+const stableEmptyUnitDataById: UnitDataById = {}
/**
* Defines the conversions page card view
* @returns Conversion page element
@@ -21,9 +24,9 @@ export default function ConversionsDetailComponent() {
// The route stops you from getting to this page if not an admin.
// Conversions state
- const { data: conversionsState = [], isFetching: conversionsFetching } = conversionsApi.useGetConversionsDetailsQuery();
+ const { data: conversionsState = stableEmptyConversions, isFetching: conversionsFetching } = conversionsApi.useGetConversionsDetailsQuery();
// Units DataById
- const { unitDataById = {}, isFetching: unitsFetching } = unitsApi.useGetUnitsDetailsQuery(undefined, {
+ const { unitDataById = stableEmptyUnitDataById, isFetching: unitsFetching } = unitsApi.useGetUnitsDetailsQuery(undefined, {
selectFromResult: ({ data, ...result }) => ({
...result,
unitDataById: data && unitsAdapter.getSelectors().selectEntities(data)
diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx
index 654871d6f..0a2d97902 100644
--- a/src/client/app/components/conversion/EditConversionModalComponent.tsx
+++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx
@@ -162,7 +162,7 @@ export default function EditConversionModalComponent(props: EditConversionModalC
id='sourceId'
name='sourceId'
type='text'
- defaultValue={unitDataById[state.sourceId].identifier}
+ defaultValue={unitDataById[state.sourceId]?.identifier}
// Disable input to prevent changing ID value
disabled>
@@ -176,7 +176,7 @@ export default function EditConversionModalComponent(props: EditConversionModalC
id='destinationId'
name='destinationId'
type='text'
- defaultValue={unitDataById[state.destinationId].identifier}
+ defaultValue={unitDataById[state.destinationId]?.identifier}
// Disable input to prevent changing ID value
disabled>
diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx
index 2c97f5b10..f2bad368e 100644
--- a/src/client/app/components/groups/EditGroupModalComponent.tsx
+++ b/src/client/app/components/groups/EditGroupModalComponent.tsx
@@ -329,8 +329,6 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr
// changes were made to the children. This avoid a reload on name change, etc.
submitGroupEdits(submitState)
});
- // The next line is unneeded since do refresh.
- // dispatch(removeUnsavedChanges());
} else {
showErrorNotification(translate('group.input.error'));
}
diff --git a/src/client/app/components/router/GraphLinkComponent.tsx b/src/client/app/components/router/GraphLinkComponent.tsx
index 8edefbb1b..dc0c32a60 100644
--- a/src/client/app/components/router/GraphLinkComponent.tsx
+++ b/src/client/app/components/router/GraphLinkComponent.tsx
@@ -2,122 +2,32 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { PayloadAction } from '@reduxjs/toolkit';
-import InitializingComponent from '../router/InitializingComponent';
-import moment from 'moment';
import * as React from 'react';
import { Navigate, useSearchParams } from 'react-router-dom';
-import { graphSlice } from '../../redux/slices/graphSlice';
import { useWaitForInit } from '../../redux/componentHooks';
import { useAppDispatch } from '../../redux/reduxHooks';
-import { validateComparePeriod, validateSortingOrder } from '../../utils/calculateCompare';
-import { AreaUnitType } from '../../utils/getAreaUnitConversion';
-import { showErrorNotification } from '../../utils/notifications';
-import translate from '../../utils/translate';
-import { TimeInterval } from '../../../../common/TimeInterval';
-import { ChartTypes, LineGraphRate, MeterOrGroup } from '../../types/redux/graph';
-import { changeSelectedMap } from '../../redux/actions/map';
-import { appStateSlice } from '../../redux/slices/appStateSlice';
+import { processGraphLink } from '../../redux/slices/graphSlice';
+import InitializingComponent from '../router/InitializingComponent';
export const GraphLink = () => {
const dispatch = useAppDispatch();
const [URLSearchParams] = useSearchParams();
const { initComplete } = useWaitForInit();
- const dispatchQueue: PayloadAction
[] = [];
+ React.useEffect(() => {
+ const linkIsValid = validateHotlink(URLSearchParams)
+ if (linkIsValid) {
+ dispatch(processGraphLink(URLSearchParams))
+ }
+ }, [])
if (!initComplete) {
return
}
-
- try {
- URLSearchParams.forEach((value, key) => {
- // TODO Needs to be refactored into a single dispatch/reducer pair.
- // It is a best practice to reduce the number of dispatch calls, so this logic should be converted into a single reducer for the graphSlice
- // TODO validation could be implemented across all cases similar to compare period and sorting order
- switch (key) {
- case 'areaNormalization':
- dispatchQueue.push(graphSlice.actions.setAreaNormalization(value === 'true'))
- break;
- case 'areaUnit':
- dispatchQueue.push(graphSlice.actions.updateSelectedAreaUnit(value as AreaUnitType))
- break;
- case 'barDuration':
- dispatchQueue.push(graphSlice.actions.updateBarDuration(moment.duration(parseInt(value), 'days')))
- break;
- case 'barStacking':
- dispatchQueue.push(graphSlice.actions.setBarStacking(Boolean(value)))
- break;
- case 'chartType':
- dispatchQueue.push(graphSlice.actions.changeChartToRender(value as ChartTypes))
- break;
- case 'comparePeriod':
- dispatchQueue.push(graphSlice.actions.updateComparePeriod({ comparePeriod: validateComparePeriod(value), currentTime: moment() }))
- break;
- case 'compareSortingOrder':
- dispatchQueue.push(graphSlice.actions.changeCompareSortingOrder(validateSortingOrder(value)))
- break;
- case 'groupIDs':
- dispatchQueue.push(graphSlice.actions.updateSelectedGroups(value.split(',').map(s => parseInt(s))))
- break;
- case 'mapID':
- // 'TODO, Verify Behavior & FIXME! MapLink not working as expected
- dispatch(changeSelectedMap(parseInt(value)))
- break;
- case 'meterIDs':
- dispatchQueue.push(graphSlice.actions.updateSelectedMeters(value.split(',').map(s => parseInt(s))))
- break;
- case 'meterOrGroup':
- dispatchQueue.push(graphSlice.actions.updateThreeDMeterOrGroup(value as MeterOrGroup));
- break;
- case 'meterOrGroupID':
- dispatchQueue.push(graphSlice.actions.updateThreeDMeterOrGroupID(parseInt(value)));
- break;
- case 'minMax':
- dispatchQueue.push(graphSlice.actions.setShowMinMax(value === 'true' ? true : false))
- break;
- case 'optionsVisibility':
- dispatchQueue.push(appStateSlice.actions.setOptionsVisibility(value === 'true' ? true : false))
- break;
- case 'rate':
- {
- const params = value.split(',');
- const rate = { label: params[0], rate: parseFloat(params[1]) } as LineGraphRate;
- dispatchQueue.push(graphSlice.actions.updateLineGraphRate(rate))
- }
- break;
- case 'readingInterval':
- dispatchQueue.push(graphSlice.actions.updateThreeDReadingInterval(parseInt(value)));
- break;
- case 'serverRange':
- dispatchQueue.push(graphSlice.actions.updateTimeInterval(TimeInterval.fromString(value)));
- /**
- * commented out since days from present feature is not currently used
- */
- // const index = info.indexOf('dfp');
- // if (index === -1) {
- // options.serverRange = TimeInterval.fromString(info);
- // } else {
- // const message = info.substring(0, index);
- // const stringField = this.getNewIntervalFromMessage(message);
- // options.serverRange = TimeInterval.fromString(stringField);
- // }
- break;
- case 'sliderRange':
- dispatchQueue.push(graphSlice.actions.changeSliderRange(TimeInterval.fromString(value)));
- break;
- case 'unitID':
- dispatchQueue.push(graphSlice.actions.updateSelectedUnit(parseInt(value)))
- break;
- default:
- throw new Error('Unknown query parameter');
- }
- })
-
- dispatchQueue.forEach(dispatch)
- } catch (err) {
- showErrorNotification(translate('failed.to.link.graph'));
- }
- // All appropriate state updates should've been executed
- // redirect to root and clear the link in the search bar
return
}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const validateHotlink = (params: URLSearchParams) => {
+ // TODO VALIDATE HOTLINK
+ return true
+}
\ No newline at end of file
diff --git a/src/client/app/redux/api/groupsApi.ts b/src/client/app/redux/api/groupsApi.ts
index 12573be07..e021694f7 100644
--- a/src/client/app/redux/api/groupsApi.ts
+++ b/src/client/app/redux/api/groupsApi.ts
@@ -69,7 +69,8 @@ export const groupsApi = baseApi.injectEndpoints({
url: 'api/groups/edit',
method: 'PUT',
body: group
- })
+ }),
+ invalidatesTags: ['GroupData', 'GroupChildrenData']
}),
deleteGroup: builder.mutation({
query: groupId => ({
diff --git a/src/client/app/redux/selectors/lineChartSelectors.ts b/src/client/app/redux/selectors/lineChartSelectors.ts
index 9d6540cbc..fec0f3dfd 100644
--- a/src/client/app/redux/selectors/lineChartSelectors.ts
+++ b/src/client/app/redux/selectors/lineChartSelectors.ts
@@ -30,7 +30,8 @@ export const selectPlotlyMeterData = selectFromLineReadingsResult(
[data => data, (_data, dependencies: PlotlyLineDeps) => dependencies],
(data, { areaUnit, areaNormalization, meterDataById, compatibleEntities, showMinMax, lineUnitLabel, rateScaling }) => {
const plotlyLineData = Object.entries(data)
- // filter entries for requested groups
+ // filter entries for requested compatible groups
+ // compatible entities is using the same data deriving selectors as the select option for group, & meter.
.filter(([groupID]) => compatibleEntities.includes((Number(groupID))))
.map(([id, readings]) => {
const meterInfo = meterDataById[Number(id)]
diff --git a/src/client/app/redux/selectors/plotlyDataSelectors.ts b/src/client/app/redux/selectors/plotlyDataSelectors.ts
index c1d6f33ab..f32452b7e 100644
--- a/src/client/app/redux/selectors/plotlyDataSelectors.ts
+++ b/src/client/app/redux/selectors/plotlyDataSelectors.ts
@@ -85,9 +85,25 @@ export const selectBarUnitLabel = (state: RootState) => {
}
// fallback to name if no identifier
-export const selectNameFromEntity = (entity: MeterData | GroupData | UnitData) => 'identifier' in entity ? entity.identifier : entity.name
+export const selectNameFromEntity = (entity: MeterData | GroupData | UnitData) => {
+ if (entity && 'identifier' in entity && entity.identifier) {
+ return entity.identifier
+ } else if (entity && 'name' in entity && entity.name) {
+ return entity.name
+ } else {
+ // Users May Possibly receive data for meters with neither identifier, or name so empty.
+ return ''
+ }
+}
+
// Determines if meter or group base on objet distinct properties of each
-export const selectMeterOrGroupFromEntity = (entity: MeterData | GroupData) => 'meterType' in entity ? MeterOrGroup.meters : MeterOrGroup.groups
+export const selectMeterOrGroupFromEntity = (entity: MeterData | GroupData) => {
+ return 'meterType' in entity ? MeterOrGroup.meters : 'childMeters' in entity ? MeterOrGroup.groups : undefined
+}
+
+export const selectDefaultGraphicUnitFromEntity = (entity: MeterData | GroupData) => {
+ return 'defaultGraphicUnit' in entity ? entity.defaultGraphicUnit : undefined
+}
// Line and Groups use these values to derive plotly data, so make selector for them to 'extend'
export const selectCommonPlotlyDependencies = createStructuredSelector(
diff --git a/src/client/app/redux/selectors/threeDSelectors.ts b/src/client/app/redux/selectors/threeDSelectors.ts
index 55df030c4..bf190bf79 100644
--- a/src/client/app/redux/selectors/threeDSelectors.ts
+++ b/src/client/app/redux/selectors/threeDSelectors.ts
@@ -10,6 +10,7 @@ import { selectGroupDataById } from '../../redux/api/groupsApi';
import { MeterOrGroup } from '../../types/redux/graph';
import { AreaUnitType } from '../../utils/getAreaUnitConversion';
import { selectMeterDataById } from '../../redux/api/metersApi';
+import { selectNameFromEntity } from './plotlyDataSelectors';
// Memoized Selectors
@@ -24,15 +25,14 @@ export const selectThreeDComponentInfo = createSelector(
let isAreaCompatible = true;
if (id && meterDataById[id]) {
+ const entity = meterOrGroup === MeterOrGroup.meters ? meterDataById[id] : groupDataById[id]
+ meterOrGroupName = selectNameFromEntity(entity)
// Get Meter or Group's info
- if (meterOrGroup === MeterOrGroup.meters && meterDataById) {
- const meterInfo = meterDataById[id]
- meterOrGroupName = meterInfo.identifier;
- isAreaCompatible = meterInfo.area !== 0 && meterInfo.areaUnit !== AreaUnitType.none;
- } else if (meterOrGroup === MeterOrGroup.groups && groupDataById) {
- const groupInfo = groupDataById[id];
- meterOrGroupName = groupInfo.name;
- isAreaCompatible = groupInfo.area !== 0 && groupInfo.areaUnit !== AreaUnitType.none;
+ if (meterOrGroup === MeterOrGroup.meters && entity) {
+ isAreaCompatible = entity.area !== 0 && entity.areaUnit !== AreaUnitType.none;
+ } else if (meterOrGroup === MeterOrGroup.groups && entity) {
+ meterOrGroupName = entity.name;
+ isAreaCompatible = entity.area !== 0 && entity.areaUnit !== AreaUnitType.none;
}
}
diff --git a/src/client/app/redux/selectors/uiSelectors.ts b/src/client/app/redux/selectors/uiSelectors.ts
index 1f46e4409..da8b82ef3 100644
--- a/src/client/app/redux/selectors/uiSelectors.ts
+++ b/src/client/app/redux/selectors/uiSelectors.ts
@@ -12,7 +12,7 @@ import { selectUnitDataById } from '../../redux/api/unitsApi';
import { RootState } from '../../store';
import { DataType } from '../../types/Datasources';
import { GroupedOption, SelectOption } from '../../types/items';
-import { ChartTypes, MeterOrGroup } from '../../types/redux/graph';
+import { ChartTypes } from '../../types/redux/graph';
import { UnitDataById, UnitRepresentType } from '../../types/redux/units';
import {
CartesianPoint, Dimensions, calculateScaleFromEndpoints, gpsToUserGrid,
@@ -27,12 +27,9 @@ import {
selectSelectedUnit
} from '../slices/graphSlice';
import { selectVisibleMetersAndGroups, selectVisibleUnitOrSuffixState } from './authVisibilitySelectors';
-import { selectNameFromEntity } from './plotlyDataSelectors';
+import { selectDefaultGraphicUnitFromEntity, selectMeterOrGroupFromEntity, selectNameFromEntity } from './plotlyDataSelectors';
import { createAppSelector } from './selectors';
-
-
-
export const selectCurrentUnitCompatibility = createAppSelector(
[
selectVisibleMetersAndGroups,
@@ -148,8 +145,6 @@ export const selectChartTypeCompatibility = createAppSelector(
const compatibleGroups = new Set(Array.from(areaCompat.compatibleGroups));
const incompatibleGroups = new Set(Array.from(areaCompat.incompatibleGroups));
-
-
// ony run this check if we are displaying a map chart
if (chartToRender === ChartTypes.map && mapState.selectedMap !== 0) {
const mp = mapState.byMapID[mapState.selectedMap];
@@ -350,6 +345,7 @@ export const selectUnitSelectData = createAppSelector(
}
});
}
+
// Ready to display unit. Put selectable ones before non-selectable ones.
const unitOptions = getSelectOptionsByEntity(compatibleUnits, incompatibleUnits, unitDataById);
const unitsGroupedOptions: GroupedOption[] = [
@@ -386,8 +382,8 @@ export function getSelectOptionsByEntity(
// Groups unit and meters have identifier, groups doesn't
const label = selectNameFromEntity(entity)
// MeterAnd Group, undefined for units
- const defaultGraphicUnit = 'defaultGraphicUnit' in entity ? entity.defaultGraphicUnit : undefined
- const meterOrGroup = 'meterType' in entity ? MeterOrGroup.meters : 'childMeters' in entity ? MeterOrGroup.groups : undefined
+ const defaultGraphicUnit = selectDefaultGraphicUnitFromEntity(entity)
+ const meterOrGroup = selectMeterOrGroupFromEntity(entity)
return {
value: Number(id),
label: label,
@@ -404,8 +400,8 @@ export function getSelectOptionsByEntity(
.map(([id, entity]) => {
const label = selectNameFromEntity(entity)
// MeterAnd Group, undefined for units
- const defaultGraphicUnit = 'defaultGraphicUnit' in entity ? entity.defaultGraphicUnit : undefined
- const meterOrGroup = 'meterType' in entity ? MeterOrGroup.meters : 'childMeters' in entity ? MeterOrGroup.groups : undefined
+ const defaultGraphicUnit = selectDefaultGraphicUnitFromEntity(entity)
+ const meterOrGroup = selectMeterOrGroupFromEntity(entity)
return {
value: Number(id),
label: label,
@@ -416,11 +412,8 @@ export function getSelectOptionsByEntity(
} as SelectOption
})
-
const compatible = _.sortBy(compatibleItemOptions, item => item.label.toLowerCase(), 'asc')
const incompatible = _.sortBy(incompatibleItemOptions, item => item.label.toLowerCase(), 'asc')
-
-
return { compatible, incompatible }
}
@@ -428,7 +421,9 @@ export function getSelectOptionsByEntity(
// Helper function for area compatibility
// areaNorm should be active when called
-const isAreaNormCompatible = (id: number, selectedUnit: number, meterOrGroupData: MeterDataByID | GroupDataByID, unitDataById: UnitDataById) => {
+export const isAreaNormCompatible = (
+ id: number, selectedUnit: number, meterOrGroupData: MeterDataByID | GroupDataByID, unitDataById: UnitDataById
+) => {
const meterGraphingUnit = meterOrGroupData[id].defaultGraphicUnit;
// If no unit is selected then no meter/group should be selected if meter type is raw
diff --git a/src/client/app/redux/slices/appStateSlice.ts b/src/client/app/redux/slices/appStateSlice.ts
index 8120875a2..3f1fceca5 100644
--- a/src/client/app/redux/slices/appStateSlice.ts
+++ b/src/client/app/redux/slices/appStateSlice.ts
@@ -2,6 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+import * as moment from 'moment';
+import { LanguageTypes } from '../../types/redux/i18n';
+import { deleteToken, getToken, hasToken } from '../../utils/token';
import { fetchMapsDetails } from '../actions/map';
import { authApi } from '../api/authApi';
import { conversionsApi } from '../api/conversionsApi';
@@ -12,10 +15,8 @@ import { unitsApi } from '../api/unitsApi';
import { userApi } from '../api/userApi';
import { versionApi } from '../api/versionApi';
import { createThunkSlice } from '../sliceCreators';
-import { deleteToken, getToken, hasToken } from '../../utils/token';
import { currentUserSlice } from './currentUserSlice';
-import { LanguageTypes } from '../../types/redux/i18n';
-import * as moment from 'moment';
+import { processGraphLink } from './graphSlice';
export interface AppState {
initComplete: boolean;
@@ -54,8 +55,8 @@ export const appStateSlice = createThunkSlice({
async (_: void, { dispatch }) => {
// These queries will trigger a api request, and add a subscription to the store.
// Typically they return an unsubscribe method, however we always want to be subscribed to any cache changes for these endpoints.
- dispatch(versionApi.endpoints.getVersion.initiate())
dispatch(preferencesApi.endpoints.getPreferences.initiate())
+ dispatch(versionApi.endpoints.getVersion.initiate())
dispatch(unitsApi.endpoints.getUnitsDetails.initiate())
dispatch(conversionsApi.endpoints.getConversionsDetails.initiate())
dispatch(conversionsApi.endpoints.getConversionArray.initiate())
@@ -100,10 +101,16 @@ export const appStateSlice = createThunkSlice({
)
}),
extraReducers: builder => {
- builder.addMatcher(preferencesApi.endpoints.getPreferences.matchFulfilled, (state, action) => {
- state.selectedLanguage = action.payload.defaultLanguage
- moment.locale(action.payload.defaultLanguage);
- })
+ builder
+ .addCase(processGraphLink, (state, { payload }) => {
+ if (payload.has('optionsVisibility')) {
+ state.optionsVisibility = payload.get('optionsVisibility') === 'true'
+ }
+ })
+ .addMatcher(preferencesApi.endpoints.getPreferences.matchFulfilled, (state, action) => {
+ state.selectedLanguage = action.payload.defaultLanguage
+ moment.locale(action.payload.defaultLanguage);
+ })
},
selectors: {
selectInitComplete: state => state.initComplete,
diff --git a/src/client/app/redux/slices/graphSlice.ts b/src/client/app/redux/slices/graphSlice.ts
index 139c21df5..f0f0e1798 100644
--- a/src/client/app/redux/slices/graphSlice.ts
+++ b/src/client/app/redux/slices/graphSlice.ts
@@ -9,7 +9,7 @@ import { TimeInterval } from '../../../../common/TimeInterval';
import { preferencesApi } from '../api/preferencesApi';
import { SelectOption } from '../../types/items';
import { ChartTypes, GraphState, LineGraphRate, MeterOrGroup, ReadingInterval } from '../../types/redux/graph';
-import { ComparePeriod, SortingOrder, calculateCompareTimeInterval } from '../../utils/calculateCompare';
+import { ComparePeriod, SortingOrder, calculateCompareTimeInterval, validateComparePeriod, validateSortingOrder } from '../../utils/calculateCompare';
import { AreaUnitType } from '../../utils/getAreaUnitConversion';
const defaultState: GraphState = {
@@ -33,7 +33,8 @@ const defaultState: GraphState = {
meterOrGroupID: undefined,
meterOrGroup: undefined,
readingInterval: ReadingInterval.Hourly
- }
+ },
+ hotlinked: false
};
interface History {
@@ -241,12 +242,82 @@ export const graphSlice = createSlice({
state.current = next
}
})
+ .addCase(processGraphLink, ({ current }, { payload }) => {
+ current.hotlinked = true
+ payload.forEach((value, key) => {
+ // TODO Needs to be refactored into a single dispatch/reducer pair.
+ // It is a best practice to reduce the number of dispatch calls, so this logic should be converted into a single reducer for the graphSlice
+ // TODO validation could be implemented across all cases similar to compare period and sorting order
+ switch (key) {
+ case 'areaNormalization':
+ current.areaNormalization = value === 'true'
+ break;
+ case 'areaUnit':
+ current.selectedAreaUnit = value as AreaUnitType
+ break;
+ case 'barDuration':
+ current.barDuration = moment.duration(parseInt(value), 'days');
+ break;
+ case 'barStacking':
+ current.barStacking = value === 'true'
+ break;
+ case 'chartType':
+ current.chartToRender = value as ChartTypes
+ break;
+ case 'comparePeriod':
+ {
+ current.comparePeriod = validateComparePeriod(value)
+ current.compareTimeInterval = calculateCompareTimeInterval(validateComparePeriod(value), moment())
+ }
+ break;
+ case 'compareSortingOrder':
+ current.compareSortingOrder = validateSortingOrder(value);
+ break;
+ case 'groupIDs':
+ current.selectedGroups = value.split(',').map(s => parseInt(s));
+ break;
+ case 'meterIDs':
+ current.selectedMeters = value.split(',').map(s => parseInt(s));
+ break;
+ case 'meterOrGroup':
+ current.threeD.meterOrGroup = value as MeterOrGroup;
+ break;
+ case 'meterOrGroupID':
+ current.threeD.meterOrGroupID = parseInt(value);
+ break;
+ case 'minMax':
+ current.showMinMax = value === 'true'
+ break;
+ case 'rate':
+ {
+ const params = value.split(',');
+ const rate = { label: params[0], rate: parseFloat(params[1]) } as LineGraphRate;
+ current.lineGraphRate = rate;
+ }
+ break;
+ case 'readingInterval':
+ current.threeD.readingInterval = parseInt(value);
+ break;
+ case 'serverRange':
+ current.queryTimeInterval = TimeInterval.fromString(value);
+ break;
+ case 'sliderRange':
+ current.rangeSliderInterval = TimeInterval.fromString(value);
+ break;
+ case 'unitID':
+ current.selectedUnit = parseInt(value)
+ break;
+ }
+ })
+ })
.addMatcher(preferencesApi.endpoints.getPreferences.matchFulfilled, ({ current }, action) => {
- const { defaultAreaUnit, defaultChartToRender, defaultBarStacking, defaultAreaNormalization } = action.payload
- current.selectedAreaUnit = defaultAreaUnit
- current.chartToRender = defaultChartToRender
- current.barStacking = defaultBarStacking
- current.areaNormalization = defaultAreaNormalization
+ if (!current.hotlinked) {
+ const { defaultAreaUnit, defaultChartToRender, defaultBarStacking, defaultAreaNormalization } = action.payload
+ current.selectedAreaUnit = defaultAreaUnit
+ current.chartToRender = defaultChartToRender
+ current.barStacking = defaultBarStacking
+ current.areaNormalization = defaultAreaNormalization
+ }
})
},
selectors: {
@@ -331,4 +402,6 @@ export const {
export const historyStepBack = createAction('graph/historyStepBack')
export const historyStepForward = createAction('graph/historyStepForward')
-export const updateHistory = createAction('graph/updateHistory')
\ No newline at end of file
+export const updateHistory = createAction('graph/updateHistory')
+
+export const processGraphLink = createAction('graph/graphLink')
\ No newline at end of file
diff --git a/src/client/app/styles/index.css b/src/client/app/styles/index.css
index 4afc34566..d1e2c491a 100644
--- a/src/client/app/styles/index.css
+++ b/src/client/app/styles/index.css
@@ -15,3 +15,12 @@ body {
margin: 0;
padding: 0;
}
+
+.no_scrollbar {
+ scrollbar-width: none; /* Firefox 64+ */
+ -ms-overflow-style: none; /* Internet Explorer and Edge */
+ /* Hide scrollbar for Webkit browsers */
+ ::-webkit-scrollbar {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts
index e9e526e19..2968ad3ef 100644
--- a/src/client/app/types/redux/graph.ts
+++ b/src/client/app/types/redux/graph.ts
@@ -74,4 +74,5 @@ export interface GraphState {
showMinMax: boolean;
threeD: ThreeDState;
queryTimeInterval: TimeInterval;
+ hotlinked: boolean;
}
\ No newline at end of file