UNPKG

@kui-shell/plugin-tutorials

Version:

IBM Cloud shell plugin for tutorials

609 lines 25.6 kB
"use strict"; 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