camman3d/jerboa.js

View on GitHub
src/jerboa.js

Summary

Maintainability
C
1 day
Test Coverage
import { getGlobalOffset, getRelativeOffset, getSelector, resolveContainer } from './positioning';
import { addEventListener, emit } from './events';
import { addBox, addText, addTextField, closeInfoBox, createInfoBox, createMarker, resetPositioning, annotationPositions } from './html-manip';
import state from './state';
import md5 from 'blueimp-md5';

/*
    Annotating Functionality Methods
    --------------------------------
 */
function generatePayload(event) {
    const container = resolveContainer(event.target, state.currentStrategy);
    if (!container) {
        return;
    }
    const selector = getSelector(event.target);
    const containerSelector = getSelector(container);
    let offset = getRelativeOffset(event.target, container);
    offset['x'] += event.offsetX;
    offset['y'] += event.offsetY;
    const rect = container.getBoundingClientRect();

    const positionObject = {
        positioning: state.currentPositioning,
        target: selector,
        container: containerSelector,
        containerSize: {
            width: rect.width,
            height: rect.height
        },
        windowSize: {
            width: window.innerWidth,
            height: window.innerHeight
        },
        offset
    };
    return {
        time: new Date(Date.now() - (new Date().getTimezoneOffset() * 60000)).toISOString(),
        position: positionObject,
        url: state.url,
        data: state.additionalData,
        user: state.currentUser,
        userId: state.currentUserId,
        pageId: state.pageId,
        comments: []
    };
}

function clickListener(event) {
    if (!state.active) {
        return;
    }
    closeInfoBox();
    if (state.feedbackBoxOpen) {
        return;
    }

    let payload = generatePayload(event);
    if (!payload) {
        return;
    }
    emit('preAnnotate', payload);

    state.feedbackBoxOpen = true;
    const spot = createMarker(payload);
    const boxParts = addBox(spot, false);
    const parts = addTextField(boxParts.container, 'Enter comment:');

    parts.cancel.addEventListener('click', () => {
        emit('cancel', payload);
        state.feedbackBoxOpen = false;
        document.body.removeChild(spot);
    });

    parts.save.addEventListener('click', () => {
        // payload.comments = [
        //     {
        //         text: parts.textarea.value,
        //         user: state.currentUser,
        //         time: new Date().toISOString(),
        //         replies: []
        //     }
        // ];
        if (parts.textarea.value) {
            payload.text = parts.textarea.value;
            emit('save', payload);
            state.feedbackBoxOpen = false;
            spot.removeChild(boxParts.box);
            createInfoBox(spot, payload);
        } else {
            emit('cancel', payload);
            state.feedbackBoxOpen = false;
            document.body.removeChild(spot);
        }
    });
}

const strategies = {
    global: e => {
        return e.tagName === 'BODY';
    },
    byClass: className => e => {
        return e.classList.contains(className);
    }
};

/*
    Create Toggle Button
    -------------
 */

function createToggleButton() {
    let bottom = '25px';
    let left = '25px';

    let buttonContainer = document.createElement('div');
    buttonContainer.classList.add('toggle-button-container');

    let buttonLabel = document.createElement('div');
    buttonLabel.classList.add('toggle-button-text');
    buttonLabel.textContent = 'Feedback Off';
    buttonContainer.appendChild(buttonLabel);

    let buttonDiv = document.createElement('div');
    buttonDiv.classList.add('toggle-button');
    buttonContainer.appendChild(buttonDiv);

    let button = document.createElement('button');
    buttonDiv.appendChild(button);

    buttonContainer.style.bottom = bottom;
    buttonContainer.style.left = left;
    buttonContainer.style.position = 'fixed';

    let matchState=function(){
        let feedbackSpots = document.getElementsByClassName('feedback-spot');
        if (state.active) {
            buttonDiv.classList.add('toggle-button-selected');
            buttonLabel.textContent = 'Feedback On';
            Array.prototype.forEach.call(feedbackSpots, (feedbackSpotElement) => {
                feedbackSpotElement.classList.remove('off');
            });
        } else {
            buttonDiv.classList.remove('toggle-button-selected');
            buttonLabel.textContent = 'Feedback Off';
            Array.prototype.forEach.call(feedbackSpots, (feedbackSpotElement) => {
                feedbackSpotElement.classList.add('off');
            });
        }
    }
    buttonDiv.addEventListener('click', event => {
        event.preventDefault();
        state.active = !state.active;
        matchState()
        emit('active', state.active);
    });
    matchState()
    document.body.appendChild(buttonContainer);
}


(function() {
    var throttle = function(type, name, obj) {
        obj = obj || window;
        var running = false;
        var func = function() {
            if (running) { return; }
            running = true;
             requestAnimationFrame(function() {
                obj.dispatchEvent(new CustomEvent(name));
                running = false;
            });
        };
        obj.addEventListener(type, func);
    };

    throttle("resize", "optimizedResize");
})();

/*
    Return object
    -------------
 */

export default {
    init(options) {
        console.log('options', options);
        options = options || {};
        state.active = options.active || false
        state.currentStrategy = options.strategy || strategies.global;
        state.currentPositioning = options.positioning || 'PERCENT';
        state.currentUser = options.currentUser;
        state.currentUserId = options.currentUserId;
        state.isAdmin = options.isAdmin || false;
        state.url = window.location.href;
        state.pageId = md5(window.location.href);
        console.log('state', state);
        if (options.data) {
            state.additionalData = options.data;
        }
        if (options.points) {
            options.points.forEach(point => {
                let spot = createMarker(point, true); //loads existing points
                createInfoBox(spot, point);
            });
        }

        document.addEventListener('click', clickListener);
        window.addEventListener("optimizedResize", function() {
            console.log("Resource conscious resize callback!");
            annotationPositions.forEach((annotationPosition) => {
                resetPositioning.apply(null, annotationPosition);
            });
        });
        createToggleButton();
    },

    close() {
        document.removeEventListener('click', clickListener);
    },

    addEventListener,
    strategies
};