├── .bowerrc
├── .gitignore
├── .travis.yml
├── FrontEnd.dia
├── FrontEnd.svg
├── Gruntfile.js
├── README.md
├── bower.json
├── client
├── app.js
├── assets
│ └── images
│ │ ├── ai
│ │ ├── ai-cursors
│ │ │ ├── albus-cursor.ai
│ │ │ ├── arrow.ai
│ │ │ ├── circle.ai
│ │ │ ├── copy.ai
│ │ │ ├── eraser.ai
│ │ │ ├── magnify.ai
│ │ │ ├── move.ai
│ │ │ ├── pan.ai
│ │ │ ├── rectangle.ai
│ │ │ ├── strokeColor.ai
│ │ │ └── text.ai
│ │ ├── ai-favicon
│ │ │ ├── favicon.png
│ │ │ └── logo.ai
│ │ ├── ai-icons
│ │ │ ├── arrow.ai
│ │ │ ├── circle.ai
│ │ │ ├── color.ai
│ │ │ ├── copy.ai
│ │ │ ├── cursor-triangle.ai
│ │ │ ├── draw.ai
│ │ │ ├── eraser.ai
│ │ │ ├── fill.ai
│ │ │ ├── line.ai
│ │ │ ├── magnify.ai
│ │ │ ├── move.ai
│ │ │ ├── pan.ai
│ │ │ ├── path.ai
│ │ │ ├── rectangle.ai
│ │ │ ├── stroke.ai
│ │ │ ├── text.ai
│ │ │ ├── thickness.ai
│ │ │ └── tool.ai
│ │ └── ai-logo
│ │ │ ├── Untitled-1.ai
│ │ │ └── logo.ai
│ │ ├── albus-screenshot-new.png
│ │ ├── arrow.png
│ │ ├── circle.png
│ │ ├── color.png
│ │ ├── color.svg
│ │ ├── copy.png
│ │ ├── cursors
│ │ ├── arrow.png
│ │ ├── circle.png
│ │ ├── copy.png
│ │ ├── eraser.png
│ │ ├── fill.ai
│ │ ├── fill.png
│ │ ├── line.png
│ │ ├── magnify.png
│ │ ├── move.png
│ │ ├── pan.png
│ │ ├── path.png
│ │ ├── rectangle.png
│ │ ├── stroke.png
│ │ ├── strokeColor.png
│ │ └── text.png
│ │ ├── draw.png
│ │ ├── eraser.png
│ │ ├── favicon.png
│ │ ├── favicons
│ │ ├── android-chrome-144x144.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-36x36.png
│ │ ├── android-chrome-48x48.png
│ │ ├── android-chrome-72x72.png
│ │ ├── android-chrome-96x96.png
│ │ ├── apple-touch-icon-114x114.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-144x144.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── apple-touch-icon-57x57.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-72x72.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon-precomposed.png
│ │ ├── apple-touch-icon.png
│ │ ├── browserconfig.xml
│ │ ├── favicon-16x16.png
│ │ ├── favicon-194x194.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon-96x96.png
│ │ ├── favicon.ico
│ │ ├── manifest.json
│ │ ├── mstile-144x144.png
│ │ ├── mstile-150x150.png
│ │ ├── mstile-310x150.png
│ │ ├── mstile-310x310.png
│ │ ├── mstile-70x70.png
│ │ └── safari-pinned-tab.svg
│ │ ├── fill.png
│ │ ├── frontend-dataflow.png
│ │ ├── line.png
│ │ ├── logo.png
│ │ ├── magnify.png
│ │ ├── move.png
│ │ ├── pan.png
│ │ ├── path.png
│ │ ├── rectangle.png
│ │ ├── stroke.png
│ │ ├── text.png
│ │ ├── thickness.png
│ │ └── tool.png
├── directives
│ ├── board.js
│ └── toolbar.js
├── dist
│ └── style.min.css
├── index.html
├── js
│ ├── raphael-min.js
│ ├── raphael.js
│ ├── resize-handler.js
│ ├── resize-handler.min.js
│ ├── socket.io.js
│ └── socket.io.min.js
├── services
│ ├── auth.js
│ ├── board-data.js
│ ├── broadcast.js
│ ├── event-handler.js
│ ├── input-handler.js
│ ├── leap.js
│ ├── receive.js
│ ├── shape-builder.js
│ ├── shape-editor.js
│ ├── shape-manipulation.js
│ ├── snap.js
│ ├── sockets.js
│ ├── token.js
│ ├── visualizer.js
│ └── zoom.js
├── styles
│ └── style.css
└── views
│ ├── board.html
│ ├── layers.html
│ └── toolbar.html
├── deploy
├── package.json
├── server
├── board.js
├── db
│ └── config.js
├── favicon.ico
├── rooms.js
├── server.js
├── sockets.js
└── utils
│ └── util.js
└── test
└── test.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "client/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
4 | # Bower
5 | bower_components
6 | client/lib
7 |
8 | sandbox.js
9 | dump.rdb
10 | /ssh
11 | .elasticbeanstalk
12 | Dockerfile
13 |
14 | *.log
15 |
16 | whiteboard.zip
17 | deploy
18 |
19 | # min
20 | client/dist/*
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.8'
4 | - '0.10'
5 |
--------------------------------------------------------------------------------
/FrontEnd.dia:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/FrontEnd.dia
--------------------------------------------------------------------------------
/FrontEnd.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
261 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
4 |
5 | var pkg = grunt.file.readJSON('package.json');
6 |
7 | grunt.initConfig({
8 | pkg: pkg,
9 |
10 | concat: {
11 | options: {
12 | separator: ';'
13 | },
14 | dist: {
15 | src: ['client/app.js', 'client/directives/*.js', 'client/services/*.js'],
16 | // the location of the resulting JS file
17 | dest: 'client/dist/whiteboard.js'
18 | }
19 | },
20 |
21 | uglify: {
22 | options: {
23 | mangle: false,
24 | compress: true
25 | },
26 | target: {
27 | files: {
28 | 'client/dist/whiteboard.min.js': ['client/dist/whiteboard.js']
29 | }
30 | }
31 | },
32 |
33 | cssmin: {
34 | options: {
35 | shorthandCompacting: false,
36 | roundingPrecision: -1
37 | },
38 | target: {
39 | files: {
40 | 'client/dist/style.min.css': ['client/style/style.css']
41 | }
42 | }
43 | },
44 |
45 | watch: {
46 | scripts: {
47 | files: ['client/app.js', 'client/directives/*.js', 'client/services/*.js'],
48 | tasks: ['concat', 'uglify']
49 | },
50 | css: {
51 | files: ['client/styles/style.css'],
52 | tasks: ['cssmin']
53 | }
54 | }
55 |
56 | });
57 |
58 | grunt.registerTask('release', 'Concats, Minifies', [
59 | 'concat',
60 | 'uglify'
61 | ]);
62 | };
63 |
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Albus
2 |
3 | > Albus is a real-time collaborative whiteboard. Key features include:
4 |
5 | - suggested snapping points to shape corners and midpoints using k-d trees;
6 | - automatic path smoothing for freehand drawing;
7 | - path autofill upon closing;
8 | - infinite board panning and zooming;
9 | - shape moving, copying, and erasing;
10 | - color and fill customization; and
11 | - responsive marching menu
12 |
13 |
14 |
15 |
16 | Data flows onto the board in the following path:
17 |
18 | 
19 |
20 | ## Team
21 |
22 | - __Product Owner__: Haley Bash
23 | - __Scrum Master__: Lorenzo De Nobili
24 | - __Development Team Members__: Christian Everett, Lorenzo De Nobili, Rory Sametz, Haley Bash
25 |
26 | ## Table of Contents
27 |
28 | 1. [Usage](#Usage)
29 | 1. [Technologies Used](#technologies-used)
30 | 1. [Requirements](#requirements)
31 | 1. [Development](#development)
32 | 1. [Installing Dependencies](#installing-dependencies)
33 | 1. [Tasks](#tasks)
34 | 1. [Contributing](#contributing)
35 |
36 | ## Usage
37 |
38 | Visit the page, currently hosted on [albus.io](http://albus.io)
39 |
40 | ## Technologies Used
41 |
42 | - [AngularJS](http://angularjs.org)
43 | - [Node](https://nodejs.org/)
44 | - [Express](http://expressjs.com/)
45 | - [Redis](http://redis.io/)
46 | - [Raphael](http://raphaeljs.com)
47 | - [Socket.io](http://socket.io/)
48 | - [Heroku Deployment](https://www.heroku.com/)
49 |
50 | ## Requirements
51 |
52 | - [Node 0.10.x](https://nodejs.org/en/download/)
53 | - [Redis](http://redis.io/download)
54 |
55 | ## Development Process
56 |
57 | ### Step 0: Fork and clone the repository from GitHub
58 |
59 | ### Step 1: Installing Dependencies
60 |
61 | Run the following in the command line, from within the repository:
62 |
63 | ```sh
64 | bower install
65 | npm install
66 | ```
67 |
68 | ### Step 2: Running Locally
69 |
70 | Run the Redis database from the command line, in one tab:
71 | ```sh
72 | redis-server
73 | ```
74 |
75 | Run the server in the other tab using node:
76 |
77 | ```sh
78 | npm run server
79 | ```
80 |
81 | ### Step 3: Making Local Changes
82 |
83 | Each time a change is made, run the following to update the minified files:
84 |
85 | ```sh
86 | grunt release
87 | ```
88 |
89 | ### Step 4: Issuing a Pull Request
90 |
91 | Feel free to add contributions by issuing a pull request to the dev branch of this repo.
92 |
93 | ### Visiting the server
94 |
95 | While node is running, visit the locally running server at [127.0.0.1:3000](127.0.0.1:3000)
96 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whiteboard",
3 | "description": "whiteboard",
4 | "main": "server/server.js",
5 | "authors": [
6 | "Christian Everett "
7 | ],
8 | "license": "ISC",
9 | "homepage": "https://github.com/nahash411/whiteboard",
10 | "moduleType": [
11 | "es6",
12 | "node"
13 | ],
14 | "ignore": [
15 | "**/.*",
16 | "node_modules",
17 | "bower_components",
18 | "client/lib",
19 | "test",
20 | "tests"
21 | ],
22 | "dependencies": {
23 | "angular-route": "~1.4.8",
24 | "angular-socket-io": "~0.7.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard', [
2 | 'btford.socket-io',
3 | 'whiteboard.services.receive',
4 | 'whiteboard.services.broadcast',
5 | 'whiteboard.services.shapebuilder',
6 | 'whiteboard.services.shapeeditor',
7 | 'whiteboard.services.shapemanipulation',
8 | 'whiteboard.services.snap',
9 | 'whiteboard.services.auth',
10 | 'whiteboard.services.token',
11 | 'whiteboard.services.sockets',
12 | 'whiteboard.services.boarddata',
13 | 'whiteboard.services.eventhandler',
14 | 'whiteboard.services.inputhandler',
15 | 'whiteboard.services.zoom',
16 | 'whiteboard.services.leapMotion',
17 | 'whiteboard.services.visualizer',
18 | 'ngRoute'
19 | ])
20 | .config(['$routeProvider', '$locationProvider', '$httpProvider',
21 | function($routeProvider, $locationProvider, $httpProvider) {
22 | $routeProvider
23 | .when('/', {
24 | resolve: {
25 | 'something': function (Sockets, Auth, $location) {
26 | var roomId = Auth.generateRandomId(5);
27 | Sockets.emit('roomId', {roomId: roomId});
28 | $location.path('/' + roomId);
29 | }
30 | }
31 | })
32 | .when('/:id', {
33 | templateUrl: 'views/board.html',
34 | resolve: {
35 | 'somethingElse': function (Sockets, $location) {
36 | Sockets.emit('roomId', {roomId: $location.path().slice(1)});
37 | }
38 | },
39 | authenticate: true
40 | });
41 |
42 | $locationProvider.html5Mode({
43 | enabled: true,
44 | requireBase: false
45 | });
46 | }]);
47 | //
48 |
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/albus-cursor.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/albus-cursor.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/arrow.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/arrow.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/circle.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/circle.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/copy.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/copy.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/eraser.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/eraser.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/magnify.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/magnify.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/move.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/move.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/pan.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/pan.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/rectangle.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/rectangle.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/strokeColor.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/strokeColor.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-cursors/text.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-cursors/text.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-favicon/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-favicon/favicon.png
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-favicon/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-favicon/logo.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/arrow.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/arrow.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/circle.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/circle.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/color.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/color.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/copy.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/copy.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/cursor-triangle.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/cursor-triangle.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/draw.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/draw.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/eraser.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/eraser.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/fill.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/fill.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/line.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/line.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/magnify.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/magnify.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/move.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/move.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/pan.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/pan.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/path.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/path.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/rectangle.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/rectangle.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/stroke.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/stroke.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/text.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/text.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/thickness.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/thickness.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-icons/tool.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-icons/tool.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-logo/Untitled-1.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-logo/Untitled-1.ai
--------------------------------------------------------------------------------
/client/assets/images/ai/ai-logo/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/ai/ai-logo/logo.ai
--------------------------------------------------------------------------------
/client/assets/images/albus-screenshot-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/albus-screenshot-new.png
--------------------------------------------------------------------------------
/client/assets/images/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/arrow.png
--------------------------------------------------------------------------------
/client/assets/images/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/circle.png
--------------------------------------------------------------------------------
/client/assets/images/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/color.png
--------------------------------------------------------------------------------
/client/assets/images/color.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/assets/images/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/copy.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/arrow.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/circle.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/copy.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/eraser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/eraser.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/fill.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/fill.ai
--------------------------------------------------------------------------------
/client/assets/images/cursors/fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/fill.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/line.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/magnify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/magnify.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/move.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/move.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/pan.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/path.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/rectangle.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/stroke.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/strokeColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/strokeColor.png
--------------------------------------------------------------------------------
/client/assets/images/cursors/text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/cursors/text.png
--------------------------------------------------------------------------------
/client/assets/images/draw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/draw.png
--------------------------------------------------------------------------------
/client/assets/images/eraser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/eraser.png
--------------------------------------------------------------------------------
/client/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicon.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/android-chrome-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/android-chrome-144x144.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/android-chrome-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/android-chrome-36x36.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/android-chrome-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/android-chrome-48x48.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/android-chrome-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/android-chrome-72x72.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/android-chrome-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/android-chrome-96x96.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | #da532c
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/assets/images/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/favicon-194x194.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/favicon-194x194.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/favicon-96x96.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/favicon.ico
--------------------------------------------------------------------------------
/client/assets/images/favicons/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Albus",
3 | "icons": [
4 | {
5 | "src": "\/android-chrome-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": 0.75
9 | },
10 | {
11 | "src": "\/android-chrome-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": 1
15 | },
16 | {
17 | "src": "\/android-chrome-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": 1.5
21 | },
22 | {
23 | "src": "\/android-chrome-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": 2
27 | },
28 | {
29 | "src": "\/android-chrome-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": 3
33 | },
34 | {
35 | "src": "\/android-chrome-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": 4
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/client/assets/images/favicons/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/mstile-144x144.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/mstile-150x150.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/mstile-310x150.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/mstile-310x310.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/favicons/mstile-70x70.png
--------------------------------------------------------------------------------
/client/assets/images/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
38 |
--------------------------------------------------------------------------------
/client/assets/images/fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/fill.png
--------------------------------------------------------------------------------
/client/assets/images/frontend-dataflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/frontend-dataflow.png
--------------------------------------------------------------------------------
/client/assets/images/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/line.png
--------------------------------------------------------------------------------
/client/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/logo.png
--------------------------------------------------------------------------------
/client/assets/images/magnify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/magnify.png
--------------------------------------------------------------------------------
/client/assets/images/move.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/move.png
--------------------------------------------------------------------------------
/client/assets/images/pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/pan.png
--------------------------------------------------------------------------------
/client/assets/images/path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/path.png
--------------------------------------------------------------------------------
/client/assets/images/rectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/rectangle.png
--------------------------------------------------------------------------------
/client/assets/images/stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/stroke.png
--------------------------------------------------------------------------------
/client/assets/images/text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/text.png
--------------------------------------------------------------------------------
/client/assets/images/thickness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/thickness.png
--------------------------------------------------------------------------------
/client/assets/images/tool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/assets/images/tool.png
--------------------------------------------------------------------------------
/client/directives/board.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard')
2 | .directive('wbBoard', ['BoardData', 'Broadcast', 'Receive', 'LeapMotion', function (BoardData) {
3 | return {
4 | restrict: 'A',
5 | require: ['wbBoard'],
6 | replace: true,
7 | template:
8 | '' +
9 | '
' +
10 | '
' +
11 | '
',
12 | controller: function (InputHandler) {
13 | this.handleEvent = function (ev) {
14 | InputHandler[ev.type](ev);
15 | }
16 | },
17 | link: function (scope, element, attrs, ctrls) {
18 | var boardCtrl = ctrls[0];
19 | BoardData.createBoard(element);
20 | BoardData.getCanvas().bind('mousedown mouseup mousemove dblclick', boardCtrl.handleEvent);
21 |
22 | $('body').on('keypress', function (ev) {
23 | boardCtrl.handleEvent(ev);
24 | });
25 |
26 | scope.$on('setCursorClass', function (evt, msg) {
27 | // console.log('A')
28 | // var oldTool = BoardData.getCurrentTool();
29 | var svg = BoardData.getCanvas();
30 |
31 | // svg.addClass('A');
32 | svg.attr("class", msg.tool);
33 | // console.log('> ', svg.attr("class").split(' '));
34 | });
35 |
36 | }
37 | }
38 | }]);
39 |
--------------------------------------------------------------------------------
/client/directives/toolbar.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard')
2 | .directive('wbToolbar', ['BoardData', 'Zoom', function (BoardData, Zoom) {
3 | return {
4 | restrict: 'A',
5 | replace: true,
6 | templateUrl: 'views/toolbar.html',
7 | require: ['^wbBoard', 'wbToolbar'],
8 | // scope: {
9 | // wbToolSelect: '@',
10 | // wbZoomScale: '@',
11 | // wbColorSelect: '@'
12 | // },
13 | controller: function ($scope) {
14 |
15 | var fill = [
16 | '#e74c3c',
17 | '#e67e22',
18 | '#f1c40f',
19 | '#1abc9c',
20 | '#2ecc71',
21 | '#3498db',
22 | '#9b59b6',
23 | '#34495e',
24 | '#95a5a6',
25 | '#ecf0f1',
26 | ];
27 |
28 | var stroke = [
29 | '#c0392b',
30 | '#d35400',
31 | '#f39c12',
32 | '#16a085',
33 | '#27ae60',
34 | '#2980b9',
35 | '#8e44ad',
36 | '#2c3e50',
37 | '#7f8c8d',
38 | '#bdc3c7',
39 | ];
40 |
41 | var thickness = [
42 | '10',
43 | '9',
44 | '8',
45 | '7',
46 | '6',
47 | '5',
48 | '4',
49 | '3',
50 | '2',
51 | '1'
52 | ];
53 |
54 | // $scope.colorIconSVG = 'data:image/svg+xml;utf8,';
55 |
56 | $scope.menuStructure = [
57 | ['Draw', ['Path', 'Line', 'Arrow', 'Rectangle', 'Circle', 'Text']],
58 | ['Tool', ['Magnify', 'Eraser', 'Pan', 'Move', 'Copy']],
59 | ['Color', [['Fill', fill], ['Stroke', stroke], ['Thickness', thickness]]]
60 | ];
61 |
62 |
63 |
64 | $scope.$on('toggleAllSubmenu', function (ev, msg) {
65 | if (msg.action === 'hide') {
66 | // console.log('wbToolbar: closing all submenus')
67 | $scope.$broadcast('toggleSubmenu', msg)
68 | }
69 | });
70 |
71 | $scope.$on('resetBackgrounds', function (ev, msg) {
72 |
73 | if (Array.isArray(msg.target)) {
74 | msg.target.forEach(function (target) {
75 | $scope.$broadcast('resetTargetBackground', {target: target});
76 | })
77 | } else {
78 | $scope.$broadcast('resetTargetBackground', msg);
79 | }
80 | })
81 |
82 | },
83 | link: function (scope, element, attrs, ctrls) {
84 |
85 | // var $colorIcon = element.find('.icon-color');
86 |
87 |
88 | scope.$on('activateMenu', function (event, action) {
89 | // console.log(event, options);
90 | if (action === 'show') {
91 | element.addClass('show');
92 | scope.$broadcast('toggleMouseEv', action);
93 | } else {
94 | // scope.$broadcast('test', 'hide');
95 | element.removeClass('show');
96 | scope.$broadcast('toggleMouseEv', action);
97 | }
98 | });
99 |
100 | // scope.$on('changeIconColors', function (event, action) {
101 | // console.log(scope.colorIconSVG)
102 | // console.log(action)
103 | // if (action.type === 'fill') {
104 | // // $colorIcon.find('small-circle').attr({'class': 'small-circle fill-' + action.color.substr(1)})
105 | // $colorIcon.css({'background-image': 'url(' + scope.colorIconSVG + ')'});
106 | // } else {
107 | // $colorIcon.css({'background-image': 'url(' + scope.colorIconSVG + ')'});
108 | // // $colorIcon.find('outer-circle').attr({'class': 'outer-circle stroke-' + action.color.substr(1)})
109 | // }
110 | // });
111 |
112 | }
113 | };
114 | }])
115 | .directive('wbMenuOpener', function () {
116 | return {
117 | restrict: 'C',
118 | replace: false,
119 | require: 'wbMenuOpener',
120 | scope: false,
121 | controller: function ($scope) {
122 |
123 | this.menuHandler = function (attr) {
124 | $scope.$emit('activateMenu', attr);
125 | }
126 |
127 | },
128 | link: function (scope, element, attrs, ctrl) {
129 |
130 | element.bind('mouseover mouseleave', function (ev) {
131 | if (ev.buttons === 0 && ev.type === 'mouseover' && (angular.element(ev.relatedTarget).is('svg') || angular.element(ev.relatedTarget)[0].raphael)) {
132 | // console.log(angular.element(ev.relatedTarget).is('svg'))
133 | // console.log('add class show');
134 | // console.log(ev.buttons)
135 | ctrl.menuHandler('show');
136 | // element.addClass('show');
137 | } else {
138 | // console.log('remove class show');
139 |
140 | // ctrl.menuHandler('hide');
141 |
142 | }
143 |
144 | });
145 | }
146 | };
147 | })
148 | .directive('wbSubmenuOpener', function () {
149 | return {
150 | restrict: 'C',
151 | replace: false,
152 | require: 'wbSubmenuOpener',
153 | controller: function ($scope) {
154 |
155 | this.submenuOpener = function (action) {
156 | //if (action.level === 2) {
157 | this.submenuCloser({action: 'hide', level: action.level});
158 | //}
159 | $scope.$broadcast('toggleSubmenu', action);
160 | }
161 |
162 | this.submenuCloser = function (action) {
163 | // console.log('close?')
164 | $scope.$emit('toggleAllSubmenu', action);
165 | }
166 |
167 | },
168 | link: function (scope, element, attrs, submenuOpenerCtrl) {
169 |
170 | var bindMouseEv = function () {
171 | element.bind('mouseover mouseleave', function (ev) {
172 | // console.log(ev, attrs.wbLevel)
173 | if (ev.type === 'mouseover' && attrs.wbLevel === '2') {
174 | // console.log('Should open submenu', ev);
175 | submenuOpenerCtrl.submenuOpener({action: 'show', level: '2'});
176 | } else if (ev.type === 'mouseover' && attrs.wbLevel === '3') {
177 | // console.log('Should open the color palette!')
178 | // console.log('Should open third level')
179 | submenuOpenerCtrl.submenuOpener({action: 'show', level: '3'});
180 | } else if (ev.type === 'mouseleave' && (angular.element(ev.toElement).hasClass('lvl1') || angular.element(ev.toElement).hasClass('level-one'))) {
181 | // console.log('Should close submenu');
182 | submenuOpenerCtrl.submenuCloser({action: 'hide', level: '2'});
183 | } else if (ev.type === 'mouseleave' && (angular.element(ev.toElement).hasClass('level-three') || angular.element(ev.toElement).hasClass('lvl2'))) {
184 | // console.log('close level three')
185 | submenuOpenerCtrl.submenuCloser({action: 'hide', level: '3'});
186 | } else if (ev.type === 'mouseleave' && angular.element(ev.toElement).hasClass('wb-submenu-opener')) {
187 | // console.log('Here is where i broke D:');
188 | // console.log(ev)
189 | submenuOpenerCtrl.submenuCloser({action: 'hide', level: attrs.wbLevel});
190 | }
191 | });
192 | };
193 |
194 | var unbindMouseEv = function () {
195 | // console.log('EVENTS BOUND: ', jQuery._data(element, 'events'));
196 | element.unbind('mouseover mouseleave');
197 | submenuOpenerCtrl.submenuCloser({action: 'hide', level: 'all'});
198 | }
199 |
200 | scope.$on('toggleMouseEv', function (event, action) {
201 | // console.log('ACTION: ', action)
202 | if (action === 'show') {
203 | element.addClass('show');
204 | bindMouseEv();
205 | } else {
206 | element.removeClass('show');
207 | unbindMouseEv();
208 | }
209 | })
210 |
211 | }
212 | };
213 | })
214 | .directive('wbSubmenu', function () {
215 | return {
216 | restrict: 'C',
217 | replace: false,
218 | controller: function () {
219 |
220 | },
221 | link: function (scope, element, attrs, ctrl) {
222 |
223 | if (attrs.wbLevel === 3) {
224 | // console.log('Sono qui?')
225 | } else {
226 | scope.$on('toggleSubmenu', function (event, msg) {
227 | // console.log(msg, attrs.wbLevel);
228 | if (msg.action === 'show') {
229 | if (msg.level === attrs.wbLevel) {
230 | element.addClass('show');
231 | }
232 | } else {
233 | if (msg.level === attrs.wbLevel) {
234 | // console.log('DIE BASTARD')
235 | element.removeClass('show');
236 | } else if (msg.level === 'all') {
237 | element.removeClass('show');
238 | }
239 | }
240 | });
241 | }
242 | }
243 | };
244 | })
245 | .directive('wbSubmenuItems', function () {
246 | return {
247 | restrict: 'C',
248 | replace: false,
249 | require: 'wbSubmenuItems',
250 | controller: function ($scope, BoardData) {
251 |
252 | $scope.setAttributeTool = function (toolName) {
253 | if (typeof toolName === 'string') {
254 | return toolName.toLowerCase();
255 | }
256 | return toolName[0];
257 | }
258 |
259 | this.setTool = function (toolName) {
260 | BoardData.setCurrentToolName(toolName);
261 | }
262 |
263 | this.setColors = function (type, color) {
264 | if (type === 'fill') {
265 | BoardData.setColors(color, null);
266 | } else {
267 | BoardData.setColors(null, color);
268 | }
269 | }
270 |
271 | this.setThickness = function (thickness) {
272 | BoardData.setStrokeWidth(thickness);
273 | }
274 |
275 | },
276 | link: function (scope, element, attrs, submenuItemsCtrl) {
277 |
278 | var updateIconColors = function (type, color) {
279 | scope.$emit('changeIconColors', {type: type, color: color});
280 | };
281 |
282 | element.bind('mouseover', function (ev) {
283 | //ev.stopPropagation();
284 | // console.log(attrs.wbTool)
285 | })
286 |
287 | element.bind('mouseleave', function (ev) {
288 | ev.stopPropagation();
289 | // console.log(angular.element(ev.currentTarget).hasClass('level-two-items'));
290 | // console.log('!!!!!!!!!!!!!!!!!', attrs.wbTool, ev);
291 | // if (angular.element(ev.currentTarget).hasClass('level-two-items')) { return; }
292 | if (attrs.wbColor && (angular.element(ev.relatedTarget).is('svg') || angular.element(ev.relatedTarget)[0].raphael)) {
293 | // console.log('A')
294 | submenuItemsCtrl.setColors(attrs.wbColorType, attrs.wbColor);
295 | // updateIconColors(attrs.wbColorType, attrs.wbColor);
296 | scope.$emit('activateMenu', 'hide');
297 | } else if (attrs.wbThickness && (angular.element(ev.relatedTarget).is('svg') || angular.element(ev.relatedTarget)[0].raphael)) {
298 | // console.log('SET THICKNESS')
299 | submenuItemsCtrl.setThickness(attrs.wbThickness);
300 | scope.$emit('activateMenu', 'hide');
301 | } else if (attrs.wbTool && (angular.element(ev.relatedTarget).is('svg') || angular.element(ev.relatedTarget)[0].raphael)) {
302 | // console.log('b')
303 | scope.$emit('setCursorClass', {tool: attrs.wbTool});
304 | submenuItemsCtrl.setTool(attrs.wbTool);
305 | scope.$emit('activateMenu', 'hide');
306 | } else if (angular.element(ev.relatedTarget).hasClass('menu') || angular.element(ev.relatedTarget).hasClass('icon')) {
307 | // console.log(ev)
308 | scope.$emit('toggleAllSubmenu', {action: 'hide', level: '3'});
309 | }
310 | // console.log(angular.element(ev.relatedTarget).is('svg'))
311 | })
312 | }
313 | };
314 | })
315 | .directive('wbMenuOverHandler', function () {
316 | return {
317 | restrict: 'A',
318 | replace: false,
319 | require: 'wbMenuOverHandler',
320 | controllerAs: 'menuOver',
321 | controller: function ($scope) {
322 | var elemWidth;
323 | // var elemLeftOffset;
324 |
325 | // this.storeElemLeftOffset = function (leftOffset) {
326 | // elemLeftOffset = leftOffset;
327 | // };
328 |
329 | // this.getElemLeftOffset = function () {
330 | // return elemLeftOffset;
331 | // };
332 |
333 | this.storeElemWidth = function (width) {
334 | elemWidth = width;
335 | };
336 |
337 | this.getElemWidth = function () {
338 | return elemWidth;
339 | };
340 |
341 | this.calcBg = function (mouseX, leftOffset) {
342 | var width = this.getElemWidth();
343 |
344 | //100 : elemWidth = x : mouseX
345 | var bgSizes = {};
346 | bgSizes.overed = (mouseX - leftOffset) * 100 / this.getElemWidth();
347 | // bgSizes.free = 100 - bgSizes.overed;
348 | // bgSizes.free = 0;
349 |
350 | return bgSizes;
351 | };
352 |
353 | this.hexToRGBA = function (hex, opacity) {
354 | opacity = opacity || 90;
355 |
356 | hex = hex.replace('#','');
357 | r = parseInt(hex.substring(0,2), 16);
358 | g = parseInt(hex.substring(2,4), 16);
359 | b = parseInt(hex.substring(4,6), 16);
360 |
361 | result = 'rgba('+r+','+g+','+b+','+opacity/100+')';
362 | return result;
363 | }
364 |
365 | },
366 | link: function (scope, element, attrs, ctrl) {
367 | // {background: linear-gradient(90deg, rgba(53,53,53,0.99) {{overed}}%, rgba(53,53,53,0.89) free%)}
368 |
369 | if (ctrl.getElemWidth() === undefined) {
370 | ctrl.storeElemWidth(element.width())
371 | }
372 |
373 | // console.log(element.offset())
374 | // ctrl.storeElemWidth(element.offset().left);
375 |
376 | var setBg = function (el, sizes) {
377 | // console.log(sizes.overed, el.offset().left)
378 | el.css({'background': 'linear-gradient(90deg, rgba(177,102,24,0.96) ' + (sizes.overed) + '%, rgba(53,53,53,0.93) 0%)'})
379 | }
380 |
381 | var setColorBg = function (el, color, sizes) {
382 | // console.log(sizes.overed, el.offset().left)
383 | var rgbaOver = ctrl.hexToRGBA(color, 100);
384 | var rgbaFree = ctrl.hexToRGBA(color, 90);
385 | el.css({'background': 'linear-gradient(90deg, ' + rgbaOver + ' ' + (sizes.overed) + '%, ' + rgbaFree + ' 0%)'})
386 | }
387 |
388 | element.bind('mouseover', function (ev) {
389 | ev.stopPropagation();
390 | // console.log(ev);
391 | // console.log(ev.currentTarget)
392 | if (angular.element(ev.currentTarget).hasClass('level-two-items')) {
393 | var $levelOne = angular.element(ev.currentTarget).parents('.level-one')
394 | // console.log($levelOne)
395 | scope.$emit('resetBackgrounds', {target: 'level-two-items'});
396 | setBg($levelOne, {overed: 100});
397 | } else if (angular.element(ev.currentTarget).hasClass('level-one')) {
398 | scope.$emit('resetBackgrounds', {target: 'level-two-items'});
399 | } else if (angular.element(ev.currentTarget).hasClass('color-palette')) {
400 | // console.log('A')
401 | var $levelTwo = angular.element(ev.currentTarget).parents('.level-two-items')
402 | setBg($levelTwo, {overed: 100});
403 | scope.$emit('resetBackgrounds', {target: 'level-three-items'});
404 | } else if (angular.element(ev.currentTarget).hasClass('thickness')) {
405 | // console.log('A')
406 | var $levelTwo = angular.element(ev.currentTarget).parents('.level-two-items');
407 | // console.log($levelTwo)
408 | setBg($levelTwo, {overed: 100});
409 | scope.$emit('resetBackgrounds', {target: 'level-three-items'});
410 | }
411 |
412 |
413 | });
414 |
415 | element.bind('mousemove', function (ev) {
416 | ev.stopPropagation();
417 |
418 | var $el = angular.element(ev.currentTarget);
419 | // console.log('ev');
420 | if ($el.hasClass('level-one') || $el.hasClass('level-two-items') || $el.hasClass('thickness')) {
421 | // console.log('over level one');
422 | var bgSizes = ctrl.calcBg(ev.clientX, $el.offset().left);
423 | setBg($el, bgSizes);
424 |
425 | } else if ($el.hasClass('color-palette')) {
426 | var bgSizes = ctrl.calcBg(ev.clientX, $el.offset().left);
427 | setColorBg($el, scope.color, bgSizes);
428 |
429 | }
430 | });
431 |
432 | element.bind('mouseleave', function (ev) {
433 |
434 | var $elTarget = angular.element(ev.currentTarget);
435 | var $elToElement = angular.element(ev.toElement);
436 |
437 | // console.log($elTarget)
438 | if ($elTarget.hasClass('level-two-items')) {
439 | // console.log(ev)
440 | if ($elToElement.is('svg') || angular.element(ev.relatedTarget)[0].raphael) {
441 | // console.log(1)
442 | scope.$emit('resetBackgrounds', {target: 'all'});
443 | } else if ($elToElement.hasClass('wb-submenu-opener')) {
444 | // console.log(2)
445 | scope.$emit('resetBackgrounds', {target: 'all'});
446 | } else {
447 | // console.log(3)
448 | scope.$emit('resetBackgrounds', {target: 'level-two-items'});
449 | }
450 | } else if ($elTarget.hasClass('level-one')) {
451 | // console.log('reset!')
452 | scope.$emit('resetBackgrounds', {target: 'level-one'});
453 | } else if ($elTarget.hasClass('color-palette') || $elTarget.hasClass('thickness')) {
454 | if ($elToElement.is('svg') || angular.element(ev.relatedTarget)[0].raphael) {
455 | scope.$emit('resetBackgrounds', {target: 'all'});
456 | } else if ($elToElement.hasClass('wb-submenu-opener') || $elToElement.hasClass('level-three-items')) {
457 | // scope.$emit('resetBackgrounds', {target: 'all'});
458 | scope.$emit('resetBackgrounds', {target: ['color-palette', 'thickness']});
459 | }
460 | }
461 | })
462 |
463 | scope.$on('resetTargetBackground', function (ev, msg) {
464 | // console.log('should reset color', element)
465 | if (msg.target === 'all') {
466 | // console.log('- ', element)
467 | element.hasClass('color-palette') ? setColorBg(element, scope.color, {overed: 0}) : setBg(element, {overed: 0});
468 | } else if (element.hasClass('color-palette') && (msg.target === 'level-three-items' || msg.target === 'color-palette')) {
469 | // console.log(scope.color)
470 | setColorBg(element, scope.color, {overed: 0});
471 | } else if (element.hasClass('thickness') && (msg.target === 'level-three-items' || msg.target === 'thickness')) {
472 | setBg(element, {overed: 0});
473 | } else if (element.hasClass(msg.target)) {
474 | // console.log('here', msg.target)
475 | setBg(element, {overed: 0});
476 | }
477 |
478 | })
479 |
480 | }
481 | };
482 | })
483 |
--------------------------------------------------------------------------------
/client/dist/style.min.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/client/dist/style.min.css
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Albus
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/client/js/resize-handler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @see https://github.com/pesla/ResizeSensor
3 | * @author Peter Slagter
4 | * @license MIT
5 | * @description Based on https://github.com/marcj/css-element-queries
6 | * @preserve
7 | */
8 |
9 | /**
10 | * @returns {ResizeSensor}
11 | */
12 | var ResizeSensor = (function () {
13 | 'use strict';
14 |
15 | /** ----- Feature tests and polyfills ----- */
16 |
17 | /** {array} */
18 | var unsuitableElements = ['IMG', 'COL', 'TR', 'THEAD', 'TFOOT'];
19 | /** {boolean} */
20 | var supportsAttachEvent = ('attachEvent' in document);
21 |
22 | if (!supportsAttachEvent) {
23 | var browserSupportsCSSAnimations = isCSSAnimationSupported();
24 | var animationPropertiesForBrowser = (browserSupportsCSSAnimations) ? getAnimationPropertiesForBrowser() : {};
25 | insertResizeSensorStyles();
26 |
27 | if (!('requestAnimationFrame' in window) || !('cancelAnimationFrame' in window)) {
28 | polyfillRAF();
29 | }
30 | }
31 |
32 | /** ----- ResizeSensor ----- */
33 |
34 | /**
35 | * @param {HTMLElement} targetElement
36 | * @param {Function} callback
37 | * @constructor
38 | */
39 | var ResizeSensor = function (targetElement, callback) {
40 | if (isUnsuitableElement(targetElement)) {
41 | console && console.error("Given element isn't suitable to act as a resize sensor. Try wrapping it with one that is. Unsuitable elements are:", unsuitableElements);
42 | return;
43 | }
44 |
45 | /** @var {HTMLElement} */
46 | this.targetElement = targetElement;
47 | /** @var {Function} */
48 | this.callback = callback;
49 | /** @var {{width: int, height: int}} */
50 | this.dimensions = {
51 | width: 0,
52 | height: 0
53 | };
54 |
55 | if (supportsAttachEvent) {
56 | this.boundOnResizeHandler = this.onElementResize.bind(this);
57 | this.targetElement.attachEvent('onresize', this.boundOnResizeHandler);
58 | return;
59 | }
60 |
61 | /** @var {{container: HTMLElement, expand: HTMLElement, expandChild: HTMLElement, contract: HTMLElement}} */
62 | this.triggerElements = {};
63 | /** @var {int} */
64 | this.resizeRAF = 0;
65 |
66 | this.setup();
67 | };
68 |
69 | ResizeSensor.prototype.setup = function () {
70 | // Make sure the target element is "positioned"
71 | forcePositionedBox(this.targetElement);
72 |
73 | // Create and append resize trigger elements
74 | this.insertResizeTriggerElements();
75 |
76 | // Start listening to events
77 | this.boundScrollListener = this.handleElementScroll.bind(this);
78 | this.targetElement.addEventListener('scroll', this.boundScrollListener, true);
79 |
80 | if (browserSupportsCSSAnimations) {
81 | this.boundAnimationStartListener = this.resetTriggersOnAnimationStart.bind(this);
82 | this.triggerElements.container.addEventListener(animationPropertiesForBrowser.animationStartEvent, this.boundAnimationStartListener);
83 | }
84 |
85 | // Initial value reset of all triggers
86 | this.resetTriggers();
87 | };
88 |
89 | ResizeSensor.prototype.insertResizeTriggerElements = function () {
90 | var resizeTrigger = document.createElement('div');
91 | var expandTrigger = document.createElement('div');
92 | var expandTriggerChild = document.createElement('div');
93 | var contractTrigger = document.createElement('div');
94 |
95 | resizeTrigger.className = 'ResizeSensor ResizeSensor__resizeTriggers';
96 | expandTrigger.className = 'ResizeSensor__expandTrigger';
97 | contractTrigger.className = 'ResizeSensor__contractTrigger';
98 |
99 | expandTrigger.appendChild(expandTriggerChild);
100 | resizeTrigger.appendChild(expandTrigger);
101 | resizeTrigger.appendChild(contractTrigger);
102 |
103 | this.triggerElements.container = resizeTrigger;
104 | this.triggerElements.expand = expandTrigger;
105 | this.triggerElements.expandChild = expandTriggerChild;
106 | this.triggerElements.contract = contractTrigger;
107 |
108 | this.targetElement.appendChild(resizeTrigger);
109 | };
110 |
111 | ResizeSensor.prototype.onElementResize = function () {
112 | var currentDimensions = this.getDimensions();
113 |
114 | if (this.isResized(currentDimensions)) {
115 | this.dimensions.width = currentDimensions.width;
116 | this.dimensions.height = currentDimensions.height;
117 | this.elementResized();
118 | }
119 | };
120 |
121 | ResizeSensor.prototype.handleElementScroll = function () {
122 | var _this = this;
123 |
124 | this.resetTriggers();
125 |
126 | if (this.resizeRAF) {
127 | window.cancelAnimationFrame(this.resizeRAF);
128 | }
129 |
130 | this.resizeRAF = window.requestAnimationFrame(function () {
131 | var currentDimensions = _this.getDimensions();
132 | if (_this.isResized(currentDimensions)) {
133 | _this.dimensions.width = currentDimensions.width;
134 | _this.dimensions.height = currentDimensions.height;
135 | _this.elementResized();
136 | }
137 | });
138 | };
139 |
140 | /**
141 | * @param {{width: number, height: number}} currentDimensions
142 | * @returns {boolean}
143 | */
144 | ResizeSensor.prototype.isResized = function (currentDimensions) {
145 | return (currentDimensions.width !== this.dimensions.width || currentDimensions.height !== this.dimensions.height)
146 | };
147 |
148 | /**
149 | * @returns {{width: number, height: number}}
150 | */
151 | ResizeSensor.prototype.getDimensions = function () {
152 | return {
153 | width: this.targetElement.offsetWidth,
154 | height: this.targetElement.offsetHeight
155 | };
156 | };
157 |
158 | /**
159 | * @param {Event} event
160 | */
161 | ResizeSensor.prototype.resetTriggersOnAnimationStart = function (event) {
162 | if (event.animationName === animationPropertiesForBrowser.animationName) {
163 | this.resetTriggers();
164 | }
165 | };
166 |
167 | ResizeSensor.prototype.resetTriggers = function () {
168 | this.triggerElements.contract.scrollLeft = this.triggerElements.contract.scrollWidth;
169 | this.triggerElements.contract.scrollTop = this.triggerElements.contract.scrollHeight;
170 | this.triggerElements.expandChild.style.width = this.triggerElements.expand.offsetWidth + 1 + 'px';
171 | this.triggerElements.expandChild.style.height = this.triggerElements.expand.offsetHeight + 1 + 'px';
172 | this.triggerElements.expand.scrollLeft = this.triggerElements.expand.scrollWidth;
173 | this.triggerElements.expand.scrollTop = this.triggerElements.expand.scrollHeight;
174 | };
175 |
176 | ResizeSensor.prototype.elementResized = function () {
177 | this.callback(this.dimensions);
178 | };
179 |
180 | ResizeSensor.prototype.destroy = function () {
181 | this.removeEventListeners();
182 | this.targetElement.removeChild(this.triggerElements.container);
183 | delete this.boundAnimationStartListener;
184 | delete this.boundScrollListener;
185 | delete this.callback;
186 | delete this.targetElement;
187 | };
188 |
189 | ResizeSensor.prototype.removeEventListeners = function () {
190 | if (supportsAttachEvent) {
191 | this.targetElement.detachEvent('onresize', this.boundOnResizeHandler);
192 | }
193 |
194 | this.triggerElements.container.removeEventListener(animationPropertiesForBrowser.animationStartEvent, this.boundAnimationStartListener);
195 | this.targetElement.removeEventListener('scroll', this.boundScrollListener, true);
196 | };
197 |
198 | /** ----- Various helper functions ----- */
199 |
200 | /**
201 | * An element is said to be positioned if its 'position' property has a value other than 'static'
202 | * @see http://www.w3.org/TR/CSS2/visuren.html#propdef-position
203 | * @param {HTMLElement} element
204 | */
205 | function forcePositionedBox (element) {
206 | var position = getStyle(element, 'position');
207 |
208 | if (position === 'static') {
209 | element.style.position = 'relative';
210 | }
211 | }
212 |
213 | /**
214 | * @returns {boolean}
215 | */
216 | function isCSSAnimationSupported () {
217 | var testElement = document.createElement('div');
218 | var isAnimationSupported = ('animationName' in testElement.style);
219 |
220 | if (isAnimationSupported) {
221 | return true;
222 | }
223 |
224 | var browserPrefixes = 'Webkit Moz O ms'.split(' ');
225 | var i = 0;
226 | var l = browserPrefixes.length;
227 |
228 | for (; i < l; i++) {
229 | if ((browserPrefixes[i] + 'AnimationName') in testElement.style) {
230 | return true;
231 | }
232 | }
233 |
234 | return false;
235 | }
236 |
237 | /**
238 | * @param {HTMLElement} targetElement
239 | * @returns {boolean}
240 | */
241 | function isUnsuitableElement (targetElement) {
242 | var tagName = targetElement.tagName.toUpperCase();
243 | return (unsuitableElements.indexOf(tagName) > -1);
244 | }
245 |
246 | /**
247 | * Determines which style convention (properties) to follow
248 | * @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_CSS_animations/Detecting_CSS_animation_support
249 | * @returns {{keyframesRule: string, styleDeclaration: string, animationStartEvent: string, animationName: string}}
250 | */
251 | function getAnimationPropertiesForBrowser () {
252 | var testElement = document.createElement('div');
253 | var supportsUnprefixedAnimationProperties = ('animationName' in testElement.style);
254 |
255 | // Unprefixed animation properties
256 | var animationStartEvent = 'animationstart';
257 | var animationName = 'resizeanim';
258 |
259 | if (supportsUnprefixedAnimationProperties) {
260 | return {
261 | keyframesRule: '@keyframes ' + animationName + ' {from { opacity: 0; } to { opacity: 0; }}',
262 | styleDeclaration: 'animation: 1ms ' + animationName + ';',
263 | animationStartEvent: animationStartEvent,
264 | animationName: animationName
265 | };
266 | }
267 |
268 | // Browser specific animation properties
269 | var keyframePrefix = '';
270 | var browserPrefixes = 'Webkit Moz O ms'.split(' ');
271 | var startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(' ');
272 |
273 | var i;
274 | var l = browserPrefixes.length;
275 |
276 | for (i = 0; i < l ; i++) {
277 | if ((browserPrefixes[i] + 'AnimationName') in testElement.style) {
278 | keyframePrefix = '-' + browserPrefixes[i].toLowerCase() + '-';
279 | animationStartEvent = startEvents[i];
280 | break;
281 | }
282 | }
283 |
284 | return {
285 | keyframesRule: '@' + keyframePrefix + 'keyframes ' + animationName + ' {from { opacity: 0; } to { opacity: 0; }}',
286 | styleDeclaration: keyframePrefix + 'animation: 1ms ' + animationName + ';',
287 | animationStartEvent: animationStartEvent,
288 | animationName: animationName
289 | };
290 | }
291 |
292 | /**
293 | * Provides requestAnimationFrame in a cross browser way
294 | * @see https://gist.github.com/mrdoob/838785
295 | */
296 | function polyfillRAF () {
297 | if (!window.requestAnimationFrame) {
298 | window.requestAnimationFrame = (function () {
299 | return window.webkitRequestAnimationFrame ||
300 | window.mozRequestAnimationFrame ||
301 | window.oRequestAnimationFrame ||
302 | window.msRequestAnimationFrame ||
303 | function (callback) {
304 | window.setTimeout(callback, 1000 / 60);
305 | };
306 | })();
307 | }
308 |
309 | if (!window.cancelAnimationFrame) {
310 | window.cancelAnimationFrame = (function () {
311 | return window.webkitCancelAnimationFrame ||
312 | window.mozCancelAnimationFrame ||
313 | window.oCancelAnimationFrame ||
314 | window.msCancelAnimationFrame ||
315 | window.clearTimeout;
316 | })();
317 | }
318 | }
319 |
320 | /**
321 | * Adds a style block that contains CSS essential for detecting resize events
322 | */
323 | function insertResizeSensorStyles () {
324 | var css = [
325 | (animationPropertiesForBrowser.keyframesRule) ? animationPropertiesForBrowser.keyframesRule : '',
326 | '.ResizeSensor__resizeTriggers { ' + ((animationPropertiesForBrowser.styleDeclaration) ? animationPropertiesForBrowser.styleDeclaration : '') + ' visibility: hidden; opacity: 0; }',
327 | '.ResizeSensor__resizeTriggers, .ResizeSensor__resizeTriggers > div, .ResizeSensor__contractTrigger:before { content: \' \'; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .ResizeSensor__resizeTriggers > div { background: #eee; overflow: auto; } .ResizeSensor__contractTrigger:before { width: 200%; height: 200%; }'
328 | ];
329 |
330 | css = css.join(' ');
331 |
332 | var headElem = document.head || document.getElementsByTagName('head')[0];
333 |
334 | var styleElem = document.createElement('style');
335 | styleElem.type = 'text/css';
336 |
337 | if (styleElem.styleSheet) {
338 | styleElem.styleSheet.cssText = css;
339 | } else {
340 | styleElem.appendChild(document.createTextNode(css));
341 | }
342 |
343 | headElem.appendChild(styleElem);
344 | }
345 |
346 | /**
347 | *
348 | * @param {HTMLElement} element
349 | * @param {string} property
350 | * @returns {null|string}
351 | */
352 | function getStyle (element, property) {
353 | var value = null;
354 |
355 | if (element.currentStyle) {
356 | value = element.currentStyle[property];
357 | } else if (window.getComputedStyle) {
358 | value = document.defaultView.getComputedStyle(element, null).getPropertyValue(property);
359 | }
360 |
361 | return value;
362 | }
363 |
364 | return ResizeSensor;
365 | })();
366 |
367 | var ResizeSensorApi = (function (ResizeSensor) {
368 | //'use strict';
369 |
370 | /** {{}} Map of all resize sensors (id => ResizeSensor) */
371 | var allResizeSensors = {};
372 |
373 | /**
374 | * @constructor
375 | */
376 | var ResizeSensorApi = function () {};
377 |
378 | /**
379 | * @param {HTMLElement} targetElement
380 | * @param {Function} callback
381 | * @returns {ResizeSensor}
382 | */
383 | ResizeSensorApi.prototype.create = function (targetElement, callback) {
384 | var sensorId = this.getSensorId(targetElement);
385 |
386 | if (allResizeSensors[sensorId]) {
387 | return allResizeSensors[sensorId];
388 | }
389 |
390 | var Instance = new ResizeSensor(targetElement, callback);
391 | allResizeSensors[sensorId] = Instance;
392 | return Instance;
393 | };
394 |
395 | /**
396 | * @param {HTMLElement} targetElement
397 | */
398 | ResizeSensorApi.prototype.destroy = function (targetElement) {
399 | var sensorId = this.getSensorId(targetElement);
400 |
401 | /** @var ResizeSensor */
402 | var Sensor = allResizeSensors[sensorId];
403 |
404 | if (!Sensor) {
405 | console && console.error("Can't destroy ResizeSensor (404 not found).", targetElement);
406 | }
407 |
408 | Sensor.destroy();
409 | delete allResizeSensors[sensorId];
410 | };
411 |
412 | /**
413 | * @param {HTMLElement} targetElement
414 | * @returns {string}
415 | */
416 | ResizeSensorApi.prototype.getSensorId = function (targetElement) {
417 | return targetElement.id;
418 | };
419 |
420 | return new ResizeSensorApi();
421 | })(ResizeSensor);
--------------------------------------------------------------------------------
/client/js/resize-handler.min.js:
--------------------------------------------------------------------------------
1 | var ResizeSensor=function(){"use strict";function e(e){var t=o(e,"position");"static"===t&&(e.style.position="relative")}function t(){var e=document.createElement("div"),t="animationName"in e.style;if(t)return!0;for(var i="Webkit Moz O ms".split(" "),n=0,r=i.length;r>n;n++)if(i[n]+"AnimationName"in e.style)return!0;return!1}function i(e){var t=e.tagName.toUpperCase();return a.indexOf(t)>-1}function n(){var e=document.createElement("div"),t="animationName"in e.style,i="animationstart",n="resizeanim";if(t)return{keyframesRule:"@keyframes "+n+" {from { opacity: 0; } to { opacity: 0; }}",styleDeclaration:"animation: 1ms "+n+";",animationStartEvent:i,animationName:n};var r,s="",o="Webkit Moz O ms".split(" "),a="webkitAnimationStart animationstart oAnimationStart MSAnimationStart".split(" "),l=o.length;for(r=0;l>r;r++)if(o[r]+"AnimationName"in e.style){s="-"+o[r].toLowerCase()+"-",i=a[r];break}return{keyframesRule:"@"+s+"keyframes "+n+" {from { opacity: 0; } to { opacity: 0; }}",styleDeclaration:s+"animation: 1ms "+n+";",animationStartEvent:i,animationName:n}}function r(){window.requestAnimationFrame||(window.requestAnimationFrame=function(){return window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}()),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(){return window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||window.clearTimeout}())}function s(){var e=[d.keyframesRule?d.keyframesRule:"",".ResizeSensor__resizeTriggers { "+(d.styleDeclaration?d.styleDeclaration:"")+" visibility: hidden; opacity: 0; }",".ResizeSensor__resizeTriggers, .ResizeSensor__resizeTriggers > div, .ResizeSensor__contractTrigger:before { content: ' '; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .ResizeSensor__resizeTriggers > div { background: #eee; overflow: auto; } .ResizeSensor__contractTrigger:before { width: 200%; height: 200%; }"];e=e.join(" ");var t=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css",i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e)),t.appendChild(i)}function o(e,t){var i=null;return e.currentStyle?i=e.currentStyle[t]:window.getComputedStyle&&(i=document.defaultView.getComputedStyle(e,null).getPropertyValue(t)),i}var a=["IMG","COL","TR","THEAD","TFOOT"],l="attachEvent"in document;if(!l){var m=t(),d=m?n():{};s(),"requestAnimationFrame"in window&&"cancelAnimationFrame"in window||r()}var h=function(e,t){return i(e)?void(console&&console.error("Given element isn't suitable to act as a resize sensor. Try wrapping it with one that is. Unsuitable elements are:",a)):(this.targetElement=e,this.callback=t,this.dimensions={width:0,height:0},l?(this.boundOnResizeHandler=this.onElementResize.bind(this),void this.targetElement.attachEvent("onresize",this.boundOnResizeHandler)):(this.triggerElements={},this.resizeRAF=0,void this.setup()))};return h.prototype.setup=function(){e(this.targetElement),this.insertResizeTriggerElements(),this.boundScrollListener=this.handleElementScroll.bind(this),this.targetElement.addEventListener("scroll",this.boundScrollListener,!0),m&&(this.boundAnimationStartListener=this.resetTriggersOnAnimationStart.bind(this),this.triggerElements.container.addEventListener(d.animationStartEvent,this.boundAnimationStartListener)),this.resetTriggers()},h.prototype.insertResizeTriggerElements=function(){var e=document.createElement("div"),t=document.createElement("div"),i=document.createElement("div"),n=document.createElement("div");e.className="ResizeSensor ResizeSensor__resizeTriggers",t.className="ResizeSensor__expandTrigger",n.className="ResizeSensor__contractTrigger",t.appendChild(i),e.appendChild(t),e.appendChild(n),this.triggerElements.container=e,this.triggerElements.expand=t,this.triggerElements.expandChild=i,this.triggerElements.contract=n,this.targetElement.appendChild(e)},h.prototype.onElementResize=function(){var e=this.getDimensions();this.isResized(e)&&(this.dimensions.width=e.width,this.dimensions.height=e.height,this.elementResized())},h.prototype.handleElementScroll=function(){var e=this;this.resetTriggers(),this.resizeRAF&&window.cancelAnimationFrame(this.resizeRAF),this.resizeRAF=window.requestAnimationFrame(function(){var t=e.getDimensions();e.isResized(t)&&(e.dimensions.width=t.width,e.dimensions.height=t.height,e.elementResized())})},h.prototype.isResized=function(e){return e.width!==this.dimensions.width||e.height!==this.dimensions.height},h.prototype.getDimensions=function(){return{width:this.targetElement.offsetWidth,height:this.targetElement.offsetHeight}},h.prototype.resetTriggersOnAnimationStart=function(e){e.animationName===d.animationName&&this.resetTriggers()},h.prototype.resetTriggers=function(){this.triggerElements.contract.scrollLeft=this.triggerElements.contract.scrollWidth,this.triggerElements.contract.scrollTop=this.triggerElements.contract.scrollHeight,this.triggerElements.expandChild.style.width=this.triggerElements.expand.offsetWidth+1+"px",this.triggerElements.expandChild.style.height=this.triggerElements.expand.offsetHeight+1+"px",this.triggerElements.expand.scrollLeft=this.triggerElements.expand.scrollWidth,this.triggerElements.expand.scrollTop=this.triggerElements.expand.scrollHeight},h.prototype.elementResized=function(){this.callback(this.dimensions)},h.prototype.destroy=function(){this.removeEventListeners(),this.targetElement.removeChild(this.triggerElements.container),delete this.boundAnimationStartListener,delete this.boundScrollListener,delete this.callback,delete this.targetElement},h.prototype.removeEventListeners=function(){l&&this.targetElement.detachEvent("onresize",this.boundOnResizeHandler),this.triggerElements.container.removeEventListener(d.animationStartEvent,this.boundAnimationStartListener),this.targetElement.removeEventListener("scroll",this.boundScrollListener,!0)},h}(),ResizeSensorApi=function(e){var t={},i=function(){};return i.prototype.create=function(i,n){var r=this.getSensorId(i);if(t[r])return t[r];var s=new e(i,n);return t[r]=s,s},i.prototype.destroy=function(e){var i=this.getSensorId(e),n=t[i];n||console&&console.error("Can't destroy ResizeSensor (404 not found).",e),n.destroy(),delete t[i]},i.prototype.getSensorId=function(e){return e.id},new i}(ResizeSensor);
2 |
--------------------------------------------------------------------------------
/client/services/auth.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.auth', [])
2 | .factory('Auth', function ($http, $window) {
3 |
4 | var generateRandomId = function (length) {
5 | var id = "";
6 | var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
7 |
8 | for (var i = 0; i < length; i++) {
9 | id += chars.charAt(Math.floor(Math.random() * chars.length));
10 | }
11 |
12 | return id;
13 | };
14 |
15 | return {
16 | generateRandomId: generateRandomId
17 | };
18 | });
19 |
--------------------------------------------------------------------------------
/client/services/board-data.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.boarddata', [])
2 | .factory('BoardData', function () {
3 | //svgWidth/Height are the width and height of the DOM element
4 | var svgWidth = 1500; //sizeX
5 | var svgHeight = 1000; //sizeY
6 | //offsetX/Y measure the top-left point of the viewbox
7 | var offsetX = 0;
8 | var offsetY = 0;
9 | //scalingFactor is the level of zooming relative to the start
10 | var scalingFactor = 1;
11 |
12 | var board;
13 | var $canvas;
14 | //canvasMarginX/Y are the left and top margin of the SVG in the browser
15 | var canvasMarginX; //canvasX
16 | var canvasMarginY; //canvasY
17 | //viewBoxWidth/Height are needed for zooming
18 | var viewBoxWidth;// = svgWidth;
19 | var viewBoxHeight;// = svgHeight;
20 | var cursor;
21 | var shapeStorage = {};
22 | var currentShape;
23 | var currentShapeId;
24 | var editorShape;
25 | var socketId;
26 |
27 | var tool = {
28 | name: 'path',
29 | 'stroke-width': 3,
30 | colors: {
31 | fill: 'transparent',
32 | stroke: '#000000'
33 | }
34 | };
35 |
36 | function createBoard (element) {
37 |
38 | ResizeSensorApi.create(document.getElementsByClassName('app-container')[0], handleWindowResize);
39 |
40 | board = Raphael(element[0]);
41 | board.setViewBox(0, 0, svgWidth, svgHeight, true);
42 | board.canvas.setAttribute('preserveAspectRatio', 'none');
43 |
44 | $canvas = element.find('svg');
45 | canvasMarginX = $canvas.position().left;
46 | canvasMarginY = $canvas.position().top;
47 | }
48 |
49 | function handleWindowResize (newPageSize) {
50 | svgWidth = newPageSize.width;
51 | svgHeight = newPageSize.height;
52 |
53 | viewBoxWidth = svgWidth * scalingFactor;
54 | viewBoxHeight = svgHeight * scalingFactor;
55 | var offset = getOffset();
56 | board.setViewBox(offset.x, offset.y, viewBoxWidth, viewBoxHeight, true);
57 | }
58 |
59 | function getBoard () {
60 | return board;
61 | }
62 |
63 | function getCursor () {
64 | return cursor;
65 | }
66 |
67 | function setCursor () {
68 | cursor = board.circle(window.innerWidth / 2, window.innerHeight / 2, 5);
69 | return cursor;
70 | }
71 |
72 | function moveCursor (screenPosition) {
73 | cursor.attr({
74 | cx: Math.floor(screenPosition[0]),
75 | cy: Math.floor(screenPosition[1])
76 | })
77 | }
78 |
79 | function setEditorShape (shape) {
80 | editorShape = shape;
81 | }
82 |
83 | function unsetEditorShape () {
84 | editorShape = null;
85 | }
86 |
87 | function getEditorShape () {
88 | return editorShape;
89 | }
90 |
91 | function getViewBoxDims () {
92 | return {
93 | width: viewBoxWidth,
94 | height: viewBoxHeight
95 | };
96 | }
97 |
98 | function setViewBoxDims (newViewBoxDims) {
99 | viewBoxWidth = newViewBoxDims.width;
100 | viewBoxHeight = newViewBoxDims.height;
101 | }
102 |
103 | function getOriginalDims () {
104 | return {
105 | width: svgWidth,
106 | height: svgHeight
107 | };
108 | }
109 |
110 | function getCanvasMargin () {
111 | return {
112 | x: canvasMarginX,
113 | y: canvasMarginY
114 | };
115 | }
116 |
117 | function getScalingFactor () {
118 | return scalingFactor;
119 | }
120 |
121 | function getOffset () {
122 | return {
123 | x: offsetX,
124 | y: offsetY
125 | }
126 | }
127 |
128 | function setOffset (newOffset) {
129 | offsetX = newOffset.x;
130 | offsetY = newOffset.y;
131 | }
132 |
133 | function getCanvas () {
134 | return $canvas;
135 | }
136 |
137 | function setSocketId (id) {
138 | socketId = id;
139 | }
140 |
141 | function getSocketId () {
142 | return socketId;
143 | }
144 |
145 | function pushToStorage (id, socketId, shape) {
146 | if (!shapeStorage[socketId]) {
147 | shapeStorage[socketId] = {};
148 | }
149 | shapeStorage[socketId][id] = shape;
150 | }
151 |
152 | function getShapeById (id, socketId) {
153 | return shapeStorage[socketId][id];
154 | }
155 |
156 | function getCurrentShape () {
157 | return currentShape;
158 | }
159 |
160 | function setCurrentShape (id) {
161 | currentShape = shapeStorage[socketId][id];
162 | }
163 |
164 | function unsetCurrentShape () {
165 | currentShape = null;
166 | }
167 |
168 | function getCurrentShapeId () {
169 | return currentShapeId;
170 | }
171 |
172 | function generateShapeId () {
173 | currentShapeId = Raphael._oid;
174 | return currentShapeId;
175 | }
176 |
177 | function getCurrentTool () {
178 | return tool;
179 | }
180 |
181 | function setCurrentToolName (name) {
182 | tool.name = name;
183 | }
184 |
185 | function setColors (fill, stroke) {
186 | fill = fill || tool.colors.fill;
187 | stroke = stroke || tool.colors.stroke;
188 |
189 | tool.colors.fill = fill;
190 | tool.colors.stroke = stroke;
191 | }
192 |
193 | function setZoomScale (scale) {
194 | scalingFactor = 1 / scale;
195 | };
196 |
197 | function getZoomScale () {
198 | return scalingFactor;
199 | }
200 |
201 | function getShapeStorage () {
202 | return shapeStorage;
203 | }
204 |
205 | function setStrokeWidth (width) {
206 | tool['stroke-width'] = width;
207 | }
208 |
209 | function getStrokeWidth () {
210 | return tool['stroke-width'];
211 | }
212 |
213 | return {
214 | getShapeStorage: getShapeStorage,
215 | getCursor: getCursor,
216 | setCursor: setCursor,
217 | moveCursor: moveCursor,
218 | createBoard: createBoard,
219 | getCurrentShape: getCurrentShape,
220 | getShapeById: getShapeById,
221 | getCurrentTool: getCurrentTool,
222 | generateShapeId: generateShapeId,
223 | getCurrentShapeId: getCurrentShapeId,
224 | setColors: setColors,
225 | setZoomScale: setZoomScale,
226 | getZoomScale: getZoomScale,
227 | getCanvas: getCanvas,
228 | setSocketId: setSocketId,
229 | getSocketId: getSocketId,
230 | setCurrentToolName: setCurrentToolName,
231 | getBoard: getBoard,
232 | getScalingFactor: getScalingFactor,
233 | getOffset: getOffset,
234 | getCanvasMargin: getCanvasMargin,
235 | pushToStorage: pushToStorage,
236 | setCurrentShape: setCurrentShape,
237 | unsetCurrentShape: unsetCurrentShape,
238 | getViewBoxDims: getViewBoxDims,
239 | setViewBoxDims: setViewBoxDims,
240 | setOffset: setOffset,
241 | getOriginalDims: getOriginalDims,
242 | setEditorShape: setEditorShape,
243 | unsetEditorShape: unsetEditorShape,
244 | getEditorShape: getEditorShape,
245 | setStrokeWidth: setStrokeWidth,
246 | getStrokeWidth: getStrokeWidth
247 | }
248 | });
249 |
--------------------------------------------------------------------------------
/client/services/broadcast.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.broadcast', [])
2 | .factory('Broadcast', function (Sockets) {
3 |
4 | var socketUserId;
5 |
6 | var getSocketId = function () {
7 | return socketUserId;
8 | };
9 |
10 | var saveSocketId = function (id) {
11 | socketUserId = id;
12 | };
13 |
14 | Sockets.emit('idRequest');
15 |
16 | var newShape = function (myid, socketId, tool, initX, initY) {
17 | Sockets.emit('newShape', {
18 | myid: myid,
19 | socketId: socketId,
20 | tool: tool,
21 | initX: initX,
22 | initY: initY
23 | });
24 | };
25 |
26 | var editShape = function (myid, socketId, currentTool, mouseX, mouseY) {
27 | var data = {};
28 | data.mouseX = mouseX;
29 | data.mouseY = mouseY;
30 | data.myid = myid;
31 | data.socketId = socketId;
32 | data.tool = currentTool;
33 | Sockets.emit('editShape', data);
34 | };
35 |
36 | var finishPath = function (myid, currentTool, pathDProps) {
37 | Sockets.emit('pathCompleted', {
38 | myid: myid,
39 | tool: currentTool,
40 | pathDProps: pathDProps
41 | });
42 | };
43 |
44 | var finishCopiedPath = function (myid, currentTool, pathDProps) {
45 | Sockets.emit('copiedPathCompleted', {
46 | myid: myid,
47 | tool: currentTool,
48 | pathDProps: pathDProps
49 | });
50 | };
51 |
52 | var finishShape = function (myid, currentTool) {
53 | Sockets.emit('shapeCompleted', {
54 | myid: myid,
55 | tool: currentTool
56 | });
57 | };
58 |
59 | var deleteShape = function (myid, socketId) {
60 | Sockets.emit('deleteShape', {
61 | myid: myid,
62 | socketId: socketId
63 | })
64 | };
65 |
66 | var moveShape = function (shape, x, y) {
67 | var type = shape.type;
68 | Sockets.emit('moveShape', {
69 | myid: shape.myid,
70 | socketId: shape.socketId,
71 | x: x,
72 | y: y,
73 | attr: shape.attr(),
74 | pathDProps: shape.pathDProps
75 | });
76 | };
77 |
78 | var finishMovingShape = function (shape) {
79 | Sockets.emit('finishMovingShape', {
80 | myid: shape.myid,
81 | socketId: shape.socketId,
82 | attr: shape.attr()
83 | })
84 | };
85 |
86 | return {
87 | getSocketId: getSocketId,
88 | saveSocketId: saveSocketId,
89 | newShape: newShape,
90 | editShape: editShape,
91 | finishPath: finishPath,
92 | finishCopiedPath: finishCopiedPath,
93 | finishShape: finishShape,
94 | deleteShape: deleteShape,
95 | finishMovingShape: finishMovingShape,
96 | moveShape: moveShape
97 | };
98 |
99 | });
100 |
--------------------------------------------------------------------------------
/client/services/event-handler.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.eventhandler', [])
2 | .factory('EventHandler', ['BoardData', 'ShapeBuilder', 'ShapeEditor', 'ShapeManipulation', 'Snap', function (BoardData, ShapeBuilder, ShapeEditor, ShapeManipulation, Snap) {
3 |
4 | function setSocketId (socketId) {
5 | BoardData.setSocketId(socketId);
6 | };
7 |
8 | function createShape (id, socketId, tool, x, y) {
9 | ShapeBuilder.newShape(id, socketId, tool, x, y);
10 | }
11 |
12 | function editShape (id, socketId, tool, x, y) {
13 | ShapeEditor.editShape(id, socketId, tool, x, y);
14 | }
15 |
16 | function finishShape (id, socketId, tool) {
17 | ShapeEditor.finishShape(id, socketId, tool);
18 | }
19 |
20 | function finishCopiedPath (id, socketId, tool, pathDProps) {
21 | ShapeEditor.finishCopiedPath(id, socketId, tool, pathDProps);
22 | }
23 |
24 | function deleteShape (id, socketId) {
25 | ShapeEditor.deleteShape(id, socketId);
26 | }
27 |
28 | function moveShape (shape, x, y) {
29 | ShapeManipulation.moveShape(shape.myid, shape.socketId, x, y);
30 | }
31 |
32 | function finishMovingShape (id, socketId) {
33 | ShapeManipulation.finishMovingShape(id, socketId);
34 | }
35 |
36 | function drawExistingPath (shape) {
37 | ShapeBuilder.drawExistingPath(shape);
38 | var currentShape = BoardData.getShapeById(shape.myid, shape.socketId);
39 | ShapeManipulation.pathSmoother(currentShape);
40 | }
41 |
42 | function cursor (screenPosition) {
43 | var cursor = BoardData.getCursor() || BoardData.setCursor();
44 | BoardData.moveCursor(screenPosition);
45 | }
46 |
47 | function grabShape (screenPosition) {
48 | var x = Math.floor(screenPosition[0]);
49 | var y = Math.floor(screenPosition[1]);
50 |
51 | var currentEditorShape;
52 |
53 | currentEditorShape = BoardData.getEditorShape();
54 |
55 | if (!currentEditorShape) {
56 | var shape = BoardData.getBoard().getElementByPoint(x, y);
57 | if (shape) {
58 | BoardData.setEditorShape(shape);
59 | currentEditorShape = BoardData.getEditorShape();
60 | }
61 | } else {
62 | moveShape(currentEditorShape, x, y);
63 | }
64 | }
65 |
66 | return {
67 | cursor: cursor,
68 | setSocketId: setSocketId,
69 | createShape: createShape,
70 | editShape: editShape,
71 | finishShape: finishShape,
72 | finishCopiedPath: finishCopiedPath,
73 | deleteShape: deleteShape,
74 | moveShape: moveShape,
75 | finishMovingShape: finishMovingShape,
76 | drawExistingPath: drawExistingPath,
77 | grabShape: grabShape
78 | };
79 | }]);
80 |
--------------------------------------------------------------------------------
/client/services/input-handler.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.inputhandler', [])
2 | .factory('InputHandler', ['BoardData', 'Snap', 'EventHandler', 'Broadcast', 'Visualizer', 'Zoom', function (BoardData, Snap, EventHandler, Broadcast, Visualizer, Zoom) {
3 | var toggleAttrs = {};
4 | function toggle (attr) {
5 | if (!toggleAttrs[attr]) {
6 | toggleAttrs[attr] = true;
7 | } else {
8 | toggleAttrs[attr] = false;
9 | }
10 | }
11 | function isToggled (attr) {
12 | return toggleAttrs[attr];
13 | }
14 |
15 | function getClosestElementByArea (ev) {
16 | var paper = BoardData.getBoard();
17 | var width = height = 5;
18 | var mouseXY = getMouseXY(ev);
19 | var bbox = {
20 | x: mouseXY.x - width / 2,
21 | y: mouseXY.y - height / 2,
22 | x2: mouseXY.x + width / 2,
23 | y2: mouseXY.y + height / 2,
24 | width: width,
25 | height: height
26 | };
27 | var bboxCenter = {x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2};
28 | // var set = this.set();
29 | var closest = null;
30 | var closestDist;
31 | paper.forEach(function (el) {
32 | var elBBox = el.getBBox();
33 | if (!(el.type === 'set') && Raphael.isBBoxIntersect(elBBox, bbox)) {
34 | var elBBoxCenter = {x: elBBox.x + elBBox.width / 2, y: elBBox.y + elBBox.height / 2};
35 | var dist = Math.sqrt(Math.pow(elBBoxCenter.x - bboxCenter.x, 2) + Math.pow(elBBoxCenter.y - bboxCenter.y, 2));
36 | if (!closestDist || dist < closestDist) {
37 | closest = el;
38 | closestDist = dist;
39 | }
40 | }
41 | });
42 | return closest;
43 | };
44 |
45 | var actions = {};
46 |
47 | var lastEv;
48 | actions.eraser = {
49 | mouseDown: function (ev) {
50 | },
51 | mouseHold: function (ev) {
52 | if (lastEv) {
53 | var mousePathString = 'M' + lastEv.clientX + ',' + lastEv.clientY + 'L' + ev.clientX + ',' + ev.clientY;
54 | BoardData.getBoard().forEach(function (shape) {
55 | if (shape.type === 'path') {
56 | if (Raphael.pathIntersection(mousePathString, shape.attr('path')).length) {
57 | Broadcast.deleteShape(shape.myid, shape.socketId);
58 | EventHandler.deleteShape(shape.myid, shape.socketId);
59 | }
60 | }
61 | });
62 | }
63 | var shape = BoardData.getBoard().getElementByPoint(ev.clientX, ev.clientY);
64 | if (shape) {
65 | Broadcast.deleteShape(shape.myid, shape.socketId);
66 | EventHandler.deleteShape(shape.myid, shape.socketId);
67 | }
68 | lastEv = ev;
69 | },
70 | mouseUp: function (ev) {
71 | lastEv = null;
72 | },
73 | mouseOver: function (ev) {
74 | }
75 | };
76 |
77 | actions.pan = {
78 | mouseDown: function (ev) {
79 | },
80 | mouseHold: function (ev) {
81 | Zoom.pan(ev);
82 | },
83 | mouseUp: function (ev) {
84 | Zoom.resetPan();
85 | },
86 | mouseOver: function (ev) {
87 | }
88 | };
89 |
90 | actions.move = {
91 | mouseDown: function (ev) {
92 | Visualizer.clearSelection();
93 | var target = getClosestElementByArea(ev);
94 |
95 | if (target) {
96 | BoardData.setEditorShape(target);
97 | } else {
98 | toggle('move');
99 | }
100 | },
101 | mouseHold: function (ev) {
102 | var currentEditorShape = BoardData.getEditorShape();
103 | var mouseXY = getMouseXY(ev);
104 |
105 | Visualizer.clearSelection();
106 | EventHandler.moveShape(currentEditorShape, mouseXY.x, mouseXY.y);
107 | Broadcast.moveShape(currentEditorShape, mouseXY.x, mouseXY.y);
108 | },
109 | mouseUp: function (ev) {
110 | var editorShape = BoardData.getEditorShape();
111 | var currentTool = BoardData.getCurrentTool();
112 |
113 | Broadcast.finishMovingShape(editorShape);
114 | EventHandler.finishMovingShape(editorShape.myid, editorShape.socketId);
115 | BoardData.unsetEditorShape();
116 | },
117 | mouseOver: function (ev) {
118 | Visualizer.clearSelection();
119 | var selection = getClosestElementByArea(ev);
120 | Visualizer.visualizeSelection(selection);
121 | }
122 | };
123 |
124 | actions.copy = {
125 | mouseDown: function (ev) {
126 | Visualizer.clearSelection();
127 | var shape = getClosestElementByArea(ev);
128 | var socketId = BoardData.getSocketId();
129 |
130 | var newId = BoardData.generateShapeId();
131 |
132 | var newInitX = shape.initX + 10;
133 | var newInitY = shape.initY + 10;
134 | var newMouseX = shape.mouseX + 10 || newInitX;
135 | var newMouseY = shape.mouseY + 10 || newInitY;
136 |
137 | EventHandler.createShape(newId, socketId, shape.tool, newInitX, newInitY);
138 | Broadcast.newShape(newId, socketId, shape.tool, newInitX, newInitY);
139 | if (shape.tool.name === 'path') {
140 | BoardData.setCurrentShape(newId);
141 |
142 | var currentShape = BoardData.getCurrentShape();
143 | var parsedPathArray = Raphael.parsePathString(shape.pathDProps);
144 |
145 | var temp = parsedPathArray.map(function (coordinate) {
146 | return coordinate.map(function (element) {
147 | return typeof element === 'number' ? element + 10 : element;
148 | });
149 | });
150 |
151 | var stringifiedPath = temp.map(function (coordinate) {
152 | return coordinate[0] + coordinate[1] + ',' + coordinate[2];
153 | }).join('');
154 |
155 | currentShape.pathDProps = stringifiedPath;
156 | }
157 |
158 | if (!!shape.tool.text) {
159 | EventHandler.editShape(newId, socketId, shape.tool, newInitX, newInitY);
160 | } else {
161 | EventHandler.editShape(newId, socketId, shape.tool, newMouseX, newMouseY);
162 | }
163 |
164 | Broadcast.editShape(newId, socketId, shape.tool, newMouseX, newMouseY);
165 |
166 | EventHandler.finishShape(newId, socketId, shape.tool);
167 | shape.tool.name === 'path' ? Broadcast.finishCopiedPath(newId, shape.tool, currentShape.pathDProps) : Broadcast.finishShape(newId, shape.tool);
168 | },
169 | mouseOver: function (ev) {
170 | Visualizer.clearSelection();
171 | var selection = getClosestElementByArea(ev);
172 | Visualizer.visualizeSelection(selection);
173 | }
174 | };
175 |
176 | var defaultText = 'Start Typing...'
177 | actions.text = {
178 | mouseDown: function (ev) {
179 | var id = BoardData.generateShapeId();
180 | var mouseXY = getMouseXY(ev);
181 | var socketId = BoardData.getSocketId();
182 | var currentTool = BoardData.getCurrentTool();
183 | currentTool.text = defaultText;
184 |
185 | EventHandler.createShape(id, socketId, currentTool, mouseXY.x, mouseXY.y);
186 | BoardData.setCurrentShape(id);
187 | Broadcast.newShape(id, socketId, currentTool, mouseXY.x, mouseXY.y);
188 | var currentShape = BoardData.getCurrentShape();
189 |
190 | document.onkeypress = function (ev) {
191 | BoardData.setEditorShape(currentShape);
192 | var editorShape = BoardData.getEditorShape();
193 | if (editorShape.attr('text') === defaultText) {
194 | editorShape.attr('text', '');
195 | currentTool.text = '';
196 | }
197 |
198 | if (ev.keyCode === 13) {
199 | // enter key to complete text insertion process
200 | editorShape.tool = currentTool;
201 | editorShape.tool.colors.fill = editorShape.trueColors.fill;
202 | editorShape.tool.colors.stroke = editorShape.trueColors.stroke;
203 | editorShape.attr({
204 | 'fill': editorShape.trueColors.fill,
205 | 'stroke': editorShape.trueColors.stroke
206 | })
207 | EventHandler.finishShape(id, socketId, editorShape.tool);
208 | Broadcast.finishShape(id, editorShape.tool);
209 | editorShape = null;
210 | document.onkeydown = document.onkeypress = function () {};
211 | } else {
212 | // typing text
213 | editorShape.attr('text', editorShape.attr('text') + String.fromCharCode(ev.keyCode));
214 | currentTool.text = editorShape.attr('text');
215 | EventHandler.editShape(id, socketId, currentTool, editorShape.initX, editorShape.initY);
216 | Broadcast.editShape(id, socketId, currentTool, editorShape.initX, editorShape.initY);
217 | }
218 | }
219 |
220 | document.onkeydown = function (ev) {
221 | BoardData.setEditorShape(currentShape);
222 | var editorShape = BoardData.getEditorShape();
223 | if (ev.which === 8) {
224 | ev.preventDefault();
225 | if (editorShape) {
226 | editorShape.attr('text', editorShape.attr('text').slice(0, editorShape.attr('text').length - 1));
227 | currentTool.text = editorShape.attr('text');
228 | EventHandler.editShape(id, socketId, currentTool, editorShape.initX, editorShape.initY);
229 | Broadcast.editShape(id, socketId, currentTool, editorShape.initX, editorShape.initY);
230 | }
231 | }
232 | }
233 |
234 | },
235 | mouseHold: function (ev) {
236 | },
237 | mouseUp: function (ev) {
238 | },
239 | mouseOver: function (ev) {
240 | }
241 | };
242 |
243 | actions.shape = {
244 | mouseDown: function (ev) {
245 | var socketId = BoardData.getSocketId();
246 | var currentTool = BoardData.getCurrentTool();
247 | var mouseXY = getMouseXY(ev);
248 | var coords = Snap.snapToPoints(mouseXY.x, mouseXY.y);
249 | var id = BoardData.generateShapeId();
250 |
251 | if (currentTool.name !== 'text' && currentTool.text) {
252 | delete currentTool.text;
253 | }
254 |
255 | EventHandler.createShape(id, socketId, currentTool, coords[0], coords[1]);
256 | BoardData.setCurrentShape(id);
257 | Broadcast.newShape(id, socketId, currentTool, coords[0], coords[1]);
258 | },
259 | mouseHold: function (ev) {
260 | var id = BoardData.getCurrentShapeId();
261 | var socketId = BoardData.getSocketId();
262 | var currentTool = BoardData.getCurrentTool();
263 | var mouseXY = getMouseXY(ev);
264 | var coords = Snap.snapToPoints(mouseXY.x, mouseXY.y);
265 |
266 | Broadcast.editShape(id, socketId, currentTool, coords[0], coords[1]);
267 | EventHandler.editShape(id, socketId, currentTool, coords[0], coords[1]);
268 | },
269 | mouseUp: function (ev) {
270 | var id = BoardData.getCurrentShapeId();
271 | var socketId = BoardData.getSocketId();
272 | var currentTool = BoardData.getCurrentTool();
273 | var shape = BoardData.getCurrentShape();
274 |
275 | var currentToolCopy = {};
276 | currentToolCopy.name = currentTool.name;
277 | currentToolCopy['stroke-width'] = currentTool['stroke-width'];
278 | currentToolCopy.colors = {};
279 | currentToolCopy.colors.fill = currentTool.colors.fill;
280 | currentToolCopy.colors.stroke = currentTool.colors.stroke;
281 |
282 | shape.tool = currentToolCopy;
283 |
284 | EventHandler.finishShape(id, socketId, currentToolCopy);
285 | BoardData.unsetCurrentShape();
286 | Visualizer.clearSnaps();
287 |
288 | if (currentTool.name === 'path') {
289 | Broadcast.finishPath(id, currentTool, shape.pathDProps);
290 | } else {
291 | Broadcast.finishShape(id, currentTool);
292 | }
293 | },
294 | mouseOver: function (ev) {
295 | var mouseXY = getMouseXY(ev);
296 | Snap.snapToPoints(mouseXY.x, mouseXY.y);
297 | }
298 | };
299 |
300 | actions.magnify = {
301 | mouseDown: function (ev) {
302 | },
303 | mouseHold: function (ev) {
304 | var mouseXY = getMouseXY(ev);
305 |
306 | Zoom.zoom(ev, mouseXY);
307 | },
308 | mouseUp: function (ev) {
309 | Zoom.resetZoom();
310 | },
311 | mouseOver: function (ev) {
312 | }
313 | };
314 |
315 | actions.noTool = {
316 | mouseDown: function (ev) {
317 | },
318 | mouseHold: function (ev) {
319 | },
320 | mouseUp: function (ev) {
321 | },
322 | mouseOver: function (ev) {
323 | }
324 | };
325 |
326 | function getMouseXY (ev) {
327 | var canvasMarginXY = BoardData.getCanvasMargin();
328 | var scalingFactor = BoardData.getScalingFactor();
329 | var offsetXY = BoardData.getOffset();
330 | return {
331 | x: (ev.clientX - canvasMarginXY.x) * scalingFactor + offsetXY.x,
332 | y: (ev.clientY - canvasMarginXY.y) * scalingFactor + offsetXY.y
333 | };
334 | }
335 |
336 | var shapeTools = ['line','circle','path','rectangle','arrow'];
337 | function parseToolName (toolName) {
338 | for (var i = 0; i < shapeTools.length; i++) {
339 | if (toolName === shapeTools[i]) {
340 | toolName = 'shape';
341 | }
342 | }
343 | if (!toolName) {
344 | toolName = 'noName';
345 | }
346 | return toolName;
347 | }
348 |
349 | function mouseDown (ev) {
350 | var toolName = parseToolName(BoardData.getCurrentTool().name);
351 |
352 | toggle(toolName);
353 | actions[toolName].mouseDown(ev);
354 | }
355 |
356 | function mouseMove (ev) {
357 | var toolName = parseToolName(BoardData.getCurrentTool().name);
358 |
359 | if (isToggled(toolName)) {
360 | actions[toolName].mouseHold(ev);
361 | } else {
362 | actions[toolName].mouseOver(ev);
363 | }
364 | }
365 |
366 | function mouseUp (ev) {
367 | var toolName = parseToolName(BoardData.getCurrentTool().name);
368 |
369 | if (isToggled(toolName)) {
370 | toggle(toolName);
371 | actions[toolName].mouseUp(ev);
372 | }
373 | }
374 |
375 | function keyPress (ev) {
376 | var toolName = parseToolName(BoardData.getCurrentTool().name);
377 |
378 | if (toolName !== 'text') {
379 | // keycode value for lowercase m
380 | if (ev.keyCode === 109) {
381 | console.log('m has been typed');
382 | }
383 | }
384 | }
385 |
386 | return {
387 | mousedown: mouseDown,
388 | mousemove: mouseMove,
389 | mouseup: mouseUp,
390 | keypress: keyPress
391 | };
392 | }]);
393 |
--------------------------------------------------------------------------------
/client/services/leap.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.leapMotion', [])
2 | .factory('LeapMotion', ['EventHandler', function (EventHandler) {
3 |
4 | // var controller = new Leap.Controller({enableGestures: true})
5 | // .use('screenPosition', {scale: 0.25})
6 | // .connect()
7 | // .on('frame', function(frame){
8 |
9 | // // if (frame.valid && frame.gestures.length > 0) {
10 | // // frame.gestures.forEach(function(gesture){
11 | // // switch (gesture.type){
12 | // // case "circle":
13 | // // console.log("Circle Gesture");
14 | // // break;
15 | // // case "keyTap":
16 | // // console.log("Key Tap Gesture");
17 | // // break;
18 | // // case "screenTap":
19 | // // console.log("Screen Tap Gesture");
20 | // // break;
21 | // // case "swipe":
22 | // // console.log("Swipe Gesture");
23 | // // break;
24 | // // }
25 | // // });
26 | // // }
27 |
28 | // frame.hands.forEach(function (hand, index) {
29 | // console.log(hand.indexFinger.touchZone);
30 | // EventHandler.cursor(hand.indexFinger.screenPosition());
31 | // if (hand.indexFinger.extended) {
32 | // //
33 | // }
34 | // if (hand.grabStrength === 1) {
35 | // // console.log('grabStrength === 1')
36 | // EventHandler.grabShape(hand.screenPosition());
37 | // //console.log(hand.pinchStrength)
38 | // }
39 |
40 | // if (hand.pinchStrength === 1) {
41 | // // console.log('pinchStrength === 1');
42 | // }
43 | // });
44 | // })
45 |
46 | return {};
47 | }]);
48 |
--------------------------------------------------------------------------------
/client/services/receive.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.receive', [])
2 | .factory('Receive', function (Sockets, EventHandler) {
3 | Sockets.on('showExisting', function (data) {
4 | for (socketId in data) {
5 | if (Object.keys(data[socketId]).length) {
6 | for (id in data[socketId]) {
7 | var thisShape = data[socketId][id];
8 | if (thisShape.tool.name === 'path') {
9 | EventHandler.drawExistingPath(thisShape);
10 | } else if (thisShape.initX && thisShape.initY) {
11 | EventHandler.createShape(id, socketId, thisShape.tool, thisShape.initX, thisShape.initY);
12 | if (thisShape.tool.name !== 'text') {
13 | EventHandler.editShape(id, socketId, thisShape.tool, thisShape.mouseX, thisShape.mouseY);
14 | }
15 | EventHandler.finishShape(thisShape.myid, thisShape.socketId, thisShape.tool);
16 | }
17 | }
18 | }
19 | }
20 | });
21 |
22 | Sockets.on('heartbeat', function () {
23 | Sockets.emit('heartbeat');
24 | })
25 |
26 | Sockets.on('socketId', function (data) {
27 | EventHandler.setSocketId(data.socketId);
28 | });
29 |
30 | Sockets.on('shapeEdited', function (data) {
31 | EventHandler.editShape(data.myid, data.socketId, data.tool, data.mouseX, data.mouseY);
32 | });
33 |
34 | Sockets.on('shapeCompleted', function (data) {
35 | EventHandler.finishShape(data.myid, data.socketId, data.tool);
36 | });
37 |
38 | Sockets.on('copiedPathCompleted', function (data) {
39 | EventHandler.finishCopiedPath(data.myid, data.socketId, data.tool, data.pathDProps);
40 | });
41 |
42 | Sockets.on('shapeCreated', function (data) {
43 | EventHandler.createShape(data.myid, data.socketId, data.tool, data.initX, data.initY);
44 | });
45 |
46 | Sockets.on('shapeMoved', function (data) {
47 | EventHandler.moveShape(data, data.x, data.y);
48 | });
49 |
50 | Sockets.on('shapeFinishedMoving', function (data) {
51 | EventHandler.finishMovingShape(data.myid, data.socketId);
52 | });
53 |
54 | Sockets.on('shapeDeleted', function (data) {
55 | EventHandler.deleteShape(data.myid, data.socketId);
56 | });
57 |
58 | return {};
59 |
60 | });
61 |
--------------------------------------------------------------------------------
/client/services/shape-builder.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.shapebuilder', [])
2 | .factory('ShapeBuilder', ['BoardData', 'Snap', function (BoardData, Snap) {
3 |
4 | function setColor (shape, colors) {
5 | if (shape.type === 'path') {
6 | shape.attr('stroke', colors.stroke);
7 | } else if (shape.type === 'text' && shape.attr('text') === 'Start Typing...') {
8 | shape.attr('stroke', '#b3b3b3');
9 | shape.attr('fill', '#b3b3b3');
10 | shape.trueColors = {
11 | stroke: colors.stroke,
12 | fill: colors.fill
13 | };
14 | } else {
15 | shape.attr('stroke', colors.stroke);
16 | shape.attr('fill', colors.fill);
17 | }
18 | }
19 |
20 | function setWidth (shape, width) {
21 | shape.attr('stroke-width', width);
22 | }
23 |
24 | function drawExistingPath (shape) {
25 | newShape(shape.myid, shape.socketId, shape.tool, shape.initX, shape.initY);
26 | var existingPath = BoardData.getShapeById(shape.myid, shape.socketId);
27 | existingPath.customSetPathD(shape.pathDProps);
28 | existingPath.pathDProps = shape.pathDProps;
29 | existingPath.attr('fill', existingPath.tool.colors.fill);
30 | BoardData.pushToStorage(shape.myid, shape.socketId, existingPath);
31 | }
32 |
33 | function newShape (id, socketId, tool, x, y) {
34 | var shapeConstructors = {
35 | 'circle': function (x, y) {
36 | return BoardData.getBoard().circle(x, y, 0);
37 | },
38 | 'line': function (x, y) {
39 | return BoardData.getBoard().path("M" + String(x) + "," + String(y))
40 | .attr({
41 | 'stroke-linecap': 'round'
42 | });
43 | },
44 | 'path': function (x, y) {
45 | var path = BoardData.getBoard().path("M" + String(x) + "," + String(y))
46 | .attr({
47 | 'stroke-linecap': 'round'
48 | });
49 | path.pathDProps = '';
50 | return path;
51 | },
52 | 'rectangle': function (x,y) {
53 | return BoardData.getBoard().rect(x, y, 0, 0);
54 | },
55 | 'text': function (x, y, text) {
56 | return BoardData.getBoard().text(x, y, text)
57 | .attr({
58 | 'font-size': 18,
59 | 'font-family': "San Francisco"
60 | });
61 | },
62 | 'arrow': function (x, y) {
63 | var arrow = BoardData.getBoard().path("M" + String(x) + ',' + String(y));
64 | arrow.attr('arrow-end', 'classic-wide-long');
65 | return arrow;
66 | }
67 | };
68 | var shape = !!tool.text ? shapeConstructors['text'](x, y, tool.text) : shapeConstructors[tool.name](x, y);
69 | shape.initX = x;
70 | shape.initY = y;
71 | shape.tool = tool;
72 | setColor(shape, tool.colors);
73 | shape.myid = id;
74 | shape.socketId = socketId;
75 | if (tool.name === 'path') Snap.createSnaps(shape);
76 | if (tool.name !== 'text') setWidth(shape, tool['stroke-width']);
77 | BoardData.pushToStorage(id, socketId, shape);
78 | };
79 |
80 | return {
81 | newShape: newShape,
82 | drawExistingPath: drawExistingPath
83 | };
84 |
85 | }]);
86 |
--------------------------------------------------------------------------------
/client/services/shape-editor.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.shapeeditor', [])
2 | .factory('ShapeEditor', ['BoardData', 'Snap', 'ShapeManipulation', function (BoardData, Snap, ShapeManipulation) {
3 |
4 | var changeCircle = function (shape, x, y) {
5 | var deltaX = x - shape.initX;
6 | var deltaY = y - shape.initY;
7 | var newRadius = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
8 | shape.attr('r', newRadius);
9 | };
10 |
11 | var changeLine = function (shape, x, y) {
12 | //"M10,20L30,40"
13 | var deltaX = x - shape.initX;
14 | var deltaY = y - shape.initY;
15 |
16 | if (Math.sqrt(Math.pow(deltaX,2) + Math.pow(deltaY,2)) > 20) {
17 | if (Math.abs(deltaY) < 5) {
18 | y = shape.initY;
19 | } else if (Math.abs(deltaX) < 5) {
20 | x = shape.initX;
21 | }
22 | }
23 |
24 | var linePathOrigin = "M" + String(shape.initX) + "," + String(shape.initY);
25 | var linePathEnd = "L" + String(x) + "," + String(y);
26 | shape.attr('path', linePathOrigin + linePathEnd);
27 | };
28 |
29 | var changePath = function (shape, x, y) {
30 | //"M10,20L30,40"
31 |
32 | shape.pathDProps += shape.pathDProps === '' ? 'M' + shape.initX + ',' + shape.initY + 'L' + x + ',' + y : 'L' + x + ',' + y;
33 | //this custom function is in raphael
34 | shape.customSetPathD(shape.pathDProps);
35 | };
36 |
37 | var changeRectangle = function (shape, x, y) {
38 | var left, top;
39 |
40 | if (x < shape.initX && y < shape.initY) {
41 | left = x;
42 | top = y;
43 | width = shape.initX - left;
44 | height = shape.initY - top;
45 | } else if (x < shape.initX) {
46 | left = x;
47 | top = shape.initY;
48 | width = shape.initX - left;
49 | height = y - shape.initY;
50 | } else if (y < shape.initY) {
51 | left = shape.attr('x');
52 | top = y;
53 | width = x - shape.initX;
54 | height = shape.initY - top;
55 | } else {
56 | left = shape.attr('x');
57 | top = shape.attr('y');
58 | width = x - shape.initX;
59 | height = y - shape.initY;
60 | }
61 |
62 | shape.attr({
63 | x: left,
64 | y: top,
65 | width: width,
66 | height: height
67 | });
68 | }
69 |
70 | var changeText = function (shape, x, y, tool) {
71 | shape.attr({
72 | x: x,
73 | y: y,
74 | text: tool.text,
75 | 'stroke-width': 1
76 | });
77 | };
78 |
79 | function editShape (id, socketId, tool, x, y) {
80 | var shapeHandlers = {
81 | 'circle': changeCircle,
82 | 'path': changePath,
83 | 'line': changeLine,
84 | 'arrow': changeLine,
85 | 'rectangle': changeRectangle,
86 | 'text': changeText
87 | };
88 | var shape = BoardData.getShapeById(id, socketId);
89 |
90 | if (tool.name !== 'text') {
91 | shape.mouseX = x;
92 | shape.mouseY = y;
93 | }
94 |
95 | // optional tool argument for text change
96 | !!tool.text ? shapeHandlers['text'](shape, x, y, tool) : shapeHandlers[tool.name](shape, x, y);
97 | };
98 |
99 | function finishShape (id, socketId, tool) {
100 | var shape = BoardData.getShapeById(id, socketId);
101 |
102 | if (shape.type === 'text') {
103 | if (shape.attr('text') === 'Start Typing...') {
104 | shape.attr('text', '');
105 | } else {
106 | shape.attr({
107 | text: tool.text,
108 | stroke: tool.colors.stroke
109 | });
110 | }
111 | }
112 |
113 | if (shape.pathDProps !== undefined) {
114 | var path = shape.pathDProps;
115 | var lastPoint = path.slice(path.lastIndexOf('L') + 1).split(',').map(Number);
116 | if (lastPoint[0] === shape.initX && lastPoint[1] === shape.initY) {
117 | shape.pathDProps = path + 'Z';
118 | shape.attr('fill', shape.tool.colors.fill ? shape.tool.colors.fill : (shape.tool.colors.fill = tool.colors.fill));
119 | }
120 | ShapeManipulation.pathSmoother(shape);
121 | }
122 |
123 | Snap.createSnaps(shape);
124 | };
125 |
126 | function finishCopiedPath (id, socketId, tool, pathDProps) {
127 | var shape = BoardData.getShapeById(id, socketId);
128 | shape.pathDProps = pathDProps;
129 | shape.attr('path', shape.pathDProps);
130 | if ((shape.myid || shape.myid === 0) && tool.name === 'path') {
131 | ShapeManipulation.pathSmoother(shape);
132 | }
133 | }
134 |
135 | function deleteShape (id, socketId) {
136 | var shape = BoardData.getShapeById(id, socketId);
137 |
138 | Snap.deleteSnaps(shape);
139 | shape.remove();
140 | };
141 |
142 | return {
143 | editShape: editShape,
144 | finishShape: finishShape,
145 | finishCopiedPath: finishCopiedPath,
146 | deleteShape: deleteShape
147 | };
148 |
149 | }]);
150 |
--------------------------------------------------------------------------------
/client/services/shape-manipulation.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.shapemanipulation', [])
2 | .factory('ShapeManipulation', ['BoardData', 'ShapeBuilder', 'Snap', function (BoardData, ShapeBuilder, Snap) {
3 |
4 | var pathSmoother = function (pathElement) {
5 | var path = pathElement.attr('path');
6 | path = path.length > 1 ? path : Raphael.parsePathString(pathElement.pathDProps);
7 | if (!path) return;
8 | var interval = 5;
9 |
10 | var newPath = path.reduce(function (newPathString, currentPoint, index, path) {
11 | if (!(index % interval) || index === (path.length - 1)) {
12 | return newPathString += currentPoint[1] + ',' + currentPoint[2] + ' ';
13 | } else {
14 | return newPathString;
15 | }
16 | }, path[0][0] + path[0][1] + ',' + path[0][2] + ' ' + "R");
17 |
18 | if (path[path.length - 1] === 'Z') {
19 | newPath += 'Z';
20 | }
21 | pathElement.attr('path', newPath);
22 | };
23 |
24 | var grabPoint;
25 | var origin;
26 | function moveCircle (shape, x, y) {
27 | var deltaX = x - grabPoint.x;
28 | var deltaY = y - grabPoint.y;
29 | shape.attr({
30 | cx: origin.cx + deltaX,
31 | cy: origin.cy + deltaY
32 | });
33 | }
34 |
35 | function moveRectangle (shape, x, y) {
36 | var deltaX = x - grabPoint.x;
37 | var deltaY = y - grabPoint.y;
38 | shape.attr({
39 | x: origin.x + deltaX,
40 | y: origin.y + deltaY
41 | });
42 | }
43 |
44 | function movePath (shape, x, y) {
45 | var deltaX = x - grabPoint.x;
46 | var deltaY = y - grabPoint.y;
47 |
48 | var pathArr = shape.attr('path');
49 | for (var seg in pathArr) {
50 | pathArr[seg][1] = origin.path[seg][1] + deltaX;
51 | pathArr[seg][2] = origin.path[seg][2] + deltaY;
52 | if (pathArr[seg].length > 3) {
53 | pathArr[seg][3] = origin.path[seg][3] + deltaX;
54 | pathArr[seg][4] = origin.path[seg][4] + deltaY;
55 | pathArr[seg][5] = origin.path[seg][5] + deltaX;
56 | pathArr[seg][6] = origin.path[seg][6] + deltaY;
57 | shape.pathDProps = origin.pathDProps.split('L').map(function (subpath, index) {
58 | var xy = subpath.split(',');
59 | var x;
60 | if (index === 0) {
61 | x = 'M' + (+xy[0].slice(1) + deltaX);
62 | } else {
63 | x = +xy[0] + deltaX;
64 | }
65 | var y = +xy[1] + deltaY;
66 | return x + ',' + y;
67 | }).join('L');
68 | }
69 | }
70 |
71 | shape.attr('path',pathArr);
72 | }
73 |
74 | function moveText (shape, x, y) {
75 | var deltaX = x - grabPoint.x;
76 | var deltaY = y - grabPoint.y;
77 | shape.attr({
78 | x: origin.x + deltaX,
79 | y: origin.y + deltaY
80 | });
81 | }
82 |
83 | function moveShape (id, socketId, x, y) {
84 | var shapeHandlers = {
85 | 'circle': moveCircle,
86 | 'path': movePath,
87 | 'line': movePath,
88 | 'rect': moveRectangle,
89 | 'text': moveText
90 | };
91 | var shape = BoardData.getShapeById(id, socketId).toFront();
92 | if (!grabPoint) {
93 | grabPoint = {x: x, y: y};
94 | origin = shape.attr();
95 | origin.pathDProps = shape.pathDProps;
96 | }
97 | shapeHandlers[shape.type](shape, x, y);
98 | }
99 |
100 | function finishMovingShape (id, socketId) {
101 | grabPoint = null;
102 | origin = null;
103 |
104 | var shape = BoardData.getShapeById(id, socketId);
105 | Snap.createSnaps(shape);
106 | }
107 |
108 | return {
109 | pathSmoother: pathSmoother,
110 | moveShape: moveShape,
111 | finishMovingShape: finishMovingShape
112 | };
113 |
114 | }]);
115 |
--------------------------------------------------------------------------------
/client/services/snap.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.snap', [])
2 | .factory('Snap', ['BoardData', 'Visualizer', function (BoardData, Visualizer) {
3 | var endSnapTree;
4 | function Point (x, y) {
5 | this.x = x;
6 | this.y = y;
7 | }
8 |
9 | function Node (val, left, right) {
10 | this.val = val;
11 | this.left = left || null;
12 | this.right = right || null;
13 | }
14 |
15 | function Rectangle (x0, y0, x1, y1) {
16 | this.left = x0;
17 | this.bottom = y0;
18 | this.right = x1;
19 | this.top = y1;
20 | }
21 |
22 | function KDTree (points, depth) {
23 | var split, sortedPoints;
24 | points = points || generatePoints(10);
25 | depth = depth || 0;
26 | if (points.length <= 1) {
27 | return points[0];
28 | }
29 | else {
30 | var mid = Math.ceil(points.length / 2);
31 | if ((depth % 2) === 0) {
32 | sortedPoints = points.slice().sort(function (a,b) {
33 | return a.x - b.x;
34 | });
35 | split = sortedPoints[mid].x;
36 | } else {
37 | sortedPoints = points.slice().sort(function (a,b) {
38 | return a.y - b.y;
39 | });
40 | split = sortedPoints[mid].y;
41 | }
42 | var left = new KDTree(sortedPoints.slice(0, mid), depth + 1);
43 | var right = new KDTree(sortedPoints.slice(mid), depth + 1);
44 | return new Node(split, left, right);
45 | }
46 | }
47 |
48 | function reportSubtree (node) {
49 | if (node instanceof Node) {
50 | var returnArr = [];
51 | return returnArr.concat(reportSubtree(node.left), reportSubtree(node.right));
52 | } else {
53 | return node;
54 | }
55 | }
56 |
57 | function pointIsInRange (point, range) {
58 | return point.x >= range.left && point.x <= range.right && point.y >= range.bottom && point.y <= range.top;
59 | }
60 |
61 | function regionIntersection (r1, r2) {
62 | var left = Math.max(r1.left, r2.left);
63 | var bottom = Math.max(r1.bottom, r2.bottom);
64 | var right = Math.min(r1.right, r2.right);
65 | var top = Math.min(r1.top, r2.top);
66 | if (right < left || top < bottom) {
67 | return null;
68 | } else {
69 | return new Rectangle(left, bottom, right, top);
70 | }
71 | }
72 |
73 | function regionContainedInRange (region, range) {
74 | return region.left > range.left && region.bottom > range.bottom && region.right < range.right && region.top < range.top;
75 | }
76 |
77 | function searchKDTree (node, range, nodeRange, depth) {
78 | depth = depth || 0;
79 | nodeRange = nodeRange || new Rectangle(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
80 | // base case if node is a leaf
81 | if (typeof node.x === 'number' && typeof node.y === 'number') {
82 | if (pointIsInRange(node, range)) {
83 | return node;
84 | }
85 | } else {
86 | var leftRange = new Rectangle(nodeRange.left, nodeRange.bottom, nodeRange.right, nodeRange.top);
87 | var rightRange = new Rectangle(nodeRange.left, nodeRange.bottom, nodeRange.right, nodeRange.top);
88 | if ((depth % 2) === 0) {
89 | // split on x
90 | leftRange.right = node.val;
91 | rightRange.left = node.val;
92 | } else {
93 | // split on y
94 | leftRange.top = node.val;
95 | rightRange.bottom = node.val;
96 | }
97 | var returnArr = [];
98 | var subtreeNodes;
99 | // check if left region is fully contained in range
100 | if (regionContainedInRange(leftRange, range)) {
101 | subtreeNodes = reportSubtree(node.left);
102 | if (subtreeNodes) returnArr = returnArr.concat(subtreeNodes);
103 | // else check if left region intersects range
104 | } else if (regionIntersection(leftRange, range)) {
105 | subtreeNodes = searchKDTree(node.left, range, leftRange, depth + 1);
106 | if (subtreeNodes) returnArr = returnArr.concat(subtreeNodes);
107 | }
108 | // check if right region is fully contained in range
109 | if (regionContainedInRange(rightRange, range)) {
110 | subtreeNodes = reportSubtree(node.right);
111 | if (subtreeNodes) returnArr = returnArr.concat(subtreeNodes);
112 | // else check if right region intersects range
113 | } else if (regionIntersection(rightRange, range)) {
114 | subtreeNodes = searchKDTree(node.right, range, rightRange, depth + 1);
115 | if (subtreeNodes) returnArr = returnArr.concat(subtreeNodes);
116 | }
117 | return returnArr;
118 | }
119 | }
120 |
121 | var findSnaps = function (shape) {
122 | var newSnaps = [];
123 | if (shape.type === 'rect') {
124 | var x = shape.attr('x');
125 | var y = shape.attr('y');
126 | var width = shape.attr('width');
127 | var height = shape.attr('height');
128 | var cornerSnaps = [
129 | new Point(x, y),
130 | new Point(x + width, y),
131 | new Point(x, y + height),
132 | new Point(x + width, y + height)
133 | ];
134 | var cardinalSnaps = [
135 | new Point(x + width / 2, y),
136 | new Point(x, y + height / 2),
137 | new Point(x + width, y + height / 2),
138 | new Point(x + width / 2, y + height),
139 | ];
140 | cornerSnaps.forEach(function (snap) {
141 | newSnaps.push(snap);
142 | }.bind(this));
143 | cardinalSnaps.forEach(function (snap) {
144 | newSnaps.push(snap);
145 | }.bind(this));
146 | } else if (shape.type === 'path') {
147 | var path = shape.attr('path');
148 | if (path.length <= 1) {
149 | startPoint = new Point(path[0][1], path[0][2]);
150 | newSnaps.push(startPoint);
151 | } else if (path.length === 2) {
152 | startPoint = new Point(path[0][1], path[0][2]);
153 | endPoint = new Point(path[1][1], path[1][2]);
154 | midPoint = new Point(startPoint.x + (endPoint.x - startPoint.x) / 2, startPoint.y + (endPoint.y - startPoint.y) / 2);
155 | newSnaps.push(startPoint, midPoint, endPoint);
156 | } else {
157 | startPoint = new Point(path[0][1], path[0][2]);
158 | if (path[path.length - 1][0] === 'Z') {
159 | endPoint = new Point(path[path.length - 2][1], path[path.length - 2][2]);
160 | } else {
161 | endPoint = new Point(path[path.length - 1][1], path[path.length - 1][2]);
162 | }
163 | newSnaps.push(startPoint, endPoint);
164 | }
165 | } else if (shape.type === 'circle') {
166 | var cx = shape.attr('cx');
167 | var cy = shape.attr('cy');
168 | var r = shape.attr('r');
169 | var centerSnap = new Point(cx, cy);
170 | cardinalSnaps = [
171 | new Point(cx + r, cy),
172 | new Point(cx - r, cy),
173 | new Point(cx, cy + r),
174 | new Point(cx, cy - r)
175 | ];
176 | newSnaps.push(centerSnap);
177 | cardinalSnaps.forEach(function (snap) {
178 | newSnaps.push(snap);
179 | });
180 | }
181 | return newSnaps;
182 | }
183 |
184 | var createSnaps = function (shape) {
185 | // Visualizer.clearSnaps();
186 | this.endSnaps[shape.myid] = findSnaps(shape);
187 | recreateKDTree(this.endSnaps);
188 | };
189 |
190 | var deleteSnaps = function (shape) {
191 | this.endSnaps[shape.myid] = null;
192 | recreateKDTree(this.endSnaps);
193 | }
194 |
195 | var recreateKDTree = function (snaps) {
196 | var flatSnaps = [];
197 | for (var key in snaps) {
198 | if (snaps[key] !== null) {
199 | flatSnaps = flatSnaps.concat(snaps[key]);
200 | }
201 | }
202 | endSnapTree = new KDTree(flatSnaps);
203 | }
204 |
205 | function objectKeysAreEmpty (object) {
206 | for (var key in object) {
207 | if (Object.keys(object[key]).length !== 0) {
208 | return false;
209 | }
210 | }
211 | return true;
212 | }
213 |
214 | var snapToPoints = function (x, y, tolerance) {
215 | var scale = BoardData.getZoomScale();
216 | if (!this.snapsEnabled || !endSnapTree || !endSnapTree.val) return [x, y];
217 | if (!tolerance) tolerance = this.tolerance;
218 | tolerance *= scale;
219 | var buffer = 50 * scale;
220 | var searchBox = new Rectangle(x - (tolerance + buffer), y - (tolerance + buffer), x + (tolerance + buffer), y + (tolerance + buffer));
221 | var localTree = searchKDTree(endSnapTree, searchBox);
222 | for (var i = 0; i < localTree.length; i++) {
223 | var pointX = localTree[i].x;
224 | var pointY = localTree[i].y;
225 | var dist = Math.sqrt(Math.pow(x - pointX, 2) + Math.pow(y - pointY, 2));
226 | if (dist < tolerance && (!closest || dist < closestDist)) {
227 | var closest = localTree[i];
228 | var closestDist = dist;
229 | }
230 | }
231 | Visualizer.visualizeSnaps(localTree, closest);
232 | if (closest) {
233 | return [closest.x, closest.y];
234 | } else {
235 | return [x,y];
236 | }
237 | };
238 |
239 | return {
240 | endSnaps: {},
241 | snapsEnabled: true,
242 | tolerance: 7,
243 | createSnaps: createSnaps,
244 | deleteSnaps: deleteSnaps,
245 | snapToPoints: snapToPoints
246 | };
247 |
248 | }]);
249 |
--------------------------------------------------------------------------------
/client/services/sockets.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.sockets', [])
2 | .factory('Sockets', function (socketFactory) {
3 | var myIoSocket = io.connect();
4 |
5 | mySocket = socketFactory({
6 | ioSocket: myIoSocket
7 | });
8 |
9 | return mySocket;
10 | });
11 |
--------------------------------------------------------------------------------
/client/services/token.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.token', [])
2 | .factory('AttachTokens', function ($window) {
3 | var attach = {
4 | request: function (object) {
5 | var jwt = $window.localStorage.getItem('token');
6 | if (jwt) {
7 | object.headers['x-access-token'] = jwt;
8 | }
9 | object.headers['Allow-Control-Allow-Origin'] = '*';
10 |
11 | return object;
12 | }
13 | };
14 | return attach;
15 | })
16 |
--------------------------------------------------------------------------------
/client/services/visualizer.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.visualizer', [])
2 | .factory('Visualizer', ['BoardData', function (BoardData) {
3 | var selectionGlow;
4 | var selected;
5 | function visualizeSelection (selection) {
6 | var board = BoardData.getBoard();
7 | var scale = BoardData.getZoomScale()
8 | if (!selection || !(selection === selected)) {
9 | if (selectionGlow) {
10 | selectionGlow.remove();
11 | selectionGlow.clear();
12 | selected = null;
13 | }
14 | }
15 | if (selection && (!selectionGlow || selectionGlow.items.length === 0)) {
16 | selected = selection;
17 | selectionGlow = selection.glow({
18 | 'color': 'blue',
19 | 'width': 10 * scale
20 | });
21 | }
22 | }
23 |
24 | function clearSelection () {
25 | if (selectionGlow) {
26 | selectionGlow.remove();
27 | selectionGlow.clear();
28 | selected = null;
29 | }
30 | }
31 |
32 | var displayedSnaps;
33 | function visualizeSnaps (snaps, closest) {
34 | var board = BoardData.getBoard();
35 | var scale = BoardData.getZoomScale();
36 | if (!displayedSnaps) {
37 | displayedSnaps = BoardData.getBoard().set();
38 | } else {
39 | displayedSnaps.remove();
40 | displayedSnaps.clear();
41 | }
42 | for (var snap in snaps) {
43 | if (snaps[snap] === closest) {
44 | displayedSnaps.push(board.circle(snaps[snap].x, snaps[snap].y, 5 * scale).attr({'stroke': 'red', 'stroke-width': 1 * scale}));
45 | } else {
46 | displayedSnaps.push(board.circle(snaps[snap].x, snaps[snap].y, 3.5 * scale).attr({'stroke': 'green', 'stroke-width': 1 * scale}));
47 | }
48 | }
49 | }
50 |
51 | function clearSnaps () {
52 | if (displayedSnaps) {
53 | displayedSnaps.remove();
54 | displayedSnaps.clear();
55 | }
56 | }
57 |
58 | return {
59 | visualizeSelection: visualizeSelection,
60 | visualizeSnaps: visualizeSnaps,
61 | clearSelection: clearSelection,
62 | clearSnaps: clearSnaps
63 | }
64 | }]);
65 |
--------------------------------------------------------------------------------
/client/services/zoom.js:
--------------------------------------------------------------------------------
1 | angular.module('whiteboard.services.zoom', [])
2 | .factory('Zoom', ['BoardData', function (BoardData) {
3 | var last;
4 | function zoom (ev, mouseXY) {
5 | var board = BoardData.getBoard();
6 | var scalingFactor = BoardData.getZoomScale();
7 | var offset = BoardData.getOffset();
8 | var originalDims = BoardData.getOriginalDims();
9 | var currentDims = BoardData.getViewBoxDims();
10 |
11 | if (mouseXY) {
12 | if (last) {
13 | var up;
14 | if (ev.clientY > last) {
15 | up = 1.05;
16 | } else if (ev.clientY < last) {
17 | up = 0.95;
18 | } else {
19 | up = 1;
20 | }
21 | scalingFactor = scalingFactor * up;
22 | BoardData.setZoomScale(1 / scalingFactor);
23 | }
24 | last = ev.clientY;
25 | }
26 |
27 | var newViewBoxDims = {
28 | width: originalDims.width * scalingFactor,
29 | height: originalDims.height * scalingFactor
30 | };
31 | BoardData.setViewBoxDims(newViewBoxDims);
32 |
33 | if (mouseXY) {
34 | var newOffset = {
35 | x: offset.x,
36 | y: offset.y
37 | };
38 | } else {
39 | var newOffset = {
40 | x: offset.x + currentDims.width / 2 - newViewBoxDims.width / 2,
41 | y: offset.y + currentDims.height / 2 - newViewBoxDims.height / 2
42 | };
43 | }
44 | BoardData.setOffset(newOffset);
45 |
46 | board.setViewBox(newOffset.x, newOffset.y, newViewBoxDims.width, newViewBoxDims.height);
47 | };
48 |
49 | function resetZoom () {
50 | last = null;
51 | }
52 |
53 | var startPanCoords;
54 | var startPanOffset;
55 | var newOffset;
56 | function pan (ev) {
57 | var board = BoardData.getBoard();
58 | var scalingFactor = BoardData.getScalingFactor();
59 | var offset = BoardData.getOffset();
60 | var currentDims = BoardData.getViewBoxDims();
61 | var canvasMargin = BoardData.getCanvasMargin();
62 |
63 | var mousePosition = {
64 | x: (ev.clientX - canvasMargin.x) * scalingFactor + offset.x,
65 | y: (ev.clientY - canvasMargin.y) * scalingFactor + offset.y
66 | };
67 |
68 | if (!startPanCoords) {
69 | startPanCoords = mousePosition;
70 | startPanOffset = offset;
71 | } else {
72 | newOffset = {
73 | x: startPanOffset.x + (startPanCoords.x - mousePosition.x),
74 | y: startPanOffset.y + (startPanCoords.y - mousePosition.y)
75 | };
76 |
77 | board.setViewBox(newOffset.x, newOffset.y, currentDims.width, currentDims.height);
78 | }
79 | }
80 |
81 | function resetPan () {
82 | startPanCoords = startPanOffset = null;
83 | BoardData.setOffset(newOffset);
84 | }
85 |
86 | return {
87 | zoom: zoom,
88 | resetZoom: resetZoom,
89 | pan: pan,
90 | resetPan: resetPan
91 | }
92 | }]);
93 |
--------------------------------------------------------------------------------
/client/styles/style.css:
--------------------------------------------------------------------------------
1 | body,
2 | html{
3 | margin : 0;
4 | padding : 0;
5 | overflow : hidden;
6 | height:100%;
7 | width:100%;
8 | font-family: San Francisco;
9 | font-weight: 200;
10 | }
11 |
12 | h1.title {
13 | position: absolute;
14 | background-image: url('../assets/images/logo.png');
15 | background-size: cover;
16 | background-repeat: no-repeat;
17 | right: 20px;
18 | width: 100px;
19 | height: 36px;
20 | }
21 |
22 | .app-container {
23 | height: 100%;
24 | }
25 |
26 | #board-container {
27 | height: 100%;
28 | width: 100%;
29 | }
30 |
31 | svg {
32 | height: 100%;
33 | width: 100%;
34 | position: absolute;
35 | cursor: url('../assets/images/cursors/path.png'), auto;
36 | }
37 |
38 | svg.path {
39 | cursor: url('../assets/images/cursors/path.png'), auto;
40 | }
41 |
42 | svg.line {
43 | cursor: url('../assets/images/cursors/line.png'), auto;
44 | }
45 |
46 | svg.arrow {
47 | cursor: url('../assets/images/cursors/arrow.png'), auto;
48 | }
49 |
50 | svg.rectangle {
51 | cursor: url('../assets/images/cursors/rectangle.png'), auto;
52 | }
53 |
54 | svg.circle {
55 | cursor: url('../assets/images/cursors/circle.png'), auto;
56 | }
57 |
58 | svg.text {
59 | cursor: url('../assets/images/cursors/text.png'), auto;
60 | }
61 |
62 | svg.magnify {
63 | cursor: url('../assets/images/cursors/magnify.png'), auto;
64 | }
65 |
66 | svg.eraser {
67 | cursor: url('../assets/images/cursors/eraser.png'), auto;
68 | }
69 |
70 | svg.pan {
71 | cursor: url('../assets/images/cursors/pan.png'), auto;
72 | }
73 |
74 | svg.move {
75 | cursor: url('../assets/images/cursors/move.png'), auto;
76 | }
77 |
78 | svg.copy {
79 | cursor: url('../assets/images/cursors/copy.png'), auto;
80 | }
81 |
82 | svg.fill {
83 | cursor: url('../assets/images/cursors/fill.png'), auto;
84 | }
85 |
86 | svg.strokeColor {
87 | cursor: url('../assets/images/cursors/strokeColor.png'), auto;
88 | }
89 | /*
90 | *
91 | * TOOLBAR
92 | *
93 | */
94 |
95 | .toolbar {
96 | position: absolute;
97 | top: 0px;
98 | left: -150px;
99 | height: 100%;
100 | width: 200px;
101 | color: white;
102 | }
103 |
104 | .toolbar.show {
105 | left: 0;
106 | }
107 |
108 | .toolbar .menu {
109 | /*width: 100%;*/
110 | width: 95.8%;
111 | /*background-color: red;*/
112 | /*background-color: rgba(10, 10, 10, 0.7);
113 | background-color: rgba(31,67,134,0.90);*/
114 | /*background-color: rgba(62,62,62,0.97);*/
115 | background: rgba(53,53,53,0.99);
116 | /*padding-top: 40%;*/
117 | /*display: table;*/
118 | margin: 8px 8px;
119 | border-radius: 3px;
120 | }
121 |
122 | /*.menu.level-one.icon.icon-draw {
123 | background: url('../assets/images/draw.png') no-repeat 49% / 90px, linear-gradient(90deg, rgba(53,53,53,0.99) 50%, rgba(53,53,53,0.89) 50%);
124 | }*/
125 |
126 | .toolbar .menu.level-one {
127 | /*background: linear-gradient(90deg, rgba(53,53,53,0.999) 0%, rgba(53,53,53,0.93) 0%);*/
128 | background: linear-gradient(90deg, rgba(177,102,24,0.96) 0%, rgba(53,53,53,0.93) 0%);
129 |
130 | }
131 |
132 | .menu-text {
133 | position: absolute;
134 | right: 10px;
135 | /*margin-top: 90%;*/
136 | height: 55.5%;
137 | }
138 |
139 | .icon-draw .menu-text {
140 | right: 9px;
141 | }
142 | .icon-tool .menu-text {
143 | right: 11px;
144 | }
145 | .icon-color .menu-text {
146 | right: 8px;
147 | }
148 |
149 | .show .menu-text {
150 | width: 96%;
151 | left: 8px;
152 | height: 66%;
153 | /*margin-top: 110%;*/
154 | }
155 |
156 | .valign-text {
157 | /*display: table-cell;
158 | vertical-align: middle;*/
159 | text-align: center;
160 | display: flex;
161 | justify-content: center; /* align horizontal */
162 | align-items: flex-end; /* align vertical */
163 | }
164 |
165 | /*.level-two-items .valign-text {
166 | margin-top: 120px;
167 | }*/
168 |
169 | /*.level-three .valign-text {
170 | line-height: 13;
171 | }*/
172 |
173 | .menu .wb-submenu-opener {
174 | width: 40%;
175 | height: 102.4%;
176 | margin: 0;
177 | float: right;
178 | position: relative;
179 | display: none;
180 | background-color: transparent;
181 | }
182 |
183 | .menu .wb-submenu-opener.show {
184 | display: block !important;
185 | }
186 |
187 | .toolbar .menu.level-two {
188 | width: 100%;
189 | /*height: 100%;*/
190 | height: 97.5%;
191 | position: absolute;
192 | margin-left: 200px;
193 | /*background-color: orange;*/
194 | /*background-color: rgba(10, 10, 10, 0.8);*/
195 | background-color: rgba(33, 33, 33, 0);
196 | top: 0px;
197 | left: -400px;
198 | }
199 |
200 | .toolbar .tool.menu.level-two,
201 | .toolbar .draw.menu.level-two {
202 | height: 95.1%;
203 | }
204 |
205 | .toolbar .menu.level-two.show {
206 | left: 0;
207 | }
208 |
209 | .toolbar .menu.level-two.draw {
210 | height: 94.3%;
211 | }
212 |
213 | .toolbar .menu.level-two.color {
214 | height: 96.8%;
215 | }
216 |
217 | .level-two-items {
218 | height: 100%;
219 | z-index: 1;
220 | /*margin: 0px 6px;*/
221 | margin: 0px 0 0 6px;
222 | /*background-color: rgba(53,53,53,0.99);*/
223 | /*background: linear-gradient(90deg, rgba(53,53,53,0.999) 0%, rgba(53,53,53,0.93) 0%);*/
224 | background: linear-gradient(90deg, rgba(177,102,24,0.96) 0%, rgba(53,53,53,0.93) 0%);
225 | border-radius: 3px;
226 | }
227 |
228 | .level-two-items:not(:last-child) {
229 | margin-bottom: 8px;
230 | }
231 |
232 | .toolbar .menu.level-three {
233 | width: 100%;
234 | height: 100%;
235 | position: absolute;
236 | margin-left: 200px;
237 | margin-right: 0px;
238 | background-color: transparent;
239 | top: 0px;
240 | left: -600px;
241 | }
242 |
243 | .level-three.level-three-container {
244 | height: 100%;
245 | width: 100%;
246 | }
247 |
248 | .toolbar .level-three.icon {
249 | width: 100%;
250 | }
251 |
252 | .toolbar .menu.level-three.show {
253 | left: 0;
254 | }
255 |
256 | .level-three-items {
257 | height: 100%;
258 | position: relative;
259 | z-index: 1;
260 | top: -8px;
261 | padding: 0 0px 8px 8px;
262 | }
263 |
264 | .level-three-items .thickness {
265 | border-radius: 3px;
266 | /*opacity: 0.9;*/
267 | width: 100%;
268 | height: 100%;
269 | background: linear-gradient(90deg, rgba(177,102,24,0.96) 0%, rgba(53,53,53,0.93) 0%);
270 | }
271 |
272 | .color-palette {
273 | border-radius: 3px;
274 | /*opacity: 0.9;*/
275 | width: 100%;
276 | height: 100%;
277 | }
278 | /*.level-three-items:after {
279 | content: '';
280 | width: 100%;
281 | height: 100%;
282 | margin: 8px 8px;
283 | }
284 | */
285 |
286 |
287 | /*
288 | *
289 | * MENU STROKE SIZES
290 | *
291 | */
292 |
293 | .thickness.stroke10:after {
294 | width: 165px;
295 | top: 5px;
296 | border-bottom: 10px solid;
297 |
298 | content: '';
299 | height: 50%;
300 | position: relative;
301 | display: block;
302 | margin: auto;
303 | box-sizing: border-box;
304 | }
305 |
306 | .thickness.stroke9:after {
307 | width: 150px;
308 | top: 4.5px;
309 | border-bottom: 9px solid;
310 |
311 | content: '';
312 | height: 50%;
313 | position: relative;
314 | display: block;
315 | margin: auto;
316 | box-sizing: border-box;
317 | }
318 |
319 | .thickness.stroke8:after {
320 | width: 135px;
321 | top: 4px;
322 | border-bottom: 8px solid;
323 |
324 | content: '';
325 | height: 50%;
326 | position: relative;
327 | display: block;
328 | margin: auto;
329 | box-sizing: border-box;
330 | }
331 |
332 | .thickness.stroke7:after {
333 | width: 120px;
334 | top: 3.5px;
335 | border-bottom: 7px solid;
336 |
337 | content: '';
338 | height: 50%;
339 | position: relative;
340 | display: block;
341 | margin: auto;
342 | box-sizing: border-box;
343 | }
344 |
345 | .thickness.stroke6:after {
346 | width: 105px;
347 | top: 3px;
348 | border-bottom: 6px solid;
349 |
350 | content: '';
351 | height: 50%;
352 | position: relative;
353 | display: block;
354 | margin: auto;
355 | box-sizing: border-box;
356 | }
357 |
358 | .thickness.stroke5:after {
359 | width: 90px;
360 | top: 2.5px;
361 | border-bottom: 5px solid;
362 |
363 | content: '';
364 | height: 50%;
365 | position: relative;
366 | display: block;
367 | margin: auto;
368 | box-sizing: border-box;
369 | }
370 |
371 | .thickness.stroke4:after {
372 | width: 75px;
373 | top: 2px;
374 | border-bottom: 4px solid;
375 |
376 | content: '';
377 | height: 50%;
378 | position: relative;
379 | display: block;
380 | margin: auto;
381 | box-sizing: border-box;
382 | }
383 |
384 | .thickness.stroke3:after {
385 | width: 50px;
386 | top: 1.5px;
387 | border-bottom: 3px solid;
388 |
389 | content: '';
390 | height: 50%;
391 | position: relative;
392 | display: block;
393 | margin: auto;
394 | box-sizing: border-box;
395 | }
396 |
397 | .thickness.stroke2:after {
398 | width: 35px;
399 | top: 1px;
400 | border-bottom: 2px solid;
401 |
402 | content: '';
403 | height: 50%;
404 | position: relative;
405 | display: block;
406 | margin: auto;
407 | box-sizing: border-box;
408 | }
409 |
410 | .thickness.stroke1:after {
411 | width: 20px;
412 | /*top: 0px;*/
413 | border-bottom: 1px solid;
414 |
415 | content: '';
416 | height: 50%;
417 | position: relative;
418 | display: block;
419 | margin: auto;
420 | box-sizing: border-box;
421 | }
422 | /*
423 | *
424 | * ICONS
425 | *
426 | */
427 |
428 | .level-one .icon {
429 | background-repeat: no-repeat;
430 | background-size: 30px;
431 | /*background-position: 94%;*/
432 | background-position: 94% 45%;
433 | width: 100%;
434 | height: 100%;
435 | position: absolute;
436 | width: 95.8%;
437 | /*height: 33%;*/
438 | }
439 |
440 | .icon,
441 | .show .level-one .icon{
442 | background-repeat: no-repeat;
443 | /*background-size: 90px;*/
444 | /*position: relative;*/
445 | background-size: 65px;
446 | background-position: 50%;
447 | }
448 |
449 | .level-two .icon {
450 | position: relative;
451 | }
452 |
453 | .level-three .icon {
454 | position: absolute;
455 | }
456 |
457 | /* Icons level one */
458 | .icon-draw {
459 | background-image: url('../assets/images/draw.png');
460 | }
461 |
462 | .icon-tool {
463 | background-image: url('../assets/images/tool.png');
464 | }
465 |
466 | .icon-color {
467 | background-image: url('../assets/images/color.png');
468 | }
469 |
470 | /* Icons level two/one */
471 | .level-two .icon.icon-path {
472 | background-image: url('../assets/images/path.png');
473 | }
474 |
475 | .icon-line {
476 | background-image: url('../assets/images/line.png');
477 | }
478 |
479 | .level-two .icon.icon-arrow {
480 | background-image: url('../assets/images/arrow.png');
481 | background-size: 55px;
482 | }
483 |
484 | .icon-rectangle {
485 | background-image: url('../assets/images/rectangle.png');
486 | }
487 |
488 | .icon-circle {
489 | background-image: url('../assets/images/circle.png');
490 | }
491 |
492 | .level-two .icon.icon-text {
493 | background-image: url('../assets/images/text.png');
494 | background-size: 55px;
495 | }
496 |
497 | /* Icons level two/one */
498 | .level-two .icon.icon-magnify {
499 | background-image: url('../assets/images/magnify.png');
500 | background-size: 50px;
501 | }
502 |
503 | .icon-eraser {
504 | background-image: url('../assets/images/eraser.png');
505 | }
506 |
507 | .icon-pan {
508 | background-image: url('../assets/images/pan.png');
509 | }
510 |
511 | .icon-move {
512 | background-image: url('../assets/images/move.png');
513 | }
514 |
515 | .icon-copy {
516 | background-image: url('../assets/images/copy.png');
517 | }
518 |
519 | /* Icons level two/three */
520 | .icon-stroke {
521 | background-image: url('../assets/images/stroke.png');
522 | }
523 |
524 | .icon-fill {
525 | background-image: url('../assets/images/fill.png');
526 | }
527 |
528 | .icon-thickness {
529 | background-image: url('../assets/images/thickness.png');
530 | }
531 |
532 | /*
533 | *
534 | * FONTS
535 | *
536 | */
537 |
538 | /** Thin */
539 | @font-face {
540 | font-family: "San Francisco";
541 | font-weight: 200;
542 | src: url("https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-thin-webfont.woff");
543 | }
544 |
545 | /*
546 | /** Regular
547 | @font-face {
548 | font-family: "San Francisco";
549 | font-weight: 400;
550 | src: url("https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-regular-webfont.woff");
551 | }
552 | */
553 |
554 | /*
555 | *
556 | * MEDIA QUERIES
557 | *
558 | */
559 |
560 | @media (max-height: 950px) {
561 |
562 | .toolbar {
563 | height: 99%;
564 | }
565 |
566 | .toolbar .draw.menu.level-two {
567 | height: 94%;
568 | }
569 |
570 | .toolbar .menu.level-three {
571 | height: 99.9%;
572 | }
573 |
574 | }
575 |
576 | @media (max-height: 900px) {
577 |
578 | .toolbar {
579 | height: 99%;
580 | }
581 |
582 | /*.toolbar .menu.level-two {
583 | height: 97.7%;
584 | }*/
585 |
586 | .toolbar .menu.level-two.tool {
587 | height: 94.9%;
588 | }
589 |
590 | .toolbar .menu.level-two.draw {
591 | height: 94.1%;
592 | }
593 |
594 | .toolbar .menu.level-three {
595 | height: 99.3%;
596 | }
597 |
598 |
599 | }
600 |
601 | @media (max-height: 850px) {
602 |
603 | /* .toolbar .menu.level-two.tool {
604 | height: 94.9%;
605 | }*/
606 |
607 | .toolbar .menu.level-two.draw {
608 | height: 93.9%;
609 | }
610 |
611 | .toolbar .menu.level-two.color {
612 | height: 96.7%;
613 | }
614 |
615 | .toolbar .menu.level-three {
616 | height: 99%;
617 | }
618 |
619 |
620 | }
621 |
622 | @media (max-height: 800px) {
623 |
624 | .toolbar {
625 | height: 99%;
626 | }
627 |
628 | .show .menu-text {
629 | margin-top: 10%;
630 | }
631 |
632 | .valign-text {
633 | line-height: 0;
634 | }
635 |
636 | /* .level-three .valign-text {
637 | line-height: 9;
638 | }*/
639 |
640 | .toolbar .menu.level-two {
641 | height: 97.7%;
642 | }
643 |
644 | .toolbar .menu.level-two.tool {
645 | height: 94.7%;
646 | }
647 |
648 | .toolbar .menu.level-two.draw {
649 | height: 93.7%;
650 | }
651 |
652 | .toolbar .menu.level-three {
653 | height: 98.5%;
654 | }
655 |
656 | }
657 |
658 | @media (max-height: 750px) {
659 |
660 | .toolbar .menu.level-two.draw {
661 | height: 93.5%;
662 | }
663 |
664 | .toolbar .menu.level-three {
665 | height: 97.9%;
666 | }
667 |
668 | }
669 |
670 | @media (max-height: 700px) {
671 |
672 | .toolbar {
673 | height: 99%;
674 | }
675 |
676 | /* .toolbar .menu.level-two {
677 | height: 97.7%;
678 | }
679 | */
680 | .toolbar .menu.level-two.tool {
681 | height: 94.4%;
682 | }
683 |
684 | .toolbar .menu.level-two.draw {
685 | height: 93.4%;
686 | }
687 |
688 | .show .level-two-items .icon {
689 | background-size: 50px;
690 | }
691 |
692 | .show .level-two-items .icon.icon-fill,
693 | .show .level-two-items .icon.icon-stroke,
694 | .show .level-two-items .icon.icon-thickness {
695 | /*background-size: 45px;*/
696 | background-size: 65px;
697 | background-position: 50% 50%;
698 | }
699 |
700 | .level-two-items .icon.icon-magnify {
701 | background-size: 40px;
702 | }
703 |
704 | .level-two-items .icon.icon-arrow {
705 | background-size: 40px;
706 | }
707 |
708 | .level-two-items .icon.icon-text {
709 | background-size: 40px;
710 | }
711 |
712 | .toolbar .menu.level-three {
713 | height: 97.2%;
714 | }
715 |
716 | }
717 |
718 | @media (max-height: 660px) {
719 |
720 | .toolbar .menu.level-two.draw {
721 | height: 93.1%;
722 | }
723 |
724 | /*.show .menu-text {
725 | margin-top: 85%;
726 | }*/
727 |
728 | .valign-text {
729 | line-height: 0;
730 | }
731 |
732 | /* .level-three .valign-text {
733 | line-height: 9;
734 | }*/
735 |
736 | .toolbar .menu.level-two {
737 | height: 97.9%;
738 | }
739 |
740 | .show .level-two-items .icon {
741 | /*background-size: 45px;*/
742 | background-position: 50% 40%;
743 | }
744 |
745 |
746 |
747 | .level-two-items .icon.icon-magnify {
748 | background-size: 35px;
749 | }
750 |
751 | .toolbar .menu.level-three {
752 | height: 96.7%;
753 | }
754 |
755 | }
756 |
757 | @media (max-height: 650px) {
758 |
759 | .toolbar .menu.level-three {
760 | height: 96.6%;
761 | }
762 |
763 | }
764 |
765 | @media (max-height: 600px) {
766 |
767 | .toolbar {
768 | height: 98%;
769 | }
770 |
771 | .menu-text {
772 | /*margin-top: 70%;*/
773 | }
774 |
775 | .toolbar .menu.level-two {
776 | height: 98.1%;
777 | }
778 |
779 | .toolbar .menu.level-two.tool {
780 | height: 94%;
781 | }
782 |
783 | .toolbar .menu.level-two.draw {
784 | height: 92.7%;
785 | }
786 |
787 | .toolbar .menu.level-three {
788 | height: 95.4%;
789 | }
790 |
791 | .level-two-items.icon {
792 | background-size: 50px;
793 | }
794 |
795 | .level-two .icon {
796 | position: relative;
797 | background-size: 65px;
798 | background-position: 50% 50%;
799 | }
800 |
801 | .show .level-two-items .icon.icon-fill,
802 | .show .level-two-items .icon.icon-stroke,
803 | .show .level-two-items .icon.icon-thickness {
804 | position: absolute;
805 | /*background-size: 65px;*/
806 | }
807 |
808 | .level-two-items.icon.icon-magnify {
809 | background-size: 35px;
810 | }
811 |
812 | .level-two-items.icon.icon-arrow {
813 | background-size: 45px;
814 | }
815 |
816 | }
817 |
818 | @media (max-height: 550px) {
819 |
820 | .toolbar .menu.level-two.tool {
821 | height: 93.7%;
822 | }
823 |
824 | .toolbar .menu.level-two.draw {
825 | height: 92.2%;
826 | }
827 |
828 | .toolbar .menu.level-three {
829 | height: 94.5%;
830 | }
831 |
832 | .show .menu-text {
833 | margin-top: 15%;
834 | }
835 |
836 | .show .level-two-items .icon {
837 | background-size: 40px;
838 | background-position: 50% 35%;
839 | }
840 |
841 | .level-two-items .icon.icon-magnify {
842 | background-size: 35px;
843 | }
844 |
845 | .level-two-items .icon.icon-arrow {
846 | background-size: 35px;
847 | }
848 |
849 | .level-two-items .icon.icon-text {
850 | background-size: 35px;
851 | }
852 |
853 | }
854 |
855 | @media (max-height: 500px) {
856 |
857 | .toolbar {
858 | height: 97%;
859 | }
860 |
861 | /* .show .menu-text {
862 | margin-top: 15%;
863 | }*/
864 |
865 | .toolbar .menu.level-two.tool {
866 | height: 93.4%;
867 | }
868 |
869 | .toolbar .menu.level-two.draw {
870 | height: 91.8%;
871 | }
872 |
873 | .toolbar .menu.level-two {
874 | height: 98.3%;
875 | }
876 |
877 | .toolbar .menu.level-three {
878 | height: 92.9%;
879 | }
880 |
881 | .level-two-items.icon {
882 | background-size: 40px;
883 | }
884 |
885 | .show .level-two-items .icon {
886 | background-size: 38px;
887 | background-position: 50% 30%;
888 | }
889 |
890 | .level-two-items .icon.icon-magnify {
891 | background-size: 30px;
892 | }
893 |
894 | .level-two-items .icon.icon-arrow {
895 | background-size: 30px;
896 | }
897 |
898 | .level-two-items .icon.icon-text {
899 | background-size: 30px;
900 | }
901 |
902 | }
903 |
--------------------------------------------------------------------------------
/client/views/board.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/views/layers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{socket}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/client/views/toolbar.html:
--------------------------------------------------------------------------------
1 |
2 |
30 |
--------------------------------------------------------------------------------
/deploy:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #find . -type f \( -name "*.js" ! -name "*-min*" \) -exec awk '1' {} + > compiled.js
4 |
5 | zip_file="whiteboard.zip" # / (root directory)
6 | if [ -e "$zip_file" ]
7 | then
8 | rm $zip_file
9 | fi
10 |
11 | #git pull --rebase upstream dev
12 | grunt release
13 | zip -r whiteboard.zip .
14 | scp -i ~/.ssh/qs2keys.pem whiteboard.zip ec2-user@52.35.39.88:
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whiteboard",
3 | "version": "1.0.0",
4 | "description": "whiteboard",
5 | "main": "server/server.js",
6 | "jshintConfig": {
7 | "bitwise": true,
8 | "camelcase": true,
9 | "curly": false,
10 | "expr": true,
11 | "eqeqeq": true,
12 | "immed": true,
13 | "indent": 4,
14 | "latedef": "nofunct",
15 | "newcap": true,
16 | "undef": false,
17 | "unused": false,
18 | "strict": false,
19 | "globalstrict": true,
20 | "trailing": true,
21 | "maxparams": 4,
22 | "maxdepth": 2,
23 | "maxcomplexity": 6,
24 | "node": true,
25 | "browser": true,
26 | "jquery": true,
27 | "globals": {
28 | "moment": true,
29 | "before": true,
30 | "describe": true,
31 | "expect": true,
32 | "it": true
33 | }
34 | },
35 | "dependencies": {
36 | "body-parser": "^1.14.1",
37 | "compression": "^1.6.0",
38 | "express": "^4.13.3",
39 | "kerberos": "0.0.17",
40 | "redis": "^2.4.2",
41 | "socket.io": "^1.3.7",
42 | "underscore": "^1.8.3"
43 | },
44 | "devDependencies": {
45 | "grunt": "^0.4.5",
46 | "grunt-contrib-concat": "",
47 | "grunt-contrib-cssmin": "^0.14.0",
48 | "grunt-contrib-uglify": "",
49 | "grunt-contrib-watch": "",
50 | "matchdep": ""
51 | },
52 | "scripts": {
53 | "start": "node server/server.js",
54 | "server": "node-inspector & nodemon --debug ./server/server.js",
55 | "test": "mocha test/test.js"
56 | },
57 | "repository": {
58 | "type": "git",
59 | "url": "git+https://github.com/nahash411/whiteboard.git"
60 | },
61 | "author": "",
62 | "license": "ISC",
63 | "bugs": {
64 | "url": "https://github.com/nahash411/whiteboard/issues"
65 | },
66 | "homepage": "https://github.com/nahash411/whiteboard#readme"
67 | }
68 |
--------------------------------------------------------------------------------
/server/board.js:
--------------------------------------------------------------------------------
1 | var Board = function (sizeX, sizeY) {
2 | this.sizeX = sizeX;
3 | this.sizeY = sizeY;
4 | this.init();
5 | };
6 |
7 | Board.prototype.init = function () {
8 |
9 | };
10 |
--------------------------------------------------------------------------------
/server/db/config.js:
--------------------------------------------------------------------------------
1 | var redis = require("redis");
2 | var client = redis.createClient();
3 |
4 | client.on("error", function (err) {
5 | console.log("Error " + err);
6 | });
7 |
8 | module.exports = client;
9 |
--------------------------------------------------------------------------------
/server/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuixoticScientist/whiteboard/eb1ab2874d15c4cc22b0716a976e5388b9013eec/server/favicon.ico
--------------------------------------------------------------------------------
/server/rooms.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils/util');
2 | var client = require('./db/config');
3 | var _ = require('underscore');
4 |
5 | var rooms = {};
6 |
7 | var roomsManager = {
8 |
9 | getRoom: function (roomId) {
10 | return rooms[roomId];
11 | },
12 |
13 | addMember: function (socket, roomId) {
14 |
15 | // ensure there isn't double counting of roomIds in client side ('/roomId' and 'roomId' emit separately)
16 | if (roomId[0] === '/') {
17 | roomId = roomId.slice(1);
18 | }
19 |
20 | socket.room = roomId;
21 | socket.join(roomId);
22 |
23 | if (!rooms[roomId]) {
24 | rooms[roomId] = {};
25 | }
26 |
27 | client.get(roomId, function (err, reply) {
28 | if (reply) {
29 | storedRoom = JSON.parse(reply);
30 | _.extend(rooms[roomId], storedRoom);
31 | } else {
32 | client.set(roomId, JSON.stringify({}));
33 | rooms[roomId] = {};
34 | }
35 |
36 | if (!rooms[roomId]) {
37 | rooms[roomId] = {};
38 | }
39 |
40 | // add member to room based on socket id
41 | // console.log(rooms[roomId]);
42 | var socketId = socket.id;
43 | rooms[roomId][socketId] = {};
44 | socket.emit('showExisting', rooms[roomId]);
45 | //console.log(rooms[roomId]);
46 |
47 | var count = 0;
48 | for (var member in rooms[roomId]) {
49 | count++;
50 | }
51 | // console.log('Current room ' + roomId + ' has ' + count + ' members');
52 | });
53 | },
54 |
55 | addShape: function (shape, socket) {
56 | rooms[socket.room][shape.socketId][shape.myid] = shape;
57 | },
58 |
59 | editShape: function (shape, socket) {
60 | rooms[socket.room][shape.socketId][shape.myid]['mouseX'] = shape.mouseX;
61 | rooms[socket.room][shape.socketId][shape.myid]['mouseY'] = shape.mouseY;
62 | },
63 |
64 | moveShape: function (shape, socket) {
65 | var storedShape = rooms[socket.room][shape.socketId][shape.myid];
66 | if (shape.attr.r) {
67 | storedShape.initX = shape.attr.cx;
68 | storedShape.initY = shape.attr.cy;
69 | storedShape.mouseX = shape.attr.cx + shape.attr.r;
70 | storedShape.mouseY = shape.attr.cy;
71 | } else if (shape.attr.width) {
72 | storedShape.initX = shape.attr.x;
73 | storedShape.initY = shape.attr.y;
74 | storedShape.mouseX = shape.attr.x + shape.attr.width;
75 | storedShape.mouseY = shape.attr.y + shape.attr.height;
76 | } else if (shape.attr.text) {
77 | storedShape.initX = shape.attr.x;
78 | storedShape.initY = shape.attr.y;
79 | } else {
80 | if (shape.pathDProps) {
81 | storedShape.pathDProps = shape.pathDProps;
82 | } else {
83 | var path = shape.attr.path;
84 | storedShape.initX = path[0][1];
85 | storedShape.initY = path[0][2];
86 | storedShape.mouseX = path[1][1];
87 | storedShape.mouseY = path[1][2];
88 | }
89 | }
90 | },
91 |
92 | completePath: function (shape, socket) {
93 | rooms[socket.room][socket.id][shape.myid]['pathDProps'] = shape.pathDProps;
94 | client.set(socket.room, JSON.stringify(rooms[socket.room]));
95 | },
96 |
97 | completeShape: function (shape, socket) {
98 | if (shape.tool && shape.tool.text) {
99 | rooms[socket.room][socket.id][shape.myid]['tool'] = shape.tool;
100 | }
101 | client.set(socket.room, JSON.stringify(rooms[socket.room]));
102 | },
103 |
104 | deleteShape: function (shape, socket) {
105 | delete rooms[socket.room][shape.socketId][shape.myid];
106 | client.set(socket.room, JSON.stringify(rooms[socket.room]));
107 | }
108 |
109 | }
110 |
111 | module.exports = roomsManager;
112 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 | var http = require('http');
4 | var bodyParser = require('body-parser');
5 | var util = require('./utils/util');
6 | var rooms = require('./rooms');
7 | var client = require('./db/config');
8 | var fs = require('fs');
9 | var compression = require('compression');
10 |
11 | app.use(compression());
12 | app.use(express.static(__dirname + '/../client'));
13 | app.use(express.static(__dirname + '/lib'));
14 | app.use(bodyParser.json());
15 | app.use(bodyParser.urlencoded({ extended: true }));
16 |
17 | var port = process.env.PORT || '3000';
18 | app.set('port', port);
19 |
20 | var server = http.createServer(app);
21 | var io = require('./sockets')(server);
22 |
23 | app.get('/:id', function (req, res) {
24 | res.sendfile('./client/index.html');
25 | });
26 |
27 | app.get('/:id/screenShot', function (req, res) {
28 | webshot('localhost:3000/' + req.params.id, req.params.id + '.png', function(err) {
29 | res.sendfile(req.params.id + '.png');
30 | });
31 | })
32 |
33 | var start = function () {
34 | server.listen(port);
35 | };
36 |
37 | var end = function () {
38 | server.close();
39 | };
40 |
41 | start();
42 |
43 | exports.start = start;
44 | exports.end = end;
45 | exports.app = app;
46 |
--------------------------------------------------------------------------------
/server/sockets.js:
--------------------------------------------------------------------------------
1 | var socketio = require('socket.io');
2 | var rooms = require('./rooms');
3 | var client = require('./db/config');
4 | var _ = require('underscore');
5 |
6 | module.exports = function(server) {
7 |
8 | var room = {};
9 | var board = {};
10 |
11 | var io = socketio.listen(server);
12 |
13 | io.on('connection', function (socket) {
14 |
15 | setInterval(function() {
16 | socket.emit('heartbeat');
17 | }, 5000);
18 |
19 | socket.on('heartbeat', function () {
20 | })
21 |
22 | socket.on('idRequest', function () {
23 | socket.emit('socketId', {socketId: socket.id});
24 | });
25 |
26 | socket.on('roomId', function (data) {
27 | rooms.addMember(socket, data.roomId);
28 | });
29 |
30 | socket.on('newShape', function (data) {
31 | socket.to(this.room).emit('shapeCreated', data);
32 | rooms.addShape(data, socket);
33 | });
34 |
35 | socket.on('editShape', function (data) {
36 | socket.to(this.room).emit('shapeEdited', data);
37 | if (data.tool.name !== 'text') {
38 | rooms.editShape(data, socket);
39 | }
40 | });
41 |
42 | socket.on('shapeCompleted', function (data) {
43 | socket.to(this.room).emit('shapeCompleted', {
44 | socketId: socket.id,
45 | myid: data.myid,
46 | tool: data.tool
47 | });
48 | rooms.completeShape(data, socket);
49 | });
50 |
51 | socket.on('pathCompleted', function (data) {
52 | socket.to(this.room).emit('shapeCompleted', {
53 | socketId: socket.id,
54 | myid: data.myid,
55 | tool: data.tool
56 | });
57 | rooms.completePath(data, socket);
58 | });
59 |
60 | socket.on('copiedPathCompleted', function (data) {
61 | socket.to(this.room).emit('copiedPathCompleted', {
62 | socketId: socket.id,
63 | myid: data.myid,
64 | tool: data.tool,
65 | pathDProps: data.pathDProps
66 | });
67 | rooms.completePath(data, socket);
68 | })
69 |
70 | socket.on('moveShape', function (data) {
71 | rooms.moveShape(data, socket);
72 | socket.to(this.room).emit('shapeMoved', data);
73 | });
74 |
75 | socket.on('finishMovingShape', function (data) {
76 | rooms.completeShape(data, socket);
77 | socket.to(this.room).emit('shapeFinishedMoving', data);
78 | });
79 |
80 | socket.on('deleteShape', function (data) {
81 | rooms.deleteShape(data, socket);
82 | socket.to(this.room).emit('shapeDeleted', {myid: data.myid, socketId: data.socketId});
83 | });
84 |
85 | socket.on('disconnect', function () {
86 | });
87 |
88 | });
89 |
90 | return io;
91 |
92 | };
93 |
--------------------------------------------------------------------------------
/server/utils/util.js:
--------------------------------------------------------------------------------
1 | var generateRandomId = function (length) {
2 | var id = "";
3 | var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4 |
5 | for (var i = 0; i < length; i++) {
6 | id += chars.charAt(Math.floor(Math.random() * chars.length));
7 | }
8 |
9 | return id;
10 | };
11 |
12 | module.exports = {
13 | generateRandomId: generateRandomId
14 | };
15 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var mocha = require('mocha');
2 | var chai = require('chai');
3 | var expect = chai.expect;
4 |
5 | var db = require('./../server/db/config');
6 | var Board = require('./../server/db/models/board');
7 |
8 | var request = require('supertest');
9 | var server = require('./../server/server');
10 | var serverUrl = 'http://localhost:3000';
11 |
12 | describe('HTTP', function () {
13 | before(function () {
14 | server.start();
15 | });
16 |
17 | after(function () {
18 | server.end();
19 | });
20 |
21 | describe('GET /:id', function () {
22 |
23 | it('should get /:id', function (done) {
24 | var id = '';
25 | var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
26 | var length = 5;
27 | for (var i = 0; i < length; i++) {
28 | id += chars.charAt(Math.floor(Math.random() * chars.length));
29 | }
30 |
31 | request(serverUrl)
32 | .get('/:' + id)
33 | .expect(200, done);
34 | });
35 | });
36 |
37 | });
38 | //
39 |
--------------------------------------------------------------------------------