├── .gitignore
├── LICENSE
├── README.md
├── dist
├── magnet.min.js
└── magnet.min.js.map
├── docs
└── resources
│ ├── magnetjs-example.gif
│ └── magnetjs-title.gif
├── example
└── html
│ ├── index.html
│ └── load_magnet.js
├── gulpfile.js
├── package.json
└── src
├── Events.js
├── Generator.js
├── Magnet.js
├── Mixins.js
├── Socket.js
├── Theme.js
├── View.js
└── component
├── Component.js
├── Letter.README.md
├── Letter.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | node_modules/
3 | .idea
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # MagnetJS
4 |
5 | > An open source javascript library that allows you to create interactive fridge magnets.
6 |
7 | [](https://npmjs.org/package/magnetjs "View this project on npm")
8 | [](http://opensource.org/licenses/MIT)
9 | [](https://npmjs.org/package/magnetjs "View this project on npm")
10 |
11 | __Live Example:__ [www.fridgeee.com/example](http://www.fridgeee.com/example)
12 |
13 | __Live Example (with dark theme):__ [www.fridgeee.com/codetheme](http://www.fridgeee.com/codetheme)
14 |
15 | ## Overview
16 |
17 | MagnetJS is an open source javascript library for creating and interacting with virtual fridge magnets.
18 |
19 | The library can be used in local mode _(local movement of magnets)_, or using the [fridgeee.com](http://www.fridgeee.com) api (anybody can move the magnets in real-time).
20 |
21 | It is designed to be modular and include other components in the future (for example post-it notes).
22 |
23 | See the [Quick Start](#quick-start) section to get started.
24 |
25 | ## Quick Start
26 |
27 | To get started using MagnetJS, include the library.
28 |
29 | ```html
30 |
31 | ```
32 |
33 | Add a target to your website (this is where you board will be built).
34 |
35 | ```html
36 |
37 | ```
38 |
39 | Create your board (`target:` is the 'id' of your target element)
40 |
41 | ```javascript
42 | const board = new Magnet({ target: 'root' });
43 | ```
44 |
45 | After the dom has loaded, mount the board.
46 |
47 | ```javascript
48 | window.onload = () => {
49 | board.mount();
50 | };
51 | ```
52 |
53 | And Vualá!!
54 |
55 | 
56 |
57 | ## Making changes
58 |
59 | To make changes to the library and test it out.
60 |
61 | ```console
62 | npm install
63 | ```
64 |
65 | And run the example app:
66 |
67 | ```console
68 | npm start
69 | ```
70 |
71 | The files that load the board are in `example/html/.`.
72 |
73 | ## Methods
74 |
75 | ### mount()
76 |
77 | Mounts the board to the target (set in config). This must only be called once the DOM has loaded.
78 |
79 | ## Config
80 |
81 | All values apart from the target are optional, however if you include a theme or item config they must be complete.
82 |
83 | ### id
84 |
85 | The `id:` value is __not__ the target element for your board. This is used to identify the board when using online mode (see [Online Mode](#Online-Mode)). The board can also be viewed by visiting www.fridgeee.com/{id here}.
86 |
87 | ### target
88 |
89 | The `target:` value mounts the board to the DOM element with that id. In the example this is `"root"`.
90 |
91 | ### width
92 |
93 | In pixels, the width of the board (not target element).
94 |
95 | ### height
96 |
97 | In pixels, the height of the board (not target element).
98 |
99 | ### items
100 |
101 | This contains the config for items on the board, they can come in one of two formats.
102 |
103 | #### items -> Array
104 |
105 | Array of items (for example, a letter item config as described [here](src/component/Letter.README.md))
106 |
107 | #### items -> Object
108 |
109 | Items config, this is the information that is required to generate items.
110 |
111 | ##### spawn
112 |
113 | The number of components to generate for the board.
114 |
115 | ##### options
116 |
117 | An object containing options for the items. These are defined in the component config (for example [here](src/component/Letter.README.md)).
118 |
119 | ### theme
120 |
121 | An array of styles to be used as a theme for the board and its content.
122 |
123 | If the structure of this config seems a little odd it is because it must also mimic the database structure for the use of the [online-fridge.com](http://www.online-fridge.com) api.
124 |
125 | Each style is a javascript object with the following structure:
126 |
127 | #### code
128 |
129 | This value should be one of the following:
130 |
131 | * `background` - This means to set the style to the background of the board, this can be an rgb value for example.
132 |
133 | * `color` - This is to create a specific theme color that can be uniquely defined per item (see item 'color' values in the Letter component [README](src/component/Letter.README.md))
134 |
135 | * Component name, e.g `letter` - This means a style to set for a specific item
136 |
137 | #### id
138 |
139 | This value should always be 1 unless it the style object has `code: 'color'`. If it has a 'color' code then it should increment per value and be used as a unique identifier for each color.
140 |
141 | #### key
142 |
143 | This value must be one of the following:
144 |
145 | * The key in css ['key / value pairs'](https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax) but with camelCase instead of css hyphen-case.
146 |
147 | * `rgb`, which would convert into an rgb color value. This is then handled differently depending on the `code` value of the style object. For example, the `background` code will set this value as the background.
148 |
149 | #### value
150 |
151 | This will the one of the following:
152 |
153 | * The value in css ['key / value pairs'](https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax). This value can also perform calculations on the color value of its item. To do this you should use the following syntax:
154 |
155 | ```
156 | key: "textShadow",
157 | value : "0 10px 0 rgb({[r]-50},{[g]-50},{[b]-50})"
158 | ```
159 |
160 | This is the same as an [es6 template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) but without the `$` and assuming that the rbg values are storage as variable with the name `r`, `g` and `b`. This allows you also use variations of the rgb color values.
161 |
162 | ### Example config
163 |
164 | ```javascript
165 | var config = {
166 | id: 'test', // ID of the board (if using sockets)
167 | target: 'root', // ID of the element to build the board onto
168 | width: 1000, // Width of the board (px)
169 | height: 1000, // Height of the board (px)
170 | items: {
171 | spawn: 100, // Spawn 100 items to the board
172 | options: {
173 | text: ['Toast','is','Food','for','my','belly'], // Text combinations to create the items with.
174 | color: [1,2,3,4,5], // The themes color unique identifiers.
175 | type: 'Letter' // The item type
176 | }
177 | },
178 | theme: [
179 | {code: "background", id: 1, key: "rgb", value: "50,50,50"}, // Set the board background to rgb(50,50,50)
180 | {code: "color", id: 1, key: "rgb", value: "132,159,187"}, // First theme color
181 | {code: "color", id: 2, key: "rgb", value: "179,159,161"}, // Second theme color
182 | {code: "color", id: 3, key: "rgb", value: "110,201,151"}, // Third theme color
183 | {code: "color", id: 4, key: "rgb", value: "183,101,191"}, // Fourth theme color
184 | {code: "color", id: 5, key: "rgb", value: "255,132,107"}, // Fifth theme color
185 | {code: "letter", id: 1, key: "fontFamily", value: "'Baloo Bhaina', cursive"}, // Font family for `letter` component
186 | // Text shadow for the `letter` component that uses the theme color to build a shade.
187 | {
188 | code: "letter",
189 | id: 1,
190 | key: "textShadow",
191 | value : "0 1px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 2px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 3px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 4px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 5px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 6px 6px 20px rgba(0,0,0,.1)"
192 | }
193 | ]
194 | };
195 | ```
196 |
197 | ## Online Mode
198 |
199 | To enable the use of the [fridgeee.com api](http://www.fridgeee.com) you must:
200 |
201 | Load socket.io from online-fridge.com
202 |
203 | ```html
204 |
205 | ```
206 |
207 | Include the id of the board you want to access in the config.
208 |
209 | ```javascript
210 | const board = new Magnet({
211 | target: 'root',
212 | id: 'example'
213 | });
214 | ```
215 |
216 | ## Compatibility
217 |
218 | Browser support could stretch back further but will need further testing to confirm.
219 |
220 | | Device | Browser | Version |
221 | |---------|---------|-------------|
222 | | Mobile | Safari | IOS 11.4.0+ |
223 | | Mobile | Chrome | 67.0.x+ |
224 | | Desktop | Chrome | 67.0.x+ |
225 | | Desktop | Safari | 11.1+ |
226 | | Desktop | Firefox | 40.0.2+ |
227 |
228 | ## FAQ
229 |
230 | ### Why did you make this?
231 |
232 | For fun.... mostly. This was also my project for familiarising myself with sockets and javascript modules.
233 |
234 | ### Why did you not choose canvas?
235 |
236 | The decision for not using canvas was based on the idea that anybody can add to their own components. As DOM is more common I decided to not use canvas.
237 |
238 | ## What I would like to change
239 |
240 | - There is some ambiguity between how classes are accessed, I would like to re-evaluate the class structure with [SOLID](https://deviq.com/solid/) design.
241 |
242 | - I would like to look at better alternative ways to load css, the current method of dynamically adding stylesheets to the webpage is probably not the bet option.
243 |
244 | - Move the config initialisation out of the socket class, this is more of a TODO as a result from originally not designing it to work offline.
245 |
246 | - Make this compatible for modern javascript libraries like ReactJS and VueJS. I started this project early on in my career as a developer and was not fully aware of these technologies and their importance at the time.
247 |
248 | ## Release History
249 |
250 | * 0.1.0
251 | * The first proper release. Includes all core functionality for magnets and socket.io implementation.
252 |
253 | ## Meta
254 |
255 | __MrVann__ - [https://github.com/MrVann](https://github.com/MrVann)
256 |
257 | Distributed under the MIT license. See [``LICENSE``](LICENSE) for more information.
258 |
259 | ## Contributing
260 |
261 | 1. Fork it ()
262 | 2. Create your feature branch (`git checkout -b feature/fooBar`)
263 | 3. Commit your changes (`git commit -am 'Add some fooBar'`)
264 | 4. Push to the branch (`git push origin feature/fooBar`)
265 | 5. Create a new Pull Request
266 |
--------------------------------------------------------------------------------
/dist/magnet.min.js:
--------------------------------------------------------------------------------
1 | "use strict";var _get=function e(t,n,i){null===t&&(t=Function.prototype);var r=Object.getOwnPropertyDescriptor(t,n);if(void 0===r){var o=Object.getPrototypeOf(t);return null===o?void 0:e(o,n,i)}if("value"in r)return r.value;var s=r.get;return void 0!==s?s.call(i):void 0},_createClass=function(){function i(e,t){for(var n=0;n>16),l((65280&r)>>8),l(255&r);return 2===o?l(255&(r=c(e.charAt(t))<<2|c(e.charAt(t+1))>>4)):1===o&&(l((r=c(e.charAt(t))<<10|c(e.charAt(t+1))<<4|c(e.charAt(t+2))>>2)>>8&255),l(255&r)),s},e.fromByteArray=function(e){var t,n,i,r,o=e.length%3,s="";function a(e){return"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)}for(t=0,i=e.length-o;t>18&63)+a(r>>12&63)+a(r>>6&63)+a(63&r);switch(o){case 1:s+=a((n=e[e.length-1])>>2),s+=a(n<<4&63),s+="==";break;case 2:s+=a((n=(e[e.length-2]<<8)+e[e.length-1])>>10),s+=a(n>>4&63),s+=a(n<<2&63),s+="="}return s}}(void 0===l?this.base64js={}:l)}).call(this,e("pBGvAp"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/../node_modules/base64-js/lib/b64.js","/../node_modules/base64-js/lib")},{buffer:2,pBGvAp:4}],2:[function(P,e,D){(function(e,t,m,n,i,r,o,s,a){var u=P("base64-js"),l=P("ieee754");function m(e,t,n){if(!(this instanceof m))return new m(e,t,n);var i,r,o,s,a,u=void 0===e?"undefined":_typeof(e);if("base64"===t&&"string"===u)for(e=(i=e).trim?i.trim():i.replace(/^\s+|\s+$/g,"");e.length%4!=0;)e+="=";if("number"===u)r=x(e);else if("string"===u)r=m.byteLength(e,t);else{if("object"!==u)throw new Error("First argument needs to be a number, array or string.");r=x(e.length)}if(m._useTypedArrays?o=m._augment(new Uint8Array(r)):((o=this).length=r,o._isBuffer=!0),m._useTypedArrays&&"number"==typeof e.byteLength)o._set(e);else if(A(a=e)||m.isBuffer(a)||a&&"object"===(void 0===a?"undefined":_typeof(a))&&"number"==typeof a.length)for(s=0;s>>0)):(t+1>>0),r}function d(e,t,n,i){if(i||(G("boolean"==typeof n,"missing or invalid endian"),G(null!=t,"missing offset"),G(t+1>>8*(i?s:1-s)}function w(e,t,n,i,r){r||(G(null!=t,"missing value"),G("boolean"==typeof i,"missing or invalid endian"),G(null!=n,"missing offset"),G(n+3>>8*(i?s:3-s)&255}function b(e,t,n,i,r){r||(G(null!=t,"missing value"),G("boolean"==typeof i,"missing or invalid endian"),G(null!=n,"missing offset"),G(n+1>8,i=t%256,r.push(i),r.push(n);return r}(e),s,a,u);break;default:throw new Error("Unknown encoding")}return o},m.prototype.toString=function(e,t,n){var i,r,o,s,a=this;if(e=String(e||"utf8").toLowerCase(),t=Number(t)||0,(n=void 0!==n?Number(n):n=a.length)===t)return"";switch(e){case"hex":i=function(e,t,n){var i=e.length;(!t||t<0)&&(t=0);(!n||n<0||ithis.length&&(i=this.length),e.length-t=this.length))return this[e]},m.prototype.readUInt16LE=function(e,t){return c(this,e,!0,t)},m.prototype.readUInt16BE=function(e,t){return c(this,e,!1,t)},m.prototype.readUInt32LE=function(e,t){return h(this,e,!0,t)},m.prototype.readUInt32BE=function(e,t){return h(this,e,!1,t)},m.prototype.readInt8=function(e,t){if(t||(G(null!=e,"missing offset"),G(e=this.length))return 128&this[e]?-1*(255-this[e]+1):this[e]},m.prototype.readInt16LE=function(e,t){return d(this,e,!0,t)},m.prototype.readInt16BE=function(e,t){return d(this,e,!1,t)},m.prototype.readInt32LE=function(e,t){return p(this,e,!0,t)},m.prototype.readInt32BE=function(e,t){return p(this,e,!1,t)},m.prototype.readFloatLE=function(e,t){return y(this,e,!0,t)},m.prototype.readFloatBE=function(e,t){return y(this,e,!1,t)},m.prototype.readDoubleLE=function(e,t){return _(this,e,!0,t)},m.prototype.readDoubleBE=function(e,t){return _(this,e,!1,t)},m.prototype.writeUInt8=function(e,t,n){n||(G(null!=e,"missing value"),G(null!=t,"missing offset"),G(t=this.length||(this[t]=e)},m.prototype.writeUInt16LE=function(e,t,n){v(this,e,t,!0,n)},m.prototype.writeUInt16BE=function(e,t,n){v(this,e,t,!1,n)},m.prototype.writeUInt32LE=function(e,t,n){w(this,e,t,!0,n)},m.prototype.writeUInt32BE=function(e,t,n){w(this,e,t,!1,n)},m.prototype.writeInt8=function(e,t,n){n||(G(null!=e,"missing value"),G(null!=t,"missing offset"),G(t=this.length||(0<=e?this.writeUInt8(e,t,n):this.writeUInt8(255+e+1,t,n))},m.prototype.writeInt16LE=function(e,t,n){b(this,e,t,!0,n)},m.prototype.writeInt16BE=function(e,t,n){b(this,e,t,!1,n)},m.prototype.writeInt32LE=function(e,t,n){E(this,e,t,!0,n)},m.prototype.writeInt32BE=function(e,t,n){E(this,e,t,!1,n)},m.prototype.writeFloatLE=function(e,t,n){k(this,e,t,!0,n)},m.prototype.writeFloatBE=function(e,t,n){k(this,e,t,!1,n)},m.prototype.writeDoubleLE=function(e,t,n){I(this,e,t,!0,n)},m.prototype.writeDoubleBE=function(e,t,n){I(this,e,t,!1,n)},m.prototype.fill=function(e,t,n){if(e||(e=0),t||(t=0),n||(n=this.length),"string"==typeof e&&(e=e.charCodeAt(0)),G("number"==typeof e&&!isNaN(e),"value is not a number"),G(t<=n,"end < start"),n!==t&&0!==this.length){G(0<=t&&t"},m.prototype.toArrayBuffer=function(){if("undefined"!=typeof Uint8Array){if(m._useTypedArrays)return new m(this).buffer;for(var e=new Uint8Array(this.length),t=0,n=e.length;t=t.length||r>=e.length);r++)t[r+n]=e[r];return r}function j(e){try{return decodeURIComponent(e)}catch(e){return String.fromCharCode(65533)}}function U(e,t){G("number"==typeof e,"cannot write a non-number as a number"),G(0<=e,"specified a negative value for writing an unsigned value"),G(e<=t,"value is larger than maximum value for type"),G(Math.floor(e)===e,"value has a fractional component")}function V(e,t,n){G("number"==typeof e,"cannot write a non-number as a number"),G(e<=t,"value larger than maximum allowed value"),G(n<=e,"value smaller than minimum allowed value"),G(Math.floor(e)===e,"value has a fractional component")}function N(e,t,n){G("number"==typeof e,"cannot write a non-number as a number"),G(e<=t,"value larger than maximum allowed value"),G(n<=e,"value smaller than minimum allowed value")}function G(e,t){if(!e)throw new Error(t||"Failed assertion")}m._augment=function(e){return e._isBuffer=!0,e._get=e.get,e._set=e.set,e.get=B.get,e.set=B.set,e.write=B.write,e.toString=B.toString,e.toLocaleString=B.toString,e.toJSON=B.toJSON,e.copy=B.copy,e.slice=B.slice,e.readUInt8=B.readUInt8,e.readUInt16LE=B.readUInt16LE,e.readUInt16BE=B.readUInt16BE,e.readUInt32LE=B.readUInt32LE,e.readUInt32BE=B.readUInt32BE,e.readInt8=B.readInt8,e.readInt16LE=B.readInt16LE,e.readInt16BE=B.readInt16BE,e.readInt32LE=B.readInt32LE,e.readInt32BE=B.readInt32BE,e.readFloatLE=B.readFloatLE,e.readFloatBE=B.readFloatBE,e.readDoubleLE=B.readDoubleLE,e.readDoubleBE=B.readDoubleBE,e.writeUInt8=B.writeUInt8,e.writeUInt16LE=B.writeUInt16LE,e.writeUInt16BE=B.writeUInt16BE,e.writeUInt32LE=B.writeUInt32LE,e.writeUInt32BE=B.writeUInt32BE,e.writeInt8=B.writeInt8,e.writeInt16LE=B.writeInt16LE,e.writeInt16BE=B.writeInt16BE,e.writeInt32LE=B.writeInt32LE,e.writeInt32BE=B.writeInt32BE,e.writeFloatLE=B.writeFloatLE,e.writeFloatBE=B.writeFloatBE,e.writeDoubleLE=B.writeDoubleLE,e.writeDoubleBE=B.writeDoubleBE,e.fill=B.fill,e.inspect=B.inspect,e.toArrayBuffer=B.toArrayBuffer,e}}).call(this,P("pBGvAp"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},P("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/../node_modules/buffer/index.js","/../node_modules/buffer")},{"base64-js":1,buffer:2,ieee754:3,pBGvAp:4}],3:[function(e,t,l){(function(e,t,n,i,r,o,s,a,u){l.read=function(e,t,n,i,r){var o,s,a=8*r-i-1,u=(1<>1,f=-7,c=n?r-1:0,h=n?-1:1,d=e[t+c];for(c+=h,o=d&(1<<-f)-1,d>>=-f,f+=a;0>=-f,f+=i;0>1,h=23===r?Math.pow(2,-24)-Math.pow(2,-77):0,d=i?0:o-1,p=i?1:-1,y=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=f):(s=Math.floor(Math.log(t)/Math.LN2),t*(u=Math.pow(2,-s))<1&&(s--,u*=2),2<=(t+=1<=s+c?h/u:h*Math.pow(2,1-c))*u&&(s++,u/=2),f<=s+c?(a=0,s=f):1<=s+c?(a=(t*u-1)*Math.pow(2,r),s+=c):(a=t*Math.pow(2,c-1)*Math.pow(2,r),s=0));8<=r;e[n+d]=255&a,d+=p,a/=256,r-=8);for(s=s<this._parent.View._width/2&&(document.getElementById(this._focus).style.left=this._parent.View._width/2),parseInt(window.getComputedStyle(t,null).getPropertyValue("top").split("px")[0])>this._parent.View._height/2&&(document.getElementById(this._focus).style.top=this._parent.View._height/2),parseInt(window.getComputedStyle(t,null).getPropertyValue("right").split("px")[0])>this._parent.View._width/2&&(document.getElementById(this._focus).style.left=-this._parent.View._width/2+this._parent.View._wrapperX),parseInt(window.getComputedStyle(t,null).getPropertyValue("bottom").split("px")[0])>this._parent.View._height/2&&(document.getElementById(this._focus).style.top=-this._parent.View._height/2+this._parent.View._wrapperY)),this._lastX=e.x,this._lastY=e.y}},{key:"stopDrag",value:function(e){var t=this._startX+(e.x-this._offsetX),n=this._startY+(e.y-this._offsetY),i=this._parent.View.convertXY(t,n,"component");if(this._component)this._parent.View.getItem(this._focus).setLocalPosition(i.x,i.y);else{document.getElementById(this._focus).style.left.split("px")[0],document.getElementById(this._focus).style.top.split("px")[0];var r=this._parent.View.getPosition();window.location.hash=r.x+","+r.y}this._focus=!1}},{key:"mouseLeave",value:function(){document.getElementById(this._focus).style.left=this._startX,document.getElementById(this._focus).style.top=this._startY,this._focus=!1}}]),t}()}).call(this,e("pBGvAp"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/Events.js","/")},{buffer:2,pBGvAp:4}],6:[function(e,l,t){(function(e,t,n,i,r,o,s,a,u){l.exports=function(){function t(e){return _classCallCheck(this,t),this._board=this.generateBoard({id:e.id||!1,name:e.name||!1,desc:e.desc||!1,width:e.width||!1,height:e.height||!1}),this._items=this.generateItems(e.items),this._theme=this.generateTheme(e.theme),{board:this._board,items:this._items,theme:this._theme,exists:!0}}return _createClass(t,[{key:"_getDefaultConfig",value:function(){return{target:"root",id:"default",width:1e3,height:1e3,items:{spawn:500,options:{text:["A","B","C","D","E","F"],color:[1,2,3,4,5],type:"Letter"}},theme:[{code:"background",id:1,key:"rgb",value:"255,255,255"},{code:"color",id:1,key:"rgb",value:"32,59,87"},{code:"color",id:2,key:"rgb",value:"79,159,161"},{code:"color",id:3,key:"rgb",value:"0,201,151"},{code:"color",id:4,key:"rgb",value:"183,101,191"},{code:"color",id:5,key:"rgb",value:"255,132,107"},{code:"letter",id:1,key:"fontFamily",value:"'Baloo Bhaina', cursive"},{code:"letter",id:1,key:"textShadow",value:"0 1px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 2px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 3px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 4px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 5px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 6px 6px 20px rgba(0,0,0,.1)"}]}}},{key:"generateBoard",value:function(e){return Object.assign(e,{name:e.name?e.name:"Empty",desc:e.desc?e.desc:"No Description",width:e.width?e.width:2e3,height:e.height?e.height:2e3})}},{key:"generateItems",value:function(f){var c=this;return f||(f=this._getDefaultConfig().items),"array"==typeof f?f:[].concat(_toConsumableArray(Array(f.spawn||this._getDefaultConfig().items.spawn))).map(function(e,t){var n=c.generateLocation(),i={},r=!0,o=!1,s=void 0;try{for(var a,u=Object.keys(f&&f.options)[Symbol.iterator]();!(r=(a=u.next()).done);r=!0){var l=a.value;Array.isArray(f.options[l])?i[l]=c.pickRandomFromArray(f.options[l]):i[l]=f.options[l]}}catch(e){o=!0,s=e}finally{try{!r&&u.return&&u.return()}finally{if(o)throw s}}return Object.assign({id:t},n,i)})}},{key:"generateTheme",value:function(e){return e||this._getDefaultConfig().theme}},{key:"pickRandomFromArray",value:function(e){return e[Math.floor(Math.random()*e.length)]}},{key:"generateLocation",value:function(){return{x:Math.floor(Math.random()*(this._board.width+1))-this._board.width/2,y:Math.floor(Math.random()*(this._board.height+1))-this._board.height/2}}}]),t}()}).call(this,e("pBGvAp"),"undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],"/Generator.js","/")},{buffer:2,pBGvAp:4}],7:[function(require,module,exports){(function(process,global,Buffer,__argument0,__argument1,__argument2,__argument3,__filename,__dirname){module.exports=function(){function Mixins(){_classCallCheck(this,Mixins)}return _createClass(Mixins,[{key:"applyStyle",value:function(e,t,n){var i=void 0!==t.length?t:Object.entries(t),r=!0,o=!1,s=void 0;try{for(var a,u=i[Symbol.iterator]();!(r=(a=u.next()).done);r=!0)t=a.value,e.style[t[0]]=this.applyTemplate(t[1],n)}catch(e){o=!0,s=e}finally{try{!r&&u.return&&u.return()}finally{if(o)throw s}}return e}},{key:"applyTemplate",value:function applyTemplate(value,params){if(-1!=value.indexOf("[")&&(value=value.replace(/\[r\]/g,params.r).replace(/\[g\]/g,params.g).replace(/\[b\]/g,params.b)),-1!=value.indexOf("{")){var regex=value.match(/\{([^}]+)\}/g),_iteratorNormalCompletion3=!0,_didIteratorError3=!1,_iteratorError3=void 0;try{for(var _iterator3=regex[Symbol.iterator](),_step3;!(_iteratorNormalCompletion3=(_step3=_iterator3.next()).done);_iteratorNormalCompletion3=!0){var match=_step3.value;value=value.replace(match,eval(match))}}catch(e){_didIteratorError3=!0,_iteratorError3=e}finally{try{!_iteratorNormalCompletion3&&_iterator3.return&&_iterator3.return()}finally{if(_didIteratorError3)throw _iteratorError3}}}return value}},{key:"convertToCSSString",value:function(e){var t=Object.entries(e),n=[],i=!0,r=!1,o=void 0;try{for(var s,a=t[Symbol.iterator]();!(i=(s=a.next()).done);i=!0){for(var u=s.value,l=0;l\n ';var i=document.createElement("button");i.innerHTML="Create",n.id="message",i.addEventListener("click",function(){document.getElementsByClassName("message").outerHTML="",document.getElementById(e._parent._id+"_board").removeChild(document.getElementById("message")),t._parent.Socket._socket.emit("start",{id:t._parent._id})}),n.appendChild(i),document.getElementById(this._parent._id+"_board").appendChild(n),this.fadeIn()}},{key:"setEvents",value:function(){var t=this;window.onresize=function(e){t.setWrapperSize(),t.loadPosition()}}},{key:"setSize",value:function(e,t){this._width=e,this._height=t;var n=document.getElementById(this._parent._id+"_board");n.style.width=this._width+"px",n.style.height=this._height+"px",n.style.margin="-"+this._height/2+"px -"+this._width/2+"px"}},{key:"setPosition",value:function(e,t){this._x=e,this._y=t,this.loadPosition()}},{key:"getPosition",value:function(){var e=document.getElementById(this._parent._id+"_board");return{x:Math.floor(parseInt(e.style.left.split("px")[0])-this._wrapperX/2),y:Math.floor(parseInt(e.style.top.split("px")[0])-this._wrapperY/2)}}},{key:"setWrapperSize",value:function(){document.getElementById(this._parent._id+"_wrapper");this._wrapperX=document.getElementById(this._parent._root).offsetWidth,this._wrapperY=document.getElementById(this._parent._root).offsetHeight}},{key:"loadPosition",value:function(){var e=document.getElementById(this._parent._id+"_board");e.style.top=parseInt(this._wrapperY/2)+parseInt(this._y)+"px",e.style.left=parseInt(this._wrapperX/2)+parseInt(this._x)+"px"}},{key:"setTheme",value:function(){this._parent.Theme.applyBackground(document.getElementById(this._parent._id+"_board"))}},{key:"setItem",value:function(e,t){this._items.set(e,t)}},{key:"getItem",value:function(e){return this._items.get(parseInt(e))}},{key:"convertXY",value:function(e,t,n){if("board"==n){var i=document.getElementById(this._parent._id+"_board");return{x:e+parseInt(i.style.width.split("px")[0])/2,y:t+parseInt(i.style.height.split("px")[0])/2}}var r=document.getElementById(this._parent._id+"_board");return{x:e-parseInt(r.style.width.split("px")[0])/2,y:t-parseInt(r.style.height.split("px")[0])/2}}},{key:"updateStats",value:function(e){document.getElementById("members").innerHTML=e.members+" Online"}},{key:"hideLoad",value:function(){document.getElementById("spinner").style.display="none"}},{key:"fadeIn",value:function(){document.getElementsByClassName("wrapper")[0].style.opacity=1}},{key:"fadeInStats",value:function(){document.getElementsByClassName("statsToggle")[0].style.opacity=1}},{key:"renderItems",value:function(){this.hideLoad();for(var e=[].concat(_toConsumableArray(this._items.values())),t=0;t
2 |
3 | MagnetJS
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/html/load_magnet.js:
--------------------------------------------------------------------------------
1 |
2 | var config = {
3 | id: 'test', // ID of the board (if using sockets)
4 | target: 'root', // ID of the element to build the board onto
5 | width: 1000, // Width of the board (px)
6 | height: 1000, // Height of the board (px)
7 | items: {
8 | spawn: 100, // Spawn 100 items to the board
9 | options: {
10 | text: ['Toast','is','Food','for','my','belly'], // Text combinations to create the items with.
11 | color: [1,2,3,4,5], // The themes color unique identifiers.
12 | type: 'Letter' // The item type
13 | }
14 | },
15 | theme: [
16 | {code: "background", id: 1, key: "rgb", value: "50,50,50"}, // Set the board background to rgb(50,50,50)
17 | {code: "color", id: 1, key: "rgb", value: "132,159,187"}, // First theme color
18 | {code: "color", id: 2, key: "rgb", value: "179,159,161"}, // Second theme color
19 | {code: "color", id: 3, key: "rgb", value: "110,201,151"}, // Third theme color
20 | {code: "color", id: 4, key: "rgb", value: "183,101,191"}, // Fourth theme color
21 | {code: "color", id: 5, key: "rgb", value: "255,132,107"}, // Fifth theme color
22 | {code: "letter", id: 1, key: "fontFamily", value: "'Baloo Bhaina', cursive"}, // Font family for `letter` component
23 | // Text shadow for the `letter` component that uses the theme color to build a shade.
24 | {
25 | code: "letter",
26 | id: 1,
27 | key: "textShadow",
28 | value : "0 1px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 2px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 3px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 4px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 5px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 6px 6px 20px rgba(0,0,0,.1)"
29 | }
30 | ]
31 | };
32 |
33 | const board = new Magnet({ target: 'root' });
34 |
35 | window.onload = function () {
36 | board.mount();
37 | };
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const babel = require('gulp-babel');
3 | const concat = require('gulp-concat');
4 | const uglify = require('gulp-uglify');
5 | const webserver = require('gulp-webserver');
6 | const watch = require('gulp-watch');
7 | const browserify = require('gulp-browserify');
8 | const sourcemaps = require('gulp-sourcemaps');
9 |
10 | gulp.task('build', () => {
11 | gulp.src('src/Magnet.js')
12 | .pipe(browserify({
13 | insertGlobals : true,
14 | debug : true
15 | }))
16 | .pipe(sourcemaps.init({loadMaps: true}))
17 | .pipe(concat('magnet.min.js'))
18 | .pipe(babel({
19 | presets: ["env", "stage-2", "stage-0"]
20 | }))
21 | .pipe(uglify())
22 | .pipe(sourcemaps.write('./'))
23 | .pipe(gulp.dest('dist'));
24 | });
25 |
26 | gulp.task('server', () => {
27 | return gulp.src(['example/html','dist'])
28 | .pipe(webserver({
29 | fallback: 'index.html',
30 | path: '/',
31 | livereload: true,
32 | directoryListing: false,
33 | open: true
34 | }));
35 | })
36 |
37 | gulp.task('watch', () => {
38 | return watch('src/**/*', function () {
39 | gulp.src('src/Magnet.js')
40 | .pipe(browserify({
41 | insertGlobals : true,
42 | debug : true
43 | }))
44 | .pipe(sourcemaps.init({loadMaps: true}))
45 | .pipe(concat('magnet.min.js'))
46 | .pipe(babel({
47 | presets: ["env", "stage-2", "stage-0"]
48 | }))
49 | .pipe(uglify())
50 | .pipe(sourcemaps.write('./'))
51 | .pipe(gulp.dest('dist'));
52 | });
53 | });
54 |
55 | gulp.task('dev', ['build','watch','server'])
56 |
57 | gulp.task('default', ['build'] );
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "magnetjs",
3 | "version": "0.1.0",
4 | "description": "Library for creating your own online magnets board.",
5 | "main": "example/index.js",
6 | "scripts": {
7 | "start": "gulp dev",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/MrVann/MagnetJS.git"
13 | },
14 | "keywords": [
15 | "magnetjs",
16 | "magnet",
17 | "fridge",
18 | "fridge-magnet",
19 | "library"
20 | ],
21 | "author": "David Vann",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/MrVann/MagnetJS/issues"
25 | },
26 | "homepage": "https://github.com/MrVann/MagnetJS#readme",
27 | "devDependencies": {
28 | "babel-core": "^6.26.3",
29 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
30 | "babel-preset-env": "^1.7.0",
31 | "babel-preset-stage-0": "^6.24.1",
32 | "babel-preset-stage-2": "^6.24.1",
33 | "gulp": "^3.9.1",
34 | "gulp-babel": "^7.0.1",
35 | "gulp-browserify": "^0.5.1",
36 | "gulp-concat": "^2.6.1",
37 | "gulp-sourcemaps": "^2.6.4",
38 | "gulp-uglify": "^3.0.0",
39 | "gulp-watch": "^5.0.0",
40 | "gulp-webserver": "^0.9.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Events.js:
--------------------------------------------------------------------------------
1 | module.exports = class Events{
2 | constructor(parent){
3 | this._parent = parent;
4 | this._startX = 0;
5 | this._startY = 0;
6 | this._focus = false;
7 | this._component = false;
8 | this.initEvents();
9 | }
10 |
11 | initEvents(){
12 | const board = document.getElementById(`${this._parent._id}_board`);
13 | const me = this;
14 | board.addEventListener("mousedown", (e) => {
15 | const id = e.target.id;
16 | const component = (e.target.getAttribute('type') == 'component');
17 | me.startDrag({
18 | id,
19 | component,
20 | x: e.x,
21 | y: e.y
22 | });
23 | });
24 | board.addEventListener("mouseup", (e) => {
25 | this.stopDrag({
26 | x: e.x,
27 | y: e.y
28 | });
29 | });
30 | board.addEventListener("mousemove", (e) => {
31 | if(this._focus) this.moveItem({
32 | x: e.x,
33 | y: e.y
34 | })
35 | });
36 | board.addEventListener("mouseleave", (e) => {
37 | if(this._focus) this.mouseLeave()
38 | });
39 | board.addEventListener("touchstart", (e) => {
40 | const id = e.changedTouches[0].target.id;
41 | const component = (e.changedTouches[0].target.getAttribute('type') == 'component');
42 | me.startDrag({
43 | id,
44 | component,
45 | x: e.changedTouches[0].clientX,
46 | y: e.changedTouches[0].clientY
47 | });
48 | });
49 | board.addEventListener("touchmove", (e) => {
50 | if(this._focus) this.moveItem({
51 | x: e.changedTouches[0].clientX,
52 | y: e.changedTouches[0].clientY
53 | })
54 | });
55 | board.addEventListener("touchend", (e) => {
56 | this.stopDrag({
57 | x: e.changedTouches[0].clientX,
58 | y: e.changedTouches[0].clientY
59 | });
60 | board.addEventListener("touchcancel", (e) => {
61 | if(this._focus) this.mouseLeave()
62 | });
63 | });
64 | }
65 |
66 | startDrag(conf){
67 | const item = document.getElementById(conf.id);
68 | this._offsetX = conf.x;
69 | this._offsetY = conf.y;
70 | this._startX = parseInt(item.style.left.split('px')[0]);
71 | this._startY = parseInt(item.style.top.split('px')[0]);
72 | this._focus = conf.id;
73 | this._component = conf.component;
74 | }
75 |
76 | moveItem(conf){
77 | const board = document.getElementById(`${this._parent._id}_board`);
78 | const left = parseInt(window.getComputedStyle(board,null).getPropertyValue("left").split('px')[0]);
79 | const right = parseInt(window.getComputedStyle(board,null).getPropertyValue("right").split('px')[0]);
80 | const top = parseInt(window.getComputedStyle(board,null).getPropertyValue("top").split('px')[0]);
81 | const bottom = parseInt(window.getComputedStyle(board,null).getPropertyValue("bottom").split('px')[0]);
82 | const xDir = this._lastX - conf.x;
83 | const yDir = this._lastY - conf.y;
84 |
85 | if(((left < (this._parent.View._width / 2) || xDir > 0 ) && (right < ((this._parent.View._width / 2))) || xDir < 0 ) || this._component) document.getElementById(this._focus).style.left = this._startX + (conf.x - this._offsetX);
86 | if(((top < (this._parent.View._width / 2) || yDir > 0 ) && (bottom < ((this._parent.View._width / 2))) || yDir < 0 ) || this._component) document.getElementById(this._focus).style.top = this._startY + (conf.y - this._offsetY);
87 |
88 |
89 | if(!this._component) {
90 | if (parseInt(window.getComputedStyle(board, null).getPropertyValue("left").split('px')[0]) > (this._parent.View._width / 2)) {
91 | document.getElementById(this._focus).style.left = (this._parent.View._width / 2)
92 | }
93 |
94 | if (parseInt(window.getComputedStyle(board, null).getPropertyValue("top").split('px')[0]) > (this._parent.View._height / 2)) {
95 | document.getElementById(this._focus).style.top = (this._parent.View._height / 2)
96 | }
97 |
98 | if ((parseInt(window.getComputedStyle(board, null).getPropertyValue("right").split('px')[0]) > (this._parent.View._width / 2))) {
99 | document.getElementById(this._focus).style.left = -((this._parent.View._width / 2)) + this._parent.View._wrapperX
100 | }
101 |
102 | if ((parseInt(window.getComputedStyle(board, null).getPropertyValue("bottom").split('px')[0]) > (this._parent.View._height / 2))) {
103 | document.getElementById(this._focus).style.top = -((this._parent.View._height / 2)) + this._parent.View._wrapperY
104 | }
105 | }
106 |
107 | this._lastX = conf.x;
108 | this._lastY = conf.y;
109 | }
110 |
111 | stopDrag(conf){
112 | const x = this._startX + (conf.x - this._offsetX);
113 | const y = this._startY + (conf.y - this._offsetY);
114 | const newXY = this._parent.View.convertXY(x, y, 'component');
115 | if(this._component) this._parent.View.getItem(this._focus).setLocalPosition(newXY.x, newXY.y);
116 | else{
117 | const left = document.getElementById(this._focus).style.left.split('px')[0];
118 | const top = document.getElementById(this._focus).style.top.split('px')[0];
119 | const boardXY = this._parent.View.getPosition();
120 | window.location.hash = boardXY.x+','+boardXY.y;
121 | }
122 | this._focus = false;
123 | }
124 |
125 | mouseLeave(){
126 | document.getElementById(this._focus).style.left = this._startX;
127 | document.getElementById(this._focus).style.top = this._startY;
128 | this._focus = false;
129 | // $('#'+this._focus).animate({
130 | // left: this._startX,
131 | // top: this._startY
132 | // }, 200);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Generator.js:
--------------------------------------------------------------------------------
1 | module.exports = class Generator {
2 | constructor(props){
3 | this._board = this.generateBoard({
4 | id: props.id || false,
5 | name: props.name || false,
6 | desc: props.desc || false,
7 | width: props.width || false,
8 | height: props.height || false
9 | });
10 | this._items = this.generateItems(props.items);
11 | this._theme = this.generateTheme(props.theme);
12 |
13 | return {
14 | board: this._board,
15 | items: this._items,
16 | theme: this._theme,
17 | exists: true // Required to align with the sockets feature (handles a scenario where the board doesn't yet exist)
18 | }
19 | }
20 |
21 | _getDefaultConfig(){
22 | return {
23 | target: 'root',
24 | id: 'default',
25 | width: 1000,
26 | height: 1000,
27 | items: {
28 | spawn: 500,
29 | options: {
30 | text: ['A','B','C','D','E','F'],
31 | color: [1,2,3,4,5],
32 | type: 'Letter'
33 | }
34 | },
35 | theme: [
36 | {code: "background", id: 1, key: "rgb", value: "255,255,255"},
37 | {code: "color", id: 1, key: "rgb", value: "32,59,87"},
38 | {code: "color", id: 2, key: "rgb", value: "79,159,161"},
39 | {code: "color", id: 3, key: "rgb", value: "0,201,151"},
40 | {code: "color", id: 4, key: "rgb", value: "183,101,191"},
41 | {code: "color", id: 5, key: "rgb", value: "255,132,107"},
42 | {code: "letter", id: 1, key: "fontFamily", value: "'Baloo Bhaina', cursive"},
43 | {
44 | code: "letter",
45 | id: 1,
46 | key: "textShadow",
47 | value : "0 1px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 2px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 3px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 4px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 0 5px 0 rgb({[r]-50},{[g]-50},{[b]-50}), 6px 6px 20px rgba(0,0,0,.1)"
48 | }
49 | ]
50 | };
51 | }
52 |
53 |
54 | generateBoard(board){
55 | return Object.assign(
56 | board,
57 | {
58 | name: board.name ? board.name : "Empty",
59 | desc: board.desc ? board.desc : "No Description",
60 | width: board.width ? board.width : 2000,
61 | height: board.height ? board.height : 2000
62 | })
63 | }
64 |
65 | generateItems(items) {
66 |
67 | if(!items){
68 | items = this._getDefaultConfig().items;
69 | }
70 |
71 | if (typeof items == 'array') {
72 | return items;
73 | }
74 |
75 | return [...Array(items.spawn || this._getDefaultConfig().items.spawn)].map((item, index) => {
76 |
77 | const location = this.generateLocation();
78 |
79 | const extraValues = {};
80 |
81 | for (const key of Object.keys(items && items.options)) {
82 | if (Array.isArray(items.options[key])){
83 | extraValues[key] = this.pickRandomFromArray(items.options[key]);
84 | }
85 | else {
86 | extraValues[key] = items.options[key];
87 | }
88 | }
89 |
90 | return Object.assign(
91 | {
92 | id: index
93 | },
94 | location,
95 | extraValues);
96 |
97 | })
98 |
99 | }
100 |
101 | generateTheme(theme){
102 | if(!theme) return this._getDefaultConfig().theme;
103 | return theme;
104 | }
105 |
106 | pickRandomFromArray(array){
107 | return array[Math.floor(Math.random() * array.length)];
108 | }
109 |
110 | generateLocation(){
111 | return {
112 | x: (Math.floor(Math.random() * (this._board.width + 1))) - (this._board.width / 2),
113 | y: (Math.floor(Math.random() * (this._board.height + 1))) - (this._board.height / 2)
114 | }
115 | }
116 |
117 | }
--------------------------------------------------------------------------------
/src/Magnet.js:
--------------------------------------------------------------------------------
1 | const component = require('./component/index.js')
2 | const View = require('./View.js')
3 | const Theme = require('./Theme.js')
4 | const Events = require('./Events.js')
5 | const Socket = require('./Socket.js')
6 | const Mixins = require('./Mixins.js')
7 | const Generator = require('./Generator')
8 |
9 | module.exports = window.Magnet;
10 |
11 | window.Magnet = class Magnet {
12 | constructor(config){
13 | this.initConfig(config);
14 | // this.initClasses();
15 | }
16 |
17 | /**
18 | * Mount the board
19 | */
20 | mount(){
21 | console.log('Magnet started');
22 | this.initClasses();
23 | }
24 |
25 | /**
26 | * Initialise Config
27 | * @param target
28 | * @param id
29 | */
30 | initConfig(config, id){
31 | this._root = config.target;
32 | this._id = config.id ? config.id : 'local';
33 | this._name = '';
34 | this._description = '';
35 | this._config = new Generator(config)
36 | }
37 |
38 | /**
39 | * Initialise Classes
40 | */
41 | initClasses(){
42 | this.Mixins = new Mixins(this);
43 | this.component = component;
44 | this.View = new View(this);
45 | this.Socket = new Socket(this);
46 | this.Events = new Events(this);
47 | }
48 |
49 | /**
50 | * Return root dom
51 | */
52 | getRoot(){
53 | return document.getElementById(this._root);
54 | }
55 |
56 | /**
57 | * Get Board data
58 | */
59 | getBoard(data){
60 | this.Socket._socket.emit('load');
61 | }
62 |
63 | /**
64 | * Set Board from Data
65 | */
66 | setBoard(data){
67 | this.setTheme(data.theme);
68 | this.setDetails(data.board);
69 | this.setItems(data.items);
70 | this.View.fadeIn();
71 | this.View.fadeInStats();
72 | }
73 |
74 | /**
75 | * Set board theme
76 | */
77 | setTheme(theme){
78 | this.Theme = new Theme(theme, this);
79 | this.View.setTheme();
80 | }
81 |
82 | /**
83 | * Set board details
84 | */
85 | setDetails(details){
86 | this._name = details.name;
87 | this._description = details.desc;
88 | this.View.setSize(details.width, details.height);
89 | this.View.setPosition(window.location.hash.split(',')[0].split('#')[1] || 0,window.location.hash.split(',')[1] || 0);
90 | }
91 |
92 | /**
93 | * Set board items
94 | */
95 | setItems(items){
96 | for(const item of items){
97 | this.View.setItem(item.id, new this.component[item.type](item, this));
98 | }
99 | this.View.renderItems();
100 | }
101 | }
--------------------------------------------------------------------------------
/src/Mixins.js:
--------------------------------------------------------------------------------
1 | module.exports = class Mixins{
2 | constructor(){
3 | }
4 |
5 | /**
6 | * Apply styles to dom object
7 | */
8 | applyStyle(dom, style, params){
9 | const arrayStyle = (typeof style.length != 'undefined') ? style : Object.entries(style);
10 | for(style of arrayStyle){
11 | dom.style[style[0]] = this.applyTemplate(style[1], params);
12 | }
13 | return dom;
14 | }
15 |
16 | /**
17 | * Apply template information
18 | */
19 | applyTemplate(value, params){
20 | if(value.indexOf('[') != -1) value = value.replace(/\[r\]/g, params.r).replace(/\[g\]/g, params.g).replace(/\[b\]/g, params.b);
21 | if(value.indexOf('{') !=- 1) {
22 | const regex = value.match(/\{([^}]+)\}/g);
23 | for (const match of regex) {
24 | value = value.replace(match, eval(match));
25 | }
26 | }
27 | return value;
28 | }
29 |
30 | /**
31 | * Convert css object to string
32 | */
33 | convertToCSSString(cssObject){
34 | const entries = Object.entries(cssObject);
35 | const cssArray = [];
36 | for(const entry of entries){
37 | for(let i = 0; i < entry[0].length; i++){
38 | if(entry[0].charAt(i) === entry[0].charAt(i).toUpperCase()){
39 | entry[0] = entry[0].slice(0,i)+'-'+entry[0].slice(i, entry[0].length).toLowerCase();
40 | }
41 | }
42 | cssArray.push(entry[0]+': '+entry[1]);
43 | }
44 | return cssArray.join('; ');
45 | }
46 |
47 | makeAnimation(item, value, to, duration){
48 | const start = item.style[value].split('px')[0];
49 | const change = to - start;
50 | const increment = 10;
51 | let currentTime = 0;
52 | const easeInOutQuad = function(t, b, c, d) {
53 | t /= d/2;
54 | if (t < 1) return c/2*t*t + b;
55 | t--;
56 | return -c/2 * (t*(t-2) - 1) + b;
57 | };
58 |
59 | var animateScroll = function(){
60 | currentTime += increment;
61 | var val = easeInOutQuad(currentTime, start, change, duration);
62 | // console.log(value, (parseFloat(start) + parseFloat(val)), val, duration, currentTime);
63 | item.style[value] = currentTime == duration ? to : (parseFloat(start) + parseFloat(val));
64 | if(currentTime < duration) {
65 | setTimeout(animateScroll, increment);
66 | }
67 | };
68 | animateScroll();
69 |
70 | }
71 | }
--------------------------------------------------------------------------------
/src/Socket.js:
--------------------------------------------------------------------------------
1 | const Generator = require('./Generator');
2 |
3 | module.exports = class Socket{
4 | constructor(parent){
5 | this._parent = parent;
6 | this.initSocket();
7 | }
8 |
9 | /**
10 | * Init socket events
11 | */
12 | initSocket(){
13 | const me = this;
14 | this._socketConnected = false;
15 | if(typeof io == 'undefined'){
16 | console.info('Socket.io not enabled, generating static board');
17 | this._socketEnabled = false;
18 | this._socket = {
19 | emit: () => {
20 | return false;
21 | }
22 | }
23 | const generatedBoard = this._parent._config;
24 | me.onLoad(generatedBoard);
25 | }
26 | else{
27 | this._socketEnabled = true;
28 | this._socket = io.connect('ws://www.online-fridge.com');
29 | this._socket.on('connect', () => {
30 | me.onSocketConnected();
31 | });
32 | this._socket.on('disconnect', () => {
33 | me.onSocketDisconnected();
34 | });
35 | this._socket.on('error', (err) => {
36 | me.onSocketError(err);
37 | });
38 | this._socket.on('authenticate', (data) => {
39 | me.onAuthenticate(data);
40 | })
41 | this._socket.on('load', (data) => {
42 | me.onLoad(data);
43 | })
44 | this._socket.on('move', (data) => {
45 | me.onMove(data);
46 | })
47 | this._socket.on('add', (data) => {
48 | me.onAdd(data);
49 | })
50 | this._socket.on('noExist', (data) => {
51 | me._parent.View.askCreate();
52 | })
53 | this._socket.on('stats', (data) => {
54 | me._parent.View.updateStats(data);
55 | })
56 | }
57 | }
58 |
59 | /**
60 | * On Authenticate
61 | */
62 | onAuthenticate(data){
63 | this._parent.getBoard(data);
64 | }
65 |
66 | /**
67 | * On Load
68 | */
69 | onLoad(data){
70 | this._parent.setBoard(data)
71 | }
72 |
73 | /**
74 | * On Move
75 | */
76 | onMove(data){
77 | for(const move of data){
78 | if(move.time) this._parent.View.getItem(move.id).setRemotePosition(move.x, move.y, move.time)
79 | else this._parent.View.getItem(move.id).setRemotePosition(move.x, move.y)
80 | }
81 | }
82 |
83 | /**
84 | * On Item Add
85 | */
86 | onAdd(data){
87 | this._parent.View.addItem(data);
88 | }
89 |
90 | /**
91 | * On Socket Connect
92 | */
93 | onSocketConnected(){
94 | this._socketConnected = true;
95 | console.log(`${this._parent._id.split('#')[0]}: Connected to socket`);
96 | this.authenticate();
97 | }
98 |
99 | /**
100 | * Authenticate Board
101 | */
102 | authenticate(){
103 | console.log('authenticating');
104 | this._socket.emit('authenticate', {id: this._parent._id});
105 | }
106 |
107 | /**
108 | * On Socket Disconnected
109 | */
110 | onSocketDisconnected(){
111 | this._socketConnected = false;
112 | console.log(`${this._parent._id}: Socket disconnected`);
113 | }
114 |
115 | /**
116 | * On Socket Error
117 | */
118 | onSocketError(err){
119 | this._socketConnected = false;
120 | console.log(error);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Theme.js:
--------------------------------------------------------------------------------
1 | module.exports = class Theme{
2 |
3 | /**
4 | * Initiate theme with config
5 | * @param theme
6 | */
7 | constructor(theme, parent){
8 | this._parent = parent;
9 | this._items = new Map();
10 | this._colors = new Map();
11 | this.setTheme(theme);
12 | }
13 |
14 | /**
15 | * Set theme state
16 | */
17 | setTheme(theme){
18 | for(const item of theme){
19 | switch(item.key){
20 | case 'rgb':{
21 | this._colors.set((item.code == 'color') ? item.id : item.code, {
22 | r: item.value.split(',')[0],
23 | g: item.value.split(',')[1],
24 | b: item.value.split(',')[2]
25 | })
26 | break;
27 | }
28 | default:{
29 | if(!this._items.has(item.code)) {
30 | this._items.set(item.code, theme.filter(other => (other.code == item.code)).map( item => ([item.key, item.value])));
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | /**
38 | * Apply theme to dom object
39 | */
40 | applyTheme(dom, code, params){
41 | //debugger;
42 | if(this._items.has(code)){
43 | // debugger;
44 | return this._parent.Mixins.applyStyle(dom, this.getItem(code), params);
45 | }
46 | return dom;
47 | }
48 |
49 | /**
50 | * Apply background to dom object
51 | */
52 | applyBackground(dom){
53 | dom.style.background = this.getColorCSS('background');
54 | return dom;
55 | }
56 |
57 | /**
58 | * Get theme item array
59 | */
60 | getItem(code){
61 | //console.log('getting: ', this._items.get(code));
62 | return this._items.get(code);
63 | }
64 |
65 | /**
66 | * Get color rgb values as object
67 | */
68 | getColor(key){
69 | return this._colors.get(key)
70 | }
71 |
72 | /**
73 | * Get color as rgb string (CSS)
74 | */
75 | getColorCSS(key){
76 | const color = this._colors.get(key);
77 | return `rgb(${color.r},${color.g},${color.b})`;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/View.js:
--------------------------------------------------------------------------------
1 | module.exports = class View{
2 | constructor(me){
3 | this._parent = me;
4 | this._items = new Map();
5 | this._classNames = new Set();
6 | this.initCSS();
7 | this.buildDOM();
8 | this.setWrapperSize();
9 | this.setEvents();
10 | }
11 |
12 | /**
13 | * Initialise CSS Objects
14 | */
15 | initCSS(){
16 | this._wrapperCSS = {
17 | width: '100%',
18 | height: '100%',
19 | overflow: 'hidden',
20 | position: 'relative'
21 | }
22 |
23 | this._boardCSS = {
24 | width: '100%',
25 | height: '100%',
26 | position: 'absolute',
27 | background: 'white'
28 | }
29 | }
30 |
31 | /**
32 | * Build DOM
33 | */
34 | buildDOM(){
35 | const root = document.getElementById(this._parent._root);
36 | const wrapper = document.createElement("div");
37 | wrapper.id = `${this._parent._id}_wrapper`;
38 | wrapper.className = "wrapper";
39 | this._parent.Mixins.applyStyle(wrapper, this._wrapperCSS);
40 | const board = document.createElement("div");
41 | board.id = `${this._parent._id}_board`;
42 | this._parent.Mixins.applyStyle(board, this._boardCSS);
43 | const stats = document.createElement('div');
44 | stats.id = 'members';
45 | stats.className = 'statsToggle';
46 | const spinner = document.createElement('div');
47 | const dot1 = document.createElement('div');
48 | const dot2 = document.createElement('div');
49 | spinner.className = 'spinner';
50 | spinner.id = 'spinner';
51 | dot1.className = 'dot1';
52 | dot2.className = 'dot2';
53 | spinner.appendChild(dot1);
54 | spinner.appendChild(dot2);
55 | wrapper.appendChild(board);
56 | root.innerHTML = '';
57 | root.appendChild(wrapper);
58 | root.appendChild(stats);
59 | root.appendChild(spinner);
60 | }
61 |
62 | /**
63 | * Add Item
64 | */
65 | addItem(item){
66 | this.setItem(item.id, new this._parent.component[item.type](item, this._parent));
67 | this.renderItems();
68 | }
69 |
70 | /**
71 | * Ask To Create Board (If Board doesn't exist)
72 | */
73 | askCreate(){
74 | const me = this;
75 | const message = document.createElement("div");
76 | message.className = "message";
77 | message.innerHTML = `
78 | Fridge "${this._parent._id}" is available!
79 | `
80 | const button = document.createElement("button");
81 | button.innerHTML = 'Create';
82 | message.id = 'message';
83 | button.addEventListener('click', () => {
84 | document.getElementsByClassName('message').outerHTML = '';
85 | document.getElementById(`${this._parent._id}_board`).removeChild(document.getElementById('message'));
86 | me._parent.Socket._socket.emit('start', {id: me._parent._id});
87 | });
88 |
89 | message.appendChild(button);
90 |
91 | document.getElementById(`${this._parent._id}_board`).appendChild(message);
92 | this.fadeIn();
93 | }
94 |
95 | /**
96 | * Set View Events (Resize window etc)
97 | */
98 | setEvents(){
99 | const me = this;
100 | window.onresize = function(event) {
101 | me.setWrapperSize();
102 | me.loadPosition();
103 | };
104 | }
105 |
106 | /**
107 | * Set Board size
108 | */
109 | setSize(width, height){
110 | this._width = width;
111 | this._height = height;
112 | const board = document.getElementById(`${this._parent._id}_board`);
113 | board.style.width = this._width+'px';
114 | board.style.height = this._height+'px';
115 | board.style.margin = `-${this._height / 2}px -${this._width / 2}px`;
116 | }
117 |
118 | /**
119 | * Set board Position
120 | */
121 | setPosition(x, y){
122 | this._x = x;
123 | this._y = y;
124 | this.loadPosition();
125 | }
126 |
127 | /**
128 | * Get board position
129 | */
130 | getPosition(){
131 | const board = document.getElementById(`${this._parent._id}_board`);
132 | return {
133 | x: Math.floor((parseInt(board.style.left.split('px')[0]) - (this._wrapperX / 2))),
134 | y: Math.floor(parseInt(board.style.top.split('px')[0]) - (this._wrapperY / 2))
135 | }
136 | }
137 |
138 | setWrapperSize(){
139 | const wrapper = document.getElementById(`${this._parent._id}_wrapper`);
140 | this._wrapperX = document.getElementById(this._parent._root).offsetWidth;
141 | this._wrapperY = document.getElementById(this._parent._root).offsetHeight;
142 | }
143 |
144 | loadPosition(){
145 | const board = document.getElementById(`${this._parent._id}_board`);
146 | board.style.top = `${parseInt((this._wrapperY / 2)) + parseInt(this._y)}px`;
147 | board.style.left = `${parseInt((this._wrapperX / 2)) + parseInt(this._x)}px`;
148 | }
149 |
150 | setTheme(){
151 | this._parent.Theme.applyBackground(document.getElementById(`${this._parent._id}_board`));
152 | }
153 |
154 | setItem(id, object){
155 | this._items.set(id, object)
156 | }
157 |
158 | getItem(id){
159 | return this._items.get(parseInt(id));
160 | }
161 |
162 | convertXY(x, y, target){
163 | if(target == 'board') {
164 | const board = document.getElementById(`${this._parent._id}_board`);
165 | return {
166 | x: x + (parseInt(board.style.width.split('px')[0]) / 2),
167 | y: y + (parseInt(board.style.height.split('px')[0]) / 2),
168 | }
169 | }
170 | else{
171 | const board = document.getElementById(`${this._parent._id}_board`);
172 | return {
173 | x: x - (parseInt(board.style.width.split('px')[0]) / 2),
174 | y: y - (parseInt(board.style.height.split('px')[0]) / 2),
175 | }
176 | }
177 | }
178 |
179 | updateStats(stats){
180 | document.getElementById('members').innerHTML = stats.members+' Online';
181 | }
182 |
183 | hideLoad(){
184 | document.getElementById('spinner').style.display = 'none';
185 | }
186 |
187 | /**
188 | * Fade the board into view
189 | */
190 | fadeIn(){
191 | document.getElementsByClassName('wrapper')[0].style.opacity = 1;
192 | }
193 |
194 | fadeInStats(){
195 | document.getElementsByClassName('statsToggle')[0].style.opacity = 1;
196 | }
197 |
198 | renderItems(){
199 | this.hideLoad();
200 | for(const item of [...this._items.values()]){
201 | if(!document.getElementById(item.getItem().id)) {
202 | const board = document.getElementById(`${this._parent._id}_board`);
203 | const itemDiv = item.getItem();
204 | board.appendChild(itemDiv);
205 | }
206 | }
207 | }
208 |
209 | /**
210 | * Create a css class from an object
211 | */
212 | addCSSClass(className, classDetails){
213 | const style = document.createElement('style');
214 | style.type = 'text/css';
215 | style.innerHTML = `.${className} { ${this._parent.Mixins.convertToCSSString(classDetails)} }`;
216 | document.getElementsByTagName('head')[0].appendChild(style);
217 | this._classNames.add(className);
218 | }
219 |
220 | /**
221 | * Has the class been dynamicaly loaded?
222 | */
223 | hasClass(className){
224 | return this._classNames.has(className);
225 | }
226 | }
--------------------------------------------------------------------------------
/src/component/Component.js:
--------------------------------------------------------------------------------
1 | const Mixins = require('../Mixins');
2 |
3 | module.exports = class Component {
4 | constructor(config, parent){
5 | this._parent = parent;
6 | this._id = config.id;
7 | this._x = config.x;
8 | this._y = config.y;
9 | this._type = 'component';
10 | }
11 |
12 | /**
13 | * Initialise default values (css class defaults)
14 | */
15 | init(){
16 | this._defaultClass = {
17 | position: 'absolute',
18 | transform: 'translate(-50%, -50%)',
19 | cursor: 'pointer',
20 | padding: '0px',
21 | userSelect: 'none'
22 | }
23 | }
24 |
25 | /**
26 | * Return item as dom object
27 | */
28 | getItem(){
29 | const r = this._parent.Theme.getColor(this._color).r;
30 | const g = this._parent.Theme.getColor(this._color).g;
31 | const b = this._parent.Theme.getColor(this._color).b;
32 | const itemDiv = document.createElement("div");
33 | const xy = this._parent.View.convertXY(this._x, this._y, 'board');
34 | itemDiv.id = this._id;
35 | itemDiv.className = this._type;
36 | itemDiv.innerHTML = this._text;
37 | itemDiv.style.top = xy.y;
38 | itemDiv.style.left = xy.x;
39 | itemDiv.setAttribute('type', 'component');
40 | this._parent.Mixins.applyStyle(itemDiv, this._defaultTheme, {r,g,b});
41 | this._parent.Theme.applyTheme(itemDiv, 'letter', {r,g,b});
42 | return itemDiv;
43 | }
44 |
45 | /**
46 | * Set the position of the item as and emit to socket (as if it was set by the current user)
47 | */
48 | setLocalPosition(x, y){
49 | this._x = x;
50 | this._y = y;
51 | console.log(`${this._type} ${this._id}: x(${Math.floor(x)}) y(${Math.floor(y)})`);
52 | this._parent.Socket._socket.emit('move', {
53 | id: this._id,
54 | x: this._x,
55 | y: this._y
56 | })
57 | }
58 |
59 | /**
60 | * Set the position of the item and create pulse animation (as if it was set by a different user)
61 | */
62 | setRemotePosition(x, y, time){
63 | this._x = x;
64 | this._y = y;
65 | this.animateSetPosition(time);
66 | if(typeof this.moveAnimation === 'function') this.moveAnimation();
67 | }
68 |
69 | /**
70 | * Animate the movement of the item
71 | */
72 | animateSetPosition(time){
73 | const xy = this._parent.View.convertXY(this._x, this._y, 'board');
74 | const item = document.getElementById(this._id);
75 |
76 | this._parent.Mixins.makeAnimation(item, 'left', xy.x, time || 200);
77 | this._parent.Mixins.makeAnimation(item, 'top', xy.y, time || 200);
78 |
79 | // $('#'+this._id).animate({
80 | // left: xy.x,
81 | // top: xy.y
82 | // }, time || 200);
83 | }
84 | }
--------------------------------------------------------------------------------
/src/component/Letter.README.md:
--------------------------------------------------------------------------------
1 | Component: Letter
2 | =================
3 |
4 | ```
5 | Component type: 'letter'
6 | ```
7 |
8 | ## Description
9 |
10 | The letter component displays a letter (or word) on the board. This is one of / the main component for magnetJS for the current time.
11 |
12 | ## Config (Array)
13 |
14 | ### text
15 |
16 | This defines the text to be displayed on the board
17 |
18 | ### color
19 |
20 | The theme id for the item (see Themes)
21 |
22 | ### x
23 |
24 | The x location of the item on the board (`0` for center).
25 |
26 | ### y
27 |
28 | The y location of the item on the board (`0` for center).
29 |
30 | ## Config (Spawn)
31 |
32 | ### text
33 |
34 | An array of the possible text to display on the board.
35 |
36 | ### color
37 |
38 | An array of potential theme ids for the items (see Themes)
39 |
40 | ### type
41 |
42 | The component type (in this case 'letter')
--------------------------------------------------------------------------------
/src/component/Letter.js:
--------------------------------------------------------------------------------
1 | const Component = require('./Component.js')
2 |
3 | module.exports = class Letter extends Component{
4 | constructor(config, parent){
5 | super(config, parent);
6 | this._text = config.text;
7 | this._color = config.color;
8 | this._type = 'letter';
9 | if(!this._parent.View.hasClass(this._type)) this.init();
10 | this.initCustom();
11 | }
12 |
13 | init(){
14 | /**
15 | * Add parent default class information
16 | */
17 | super.init();
18 | /**
19 | * Insert custom default class information
20 | */
21 | this._defaultClass = Object.assign({
22 | fontStyle: 'normal',
23 | fontSize: '40px',
24 | lineHeight: '40px',
25 | color: 'rgb([r],[g],[b])',
26 | height: '40px',
27 | outline: '1px solid transparent'
28 | },this._defaultClass);
29 | /**
30 | * Dynamically insert class
31 | */
32 | this._parent.View.addCSSClass(this._type, this._defaultClass);
33 | }
34 |
35 | initCustom(){
36 | this._defaultTheme = {
37 | color: 'rgb([r],[g],[b])'
38 | };
39 | }
40 |
41 | moveAnimation(){
42 | const pulse = document.createElement('div');
43 | const me = this;
44 | pulse.id = 'pulse_'+this._id;
45 | pulse.className = 'pulse';
46 | pulse.style.borderColor = `rgb(${this._parent.Theme.getColor(this._color)})`;
47 | document.getElementById(this._id).appendChild(pulse);
48 | setTimeout(() => { document.getElementById(me._id).removeChild(pulse); },10000);
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/src/component/index.js:
--------------------------------------------------------------------------------
1 | const Letter = require('./Letter.js')
2 |
3 | module.exports = {
4 | Letter
5 | }
--------------------------------------------------------------------------------