Files
Superalgos/Projects/Education/UI/Spaces/Tutorial-Space/TutorialSpace.js
T

1563 lines
68 KiB
JavaScript

function newEducationTutorialSpace() {
const MODULE_NAME = 'Tutorial Space'
let thisObject = {
stop: stop,
skip: skip,
previous: previous,
next: next,
playTutorial: playTutorial,
resumeTutorial: resumeTutorial,
playTutorialTopic: playTutorialTopic,
resumeTutorialTopic: resumeTutorialTopic,
playTutorialStep: playTutorialStep,
resumeTutorialStep: resumeTutorialStep,
resetTutorialProgress: resetTutorialProgress,
sidePanelTab: undefined,
container: undefined,
physics: physics,
draw: draw,
getContainer: getContainer,
initialize: initialize,
finalize: finalize,
reset: reset
}
let TUTORIAL_NAME
let PAGE_NUMBER
let isInitialized = false
thisObject.container = newContainer()
thisObject.container.name = MODULE_NAME
thisObject.container.initialize()
thisObject.container.isClickeable = true
thisObject.container.isDraggeable = false
thisObject.container.detectMouseOver = true
thisObject.container.status = 'hidden'
resize()
let browserResizedEventSubscriptionId
let openingEventSubscriptionId
let closingEventSubscriptionId
let tutorialRootNode
let currentNode
let currentStatus = 'Stopped'
let navigationStack
let tutorialDiv = document.getElementById('tutorialDiv')
let tutorialFormDiv = document.getElementById('tutorialFormDiv')
let htmlGif = document.createElement("IMG")
let htmlImage = document.createElement("IMG")
let currentGifName
let newGifName
let currentImageName
let newImageName
let newImageProject
let currentConfig
let newConfig
let currentDocument
let newDocument
let resumeModeActivated
let lastExecutedAction
let actionCounter
let forcingFocus
let currentZoomStep
let viewportCentered
let repositionAtTimeMachineCounter
let positionAtNodeCounter
let timeMachineKeyPressedCounter
let keyPressedCounter
let sliderCounter
let changeNodeConfigCounter
let changeNodeConfigCounterWithEval
let documentationCounter
let batchConfigChangesCounter
let workspacesCounter
return thisObject
function initialize() {
currentGifName = 'Never Set'
newGifName = 'Never Set'
currentImageName = 'Never Set'
newImageName = 'Never Set'
newImageProject = 'Never Set'
currentConfig
newConfig
currentDocument
newDocument
resumeModeActivated // In this mode, we skip all the node which status is 'Done'
lastExecutedAction = ''
actionCounter = 0
forcingFocus = false
currentZoomStep = 0
viewportCentered = false
repositionAtTimeMachineCounter = 0
positionAtNodeCounter = 0
timeMachineKeyPressedCounter = 0
keyPressedCounter = 0
sliderCounter = 0
changeNodeConfigCounter = 0
changeNodeConfigCounterWithEval = 0
documentationCounter = 0
batchConfigChangesCounter = 0
workspacesCounter = 0
setupSidePanelTab()
browserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize)
let workspace = UI.projects.foundations.spaces.designSpace.workspace
workspace.executeAction({ name: 'Play Tutorials', project: 'Foundations' })
isInitialized = true
}
function finalize() {
canvas.eventHandler.stopListening(browserResizedEventSubscriptionId)
thisObject.sidePanelTab.container.eventHandler.stopListening(openingEventSubscriptionId)
thisObject.sidePanelTab.container.eventHandler.stopListening(closingEventSubscriptionId)
tutorialRootNode = undefined
currentNode = undefined
navigationStack = undefined
}
async function reset() {
finalize()
initialize()
}
function resize() {
thisObject.container.frame.width = 0
thisObject.container.frame.height = 0
}
function getContainer(point, purpose) {
if (thisObject.sidePanelTab === undefined) { return }
let container
container = thisObject.sidePanelTab.getContainer(point, purpose)
if (container !== undefined) { return container }
if (thisObject.container.frame.isThisPointHere(point, true) === true) {
return thisObject.container
} else {
return undefined
}
}
function physics() {
if (currentNode === undefined) { return }
switch (currentStatus) {
case 'Stopped': {
makeInvisible()
//thisObject.sidePanelTab.close()
currentNode = undefined
break
}
case 'Playing Tutorial': {
makeVsible()
// Animation Caused the advance() function to be called skipping the first tutorial step commented out until better implementation can be added
//thisObject.sidePanelTab.open()
break
}
case 'Playing Topic': {
makeVsible()
break
}
case 'Playing Step': {
makeVsible()
break
}
}
keyPressed()
toggleMapView()
checkPressButton()
checkGif()
checkImage()
checkDocumentation()
checkWorkspaces()
checkSlider()
checkReference()
checkViewportZoom()
checkViewportZoomAtCenter()
return
function keyPressed() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
if (
config.keyPressed !== undefined
) {
keyPressedCounter++
if (keyPressedCounter === 10) {
let event = {
metaKey: config.keyPressed.metaKey,
shiftKey: config.keyPressed.shiftKey,
ctrlKey: config.keyPressed.ctrlKey,
code: config.keyPressed.code,
key: config.keyPressed.key
}
canvas.onKeyDown(event, true)
}
}
}
function toggleMapView() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
if (
config.toggleMapView !== undefined
) {
if (config.toggleMapView.view === "On") {
UI.projects.foundations.spaces.floatingSpace.inMapMode = true
} else
if (config.toggleMapView.view === "Off") {
UI.projects.foundations.spaces.floatingSpace.inMapMode = false
}
}
}
function checkViewportZoom() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
if (config.viewportZoom !== undefined) {
let totalZoomSteps = config.viewportZoom
let delta
if (totalZoomSteps > 0) {
delta = 1
} else {
delta = -1
}
currentZoomStep = currentZoomStep + delta
let event = {
delta: delta
}
if (totalZoomSteps > 0) {
if (currentZoomStep < totalZoomSteps) {
UI.projects.foundations.spaces.chartingSpace.viewport.onMouseWheel(event)
}
} else {
if (currentZoomStep > totalZoomSteps) {
UI.projects.foundations.spaces.chartingSpace.viewport.onMouseWheel(event)
}
}
}
}
function checkViewportZoomAtCenter() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
if (config.viewportZoomAtCenter !== undefined) {
UI.projects.foundations.spaces.chartingSpace.viewport.zoomAtCenter(config.viewportZoomAtCenter)
}
}
function checkPressButton() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
if (config.pressButton === undefined || config.pressButton === "") {
return
}
/* Remove this item from the navigation stack. */
navigationStack.splice(navigationStack.length - 1)
switch (config.pressButton) {
case "Stop": {
stop()
return
}
case "Skip": {
skip()
return
}
case "Previous": {
previous()
return
}
case "Next": {
next()
return
}
}
}
function checkGif() {
let tutorialGifDiv = document.getElementById('tutorialGifDiv')
if (tutorialGifDiv !== null && tutorialGifDiv !== undefined) {
tutorialGifDiv.appendChild(htmlGif)
}
if (currentGifName === newGifName) { return }
currentGifName = newGifName
htmlGif.src = currentGifName
htmlGif.width = "400"
htmlGif.height = "580"
}
function checkImage() {
let tutorialImageDiv = document.getElementById('tutorialImageDiv')
if (tutorialImageDiv !== null && tutorialImageDiv !== undefined) {
tutorialImageDiv.appendChild(htmlImage)
}
if (newImageName === undefined) { return }
if (currentImageName === newImageName) { return }
currentImageName = newImageName
// If gathering Icon by Project and Name get it now
let icon = UI.projects.foundations.spaces.designSpace.getIconByProjectAndName(newImageProject, newImageName)
if (icon !== undefined) {
htmlImage.src = icon.src
htmlImage.width = "100"
htmlImage.height = "100"
} else if (icon === undefined) {
// Legacy Code to handle icons being fetched by literal path
// Note: This if statment should be removed once all tutoials have been refactored
let webParam = 'Icons/' + newImageProject + '/' + newImageName + '.png'
htmlImage.src = webParam
htmlImage.width = "100"
htmlImage.height = "100"
}
}
function checkDocumentation() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
documentationCounter++
if (documentationCounter === 10) {
/* Deprecated Property Documentation URL */
if (config.documentationURL !== undefined && config.documentationURL !== "") {
console.log('Trying to navigate to: ' + config.documentationURL + ' at ' + config.docs.type)
}
if (config.controlDocs === undefined) {
/*
The doc panel will remain as it is, and the user will be free to open or close it at will.
*/
return
}
if (config.controlDocs.panel === "Open") {
/*
This forces the tutorial to open the documentation panel.
*/
UI.projects.education.spaces.docsSpace.sidePanelTab.open()
}
if (config.controlDocs.panel === "Close") {
/*
This forces the tutorial to close the documentation panel.
*/
UI.projects.education.spaces.docsSpace.sidePanelTab.close()
}
if (
config.controlDocs.page !== undefined &&
config.controlDocs.page.project !== undefined &&
config.controlDocs.page.category !== undefined &&
config.controlDocs.page.type !== undefined &&
config.controlDocs.page.project !== '' &&
config.controlDocs.page.category !== '' &&
config.controlDocs.page.type !== ''
) {
/*
This produces the Docs to laod the specified page.
*/
UI.projects.education.spaces.docsSpace.openSpaceAreaAndNavigateTo(config.controlDocs.page.project, config.controlDocs.page.category, config.controlDocs.page.type, config.controlDocs.page.anchor)
}
}
}
function checkWorkspaces() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
workspacesCounter++
if (config.workspaces === undefined) {
/*
The worspaces panel will remain as it is, and the user will be free to open or close it at will.
*/
return
}
if (config.workspaces === "" || config.workspaces === "Close") {
/*
This forces the tutorial to close the workspaces panel and to keep it closed.
*/
if (workspacesCounter === 5) {
UI.projects.foundations.spaces.workspaceSpace.sidePanelTab.close()
}
return
}
if (config.workspaces === "Open") {
/*
This forces the tutorial to open the workspaces panel and to keep it closed.
*/
if (workspacesCounter === 5) {
UI.projects.foundations.spaces.workspaceSpace.sidePanelTab.open()
}
return
}
}
function checkSlider() {
if (currentNode === undefined) { return }
let config = JSON.parse(currentNode.config)
if (config.slider === undefined) {
/*
The cockpit space will remain where it is. The user is free to move it.
*/
return
}
sliderCounter++
if (sliderCounter === 1 || sliderCounter === 100) {
switch (config.slider) {
case "toTop": {
/*
This forces the tutorial to close the charting space and to keep it closed.
*/
UI.projects.foundations.spaces.cockpitSpace.toTop()
return
}
case "toMiddle": {
/*
This forces the tutorial to share the screen half with the designer and half
with the charting space and force it in that way.
*/
UI.projects.foundations.spaces.cockpitSpace.toMiddle()
return
}
case "toBottom": {
/*
This forces the tutorial to fully open the charting space and to keep it open.
*/
UI.projects.foundations.spaces.cockpitSpace.toBottom()
return
}
}
}
}
function checkReference() {
/*
If there is a reference parent defined, we will highlight it
and move the designer so that that node be at the center of the screen.
*/
if (currentNode !== undefined) {
if (currentNode.payload !== undefined) {
if (currentNode.payload.referenceParent !== undefined) {
if (currentNode.payload.referenceParent.payload !== undefined) {
if (currentNode.payload.referenceParent.payload.uiObject !== undefined) {
let config = JSON.parse(currentNode.config)
if (
config.positionAtReferenceParent === true
) {
/*
This moves the Designs Space so that the referenced node is at the center of the screen.
*/
positionAtNodeCounter++
if (positionAtNodeCounter === 1) {
currentNode.payload.referenceParent.payload.floatingObject.unCollapseParent()
}
if (positionAtNodeCounter < 100) {
UI.projects.foundations.spaces.floatingSpace.positionAtNode(currentNode.payload.referenceParent)
}
}
if (
config.highlightReferenceParent === true
) {
currentNode.payload.referenceParent.payload.uiObject.highlight()
}
if (
config.highlightReferenceParent === true
) {
currentNode.payload.referenceParent.payload.uiObject.highlight()
}
if (
config.setErrorMessageReferenceParent !== undefined
) {
currentNode.payload.referenceParent.payload.uiObject.setErrorMessage(config.setErrorMessageReferenceParent)
}
if (
config.setWarningMessageReferenceParent !== undefined
) {
currentNode.payload.referenceParent.payload.uiObject.setWarningMessage(config.setWarningMessageReferenceParent)
}
if (
config.setInfoMessageReferenceParent !== undefined
) {
currentNode.payload.referenceParent.payload.uiObject.setInfoMessage(config.setInfoMessageReferenceParent)
}
if (
config.setValueReferenceParent !== undefined
) {
currentNode.payload.referenceParent.payload.uiObject.setValue(config.setValueReferenceParent)
}
if (
config.setPercentageReferenceParent !== undefined
) {
currentNode.payload.referenceParent.payload.uiObject.setPercentage(config.setPercentageReferenceParent)
}
if (
config.setStatusReferenceParent !== undefined
) {
currentNode.payload.referenceParent.payload.uiObject.setStatus(config.setStatusReferenceParent)
}
if (
config.setFocusReferenceParent === true
) {
currentNode.payload.referenceParent.payload.floatingObject.forceFocus()
forcingFocus = true
}
if (
config.batchConfigChangesReferenceParent !== undefined
) {
batchConfigChangesCounter++
if (batchConfigChangesCounter === 5 || batchConfigChangesCounter === 10) {
/*
Here we are going to process an array of different changes to descendents
of the reference parent. The end user needs to specify a nodePath to the
descendent and an object with the properties and new values that want to
change at each descendent. The nodePath route starts at the object
referenceParent.
*/
let referenceParent = currentNode.payload.referenceParent
for (let i = 0; i < config.batchConfigChangesReferenceParent.length; i++) {
let arrayItem = config.batchConfigChangesReferenceParent[i]
let nodePath = arrayItem.nodePath
let properties = arrayItem.properties
let childNode
try {
childNode = eval(nodePath)
} catch (err) {
console.log(MODULE_NAME + ' -> The nodePath provided is not good. -> nodePath = ' + nodePath)
let splittedPath = nodePath.split('.')
let partialPath = ''
let separator = ''
for (let j = 0; j < splittedPath.length; j++) {
let part = splittedPath[j]
let partialNode
let splittedParts = part.split('[')
if (splittedParts.length > 1) {
partialNode = eval(partialPath + separator + splittedParts[0])
if (partialNode === undefined) {
console.log(MODULE_NAME + ' -> This part can not be undefined -> part = ' + splittedParts[0])
currentNode.payload.uiObject.setErrorMessage('Node Path is incorrect. See console log for details.')
break
}
}
partialPath = partialPath + separator + part
separator = '.'
partialNode = eval(partialPath)
if (partialNode === undefined) {
console.log(MODULE_NAME + ' -> This part can not be undefined -> part = ' + part)
currentNode.payload.uiObject.setErrorMessage('Node Path is incorrect. See console log for details.')
break
}
}
continue
}
let nodeConfig = JSON.parse(childNode.config)
for (const property in properties) {
nodeConfig[property] = properties[property]
}
childNode.config = JSON.stringify(nodeConfig, undefined, 5)
}
}
}
if (
config.changeNodeConfigReferenceParent !== undefined
) {
changeNodeConfigCounter++
if (changeNodeConfigCounter === 5) {
let nodeConfig = JSON.parse(currentNode.payload.referenceParent.config)
for (const property in config.changeNodeConfigReferenceParent) {
nodeConfig[property] = config.changeNodeConfigReferenceParent[property]
}
currentNode.payload.referenceParent.config = JSON.stringify(nodeConfig)
}
}
if (
config.changeNodeConfigWithEvalReferenceParent !== undefined
) {
changeNodeConfigCounterWithEval++
if (changeNodeConfigCounterWithEval === 5 || changeNodeConfigCounterWithEval === 10) {
let nodeConfig = JSON.parse(currentNode.payload.referenceParent.config)
for (const property in config.changeNodeConfigWithEvalReferenceParent) {
nodeConfig[property] = eval(config.changeNodeConfigWithEvalReferenceParent[property])
}
currentNode.payload.referenceParent.config = JSON.stringify(nodeConfig)
}
}
if (
config.menuActionReferenceParent !== undefined
) {
/*
Some actions needs a little delay because they depend on animations to happen.
For that reason, we will count until 10 before executing actions from tutorial.
*/
actionCounter++
if (actionCounter > 10) {
if (config.menuActionReferenceParent + currentNode.id !== lastExecutedAction) {
currentNode.payload.referenceParent.payload.uiObject.menu.internalClick(config.menuActionReferenceParent)
lastExecutedAction = config.menuActionReferenceParent + currentNode.id
}
}
} else {
lastExecutedAction = ""
}
if (
config.keyPressedTimeMachineReferenceParent !== undefined
) {
if (currentNode.payload.referenceParent.type === 'Time Machine') {
let timeMachine = UI.projects.foundations.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent)
if (timeMachine !== undefined) {
timeMachineKeyPressedCounter++
if (timeMachineKeyPressedCounter === 10) {
let event = {
metaKey: config.keyPressedTimeMachineReferenceParent.metaKey,
shiftKey: config.keyPressedTimeMachineReferenceParent.shiftKey,
ctrlKey: config.keyPressedTimeMachineReferenceParent.ctrlKey,
code: config.keyPressedTimeMachineReferenceParent.code,
key: config.keyPressedTimeMachineReferenceParent.key
}
timeMachine.edgeEditor.onKeyPressed(event, true)
}
}
}
}
if (
config.viewportCenterReferenceParent === true
) {
if (currentNode.payload.referenceParent.type === 'Time Machine') {
let timeMachine = UI.projects.foundations.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent)
if (timeMachine !== undefined && viewportCentered === false) {
UI.projects.foundations.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container)
viewportCentered = true
}
}
}
if (
config.repositionAtTimeMachineReferenceParent === true
) {
if (currentNode.payload.referenceParent.type === 'Time Machine') {
let timeMachine = UI.projects.foundations.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent)
if (timeMachine !== undefined) {
repositionAtTimeMachineCounter++
switch (true) {
case (repositionAtTimeMachineCounter > 1 && repositionAtTimeMachineCounter < 20): {
let event = {
delta: -1
}
for (let i = 0; i < 100; i++) {
UI.projects.foundations.spaces.chartingSpace.viewport.onMouseWheel(event)
}
break
}
case (repositionAtTimeMachineCounter === 30): {
UI.projects.foundations.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container)
break
}
case (repositionAtTimeMachineCounter === 60): {
let event = {
metaKey: true,
shiftKey: true,
ctrlKey: true,
code: undefined,
key: 'A'
}
timeMachine.edgeEditor.onKeyPressed(event, true)
break
}
case (repositionAtTimeMachineCounter === 90): {
UI.projects.foundations.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container)
break
}
case (repositionAtTimeMachineCounter === 120): {
UI.projects.foundations.spaces.chartingSpace.viewport.zoomAtCenter(8)
break
}
}
}
}
}
if (
config.repositionAtTimeMachineNoZoomReferenceParent === true
) {
if (currentNode.payload.referenceParent.type === 'Time Machine') {
let timeMachine = UI.projects.foundations.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent)
if (timeMachine !== undefined) {
repositionAtTimeMachineCounter++
switch (true) {
case (repositionAtTimeMachineCounter > 1 && repositionAtTimeMachineCounter < 30): {
for (let i = 0; i < 10; i++) {
let event = {
delta: -1
}
UI.projects.foundations.spaces.chartingSpace.viewport.onMouseWheel(event)
}
break
}
case (repositionAtTimeMachineCounter === 30): {
UI.projects.foundations.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container)
break
}
}
}
}
}
}
}
}
}
}
}
function makeInvisible() {
const HEIGHT = 0
const WIDTH = 0
const FORM_HEIGHT = 0
tutorialPosition = {
x: 100000,
y: 100000
}
tutorialDiv.style = ' ' +
'position:fixed; top:' + tutorialPosition.y + 'px; ' +
'left:' + tutorialPosition.x + 'px; ' +
'width: ' + WIDTH + 'px;' +
'height: ' + HEIGHT + 'px;' +
'z-index:1;'
tutorialFormDiv.style = ' ' +
'position:fixed; top:' + (tutorialPosition.y) + 'px; ' +
'left:' + tutorialPosition.x + 'px; ' +
'width: ' + WIDTH + 'px;' +
'height: ' + FORM_HEIGHT + 'px;' +
'z-index:1;'
}
function makeVsible() {
const HEIGHT = 675
const WIDTH = 400
const MARGIN = 100
const FORM_HEIGHT = 40
let nodeConfig
try {
nodeConfig = JSON.parse(currentNode.config)
} catch (err) {
return
}
let position = nodeConfig.position
if (UI.projects.education.spaces.docsSpace.isVisible === true) {
position = 'Left'
}
switch (position) {
case 'Left': {
tutorialPosition = {
x: MARGIN,
y: (browserCanvas.height - HEIGHT) / 2
}
break
}
case 'Center': {
tutorialPosition = {
x: browserCanvas.width / 2 - WIDTH / 2,
y: (browserCanvas.height - HEIGHT) / 2
}
break
}
case 'Right': {
tutorialPosition = {
x: browserCanvas.width - WIDTH - MARGIN,
y: (browserCanvas.height - HEIGHT) / 2
}
break
}
default: {
tutorialPosition = {
x: browserCanvas.width / 2 - WIDTH / 2,
y: (browserCanvas.height - HEIGHT) / 2
}
break
}
}
tutorialDiv.style = ' ' +
'position:fixed; top:' + tutorialPosition.y + 'px; ' +
'left:' + tutorialPosition.x + 'px; ' +
'width: ' + WIDTH + 'px;' +
'height: ' + HEIGHT + 'px;' +
'z-index:1;'
buildHTML()
tutorialFormDiv.style = ' ' +
'position:fixed; top:' + (tutorialPosition.y + HEIGHT - 30) + 'px; ' +
'left:' + tutorialPosition.x + 'px; ' +
'width: ' + (WIDTH + 20) + 'px;' +
'height: ' + FORM_HEIGHT + 'px;' +
'z-index:1;'
}
}
function stop() {
resumeModeActivated = false
resetAfterButtonPressed()
checkForceFocus()
if (tutorialRootNode !== undefined) {
tutorialRootNode.payload.uiObject.isPlaying = false
tutorialRootNode = undefined
}
currentStatus = 'Stopped'
}
function skip() {
resumeModeActivated = false
resetAfterButtonPressed()
checkForceFocus()
let tutorial = {
status: 'Skipped'
}
UI.projects.foundations.utilities.tutorial.saveTutorial(currentNode.payload, tutorial)
advance(true)
}
function previous() {
resumeModeActivated = false
resetAfterButtonPressed()
checkForceFocus()
if (navigationStack.length > 1) {
let tutorial = {
status: undefined
}
let previousNode
/*
Reseet the Status of Current and Previous Node
so that RESUME goes to the right node.
*/
UI.projects.foundations.utilities.tutorial.saveTutorial(currentNode.payload, tutorial)
previousNode = navigationStack[navigationStack.length - 3]
if (previousNode !== undefined) {
UI.projects.foundations.utilities.tutorial.saveTutorial(previousNode.payload, tutorial)
}
previousNode = navigationStack[navigationStack.length - 2]
switch (previousNode.type) {
case 'Tutorial': {
currentNode = previousNode
findTutorialNode(currentNode)
currentStatus = 'Playing Tutorial'
navigationStack.splice(navigationStack.length - 1)
break
}
case 'Tutorial Topic': {
currentNode = previousNode
findTutorialNode(currentNode)
currentStatus = 'Playing Topic'
navigationStack.splice(navigationStack.length - 1)
break
}
case 'Tutorial Step': {
currentNode = previousNode
findTutorialNode(currentNode)
currentStatus = 'Playing Step'
navigationStack.splice(navigationStack.length - 1)
break
}
}
}
}
function next() {
resumeModeActivated = false
resetAfterButtonPressed()
checkForceFocus()
if (ckeckGoingToAnotherTutorial() === true) { return }
let tutorial = {
status: 'Done'
}
UI.projects.foundations.utilities.tutorial.saveTutorial(currentNode.payload, tutorial)
advance()
}
function resetAfterButtonPressed() {
resetViewport()
resetActions()
resetWorkspaces()
resetDocumentation()
resetRepositionAtTimeMachine()
resetPositionAtNode()
resetTimeMachineKeyPressed()
resetKeyPressed()
resetSlider()
changeNodeConfig()
batchConfigChanges()
function resetActions() {
lastExecutedAction = ""
actionCounter = 0
}
function resetViewport() {
currentZoomStep = 0
viewportCentered = false
}
function resetWorkspaces() {
workspacesCounter = 0
}
function resetDocumentation() {
currentDocumentationPage = ''
documentationCounter = 0
}
function resetRepositionAtTimeMachine() {
repositionAtTimeMachineCounter = 0
}
function resetPositionAtNode() {
positionAtNodeCounter = 0
}
function resetTimeMachineKeyPressed() {
timeMachineKeyPressedCounter = 0
}
function resetKeyPressed() {
keyPressedCounter = 0
}
function resetSlider() {
sliderCounter = 0
}
function changeNodeConfig() {
changeNodeConfigCounter = 0
changeNodeConfigCounterWithEval = 0
}
function batchConfigChanges() {
batchConfigChangesCounter = 0
}
}
function checkForceFocus() {
if (forcingFocus === true) {
currentNode.payload.referenceParent.payload.floatingObject.removeForceFocus()
forcingFocus = false
}
}
function ckeckGoingToAnotherTutorial() {
if (currentNode.payload !== undefined) {
if (currentNode.payload.referenceParent !== undefined) {
let config = JSON.parse(currentNode.config)
if (config.startTutorialReferenceParent === true) {
if (currentNode.payload.referenceParent.type === 'Tutorial') {
tutorialRootNode = currentNode.payload.referenceParent
currentNode = currentNode.payload.referenceParent
currentStatus = 'Playing Tutorial'
resumeModeActivated = false
navigationStack.push(currentNode)
findTutorialNode(currentNode)
return true
}
}
}
}
}
function playTutorial(node) {
if (UI.projects.foundations.spaces.designSpace.workspace.isInitialized !== true) { return }
PAGE_NUMBER = 0
TUTORIAL_NAME = node.name
navigationStack = []
node.payload.uiObject.isPlaying = true
tutorialRootNode = node
currentNode = node
currentStatus = 'Playing Tutorial'
resumeModeActivated = false
navigationStack.push(currentNode)
}
function resumeTutorial(node) {
if (UI.projects.foundations.spaces.designSpace.workspace.isInitialized !== true) { return }
//Testing if removing the navigationStack reset here fixes the tutorial resume issues
//navigationStack = []
node.payload.uiObject.isPlaying = true
tutorialRootNode = node
currentNode = node
currentStatus = 'Playing Tutorial'
resumeModeActivated = true
navigationStack.push(currentNode)
advance()
}
function playTutorialTopic(node) {
navigationStack = []
currentTopicNode = node
currentNode = node
currentStatus = 'Playing Topic'
resumeModeActivated = false
navigationStack.push(currentNode)
findTutorialNode(node)
}
function resumeTutorialTopic(node) {
//Testing if removing the navigationStack reset here fixes the tutorial resume issues
//navigationStack = []
currentTopicNode = node
currentNode = node
currentStatus = 'Playing Topic'
resumeModeActivated = true
navigationStack.push(currentNode)
findTutorialNode(node)
}
function playTutorialStep(node) {
navigationStack = []
currentStepNode = node
currentNode = node
currentStatus = 'Playing Step'
resumeModeActivated = false
navigationStack.push(currentNode)
findTutorialNode(node)
}
function resumeTutorialStep(node) {
//Testing if removing the navigationStack reset here fixes the tutorial resume issues
//navigationStack = []
currentStepNode = node
currentNode = node
currentStatus = 'Playing Step'
resumeModeActivated = true
navigationStack.push(currentNode)
findTutorialNode(node)
}
function resetTutorialProgress(node) {
resetNextNode(node)
function resetNextNode(node) {
for (let i = 0; i < node.tutorialSteps.length; i++) {
let tutorialStep = node.tutorialSteps[i]
let tutorial = {
status: 'Reset'
}
UI.projects.foundations.utilities.tutorial.saveTutorial(tutorialStep.payload, tutorial)
}
for (let i = 0; i < node.tutorialTopics.length; i++) {
let tutorialTopic = node.tutorialTopics[i]
let tutorial = {
status: 'Reset'
}
UI.projects.foundations.utilities.tutorial.saveTutorial(tutorialTopic.payload, tutorial)
resetNextNode(tutorialTopic)
}
}
}
function findTutorialNode(node) {
/*
We will consider the tutorialRootNode the head of the hirierchy
*/
tutorialRootNode = node
if (node.payload !== undefined) {
if (node.payload.parentNode !== undefined) {
findTutorialNode(node.payload.parentNode)
}
}
}
function advance(isSkipping) {
let found
let advanced
switch (currentStatus) {
case 'Playing Tutorial': {
found = true
findNextNode(tutorialRootNode)
break
}
case 'Playing Topic': {
found = false
if (isSkipping === true) {
findNextTopic(tutorialRootNode)
} else {
findNextNode(tutorialRootNode)
}
break
}
case 'Playing Step': {
found = false
findNextNode(tutorialRootNode)
break
}
}
/*
If we can not find a next node, then we stop the tutorial.
*/
if (advanced === undefined) {
stop()
return
}
function findNextNode(node) {
if (node.tutorialSteps !== undefined) {
for (let i = 0; i < node.tutorialSteps.length; i++) {
let tutorialStep = node.tutorialSteps[i]
if (found === true) {
if (resumeModeActivated !== true) {
currentNode = tutorialStep
currentStatus = 'Playing Step'
advanced = true
navigationStack.push(currentNode)
findTutorialNode(currentNode)
return
} else {
let tutorial = {
status: undefined
}
UI.projects.foundations.utilities.tutorial.loadTutorial(tutorialStep.payload, tutorial)
currentNode = tutorialStep
currentStatus = 'Playing Step'
navigationStack.push(currentNode)
if (tutorial.status !== 'Done') {
advanced = true
findTutorialNode(currentNode)
return
}
}
}
if (tutorialStep.id === currentNode.id) {
found = true
}
}
}
for (let i = 0; i < node.tutorialTopics.length; i++) {
if (advanced === true) {
return
}
let tutorialTopic = node.tutorialTopics[i]
if (found === true) {
if (resumeModeActivated !== true) {
currentNode = tutorialTopic
currentStatus = 'Playing Topic'
advanced = true
navigationStack.push(currentNode)
findTutorialNode(currentNode)
return
} else {
let tutorial = {
status: undefined
}
UI.projects.foundations.utilities.tutorial.loadTutorial(tutorialTopic.payload, tutorial)
currentNode = tutorialTopic
currentStatus = 'Playing Topic'
navigationStack.push(currentNode)
if (tutorial.status !== 'Done') {
advanced = true
findTutorialNode(currentNode)
return
}
}
}
if (tutorialTopic.id === currentNode.id) {
found = true
}
findNextNode(tutorialTopic, found)
}
}
function findNextTopic(node) {
for (let i = 0; i < node.tutorialTopics.length; i++) {
if (advanced === true) {
return
}
let tutorialTopic = node.tutorialTopics[i]
if (found === true) {
if (resumeModeActivated !== true) {
currentNode = tutorialTopic
currentStatus = 'Playing Topic'
advanced = true
navigationStack.push(currentNode)
findTutorialNode(currentNode)
return
} else {
let tutorial = {
status: undefined
}
UI.projects.foundations.utilities.tutorial.loadTutorial(tutorialTopic.payload, tutorial)
currentNode = tutorialTopic
currentStatus = 'Playing Topic'
navigationStack.push(currentNode)
if (tutorial.status !== 'Done') {
advanced = true
findTutorialNode(currentNode)
return
}
}
}
if (tutorialTopic.id === currentNode.id) {
found = true
} else {
findNextTopic(tutorialTopic, found)
}
}
}
}
function buildHTML() {
let nodeConfig = JSON.parse(currentNode.config)
if (nodeConfig.docs === undefined) {
/* The current node is not referencing any page at the Docs */
return
}
let schemaDocument = SCHEMAS_BY_PROJECT.get(nodeConfig.docs.project).map.docsTutorialSchema.get(nodeConfig.docs.type)
if (schemaDocument === undefined) {
schemaDocument = {
type: 'Tutorial',
definition: {
text: "If you are a new user then please refresh the UI with F5."
},
icon: {
project: "Foundations",
name: "warning"
},
paragraphs: [
{
style: "Title",
text: "Reference Missing"
},
{
style: "Json",
text: ' \"docs\": { \n' +
'\"project\": \"Foundations\",\n' +
'\"category\": \"Tutorial\",\n' +
'\"type\": \"Tutorial Step - Bla Bla\"\n' +
'}'
},
{
style: "Note",
text: "Add at the config a reference to a Tutorial Page."
}
]
}
}
newConfig = currentNode.config
newDocument = JSON.stringify(schemaDocument) + ' - ' + UI.projects.education.spaces.docsSpace.language
if (newConfig === currentConfig && newDocument === currentDocument) { return }
currentConfig = newConfig
currentDocument = newDocument
let title
syncConfigIconWithDocumentIcon()
transformDocsInfoIntoTutorialInfo()
createTheHTML()
if (nodeConfig.controlDocs !== undefined) {
if (nodeConfig.controlDocs.closeTutorialEditor !== true) {
UI.projects.education.spaces.docsSpace.navigateTo(nodeConfig.docs.project, 'Tutorial', nodeConfig.docs.type)
}
if (nodeConfig.controlDocs.searchPage === true) {
UI.projects.education.spaces.docsSpace.searchPage()
}
} else {
UI.projects.education.spaces.docsSpace.navigateTo(nodeConfig.docs.project, 'Tutorial', nodeConfig.docs.type)
}
function syncConfigIconWithDocumentIcon() {
/*
We update here the image of the document with whatever we find at the config, since
we need to mechanism to keep this syncronized.
*/
if (nodeConfig.icon === undefined) {
nodeConfig.icon = {
name: nodeConfig.image, // This property comes from the migration
project: 'Foundations'
}
nodeConfig.image = undefined // Removing this legacy property.
currentNode.config = JSON.stringify(nodeConfig)
}
if (nodeConfig.icon.name !== undefined) {
if (schemaDocument.definition.icon !== undefined) {
if (nodeConfig.icon.name + '-' + nodeConfig.icon.project !== schemaDocument.definition.icon.name + '-' + schemaDocument.definition.icon.project) {
schemaDocument.definition.icon = {
name: nodeConfig.icon.name,
project: nodeConfig.icon.project
}
schemaDocument.updated = true
}
} else {
schemaDocument.definition.icon = {
name: nodeConfig.icon.name,
project: nodeConfig.icon.project
}
schemaDocument.updated = true
}
} else {
if (schemaDocument.definition.icon !== undefined) {
schemaDocument.definition.icon = undefined
schemaDocument.updated = true
}
}
}
function transformDocsInfoIntoTutorialInfo() {
if (schemaDocument === undefined) {
title = "Configuration Error"
schemaDocument = {
paragraphs: [
{
style: "Warning",
text: "The configured Docs reference does not point to any page. Correct that to see here the page contents."
}
]
}
return
}
let splittedType = nodeConfig.docs.type.split(' - ')
title = splittedType[1]
}
function createTheHTML() {
let fullscreenMode = false
let html = ''
/* Check for Full Screen Mode */
for (let i = 0; i < schemaDocument.paragraphs.length; i++) {
let paragraph = schemaDocument.paragraphs[i]
if (paragraph.style === 'Gif') {
fullscreenMode = true
}
}
if (schemaDocument.definition !== undefined && schemaDocument.definition.text !== '') {
if (schemaDocument.definition.text.indexOf('full screen mode') >= 0) {
fullscreenMode = true
}
}
if (fullscreenMode === false) {
html = html + '<h2 class="tutorial-font-medium">' + title + '</h2>'
}
html = html + '<div>'
if (schemaDocument.definition !== undefined && schemaDocument.definition.text !== '') {
if (fullscreenMode === false) {
if (schemaDocument.definition.icon === undefined || schemaDocument.definition.icon.name === '') {
/* When there is no image, we will render a Summary instead of a Table */
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(schemaDocument.definition)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small tutorial-summary">' + text + '</div>'
} else {
html = html + '<table class="tutorial-definitionTable">'
html = html + '<tr>'
html = html + '<td>'
if (schemaDocument.definition.icon !== undefined && schemaDocument.definition.icon.name !== '') {
html = html + '<div id="tutorialImageDiv" class="tutorial-image-container"/>'
newImageName = schemaDocument.definition.icon.name
newImageProject = schemaDocument.definition.icon.project
}
html = html + '</td>'
html = html + '<td>'
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(schemaDocument.definition)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<strong class="tutorial-font-bold-small">' + text + '</strong>'
html = html + '</td>'
html = html + '</tr>'
html = html + '</table>'
}
}
}
for (let i = 0; i < schemaDocument.paragraphs.length; i++) {
let paragraph = schemaDocument.paragraphs[i]
switch (paragraph.style) {
case 'Summary': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small tutorial-summary">' + text + '</div>'
break
}
case 'Title': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
html = html + '<h2 class="tutorial-font-medium">' + text + '</h2>'
break
}
case 'Gif': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
html = html + '<div id="tutorialGifDiv" width="200" width="290"/>'
newGifName = text
break
}
case 'Text': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small">' + text + '</div>'
break
}
case 'Json': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
html = html + '<pre><code class="language-json">' + text + '</code></pre>'
break
}
case 'Javascript': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
html = html + '<pre><code class="language-javascript">' + text + '</code></pre>'
break
}
case 'List': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addBold(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-list"><ul><li>' + text + '</li></ul></div>'
break
}
case 'Callout': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-bold-small tutorial-callout" > ' + text + '</div>'
break
}
case 'Link': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
let splittedText = text.split('->')
html = html + '<a class="tutorial-font-small tutorial-external-link" href="' + 'https://' + splittedText[1] + '" target="_blank">' + splittedText[0] + '</a>'
break
}
case 'Note': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small tutorial-alert-info" role="alert"><i class="tutorial-fa tutorial-info-circle"></i> <b>Note:</b> ' + text + '</div>'
break
}
case 'Success': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small tutorial-alert-success" role="alert"><i class="tutorial-fa tutorial-check-square-o"></i> <b>Tip:</b> ' + text + '</div>'
break
}
case 'Important': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small tutorial-alert-important" role="alert"><i class="tutorial-fa tutorial-warning"></i> <b>Important:</b> ' + text + '</div>'
break
}
case 'Warning': {
let text = UI.projects.education.utilities.docs.getTextBasedOnLanguage(paragraph)
text = UI.projects.education.utilities.docs.addKeyboard(text)
text = UI.projects.education.utilities.docs.addCodeToCamelCase(text)
text = UI.projects.education.utilities.docs.addCodeToWhiteList(text)
text = UI.projects.education.utilities.docs.addToolTips(text)
html = html + '<div class="tutorial-font-small tutorial-alert-warning" role="alert"><i class="tutorial-fa tutorial-warning"></i> <b>Warning:</b> ' + text + '</div>'
break
}
}
}
html = html + '</div>'
tutorialDiv.innerHTML = html
_self.Prism.highlightAllUnder(tutorialDiv, true)
}
}
function draw() {
if (isInitialized === false) { return }
}
function setupSidePanelTab() {
thisObject.sidePanelTab = UI.projects.foundations.spaces.sideSpace.createSidePanelTab(thisObject.container, 'Education', 'tutorial', 'Tutorial', 'left')
openingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('opening', onOpening)
closingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('closing', onClosing)
}
function onOpening() {
//console.log('onOpening');
let nodeResume = navigationStack.pop()
switch (nodeResume.type) {
case 'Tutorial': {
resumeTutorial(nodeResume)
break
}
case 'Tutorial Topic': {
resumeTutorialTopic(nodeResume)
break
}
case 'Tutorial Step': {
resumeTutorialStep(nodeResume)
break
}
}
}
function onClosing() {
//console.log('onClosing');
stop()
}
}