blob: b4d41da63c8d83436aaa8ddb6601af4815dbe338 [file] [log] [blame]
var mercury = require('mercury');
var AttributeHook = require('../../../lib/mercury/attribute-hook');
var insertCss = require('insert-css');
var displayItemDetails = require('./display-item-details');
var makeRPC = require('./make-rpc');
var browseService = require('../../../services/browse-service');
var smartService = require('../../../services/smart-service');
var PaperInputValueEvent =
require('../../../lib/mercury/paper-input-value-event');
var h = mercury.h;
var css = require('./index.css');
module.exports = create;
module.exports.render = render;
/*
* ItemDetails component provides user interfaces for displaying details for
* a browse item such is its type, signature, etc.
*/
function create() {
var state = mercury.struct({
/*
* Item name to display settings for
* @type {string}
*/
itemName: mercury.value(''),
/*
* Method signature for the name, if pointing to a server
* @type {Object}
*/
signature: mercury.value(null),
/*
* Which tab to display; 0 is Details, 1 is Methods
* @type {integer}
*/
selectedTabIndex: mercury.value(0),
/*
* The details information for each service object.
* Can include recommended details information.
* @type {map[string]map[string]string|float}
* details information: string
* recommended details information: float
*/
details: mercury.varhash(),
/*
* Method name currently selected for the RPC input form.
* @type {string}
*/
selectedMethod: mercury.value(''),
/*
* List of RPC method outputs
* @type {Array<string>}
*/
methodOutputs: mercury.array([]),
});
var events = mercury.input([
'displayItemDetails',
'tabSelected',
'methodSelected',
'methodCalled',
'methodRemoved',
'methodCancelled'
]);
wireUpEvents(state, events);
return {
state: state,
events: events
};
}
/*
* Render the item details page, which includes tabs for details and methods.
*/
function render(state, events) {
insertCss(css);
// Only render the selected tab to avoid needless re-rendering.
var detailsTab, methodsTab;
if (state.selectedTabIndex === 0) {
detailsTab = renderDetailsTab(state, events);
methodsTab = '';
} else {
detailsTab = '';
methodsTab = renderMethodsTab(state, events);
}
return [h('paper-tabs.tabs', {
'selected': new AttributeHook(state.selectedTabIndex),
'noink': new AttributeHook(true)
}, [
h('paper-tab.tab', {
'ev-click': mercury.event(events.tabSelected, {
index: 0
})
}, 'Details'),
h('paper-tab.tab', {
'ev-click': mercury.event(events.tabSelected, {
index: 1
})
}, 'Methods'),
]),
h('core-selector', {
'selected': new AttributeHook(state.selectedTabIndex)
}, [
h('div.tab-content', detailsTab),
h('div.tab-content', methodsTab)
])
];
}
/*
* The details tab renders details about the current service object.
* This includes the output of parameterless RPCs made on that object.
* It also includes recommendations for parameterless RPCs.
*/
function renderDetailsTab(state, events) {
var typeInfo = browseService.getTypeInfo(state.signature);
var displayItems = [
renderFieldItem('Name', (state.itemName || '<root>')),
renderFieldItem('Type', typeInfo.name, typeInfo.description),
];
// In addition to the Name and Type, render additional service details.
var details = state.details[state.itemName];
for (var method in details) {
if (details.hasOwnProperty(method)) {
// TODO(alexfandrianto): We may wish to replace this with something less
// arbitrary. Currently, strings are treated as stringified RPC output.
// Floats are treated as the prediction values of recommended items.
if (typeof details[method] === 'string') {
// These details are already known.
displayItems.push(
renderFieldItem(
method,
details[method]
)
);
} else {
// These details need to be queried.
displayItems.push(
renderFieldItem(
method,
renderSuggestRPC(state, events, method, details[method])
)
);
}
}
}
return [
h('div', displayItems),
];
}
/*
* The methods tab renders the signature, input form, and output area.
*/
function renderMethodsTab(state, events) {
return h('div', [
renderSignature(state, events),
renderMethodInput(state, events),
renderMethodOutput(state)
]);
}
/*
* Renders the signature of the current service object.
*/
function renderSignature(state, events) {
var methods = [];
var sig = state.signature;
for (var m in sig) {
if (sig.hasOwnProperty(m)) {
methods.push(renderMethod(m, sig[m]));
}
}
if (methods.length > 0) {
return h('div.signature', methods);
} else {
return h('div.empty', 'No method signature');
}
/*
* Pretty prints a method's signature.
*/
function renderMethod(name, param) {
var text = name + '(';
for (var i = 0; i < param.inArgs.length; i++) {
var arg = param.inArgs[i];
if (i > 0) {
text += ',';
}
text += arg;
}
text += ')';
if (param.isStreaming) {
text += ' - streaming';
}
return h('pre', {
'ev-click': mercury.event(events.methodSelected, { methodName: name })
}, text);
}
}
/*
* Renders a input field form for the selected method.
*/
function renderMethodInput(state, events) {
if (state.selectedMethod === '') {
return h('div.method-input', 'No method selected');
}
// Display the selected method name
var methodNameHeader = h('pre', state.selectedMethod);
// Form for filling up the arguments
var param = state.signature[state.selectedMethod];
var argForm = []; // contains form elements
var args = []; // contains form values
for (var i = 0; i < param.inArgs.length; i++) {
// Prefill args with undefined
args.push(undefined);
// Fill argForm with the relevant form element.
argForm.push(renderMethodInputArgument(param.inArgs[i], args, i));
}
// Setup the RUN button.
var runButton = renderRPCRunButton(
state,
events,
state.selectedMethod,
param.inArgs.length !== 0,
args
);
return h('div.method-input', [methodNameHeader, argForm, runButton]);
}
/*
* Renders the method outputs received by the current service object.
*/
function renderMethodOutput(state) {
if (state.methodOutputs.length === 0) {
return h('div.method-output', 'No method output');
}
var outputs = state.methodOutputs.map(function(output) {
return h('pre', output);
});
return h('div.method-output', outputs);
}
/*
* Renders an input element whose change events modify the given args array at
* the specified index. The placeholder is generally an argument name.
*/
function renderMethodInputArgument(placeholder, args, index) {
var changeEvent = new PaperInputValueEvent(function(data) {
args[index] = data;
});
var elem = h('paper-input.method-input-item', {
'placeholder': placeholder,
'ev-change': changeEvent
});
return elem;
}
/*
* Renders the suggestion for an rpc.
* Suggestion changes as the prediction level rises.
*/
function renderSuggestRPC(state, events, methodName, prediction) {
var extra = 'Recommended';
var buttons = [];
// The run button is rendered to initiate the RPC.
buttons.push(renderRPCRunButton(state, events, methodName, false, []));
if (prediction > 0.75) {
// This hook is a Mercury workaround; webkitAnimationEnd is not supported.
var AnimationHook = function() {};
AnimationHook.prototype.hook = function (elem, propName) {
// On animation end, call the method.
function animationEndHandler() {
events.methodCalled({
name: state.itemName,
methodName: methodName,
signature: state.signature,
hasParams: false,
args: [],
});
}
elem.addEventListener('webkitAnimationEnd', animationEndHandler);
};
// Render the box that will fire methodCalled after the timeout.
// Chrome/Mercury workaround: Distinguish animated divs by assigning a key.
var uniqueID = state.itemName + '|' + methodName;
extra = h('div.animate', {
'key': uniqueID,
'ev-animationHook': new AnimationHook()
}, 'Automatic');
// A cancel button is rendered to stop this timeout.
buttons.push(renderRPCCancelButton(state, events, methodName));
} else {
// A remove button is rendered to remove the recommendation.
buttons.push(renderRPCRemoveSuggestButton(state, events, methodName));
}
return h('div', [ h('div.background', [extra]), buttons]);
}
/*
* Renders a Run button to make RPCs
*/
function renderRPCRunButton(state, events, methodName, hasParams, args) {
var ev = mercury.event(events.methodCalled, {
name: state.itemName,
methodName: methodName,
hasParams: hasParams,
signature: state.signature,
args: args,
});
var runButton = h(
'paper-button.method-input-run',
{
'href': '#',
'ev-click': ev,
'label': 'RUN',
}
);
return runButton;
}
/*
* Renders a button to remove suggested RPCs.
*/
function renderRPCRemoveSuggestButton(state, events, methodName) {
var ev = mercury.event(events.methodRemoved, {
name: state.itemName,
methodName: methodName,
signature: state.signature,
reward: -1,
});
return h(
'paper-button.method-input-remove',
{
'href': '#',
'ev-click': ev,
'label': 'REMOVE',
}
);
}
/*
* Renders a button to cancel an automatic RPC from occurring.
*/
function renderRPCCancelButton(state, events, methodName) {
var ev = mercury.event(events.methodCancelled, {
name: state.itemName,
methodName: methodName,
signature: state.signature,
reward: 0,
});
return h(
'paper-button.method-input-cancel',
{
'href': '#',
'ev-click': ev,
'label': 'CANCEL',
}
);
}
/*TODO(aghassemi) make a web component for this*/
function renderFieldItem(label, content, tooltip) {
var hlabel = h('h4', label);
if (tooltip) {
// If there is a tooltip, wrap the content in it
content = h('core-tooltip.tooltip', {
'label': new AttributeHook(tooltip),
'position': 'right',
}, content);
}
return h('div.field', [
h('h4', hlabel),
h('div.content', content)
]);
}
// Wire up events that we know how to handle
function wireUpEvents(state, events) {
events.displayItemDetails(displayItemDetails.bind(null, state));
events.tabSelected(function(data) {
state.selectedTabIndex.set(data.index);
});
events.methodSelected(function(data) {
state.selectedMethod.set(data.methodName);
});
events.methodCalled(makeRPC.bind(null, state));
events.methodRemoved(function(data) {
var detail = state.details[data.name];
delete detail[data.methodName];
// TODO(alexfandrianto): Split to different file
// Log the removed RPC to the smart service.
smartService.record('learner-autorpc', data);
state.details.put(data.name, detail);
});
events.methodCancelled(function(data) {
var detail = state.details[data.name];
detail[data.methodName] = 0;
// TODO(alexfandrianto): Split to different file
// Log the removed RPC to the smart service.
smartService.record('learner-autorpc', data);
state.details.put(data.name, detail);
});
}