@nebula.js/cli-sense 4.0.0-alpha.9 → 4.0.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/README.md CHANGED
@@ -22,7 +22,6 @@ Options:
22
22
  --output Destination directory [string] [default: "<name>-ext"]
23
23
  --minify Minify and uglify code [boolean] [default: true]
24
24
  --sourcemap Generate sourcemaps [boolean] [default: false]
25
- --legacy Generate legacy extension [boolean] [default: false]
26
25
  -h, --help Show help [boolean]
27
26
  ```
28
27
 
@@ -123,21 +122,3 @@ example:
123
122
  "supernova": true
124
123
  }
125
124
  ```
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.
@@ -27,12 +27,6 @@ const options = {
27
27
  default: false,
28
28
  desc: 'Generate sourcemaps',
29
29
  },
30
- legacy: {
31
- type: 'boolean',
32
- required: false,
33
- default: false,
34
- desc: 'Generate legacy extension',
35
- },
36
30
  };
37
31
 
38
32
  module.exports = (yargs) => yargs.options(options);
package/lib/sense.js CHANGED
@@ -1,9 +1,9 @@
1
- const buildLegacy = require('./build-legacy');
2
1
  const build = require('./build');
3
2
 
4
3
  function sense(argv) {
5
4
  if (argv.legacy) {
6
- return buildLegacy(argv);
5
+ console.error('Legacy sense-build support removed in 4.0, it is not required for sense compatability.');
6
+ process.exit(1);
7
7
  }
8
8
  return build(argv);
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebula.js/cli-sense",
3
- "version": "4.0.0-alpha.9",
3
+ "version": "4.0.1",
4
4
  "description": "Build a supernova as a Qlik Sense extension",
5
5
  "license": "MIT",
6
6
  "author": "QlikTech International AB",
@@ -27,20 +27,20 @@
27
27
  "lint": "eslint src"
28
28
  },
29
29
  "dependencies": {
30
- "@babel/cli": "7.21.0",
31
- "@babel/core": "7.21.4",
32
- "@babel/preset-env": "7.21.4",
33
- "@nebula.js/stardust": "^4.0.0-alpha.9",
30
+ "@babel/cli": "7.21.5",
31
+ "@babel/core": "7.21.8",
32
+ "@babel/preset-env": "7.21.5",
33
+ "@nebula.js/stardust": "^4.0.1",
34
34
  "@rollup/plugin-babel": "6.0.3",
35
- "@rollup/plugin-commonjs": "24.0.1",
36
- "@rollup/plugin-node-resolve": "15.0.1",
35
+ "@rollup/plugin-commonjs": "24.1.0",
36
+ "@rollup/plugin-node-resolve": "15.0.2",
37
37
  "@rollup/plugin-replace": "5.0.2",
38
- "@rollup/plugin-terser": "0.4.0",
38
+ "@rollup/plugin-terser": "0.4.1",
39
39
  "extend": "3.0.2",
40
40
  "fs-extra": "11.1.1",
41
41
  "node-event-emitter": "0.0.1",
42
- "rollup": "3.20.2",
43
- "yargs": "17.7.1"
42
+ "rollup": "3.21.5",
43
+ "yargs": "17.7.2"
44
44
  },
45
- "gitHead": "4041f477f85702b2e55626d5920ca5604a45fbf2"
45
+ "gitHead": "c105707099dc79c405f01d65cc338f1614ea9750"
46
46
  }
@@ -1,116 +0,0 @@
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
- babelHelpers: 'bundled',
80
- babelrc: false,
81
- exclude: [/node_modules/],
82
- presets: [
83
- [
84
- '@babel/preset-env',
85
- {
86
- modules: false,
87
- targets: {
88
- browsers: ['chrome 62'],
89
- },
90
- },
91
- ],
92
- ],
93
- }),
94
- argv.minify &&
95
- terser({
96
- output: {
97
- comments: /@license|@preserve|Copyright|license/,
98
- },
99
- }),
100
- ],
101
- });
102
-
103
- await bundle.write({
104
- file: qextFileNameJs,
105
- format: 'amd',
106
- sourcemap: argv.sourcemap,
107
- });
108
- }
109
-
110
- await moveSnBundle();
111
- await moveResources();
112
- await wrapIt();
113
- await createQextFiles();
114
- }
115
-
116
- module.exports = build;
@@ -1,63 +0,0 @@
1
- import createSelections from '../selections';
2
-
3
- describe('supernova-wrapper', () => {
4
- describe('selections', () => {
5
- let selectionsApi;
6
- let selectionsApiActivateMock;
7
- let selectionsApiDeactivateMock;
8
-
9
- let backendApi;
10
- let backendApiBeganSelectionMock;
11
- let backendApiEndSelectionMock;
12
- let backendApiResetSelectionMock;
13
- let backendApiSwitchModalSelectionMock;
14
-
15
- let selections;
16
- let selectionsEmitMock;
17
-
18
- beforeEach(() => {
19
- selectionsApiActivateMock = jest.fn();
20
- selectionsApiDeactivateMock = jest.fn();
21
-
22
- backendApiBeganSelectionMock = jest.fn();
23
- backendApiEndSelectionMock = jest.fn();
24
- backendApiResetSelectionMock = jest.fn();
25
- backendApiSwitchModalSelectionMock = jest.fn();
26
-
27
- selectionsEmitMock = jest.fn();
28
-
29
- selectionsApi = {
30
- activated: selectionsApiActivateMock,
31
- deactivated: selectionsApiDeactivateMock,
32
- };
33
- backendApi = {
34
- beginSelections: backendApiBeganSelectionMock,
35
- endSelections: backendApiEndSelectionMock,
36
- model: {
37
- resetMadeSelections: backendApiResetSelectionMock,
38
- app: {
39
- switchModalSelection: backendApiSwitchModalSelectionMock,
40
- },
41
- },
42
- };
43
- selections = createSelections({ selectionsApi, backendApi });
44
- selections.emit = selectionsEmitMock;
45
- });
46
-
47
- it('should `begin`', () => {
48
- selections.begin();
49
- expect(selectionsApi.activated).toHaveBeenCalledWith(true);
50
- expect(backendApi.beginSelections).toHaveBeenCalledWith();
51
- expect(selections.emit).toHaveBeenCalledWith('activated');
52
- });
53
-
54
- it('should `begin` with paths', () => {
55
- const paths = ['hypercubePath', 'otherHypercubePath'];
56
- selections.begin(paths);
57
- expect(selectionsApi.activated).toHaveBeenCalledWith(true);
58
- expect(backendApi.beginSelections).toHaveBeenCalledTimes(0);
59
- expect(backendApi.model.app.switchModalSelection).toHaveBeenCalledWith(backendApi.model, paths);
60
- expect(selections.emit).toHaveBeenCalledWith('activated');
61
- });
62
- });
63
- });
@@ -1,3 +0,0 @@
1
- const x = {};
2
-
3
- export default x;
@@ -1,36 +0,0 @@
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;
@@ -1,82 +0,0 @@
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
- };
@@ -1,290 +0,0 @@
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
- onContextMenu(menu, event) {
272
- return this.snComponent.onContextMenu(menu, event);
273
- },
274
- getViewState() {
275
- const ref = this.snComponent.getImperativeHandle();
276
- if (ref && typeof ref.getViewState === 'function') {
277
- return ref.getViewState();
278
- }
279
- return undefined;
280
- },
281
- destroy() {
282
- this.sn.destroy();
283
- if (this.snComponent) {
284
- this.snComponent.willUnmount();
285
- this.snComponent.destroy();
286
- }
287
- },
288
- };
289
-
290
- export default X;