diff --git a/dist/plugins/iwsy.js b/dist/plugins/iwsy.js index 636c08a..a725f07 100644 --- a/dist/plugins/iwsy.js +++ b/dist/plugins/iwsy.js @@ -1,9 +1,9 @@ const EasyCoder_IWSY = { - name: `EasyCoder_IWSY`, + name: `EasyCoder_IWSY`, - iwsy: { - }, + iwsy: { + }, IWSY: { @@ -12,138 +12,148 @@ const EasyCoder_IWSY = { const lino = compiler.getLino(); const action = compiler.nextToken(); switch (action) { - case `load`: - if (compiler.nextIsSymbol()) { - const playerRecord = compiler.getSymbolRecord(); - if (playerRecord.keyword === `div`) { - const script = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - player: playerRecord.name, - script - }); - return true; - } - } - break; - case `init`: - case `stop`: - compiler.next(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action - }); - return true; - case `remove`: - if (compiler.nextTokenIs(`styles`)) { - compiler.next(); + case `load`: + if (compiler.nextIsSymbol()) { + const playerRecord = compiler.getSymbolRecord(); + if (playerRecord.keyword === `div`) { + const script = compiler.getNextValue(); compiler.addCommand({ domain: `iwsy`, keyword: `iwsy`, lino, - action: `removeStyles` + action, + player: playerRecord.name, + script }); return true; } - return false; - case `path`: - const path = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - path - }); - return true; - case `script`: - const script = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - script - }); - return true; - case `goto`: - const target = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - target - }); - return true; - case `block`: - const block = compiler.getNextValue(); + } + break; + case `init`: + case `stop`: + compiler.next(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action + }); + return true; + case `remove`: + if (compiler.nextTokenIs(`styles`)) { + compiler.next(); compiler.addCommand({ domain: `iwsy`, keyword: `iwsy`, lino, - action, - block + action: `removeStyles` }); return true; - case `run`: - const pc = compiler.getPc(); - let mode = `normal`; - let startMode = `wait`; - if (compiler.nextToken() === `fullscreen`) { - mode = compiler.getToken(); - if ([`auto`, `manual`].includes(compiler.nextToken())) { - startMode = compiler.getToken(); - compiler.next(); - } + } + return false; + case `path`: + const path = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + path + }); + return true; + case `script`: + const script = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + script + }); + return true; + case `goto`: + const target = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + target + }); + return true; + case `block`: + const block = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + block + }); + return true; + case `run`: + const pc = compiler.getPc(); + let mode = `normal`; + let startMode = `wait`; + if (compiler.nextToken() === `fullscreen`) { + mode = compiler.getToken(); + if ([`auto`, `manual`].includes(compiler.nextToken())) { + startMode = compiler.getToken(); + compiler.next(); } + } + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + mode, + startMode, + then: 0 + }); + // Get the 'then' code, if any + if (compiler.tokenIs(`then`)) { + const goto = compiler.getPc(); + // Add a 'goto' to skip the 'then' compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - mode, - startMode, - then: 0 + domain: `core`, + keyword: `goto`, + goto: 0 }); - // Get the 'then' code, if any - if (compiler.tokenIs(`then`)) { - const goto = compiler.getPc(); - // Add a 'goto' to skip the 'then' - compiler.addCommand({ - domain: `core`, - keyword: `goto`, - goto: 0 - }); - // Fixup the link to the 'then' branch - compiler.getCommandAt(pc).then = compiler.getPc(); - // Process the 'then' branch - compiler.next(); - compiler.compileOne(true); - compiler.addCommand({ - domain: `core`, - keyword: `stop` - }); - // Fixup the 'goto' - compiler.getCommandAt(goto).goto = compiler.getPc(); - } - return true; - case `onstep`: + // Fixup the link to the 'then' branch + compiler.getCommandAt(pc).then = compiler.getPc(); + // Process the 'then' branch compiler.next(); + compiler.compileOne(true); compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action + domain: `core`, + keyword: `stop` }); - return compiler.completeHandler(); - default: - break; + // Fixup the 'goto' + compiler.getCommandAt(goto).goto = compiler.getPc(); + } + return true; + case `onstep`: + compiler.next(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action + }); + return compiler.completeHandler(); + case `panzoom`: + const spec = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + spec + }); + return true; + default: + break; } return false; }, @@ -153,83 +163,88 @@ const EasyCoder_IWSY = { const action = command.action; let script; switch (action) { - case `init`: - program.require(`js`, `iwsy.js`, + case `init`: + program.require(`js`, `iwsy.js`, function () { program.run(command.pc + 1); }); + return 0; + case `load`: + // if (program.iwsyFunctions) { + // throw Error(`IWSY has already been set up`); + // } + const playerRecord = program.getSymbolRecord(command.player); + const player = playerRecord.element[playerRecord.index]; + player.innerHTML = ``; + player.style.background = `none`; + player.style.border = `none`; + script = program.getValue(command.script); + try { + script = JSON.parse(script); + } catch (err) { + alert(`iwsy load: Badly formatted script`); + } + program.iwsyFunctions = IWSY(player, script); + break; + case `path`: + if (program.iwsyFunctions) { + program.iwsyFunctions.setPath(program.getValue(command.path)); + } + break; + case `script`: + script = program.getValue(command.script); + try { + script = JSON.parse(script); + } catch (err) { + alert(`iwsy script: Badly formatted script`); + } + if (program.iwsyFunctions) { + program.iwsyFunctions.setScript(script); + } + break; + case `goto`: + if (program.iwsyFunctions) { + program.iwsyFunctions.gotoStep(program.getValue(command.target)); + } + break; + case `block`: + if (program.iwsyFunctions) { + program.iwsyFunctions.block(program.getValue(command.block)); + } + break; + case `run`: + if (program.iwsyFunctions) { + program.iwsyFunctions.run(command.mode, command.startMode, () => { + program.run(command.then); + }); return 0; - case `load`: - if (program.iwsyFunctions) { - throw Error(`IWSY has already been set up`); - } - const playerRecord = program.getSymbolRecord(command.player); - const player = playerRecord.element[playerRecord.index]; - player.innerHTML = ``; - player.style.background = `none`; - player.style.border = `none`; - script = program.getValue(command.script); - try { - script = JSON.parse(script); - } catch (err) { - alert(`iwsy load: Badly formatted script`); - } - program.iwsyFunctions = IWSY(player, script); - break; - case `path`: - if (program.iwsyFunctions) { - program.iwsyFunctions.setPath(program.getValue(command.path)); - } - break; - case `script`: - script = program.getValue(command.script); - try { - script = JSON.parse(script); - } catch (err) { - alert(`iwsy script: Badly formatted script`); - } - if (program.iwsyFunctions) { - program.iwsyFunctions.setScript(script); - } - break; - case `goto`: - if (program.iwsyFunctions) { - program.iwsyFunctions.gotoStep(program.getValue(command.target)); - } - break; - case `block`: - if (program.iwsyFunctions) { - program.iwsyFunctions.block(program.getValue(command.block)); - } - break; - case `run`: - if (program.iwsyFunctions) { - program.iwsyFunctions.run(command.mode, command.startMode, () => { - program.run(command.then); - }); - return 0; - } - break; - case `stop`: - if (program.iwsyFunctions) { - program.iwsyFunctions.stop(); - delete program.iwsyFunctions; - } - break; - case `removeStyles`: - if (program.iwsyFunctions) { - program.iwsyFunctions.removeStyles(); - } - break; - case `onstep`: - const cb = command.pc + 2; - if (program.iwsyFunctions) { - program.iwsyFunctions.onStep(function(step) { - program.iwsyStep = step; - program.run(cb); - }); - } - break; + } + break; + case `stop`: + if (program.iwsyFunctions) { + program.iwsyFunctions.stop(); + // delete program.iwsyFunctions; + } + break; + case `removeStyles`: + if (program.iwsyFunctions) { + program.iwsyFunctions.removeStyles(); + } + break; + case `onstep`: + const cb = command.pc + 2; + if (program.iwsyFunctions) { + program.iwsyFunctions.onStep(function(step) { + program.iwsyStep = step; + program.run(cb); + }); + } + break; + case `panzoom`: + const spec = program.getValue(command.spec); + if (program.iwsyFunctions) { + program.iwsyFunctions.panzoom(spec); + } } return command.pc + 1; } @@ -237,10 +252,10 @@ const EasyCoder_IWSY = { getHandler: (name) => { switch (name) { - case `iwsy`: - return EasyCoder_IWSY.IWSY; - default: - return null; + case `iwsy`: + return EasyCoder_IWSY.IWSY; + default: + return null; } }, @@ -281,7 +296,7 @@ const EasyCoder_IWSY = { type: `constant`, numeric: false, content: JSON.stringify(script) - } + }; } break; case `step`: diff --git a/iwsy/iwsy.js b/iwsy/iwsy.js index 3c14873..f892de3 100644 --- a/iwsy/iwsy.js +++ b/iwsy/iwsy.js @@ -1,675 +1,793 @@ -// IWSY - +// eslint-disable-next-line no-unused-vars const IWSY = (playerElement, scriptObject) => { - let player = playerElement; - let script = scriptObject; - let homeScript; - let afterRun; - let path; + let player = playerElement; + let script = scriptObject; + let homeScript = script; + let afterRun; + let path; + let plugins; + const timeouts = []; + const intervals = []; - // Set up all the blocks - const setupBlocks = () => { - for (const block of script.blocks) { - const current = {}; - for (const name in block.defaults) { - current[name] = block.defaults[name]; - } - block.current = current; - } - }; + // Set up all the blocks + const setupBlocks = () => { + for (const block of script.blocks) { + const current = {}; + for (const name in block.defaults) { + current[name] = block.defaults[name]; + } + block.current = current; + } + }; - const pause = step => { - if (script.speed === `scan`) { - step.next(); - return; - } - setTimeout(() => { - step.next(); - }, script.runMode === `manual` ? 0 : step.duration * 1000); - }; + const pause = step => { + if (script.speed === `scan`) { + step.next(); + return; + } + addTimeoutTimer(setTimeout(() => { + step.next(); + }, script.runMode === `manual` ? 0 : step.duration * 1000)); + }; - // Get the bounding rectangle of a block - const getBlockRect = (block, r) => { - const left = block.defaults.left; - const top = block.defaults.top; - const width = block.defaults.width; - const height = block.defaults.height; - if (isNaN(left) || isNaN(top) || isNaN(width) || isNaN(height)) { - return rect; - } - const w = r.width / 1000; - const h = r.height / 1000; - const rect = {}; - rect.width = width * w; - rect.height = height * h; - rect.left = left * w; - rect.top = top * h; - return rect; - }; + // Get the bounding rectangle of a block + const getBlockRect = (block, r) => { + const left = block.defaults.left; + const top = block.defaults.top; + const width = block.defaults.width; + const height = block.defaults.height; + if (isNaN(left) || isNaN(top) || isNaN(width) || isNaN(height)) { + return rect; + } + const w = r.width / 1000; + const h = r.height / 1000; + const rect = {}; + rect.width = width * w; + rect.height = height * h; + rect.left = left * w; + rect.top = top * h; + return rect; + }; - // Create a block - const createBlock = block => { - let rect = { - width: player.clientWidth, - height: player.clientHeight, - left: 0, - top: 0 - } - if (block.defaults.parent) { - for (b of script.blocks) { - if (b.defaults.name === block.defaults.parent) { - rect = getBlockRect(b, rect); - break; - } - } - }; - const w = rect.width / 1000; - const h = rect.height / 1000; - const l = rect.left; - const t = rect.top; - const defaults = block.defaults; - const element = document.createElement(`div`); - player.appendChild(element); - block.element = element; - if (script.speed === `scan`) { - element.style.display = `none`; - } - element.style.position = `absolute`; - element.style.opacity = `0.0`; - let val = defaults.left; - if (isNaN(val)) { - element.style.left = val; - } else { - val *= w; - element.style.left = `calc(${l}px + ${val}px)`; - } - val = defaults.top; - if (isNaN(val)) { - element.style.left = val; - } else { - val *= h; - element.style.top = `calc(${t}px + ${val}px)`; - } - val = defaults.width; - if (!isNaN(val)) { - val = `${val * w}px`; - } - element.style.width = val; - val = defaults.height; - if (!isNaN(val)) { - val = `${val * h}px`; - } - element.style.height = val; - if (defaults.background) { - element.style.background = defaults.background; - } - element.style[`background-size`] = `cover`; - element.style.border = defaults.border; - element.style[`overflow`] = `hidden`; - element.style[`display`] = `none`; - element.style[`opacity`] = `0`; - val = defaults.textMarginLeft; - if (!isNaN(val)) { - val *= w; - } - const marginLeft = val; - val = defaults.textMarginTop; - if (!isNaN(val)) { - val *= h; - } - const marginTop = val; - const text = document.createElement(`div`); - element.appendChild(text); - block.textPanel = text; - text.style.position = `absolute`; - text.style.left = marginLeft; - text.style.top = marginTop; - text.style.width = `calc(100% - ${marginLeft}px - ${marginLeft}px)`; - text.style.height = `calc(100% - ${marginTop}px - ${marginTop}px)`; - text.style[`font-family`] = defaults.fontFamily; - val = defaults.fontSize; - if (!isNaN(val)) { - val *= h; - } - text.style[`font-size`] = `${val}px`; - text.style[`font-weight`] = defaults.fontWeight; - text.style[`font-style`] = defaults.fontStyle; - text.style.color = defaults.fontColor; - text.style[`text-align`] = defaults.textAlign; - }; + // Create a block + const createBlock = block => { + let rect = { + width: player.clientWidth, + height: player.clientHeight, + left: 0, + top: 0 + }; + if (block.defaults.parent) { + for (b of script.blocks) { + if (b.defaults.name === block.defaults.parent) { + rect = getBlockRect(b, rect); + break; + } + } + } + const w = rect.width / 1000; + const h = rect.height / 1000; + const l = rect.left; + const t = rect.top; + const defaults = block.defaults; + const element = document.createElement(`div`); + player.appendChild(element); + block.element = element; + if (script.speed === `scan`) { + element.style.display = `none`; + } + element.style.position = `absolute`; + element.style.opacity = `0.0`; + let val = defaults.left; + if (isNaN(val)) { + element.style.left = val; + } else { + val *= w; + element.style.left = `calc(${l}px + ${val}px)`; + } + val = defaults.top; + if (isNaN(val)) { + element.style.left = val; + } else { + val *= h; + element.style.top = `calc(${t}px + ${val}px)`; + } + val = defaults.width; + if (!isNaN(val)) { + val = `${val * w}px`; + } + element.style.width = val; + val = defaults.height; + if (!isNaN(val)) { + val = `${val * h}px`; + } + element.style.height = val; + if (defaults.background) { + element.style.background = defaults.background; + } + element.style[`background-size`] = `cover`; + element.style.border = defaults.border; + element.style[`overflow`] = `hidden`; + element.style[`display`] = `none`; + element.style[`opacity`] = `0`; + val = defaults.textMarginLeft; + if (!isNaN(val)) { + val *= w; + } + const marginLeft = val; + val = defaults.textMarginTop; + if (!isNaN(val)) { + val *= h; + } + const marginTop = val; + const text = document.createElement(`div`); + element.appendChild(text); + block.textPanel = text; + text.style.position = `absolute`; + text.style.left = marginLeft; + text.style.top = marginTop; + text.style.width = `calc(100% - ${marginLeft}px - ${marginLeft}px)`; + text.style.height = `calc(100% - ${marginTop}px - ${marginTop}px)`; + text.style[`font-family`] = defaults.fontFamily; + val = defaults.fontSize; + if (!isNaN(val)) { + val *= h; + } + text.style[`font-size`] = `${val}px`; + text.style[`font-weight`] = defaults.fontWeight; + text.style[`font-style`] = defaults.fontStyle; + text.style.color = defaults.fontColor; + text.style[`text-align`] = defaults.textAlign; + }; - // Set the content of one or more blocks - const setcontent = step => { - for (const item of step.blocks) - { - for (const block of script.blocks) { - if (block.defaults.name === item.block) { - if (!block.element) { - createBlock(block); - } - for (const text of script.content) { - if (text.name === item.content) { - const converter = new showdown.Converter({ - extensions: [`IWSY`] - }); - block.textPanel.innerHTML = + // Set the content of one or more blocks + const setcontent = step => { + for (const item of step.blocks) + { + for (const block of script.blocks) { + if (block.defaults.name === item.block) { + if (!block.element) { + createBlock(block); + } + for (const text of script.content) { + if (text.name === item.content) { + const converter = new showdown.Converter({ + extensions: [`IWSY`] + }); + block.textPanel.innerHTML = converter.makeHtml(text.content.split(`%0a`).join(`\n`)); - break; - } - } - break; - } - } - } - step.next(); - }; + break; + } + } + const vfxElements = block.textPanel.getElementsByClassName(`iwsy-vfx`); + // Save all the vfx items in this step + for (const vfxElement of vfxElements) { + script.vfxElements.push({ + block, + vfxElement + }); + } + break; + } + } + } + step.next(); + }; - // Show or hide a block - const doShowHide = (step, showHide) => { - for (const name of step.blocks) - { - for (const block of script.blocks) { - if (block.defaults.name === name) { - block.element.style.opacity = showHide ? `1.0` : `0.0`; - block.element.style.display = showHide ? `block` : `none`; - break; - } - } - } - if (script.runMode === `manual`) { - enterManualMode(step); - } else { - step.next(); - } - }; + // Set the visibility of a block + const setVisibility = (block, showHide) => { + if (showHide) { + block.element.style.opacity = `1.0`; + block.element.style.display = `block`; + } else { + block.element.style.opacity = `0.0`; + block.element.style.display = `none`; + } + }; - const show = step => { - doShowHide(step, true); - }; + // Show or hide a block + const doShowHide = (step, showHide) => { + for (const name of step.blocks) + { + for (const block of script.blocks) { + if (block.defaults.name === name) { + if (!block.element) { + createBlock(block); + } + setVisibility(block, showHide); + break; + } + } + } + if (script.runMode === `manual`) { + enterManualMode(step); + } else { + step.next(); + } + }; + + const show = step => { + doShowHide(step, true); + }; + + const hide = step => { + doShowHide(step, false); + }; + + addIntervalTimer = (interval) => { + intervals.push(interval); + }; + + clearIntervalTimer = interval => { + clearInterval(interval); + const pos = intervals.indexOf(interval); + intervals.splice(pos, 1); + }; + + addTimeoutTimer = (interval) => { + intervals.push(interval); + }; + + clearAllTimers = () => { + while (intervals.length) { + clearInterval(intervals[0]); + intervals.splice(0, 1); + } + while (timeouts.length) { + clearTimeout(timeouts[0]); + timeouts.splice(0, 1); + } + }; - const hide = step => { - doShowHide(step, false); - }; + // Fade up or down + const doFade = (step, upDown) => { + const stepBlocks = []; + for (const name of step.blocks) { + script.blocks.every((block) => { + if (block.defaults.name === name) { + stepBlocks.push(block); + if (!block.element) { + createBlock(block); + } + return false; + } + return true; + }); + } + if (script.speed === `scan`) { + for (const block of stepBlocks) + { + setVisibility(step, block, showHide); + } + step.next(); + } else { + const animSteps = Math.round(step.duration * 25); + const continueFlag = step.continue; + for (const block of stepBlocks) + { + block.element.style.display = `block`; + } + let animStep = 0; + const interval = setInterval(() => { + try { + if (animStep < animSteps) { + const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; + for (const block of stepBlocks) + { + // if (block.element) { + block.element.style.opacity = upDown ? ratio : 1.0 - ratio; + // } + } + animStep++; + } else { + for (const block of stepBlocks) + { + // if (block.element) { + block.element.style.opacity = upDown ? 1 : 0; + block.element.style.display = upDown ? `block` :`none`; + // } + } + clearIntervalTimer(interval); + if (!continueFlag) { + if (script.runMode === `manual`) { + enterManualMode(step); + } else { + step.next(); + } + } + } + } catch(err) { + clearIntervalTimer(interval); + throw Error(err); + } + }, 40); + addIntervalTimer(interval); + if (continueFlag) { + step.next(); + } + } + }; - // Fade up or down - const doFade = (step, upDown) => { - const stepBlocks = []; - for (const name of step.blocks) { - script.blocks.every((block, index) => { - if (block.defaults.name === name) { - stepBlocks.push(block); - if (!block.element) { - createBlock(block); - } - return false; - } - return true; - }); - } - if (script.speed === `scan`) { - for (const block of stepBlocks) - { - block.element.style.opacity = upDown ? 1.0 : 0.0; - block.element.style.display = upDown ? `block` : `none`; - } - step.next(); - } else { - const animSteps = Math.round(step.duration * 25); - const continueFlag = step.continue; - for (const block of stepBlocks) - { - block.element.style.display = `block`; - } - let animStep = 0; - const interval = setInterval(() => { - try { - if (animStep < animSteps) { - const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; - for (const block of stepBlocks) - { - if (block.element) { - block.element.style.opacity = upDown ? ratio : 1.0 - ratio; - } - } - animStep++; - } else { - for (const block of stepBlocks) - { - if (block.element) { - block.element.style.opacity = upDown ? 1 : 0; - block.element.style.display = upDown ? `block` :`none`; - } - } - clearInterval(interval); - if (!continueFlag) { - if (script.runMode === `manual`) { - enterManualMode(step); - } else { - step.next(); - } - } - } - } catch(err) { - clearInterval(interval); - throw Error(err); - } - }, 40); - if (continueFlag) { - step.next(); - } - } - }; + const fadeup = step => { + doFade(step, true); + }; - const fadeup = step => { - doFade(step, true); - }; + const fadedown = step => { + doFade(step, false); + }; - const fadedown = step => { - doFade(step, false); - }; + // This is where the vfx animations are started + const startVFX = (step, vfxElement) => { + vfxElement.style.position = `relative`; + vfxElement.style.display = `inline-block`; + const slide = vfxElement.dataset.slide; + const vfx = script.vfx; + for (const item of vfx) { + if (item.name === slide) { + step.vfxRunning.push(vfxElement); + doPanzoom(vfxElement, item, () => { + if (step.continue !== `yes`) { + const index = step.vfxRunning.indexOf(vfxElement); + if (index > -1) { + step.vfxRunning.splice(index, 1); + } + if (step.vfxRunning.length === 0) { + step.next(); + } + } + }); + } + } + }; - // Handle a crossfade - const crossfade = step => { - for (const content of script.content) { - if (content.name === step.target) { - const converter = new showdown.Converter({ - extensions: [`IWSY`] - }); - const newText = converter.makeHtml(content.content.split(`%0a`).join(`\n`)); - for (const block of script.blocks) { - if (block.defaults.name === step.block) { - if (script.speed === `scan`) { - block.textPanel.innerHTML = newText; - step.next(); - } else { - const element = document.createElement(`div`); - player.appendChild(element); - element.style.position = `absolute`; - element.style.opacity = `0.0`; - element.style.left = block.element.style.left; - element.style.top = block.element.style.top; - element.style.width = block.element.style.width; - element.style.height = block.element.style.height; - if (block.element.style.background) { - element.style.background = block.element.style.background; - } - element.style.border = block.element.style.border - element.style[`border-radius`] = block.element.style[`border-radius`] - const text = document.createElement(`div`); - element.appendChild(text); - text.style.position = `absolute`; - text.style.left = block.textPanel.style.left; - text.style.top = block.textPanel.style.top; - text.style.width = block.textPanel.style.width; - text.style[`font-family`] = block.textPanel.style[`font-family`]; - text.style[`font-size`] = block.textPanel.style[`font-size`]; - text.style[`font-weight`] = block.textPanel.style[`font-weight`]; - text.style[`font-style`] = block.textPanel.style[`font-style`]; - text.style[`text-align`] = block.textPanel.style[`text-align`]; - text.style.color = block.textPanel.style.color; - text.innerHTML = newText; + // Animate blocks + const animate = step => { + const continueFlag = step.continue === `yes`; + for (const name of step.blocks) + { + for (const block of script.blocks) { + if (block.defaults.name === name) { + if (!block.element) { + createBlock(block); + setVisibility(block, true); + } + for (const item of script.vfxElements) { + if (item.block === block) { + startVFX(step, item.vfxElement); + } + } + break; + } + } + } + if (script.runMode === `manual`) { + enterManualMode(step); + } else if (continueFlag || step.vfxRunning.length === 0) { + step.next(); + } + }; + + // Handle a crossfade + const crossfade = step => { + for (const content of script.content) { + if (content.name === step.target) { + const converter = new showdown.Converter({ + extensions: [`IWSY`] + }); + const newText = converter.makeHtml(content.content.split(`%0a`).join(`\n`)); + for (const block of script.blocks) { + if (block.defaults.name === step.block) { + if (script.speed === `scan`) { + block.textPanel.innerHTML = newText; + step.next(); + } else { + const element = document.createElement(`div`); + player.appendChild(element); + element.style.position = `absolute`; + element.style.opacity = `0.0`; + element.style.left = block.element.style.left; + element.style.top = block.element.style.top; + element.style.width = block.element.style.width; + element.style.height = block.element.style.height; + if (block.element.style.background) { + element.style.background = block.element.style.background; + } + element.style.border = block.element.style.border; + element.style[`border-radius`] = block.element.style[`border-radius`]; + const text = document.createElement(`div`); + element.appendChild(text); + text.style.position = `absolute`; + text.style.left = block.textPanel.style.left; + text.style.top = block.textPanel.style.top; + text.style.width = block.textPanel.style.width; + text.style[`font-family`] = block.textPanel.style[`font-family`]; + text.style[`font-size`] = block.textPanel.style[`font-size`]; + text.style[`font-weight`] = block.textPanel.style[`font-weight`]; + text.style[`font-style`] = block.textPanel.style[`font-style`]; + text.style[`text-align`] = block.textPanel.style[`text-align`]; + text.style.color = block.textPanel.style.color; + text.innerHTML = newText; - const animSteps = Math.round(step.duration * 25); - const continueFlag = step.continue; - let animStep = 0; - const interval = setInterval(() => { - if (animStep < animSteps) { - const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; - block.element.style.opacity = 1.0 - ratio; - element.style.opacity = ratio; - animStep++; - } else { - clearInterval(interval); - block.textPanel.innerHTML = newText; - if (content.url) { - block.element.style.background = `url("${content.url}")`; - } - block.element.style[`background-size`] = `cover`; - block.element.style.opacity = 1.0 ; - removeElement(element); - if (!continueFlag) { - if (script.runMode === `manual`) { - enterManualMode(step); - } else { - step.next(); - } - } - } - }, 40); - if (continueFlag) { - step.next(); - } - } - break; - } - } - break; - } - } - }; + const animSteps = Math.round(step.duration * 25); + const continueFlag = step.continue === `yes`; + let animStep = 0; + const interval = setInterval(() => { + if (animStep < animSteps) { + const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; + block.element.style.opacity = 1.0 - ratio; + element.style.opacity = ratio; + animStep++; + } else { + clearIntervalTimer(interval); + block.textPanel.innerHTML = newText; + if (content.url) { + block.element.style.background = `url("${content.url}")`; + } + block.element.style[`background-size`] = `cover`; + block.element.style.opacity = 1.0 ; + removeElement(element); + if (!continueFlag) { + if (script.runMode === `manual`) { + enterManualMode(step); + } else { + step.next(); + } + } + } + }, 40); + addIntervalTimer(interval); + if (continueFlag) { + step.next(); + } + } + break; + } + } + break; + } + } + }; - // Compute a block size - const setComputedBlockSize = (block, target, ratio) => { - const boundingRect = player.getBoundingClientRect(); - const w = boundingRect.width / 1000; - const h = boundingRect.height / 1000; - let width = block.current.width; - if (!isNaN(width)) { - width *= w; - } - let height = block.current.height; - if (!isNaN(height)) { - height *= h; - } - let endWidth = target.defaults.width; - if (!isNaN(endWidth)) { - endWidth *= w; - } - let endHeight = target.defaults.height; - if (!isNaN(endHeight)) { - endHeight *= h; - } - block.element.style.width = + // Compute a block size + const setComputedBlockSize = (block, target, ratio) => { + const boundingRect = player.getBoundingClientRect(); + const w = boundingRect.width / 1000; + const h = boundingRect.height / 1000; + let width = block.current.width; + if (!isNaN(width)) { + width *= w; + } + let height = block.current.height; + if (!isNaN(height)) { + height *= h; + } + let endWidth = target.defaults.width; + if (!isNaN(endWidth)) { + endWidth *= w; + } + let endHeight = target.defaults.height; + if (!isNaN(endHeight)) { + endHeight *= h; + } + block.element.style.width = `${width + (endWidth - width) * ratio}px`; - block.element.style.height = + block.element.style.height = `${height + (endHeight - height) * ratio}px`; - }; + }; - // Compute a block position - const setComputedBlockPosition = (block, target, ratio) => { - const boundingRect = player.getBoundingClientRect(); - const w = boundingRect.width / 1000; - const h = boundingRect.height / 1000; - let left = block.current.left; - if (!isNaN(left)) { - left *= w; - } - let top = block.current.top; - if (!isNaN(top)) { - top *= h; - } - let endLeft = target.defaults.left; - if (!isNaN(endLeft)) { - endLeft *= w; - } - let endTop = target.defaults.top; - if (!isNaN(endTop)) { - endTop *= h; - } - block.element.style.left = left + (endLeft - left) * ratio; - block.element.style.top = top + (endTop - top) * ratio; - }; + // Compute a block position + const setComputedBlockPosition = (block, target, ratio) => { + const boundingRect = player.getBoundingClientRect(); + const w = boundingRect.width / 1000; + const h = boundingRect.height / 1000; + let left = block.current.left; + if (!isNaN(left)) { + left *= w; + } + let top = block.current.top; + if (!isNaN(top)) { + top *= h; + } + let endLeft = target.defaults.left; + if (!isNaN(endLeft)) { + endLeft *= w; + } + let endTop = target.defaults.top; + if (!isNaN(endTop)) { + endTop *= h; + } + block.element.style.left = left + (endLeft - left) * ratio; + block.element.style.top = top + (endTop - top) * ratio; + }; - // Compute a font size - const setComputedFontSize = (block, target, ratio) => { - const h = Math.round(player.getBoundingClientRect().height) / 1000; - let size = block.current.fontSize; - if (!isNaN(size)) { - size *= h; - } - let endSize = target.defaults.fontSize; - if (!isNaN(endSize)) { - endSize *= h; - } - block.textPanel.style[`font-size`] = + // Compute a font size + const setComputedFontSize = (block, target, ratio) => { + const h = Math.round(player.getBoundingClientRect().height) / 1000; + let size = block.current.fontSize; + if (!isNaN(size)) { + size *= h; + } + let endSize = target.defaults.fontSize; + if (!isNaN(endSize)) { + endSize *= h; + } + block.textPanel.style[`font-size`] = `${size + (endSize - size) * ratio}px`; - }; + }; - // Compute a font color - const setComputedFontColor = (block, target, ratio) => { - const color = block.current.fontColor; - const endColor = target.defaults.fontColor; - const rStart = parseInt(color.slice(1, 3), 16); - const gStart = parseInt(color.slice(3, 5), 16); - const bStart = parseInt(color.slice(5, 7), 16); - const rFinish = parseInt(endColor.slice(1, 3), 16); - const gFinish = parseInt(endColor.slice(3, 5), 16); - const bFinish = parseInt(endColor.slice(5, 7), 16); - const red = rStart + Math.round((rFinish - rStart) * ratio); - const green = gStart + Math.round((gFinish - gStart) * ratio); - const blue = bStart + Math.round((bFinish - bStart) * ratio); - const r = ("0" + red.toString(16)).slice(-2); - const g = ("0" + green.toString(16)).slice(-2); - const b = ("0" + blue.toString(16)).slice(-2); - block.textPanel.style.color = `#${r}${g}${b}`; - }; + // Compute a font color + const setComputedFontColor = (block, target, ratio) => { + const color = block.current.fontColor; + const endColor = target.defaults.fontColor; + const rStart = parseInt(color.slice(1, 3), 16); + const gStart = parseInt(color.slice(3, 5), 16); + const bStart = parseInt(color.slice(5, 7), 16); + const rFinish = parseInt(endColor.slice(1, 3), 16); + const gFinish = parseInt(endColor.slice(3, 5), 16); + const bFinish = parseInt(endColor.slice(5, 7), 16); + const red = rStart + Math.round((rFinish - rStart) * ratio); + const green = gStart + Math.round((gFinish - gStart) * ratio); + const blue = bStart + Math.round((bFinish - bStart) * ratio); + const r = (`0` + red.toString(16)).slice(-2); + const g = (`0` + green.toString(16)).slice(-2); + const b = (`0` + blue.toString(16)).slice(-2); + block.textPanel.style.color = `#${r}${g}${b}`; + }; - // Handle a single step of a transition - const doTransitionStep = (block, target, ratio) => { - setComputedBlockSize(block, target, ratio); - setComputedBlockPosition(block, target, ratio); - setComputedFontSize(block, target, ratio); - setComputedFontColor(block, target, ratio); - }; + // Handle a single step of a transition + const doTransitionStep = (block, target, ratio) => { + setComputedBlockSize(block, target, ratio); + setComputedBlockPosition(block, target, ratio); + setComputedFontSize(block, target, ratio); + setComputedFontColor(block, target, ratio); + }; - // Set the final state of a transition - const setFinalState = (block, target) => - { - block.current.width = target.defaults.width; - block.current.height = target.defaults.height; - block.current.left = target.defaults.left; - block.current.top = target.defaults.top; - block.current.fontColor = target.defaults.fontColor; - block.current.fontSize = target.defaults.fontSize; - }; + // Set the final state of a transition + const setFinalState = (block, target) => + { + block.current.width = target.defaults.width; + block.current.height = target.defaults.height; + block.current.left = target.defaults.left; + block.current.top = target.defaults.top; + block.current.fontColor = target.defaults.fontColor; + block.current.fontSize = target.defaults.fontSize; + }; - // Handle a transition - const transition = step => { - let block = null; - let target = null; - script.blocks.every(item => { - if (item.defaults.name === step.block) { - block = item; - } - if (item.defaults.name === step.target) { - target = item; - } - return true; - }); - if (typeof block.element === `undefined`) { - throw Error(`Block '${block.defaults.name}' has not been set up`); - } - block.element.style.opacity = 1; - block.element.style.display = `block`; - if (script.speed === `scan`) { - doTransitionStep(block, target, 1.0); - setFinalState(block,target); - step.next(); - } else { - const animSteps = Math.round(step.duration * 25); - let animStep = 0; - const continueFlag = step.continue; - const interval = setInterval(() => { - if (animStep < animSteps) { - const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; - try { - doTransitionStep(block, target, ratio); - } catch (err) { - clearInterval(interval); - } - animStep++; - } else { - clearInterval(interval); - setFinalState(block,target); - if (!continueFlag) { - if (script.runMode === `manual`) { - enterManualMode(step); - } else { - step.next(); - } - } - } - }, 40); - if (continueFlag) { - step.next(); - } - } - }; + // Handle a transition + const transition = step => { + let block = null; + let target = null; + script.blocks.every(item => { + if (item.defaults.name === step.block) { + block = item; + } + if (item.defaults.name === step.target) { + target = item; + } + return true; + }); + if (typeof block.element === `undefined`) { + throw Error(`Block '${block.defaults.name}' has not been set up`); + } + block.element.style.opacity = 1; + block.element.style.display = `block`; + if (script.speed === `scan`) { + doTransitionStep(block, target, 1.0); + setFinalState(block,target); + step.next(); + } else { + const animSteps = Math.round(step.duration * 25); + let animStep = 0; + const continueFlag = step.continue; + const interval = setInterval(() => { + if (animStep < animSteps) { + const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; + try { + doTransitionStep(block, target, ratio); + } catch (err) { + clearIntervalTimer(interval); + } + animStep++; + } else { + clearIntervalTimer(interval); + setFinalState(block,target); + if (!continueFlag) { + if (script.runMode === `manual`) { + enterManualMode(step); + } else { + step.next(); + } + } + } + }, 40); + addIntervalTimer(interval); + if (continueFlag) { + step.next(); + } + } + }; - // Remove all the blocks from the player - const removeBlocks = () => { - if (Array.isArray(script.blocks)) { - for (const block of script.blocks) { - if (block.element) { - removeElement(block.element); - delete(block.element); - } - } - } - }; + // Remove all the blocks from the player + const removeBlocks = () => { + if (Array.isArray(script.blocks)) { + for (const block of script.blocks) { + if (block.element) { + removeElement(block.element); + delete(block.element); + } + } + } + }; - // Remove an element - const removeElement = element => { - const parent = element.parentElement; - if (parent) { - parent.removeChild(element); - } - element.remove(); - }; + // Remove an element + const removeElement = element => { + const parent = element.parentElement; + if (parent) { + parent.removeChild(element); + } + }; - // Load a plugin action - const load = step => { - if (script.speed === `scan`) { - step.next(); - } else { - const element = document.createElement(`script`); - element.src = step.url; - element.onload = () => { - console.log(`Plugin ${element.src} loaded`); - step.next(); - }; - element.onerror = () => { - throw Error(`Can't load plugin ${step.url}`); - }; - document.head.appendChild(element); - } - }; + // Load a plugin action + const load = step => { + if (script.speed === `scan`) { + step.next(); + } else { + const element = document.createElement(`script`); + element.src = step.url; + element.onload = () => { + console.log(`Plugin ${element.src} loaded`); + step.next(); + }; + element.onerror = () => { + throw Error(`Can't load plugin ${step.url}`); + }; + document.head.appendChild(element); + } + }; - // Initialize the presentation - const init = step => { - if (step.title) { - document.title = step.title; - } - if (step.css) { - setHeadStyle(step.css.split(`%0a`).join(`\n`)); - } - const aspect = step[`aspect ratio`]; - if (aspect) { - const colon = aspect.indexOf(`:`); - if (colon > 0) { - const aspectW = aspect.substr(0, colon); - const aspectH = aspect.substr(colon + 1); - const height = Math.round(parseFloat(player.offsetWidth) * aspectH / aspectW); - player.style.height = `${Math.round(height)}px`; - } - player.style.position = `relative`; - player.style.overflow = `hidden`; - player.style.cursor = `none`; - player.style.border = step.border; - if (step.background) { - player.style.background = step.background.split(`"`).join(`"`); - } - player.style[`background-size`] = `cover`; - } - step.next(); - }; + // Initialize the presentation + const init = step => { + if (step.title) { + document.title = step.title; + } + if (step.css) { + setHeadStyle(step.css.split(`%0a`).join(`\n`)); + } + const aspect = step[`aspect ratio`]; + if (aspect) { + const colon = aspect.indexOf(`:`); + if (colon > 0) { + const aspectW = aspect.substr(0, colon); + const aspectH = aspect.substr(colon + 1); + const height = Math.round(parseFloat(player.offsetWidth) * aspectH / aspectW); + player.style.height = `${Math.round(height)}px`; + } + player.style.position = `relative`; + player.style.overflow = `hidden`; + player.style.cursor = `none`; + player.style.border = step.border; + if (step.background) { + player.style.background = step.background.split(`"`).join(`"`); + } + player.style[`background-size`] = `cover`; + } + step.next(); + }; - // Scan the script - const scan = () => { - script.speed = `scan`; - removeBlocks(); - setupBlocks(); - player.innerHTML = ``; - doStep(script.steps[0]); - }; + // Scan the script + const scan = () => { + script.speed = `scan`; + removeBlocks(); + setupBlocks(); + player.innerHTML = ``; + doStep(script.steps[0]); + }; - // Go to a specified label - const goto = step => { - const target = script.labels[step.target]; - if (typeof target !== `undefined`) { - script.scanTarget = target; - scan(); - } else { - throw Error(`Unknown label '${step.target}`); - } - }; + // Go to a specified label + const goto = step => { + const target = script.labels[step.target]; + if (typeof target !== `undefined`) { + script.scanTarget = target; + scan(); + } else { + throw Error(`Unknown label '${step.target}`); + } + }; - // Restore the cursor - const restoreCursor = () => { - player.style.cursor = `pointer`; - script = homeScript; - if (afterRun) { - afterRun(); - } - }; + // Restore the cursor + const restoreCursor = () => { + player.style.cursor = `pointer`; + script = homeScript; + if (afterRun) { + afterRun(); + } + }; - // Set up Showdown - const setupShowdown = () => { - if (typeof showdown === `undefined`) { - require(`js`, `https://cdn.rawgit.com/showdownjs/showdown/1.9.1/dist/showdown.min.js`, - () => { - showdown.extension(`IWSY`, { - type: `lang`, - filter: function (text, converter) { - return text.replace(/~([^~]+)~/g, function (match, group) { - return decodeShowdown(group); - }); - } - }); - }); - } - else { - } - }; + // Set up Showdown + const setupShowdown = () => { + if (typeof showdown === `undefined`) { + require(`js`, `https://cdn.rawgit.com/showdownjs/showdown/1.9.1/dist/showdown.min.js`, + () => { + showdown.extension(`IWSY`, { + type: `lang`, + filter: function (text) { + return text.replace(/~([^~]+)~/g, function (match, group) { + return decodeShowdown(group); + }); + } + }); + }); + } + }; - // Decode special Showdown tags - const decodeShowdown = group => { - if (group.slice(0, 5) === `code:`) { - return `${group.slice(5)}`; - } - if (group.slice(0, 5) === `html:`) { - return group.slice(5); - } - if (group.slice(0, 4) === `img:`) { - const data = group.slice(4); - const colon = data.indexOf(`:`); - if (colon > 0) { - const src = data.slice(0, colon); - const classes = data.slice(colon + 1).split(` `); - const styles = []; - for (const item of classes) { - if (item.endsWith(`%`)) { - styles.push(`width:${item}`); - } else if (item.startsWith(`{`) && item.endsWith(`}`)) { - styles.push(item.slice(1, -1)); - } else { - switch (item) { - case `left`: - styles.push(`float:left;margin-right:1em`); - break; - case `center`: - styles.push(`margin:0 auto`); - break; - case `right`: - styles.push(`float:right;margin-left:1em`); - break; - case `clear`: - styles.push(`clear:both`); - break; - case `border`: - styles.push(`padding:2px;border:1px solid black`); - break; - } - } - } - return ``; - } - } - return group; - }; + // Decode special Showdown tags + const decodeShowdown = group => { + if (group.slice(0, 5) === `code:`) { + return `${group.slice(5)}`; + } + if (group.slice(0, 5) === `html:`) { + return group.slice(5); + } + if (group.slice(0, 4) === `img:`) { + const data = group.slice(4); + const colon = data.indexOf(`:`); + if (colon > 0) { + const src = data.slice(0, colon); + const classes = data.slice(colon + 1).split(` `); + const styles = []; + for (const item of classes) { + setImageStyles(item, styles); + } + return ``; + } + } + if (group.slice(0, 4) === `vfx:`) { + const styles = []; + let slide = ``; + const items = group.slice(4).split(` `); + for (const item of items) { + if (!setImageStyles(item, styles)) { + slide = item; + } + } + return `
`; + } + return group; + }; - // Load a JS or CSS library + // Set the image styles + const setImageStyles = (item, styles) => { + if (item.endsWith(`%`)) { + styles.push(`width:${item}`); + } else if (item.startsWith(`{`) && item.endsWith(`}`)) { + styles.push(item.slice(1, -1)); + } else { + switch (item) { + case `left`: + styles.push(`float:left;margin-right:1em`); + break; + case `center`: + styles.push(`position:absolute;left:50%;top:0;transform:translate(-50%, 0)`); + break; + case `right`: + styles.push(`float:right;margin-left:1em`); + break; + case `clear`: + styles.push(`clear:both`); + break; + case `border`: + styles.push(`padding:2px;border:1px solid black`); + break; + default: + return false; + } + } + return true; + }; + + // Load a JS or CSS library const require = (type, src, cb) => { let prefix = ``; if (src[0] == `/`) { @@ -694,367 +812,446 @@ const IWSY = (playerElement, scriptObject) => { cb(); }; document.head.appendChild(element); - }; + }; - // Set a HEAD style - const setHeadStyle = (styleName, styleValue) => { - for (let i = 0; i < document.head.childNodes.length; i++) { - let node = document.head.childNodes[i]; - if (node.tagName === `STYLE`) { - let data = node.innerHTML; - if (data.indexOf(`${styleName} `) === 0) { - document.head.removeChild(node); - break; - } - } - } - var style = document.createElement('style'); - style.className = `iwsy-css`; - style.innerHTML = `${styleName} ${styleValue}`; - document.head.appendChild(style); - }; + // Set a HEAD style + const setHeadStyle = (styleName, styleValue) => { + for (let i = 0; i < document.head.childNodes.length; i++) { + let node = document.head.childNodes[i]; + if (node.tagName === `STYLE`) { + let data = node.innerHTML; + if (data.indexOf(`${styleName} `) === 0) { + document.head.removeChild(node); + break; + } + } + } + var style = document.createElement(`style`); + style.className = `iwsy-css`; + style.innerHTML = `${styleName} ${styleValue}`; + document.head.appendChild(style); + }; - // Release the presentation to continue - const release = () => { - player.style.cursor = 'none'; - document.removeEventListener(`click`, release); - document.addEventListener(`click`, () => { - script.runMode = `manual`; - }); - document.onkeydown = () => { - script.runMode = `manual`; - }; - doStep(script.nextStep); - }; + // Release the presentation to continue + const release = () => { + player.style.cursor = `none`; + document.removeEventListener(`click`, release); + document.addEventListener(`click`, () => { + script.runMode = `manual`; + }); + document.onkeydown = () => { + script.runMode = `manual`; + }; + doStep(script.nextStep); + }; - // Manual mode. Set up listeners and wait for the user - const enterManualMode = step => { - script.nextStep = step ? script.steps[step.index + 1] : script.steps[0]; - player.style.cursor = 'pointer'; - document.addEventListener(`click`, release); - document.onkeydown = event => { - switch (event.code) { - case `Space`: - case `ArrowRight`: - document.onkeydown = null; - script.runMode = `manual`; - release(); - break; - case `ArrowLeft`: - break; - case `Enter`: - document.addEventListener(`click`, () => { - script.runMode = `manual`; - }); - player.style.cursor = 'none'; - script.runMode = `auto`; - release(); - break; - } - return true; - }; - }; + // Manual mode. Set up listeners and wait for the user + const enterManualMode = step => { + script.nextStep = step ? script.steps[step.index + 1] : script.steps[0]; + player.style.cursor = `pointer`; + document.addEventListener(`click`, release); + document.onkeydown = event => { + switch (event.code) { + case `Space`: + case `ArrowRight`: + document.onkeydown = null; + script.runMode = `manual`; + release(); + break; + case `ArrowLeft`: + break; + case `Enter`: + document.addEventListener(`click`, () => { + script.runMode = `manual`; + }); + player.style.cursor = `none`; + script.runMode = `auto`; + release(); + break; + } + return true; + }; + }; - // Initialize the script - const initScript = () => { - document.onkeydown = null; - player.style.position = `relative`; - player.style.overflow = `hidden`; - player.style.cursor = 'none'; - script.speed = `normal`; - script.labels = {}; - script.stop = false; - removeStyles(); - for (const block of script.blocks) { - const element = block.element; - if (element != null && typeof element !== `undefined`) { - removeElement(element); - block.element = null; - } - } - player.innerHTML = null; - script.steps.forEach((step, index) => { - step.index = index; - if (typeof step.label !== `undefined`) { - script.labels[step.label] = index; - } - if (index < script.steps.length - 1) { - const nextStep = script.steps[step.index + 1]; - step.next = () => { - if (script.runMode == `auto` || script.speed === `scan`) { - setTimeout(() => { - if (script.stop) { - script.stop = false; - restoreCursor(); - } else { - doStep(nextStep); - } - }, 0); - } else { - doStep(nextStep); - } - } - } - else { - step.next = () => { - console.log(`Step ${index + 1}: Finished`); - restoreCursor(); - } - }; - }); - setupBlocks(); - } + // Initialize the script + const initScript = () => { + document.onkeydown = null; + player.style.position = `relative`; + player.style.overflow = `hidden`; + player.style.cursor = `none`; + script.speed = `normal`; + script.labels = {}; + script.stop = false; + removeStyles(); + for (const block of script.blocks) { + const element = block.element; + if (element != null && typeof element !== `undefined`) { + removeElement(element); + block.element = null; + } + } + player.innerHTML = null; + script.steps.forEach((step, index) => { + step.index = index; + if (typeof step.label !== `undefined`) { + script.labels[step.label] = index; + } + if (index < script.steps.length - 1) { + const nextStep = script.steps[step.index + 1]; + step.next = () => { + if (script.runMode == `auto` || script.speed === `scan`) { + setTimeout(() => { + if (script.stop) { + script.stop = false; + restoreCursor(); + } else { + doStep(nextStep); + } + }, 0); + } else { + doStep(nextStep); + } + }; + } + else { + step.next = () => { + console.log(`Step ${index + 1}: Finished`); + restoreCursor(); + }; + } + }); + setupBlocks(); + script.vfxElements = []; + }; - const actions = { - init, - setcontent, - show, - hide, - pause, - fadeup, - fadedown, - crossfade, - transition, - goto, - load - }; + const actions = { + init, + setcontent, + show, + hide, + fadeup, + fadedown, + animate, + crossfade, + transition, + pause, + goto, + load + }; - // Process a single step - const doStep = step => { - try { - if (!step) { - return; - } - if (step.title) { - console.log(`Step ${step.index}: ${step.title}`); - } else { - console.log(`Step ${step.index}: ${step.action}`); - } - if (script.speed === `scan` && step.index === script.scanTarget) { - script.speed = `normal`; - for (const block of script.blocks) { - if (block.element) { - block.element.style.display = `block`; - } - } - } + // Process a single step + const doStep = step => { + try { + if (!step) { + return; + } + if (step.title) { + console.log(`Step ${step.index}: ${step.title}`); + } else { + console.log(`Step ${step.index}: ${step.action}`); + } + if (script.speed === `scan` && step.index === script.scanTarget) { + script.speed = `normal`; + for (const block of script.blocks) { + if (block.element) { + block.element.style.display = `block`; + } + } + } - const onStepCB = script.onStepCB; - if (step.action === `chain`) { - const runMode = script.runMode; - fetch(`${path}${step.script}?v=${Date.now()}`) - .then(response => { - if (response.status >= 400) { - throw Error(`Unable to load ${step.script}: ${response.status}`) - } - response.json().then(data => { - script = data; - if (onStepCB) { - onStepCB(-1); - } - initScript(); - script.runMode = runMode; - doStep(script.steps[1]); - }); - }) - .catch(err => { - throw Error(`Fetch Error :${err}`); - }); - return; - } + const onStepCB = script.onStepCB; + if (step.action === `chain`) { + const runMode = script.runMode; + fetch(`${path}${step.script}?v=${Date.now()}`) + .then(response => { + if (response.status >= 400) { + throw Error(`Unable to load ${step.script}: ${response.status}`); + } + response.json().then(data => { + script = data; + if (onStepCB) { + onStepCB(-1); + } + initScript(); + script.runMode = runMode; + doStep(script.steps[1]); + }); + }) + .catch(err => { + throw Error(`Fetch Error :${err}`); + }); + return; + } - const actionName = step.action.split(` `).join(``); - let handler = actions[actionName]; - if (script.runMode === `auto`) { - if (typeof handler === `undefined`) { - handler = IWSY.plugins[actionName]; - if (typeof handler === `undefined`) { - throw Error(`Unknown action: '${step.action}'`); - } - } - if (onStepCB) { - onStepCB(step.index); - } - try { - handler(step); - } catch (err) { - console.log(`Step ${step.index} (${step.action}): ${err}`); - alert(`Step ${step.index} (${step.action}): ${err}`); - } - } else { - try { - handler(step); - } catch (err) { - console.log(JSON.stringify(step,0,2) + `\n` + JSON.stringify(handler,0,2)); - console.log(`Step ${step.index} (${step.action}): ${err}`); - alert(`Step ${step.index} (${step.action}): ${err}`); - } - } - } - catch (err) { - console.log(`Step error: ${err}`); - throw Error(err); - } - }; + const actionName = step.action.split(` `).join(``); + let handler = actions[actionName]; + if (script.runMode === `auto`) { + if (typeof handler === `undefined`) { + handler = plugins[actionName]; + if (typeof handler === `undefined`) { + throw Error(`Unknown action: '${step.action}'`); + } + } + if (onStepCB) { + onStepCB(step.index); + } + step.vfxRunning = []; + try { + handler(step); + } catch (err) { + console.log(`Step ${step.index} (${step.action}): ${err}`); + alert(`Step ${step.index} (${step.action}): ${err}`); + } + } else { + try { + handler(step); + } catch (err) { + console.log(JSON.stringify(step,0,2) + `\n` + JSON.stringify(handler,0,2)); + console.log(`Step ${step.index} (${step.action}): ${err}`); + alert(`Step ${step.index} (${step.action}): ${err}`); + } + } + } + catch (err) { + console.log(`Step error: ${err}`); + throw Error(err); + } + }; - /////////////////////////////////////////////////////////////////////////////// - // These are all the exported functions + /////////////////////////////////////////////////////////////////////////////// + // These are all the exported functions - // Get the script - const getScript = () => { - return script; - }; + // Get the script + const getScript = () => { + return script; + }; - // Set the script - const setScript = newScript => { - removeBlocks(); - script = newScript; - initScript(); - }; + // Set the script + const setScript = newScript => { + removeBlocks(); + script = newScript; + initScript(); + }; - // Set the path - const setPath = p => { - path = p; - }; + // Set the path + const setPath = p => { + path = p; + }; - // Go to a specified step number - const gotoStep = target => { - script.scanTarget = target; - script.runMode = `manual`; - scan(); - }; + // Go to a specified step number + const gotoStep = target => { + script.scanTarget = target; + script.runMode = `manual`; + scan(); + }; - // Show a block - const block = blockIndex => { - player.innerHTML = ``; - const w = player.getBoundingClientRect().width / 1000; - const h = player.getBoundingClientRect().height / 1000; - script.blocks.forEach((block, index) => { - const defaults = block.defaults; - const element = document.createElement(`div`); - player.appendChild(element); - if (script.speed === `scan`) { - element.style.display = `none`; - } - element.style.position = `absolute`; - element.style.opacity = `0.5`; - let val = defaults.left; - if (!isNaN(val)) { - val *= w; - } - element.style.left = val; - val = defaults.top; - if (!isNaN(val)) { - val *= h; - } - element.style.top = val; - val = defaults.width; - if (!isNaN(val)) { - val = `${val * w - 2}px`; - } else { - val = `calc(${val} - 2px)` - } - element.style.width = val; - val = defaults.height; - if (!isNaN(val)) { - val = `${val * h - 2}px`; - } else { - val = `calc(${val} - 2px)` - } - element.style.height = val; - element.style[`font-size`] = `${h * 40}px` - element.innerHTML = defaults.name; - if (index == blockIndex) { - element.style.background = `#ddffdd`; - element.style.border = `1px solid #00ff00`; - element.style[`font-weight`] = `bold` - element.style[`z-index`] = 10; - element.style.color = `#006600`; - } else { - element.style.border = `1px solid #ff0000`; - element.style[`text-align`] = `right`; - element.style[`z-index`] = 0; - element.style.color = `#ff0000`; - } - }); - }; + // Show a block + const block = blockIndex => { + player.innerHTML = ``; + const w = player.getBoundingClientRect().width / 1000; + const h = player.getBoundingClientRect().height / 1000; + script.blocks.forEach((block, index) => { + const defaults = block.defaults; + const element = document.createElement(`div`); + player.appendChild(element); + if (script.speed === `scan`) { + element.style.display = `none`; + } + element.style.position = `absolute`; + element.style.opacity = `0.5`; + let val = defaults.left; + if (!isNaN(val)) { + val *= w; + } + element.style.left = val; + val = defaults.top; + if (!isNaN(val)) { + val *= h; + } + element.style.top = val; + val = defaults.width; + if (!isNaN(val)) { + val = `${val * w - 2}px`; + } else { + val = `calc(${val} - 2px)`; + } + element.style.width = val; + val = defaults.height; + if (!isNaN(val)) { + val = `${val * h - 2}px`; + } else { + val = `calc(${val} - 2px)`; + } + element.style.height = val; + element.style[`font-size`] = `${h * 40}px`; + element.innerHTML = defaults.name; + if (index == blockIndex) { + element.style.background = `#ddffdd`; + element.style.border = `1px solid #00ff00`; + element.style[`font-weight`] = `bold`; + element.style[`z-index`] = 10; + element.style.color = `#006600`; + } else { + element.style.border = `1px solid #ff0000`; + element.style[`text-align`] = `right`; + element.style[`z-index`] = 0; + element.style.color = `#ff0000`; + } + }); + }; - // Run the presentation - const run = (mode, startMode, then) => { - try { - homeScript = JSON.parse(JSON.stringify(script)); - afterRun = then; - initScript(); - if (mode === `fullscreen`) { - if (document.fullscreenElement) { - document.exitFullscreen(); - } else { - player.requestFullscreen(); - document.onfullscreenchange = () => { - if (document.fullscreenElement) { - player = document.fullscreenElement; - script.nextStep = script.steps[0]; - switch (startMode) { - case `auto`: - script.runMode = `auto`; - release(); - break; - case `manual`: - script.runMode = `manual`; - release(); - break; - case `wait`: - script.runMode = `manual`; - enterManualMode(null); - break; - } - } else { - player = playerElement; - script.stop = true; - } - }; - } - } else { - script.runMode = `auto`; - doStep(script.steps[0]); - } - } catch (err) { - console.log(`Run error: ${err}`); - throw Error(err); - } - } + // Run the presentation + const run = (mode, startMode, then) => { + try { + player.innerHTML = ``; + homeScript = JSON.parse(JSON.stringify(script)); + afterRun = then; + initScript(); + if (mode === `fullscreen`) { + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + player.requestFullscreen(); + document.onfullscreenchange = () => { + if (document.fullscreenElement) { + player = document.fullscreenElement; + script.nextStep = script.steps[0]; + switch (startMode) { + case `auto`: + script.runMode = `auto`; + release(); + break; + case `manual`: + script.runMode = `manual`; + release(); + break; + case `wait`: + script.runMode = `manual`; + enterManualMode(null); + break; + } + } else { + player = playerElement; + script.stop = true; + } + }; + } + } else { + script.runMode = `auto`; + doStep(script.steps[0]); + } + } catch (err) { + console.log(`Run error: ${err}`); + throw Error(err); + } + }; - // Stop the run - const stop = () => { - script.stop = true; - }; + // Stop the run + const stop = () => { + clearAllTimers(); + script.stop = true; + }; - // Set a step callback - const onStep = onStepCB => { - script.onStepCB = onStepCB; - }; + // Set a step callback + const onStep = onStepCB => { + script.onStepCB = onStepCB; + }; - // Remove all the CSS styles - const removeStyles = () => { - const styles = document.getElementsByClassName("iwsy-css"); - for (const style of styles) { - style.parentNode.removeChild(style); - } - }; + // Remove all the CSS styles + const removeStyles = () => { + const styles = document.getElementsByClassName(`iwsy-css`); + for (const style of styles) { + style.parentNode.removeChild(style); + } + }; + + // Run a pan-zoom + const panzoom = arg => { + player.innerText = ``; + doPanzoom(player, JSON.parse(arg)); + }; + + const doPanzoom = (container, spec, then) => { + let aspectW = 4; + let aspectH = 3; + const colon = spec.aspect.indexOf(`:`); + if (colon) { + aspectW = spec.aspect.slice(0,colon); + aspectH = spec.aspect.slice(colon + 1); + } + const ratio = aspectW / aspectH; + const width = container.offsetWidth; + const height = width / ratio; + container.style.height = `${Math.round(height)}px`; + container.style.display = `inline-block`; + container.style.overflow = `hidden`; + + const image = new Image(); + container.appendChild(image); + + image.addEventListener(`load`, () => { + const realWidth = image.naturalWidth; + const realHeight = image.naturalHeight; + const realRatio = realWidth / realHeight; + let w; + let h; + if (ratio < realRatio) { + h = height; + w = height * realRatio; + } else { + w = width; + h = width / realRatio; + } + const w2 = w * spec.endsize / 100; + const h2 = h * spec.endsize / 100; + w *= spec.startsize / 100; + h *= spec.startsize / 100; + const xoff = width * spec.startxoff / 100; + const yoff = height * spec.startyoff / 100; + const xoff2 = width * spec.endxoff / 100; + const yoff2 = height * spec.endyoff / 100; + + image.style.position = `absolute`; + image.style.width = `${w}px`; + image.style.height = `${h}px`; + image.style.left = `${xoff}px`; + image.style.top = `${yoff}px`; + const animSteps = Math.round(spec.duration * 25); + let animStep = 0; + const interval = setInterval(() => { + if (animStep < animSteps) { + const ratio = 0.5 - Math.cos(Math.PI * animStep / animSteps) / 2; + image.style.width = `${w + (w2 - w) * ratio}px`; + image.style.height = `${h + (h2 - h) * ratio}px`; + image.style.left = `${xoff + (xoff2 - xoff) * ratio}px`; + image.style.top = `${yoff + (yoff2 - yoff) * ratio}px`; + animStep++; + } else { + clearIntervalTimer(interval); + if (then) { + then(); + } + } + }, 40); + addIntervalTimer(interval); + }); + image.src = spec.url; + }; - /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// - setupShowdown(); - initScript(); - return { - getScript, - setScript, - setPath, - gotoStep, - block, - run, - stop, - onStep, - removeStyles - }; + setupShowdown(); + initScript(); + return { + getScript, + setScript, + setPath, + gotoStep, + block, + run, + stop, + onStep, + removeStyles, + panzoom + }; }; \ No newline at end of file diff --git a/iwsy/resources/ecs/README.md b/iwsy/resources/ecs/README.md new file mode 100644 index 0000000..7d10ea6 --- /dev/null +++ b/iwsy/resources/ecs/README.md @@ -0,0 +1,3 @@ +# EasyCoder scripts + +The entire IWSY UI is a web application driven by EasyCoder scripts in this folder. The "home" script is `iwsy.js` and this loads others as it needs them. `scripted` is a color-coded editor for EasyCoder script files, that runs in `scripted.html`. \ No newline at end of file diff --git a/iwsy/resources/ecs/blocks.txt b/iwsy/resources/ecs/blocks.txt index 680ebcf..a135570 100644 --- a/iwsy/resources/ecs/blocks.txt +++ b/iwsy/resources/ecs/blocks.txt @@ -147,6 +147,7 @@ Restart: end on click EditButton begin + if the elements of EditButton is not greater than the index of EditButton stop put the index of EditButton into SavedIndex gosub to SaveSelectedBlock put SavedIndex into SelectedBlock diff --git a/iwsy/resources/ecs/content.txt b/iwsy/resources/ecs/content.txt index de3a5d5..0f41eba 100644 --- a/iwsy/resources/ecs/content.txt +++ b/iwsy/resources/ecs/content.txt @@ -130,6 +130,7 @@ Restart: end on click EditButton begin + if the elements of EditButton is not greater than the index of EditButton stop put the index of EditButton into SavedIndex gosub to SaveSelectedItem put SavedIndex into SelectedItem @@ -153,6 +154,7 @@ Restart: index Editor to SelectedItem if style `display` of Editor is `none` begin + set style `display` of Editor to `block` put element SelectedItem of Items into Item set style `background-color` of EditButton to `lightgray` create Row in Editor @@ -163,7 +165,6 @@ Restart: create ItemNameInput in Row set the style of ItemNameInput to `flex:1` set the text of ItemNameInput to property `name` of Item - set style `display` of Editor to `block` create TextArea in Editor set the style of TextArea to `width:100%;max-width:100%;height:20em` put property `content` of Item into Content diff --git a/iwsy/resources/ecs/iwsy.txt b/iwsy/resources/ecs/iwsy.txt index 1405691..3a307b1 100644 --- a/iwsy/resources/ecs/iwsy.txt +++ b/iwsy/resources/ecs/iwsy.txt @@ -12,10 +12,11 @@ div ContentDiv div Buttons div Tabs - div Tab div StepsPanel div BlocksPanel div ContentPanel + div VFXPanel + div JSONPanel div Player div UserPanel div FileManPanel @@ -25,7 +26,7 @@ tr TR td TD input Status - input NameEditor + input ScriptName button SectionButton img New img Open @@ -42,6 +43,8 @@ module StepsModule module BlocksModule module ContentModule + module VFXModule + module JSONModule module UserModule module FileManModule module HelpModule @@ -149,8 +152,8 @@ Start: create TD in TR set the style of TD to `width:8em` - create NameEditor in TD - set the style of NameEditor to `width:100%` + create ScriptName in TD + set the style of ScriptName to `width:100%` create TD in TR set the style of TD to `width:2em;text-align:center` @@ -230,34 +233,49 @@ Start: set the style of ContentDiv to `width:100%;height:100%;position:relative` create Tabs in ContentDiv - set the style of Tabs to `width:100%;padding:0.5em 0;text-align:center` - set the elements of SectionButton to 3 - divide 100 by the elements of SectionButton giving N - create Tab in Tabs - set the style of Tab to `display:inline-block;width:` cat N cat `%` + set the style of Tabs to `width:100%;padding:0.5em 0;text-align:center;display:flex` + set the elements of SectionButton to 5 + index SectionButton to 0 - create SectionButton in Tab - set the style of SectionButton to `width:100%;background-color:lightgray` + create SectionButton in Tabs + set the style of SectionButton to `flex:1;background-color:lightgray;margin-right:0.2em` set the content of SectionButton to `Steps` + set attribute `title` of SectionButton to `Show the Steps manager` create StepsPanel in ContentDiv set the style of StepsPanel to `position:absolute;left:0;top:2.5em;width:100%;height:calc(100% - 2.5em)` - create Tab in Tabs - set the style of Tab to `display:inline-block;width:` cat N cat `%` + index SectionButton to 1 - create SectionButton in Tab - set the style of SectionButton to `width:100%;background-color:#eee` + create SectionButton in Tabs + set the style of SectionButton to `flex:1;background-color:#eee;margin-right:0.2em` set the content of SectionButton to `Blocks` + set attribute `title` of SectionButton to `Show the Blocks manager` create BlocksPanel in ContentDiv set the style of BlocksPanel to `position:absolute;left:0;top:2.5em;width:100%;height:calc(100% - 2.5em)` - create Tab in Tabs - set the style of Tab to `display:inline-block;width:` cat N cat `%` + index SectionButton to 2 - create SectionButton in Tab - set the style of SectionButton to `width:100%;background-color:#eee` + create SectionButton in Tabs + set the style of SectionButton to `flex:1;background-color:#eee;margin-right:0.2em` set the content of SectionButton to `Content` + set attribute `title` of SectionButton to `Show the Content manager` create ContentPanel in ContentDiv set the style of ContentPanel to `position:absolute;left:0;top:2.5em;width:100%;height:calc(100% - 2.5em)` + index SectionButton to 3 + create SectionButton in Tabs + set the style of SectionButton to `flex:1;background-color:#eee;margin-right:0.2em` + set the content of SectionButton to `VFX` + set attribute `title` of SectionButton to `Show the VFX manager` + create VFXPanel in ContentDiv + set the style of VFXPanel to `position:absolute;left:0;top:2.5em;width:100%;height:calc(100% - 2.5em)` + + index SectionButton to 4 + create SectionButton in Tabs + set the style of SectionButton to `flex:1;background-color:#eee` + set the content of SectionButton to `JSON` + set attribute `title` of SectionButton to `Import and Export` + create JSONPanel in ContentDiv + set the style of JSONPanel to `position:absolute;left:0;top:2.5em;width:100%;height:calc(100% - 2.5em)` + put 0 into Section index SectionButton to 0 on click SectionButton @@ -299,7 +317,7 @@ Start: begin if confirm `Content has changed. Do you want to save it?` begin - put the content of NameEditor into Name + put the content of ScriptName into Name if Name is empty begin gosub to SetStatusRed @@ -315,7 +333,7 @@ Start: clear Running iwsy stop set attribute `src` of RunStop to `resources/icon/run.png` - set the content of NameEditor to empty + set the content of ScriptName to empty put empty into CurrentScriptName gosub to CreateNewPresentation iwsy script Presentation @@ -328,7 +346,7 @@ Start: on click Save begin gosub to SaveChanges - put the content of NameEditor into Name + put the content of ScriptName into Name if Name is empty begin gosub to SetStatusRed @@ -337,7 +355,7 @@ Start: end if the position of `.json` in Name is -1 put Name cat `.json` into Name replace ` ` with `_` in Name - set the content of NameEditor to Name + set the content of ScriptName to Name if Name is not CurrentScriptName put empty into LastSavedState if Presentation is not LastSavedState begin @@ -364,7 +382,7 @@ Start: on click Delete begin - put the content of NameEditor into Name + put the content of ScriptName into Name if Name is empty begin gosub to SetStatusGreen @@ -377,7 +395,7 @@ Start: else remove Name from storage gosub to SetStatusGreen set the text of Status to `Script "` cat Name cat `" deleted` - set the content of NameEditor to empty + set the content of ScriptName to empty put empty into CurrentScriptName put empty into Presentation put Presentation into LastSavedState @@ -443,7 +461,9 @@ Start: put Presentation into LastSavedState gosub to SetupSteps gosub to SetupBlocks - gosub to SetupContent + gosub to SetupContent + gosub to SetupVFX + gosub to SetupJSON put 0 into N gosub to SelectSection clear Running @@ -497,6 +517,13 @@ Start: set the text of Status to `Logged in as '` cat Name cat property `name` of UserRecord cat `'` fork to ResetStatus end + else if Action is `panzoom` + begin + set style `display` of Player to `block` + set style `display` of FileManPanel to `none` + set style `display` of HelpPanel to `none` + iwsy panzoom property `item` of Message + end end wait 10 ticks @@ -505,9 +532,7 @@ Start: if Arg includes `.json` begin rest get Presentation from `/resources/users/` cat Arg cat `?v=` cat now - send to StepsModule - send to BlocksModule - send to ContentModule + gosub to NotifyModules iwsy script Presentation set style `display` of Player to `block` create Mask in Body @@ -596,6 +621,8 @@ CreateNewPresentation: set Item to array set property `blocks` of Presentation to Item set property `content` of Presentation to Item + set property `vfx` of Presentation to Item + json format Presentation return SetupSteps: @@ -622,8 +649,25 @@ SetupContent: end return -! Select one of the 3 sections +SetupVFX: + if VFXModule is not running + begin + rest get Script from `/resources/ecs/vfx.txt?v=` cat now + run Script with VFXPanel and Presentation as VFXModule + end + return + +SetupJSON: + if JSONModule is not running + begin + rest get Script from `/resources/ecs/json.txt?v=` cat now + run Script with JSONPanel and Presentation as JSONModule + end + return + +! Select one of the sections SelectSection: + if Running return gosub to SaveChanges index SectionButton to Section set style `background-color` of SectionButton to `#eee` @@ -636,6 +680,8 @@ UpdateCurrentSection: set style `display` of StepsPanel to `none` set style `display` of BlocksPanel to `none` set style `display` of ContentPanel to `none` + set style `display` of VFXPanel to `none` + set style `display` of JSONPanel to `none` if Section is 0 begin set style `display` of StepsPanel to `block` @@ -650,13 +696,26 @@ UpdateCurrentSection: begin set style `display` of ContentPanel to `block` send to ContentModule + end + else if Section is 3 + begin + set style `display` of VFXPanel to `block` + send to VFXModule + end + else if Section is 4 + begin + set style `display` of JSONPanel to `block` + send to JSONModule end return SaveChanges: + if Running gosub to StopRun send `save` to StepsModule send `save` to BlocksModule send `save` to ContentModule + send `save` to VFXModule + send `save` to JSONModule return DoOpen: @@ -689,7 +748,7 @@ DoOpen: if UserRecord begin rest get Files from `ulist/` cat UserEmail cat `/` cat UserPassword cat `/` - cat `users/` cat UserHome cat `/scripts` + cat `users/` cat UserHome cat `/scripts?v=` cat now put the json count of Files into FileCount put empty into Content put 0 into N @@ -725,15 +784,15 @@ DoOpen: put 0 into N while N is less than FileCount begin - index File to N - index FileName to N - put `
` - cat `
` into File - replace `INDEX` with N in File - if N is even replace `ODDEVEN` with `ec-even` in File - else replace `ODDEVEN` with `ec-odd` in File - put FileList cat File into FileList - add 1 to N + index File to N + index FileName to N + put `
` + cat `
` into File + replace `INDEX` with N in File + if N is even replace `ODDEVEN` with `ec-even` in File + else replace `ODDEVEN` with `ec-odd` in File + put FileList cat File into FileList + add 1 to N end set the content of Scroller to FileList @@ -741,33 +800,32 @@ DoOpen: put 0 into N while N is less than FileCount begin - index File to N - index FileName to N - put element N of Content into File - attach FileRow to `ec-file-row-` cat N - attach FileName to `ec-file-name-` cat N - set the content of FileName to File - if N is even set style `background` of FileRow to `lightgray` - on click FileName go to SelectFile - add 1 to N + index File to N + index FileName to N + put element N of Content into File + attach FileRow to `ec-file-row-` cat N + attach FileName to `ec-file-name-` cat N + set the content of FileName to File + if N is even set style `background` of FileRow to `lightgray` + on click FileName go to SelectFile + add 1 to N end on click CloseButton begin - put LastSavedState into Content - go to CloseBrowser + put LastSavedState into Content + go to CloseBrowser end stop SelectFile: index File to the index of FileName - set the content of NameEditor to File + set the content of ScriptName to File put File into CurrentScriptName if UserRecord rest get Presentation from `/resources/users/` cat UserHome cat `/scripts/` cat File cat `?v=` cat now else get Presentation from storage as File - send to StepsModule - send to BlocksModule - send to ContentModule + gosub to NotifyModules + json format Presentation put Presentation into LastSavedState gosub to UpdateCurrentSection gosub to SetStatusGreen @@ -801,14 +859,15 @@ ResetStatus: set attribute `title` of Status to `` stop +StopRun: + iwsy stop + clear Running + gosub to ClearStepsButtons + set attribute `src` of RunStop to `resources/icon/run.png` + return + DoRunStop: - if Running - begin - clear Running - gosub to ClearStepsButtons - set attribute `src` of RunStop to `resources/icon/run.png` - iwsy stop - end + if Running gosub to StopRun else begin put property `steps` of Presentation into Item @@ -819,10 +878,10 @@ DoRunStop: set style `display` of HelpPanel to `none` gosub to ClearStepsButtons set attribute `src` of RunStop to `resources/icon/runstop.png` - set Running put 0 into N gosub to SelectSection + set Running iwsy onstep begin @@ -859,13 +918,19 @@ DoRunStop: stop PrepareScript: - send to StepsModule - send to BlocksModule - send to ContentModule + gosub to NotifyModules put Presentation into LastSavedState gosub to UpdateCurrentSection stop +NotifyModules: + send to StepsModule + send to BlocksModule + send to ContentModule + send to VFXModule + send to JSONModule + return + RunFullScreen: put property `steps` of Presentation into Item if the json count of Item is 1 stop diff --git a/iwsy/resources/ecs/json.txt b/iwsy/resources/ecs/json.txt new file mode 100644 index 0000000..c2ba343 --- /dev/null +++ b/iwsy/resources/ecs/json.txt @@ -0,0 +1,109 @@ +! JSON + + script JSON + + import div Container and variable Presentation + + div Panel + div Row + select SectionSelector + textarea Script + button SaveButton + variable Sections + variable Section + variable Content + variable LastSaved + + set Sections to array + json add `All` to Sections + json add `Steps` to Sections + json add `Blocks` to Sections + json add `Content` to Sections + json add `VFX` to Sections + + on message go to Start + set ready + stop + +Start: + if the message is `save` + begin + stop + end + + clear Container + create Panel in Container + set the style of Panel to `height:100%;display:flex;flex-direction:column` + + create Row in Panel + set the style of Row to `font-size:1.2em;font-weight:bold;margin-bottom:0.2em` + set the content of Row to `Section: ` + + create SectionSelector in Row + set the style of SectionSelector to `height:1.5em` + set SectionSelector from Sections as `All` + on change SectionSelector + begin + if Script is not LastSaved + begin + if confirm `Content has changed. Do you want to save it?` + begin + if Section set property Section of Presentation to Script + end + end + if SectionSelector is `All` + begin + put Presentation into Content + go to SS2 + end + else if SectionSelector is `Steps` + begin + put `steps` into Section + go to SelectSection + end + else if SectionSelector is `Blocks` + begin + put `blocks` into Section + go to SelectSection + end + else if SectionSelector is `Content` + begin + put `content` into Section + go to SelectSection + end + else if SectionSelector is `VFX` + begin + put `vfx` into Section + go to SelectSection + end + end + + create Script in Panel + set the style of Script to `flex:1;width:100%` + + set the content of Script to Presentation + put Presentation into LastSaved + + create SaveButton in Panel + set the style of SaveButton to `display:block;width:100%` + set the text of SaveButton to `Save` + on click SaveButton + begin + print `Save` + if Section set property Section of Presentation to Script + print Script + put Script into LastSaved + json format Presentation + end + + put empty into Section + put Presentation into Content + go to SS2 + +SelectSection: + put property Section of Presentation into Content +SS2: + json format Content + put Content into LastSaved + set the content of Script to Content + stop diff --git a/iwsy/resources/ecs/steps.txt b/iwsy/resources/ecs/steps.txt index e62c505..fadd063 100644 --- a/iwsy/resources/ecs/steps.txt +++ b/iwsy/resources/ecs/steps.txt @@ -69,7 +69,7 @@ variable ContentNames variable ContinueTypes variable ChainNames - variable TrueFalse + variable YesNo variable Response variable Message variable Running @@ -292,6 +292,7 @@ Restart: json add `pause` to ActionNames json add `fade up` to ActionNames json add `fade down` to ActionNames + json add `animate` to ActionNames json add `crossfade` to ActionNames json add `transition` to ActionNames json add `goto` to ActionNames @@ -303,8 +304,8 @@ Restart: json add `border` to InitProperties json add `css` to InitProperties set ContinueTypes to array - json add `true` to ContinueTypes - json add `false` to ContinueTypes + json add `yes` to ContinueTypes + json add `no` to ContinueTypes put property `steps` of Presentation into Steps put -1 into SelectedStep @@ -441,6 +442,7 @@ ReloadStepEditor: else if Action is `pause` gosub to EditPause else if Action is `fade up` gosub to EditFadeUp else if Action is `fade down` gosub to EditFadeDown + else if Action is `animate` gosub to EditAnimate else if Action is `crossfade` gosub to EditCrossfade else if Action is `transition` gosub to EditTransition else if Action is `goto` gosub to EditGoto @@ -671,6 +673,11 @@ EditFadeDown: gosub to EditContinue return +EditAnimate: + gosub to EditBlockList + gosub to EditContinue + return + EditCrossfade: put ContentNames into Names put `New text` into TargetText @@ -866,9 +873,9 @@ EditContinue: index ContinueSelect to SelectedStep create ContinueSelect in TD set the style of ContinueSelect to `width:100%` - put property `continue` of CurrentStep into TrueFalse - if TrueFalse put `true` into TrueFalse else put `false` into TrueFalse - set ContinueSelect from ContinueTypes as TrueFalse + put property `continue` of CurrentStep into YesNo + if not YesNo put `no` into YesNo + set ContinueSelect from ContinueTypes as YesNo return ! Create a new action with a full set of empty properties @@ -1031,11 +1038,6 @@ SaveCurrentStep: set Blocks to array set property `blocks` of CurrentStep to Blocks end - end - else if Action is `pause` - begin - index DurationInput to SelectedStep - set property `duration` of CurrentStep to the text of DurationInput end else if Action is `fade up` begin @@ -1062,6 +1064,17 @@ SaveCurrentStep: set property `duration` of CurrentStep to the text of DurationInput index ContinueSelect to SelectedStep set property `continue` of CurrentStep to ContinueSelect + end + else if Action is `animate` + begin + put property `blocks` of CurrentStep into Blocks + if Blocks is empty + begin + set Blocks to array + set property `blocks` of CurrentStep to Blocks + end + index ContinueSelect to SelectedStep + set property `continue` of CurrentStep to ContinueSelect end else if Action is `crossfade` begin @@ -1080,6 +1093,11 @@ SaveCurrentStep: set property `duration` of CurrentStep to the text of DurationInput index ContinueSelect to SelectedStep set property `continue` of CurrentStep to ContinueSelect + end + else if Action is `pause` + begin + index DurationInput to SelectedStep + set property `duration` of CurrentStep to the text of DurationInput end else if Action is `goto` begin @@ -1096,6 +1114,7 @@ SaveCurrentStep: index ChainSelect to SelectedStep set property `script` of CurrentStep to ChainSelect end + index URLInput to SelectedStep set element SelectedStep of Steps to CurrentStep set property `steps` of Presentation to Steps diff --git a/iwsy/resources/ecs/vfx.txt b/iwsy/resources/ecs/vfx.txt new file mode 100644 index 0000000..9cfd01d --- /dev/null +++ b/iwsy/resources/ecs/vfx.txt @@ -0,0 +1,402 @@ +! VFX + + script VFX + + import div Panel and variable Presentation + + div Editor + div Title + div Table + div Row + div Cell + div Empty + input ItemNameInput + input AspectRatioInput + input URLInput + input DurationInput + input StartSizeInput + input StartXOffsetInput + input StartYOffsetInput + input EndSizeInput + input EndXOffsetInput + input EndYOffsetInput + select VFXTypeSelect + button EditButton + button ShowButton + button SaveButton + img AddItem + img Up + img DeleteItem + a Link + variable Items + variable Item + variable Item2 + variable ItemName + variable SelectedItem + variable SavedIndex + variable Message + variable N + variable NumItems + variable VFXTypes + variable Value + + put -1 into SelectedItem + set VFXTypes to array + json add `panzoom` to VFXTypes + + on message go to Start + set ready + stop + +Start: + if the message is `save` + begin + gosub to SaveSelectedItem + stop + end + +Restart: + put property `vfx` of Presentation into Items + if Items is empty set Items to array + + clear Panel + create Row in Panel + set the style of Row to `display:flex` + create Title in Row + set the style of Title to + `flex:1;font-size:110%;font-weight:bold;background:lightgray;text-align:center;margin-bottom:0.5em` + set the content of Title to `Content Items` + create Cell in Row + set the style of Cell to `width:1.4em;text-align:center` + create Link in Cell + create AddItem in Link + set the style of AddItem to `width:1em;margin-top:0.1em` + set attribute `src` of AddItem to `resources/icon/plus.png` + on click AddItem + begin + set Item to object + set property `name` of Item to `New vfx` + json add Item to Items + set property `vfx` of Presentation to Items + put -1 into SelectedItem + go to Restart + end + + put the json count of Items into NumItems + if NumItems is greater than 0 + begin + set the elements of EditButton to NumItems + set the elements of Editor to NumItems + set the elements of ShowButton to NumItems + set the elements of SaveButton to NumItems + set the elements of Up to NumItems + set the elements of DeleteItem to NumItems + set the elements of ItemNameInput to NumItems + set the elements of VFXTypeSelect to NumItems + set the elements of AspectRatioInput to NumItems + set the elements of URLInput to NumItems + set the elements of DurationInput to NumItems + set the elements of StartSizeInput to NumItems + set the elements of StartXOffsetInput to NumItems + set the elements of StartYOffsetInput to NumItems + set the elements of EndSizeInput to NumItems + set the elements of EndXOffsetInput to NumItems + set the elements of EndYOffsetInput to NumItems + end + create Table in Panel + set the style of Table to `width:100%` + put 0 into N + while N is less than NumItems + begin + index EditButton to N + index Up to N + index DeleteItem to N + index Editor to N + index ItemNameInput to N + put element N of Items into Item + put property `name` of Item into ItemName + create Row in Table + set the style of Row to `margin-bottom:0.2em` + create Cell in Row + set the style of Cell to `width:100%;display:flex` + create EditButton in Cell + set the style of EditButton to `flex:1` + set the text of EditButton to ItemName + if N is 0 + begin + create Empty in Cell + set the style of Empty to `width:1em;margin:0.2em 0 0 0.2em` + end + else + begin + create Link in Cell + create Up in Link + set the style of Up to `width:1em;margin:0.2em 0 0 0.2em` + set attribute `src` of Up to `/resources/icon/up.png` + end + create Link in Cell + create DeleteItem in Link + set the style of DeleteItem to `width:1em;margin:0.2em 0 0 0.2em` + set attribute `src` of DeleteItem to `/resources/icon/stop.png` + create Editor in Row + set the style of Editor to `margin:0.5em;border:1px solid black;padding:0.2em;display:none` + add 1 to N + end + on click EditButton + begin + if the elements of EditButton is not greater than the index of EditButton stop + put the index of EditButton into SavedIndex + gosub to SaveSelectedItem + put SavedIndex into SelectedItem + put 0 into N + while N is less than NumItems + begin + if N is not SelectedItem + begin + index EditButton to N + set style `background` of EditButton to `#eee` + index Editor to N + set style `display` of Editor to `none` + clear Editor + end + add 1 to N + end + index EditButton to SelectedItem + index ItemNameInput to SelectedItem + index VFXTypeSelect to SelectedItem + index AspectRatioInput to SelectedItem + index URLInput to SelectedItem + index DurationInput to SelectedItem + index StartSizeInput to SelectedItem + index StartXOffsetInput to SelectedItem + index StartYOffsetInput to SelectedItem + index EndSizeInput to SelectedItem + index EndXOffsetInput to SelectedItem + index EndYOffsetInput to SelectedItem + index ShowButton to SelectedItem + index SaveButton to SelectedItem + index Editor to SelectedItem + if style `display` of Editor is `none` + begin + set style `display` of Editor to `block` + set style `background-color` of EditButton to `lightgray` + put element SelectedItem of Items into Item +ReloadEditor: + clear Editor + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Type` + create VFXTypeSelect in Row + set the style of VFXTypeSelect to `flex:1` + set VFXTypeSelect from VFXTypes as property `type` of Item + on change VFXTypeSelect + begin + gosub to SaveSelectedItem + set property `type` of Item to VFXTypeSelect + set element SelectedItem of Items to Item + go to ReloadEditor + end + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Name` + create ItemNameInput in Row + set the style of ItemNameInput to `flex:1` + set the text of ItemNameInput to property `name` of Item + if VFXTypeSelect is `panzoom` + begin + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Aspect` + create AspectRatioInput in Row + set the style of AspectRatioInput to `flex:1` + put property `aspect` of Item into Value + if Value is empty put `4:3` into Value + set the text of AspectRatioInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Image` + create URLInput in Row + set the style of URLInput to `flex:1` + put property `url` of Item into Value + if Value is empty put empty into Value + set the text of URLInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Duration` + create DurationInput in Row + set the style of DurationInput to `flex:1` + put property `duration` of Item into Value + if Value is empty put 1 into Value + set the text of DurationInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Start Size` + create StartSizeInput in Row + set the style of StartSizeInput to `flex:1` + put property `startsize` of Item into Value + if Value is empty put 100 into Value + set the text of StartSizeInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Start Xoff` + create StartXOffsetInput in Row + set the style of StartXOffsetInput to `flex:1` + put property `startxoff` of Item into Value + if Value is empty put 0 into Value + set the text of StartXOffsetInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `Start Yoff` + create StartYOffsetInput in Row + set the style of StartYOffsetInput to `flex:1` + put property `startyoff` of Item into Value + if Value is empty put 0 into Value + set the text of StartYOffsetInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `End Size` + create EndSizeInput in Row + set the style of EndSizeInput to `flex:1` + put property `endsize` of Item into Value + if Value is empty put 100 into Value + set the text of EndSizeInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `End Xoff` + create EndXOffsetInput in Row + set the style of EndXOffsetInput to `flex:1` + put property `endxoff` of Item into Value + if Value is empty put 0 into Value + set the text of EndXOffsetInput to Value + + create Row in Editor + set the style of Row to `display:flex` + create Cell in Row + set the style of Cell to `width:5em` + set the content of Cell to `End Yoff` + create EndYOffsetInput in Row + set the style of EndYOffsetInput to `flex:1` + put property `endyoff` of Item into Value + if Value is empty put 0 into Value + set the text of EndYOffsetInput to Value + end + create Row in Editor + set the style of Row to `display:flex;margin-top:0.5em` + create SaveButton in Row + set the style of SaveButton to `flex:1;margin-right:0.2em` + set the text of SaveButton to `Save` + on click SaveButton gosub to SaveSelectedItem + create ShowButton in Row + set the style of ShowButton to `flex:1;margin-left:0.2em` + set the text of ShowButton to `Show` + on click ShowButton + begin + gosub to SaveSelectedItem + put element SelectedItem of Items into Item + set Message to object + set property `action` of Message to `panzoom` + set property `item` of Message to Item + send Message to parent + end + end + else + begin + clear Editor + set style `display` of Editor to `none` + end + end + on click Up + begin + put the index of Up into N + put element N of Items into Item + take 1 from N + put element N of Items into Item2 + set element N of Items to Item + add 1 to N + set element N of Items to Item2 + set property `vfx` of Presentation to Items + put -1 into SelectedItem + go to Restart + end + on click DeleteItem + begin + put the index of DeleteItem into N + put property `vfx` of Presentation into Items + json delete element N of Items + set property `vfx` of Presentation to Items + put -1 into SelectedItem + go to Restart + end + stop + +! Save the selected content +SaveSelectedItem: + if SelectedItem is -1 return + if the json count of Items is 0 return + index ItemNameInput to SelectedItem + index VFXTypeSelect to SelectedItem + index AspectRatioInput to SelectedItem + index EditButton to SelectedItem + put element SelectedItem of Items into Item + put ItemNameInput into Value + replace ` ` with `-` in Value + set the text of ItemNameInput to Value + set the text of EditButton to Value + set property `name` of Item to Value + if property `type` of Item is `panzoom` + begin + index AspectRatioInput to SelectedItem + index URLInput to SelectedItem + index DurationInput to SelectedItem + index StartSizeInput to SelectedItem + index StartXOffsetInput to SelectedItem + index StartYOffsetInput to SelectedItem + index EndSizeInput to SelectedItem + index EndXOffsetInput to SelectedItem + index EndYOffsetInput to SelectedItem + set property `aspect` of Item to AspectRatioInput + set property `url` of Item to URLInput + set property `duration` of Item to DurationInput + set property `startsize` of Item to StartSizeInput + set property `startxoff` of Item to StartXOffsetInput + set property `startyoff` of Item to StartYOffsetInput + set property `endsize` of Item to EndSizeInput + set property `endxoff` of Item to EndXOffsetInput + set property `endyoff` of Item to EndYOffsetInput + end + set element SelectedItem of Items to Item + set property `vfx` of Presentation to Items + +! Tell the parent to refresh the script + set Message to object + set property `action` of Message to `refresh` + set property `source` of Message to `vfx` + send Message to parent + return \ No newline at end of file diff --git a/iwsy/resources/help/iwsy.md b/iwsy/resources/help/iwsy.md index 8485d5c..52f36e5 100644 --- a/iwsy/resources/help/iwsy.md +++ b/iwsy/resources/help/iwsy.md @@ -4,6 +4,8 @@ # ~iwsy~ - I Wanna Show You +## Embedded slideshows and presentations + I Wanna Show You (~iwsy~, pronounced "You-zee" as an approximation to the initials) is a web app you can use to create slideshows that run under either manual control or unattended. Instead of being a PC application that's tied to the desktop, ~iwsy~ is entirely browser-based. ~iwsy~ presentations can run here in this web app, they can be embedded in other web pages or they can run as independent web pages. ## An example diff --git a/iwsy/resources/help/project.md b/iwsy/resources/help/project.md index 028d066..fe22aa3 100644 --- a/iwsy/resources/help/project.md +++ b/iwsy/resources/help/project.md @@ -9,7 +9,7 @@ There are 3 main sections. ## 1 - Steps -This lists the steps of your presentation. These are in some way analogous to the 'slides' of a PowerPoint presentation. A step is any action that affects the presenttion, whether it causes any visual change or not. Every presentation starts with an `init` step where you define the size and proportions of your screen. This step is already provided, with its name on a clickable button. When you click the button it opens the ~page:action-init:`init` step editor~. All the action types are documented in their own pages - see the ~page:contents:Contents~. +This lists the steps of your presentation. These are in some way analogous to the 'slides' of a PowerPoint presentation. A step is any action that affects the presentation, whether it causes a visual change or not. Every presentation starts with an `init` step where you define the size and proportions of your screen. This step is already provided, with its name on a clickable button. When you click the button it opens the ~page:action-init:`init` step editor~. All the action types are documented in their own pages - see the ~page:contents:Contents~. To the left of the button is a tiny ~img:/resources/icon/binoculars.png:icon~ binoculars symbol. When you click one of these the panel on the left - called the _player_ - shows you what the presentation looks like at this step. diff --git a/iwsy/resources/help/technical.md b/iwsy/resources/help/technical.md index 1f56598..a0fb705 100644 --- a/iwsy/resources/help/technical.md +++ b/iwsy/resources/help/technical.md @@ -2,22 +2,28 @@ # Technical stuff -The UI of this website is coded in [**_EasyCoder_**](easycoder.github.io), a high-level scripting language that takes plain text scripts and compiles them in the browser as they are required. **_EasyCoder_** scripts tend to be much shorter than their JavaScript equivalents, and more importantly they are readable by any intelligent person who understands the target domain, so the long term maintenance prospects of an **_EasyCoder_** website such as ~iwsy~ are good as there will always be someone around who can learn it quickly. +The ~iwsy~ presentation engine is about 1200 lines of vanilla JavaScript. It can be loaded from a CDN and be included in any web page. To avoid name clashes it comprises a single JavaScript object called `IWSY`. + +Rather than relying on graphic libraries, ~iwsy~ leverages the power of CSS to create animated slideshows. A good deal of processing power is needed to achieve smooth animation but computer performance increases year by year, bringing improved graphic performance along with it. + +All of the animations and transitions in this package have a smooth start and finish, achieved wih a cosine function. -The presentation engine itself can be used independently of the ~iwsy~ website. The project is hosted on GitHub as part of **_EasyCoder_** and enquiries are welcome from other programmers who are interested in contributing. The range of transition effects is currently limited so there is considerable scope for extension. An outline plug-in mechanism is present so it should be possible to enhance the system without major disruption each time. +## The UI + +The UI of this website is coded in [**_EasyCoder_**](easycoder.github.io), a high-level scripting language that takes plain text scripts and compiles them in the browser as they are required. **_EasyCoder_** scripts tend to be much shorter than their JavaScript equivalents, and more importantly they are readable by any intelligent person who understands the target domain, so the long term maintenance prospects of an **_EasyCoder_** website such as ~iwsy~ are good as there will always be someone around who can learn it quickly. -## Statistics +The presentation engine itself can be used independently of the ~iwsy~ website. The project is [hosted on GitHub](https://github.com/easycoder/easycoder.github.io/tree/master/iwsy) as part of **_EasyCoder_** and enquiries are welcome from other programmers who are interested in contributing. The range of transition effects is currently limited so there is considerable scope for extension. An outline plug-in mechanism is present so it should be possible to enhance the system without major disruption each time. -The presentation engine is a little over 1000 lines of vanilla JavaScript. +The UI comprises about 3500 lines of EasyCoder scripts. The longest of these, at about 1100 lines, compiles in under 40ms on mid-range (core-i5) hardware. -The UI comprises 3343 lines of EasyCoder scripts. The longest of these, at about 1100 lines, compiles in under 40ms on mid-range (core-i5) hardware. Pingdom Tools report a page load time of just over a second. +[Pingdom Tools](https://tools.pingdom.com/) report a page load time of just over a second. ## Structure -The home page is simply a bootloader for the application scripts themselves. These are plain text files that load on demand and are compiled on the fly by the EasyCoder runtime library. There is no build process and the only tool required is a text editor. +The home page of a EasyCoder website is usually a bootloader for the application scripts themselves. These are plain text files that load on demand and are compiled on the fly by the EasyCoder runtime library. There is no build process and the only tool required is a text editor. User management is done with a small PHP REST module that runs on the server and keeps records either in a database or in file storage. Each user is allocated a directory on the server in which are their scripts and uploaded images, the latter which are scaled to a maximum width of 1200 pixels to avoid excessive file sizes. -~iwsy~ can be used without any login but the features available are restricted. In particular, the file manager is not available so if images are wanted they must be provided as independent URLs. Scripts can still be saved, but go to browser storage. +The ~iwsy~ website can be used without any login but the features available are restricted. In particular, the file manager is not available so if images are wanted they must be provided as independent URLs. Scripts can still be saved, but go into browser storage. ~page:contents:Contents~ diff --git a/iwsy/scripted.html b/iwsy/scripted.html index 55a33d2..f978196 100644 --- a/iwsy/scripted.html +++ b/iwsy/scripted.html @@ -16,7 +16,7 @@ variable Script require js `https://cdn.jsdelivr.net/gh/easycoder/easycoder.github.io/dist/plugins/codemirror.js?v=2.7.1` - rest get Script from `/resources/ecs/scripted.txt?v=` cat now + rest get Script from `https://cdn.jsdelivr.net/gh/easycoder/easycoder.github.io/iwsy/resources/ecs/scripted.txt?v=` cat now run Script diff --git a/js/plugins/iwsy.js b/js/plugins/iwsy.js index 636c08a..a725f07 100644 --- a/js/plugins/iwsy.js +++ b/js/plugins/iwsy.js @@ -1,9 +1,9 @@ const EasyCoder_IWSY = { - name: `EasyCoder_IWSY`, + name: `EasyCoder_IWSY`, - iwsy: { - }, + iwsy: { + }, IWSY: { @@ -12,138 +12,148 @@ const EasyCoder_IWSY = { const lino = compiler.getLino(); const action = compiler.nextToken(); switch (action) { - case `load`: - if (compiler.nextIsSymbol()) { - const playerRecord = compiler.getSymbolRecord(); - if (playerRecord.keyword === `div`) { - const script = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - player: playerRecord.name, - script - }); - return true; - } - } - break; - case `init`: - case `stop`: - compiler.next(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action - }); - return true; - case `remove`: - if (compiler.nextTokenIs(`styles`)) { - compiler.next(); + case `load`: + if (compiler.nextIsSymbol()) { + const playerRecord = compiler.getSymbolRecord(); + if (playerRecord.keyword === `div`) { + const script = compiler.getNextValue(); compiler.addCommand({ domain: `iwsy`, keyword: `iwsy`, lino, - action: `removeStyles` + action, + player: playerRecord.name, + script }); return true; } - return false; - case `path`: - const path = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - path - }); - return true; - case `script`: - const script = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - script - }); - return true; - case `goto`: - const target = compiler.getNextValue(); - compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - target - }); - return true; - case `block`: - const block = compiler.getNextValue(); + } + break; + case `init`: + case `stop`: + compiler.next(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action + }); + return true; + case `remove`: + if (compiler.nextTokenIs(`styles`)) { + compiler.next(); compiler.addCommand({ domain: `iwsy`, keyword: `iwsy`, lino, - action, - block + action: `removeStyles` }); return true; - case `run`: - const pc = compiler.getPc(); - let mode = `normal`; - let startMode = `wait`; - if (compiler.nextToken() === `fullscreen`) { - mode = compiler.getToken(); - if ([`auto`, `manual`].includes(compiler.nextToken())) { - startMode = compiler.getToken(); - compiler.next(); - } + } + return false; + case `path`: + const path = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + path + }); + return true; + case `script`: + const script = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + script + }); + return true; + case `goto`: + const target = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + target + }); + return true; + case `block`: + const block = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + block + }); + return true; + case `run`: + const pc = compiler.getPc(); + let mode = `normal`; + let startMode = `wait`; + if (compiler.nextToken() === `fullscreen`) { + mode = compiler.getToken(); + if ([`auto`, `manual`].includes(compiler.nextToken())) { + startMode = compiler.getToken(); + compiler.next(); } + } + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + mode, + startMode, + then: 0 + }); + // Get the 'then' code, if any + if (compiler.tokenIs(`then`)) { + const goto = compiler.getPc(); + // Add a 'goto' to skip the 'then' compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action, - mode, - startMode, - then: 0 + domain: `core`, + keyword: `goto`, + goto: 0 }); - // Get the 'then' code, if any - if (compiler.tokenIs(`then`)) { - const goto = compiler.getPc(); - // Add a 'goto' to skip the 'then' - compiler.addCommand({ - domain: `core`, - keyword: `goto`, - goto: 0 - }); - // Fixup the link to the 'then' branch - compiler.getCommandAt(pc).then = compiler.getPc(); - // Process the 'then' branch - compiler.next(); - compiler.compileOne(true); - compiler.addCommand({ - domain: `core`, - keyword: `stop` - }); - // Fixup the 'goto' - compiler.getCommandAt(goto).goto = compiler.getPc(); - } - return true; - case `onstep`: + // Fixup the link to the 'then' branch + compiler.getCommandAt(pc).then = compiler.getPc(); + // Process the 'then' branch compiler.next(); + compiler.compileOne(true); compiler.addCommand({ - domain: `iwsy`, - keyword: `iwsy`, - lino, - action + domain: `core`, + keyword: `stop` }); - return compiler.completeHandler(); - default: - break; + // Fixup the 'goto' + compiler.getCommandAt(goto).goto = compiler.getPc(); + } + return true; + case `onstep`: + compiler.next(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action + }); + return compiler.completeHandler(); + case `panzoom`: + const spec = compiler.getNextValue(); + compiler.addCommand({ + domain: `iwsy`, + keyword: `iwsy`, + lino, + action, + spec + }); + return true; + default: + break; } return false; }, @@ -153,83 +163,88 @@ const EasyCoder_IWSY = { const action = command.action; let script; switch (action) { - case `init`: - program.require(`js`, `iwsy.js`, + case `init`: + program.require(`js`, `iwsy.js`, function () { program.run(command.pc + 1); }); + return 0; + case `load`: + // if (program.iwsyFunctions) { + // throw Error(`IWSY has already been set up`); + // } + const playerRecord = program.getSymbolRecord(command.player); + const player = playerRecord.element[playerRecord.index]; + player.innerHTML = ``; + player.style.background = `none`; + player.style.border = `none`; + script = program.getValue(command.script); + try { + script = JSON.parse(script); + } catch (err) { + alert(`iwsy load: Badly formatted script`); + } + program.iwsyFunctions = IWSY(player, script); + break; + case `path`: + if (program.iwsyFunctions) { + program.iwsyFunctions.setPath(program.getValue(command.path)); + } + break; + case `script`: + script = program.getValue(command.script); + try { + script = JSON.parse(script); + } catch (err) { + alert(`iwsy script: Badly formatted script`); + } + if (program.iwsyFunctions) { + program.iwsyFunctions.setScript(script); + } + break; + case `goto`: + if (program.iwsyFunctions) { + program.iwsyFunctions.gotoStep(program.getValue(command.target)); + } + break; + case `block`: + if (program.iwsyFunctions) { + program.iwsyFunctions.block(program.getValue(command.block)); + } + break; + case `run`: + if (program.iwsyFunctions) { + program.iwsyFunctions.run(command.mode, command.startMode, () => { + program.run(command.then); + }); return 0; - case `load`: - if (program.iwsyFunctions) { - throw Error(`IWSY has already been set up`); - } - const playerRecord = program.getSymbolRecord(command.player); - const player = playerRecord.element[playerRecord.index]; - player.innerHTML = ``; - player.style.background = `none`; - player.style.border = `none`; - script = program.getValue(command.script); - try { - script = JSON.parse(script); - } catch (err) { - alert(`iwsy load: Badly formatted script`); - } - program.iwsyFunctions = IWSY(player, script); - break; - case `path`: - if (program.iwsyFunctions) { - program.iwsyFunctions.setPath(program.getValue(command.path)); - } - break; - case `script`: - script = program.getValue(command.script); - try { - script = JSON.parse(script); - } catch (err) { - alert(`iwsy script: Badly formatted script`); - } - if (program.iwsyFunctions) { - program.iwsyFunctions.setScript(script); - } - break; - case `goto`: - if (program.iwsyFunctions) { - program.iwsyFunctions.gotoStep(program.getValue(command.target)); - } - break; - case `block`: - if (program.iwsyFunctions) { - program.iwsyFunctions.block(program.getValue(command.block)); - } - break; - case `run`: - if (program.iwsyFunctions) { - program.iwsyFunctions.run(command.mode, command.startMode, () => { - program.run(command.then); - }); - return 0; - } - break; - case `stop`: - if (program.iwsyFunctions) { - program.iwsyFunctions.stop(); - delete program.iwsyFunctions; - } - break; - case `removeStyles`: - if (program.iwsyFunctions) { - program.iwsyFunctions.removeStyles(); - } - break; - case `onstep`: - const cb = command.pc + 2; - if (program.iwsyFunctions) { - program.iwsyFunctions.onStep(function(step) { - program.iwsyStep = step; - program.run(cb); - }); - } - break; + } + break; + case `stop`: + if (program.iwsyFunctions) { + program.iwsyFunctions.stop(); + // delete program.iwsyFunctions; + } + break; + case `removeStyles`: + if (program.iwsyFunctions) { + program.iwsyFunctions.removeStyles(); + } + break; + case `onstep`: + const cb = command.pc + 2; + if (program.iwsyFunctions) { + program.iwsyFunctions.onStep(function(step) { + program.iwsyStep = step; + program.run(cb); + }); + } + break; + case `panzoom`: + const spec = program.getValue(command.spec); + if (program.iwsyFunctions) { + program.iwsyFunctions.panzoom(spec); + } } return command.pc + 1; } @@ -237,10 +252,10 @@ const EasyCoder_IWSY = { getHandler: (name) => { switch (name) { - case `iwsy`: - return EasyCoder_IWSY.IWSY; - default: - return null; + case `iwsy`: + return EasyCoder_IWSY.IWSY; + default: + return null; } }, @@ -281,7 +296,7 @@ const EasyCoder_IWSY = { type: `constant`, numeric: false, content: JSON.stringify(script) - } + }; } break; case `step`: