const BlockType = require('../../extension-support/block-type'); const ArgumentType = require('../../extension-support/argument-type'); const Cast = require('../../util/cast'); const uid = require('../../util/uid'); const Textures = { Snow: require('./snow.png'), Light: require('./light.png'), Present: require('./present.png'), }; /** * Class for Extension blocks * @constructor */ class Extension { constructor(runtime) { /** * The runtime instantiating this block package. * @type {Runtime} */ this.runtime = runtime; /** * @type {HTMLDivElement} */ this.mainContainer = null; /** * @type {HTMLCanvasElement} */ this.mainCanvas = null; /** * canvas context * @type {CanvasRenderingContext2D} */ this.ctx = null; this.initialize(); this.snowParticles = {}; this.lights = {}; this.runtime.on('RUNTIME_STEP_START', () => { const viewBox = this.mainContainer.getBoundingClientRect(); for (const particleId in this.snowParticles) { const particle = this.snowParticles[particleId]; const element = particle.element; element.style.left = `calc(${particle.origin}% + ${particle.x}px)`; particle.x -= 3; const y = Cast.toNumber(element.style.top.replace('px', '')) + particle.speed; element.style.top = `${y}px`; if (element.getBoundingClientRect().right < 0 || y > viewBox.height) { element.remove(); delete this.snowParticles[particleId]; } } this.drawLightBackground(); }); this.runtime.on('PROJECT_STOP_ALL', () => { this.ctx.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height); this.clearSnow(); this.removeLights(); }); } initialize() { const mainContainer = document.body.appendChild(document.createElement("div")); mainContainer.style = 'position: absolute;' + 'left: 0; top: 0; width: 100%; height: 100%;' + 'pointer-events: none; overflow: hidden;' + 'z-index: 1000009;'; this.mainContainer = mainContainer; const mainCanvas = mainContainer.appendChild(document.createElement("canvas")); mainCanvas.style = 'position: absolute;' + 'left: 0; top: 0; width: 100%; height: 100%;' + 'pointer-events: none; background: none;' + 'border: 0; margin: 0; padding: 0;' + 'z-index: 999999;'; this.mainCanvas = mainCanvas; mainCanvas.width = 1280; mainCanvas.height = 720; const canvasContext = mainCanvas.getContext('2d'); this.ctx = canvasContext; } /** * @returns {object} metadata for this extension and its blocks. */ getInfo() { return { id: 'jgChristmas', name: 'Christmas', color1: '#ff0000', color2: '#00ff00', blockIconURI: require('./icon.png'), blocks: [ { opcode: 'snow', text: 'snow', blockType: BlockType.COMMAND }, { opcode: 'clearSnow', text: 'clear snow', blockType: BlockType.COMMAND }, // { // opcode: 'addPresent', // text: 'add present', // blockType: BlockType.COMMAND // }, // { // opcode: 'removePresents', // text: 'remove all presents', // blockType: BlockType.COMMAND // }, { opcode: 'addLight', text: 'add light', blockType: BlockType.COMMAND }, { opcode: 'removeLights', text: 'remove all lights', blockType: BlockType.COMMAND }, ] }; } snow() { const snowImage = this.mainContainer.appendChild(document.createElement("img")); const size = Math.round(8 + (Math.random() * 16)); const opacity = 0.5 + (Math.random() / 2); const originX = Math.random() * 150; snowImage.style = 'position: absolute;' + `left: ${originX}%; top: -${size}px; width: ${size}px; height: ${size}px;` + `pointer-events: none; opacity: ${opacity};` + 'z-index: 1000008;'; snowImage.src = Textures.Snow; const id = uid(); this.snowParticles[id] = { element: snowImage, origin: originX, x: 0, size: size, speed: 2 + Math.random() * 6 }; } removeLights() { this.ctx.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height); for (const particleId in this.lights) { const particle = this.lights[particleId]; const element = particle.element; element.remove(); delete this.lights[particleId]; } } addLight() { const lightImage = this.mainContainer.appendChild(document.createElement("img")); const viewBox = this.mainContainer.getBoundingClientRect(); const originX = Math.random() * viewBox.width; const originY = Math.random() * viewBox.height; const direction = Math.random() * 360; lightImage.style = 'position: absolute;' + `left: 0; top: 0; width: 70px; height: 70px;` + `transform: translate(${originX}px, ${originY}px) rotate(${direction}deg);` + `transform-origin: 34px 41px; pointer-events: none;` + 'z-index: 1000005;'; lightImage.src = Textures.Light; const id = uid(); this.lights[id] = { element: lightImage, x: originX, y: originY }; this.drawLightBackground(); let filterGreen = false; setInterval(() => { lightImage.style.filter = filterGreen ? 'hue-rotate(90deg) brightness(1.5)' : ''; filterGreen = !filterGreen; }, 700); } drawLightBackground() { const canvas = this.mainCanvas; const viewBox = canvas.getBoundingClientRect(); const ctx = this.ctx; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.strokeStyle = '#3D5C3A'; ctx.lineWidth = 1; ctx.moveTo(0, 70); ctx.beginPath(); for (const particleId in this.lights) { const particle = this.lights[particleId]; ctx.lineTo(((particle.x + 34) / viewBox.width) * 1280, ((particle.y + 41) / viewBox.height) * 720); ctx.moveTo(((particle.x + 34) / viewBox.width) * 1280, ((particle.y + 41) / viewBox.height) * 720); ctx.stroke(); } } clearSnow() { for (const particleId in this.snowParticles) { const particle = this.snowParticles[particleId]; const element = particle.element; element.remove(); delete this.snowParticles[particleId]; } } addPresent() { } } module.exports = Extension;