gethue
Version:
Hue is an Open source SQL Query Editor for Databases/Warehouses
345 lines (306 loc) • 11.8 kB
JavaScript
// Licensed to Cloudera, Inc. under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. Cloudera, Inc. licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import $ from 'jquery';
import * as ko from 'knockout';
import { ASSIST_LANG_REF_PANEL_SHOW_TOPIC_EVENT } from './events';
import { simpleGet } from '../../../api/apiUtils';
import { CONFIG_REFRESHED_TOPIC } from '../../../config/events';
import { filterEditorConnectors } from '../../../config/hueConfig';
import componentUtils from '../../../ko/components/componentUtils';
import huePubSub from '../../../utils/huePubSub';
import I18n from '../../../utils/i18n';
export const NAME = 'assist-language-reference-panel';
// prettier-ignore
const TEMPLATE = `
<script type="text/html" id="language-reference-topic-tree">
<!-- ko if: $data.length -->
<ul class="assist-docs-topic-tree " data-bind="foreach: $data">
<li>
<a class="black-link" href="javascript: void(0);" data-bind="click: function () { $component.selectedTopic($data); }, toggle: open">
<i class="fa fa-fw" style="font-size: 12px;" data-bind="css: { 'fa-chevron-right': children.length && !open(), 'fa-chevron-down': children.length && open() }"></i>
<span class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, text: title"></span>
</a>
<!-- ko if: open -->
<!-- ko template: { name: 'language-reference-topic-tree', data: children } --><!-- /ko -->
<!-- /ko -->
</li>
</ul>
<!-- /ko -->
</script>
<div class="assist-inner-panel">
<div class="assist-flex-panel">
<div class="assist-flex-header">
<div class="assist-inner-header">
<div class="function-dialect-dropdown" data-bind="component: { name: 'hue-drop-down', params: { fixedPosition: true, value: activeDialect, entries: availableDialects, linkTitle: '${I18n(
'Selected dialect'
)}' } }" style="display: inline-block"></div>
</div>
</div>
<div class="assist-flex-search">
<div class="assist-filter">
<form autocomplete="off">
<input class="clearable" type="text" ${ window.PREVENT_AUTOFILL_INPUT_ATTRS } placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
</form>
</div>
</div>
<div class="assist-docs-topics" data-bind="css: { 'assist-flex-fill': !selectedTopic(), 'assist-flex-40': selectedTopic() }">
<!-- ko ifnot: query -->
<!-- ko template: { name: 'language-reference-topic-tree', data: topics } --><!-- /ko -->
<!-- /ko -->
<!-- ko if: query -->
<!-- ko if: filteredTopics().length > 0 -->
<ul class="assist-docs-topic-tree" data-bind="foreach: filteredTopics">
<li>
<a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, click: function () { $component.selectedTopic($data); }, html: titleMatch() || title"></a>
</li>
</ul>
<!-- /ko -->
<!-- ko if: filteredTopics().length === 0 -->
<ul class="assist-docs-topic-tree">
<li class="assist-no-entries">${I18n('No results found.')}</li>
</ul>
<!-- /ko -->
<!-- /ko -->
</div>
<!-- ko if: selectedTopic -->
<div class="assist-flex-60 assist-docs-details" data-bind="with: selectedTopic">
<div class="assist-panel-close"><button class="close" data-bind="click: function() { $component.selectedTopic(undefined); }">×</button></div>
<div data-bind="htmlUnsecure: bodyMatch() || body()"></div>
</div>
<!-- /ko -->
</div>
</div>
`;
class LanguageReferenceTopic {
constructor(entry, index) {
this.ref = entry.ref;
this.title = entry.title;
this.index = index;
this.weight = 1;
this.children = [];
entry.children.forEach(child => {
this.children.push(new LanguageReferenceTopic(child, this.index));
});
this.loadDeferred = $.Deferred();
this.loading = ko.observable(false);
this.body = ko.observable();
this.bodyMatch = ko.observable();
this.open = ko.observable(false);
this.titleMatch = ko.observable();
}
load() {
if (this.body() || this.loading()) {
return this.loadDeferred.promise();
}
this.loading(true);
simpleGet(this.index[this.ref])
.done(doc => {
this.body(doc.body);
})
.always(() => {
this.loading(false);
this.loadDeferred.resolve(this);
});
return this.loadDeferred.promise();
}
}
class AssistLangRefPanel {
constructor(params, element) {
this.connector = params.connector;
this.availableDialects = ko.observableArray();
this.activeDialect = ko.observable();
this.allTopics = {
impala: [],
hive: []
};
window.IMPALA_DOC_TOP_LEVEL.forEach(topLevelItem => {
this.allTopics.impala.push(new LanguageReferenceTopic(topLevelItem, window.IMPALA_DOC_INDEX));
});
window.HIVE_DOC_TOP_LEVEL.forEach(topLevelItem => {
this.allTopics.hive.push(new LanguageReferenceTopic(topLevelItem, window.HIVE_DOC_INDEX));
});
const updateDialect = dialect => {
if (this.availableDialects().indexOf(dialect) !== -1) {
this.activeDialect(dialect);
}
};
this.connector.subscribe(connector => {
if (connector) {
updateDialect(connector.dialect);
}
});
const configUpdated = () => {
const lastActiveDialect = this.activeDialect();
const configuredDialects = filterEditorConnectors(
connector => connector.dialect === 'hive' || connector.dialect === 'impala'
).map(connector => connector.dialect);
configuredDialects.sort();
this.availableDialects(configuredDialects);
if (
lastActiveDialect &&
this.availableDialects().find(dialect => dialect === lastActiveDialect)
) {
this.activeDialect(lastActiveDialect);
} else {
this.activeDialect(
this.availableDialects().length ? this.availableDialects()[0] : undefined
);
}
};
configUpdated();
huePubSub.subscribe(CONFIG_REFRESHED_TOPIC, configUpdated);
if (this.connector()) {
updateDialect(this.connector().dialect);
}
this.topics = ko.pureComputed(() =>
this.activeDialect() ? this.allTopics[this.activeDialect()] : []
);
this.selectedTopic = ko.observable();
this.selectedTopic.subscribe(newTopic => {
if (newTopic) {
newTopic.load();
}
});
this.query = ko.observable().extend({ throttle: 200 });
this.filteredTopics = ko.observableArray();
const sortFilteredTopics = () => {
this.filteredTopics.sort((a, b) => {
if (a.weight !== b.weight) {
return b.weight - a.weight;
}
return a.title.localeCompare(b.title);
});
};
this.query.subscribe(newVal => {
if (!newVal) {
return;
}
const lowerCaseQuery = this.query().toLowerCase();
const replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
this.filteredTopics([]);
let sortTimeout = -1;
const findInside = topic => {
topic.load().done(loadedTopic => {
let match = false;
const titleIndex = loadedTopic.title.toLowerCase().indexOf(lowerCaseQuery);
if (titleIndex !== -1) {
loadedTopic.weight = titleIndex === 0 ? 2 : 1;
loadedTopic.titleMatch(
loadedTopic.title.replace(new RegExp('(' + lowerCaseQuery + ')', 'i'), '<b>$1</b>')
);
loadedTopic.bodyMatch(undefined);
this.filteredTopics.push(loadedTopic);
match = true;
} else if (
loadedTopic.body() &&
loadedTopic.body().toLowerCase().indexOf(lowerCaseQuery) !== -1
) {
loadedTopic.weight = 0;
loadedTopic.titleMatch(undefined);
loadedTopic.bodyMatch(loadedTopic.body().replace(replaceRegexp, '<b>$1</b>'));
this.filteredTopics.push(loadedTopic);
match = true;
} else {
loadedTopic.titleMatch(undefined);
loadedTopic.bodyMatch(undefined);
}
if (match) {
window.clearTimeout(sortTimeout);
sortTimeout = window.setTimeout(sortFilteredTopics, 100);
}
});
topic.children.forEach(findInside);
};
this.topics().forEach(findInside);
window.setTimeout(() => {
// Initial sort deferred for promises to complete
sortFilteredTopics();
}, 0);
});
this.selectedTopic.subscribe(() => {
$(element).find('.assist-docs-details').scrollTop(0);
});
this.query.subscribe(() => {
$(element).find('.assist-docs-topics').scrollTop(0);
});
const scrollToSelectedTopic = function () {
const topics = $(element).find('.assist-docs-topics');
if (topics.find('.blue').length) {
topics.scrollTop(
Math.min(
topics.scrollTop() + topics.find('.blue').position().top - 20,
topics.find('> ul').height() - topics.height()
)
);
}
};
const scrollToAnchor = function (anchorId) {
if (!anchorId) {
return;
}
const detailsPanel = $(element).find('.assist-docs-details');
const found = detailsPanel.find('#' + anchorId.split('/').join(' #'));
if (found.length) {
detailsPanel.scrollTop(found.position().top - 10);
}
};
huePubSub.subscribe('scroll.test', scrollToSelectedTopic);
huePubSub.subscribe(ASSIST_LANG_REF_PANEL_SHOW_TOPIC_EVENT, targetTopic => {
const topicStack = [];
const findTopic = topics => {
topics.some(topic => {
topicStack.push(topic);
if (topic.ref === targetTopic.ref) {
while (topicStack.length) {
topicStack.pop().open(true);
}
this.query('');
this.selectedTopic(topic);
window.setTimeout(() => {
scrollToAnchor(targetTopic.anchorId);
scrollToSelectedTopic();
}, 0);
return true;
} else if (topic.children.length) {
const inChild = findTopic(topic.children);
if (inChild) {
return true;
}
}
topicStack.pop();
});
};
findTopic(this.topics());
});
$(element).on('click.langref', event => {
if (event.target.className === 'hue-doc-internal-link') {
huePubSub.publish(ASSIST_LANG_REF_PANEL_SHOW_TOPIC_EVENT, {
ref: $(event.target).data('doc-ref'),
anchorId: $(event.target).data('doc-anchor-id')
});
}
});
}
}
componentUtils.registerStaticComponent(
NAME,
{
createViewModel: (params, componentInfo) =>
new AssistLangRefPanel(params, componentInfo.element)
},
TEMPLATE
);
export default AssistLangRefPanel;