UNPKG

slide-cast

Version:

Web presentation tool with video recording included

231 lines (201 loc) 6.1 kB
import Symbiote from '@symbiotejs/symbiote'; import template from './SlideIt.html.js'; import styles from './SlideIt.css.js'; import { md2html } from '@jam-do/jam-tools/iso/md2html.js'; import { randClr } from '../../common-css/styles.css.js'; import { PubSub } from '@symbiotejs/symbiote'; const intObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { let appData = PubSub.getCtx('APP'); if (appData.read('currentSlide') !== entry.target) { entry.target.focus(); } } }); }, { rootMargin: '0px', threshold: 0.2, }); window.addEventListener('scroll', (e) => { let targets = document.querySelectorAll('slide-it'); intObserver.disconnect(); window.setTimeout(() => { targets.forEach((target) => { intObserver.observe(target); }); }, 200); }); class SlideIt extends Symbiote { renderShadow = true; gradClr1 = randClr(); gradClr2 = randClr(); init$ = { caption: '', number: '', } nextSlide() { /** @type {SlideIt} */ let nextSlide = this.nextElementSibling; if (nextSlide && nextSlide instanceof SlideIt) { nextSlide.focus(); } } prevSlide() { /** @type {SlideIt} */ let prevSlide = this.previousElementSibling; if (prevSlide && prevSlide instanceof SlideIt) { prevSlide.focus(); } } focus() { // super.focus(); this.$['APP/currentSlide'] = this; this.scrollIntoView({ behavior: 'smooth', block: 'center' }); document.body.style.setProperty('--grad-clr-1', this.gradClr1); document.body.style.setProperty('--grad-clr-2', this.gradClr2); document.body.style.setProperty('--slide-accent-clr', this.gradClr1); document.body.style.setProperty('--slide-sub-clr', this.gradClr2); this.$['APP/hideVideoSpot'] = this.hasAttribute('hide-video-spot'); } get canvasRect() { return this.canvas.getBoundingClientRect(); } get rect() { return this.getBoundingClientRect(); } #draw(e) { if (e.touches) { e = e.touches[0]; } // draw line to the current mouse position let x = e.clientX - this.canvasRect.left; let y = e.clientY - this.canvasRect.y; this.ctx.beginPath(); this.ctx.moveTo(this.x || x, this.y || y); this.ctx.lineTo(x, y); this.ctx.strokeStyle = this.$['APP/drawColor']; this.ctx.lineWidth = this.$['APP/eraseMode'] ? 10 : 5; this.ctx.globalCompositeOperation = this.$['APP/eraseMode'] ? 'destination-out' : 'source-over'; this.ctx.stroke(); this.x = x; this.y = y; } #initDrawing() { this.canvas = this.ref.canvas; this.ctx = this.canvas.getContext('2d'); this.canvas.width = this.rect.width; this.canvas.height = this.rect.height; let draw = this.#draw.bind(this); this.canvas.addEventListener('mousedown', (e) => { this.canvas.addEventListener('mousemove', draw); }); this.canvas.addEventListener('mouseup', () => { this.canvas.removeEventListener('mousemove', draw); this.x = null; this.y = null; }); this.canvas.addEventListener('touchstart', (e) => { e.preventDefault(); this.canvas.addEventListener('touchmove', draw); }); this.canvas.addEventListener('touchend', (e) => { e.preventDefault(); this.canvas.removeEventListener('touchmove', draw); this.x = null; this.y = null; }); this.canvas.addEventListener('touchcancel', (e) => { e.preventDefault(); this.canvas.removeEventListener('touchmove', draw); this.x = null; this.y = null; }); this.canvas.addEventListener('mouseleave', (e) => { this.canvas.removeEventListener('mousemove', draw); this.x = null; this.y = null; }); this.canvas.addEventListener('contextmenu', (e) => { this.canvas.removeEventListener('mousemove', draw); this.x = null; this.y = null; }); } clearDrawing() { this.ctx.clearRect(0, 0, this.rect.width, this.rect.height); } renderCallback() { this.sub('importJSDWA', (val) => { if (!val) { return; } if (!val.includes('//')) { if (val.startsWith('/')) { val = window.location.origin + val; } else { val = window.location.origin + '/' + val; } } try { import(val).then((module) => { this.innerHTML += module.default; }); } catch (e) { console.error('Failed to import slide', val, e); } }); this.sub('importMd', async (val) => { if (!val) { return; } try { let md = await (await fetch(val)).text(); this.innerHTML += await md2html(md); } catch (e) { console.error('Failed to import slide', val, e); } }); window.addEventListener('keydown', (e) => { if (this.$['APP/currentSlide'] !== this) return; if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { e.preventDefault(); this.nextSlide(); } if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { e.preventDefault(); this.prevSlide(); } }); window.addEventListener('resize', (e) => { this.canvas.width = this.rect.width; this.canvas.height = this.rect.height; }); this.addEventListener('click', (e) => { this.focus(); }); window.requestAnimationFrame(() => { this.#initDrawing(); }); if (!this.hasAttribute('slide-number') && this.$.caption) { window.requestIdleCallback(() => { let idx = [...document.querySelectorAll('slide-it:not([slide-number])')].indexOf(this); this.$.number = (idx + 1) + '. '; }); } intObserver.observe(this); } disconnectedCallback() { super.disconnectedCallback(); intObserver.unobserve(this); } } SlideIt.bindAttributes({ caption: 'caption', 'slide-number': 'number', 'import-jsdwa': 'importJSDWA', 'import-md': 'importMd', }); SlideIt.template = template; SlideIt.rootStyles = styles; SlideIt.reg('slide-it');