@hopara/iframe 2.4.57 → 2.4.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/client.js +1 -1
- package/build/service-worker.js +1 -1
- package/build/types/src/client/index.d.ts +43 -0
- package/build/types/src/demo/Demo.d.ts +5 -0
- package/build/types/src/demo/index.d.ts +1 -0
- package/build/types/src/events/EventEmitter.d.ts +15 -0
- package/build/types/src/events/EventReceiver.d.ts +5 -0
- package/build/types/src/events/Events.d.ts +69 -0
- package/build/types/src/index.d.ts +1 -0
- package/build/types/src/service-worker.d.ts +1 -0
- package/build/types/src/view/Provider.d.ts +73 -0
- package/build/types/src/view/Router.d.ts +1 -0
- package/build/types/src/view/View.d.ts +24 -0
- package/build/types/src/view/index.d.ts +1 -0
- package/package.json +8 -8
- package/src/client/index.ts +256 -0
- package/src/demo/Demo.tsx +119 -0
- package/src/demo/index.tsx +7 -0
- package/src/demo/resources/assets-data.json +20552 -0
- package/src/demo/resources/buildings-data.json +56 -0
- package/src/demo/resources/sensors-data.json +830 -0
- package/src/events/EventEmitter.ts +90 -0
- package/src/events/EventReceiver.ts +24 -0
- package/src/events/Events.ts +86 -0
- package/src/index.ts +2 -0
- package/src/react-app-env.d.ts +9 -0
- package/src/service-worker.ts +1 -0
- package/src/view/Provider.tsx +258 -0
- package/src/view/Router.tsx +35 -0
- package/src/view/View.tsx +159 -0
- package/src/view/index.tsx +20 -0
- package/tasks/preprocessHTML.js +0 -74
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { EventType, HOPARA_EVENT_TYPE, LoadDataRequestData } from './Events'
|
|
2
|
+
import { DataLoader, FilterSet, LoaderResponse } from '@hopara/dataset'
|
|
3
|
+
import { Row } from '@hopara/dataset'
|
|
4
|
+
|
|
5
|
+
export class EventEmitter {
|
|
6
|
+
private static sendMessage(data: any, targetWindow?: Window) {
|
|
7
|
+
const target = targetWindow || window.parent || window.top
|
|
8
|
+
if (!window) return
|
|
9
|
+
// eslint-disable-next-line @microsoft/sdl/no-postmessage-star-origin
|
|
10
|
+
target.postMessage(data, '*')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static ready(): void {
|
|
14
|
+
this.sendMessage({[HOPARA_EVENT_TYPE]: EventType.READY})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static init(config: any, targetWindow: Window): void {
|
|
18
|
+
this.sendMessage(Object.assign(config, {[HOPARA_EVENT_TYPE]: EventType.INIT}), targetWindow)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static loadDataRequest(dataLoader: Partial<DataLoader>, filterSet: FilterSet): void {
|
|
22
|
+
this.sendMessage({
|
|
23
|
+
[HOPARA_EVENT_TYPE]: EventType.LOAD_DATA,
|
|
24
|
+
data: {query: dataLoader.query, name: dataLoader.query, source: dataLoader.source},
|
|
25
|
+
filterSet,
|
|
26
|
+
} as LoadDataRequestData)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static getRows(response: any, error?: Error) {
|
|
30
|
+
if (error || !response) {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Array.isArray(response) ? response : response.rows
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static getColumns(response:any, error?: Error) {
|
|
38
|
+
if (error) {
|
|
39
|
+
return []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Array.isArray(response) ? [] : response.columns
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static getError(response:any, error?: Error) {
|
|
46
|
+
if (error) {
|
|
47
|
+
return error
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!response) {
|
|
51
|
+
return new Error('Load data with undefined response')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
static loadDataResponse(config: any, dataLoader: Partial<DataLoader>, response: any[] | LoaderResponse, error: Error | undefined, targetWindow: Window): void {
|
|
59
|
+
const rows = this.getRows(response, error)
|
|
60
|
+
const columns = this.getColumns(response, error)
|
|
61
|
+
this.sendMessage(Object.assign(
|
|
62
|
+
config,
|
|
63
|
+
{[HOPARA_EVENT_TYPE]: EventType.LOAD_DATA_RESPONSE},
|
|
64
|
+
{data: {query: dataLoader.query ?? (dataLoader as any).name, source: dataLoader.source},
|
|
65
|
+
rows, columns, error: this.getError(response, error)},
|
|
66
|
+
), targetWindow)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static refresh(config: any, targetWindow: Window): void {
|
|
70
|
+
this.sendMessage({
|
|
71
|
+
...config,
|
|
72
|
+
[HOPARA_EVENT_TYPE]: EventType.REFRESH,
|
|
73
|
+
}, targetWindow)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static update(config:any, targetWindow: Window): void {
|
|
77
|
+
this.sendMessage({
|
|
78
|
+
...config,
|
|
79
|
+
[HOPARA_EVENT_TYPE]: EventType.UPDATE,
|
|
80
|
+
}, targetWindow)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static callback(name: string, row: Row): void {
|
|
84
|
+
this.sendMessage({
|
|
85
|
+
[HOPARA_EVENT_TYPE]: EventType.FUNCTION_CALLBACK,
|
|
86
|
+
name,
|
|
87
|
+
row,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DataLoader, LoaderResponse } from '@hopara/dataset'
|
|
2
|
+
import { EventType, HOPARA_EVENT_TYPE, LoadDataResponseData, PostMessageEvent } from './Events'
|
|
3
|
+
|
|
4
|
+
export class EventReceiver {
|
|
5
|
+
static isHoparaMessage(event: any): boolean {
|
|
6
|
+
return event.data[HOPARA_EVENT_TYPE] !== undefined
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static loadDataResponse(dataLoader: Partial<DataLoader>): Promise<LoaderResponse> {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const callback = (event: PostMessageEvent<LoadDataResponseData>) => {
|
|
12
|
+
if (
|
|
13
|
+
event.data[HOPARA_EVENT_TYPE] === EventType.LOAD_DATA_RESPONSE &&
|
|
14
|
+
(event.data.data.query === dataLoader.query || (event.data.data as any).name === dataLoader.query) &&
|
|
15
|
+
event.data.data.source === dataLoader.source) {
|
|
16
|
+
window.removeEventListener('message', callback, false)
|
|
17
|
+
resolve({rows: event.data.rows ?? [], columns: event.data.columns ?? [], error: event.data.error})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
window.addEventListener('message', callback, false)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ConfigEnvironment } from '@hopara/config'
|
|
2
|
+
import { FilterSet } from '@hopara/dataset'
|
|
3
|
+
import { Position } from '@hopara/components/src/view-state/ViewState'
|
|
4
|
+
import { SelectedFilter } from '@hopara/components/src/filter/domain/SelectedFilter'
|
|
5
|
+
import { InitialRow } from '@hopara/components/src/initial-row/InitialRow'
|
|
6
|
+
import { PlainError } from '@hopara/dataset/src/repository/LoaderDatasetRepository'
|
|
7
|
+
import { PlainDataLoader } from '@hopara/dataset/src/loader/DataLoader'
|
|
8
|
+
import { HoparaTab } from '../view/Provider'
|
|
9
|
+
import { Language } from '@hopara/browser'
|
|
10
|
+
|
|
11
|
+
export enum EventType {
|
|
12
|
+
INIT = 'init',
|
|
13
|
+
READY = 'ready',
|
|
14
|
+
UPDATE = 'update',
|
|
15
|
+
LOAD_DATA = 'loadData',
|
|
16
|
+
LOAD_DATA_RESPONSE = 'loadDataResponse',
|
|
17
|
+
UPDATE_DATA = 'updateData',
|
|
18
|
+
UPDATE_DATA_RESPONSE = 'updateDataResponse',
|
|
19
|
+
REFRESH = 'refresh',
|
|
20
|
+
FUNCTION_CALLBACK = 'functionCallback',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const HOPARA_EVENT_TYPE = '__hopara__eventType__'
|
|
24
|
+
|
|
25
|
+
export interface EventData {
|
|
26
|
+
[HOPARA_EVENT_TYPE]?: EventType
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface InitData extends EventData {
|
|
30
|
+
accessToken?: string
|
|
31
|
+
refreshToken?: string
|
|
32
|
+
visualizationId?: string
|
|
33
|
+
fallbackVisualizationId?: string
|
|
34
|
+
visualizationScope?: string
|
|
35
|
+
tenant?: string
|
|
36
|
+
env?: ConfigEnvironment
|
|
37
|
+
initialPosition?: Position
|
|
38
|
+
initialRow?: InitialRow
|
|
39
|
+
dataLoaders?: PlainDataLoader[]
|
|
40
|
+
darkMode?: boolean
|
|
41
|
+
callbackNames?: string[]
|
|
42
|
+
filters?: Partial<SelectedFilter>[]
|
|
43
|
+
language?: Language | `${Language}`
|
|
44
|
+
initialTab?: HoparaTab | `${HoparaTab}`
|
|
45
|
+
navigationControls?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface LoadDataRequestData extends EventData {
|
|
49
|
+
data: {
|
|
50
|
+
query: string
|
|
51
|
+
source: string
|
|
52
|
+
}
|
|
53
|
+
filterSet: FilterSet
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface LoadDataResponseData extends EventData {
|
|
57
|
+
data: {
|
|
58
|
+
query: string
|
|
59
|
+
source: string
|
|
60
|
+
}
|
|
61
|
+
rows: any[]
|
|
62
|
+
columns: any[]
|
|
63
|
+
error?: PlainError
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CallbackFunctionData extends EventData {
|
|
67
|
+
name: string
|
|
68
|
+
row: any
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PostMessageEvent<D> extends MessageEvent {
|
|
72
|
+
origin: string
|
|
73
|
+
data: D
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const isReadyEvent = (event: PostMessageEvent<EventData>): event is PostMessageEvent<EventData> => {
|
|
77
|
+
return event.data[HOPARA_EVENT_TYPE] === EventType.READY
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const isLoadDataEvent = (event: PostMessageEvent<EventData>): event is PostMessageEvent<LoadDataRequestData> => {
|
|
81
|
+
return event.data[HOPARA_EVENT_TYPE] === EventType.LOAD_DATA
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const isCallbackFunctionEvent = (event: PostMessageEvent<EventData>): event is PostMessageEvent<CallbackFunctionData> => {
|
|
85
|
+
return event.data[HOPARA_EVENT_TYPE] === EventType.FUNCTION_CALLBACK
|
|
86
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@hopara/service-worker'
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import {connect} from 'react-redux'
|
|
3
|
+
import jwtDecode from 'jwt-decode'
|
|
4
|
+
import { Logger } from '@hopara/internals'
|
|
5
|
+
import actions, { HocLoadedPayload } from '@hopara/components/src/hoc/HocActions'
|
|
6
|
+
import { InitialRow } from '@hopara/components/src/initial-row/InitialRow'
|
|
7
|
+
import { HoparaController } from '@hopara/components/src/hoc/HoparaController'
|
|
8
|
+
import { Config, ConfigEnvironment } from '@hopara/config'
|
|
9
|
+
import { CallbackFunction } from '@hopara/components/src/action/ActionReducer'
|
|
10
|
+
import { SelectedFilter } from '@hopara/components/src/filter/domain/SelectedFilter'
|
|
11
|
+
import { SelectedFilters } from '@hopara/components/src/filter/domain/SelectedFilters'
|
|
12
|
+
import { Authorization } from '@hopara/authorization'
|
|
13
|
+
import { Store } from '@hopara/components/src/state/Store'
|
|
14
|
+
import { getPageUrl } from '@hopara/page/src/PageNavigation'
|
|
15
|
+
import { PageType } from '@hopara/page/src/Pages'
|
|
16
|
+
import { useNavigate } from 'react-router-dom'
|
|
17
|
+
import { FilterStore } from '@hopara/components/src/filter/state/FilterStore'
|
|
18
|
+
import QueryStore from '@hopara/components/src/query/QueryStore'
|
|
19
|
+
import { isEqual } from 'lodash/fp'
|
|
20
|
+
import { PlainDataLoader } from '@hopara/dataset/src/loader/DataLoader'
|
|
21
|
+
import { Language } from '@hopara/browser'
|
|
22
|
+
|
|
23
|
+
export enum HoparaTab {
|
|
24
|
+
OBJECTS='objects',
|
|
25
|
+
LAYERS='layers',
|
|
26
|
+
SETTINGS='settings',
|
|
27
|
+
VISUALIZATION='visualization'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type EmbeddedProps = {
|
|
31
|
+
env?: ConfigEnvironment
|
|
32
|
+
visualizationId: string
|
|
33
|
+
fallbackVisualizationId?: string
|
|
34
|
+
visualizationScope?: string
|
|
35
|
+
accessToken: string
|
|
36
|
+
refreshToken?: string
|
|
37
|
+
tenant?: string
|
|
38
|
+
dataLoaders?: PlainDataLoader[]
|
|
39
|
+
initialRow?: InitialRow
|
|
40
|
+
controller?: HoparaController
|
|
41
|
+
darkMode?: boolean
|
|
42
|
+
callbacks?: CallbackFunction[]
|
|
43
|
+
attribution?: boolean
|
|
44
|
+
filters?: Partial<SelectedFilter>[]
|
|
45
|
+
language?: Language
|
|
46
|
+
initialTab?: HoparaTab
|
|
47
|
+
navigationControls?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const logInit = (message:string, props: EmbeddedProps) => {
|
|
51
|
+
Logger.debug(message, {
|
|
52
|
+
visualizationId: props.visualizationId,
|
|
53
|
+
accessToken: props.accessToken ? `${props.accessToken?.substring(0, 50)}...` : undefined,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const getDecodedToken = (accessToken: string) => {
|
|
58
|
+
try {
|
|
59
|
+
return jwtDecode<any>(accessToken)
|
|
60
|
+
} catch {
|
|
61
|
+
return {}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ProviderStateProps extends EmbeddedProps {
|
|
66
|
+
stateVisualizationId: string
|
|
67
|
+
stateFallbackVisualizationId: string
|
|
68
|
+
stateTenant: string
|
|
69
|
+
stateAuthorization?: Authorization
|
|
70
|
+
filterStore: FilterStore
|
|
71
|
+
queryStore: QueryStore
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface ProviderStateActions {
|
|
75
|
+
onLoad: (navigateToTabUrl) => void
|
|
76
|
+
onUpdate: () => void
|
|
77
|
+
onVisualizationChanged: (navigateFn: any, tenant: string) => void
|
|
78
|
+
forceRefresh: () => void
|
|
79
|
+
onTabChange: (navigateFn: any, tab: HoparaTab) => void
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const Provider = (props: ProviderStateProps & ProviderStateActions) => {
|
|
83
|
+
const navigate = useNavigate()
|
|
84
|
+
const [isFirstLoad, setIsFirstLoad] = useState(true)
|
|
85
|
+
const [oldVisualization, setOldVisualizationId] = useState({id: props.visualizationId, fallback: props.fallbackVisualizationId})
|
|
86
|
+
const [oldToken, setOldToken] = useState({token: props.accessToken, refreshToken: props.refreshToken, tenant: props.tenant})
|
|
87
|
+
const [oldFilters, setOldFilters] = useState(props.filters)
|
|
88
|
+
const [oldDataLoaders, setOldDataLoaders] = useState(props.dataLoaders)
|
|
89
|
+
const [oldInitialRow, setOldInitialRow] = useState(props.initialRow)
|
|
90
|
+
const [oldInitialTab, setOldInitialTab] = useState(props.initialTab)
|
|
91
|
+
|
|
92
|
+
const isRenderable = !!props.accessToken && !!props.visualizationId
|
|
93
|
+
const isTokenRefreshable = !!props.accessToken && !!props.refreshToken
|
|
94
|
+
|
|
95
|
+
const hasVisualizationChanged = !isEqual(oldVisualization.id, props.visualizationId) || !isEqual(oldVisualization.fallback, props.fallbackVisualizationId)
|
|
96
|
+
const hasTokenChanged = !isTokenRefreshable && (!isEqual(oldToken.token, props.accessToken) || !isEqual(oldToken.refreshToken, props.refreshToken))
|
|
97
|
+
const hasFiltersChanged = !isEqual(oldFilters, props.filters)
|
|
98
|
+
const hasDataLoadersChanged = !isEqual(oldDataLoaders, props.dataLoaders)
|
|
99
|
+
const hasInitialRowChanged = !isEqual(oldInitialRow, props.initialRow)
|
|
100
|
+
const hasInitialTabChanged = !isEqual(oldInitialTab, props.initialTab)
|
|
101
|
+
|
|
102
|
+
const shouldUpdate = hasVisualizationChanged || hasTokenChanged || hasFiltersChanged || hasDataLoadersChanged || hasInitialRowChanged || hasInitialTabChanged
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!shouldUpdate || !isRenderable) return
|
|
106
|
+
if (props.controller) props.controller.setRefreshSignal(props.forceRefresh)
|
|
107
|
+
if (props.env && props.env !== Config.environment) Config.setEnv(props.env)
|
|
108
|
+
|
|
109
|
+
setOldToken({token: props.accessToken, refreshToken: props.refreshToken, tenant: props.tenant})
|
|
110
|
+
setOldVisualizationId({id: props.visualizationId, fallback: props.fallbackVisualizationId})
|
|
111
|
+
setOldFilters(props.filters)
|
|
112
|
+
setOldDataLoaders(props.dataLoaders)
|
|
113
|
+
setOldInitialRow(props.initialRow)
|
|
114
|
+
setOldInitialTab(props.initialTab)
|
|
115
|
+
|
|
116
|
+
if (isFirstLoad) {
|
|
117
|
+
setIsFirstLoad(false)
|
|
118
|
+
return props.onLoad((navigateToTabUrl) => navigate(navigateToTabUrl, {replace: false, state: {jumpBack: false}}))
|
|
119
|
+
} else if (hasVisualizationChanged) {
|
|
120
|
+
props.onVisualizationChanged((url) => navigate(url, {replace: false, state: {jumpBack: false}}), props.tenant ?? props.stateTenant)
|
|
121
|
+
} else if (hasInitialTabChanged && props.initialTab) {
|
|
122
|
+
props.onTabChange((url) => navigate(url, {replace: false, state: {jumpBack: false}}), props.initialTab)
|
|
123
|
+
} else {
|
|
124
|
+
props.onUpdate()
|
|
125
|
+
}
|
|
126
|
+
}, [props.visualizationId, props.accessToken, props.refreshToken, props.tenant, props.filters, props.dataLoaders, props.initialTab, shouldUpdate])
|
|
127
|
+
|
|
128
|
+
return <></>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const tabToPageType = (tab: HoparaTab): PageType => {
|
|
132
|
+
switch (tab) {
|
|
133
|
+
case HoparaTab.LAYERS:
|
|
134
|
+
return PageType.VisualizationLayerEditor
|
|
135
|
+
case HoparaTab.OBJECTS:
|
|
136
|
+
return PageType.VisualizationObjectEditor
|
|
137
|
+
case HoparaTab.SETTINGS:
|
|
138
|
+
return PageType.VisualizationSettings
|
|
139
|
+
default:
|
|
140
|
+
return PageType.VisualizationDetail
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const mapState = (state: Store, props: EmbeddedProps): ProviderStateProps => {
|
|
145
|
+
return {
|
|
146
|
+
...props,
|
|
147
|
+
tenant: props.tenant,
|
|
148
|
+
stateTenant: state.auth.authorization.tenant,
|
|
149
|
+
visualizationId: props.visualizationId ?? '',
|
|
150
|
+
stateVisualizationId: state.visualizationStore.visualization?.id ?? '',
|
|
151
|
+
stateFallbackVisualizationId: state.visualizationStore.fallbackVisualizationId ?? '',
|
|
152
|
+
stateAuthorization: state.auth.authorization,
|
|
153
|
+
filterStore: state.filterStore,
|
|
154
|
+
queryStore: state.queryStore,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const mapActions = (dispatch: any, props: EmbeddedProps): ProviderStateActions => {
|
|
159
|
+
const getActionPayload = (): HocLoadedPayload => {
|
|
160
|
+
const {tenants = [], scope = '', exp = 0, username} = getDecodedToken(props.accessToken)
|
|
161
|
+
const authorization = new Authorization({
|
|
162
|
+
accessToken: props.accessToken,
|
|
163
|
+
refreshToken: props.refreshToken,
|
|
164
|
+
expiration: exp,
|
|
165
|
+
tenant: props.tenant ?? tenants[0],
|
|
166
|
+
tenants,
|
|
167
|
+
permissions: scope?.split(' '),
|
|
168
|
+
clientId: username,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const filters = props.filters?.length ?
|
|
172
|
+
new SelectedFilters(...props.filters.map((filter) => new SelectedFilter(filter))) :
|
|
173
|
+
undefined
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
visualizationId: props.visualizationId,
|
|
177
|
+
fallbackVisualizationId: props.fallbackVisualizationId,
|
|
178
|
+
visualizationScope: props.visualizationScope,
|
|
179
|
+
dataLoaders: props.dataLoaders,
|
|
180
|
+
initialRow: props.initialRow,
|
|
181
|
+
darkMode: !!props.darkMode,
|
|
182
|
+
callbacks: props.callbacks,
|
|
183
|
+
filters,
|
|
184
|
+
authorization,
|
|
185
|
+
language: props.language,
|
|
186
|
+
navigationControls: props.navigationControls,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
onLoad: (navigate) => {
|
|
192
|
+
logInit('initializing module', props)
|
|
193
|
+
|
|
194
|
+
const actionPayload = getActionPayload()
|
|
195
|
+
dispatch(actions.configLoaded(actionPayload))
|
|
196
|
+
if (!props.initialTab) return
|
|
197
|
+
|
|
198
|
+
const initalTabURL = getPageUrl(
|
|
199
|
+
tabToPageType(props.initialTab),
|
|
200
|
+
actionPayload.authorization.tenant,
|
|
201
|
+
{
|
|
202
|
+
visualizationId: props.visualizationId,
|
|
203
|
+
fallbackVisualizationId: props.fallbackVisualizationId,
|
|
204
|
+
visualizationScope: props.visualizationScope,
|
|
205
|
+
initialLayerId: props.initialRow?.layerId,
|
|
206
|
+
initialRowId: props.initialRow?.rowId,
|
|
207
|
+
filters: props.filters,
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return navigate(initalTabURL)
|
|
212
|
+
},
|
|
213
|
+
onTabChange: (navigate: any, tab: HoparaTab) => {
|
|
214
|
+
const actionPayload = getActionPayload()
|
|
215
|
+
const initalTabURL = getPageUrl(
|
|
216
|
+
tabToPageType(tab),
|
|
217
|
+
actionPayload.authorization.tenant,
|
|
218
|
+
{
|
|
219
|
+
visualizationId: props.visualizationId,
|
|
220
|
+
fallbackVisualizationId: props.fallbackVisualizationId,
|
|
221
|
+
visualizationScope: props.visualizationScope,
|
|
222
|
+
initialLayerId: props.initialRow?.layerId,
|
|
223
|
+
initialRowId: props.initialRow?.rowId,
|
|
224
|
+
filters: props.filters,
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return navigate(initalTabURL)
|
|
229
|
+
},
|
|
230
|
+
onUpdate: () => {
|
|
231
|
+
logInit('updating module', props)
|
|
232
|
+
dispatch(actions.configUpdated(getActionPayload()))
|
|
233
|
+
},
|
|
234
|
+
onVisualizationChanged: (navigate, tenant) => {
|
|
235
|
+
if (!tenant) {
|
|
236
|
+
tenant = getActionPayload().authorization.tenant
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const url = getPageUrl(
|
|
240
|
+
PageType.VisualizationDetail,
|
|
241
|
+
tenant,
|
|
242
|
+
{
|
|
243
|
+
visualizationId: props.visualizationId,
|
|
244
|
+
fallbackVisualizationId: props.fallbackVisualizationId,
|
|
245
|
+
visualizationScope: props.visualizationScope,
|
|
246
|
+
initialLayerId: props.initialRow?.layerId,
|
|
247
|
+
initialRowId: props.initialRow?.rowId,
|
|
248
|
+
filters: props.filters,
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return navigate(url)
|
|
253
|
+
},
|
|
254
|
+
forceRefresh: () => dispatch(actions.forceRefresh()),
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const EmbeddedProvider = connect(mapState, mapActions)(Provider)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, {lazy, Suspense} from 'react'
|
|
2
|
+
import {Route, Routes} from 'react-router-dom'
|
|
3
|
+
import {Pages, PageType} from '@hopara/page/src/Pages'
|
|
4
|
+
import {LoadingSpinner} from '@hopara/design-system/src/loading-spinner/Spinner'
|
|
5
|
+
import {ConnectedAuthProvider} from '@hopara/components/src/hoc/AuthProvider'
|
|
6
|
+
import {RumRouteMonitoring} from '@hopara/rum/src/RumMonitoring'
|
|
7
|
+
|
|
8
|
+
const VisualizationOutlet = lazy(() => import(/* webpackPreload: true */ '@hopara/components/src/visualization/pages/VisualizationOutlet'))
|
|
9
|
+
const VisualizationPageTemplate = lazy(() => import(/* webpackPreload: true */ '@hopara/components/src/visualization/pages/VisualizationPageTemplate'))
|
|
10
|
+
const ObjectEditorOutlet = lazy(() => import(/* webpackPreload: true */ '@hopara/components/src/object/editor/ObjectEditorOutletContainer'))
|
|
11
|
+
const SettingsOutlet = lazy(() => import(/* webpackPreload: true */ '@hopara/components/src/settings/SettingsOutletContainer'))
|
|
12
|
+
const LayerEditorOutlet = lazy(() => import(/* webpackPreload: true */ '@hopara/components/src/layer/editor/LayerEditorOutletContainer'))
|
|
13
|
+
const ForbiddenPage = lazy(() => import(/* webpackPreload: true */ '@hopara/design-system/src/static-pages/ForbiddenPage'))
|
|
14
|
+
const NotFoundPage = lazy(() => import(/* webpackPreload: true */ '@hopara/design-system/src/static-pages/NotFoundPage'))
|
|
15
|
+
|
|
16
|
+
export const Router = () => {
|
|
17
|
+
return (
|
|
18
|
+
<ConnectedAuthProvider>
|
|
19
|
+
<Suspense fallback={<LoadingSpinner fullscreen={true}/>}>
|
|
20
|
+
<RumRouteMonitoring />
|
|
21
|
+
<Routes>
|
|
22
|
+
<Route path={Pages.getPath(PageType.Forbidden)} element={<ForbiddenPage/>}/>
|
|
23
|
+
<Route path={Pages.getPath(PageType.NotFound)} element={<NotFoundPage/>}/>
|
|
24
|
+
<Route element={<VisualizationPageTemplate/>}>
|
|
25
|
+
<Route path={Pages.getPath(PageType.VisualizationDetail)} element={<VisualizationOutlet/>}/>
|
|
26
|
+
<Route path={Pages.getPath(PageType.VisualizationObjectEditor)} element={<ObjectEditorOutlet/>}/>
|
|
27
|
+
<Route path={Pages.getPath(PageType.VisualizationSettings)} element={<SettingsOutlet/>}/>
|
|
28
|
+
<Route path={Pages.getPath(PageType.VisualizationLayerEditor)} element={<LayerEditorOutlet/>}/>
|
|
29
|
+
</Route>
|
|
30
|
+
<Route path="*" element={<LoadingSpinner fullscreen={true}/>}/>
|
|
31
|
+
</Routes>
|
|
32
|
+
</Suspense>
|
|
33
|
+
</ConnectedAuthProvider>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {MemoryRouter} from 'react-router-dom'
|
|
3
|
+
import {EmbeddedProvider, EmbeddedProps} from './Provider'
|
|
4
|
+
import { HoparaController } from '@hopara/components/src/hoc/HoparaController'
|
|
5
|
+
import { EventType, HOPARA_EVENT_TYPE, InitData, PostMessageEvent } from '../events/Events'
|
|
6
|
+
import { EventReceiver } from '../events/EventReceiver'
|
|
7
|
+
import { EventEmitter } from '../events/EventEmitter'
|
|
8
|
+
import {Router} from './Router'
|
|
9
|
+
import { PureComponent } from '@hopara/design-system'
|
|
10
|
+
import { Box } from '@mui/material'
|
|
11
|
+
import HoparaProvider from '@hopara/components/src/hoc/Provider'
|
|
12
|
+
import { DataLoader } from '@hopara/dataset'
|
|
13
|
+
import { PlainDataLoader } from '@hopara/dataset/src/loader/DataLoader'
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
children?: React.ReactElement
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class View extends PureComponent<Props, EmbeddedProps> {
|
|
20
|
+
constructor(props) {
|
|
21
|
+
super(props)
|
|
22
|
+
this.state = {
|
|
23
|
+
accessToken: '',
|
|
24
|
+
visualizationId: '',
|
|
25
|
+
fallbackVisualizationId: undefined,
|
|
26
|
+
visualizationScope: undefined,
|
|
27
|
+
env: undefined,
|
|
28
|
+
refreshToken: undefined,
|
|
29
|
+
tenant: undefined,
|
|
30
|
+
dataLoaders: undefined,
|
|
31
|
+
initialRow: undefined,
|
|
32
|
+
controller: undefined,
|
|
33
|
+
darkMode: undefined,
|
|
34
|
+
callbacks: undefined,
|
|
35
|
+
attribution: undefined,
|
|
36
|
+
filters: undefined,
|
|
37
|
+
language: undefined,
|
|
38
|
+
initialTab: undefined,
|
|
39
|
+
navigationControls: undefined,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private dataLoaderAsPostMessage(dataLoader: PlainDataLoader): DataLoader {
|
|
44
|
+
const migratedDataLoader = {
|
|
45
|
+
query: dataLoader.query ?? (dataLoader as any).name,
|
|
46
|
+
source: dataLoader.source,
|
|
47
|
+
cache: dataLoader.cache,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new DataLoader({
|
|
51
|
+
...migratedDataLoader,
|
|
52
|
+
loader: async (filterSet) => {
|
|
53
|
+
EventEmitter.loadDataRequest(migratedDataLoader, filterSet)
|
|
54
|
+
return EventReceiver.loadDataResponse(migratedDataLoader)
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private callbacksAsPostMessage(name: string): any {
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
callback: (data) => EventEmitter.callback(name, data),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
private getVisualizationId(event:PostMessageEvent<InitData>): string {
|
|
66
|
+
return event.data.visualizationId ?? event.data.fallbackVisualizationId!
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
handleInitEvent(event:PostMessageEvent<InitData>, callback?: () => void) {
|
|
70
|
+
const newState = Object.keys(this.state).reduce((state, key) => {
|
|
71
|
+
// we only update the state if the key is present in the new event data
|
|
72
|
+
return { ...state, [key]: key in event.data ? event.data[key] : state[key] }
|
|
73
|
+
}, this.state)
|
|
74
|
+
|
|
75
|
+
this.setState({
|
|
76
|
+
...newState,
|
|
77
|
+
accessToken: event.data.accessToken!,
|
|
78
|
+
visualizationId: this.getVisualizationId(event),
|
|
79
|
+
callbacks: event.data.callbackNames?.map(this.callbacksAsPostMessage.bind(this)),
|
|
80
|
+
dataLoaders: event.data.dataLoaders?.map(this.dataLoaderAsPostMessage.bind(this)),
|
|
81
|
+
controller: new HoparaController(),
|
|
82
|
+
}, callback)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private shouldSkipEvent(event:PostMessageEvent<any>): boolean {
|
|
86
|
+
const isValidEvent = event.data.__hopara__eventType__ === EventType.INIT ||
|
|
87
|
+
event.data.__hopara__eventType__ === EventType.UPDATE ||
|
|
88
|
+
event.data.__hopara__eventType__ == EventType.REFRESH
|
|
89
|
+
const hasVisualizationId = !!event.data.visualizationId ||
|
|
90
|
+
!!event.data.visualization ||
|
|
91
|
+
!!event.data.app ||
|
|
92
|
+
!!event.data.fallbackVisualizationId
|
|
93
|
+
|
|
94
|
+
return !isValidEvent || !event.data.accessToken || !hasVisualizationId
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
initMessageListener(event:PostMessageEvent<InitData>) {
|
|
98
|
+
if (this.shouldSkipEvent(event)) return
|
|
99
|
+
|
|
100
|
+
switch (event.data[HOPARA_EVENT_TYPE]) {
|
|
101
|
+
case (EventType.INIT):
|
|
102
|
+
case (EventType.UPDATE):
|
|
103
|
+
return this.handleInitEvent(event)
|
|
104
|
+
case (EventType.REFRESH):
|
|
105
|
+
return this.state.controller?.refresh()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
cachedInitMessageListener: EventListenerOrEventListenerObject
|
|
110
|
+
|
|
111
|
+
registerToReceiveMessage() {
|
|
112
|
+
if (!window) return
|
|
113
|
+
|
|
114
|
+
this.cachedInitMessageListener = this.initMessageListener.bind(this) as any
|
|
115
|
+
window.addEventListener('message', this.cachedInitMessageListener, false)
|
|
116
|
+
|
|
117
|
+
EventEmitter.ready()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
cachedRegisterToReceiveMessage: any
|
|
121
|
+
|
|
122
|
+
componentDidMount(): void {
|
|
123
|
+
this.cachedRegisterToReceiveMessage = this.registerToReceiveMessage.bind(this)
|
|
124
|
+
window.addEventListener('load', this.cachedRegisterToReceiveMessage, false)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
unregisterToReceiveMessage() {
|
|
128
|
+
if (!window) return
|
|
129
|
+
|
|
130
|
+
if (this.cachedRegisterToReceiveMessage) {
|
|
131
|
+
window.removeEventListener('load', this.cachedRegisterToReceiveMessage, false)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.cachedInitMessageListener) {
|
|
135
|
+
window.removeEventListener('message', this.cachedInitMessageListener, false)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
componentWillUnmount(): void {
|
|
140
|
+
this.unregisterToReceiveMessage()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
render() {
|
|
144
|
+
return (
|
|
145
|
+
<Box sx={{
|
|
146
|
+
'width': '100vw',
|
|
147
|
+
'height': '100vh',
|
|
148
|
+
'@supports (height: 100dvh)': { height: '100dvh' },
|
|
149
|
+
}}>
|
|
150
|
+
<HoparaProvider>
|
|
151
|
+
<MemoryRouter>
|
|
152
|
+
<EmbeddedProvider {...this.state as any} />
|
|
153
|
+
<Router />
|
|
154
|
+
</MemoryRouter>
|
|
155
|
+
</HoparaProvider>
|
|
156
|
+
</Box>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|