<repo_name>maweiche/Animates<file_sep>/public/javascript/login.js
// Get the modal
var loginModal = document.getElementById("loginModal");
var signupModal = document.getElementById("signupModal");
var isSignupActive =false; 
var isLoginActive =false; 
async function loginFormHandler(event) {
    event.preventDefault();

    const username = document.querySelector('#username-login').value.trim();
    const password = document.querySelector('#password-login').value.trim();

    if (username && password) {
        const response = await fetch('/api/users/login', {
            method: 'post',
            body: JSON.stringify({
                username,
                password
            }),
            headers: { 'Content-Type': 'application/json' }
        });

        if (response.ok) {
            document.location.replace('/');
        } else {
            alert('Failed to log in');
        }
    }
}
//login modal
function loginDisplay(event) {
    event.preventDefault()
    console.log("login modal display activated")
    isLoginActive = true; 
    loginModal.style.display = "inline"
}
//signup modal
function signupDisplay(event) {
    event.preventDefault()
    console.log("signup modal activated"); 
    isSignupActive = true; 
    signupModal.style.display = "inline"
}

// Hides login modal by clicking on window
// window.onclick = (function (event) {
//     event.preventDefault(); 
//     //console.log(event.target, loginModal, signupModal); 
//     if(isLoginActive){ 
//         signupModal.style.display = "none"
//         isLoginActive = false; 
//         console.log("deactivate login ", isLoginActive); 
//     }else { 
//         loginModal.style.display = "none"
//     }

//     if(isSignupActive){ 
//         loginModal.style.display = "none"
//         isSignupActive = false; 
//         console.log("deactivate signup ", isSignupActive); 
//     }else { 
//         signupModal.style.display = "none"
//     }


//     // if (event.target == signupModal) {
//     //     signupModal.style.display = "none"
//     // }
// })

//click to redirect to signup modal
if (document.querySelector('#signupRedirect') !== null) {
document.querySelector('#signupRedirect').addEventListener('click', signupDisplay);
}

//Hides signup modal by clicking on window
// window.onclick = (function(event){
//     if (event.target == signupModal){
//       signupModal.style.display="none"
//     }
//   })
// console.log(document.querySelector('#login'));
if (document.querySelector('#login') !== null) {
    document.querySelector('#login').addEventListener('click', loginDisplay);
}
// document.querySelector('#signup').addEventListener('click', signupDisplay);
document.querySelector('#submitLogin').addEventListener('click', loginFormHandler);<file_sep>/README.md
# Animates

For this project we used Bootstrap, CSS, Javascript, Two.js, and local storage on the front-end, and Node.js, Express.js, NPM, MySql, Sequelize ORM, Handlebars, JQuery, and GIF Encoder on the backend to create an interactive drawing application. We researched and utilized a variety of technologies (listed below) in order to make the app interactive, useful, and fun. We used a collaborative approach to the creation of this app by using pair programming. We maintained an organized task flow by utilizing Github's project cards, issue submissions and branch protection rules so that we could review and approve updates to the app.

## App Overview

This app allows users to log in or sign up using a username and login or by providing and email and creating a username and password. Once logged in, the user may direct themselves using the navigation panel on the left. From the navigation bar the user can access the explore page in which they can make animations. 

## User Stories

<img src="/images/userstory1.png" alt="Screenshot of User Stories">  
<img src="/images/userstory2.png" alt="Screenshot of User Stories">  
<img src="/images/userstory3.png" alt="Screenshot of User Stories">  

## Built With

* [Visual Studio Code](https://code.visualstudio.com/) - to create and modify the code on my local device
* [Bootstrap](https://getbootstrap.com/) - to structure html elements
* [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) - for styling of handlebars files
* [Javascript](https://www.javascript.com/) - for routing and functionality
* [jQuery](https://jquery.com/) - to add actions to the page
* [Two.js](https://two.js.org/) - to contruct an animation tool
* [Node.js](https://nodejs.org/en/) - to run our server
* [NPM](https://www.npmjs.com/) - to utilize packages
* [Express.js](https://expressjs.com/) - for web framework
* [MySql](https://www.mysql.com/) - for database access
* [Sequelize ORM](https://sequelize.org/) - For SQL server access
* [Handlebars](https://handlebarsjs.com/) - For design templating
* [GIF Encoder (svg2img)](https://www.npmjs.com/package/svg2img) - for in-memory conversion of images to svg
* [Git](https://git-scm.com/) - to track changes and push commits
* [GitHub](github.com) - to host the repository and deploy with GitHub pages
* [Heroku](Heroku.com) - to host the repository and deploy 

## App Visual

<img src="images/create-screenshot.png" alt="Screenshot of Drawing Tool/Application"/> 

<img src="images/main-screenshot.png" alt="Screenshot of Drawing Tool/Application"/> 


## Deployed Link

https://peaceful-cliffs-69062.herokuapp.com/


## Future Developments

We plan to develop further a more extensive Explore page. We also plan to add more social media components such as a like feature and a friends list. A galleries feature will eventually have a polished interactive user interface. 


## Repositories and Links

https://github.com/amackenzie26/Animates
https://peaceful-cliffs-69062.herokuapp.com/
https://amackenzie26.github.io/Animates/

## Authors

* <NAME> [https://www.linkedin.com/in/adamkruschwitz/]
* <NAME> [https://www.linkedin.com/in/mattweichel/]
* <NAME> [https://www.linkedin.com/in/alec-mackenzie-5119a5b8/]
<file_sep>/models/Animation.js

const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/connection');

class Animation extends Model {}

Animation.init(
    {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            allowNull: false
        },
        title: {
            type: DataTypes.STRING(50),
            defaultValue: 0,
            allowNull: false
        },
        path: { // The path from the public directory to the 
            type: DataTypes.STRING(150),
            allowNull: false
        },
        playbackSpeed: {
            type: DataTypes.INTEGER,
            allowNull: false,
        },
        author_id: {
            type: DataTypes.INTEGER,
            references: {
                model: 'user',
                key: 'id'
            }
        }
    },
    {
        sequelize,
        timestamps: false,
        freezeTableName: true,
        underscored: true,
        modelName: 'animation'
    });

module.exports = Animation;<file_sep>/config/connection.js
//import sequelize
const Sequelize = require('sequelize');

require('dotenv').config();

let sequelize;

//if deployed on heroku
if(process.env.JAWSDB_URL) {
    sequelize = new Sequelize(process.env.JAWSDB_URL);
} else {
    //localhost
    sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, {
        host: 'localhost',
        dialect: 'mysql',
        port: 3306
    });
}
//export
module.exports = sequelize;<file_sep>/db/schema.sql
DROP DATABASE IF EXISTS animates_db;

CREATE DATABASE animates_db;<file_sep>/controllers/animations.js
const router = require('express').Router();
const { response } = require('express');
const { Animation } = require('../models');

router.get('/create', async (req, res) => {
    try {
    res.render('create-animation', {
        loggedIn: req.session.loggedIn
    });
    } catch (err) {
        console.log(err);
    }
});

router.get('/:id', async (req, res) => {
    try {
        const animation = await Animation.findByPk(req.params.id);
        const animationJSON = await animation.get({ plain: true });
        res.render('animation', {
            loggedIn: req.session.loggedIn,
            animation: animationJSON
        });
    } catch (err) {
        console.log(err);
    }
});

module.exports = router;<file_sep>/public/javascript/drawing-tool.js
const drawSpace = $("#draw-space");
const linewidth = $("#linewidth");
const clearBtn = $("#clear");
const undoBtn = $("#undo");
const redoBtn = $("#redo");
const framesContainer = $("#frames");
const addFrameBtn = $("#add-frame");
const playBtn = $("#play");
const stopBtn = $("#stop");
const FPS = $("#fps");
const saveBtn = $("#save");
const loadBtn = $("#load");
const loadData = $("#data");
const title = $("#title");
let animation;


// Stack
let undoHistory = [];

const lastPosition = new Two.Anchor(0, 0);

// var two = new Two({
//     width: drawSpace.width(),
//     height: drawSpace.height()
// });
var frames = new LinkedList();
let curFrame = 0;
let line = null;

// console.log(drawSpace[0]);
addFrame();

// two.appendTo(drawSpace[0]); // Get's the DOM Element from the jquery object
// two.add(frames[0]);

function startDraw(event) {

    // Set initial position
    const x = event.clientX - $(event.target).closest('#draw-space').offset().left;
    const y = event.clientY - $(event.target).closest('#draw-space').offset().top;
    lastPosition.set(x, y);

    // Add event listeners
    drawSpace.mouseup(endDraw);
    // drawSpace.mouseout(endDraw); // Doesn't work, triggers when going over another line for whatever reason.
    drawSpace.mousemove(draw);
}

function draw(event) {
    // Get the current position as a Two.Anchor
    const x = event.clientX - $(event.target).closest('#draw-space').offset().left;
    const y = event.clientY - $(event.target).closest('#draw-space').offset().top;
    const curPosition = new Two.Anchor(x, y);
    
    // If a line hasn't been created, start one. otherwise, add the new position to the line.
    const frame = frames.getIndex(curFrame);
    // console.log(frame);
    if(!line) {
        
        line = frame.makeCurve([lastPosition.clone(), curPosition.clone()], true); // Make sure to clone these so that the array has no shallow copies
        line.noFill();
        line.stroke = '#333';
        line.linewidth = linewidth.val();
        line.translation.clear();
        // Adding an offset to account for a problem with drawing.
        line.translation.set(-10, -35);

        // For some reason two.js adds 2 vertices near 0,0 by default to the start of each curve. This removes those.
        line.vertices.shift();
        line.vertices.shift();

        // Sets the end of each line to be a half circle.
        line.cap = "round";
        console.log(line);
        // Add this line to the current frame
        frame.add(line);
        
        // Clear the undo history
        undoHistory = [];
    } else {
        line.vertices.push(curPosition);
    }
    lastPosition.set(curPosition.x, curPosition.y);
    frame.update();
}

function endDraw(event) {
    // Remove event listeners
    drawSpace.off('mouseup');
    // drawSpace.off('mouseout');
    drawSpace.off('mousemove');

    // set the line to null so a new line is created on the next click
    line = null;
}

function addFrame() {
    // Create the new frame and add it to the scene
    const frame = new Two({
        width: drawSpace.width(),
        height: drawSpace.height()
    });
    frame.appendTo(drawSpace[0]); // Get's the DOM Element from the jquery object
    frame.add(new Two.Group());
    curFrame = frames.size();
    frames.push(frame);
    console.log(frame);

    // update display
    presentFrame(frames.getIndex(curFrame));

    // Add new frame button to the page
    addButton(curFrame);

    // TODO - add delete frame button
    
    return frame;
}

function addButton(i) {
    const button = $("<button>");
    button.addClass("frame-button");
    button.attr("data-frame", i);
    framesContainer.append(button);

    // add frame event listeners
    button.on('click', (event) => {
        const id = parseInt(event.target.dataset.frame, 10);
        console.log(id);
        curFrame = id;
        presentFrame(frames.getIndex(id));
    });
}

function presentFrame(frame) {
    hideFrames();
    frame.appendTo(drawSpace[0]); // Get's the DOM Element from the jquery object
    frame.update();
}

function hideFrames(frame) {
    drawSpace.empty();
    
}

drawSpace.on('mousedown', startDraw);

// Clear button
clearBtn.on('click', (event) => {
    const confirmed = confirm("Are you sure you want to clear your frame?");
    if(confirmed) {
        const frame = frames.getIndex(curFrame)
        // Remove all children from the current group
        frame.clear();
        // Clear undo history
        undoHistory = [];
        frame.update();
    }
});

// Undo button
undoBtn.on('click', (event) => {
    const frame = frames.getIndex(curFrame);

    if(frame.scene.children.length > 1) {
        const undid = frame.scene.children.pop();
        // console.log(undid);
        undoHistory.push(undid);
        frame.update();
    }
});

// Redo button
redoBtn.on('click', (event) => {
    const frame = frames.getIndex(curFrame);
    const redid = undoHistory.pop();
    if(redid) {
        frame.scene.children.push(redid);
        frame.update();
    }
});

// Add frame button
addFrameBtn.on('click', addFrame);

// Add animation loop
function startAnimation() {
    console.log('starting animation...')
    if(!animation) {
        console.log('setting animation interval...');
        const framerate = Math.floor(1000.0 / FPS.val());
        animation = setInterval(() => {
            curFrame += 1;
            if(curFrame === frames.size()) curFrame = 0;
            presentFrame(frames.getIndex(curFrame));
        }, framerate);
    }
    
}

function stopAnimation() {
    clearInterval(animation);
    animation = null;
}

// Set play and stop button controls
playBtn.on('click', startAnimation);
stopBtn.on('click', stopAnimation);

// Save Animation
async function save() {
    // Get the rendered SVG from the DOM
    const svgs = [];
    frames.forEach((frame) => {
        presentFrame(frame);
        // drawSpace.children();
        svgs.push(drawSpace.html());
    });

    // console.log(svgs.toString());

    const response = await fetch('/api/animations', {
        method: 'POST',
        body: JSON.stringify({
            "animationData": svgs.toString(),
            "playbackSpeed": Math.floor(1000.0 / FPS.val()),
            title: title.val(),
            width: $("#draw-space").width(),
            height: $("#draw-space").height()
        }),
        headers: { 'Content-Type': 'application/json' }
    });

    if(response.ok) {
        // Should redirect to the post page
        document.location.replace('/');
    } else {
        alert('an error occured!');
    }
}

saveBtn.on('click', save);

async function load(animationData) {
    const svgs = animationData.split(",");
    // TODO: add xmlns='http://www.w3.org/2000/svg'
    frames.clear();
    drawSpace.empty();
    framesContainer.empty();
    curFrame=-1;


    svgs.forEach(svg => {
        const frame = addFrame();
        frame.interpret($(svg)[0]);
    });
}

loadBtn.on('click', (event) => {
    load(loadData.val());
});