@nebula.js/cli-sense 2.0.0-beta.1
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/CHANGELOG.md +87 -0
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/command.js +13 -0
- package/lib/api-entry.js +11 -0
- package/lib/build-legacy.js +115 -0
- package/lib/build.js +121 -0
- package/lib/init-config.js +38 -0
- package/lib/sense.js +11 -0
- package/package.json +46 -0
- package/src/empty-ext.js +1 -0
- package/src/ext.js +12 -0
- package/src/legacy/__tests__/selections.spec.js +44 -0
- package/src/legacy/empty-ext.js +3 -0
- package/src/legacy/permissions.js +36 -0
- package/src/legacy/selections.js +82 -0
- package/src/legacy/sn-ext.js +287 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [2.0.0-beta.1](https://github.com/qlik-oss/nebula.js/compare/v1.7.0...v2.0.0-beta.1) (2021-09-21)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
9
|
+
|
|
10
|
+
# [1.7.0](https://github.com/qlik-oss/nebula.js/compare/v1.6.0...v1.7.0) (2021-08-24)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
13
|
+
|
|
14
|
+
# [1.6.0](https://github.com/qlik-oss/nebula.js/compare/v1.5.0...v1.6.0) (2021-08-16)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- use same event in extensionwrapper ([#651](https://github.com/qlik-oss/nebula.js/issues/651)) ([f70c575](https://github.com/qlik-oss/nebula.js/commit/f70c575ecab3916e89bd4d5dbc7c5b76b1afe11e))
|
|
19
|
+
- **extension-wrapper:** support paths argument in begin ([#639](https://github.com/qlik-oss/nebula.js/issues/639)) ([dbf7ea9](https://github.com/qlik-oss/nebula.js/commit/dbf7ea97ecec73c00d7a1e1a3d49ec57806cd763))
|
|
20
|
+
- add isModal for sense legacy extension ([#637](https://github.com/qlik-oss/nebula.js/issues/637)) ([54171df](https://github.com/qlik-oss/nebula.js/commit/54171dfed03f42d71d825127104090777a38fe53))
|
|
21
|
+
- **cli-sense:** apply default when using javascript api ([#633](https://github.com/qlik-oss/nebula.js/issues/633)) ([8eded30](https://github.com/qlik-oss/nebula.js/commit/8eded3055e2074a1ff718af69920f0eea0ea6462))
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
- switch --partial to default and add --legacy option ([#632](https://github.com/qlik-oss/nebula.js/issues/632)) ([6381cf6](https://github.com/qlik-oss/nebula.js/commit/6381cf6e796787823b888c08dae33342ca6606c7))
|
|
26
|
+
|
|
27
|
+
# [1.5.0](https://github.com/qlik-oss/nebula.js/compare/v1.4.0...v1.5.0) (2021-07-01)
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
- update build and build legacy for nebula sense ([#627](https://github.com/qlik-oss/nebula.js/issues/627)) ([da61276](https://github.com/qlik-oss/nebula.js/commit/da61276431d3356aca98781a4be736251d08e384))
|
|
32
|
+
|
|
33
|
+
# [1.4.0](https://github.com/qlik-oss/nebula.js/compare/v1.3.0...v1.4.0) (2021-06-28)
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
- **deps:** update dependency fs-extra to v10 ([#616](https://github.com/qlik-oss/nebula.js/issues/616)) ([73bf35f](https://github.com/qlik-oss/nebula.js/commit/73bf35f53f1882f89207a1215c35fc3c59f9c35a))
|
|
38
|
+
|
|
39
|
+
# [1.2.0](https://github.com/qlik-oss/nebula.js/compare/v1.1.1...v1.2.0) (2021-05-12)
|
|
40
|
+
|
|
41
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
42
|
+
|
|
43
|
+
## [1.1.1](https://github.com/qlik-oss/nebula.js/compare/v1.1.0...v1.1.1) (2021-04-19)
|
|
44
|
+
|
|
45
|
+
### Features
|
|
46
|
+
|
|
47
|
+
- add device type ([#587](https://github.com/qlik-oss/nebula.js/issues/587)) ([f6e1ead](https://github.com/qlik-oss/nebula.js/commit/f6e1ead7e14f029b1f78fa354fb98b2e089b52a9))
|
|
48
|
+
|
|
49
|
+
# [1.1.0](https://github.com/qlik-oss/nebula.js/compare/v1.1.0-alpha0...v1.1.0) (2021-02-01)
|
|
50
|
+
|
|
51
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
52
|
+
|
|
53
|
+
# [1.1.0-alpha0](https://github.com/qlik-oss/nebula.js/compare/v1.0.2-alpha.1...v1.1.0-alpha0) (2021-01-21)
|
|
54
|
+
|
|
55
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
56
|
+
|
|
57
|
+
## [1.0.2-alpha.1](https://github.com/qlik-oss/nebula.js/compare/v1.0.2-alpha.0...v1.0.2-alpha.1) (2020-12-04)
|
|
58
|
+
|
|
59
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
60
|
+
|
|
61
|
+
## [1.0.1](https://github.com/qlik-oss/nebula.js/compare/v1.0.0...v1.0.1) (2020-06-22)
|
|
62
|
+
|
|
63
|
+
### Bug Fixes
|
|
64
|
+
|
|
65
|
+
- **cli-sense:** do not remove target dir ([#451](https://github.com/qlik-oss/nebula.js/issues/451)) ([0f5432e](https://github.com/qlik-oss/nebula.js/commit/0f5432e))
|
|
66
|
+
|
|
67
|
+
# [1.0.0](https://github.com/qlik-oss/nebula.js/compare/v1.0.0-alpha.0...v1.0.0) (2020-06-09)
|
|
68
|
+
|
|
69
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
70
|
+
|
|
71
|
+
# [1.0.0-alpha.0](https://github.com/qlik-oss/nebula.js/compare/v0.6.1...v1.0.0-alpha.0) (2020-06-09)
|
|
72
|
+
|
|
73
|
+
### Features
|
|
74
|
+
|
|
75
|
+
- **cli-sense:** build for legacy Sense ([#438](https://github.com/qlik-oss/nebula.js/issues/438)) ([5485eab](https://github.com/qlik-oss/nebula.js/commit/5485eab))
|
|
76
|
+
|
|
77
|
+
# [0.5.0](https://github.com/qlik-oss/nebula.js/compare/v0.4.0...v0.5.0) (2020-04-20)
|
|
78
|
+
|
|
79
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
80
|
+
|
|
81
|
+
# [0.4.0](https://github.com/qlik-oss/nebula.js/compare/v0.3.0...v0.4.0) (2020-04-02)
|
|
82
|
+
|
|
83
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
|
84
|
+
|
|
85
|
+
# [0.3.0](https://github.com/qlik-oss/nebula.js/compare/v0.2.1...v0.3.0) (2020-03-30)
|
|
86
|
+
|
|
87
|
+
**Note:** Version bump only for package @nebula.js/cli-sense
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-present QlikTech International AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @nebula.js/cli-sense
|
|
2
|
+
|
|
3
|
+
The `nebula sense` command builds a nebula visualization so that it can be used as an extension in Qlik Sense.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @nebula.js/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
nebula sense
|
|
15
|
+
|
|
16
|
+
Build a nebula visualization as a Qlik Sense extension
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--version Show version number [boolean]
|
|
20
|
+
--ext Extension definition [string]
|
|
21
|
+
--meta Extension meta information [string]
|
|
22
|
+
--output Destination directory [string] [default: "<name>-ext"]
|
|
23
|
+
--minify Minify and uglify code [boolean] [default: true]
|
|
24
|
+
--sourcemap Generate sourcemaps [boolean] [default: false]
|
|
25
|
+
--legacy Generate legacy extension [boolean] [default: false]
|
|
26
|
+
-h, --help Show help [boolean]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
### EXT
|
|
32
|
+
|
|
33
|
+
You can set property panel definition and feature supprts by creating a separate file for the extension info and providing it as argument to `--ext`:
|
|
34
|
+
|
|
35
|
+
Create a file called def.js and add the following:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// def.js
|
|
39
|
+
export default {
|
|
40
|
+
definition: {
|
|
41
|
+
// Property panel definition
|
|
42
|
+
},
|
|
43
|
+
support: {
|
|
44
|
+
export: true,
|
|
45
|
+
exportData: true,
|
|
46
|
+
snapshot: true,
|
|
47
|
+
viewData: true,
|
|
48
|
+
},
|
|
49
|
+
importProperties: null,
|
|
50
|
+
exportProperties: null,
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run the command:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
nebula sense --ext def.js
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Note:
|
|
61
|
+
Using the --ext option will overwrite any ext definition already presented on the chart, its main purpose is to support legacy option below.
|
|
62
|
+
|
|
63
|
+
### Meta
|
|
64
|
+
|
|
65
|
+
You can improve meta info about the extension, such as extension name, extension
|
|
66
|
+
icon, and description by providing a `.json` formatted file and supply that filename
|
|
67
|
+
as an argument to the `nebula sense` command with `--meta` option.
|
|
68
|
+
|
|
69
|
+
Create a file called meta.json and add the following code demonstrating an example
|
|
70
|
+
to set the extension meta information:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"name": "My tasty banana extension",
|
|
75
|
+
"icon": "barchart",
|
|
76
|
+
"description": "Nebula test table wrapped in a Qlik Sense extension"
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Run the command:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
nebula sense --meta meta.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The meta data will be ended up in the `.qext` file used by the Qlik Sense.
|
|
87
|
+
|
|
88
|
+
Copy the updated `your-chart-ext` directory to your `Extension` directory,
|
|
89
|
+
overwriting the old version. Then the meta data of the extension has been changed.
|
|
90
|
+
|
|
91
|
+
The rest of the required information is populated automatically based on the content in `package.json`.
|
|
92
|
+
|
|
93
|
+
### Output
|
|
94
|
+
|
|
95
|
+
Generate all required files into the specified `--output` folder called sn-table-ext:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
nebula sense --output sn-table-ext
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
You can upload that folder as an extension on [Qlik Sense Enterprise for Windows](https://help.qlik.com/en-US/sense-developer/August2021/Subsystems/Extensions/Content/Sense_Extensions/Howtos/deploy-extensions.htm) or [SaaS editions of Qlik Sense](https://help.qlik.com/en-US/cloud-services/Subsystems/Hub/Content/Sense_Hub/Admin/mc-extensions.htm)
|
|
102
|
+
|
|
103
|
+
### QEXT
|
|
104
|
+
|
|
105
|
+
Running nebula sense generates a `QEXT` file for you, which is
|
|
106
|
+
required for loading the visualization into sense.
|
|
107
|
+
|
|
108
|
+
The `QEXT` file can also be manually created by yourself.
|
|
109
|
+
Create a file called `your-extension-name.qext` and add the following code as an
|
|
110
|
+
example:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"name": "your-extension-name",
|
|
115
|
+
"version": "0.1.0",
|
|
116
|
+
"description": "",
|
|
117
|
+
"author": {
|
|
118
|
+
"name": "",
|
|
119
|
+
"email": ""
|
|
120
|
+
},
|
|
121
|
+
"icon": "extension",
|
|
122
|
+
"type": "visualization",
|
|
123
|
+
"supernova": true
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The `"supernova": true` attribute should not be added when building with the
|
|
128
|
+
--legacy option below.
|
|
129
|
+
|
|
130
|
+
### Legacy
|
|
131
|
+
|
|
132
|
+
Qlik Sense before 2020 does not support nebula supernova natively, so a special
|
|
133
|
+
build path is needed for visualizations to load properly in the client.
|
|
134
|
+
For this purpose, use the --legacy option.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
nebula sense --legacy
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
You can find that the generated `QEXT` file does not include `supernova: true`.
|
|
141
|
+
|
|
142
|
+
Note:
|
|
143
|
+
For old Qlik Sense, not all features of extension are presented.
|
package/command.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const init = require('./lib/init-config');
|
|
2
|
+
const sense = require('./lib/sense');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
command: 'sense',
|
|
6
|
+
desc: 'Build a nebula visualization as a Qlik Sense extension',
|
|
7
|
+
builder(yargs) {
|
|
8
|
+
init(yargs);
|
|
9
|
+
},
|
|
10
|
+
handler(argv) {
|
|
11
|
+
sense(argv);
|
|
12
|
+
},
|
|
13
|
+
};
|
package/lib/api-entry.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const extend = require('extend');
|
|
2
|
+
const yargs = require('yargs');
|
|
3
|
+
const initConfig = require('./init-config');
|
|
4
|
+
const sense = require('./sense');
|
|
5
|
+
|
|
6
|
+
module.exports = (argv) => {
|
|
7
|
+
// not runnning via command line, run the config to inject default values
|
|
8
|
+
const defaultBuildConfig = initConfig(yargs([])).argv;
|
|
9
|
+
const senseConfig = extend(true, {}, defaultBuildConfig, argv);
|
|
10
|
+
sense(senseConfig);
|
|
11
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
const rollup = require('rollup');
|
|
5
|
+
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
|
6
|
+
const common = require('@rollup/plugin-commonjs');
|
|
7
|
+
const replace = require('@rollup/plugin-replace');
|
|
8
|
+
const babel = require('rollup-plugin-babel');
|
|
9
|
+
const { terser } = require('rollup-plugin-terser');
|
|
10
|
+
|
|
11
|
+
async function build(argv) {
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
|
|
14
|
+
const supernovaPkg = require(path.resolve(cwd, 'package.json')); // eslint-disable-line
|
|
15
|
+
|
|
16
|
+
let extName = supernovaPkg.name.split('/').reverse()[0];
|
|
17
|
+
|
|
18
|
+
const { main } = supernovaPkg;
|
|
19
|
+
|
|
20
|
+
if (extName === main) {
|
|
21
|
+
extName = extName.replace(/\.js$/, '-ext.js');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const targetDir = argv.output !== '<name>-ext' ? argv.output : `${extName}-ext`;
|
|
25
|
+
const qextLegacyTargetDir = path.resolve(cwd, targetDir);
|
|
26
|
+
const qextFileName = path.resolve(qextLegacyTargetDir, `${extName}.qext`);
|
|
27
|
+
const qextFileNameJs = qextFileName.replace(/\.qext$/, '.js');
|
|
28
|
+
|
|
29
|
+
const extDefinition = argv.ext ? path.resolve(argv.ext) : '';
|
|
30
|
+
|
|
31
|
+
const relativeMainFile = `./${main.replace(/\.js$/, '')}`;
|
|
32
|
+
|
|
33
|
+
async function moveSnBundle() {
|
|
34
|
+
const code = await fs.readFile(path.resolve(cwd, main), { encoding: 'utf8' });
|
|
35
|
+
const replacedCode = code.replace(/@nebula.js\/stardust/g, '../nlib/@nebula.js/stardust/dist/stardust');
|
|
36
|
+
|
|
37
|
+
await fs.outputFile(path.resolve(qextLegacyTargetDir, main), replacedCode);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function moveResources() {
|
|
41
|
+
await fs.copy(
|
|
42
|
+
path.resolve(path.dirname(require.resolve('@nebula.js/stardust')), 'dist'),
|
|
43
|
+
path.resolve(qextLegacyTargetDir, 'nlib/@nebula.js/stardust/dist')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function createQextFiles() {
|
|
48
|
+
const qext = supernovaPkg.qext || {};
|
|
49
|
+
if (argv.meta) {
|
|
50
|
+
const meta = require(path.resolve(cwd, argv.meta)); // eslint-disable-line
|
|
51
|
+
Object.assign(qext, meta);
|
|
52
|
+
}
|
|
53
|
+
const contents = {
|
|
54
|
+
name: qext.name || extName,
|
|
55
|
+
version: supernovaPkg.version,
|
|
56
|
+
description: supernovaPkg.description,
|
|
57
|
+
author: supernovaPkg.author,
|
|
58
|
+
icon: qext.icon || 'extension',
|
|
59
|
+
preview: qext.preview,
|
|
60
|
+
type: 'visualization',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
await fs.writeFile(qextFileName, JSON.stringify(contents, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function wrapIt() {
|
|
67
|
+
const bundle = await rollup.rollup({
|
|
68
|
+
input: path.resolve(__dirname, '../src/legacy/sn-ext.js'),
|
|
69
|
+
external: ['translator', 'qlik', './nlib/@nebula.js/stardust/dist/stardust', relativeMainFile],
|
|
70
|
+
plugins: [
|
|
71
|
+
replace({
|
|
72
|
+
__SN_DEF__: `${relativeMainFile}`,
|
|
73
|
+
__EXT_DEF__: `${extDefinition.replace(/\\/g, '/')}`,
|
|
74
|
+
preventAssignment: true,
|
|
75
|
+
}),
|
|
76
|
+
nodeResolve(),
|
|
77
|
+
common(),
|
|
78
|
+
babel({
|
|
79
|
+
babelrc: false,
|
|
80
|
+
exclude: [/node_modules/],
|
|
81
|
+
presets: [
|
|
82
|
+
[
|
|
83
|
+
'@babel/preset-env',
|
|
84
|
+
{
|
|
85
|
+
modules: false,
|
|
86
|
+
targets: {
|
|
87
|
+
browsers: ['ie 11', 'chrome 47'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
],
|
|
92
|
+
}),
|
|
93
|
+
argv.minify &&
|
|
94
|
+
terser({
|
|
95
|
+
output: {
|
|
96
|
+
comments: /@license|@preserve|Copyright|license/,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await bundle.write({
|
|
103
|
+
file: qextFileNameJs,
|
|
104
|
+
format: 'amd',
|
|
105
|
+
sourcemap: argv.sourcemap,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await moveSnBundle();
|
|
110
|
+
await moveResources();
|
|
111
|
+
await wrapIt();
|
|
112
|
+
await createQextFiles();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = build;
|
package/lib/build.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
const rollup = require('rollup');
|
|
5
|
+
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
|
6
|
+
const common = require('@rollup/plugin-commonjs');
|
|
7
|
+
const babel = require('rollup-plugin-babel');
|
|
8
|
+
const { terser } = require('rollup-plugin-terser');
|
|
9
|
+
|
|
10
|
+
async function build(argv) {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
const supernovaPkg = require(path.resolve(cwd, 'package.json')); // eslint-disable-line
|
|
14
|
+
|
|
15
|
+
let extName = supernovaPkg.name.split('/').reverse()[0]; // replace(/\//, '-').replace('@', '');
|
|
16
|
+
|
|
17
|
+
const { main } = supernovaPkg;
|
|
18
|
+
|
|
19
|
+
if (extName === main) {
|
|
20
|
+
extName = extName.replace(/\.js$/, '-ext.js');
|
|
21
|
+
}
|
|
22
|
+
const { sourcemap } = argv;
|
|
23
|
+
const targetDir = argv.output !== '<name>-ext' ? argv.output : `${extName}-ext`;
|
|
24
|
+
const targetFile = 'ext';
|
|
25
|
+
const qextLegacyTargetDir = path.resolve(cwd, targetDir);
|
|
26
|
+
const qextFileName = path.resolve(qextLegacyTargetDir, `${extName}.qext`);
|
|
27
|
+
const qextFileNameJs = qextFileName.replace(/\.qext$/, '.js');
|
|
28
|
+
|
|
29
|
+
const extDefinition = argv.ext ? path.resolve(argv.ext) : undefined;
|
|
30
|
+
|
|
31
|
+
const createQextFiles = () => {
|
|
32
|
+
const qext = supernovaPkg.qext || {};
|
|
33
|
+
if (argv.meta) {
|
|
34
|
+
const meta = require(path.resolve(cwd, argv.meta)); // eslint-disable-line
|
|
35
|
+
Object.assign(qext, meta);
|
|
36
|
+
}
|
|
37
|
+
const contents = {
|
|
38
|
+
name: qext.name || extName,
|
|
39
|
+
version: supernovaPkg.version,
|
|
40
|
+
description: supernovaPkg.description,
|
|
41
|
+
author: supernovaPkg.author,
|
|
42
|
+
icon: qext.icon || 'extension',
|
|
43
|
+
preview: qext.preview,
|
|
44
|
+
type: 'visualization',
|
|
45
|
+
supernova: true,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
let qextjs = fs.readFileSync(path.resolve(__dirname, extDefinition ? '../src/ext.js' : '../src/empty-ext.js'), {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
});
|
|
51
|
+
qextjs = qextjs.replace('{{DIST}}', `./${main.replace(/^[./]*/, '').replace(/\.js$/, '')}`);
|
|
52
|
+
qextjs = qextjs.replace('{{EXT}}', `./${targetFile}`);
|
|
53
|
+
|
|
54
|
+
fs.writeFileSync(qextFileName, JSON.stringify(contents, null, 2));
|
|
55
|
+
fs.writeFileSync(qextFileNameJs, qextjs);
|
|
56
|
+
|
|
57
|
+
if (supernovaPkg.files) {
|
|
58
|
+
[
|
|
59
|
+
extDefinition ? path.basename(qextLegacyTargetDir) : false,
|
|
60
|
+
path.basename(qextFileNameJs),
|
|
61
|
+
path.basename(qextFileName),
|
|
62
|
+
]
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.forEach((f) => {
|
|
65
|
+
if (!supernovaPkg.files.includes(f)) {
|
|
66
|
+
console.warn(` \x1b[33mwarn:\x1b[0m \x1b[36m${f}\x1b[0m should be included in package.json 'files' array`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const copySource = () => {
|
|
73
|
+
fs.copySync(path.resolve(main), path.resolve(targetDir, main));
|
|
74
|
+
|
|
75
|
+
if (sourcemap) {
|
|
76
|
+
fs.copySync(path.resolve(`${main}.map`), path.resolve(targetDir, `${main}.map`));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (extDefinition) {
|
|
81
|
+
const bundle = await rollup.rollup({
|
|
82
|
+
input: extDefinition,
|
|
83
|
+
plugins: [
|
|
84
|
+
nodeResolve(),
|
|
85
|
+
common(),
|
|
86
|
+
babel({
|
|
87
|
+
babelrc: false,
|
|
88
|
+
exclude: [/node_modules/],
|
|
89
|
+
presets: [
|
|
90
|
+
[
|
|
91
|
+
'@babel/preset-env',
|
|
92
|
+
{
|
|
93
|
+
modules: false,
|
|
94
|
+
targets: {
|
|
95
|
+
browsers: ['ie 11', 'chrome 47'],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
],
|
|
100
|
+
}),
|
|
101
|
+
argv.minify &&
|
|
102
|
+
terser({
|
|
103
|
+
output: {
|
|
104
|
+
comments: /@license|@preserve|Copyright|license/,
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await bundle.write({
|
|
111
|
+
file: path.resolve(qextLegacyTargetDir, `${targetFile}.js`),
|
|
112
|
+
format: 'amd',
|
|
113
|
+
sourcemap,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
copySource();
|
|
118
|
+
createQextFiles();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = build;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const options = {
|
|
2
|
+
ext: {
|
|
3
|
+
type: 'string',
|
|
4
|
+
required: false,
|
|
5
|
+
desc: 'Extension definition',
|
|
6
|
+
},
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
required: false,
|
|
10
|
+
desc: 'Extension meta information',
|
|
11
|
+
},
|
|
12
|
+
output: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
required: false,
|
|
15
|
+
default: '<name>-ext',
|
|
16
|
+
desc: 'Destination directory',
|
|
17
|
+
},
|
|
18
|
+
minify: {
|
|
19
|
+
type: 'boolean',
|
|
20
|
+
required: false,
|
|
21
|
+
default: true,
|
|
22
|
+
desc: 'Minify and uglify code',
|
|
23
|
+
},
|
|
24
|
+
sourcemap: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
required: false,
|
|
27
|
+
default: false,
|
|
28
|
+
desc: 'Generate sourcemaps',
|
|
29
|
+
},
|
|
30
|
+
legacy: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
required: false,
|
|
33
|
+
default: false,
|
|
34
|
+
desc: 'Generate legacy extension',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports = (yargs) => yargs.options(options);
|
package/lib/sense.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebula.js/cli-sense",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"description": "Build a supernova as a Qlik Sense extension",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "QlikTech International AB",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nebula",
|
|
9
|
+
"sense",
|
|
10
|
+
"qlik",
|
|
11
|
+
"extension"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/qlik-oss/nebula.js.git"
|
|
19
|
+
},
|
|
20
|
+
"main": "lib/api-entry.js",
|
|
21
|
+
"files": [
|
|
22
|
+
"command.js",
|
|
23
|
+
"lib",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"lint": "eslint src"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@babel/cli": "7.14.8",
|
|
31
|
+
"@babel/core": "7.15.0",
|
|
32
|
+
"@babel/preset-env": "7.15.0",
|
|
33
|
+
"@nebula.js/stardust": "^2.0.0-beta.1",
|
|
34
|
+
"@rollup/plugin-commonjs": "20.0.0",
|
|
35
|
+
"@rollup/plugin-node-resolve": "13.0.4",
|
|
36
|
+
"@rollup/plugin-replace": "3.0.0",
|
|
37
|
+
"extend": "3.0.2",
|
|
38
|
+
"fs-extra": "10.0.0",
|
|
39
|
+
"node-event-emitter": "0.0.1",
|
|
40
|
+
"rollup": "2.56.3",
|
|
41
|
+
"rollup-plugin-babel": "4.4.0",
|
|
42
|
+
"rollup-plugin-terser": "7.0.2",
|
|
43
|
+
"yargs": "17.1.1"
|
|
44
|
+
},
|
|
45
|
+
"gitHead": "851bf623f70a49f07615fe3b7e37b8b44fad534f"
|
|
46
|
+
}
|
package/src/empty-ext.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
define(['{{DIST}}'], (supernova) => supernova);
|
package/src/ext.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
define(['{{DIST}}', '{{EXT}}'], (supernova, ext) =>
|
|
2
|
+
function supernovaExtension(env) {
|
|
3
|
+
var v = supernova(env);
|
|
4
|
+
var extDef = ext;
|
|
5
|
+
/* eslint no-underscore-dangle:0 */
|
|
6
|
+
if (ext.__esmodule === true) {
|
|
7
|
+
// Handles es modules with multiple exports
|
|
8
|
+
extDef = ext.default;
|
|
9
|
+
}
|
|
10
|
+
v.ext = typeof extDef === 'function' ? extDef(env) : extDef;
|
|
11
|
+
return v;
|
|
12
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import createSelections from '../selections';
|
|
2
|
+
|
|
3
|
+
describe('supernova-wrapper', () => {
|
|
4
|
+
describe('selections', () => {
|
|
5
|
+
let selectionsApi;
|
|
6
|
+
let backendApi;
|
|
7
|
+
let selections;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
selectionsApi = {
|
|
11
|
+
activated: sinon.stub(),
|
|
12
|
+
deactivated: sinon.stub(),
|
|
13
|
+
};
|
|
14
|
+
backendApi = {
|
|
15
|
+
beginSelections: sinon.stub(),
|
|
16
|
+
endSelections: sinon.stub(),
|
|
17
|
+
model: {
|
|
18
|
+
resetMadeSelections: sinon.stub(),
|
|
19
|
+
app: {
|
|
20
|
+
switchModalSelection: sinon.stub(),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
selections = createSelections({ selectionsApi, backendApi });
|
|
25
|
+
selections.emit = sinon.stub();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should `begin`', () => {
|
|
29
|
+
selections.begin();
|
|
30
|
+
expect(selectionsApi.activated).to.have.been.calledWithExactly(true);
|
|
31
|
+
expect(backendApi.beginSelections).to.have.been.calledWithExactly();
|
|
32
|
+
expect(selections.emit).to.have.been.calledWithExactly('activated');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should `begin` with paths', () => {
|
|
36
|
+
const paths = ['hypercubePath', 'otherHypercubePath'];
|
|
37
|
+
selections.begin(paths);
|
|
38
|
+
expect(selectionsApi.activated).to.have.been.calledWithExactly(true);
|
|
39
|
+
expect(backendApi.beginSelections).to.not.been.called;
|
|
40
|
+
expect(backendApi.model.app.switchModalSelection).to.have.been.calledWithExactly(backendApi.model, paths);
|
|
41
|
+
expect(selections.emit).to.have.been.calledWithExactly('activated');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const INTERACTION_STATES = {
|
|
2
|
+
STATIC: 0,
|
|
3
|
+
ANALYSIS: 1,
|
|
4
|
+
EDIT: 2,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function showTooltip(options) {
|
|
8
|
+
if (options.tooltips) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return options.interactionState === INTERACTION_STATES.ANALYSIS && options.tooltips !== false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function permissions(options, backendApi) {
|
|
15
|
+
const p = [];
|
|
16
|
+
if (showTooltip(options)) {
|
|
17
|
+
p.push('passive');
|
|
18
|
+
}
|
|
19
|
+
if (
|
|
20
|
+
options.interactionState === INTERACTION_STATES.ANALYSIS &&
|
|
21
|
+
options.tooltips !== false &&
|
|
22
|
+
options.limitedInteraction !== true
|
|
23
|
+
) {
|
|
24
|
+
p.push('interact');
|
|
25
|
+
}
|
|
26
|
+
if (!backendApi.isSnapshot) {
|
|
27
|
+
if (options.selections !== false) {
|
|
28
|
+
p.push('select');
|
|
29
|
+
}
|
|
30
|
+
p.push('fetch');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return p;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default permissions;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* eslint no-param-reassign: 0 */
|
|
2
|
+
import EventEmitter from 'node-event-emitter';
|
|
3
|
+
|
|
4
|
+
export default (scope) => {
|
|
5
|
+
const mixin = (obj) => {
|
|
6
|
+
Object.keys(EventEmitter.prototype).forEach((key) => {
|
|
7
|
+
obj[key] = EventEmitter.prototype[key];
|
|
8
|
+
});
|
|
9
|
+
EventEmitter.init(obj);
|
|
10
|
+
return obj;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// TODO one and only one
|
|
14
|
+
|
|
15
|
+
const selectionAPI = {
|
|
16
|
+
begin(paths) {
|
|
17
|
+
const suppressBeginSelections = true;
|
|
18
|
+
scope.selectionsApi.activated(suppressBeginSelections);
|
|
19
|
+
if (paths) {
|
|
20
|
+
scope.backendApi.model.app.switchModalSelection(scope.backendApi.model, paths);
|
|
21
|
+
} else {
|
|
22
|
+
scope.backendApi.beginSelections();
|
|
23
|
+
}
|
|
24
|
+
selectionAPI.emit('activated');
|
|
25
|
+
},
|
|
26
|
+
clear() {
|
|
27
|
+
scope.backendApi.model.resetMadeSelections();
|
|
28
|
+
},
|
|
29
|
+
confirm() {
|
|
30
|
+
scope.selectionsApi.confirm();
|
|
31
|
+
},
|
|
32
|
+
cancel() {
|
|
33
|
+
scope.selectionsApi.cancel();
|
|
34
|
+
},
|
|
35
|
+
select(s) {
|
|
36
|
+
if (!scope.selectionsApi.active) {
|
|
37
|
+
scope.backendApi.beginSelections();
|
|
38
|
+
}
|
|
39
|
+
scope.backendApi.model[s.method](...s.params).then((qSuccess) => {
|
|
40
|
+
if (!qSuccess) {
|
|
41
|
+
scope.selectionsApi.selectionsMade = false;
|
|
42
|
+
this.clear();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
scope.selectionsApi.selectionsMade = s.method !== 'resetMadeSelections';
|
|
46
|
+
},
|
|
47
|
+
isActive() {
|
|
48
|
+
return scope.selectionsApi.active;
|
|
49
|
+
},
|
|
50
|
+
isModal() {
|
|
51
|
+
return scope.backendApi.model === (scope.backendApi.model.app && scope.backendApi.model.app.modalSelectionObject);
|
|
52
|
+
},
|
|
53
|
+
refreshToolbar() {
|
|
54
|
+
scope.selectionsApi.refreshToolbar();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
mixin(selectionAPI);
|
|
59
|
+
|
|
60
|
+
scope.selectionsApi.confirm = () => {
|
|
61
|
+
scope.backendApi.endSelections(true).then(() => {
|
|
62
|
+
scope.selectionsApi.deactivated();
|
|
63
|
+
selectionAPI.emit('confirmed');
|
|
64
|
+
selectionAPI.emit('deactivated');
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
scope.selectionsApi.clear = () => {
|
|
69
|
+
scope.backendApi.clearSelections();
|
|
70
|
+
scope.selectionsApi.selectionsMade = false;
|
|
71
|
+
selectionAPI.emit('cleared');
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
scope.selectionsApi.cancel = () => {
|
|
75
|
+
scope.backendApi.endSelections(false);
|
|
76
|
+
scope.selectionsApi.deactivated();
|
|
77
|
+
selectionAPI.emit('canceled');
|
|
78
|
+
selectionAPI.emit('deactivated');
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return selectionAPI;
|
|
82
|
+
};
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/* eslint no-underscore-dangle: 0 */
|
|
2
|
+
/* eslint import/no-unresolved: 0 */
|
|
3
|
+
/* eslint import/extensions: 0 */
|
|
4
|
+
|
|
5
|
+
// Sense dependencies
|
|
6
|
+
import qlik from 'qlik';
|
|
7
|
+
import senseTranslator from 'translator';
|
|
8
|
+
|
|
9
|
+
// injected
|
|
10
|
+
import snDefinition from '__SN_DEF__';
|
|
11
|
+
import extDefinition from '__EXT_DEF__';
|
|
12
|
+
import emptyExtDefinition from './empty-ext';
|
|
13
|
+
|
|
14
|
+
// lib dependencies
|
|
15
|
+
import { __DO_NOT_USE__ } from './nlib/@nebula.js/stardust/dist/stardust';
|
|
16
|
+
|
|
17
|
+
// wrapper code
|
|
18
|
+
import permissions from './permissions';
|
|
19
|
+
import selectionsApi from './selections';
|
|
20
|
+
|
|
21
|
+
const { generator: supernova, theme: themeFn, locale: localeFn } = __DO_NOT_USE__;
|
|
22
|
+
|
|
23
|
+
// ------- locale ---------
|
|
24
|
+
// use nebula locale API to ensure all viz get the same API regardless
|
|
25
|
+
// if consumed in Sense or outside
|
|
26
|
+
const loc = localeFn({
|
|
27
|
+
initial: senseTranslator.language,
|
|
28
|
+
});
|
|
29
|
+
const translator = {
|
|
30
|
+
...loc.translator,
|
|
31
|
+
// wrap get() for this API in order to prefer strings provided by Sense
|
|
32
|
+
get(str, args) {
|
|
33
|
+
const s = senseTranslator.get(str, args);
|
|
34
|
+
if (s === str) {
|
|
35
|
+
return loc.translator.get(str, args);
|
|
36
|
+
}
|
|
37
|
+
return s;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// ------------------------
|
|
41
|
+
|
|
42
|
+
// Galaxy is usually provided by @nebula.js/nucleus when a viz is instantiated.
|
|
43
|
+
// Since nucleus is not being used in Sense yet, this interface MUST be provided explicitly here
|
|
44
|
+
// and it MUST have the same interface as the one provided by nucleus.
|
|
45
|
+
const galaxy = {
|
|
46
|
+
deviceType: 'auto',
|
|
47
|
+
translator,
|
|
48
|
+
flags: {
|
|
49
|
+
isEnabled: () => false,
|
|
50
|
+
},
|
|
51
|
+
anything: {},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const ALLOWED_OPTIONS = ['viewState', 'direction', 'renderer'];
|
|
55
|
+
function limitOptions(newOptions, oldOptions) {
|
|
56
|
+
const op = {};
|
|
57
|
+
let opChanged = false;
|
|
58
|
+
Object.keys(newOptions).forEach((key) => {
|
|
59
|
+
if (ALLOWED_OPTIONS.indexOf(key) === -1) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
op[key] = newOptions[key];
|
|
63
|
+
if (oldOptions[key] !== newOptions[key]) {
|
|
64
|
+
opChanged = true;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (opChanged) {
|
|
68
|
+
return op;
|
|
69
|
+
}
|
|
70
|
+
return oldOptions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createActions(actions) {
|
|
74
|
+
return actions.map((a) => {
|
|
75
|
+
const senseItem = {
|
|
76
|
+
isIcon: true,
|
|
77
|
+
buttonClass: 'sel-toolbar-icon-toggle',
|
|
78
|
+
isActive() {
|
|
79
|
+
return a.active;
|
|
80
|
+
},
|
|
81
|
+
isDisabled() {
|
|
82
|
+
return a.disabled;
|
|
83
|
+
},
|
|
84
|
+
action() {
|
|
85
|
+
a.action();
|
|
86
|
+
},
|
|
87
|
+
getSvgIcon() {
|
|
88
|
+
const icon = a.getSvgIconShape();
|
|
89
|
+
const { viewBox = '0 0 16 16' } = icon;
|
|
90
|
+
const shapes = icon.shapes
|
|
91
|
+
.map(
|
|
92
|
+
({ type, attrs }) =>
|
|
93
|
+
`<${type} ${Object.keys(attrs)
|
|
94
|
+
.map((k) => `${k}="${attrs[k]}"`)
|
|
95
|
+
.join(' ')}/>`
|
|
96
|
+
)
|
|
97
|
+
.join('');
|
|
98
|
+
return `<i style="display:inline-block;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.2em;text-rendering:optimizeLegibility;web-kit-font-smoothing:antialiased;moz-osx-font-smoothing:grayscale"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="${viewBox}" fill="currentColor">${shapes}</svg></i>`;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
Object.defineProperties(senseItem, {
|
|
102
|
+
name: {
|
|
103
|
+
get: () => a.label,
|
|
104
|
+
},
|
|
105
|
+
hidden: {
|
|
106
|
+
get: () => a.hidden,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
return senseItem;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function updateTheme(ref) {
|
|
114
|
+
return qlik
|
|
115
|
+
.currApp(ref)
|
|
116
|
+
.theme.getApplied()
|
|
117
|
+
.then((qTheme) => {
|
|
118
|
+
if (ref.nTheme.externalAPI.name() !== qTheme.id) {
|
|
119
|
+
ref.nTheme.internalAPI.setTheme(qTheme.properties, qTheme.id);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============= EXTENSON =====================================================
|
|
125
|
+
|
|
126
|
+
const snGenerator = supernova(snDefinition, galaxy);
|
|
127
|
+
const ext =
|
|
128
|
+
typeof extDefinition === 'function'
|
|
129
|
+
? extDefinition({ translator })
|
|
130
|
+
: extDefinition || snGenerator.definition.ext || emptyExtDefinition || {};
|
|
131
|
+
let data;
|
|
132
|
+
|
|
133
|
+
if (snGenerator.qae.data.targets[0]) {
|
|
134
|
+
const d = snGenerator.qae.data.targets[0];
|
|
135
|
+
// supernova uses the convention of 'added' instead of 'add' since the
|
|
136
|
+
// method is called after the dimension/measures has been added to the hypercube.
|
|
137
|
+
// we therefore need to map supernova callbacks to the ones supported in Sense
|
|
138
|
+
const map = (v) => ({
|
|
139
|
+
...v,
|
|
140
|
+
add: v.added,
|
|
141
|
+
remove: v.removed,
|
|
142
|
+
replace: v.replaced,
|
|
143
|
+
move: v.moved,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
data = {};
|
|
147
|
+
if (d.dimensions) {
|
|
148
|
+
data.dimensions = map(d.dimensions);
|
|
149
|
+
}
|
|
150
|
+
if (d.measures) {
|
|
151
|
+
data.measures = map(d.measures);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const X = {
|
|
156
|
+
// overridable properties
|
|
157
|
+
definition: {
|
|
158
|
+
type: 'items',
|
|
159
|
+
component: 'accordion',
|
|
160
|
+
items: {
|
|
161
|
+
data: data
|
|
162
|
+
? {
|
|
163
|
+
uses: 'data',
|
|
164
|
+
}
|
|
165
|
+
: undefined,
|
|
166
|
+
sorting: data
|
|
167
|
+
? {
|
|
168
|
+
uses: 'sorting',
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
settings: {
|
|
172
|
+
uses: 'settings',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
data,
|
|
177
|
+
// -- defaults for the following are injected in default-extension.js --
|
|
178
|
+
// support
|
|
179
|
+
// importProperties,
|
|
180
|
+
// exportProperties,
|
|
181
|
+
// ----
|
|
182
|
+
getSelectionToolbar() {
|
|
183
|
+
return [
|
|
184
|
+
...this.senseItems,
|
|
185
|
+
{
|
|
186
|
+
name: 'Tooltip.clearSelection',
|
|
187
|
+
tid: 'selection-toolbar.clear',
|
|
188
|
+
isIcon: true,
|
|
189
|
+
buttonClass: 'clear',
|
|
190
|
+
iconClass: 'lui-icon lui-icon--clear-selections',
|
|
191
|
+
action: function action() {
|
|
192
|
+
this.selectionsApi.clear();
|
|
193
|
+
},
|
|
194
|
+
isDisabled: function isDisabled() {
|
|
195
|
+
return !this.selectionsApi.selectionsMade;
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
},
|
|
200
|
+
// override with user config
|
|
201
|
+
...ext,
|
|
202
|
+
|
|
203
|
+
// =============================================
|
|
204
|
+
// non-overridable properties
|
|
205
|
+
initialProperties: snGenerator.qae.properties.initial,
|
|
206
|
+
template: '<div style="height: 100%;position: relative"></div>',
|
|
207
|
+
mounted($element) {
|
|
208
|
+
const element = $element[0].children[0];
|
|
209
|
+
const selectionAPI = selectionsApi(this.$scope);
|
|
210
|
+
const sn = snGenerator.create({
|
|
211
|
+
model: this.backendApi.model,
|
|
212
|
+
app: this.backendApi.model.session.app,
|
|
213
|
+
selections: selectionAPI,
|
|
214
|
+
explicitResize: true,
|
|
215
|
+
});
|
|
216
|
+
this.sn = sn;
|
|
217
|
+
this.snComponent = sn.component;
|
|
218
|
+
this.snComponent.created();
|
|
219
|
+
this.snComponent.mounted(element);
|
|
220
|
+
this.senseItems = [];
|
|
221
|
+
const { senseItems } = this;
|
|
222
|
+
this.snComponent.observeActions((actions) => {
|
|
223
|
+
senseItems.length = 0;
|
|
224
|
+
senseItems.push(...createActions(actions));
|
|
225
|
+
this.$scope.selectionsApi.refreshToolbar();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this.nTheme = themeFn();
|
|
229
|
+
|
|
230
|
+
this.initiatedTheme = updateTheme(this);
|
|
231
|
+
},
|
|
232
|
+
suppressOnPaint: () => false,
|
|
233
|
+
paint() {
|
|
234
|
+
const perms = permissions(this.options, this.backendApi);
|
|
235
|
+
const constraints = {
|
|
236
|
+
passive: perms.indexOf('passive') === -1 || undefined,
|
|
237
|
+
active: perms.indexOf('interact') === -1 || undefined,
|
|
238
|
+
select: perms.indexOf('select') === -1 || undefined,
|
|
239
|
+
};
|
|
240
|
+
const opts = limitOptions(this.options, this.snComponent.context.options);
|
|
241
|
+
this._pureLayout = this.backendApi.model.pureLayout || this.backendApi.model.layout;
|
|
242
|
+
return this.initiatedTheme.then(() => {
|
|
243
|
+
updateTheme(this);
|
|
244
|
+
return this.snComponent.render({
|
|
245
|
+
layout: this.backendApi.model.pureLayout || this.backendApi.model.layout,
|
|
246
|
+
context: {
|
|
247
|
+
appLayout: {
|
|
248
|
+
qLocaleInfo: this.backendApi.localeInfo,
|
|
249
|
+
},
|
|
250
|
+
constraints,
|
|
251
|
+
theme: this.nTheme.externalAPI,
|
|
252
|
+
},
|
|
253
|
+
options: opts,
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
resize() {
|
|
258
|
+
if (this._pureLayout !== this.backendApi.model.pureLayout) {
|
|
259
|
+
return this.paint();
|
|
260
|
+
}
|
|
261
|
+
const opts = limitOptions(this.options, this.snComponent.context.options);
|
|
262
|
+
this.snComponent.context.options = opts;
|
|
263
|
+
return this.snComponent.resize();
|
|
264
|
+
},
|
|
265
|
+
setInteractionState() {
|
|
266
|
+
this.paint();
|
|
267
|
+
},
|
|
268
|
+
setSnapshotData(layout) {
|
|
269
|
+
return this.snComponent.setSnapshotData(layout);
|
|
270
|
+
},
|
|
271
|
+
getViewState() {
|
|
272
|
+
const ref = this.snComponent.getImperativeHandle();
|
|
273
|
+
if (ref && typeof ref.getViewState === 'function') {
|
|
274
|
+
return ref.getViewState();
|
|
275
|
+
}
|
|
276
|
+
return undefined;
|
|
277
|
+
},
|
|
278
|
+
destroy() {
|
|
279
|
+
this.sn.destroy();
|
|
280
|
+
if (this.snComponent) {
|
|
281
|
+
this.snComponent.willUnmount();
|
|
282
|
+
this.snComponent.destroy();
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export { X as default };
|