feat: Support custom display

This commit is contained in:
1ambda 2017-01-24 02:52:40 +09:00
parent c906da6230
commit 0fa7eda609
6 changed files with 108 additions and 78 deletions

View file

@ -23,9 +23,8 @@ import {
/*eslint-enable no-unused-vars */
export class AbstractFrontendInterpreter {
constructor(magic, displayType) {
constructor(magic) {
this.magic = magic;
this.displayType = displayType;
}
/**
@ -37,6 +36,7 @@ export class AbstractFrontendInterpreter {
*/
interpret(paragraphText) {
/** implement this if you want to add a frontend interpreter */
throw new Error('AbstractFrontendInterpreter.interpret is not overrided');
}
/**
@ -47,13 +47,4 @@ export class AbstractFrontendInterpreter {
getMagic() {
return this.magic;
}
/**
* return display type for this frontend interpreter.
* (e.g `DefaultDisplayType.TEXT`)
* @return {string}
*/
getDisplayType() {
return this.displayType;
}
}

View file

@ -53,7 +53,7 @@ export class GeneratorWithType {
return magic && (DefaultDisplayMagic[magic] || customDisplayMagic[magic]);
}
const splited = generator.split("\n");
const splited = generator.split('\n');
const gensWithTypes = [];
let mergedGens = [];

View file

@ -230,18 +230,18 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
$scope.paragraph.errorMessage = error.stack;
console.error('Failed to execute FrontendInterpreter.interpret\n', error);
$scope.$digest();
}
};
$scope.runParagraphUsingFrontendInterpreter = function(intp, paragraphText, magic) {
// clear results which is watched by `ng-repeat`. without this,
// frontend interpreter results cannot be rendered in view more than once
$scope.runParagraphUsingFrontendInterpreter = function(intp, paragraphText,
magic, digestRequired) {
$scope.paragraph.results = {};
$scope.$digest();
if (digestRequired) { $scope.$digest(); }
try {
// remove magic from paragraphText
const splited = paragraphText.split(magic);
const textWithoutMagic = splited[1];
// remove leading spaces
const textWithoutMagic = splited[1].replace(/^\s+/g, '');
$scope.paragraph.status = 'FINISHED';
const frontIntpResult = intp.interpret(textWithoutMagic);
const parsed = frontIntpResult.getAllParsedGeneratorsWithTypes(
@ -249,9 +249,8 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
parsed.then(resultsMsg => {
$scope.paragraph.results.msg = resultsMsg;
$scope.paragraph.config.tableHide = false;
$scope.$digest();
if (digestRequired) { $scope.$digest(); }
}).catch($scope.handleFrontendInterpreterError);
// TODO(1ambda): broadcast to other clients
} catch (error) {
$scope.handleFrontendInterpreterError(error);
}
@ -277,7 +276,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
commitParagraph(paragraph);
};
$scope.runParagraph = function(paragraphText) {
$scope.runParagraph = function(paragraphText, digestRequired) {
if (!paragraphText || $scope.isRunning($scope.paragraph)) {
return;
}
@ -286,7 +285,8 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
const frontendIntp = heliumService.getFrontendInterpreterUsingMagic(magic);
if (frontendIntp) {
$scope.runParagraphUsingFrontendInterpreter(frontendIntp, paragraphText, magic);
$scope.runParagraphUsingFrontendInterpreter(
frontendIntp, paragraphText, magic, digestRequired);
} else {
$scope.runParagraphUsingBackendInterpreter(paragraphText);
}
@ -306,14 +306,14 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
};
$scope.runParagraphFromShortcut = function(paragraphText) {
// we need to call `$digest()` to update view immediately
$scope.runParagraph(paragraphText);
$scope.$digest();
// passing `digestRequired` as true to update view immediately
// without this, results cannot be rendered in view more than once
$scope.runParagraph(paragraphText, true);
};
$scope.runParagraphFromButton = function(paragraphText) {
// we come here from `$scope.on`, so we don't need to call `$digest()`
$scope.runParagraph(paragraphText)
$scope.runParagraph(paragraphText, false)
};
$scope.moveUp = function(paragraph) {

View file

@ -252,8 +252,40 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
};
var renderResult = function(type, refresh) {
var activeApp;
$scope.createDisplayDOMId = function(baseDOMId, type) {
if (type === DefaultDisplayType.TABLE) {
return `${baseDOMId}_graph`;
} else if (type === DefaultDisplayType.HTML) {
return `${baseDOMId}_html`;
} else if (type === DefaultDisplayType.ANGULAR) {
return `${baseDOMId}_angular`;
} else if (type === DefaultDisplayType.TEXT) {
return `${baseDOMId}_text`;
} else if (type === DefaultDisplayType.ELEMENT) {
return `${baseDOMId}_elem`;
} else {
console.error(`Cannot create display DOM Id due to unknown display type: ${type}`);
}
};
$scope.renderDefaultDisplay = function(targetElemId, type, data, refresh) {
if (type === DefaultDisplayType.TABLE) {
$scope.renderGraph(targetElemId, $scope.graphMode, refresh);
} else if (type === DefaultDisplayType.HTML) {
renderHtml(targetElemId, data);
} else if (type === DefaultDisplayType.ANGULAR) {
renderAngular(targetElemId, data);
} else if (type === DefaultDisplayType.TEXT) {
renderText(targetElemId, data);
} else if (type === DefaultDisplayType.ELEMENT) {
renderElem(targetElemId, data);
} else {
console.error(`Unknown Display Type: ${type}`);
}
};
const renderResult = function(type, refresh) {
let activeApp;
if (enableHelium) {
getSuggestions();
getApplicationStates();
@ -261,50 +293,64 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
if (activeApp) {
const app = _.find($scope.apps, {id: activeApp});
renderApp(app, `p${appState.id}`);
const appState = _.find($scope.apps, {id: activeApp});
renderApp(`p${appState.id}`, appState);
} else {
/**
* find proper interpreter which can handle the non-default display type
* the displayed type which is generated from `display()`
* should be one of the default interpreter type
*/
// const isDefaultDisplayType = DefaultDisplayType[type];
// if (!isDefaultDisplayType) {
// const intp = heliumService.getFrontendInterpreterWithDisplayType(type);
//
// if (intp) {
// // currently display only accepts `Object` data not
// // doesn't support function and promise
// const result = intp.display(data);
// type = result.getType();
// data = result.getData();
// }
// }
if (type === DefaultDisplayType.TABLE) {
$scope.renderGraph($scope.graphMode, refresh);
} else if (type === DefaultDisplayType.HTML) {
renderHtml(`p${$scope.id}_html`, data);
} else if (type === DefaultDisplayType.ANGULAR) {
renderAngular(`p${$scope.id}_angular`, data);
} else if (type === DefaultDisplayType.TEXT) {
renderText(`p${$scope.id}_text`, data);
} else if (type === DefaultDisplayType.ELEMENT) {
renderElem(`p${$scope.id}_elem`, data);
if (!DefaultDisplayType[type]) {
const frontendIntp = heliumService.getFrontendInterpreterUsingMagic(type);
if (!frontendIntp) {
console.error(`Unknown Display Type: ${type}`);
return;
}
$scope.renderCustomDisplay(type, data, frontendIntp);
} else {
console.error(`Unknown Display Type: ${type}`);
const targetElemId = $scope.createDisplayDOMId(`p${$scope.id}`, type);
$scope.renderDefaultDisplay(targetElemId, type, data, refresh);
}
}
};
$scope.isDefaultDisplay = function() {
return DefaultDisplayType[$scope.type];
};
/**
* Render multiple sub results for custom display
*/
$scope.renderCustomDisplay = function(type, data, frontendIntp) {
// get result from intp
const frontIntpResult = frontendIntp.interpret(data.trim());
const parsed = frontIntpResult.getAllParsedGeneratorsWithTypes(
heliumService.getAvailableFrontendInterpreters());
// custom display result can include multiple subset results
parsed.then(dataWithTypes => {
const containerDOM = document.getElementById(`p${$scope.id}_custom`);
for(let i = 0; i < dataWithTypes.length; i++) {
const dt = dataWithTypes[i];
const data = dt.data;
const type = dt.type;
// prepare DOM to be filled
const subResultDOMId = $scope.createDisplayDOMId(`p${$scope.id}_custom`, type);
const subResultDOM = document.createElement('div');
containerDOM.appendChild(subResultDOM);
subResultDOM.setAttribute('id', subResultDOMId);
$scope.renderDefaultDisplay(subResultDOMId, type, data, true);
}
}).catch(error => {
console.error(`Failed to render custom display: ${$scope.type}\n` + error);
});
};
/**
* generates actually object which will be consumed from `data` property
* feed it to the success callback.
* if error occurs, the error is passed to the failure callback
*
* @param generator {Object or Promise or Function}
* @param generator {Object or Function}
* @param type {string} Display Type
* @param successCallback
* @param failureCallback
@ -317,13 +363,6 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
failureCallback(error);
console.error(`Failed to handle ${type} type, function generator\n`, error);
}
} else if (FrontendInterpreterResult.isPromiseGenerator(generator)) {
generator
.then((generated) => { successCallback(generated); })
.catch((error) => {
failureCallback(error);
console.error(`Failed to handle ${type} type, promise generator\n`, error);
});
} else if (FrontendInterpreterResult.isObjectGenerator(generator)) {
try {
successCallback(generator);
@ -343,7 +382,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
generateData(() => { generator(targetElemId) }, DefaultDisplayType.ELEMENT,
() => {}, /** HTML element will be filled in generator . thus pass empty success callback */
(error) => { elem.html(`${error.stack}`); }
(error) => { elem.html(`${error.stack}`); }
);
}
@ -421,7 +460,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
$timeout(retryRenderer);
}
};
var clearTextOutput = function() {
var textEl = getTextResultElem($scope.id);
@ -457,11 +496,11 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
};
$scope.renderGraph = function(type, refresh) {
$scope.renderGraph = function(targetElemId, type, refresh) {
// set graph height
var height = $scope.config.graph.height;
var graphContainerEl = angular.element('#p' + $scope.id + '_graph');
graphContainerEl.height(height);
var targetElem = angular.element(`#${targetElemId}`);
targetElem.height(height);
if (!type) {
type = 'table';
@ -807,7 +846,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
const renderApp = function(targetElemId, appState) {
function retryRenderer() {
var elem = angular.element(document.getElementById(elememId));
var elem = angular.element(document.getElementById(targetElemId));
console.log('retry renderApp %o', elem);
if (!elem.length) {
$timeout(retryRenderer, 1000);

View file

@ -67,6 +67,10 @@ limitations under the License.
tooltip="Scroll Top"></div>
</div>
<div id="p{{id}}_custom" class="resultContained"
ng-if="!isDefaultDisplay()">
</div>
<div id="p{{id}}_elem" class="resultContained"
ng-if="type == 'ELEMENT'">
</div>

View file

@ -13,10 +13,6 @@
*/
import { HeliumType, } from './helium-type';
import {
AbstractFrontendInterpreter,
DefaultDisplayType
} from '../../app/frontend-interpreter'
(function() {
angular.module('zeppelinWebApp').service('heliumService', heliumService);