mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-19 17:58:43 -05:00
fix(nucleus): wait for curr selections (#117)
This commit is contained in:
@@ -1,19 +1,6 @@
|
||||
import vizualizationAPI from '../viz';
|
||||
import ObjectAPI from './object-api';
|
||||
|
||||
export function observe(model, objectAPI) {
|
||||
const onChanged = () =>
|
||||
model.getLayout().then(layout => {
|
||||
objectAPI.setLayout(layout);
|
||||
});
|
||||
model.on('changed', onChanged);
|
||||
model.once('closed', () => {
|
||||
model.removeListener('changed', onChanged);
|
||||
objectAPI.close();
|
||||
});
|
||||
|
||||
onChanged();
|
||||
}
|
||||
import { observe } from './observer';
|
||||
|
||||
export default function initiate(getCfg, optional, context) {
|
||||
return context.app.getObject(getCfg.id).then(model => {
|
||||
@@ -24,7 +11,7 @@ export default function initiate(getCfg, optional, context) {
|
||||
|
||||
const objectAPI = new ObjectAPI(model, context, viz);
|
||||
|
||||
observe(model, objectAPI);
|
||||
observe(model, layout => objectAPI.setLayout(layout)); // TODO - call unobserve when viz is destroyed
|
||||
|
||||
const api = objectAPI.getPublicAPI();
|
||||
|
||||
|
||||
@@ -46,15 +46,23 @@ export function observe(model, callback, property = 'layout') {
|
||||
affected.forEach(key => {
|
||||
c.props[key].state = STATES.VALIDATING;
|
||||
const method = OBSERVABLE[key].filter(m => model[m])[0];
|
||||
model[method]().then(value => {
|
||||
if (cache[model.id] && cache[model.id].props[key]) {
|
||||
if (cache[model.id].props[key].state < STATES.CLOSED && cache[model.id].props[key].state !== STATES.VALID) {
|
||||
cache[model.id].props[key].state = STATES.VALID;
|
||||
cache[model.id].props[key].value = value;
|
||||
cache[model.id].props[key].callbacks.forEach(cb => cb(value));
|
||||
model[method]()
|
||||
.then(value => {
|
||||
if (cache[model.id] && cache[model.id].props[key]) {
|
||||
if (
|
||||
cache[model.id].props[key].state < STATES.CLOSED &&
|
||||
cache[model.id].props[key].state !== STATES.VALID
|
||||
) {
|
||||
cache[model.id].props[key].state = STATES.VALID;
|
||||
cache[model.id].props[key].value = value;
|
||||
cache[model.id].props[key].callbacks.forEach(cb => cb(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO - retry?
|
||||
cache[model.id].props[key].state = STATES.INVALID;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@ const create = app => {
|
||||
let modalObject;
|
||||
// let mounted;
|
||||
let lyt;
|
||||
let currentSelectionsModel;
|
||||
let prom;
|
||||
const api = {
|
||||
model: app,
|
||||
switchModal(object, path, accept = true) {
|
||||
if (object === modalObject) {
|
||||
return Promise.resolve();
|
||||
return prom || Promise.resolve();
|
||||
}
|
||||
if (modalObject) {
|
||||
modalObject.endSelections(accept);
|
||||
@@ -30,17 +32,22 @@ const create = app => {
|
||||
// TODO check model state
|
||||
modalObject = object;
|
||||
api.emit('modal', modalObject._selections);
|
||||
return modalObject.beginSelections(Array.isArray(path) ? path : [path]).catch(err => {
|
||||
if (err.code === 6003) {
|
||||
// If another object already is in modal -> abort and take over
|
||||
return api.abortModal().then(() => object.beginSelections(Array.isArray(path) ? path : [path]));
|
||||
}
|
||||
throw err;
|
||||
prom = currentSelectionsModel.then(() => {
|
||||
// do not return the call to beginSelection to avoid waiting for it's response
|
||||
modalObject.beginSelections(Array.isArray(path) ? path : [path]).catch(err => {
|
||||
if (err.code === 6003) {
|
||||
// If another object already is in modal -> abort and take over
|
||||
return api.abortModal().then(() => object.beginSelections(Array.isArray(path) ? path : [path]));
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
return prom;
|
||||
}
|
||||
modalObject = null;
|
||||
api.emit('modal-unset');
|
||||
return Promise.resolve();
|
||||
prom = Promise.resolve();
|
||||
return prom;
|
||||
},
|
||||
isModal(objectModel) {
|
||||
// TODO check model state
|
||||
@@ -83,7 +90,7 @@ const create = app => {
|
||||
|
||||
eventmixin(api);
|
||||
|
||||
modelCache(
|
||||
currentSelectionsModel = modelCache(
|
||||
{
|
||||
qInfo: {
|
||||
qType: 'current-selections',
|
||||
@@ -94,47 +101,51 @@ const create = app => {
|
||||
alternateStates: [],
|
||||
},
|
||||
app
|
||||
).then(model => {
|
||||
observe(app, appLayout => {
|
||||
const states = [...appLayout.qStateNames].map(s => ({
|
||||
stateName: s, // need this as reference in selection toolbar since qSelectionObject.qStateName is not in the layout
|
||||
qSelectionObjectDef: {
|
||||
qStateName: s,
|
||||
},
|
||||
}));
|
||||
const existingStates = (lyt ? lyt.alternateStates.map(s => s.stateName) : []).join('::');
|
||||
const newStates = appLayout.qStateNames.map(s => s).join('::');
|
||||
if (existingStates !== newStates) {
|
||||
model.applyPatches(
|
||||
[
|
||||
{
|
||||
qOp: 'replace',
|
||||
qPath: '/alternateStates',
|
||||
qValue: JSON.stringify(states),
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
observe(model, layout => {
|
||||
canGoBack = false;
|
||||
canGoForward = false;
|
||||
canClear = false;
|
||||
[layout, ...layout.alternateStates].forEach(state => {
|
||||
canGoBack = canGoBack || state.qSelectionObject.qBackCount > 0;
|
||||
canGoForward = canGoForward || state.qSelectionObject.qForwardCount > 0;
|
||||
canClear = canClear || state.qSelectionObject.qSelections.filter(s => s.qLocked !== true).length > 0;
|
||||
)
|
||||
.then(model => {
|
||||
observe(app, appLayout => {
|
||||
const states = [...appLayout.qStateNames].map(s => ({
|
||||
stateName: s, // need this as reference in selection toolbar since qSelectionObject.qStateName is not in the layout
|
||||
qSelectionObjectDef: {
|
||||
qStateName: s,
|
||||
},
|
||||
}));
|
||||
const existingStates = (lyt ? lyt.alternateStates.map(s => s.stateName) : []).join('::');
|
||||
const newStates = appLayout.qStateNames.map(s => s).join('::');
|
||||
if (existingStates !== newStates) {
|
||||
model.applyPatches(
|
||||
[
|
||||
{
|
||||
qOp: 'replace',
|
||||
qPath: '/alternateStates',
|
||||
qValue: JSON.stringify(states),
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
lyt = layout;
|
||||
api.emit('changed');
|
||||
|
||||
observe(model, layout => {
|
||||
canGoBack = false;
|
||||
canGoForward = false;
|
||||
canClear = false;
|
||||
[layout, ...layout.alternateStates].forEach(state => {
|
||||
canGoBack = canGoBack || state.qSelectionObject.qBackCount > 0;
|
||||
canGoForward = canGoForward || state.qSelectionObject.qForwardCount > 0;
|
||||
canClear = canClear || state.qSelectionObject.qSelections.filter(s => s.qLocked !== true).length > 0;
|
||||
});
|
||||
lyt = layout;
|
||||
api.emit('changed');
|
||||
});
|
||||
model.once('closed', () => {
|
||||
app._selections = null; // eslint-disable-line no-param-reassign
|
||||
cache[app.id] = null;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// do something
|
||||
});
|
||||
model.once('closed', () => {
|
||||
app._selections = null; // eslint-disable-line no-param-reassign
|
||||
cache[app.id] = null;
|
||||
});
|
||||
});
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
@@ -55,16 +55,18 @@ export default function(model, app) {
|
||||
return appAPI().switchModal(null, null, false, false);
|
||||
},
|
||||
select(s) {
|
||||
this.begin([s.params[0]]);
|
||||
const b = this.begin([s.params[0]]);
|
||||
if (!appAPI().isModal()) {
|
||||
return;
|
||||
}
|
||||
hasSelected = true;
|
||||
model[s.method](...s.params).then(qSuccess => {
|
||||
if (!qSuccess) {
|
||||
this.clear();
|
||||
}
|
||||
});
|
||||
b.then(() =>
|
||||
model[s.method](...s.params).then(qSuccess => {
|
||||
if (!qSuccess) {
|
||||
this.clear();
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
canClear() {
|
||||
return hasSelected && layout.qSelectionInfo.qMadeSelections;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"build": "nebula build",
|
||||
"lint": "eslint src",
|
||||
"start": "nebula serve",
|
||||
"test:integration": "aw puppet --testExt '*.int.js' --glob 'test/integration/**/*.int.js' --chrome.headless true --mocha.timeout 15000"
|
||||
"test:integration": "aw puppet --testExt '*.int.js' --glob 'test/integration/**/*.int.js'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@after-work.js/aw": "^6.0.3",
|
||||
|
||||
@@ -2,7 +2,8 @@ const serve = require('@nebula.js/cli-serve'); // eslint-disable-line
|
||||
|
||||
let s;
|
||||
|
||||
before(async () => {
|
||||
before(async function setup() {
|
||||
this.timeout(15000);
|
||||
s = await serve({
|
||||
open: false,
|
||||
});
|
||||
|
||||
@@ -9,8 +9,12 @@ describe('interaction', () => {
|
||||
|
||||
await page.click('rect[data-label="K"]');
|
||||
await page.click('rect[data-label="S"]');
|
||||
|
||||
await page.waitForSelector('button[title="Confirm selection"]');
|
||||
await page.click('button[title="Confirm selection"]');
|
||||
|
||||
await page.waitFor(100); // wait a bit to make sure websocket traffic has gone through
|
||||
|
||||
const rects = await page.$$eval('rect[data-label]', sel => sel.map(r => r.getAttribute('data-label')));
|
||||
expect(rects).to.eql(['K', 'S']);
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"build": "nebula build",
|
||||
"lint": "eslint src",
|
||||
"start": "nebula serve",
|
||||
"test:integration": "aw puppet --testExt '*.int.js' --glob 'test/integration/**/*.int.js' --chrome.headless true --chrome.slowMo 10"
|
||||
"test:integration": "aw puppet --testExt '*.int.js' --glob 'test/integration/**/*.int.js'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@after-work.js/aw": "^6.0.3",
|
||||
|
||||
@@ -2,7 +2,8 @@ const serve = require('@nebula.js/cli-serve'); // eslint-disable-line
|
||||
|
||||
let s;
|
||||
|
||||
before(async () => {
|
||||
before(async function setup() {
|
||||
this.timeout(15000); // to allow time for the server to start
|
||||
s = await serve({
|
||||
build: false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user