@kui-shell/plugin-tutorials
Version:
IBM Cloud shell plugin for tutorials
609 lines • 25.6 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const debug_1 = require("debug");
const util_1 = require("./util");
const wskflow_1 = require("./wskflow");
const core_1 = require("@kui-shell/core");
const debug = debug_1.default('plugins/tutorials/play');
const Marked = require("marked");
const renderer = new Marked.Renderer();
renderer.link = (href, title, text) => {
return `<a class='bx--link' href='${href}'` + (title ? ' title="' + title + '"' : '') + `}>${text}</a>`;
};
const marked = _ => Marked(_, { renderer });
let $;
try {
$ = require('jquery');
}
catch (err) {
debug('not loading jquery in headless mode ');
}
const rowFilters = {
auth: () => {
return true;
},
'no-auth': () => {
return true;
}
};
const injectOurCSS = () => {
core_1.injectCSS({
css: require('@kui-shell/plugin-tutorials/web/css/main.css').toString(),
key: 'tutorial.main'
});
core_1.injectCSS({
css: require('@kui-shell/plugin-tutorials/web/css/tutorials.css').toString(),
key: 'tutorial.tutorials'
});
};
const injectHTML = () => {
const loader = Promise.resolve(require('@kui-shell/plugin-tutorials/web/html/index.html').default);
return loader.then(html => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
document.querySelector('body .page .main .tab-container').appendChild(wrapper.children[0]);
});
};
const cancelAsyncs = obj => {
if (obj.cancellables) {
debug('processing cancellables', obj.cancellables);
obj.cancellables.forEach(cancel => cancel());
}
obj.cancellables = [];
};
const sidecarManager = {
enterFullscreen: (tab) => {
core_1.showSidecar(tab);
core_1.toggleMaximization(tab);
},
exitFullscreen: (tab) => {
core_1.clearSelection(tab);
core_1.toggleMaximization(tab);
}
};
const clearHighlights = () => {
const elements = document.querySelectorAll('.lightbox');
for (let idx = 0; idx < elements.length; idx++) {
elements[idx].classList.remove('lightbox');
elements[idx].classList.remove('lightbox-visible');
}
};
const close = (tab, pane, obj, delay = 500) => () => new Promise(resolve => {
debug('close');
cancelAsyncs(pane);
if (pane.hasAttribute('remember-to-remove-sidecar-fullscreen')) {
pane.removeAttribute('remember-to-remove-sidecar-fullscreen');
sidecarManager.exitFullscreen(tab);
}
pane.classList.remove('visible');
setTimeout(() => pane.parentNode.removeChild(pane), delay);
document.querySelector('body').classList.remove('tutorial-in-progress');
clearHighlights();
if (delay === 0) {
resolve(true);
}
else {
setTimeout(() => {
resolve(true);
}, delay);
}
core_1.getCurrentPrompt().focus();
});
const setHighlightPosition = ({ selector }) => {
const element = document.querySelector(selector);
if (!element) {
console.error('highlight element not found');
}
else {
element.classList.add('lightbox');
setTimeout(() => element.classList.add('lightbox-visible'), 0);
document.addEventListener('click', clearHighlights, true);
}
};
const commandFromFullscreen = (tab, pane, command, display = command, nested = false) => () => {
const go = () => {
tab.REPL.pexec(command);
if (command.startsWith('preview')) {
const cancellable = setTimeout(() => wskflow_1.wskflowCycle(pane), 2000);
pane.cancellables.push(() => clearTimeout(cancellable));
}
};
if (nested) {
go();
return;
}
if (pane.hasAttribute('tutorial-is-fullscreen')) {
pane.setAttribute('tutorial-was-fullscreen', '1');
pane.removeAttribute('tutorial-is-fullscreen');
document.body.classList.remove('tutorial-is-fullscreen');
}
else if (pane.hasAttribute('tutorial-was-fullscreen')) {
pane.setAttribute('tutorial-was-fullscreen', (1 + parseInt(pane.getAttribute('tutorial-was-fullscreen'), 10)).toString());
}
if (!(command.startsWith('play') || command.startsWith('tutorial play'))) {
pane.classList.add('minimized');
pane.querySelector('.tutorial-minimized-message').innerHTML = `Tutorial paused while we execute the command <span class='monospace bx--link clickable clickable-blatant' onclick='repl.pexec("${command}"})'>${display}</span>.`;
}
else if (pane.hasAttribute('tutorial-was-fullscreen')) {
setTimeout(go, 1000);
return;
}
go();
};
const renderOneTable = (tab, parent, pane, nested = false) => table => {
const template = document.querySelector('#tutorial-structured-list-template');
const tableDom = template.cloneNode(true);
const tableBody = tableDom.querySelector('.bx--structured-list-tbody');
parent.appendChild(tableDom);
tableDom.classList.remove('tutorial-template');
tableDom.removeAttribute('id');
const titleDom = tableDom.querySelector('.tutorial-content-extras-title');
if (table.title) {
titleDom.innerText = table.title;
titleDom.classList.remove('hidden');
}
else {
titleDom.classList.add('hidden');
}
parent.classList.add('visible');
if (table.columns) {
const headerRow = tableDom.querySelector('.bx--structured-list-row.bx--structured-list-row--header-row');
table.columns.forEach(column => {
const headerDom = document.createElement('th');
headerDom.classList.add('bx--structured-list-th');
headerDom.innerText = column;
headerRow.appendChild(headerDom);
});
}
if (table.rows) {
table.rows
.filter(row => !row[0].when || rowFilters[row[0].when]())
.forEach(row => {
const rowDom = document.createElement('tr');
rowDom.className = 'bx--structured-list-row';
tableBody.appendChild(rowDom);
row.forEach((cell, idx) => {
const value = typeof cell === 'string' ? cell : cell.value;
const onclick = cell.onclick || (cell.command && commandFromFullscreen(tab, pane, cell.command, cell.display, nested));
debug('cell', value);
const cellDom = document.createElement('td');
cellDom.classList.add('bx--structured-list-td');
const cellDomClickable = document.createElement('div');
cellDomClickable.innerHTML = idx === 1 ? marked(value) : value;
cellDom.appendChild(cellDomClickable);
if (onclick) {
cellDomClickable.className = 'tutorial-content-command clickable clickable-blatant bx--link';
cellDomClickable.setAttribute('data-command', cell.value);
cellDom.onclick = onclick;
}
if (idx === 0) {
cellDom.classList.add('bx--structured-list-content--nowrap');
cellDomClickable.classList.add('monospace');
}
rowDom.appendChild(cellDom);
});
});
}
};
const transitionSteps = (tab, stepNum, obj, pane, nested = false) => {
debug('step', stepNum, obj);
cancelAsyncs(pane);
const { heading, content, transition, input, extras, fontawesome, highlight, autocomplete, execute, preview, sidecar } = obj.steps[stepNum];
const headingDom = pane.querySelector('.tutorial-heading');
if (headingDom) {
headingDom.innerText = heading;
}
const paragraphs = pane.querySelector('.tutorial-content .tutorial-paragraphs');
if (paragraphs) {
paragraphs.innerHTML = marked(content);
}
const fontGraphics = pane.querySelector('.tutorial-font-graphics');
if (fontGraphics) {
core_1.empty(fontGraphics);
if (fontawesome) {
debug('fontawesome', fontawesome);
const graphics = document.createElement('i');
graphics.className = fontawesome;
fontGraphics.appendChild(graphics);
fontGraphics.classList.add('visible');
}
else {
fontGraphics.classList.remove('visible');
}
}
pane.removeAttribute('data-rendering-hints');
if (obj.steps[stepNum].renderingHints) {
pane.setAttribute('data-rendering-hints', obj.steps[stepNum].renderingHints);
}
if (sidecar === 'fullscreen') {
if (!core_1.isSidecarFullscreen(tab)) {
pane.setAttribute('remember-to-remove-sidecar-fullscreen', true.toString());
}
sidecarManager.enterFullscreen(tab);
}
const extrasPart = pane.querySelector('.tutorial-content-extras');
const learnMore = pane.querySelector('.tutorial-learn-more');
if (learnMore) {
learnMore.classList.remove('has-learn-more');
if (!extras) {
pane.classList.add('tutorial-no-extras');
}
else {
debug('extras', extras);
pane.classList.remove('tutorial-no-extras');
if (extras.learnMore) {
const titleDom = learnMore.querySelector('.tutorial-content-extras-title');
titleDom.innerText = extras.learnMore.title || 'Notes';
learnMore.classList.add('has-learn-more');
learnMore.querySelector('.tutorial-learn-more-content').innerHTML = marked(extras.learnMore.doc);
}
const codeContainer = pane.querySelector('.tutorial-code-snippet');
if (codeContainer) {
if (!extras.code) {
codeContainer.classList.remove('has-code');
}
else {
codeContainer.classList.add('has-code');
const codePart = codeContainer.querySelector('code');
codePart.className = `language-${extras.code.language}`;
codePart.innerText = extras.code.body;
hljs.highlightBlock(codePart);
}
}
let table = extras.table;
const nextSteps = extras.nextSteps || extras.alternate;
if (nextSteps) {
table = {
title: extras.alternate ? 'Alternate Adventures' : 'Next Steps',
columns: ['Command', 'Description'],
rows: nextSteps
.filter(_ => !_.hidden)
.map(({ command, display = command, doc, when }) => [
{
value: display,
when,
onclick: commandFromFullscreen(tab, pane, command, display, nested)
},
doc
])
};
}
const tables = extrasPart.querySelectorAll('.tutorial-content-extras-as-structured-list:not(.tutorial-template)');
for (let idx = 0; idx < tables.length; idx++) {
tables[idx].parentNode.removeChild(tables[idx]);
}
if (!table) {
extrasPart.classList.remove('visible');
}
else {
if (Array.isArray(table)) {
table.forEach(renderOneTable(tab, extrasPart, pane, nested));
}
else {
renderOneTable(tab, extrasPart, pane, nested)(table);
}
}
if (extras.showcase) {
const container = pane.querySelector('.tutorial-bottom');
core_1.empty(container);
pane.setAttribute('tutorial-has-showcase', 'tutorial-has-showcase');
extras.showcase.forEach(({ title, command, display = command, description, image, groupWith }) => {
const element = document.createElement('div');
element.className = 'tutorial-showcase-element';
if (command) {
element.onclick = commandFromFullscreen(tab, pane, command, display, nested);
}
const imagePart = document.createElement('img');
imagePart.className = 'clickable';
imagePart.setAttribute('src', image);
element.appendChild(imagePart);
const overlayPart = document.createElement('div');
const titlePart = document.createElement('h2');
const descriptionPart = document.createElement('div');
overlayPart.className = 'tutorial-showcase-element-overlay bx--tile';
titlePart.className = 'tutorial-showcase-element-overlay-title';
descriptionPart.className = 'tutorial-showcase-element-overlay-description smaller-text';
overlayPart.appendChild(titlePart);
overlayPart.appendChild(descriptionPart);
titlePart.innerText = title;
descriptionPart.innerHTML = marked(description);
element.appendChild(overlayPart);
const newGroup = () => {
const group = document.createElement('div');
group.className = 'tutorial-showcase-group';
group.appendChild(element);
container.appendChild(group);
};
if (!groupWith) {
newGroup();
}
else {
try {
const fn = eval(groupWith);
const group = fn(container.children);
group.appendChild(element);
}
catch (err) {
debug('error in groupWith', groupWith);
console.error(err);
newGroup();
}
}
});
}
}
}
clearHighlights();
if (stepNum === 0) {
$(pane)
.find('.tBack')
.hide();
}
else {
$(pane)
.find('.tBack')
.show();
}
const prevStepBlock = pane.querySelector(`.tutorial-header-right .tutorial-step-block.active`);
if (prevStepBlock) {
prevStepBlock.classList.remove('active');
}
const stepBlock = pane.querySelector(`.tutorial-header-right .tutorial-step-block[step="${stepNum}"]`);
if (stepBlock) {
stepBlock.classList.add('active');
}
const nextButton = pane.querySelector('.tNext');
if (nextButton) {
nextButton.setAttribute('disabled', 'disabled');
if (transition === 'next' || transition === undefined) {
if (stepNum !== obj.steps.length - 1) {
nextButton.removeAttribute('disabled');
}
}
else if (transition === 'input') {
const { selector, value } = input;
const handler = function (event) {
if (event.keyCode === 13) {
if ($(selector)
.val()
.trim() === value) {
$(document).unbind('keydown', handler);
$(pane).prop('step', stepNum + 1);
transitionSteps(tab, stepNum + 1, obj, pane);
}
}
};
$(document).bind('keydown', handler);
}
else if (transition === 'enter') {
$(pane)
.find('.tBack')
.css('display', 'inline-block');
const handler = function (event) {
if (event.keyCode === 13) {
$(document).unbind('keydown', handler);
$(pane).prop('step', stepNum + 1);
transitionSteps(tab, stepNum + 1, obj, pane);
}
};
$(document).bind('keydown', handler);
}
else if (transition === 'click') {
const { selector } = input;
$(pane)
.find('.tBack')
.css('display', 'inline-block');
const handler = function () {
$(this).unbind('click', handler);
$(pane).prop('step', stepNum + 1);
transitionSteps(tab, stepNum + 1, obj, pane);
};
$(selector).bind('click', handler);
}
}
if (highlight) {
setHighlightPosition(highlight);
}
if (autocomplete) {
const { selector, value } = autocomplete;
if (selector) {
debug('selector', selector, value);
$(selector).val(value);
}
else {
debug('autocomplete', value);
core_1.partialInput(value);
}
}
if (execute) {
debug('execute', execute);
tab.REPL.pexec(execute);
}
if (preview) {
const { file } = preview;
if (file) {
debug('preview', file);
tab.REPL.pexec(`preview ${file}`);
}
}
};
const focusOnBiggestScrollable = () => {
const allScrollables = document.querySelectorAll('#tutorialPane .scrollable');
let biggest;
for (let idx = 0; idx < allScrollables.length; idx++) {
const rect = allScrollables[idx].getBoundingClientRect();
if (!biggest || rect.height > biggest.rect.height) {
biggest = { element: allScrollables[idx], rect };
}
}
if (biggest) {
debug('focus', biggest.element);
biggest.element.focus();
}
};
const showTutorial = (tab, tutorialName, obj) => {
debug('showTutorial', obj);
core_1.clearSelection(tab);
const pane = document.querySelector('#tutorialPane');
pane.classList.remove('minimized');
pane.removeAttribute('tutorial-has-showcase');
pane.setAttribute('now-playing', tutorialName);
if (obj.fullscreen) {
pane.setAttribute('tutorial-is-fullscreen', 'tutorial-is-fullscreen');
document.body.classList.add('tutorial-is-fullscreen');
}
else {
pane.removeAttribute('tutorial-is-fullscreen');
document.body.classList.remove('tutorial-is-fullscreen');
}
const tutorialNameDom = pane.querySelector('.tutorial-header-tutorial-name');
tutorialNameDom.classList.remove('zoom-in');
setTimeout(() => tutorialNameDom.classList.add('zoom-in'), 0);
tutorialNameDom.innerText = tutorialName.replace(/-/g, ' ');
pane.querySelector('.tNext').onclick = () => {
$(pane).prop('step', $(pane).prop('step') + 1);
transitionSteps(tab, $(pane).prop('step'), obj, pane);
};
pane.querySelector('.tBack').onclick = () => {
$(pane).prop('step', $(pane).prop('step') - 1);
transitionSteps(tab, $(pane).prop('step'), obj, pane);
};
pane.querySelector('.tCloseButton').onclick = close(tab, pane, obj);
pane.querySelector('.tRestoreButton').onclick = () => {
cancelAsyncs(pane);
if (pane.hasAttribute('tutorial-was-fullscreen')) {
const stack = parseInt(pane.getAttribute('tutorial-was-fullscreen'), 10) - 1;
if (stack === 0) {
pane.removeAttribute('tutorial-was-fullscreen');
pane.setAttribute('tutorial-is-fullscreen', 'tutorial-is-fullscreen');
document.body.classList.add('tutorial-is-fullscreen');
}
else {
pane.setAttribute('tutorial-was-fullscreen', stack.toString());
}
}
core_1.hideSidecar(tab);
pane.classList.remove('minimized');
};
const ready = Promise.resolve(true);
return ready.then(() => {
debug('ready');
setTimeout(() => pane.classList.add('visible'), 100);
$(pane).prop('step', 0);
const shell = require('electron').shell;
$(document).on('click', 'a[href^="http"]', function (event) {
event.preventDefault();
shell.openExternal(this.href);
});
document.querySelector('body').classList.add('tutorial-in-progress');
if (obj.height) {
pane.setAttribute('data-height', obj.height);
}
const headerExtrasContainer = pane.querySelector('.tutorial-header-extras');
const skillsContainer = headerExtrasContainer.querySelector('.tutorial-skills');
core_1.empty(skillsContainer);
if (obj.skills) {
obj.skills.forEach(skill => {
const skillBadge = document.createElement('badge');
skillBadge.innerText = skill;
skillsContainer.appendChild(skillBadge);
});
}
const stepBlocksContainer = pane.querySelector('.tutorial-header-blocks');
core_1.empty(stepBlocksContainer);
pane.setAttribute('num-steps', obj.steps.length.toString());
if (obj.steps.length > 1) {
for (let idx = 0; idx < obj.steps.length; idx++) {
;
(function (idx) {
const block = document.createElement('div');
const blockInner = document.createElement('div');
blockInner.classList.add('tutorial-step-block');
blockInner.setAttribute('step', idx.toString());
block.setAttribute('data-balloon', obj.steps[idx].heading);
block.setAttribute('data-balloon-pos', idx > obj.steps.length / 2 ? 'down-right' : 'down');
block.setAttribute('data-balloon-length', 'small');
block.onclick = () => {
$(pane).prop('step', idx);
transitionSteps(tab, idx, obj, pane);
};
block.appendChild(blockInner);
stepBlocksContainer.appendChild(block);
})(idx);
}
}
transitionSteps(tab, 0, obj, pane);
core_1.scrollIntoView({ when: 800 });
setTimeout(focusOnBiggestScrollable, 800);
return true;
});
};
const use = (cmd) => ({ argvNoOptions, tab, execOptions, parsedOptions }) => __awaiter(void 0, void 0, void 0, function* () {
injectOurCSS();
const ready = document.querySelector('#tutorialPane') ? Promise.resolve() : injectHTML();
const filepath = argvNoOptions[argvNoOptions.indexOf(cmd) + 1];
const [{ config, tutorial }] = yield Promise.all([util_1.readProject(core_1.findFile(filepath)), ready]);
if (execOptions.type === core_1.ExecType.Nested && !parsedOptions['top-level']) {
const pane = document.createElement('div');
pane.classList.add('tutorialPane');
const body = document.createElement('div');
body.classList.add('tutorial-body');
pane.appendChild(body);
const content = document.createElement('div');
content.classList.add('tutorial-content');
body.appendChild(content);
const paragraphs = document.createElement('div');
paragraphs.classList.add('tutorial-paragraphs');
content.appendChild(paragraphs);
const learnMore = document.createElement('div');
learnMore.classList.add('tutorial-learn-more');
content.appendChild(learnMore);
const extras = document.createElement('div');
extras.classList.add('tutorial-content-extras');
content.appendChild(extras);
const list = document.createElement('div');
list.classList.add('tutorial-content-extras-as-structured-list');
extras.appendChild(list);
transitionSteps(tab, 0, tutorial || config.tutorial, pane, true);
return pane;
}
else {
return showTutorial(tab, config.name, tutorial || config.tutorial);
}
});
const usage = (cmd) => ({
command: cmd,
strict: cmd,
title: 'Start tutorial',
header: 'Start playing a tutorial',
example: `tutorial ${cmd} @tutorials/<tutorialName>`,
required: [{ name: 'tutorialPath', file: true, docs: 'Path or URI to a tutorial' }],
optional: [{ name: '--top-level', docs: 'Render as a top-level tutorial' }]
});
exports.default = (commandTree) => __awaiter(void 0, void 0, void 0, function* () {
const cmd = commandTree.listen('/tutorial/play', use('play'), {
usage: usage('play'),
needsUI: true,
noAuthOk: true
});
commandTree.synonym('/tutorial/use', use('use'), cmd, {
usage: usage('use'),
needsUI: true,
noAuthOk: true
});
commandTree.synonym('/tutorial/start', use('start'), cmd, {
usage: usage('start'),
needsUI: true,
noAuthOk: true
});
});
//# sourceMappingURL=play.js.map