saji / GetFlow presentation mode

// ==UserScript==
// @name         GetFlow presentation mode
// @namespace    http://redrice.io/
// @version      0.2
// @description  Enter presentation mode when entering fullscreen. Usefull for standups and such.
// @author       Marek ‘saji’ Augustynowicz
// @copyright    2019, Marek Augustynowicz
// @license      MIT
// @match        https://app.getflow.com/organizations/*
// @updateURL    https://openuserjs.org/meta/saji/GetFlow_presentation_mode.meta.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const LC_NAME = 'DAILY_TIMERS';
    const TIMER_ID = 'presentationTimer';
    const SUMMARY_ID = 'timesSummary';
    let start = null;
    let prevElapsed = null;

    function injectStyles ()
    {
        const style = document.createElement('style');
        style.setAttribute('id', 'presentation-mode');
        style.textContent = `
            :-webkit-full-screen #app-header-wrap,
            :-webkit-full-screen .list-view-header-container
            {
                display: none;
            }

            :-webkit-full-screen .main-content-view
            {
                top: 0 !important;
            }

            :-webkit-full-screen
            {
                font-size: 1.25em;
            }
            :-webkit-full-screen *
            {
                font-size: 1em !important;
            }

            :-webkit-full-screen  .content-view[data-view-mode="column"] .task-list-section
            {
                width: calc(100vw / 6);
            }

            .DAILY_TIMER__summary dt,
            .DAILY_TIMER__summary dd
            {
                display: inline;
            }
            .DAILY_TIMER__summary dt::after
            {
                content: ': ';
            }
            .DAILY_TIMER__summary dd::after
            {
                content: '\\A';
                white-space: pre;
            }

            .DAILY_TIMER__summaryDT--last,
            .DAILY_TIMER__summaryDD--last
            {
                background-color: #fa8b54;
            }
        `;
        document.getElementsByTagName('head')[0].appendChild(style);
    }

    function removeTimer ()
    {
        const timer = document.getElementById(TIMER_ID)
        if (timer) {
            timer.remove();
        }
    }

    function addTimer ()
    {
        removeTimer();

        const timer = document.createElement('aside');
        timer.id = TIMER_ID;

        timer.style = `
            pointer-events: none;
            opacity: 0.25;
            font: 3em/1 Iosevka, monospace !important;
            position: fixed;
            z-index: 10000;
            bottom: 1ex;
            right: 1ex;
            padding: 0.1ex 0.2ex 0;
            background: white;
            border-radius: 0.5ex;
        `;

        document.body.appendChild(timer);

        const times = JSON.parse(localStorage.getItem(LC_NAME) || '{}');
        prevElapsed = times[getTodayDateString()] || 0;

        start = performance.now();
        updateTimer(timer);
    }

    function formatTime (elapsed)
    {
        elapsed = parseInt(elapsed / 1000, 10);
        const sec = elapsed % 60;
        const min = (elapsed - sec) / 60;

        return [
            String(min).padStart(2, '0'),
            String(sec).padStart(2, '0')
        ].join(':');
    }

    function getTodayDateString ()
    {
        return (new Date).toLocaleDateString('pl-PL');
    }

    function updateTimer (timer)
    {
        if (! timer.parentElement || !start) {
            return;
        }

        const elapsed = parseInt(performance.now() - start, 10);
        timer.textContent = formatTime(elapsed + prevElapsed);
        requestAnimationFrame(() => updateTimer(timer));
    }

    function stopTimer ()
    {
        if (start)
        {
            const elapsed = parseInt(performance.now() - start, 10);
            const times = JSON.parse(localStorage.getItem(LC_NAME) || '{}');
            times[getTodayDateString()] = (times[getTodayDateString()] || 0) + elapsed;
            localStorage.setItem(LC_NAME, JSON.stringify(times));
        }

        start = null;
        prevElapsed = null;
    }


    function addSummary ()
    {
        const dialog = document.createElement('dialog');
        dialog.id = SUMMARY_ID;
        dialog.style = `
            white-space: pre;
            top: 50%;
            transform: translateY(-50%);
            z-index: 65000;
            font-size: 4em;
            font-family: monospace;
        `;
        const dl = document.createElement('dl');
        dl.classList.add('DAILY_TIMER__summary');
        dialog.appendChild(dl);
        const times = JSON.parse(localStorage.getItem(LC_NAME) || '{}');
        const timesEntries = Object.entries(times);
        timesEntries.forEach(
            ([date, time], idx) => {
                let emoji;
                if (time < 10 * 60 * 1000) {
                    emoji = '🎉';
                } else if (time < 15 * 60 * 1000) {
                    emoji = '😒';
                } else if (time < 20 * 60 * 1000) {
                    emoji = '😖';
                } else {
                    emoji = '😱';
                }
                const dt = document.createElement('dt');
                dt.textContent = date;
                const dd = document.createElement('dd');
                dd.textContent = formatTime(time);

                const emojiNode = document.createTextNode(` ${emoji}`);
                dd.appendChild(emojiNode);

                if (idx === timesEntries.length - 1)
                {
                    dt.classList.add('DAILY_TIMER__summaryDT--last');
                    dd.classList.add('DAILY_TIMER__summaryDD--last');
                }

                dl.appendChild(dt);
                dl.appendChild(dd);
            }
        );

        const closeButton = document.createElement('button');
        closeButton.type = 'button';
        closeButton.textContent = '×';
        closeButton.style = `
            position: absolute;
            top: 1ex;
            right: 1ex;
            border: none;
            background; black;
            color: white;
        `;
        closeButton.addEventListener('click', () => removeSummary());
        dialog.appendChild(closeButton);

        document.body.appendChild(dialog);
        dialog.showModal();
    }

    function removeSummary ()
    {
        const summary = document.getElementById(SUMMARY_ID);
        if (summary) {
            summary.remove();
        }
    }


    function handleFullscreenEnter ()
    {
        removeSummary();
        addTimer();
        window.addEventListener('beforeunload', stopTimer);
    }

    function handleFullscreenLeave ()
    {
        stopTimer();
        removeTimer();
        addSummary();
        window.removeEventListener('beforeunload', stopTimer);
    }


    injectStyles();

    document.addEventListener('fullscreenchange', () => {
        if (!document.isFullScreen && !document.webkitIsFullScreen)
        {
            handleFullscreenLeave();
        }
    });

    document.addEventListener('keydown', (event) => {
        if (event.key === 'F11')
        {
            event.preventDefault();
            if (document.isFullScreen || document.webkitIsFullScreen)
            {
                document.exitFullscreen();
                handleFullscreenLeave();
            }
            else
            {
                document.documentElement.requestFullscreen();
                handleFullscreenEnter();
            }
        }
    });
})();