@hopara/iframe 0.2.1 → 0.2.3
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/README.md +136 -15
- package/build/client.js +1 -1
- package/package.json +3 -3
- package/tests/golden-images/circle-layer-with-entities.png +0 -0
- package/tests/golden-images/diff/circle-layer-with-entities.png +0 -0
- package/tests/golden-images/temp/circle-layer-with-entities.png +0 -0
- package/tests/spec/view.system.test.js +26 -7
package/README.md
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# Hopara Iframe Client
|
|
2
|
-
|
|
2
|
+
This package provides an iframe to add [Hopara](https://hopara.io) visualizations to your project.
|
|
3
3
|
|
|
4
4
|
# Installing
|
|
5
|
-
|
|
6
|
-
```shell
|
|
7
|
-
$ npm install @hopara/iframe
|
|
8
|
-
```
|
|
5
|
+
You can install it as a NPM module or using Hopara CDN directly:
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
As a module:
|
|
11
8
|
```shell
|
|
12
|
-
$
|
|
9
|
+
$ npm install @hopara/iframe
|
|
13
10
|
```
|
|
14
11
|
|
|
15
12
|
Using Hopara CDN:
|
|
@@ -17,15 +14,18 @@ Using Hopara CDN:
|
|
|
17
14
|
<script src="https://statics.hopara.app/embedded/latest/client.js"></script>
|
|
18
15
|
```
|
|
19
16
|
|
|
17
|
+
## Authentication
|
|
18
|
+
The iframe requires a valid `accessToken`. Use the `Auth API` to fetch it, as explained in our [integration guide](https://docs.hopara.app/docs/integration-guide/authentication-integration.html)
|
|
19
|
+
|
|
20
20
|
# Example
|
|
21
|
-
###
|
|
21
|
+
### Node.JS
|
|
22
22
|
```js
|
|
23
23
|
import React from 'react'
|
|
24
|
-
import
|
|
24
|
+
import Hopara from 'hopara'
|
|
25
25
|
|
|
26
26
|
class MyComponent extends React.Component {
|
|
27
27
|
componentDidMount(): void {
|
|
28
|
-
|
|
28
|
+
Hopara.init({
|
|
29
29
|
app: 'my-hopara-app',
|
|
30
30
|
accessToken: 'my-hopara-token',
|
|
31
31
|
targetElementId: 'my-target-element',
|
|
@@ -46,7 +46,7 @@ class MyComponent extends React.Component {
|
|
|
46
46
|
<script src="https://statics.hopara.app/embedded/latest/client.js"></script>
|
|
47
47
|
<script>
|
|
48
48
|
document.addEventListener("DOMContentLoaded", function(){
|
|
49
|
-
|
|
49
|
+
Hopara.init({
|
|
50
50
|
app: 'my-hopara-app',
|
|
51
51
|
accessToken: 'my-hopara-token',
|
|
52
52
|
targetElementId: 'my-target-element',
|
|
@@ -64,7 +64,7 @@ class MyComponent extends React.Component {
|
|
|
64
64
|
|
|
65
65
|
### Init
|
|
66
66
|
```js
|
|
67
|
-
|
|
67
|
+
Hopara.init(config)
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
### Hopara Config
|
|
@@ -73,10 +73,10 @@ hopara.init(config)
|
|
|
73
73
|
// The app id
|
|
74
74
|
app: string
|
|
75
75
|
|
|
76
|
-
// The accessToken
|
|
76
|
+
// The accessToken provided by auth API
|
|
77
77
|
accessToken: string
|
|
78
78
|
|
|
79
|
-
// Overwrites
|
|
79
|
+
// Overwrites data at rendering time
|
|
80
80
|
dataLoaders: DataLoader[] | undefined
|
|
81
81
|
|
|
82
82
|
// Provides a custom ways to update data
|
|
@@ -89,4 +89,125 @@ hopara.init(config)
|
|
|
89
89
|
initialRow: InitialRow | undefined
|
|
90
90
|
}
|
|
91
91
|
```
|
|
92
|
-
|
|
92
|
+
|
|
93
|
+
### Data Loader
|
|
94
|
+
By default Hopara will load the same visualization and data as seen as in hopara.app. You can use the data loader prop to provide a custom way to load the data.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
type DataLoader = {
|
|
98
|
+
// query name
|
|
99
|
+
name: string
|
|
100
|
+
|
|
101
|
+
// data source name
|
|
102
|
+
source: string
|
|
103
|
+
|
|
104
|
+
// callback to be used on data load
|
|
105
|
+
loader: (filterSet: {limit: number, offset:number, filters: any[]}) => Promise<Record<string, any>[]>
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
#### Example
|
|
111
|
+
|
|
112
|
+
```jsx
|
|
113
|
+
const dataLoaders = [{
|
|
114
|
+
name: 'queryName',
|
|
115
|
+
source: 'dataSourceName',
|
|
116
|
+
loader: () => {
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
id: 1,
|
|
120
|
+
name: 'Eiffel Tower',
|
|
121
|
+
country: 'France',
|
|
122
|
+
longitude: 48.85845461500544,
|
|
123
|
+
latitude: 2.294467845588995
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 2,
|
|
127
|
+
name: 'Liberty Statue',
|
|
128
|
+
country: 'USA',
|
|
129
|
+
longitude: 40.68815550804761,
|
|
130
|
+
latitude: -74.02620077137483
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 3,
|
|
134
|
+
name: 'Great Wall of China',
|
|
135
|
+
country: 'China',
|
|
136
|
+
longitude: 40.43211592595951,
|
|
137
|
+
latitude: 116.57040708445938,
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
}]
|
|
142
|
+
...
|
|
143
|
+
<div className="HoparaEmbedded">
|
|
144
|
+
<Hopara
|
|
145
|
+
app="your-app-id"
|
|
146
|
+
accessToken="your-access-token"
|
|
147
|
+
dataLoaders={dataLoaders}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Data Updater
|
|
152
|
+
By default Hopara will update the visualization in the same database configured in hopara.app. You can use the data updater prop to provide a custom way to update the data.
|
|
153
|
+
|
|
154
|
+
The data updater is called when placing objetcs in a visualization and when resizing images.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
type DataUpdater = {
|
|
158
|
+
// query name
|
|
159
|
+
name: string
|
|
160
|
+
|
|
161
|
+
// data source name
|
|
162
|
+
source: string
|
|
163
|
+
|
|
164
|
+
// callback to be used on data update
|
|
165
|
+
updater: (updatedRow: Record<string, any>, originalRow: Record<string, any>, diff: Partial<Record<string, any>>) => Promise<void>
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```jsx
|
|
170
|
+
const dataUpdaters = [{
|
|
171
|
+
name: 'queryName',
|
|
172
|
+
source: 'dataSourceName',
|
|
173
|
+
updater: (newRow) => {
|
|
174
|
+
const rowIndex = dataInMemory.findIndex((row) => row.id === newRow.id)
|
|
175
|
+
dataInMemory[rowIndex] = newRow
|
|
176
|
+
}
|
|
177
|
+
}]
|
|
178
|
+
|
|
179
|
+
...
|
|
180
|
+
|
|
181
|
+
<div className="HoparaEmbedded">
|
|
182
|
+
<Hopara
|
|
183
|
+
app="your-app-id"
|
|
184
|
+
accessToken="your-access-token"
|
|
185
|
+
dataUpdaters={dataUpdaters}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Initial Position
|
|
191
|
+
The initial position prop overrides the initial position of the visualization.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
type InitialPosition = {
|
|
195
|
+
latitude: number | undefined
|
|
196
|
+
longitude: number | undefined
|
|
197
|
+
x: number | undefined
|
|
198
|
+
y: number | undefined
|
|
199
|
+
z: number | undefined
|
|
200
|
+
zoom: number | undefined
|
|
201
|
+
bearing: number | undefined
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Initial Row
|
|
206
|
+
The initial row prop centers the visualization around a specific row (e.g. asset, facility).
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
type InitialRow = {
|
|
210
|
+
entityId: string
|
|
211
|
+
rowId: string
|
|
212
|
+
}
|
|
213
|
+
```
|
package/build/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function webpackUniversalModuleDefinition(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define([],factory);else
|
|
1
|
+
(function webpackUniversalModuleDefinition(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define([],factory);else{var a=factory();for(var i in a)(typeof exports==="object"?exports:root)[i]=a[i]}})(this,(function(){return function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(exports,"__esModule",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value==="object"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,"default",{enumerable:true,value});if(mode&2&&typeof value!="string")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module["default"]}:function getModuleExports(){return module};__webpack_require__.d(getter,"a",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p="";return __webpack_require__(__webpack_require__.s=0)}([function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"Hopara",(function(){return client_Hopara}));let EventType;(function(EventType){EventType["INIT"]="init";EventType["READY"]="ready";EventType["LOAD_DATA"]="loadData";EventType["LOAD_DATA_RESPONSE"]="loadDataResponse";EventType["UPDATE_DATA"]="updateData";EventType["UPDATE_DATA_RESPONSE"]="updateDataResponse"})(EventType||(EventType={}));const HOPARA_EVENT_TYPE="__hopara__eventType__";const isReadyEvent=event=>event.data[HOPARA_EVENT_TYPE]===EventType.READY;const isLoadDataEvent=event=>event.data[HOPARA_EVENT_TYPE]===EventType.LOAD_DATA;const isUpdateDataEvent=event=>event.data[HOPARA_EVENT_TYPE]===EventType.UPDATE_DATA;class EventEmitter_EventEmitter{static sendMessage(data,targetWindow){const target=targetWindow||window.parent||window.top;if(!window||window===target)return;target.postMessage(data,"*")}static ready(){this.sendMessage({[HOPARA_EVENT_TYPE]:EventType.READY})}static init(config,targetWindow){this.sendMessage(Object.assign(config,{[HOPARA_EVENT_TYPE]:EventType.INIT}),targetWindow)}static loadDataRequest(dataLoader,filterSet){this.sendMessage({[HOPARA_EVENT_TYPE]:EventType.LOAD_DATA,data:{name:dataLoader.name,source:dataLoader.source},filterSet})}static loadDataResponse(config,dataLoader,rows,targetWindow){this.sendMessage(Object.assign(config,{[HOPARA_EVENT_TYPE]:EventType.LOAD_DATA_RESPONSE},{data:{name:dataLoader.name,source:dataLoader.source},rows}),targetWindow)}static updateDataRequest(dataUpdater,newRow,oldRow,diff){this.sendMessage({[HOPARA_EVENT_TYPE]:EventType.UPDATE_DATA,data:{name:dataUpdater.name,source:dataUpdater.source},newRow,oldRow,diff})}}class client_Hopara{constructor(config){this.config=void 0;this.doInit=()=>{const targetElement=document.getElementById(this.config.targetElementId);if(!targetElement)return console&&console.warn("Hopara: targetElement not found");const iframe=this.createIframe();this.createListeners(iframe);targetElement.appendChild(iframe)};this.config=config}getIframeSrc(){const url=this.config.embeddedUrl?this.config.embeddedUrl:`https://statics.hopara.app/embedded/${this.config.version?this.config.version:"latest"}/index.html`;return`${url}${this.config.debug?"?debug=true":""}`}getIframeStyle(){return"background-color: transparent; border: 0px none transparent; padding: 0px; overflow: hidden; width: 100%; height: 100%;"}createIframe(){const iframe=document.createElement("iframe");iframe.src=this.getIframeSrc();iframe.style=this.getIframeStyle();return iframe}getCleanConfig(){var _this$config$dataLoad,_this$config$dataUpda;return{accessToken:this.config.accessToken,app:this.config.app,tenant:this.config.tenant,initialPosition:this.config.initialPosition,initialDataBrowserId:this.config.initialDataBrowserId,initialRowId:this.config.initialRowId,dataLoaders:(_this$config$dataLoad=this.config.dataLoaders)===null||_this$config$dataLoad===void 0?void 0:_this$config$dataLoad.map((dataLoader=>({name:dataLoader.name,source:dataLoader.source}))),dataUpdaters:(_this$config$dataUpda=this.config.dataUpdaters)===null||_this$config$dataUpda===void 0?void 0:_this$config$dataUpda.map((dataUpdater=>({name:dataUpdater.name,source:dataUpdater.source})))}}createListeners(iframe){window.addEventListener("message",(event=>{const targetWindow=iframe.contentWindow;if(!targetWindow)throw new Error("Hopara: targetWindow is not available");if(isReadyEvent(event)){return EventEmitter_EventEmitter.init(this.getCleanConfig(),targetWindow)}if(isLoadDataEvent(event)){var _this$config$dataLoad2;const dataLoader=(_this$config$dataLoad2=this.config.dataLoaders)===null||_this$config$dataLoad2===void 0?void 0:_this$config$dataLoad2.find((dataLoader=>dataLoader.name===event.data.data.name&&dataLoader.source===event.data.data.source));if(dataLoader)return dataLoader.loader(event.data.filterSet).then((data=>EventEmitter_EventEmitter.loadDataResponse(this.getCleanConfig(),dataLoader,data,targetWindow)))}if(isUpdateDataEvent(event)){var _this$config$dataUpda2;const dataUpdater=(_this$config$dataUpda2=this.config.dataUpdaters)===null||_this$config$dataUpda2===void 0?void 0:_this$config$dataUpda2.find((dataUpdater=>dataUpdater.name===event.data.data.name&&dataUpdater.source===event.data.data.source));if(dataUpdater)return dataUpdater.updater(event.data.newRow,event.data.oldRow,event.data.diff)}}))}static init(config){if(!config&&console){console.warn("Hopara: init config not present");return}if((!window||!window.document)&&console){console.warn("Hopara: window is not available");return}const client=new client_Hopara(config);return client.doInit()}}var client=__webpack_exports__["default"]=client_Hopara}])}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hopara/iframe",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"main": "build/client.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "ESLINT_NO_DEV_ERRORS=true react-app-rewired start",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
],
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@babel/register": "^7.15.3",
|
|
30
|
-
"@hopara/react": "^0.2.
|
|
31
|
-
"@hopara/system-test": "^0.2.
|
|
30
|
+
"@hopara/react": "^0.2.3",
|
|
31
|
+
"@hopara/system-test": "^0.2.3",
|
|
32
32
|
"babel-loader": "8.1.0",
|
|
33
33
|
"customize-cra": "^1.0.0",
|
|
34
34
|
"react": "^18.2.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -19,16 +19,35 @@ module.exports = {
|
|
|
19
19
|
browser.setWindowSize(1280, 800)
|
|
20
20
|
.url('http://localhost:3456/')
|
|
21
21
|
.execute(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
window.postMessage({
|
|
23
|
+
app: 'circle',
|
|
24
|
+
tenant: 'hopara.io',
|
|
25
|
+
accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJ0ZW5hbnRzIjpbImhvcGFyYS5pbyJdLCJzY29wZSI6InJvdzp3cml0ZSJ9.qJwSd1EUmiVX5NrgelVQdn9MDbdVNq4GGjzHPQlDQ1A',
|
|
26
|
+
targetElementId: 'embedded-target-element',
|
|
27
|
+
__hopara__eventType__: 'init'
|
|
28
|
+
}, '*')
|
|
29
29
|
})
|
|
30
30
|
.pause(waitTime())
|
|
31
31
|
.waitForElementVisible('#deckgl-overlay')
|
|
32
32
|
.assert.compareScreenshot('circle-layer.png', mismatchPercentage)
|
|
33
33
|
},
|
|
34
|
+
'Circle Layer With Entites': (browser) => {
|
|
35
|
+
mockServer.get('/app/circle').thenReply(200, JSON.stringify(responses.app.circleWithEntities))
|
|
36
|
+
mockServer.get('/view/queryName/row').thenReply(200, JSON.stringify(responses.geo.circle))
|
|
37
|
+
|
|
38
|
+
browser.setWindowSize(1280, 800)
|
|
39
|
+
.url('http://localhost:3456/')
|
|
40
|
+
.execute(() => {
|
|
41
|
+
window.postMessage({
|
|
42
|
+
app: 'circle',
|
|
43
|
+
tenant: 'hopara.io',
|
|
44
|
+
accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJ0ZW5hbnRzIjpbImhvcGFyYS5pbyJdLCJzY29wZSI6InJvdzp3cml0ZSJ9.qJwSd1EUmiVX5NrgelVQdn9MDbdVNq4GGjzHPQlDQ1A',
|
|
45
|
+
targetElementId: 'embedded-target-element',
|
|
46
|
+
__hopara__eventType__: 'init'
|
|
47
|
+
}, '*')
|
|
48
|
+
})
|
|
49
|
+
.pause(waitTime())
|
|
50
|
+
.waitForElementVisible('#deckgl-overlay')
|
|
51
|
+
.assert.compareScreenshot('circle-layer-with-entities.png', mismatchPercentage)
|
|
52
|
+
},
|
|
34
53
|
}
|