├── .babelrc ├── .travis.yml ├── LICENSE ├── cardkit.js ├── dist ├── README.md ├── cardkit.js └── dom.js ├── docs ├── bundle.js ├── cardkit.js ├── code │ ├── Card.html │ ├── CardKit.html │ ├── CardKitDOM.html │ ├── CardKitServer.html │ ├── SVGToImage.html │ ├── cardkit.js.html │ ├── fonts │ │ ├── OpenSans-Bold-webfont.eot │ │ ├── OpenSans-Bold-webfont.svg │ │ ├── OpenSans-Bold-webfont.woff │ │ ├── OpenSans-BoldItalic-webfont.eot │ │ ├── OpenSans-BoldItalic-webfont.svg │ │ ├── OpenSans-BoldItalic-webfont.woff │ │ ├── OpenSans-Italic-webfont.eot │ │ ├── OpenSans-Italic-webfont.svg │ │ ├── OpenSans-Italic-webfont.woff │ │ ├── OpenSans-Light-webfont.eot │ │ ├── OpenSans-Light-webfont.svg │ │ ├── OpenSans-Light-webfont.woff │ │ ├── OpenSans-LightItalic-webfont.eot │ │ ├── OpenSans-LightItalic-webfont.svg │ │ ├── OpenSans-LightItalic-webfont.woff │ │ ├── OpenSans-Regular-webfont.eot │ │ ├── OpenSans-Regular-webfont.svg │ │ ├── OpenSans-Regular-webfont.woff │ │ ├── OpenSans-Semibold-webfont.eot │ │ ├── OpenSans-Semibold-webfont.svg │ │ ├── OpenSans-Semibold-webfont.ttf │ │ ├── OpenSans-Semibold-webfont.woff │ │ ├── OpenSans-SemiboldItalic-webfont.eot │ │ ├── OpenSans-SemiboldItalic-webfont.svg │ │ ├── OpenSans-SemiboldItalic-webfont.ttf │ │ └── OpenSans-SemiboldItalic-webfont.woff │ ├── index.html │ ├── renderers_dom_SVGToImage.js.html │ ├── renderers_dom_dom.js.html │ ├── renderers_server_server.js.html │ ├── renderers_shared_Card.js.html │ ├── scripts │ │ ├── linenumber.js │ │ └── prettify │ │ │ ├── Apache-License-2.0.txt │ │ │ ├── lang-css.js │ │ │ └── prettify.js │ └── styles │ │ ├── jsdoc-default.css │ │ ├── prettify-jsdoc.css │ │ └── prettify-tomorrow.css ├── favicon.png └── index.html ├── dom.js ├── eslintrc.json ├── examples ├── browser-script │ └── index.html ├── browser │ ├── cardkit.js │ └── index.html ├── configurations │ └── sample.js └── server │ └── server.js ├── favicon.png ├── index.html ├── package.json ├── server.js ├── src ├── cardkit.js ├── demo-dom.js ├── helpers.js ├── index.html └── renderers │ ├── dom │ ├── dom.js │ ├── svgToImage.js │ └── ui │ │ ├── _settings.scss │ │ ├── base.scss │ │ ├── elements.js │ │ ├── elements │ │ ├── Canvas │ │ │ ├── component.js │ │ │ └── style.scss │ │ ├── Controls │ │ │ ├── ColorControl.js │ │ │ ├── SizeControl.js │ │ │ ├── SourceControl.js │ │ │ └── TextControl.js │ │ ├── Header │ │ │ ├── component.js │ │ │ └── style.scss │ │ ├── LayerConfig │ │ │ ├── component.js │ │ │ └── style.scss │ │ ├── Panels │ │ │ ├── Content │ │ │ │ ├── component.js │ │ │ │ └── style.scss │ │ │ ├── Layout │ │ │ │ ├── CardLayout.js │ │ │ │ ├── component.js │ │ │ │ └── style.scss │ │ │ ├── Template │ │ │ │ ├── CardTemplate.js │ │ │ │ ├── component.js │ │ │ │ └── style.scss │ │ │ └── Theme │ │ │ │ ├── component.js │ │ │ │ └── style.scss │ │ ├── Sidebar │ │ │ ├── PanelButton.js │ │ │ ├── component.js │ │ │ └── style.scss │ │ └── panels.js │ │ ├── images │ │ └── logo.png │ │ └── ui.js │ ├── server │ └── server.js │ └── shared │ ├── Card.js │ └── base.js └── test ├── SVGToImage.spec.js ├── cardkit.spec.js ├── dom.spec.js └── server.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ['es2015', 'react'] 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | after_success: 5 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Times Newspapers Limited 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cardkit.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("deep-extend")); 4 | else if(typeof define === 'function' && define.amd) 5 | define("CardKit", ["deep-extend"], factory); 6 | else if(typeof exports === 'object') 7 | exports["CardKit"] = factory(require("deep-extend")); 8 | else 9 | root["CardKit"] = factory(root["deep-extend"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 60 | 61 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 62 | 63 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 64 | 65 | var deepExtend = __webpack_require__(1); 66 | 67 | /** 68 | * @name CardKit 69 | * @class Core CardKit class used for managing a single card instance 70 | */ 71 | 72 | var CardKit = function () { 73 | 74 | /** 75 | * Constructor takes in the configuration and stores it for later user 76 | * 77 | * @param {object} configuration - The configuration object to initialise the CardKit image with. 78 | * @param {object} options - The additional options for use 79 | */ 80 | function CardKit(configuration) { 81 | var options = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 82 | 83 | _classCallCheck(this, CardKit); 84 | 85 | if (!configuration) { 86 | throw new Error('A configuration object was not provided'); 87 | } 88 | 89 | if (!this._isValidConfiguration(configuration)) { 90 | throw new Error('Invalid configuration object provided'); 91 | } 92 | 93 | // Store the configuration 94 | this.configuration = configuration; 95 | 96 | // Configure the options 97 | this._configureOptions(options); 98 | 99 | // Setup an empty array of renderers 100 | this.renderers = []; 101 | } 102 | 103 | /** 104 | * Configures the supplied options on this instance of CardKit 105 | * 106 | * @param {object} options - The options to configure 107 | */ 108 | 109 | 110 | _createClass(CardKit, [{ 111 | key: '_configureOptions', 112 | value: function _configureOptions(options) { 113 | if (options) { 114 | if (options.templates) { 115 | if (!this._isValidTemplatesConfiguration(options.templates)) { 116 | throw new Error('Invalid templates configuration object provided'); 117 | } 118 | 119 | this.templates = options.templates; 120 | } else { 121 | this.templates = null; 122 | } 123 | 124 | if (options.themes) { 125 | if (!this._isValidThemesConfiguration(options.themes)) { 126 | throw new Error('Invalid themes configuration object provided'); 127 | } 128 | 129 | this.themes = options.themes; 130 | } else { 131 | this.themes = null; 132 | } 133 | 134 | if (options.layouts) { 135 | if (!this._isValidLayoutsConfiguration(options.layouts)) { 136 | throw new Error('Invalid layouts configuration object provided'); 137 | } 138 | 139 | this.layouts = options.layouts; 140 | } else { 141 | this.layouts = null; 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * Validates the provided configuration object 148 | * 149 | * @param {object} configuration - The configuration object to validate 150 | * 151 | * @return {boolean} Is the configuration object valid 152 | */ 153 | 154 | }, { 155 | key: '_isValidConfiguration', 156 | value: function _isValidConfiguration(configuration) { 157 | return (typeof configuration === 'undefined' ? 'undefined' : _typeof(configuration)) === 'object' && // Should be an object 158 | typeof configuration.card !== 'undefined' && // Should have a card property 159 | _typeof(configuration.card) === 'object' && // Card property should be an object 160 | typeof configuration.card.height !== 'undefined' && // Should have a height 161 | typeof configuration.card.width !== 'undefined'; // Should have a width 162 | } 163 | 164 | /** 165 | * Validates the provided templates configuration object 166 | * 167 | * @param {object} configuration - The templates configuration object to validate 168 | * 169 | * @return {boolean} Is the templates configuration object valid 170 | */ 171 | 172 | }, { 173 | key: '_isValidTemplatesConfiguration', 174 | value: function _isValidTemplatesConfiguration(templates) { 175 | return (typeof templates === 'undefined' ? 'undefined' : _typeof(templates)) === 'object'; // Should be an object 176 | } 177 | 178 | /** 179 | * Validates the provided themes configuration object 180 | * 181 | * @param {object} configuration - The themes configuration object to validate 182 | * 183 | * @return {boolean} Is the themes configuration object valid 184 | */ 185 | 186 | }, { 187 | key: '_isValidThemesConfiguration', 188 | value: function _isValidThemesConfiguration(themes) { 189 | return (typeof themes === 'undefined' ? 'undefined' : _typeof(themes)) === 'object'; // Should be an object 190 | } 191 | 192 | /** 193 | * Validates the provided layouts configuration object 194 | * 195 | * @param {object} configuration - The layouts configuration object to validate 196 | * 197 | * @return {boolean} Is the layouts configuration object valid 198 | */ 199 | 200 | }, { 201 | key: '_isValidLayoutsConfiguration', 202 | value: function _isValidLayoutsConfiguration(layouts) { 203 | return (typeof layouts === 'undefined' ? 'undefined' : _typeof(layouts)) === 'object'; // Should be an object 204 | } 205 | 206 | /** 207 | * Validates the supplied renderer 208 | * 209 | * @param {object} renderer - The renderer to validate 210 | * 211 | * @return {boolean} If the renderer is valid 212 | */ 213 | 214 | }, { 215 | key: '_isValidRenderer', 216 | value: function _isValidRenderer(renderer) { 217 | return renderer.cardkit === this; 218 | } 219 | 220 | /** 221 | * Compute the configuration 222 | * 223 | * @param {object} options - Any options (e.g. a specific theme and / or layout) to use when computing the configuration 224 | * 225 | * @return {object} The computed configuration 226 | */ 227 | 228 | }, { 229 | key: 'computeConfiguration', 230 | value: function computeConfiguration() { 231 | var options = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; 232 | 233 | // Get the base configuration 234 | var configuration = Object.assign({}, this.configuration); 235 | 236 | // If we got options supplied 237 | if (options) { 238 | if (options.template && typeof this.templates[options.template] !== 'undefined') { 239 | // Get the template based on the name and merge it onto the base configuration 240 | configuration = deepExtend(configuration, this.templates[options.template]); 241 | } 242 | 243 | if (options.theme && typeof this.themes[options.theme] !== 'undefined') { 244 | // Get the theme based on the name and merge it onto the base configuration 245 | configuration = deepExtend(configuration, this.themes[options.theme]); 246 | } 247 | 248 | if (options.layout && typeof this.layouts[options.layout] !== 'undefined') { 249 | // Get the layout based on the name and merge it onto the base configuration 250 | configuration = deepExtend(configuration, this.layouts[options.layout]); 251 | } 252 | } 253 | 254 | // Return the computed configuration 255 | return configuration; 256 | } 257 | 258 | /** 259 | * Updates the configuration, and optionally rerenders the image (if previously rendered) 260 | * 261 | * @param {object} configuration - The configuration object to update to 262 | * @param {object} options - Any options to supply (templates, themes, layouts) 263 | * @param {boolean} rerender - Whether or not to attempt to rerender the image 264 | */ 265 | 266 | }, { 267 | key: 'updateConfiguration', 268 | value: function updateConfiguration(configuration) { 269 | var options = arguments.length <= 1 || arguments[1] === undefined ? { layouts: null, templates: null, themes: null } : arguments[1]; 270 | var rerender = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; 271 | 272 | this.configuration = configuration; 273 | 274 | this._configureOptions(options); 275 | 276 | if (rerender) { 277 | var renderers = this.getRenderers(); 278 | 279 | renderers.forEach(function (renderer) { 280 | switch (renderer.constructor.name) { 281 | case 'CardKitDOM': 282 | renderer.rerender(); 283 | break; 284 | } 285 | }); 286 | } 287 | } 288 | 289 | /** 290 | * Get the renderers 291 | * 292 | * @return {array} The configured renderers 293 | */ 294 | 295 | }, { 296 | key: 'getRenderers', 297 | value: function getRenderers() { 298 | return this.renderers; 299 | } 300 | 301 | /** 302 | * Add a renderer 303 | * 304 | * @param {object} renderer - A renderer to add 305 | */ 306 | 307 | }, { 308 | key: 'addRenderer', 309 | value: function addRenderer(renderer) { 310 | if (!this._isValidRenderer(renderer)) { 311 | throw new Error('Invalid renderer object provided'); 312 | } 313 | 314 | this.renderers.push(renderer); 315 | } 316 | }]); 317 | 318 | return CardKit; 319 | }(); 320 | 321 | // Export it 322 | 323 | 324 | module.exports = CardKit; 325 | 326 | // Add it to the window object if we have one 327 | if (typeof window !== 'undefined') { 328 | window.CardKit = CardKit; 329 | } 330 | 331 | /***/ }, 332 | /* 1 */ 333 | /***/ function(module, exports) { 334 | 335 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 336 | 337 | /***/ } 338 | /******/ ]) 339 | }); 340 | ; -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # CardKit `./dist` directory 2 | 3 | The files in here can be used in ` 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/cardkit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CardKit example: Browser bundle using Webpack 3 | * 4 | * This bundle shows how to use CardKit as part of a bundle built by Webpack. 5 | * This `cardkit.js` file will be run through Webpack and create a bundle file, that can be included into `index.html` 6 | */ 7 | 8 | // Load dependencies 9 | var CardKit = require('../cardkit'); 10 | var CardKitDOM = require('../dom'); 11 | 12 | // Import configuration 13 | var data = require('../examples/configurations/sample'); 14 | 15 | // Initialise 16 | var cardkit = new CardKit(data.configuration, { 17 | templates: data.templates, 18 | themes: data.themes, 19 | layouts: data.layouts 20 | }); 21 | 22 | // Start the renderer 23 | var renderer = new CardKitDOM(cardkit); 24 | 25 | // Render the UI 26 | renderer.renderUI('ui'); -------------------------------------------------------------------------------- /docs/code/cardkit.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cardkit.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 |
30 | 31 |

cardkit.js

32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
const deepExtend = require('deep-extend');
 42 | 
 43 | /**
 44 |  * @name CardKit
 45 |  * @class Core CardKit class used for managing a single card instance
 46 |  */
 47 | class CardKit {
 48 | 
 49 |   /**
 50 |    * Constructor takes in the configuration and stores it for later user
 51 |    *
 52 |    * @param {object} configuration - The configuration object to initialise the CardKit image with.
 53 |    * @param {object} options - The additional options for use
 54 |    */
 55 |   constructor (configuration, options = false) {
 56 |     if (!configuration) {
 57 |       throw new Error('A configuration object was not provided')
 58 |     }
 59 | 
 60 |     if (!this._isValidConfiguration(configuration)) {
 61 |       throw new Error('Invalid configuration object provided')
 62 |     }
 63 | 
 64 |     // Store the configuration
 65 |     this.configuration = configuration;
 66 | 
 67 |     // Configure the options
 68 |     this._configureOptions(options);
 69 | 
 70 |     // Setup an empty array of renderers
 71 |     this.renderers = [];
 72 |   }
 73 | 
 74 |   /**
 75 |    * Configures the supplied options on this instance of CardKit
 76 |    *
 77 |    * @param {object} options - The options to configure
 78 |    */
 79 |   _configureOptions (options) {
 80 |     if (options) {
 81 |       if (options.templates) {
 82 |         if (!this._isValidTemplatesConfiguration(options.templates)) {
 83 |           throw new Error('Invalid templates configuration object provided');
 84 |         }
 85 | 
 86 |         this.templates = options.templates
 87 |       } else {
 88 |         this.templates = null;
 89 |       }
 90 | 
 91 |       if (options.themes) {
 92 |         if (!this._isValidThemesConfiguration(options.themes)) {
 93 |           throw new Error('Invalid themes configuration object provided');
 94 |         }
 95 | 
 96 |         this.themes = options.themes
 97 |       } else {
 98 |         this.themes = null;
 99 |       }
100 | 
101 |       if (options.layouts) {
102 |         if (!this._isValidLayoutsConfiguration(options.layouts)) {
103 |           throw new Error('Invalid layouts configuration object provided');
104 |         }
105 | 
106 |         this.layouts = options.layouts
107 |       } else {
108 |         this.layouts = null;
109 |       }
110 |     }
111 |   }
112 | 
113 |   /**
114 |    * Validates the provided configuration object
115 |    *
116 |    * @param {object} configuration - The configuration object to validate
117 |    *
118 |    * @return {boolean} Is the configuration object valid
119 |    */
120 |   _isValidConfiguration (configuration) {
121 |     return (typeof configuration === 'object') && // Should be an object
122 |            (typeof configuration.card !== 'undefined') && // Should have a card property
123 |            (typeof configuration.card === 'object') && // Card property should be an object
124 |            (typeof configuration.card.height !== 'undefined') && // Should have a height
125 |            (typeof configuration.card.width !== 'undefined'); // Should have a width
126 |   }
127 | 
128 |   /**
129 |    * Validates the provided templates configuration object
130 |    *
131 |    * @param {object} configuration - The templates configuration object to validate
132 |    *
133 |    * @return {boolean} Is the templates configuration object valid
134 |    */
135 |   _isValidTemplatesConfiguration (templates) {
136 |     return (typeof templates === 'object'); // Should be an object
137 |   }
138 | 
139 |   /**
140 |    * Validates the provided themes configuration object
141 |    *
142 |    * @param {object} configuration - The themes configuration object to validate
143 |    *
144 |    * @return {boolean} Is the themes configuration object valid
145 |    */
146 |   _isValidThemesConfiguration (themes) {
147 |     return (typeof themes === 'object'); // Should be an object
148 |   }
149 | 
150 |   /**
151 |    * Validates the provided layouts configuration object
152 |    *
153 |    * @param {object} configuration - The layouts configuration object to validate
154 |    *
155 |    * @return {boolean} Is the layouts configuration object valid
156 |    */
157 |   _isValidLayoutsConfiguration (layouts) {
158 |     return (typeof layouts === 'object'); // Should be an object
159 |   }
160 | 
161 |   /**
162 |    * Validates the supplied renderer
163 |    *
164 |    * @param {object} renderer - The renderer to validate
165 |    *
166 |    * @return {boolean} If the renderer is valid
167 |    */
168 |   _isValidRenderer (renderer) {
169 |     return (renderer.cardkit === this);
170 |   }
171 | 
172 |   /**
173 |    * Compute the configuration
174 |    *
175 |    * @param {object} options - Any options (e.g. a specific theme and / or layout) to use when computing the configuration
176 |    *
177 |    * @return {object} The computed configuration
178 |    */
179 |   computeConfiguration (options = null) {
180 |     // Get the base configuration
181 |     let configuration = Object.assign({}, this.configuration);
182 | 
183 |     // If we got options supplied
184 |     if (options) {
185 |       if (options.template && typeof this.templates[options.template] !== 'undefined') {
186 |         // Get the template based on the name and merge it onto the base configuration
187 |         configuration = deepExtend(configuration, this.templates[options.template]);
188 |       }
189 | 
190 |       if (options.theme && typeof this.themes[options.theme] !== 'undefined') {
191 |         // Get the theme based on the name and merge it onto the base configuration
192 |         configuration = deepExtend(configuration, this.themes[options.theme]);
193 |       }
194 | 
195 |       if (options.layout && typeof this.layouts[options.layout] !== 'undefined') {
196 |         // Get the layout based on the name and merge it onto the base configuration
197 |         configuration = deepExtend(configuration, this.layouts[options.layout]);
198 |       }
199 |     }
200 | 
201 |     // Return the computed configuration
202 |     return configuration;
203 |   }
204 | 
205 |   /**
206 |    * Updates the configuration, and optionally rerenders the image (if previously rendered)
207 |    *
208 |    * @param {object} configuration - The configuration object to update to
209 |    * @param {object} options - Any options to supply (templates, themes, layouts)
210 |    * @param {boolean} rerender - Whether or not to attempt to rerender the image
211 |    */
212 |   updateConfiguration (configuration, options = { layouts: null, templates: null, themes: null }, rerender = true) {
213 |     this.configuration = configuration;
214 | 
215 |     this._configureOptions(options);
216 | 
217 |     if (rerender) {
218 |       const renderers = this.getRenderers();
219 | 
220 |       renderers.forEach((renderer) => {
221 |         switch (renderer.constructor.name) {
222 |           case 'CardKitDOM':
223 |             renderer.rerender();
224 |             break;
225 |         }
226 |       });
227 |     }
228 |   }
229 | 
230 |   /**
231 |    * Get the renderers
232 |    *
233 |    * @return {array} The configured renderers
234 |    */
235 |   getRenderers () {
236 |     return this.renderers;
237 |   }
238 | 
239 |   /**
240 |    * Add a renderer
241 |    *
242 |    * @param {object} renderer - A renderer to add
243 |    */
244 |   addRenderer (renderer) {
245 |     if (!this._isValidRenderer(renderer)) {
246 |       throw new Error('Invalid renderer object provided')
247 |     }
248 | 
249 |     this.renderers.push(renderer);
250 |   }
251 | }
252 | 
253 | // Export it
254 | module.exports = CardKit
255 | 
256 | // Add it to the window object if we have one
257 | if (typeof window !== 'undefined') {
258 |   window.CardKit = CardKit
259 | }
260 | 
261 |
262 |
263 | 264 | 265 | 266 | 267 |
268 | 269 |
270 | 271 | 274 | 275 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/code/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/code/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Home - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |

CardKit 2

A simple, powerful and fully configurable image editor for web browers and servers. Optional UI included.

51 |

CardKit 2 has three main parts:

52 |
    53 |
  • CardKit: The core library, that manages and maintains the configuration object which defines the structure and options of a card
  • 54 |
  • CardKitDOM: A DOM renderer, that takes an instance of CardKit and renders either a standalone image, or a pre-packaged UI for editing the image
  • 55 |
  • CardKitServer: A server renderer, that allows you to take an instance of CardKit and render it into an image on a Node.js server
  • 56 |
57 |

Additionally, a base class allows you to create your own renderers. See more in the Custom Renderers section.

58 |

Installation

$ npm install cardkit --save

59 |

Running locally

To run a sample UI locally, run: $ npm start

60 |

You can optionally pass a port like so: $ npm start -- --port=8080

61 |

Configuring

See the Wiki for all the available options for your configuration.

62 |

Usage

Browser with Webpack / Browserify usage

// Load CardKit and CardKit DOM
 63 | const CardKit = require('cardkit');
 64 | const CardKitDOM = require('cardkit/dom');
 65 | 
 66 | // Base configuration object - see `./examples/configurations` for examples
 67 | var configuration = {};
 68 | 
 69 | // Optional themes object - see `./examples/configurations` for examples
 70 | var themes = {};
 71 | 
 72 | // Optional layouts object - see `./examples/configurations` for examples
 73 | var layouts = {};
 74 | 
 75 | // Initialise CardKit
 76 | var cardkit = new CardKit(configuration, {
 77 |   themes: themes,
 78 |   layouts: layouts
 79 | });
 80 | 
 81 | // Initialise Renderer
 82 | var renderer = new CardKitDOM(cardkit);
 83 | 
 84 | // To render the card only (with optional theme / layout overrides)
 85 | renderer.renderCard('card', {
 86 |   theme: 'Alt',
 87 |   layout: 'Square'
 88 | });
 89 | 
 90 | // OR To render the editing UI
 91 | renderer.renderUI('card');

Browser with <script> tag usage

<!-- Load in React from a CDN (or similar) -->
 92 | <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.min.js"></script>
 93 | <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.min.js"></script>
 94 | 
 95 | <!-- Load in the CardKit and CardKitDOM Libraries -->
 96 | <script type="text/javascript" src="url/to/cardkit.js"></script>
 97 | <script type="text/javascript" src="url/to/cardkit-dom.js"></script>
 98 | 
 99 | <!-- Your container element to render into -->
100 | <div id="card"></div>
101 | 
102 | <script type="text/javascript">
103 |   // Base configuration object - see `./examples/configurations` for examples
104 |   var configuration = {};
105 | 
106 |   // Optional themes object - see `./examples/configurations` for examples
107 |   var themes = {};
108 | 
109 |   // Optional layouts object - see `./examples/configurations` for examples
110 |   var layouts = {};
111 | 
112 |   // Initialise CardKit
113 |   var cardkit = new CardKit(configuration, {
114 |     themes: themes,
115 |     layouts: layouts
116 |   });
117 | 
118 |   // Initialise Renderer
119 |   var renderer = new CardKitDOM(cardkit);
120 | 
121 |   // To render the card only (with optional theme / layout overrides)
122 |   renderer.renderCard('card', {
123 |     theme: 'Alt',
124 |     layout: 'Square'
125 |   });
126 | 
127 |   // OR To render the editing UI
128 |   renderer.renderUI('card');
129 | </script>

Server usage

// Require CardKit and CardKitServer
130 | const CardKit = require('cardkit');
131 | const CardKitServer = require('cardkit/server');
132 | 
133 | // Base configuration object - see `./examples/configurations` for examples
134 | const configuration = {};
135 | 
136 | // Initialise CardKit
137 | const cardkit = new CardKit(configuration);
138 | 
139 | // Initialise Renderer
140 | var renderer = new CardKitServer(cardkit);
141 | 
142 | // Render to image
143 | renderer.renderToImage(2)
144 |        .then((image) => {
145 |           // Do what you want with the image here...
146 |           console.log('<img src="data:image/png;base64,' + image + '" />');
147 |           process.exit();
148 |        })
149 |        .catch((e) => {
150 |           console.log('[ERR]', e);
151 |           process.exit();
152 |        });

APIs

CardKit

new CardKit(configuration, options)

153 |

Initialisation. Pass in a required configuration object, and optional themes, templates and layouts

154 |
155 |

cardkit.updateConfiguration(configuration, options, rerender)

156 |

Updates the configuration in your instance of CardKit. Can optionally rerender with a flag if previously rendered (supported in CardKitDOM).

157 |
158 |

cardkit.computeConfiguration(options)

159 |

Computes a configuaration object, optionally accepting a named template, theme and layout. These get merged into the base configuration and returned.

160 |
161 |

CardKitDOM

new CardKitDOM(cardkit)

162 |

Accepts an instance of CardKit and initialises the renderer

163 |
164 |

cardkit.renderUI(id, overrides)

165 |

Renders the include user interface to the specified DOM element

166 |
167 |

cardkit.renderCard(id)

168 |

Renders just the card in it's SVG form to the specified DOM element

169 |
170 |

cardkit.rerender()

171 |

Will re-render the existing UI or card

172 |
173 |

cardkit.download(scale, element)

174 |

Downloads the image to your local machine. Accepts a scale (default=2), and an element to grab from. If not provided it will fall back to the existing card being rendererd (if renderCard() was used).

175 |
176 |

CardKitServer

new CardKitDOM(cardkit)

177 |

Accepts an instance of CardKit and initialises the renderer

178 |
179 |

cardkit.renderToString()

180 |

Renders the card to a HTML string (e.g. <svg...></svg>)

181 |
182 |

cardkit.renderToImage(scale)

183 |

Renders the card to an image returning a Promise containing the image as a base64 string

184 |
185 |

Custom Renderers

A base class CardKitRenderer allows you to create your own renderer for CardKit. For example, CardKitDOM currently uses SVG to create the card, and React to render the UI. You may, however, wish to render your card using HTML canvas, or build a UI using Vue.js. Creating a custom renderer is a good way to achieve this. Below is a brief example of how you might achieve this:

186 |
class CardKitCanvas extends CardKitRenderer {
187 | 
188 |   renderCard () {
189 |     // Canvas-specific code here
190 |   }
191 | 
192 |   rerender () { // A method that `CardKit` calls if the base configuration object is updated
193 |     // Handle an update to the base configuration, e.g. you may want to re-render the canvas element here
194 |   }
195 | 
196 |   yourCustomMethod () {
197 |     // You can implement any custom methods here, for example you may wish to expose or manipulate the <canvas> element for other users to take advantage of
198 |   }
199 | 
200 | }
201 | 
202 | const cardkit = new CardKit(configuration);
203 | 
204 | const renderer = new CardKitCanvas(cardkit);
205 | 
206 | renderer.yourCustomMethod();

Custom Fonts

CardKit allows you to load in custom fonts for use on your cards, see the Wiki for details. These need to be encoded into base64 format. If you wish to use a Google font, you can use the googlefontcss64 library to generate a base64 version of any Google font.

207 |

Upgrading from v1.x

Upgrading from v1.x to v2 should be a fairly straightforward process if you haven't made any major modifications to the v1.x user interface. Your configuration object from v1.x should be compatible with v2 with a few minor tweaks. Specific variations are available in the Wiki.

208 |

Tests

To trigger the test suite, run $ npm run test

209 |
210 | 211 | 212 | 213 | 214 | 215 | 216 |
217 | 218 |
219 | 220 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /docs/code/renderers_dom_SVGToImage.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | renderers/dom/SVGToImage.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 |
30 | 31 |

renderers/dom/SVGToImage.js

32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
const helpers = require('../../helpers');
 42 | 
 43 | /**
 44 |  * @name SVGToImage
 45 |  * @class Used for downloading an SVG DOM element in your browser
 46 |  */
 47 | class SVGToImage {
 48 | 
 49 |   /**
 50 |    * Constructor takes in the element for later use
 51 |    *
 52 |    * @param {object} element - The SVG element to convert to an image
 53 |    */
 54 |   constructor (element) {
 55 |     // Ensure we got an element
 56 |     if (typeof element === 'undefined') {
 57 |       throw new Error('No element provided');
 58 |     }
 59 | 
 60 |     // Validate that the provided element is an HTML element
 61 |     if (!this._isValidElement(element)) {
 62 |       throw new Error('Provided element is not a valid element');
 63 |     }
 64 | 
 65 |     // Check the provided element is an SVG element
 66 |     if (element.tagName.toLowerCase() !== 'svg') {
 67 |       throw new Error('Invalid element provided');
 68 |     }
 69 | 
 70 |     // Store the element
 71 |     this.element = element;
 72 |   }
 73 | 
 74 |   /**
 75 |    * Validates the provided element is an HTMLElement
 76 |    * Source: http://stackoverflow.com/a/384380/3886818
 77 |    *
 78 |    * @param {mixed} element - The element to validate
 79 |    *
 80 |    * @return {boolean} True if the provided element is valid
 81 |    */
 82 |   _isValidElement (element) {
 83 |     return (typeof element !== 'undefined') &&
 84 |            (element !== null) &&
 85 |            (typeof element === 'object') &&
 86 |            (element.nodeType === 1) &&
 87 |            (typeof element.nodeName === 'string');
 88 |   }
 89 | 
 90 |   /**
 91 |    * Downloads the SVG as an image
 92 |    *
 93 |    * @param {string} name - The name to download the image with
 94 |    * @param {object} options - The configurable options
 95 |    */
 96 |   download (name, options = {}) {
 97 |     // Setup default options
 98 |     options.format = options.format || 'image/jpeg';
 99 | 
100 |     // Convert it to a data URI
101 |     this._toDataURI(options, (uri) => {
102 |       // We have our data URI
103 | 
104 |       // Create an image
105 |       const image = new window.Image();
106 |       image.src = uri;
107 | 
108 |       // Confiugre the image onload callback
109 |       image.onload = function () {
110 |         // Create a canvas element sized to fit the image
111 |         const canvas = document.createElement('canvas');
112 |         canvas.width = image.width;
113 |         canvas.height = image.height;
114 | 
115 |         // Get the canvas context and draw the image onto it
116 |         const context = canvas.getContext('2d');
117 |         context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
118 | 
119 |         // Create a link to dynamically click and trigger the download
120 |         const a = document.createElement('a');
121 |         a.download = name;
122 |         a.href = canvas.toDataURL(options.format || 'image/jpeg');
123 |         document.body.appendChild(a);
124 | 
125 |         // I'm aware that `a.click()` below may not work reliably on all browsers. This is something to explore at a later date.
126 | 
127 |         // Click and download
128 |         a.click();
129 |       }
130 |     });
131 |   }
132 | 
133 |   /**
134 |    * Verifies if the supplied URL is external or local
135 |    *
136 |    * @param {string} url - The URL to check
137 |    *
138 |    * @return {boolean} True if the supplied URL is external
139 |    */
140 |   _isExternal (url) {
141 |     return (url) && // We have a URL
142 |            (url.lastIndexOf('http', 0) === 0) && // It starts with http
143 |            (url.lastIndexOf(window.location.host) === -1); // It doesn't contain the current hostname
144 |   }
145 | 
146 |   /**
147 |    * Inlines all images
148 |    *
149 |    * @param {function} callback - The callback to run after images have been loaded and inlined
150 |    */
151 |   _inlineImages (callback) {
152 |     // Get any images
153 |     const images = this.element.querySelectorAll('image');
154 | 
155 |     // If there are no images, immediately call the callback
156 |     if (images.length === 0) {
157 |       callback();
158 |       return;
159 |     }
160 | 
161 |     const promises = [];
162 | 
163 |     // Iterate over the images
164 |     images.forEach((image) => {
165 |       // Get the href for the image
166 |       const href = image.getAttribute('xlink:href') || image.getAttribute('href');
167 | 
168 |       // If no href for this image, skip this image
169 |       if (href === null) return;
170 | 
171 |       // If we had a href, check if it's external
172 |       if (href && this._isExternal(href)) {
173 |         throw new Error('Cannot render embedded images linking to external hosts: ' + href);
174 |       }
175 | 
176 |       // Create a canvas and image
177 |       const canvas = document.createElement('canvas');
178 |       const ctx = canvas.getContext('2d');
179 |       const img = new window.Image();
180 | 
181 |       // Create a promise and push it to the promises array
182 |       promises.push(new Promise((resolve, reject) => {
183 |         // Set the image source
184 |         img.src = href;
185 | 
186 |         // Image load callback
187 |         img.onload = function () {
188 |           // Set the canvases size
189 |           canvas.width = img.width;
190 |           canvas.height = img.height;
191 | 
192 |           // Draw it onto the canvas
193 |           ctx.drawImage(img, 0, 0);
194 | 
195 |           // Update the href attribute of the image element
196 |           image.setAttribute('xlink:href', canvas.toDataURL('image/png'));
197 |           image.setAttribute('href', canvas.toDataURL('image/png'));
198 | 
199 |           // Resolve the promise
200 |           resolve();
201 |         }
202 | 
203 |         // Image error callback
204 |         img.onerror = function () {
205 |           // Image couldn't be loaded, reject the promise
206 |           reject('Could not load image: ' + href);
207 |         }
208 |       }));
209 |     });
210 | 
211 |     // Wait for promises to resolve and call the callback
212 |     Promise.all(promises)
213 |            .then(callback)
214 |            .catch(e => { throw new Error(e) });
215 |   }
216 | 
217 |   /**
218 |    * Converts the element to a data URI
219 |    *
220 |    * @param {object} options - Configuration options
221 |    * @param {function} callback - The callback to run after the element has been converted
222 |    */
223 |   _toDataURI (options = {}, callback) {
224 |     // Setup default options
225 |     options.scale = options.scale || 1;
226 | 
227 |     // Setup some SVG data
228 |     const xmlns = 'http://www.w3.org/2000/xmlns/';
229 | 
230 |     // Inline images first
231 |     this._inlineImages(() => {
232 |       // Setup a container <div>
233 |       const outer = document.createElement('div');
234 | 
235 |       // Clone the element
236 |       const clone = this.element.cloneNode(true);
237 | 
238 |       // Setup some vars
239 |       let width,
240 |         height,
241 |         svg;
242 | 
243 |       // If the element is an SVG we work out the size of the SVG using a variety of methods,
244 |       //  depending on how the user has defined the size of their SVG
245 |       if (this.element.tagName !== 'svg') {
246 |         throw new Error('Invalid element provided, must be SVG');
247 |       }
248 | 
249 |       // Get the width and height
250 |       width = parseInt(this.element.viewBox.baseVal.width || clone.getAttribute('data-width') || clone.style.width);
251 |       height = parseInt(this.element.viewBox.baseVal.height || clone.getAttribute('data-height') || clone.style.height);
252 | 
253 |       // Configure the clone's wrapper attributes
254 |       clone.setAttribute('version', '1.1');
255 |       clone.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/2000/svg');
256 |       clone.setAttributeNS(xmlns, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');
257 |       clone.setAttribute('width', width * options.scale);
258 |       clone.setAttribute('height', height * options.scale);
259 |       clone.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
260 |       outer.appendChild(clone);
261 | 
262 |       // Setup the SVG by adding the XML doctype
263 |       const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
264 | 
265 |       // Combine the doctype and the innerHTML of the cloned SVG to get the final product
266 |       svg = doctype + outer.innerHTML;
267 | 
268 |       // Create the URI
269 |       const uri = 'data:image/svg+xml;base64,' + helpers.svgToBase64(svg, window.btoa);
270 | 
271 |       // Run the callback
272 |       callback(uri);
273 |     });
274 |   }
275 | 
276 | }
277 | 
278 | module.exports = SVGToImage;
279 | 
280 |
281 |
282 | 283 | 284 | 285 | 286 |
287 | 288 |
289 | 290 | 293 | 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /docs/code/renderers_dom_dom.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | renderers/dom/dom.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 |
30 | 31 |

renderers/dom/dom.js

32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
// Dependencies
 42 | const React = require('react');
 43 | const ReactDOM = require('react-dom');
 44 | const Card = require('../shared/Card');
 45 | const CardKitRenderer = require('../shared/base');
 46 | const UI = require('./ui/ui');
 47 | const SVGToImage = require('./svgToImage');
 48 | const { slugify } = require('../../helpers');
 49 | 
 50 | /**
 51 |  * @name CardKitDOM
 52 |  * @class Additional CardKit class used for rendering in the DOM
 53 |  */
 54 | class CardKitDOM extends CardKitRenderer {
 55 | 
 56 |   /**
 57 |    * Constructor takes in an instance of CardKit and stores it for later use
 58 |    *
 59 |    * @param {CardKit} cardkit - An instance of CardKit
 60 |    */
 61 |   constructor (cardkit) {
 62 |     // Ensure we're operating in a browser environment
 63 |     if (typeof document === 'undefined') {
 64 |       throw new Error('CardKitDOM can only be used in a browser environment');
 65 |     }
 66 | 
 67 |     super(cardkit);
 68 | 
 69 |     // Store render IDs
 70 |     this.renderedCardID = null;
 71 |     this.renderedUIID = null;
 72 |   }
 73 | 
 74 |   /**
 75 |    * Renders the built-in UI to the supplied element
 76 |    *
 77 |    * @param {string} id - The ID of the element to render the UI into
 78 |    */
 79 |   renderUI (id) {
 80 |     if (!this._isValidElement(id)) {
 81 |       throw new Error('Invalid element ID provided');
 82 |     }
 83 | 
 84 |     const element = document.getElementById(id);
 85 | 
 86 |     const template = ((this.cardkit.templates) ? Object.keys(this.cardkit.templates)[0] : false);
 87 |     const theme = ((this.cardkit.themes) ? Object.keys(this.cardkit.themes)[0] : false);
 88 |     const layout = ((this.cardkit.layouts) ? Object.keys(this.cardkit.layouts)[0] : false);
 89 | 
 90 |     this.renderedUIID = id;
 91 |     ReactDOM.render(
 92 |       React.createElement(UI, {
 93 |         configuration: this.computeConfiguration({
 94 |           template: template,
 95 |           theme: theme,
 96 |           layout: layout
 97 |         }),
 98 |         templates: this.cardkit.templates,
 99 |         themes: this.cardkit.themes,
100 |         layouts: this.cardkit.layouts,
101 |         cardKit: this
102 |       }),
103 |       element
104 |     );
105 |   }
106 | 
107 |   /**
108 |    * Renders just the Card as a React component to the supplied element
109 |    *
110 |    * @param {string} id - The ID of the element to render the card into
111 |    * @param {object} options - Any override data (e.g. theme, layout) to use when rendering the card
112 |    */
113 |   renderCard (id, options) {
114 |     if (!this._isValidElement(id)) {
115 |       throw new Error('Invalid element ID provided');
116 |     }
117 | 
118 |     const element = document.getElementById(id);
119 | 
120 |     this.renderedCardID = id;
121 | 
122 |     ReactDOM.render(
123 |       React.createElement(Card, {configuration: this.computeConfiguration(options)}),
124 |       element
125 |     );
126 |   }
127 | 
128 |   /**
129 |    * Checks if the ID provided is valid
130 |    *
131 |    * @param {string} id - The ID to validate
132 |    * @return {boolean} If the ID was valid
133 |    */
134 |   _isValidElement (id) {
135 |     if (!id) {
136 |       return false;
137 |     }
138 | 
139 |     const element = document.getElementById(id);
140 |     if (!element) {
141 |       return false;
142 |     }
143 | 
144 |     return true;
145 |   }
146 | 
147 |   /**
148 |    * Re-renders the Card or UI
149 |    */
150 |   rerender () {
151 |     if (this.renderedUIID) {
152 |       this.renderUI(this.renderedUIID);
153 |     }
154 | 
155 |     if (this.renderedCardID) {
156 |       this.renderCard(this.renderedCardID);
157 |     }
158 |   }
159 | 
160 |   /**
161 |    * Downloads the card as an image in the browser
162 |    *
163 |    * @param {number} scale - The scale to output at
164 |    * @param {object} element - The element to use to generate the image
165 |    */
166 |   download (scale = 2, element) {
167 |     element = element.childNodes[0] || document.getElementById(this.renderedCardID).childNodes[0];
168 | 
169 |     const svgToImage = new SVGToImage(element);
170 | 
171 |     // Setup default filename
172 |     let filename = 'cardkit-default.jpg';
173 | 
174 |     // Get the configuration
175 |     const configuration = this.computeConfiguration();
176 | 
177 |     // If there's a layer that has the useAsFilename property, find it
178 |     const filenameLayerKey = Object.keys(configuration.layers)
179 |                                    .find((key) => {
180 |                                      const layer = configuration.layers[key];
181 | 
182 |                                      return (layer.useAsFilename === true) && // Has the useAsFilename property
183 |                                             (layer.hidden !== true) && // Is not hidden
184 |                                             (layer.type === 'text'); // Is of type text
185 |                                    });
186 | 
187 |     // Get the layer that has the filename on it
188 |     const filenameLayer = configuration.layers[filenameLayerKey];
189 | 
190 |     // Update the filename
191 |     if (filenameLayer) {
192 |       filename = slugify(filenameLayer.text) + '.jpg';
193 |     }
194 | 
195 |     // Trigger the download
196 |     svgToImage.download(filename, {
197 |       format: 'image/jpeg',
198 |       scale: scale
199 |     });
200 |   }
201 | }
202 | 
203 | module.exports = CardKitDOM
204 | 
205 |
206 |
207 | 208 | 209 | 210 | 211 |
212 | 213 |
214 | 215 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /docs/code/renderers_server_server.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | renderers/server/server.js - Documentation 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 |
30 | 31 |

renderers/server/server.js

32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
// Dependencies
 42 | const btoa = require('btoa');
 43 | const svg2png = require('svg2png');
 44 | const React = require('react');
 45 | const ReactDOMServer = require('react-dom/server');
 46 | const Card = require('../shared/Card');
 47 | const CardKitRenderer = require('../shared/base');
 48 | const helpers = require('../../helpers');
 49 | 
 50 | /**
 51 |  * @name CardKitServer
 52 |  * @class Additional CardKit class used for rendering on the server
 53 |  */
 54 | class CardKitServer extends CardKitRenderer {
 55 | 
 56 |   /**
 57 |    * Constructor takes in an instance of CardKit and stores it for later user
 58 |    *
 59 |    * @param {CardKit} cardkit - An instance of CardKit
 60 |    */
 61 |   constructor (cardkit) {
 62 |     // Ensure we're operating in a server environment
 63 |     if (typeof document !== 'undefined') {
 64 |       throw new Error('CardKitServer can only be used in a server environment');
 65 |     }
 66 | 
 67 |     super(cardkit);
 68 |   }
 69 | 
 70 |   /**
 71 |    * Renders the card as an SVG string <svg ..></svg>
 72 |    *
 73 |    * @return {string} The SVG string representation of the image
 74 |    */
 75 |   renderToString () {
 76 |     const string = ReactDOMServer.renderToString(
 77 |                       React.createFactory(Card)({
 78 |                         configuration: this.computeConfiguration()
 79 |                       }),
 80 |                       {}
 81 |                     );
 82 | 
 83 |     return string;
 84 |   }
 85 | 
 86 |   /**
 87 |    * Renders the current configuration to an image
 88 |    *
 89 |    * @param {number} scale - The scale to output at
 90 |    */
 91 |   renderToImage (scale = 2) {
 92 |     // Get the SVG in string-form
 93 |     const string = this.renderToString();
 94 | 
 95 |     // Encode the string into a data URI
 96 |     const uri = helpers.svgToBase64(string, btoa);
 97 | 
 98 |     // Convert to png and fulfill promise
 99 |     return svg2png(uri, {
100 |       width: this.computeConfiguration().card.width * scale,
101 |       height: this.computeConfiguration().card.height * scale
102 |     })
103 |     .then((buffer) => {
104 |       return buffer.toString('base64');
105 |     });
106 |   }
107 | 
108 | }
109 | 
110 | module.exports = CardKitServer;
111 | 
112 |
113 |
114 | 115 | 116 | 117 | 118 |
119 | 120 |
121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /docs/code/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/code/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/code/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/code/styles/jsdoc-default.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Montserrat:400,700); 2 | 3 | * { 4 | box-sizing: border-box 5 | } 6 | 7 | html, body { 8 | height: 100%; 9 | width: 100%; 10 | } 11 | 12 | body { 13 | color: #4d4e53; 14 | background-color: white; 15 | margin: 0 auto; 16 | padding: 0 20px; 17 | 18 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 19 | font-size: 16px; 20 | line-height: 160%; 21 | } 22 | 23 | a, 24 | a:active { 25 | color: #0095dd; 26 | text-decoration: none; 27 | } 28 | 29 | a:hover { 30 | text-decoration: underline 31 | } 32 | 33 | p, ul, ol, blockquote { 34 | margin-bottom: 1em; 35 | } 36 | 37 | h1, h2, h3, h4, h5, h6 { 38 | font-family: 'Montserrat', sans-serif; 39 | } 40 | 41 | h1, h2, h3, h4, h5, h6 { 42 | color: #000; 43 | font-weight: 400; 44 | margin: 0; 45 | } 46 | 47 | h1 { 48 | font-weight: 300; 49 | font-size: 48px; 50 | margin: 1em 0 .5em; 51 | } 52 | 53 | h1.page-title { 54 | font-size: 48px; 55 | margin: 1em 30px; 56 | } 57 | 58 | h2 { 59 | font-size: 24px; 60 | margin: 1.5em 0 .3em; 61 | } 62 | 63 | h3 { 64 | font-size: 24px; 65 | margin: 1.2em 0 .3em; 66 | } 67 | 68 | h4 { 69 | font-size: 18px; 70 | margin: 1em 0 .2em; 71 | color: #4d4e53; 72 | } 73 | 74 | h5, .container-overview .subsection-title { 75 | font-size: 120%; 76 | letter-spacing: -0.01em; 77 | margin: 8px 0 3px 0; 78 | } 79 | 80 | h6 { 81 | font-size: 100%; 82 | letter-spacing: -0.01em; 83 | margin: 6px 0 3px 0; 84 | font-style: italic; 85 | } 86 | 87 | tt, code, kbd, samp { 88 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 89 | background: #f4f4f4; 90 | padding: 1px 5px; 91 | border-radius: 5px; 92 | } 93 | 94 | .class-description { 95 | font-size: 130%; 96 | line-height: 140%; 97 | margin-bottom: 1em; 98 | margin-top: 1em; 99 | } 100 | 101 | .class-description:empty { 102 | margin: 0 103 | } 104 | 105 | #main { 106 | float: right; 107 | min-width: 360px; 108 | width: calc(100% - 250px); 109 | } 110 | 111 | header { 112 | display: block 113 | } 114 | 115 | section { 116 | display: block; 117 | background-color: #fff; 118 | padding: 0 30px; 119 | } 120 | 121 | .variation { 122 | display: none 123 | } 124 | 125 | .signature-attributes { 126 | font-size: 60%; 127 | color: #aaa; 128 | font-style: italic; 129 | font-weight: lighter; 130 | } 131 | 132 | nav { 133 | float: left; 134 | display: block; 135 | width: 250px; 136 | background: #fff; 137 | overflow: auto; 138 | position: fixed; 139 | height: 100%; 140 | } 141 | 142 | nav h3 { 143 | margin-top: 12px; 144 | font-size: 13px; 145 | text-transform: uppercase; 146 | letter-spacing: 1px; 147 | font-weight: 700; 148 | line-height: 24px; 149 | margin: 15px 0 10px; 150 | padding: 0; 151 | color: #000; 152 | } 153 | 154 | nav ul { 155 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 156 | font-size: 100%; 157 | line-height: 17px; 158 | padding: 0; 159 | margin: 0; 160 | list-style-type: none; 161 | } 162 | 163 | nav ul a, 164 | nav ul a:active { 165 | font-family: 'Montserrat', sans-serif; 166 | line-height: 18px; 167 | padding: 0; 168 | display: block; 169 | font-size: 12px; 170 | } 171 | 172 | nav ul a:hover, 173 | nav ul a:active { 174 | color: hsl(200, 100%, 43%); 175 | text-decoration: none; 176 | } 177 | 178 | nav > ul { 179 | padding: 0 10px; 180 | } 181 | 182 | nav > ul > li > a { 183 | color: #000; 184 | } 185 | 186 | nav ul ul { 187 | margin-bottom: 10px 188 | } 189 | 190 | nav ul ul a { 191 | color: hsl(207, 1%, 60%); 192 | border-left: 1px solid hsl(207, 10%, 86%); 193 | } 194 | 195 | nav ul ul a, 196 | nav ul ul a:active { 197 | padding-left: 20px 198 | } 199 | 200 | nav h2 { 201 | font-size: 12px; 202 | margin: 0; 203 | padding: 0; 204 | } 205 | 206 | nav > h2 > a { 207 | display: block; 208 | margin: 10px 0 -10px; 209 | color: hsl(202, 71%, 50%) !important; 210 | } 211 | 212 | footer { 213 | color: hsl(0, 0%, 28%); 214 | margin-left: 250px; 215 | display: block; 216 | padding: 15px; 217 | font-style: italic; 218 | font-size: 90%; 219 | } 220 | 221 | .ancestors { 222 | color: #999 223 | } 224 | 225 | .ancestors a { 226 | color: #999 !important; 227 | text-decoration: none; 228 | } 229 | 230 | .clear { 231 | clear: both 232 | } 233 | 234 | .important { 235 | font-weight: bold; 236 | color: #950B02; 237 | } 238 | 239 | .yes-def { 240 | text-indent: -1000px 241 | } 242 | 243 | .type-signature { 244 | color: #aaa 245 | } 246 | 247 | .name, .signature { 248 | font-family: Consolas, Monaco, 'Andale Mono', monospace 249 | } 250 | 251 | .details { 252 | margin-top: 14px; 253 | border-left: 2px solid #DDD; 254 | line-height: 30px; 255 | } 256 | 257 | .details dt { 258 | width: 120px; 259 | float: left; 260 | padding-left: 10px; 261 | } 262 | 263 | .details dd { 264 | margin-left: 70px 265 | } 266 | 267 | .details ul { 268 | margin: 0 269 | } 270 | 271 | .details ul { 272 | list-style-type: none 273 | } 274 | 275 | .details li { 276 | margin-left: 30px 277 | } 278 | 279 | .details pre.prettyprint { 280 | margin: 0 281 | } 282 | 283 | .details .object-value { 284 | padding-top: 0 285 | } 286 | 287 | .description { 288 | margin-bottom: 1em; 289 | margin-top: 1em; 290 | } 291 | 292 | .code-caption { 293 | font-style: italic; 294 | font-size: 107%; 295 | margin: 0; 296 | } 297 | 298 | .prettyprint { 299 | font-size: 13px; 300 | border: 1px solid #ddd; 301 | border-radius: 3px; 302 | box-shadow: 0 1px 3px hsla(0, 0%, 0%, 0.05); 303 | overflow: auto; 304 | } 305 | 306 | .prettyprint.source { 307 | width: inherit 308 | } 309 | 310 | .prettyprint code { 311 | font-size: 100%; 312 | line-height: 18px; 313 | display: block; 314 | margin: 0 30px; 315 | background-color: #fff; 316 | color: #4D4E53; 317 | } 318 | 319 | .prettyprint > code { 320 | padding: 15px 321 | } 322 | 323 | .prettyprint .linenums code { 324 | padding: 0 15px 325 | } 326 | 327 | .prettyprint .linenums li:first-of-type code { 328 | padding-top: 15px 329 | } 330 | 331 | .prettyprint code span.line { 332 | display: inline-block 333 | } 334 | 335 | .prettyprint.linenums { 336 | padding-left: 70px; 337 | -webkit-user-select: none; 338 | -moz-user-select: none; 339 | -ms-user-select: none; 340 | user-select: none; 341 | } 342 | 343 | .prettyprint.linenums ol { 344 | padding-left: 0 345 | } 346 | 347 | .prettyprint.linenums li { 348 | border-left: 3px #ddd solid 349 | } 350 | 351 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 352 | background-color: lightyellow 353 | } 354 | 355 | .prettyprint.linenums li * { 356 | -webkit-user-select: text; 357 | -moz-user-select: text; 358 | -ms-user-select: text; 359 | user-select: text; 360 | } 361 | 362 | .params, .props { 363 | border-spacing: 0; 364 | border: 1px solid #ddd; 365 | border-collapse: collapse; 366 | border-radius: 3px; 367 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 368 | width: 100%; 369 | font-size: 14px; 370 | } 371 | 372 | .params .name, .props .name, .name code { 373 | color: #4D4E53; 374 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 375 | font-size: 100%; 376 | } 377 | 378 | .params td, .params th, .props td, .props th { 379 | margin: 0px; 380 | text-align: left; 381 | vertical-align: top; 382 | padding: 10px; 383 | display: table-cell; 384 | } 385 | 386 | .params td { 387 | border-top: 1px solid #eee 388 | } 389 | 390 | .params thead tr, .props thead tr { 391 | background-color: #fff; 392 | font-weight: bold; 393 | } 394 | 395 | .params .params thead tr, .props .props thead tr { 396 | background-color: #fff; 397 | font-weight: bold; 398 | } 399 | 400 | .params td.description > p:first-child, .props td.description > p:first-child { 401 | margin-top: 0; 402 | padding-top: 0; 403 | } 404 | 405 | .params td.description > p:last-child, .props td.description > p:last-child { 406 | margin-bottom: 0; 407 | padding-bottom: 0; 408 | } 409 | 410 | dl.param-type { 411 | border-bottom: 1px solid hsl(0, 0%, 87%); 412 | margin-bottom: 30px; 413 | padding-bottom: 30px; 414 | } 415 | 416 | .param-type dt, .param-type dd { 417 | display: inline-block 418 | } 419 | 420 | .param-type dd { 421 | font-family: Consolas, Monaco, 'Andale Mono', monospace 422 | } 423 | 424 | .disabled { 425 | color: #454545 426 | } 427 | 428 | /* navicon button */ 429 | .navicon-button { 430 | display: none; 431 | position: relative; 432 | padding: 2.0625rem 1.5rem; 433 | transition: 0.25s; 434 | cursor: pointer; 435 | user-select: none; 436 | opacity: .8; 437 | } 438 | .navicon-button .navicon:before, .navicon-button .navicon:after { 439 | transition: 0.25s; 440 | } 441 | .navicon-button:hover { 442 | transition: 0.5s; 443 | opacity: 1; 444 | } 445 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 446 | transition: 0.25s; 447 | } 448 | .navicon-button:hover .navicon:before { 449 | top: .825rem; 450 | } 451 | .navicon-button:hover .navicon:after { 452 | top: -.825rem; 453 | } 454 | 455 | /* navicon */ 456 | .navicon { 457 | position: relative; 458 | width: 2.5em; 459 | height: .3125rem; 460 | background: #000; 461 | transition: 0.3s; 462 | border-radius: 2.5rem; 463 | } 464 | .navicon:before, .navicon:after { 465 | display: block; 466 | content: ""; 467 | height: .3125rem; 468 | width: 2.5rem; 469 | background: #000; 470 | position: absolute; 471 | z-index: -1; 472 | transition: 0.3s 0.25s; 473 | border-radius: 1rem; 474 | } 475 | .navicon:before { 476 | top: .625rem; 477 | } 478 | .navicon:after { 479 | top: -.625rem; 480 | } 481 | 482 | /* open */ 483 | .nav-trigger:checked + label:not(.steps) .navicon:before, 484 | .nav-trigger:checked + label:not(.steps) .navicon:after { 485 | top: 0 !important; 486 | } 487 | 488 | .nav-trigger:checked + label .navicon:before, 489 | .nav-trigger:checked + label .navicon:after { 490 | transition: 0.5s; 491 | } 492 | 493 | /* Minus */ 494 | .nav-trigger:checked + label { 495 | transform: scale(0.75); 496 | } 497 | 498 | /* × and + */ 499 | .nav-trigger:checked + label.plus .navicon, 500 | .nav-trigger:checked + label.x .navicon { 501 | background: transparent; 502 | } 503 | 504 | .nav-trigger:checked + label.plus .navicon:before, 505 | .nav-trigger:checked + label.x .navicon:before { 506 | transform: rotate(-45deg); 507 | background: #FFF; 508 | } 509 | 510 | .nav-trigger:checked + label.plus .navicon:after, 511 | .nav-trigger:checked + label.x .navicon:after { 512 | transform: rotate(45deg); 513 | background: #FFF; 514 | } 515 | 516 | .nav-trigger:checked + label.plus { 517 | transform: scale(0.75) rotate(45deg); 518 | } 519 | 520 | .nav-trigger:checked ~ nav { 521 | left: 0 !important; 522 | } 523 | 524 | .nav-trigger:checked ~ .overlay { 525 | display: block; 526 | } 527 | 528 | .nav-trigger { 529 | position: fixed; 530 | top: 0; 531 | clip: rect(0, 0, 0, 0); 532 | } 533 | 534 | .overlay { 535 | display: none; 536 | position: fixed; 537 | top: 0; 538 | bottom: 0; 539 | left: 0; 540 | right: 0; 541 | width: 100%; 542 | height: 100%; 543 | background: hsla(0, 0%, 0%, 0.5); 544 | z-index: 1; 545 | } 546 | 547 | @media only screen and (min-width: 320px) and (max-width: 680px) { 548 | body { 549 | overflow-x: hidden; 550 | } 551 | 552 | nav { 553 | background: #FFF; 554 | width: 250px; 555 | height: 100%; 556 | position: fixed; 557 | top: 0; 558 | right: 0; 559 | bottom: 0; 560 | left: -250px; 561 | z-index: 3; 562 | padding: 0 10px; 563 | transition: left 0.2s; 564 | } 565 | 566 | .navicon-button { 567 | display: inline-block; 568 | position: fixed; 569 | top: 1.5em; 570 | right: 0; 571 | z-index: 2; 572 | } 573 | 574 | #main { 575 | width: 100%; 576 | min-width: 360px; 577 | } 578 | 579 | #main h1.page-title { 580 | margin: 1em 0; 581 | } 582 | 583 | #main section { 584 | padding: 0; 585 | } 586 | 587 | footer { 588 | margin-left: 0; 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /docs/code/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/code/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/docs/favicon.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CardKit 5 | 6 | 7 | 50 | 51 | 52 | 53 | 60 | 61 |
62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "standard-react"] 3 | } -------------------------------------------------------------------------------- /examples/browser-script/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CardKit 5 | 6 | 12 | 13 | 14 | 15 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/browser/cardkit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CardKit example: Browser bundle using Webpack 3 | * 4 | * This bundle shows how to use CardKit as part of a bundle built by Webpack. 5 | * This `cardkit.js` file will be run through Webpack and create a bundle file, that can be included into `index.html` 6 | */ 7 | 8 | // Load dependencies 9 | const CardKit = require('../../cardkit'); 10 | const CardKitDOM = require('../../dom'); 11 | 12 | 13 | // Import configuration 14 | const { configuration, templates, themes, layouts } = require('../configurations/sample'); 15 | 16 | // Initialise 17 | const cardkit = new CardKit(configuration, { 18 | templates: templates, 19 | themes: themes, 20 | layouts: layouts 21 | }); 22 | 23 | // Start the renderer 24 | const renderer = new CardKitDOM(cardkit); 25 | 26 | // Render the UI 27 | renderer.renderUI('ui'); -------------------------------------------------------------------------------- /examples/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CardKit 5 | 6 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/server/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CardKit example: Server using Node.js 3 | * 4 | * This bundle shows how to use CardKit on a Node server. 5 | * This `server.js` file will be run via Node (`$ node server.js`) 6 | */ 7 | 8 | // Import CardKit 9 | const CardKit = require('../../cardkit'); 10 | const CardKitServer = require('../../server'); 11 | 12 | // Load our configuration 13 | const { configuration } = require('../configurations/sample'); 14 | 15 | // Initialise with our configuration 16 | const cardkit = new CardKit(configuration); 17 | 18 | // Initialise renderer 19 | const renderer = new CardKitServer(cardkit); 20 | 21 | // Render to an image 22 | renderer.renderToImage(2) 23 | .then((image) => { 24 | // Output the image in the console with a `` tag wrapped around it 25 | console.log(''); 26 | process.exit(); 27 | }) 28 | .catch((e) => { 29 | console.log('[ERR]', e); 30 | process.exit(); 31 | }); 32 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/times/cardkit/f74d73c31c1c6414cdde5c5b031b287843202efd/favicon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cardkit", 3 | "version": "2.0.6", 4 | "description": "A simple, powerful and fully configurable image editor for web browsers and servers. Optional UI included.", 5 | "main": "cardkit.js", 6 | "homepage": "https://times.github.io/cardkit", 7 | "bugs": { 8 | "url": "https://github.com/times/cardkit/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/times/cardkit.git" 13 | }, 14 | "scripts": { 15 | "test": "./node_modules/.bin/istanbul cover --dir test/coverage ./node_modules/.bin/_mocha -- --compilers js:babel-core/register --require ignore-styles", 16 | "jsdoc": "./node_modules/.bin/jsdoc -c ./jsdoc.config.json -d ./docs/code -t ./node_modules/minami --verbose -R ./README.md", 17 | "demo": "./node_modules/webpack/bin/webpack.js --env=dist-docs", 18 | "docs": "npm run jsdoc; npm run demo", 19 | "start": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js -d --content-base ./src --hot --open", 20 | "dist": "npm run build", 21 | "build": "echo \"[NOTICE] This task will build ALL CardKit versions\"; echo \"[LOG] Building DOM library\"; npm run build-dom; echo \"[LOG] Building server library\"; npm run build-server; echo \"[LOG] Building core library\"; npm run build-core; echo \"[LOG] Building DOM 19 | 20 | -------------------------------------------------------------------------------- /src/renderers/dom/dom.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | const React = require('react'); 3 | const ReactDOM = require('react-dom'); 4 | const Card = require('../shared/Card'); 5 | const CardKitRenderer = require('../shared/base'); 6 | const UI = require('./ui/ui'); 7 | const SVGToImage = require('./svgToImage'); 8 | const { slugify } = require('../../helpers'); 9 | 10 | /** 11 | * @name CardKitDOM 12 | * @class Additional CardKit class used for rendering in the DOM 13 | */ 14 | class CardKitDOM extends CardKitRenderer { 15 | 16 | /** 17 | * Constructor takes in an instance of CardKit and stores it for later use 18 | * 19 | * @param {CardKit} cardkit - An instance of CardKit 20 | */ 21 | constructor (cardkit) { 22 | // Ensure we're operating in a browser environment 23 | if (typeof document === 'undefined') { 24 | throw new Error('CardKitDOM can only be used in a browser environment'); 25 | } 26 | 27 | super(cardkit); 28 | 29 | // Store render IDs 30 | this.renderedCardID = null; 31 | this.renderedUIID = null; 32 | } 33 | 34 | /** 35 | * Renders the built-in UI to the supplied element 36 | * 37 | * @param {string} id - The ID of the element to render the UI into 38 | */ 39 | renderUI (id) { 40 | if (!this._isValidElement(id)) { 41 | throw new Error('Invalid element ID provided'); 42 | } 43 | 44 | const element = document.getElementById(id); 45 | 46 | const template = ((this.cardkit.templates) ? Object.keys(this.cardkit.templates)[0] : false); 47 | const theme = ((this.cardkit.themes) ? Object.keys(this.cardkit.themes)[0] : false); 48 | const layout = ((this.cardkit.layouts) ? Object.keys(this.cardkit.layouts)[0] : false); 49 | 50 | this.renderedUIID = id; 51 | ReactDOM.render( 52 | React.createElement(UI, { 53 | configuration: this.computeConfiguration({ 54 | template: template, 55 | theme: theme, 56 | layout: layout 57 | }), 58 | templates: this.cardkit.templates, 59 | themes: this.cardkit.themes, 60 | layouts: this.cardkit.layouts, 61 | cardKit: this 62 | }), 63 | element 64 | ); 65 | } 66 | 67 | /** 68 | * Renders just the Card as a React component to the supplied element 69 | * 70 | * @param {string} id - The ID of the element to render the card into 71 | * @param {object} options - Any override data (e.g. theme, layout) to use when rendering the card 72 | */ 73 | renderCard (id, options) { 74 | if (!this._isValidElement(id)) { 75 | throw new Error('Invalid element ID provided'); 76 | } 77 | 78 | const element = document.getElementById(id); 79 | 80 | this.renderedCardID = id; 81 | 82 | ReactDOM.render( 83 | React.createElement(Card, {configuration: this.computeConfiguration(options)}), 84 | element 85 | ); 86 | } 87 | 88 | /** 89 | * Checks if the ID provided is valid 90 | * 91 | * @param {string} id - The ID to validate 92 | * @return {boolean} If the ID was valid 93 | */ 94 | _isValidElement (id) { 95 | if (!id) { 96 | return false; 97 | } 98 | 99 | const element = document.getElementById(id); 100 | if (!element) { 101 | return false; 102 | } 103 | 104 | return true; 105 | } 106 | 107 | /** 108 | * Re-renders the Card or UI 109 | */ 110 | rerender () { 111 | if (this.renderedUIID) { 112 | this.renderUI(this.renderedUIID); 113 | } 114 | 115 | if (this.renderedCardID) { 116 | this.renderCard(this.renderedCardID); 117 | } 118 | } 119 | 120 | /** 121 | * Downloads the card as an image in the browser 122 | * 123 | * @param {number} scale - The scale to output at 124 | * @param {object} element - The element to use to generate the image 125 | */ 126 | download (scale = 2, element) { 127 | element = element.childNodes[0] || document.getElementById(this.renderedCardID).childNodes[0]; 128 | 129 | const svgToImage = new SVGToImage(element); 130 | 131 | // Setup default filename 132 | let filename = 'cardkit-default.jpg'; 133 | 134 | // Get the configuration 135 | const configuration = this.computeConfiguration(); 136 | 137 | // If there's a layer that has the useAsFilename property, find it 138 | const filenameLayerKey = Object.keys(configuration.layers) 139 | .find((key) => { 140 | const layer = configuration.layers[key]; 141 | 142 | return (layer.useAsFilename === true) && // Has the useAsFilename property 143 | (layer.hidden !== true) && // Is not hidden 144 | (layer.type === 'text'); // Is of type text 145 | }); 146 | 147 | // Get the layer that has the filename on it 148 | const filenameLayer = configuration.layers[filenameLayerKey]; 149 | 150 | // Update the filename 151 | if (filenameLayer) { 152 | filename = slugify(filenameLayer.text) + '.jpg'; 153 | } 154 | 155 | // Trigger the download 156 | svgToImage.download(filename, { 157 | format: 'image/jpeg', 158 | scale: scale 159 | }); 160 | } 161 | } 162 | 163 | module.exports = CardKitDOM 164 | -------------------------------------------------------------------------------- /src/renderers/dom/svgToImage.js: -------------------------------------------------------------------------------- 1 | const helpers = require('../../helpers'); 2 | 3 | /** 4 | * @name SVGToImage 5 | * @class Used for downloading an SVG DOM element in your browser 6 | */ 7 | class SVGToImage { 8 | 9 | /** 10 | * Constructor takes in the element for later use 11 | * 12 | * @param {object} element - The SVG element to convert to an image 13 | */ 14 | constructor (element) { 15 | // Ensure we got an element 16 | if (typeof element === 'undefined') { 17 | throw new Error('No element provided'); 18 | } 19 | 20 | // Validate that the provided element is an HTML element 21 | if (!this._isValidElement(element)) { 22 | throw new Error('Provided element is not a valid element'); 23 | } 24 | 25 | // Check the provided element is an SVG element 26 | if (element.tagName.toLowerCase() !== 'svg') { 27 | throw new Error('Invalid element provided'); 28 | } 29 | 30 | // Store the element 31 | this.element = element; 32 | } 33 | 34 | /** 35 | * Validates the provided element is an HTMLElement 36 | * Source: http://stackoverflow.com/a/384380/3886818 37 | * 38 | * @param {mixed} element - The element to validate 39 | * 40 | * @return {boolean} True if the provided element is valid 41 | */ 42 | _isValidElement (element) { 43 | return (typeof element !== 'undefined') && 44 | (element !== null) && 45 | (typeof element === 'object') && 46 | (element.nodeType === 1) && 47 | (typeof element.nodeName === 'string'); 48 | } 49 | 50 | /** 51 | * Downloads the SVG as an image 52 | * 53 | * @param {string} name - The name to download the image with 54 | * @param {object} options - The configurable options 55 | */ 56 | download (name, options = {}) { 57 | // Setup default options 58 | options.format = options.format || 'image/jpeg'; 59 | 60 | // Convert it to a data URI 61 | this._toDataURI(options, (uri) => { 62 | // We have our data URI 63 | 64 | // Create an image 65 | const image = new window.Image(); 66 | image.src = uri; 67 | 68 | // Confiugre the image onload callback 69 | image.onload = function () { 70 | // Create a canvas element sized to fit the image 71 | const canvas = document.createElement('canvas'); 72 | canvas.width = image.width; 73 | canvas.height = image.height; 74 | 75 | // Get the canvas context and draw the image onto it 76 | const context = canvas.getContext('2d'); 77 | context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); 78 | 79 | // Create a link to dynamically click and trigger the download 80 | const a = document.createElement('a'); 81 | a.download = name; 82 | a.href = canvas.toDataURL(options.format || 'image/jpeg'); 83 | document.body.appendChild(a); 84 | 85 | // I'm aware that `a.click()` below may not work reliably on all browsers. This is something to explore at a later date. 86 | 87 | // Click and download 88 | a.click(); 89 | } 90 | }); 91 | } 92 | 93 | /** 94 | * Verifies if the supplied URL is external or local 95 | * 96 | * @param {string} url - The URL to check 97 | * 98 | * @return {boolean} True if the supplied URL is external 99 | */ 100 | _isExternal (url) { 101 | return (url) && // We have a URL 102 | (url.lastIndexOf('http', 0) === 0) && // It starts with http 103 | (url.lastIndexOf(window.location.host) === -1); // It doesn't contain the current hostname 104 | } 105 | 106 | /** 107 | * Inlines all images 108 | * 109 | * @param {function} callback - The callback to run after images have been loaded and inlined 110 | */ 111 | _inlineImages (callback) { 112 | // Get any images 113 | const images = this.element.querySelectorAll('image'); 114 | 115 | // If there are no images, immediately call the callback 116 | if (images.length === 0) { 117 | callback(); 118 | return; 119 | } 120 | 121 | const promises = []; 122 | 123 | // Iterate over the images 124 | images.forEach((image) => { 125 | // Get the href for the image 126 | const href = image.getAttribute('xlink:href') || image.getAttribute('href'); 127 | 128 | // If no href for this image, skip this image 129 | if (href === null) return; 130 | 131 | // If we had a href, check if it's external 132 | if (href && this._isExternal(href)) { 133 | throw new Error('Cannot render embedded images linking to external hosts: ' + href); 134 | } 135 | 136 | // Create a canvas and image 137 | const canvas = document.createElement('canvas'); 138 | const ctx = canvas.getContext('2d'); 139 | const img = new window.Image(); 140 | 141 | // Create a promise and push it to the promises array 142 | promises.push(new Promise((resolve, reject) => { 143 | // Set the image source 144 | img.src = href; 145 | 146 | // Image load callback 147 | img.onload = function () { 148 | // Set the canvases size 149 | canvas.width = img.width; 150 | canvas.height = img.height; 151 | 152 | // Draw it onto the canvas 153 | ctx.drawImage(img, 0, 0); 154 | 155 | // Update the href attribute of the image element 156 | image.setAttribute('xlink:href', canvas.toDataURL('image/png')); 157 | image.setAttribute('href', canvas.toDataURL('image/png')); 158 | 159 | // Resolve the promise 160 | resolve(); 161 | } 162 | 163 | // Image error callback 164 | img.onerror = function () { 165 | // Image couldn't be loaded, reject the promise 166 | reject('Could not load image: ' + href); 167 | } 168 | })); 169 | }); 170 | 171 | // Wait for promises to resolve and call the callback 172 | Promise.all(promises) 173 | .then(callback) 174 | .catch(e => { throw new Error(e) }); 175 | } 176 | 177 | /** 178 | * Converts the element to a data URI 179 | * 180 | * @param {object} options - Configuration options 181 | * @param {function} callback - The callback to run after the element has been converted 182 | */ 183 | _toDataURI (options = {}, callback) { 184 | // Setup default options 185 | options.scale = options.scale || 1; 186 | 187 | // Setup some SVG data 188 | const xmlns = 'http://www.w3.org/2000/xmlns/'; 189 | 190 | // Inline images first 191 | this._inlineImages(() => { 192 | // Setup a container
193 | const outer = document.createElement('div'); 194 | 195 | // Clone the element 196 | const clone = this.element.cloneNode(true); 197 | 198 | // Setup some vars 199 | let width, 200 | height, 201 | svg; 202 | 203 | // If the element is an SVG we work out the size of the SVG using a variety of methods, 204 | // depending on how the user has defined the size of their SVG 205 | if (this.element.tagName !== 'svg') { 206 | throw new Error('Invalid element provided, must be SVG'); 207 | } 208 | 209 | // Get the width and height 210 | width = parseInt(this.element.viewBox.baseVal.width || clone.getAttribute('data-width') || clone.style.width); 211 | height = parseInt(this.element.viewBox.baseVal.height || clone.getAttribute('data-height') || clone.style.height); 212 | 213 | // Configure the clone's wrapper attributes 214 | clone.setAttribute('version', '1.1'); 215 | clone.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/2000/svg'); 216 | clone.setAttributeNS(xmlns, 'xmlns:xlink', 'http://www.w3.org/1999/xlink'); 217 | clone.setAttribute('width', width * options.scale); 218 | clone.setAttribute('height', height * options.scale); 219 | clone.setAttribute('viewBox', '0 0 ' + width + ' ' + height); 220 | outer.appendChild(clone); 221 | 222 | // Setup the SVG by adding the XML doctype 223 | const doctype = ''; 224 | 225 | // Combine the doctype and the innerHTML of the cloned SVG to get the final product 226 | svg = doctype + outer.innerHTML; 227 | 228 | // Create the URI 229 | const uri = 'data:image/svg+xml;base64,' + helpers.svgToBase64(svg, window.btoa); 230 | 231 | // Run the callback 232 | callback(uri); 233 | }); 234 | } 235 | 236 | } 237 | 238 | module.exports = SVGToImage; 239 | -------------------------------------------------------------------------------- /src/renderers/dom/ui/_settings.scss: -------------------------------------------------------------------------------- 1 | $mobile-breakpoint: 768px; -------------------------------------------------------------------------------- /src/renderers/dom/ui/base.scss: -------------------------------------------------------------------------------- 1 | @import "settings"; 2 | 3 | html { font-size: 62.5%; } 4 | body { 5 | font-size: 1.4rem; 6 | margin: 0; 7 | font-family: "Open Sans", Helvetica, sans-serif; 8 | font-weight: 400; 9 | } 10 | 11 | *, *:before, *:after { 12 | box-sizing: border-box; 13 | } 14 | 15 | main.main { 16 | display: flex; 17 | flex-direction: column; 18 | height: calc(100vh - 5.7rem); 19 | 20 | @media screen and (min-width: $mobile-breakpoint) { 21 | flex-direction: row; 22 | } 23 | } 24 | 25 | h1, h2, h3, h4, h5, h6 { 26 | font-weight: 800; 27 | margin: 0 0 0.67rem; 28 | letter-spacing: 0.1rem; 29 | text-transform: uppercase; 30 | } 31 | 32 | hr { 33 | border: 0; 34 | border-bottom: 1px solid #bababa; 35 | margin-bottom: 1.5rem; 36 | } 37 | 38 | .pull-bottom { 39 | margin-left: auto; 40 | 41 | @media screen and (min-width: $mobile-breakpoint) { 42 | margin-top: auto; 43 | } 44 | } 45 | 46 | input[type="text"], 47 | input[type="email"], 48 | input[type="number"], 49 | input[type="range"], 50 | input[type="file"], 51 | select, 52 | textarea { 53 | width: 100%; 54 | border: 1px solid #eaeaea; 55 | background: #FFF; 56 | font-family: "Open Sans", Helvetica, sans-serif; 57 | font-size: 1.4rem; 58 | padding: 0.8rem; 59 | margin: 0.2rem 0 1.5rem; 60 | } 61 | 62 | input[type="range"], 63 | input[type="file"] { 64 | padding: 0rem; 65 | border: 0; 66 | } 67 | 68 | 69 | input[type="file"] { 70 | background: transparent; 71 | } 72 | 73 | textarea { 74 | min-height: 12rem; 75 | } -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Header: require('./elements/Header/component'), 3 | Sidebar: require('./elements/Sidebar/component'), 4 | Canvas: require('./elements/Canvas/component') 5 | } 6 | -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements/Canvas/component.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | const React = require('react'); 3 | 4 | // Styles 5 | require('./style.scss'); 6 | 7 | // Card 8 | const Card = require('../../../../shared/Card'); 9 | 10 | // Canvas class 11 | class Canvas extends React.Component { 12 | 13 | render () { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | 21 | } 22 | 23 | Canvas.propTypes = { 24 | sidebarOpen: React.PropTypes.bool, 25 | configuration: React.PropTypes.object.isRequired 26 | } 27 | 28 | // Export 29 | module.exports = Canvas; 30 | -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements/Canvas/style.scss: -------------------------------------------------------------------------------- 1 | @import "../../settings"; 2 | 3 | .canvas { 4 | transition: transform 0.2s ease; 5 | width: 100%; 6 | padding: 2rem 0; 7 | transform: translateY(-40vh); 8 | 9 | @media screen and (min-width: $mobile-breakpoint) { 10 | width: calc(100% - 37rem); 11 | transform: translateX(-15rem); 12 | } 13 | 14 | &.canvas--with-sidebar { 15 | transform: translateX(0rem); 16 | } 17 | 18 | div.card { 19 | width: 100%; 20 | padding: 0 1rem; 21 | display: block; 22 | height: 100%; 23 | margin: 0 auto; 24 | 25 | svg:not(:root) { 26 | overflow: hidden; 27 | } 28 | 29 | svg { 30 | text { 31 | cursor: default; 32 | -webkit-user-select: none; 33 | -moz-user-select: none; 34 | -ms-user-select: none; 35 | user-select: none; 36 | } 37 | text::selection { 38 | background: none; 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements/Controls/ColorControl.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | const React = require('react'); 3 | const { ChromePicker, CirclePicker } = require('react-color'); 4 | const helpers = require('../../../../../helpers'); 5 | 6 | // ColorControl class 7 | class ColorControl extends React.Component { 8 | 9 | constructor (props) { 10 | super(props); 11 | this.handleChange = this.handleChange.bind(this); 12 | this.handleColorChange = this.handleColorChange.bind(this); 13 | } 14 | 15 | handleChange (e) { 16 | let element = e.target; 17 | this.props.onNewValue(this.props.name, element.value); 18 | } 19 | 20 | handleColorChange (color) { 21 | this.props.onNewValue(this.props.name, color.hex); 22 | } 23 | 24 | render () { 25 | if (!this.props.layer.editable[this.props.name]) return null; 26 | 27 | if (typeof this.props.layer.editable[this.props.name] === 'object' && this.props.layer.editable[this.props.name].options) { 28 | return ( 29 |
30 | Fill 31 | 34 |
35 | ); 36 | } 37 | 38 | if (this.props.layer.editable[this.props.name] === 'picker') { 39 | return ( 40 |
41 | {helpers.capitaliseFirstLetter(this.props.name)} 42 | 44 |
45 | ); 46 | } 47 | 48 | if (this.props.layer.editable[this.props.name] === true) { 49 | return ( 50 |
51 | {helpers.capitaliseFirstLetter(this.props.name)} 52 | 55 |
56 | ); 57 | } 58 | 59 | return null; 60 | } 61 | 62 | } 63 | 64 | ColorControl.propTypes = { 65 | name: React.PropTypes.string.isRequired, 66 | onNewValue: React.PropTypes.func.isRequired, 67 | layer: React.PropTypes.object.isRequired 68 | } 69 | 70 | // Export 71 | module.exports = ColorControl; 72 | -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements/Controls/SizeControl.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | const React = require('react'); 3 | const helpers = require('../../../../../helpers'); 4 | 5 | // SizeControl class 6 | class SizeControl extends React.Component { 7 | 8 | constructor (props) { 9 | super(props); 10 | this.handleChange = this.handleChange.bind(this); 11 | } 12 | 13 | handleChange (e) { 14 | let element = e.target; 15 | this.props.onNewValue(this.props.name, element.value); 16 | } 17 | 18 | render () { 19 | if (!this.props.layer.editable[this.props.name]) return null; 20 | 21 | if (typeof this.props.layer.editable[this.props.name] === 'object' || (this.props.min || this.props.max || this.props.step)) { 22 | // We have some config 23 | 24 | const config = { 25 | min: this.props.layer.editable[this.props.name].min || this.props.min || 0, 26 | max: this.props.layer.editable[this.props.name].max || this.props.max || null, 27 | step: this.props.layer.editable[this.props.name].step || this.props.step || 1 28 | }; 29 | 30 | config.type = (config.max ? 'range' : 'number'); 31 | 32 | return ( 33 |
34 | {helpers.capitaliseFirstLetter(this.props.name)} {this.props.layer[this.props.name]} 35 | 38 |
39 | ); 40 | } 41 | 42 | if (this.props.layer.editable[this.props.name] === true) { 43 | return ( 44 |
45 | {helpers.capitaliseFirstLetter(this.props.name)} {this.props.layer[this.props.name]} 46 | 51 |
52 | ); 53 | } 54 | } 55 | 56 | } 57 | 58 | SizeControl.propTypes = { 59 | name: React.PropTypes.string.isRequired, 60 | onNewValue: React.PropTypes.func.isRequired, 61 | layer: React.PropTypes.object.isRequired, 62 | min: React.PropTypes.number, 63 | max: React.PropTypes.number, 64 | step: React.PropTypes.number, 65 | type: React.PropTypes.string 66 | } 67 | 68 | // Export 69 | module.exports = SizeControl; 70 | -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements/Controls/SourceControl.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | const React = require('react'); 3 | 4 | // SizeControl class 5 | class SizeControl extends React.Component { 6 | 7 | constructor (props) { 8 | super(props); 9 | this.handleSelectChange = this.handleSelectChange.bind(this); 10 | this.handleFileChange = this.handleFileChange.bind(this); 11 | this._processFile = this._processFile.bind(this); 12 | } 13 | 14 | handleSelectChange (e) { 15 | const value = e.target.value; 16 | this.props.onNewValue('src', value); 17 | } 18 | 19 | handleFileChange (e) { 20 | e.stopPropagation(); 21 | e.preventDefault(); 22 | const file = e.target.files[0]; 23 | this._processFile(file); 24 | } 25 | 26 | _processFile (file) { 27 | const reader = new window.FileReader(); 28 | 29 | reader.onload = () => { 30 | let layer = this.props.layer; 31 | layer['src'] = reader.result; 32 | 33 | this.props.onNewValue('src', reader.result); 34 | }; 35 | 36 | reader.readAsDataURL(file); 37 | } 38 | 39 | render () { 40 | if (!this.props.layer.editable.src) return null; 41 | 42 | // If an object of options is given, create a dropdown 43 | if (typeof this.props.layer.editable.src === 'object') { 44 | return ( 45 |
46 | Source 47 | 55 |
56 | ); 57 | } 58 | 59 | // Default behaviour is to show a file upload 60 | if (this.props.layer.editable.src) { 61 | return ( 62 |
63 | Source 64 | 66 |
67 | ); 68 | } 69 | } 70 | 71 | } 72 | 73 | SizeControl.propTypes = { 74 | onNewValue: React.PropTypes.func.isRequired, 75 | layer: React.PropTypes.object.isRequired 76 | } 77 | 78 | // Export 79 | module.exports = SizeControl; 80 | -------------------------------------------------------------------------------- /src/renderers/dom/ui/elements/Controls/TextControl.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | const React = require('react'); 3 | 4 | // TextControl class 5 | class TextControl extends React.Component { 6 | 7 | constructor (props) { 8 | super(props); 9 | 10 | this.handleChange = this.handleChange.bind(this); 11 | } 12 | 13 | handleChange (e) { 14 | let element = e.target; 15 | this.props.onNewValue('text', element.value); 16 | } 17 | 18 | render () { 19 | if (!this.props.layer.editable.text) return null; 20 | 21 | if (typeof this.props.layer.editable.text === 'object') { 22 | if (this.props.layer.editable.text.options) { 23 | // We have a select instead of a free-input 24 | return ( 25 |
26 | Text 27 | 33 |
34 | ); 35 | } 36 | 37 | // We have some config 38 | const config = { 39 | maxLength: this.props.layer.editable.text.max || null 40 | }; 41 | 42 | return ( 43 |
44 | Text 45 |